72

I have an already established ssh connection between two machines.

Is there a way to send commands to the remote machine from a shell script that is run on the local machine, using the already open connection, and without starting another ssh session?

SWeko
  • 823

3 Answers3

79

It's very simple if you plan in advance.

Open a master connection the first time. For subsequent connections, route slave connections through the existing master connection. In your ~/.ssh/config, set up connection sharing to happen automatically:

ControlMaster auto
ControlPath ~/.ssh/control:%C

If you start an ssh session to the same (user, port, machine) as an existing connection, the second session will be tunneled over the first. Establishing the second connection requires no new authentication and is very fast.

If you also put ControlPersist 600 in your .ssh/config, the tunnel will remain for 10 minutes (600 seconds) after you stop using it, which means you don't need to keep the first session running: you can have sequential sessions as long as you don't pause for more than 10 minutes.

  • It looks the second one will not be registered in /var/log/secure or /var/log/auth.log. However, will the second one be registered in /var/log/wtmp? – SOUser Sep 06 '13 at 15:21
  • @XichenLi /var/log/secure and /var/log/auth.log log SSH connections; a slave connection does not appear there because it piggybacks on an existing connection. If your ssh session allocates a terminal (i.e. ssh somehost with no command supplied, or ssh -t), that is (normally) logged in wtmp, regardless of how that terminal appeared (sshd whatever method was used to establish the connection, terminal emulator application, …). – Gilles 'SO- stop being evil' Sep 06 '13 at 15:51
  • 6
    You can also use ControlPersist 600 which is a delay in seconds of the socket being in idle before it gets automatically deleted. Otherwise it will close down automatically when the master connection ends. That's no good for executing a series of commands remotely (eg. a series of rsync commands to different folders) –  Jun 19 '16 at 01:05
  • 3
    If you do not want to edit the global configuration, you can also use the options -S (specify socket) and -M (create master connection) of the SSH client. – yankee Oct 07 '16 at 20:07
29

That's pretty easy to achieve using the nc tool and ssh tunnels.

1. Open ssh tunnel

In your ssh session, type ~C on a new line. You will get the ssh "service console" prompt which looks like this:

ssh> 

Type in the local forward command to open an ssh tunnel:

ssh> -L22000:targethost:22001
Forwarding port.

Where targethost is the hostname or IP address of the machine you are connected to.

Now, assuming the ssh server on the target machine wasn't configured to forbid tunnels, you have the desired connection forwarding: ssh client on your machine listens to port 22000, and it will forward any traffic sent to it to the 22001 port on targethost.

2. Start a network server on the remote machine

This is as simple as entering into your already open ssh session the following command:

remote$ nc -l localhost 22001 | sh

This will start a TCP server listening on port 22001 – which is the target port of our ssh tunnel – and route the received data (presumably, shell commands) to a targethost shell instance.

3. Send your script over the tunnel

local$ cat yourscript.sh | nc localhost 22000

This will send the script's body to your ssh tunnel and will end up being executed in a shell on the targethost. You will see script's output in your terminal with ssh session.


I'll also note that ssh tunnel (step 1.) in this scenario isn't strictly required; you could as well start the server open and connect to it directly over the internet. However, you will need to use the tunnel if the target host can't be reached directly (e.g. is behind a NAT), or ssh encryption is desired.

ulidtko
  • 1,018
  • 8
  • 16
  • 3
    Great answer. Technically, to get to the service console, the ~ character must come after a newline, so LF ~ C is probably a better sequence. – Alexios Mar 06 '12 at 14:54
  • @Alexios, correct. – ulidtko Mar 06 '12 at 16:22
  • Amazing trick! For the record, ssh man page shows some more information about it (scroll down to section called "ESCAPE CHARACTERS") – Carles Sala Feb 18 '15 at 16:07
  • @CarlesSala also good to know that less, the common default pager, supports search which can save you some scrolling if you know your keywords: just type man ssh /ESCAPE and you're there. – ulidtko Feb 18 '15 at 20:12
  • @ulidtko sure, but as far as I know, one can not link a CLI command into a SO comment, so I found the html man page a better option ;-) – Carles Sala Feb 19 '15 at 12:30
  • ~C does not work on multiplexed connections. – Ken Sharp Feb 21 '17 at 04:30
  • I am getting following error channel 2: open failed: connect failed: Connection refused on the remote machine @ulidtko – alper Jul 03 '20 at 18:58
  • @alper did you start the listener program on the remote side? From the error, you did get the ssh tunnel working — only, its remote side gets ECONNREFUSED when trying to connect(2) to remotehost:remoteport in -Llocalport:remotehost:remoteport. Check netstat -tlpn on remotehost to see if the port is open. – ulidtko Jul 04 '20 at 10:50
3

It took a while to get this to work, see here for a full example and explanations.

In short you need to:

  1. create an ssh config file that hold information about the ssh session

    remote=your_ip_address
    echo "HostName $remote" > ssh_config
    echo "User root" >> ssh_config
    echo "ControlMaster auto" >> ssh_config
    echo "ControlPath ~/.ssh/%C" >> ssh_config
    master_ssh='ssh -F ssh_config'
    
  2. start the master session

    $master_ssh -MNf $remote
    
  3. do the rsyncs

    rsync -e "$master_ssh" $opts $remote:/etc/some_file1  local_directory/
    rsync -e "$master_ssh" $opts $remote:/etc/some_file2  local_directory/
    rsync -e "$master_ssh" $opts $remote:/etc/some_file3  local_directory/
    
  4. close the master session

    $master_ssh -O exit $remote
    
Archemar
  • 31,554
JohnA
  • 131