On Sat, 11 Nov 2006, Simon Richter wrote:> quite often I find myself using commands like
>
> $ ssh foo nc bar 12345
[...]> I wonder whether it would make sense to have an option in the ssh
> client that told it to connect to the server, then open a tunneled
> connection and connect that to stdin/stdout.
That would be nice. Syntax wise, I suggest an extension of the "-L"
or "LocalForward" option to let it interpret "-" in the
local port number
position as a request to forward stdin/stdout instead of a TCP port.
So this:
ssh -N -L -:bar:12345 foo
would make an ssh connection to foo, ask foo to forward a channel to
bar:12345, amd connect the local stdin/stdout to the forwarded channel.
I append a proof of concept wrapper script that can be used as
sshconnect foo bar 12345
to do much the same thing, through a combination of "ssh -N -L
localhost:localport:bar:12345 foo" and "connect localhost
localport",
where localport is a dynamically allocated port. It works without any
support from the server, but requires perl and connect on the client.
> As an extension, there could also be a source routing option in
> the config file that would take care of setting up a chain of ssh
> connections if I need multiple hops.
I'd suggest using "ProxyCommand some_clever_script %h %p". Let
the
clever script have knowledge of which hosts can see which other hosts,
which firewall you are behind today, etc.
--apb (Alan Barrett)
#!/usr/bin/perl -w
#
# sshconnect - connect stdin/stdout to a distant host and port,
# via ssh port forwarding through a nearby host.
#
# Placed in the public domain by A P Barrett, November 2006.
#
# usage: sshconnect [-sshoptions] [user@]nearby distanthost distantport
#
# usage in .ssh/config file:
# ProxyCommand sshconnect [-sshoptions] [user@]nearby %h %p
#
# What it does:
# 1. Find an available local port number;
# 2. Use ssh to connect to the nearby host, and set up
# port forwarding from the local port allocated above to the
# desired distant host and port.
# 3. Use the "connect" program to connect stdin/stdout to the
# local port allocated above. This will cause the local
# ssh client to ask the nearby ssh server to forward the connection
# to the distant destination.
#
# returns a local port number that is not currently in use.
# XXX: There is a race between choosing the port here,
# and using the port elsewhere.
sub choose_port
{
my (@netstat_lines) = `netstat -an -f inet`;
foreach my $port (2048..3000) {
if (! grep /\.\Q${port}\E\s/, @netstat_lines) {
#print STDERR "Chose port $port\n";
return $port;
}
}
return undef;
}
# sleep for a specified (possibly fractional) number of seconds
sub fracsleep
{
my ($delay) = @_;
select(undef, undef, undef, $delay);
}
# really kill a process, by sending SIGTERM immediately,
# and SIGKILL after a short delay if the process doesn't exit by itelf.
sub really_kill
{
my ($pid) = @_;
kill TERM, $pid;
foreach my $delay (((0.1) x 5, (0.5) x 4)) {
return unless kill 0, $pid;
fracsleep $delay;
}
kill KILL, $pid;
}
sub main
{
my (@ARGV) = @_;
my $localhost = "127.0.0.1";
my $localport = choose_port();
my (@delays) = ((0.2) x 5, (0.5) x 6, (1) x 10, (2) x 60);
my $connect_command = "connect";
my $connect_status = undef;
my $childpid = undef;
my $sighandler = sub {
#print STDERR "CAUGHT SIGNAL\n";
really_kill($childpid) if defined $childpid;
exit(1);
};
local $SIG{INT} = $sighandler;
die "Can't assign local port\n" unless defined $localport;
$childpid = fork();
die "Can't fork\n" unless defined $childpid;
if ($childpid == 0) {
# This is the child.
# Run ssh, asking it to set up port forwarding.
my ($distant_port) = pop(@ARGV);
my ($distant_host) = pop(@ARGV);
my ($user_at_nearby_host) = pop(@ARGV);
my (@ssh_options) = @ARGV;
push @ssh_options, (
# no login or command
"-N",
# port forwarding does not play nicely with
# session multiplexing, so disable session multiplexing.
"-o", "ControlPath=none",
"-o", "ControlMaster=no",
# forward the local port to the distant port
"-o", "ExitOnForwardFailure=yes",
"-L",
"localhost:${localport}:${distant_host}:${distant_port}",
);
exec ("ssh", @ssh_options, $user_at_nearby_host);
exit(0);
} else {
# This is the parent.
# Connect sdin/stdout to the TCP port that ssh will forward.
# Wait until netstat shows that $localport is listening,
# or the child dies.
$ok = 0;
foreach my $delay (@delays) {
@netstat_lines = `netstat -an -f inet`;
$ok = 1 if (grep /\s\Q${localhost}.${localport}\E\s.*LISTEN/,
@netstat_lines);
if ($ok) {
#print STDERR "OK\n";
last;
}
if (kill(0, $childpid) != 1) {
#print STDERR "ssh died\n";
last;
}
#print STDERR "SLEEP $delay\n";
fracsleep $delay;
}
if (! $ok) {
#print STDERR "Could not forward port\n";
}
# Connect. This will run until EOF or network error.
if ($ok) {
#print STDERR "CONNECTING to port $localport\n";
$connect_status = system($connect_command, $localhost, $localport);
#print STDERR "CONNECT STATUS $connect_status\n";
}
# Kill the child and exit.
#print STDERR "KILL $childpid\n";
really_kill($childpid);
if (defined($connect_status)) {
exit($connect_status);
} else {
exit(1);
}
}
}
main @ARGV;