Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report errors from mosh-server startup to the user #1232

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 120 additions & 89 deletions scripts/mosh.pl
Original file line number Diff line number Diff line change
Expand Up @@ -350,121 +350,135 @@ sub predict_check {
$userhost = "$user$ip";
}

my $pid = open(my $pipe, "-|");
die "$0: fork: $!\n" unless ( defined $pid );
if ( $pid == 0 ) { # child
open(STDERR, ">&STDOUT") or die;
# Construct server exec arguments.
my @sshopts = ( '-n' );
if ($ssh_pty) {
push @sshopts, '-tt';
}

my @sshopts = ( '-n' );
if ($ssh_pty) {
push @sshopts, '-tt';
my $ssh_connection = "";
if ( $use_remote_ip eq 'remote' ) {
# Ask the server for its IP. The user's shell may not be
# Posix-compatible so invoke sh explicitly.
$ssh_connection = "sh -c " .
shell_quote ( '[ -n "$SSH_CONNECTION" ] && printf "\nMOSH SSH_CONNECTION %s\n" "$SSH_CONNECTION"' ) .
" ; ";
# Only with 'remote', we may need to tell SSH which protocol to use.
if ( $family eq 'inet' ) {
push @sshopts, '-4';
} elsif ( $family eq 'inet6' ) {
push @sshopts, '-6';
}
}
my @server = ( 'new' );

my $ssh_connection = "";
if ( $use_remote_ip eq 'remote' ) {
# Ask the server for its IP. The user's shell may not be
# Posix-compatible so invoke sh explicitly.
$ssh_connection = "sh -c " .
shell_quote ( '[ -n "$SSH_CONNECTION" ] && printf "\nMOSH SSH_CONNECTION %s\n" "$SSH_CONNECTION"' ) .
" ; ";
# Only with 'remote', we may need to tell SSH which protocol to use.
if ( $family eq 'inet' ) {
push @sshopts, '-4';
} elsif ( $family eq 'inet6' ) {
push @sshopts, '-6';
}
}
my @server = ( 'new' );
push @server, ( '-c', $colors );

push @server, ( '-c', $colors );
push @server, @bind_arguments;

push @server, @bind_arguments;
if ( defined $port_request ) {
push @server, ( '-p', $port_request );
}

if ( defined $port_request ) {
push @server, ( '-p', $port_request );
}
for ( &locale_vars ) {
push @server, ( '-l', $_ );
}

for ( &locale_vars ) {
push @server, ( '-l', $_ );
}
if ( scalar @command > 0 ) {
push @server, '--', @command;
}

if ( $use_remote_ip eq 'proxy' ) {
my $quoted_proxy_command = shell_quote( $0, "--family=$family" );
push @sshopts, ( '-S', 'none', '-o', "ProxyCommand=$quoted_proxy_command --fake-proxy -- %h %p" );
}
my @exec_argv = ( @ssh, @sshopts, $userhost, '--', $ssh_connection . "$server " . shell_quote( @server ) );

# Override command line for local execution.
if ( defined( $localhost )) {
@exec_argv = ( "$server " . shell_quote( @server ) );
}

my $pid = open(my $pipe, "-|");
die "$0: fork: $!\n" unless ( defined $pid );
if ( $pid == 0 ) { # child
open(STDERR, ">&STDOUT") or die;

if ( scalar @command > 0 ) {
push @server, '--', @command;
if ( !defined( $localhost) && $use_remote_ip eq 'proxy' ) {
# Non-standard shells and broken shrc files cause the ssh
# proxy to break mysteriously.
$ENV{ 'SHELL' } = '/bin/sh';
}

if ( defined( $localhost )) {
delete $ENV{ 'SSH_CONNECTION' };
chdir; # $HOME
print "MOSH IP ${userhost}\n";
exec( "$server " . shell_quote( @server ) );
die "Cannot exec $server: $!\n";
}
if ( $use_remote_ip eq 'proxy' ) {
# Non-standard shells and broken shrc files cause the ssh
# proxy to break mysteriously.
$ENV{ 'SHELL' } = '/bin/sh';
my $quoted_proxy_command = shell_quote( $0, "--family=$family" );
push @sshopts, ( '-S', 'none', '-o', "ProxyCommand=$quoted_proxy_command --fake-proxy -- %h %p" );
}
my @exec_argv = ( @ssh, @sshopts, $userhost, '--', $ssh_connection . "$server " . shell_quote( @server ) );

exec @exec_argv;
die "Cannot exec ssh: $!\n";
} else { # parent
my ( $sship, $port, $key );
my $bad_udp_port_warning = 0;
LINE: while ( <$pipe> ) {
chomp;
if ( m{^MOSH IP } ) {
if ( defined $ip ) {
die "$0 error: detected attempt to redefine MOSH IP.\n";
}
( $ip ) = m{^MOSH IP (\S+)\s*$} or die "Bad MOSH IP string: $_\n";
} elsif ( m{^MOSH SSH_CONNECTION } ) {
my @words = split;
if ( scalar @words == 6 ) {
$sship = $words[4];
} else {
die "Bad MOSH SSH_CONNECTION string: $_\n";
}
} elsif ( m{^MOSH CONNECT } ) {
if ( ( $port, $key ) = m{^MOSH CONNECT (\d+?) ([A-Za-z0-9/+]{22})\s*$} ) {
last LINE;
} else {
die "Bad MOSH CONNECT string: $_\n";
}
die "Cannot exec child: $!\n";
}
# parent
my ( $sship, $port, $key );
my $bad_udp_port_warning = 0;
LINE: while ( <$pipe> ) {
chomp;
if ( m{^MOSH IP } ) {
if ( defined $ip ) {
die "$0 error: detected attempt to redefine MOSH IP.\n";
}
( $ip ) = m{^MOSH IP (\S+)\s*$} or die "Bad MOSH IP string: $_\n";
} elsif ( m{^MOSH SSH_CONNECTION } ) {
my @words = split;
if ( scalar @words == 6 ) {
$sship = $words[4];
} else {
if ( defined $port_request and $port_request =~ m{:} and m{Bad UDP port} ) {
$bad_udp_port_warning = 1;
}
print "$_\n";
die "Bad MOSH SSH_CONNECTION string: $_\n";
}
}
close $pipe;
waitpid $pid, 0;

if ( not defined $ip ) {
if ( defined $sship ) {
warn "$0: Using remote IP address ${sship} from \$SSH_CONNECTION for hostname ${userhost}\n";
$ip = $sship;
} elsif ( m{^MOSH CONNECT } ) {
if ( ( $port, $key ) = m{^MOSH CONNECT (\d+?) ([A-Za-z0-9/+]{22})\s*$} ) {
last LINE;
} else {
die "$0: Did not find remote IP address (is SSH ProxyCommand disabled?).\n";
die "Bad MOSH CONNECT string: $_\n";
}
} else {
if ( defined $port_request and $port_request =~ m{:} and m{Bad UDP port} ) {
$bad_udp_port_warning = 1;
}
print "$_\n";
}
}
if ( not close $pipe ) {
if ( $! == 0 ) {
die_on_exitstatus($?, "server command", shell_quote(@exec_argv));
} else {
die("$0: error closing server pipe: $!\n")
}
}

if ( not defined $key or not defined $port ) {
if ( $bad_udp_port_warning ) {
die "$0: Server does not support UDP port range option.\n";
}
die "$0: Did not find mosh server startup message. (Have you installed mosh on your server?)\n";
if ( not defined $ip ) {
if ( defined $sship ) {
warn "$0: Using remote IP address ${sship} from \$SSH_CONNECTION for hostname ${userhost}\n";
$ip = $sship;
} else {
die "$0: Did not find remote IP address (is SSH ProxyCommand disabled?).\n";
}
}

# Now start real mosh client
$ENV{ 'MOSH_KEY' } = $key;
$ENV{ 'MOSH_PREDICTION_DISPLAY' } = $predict;
$ENV{ 'MOSH_NO_TERM_INIT' } = '1' if !$term_init;
exec {$client} ("$client", "-# @cmdline |", $ip, $port);
if ( not defined $key or not defined $port ) {
if ( $bad_udp_port_warning ) {
die "$0: Server does not support UDP port range option.\n";
}
die "$0: Did not find mosh server startup message. (Have you installed mosh on your server?)\n";
}

# Now start real mosh client
$ENV{ 'MOSH_KEY' } = $key;
$ENV{ 'MOSH_PREDICTION_DISPLAY' } = $predict;
$ENV{ 'MOSH_NO_TERM_INIT' } = '1' if !$term_init;
exec {$client} ("$client", "-# @cmdline |", $ip, $port);

sub shell_quote { join ' ', map {(my $a = $_) =~ s/'/'\\''/g; "'$a'"} @_ }

sub locale_vars {
Expand Down Expand Up @@ -536,3 +550,20 @@ sub resolvename {
}
return @res;
}

sub die_on_exitstatus {
my ($exitstatus, $what_command, $exec_string) = @_;

if (POSIX::WIFSIGNALED($exitstatus)) {
my $termsig = POSIX::WTERMSIG($exitstatus);
die("$0: $what_command exited on signal $termsig: $exec_string\n" );
}
if (!POSIX::WIFEXITED($exitstatus)) {
die("$0: $what_command unexpectedly terminated with exit status $exitstatus: $exec_string\n");
}
my $exitcode = POSIX::WEXITSTATUS($exitstatus);
if ($exitcode != 0) {
die("$0: $what_command failed with exitstatus $exitcode: $exec_string\n");
}
return;
}
1 change: 1 addition & 0 deletions src/tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ displaytests = \
pty-deadlock.test \
repeat.test \
repeat-with-input.test \
server-failure.test \
server-network-timeout.test \
server-signal-timeout.test \
window-resize.test \
Expand Down
28 changes: 14 additions & 14 deletions src/tests/e2e-test
Original file line number Diff line number Diff line change
Expand Up @@ -237,21 +237,21 @@ for run in $server_tests; do
if [ "$server_rv" -ne 0 ]; then
test_error "server harness exited with status %s\n" "$server_rv"
fi
fi
if [ "${run}" != "direct" ]; then
# Check for "round-trip" failures
if grep -q "round-trip Instruction verification failed" "${test_dir}/${run}.server.stderr"; then
test_error "Round-trip Instruction verification failed on server during %s\n" "$run"
fi
# Check for 0-timeout select() issue
if egrep -q "(polls, rate limiting|consecutive polls)" "${test_dir}/${run}.server.stderr"; then
if [ "osx" != "${TRAVIS_OS_NAME}" ]; then
test_error "select() with zero timeout called too often on server during %s\n" "$run"
if [ "${run}" != "direct" ]; then
# Check for "round-trip" failures
if grep -q "round-trip Instruction verification failed" "${test_dir}/${run}.server.stderr"; then
test_error "Round-trip Instruction verification failed on server during %s\n" "$run"
fi
# Check for 0-timeout select() issue
if egrep -q "(polls, rate limiting|consecutive polls)" "${test_dir}/${run}.server.stderr"; then
if [ "osx" != "${TRAVIS_OS_NAME}" ]; then
test_error "select() with zero timeout called too often on server during %s\n" "$run"
fi
fi
# Check for assert()
if egrep -q "assertion.*failed" "${test_dir}/${run}.server.stderr"; then
test_error "assertion during %s\n" "$run"
fi
fi
# Check for assert()
if egrep -q "assertion.*failed" "${test_dir}/${run}.server.stderr"; then
test_error "assertion during %s\n" "$run"
fi
fi
# XXX We'd also like to check for "target state Instruction
Expand Down
69 changes: 69 additions & 0 deletions src/tests/server-failure.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/sh

#
# Run mosh with a bad server command, check that it reports this usefully.
#

# shellcheck source=e2e-test-subrs
. "$(dirname "$0")/e2e-test-subrs"
PATH=$PATH:.:$srcdir
# Top-level wrapper.
if [ $# -eq 0 ]; then
e2e-test "$0" baseline client server mosh-args post
exit
fi

# OK, we have arguments, we're one of the test hooks.
if [ $# -lt 1 ]; then
fail "bad arguments %s\n" "$@"
fi

# The mosh session test command is never run.
baseline()
{
printf "@@@ done\n"
}

# Return inverted exitstatus from mosh-client.
client()
{
! "$@"
exit
}

# Add a do-nothing server command, to disable the standard harness
# checks in e2e-test.
server()
{
exit 0
}

# Make mosh.pl fail with a bad server.
mosh_args()
{
printf "%s\n" "--server false"
}

# Check for correct reporting of that failure.
post()
{
if ! grep 'server command failed with exitstatus' "$(basename "$0").d/baseline.tmux.log"; then
exit 1
fi
}

case $1 in
baseline)
baseline;;
client)
shift
client "$@";;
mosh-args)
mosh_args;;
post)
post;;
server)
server;;
*)
fail "unknown test argument %s\n" "$1";;
esac