The SSH tunnel kit

👉This project’s page at GitHub

This project’s homepage is published at GitHub: dadooda/tunkit. All links point there, too.

Overview

SSH tunneling (port forwarding) is a method of transporting arbitrary data over an encrypted SSH connection. SSH tunnel reroutes your traffic through a remote server, like VPS or a dedicated server. All your traffic, “proxied” through the tunnel, appears to be coming from the remote server instead of your local machine. These feature of the SSH tunnels is used to encrypt legacy applications’ traffic, implement VPNs (virtual private networks), access local (intranet) services through firewalls etc.

Most of the well-known SSH implementations for various platforms do a great job of creating one-off tunnels. The Internet is literally jammed with recipes like “create an SSH-based SOCKS proxy in 5 minutes to bypass firewall restrictions” or “create an SSH tunnel for Remote Desktop”. However, none of such “user-friendly” articles explain how to control and manage such tunnels, make them reliable and secure.

Tunkit originated as a set of scripts around autossh to help start and stop a few simple tunnels when booting the machine. Scenarios were gradually added, and with them, opportunities. By setting up various tunnels again and again in different environments, I kept making the solution more flexible, but at the same time simpler and more reliable.

As a result, you get what you see — an “assebly kit” to create a production-grade tunnel setup for almost any task in just minutes, guided by precise step-by-step instructions from this README.

🔝

Created using Procdown

This README file was composed using Procdown, the tool to write and maintain multi-page Markdown documents with a structure and a lot of internal links.

🔝

Quick start

These basic steps must be completed for any of the scenarios listed below:

  1. Complete the steps of “Server setup: SSH”.
  2. Complete the steps of “Server setup: User and key”.
  3. Complete the steps of “Client setup”: 🎲Linux or WSL, 🎲Cygwin.

A few typical scenarios follow.

Set up SOCKS proxy to bypass sites blocking

  1. Complete the basic steps.
  2. Set up the SOCKS tunnel.
  3. Set up service autostart: 🎲Linux, 🎲WSL, 🎲Cygwin.

🔝

Set up direct SSH access to a Linux server running on a virtual machine or in the cloud

  1. Complete the basic steps.
  2. Set up the remote access tunnel.
  3. Optionally, set up the service autostart.
  4. Optionally, set up the on-demand monitor.

🔝

Set up Remote Desktop (RDP) or VNC access to my Windows machine

  1. Complete the basic steps.
  2. Set up the remote access tunnel.
  3. Optionally, set up the service autostart.
  4. Optionally, set up the on-demand monitor.

🔝

Set up the home server or NAS to build multiple tunnels to other machines on my local network

  1. Complete the basic steps.
  2. On the server, set up multiple remote access tunnels (🎲Linux, 🎲WSL) to other machines/services on the local network.
  3. Set up the multi-channel on-demand monitor.
  4. Optionally, set up the service autostart: 🎲Linux, 🎲WSL.

🔝

Step-by-step setup

Server setup: SSH

📝 Pre-requisites:

  1. The gateway server, ec2-13-34-43-202.compute-1.amazonaws.com, runs a reasonably fresh popular Linux distribution, such as Ubuntu.
  2. The SSH server running on ec2-13-34-43-202.compute-1.amazonaws.com is set up per up-to-date defaults.
  3. User joe can sudo to change the Linux system settings.

With SSH, log into the gateway server, ec2-13-34-43-202.compute-1.amazonaws.com:

ssh joe@ec2-13-34-43-202.compute-1.amazonaws.com

💡 As of now, all commands are run on the gateway server, ec2-13-34-43-202.compute-1.amazonaws.com.

Edit the SSH server configuration:

sudoedit /etc/ssh/sshd_config

Specify the GatewayPorts setting:

GatewayPorts clientspecified

Activate configuration changes:

sudo service ssh reload

Our SSH server should be good to go now.

🔝

Server setup: User and key

With SSH, log into the gateway server, ec2-13-34-43-202.compute-1.amazonaws.com:

ssh joe@ec2-13-34-43-202.compute-1.amazonaws.com

💡 As of now, all commands are run on the gateway server, ec2-13-34-43-202.compute-1.amazonaws.com.

Add a non-interactive user, joetun:

sudo adduser --disabled-password --shell /usr/sbin/nologin --gecos "Joe's tunnel" joetun
Adding user `joetun' ...
Adding new group `joetun' (1001) ...
Adding new user `joetun' (1001) with group `joetun' ...
Creating home directory `/home/joetun' ...
Copying files from `/etc/skel' ...

Let’s see what we’ve got so far:

grep joetun /etc/passwd
joetun❌1001:1001:Joe's tunnel,,,:/home/joetun:/usr/sbin/nologin

Looks good. Now let’s proceed with key generation:

mkdir -p /tmp/joetun-key &&
cd /tmp/joetun-key &&
ssh-keygen -t ed25519 -b 512 -N "" -C "Joe's tunnel" -f joetun.key
Generating public/private ed25519 key pair.
Your identification has been saved in joetun.key
Your public key has been saved in joetun.key.pub
…

💡 We’ve chosen ed25519 as key type. For more information on key type selection, please consult Comparing SSH Keys - RSA, DSA, ECDSA, or EdDSA? or similar articles.

⚠️ The generated private key, joetun.key hasn’t got a passphrase. Make sure you don’t use this key for anything but the tunnels.

Now, copy both joetun.key and joetun.key.pub from the remote server to a safe location.

  • If you’re on Linux, use scp, e.g.:

    mkdir -p ~/saved-keys &&
    scp joe@ec2-13-34-43-202.compute-1.amazonaws.com:/tmp/joetun-key/joetun.key* ~/saved-keys
    
  • If you’re on Windows and you have PuTTY installed, use pscp, like this:

    mkdir %USERPROFILE%\saved-keys
    pscp joe@ec2-13-34-43-202.compute-1.amazonaws.com:/tmp/joetun-key/joetun.key* %USERPROFILE%\saved-keys
    
  • If none of the above works for you, you can always default to saving the actual file contents. Since key files are made to be copy-paste friendly, just collect and store the exact cat command output.

    cat joetun.key.pub joetun.key
    
    ssh-ed25519 AAAAC3Nzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxov2DWA0z Joe's tunnel
    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    DqXqkQQZ9af0ov2DWA0zAAAADEpvZSdzIHR1bm5lbAE=
    -----END OPENSSH PRIVATE KEY-----
    

Now let’s set configure our newly created user, joetun, to use the SSH key authorization. While still in /tmp/joetun-key, run the following commands:

sudo runuser -u joetun bash
cd &&
touch .hushlogin &&
mkdir -pm 700 .ssh &&
touch .ssh/authorized_keys &&
chmod 600 .ssh/authorized_keys &&
cat /tmp/joetun-key/joetun.key.pub >> .ssh/authorized_keys &&
exit

💡 As you might have guessed, in the command sequence above, we became user joetun (via runuser), did a couple things on his behalf, and returned to our regular shell after exit.

Now let’s test if key authorization works for joetun. While still in /tmp/joetun-key, do a:

ssh -i joetun.key joetun@localhost
The authenticity of host 'localhost (::1)' can't be established.
ECDSA key fingerprint is SHA256:4fyuZVMRxxxxxxxxxxxxxxxxxxxxxxxxxxxAJ6KR3bQ.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Type yes and press ENTER. Then you should see:

This account is currently not available.
Connection to localhost closed.

Hooray. Our tunnel user, joetun is good to go and we’ve got both private and public keys copied to a safe location.

Let’s clean up:

cd &&
rm -rf /tmp/joetun-key

🔝

Server setup: Nginx-based semaphore website

📝 Pre-requisites:

  1. You can create A or CNAME DNS records in an existing domain. In this example, the domain is joescompany.com.
  2. You have a server on the Internet, running a modern Linux like Ubuntu, capable of running the web server software.
  3. User joe can sudo to change the Linux system settings on the web server.

Creating the semaphore website is required for the on-demand scenarios such as “Tunnel setup: On-demand remote access (Linux or WSL)” and “Tunnel setup: Multi-channel on-demand remote access (Linux or WSL)”.

When setting up the semaphore website, consider the following:

  1. The site must be HTTPS. Today, many ISPs intercept the HTTP 404 responses and display their ads, force-rewriting the HTTP status to 200.
  2. The semaphore URLs should respond with HTTP status 200 if the semaphore is up and 404 if it’s down.
  3. For reliability reasons, it’s desirable to keep the semaphore website separate from any existing sites, on its own domain with its own SSL certificate.

In the DNS control panel of joescompany.com, create the record secret of type A or CNAME, pointing to the web server host. For example:

secret IN A 45.56.76.21

Wait several minutes for the changes to take effect. It usually takes 10-15 minutes up to an hour.

Let’s check our new DNS record:

ping secret.joescompany.com
PING li926-21.members.linode.com (45.56.76.21) 56(84) bytes of data.
64 bytes from li926-21.members.linode.com (45.56.76.21): icmp_seq=1 ttl=42 time=105 ms
64 bytes from li926-21.members.linode.com (45.56.76.21): icmp_seq=2 ttl=42 time=105 ms
…

👍 Great, the DNS name secret.joescompany.com responds.

Now let’s set up a simple static website on Nginx.

ssh joe@secret.joescompany.com

💡 As of now, all commands are run on the web server, secret.joescompany.com.

Install the automated SSL certificate generator, Certbot.

💡 You may use any other SSL provider and software. This example assumes you’re using Certbot.

Install the Nginx web server:

sudo apt install nginx

Create the new Nginx website configuration:

sudoedit /etc/nginx/sites-available/secret.joescompany.com
server {
  listen 443 ssl;
  server_name secret.joescompany.com;

  ssl_certificate /etc/letsencrypt/live/secret.joescompany.com/cert.pem;
  ssl_certificate_key /etc/letsencrypt/live/secret.joescompany.com/privkey.pem;

  access_log /var/log/nginx/secret.joescompany.com_access.log;
  error_log /var/log/nginx/secret.joescompany.com_error.log;

  location / {
    root /home/joe/www/secret.joescompany.com/html;
    index index.html;
  }
}

Enable our website:

cd /etc/nginx/sites-enabled &&
sudo ln -s ../sites-available/secret.joescompany.com

With Certbot, generate the certificate for secret.joescompany.com:

sudo certbot certonly --nginx

💡 In this short example, we don’t cover the details of SSL certificate generation and setup. Please consult the relevant Certbot pages.

Restart Nginx:

sudo service nginx reload

Create the dummy index.html:

mkdir -p ~/www/secret.joescompany.com/html &&
cd ~/www/secret.joescompany.com/html &&
echo "Go away" > index.html

Test it:

curl -D - -k https://secret.joescompany.com
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: …
Content-Type: text/html
Content-Length: 17
Last-Modified: …
Connection: keep-alive
ETag: …
Accept-Ranges: bytes

Go away

If you see this, then the semaphore website is working correctly.

🔝

Client setup (Linux or WSL)

📝 Pre-requisites:

  1. User joe can sudo to change the Linux system settings.
  2. You’ve successfully completed the steps of “Server setup: User and key”.

On your local machine, install the required packages (Ubuntu names):

sudo apt update &&
sudo apt install autossh curl git openssh-client

Get Tunkit:

cd &&
git clone https://github.com/dadooda/tunkit.git &&
cd tunkit
Cloning into 'tunkit'...
remote: Enumerating objects: 510, done.
remote: Counting objects: 100% (145/145), done.
…
Resolving deltas: 100% (306/306), done.

Copy the previously saved private key, joetun.key, to Tunkit’s keys/:

cp -nt keys ~/saved-keys/joetun.key &&
chmod 600 keys/joetun.key

🔝

Client setup (Cygwin)

Install the following packages:

  1. From the “Devel” category: git.
  2. From the “Net” category: autossh, curl, openssh.

Follow the steps of “Client setup (Linux or WSL)”, starting from “get Tunkit”.

🔝

Tunnel setup: SOCKS (Linux, WSL or Cygwin)

📝 Pre-requisites:

  1. You’ve successfully completed the steps of “Client setup”: 🎲Linux or WSL, 🎲Cygwin.

Step into Tunkit’s directory:

cd ~/tunkit

Create a config for script socks and edit it:

cp -n socks.d/conf.sh.example socks.d/conf.sh &&
${EDITOR} socks.d/conf.sh
C_HOST="ec2-13-34-43-202.compute-1.amazonaws.com"
C_USER="joetun"
C_KEY="joetun.key"
C_SOCKS_PORT="1080"

💡 Provide your gateway hostname in C_HOST= instead of ec2-13-34-43-202.compute-1.amazonaws.com.

Discover the IP address of our gateway host, we’ll use it a couple steps later:

ping ec2-13-34-43-202.compute-1.amazonaws.com
PING ec2-13-34-43-202.compute-1.amazonaws.com (13.34.43.202) 56(84) bytes of data.
64 bytes from ec2-13-34-43-202.compute-1.amazonaws.com (13.34.43.202): icmp_seq=1 ttl=41 time=45.6 ms
…

Start the tunnel, yet in debug mode:

DEBUG=! ./socks
Running AutoSSH in the foreground
++ autossh -M 0 joetun@ec2-13-34-43-202.compute-1.amazonaws.com -D 0.0.0.0:1080 -i …
…
Authenticated to ec2-13-34-43-202.compute-1.amazonaws.com ([13.34.43.202]:22) using "publickey".
debug1: Local connections to 0.0.0.0:1080 forwarded to remote address socks:0
debug1: Local forwarding listening on 0.0.0.0 port 1080.
…

⚠️ There should not be messages like this:

bind [0.0.0.0]:1080: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 1080
Could not request local forwarding.

If there are such messages, then port 1080 is being used by another process. In this case, stop ./socks by pressing Ctrl+C, and then either find and stop the competing process, or configure to use a different port, for example, C_PORT="8010". Then run ./socks again with the command listed above.

👍 If all looks good and there are no explicit error messages, then the tunnel is up.

Keep ./socks running where it is, open a new terminal window and move on.

Request an Internet page to discover our external IP address:

curl -s https://ipchicken.com | grep "^[0-9]*\..*<br>$"
172.58.44.119<br>

And now the same via the tunnel:

curl -x socks5://localhost:1080 -s https://ipchicken.com | grep "^[0-9]*\..*<br>$"
13.34.43.202<br>

What do we see? An external resource recognized our traffic originating from 13.34.43.202, which is our gateway host’s IP address. Which, in turn, means that our tunnel is working.

Return to the original ./socks terminal and stop the script by pressing Ctrl+C.

Now, let’s start the tunnel in the background:

./socks-ctl start
Starting AutoSSH
AutoSSH is running, PID 11046

Check it again with the curl -x socks5://… command written above.

👍 Works? It means everything is OK. What we ended up with:

  1. Script socks is running in the background, started with ./socks-ctl start.
  2. Local SOCKS server is listening on localhost:1080. Any Internet client (for example, the Web browser) can send its data through the gateway host by connecting to the local SOCKS server.

Next, if you want the tunnel to start automatically on boot, please complete the steps of “System setup: Tunnel autostart upon boot”: 🎲Linux, 🎲WSL, 🎲Cygwin.

🔝

Tunnel setup: Remote access (Linux)

💡 In this example, the Linux server is running on a virtual machine. If your Linux server is hosted in a cloud, the steps are exactly the same.

💡 In this example, we’ll build a tunnel to an SSH daemon running inside our Linux machine. To build a tunnel to another service, use a different local port number instead of 22.

📝 Pre-requisites:

  1. You’ve successfully completed the steps of “Client setup (Linux or WSL)”.

Step into Tunkit’s directory:

cd ~/tunkit

Create a config for script ra and edit it:

cp -n ra.d/conf.sh.example ra.d/conf.sh &&
${EDITOR} ra.d/conf.sh

The configuration, ra.d/conf.sh, looks like this:

C_HOST="ec2-13-34-43-202.compute-1.amazonaws.com"
C_USER="joetun"
C_KEY="joetun.key"
C_R_PORT="50022"

C_L_HOST="127.0.0.1"
C_L_PORT="22"

💡 Provide your gateway hostname in C_HOST= instead of ec2-13-34-43-202.compute-1.amazonaws.com.

💡 If you build the tunnel to another machine on the local network, provide its local IP address in C_L_HOST=.

Start the tunnel, yet in debug mode:

DEBUG=! ./ra
Running AutoSSH in the foreground
++ autossh -M 0 joetun@ec2-13-34-43-202.compute-1.amazonaws.com -R 0.0.0.0:50022:127.0.0.1:22 -i …
…
Authenticated to ec2-13-34-43-202.compute-1.amazonaws.com ([13.34.43.202]:22) using "publickey".
…
debug1: Remote connections from 0.0.0.0:50022 forwarded to local address 127.0.0.1:22
…
debug1: remote forward success for: listen 0.0.0.0:50022, connect 127.0.0.1:22
…

👍 If all looks good and there are no explicit error messages, then the tunnel is up.

Keep ./ra running, open a new terminal window and move on.

With a regular SSH client, connect to the public (listening) end of the tunnel. Once asked to confirm the host key authenticity, enter yes:

ssh joe@ec2-13-34-43-202.compute-1.amazonaws.com -p 50022
joe@linuxvm:~$

If you see this prompt, congratulations — you’ve just connected to your Linux VM via the tunnel.

Return to the original ./ra terminal and stop the script by pressing Ctrl+C.

Now, let’s start the tunnel in the background:

./ra-ctl start
Starting AutoSSH
AutoSSH is running, PID 2337

Check it again with the ssh … command written above.

👍 Works? It means everything is OK. What we ended up with:

  1. Script ra is running in the background, started with ./ra-ctl start.
  2. The public (listening) end of the tunnel is deployed at ec2-13-34-43-202.compute-1.amazonaws.com:50022. Anyone knowing the IP:port pair can connect, as long as the tunnel is up.

Next, if you want the tunnel to start automatically on boot, please complete these steps.

Or, if you want to boost the tunnel security and set it up to start on demand, please complete these steps.

🔝

Tunnel setup: Remote access (WSL)

📝 Pre-requisites:

  1. Your version of Windows supports connections to it with Remote Desktop.
  2. You have enabled Remote Desktop in your Windows system.
  3. You have another machine with a Remote Desktop client from which you can connect over the tunnel to test it.
  4. You’ve successfully completed the steps of “Client setup (Linux or WSL)”.
  5. You have successfully connected to your PC with a Remote Desktop client over the LAN.

💡 Some Windows versions, such as Home, don’t allow incoming Remote Desktop connections. A firewall or an antivirus may also intervene. Please don’t ignore pre-requisites 3 to 5. If you skip them, you risk wasting your time on unnecessary debugging.

Open the WSL terminal. Step into Tunkit’s directory:

cd ~/tunkit

Create a config for script ra and edit it:

cp -n ra.d/conf.sh.example ra.d/conf.sh &&
${EDITOR} ra.d/conf.sh

The configuration, ra.d/conf.sh, looks like this:

set -e

C_HOST="ec2-13-34-43-202.compute-1.amazonaws.com"
C_USER="joetun"
C_KEY="joetun.key"
C_R_PORT="50389"

C_L_HOST=`ip route show default | awk '{ print $3 }'`
C_L_PORT="3389"

set +e

💡 Provide your gateway hostname in C_HOST= instead of ec2-13-34-43-202.compute-1.amazonaws.com.

💡 If you build the tunnel to another machine on the local network, provide its local IP address in C_L_HOST=.

Start the tunnel, yet in debug mode:

DEBUG=! ./ra
Running AutoSSH in the foreground
++ autossh -M 0 joetun@ec2-13-34-43-202.compute-1.amazonaws.com -R 0.0.0.0:50389:172.17.80.1:3389 -i …
…
Authenticated to ec2-13-34-43-202.compute-1.amazonaws.com ([13.34.43.202]:22) using "publickey".
…
debug1: Remote connections from 0.0.0.0:50389 forwarded to local address 172.17.80.1:3389
…
debug1: remote forward success for: listen 0.0.0.0:50389, connect 172.17.80.1:3389
…

👍 If all looks good and there are no explicit error messages, then the tunnel is up.

Keep ./ra running. From another machine, with a Remote Desktop client, connect to the public (listening) end of the tunnel, ec2-13-34-43-202.compute-1.amazonaws.com:50389.

If you can see your PC in a Remote Desktop session, congratulations.

Return to the original ./ra terminal and stop the script by pressing Ctrl+C.

Now, let’s start the tunnel in the background:

./ra-ctl start
Starting AutoSSH
AutoSSH is running, PID 5347

Check the connection from another machine again.

👍 Works? It means everything is OK. What we ended up with:

  1. Script ra is running in the background, started with ./ra-ctl start.
  2. The public (listening) end of the tunnel is deployed at ec2-13-34-43-202.compute-1.amazonaws.com:50389. Anyone knowing the IP:port pair can connect, as long as the tunnel is up.

Next, if you want the tunnel to start automatically on boot, please complete these steps.

Or, if you want to boost the tunnel security and set it up to start on demand, please complete these steps.

🔝

Tunnel setup: Having a working remote access tunnel, make its clone (Linux or WSL)

📝 Pre-requisites:

  1. You’ve successfully completed the steps of “Tunnel setup: Remote access”: 🎲Linux, 🎲WSL.

Let’s say we have more than one gateway server on the Internet, and we want to utilize all of the servers to duplicate our remote access tunnels. The ra tunnel is already set up, now we want to build the xyz tunnel according to the same model.

Step into Tunkit’s directory:

cd ~/tunkit

Create a set of directories and files:

mkdir xyz.d xyz-mon.d &&
ln -s ra xyz &&
for P in ctl mon mon-ctl; do cp -d ra-${P} xyz-${P}; done

Create a config for script xyz and edit it:

cp -nt xyz.d ra.d/conf.sh &&
${EDITOR} xyz.d/conf.sh

Provide the necessary settings in xyz.d/conf.sh as described in the main chapter “Tunnel setup: Remote access” (🎲Linux, 🎲WSL). Set up, debug and start the tunnel.

What we ended up with:

  1. xyz script is ready to use, controlled by ./xyz-ctl script.
  2. To set up the on-demand monitor, follow the steps of “Tunnel setup: On-demand remote access (Linux or WSL)”, using xyz-mon instead of ra-mon.

🔝

Tunnel setup: On-demand remote access (Linux or WSL)

📝 Pre-requisites:

  1. You’ve successfully completed the steps of “Server setup: Nginx-based semaphore website”.
  2. You’ve successfully completed the steps of “Tunnel setup: Remote access”: 🎲Linux, 🎲WSL.

Let’s say we’ve built a remote access tunnel (🎲Linux, 🎲WSL), configured its autostart (🎲Linux, 🎲WSL) and are ejoying it. But what about security, especially when it comes to full control of the machine over SSH or RDP? What if the IP:port combination pointing to our internal, and therefore obviously less protected machine, gets somehow known to an attacker? What if he guesses or otherwise finds out our simple login and password?

Again, the very fact that we at some point decide to provide access to an internal resource through a tunnel implies a generally lower level of security.

Most likely, the resource to which we provide access (most often to ourselves) has long been used exclusively on our local network, and passwords, purely historically, are assigned simple, if assigned at all.

To tackle all this, Tunkit makes it possible to significantly raise the level of security of the remote access tunnels by letting us enable and disable them on demand.

⚠️ If you’re setting up a tunnel for permanent production access to critical infrastructure, such as your main computer at work, following the steps of this scenario is highly desirable from a security point of view.

Well, let’s get started.

If you’ve previously set up tunnel autostart (🎲Linux, 🎲WSL), now it’s time to reliably disable it: 🎲Linux, 🎲WSL.

We’ve already set up the semaphore website secret.joescompany.com earlier. Let’s create a semaphore on it, and see if it works.

⚠️ Don’t ignore this step, no matter how simple it may seem to you. If you are setting up a tunnel for production use, the semaphore must be bulletproof. Whoever controls the semaphore controls the tunnel.

Bring the semaphore up by creating an empty website page:

ssh joe@secret.joescompany.com "cd ~/www/secret.joescompany.com/html && mkdir -p powah && touch powah/uno"

Check from the outside:

curl -fk https://secret.joescompany.com/powah/uno; echo code:$?
code:0

👍 Great. Now bring the semaphore down:

ssh joe@secret.joescompany.com "rm ~/www/secret.joescompany.com/html/powah/uno"

Check again:

curl -fk https://secret.joescompany.com/powah/uno; echo code:$?
curl: (22) The requested URL returned error: 404 Not Found
code:22

Now the semaphore is down, just as we wanted it to be.

Now let’s get to the tunnel monitor setup.

💡 The tunnel monitor is a script, which watches the semaphore on the Internet, and starts a pre-configured tunnel once the semaphore is up, or stops the tunnel once the semaphore is down.

💡 The relationship between the monitor script and the tunnel script is defined by their filenames. Thus, ra-mon is connected with ra. Likewise, xyz-mon will be connected with xyz, and so forth.

Step into Tunkit’s directory:

cd ~/tunkit

Create a config for script ra-mon and edit it:

cp -n ra-mon.d/conf.sh.example ra-mon.d/conf.sh &&
${EDITOR} ra-mon.d/conf.sh
# Semaphore. Must be HTTPS.
C_SEMA_URL="https://secret.joescompany.com/powah/uno"

Start the monitor script:

./ra-mon-ctl start
Monitor is running, PID 3298

In a new terminal window, watch the live monitor log:

cd ~/tunkit &&
./ra-mon-ctl log

Using a free terminal, bring the semaphore up:

ssh joe@secret.joescompany.com "cd ~/www/secret.joescompany.com/html && mkdir -p powah && touch powah/uno"

The monitor log should grow with messages like these:

[2023-01-26 19:01:30] Semaphore is up, triggering START
Starting AutoSSH
AutoSSH is running, PID 32823

Additionally, the control script should indicate that the tunnel is running:

./ra-ctl status
AutoSSH is running, PID 32823

Bring the semaphore down:

ssh joe@secret.joescompany.com "rm ~/www/secret.joescompany.com/html/powah/uno"

The monitor log should grow with messages like these:

[2023-01-26 19:07:00] Semaphore is down, triggering STOP
Stopping AutoSSH, PID 32823
AutoSSH is not running

👍 If all is as above, then the semaphore and the monitor script are working correctly.

💡 If for some reason the monitor script behaves inappropriately, you can run it in the foreground in debug mode:

DEBUG=! ./ra-mon

What we ended up with:

  1. The ra tunnel is now controlled by the monitor, which watches the semaphore on the Internet.
  2. Manual control via ./ra-ctl start and ./ra-ctl stop is now pointless. For example, if the semaphore is down and you do a ./ra-ctl start, the monitor will instantly stop the tunnel. The opposite is also true: as long as the semaphore is up, the ra tunnel will be started by the monitor, even if you stop it by hand.

Next, if you want the monitor to start automatically on boot, please complete these steps: 🎲Linux, 🎲WSL.

🔝

Tunnel setup: Multi-channel on-demand remote access (Linux or WSL)

📝 Pre-requisites:

  1. All pre-requisites of “Tunnel setup: On-demand remote access (Linux or WSL)”.
  2. You’ve successfully completed the steps of “Tunnel setup: Remote access” (🎲Linux, 🎲WSL) to produce two independently working tunnels: ra1 and ra2.

The main chapter explains what’s an on-demand monitor, when it’s useful, and how to set it up for a single tunnel. If you have’t read it, please do.

Here we describe a scenario where multiple tunnels are governed by a single semaphore. Of course, we can start multiple monitors (ra1-mon, ra2-mon, …) that watch the same semaphore, but that’ll be wasteful in terms of manageability, traffic, and the number of running processes.

Therefore, Tunkit includes a raduo-mon monitor, which we describe here. This monitor watches the semaphore and controls multiple tunnels configured in the body of the script.

By default, these are ra1 and ra2, but the set can be extended by a few simple changes in raduo-mon:

#--------------------------------------- Configuration

ALLSEQ=`seq 1 2`
c1() { ./ra1-ctl "$@" 2>&1 | ppipe "ra1-ctl: "; }
c2() { ./ra2-ctl "$@" 2>&1 | ppipe "ra2-ctl: "; }
# Add `c3()` and edit `ALLSEQ` to control yet another tunnel.

Some of my servers control 8-10 tunnels with raduo-mon.

The steps to do the setup are the same as in the main chapter, except for the following differences:

  • The on-demand monitor filename is raduo-mon.
  • The control script filename is raduo-mon-ctl.
  • The data directory name is raduo-mon.d/.

Next, if you want the monitor to start automatically on boot, please complete these steps: 🎲Linux, 🎲WSL.

🔝

System setup: Tunnel autostart upon boot (Linux)

💡 Here we describe a common scenario for configuring a service to start automatically upon boot in a modern Linux system. We use the SOCKS tunnel as an example.

📝 Pre-requisites:

  1. Our Linux system uses systemd to manage boot-time services startup.
  2. User joe has set up the SOCKS tunnel properly.

In joe’s home directory, create a unit file for the new systemd service:

cd &&
mkdir -p .config/systemd/user &&
${EDITOR} .config/systemd/user/tunkit-socks.service
[Unit]
Description=Tunkit SOCKS

[Service]
Type=forking
Restart=on-failure
PIDFile=%h/tunkit/socks.d/autossh.pid
ExecStart=-%h/tunkit/socks-ctl start
ExecStop=%h/tunkit/socks-ctl stop

[Install]
WantedBy=default.target

Enable the service:

systemctl --user enable tunkit-socks
Created symlink /home/joe/.config/systemd/user/default.target.wants/tunkit-socks.service → /home/joe/.config/systemd/user/tunkit-socks.service.

Reboot. After it, check if the service is running:

systemctl --user status tunkit-socks
● tunkit-socks.service - Tunkit SOCKS
     Loaded: loaded (/home/joe/.config/systemd/user/tunkit-socks.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2023-01-18 17:11:59 UTC; 7min ago
    Process: 1280 ExecStart=/home/joe/tunkit/socks-ctl start (code=exited, status=0/SUCCESS)
   Main PID: 1337 (autossh)
     CGroup: /user.slice/user-1002.slice/user@1002.service/tunkit-socks.service
             ├─1337 /usr/lib/autossh/autossh -M 0    joetun@ec2-13-34-43-202.compute-1.amazonaws.com -D 0.0.0.0:1080 -i /home/joe/tunkit>
             └─1339 /usr/bin/ssh -D 0.0.0.0:1080 -i /home/joe/tunkit/keys/joetun.key -N -o StrictHostKeyCheck>

Jan 18 17:11:58 joehost systemd[1274]: Starting Tunkit SOCKS...
Jan 18 17:11:59 joehost socks-ctl[1321]: Starting AutoSSH
Jan 18 17:11:59 joehost socks-ctl[1280]: AutoSSH is running, PID 1337
Jan 18 17:11:59 joehost systemd[1274]: Started Tunkit SOCKS.

Just in case, check if manual restart works:

systemctl --user restart tunkit-socks &&
systemctl --user status tunkit-socks

If everything looks normal, then the service is working.

💡 If something doesn’t work, try these diagnostic commands:

journalctl --user
journalctl --user -u tunkit-socks
systemctl --user --failed

In addition to the above, I highly recommend reading this article at DigitalOcean: Understanding Systemd Units and Unit Files It’s a very detailed and thoughtful material explaining how systemd units work.

🔝

For the remote access script (ra)

The steps to do the setup are the same as in the main chapter, except for the following differences:

  • The unit file for systemd is called .config/systemd/user/tunkit-ra.service and has the following content:

    [Unit]
    Description=Tunkit RA
    
    [Service]
    Type=forking
    Restart=on-failure
    PIDFile=%h/tunkit/ra.d/autossh.pid
    ExecStart=-%h/tunkit/ra-ctl start
    ExecStop=%h/tunkit/ra-ctl stop
    
    [Install]
    WantedBy=default.target
    
  • The command to enable the service is:

    systemctl --user enable tunkit-ra
    
  • The command to check the service after reboot is:

    systemctl --user status tunkit-ra
    

🔝

For the on-demand monitor script (ra-mon)

The steps to do the setup are the same as in the main chapter, except for the following differences:

  • The unit file for systemd is called .config/systemd/user/tunkit-ra-mon.service and has the following content:

    [Unit]
    Description=Tunkit RA monitor
    
    [Service]
    Type=forking
    Restart=on-failure
    PIDFile=%h/tunkit/ra-mon.d/monitor.pid
    ExecStart=-%h/tunkit/ra-mon-ctl start
    ExecStop=%h/tunkit/ra-mon-ctl stop
    
    [Install]
    WantedBy=default.target
    
  • The command to enable the service is:

    systemctl --user enable tunkit-ra-mon
    
  • The command to check the service after reboot is:

    systemctl --user status tunkit-ra-mon
    

🔝

For the multi-channel on-demand monitor script (raduo-mon)

The steps to do the setup are the same as in the main chapter, except for the following differences:

  • The unit file for systemd is called .config/systemd/user/tunkit-raduo-mon.service and has the following content:

    [Unit]
    Description=Tunkit RAduo monitor
    
    [Service]
    Type=forking
    Restart=on-failure
    PIDFile=%h/tunkit/raduo-mon.d/monitor.pid
    ExecStart=-%h/tunkit/raduo-mon-ctl start
    ExecStop=%h/tunkit/raduo-mon-ctl stop
    
    [Install]
    WantedBy=default.target
    
  • The command to enable the service is:

    systemctl --user enable tunkit-raduo-mon
    
  • The command to check the service after reboot is:

    systemctl --user status tunkit-raduo-mon
    

🔝

Disabling the autostart

Depending on which systemd unit file you’ve created earlier, run the commands:

  • For the socks script:

    systemctl --user stop tunkit-socks &&
    systemctl --user disable tunkit-socks &&
    systemctl --user status tunkit-socks
    
  • For the ra script:

    systemctl --user stop tunkit-ra &&
    systemctl --user disable tunkit-ra &&
    systemctl --user status tunkit-ra
    
  • For the ra-mon script:

    systemctl --user stop tunkit-ra-mon &&
    systemctl --user disable tunkit-ra-mon &&
    systemctl --user status tunkit-ra-mon
    
  • For the raduo-mon script:

    systemctl --user stop tunkit-raduo-mon &&
    systemctl --user disable tunkit-raduo-mon &&
    systemctl --user status tunkit-raduo-mon
    

🔝

System setup: Tunnel autostart upon boot (WSL)

💡 Here we describe a common scenario for configuring a service to start automatically upon boot in a Linux system running under WSL. We use the SOCKS tunnel as an example.

📝 Pre-requisites:

  1. User joe has set up the SOCKS tunnel properly.
  2. User joe can sudo to change the Linux system settings.

Edit or create /etc/wsl.conf:

sudoedit /etc/wsl.conf

Find or create the [boot] section and add the command setting to it. If there are other sections and settings, leave them as they are.

[boot]
command = "sudo -u joe ~joe/tunkit/socks-ctl start"

If boot.command already exists, follow these steps to handle it.

Restart the WSL instance. In the classic Command Prompt do a:

wsl --shutdown & wsl echo hey

💡 If you’re in PowerShell, do a:

wsl --shutdown ; wsl echo hey

The VM running Linux will stop and then start again. After a few seconds (up to 10 seconds on slow machines) you’ll see hey.

Let’s check if our service has started. Open a WSL terminal and run:

~joe/tunkit/socks-ctl status
AutoSSH is running, PID 49

If everything looks normal, then the service is working.

🔝

For the remote access script (ra)

The steps to do the setup are the same as in the main chapter, except for the following differences:

  • The boot.command setting in /etc/wsl.conf is:

    command = "sudo -u joe ~joe/tunkit/ra-ctl start"
    

    If boot.command already exists, follow these steps to handle it.

  • The command to check the service after WSL restart is:

    ~joe/tunkit/ra-ctl status
    

🔝

For the on-demand monitor script (ra-mon)

The steps to do the setup are the same as in the main chapter, except for the following differences:

  • The boot.command setting in /etc/wsl.conf is:

    command = "sudo -u joe ~joe/tunkit/ra-mon-ctl start"
    

    If boot.command already exists, follow these steps to handle it.

  • The command to check the service after WSL restart is:

    ~joe/tunkit/ra-mon-ctl status
    

🔝

For the multi-channel on-demand monitor script (raduo-mon)

The steps to do the setup are the same as in the main chapter, except for the following differences:

  • The boot.command setting in /etc/wsl.conf is:

    command = "sudo -u joe ~joe/tunkit/ra-mon-ctl start"
    

    If boot.command already exists, follow these steps to handle it.

  • The command to check the service after WSL restart is:

    ~joe/tunkit/ra-mon-ctl status
    

🔝

If boot.command already exists in /etc/wsl.conf

/etc/wsl.conf is a configuration file with a primitive syntax. Each setting can only be present once, and only the last setting has effect.

Our boot.command is no exception. What if we need to execute more than one command, and boot.command is already there?

For example, we’re about to add ra-ctl start, but command is already set:

[boot]
command = "sudo -u joe ~joe/tunkit/socks-ctl start"

The answer is simple. We need to carefully append (“chain”) the new command to the existing one via && or ;. For example:

[boot]
command = "sudo -u joe ~joe/tunkit/socks-ctl start && sudo -u joe ~joe/tunkit/ra-ctl start"
#command = "sudo -u joe ~joe/tunkit/socks-ctl start"

⚠️ I highly recommend not overwrite, but comment out the existing command = line so that in case of an error you can quickly restore a working setup. Remember, you’re editing a file with primitive syntax and making a mistake is very easy.

You can learn more about chaining operators && and ; in this article on GeeksforGeeks or another Unix shell manual.

🔝

Disabling the autostart

Edit /etc/wsl.conf:

sudoedit /etc/wsl.conf

Locate the [boot] section and comment out the command = line:

[boot]
#command = "sudo -u joe ~joe/tunkit/socks-ctl start"

Next time you boot WSL, the tunnel will not start.

🔝

System setup: Tunnel autostart upon boot (Cygwin)

Activate the “Run” dialog (Win+R). In a dialog that appears, input shell:startup and press ENTER.

In the window that appears, create a new shortcut. Input the following item location:

c:\cygwin64\bin\bash.exe -l -c "~/tunkit/socks-ctl start"

💡 If you have installed Cygwin to a path different from c:\cygwin64, adjust the above value accordingly.

Input shortcut name, e.g. Tunkit SOCKS. Confirm by clicking “OK”.

Next time you log into your machine, the SOCKS tunnel will automatically start in the background.

⚠️ The steps above assume that the tunnel is started when the user logs on interactively. This method will not work the remote access scenarios. Some aspects of this are partially covered in “Compatibility notes (Cygwin)”.

🔝

Miscellaneous

Compatibility notes (WSL)

Throughout the document, the term “WSL” refers to WSL 2. I’ve never set up Tunkit under WSL 1, although I think it’ll work under this version, too.

🔝

Compatibility notes (Cygwin)

I’ve been successfully using Tunkit under Cygwin, but over time I’ve come across a number of aspects, mostly negative ones, which you’ll have to consider if you want to set up durable tunnels for production use.

Let’s say, if you need to set a “quick and dirty” tunnel for one-time use, Cygwin suits quite well.

But if you want to set up advanced production scenarios like “Tunnel setup: Remote access”, “Tunnel setup: On-demand remote access” or “Tunnel setup: Multi-channel on-demand remote access”, I’d advise you to take a minute and install WSL. Especially considering that the most recent WSL editions, especially under Windows 11, can be installed in just minutes. If some time ago Cygwin used to have the advantage of a relatively simpler and faster installation, now this advantage is gone.

I summarize the known Cygwin compatibility aspects in the list below. Items are marked 🍏, 🍊 and 🍎, according to the nature of the aspect:

  1. 🍏 Both autossh and ssh packages are very stable, although they may look slightly outdated by version number. All works without any issues.
  2. 🍏 Tunnel autostart after interactive logon into the machine is set up without any problems. For a simple socks tunnel it works just fine.
  3. 🍊 Difficulties were observed trying to set up tunnel autostart without the interactive logon. I’ve used the Task Scheduler. The processes start, but they can’t be controlled from the Cygwin shell, because they belong to another user. Also, some very specific non-default settings need to be provided if you want the task to stay alive.
  4. 🍎 Severe problems with the Windows Defender firewall were observed. In order for the RDP tunnel to the same machine work, I had do completely disable the firewall. At the same time, if my machine was building a tunnel to another machine on the local network, everything worked fine.

Since I yet don’t have reliable answers to the questions above, I refrain from detailed description of Cygwin setup steps, except for the most basic SOCKS scenario.

🔝

The product is free to use by everyone. Feedback of any kind is greatly appreciated.

— © 2021-2023 Alex Fortuna