Unfortunately I didn't find your question earlier, but I've just got a really good answer from kamil-maciorowski:
https://unix.stackexchange.com/a/584505/251179
In summary, establish a master connection first, keep that in the background, then issue a second command with -O *ctl_cmd*
set to forward
to request/setup port forwarding:
ssh -fNMS /path/to/socket user@server
port="$(ssh -S /path/to/socket -O forward -R 0:localhost:22 placeholder)"
This will give you $port
on your local machine, and a connection in the background.
You can then use $port
locally; or use ssh
again to run a command on the remote server, where you can use the same control socket.
As to the flags, these are:
- -f = Requests ssh to go to background.
- -N = Do not execute a remote command.
- -M = Places client into “master” mode, for connection sharing.
- -S = Location of a control socket for connection sharing.
- -O = Control an active connection multiplexing master process.
In my case I've added a bit more to keep checking the connection:
#!/bin/bash
#--------------------------------------------------
# Setup
#--------------------------------------------------
set -u;
tunnel_user="user";
tunnel_host="1.1.1.1";
local_port="22";
local_name="my-name";
path_key="$HOME/.ssh/tunnel_ed25519";
path_lock="/tmp/tunnel.${tunnel_host}.pid"
path_port="/tmp/tunnel.${tunnel_host}.port"
path_log="/tmp/tunnel.${tunnel_host}.log"
path_socket="/tmp/tunnel.${tunnel_host}.socket"
#--------------------------------------------------
# Key file
#--------------------------------------------------
if [ ! -f "${path_key}" ]; then
ssh-keygen -q -t ed25519 -f "${path_key}" -N "";
/usr/local/bin/tunnel-client-key.sh
# Sends the public key to a central server, also run via cron, so it can be added to ~/.ssh/authorized_keys
# curl -s --form-string "pass=${pass}" --form-string "name=$(local_name)" -F "key=@${path_key}.pub" "https://example.com/key/";
fi
#--------------------------------------------------
# Lock
#--------------------------------------------------
if [ -e "${path_lock}" ]; then
c=$(pgrep -F "${path_lock}" 2>/dev/null | wc -l);
# MacOS 10.15.4 does not support "-c" to count processes, or the full "--pidfile" flag.
else
c=0;
fi
if [[ "${c}" -gt 0 ]]; then
if tty -s; then
echo "Already running";
fi;
exit;
fi;
echo "$$" > "${path_lock}";
#--------------------------------------------------
# Port forward
#--------------------------------------------------
retry=0;
while true; do
#--------------------------------------------------
# Log cleanup
#--------------------------------------------------
if [ ! -f "${path_log}" ]; then
touch "${path_log}";
fi
tail -n 30 "${path_log}" > "${path_log}.tmp";
mv "${path_log}.tmp" "${path_log}";
#--------------------------------------------------
# Exit old sockets
#--------------------------------------------------
if [ -S "${path_socket}" ]; then
echo "$(date) : Exit" >> "${path_log}";
ssh -S "${path_socket}" -O exit placeholder;
fi
#--------------------------------------------------
# Lost lock
#--------------------------------------------------
if [ ! -e "${path_lock}" ] || ! grep -q "$$" "${path_lock}"; then
echo "$(date) : Lost Lock" >> "${path_log}";
exit;
fi
#--------------------------------------------------
# Master connection
#--------------------------------------------------
echo "$(date) : Connect ${retry}" >> "${path_log}";
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -fNTMS "${path_socket}" -i "${path_key}" "${tunnel_user}@${tunnel_host}" >> "${path_log}" 2>&1;
#--------------------------------------------------
# Setup and keep checking the port forwarding
#--------------------------------------------------
old_port=0;
while ssh -S "${path_socket}" -O check placeholder 2>/dev/null; do
new_port=$(ssh -S "${path_socket}" -O forward -R "0:localhost:${local_port}" placeholder 2>&1);
if [[ "${new_port}" -gt 0 ]]; then
retry=0;
if [[ "${new_port}" -ne "${old_port}" ]]; then
ssh -i "${path_key}" "${tunnel_user}@${tunnel_host}" "tunnel.port.sh '${new_port}' '${local_name}'" >> "${path_log}" 2>&1;
# Tell remote server what the port is, and local_name.
# Don't use socket, it used "-N"; if done, a lost connection keeps sshd running on the remote host, even with ClientAliveInterval/ClientAliveCountMax.
echo "$(date) : ${new_port}" >> "${path_log}";
echo "${new_port}" > "${path_port}";
old_port="${new_port}";
sleep 1;
else
sleep 300; # Looks good, check again in 5 minutes.
fi
else # Not a valid port number (0, empty string, number followed by an error message, etc?)
ssh -S "${path_socket}" -O exit placeholder 2>/dev/null;
fi
done
#--------------------------------------------------
# Cleanup
#--------------------------------------------------
if [ ! -f "${path_port}" ]; then
rm "${path_port}";
fi
echo "$(date) : Disconnected" >> "${path_log}";
#--------------------------------------------------
# Delay before next re-try
#--------------------------------------------------
retry=$((retry+1));
if [[ $retry -gt 10 ]]; then
sleep 180; # Too many connection failures, try again in 3 minutes
else
sleep 5;
fi
done
First, with your kind of setting, what would work, requiring anyway two ssh:
Host foo
LocalForward 10022:bar:22
Host bar
Hostname localhost
Port 10022
LocalForward 33389 rdp:3389
term1$ ssh foo # or use ssh -f -N -o ExitOnForwardFailure=yes foo for background task
term2$ ssh bar
What you'd really need wouldn't be RemoteCommand but ProxyJump, really simplifying your configuration, with goal reached only with:
ssh -L 33389:rdp:3389 -J foo bar
Or the equivalent (only) configuration:
Host bar
ProxyJump foo
LocalForward 33389 rdp:3389
With zero intermediate port needed.
Unfortunately ProxyJump is available only starting from openssh 7.3
But it's easily replaced with the ProxyCommand
/-W
combo you were using before.
ssh -L 33389:rdp:3389 -o 'ProxyCommand=ssh foo -W %h:%p' bar
or as configuration file:
Host bar
ProxyCommand ssh -W %h:%p foo
LocalForward 33389 rdp:3389
There are still two ssh running: the hidden one is the one as ProxyCommand
parameter still running on the local host doing the link using a pair of pipes with the main ssh, instead of an extra tcp port. Trying to involve the intermediate host in participating with the port forwarding is insecure or can clash (other users of foo can access the tunnel or use the same port), and prone to errors. Tunnel ends should always be kept on the client host if possible, where there's full control. Intermediate tunnels should either not exist or point to the next ssh entry point (that's the -W
's usage here).
Best Answer
With a tunnel SSH, you can do something like that:
But take care that only the connection between laptop and Ubuntu server will be encrypted. And the port 3389 has to be opened so that's not what you're looking for. You should go with a VPN if you want to do RDP with a server reachable from Internet.
If the Ubuntu server is in the same protected Local Area Network (LAN) than windowsguest, then it's ok. You just have to open your 3389 port on windowsguest and close it on your firewall, so that it remains protected. Even if windowsguest is reachable from Internet, it won't use the same interface to communicate with the Ubuntu server and with the rest of the world. So, just make sure the 3389 port is closed on the Internet interface and open on the LAN interface.
With the
openssh
program, the commandline would look likessh -L 10467:windowsguest:3389 user@ubuntuhost
.The same option exists for PuTTY.
In this window:
This image was taken from http://howto.ccs.neu.edu/howto/windows/ssh-port-tunneling-with-putty/ which is a pretty good tutorial for port tunneling with SSH.