diff --git a/scripts/mosh b/scripts/mosh index e85edb1..5a1c4b2 100755 --- a/scripts/mosh +++ b/scripts/mosh @@ -29,6 +29,8 @@ my $server = 'mosh-server'; my $predict = undef; +my $port_request = undef; + my $usage = qq{Usage: $0 [options] [user@]host --client=PATH mosh client on local machine (default: "mosh-client") @@ -38,6 +40,8 @@ qq{Usage: $0 [options] [user@]host -a --predict=always use local echo even on fast links -n --predict=never never use local echo +-p --port=NUM server-side UDP port + Please report bugs to mosh-devel\@mit.edu. Mosh home page: http://mosh.mit.edu\n}; @@ -55,8 +59,10 @@ sub predict_check { GetOptions( 'client=s' => \$client, 'server=s' => \$server, 'predict=s' => \$predict, + 'port=i' => \$port_request, 'a' => sub { $predict = 'always' }, 'n' => sub { $predict = 'never' }, + 'p=i' => \$port_request, 'fake-proxy!' => \my $fake_proxy ) or die $usage; if ( defined $predict ) { @@ -69,6 +75,16 @@ if ( defined $predict ) { predict_check( $predict, 0 ); } +if ( defined $port_request ) { + if ( $port_request =~ m{^[0-9]+$} + and $port_request >= 0 + and $port_request <= 65535 ) { + # good port + } else { + die "$0: Server-side port ($port_request) must be within valid range [0..65535].\n"; + } +} + delete $ENV{ 'MOSH_PREDICTION_DISPLAY' }; if ( defined $fake_proxy ) { @@ -133,7 +149,13 @@ if ( $pid == 0 ) { # child open STDERR, ">&", $pty_slave->fileno() or die; close $pty_slave; - my $s = q{sh -c 'exec "$@" "`set -- $SSH_CONNECTION; echo $3`"' -- } . $server; + my $s; + if ( defined $port_request ) { + $s = q{sh -c 'MOSH_PORT=$1; shift; MOSH_IP=`set -- $SSH_CONNECTION; echo $3`; exec "$@" $MOSH_IP $MOSH_PORT' -- } . $port_request . ' ' . $server; + } else { + # retain compatibility with older server when no port requested + $s = q{sh -c 'exec "$@" "`set -- $SSH_CONNECTION; echo $3`"' -- } . $server; + } exec 'ssh', '-S', 'none', '-o', "ProxyCommand=$0 --fake-proxy -- %h %p", '-t', $userhost, '--', $s; die "Cannot exec ssh: $!\n"; } else { # server diff --git a/src/crypto/crypto.cc b/src/crypto/crypto.cc index 2e90ca8..c9da604 100644 --- a/src/crypto/crypto.cc +++ b/src/crypto/crypto.cc @@ -30,7 +30,7 @@ using namespace Crypto; const char rdev[] = "/dev/urandom"; -long int myatoi( char *str ) +long int myatoi( const char *str ) { char *end; diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 9337637..c388795 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -26,7 +26,7 @@ using std::string; -long int myatoi( char *str ); +long int myatoi( const char *str ); namespace Crypto { class CryptoException { diff --git a/src/examples/ntester.cc b/src/examples/ntester.cc index 3f67dd5..00dbbd3 100644 --- a/src/examples/ntester.cc +++ b/src/examples/ntester.cc @@ -48,7 +48,7 @@ int main( int argc, char *argv[] ) n = new Transport( me, remote, key, ip, port ); } else { - n = new Transport( me, remote, NULL ); + n = new Transport( me, remote, NULL, NULL ); } } catch ( CryptoException e ) { fprintf( stderr, "Fatal error: %s\n", e.text.c_str() ); diff --git a/src/frontend/mosh-server.cc b/src/frontend/mosh-server.cc index b8ac424..5ab1f16 100644 --- a/src/frontend/mosh-server.cc +++ b/src/frontend/mosh-server.cc @@ -62,17 +62,23 @@ void serve( int host_fd, Terminal::Complete &terminal, ServerConnection &network ); +int run_server( const char *desired_ip, const char *desired_port ); + using namespace std; int main( int argc, char *argv[] ) { char *desired_ip = NULL; + char *desired_port = NULL; if ( argc == 1 ) { desired_ip = NULL; } else if ( argc == 2 ) { desired_ip = argv[ 1 ]; + } else if ( argc == 3 ) { + desired_ip = argv[ 1 ]; + desired_port = argv[ 2 ]; } else { - fprintf( stderr, "Usage: %s [LOCALADDR]\n", argv[ 0 ] ); + fprintf( stderr, "Usage: %s [LOCALADDR] [PORT]\n", argv[ 0 ] ); exit( 1 ); } @@ -88,6 +94,20 @@ int main( int argc, char *argv[] ) exit( 1 ); } + try { + return run_server( desired_ip, desired_port ); + } catch ( Network::NetworkException e ) { + fprintf( stderr, "Network exception: %s: %s\n", + e.function.c_str(), strerror( e.the_errno ) ); + return 1; + } catch ( Crypto::CryptoException e ) { + fprintf( stderr, "Crypto exception: %s\n", + e.text.c_str() ); + return 1; + } +} + +int run_server( const char *desired_ip, const char *desired_port ) { /* get initial window size */ struct winsize window_size; if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ) { @@ -100,7 +120,7 @@ int main( int argc, char *argv[] ) /* open network */ Network::UserStream blank; - ServerConnection network( terminal, blank, desired_ip ); + ServerConnection network( terminal, blank, desired_ip, desired_port ); /* network.set_verbose(); */ @@ -131,7 +151,7 @@ int main( int argc, char *argv[] ) _exit( 0 ); } - fprintf( stderr, "[mosh-server detached, pid=%d.]\n", (int)getpid() ); + fprintf( stderr, "[mosh-server detached, pid = %d]\n", (int)getpid() ); int master; diff --git a/src/network/network.cc b/src/network/network.cc index da1d2c7..4200763 100644 --- a/src/network/network.cc +++ b/src/network/network.cc @@ -115,7 +115,7 @@ void Connection::setup( void ) #endif } -Connection::Connection( const char *desired_ip ) /* server */ +Connection::Connection( const char *desired_ip, const char *desired_port ) /* server */ : sock( -1 ), has_remote_addr( false ), remote_addr(), @@ -134,30 +134,92 @@ Connection::Connection( const char *desired_ip ) /* server */ { setup(); - /* Attempt to bind free local port, with - address client used to connect to us. + /* The mosh wrapper always gives an IP request, in order + to deal with multihomed servers. The port is optional. */ - This usage does not seem to be endorsed by POSIX. */ + /* If an IP request is given, we try to bind to that IP, but we also + try INADDR_ANY. If a port request is given, we bind only to that port. */ - struct sockaddr_in local_addr; - local_addr.sin_family = AF_INET; - local_addr.sin_port = htons( 0 ); - if ( desired_ip - && inet_aton( desired_ip, &local_addr.sin_addr ) - && (bind( sock, (sockaddr *)&local_addr, sizeof( local_addr ) ) == 0) ) { - return; + /* convert port number */ + long int desired_port_no = 0; + + if ( desired_port ) { + char *end; + errno = 0; + desired_port_no = strtol( desired_port, &end, 10 ); + if ( (errno != 0) || (end != desired_port + strlen( desired_port )) ) { + throw NetworkException( "Invalid port number", errno ); + } } + if ( (desired_port_no < 0) || (desired_port_no > 65535) ) { + throw NetworkException( "Port number outside valid range [0..65535]", 0 ); + } + + /* convert desired IP */ + uint32_t desired_ip_addr = INADDR_ANY; + if ( desired_ip ) { - fprintf( stderr, "Could not bind to desired local address %s.\n", desired_ip ); + struct in_addr sin_addr; + if ( inet_aton( desired_ip, &sin_addr ) == 0 ) { + throw NetworkException( "Invalid IP address", errno ); + } + desired_ip_addr = sin_addr.s_addr; } - /* Could not bind to that IP (maybe we are behind NAT). - Try again with any IP. */ - local_addr.sin_addr.s_addr = INADDR_ANY; - if ( bind( sock, (sockaddr *)&local_addr, sizeof( local_addr ) ) < 0 ) { - throw NetworkException( "bind", errno ); + /* try to bind to desired IP first */ + if ( desired_ip_addr != INADDR_ANY ) { + try { + if ( try_bind( sock, desired_ip_addr, desired_port_no ) ) { return; } + } catch ( NetworkException e ) { + struct in_addr sin_addr; + sin_addr.s_addr = desired_ip_addr; + fprintf( stderr, "Error binding to IP %s: %s: %s\n", + inet_ntoa( sin_addr ), + e.function.c_str(), strerror( e.the_errno ) ); + } } + + /* now try any local interface */ + try { + if ( try_bind( sock, INADDR_ANY, desired_port_no ) ) { return; } + } catch ( NetworkException e ) { + fprintf( stderr, "Error binding to any interface: %s: %s\n", + e.function.c_str(), strerror( e.the_errno ) ); + throw; /* this time it's fatal */ + } + + assert( false ); + throw NetworkException( "Could not bind", errno ); +} + +bool Connection::try_bind( int socket, uint32_t s_addr, int port ) +{ + struct sockaddr_in local_addr; + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = s_addr; + + int search_low = PORT_RANGE_LOW, search_high = PORT_RANGE_HIGH; + + if ( port != 0 ) { /* port preference */ + search_low = search_high = port; + } + + for ( int i = search_low; i <= search_high; i++ ) { + local_addr.sin_port = htons( i ); + + if ( bind( socket, (sockaddr *)&local_addr, sizeof( local_addr ) ) == 0 ) { + fprintf( stderr, "Server now bound to %s:%d\n", + inet_ntoa( local_addr.sin_addr ), + ntohs( local_addr.sin_port ) ); + return true; + } else if ( i == search_high ) { /* last port to search */ + throw NetworkException( "bind", errno ); + } + } + + assert( false ); + return false; } Connection::Connection( const char *key_str, const char *ip, int port ) /* client */ @@ -266,10 +328,10 @@ string Connection::recv( void ) /* auto-adjust to remote host */ has_remote_addr = true; - if ( (remote_addr.sin_addr.s_addr != packet_remote_addr.sin_addr.s_addr) - || (remote_addr.sin_port != packet_remote_addr.sin_port) ) { - remote_addr = packet_remote_addr; - if ( server ) { + if ( server ) { /* only client can roam */ + if ( (remote_addr.sin_addr.s_addr != packet_remote_addr.sin_addr.s_addr) + || (remote_addr.sin_port != packet_remote_addr.sin_port) ) { + remote_addr = packet_remote_addr; fprintf( stderr, "Server now attached to client at %s:%d\n", inet_ntoa( remote_addr.sin_addr ), ntohs( remote_addr.sin_port ) ); diff --git a/src/network/network.h b/src/network/network.h index 2959e89..dd9d79b 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -74,6 +74,11 @@ namespace Network { static const uint64_t MIN_RTO = 50; /* ms */ static const uint64_t MAX_RTO = 1000; /* ms */ + static const int PORT_RANGE_LOW = 60001; + static const int PORT_RANGE_HIGH = 60999; + + static bool try_bind( int socket, uint32_t s_addr, int port ); + int sock; bool has_remote_addr; struct sockaddr_in remote_addr; @@ -100,7 +105,7 @@ namespace Network { Packet new_packet( string &s_payload ); public: - Connection( const char *desired_ip ); /* server */ + Connection( const char *desired_ip, const char *desired_port ); /* server */ Connection( const char *key_str, const char *ip, int port ); /* client */ void send( string s ); diff --git a/src/network/networktransport.cc b/src/network/networktransport.cc index 2f324d6..8cdadc6 100644 --- a/src/network/networktransport.cc +++ b/src/network/networktransport.cc @@ -28,8 +28,8 @@ using namespace std; template Transport::Transport( MyState &initial_state, RemoteState &initial_remote, - const char *desired_ip ) - : connection( desired_ip ), + const char *desired_ip, const char *desired_port ) + : connection( desired_ip, desired_port ), sender( &connection, initial_state ), received_states( 1, TimestampedState( timestamp(), 0, initial_remote ) ), last_receiver_state( initial_remote ), diff --git a/src/network/networktransport.h b/src/network/networktransport.h index e7834cb..c5f118c 100644 --- a/src/network/networktransport.h +++ b/src/network/networktransport.h @@ -51,7 +51,8 @@ namespace Network { bool verbose; public: - Transport( MyState &initial_state, RemoteState &initial_remote, const char *desired_ip ); + Transport( MyState &initial_state, RemoteState &initial_remote, + const char *desired_ip, const char *desired_port ); Transport( MyState &initial_state, RemoteState &initial_remote, const char *key_str, const char *ip, int port );