SSH tunnel handling like what tigervnc does
Some remote servers are disallowed to enable remote VNC connections, given not all VNC servers allow encryption (some paid solutions do, but not all). So one must run the server with -localhost to allow only connections from the same machine (no remote connections). So if one wants establish a connection one must use a SSH tunnel. That regardless there's a VPN preventing unencrypted remote access.
In such situations, if not using tiegervnc, one must create the SSH tunnel manually, like:
ssh -NfL 59<num>:127.0.0.1:59<num> <user>@<server>
Or:
ssh -NfL 59<num>:127.0.0.1:59<num> <ssh_host>
Where <ssh_host> is the Host one might define on ~/.ssh/config, specifying for it the full domain name of the server, the user to connect and more, to make SSH related things way easier, by specifying just the defined host.
Notice one needs to find out the SSH process ID for that tunnel, if one wants to kill it with the kill utility. Once the tunnel is up and running, the way to call the client is with:
wlvncc localhost 59<num>
I've alredy tested this BTW.
When creating the VNC session, one can specify the port to be used, which always starts with 59and the reminder of the port number is what one can specify, that's why I'm using 59
Creating the tunnel manually can be automated, but killing it requires first finding out the tunnel PID. It's not that complex, but it's not a easy like looking for a SSH process or tunnel, since one might be connected through SSH to remote SSH sessions, or one can actually have different SSH tunnels.
So, tigervnc has made it really easy to create and kill the SSH tunnel for the user, so one doesn't need to deal with SSH manually. All one needs to do with tigervnc is (replacing vincviewer with wlvncc, and also using the wlvncc server and port in different args rather than the common <server>:<port> single arg used by tigervnc client):
wlvncc -via <user>@<server> localhost 59<num>
Or :
wlvncc -via <ssh_defined_remote_host> localhost 59<num>
This way the SSH tunnel is not created neither killed manually, and wlvncc would handle that for the user, just like how tigervnc client does it for the users.
This would be really really useful. And allows using *.desktop files calling xlvncc + wofi for example, very easily.
At this point, actually I prefer to use tigervnc client, which is not wayland native, than wlvncc, just because of the nice SSH tunnel handling provided by tigervnc. This would be really really useful, and would actually allow me to use a wayland native VNC client.
Many thanks !
It should be possible to create a shell script for this; something like (not tested):
#!/bin/bash
set -e
main()
{
local host="$1"
local port="$2"
shift; shift
ssh -NL 1337:localhost:$port $host &
local sshpid=$!
trap "kill $sshpid" INT QUIT TERM EXIT
wlvncc localhost:1337 $@
}
main $@
Then you'd invoke it like my-vnc-ssh-script.sh remote-host.net 5900
Just tested the script. I had to add a sleep 3 after the ssh command. Else wlvncc failed with
"Unable to connect to VNC server".
Also it should be "wlvncc localhost 1337" or you get an error:
"ConnectClientToTcpAddr6: getaddrinfo (Name or service not known)"
It would be more robust to retry the wlvncc command until it succeeds.
It would be nice to have a proper and fully tested script like this in the scripts/ directory. Maybe someone would like to have a go at it? ;)
This is my current script:
#!/bin/bash
set -e
show_help()
{
echo "usage: $(basename $BASH_SOURCE) [-p|--ssh-port port] host vnc_port"
echo
echo "host: hostname"
echo "vnc_port: vnc server port at the host"
echo
echo "-p|--ssh-port: ssh port at the host. Default 22"
exit 1
}
free_port()
{
local start=49152
local range=5000
while true; do
local port=$[$start + ($RANDOM % $range)]
(echo -n >/dev/tcp/127.0.0.1/${port}) >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo $port
break
fi
done
}
main()
{
POSITIONAL_ARGS=()
local remote_port="22"
while [[ $# -gt 0 ]]; do
case $1 in
-p|--ssh-port)
remote_port="$2"
shift
shift
;;
-h|--help)
show_help
;;
-*|--*)
echo "Unknown option: $1"
show_help
exit 1
;;
*)
POSITIONAL_ARGS+=("$1")
shift
;;
esac
done
set -- "${POSITIONAL_ARGS[@]}"
local host="$1"
local port="$2"
if [ -z "$host" ]; then
show_help
fi
if [ -z "$port" ]; then
show_help
fi
shift; shift
master_file="$(mktemp -d)/wlvncc"
port_to_use=$(free_port)
echo "Using port $port_to_use"
ssh -f -M -p${remote_port} -NL ${port_to_use}:localhost:$port $host -o ControlMaster=yes -o ControlPath=$master_file
trap "ssh -p $port_to_use -o ControlMaster=no -o ControlPath=$master_file localhost -O exit" INT QUIT TERM EXIT
wlvncc localhost $port_to_use $@
}
main $@
It uses ssh -f to run in the background. It forks a child process and returns when the connection is established.
So no need for sleep here.
Problem then is to exit the child process because the $! will only return the parent.
I used the master/slave feature with a control socket to end this process ( https://bugzilla.mindrot.org/show_bug.cgi?id=1473#c5 ).
Also I added the free_port function ( https://unix.stackexchange.com/a/330776 )
//edit Oh, the 35999 is the ssh port I use here. Maybe this also can be a parameter of the script.
//edit2 Added help text and optional argument for the remote ssh port.