Labtunnel is tool to help manage and activate ad-hoc SOCKS5 tunnels, using SSH and a bastion server. It is a simple CLI with sub-commands that allow you to use the connections with a high degree of resiliancy and efficiency, and a low degree of friction. If you want to jump right in, just go to Github. Here, I will explain the concepts behind it.

Hieronymus Bosch SOCKS5 is basically a poor man’s VPN. It’s easier and lighter and often the better choice, depending on your needs. SOCKS5 (and it’s predecessor SOCKS4) is the mechanism by which various types of traffic, served over various ports, can be multiplexed over a single connection. A traditional proxy, by contrast, is designed to only handle a single type of connection over a single port. If you have passwordless SSH access to a server and the right openSSH config (the default config should work just fine), you can use SOCKS5, and therefore labtunnel.

Key technologies that labtunnel uses are systemd and SSH. Systemd has some features that are perfect for handling the lifecycle of SSH tunnels. Socket Activation provides an efficient way to reserve sockets and only activate related services when they are asked for. systemd-socket-proxyd provides the glue between a service and socket-activation for apps (like openSSH) that don’t natively support it (socket-activation is a kernel-level feature with userspace API, with bindings available for Rust and Go). SSH can create SOCKS5 proxies with the -D flag.

Here’s how labtunnel sets up the listener:




ExecStartPre=mkdir -p %t/labtunnel
ExecStart=/lib/systemd/systemd-socket-proxyd %t/labtunnel/v0.1.0.%i.sock




In systemd-speak, this means “grab on to a socket that looks like /run/user/1000/labtunnel/v0.1.0.mybastionhost.sock, and forward the traffic to”, assuming you want to connect to server called mybastionhost and your Unix user id is 1000. 1080 is the standard port for SOCKS5, but labtunnel allows you to choose a different port. That facility becomes useful when you run more than one tunnel at once, or want to distinguish different tunnels by their port numbers.

Now for the tunnel itself

Again, this is defined using a systemd unit:

Description=Labtunnel for %i.

ExecStart=/usr/bin/ssh -CTNqv \
	-D %t/labtunnel/v0.1.0.%i.sock \
	-o "ExitOnForwardFailure=yes" \
	-o "StreamLocalBindUnlink=yes" \
	-o "NoHostAuthenticationForLocalhost=yes" \

And again, we’ll translate to standard nerd-speak:

The [Unit] section connects this service to the listener that was previously defined, which is listening on locahost:1080 and ready to forward traffic over the socket in /run/user/

The [Service] section sets up the SSH tunnel, instructing the SSH client to die if the connection dies or times out. The Restart=no tells SystemD to do the same: If the SSH connection times out, the SystemD service wrapping it dies. There is no sense in keeping tunnels open if they’re not being used. They will be re-initialized automatically if traffic picks up again later, thanks to socket activation. In this way, we honor the original intent of connection timeouts (closing connections that aren’t being used), and avoid the ugly hacks which have been put in place in the intervening years (keep-alive packets, or an infinite loop of service restarts, à la autossh).

Getting Started

Clone the repo and install:

git clone
cd labtunnel
sudo -E make install

You will now have a utility called labtunnel which will make use of code that has been installed to ~/.local/opt/labtunnel.

Since the installation adds systemd unit files, you may need to reload the daemon:

systemctl --user daemon-reload

Setting up the bastion host

First off, you’ll want to make sure you have at least one server you can connect to securely using SSH without passwords. You may have an ~/.ssh/config file that looks something like this:

    User bob
    IdentityFile ~/.ssh/id_rsa


Using the example of mybastionhost, here’s how you would tell labtunnel to direct traffic through it:

labtunnel enable mybastionhost # to enable
labtunnel start mybastionhost # start the tunnel
labtunnel on # set some global settings
firefox # check your public IP to verify

The labtunnel on command simply sets some global environment variables and on Gnome desktops also issues these commmands:

gsettings set org.gnome.system.proxy mode 'manual'
gsettings set org.gnome.system.proxy.socks host "$HOST"
gsettings set org.gnome.system.proxy.socks port "$PORT"

This is not a transparent proxy. It’s up to individual applications or your desktop environment to honor the settings. For example, in the case of Firefox, so long as preferences => Connection Settings => Use System Proxy Settings is checked, you’re good. With curl, you need to explicitly add the -x flag:

curl -x $all_proxy