VPN over SSH

There are several ways to set up a Virtual Private Network through SSH. Note that, while this may be useful from time to time, it may not be a full replacement for a regular VPN. See for example
[1]
.
Using badvpn's tun2socks
Note
The badvpn project has been discontinued in August 2022, and the latest stable release is from April 2015. There is a Go reimplementation at
https://github.com/xjasonlyu/tun2socks
.
badvpn
is a collection of utilities for various VPN-related use cases.
Start SSH dynamic SOCKS proxy
First, we will set up a normal SSH dynamic socks proxy like usual:
$ ssh -TND 4711 <your_user>@<SSH_server>
Set up badvpn and tunnel interface
Afterwards, we can go ahead with setting up the TUN.
# ip tuntap add dev tun0 mode tun user <your_local_user>
# ip addr replace 10.0.0.1/24 dev tun0
# badvpn-tun2socks --tundev tun0 --netif-ipaddr 10.0.0.2 --netif-netmask 255.255.255.0 --socks-server-addr localhost:4711
Now you have a working local
tun0
interface which routes all traffic going into it through the SOCKS proxy you set up earlier.
Get traffic into the tunnel
All that's left to do now is to set up a local route to get some traffic into it. Let us set up a route that routes all traffic into it. We will need three routes:
Route that goes to the SSH server that we use for the tunnel with a low metric.
Route for DNS server (because tun2socks does not do UDP which is necessary for DNS) with a low metric.
Default route for all other traffic with a higher metric than the other routes.
The idea behind setting the metrics specifically is because we need to ensure that the route picked to the SSH server is always direct because otherwise it would go back into the SSH tunnel which would cause a loop and we would lose the SSH connection as a result. Apart from that, we need to set an explicit DNS route because tun2socks does not tunnel UDP (required for DNS). We also need a new default route with a lower metric than your old default route so that traffic goes into the tunnel at all. With all of that said, let us get to work:
# ip route add <IP_of_SSH_server> via <IP_of_original_gateway> metric 5
# ip route add <IP_of_DNS_server> via <IP_of_original_gateway> metric 5
# ip route add default via 10.0.0.2 metric 6
Now all traffic (except for DNS and the SSH server itself) should go through
tun0
.
OpenSSH's built in tunneling
OpenSSH has built-in TUN/TAP support using
-w<local-tun-number>:<remote-tun-number>
. Here, a layer 3/point-to-point/ TUN tunnel is described. It is also possible to create a layer 2/ethernet/TAP tunnel.
Enable forwarding for the TUN device
To enable forwarding for the TUN device, edit
/etc/ssh/sshd_config
and set
PermitTunnel
to
yes
,
point-to-point
or
ethernet
. Setting
yes
enables forwarding for both
point-to-point
and
ethernet
tunnels. See
sshd_config(5)
for details.
/etc/ssh/sshd_config
...
PermitTunnel yes
...
Then
reload
sshd.service
.
Create tun interfaces using systemd-networkd
/etc/systemd/network/vpn.netdev
[NetDev]
Name=tun5
Kind=tun

[Tun]
User=vpn
Group=network
/etc/systemd/network/vpn.network
[Match]
Name=tun5

[Address]
Address=192.168.200.2/24
Once these files are created, enable them by
restarting
systemd-networkd.service
.
Also, you may manage tun interfaces with
ip tunnel
command.
Creating interfaces in SSH command
SSH creates both interfaces automatically, but IP and routing should be configured after the connection is established.
ssh \
  -o PermitLocalCommand=yes \
  -o LocalCommand="sudo ifconfig tun5 192.168.244.2 pointopoint 192.168.244.1 netmask 255.255.255.0" \
  -o ServerAliveInterval=60 \
  -w 5:5 vpn@example.com \
  'sudo ifconfig tun5 192.168.244.1 pointopoint 192.168.244.2 netmask 255.255.255.0; echo tun0 ready'
Start SSH
ssh -f -w5:5 vpn@example.com -i ~/.ssh/key "sleep 1000000000"
or you may add keep-alive options if you are behind a NAT.
ssh -f -w5:5 vpn@example.com \
        -o ServerAliveInterval=30 \
        -o ServerAliveCountMax=5 \
        -o TCPKeepAlive=yes \
        -i ~/.ssh/key "sleep 1000000000"
Troubleshooting
ssh should have access rights to tun interface or permissions to create it. Check owner of tun interface and/or /dev/net/tun.
Obviously if you want to access a network rather than a single machine you should properly set up IP packet forwarding, routing and maybe a netfilter on both sides.
If you do not enable tunneling, you may get the following error when you want to create an SSH tunnel using
-w
:
channel 0: open failed: connect failed: open failed
Tunnel forwarding failed
Using PPP over SSH
pppd
can easily be used to create a tunnel through an SSH server:
# pppd updetach noauth silent nodeflate pty "/usr/bin/ssh root@remote-gw /usr/sbin/pppd nodetach notty noauth" ipparam vpn 10.0.8.1:10.0.8.2
When the VPN is established, you can route traffic through it. To get access to an internal network:
# ip route add 192.168.0.0/16 via 10.0.8.2
To route all Internet traffic through the tunnel, for example, to protect your communication on an unencrypted network, first add a route to the SSH server through your regular gateway:
# ip route add <remote-gw> via <current default gateway>
Next, replace the default route with the tunnel
# ip route replace default via 10.0.8.2
Helper script
pvpn
(package
pvpn
AUR
) is a wrapper script around
pppd
over SSH.
See also
Network configuration
Router
OpenSSH
sshuttle
, a tool written in
Python
which can create tunnels