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 -w
command
line flag which allows bridging of two ethernet networks using tap
-devices.
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 tap
-device while
a Layer-3-VPN is usually implemented using a tun
-device.
OpenSSH supports both tunneling on Layer-2 and Layer-3 but the rest of this post
will focus on Layer-2-VPNs using tap
-devices.
Prerequisites
This feature requires an OpenSSH server to be installed on the remote site
with the following settings configured in /etc/ssh/sshd_config
:
1 |
# ... 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
the brctl
command provided by the bridge-utils
package. For distributions
using systemd
and its network daemon the following two config files
will create the bridge interface br-remote
in a way that survives a reboot.
1 |
[NetDev] Name=br-remote Kind=bridge |
1 |
[Match] Name=br-remote |
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:
1 |
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" |