SSH Features: Bridging two networks
There are many cases where two networks have to be connected on Layer 2 in a virtual fashion, which is referred to as a Virtual Private Network (VPN). Typically you would use OpenVPN or similar software for that. All of these tools have in common that they require some non trivial setup steps like setting up a PKI or exchanging keys or certificates in a safe way.
As a programmer, setting up a full fledged VPN software for a development environment which is destroyed regularly seems not be the best option. A question that normally comes to my mind in these moments is:
Can I use SSH for that?
The answer is usually YES, like in this case. For the first part of an ongoing
series about little known SSH features, we’ll take a look at the
line flag which allows bridging of two ethernet networks using
Bridging via tap-devices
VPNs can be implemented on different
OSI-layers which depends on the
desired network architecture. Usually you have to choose between a Layer-2 or
Layer-3-VPN which can be compared to connecting two separate network segments
using either a switch (Layer 2) or a router (Layer 3). Choosing between these
two types of VPNs also corresponds to the type of virtual network interface
which have to be used. A Layer-2-VPN requires the usage of a
a Layer-3-VPN is usually implemented using a
OpenSSH supports both tunneling on Layer-2 and Layer-3 but the rest of this post
will focus on Layer-2-VPNs using
This feature requires an OpenSSH server to be installed on the remote site
with the following settings configured in
# ... PermitTunnel yes PermitRootLogin yes
As you can see this setup requires root login, because creating tap devices
normally requires these permissions.
PermitRootLogin yes allows login as
root using a password which is naturally a bad idea. If possible
PermitRootLogin without-password should be used instead, which allows login
as root only using public key authentication.
Creating the tunnel
root » ssh -o Tunnel=ethernet -w 5:5 -t root@REMOTE_HOST
After executing this command with successful authentication a device called
tap5 is created on each side of the tunnel, which works but the interface
are shut down. The argument
-w X:Y specifies which device numbers should
be used on the local and remote side.
Configuring bridge interfaces using systemd-networkd
tap-devices are not that useful without being attached to a real network
interface. That’s the reason why they are normally attached to a bridge
interface on each side. Setting up bridge interfaces is usually done using
brctl command provided by the
bridge-utils package. For distributions
systemd and its network daemon the following two config files
will create the bridge interface
br-remote in a way that survives a reboot.
[NetDev] Name=br-remote Kind=bridge
After adding these files the following command has to be executed for the network daemon to apply the new configuration:
systemctl restart systemd-networkd
Now we have a working bridge interface called
br-remote which has no
interfaces attached to it. Adding the
tap5 interface to the bridge is done
by the following command:
root » brctl addif br-remote tap5
Putting it all together
Because the simple SSH command described above requires some additional commands to be executed on both sides of the tunnel it would be cool to join these commands in a single command executed on the client.
A well known feature of SSH is that the first real argument to the ssh command is
treated as a command that is executed on the remote side after setting up the
tunnel. A lesser known feature is that this also exists for the local
side through the
LocalCommand option which executes a command on the local
side after setting up the SSH tunnel.
The following command assumes that a bridge interface
br-local exists on
the local side while
br-remote exists on the remote side:
root » ssh -o "PermitLocalCommand=yes" \ -o "LocalCommand=brctl addif br-local tap5 && ifconfig tap5 up" \ -o Tunnel=ethernet \ -w 5:5 \ -t root@REMOTE_HOST \ "brctl addif br-remote tap5 && ifconfig tap5 up"