Describes how to connect to a machine under NAT with SSH Tunnels initiated by systemd unit files.
Note: This page has previously been posted as the following page on SSH Tunnels. The publication date is replicated here.
Problem
The problem with a normal home network is that it is hidden behind a NAT. Moreover setting up port forwarding is not exactly trivial for end-user. Therefore, since we have a readily accessible remote server, we can leverage it for our purpose.
Solution
Basically now there are three devices under our control:
homecomputermymobilecloudserver
We can then configure cloudserver as a bridge, as we are in control with it.
That way we can, configure a ssh reverse tunnel homecomputer > cloudserver connection, and also another ssh reverse tunnel to mymobile > cloudserver .
Then we should also create their corresponding ssh tunnels: homecomputer < cloudserver and mymobile < cloudserver.
SSH tunnel from home and phone to cloud
From homecomputer we can issue:
ssh -R 2222:localhost:22 \
-L 2223:localhost:2223 \
user@cloudserver
Here’s how it works:
- The
-Rargument tellshomecomputerto: expose port22onlocalhost, and any connection fromcloudserverport2222should be accepted by our exposed port. - The
-Largument tellshomecomputerto: tunnel any connection made tolocalhost:2223into port2223oncloudserver.
Then we just have to configure the reverse from mymobile:
ssh -R 2223:localhost:8022 \
-L 8023:localhost:2222 \
user@cloudserver
SSH tunnel between home and phone with cloud as a bridge
This way, from homecomputer to mymobile, I just have to run:
ssh -p 2223 localhost
And from mymobile to homecomputer, I just have to run:
ssh -p 8023 user@localhost
Even better, we should consider also adding -TNv argument to ssh.
Automatic configuration on home
Like the one we write in SSH proxy as VPN replacement, we can actually configure them as a systemd/user service:
# file ~/.config/systemd/user/remote-tunnel.service
[Unit]
Description=SSH tunnel to domain.tld
[Service]
Type=simple
Restart=always
RestartSec=10
ExecStart=/usr/bin/ssh -F %h/.ssh/config -N remote-tunnel
[Install]
WantedBy=default.target
This service will be run after user logs in to a session. It will be killed after the last session of that user is closed. If we want it to start at boot and would remain be open after the last session of a user is closed, we can let our service to linger:
loginctl enable-linger
Then we write a configuration file as follows:
# file ~/.ssh/config
# Equivalent to:
# ssh \
# -R 2222:localhost:22 \
# -L 2223:localhost:2223 \
# -D 8257 \
# user@cloudserver
Host remote-tunnel
User user
HostName domain.tld
LocalForward 2223 localhost:2223
RemoteForward 2222 localhost:22
DynamicForward 8257
ServerAliveInterval 120
It would then enable us to establish the tunnel (bonus the proxy port) as soon as we login into our account. Then connection to phone is as simple as:
ssh -p 2223 localhost
Automatic configuration on phone
I use termux on my phone. From my phone, we can also set a configuration file:
# ssh -R 2223:localhost:8022 \
# -L 8023:localhost:2222 \
# [email protected]
Host remote-tunnel
User user
HostName domain.tld
LocalForward 8023 localhost:2222
RemoteForward 2223 localhost:8022
ServerAliveInterval 120
Therefore we can just do this to initiate connection:
ssh -TNv remote-tunnel
It can be done with the same tmux setup described in the SSH proxy as VPN replacement article.
Then to connect to our computer, we invoke:
ssh -p 8023 user@localhost
Conclusion
The pattern is then:
ssh -R port:localhost:hostport \
-L localport:localhost:remoteport \
[user@]remotehost
Note that localhost in -L part is actually local to remotehost, not our localhost.
Then for [user@]remotehost part, it means that the username is optional.
If the username is not given, and only remote host is specified, ssh client will attempt to connect to the remotehost with the same username as the local computer’s username.
The map from homecomputer is then:
localhost:22 listens from cloudserver:2222
localhost:2223 forwarded to cloudserver:2223
The map from mymobile is then:
localhost:8022 listens from cloudserver:2223
localhost:8023 forwarded to cloudserver:2222
The map from cloudserver is then:
localhost:2222 to homecomputer:22
localhost:2223 to mymobile:8022
localhost:2223 from homecomputer:2223
localhost:2222 from mymobile:8023
Appendix
In an afterthought, we realized that we don’t really need to connect to and from our phone with this method most of the time. At home, we would prefer to connect to the phone via our home’s local network, simply because it’s much faster. Conversely, at work, we would prefer to connect to the phone via our work’s local network.
We would only need to connect to our home computer from our phone when we’re outside and our phone is not in the same network as our work computer. Even then when it does happen, our work computer would not be normally connected to our home computer.
It means our phone really only need to connect to home computer when we explicitly told it so.
Therefore, our work computer can use a similar ~/.ssh/config as our phone, plus we can set up a systemd/user service.
At such event where we are outside and our work computer are also on, we can always connect to our home computer with extra steps from our phone: go to cloudserver, and then ssh -p 2222 user@localhost will serve the job well.
SSH tunnel between home and office
We put the following in our office’s ssh configuration:
# file ~/.ssh/config
Host remote-tun
User user
HostName domain.tld
LocalForward 8022 localhost:2222
RemoteForward 2223 localhost:22
DynamicForward 8257
ServerAliveInterval 120
Host home
User user
HostName localhost
Port 8022
ServerAliveInterval 120
Then we set up a systemd user service at work:
# file ~/.config/systemd/user/tunnel.service
[Unit]
Description=SSH tunnel to domain.tld
[Service]
Type=simple
Restart=always
RestartSec=10
ExecStart=/usr/bin/ssh -F %h/.ssh/config -N remote-tun
Therefore we can connect to our home computer with:
$ ssh home
Further reading
Read more from the sources below: