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