1 /*
2     Mosh: the mobile shell
3     Copyright 2012 Keith Winstein
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18     In addition, as a special exception, the copyright holders give
19     permission to link the code of portions of this program with the
20     OpenSSL library under certain conditions as described in each
21     individual source file, and distribute linked combinations including
22     the two.
23 
24     You must obey the GNU General Public License in all respects for all
25     of the code used other than OpenSSL. If you modify file(s) with this
26     exception, you may extend this exception to your version of the
27     file(s), but you are not obligated to do so. If you do not wish to do
28     so, delete this exception statement from your version. If you delete
29     this exception statement from all source files in the program, then
30     also delete it here.
31 */
32 
33 #include "config.h"
34 
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #ifdef HAVE_SYS_UIO_H
38 #include <sys/uio.h>
39 #endif
40 #include <netdb.h>
41 #include <netinet/in.h>
42 #include <assert.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include "dos_assert.h"
48 #include "fatal_assert.h"
49 #include "byteorder.h"
50 #include "network.h"
51 #include "crypto.h"
52 
53 #include "timestamp.h"
54 
55 #ifndef MSG_DONTWAIT
56 #define MSG_DONTWAIT MSG_NONBLOCK
57 #endif
58 
59 #ifndef AI_NUMERICSERV
60 #define AI_NUMERICSERV 0
61 #endif
62 
63 using namespace std;
64 using namespace Network;
65 using namespace Crypto;
66 
67 const uint64_t DIRECTION_MASK = uint64_t(1) << 63;
68 const uint64_t SEQUENCE_MASK = uint64_t(-1) ^ DIRECTION_MASK;
69 
70 /* Read in packet */
Packet(const Message & message)71 Packet::Packet( const Message & message )
72   : seq( message.nonce.val() & SEQUENCE_MASK ),
73     direction( (message.nonce.val() & DIRECTION_MASK) ? TO_CLIENT : TO_SERVER ),
74     timestamp( -1 ),
75     timestamp_reply( -1 ),
76     payload()
77 {
78   dos_assert( message.text.size() >= 2 * sizeof( uint16_t ) );
79 
80   const uint16_t *data = (uint16_t *)message.text.data();
81   timestamp = be16toh( data[ 0 ] );
82   timestamp_reply = be16toh( data[ 1 ] );
83 
84   payload = string( message.text.begin() + 2 * sizeof( uint16_t ), message.text.end() );
85 }
86 
87 /* Output from packet */
toMessage(void)88 Message Packet::toMessage( void )
89 {
90   uint64_t direction_seq = (uint64_t( direction == TO_CLIENT ) << 63) | (seq & SEQUENCE_MASK);
91 
92   uint16_t ts_net[ 2 ] = { static_cast<uint16_t>( htobe16( timestamp ) ),
93                            static_cast<uint16_t>( htobe16( timestamp_reply ) ) };
94 
95   string timestamps = string( (char *)ts_net, 2 * sizeof( uint16_t ) );
96 
97   return Message( Nonce( direction_seq ), timestamps + payload );
98 }
99 
new_packet(const string & s_payload)100 Packet Connection::new_packet( const string &s_payload )
101 {
102   uint16_t outgoing_timestamp_reply = -1;
103 
104   uint64_t now = timestamp();
105 
106   if ( now - saved_timestamp_received_at < 1000 ) { /* we have a recent received timestamp */
107     /* send "corrected" timestamp advanced by how long we held it */
108     outgoing_timestamp_reply = saved_timestamp + (now - saved_timestamp_received_at);
109     saved_timestamp = -1;
110     saved_timestamp_received_at = 0;
111   }
112 
113   Packet p( direction, timestamp16(), outgoing_timestamp_reply, s_payload );
114 
115   return p;
116 }
117 
hop_port(void)118 void Connection::hop_port( void )
119 {
120   assert( !server );
121 
122   setup();
123   assert( remote_addr_len != 0 );
124   socks.push_back( Socket( remote_addr.sa.sa_family ) );
125 
126   prune_sockets();
127 }
128 
prune_sockets(void)129 void Connection::prune_sockets( void )
130 {
131   /* don't keep old sockets if the new socket has been working for long enough */
132   if ( socks.size() > 1 ) {
133     if ( timestamp() - last_port_choice > MAX_OLD_SOCKET_AGE ) {
134       int num_to_kill = socks.size() - 1;
135       for ( int i = 0; i < num_to_kill; i++ ) {
136 	socks.pop_front();
137       }
138     }
139   } else {
140     return;
141   }
142 
143   /* make sure we don't have too many receive sockets open */
144   if ( socks.size() > MAX_PORTS_OPEN ) {
145     int num_to_kill = socks.size() - MAX_PORTS_OPEN;
146     for ( int i = 0; i < num_to_kill; i++ ) {
147       socks.pop_front();
148     }
149   }
150 }
151 
Socket(int family)152 Connection::Socket::Socket( int family )
153   : _fd( socket( family, SOCK_DGRAM, 0 ) )
154 {
155   if ( _fd < 0 ) {
156     throw NetworkException( "socket", errno );
157   }
158 
159   /* Disable path MTU discovery */
160 #ifdef HAVE_IP_MTU_DISCOVER
161   int flag = IP_PMTUDISC_DONT;
162   if ( setsockopt( _fd, IPPROTO_IP, IP_MTU_DISCOVER, &flag, sizeof flag ) < 0 ) {
163     throw NetworkException( "setsockopt", errno );
164   }
165 #endif
166 
167   //  int dscp = 0x92; /* OS X does not have IPTOS_DSCP_AF42 constant */
168   int dscp = 0x02; /* ECN-capable transport only */
169   if ( setsockopt( _fd, IPPROTO_IP, IP_TOS, &dscp, sizeof dscp ) < 0 ) {
170     //    perror( "setsockopt( IP_TOS )" );
171   }
172 
173   /* request explicit congestion notification on received datagrams */
174 #ifdef HAVE_IP_RECVTOS
175   int tosflag = true;
176   if ( setsockopt( _fd, IPPROTO_IP, IP_RECVTOS, &tosflag, sizeof tosflag ) < 0 ) {
177     /* FreeBSD disallows this option on IPv6 sockets. */
178     if ( family == IPPROTO_IP ) {
179       perror( "setsockopt( IP_RECVTOS )" );
180     }
181   }
182 #endif
183 }
184 
setup(void)185 void Connection::setup( void )
186 {
187   last_port_choice = timestamp();
188 }
189 
fds(void) const190 const std::vector< int > Connection::fds( void ) const
191 {
192   std::vector< int > ret;
193 
194   for ( std::deque< Socket >::const_iterator it = socks.begin();
195 	it != socks.end();
196 	it++ ) {
197     ret.push_back( it->fd() );
198   }
199 
200   return ret;
201 }
202 
set_MTU(int family)203 void Connection::set_MTU( int family )
204 {
205   switch ( family ) {
206   case AF_INET:
207     MTU = DEFAULT_IPV4_MTU - IPV4_HEADER_LEN;
208     break;
209   case AF_INET6:
210     MTU = DEFAULT_IPV6_MTU - IPV6_HEADER_LEN;
211     break;
212   default:
213     throw NetworkException( "Unknown address family", 0 );
214   }
215 }
216 
217 class AddrInfo {
218 public:
219   struct addrinfo *res;
AddrInfo(const char * node,const char * service,const struct addrinfo * hints)220   AddrInfo( const char *node, const char *service,
221 	    const struct addrinfo *hints ) :
222     res( NULL ) {
223     int errcode = getaddrinfo( node, service, hints, &res );
224     if ( errcode != 0 ) {
225       throw NetworkException( std::string( "Bad IP address (" ) + (node != NULL ? node : "(null)") + "): " + gai_strerror( errcode ), 0 );
226     }
227   }
~AddrInfo()228   ~AddrInfo() { freeaddrinfo(res); }
229 private:
230   AddrInfo(const AddrInfo &);
231   AddrInfo &operator=(const AddrInfo &);
232 };
233 
Connection(const char * desired_ip,const char * desired_port)234 Connection::Connection( const char *desired_ip, const char *desired_port ) /* server */
235   : socks(),
236     has_remote_addr( false ),
237     remote_addr(),
238     remote_addr_len( 0 ),
239     server( true ),
240     MTU( DEFAULT_SEND_MTU ),
241     key(),
242     session( key ),
243     direction( TO_CLIENT ),
244     saved_timestamp( -1 ),
245     saved_timestamp_received_at( 0 ),
246     expected_receiver_seq( 0 ),
247     last_heard( -1 ),
248     last_port_choice( -1 ),
249     last_roundtrip_success( -1 ),
250     RTT_hit( false ),
251     SRTT( 1000 ),
252     RTTVAR( 500 ),
253     send_error()
254 {
255   setup();
256 
257   /* The mosh wrapper always gives an IP request, in order
258      to deal with multihomed servers. The port is optional. */
259 
260   /* If an IP request is given, we try to bind to that IP, but we also
261      try INADDR_ANY. If a port request is given, we bind only to that port. */
262 
263   /* convert port numbers */
264   int desired_port_low = -1;
265   int desired_port_high = -1;
266 
267   if ( desired_port && !parse_portrange( desired_port, desired_port_low, desired_port_high ) ) {
268     throw NetworkException("Invalid port range", 0);
269   }
270 
271   /* try to bind to desired IP first */
272   if ( desired_ip ) {
273     try {
274       if ( try_bind( desired_ip, desired_port_low, desired_port_high ) ) { return; }
275     } catch ( const NetworkException &e ) {
276       fprintf( stderr, "Error binding to IP %s: %s\n",
277 	       desired_ip,
278 	       e.what() );
279     }
280   }
281 
282   /* now try any local interface */
283   try {
284     if ( try_bind( NULL, desired_port_low, desired_port_high ) ) { return; }
285   } catch ( const NetworkException &e ) {
286     fprintf( stderr, "Error binding to any interface: %s\n",
287 	     e.what() );
288     throw; /* this time it's fatal */
289   }
290 
291   assert( false );
292   throw NetworkException( "Could not bind", errno );
293 }
294 
try_bind(const char * addr,int port_low,int port_high)295 bool Connection::try_bind( const char *addr, int port_low, int port_high )
296 {
297   struct addrinfo hints;
298   memset( &hints, 0, sizeof( hints ) );
299   hints.ai_family = AF_UNSPEC;
300   hints.ai_socktype = SOCK_DGRAM;
301   hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST | AI_NUMERICSERV;
302   AddrInfo ai( addr, "0", &hints );
303 
304   Addr local_addr;
305   socklen_t local_addr_len = ai.res->ai_addrlen;
306   memcpy( &local_addr.sa, ai.res->ai_addr, local_addr_len );
307 
308   int search_low = PORT_RANGE_LOW, search_high = PORT_RANGE_HIGH;
309 
310   if ( port_low != -1 ) { /* low port preference */
311     search_low = port_low;
312   }
313   if ( port_high != -1 ) { /* high port preference */
314     search_high = port_high;
315   }
316 
317   socks.push_back( Socket( local_addr.sa.sa_family ) );
318   for ( int i = search_low; i <= search_high; i++ ) {
319     switch (local_addr.sa.sa_family) {
320     case AF_INET:
321       local_addr.sin.sin_port = htons( i );
322       break;
323     case AF_INET6:
324       local_addr.sin6.sin6_port = htons( i );
325       break;
326     default:
327       throw NetworkException( "Unknown address family", 0 );
328     }
329 
330     if ( local_addr.sa.sa_family == AF_INET6
331       && memcmp(&local_addr.sin6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 ) {
332       const int off = 0;
333       if ( setsockopt( sock(), IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off) ) ) {
334         perror( "setsockopt( IPV6_V6ONLY, off )" );
335       }
336     }
337 
338     if ( ::bind( sock(), &local_addr.sa, local_addr_len ) == 0 ) {
339       set_MTU( local_addr.sa.sa_family );
340       return true;
341     } else if ( i == search_high ) { /* last port to search */
342       int saved_errno = errno;
343       socks.pop_back();
344       char host[ NI_MAXHOST ], serv[ NI_MAXSERV ];
345       int errcode = getnameinfo( &local_addr.sa, local_addr_len,
346 				 host, sizeof( host ), serv, sizeof( serv ),
347 				 NI_DGRAM | NI_NUMERICHOST | NI_NUMERICSERV );
348       if ( errcode != 0 ) {
349 	throw NetworkException( std::string( "bind: getnameinfo: " ) + gai_strerror( errcode ), 0 );
350       }
351       fprintf( stderr, "Failed binding to %s:%s\n",
352 	       host, serv );
353       throw NetworkException( "bind", saved_errno );
354     }
355   }
356 
357   assert( false );
358   return false;
359 }
360 
Connection(const char * key_str,const char * ip,const char * port)361 Connection::Connection( const char *key_str, const char *ip, const char *port ) /* client */
362   : socks(),
363     has_remote_addr( false ),
364     remote_addr(),
365     remote_addr_len( 0 ),
366     server( false ),
367     MTU( DEFAULT_SEND_MTU ),
368     key( key_str ),
369     session( key ),
370     direction( TO_SERVER ),
371     saved_timestamp( -1 ),
372     saved_timestamp_received_at( 0 ),
373     expected_receiver_seq( 0 ),
374     last_heard( -1 ),
375     last_port_choice( -1 ),
376     last_roundtrip_success( -1 ),
377     RTT_hit( false ),
378     SRTT( 1000 ),
379     RTTVAR( 500 ),
380     send_error()
381 {
382   setup();
383 
384   /* associate socket with remote host and port */
385   struct addrinfo hints;
386   memset( &hints, 0, sizeof( hints ) );
387   hints.ai_family = AF_UNSPEC;
388   hints.ai_socktype = SOCK_DGRAM;
389   hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
390   AddrInfo ai( ip, port, &hints );
391   fatal_assert( static_cast<size_t>( ai.res->ai_addrlen ) <= sizeof( remote_addr ) );
392   remote_addr_len = ai.res->ai_addrlen;
393   memcpy( &remote_addr.sa, ai.res->ai_addr, remote_addr_len );
394 
395   has_remote_addr = true;
396 
397   socks.push_back( Socket( remote_addr.sa.sa_family ) );
398 
399   set_MTU( remote_addr.sa.sa_family );
400 }
401 
send(const string & s)402 void Connection::send( const string & s )
403 {
404   if ( !has_remote_addr ) {
405     return;
406   }
407 
408   Packet px = new_packet( s );
409 
410   string p = session.encrypt( px.toMessage() );
411 
412   ssize_t bytes_sent = sendto( sock(), p.data(), p.size(), MSG_DONTWAIT,
413 			       &remote_addr.sa, remote_addr_len );
414 
415   if ( bytes_sent != static_cast<ssize_t>( p.size() ) ) {
416     /* Make sendto() failure available to the frontend. */
417     send_error = "sendto: ";
418     send_error += strerror( errno );
419 
420     if ( errno == EMSGSIZE ) {
421       MTU = DEFAULT_SEND_MTU; /* payload MTU of last resort */
422     }
423   }
424 
425   uint64_t now = timestamp();
426   if ( server ) {
427     if ( now - last_heard > SERVER_ASSOCIATION_TIMEOUT ) {
428       has_remote_addr = false;
429       fprintf( stderr, "Server now detached from client.\n" );
430     }
431   } else { /* client */
432     if ( ( now - last_port_choice > PORT_HOP_INTERVAL )
433 	 && ( now - last_roundtrip_success > PORT_HOP_INTERVAL ) ) {
434       hop_port();
435     }
436   }
437 }
438 
recv(void)439 string Connection::recv( void )
440 {
441   assert( !socks.empty() );
442   for ( std::deque< Socket >::const_iterator it = socks.begin();
443 	it != socks.end();
444 	it++ ) {
445     bool islast = (it + 1) == socks.end();
446     string payload;
447     try {
448       payload = recv_one( it->fd(), !islast );
449     } catch ( NetworkException & e ) {
450       if ( (e.the_errno == EAGAIN)
451 	   || (e.the_errno == EWOULDBLOCK) ) {
452 	assert( !islast );
453 	continue;
454       } else {
455 	throw;
456       }
457     }
458 
459     /* succeeded */
460     prune_sockets();
461     return payload;
462   }
463   assert( false );
464   return "";
465 }
466 
recv_one(int sock_to_recv,bool nonblocking)467 string Connection::recv_one( int sock_to_recv, bool nonblocking )
468 {
469   /* receive source address, ECN, and payload in msghdr structure */
470   Addr packet_remote_addr;
471   struct msghdr header;
472   struct iovec msg_iovec;
473 
474   char msg_payload[ Session::RECEIVE_MTU ];
475   char msg_control[ Session::RECEIVE_MTU ];
476 
477   /* receive source address */
478   header.msg_name = &packet_remote_addr;
479   header.msg_namelen = sizeof packet_remote_addr;
480 
481   /* receive payload */
482   msg_iovec.iov_base = msg_payload;
483   msg_iovec.iov_len = sizeof msg_payload;
484   header.msg_iov = &msg_iovec;
485   header.msg_iovlen = 1;
486 
487   /* receive explicit congestion notification */
488   header.msg_control = msg_control;
489   header.msg_controllen = sizeof msg_control;
490 
491   /* receive flags */
492   header.msg_flags = 0;
493 
494   ssize_t received_len = recvmsg( sock_to_recv, &header, nonblocking ? MSG_DONTWAIT : 0 );
495 
496   if ( received_len < 0 ) {
497     throw NetworkException( "recvmsg", errno );
498   }
499 
500   if ( header.msg_flags & MSG_TRUNC ) {
501     throw NetworkException( "Received oversize datagram", errno );
502   }
503 
504   /* receive ECN */
505   bool congestion_experienced = false;
506 
507   struct cmsghdr *ecn_hdr = CMSG_FIRSTHDR( &header );
508   if ( ecn_hdr
509        && (ecn_hdr->cmsg_level == IPPROTO_IP)
510        && ((ecn_hdr->cmsg_type == IP_TOS)
511 #ifdef IP_RECVTOS
512 	   || (ecn_hdr->cmsg_type == IP_RECVTOS)
513 #endif
514 	   )) {
515     /* got one */
516     uint8_t *ecn_octet_p = (uint8_t *)CMSG_DATA( ecn_hdr );
517     assert( ecn_octet_p );
518 
519     if ( (*ecn_octet_p & 0x03) == 0x03 ) {
520       congestion_experienced = true;
521     }
522   }
523 
524   Packet p( session.decrypt( msg_payload, received_len ) );
525 
526   dos_assert( p.direction == (server ? TO_SERVER : TO_CLIENT) ); /* prevent malicious playback to sender */
527 
528   if ( p.seq >= expected_receiver_seq ) { /* don't use out-of-order packets for timestamp or targeting */
529     expected_receiver_seq = p.seq + 1; /* this is security-sensitive because a replay attack could otherwise
530 					  screw up the timestamp and targeting */
531 
532     if ( p.timestamp != uint16_t(-1) ) {
533       saved_timestamp = p.timestamp;
534       saved_timestamp_received_at = timestamp();
535 
536       if ( congestion_experienced ) {
537 	/* signal counterparty to slow down */
538 	/* this will gradually slow the counterparty down to the minimum frame rate */
539 	saved_timestamp -= CONGESTION_TIMESTAMP_PENALTY;
540 	if ( server ) {
541 	  fprintf( stderr, "Received explicit congestion notification.\n" );
542 	}
543       }
544     }
545 
546     if ( p.timestamp_reply != uint16_t(-1) ) {
547       uint16_t now = timestamp16();
548       double R = timestamp_diff( now, p.timestamp_reply );
549 
550       if ( R < 5000 ) { /* ignore large values, e.g. server was Ctrl-Zed */
551 	if ( !RTT_hit ) { /* first measurement */
552 	  SRTT = R;
553 	  RTTVAR = R / 2;
554 	  RTT_hit = true;
555 	} else {
556 	  const double alpha = 1.0 / 8.0;
557 	  const double beta = 1.0 / 4.0;
558 
559 	  RTTVAR = (1 - beta) * RTTVAR + ( beta * fabs( SRTT - R ) );
560 	  SRTT = (1 - alpha) * SRTT + ( alpha * R );
561 	}
562       }
563     }
564 
565     /* auto-adjust to remote host */
566     has_remote_addr = true;
567     last_heard = timestamp();
568 
569     if ( server ) { /* only client can roam */
570       if ( remote_addr_len != header.msg_namelen ||
571 	   memcmp( &remote_addr, &packet_remote_addr, remote_addr_len ) != 0 ) {
572 	remote_addr = packet_remote_addr;
573 	remote_addr_len = header.msg_namelen;
574 	char host[ NI_MAXHOST ], serv[ NI_MAXSERV ];
575 	int errcode = getnameinfo( &remote_addr.sa, remote_addr_len,
576 				   host, sizeof( host ), serv, sizeof( serv ),
577 				   NI_DGRAM | NI_NUMERICHOST | NI_NUMERICSERV );
578 	if ( errcode != 0 ) {
579 	  throw NetworkException( std::string( "recv_one: getnameinfo: " ) + gai_strerror( errcode ), 0 );
580 	}
581 	fprintf( stderr, "Server now attached to client at %s:%s\n",
582 		 host, serv );
583       }
584     }
585   }
586 
587   return p.payload; /* we do return out-of-order or duplicated packets to caller */
588 }
589 
port(void) const590 std::string Connection::port( void ) const
591 {
592   Addr local_addr;
593   socklen_t addrlen = sizeof( local_addr );
594 
595   if ( getsockname( sock(), &local_addr.sa, &addrlen ) < 0 ) {
596     throw NetworkException( "getsockname", errno );
597   }
598 
599   char serv[ NI_MAXSERV ];
600   int errcode = getnameinfo( &local_addr.sa, addrlen,
601 			     NULL, 0, serv, sizeof( serv ),
602 			     NI_DGRAM | NI_NUMERICSERV );
603   if ( errcode != 0 ) {
604     throw NetworkException( std::string( "port: getnameinfo: " ) + gai_strerror( errcode ), 0 );
605   }
606 
607   return std::string( serv );
608 }
609 
timestamp(void)610 uint64_t Network::timestamp( void )
611 {
612   return frozen_timestamp();
613 }
614 
timestamp16(void)615 uint16_t Network::timestamp16( void )
616 {
617   uint16_t ts = timestamp() % 65536;
618   if ( ts == uint16_t(-1) ) {
619     ts++;
620   }
621   return ts;
622 }
623 
timestamp_diff(uint16_t tsnew,uint16_t tsold)624 uint16_t Network::timestamp_diff( uint16_t tsnew, uint16_t tsold )
625 {
626   int diff = tsnew - tsold;
627   if ( diff < 0 ) {
628     diff += 65536;
629   }
630 
631   assert( diff >= 0 );
632   assert( diff <= 65535 );
633 
634   return diff;
635 }
636 
timeout(void) const637 uint64_t Connection::timeout( void ) const
638 {
639   uint64_t RTO = lrint( ceil( SRTT + 4 * RTTVAR ) );
640   if ( RTO < MIN_RTO ) {
641     RTO = MIN_RTO;
642   } else if ( RTO > MAX_RTO ) {
643     RTO = MAX_RTO;
644   }
645   return RTO;
646 }
647 
~Socket()648 Connection::Socket::~Socket()
649 {
650   fatal_assert ( close( _fd ) == 0 );
651 }
652 
Socket(const Socket & other)653 Connection::Socket::Socket( const Socket & other )
654   : _fd( dup( other._fd ) )
655 {
656   if ( _fd < 0 ) {
657     throw NetworkException( "socket", errno );
658   }
659 }
660 
operator =(const Socket & other)661 Connection::Socket & Connection::Socket::operator=( const Socket & other )
662 {
663   if ( dup2( other._fd, _fd ) < 0 ) {
664     throw NetworkException( "socket", errno );
665   }
666 
667   return *this;
668 }
669 
parse_portrange(const char * desired_port,int & desired_port_low,int & desired_port_high)670 bool Connection::parse_portrange( const char * desired_port, int & desired_port_low, int & desired_port_high )
671 {
672   /* parse "port" or "portlow:porthigh" */
673   desired_port_low = desired_port_high = 0;
674   char *end;
675   long value;
676 
677   /* parse first (only?) port */
678   errno = 0;
679   value = strtol( desired_port, &end, 10 );
680   if ( (errno != 0) || (*end != '\0' && *end != ':') ) {
681     fprintf( stderr, "Invalid (low) port number (%s)\n", desired_port );
682     return false;
683   }
684   if ( (value < 0) || (value > 65535) ) {
685     fprintf( stderr, "(Low) port number %ld outside valid range [0..65535]\n", value );
686     return false;
687   }
688 
689   desired_port_low = (int)value;
690   if (*end == '\0') { /* not a port range */
691     desired_port_high = desired_port_low;
692     return true;
693   }
694   /* port range; parse high port */
695   const char * cp = end + 1;
696   errno = 0;
697   value = strtol( cp, &end, 10 );
698   if ( (errno != 0) || (*end != '\0') ) {
699     fprintf( stderr, "Invalid high port number (%s)\n", cp );
700     return false;
701   }
702   if ( (value < 0) || (value > 65535) ) {
703     fprintf( stderr, "High port number %ld outside valid range [0..65535]\n", value );
704     return false;
705   }
706 
707   desired_port_high = (int)value;
708   if ( desired_port_low > desired_port_high ) {
709     fprintf( stderr, "Low port %d greater than high port %d\n", desired_port_low, desired_port_high );
710     return false;
711   }
712 
713   if ( desired_port_low == 0 ) {
714     fprintf( stderr, "Low port 0 incompatible with port ranges\n" );
715     return false;
716   }
717 
718 
719   return true;
720 }
721