L2TP/IPSec VPN client on Slackware

29 December 2016
Following on from the article on L2TP/IPSec VPN server setup, here are instructions for setting up a corresponding Linux VPN client. A Slackware system is assumed because Slackware is now my preferred distribution, although the same instructions should work with any distribution. I had intended to also include some commentary on my choice of L2TP/IPSec, but have decided to leave that for a future article.

Choice of IPSec implementation

Linux VPN software seems to be one of those areas that has been plagued by multiple politicially-motivated forks. FreeS/WAN forked into OpenSwan and strongSwan, and the former then forked to LibreSwan. My first practical use of VPN used OpenSwan because that was what the spare server had installed, but OpenSwan seems to have fallen out of favour. To help muddy the water distributions decided to switch from OpenSwan to strongSwan rather than LibreSwan, which is a bit irritating because major changes to both command-line and configuration options mean that is is far from a drop-in replacement.

Pre-shared key client setup

This is the client-side compliment of my L2TP/IPSec server setup. Likewise the content of configuration files that need changing from a clean install are shown. Replace xxx.xxx.xxx.xxx with your VPS's IP address.

/etc/ipsec.conf

config setup conn VPS keyexchange = ikev1 authby=secret auto=add keyingtries=3 dpddelay=30 dpdtimeout=120 dpdaction=clear rekey=yes ikelifetime=8h keylife=1h type=transport left=%defaultroute leftprotoport=17/1701 right=xxx.xxx.xxx.xxx rightprotoport=17/1701

/etc/xl2tpd/xl2tpd.conf

[global] debug state = no debug tunnel = no debug avp = no debug network = no debug packet = no [lac vps] lns = vps.somewhere.net ppp debug = no pppoptfile = /etc/ppp/options.client length bit = yes require chap = yes refuse pap = yes require authentication = yes name = l2tpd

/etc/ppp/options.client

ipcp-accept-local ipcp-accept-remote noccp noauth mtu 1410 mru 1410 nodefaultroute usepeerdns lock name laptop password magick

Predictably the values for name and password will need to be changed to match the server. Having defaultroute seems ineffective when a system already has a network connection, so I've opted to have nodefaultroute which only sets up a route to the VPN over the L2TP/IPSec tunnel.

/etc/ipsec.secrets

: PSK "sekret"

Note: Change to something better, such as outout of “openssl rand -base64 32”. Form those who prefer certificate-based authentication over pre-shared keys, this is the compliment of the server-side certificate setup. It is assumed that all the keys & certificates have been generated as part of the former setup, so first thing to do is copy the ones required by the client into place:

cp caCert /etc/ipsec.d/cacerts/ cp clientKey /etc/ipsec.d/private/client.key cp clientCert /etc/ipsec.d/certs/client.crt cp serverCert /etc/ipsec.d/certs/server.crt

Once these are in place, the changes to the strongSwan configuration files required with respect to a pre-shared key setup are highlighted in bold below. I suspect the configuration has more elaboration than is actually required.

/etc/ipsec.conf

config setup conn VPS keyexchange = ikev1 authby=rsasig auto=add keyingtries=3 dpddelay=30 dpdtimeout=120 dpdaction=clear rekey=yes ikelifetime=8h keylife=1h type=transport left=%defaultroute leftprotoport=17/1701 leftcert=client.crt leftid="C=UK, OU=VPN, O=MyNET, CN=S4" right=xxx.xxx.xxx.xxx rightprotoport=17/1701 rightrsasigkey=%cert rightid="C=UK, OU=VPN, O=MyNET, CN=VPN"

/etc/ipsec.secrets

: RSA client.key

Opening up a VPN connection

Here a root shell is assumed, although in production it is expected that these commands would be put into a suid root script. Idea is to show how it can be done manually to get an understanding of what is going on.

Start strongSwan

# ipsec start Starting strongSwan 5.3.4 IPsec [starter]...

Establish IPSec Security Association

This will print out a screen or two worth of output, but all you are really interested in is the last line:

# ipsec up VPS ... parsed QUICK_MODE response 3270260035 [ HASH SA No ID ID NAT-OA NAT-OA ] connection 'VPS' established successfully

Start xl2tpd

In this case I'm starting xl2tpd in debug mode so that it remains in the foreground and prints log output to stdout.

# xl2tpd -D xl2tpd[xxxxx]: setsockopt recvref[30]: Protocol not available xl2tpd[xxxxx]: Using l2tp kernel support. xl2tpd[xxxxx]: xl2tpd version xl2tpd-1.3.2 started on Pyre PID:24615 xl2tpd[xxxxx]: Written by Mark Spencer, Copyright (C) 1998, Adtran, Inc. xl2tpd[xxxxx]: Forked by Scott Balmos and David Stipp, (C) 2001 xl2tpd[xxxxx]: Inherited by Jeff McAdams, (C) 2002 xl2tpd[xxxxx]: Forked again by Xelerance (www.xelerance.com) (C) 2006 xl2tpd[xxxxx]: Listening on IP address 0.0.0.0, port 1701

Send connect command to xl2tpd

# echo "c vps" > /var/run/xl2tpd/l2tp-control

If the file doesn't exist you'll need to create it using touch before starting xl2tpd. If all went well you should see xl2tpd printing out a lot of extra information:

xl2tpd[xxxxx]: get_call: allocating new tunnel for host xxx.xxx.xxx.xxx, port 1701. xl2tpd[xxxxx]: Connecting to host vps.example.com, port 1701 xl2tpd[xxxxx]: control_finish: message type is (null)(0). Tunnel is 0, call is 0. xl2tpd[xxxxx]: control_finish: sending SCCRQ xl2tpd[xxxxx]: control_finish: message type is Start-Control-Connection-Reply(2). Tunnel is 22479, call is 0. xl2tpd[xxxxx]: control_finish: sending SCCCN xl2tpd[xxxxx]: Connection established to xxx.xxx.xxx.xxx, 1701. Local: 9030, Remote: 22479 (ref=0/0). xl2tpd[xxxxx]: Calling on tunnel 9030 xl2tpd[xxxxx]: control_finish: message type is (null)(0). Tunnel is 22479, call is 0. xl2tpd[xxxxx]: control_finish: sending ICRQ xl2tpd[xxxxx]: control_finish: message type is Incoming-Call-Reply(11). Tunnel is 22479, call is 42196. xl2tpd[xxxxx]: control_finish: Sending ICCN xl2tpd[xxxxx]: Call established with xxx.xxx.xxx.xxx, Local: 61354, Remote: 42196, Serial: 1 (ref=0/0) xl2tpd[xxxxx]: start_pppd: I'm running: xl2tpd[xxxxx]: "/usr/sbin/pppd" xl2tpd[xxxxx]: "passive" xl2tpd[xxxxx]: "nodetach" xl2tpd[xxxxx]: ":" xl2tpd[xxxxx]: "refuse-pap" xl2tpd[xxxxx]: "auth" xl2tpd[xxxxx]: "require-chap" xl2tpd[xxxxx]: "file" xl2tpd[xxxxx]: "/etc/ppp/options.client" xl2tpd[xxxxx]: "plugin" xl2tpd[xxxxx]: "pppol2tp.so" xl2tpd[xxxxx]: "pppol2tp" xl2tpd[xxxxx]: "7"

Thing to look for is all the PPPd stuff toewards the end. If you see it then the L2TP/IPSec tunnel has been successfully established. You should now see a ppp entry in the routing table:

$ /sbin/ip route default via 192.168.0.254 dev wlan0 metric 303 127.0.0.0/8 dev lo scope link 192.168.0.0/24 dev wlan0 proto kernel scope link src 192.168.1.8 metric 303 192.168.128.1 dev ppp0 proto kernel scope link src 192.168.128.50

You should also be able to ping 192.168.128.1 (the VPS on the other side of the VPN connection). At this point you can setup IP routing to send traffic over the VPN tunnel rather than using the local gateway.

Closing the VPN connection

Tearing down the VPN connection is a case of shutting down the various parts in order:
  1. Telling xl2tpd to disconnect the L2TP tunnel
  2. Shutting down xl2tpd
  3. Closing the IPSec Security Association
  4. Shutting down strongSwan
Technically these are not all strictly necessary, but here I'm assuming you want to cold-shutdown all the VON software. All these shutdown stages can be done using the following commands:

echo "d nl" > /var/run/xl2tpd/l2tp-control kill `cat /var/run/xl2tpd.pid` ipsec down VPS ipsec stop

Routing traffic over the VPN connection

Once an L2TP/IPSec tunnel is in place, all that is left to be done is to delete the existing default route to the local gateway and add the VPS network interface as a new default route. The one trick is that traffic to the VPS itself (i.e the L2TP/IPSec connection) needs to be specifically routed to the local gateway using a single-host routing rule. Prior to disconnecting the reverse of this needs to be done. Below are two shell scripts to perform these routing table manipulations (but as usual caveat emptor, and you'll need to edit in your actual VPS IP address):

Script to setup VPN routes

#!/bin/bash VPNIP=xxx.xxx.xxx.xxx ISPGW=`/sbin/ip route list | grep default | tr -s '[:space:]' '\n' | sed -n '3p'` PPPIP=`/sbin/ifconfig ppp0 | grep inet | tr -s '[:space:]' | awk -F" " '{print $2}'` ip route add ${VPNIP} via ${ISPGW} ip route del default via ${ISPGW} ip route add default via ${PPPIP}

Script to revert to non-VPN routes

#!/bin/bash VPNIP=xxx.xxx.xxx.xxx PPPIP=`/sbin/ifconfig ppp0 | grep inet | tr -s '[:space:]' | awk -F" " '{print $2}'` ISPGW=`/sbin/ip route | grep ${VPNIP} | awk -F" " '{print $3}'` ip route del default via ${PPPIP} ip route add default via ${ISPGW} ip route del ${VPNIP}

Gotchas

Lookup mismatches

It seems that IPSec does some sort of reverse-lookup on IP addresses, so you may see log messages such as the following:

IDir 'xxx.xxx.xxx.xxx' does not match to 'vps.example.com'

This ought not be a problem per-se, but I have the strong suspicion it can cause failed IPSec connections. Solution is to use resolved IP addresses rather than FQDN in the IPSec configuration.

Connection problems with multiple clients

When there is more than one L2TP/IPSec client behind the same NAT router, it is quite common to see failures such as the following:

xl2tpd[xxxxx]: Connecting to host xxx.xxx.xxx.xxx, port 1701 xl2tpd[xxxxx]: control_finish: message type is (null)(0). Tunnel is 0, call is 0. xl2tpd[xxxxx]: control_finish: sending SCCRQ xl2tpd[xxxxx]: Maximum retries exceeded for tunnel 49265. Closing. xl2tpd[xxxxx]: Connection 0 closed to xxx.xxx.xxx.xxx, port 1701 (Timeout) xl2tpd[xxxxx]: Unable to deliver closing message for tunnel 49265. Destroying anyway.

The solution is to get clients to use different port numbers. Deleting the leftprotoport and rightprotoport directive from the client's ipsec.conf seems to fix the issue, so guessing that without those directives the strongSwan client is guessing it needs to use different ephemeral ports.

DNS issues

One complicating factor is DNS addresses, as the address of the local DNS server is likely different from the one accessible from the VPS. The easiest thing to do is to setup the local machine to use Google's public DNS servers which are 8.8.8.8and 8.8.4.4, as these will always be useable regardless of which gateway is in use. In fact some VPS providers actually use these themselves rather than running their own DNS proxies.