Setting up a Layer 3 tunneling VPN with using OpenSSH

Posted by emeitner on Mon 2 Jul 2007 at 16:37

This article describes how to use the new tunneling features of OpenSSH V 4.3 to establish a VPN between two Debian or Debian-like systems. Note that by tunneling I am referring to layer-3 IP-in-SSH tunneling, not the TCP connection forwarding that most people refer to as tunneling.

When operational this VPN will allow you to route traffic from one computer to another network via an SSH connection.

This is a brief recipe rather than a "HOW-TO". It it assumed you are familiar with all of the basic concepts.

Requirements

  • Debian Etch and/or Ubuntu Edgy systems
  • SSH version 4.3 or higher is required on both ends of the VPN.

Introduction

SSH V 4.3 introduced true layer-2 and layer-3 tunneling allowing easy to configure VPNs that can be built upon existing SSH authentication mechanisms. The VPN configuration described below allows a client(or if you prefer the stupid term: road warrior) to connect to a firewall/server and access the entire private network that is behind it.

Previously I never allowed root login via SSH to any machines because I always logged in under a personal account and then used sudo. It made sense to turn off root logins via SSH(PermitRootLogin=no). With the advent of the new tunneling features there seems to be a need to have a limited root login for the purposes of establishing the SSH VPN. This is required because the user that connects to the sshd server must have the permissions to set up a tunnnel(tun) interface. Until OpenSSH allows non-root users to do so (such as: http://marc.info/?l=openssh-unix-dev&m=115651728700190&w=2) we will have to do it this way.

OpenSSH also has a few features to allow for easily tearing down an SSH connection without having to track all sorts of PIDs and such. I use the control connection feature to do so. See the SSH man page for these switches: -M -O -S. This allows one to use the ifup and ifdown commands to easily control the SSH VPN.

Scenario

In this recipe two machines will be configured:

  • A server which is a firewall and has access to a private network ¹
  • A client which initiates the connections to the server and gains direct access to the private network
 --------         /\_/-\/\/-\       -----------------     
| Client |~~~~~~~/ Internet /~~~~~~| Server/Firewall |~~~[ private net ]
 --------        \_/-\/\_/\/      / ----------------- \            
    ||\                           \          ||\       \
    || {tun0}                      {eth0}    || {tun0}  {eth1}
    ||                                       ||
    \-================= tunnel ==============-/ 

For this recipe lets number things like this:

  • the private net is 10.99.99.0/24
  • eth0 on the server has public IP 5.6.7.8
  • eth1 on the server has private IP 10.99.99.1
  • the VPN network is 10.254.254.0/30
  • tun0 on the server has private IP 10.254.254.1
  • tun0 on the client has private IP 10.254.254.2

On the Client

If you do not already have them, generate an SSH keypair for root:

$ sudo ssh-keygen -t rsa

/etc/network/interfaces: Add this stanza to the file:

iface tun0 inet static
      pre-up ssh -S /var/run/ssh-myvpn-tunnel-control -M -f -w 0:0 5.6.7.8 true
      pre-up sleep 5
      address 10.254.254.2
      pointopoint 10.254.254.1
      netmask 255.255.255.252
      up route add -net 10.99.99.0 netmask 255.255.255.0 gw 10.254.254.1 tun0
      post-down ssh -S /var/run/ssh-myvpn-tunnel-control -O exit 5.6.7.8

The first time we connect to the server as root we may need to acknowledge saving the servers SSH key fingerprint:

$ sudo ssh 5.6.7.8
The authenticity of host '5.6.7.8 (5.6.7.8)' can't be established.
RSA key fingerprint is aa:fe:a0:38:7d:11:78:60:01:b0:80:78:90:ab:6a:d2.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '5.6.7.8' (RSA) to the list of known hosts.

Don't bother logging in, just hit CTRL-C.

On the server

/etc/ssh/sshd_config: Add/modify the two keywords to have the same values as below.

PermitTunnel point-to-point
PermitRootLogin forced-commands-only

The PermitRootLogin line is changed from the default of no. You do restrict root SSH login, right?

/root/.ssh/authorized_keys: Add the following line.

tunnel="0",command="/sbin/ifdown tun0;/sbin/ifup tun0" ssh-rsa AAAA ..snipped.. == root@server

Replace everything starting with "ssh-rsa" with the contents of root's public SSH key from the client(/root/.ssh/id_rsa.pub on the client).

/etc/network/interfaces: Add the following stanza.

iface tun0 inet static
      address 10.254.254.1
      netmask 255.255.255.252
      pointopoint 10.254.254.2

/etc/sysctl.conf: Make sure net.ipv4.conf.default.forwarding is set to 1

net.ipv4.conf.default.forwarding=1

This will take effect upon the next reboot so make it active now:

$ sudo sysctl net.ipv4.conf.default.forwarding=1

Using the VPN

user@client:~$ sudo ifup tun0
RTNETLINK answers: File exists
run-parts: /etc/network/if-up.d/avahi-autoipd exited with return code 2

user@client:~$ ping -c 2 10.99.99.1
PING 10.99.99.1 (10.99.99.1) 56(84) bytes of data.
64 bytes from 10.99.99.1 icmp_seq=1 ttl=64 time=96.3 ms
64 bytes from 10.99.99.1 icmp_seq=2 ttl=64 time=94.9 ms

--- 10.99.99.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 94.954/95.670/96.387/0.780 ms
user@client:~$ sudo ifdown tun0
Exit request sent.

You may get the two errors after running ifup. No problem, they are harmless.

Things to watch for

  • Client side VPNs. Firewalls such as Firestarter will block all traffic to and from any tun interface. You will need to modify the scripts in /etc/firestarter to get around this.

Next steps

Once you have this running it is fairly easy to route traffic between two networks on each end of the VPN. See the first reference link below for details.

Possible improvements

  • Something to monitor and restart the VPN if it fails, such as autossh: http://packages.debian.org/stable/net/autossh
  • Automatic starting of the VPN upon first packet from client destined for the remote private network.
  • Use of a remote DNS server by client when VPN is active.

References


1) The server can be behind a firewall, but this requires some additional configuration of the firewall. Primarily, the firewall must forward some port to port 22 on the server. The firewall will need to also know how to route traffic destined for the VPN to the server.

 

 


Posted by drgraefy (128.59.xx.xx) on Mon 2 Jul 2007 at 18:31
[ Send Message | View Weblogs ]
Thank you so much for writing this up. So timely for me. I have been thinking about how to set this up for a couple of different reasons. I can't wait to try it.

[ Parent | Reply to this comment ]

Posted by wuzzeb (64.5.xx.xx) on Mon 2 Jul 2007 at 23:15
[ Send Message ]
If you don't want to run the server as root, you could probably use tunctl in the /etc/network/interface script on the server.

apt-get install uml-utilities

Something like


iface tun-ssh- inet static
pre-up tunctl -u -t tun-ssh-
address 10.254.254.1
netmask 255.255.255.252
pointopoint 10.254.254.2
post-down tunctl -d tun-ssh-


or if you want to use tun0...


iface tun0 inet static
pre-up tunctl -u -t tun0
address 10.254.254.1
netmask 255.255.255.252
pointopoint 10.254.254.2
post-down tunctl -d tun0

[ Parent | Reply to this comment ]

Posted by Anonymous (207.61.xx.xx) on Tue 3 Jul 2007 at 19:47
What are the advantages of this OpenSSH VPN method over using OpenVPN? OpenVPN is easy to setup in PSK mode, and with the wrapper scripts certificate mode isn't much harder. OpenVPN also supports IP over UDP or TCP. UDP is usually the better choice. OpenVPN can easily be configured for extruded intranet mode where all clear text Internet traffic passes through the VPN first. The config for the OpenSSH VPN is messy in comparison.

I am trying to imagine a scenario when an OpenSSH VPN would be better then an OpenVPN tunnel. The only one I can think of is when the outbound firewall has been viciously locked down to allow port 22 only. In that case I would be tempted to just run OpenVPN on port 22.

[ Parent | Reply to this comment ]

Posted by Anonymous (209.217.xx.xx) on Fri 20 Jul 2007 at 17:08
Agreed. There is another scenario, where the server is located behind a firewall that only allows incoming port 22, and you need to run SSH on that port for other reasons.

Besides, this capability is not exactly new. 9 years ago, if you had network connectivity between two machines, SSH, and something that converts a stream of IP packets into a stream of bytes (e.g. slip or PPP), you could build a clumsy, slow VPN out of it. Today, the "convert a stream of IP packets into a stream of bytes" part of that process is done at the packet level with a tunnel interface, support is built into the SSH binary, and it can be activated with a bunch of non-backward-compatible configuration directives and command line options.

Half of the problem with VPNs over TCP is that a TCP connection may delay packets arbitrarily long times, while a UDP or other packet-based transport generally loses the packets instead of delaying them. The other half of the problem is that TCP peers literally hurl packets down the pipe until they start getting lost, then observing which packets are lost and when to derive an estimate for bandwidth availability. Since a TCP-based VPN never drops a packet until it is completely overwhelmed, and a TCP peer communicating through the VPN is trying to optimize its performance by watching for dropped packets, a TCP-based VPN with TCP peers on both sides therefore always tends toward being overwhelmed with traffic. This problem can be mitigated by using traffic control to artificially limit outgoing bandwidth through the VPN, but this only works at all if you limit the bandwidth to significantly less than what is actually available.

There are other problems too, e.g. a lost packet on the VPN carrier generally causes a delay which causes the TCP peers sending data through the VPN to believe their packets were lost, so they retransmit their packets. But since the VPN is running over TCP, the original packets are not lost, only delayed and retransmitted by the VPN's TCP stack, so the data in question is actually transmitted *three* times over the network (once lost, once retransmitted by the VPN, and once retransmitted by each TCP peer over the VPN). If there are multiple TCP connections over the VPN then this problem is multiplied by the number of connections. On slow carrier networks the extra retransmissions can cause more delays, and therefore more retransmits, so the problem cascades until the VPN is fully saturated by its own overhead (i.e. available bandwidth for user packets is zero).

OpenVPN supports TCP (and HTTP) as a carrier, but it really does so only as a means of last resort. If you still can't use OpenVPN, then OpenSSH provides another level of last resort. If you can't use a version of OpenSSH new enough to provide the VPN feature, then you can tunnel OpenVPN over OpenSSH too. ;-)

[ Parent | Reply to this comment ]

Posted by Anonymous (213.207.xx.xx) on Sat 29 Sep 2007 at 12:17
My friend's (who knows more than me about these things) reply:

The only problem is that UDP doesn't get past 90% of hotel networks... So yes, TCP has disadvantages but being stateful it is better handled by badly-configured hotel firewalls...

[ Parent | Reply to this comment ]

Posted by Anonymous (88.200.xx.xx) on Thu 5 Jul 2007 at 07:24
Hey, it is well-known that tunnelimng TCP into SSH tunnel is a bad idea because of the TCP float-frame algorithm that causes the "normal" TCP packets size the SSH uses to send/receive less and less as the time goes by. So the TCP overhead becomes more and more up to reconnection and again. This is unavoidable by IP design.
But this is not the case of openvpn tunneling. Think about it.

[ Parent | Reply to this comment ]

Posted by Anonymous (74.130.xx.xx) on Tue 17 Jul 2007 at 04:19
I've set this up. My server only has one ethernet connection. eth1.

I can connect the tunnel, and ping eth1 to and from each machine. I am having trouble pinging a different server on the servers local network from my client.

Any help? is this a packet forwarding problem?

[ Parent | Reply to this comment ]

Posted by Anonymous (74.130.xx.xx) on Tue 17 Jul 2007 at 05:08
alright. so far, i have this vpn connected. my server is behind a DSL router and obviously port 22 is forwarded correctly. I can ping (from here, the client) my servers local ip address (192.168.1.58) and it's pointopoint ip address (10.254.254.1) but I can't ping anything else on my office network, like 192.168.1.200

[ Parent | Reply to this comment ]

Posted by emeitner (216.153.xx.xx) on Tue 17 Jul 2007 at 16:54
[ Send Message | View emeitner's Scratchpad | View Weblogs ]
Running
 cat /proc/sys/net/ipv4/ip_forward 
should return 1. If not you need to make sure net.ipv4.conf.default.forwarding was set properly. Run
ip route list
on your client. You should see an entry like:
192.168.1.0/24 via 10.254.254.1 dev tun0

[ Parent | Reply to this comment ]

Posted by eperede (82.131.xx.xx) on Sun 12 Aug 2007 at 09:55
[ Send Message ]
Thank you for this howto. If you made all like here described, it works great, except one thing. If the network connection under the openssh tunnel breaks down (example: DSL periodically break), you cannot bring the tunnel again up.

# ifup tun0
channel 1: open failed: administratively prohibited: open failed
/sbin/ifdown: interface tun0 not configured
SIOCSIFADDR: No such device
tun0: ERROR while getting interface flags: No such device
SIOCSIFNETMASK: No such device
ââ‚&Acir c;¬Ã‚¦ (A lot of setup fail.)
Failed to bring up tun0

I have tested it in Debian Etch. I removed tun0 interface manually on both side but it didn't help. I have to restart the server if I want to establish a new connection.
Is it a bug?
How could I establish a new connection without a restart of the server?

Thanks for the answer(s)!

[ Parent | Reply to this comment ]

Posted by emeitner (69.129.xx.xx) on Sun 12 Aug 2007 at 13:07
[ Send Message | View emeitner's Scratchpad | View Weblogs ]
It may be that a firewall that sits between the two machines is dropping idle TCP connections. Try adding
-oServerAliveInterval=60
To the ssh command in the pre-up stanza in /etc/network/interfaces on the client.

[ Parent | Reply to this comment ]

Posted by eperede (82.77.xx.xx) on Sun 12 Aug 2007 at 19:00
[ Send Message ]
I think, it's not a solution for my problem.

Your solution is good, if the VPN connection alone breaks down.

My problem is the following:
OpenSSH VPN connection works upon an ADSL connection. If the ADSL goes down (it's normally every day) without turning VPN off (without ifdown tun0), then I cannot bring up the VPN connection again.
How could I reconnect without restarting the server?

Thanks for the answer(s).

[ Parent | Reply to this comment ]

Posted by Anonymous (217.12.xx.xx) on Mon 27 Aug 2007 at 23:37
Sorry for not answering your question, I just want to say how much I *love* OpenVPN because it can handle this situation :)
I have OpenVPN tunnel from my laptop to a server, NFS mounted disk over it and I play some music from that disk.. Sometimes the wireless access point in my flat freezes, the first sign is that the music stops playing :) I manually restart the AP and when it comes back online, the music just starts playing again, without any OpenVPN restart or NFS remount :)

[ Parent | Reply to this comment ]

Posted by Anonymous (203.6.xx.xx) on Thu 13 Sep 2007 at 07:13
You may want to consider using a specific sudo setup to allow a non-root user to set up the tun interface. That way you steer clear of allowing root logins at all over SSH

[ Parent | Reply to this comment ]

Posted by jaume (81.248.xx.xx) on Tue 30 Oct 2007 at 03:16
[ Send Message ]
Regarding the use of allowing ssh root logins, I think you can make a special user, say ssh-vpn, add it to the sudoers file allowing only to up/down the tun interface, and then adding "sudo ifup tun" to the authorized keys. I have used this sort of trick to do other things (performing a centralised backup) with root authority without enabling root ssh logins.
Nice article!

[ Parent | Reply to this comment ]

Posted by emeitner (216.153.xx.xx) on Tue 30 Oct 2007 at 17:21
[ Send Message | View emeitner's Scratchpad | View Weblogs ]
Yes I did try that. It appears that the tun interface is set up before the user has the ability to run any commands. So unless the user has root access from the start, bringing up the tun interface fails.

[ Parent | Reply to this comment ]

Posted by Anonymous (131.215.xx.xx) on Wed 5 Dec 2007 at 21:42
Can this be ported to Windows?

[ Parent | Reply to this comment ]

Posted by Anonymous (220.233.xx.xx) on Sat 11 Apr 2009 at 08:36
Awesome guide. This has helped me create a remote access connection to my work server where I have no control over the internet end connections.

[ Parent | Reply to this comment ]

Posted by Anonymous (99.51.xx.xx) on Mon 10 Aug 2009 at 09:36
Remember to cd /dev/ && MAKEDEV tun ... as root on (both ends) of the connection. Debian and Ubuntu seem to default to omitting this device node. (ls /dev/net to see if their is a tap0 or tun device node thereunder).

Also note that MAKEDEV (at least one version I used) must be run from the /dev/ directory ... or it will make the directory and device node under wherever you happen to be.

It also seems that the best place to get the tunctl command in recent versions of Debian and Ubuntu is from the uml-utilities package?

[ Parent | Reply to this comment ]

Posted by Anonymous (77.164.xx.xx) on Thu 24 Sep 2009 at 13:59
Good guide, and the only one I found that makes explicit that root permissions I needed. That explained a lot of problems I've had!

Being root at both ends finally got it working for me. Then I tried lowering privileges, which failed _until_ I made /dev/net/tun accessible to me. So a bit of playing around with chmod/chgrp seems to resolve having to be root for setting up a tunnel.

[ Parent | Reply to this comment ]

Posted by Anonymous (80.108.xx.xx) on Fri 6 Nov 2009 at 22:46
Hi, I have the same problem, but just lowering the privs for /dev/net/tun is not sufficient. Du you have some more hints, where to tickle privs.

Thanks

[ Parent | Reply to this comment ]

Posted by Anonymous (190.136.xx.xx) on Wed 24 Mar 2010 at 20:27
Hey i had the same problem w/my ubuntu book and my ubuntu server, anyway i came up with a script that automates everything, and doesn't need /etc/network/interfaces. I'm pasting it everywhere now because this ssh techique is extremely useful, though dismissed as for real use. I can use my sip extension (udp) over this and every other nastiness involving L2 tunnels. here it goes:

#!/bin/bash

# prereqs:
# remote host's sshd_config must have "PermitRootLogin=no", "AllowUsers user", and "PermitTunnel=yes"
# "tunctl", in debians it is found in uml-utils, redhats another (dont remember but "yum provides tunctl" must tell)
# remote user must be able to sudo-as-root
# can opt by routing as in this case or soft bridge with brctl and you get full remote ethernet segment membership :D
# that last i think i'll implement later as an option
# other stuff to do is error checking, etcetc, this is just as came from the oven

userhost='user@host'
sshflags='-Ap 2020 -i /path/to/some/authkey'
vpn='10.0.0.0/24'
rnet=192.168.40.0/24

# START VPN
if [ "$1" == "start" ]; then
echo setting up local tap ...
ltap=$(tunctl -b)
ifconfig $ltap ${vpn%%?/*}2/${vpn##*/} up

echo setting remote configuration and enabling root login ...
rtap="ssh $sshflags $userhost sudo 'bash -c \"rtap=\\\$(tunctl -b); echo \\\$rtap; ifconfig \\\$rtap ${vpn%%?/*}1/${vpn##*/} up; iptables -A FORWARD -i \\\$rtap -j ACCEPT; iptables -A FORWARD -o \\\$rtap -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -s ${vpn%%?/*}2 -j SNAT --to \\\$(ip r | grep $rnet | sed \\\"s/^.*src \\\(.*\\\$\\\)/\1/g\\\"); sed -i -e \\\"s/\\\(PermitRootLogin\\\).*\\\$/\1 without-password/g\\\" -e \\\"s/\\\(AllowUsers.*\\\)\\\$/\1 root/g\\\" /etc/ssh/sshd_config; /usr/sbin/sshd -t\"'"
rtap=$(sh -c "$rtap")

echo setting up local routes ...
# since my ISP sucks with transparent filters (i can't opt for another where i live), i'll just use my work net as gateway
ip r a $(ip r | grep default | sed "s/default/${userhost##*@}/")
ip r c default via ${vpn%%?/*}1 dev $ltap

echo bringing up the tunnel and disabling root login ...
ssh $sshflags -f -w ${ltap##tap}:${rtap##tap} -o Tunnel=ethernet -o ControlMaster=yes -o ControlPath=/root/.ssh/vpn-$userhost-l$ltap-r$rtap root@${userhost##*@} bash -c "\"sed -i -e 's/\(PermitRootLogin\).*\$/\1 no/g' -e 's/\(AllowUsers.*\) root\$/\1/g' /etc/ssh/sshd_config; /usr/sbin/sshd -t\""

echo connected.

# STOP VPN
elif [ "$1" == "stop" ]; then
echo searching control socket and determining configuration ...
controlpath=$(echo /root/.ssh/vpn-$userhost*)
ltap=${controlpath%%-rtap*} && ltap=tap${ltap##*-ltap}
rtap=${controlpath##*rtap} && rtap=tap${rtap%%-*}

echo bringing the tunnel down ...
ssh $sshflags -o ControlPath=$controlpath -O exit $userhost

echo restoring local routes ...
ip r c default $(ip r | grep ${userhost##*@} | sed "s/${userhost##*@}\(.*$\)/\1/g")
ip r d ${userhost##*@}

echo restoring remote configuration ...
sh -c "ssh $sshflags $userhost sudo 'bash -c \"tunctl -d $rtap; iptables -D FORWARD -i $rtap -j ACCEPT; iptables -D FORWARD -o $rtap -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -s ${vpn%%?/*}2 -j SNAT --to \$(ip r | grep $rnet | sed \"s/^.*src \(.*\$\)/\1/g\")\"'"

echo deleting local tap ...
tunctl -d $ltap

echo disconnected.
fi

[ Parent | Reply to this comment ]

Posted by Anonymous (91.121.xx.xx) on Tue 8 Feb 2011 at 11:12
I followed every step, and here is the error message when I try to do the ifup tun0 on the client:
nautilus:/var/run# ifup tun0
channel 0: open failed: administratively prohibited: open failed
/sbin/ifdown: interface tun0 not configured
SIOCSIFADDR: No such device
tun0: ERROR while getting interface flags: No such device
SIOCSIFNETMASK: No such device
Failed to bring up tun0.
tun0: ERROR while getting interface flags: No such device
SIOCSIFADDR: No such device
tun0: ERROR while getting interface flags: No such device
SIOCSIFNETMASK: No such device
tun0: ERROR while getting interface flags: No such device
Failed to bring up tun0.

I noticed that when I try a simple ssh connection from the client to the server, here is what I get this error:
/sbin/ifdown: interface tun0 not configured
SIOCSIFADDR: No such device
tun0: ERROR while getting interface flags: No such device
SIOCSIFNETMASK: No such device
tun0: ERROR while getting interface flags: No such device
Failed to bring up tun0.
Connection to cachalot.xxx.xxx closed.

any clues?

[ Parent | Reply to this comment ]

Sign In

Username:

Password:

[Register|Advanced]

 

Flattr

 

Current Poll

Which init system are you using in Debian?






( 1643 votes ~ 7 comments )