Routing-based VPN with StrongSwan

This blog describes the setup of a route-based VPN with strongSwan. Of course there are many tutorials available. The best one, of course, is from the strongswan project itself. But since I want to document the combined setup of IPsec VPN together with BGP dynamic routing I start with the VPN part for the sake of completeness.

Route-based vs. Policy-based VPN

Standard installations of IPsec VPNs in Linux use the kernel policying to encrypt packages to the destination. After successful IKE negotiation the ipsec service (charon in the strongSwan project) installs a policy that tells the kernel to use encryption if the packet matches the security association (SA). Of course, the source IP and the destination IP is included in such a SA. You can display the policy with a 'ip xfrm policy show':

# ip xfrm policy show
src 192.0.2.0/28 dst 192.0.2.16/28
    dir out priority 2851
    tmpl src 198.51.100.1 dst 198.51.100.17
        proto esp reqid 1 mode tunnel
src 192.0.2.16/28 dst 192.0.2.0/28
    dir in priority 2851
    tmpl src 198.51.100.17 dst 198.51.100.1
        proto esp reqid 1 mode tunnel

Please note the /28 network masks in the example above.

Lets' assume the following setup:

 +-------+     +----------+          +----------+     +-------+
 | Net A | --- | Router A | <------> | Router B | --- | Net B |
 +-------+     +----------+          +----------+     +-------+
                    ^                    ^
                    |    +----------+    |
                    +--> | Router C | <--+
                         +----------+
                              |
                              |
                          +-------+
                          | Net C |
                          +-------+

The policy on router A tells the kernel to use one SA to route packets to network B and another SA to route to packets to network C. If the link between A and B fails the SA agreed with router C does not change. There is not way to send packets form A to B although the route via C would be possible. The SA between A and C just does not match the destination network B.

The options would be to renegotiate the VPN to C to include the network behind B. A very pointless solution. The next option would be to use GRE tunnel between the routers. That is how dynamic routing was combined with VPN the old way. The disadvantage of this solution is the both partners have to use GRE as an additional layer. New protocol, new complexity.

The optimal solution is to set up a route-based VPN instead of the policy based setup. Basically you set up a virtual tunnel interface (VTI) as an additional layer between the kernel an the real interface. The VTI includes the encryption. The routing is changed to the interface. The SA can be agreed upon 0.0.0.0/0. The trafic selection takes place by the routing. Router A only routes the traffic behind router B through the VTI towards router B.

In the following setup I will use the RFC 5737 IPv4 addresses for documentation. To make the setup a bit more realistic every router (A, B, and C) is connected to the internet. Of course not the real one, but my lab internet. So every router has its own default route via interface 'eth0'.

The second interface 'eth1' leads to the local network. So the kernel routing table of the router A looks like

# ip route list
default via 198.51.100.1/28 dev eth0
198.51.100.0/28 dev eth0 proto kernel scope link src 198.51.100.14
192.0.2.0/28 dev eth1 proto kernel scope link src 192.0.2.1

Site A uses the network 198.51.100.0/28 for transportation and 192.0.2.0/28 for the local network. The table below shows the networks used:

Site Transport Net Local Net

A

198.51.100.0/28

192.0.2.0/28

B

198.51.100.16/28

192.0.2.16/28

C

198.51.100.32/28

192.0.2.32/28

Virtual Tunnel Interface Setup

Let’s start with the VPN setup. First you have to set up the tunnel interface. On router A we add two 'vti' interfaces to tunnel traffic to B and C:

# ip tunnel add vti0 local 198.51.100.14 remote 198.51.100.30 mode vti key 12
# ip link set up dev vti0
# ip tunnel add vti1 local 198.51.100.14 remote 198.51.100.46 mode vti key 13
# ip link set up dev vti1

Please note the keys the indicate the kernel which traffic has to be routed through this interface. We will use this key in the VPN setup afterwards. If you use Debian you can create the 'vti' interfaces in the system network configuration /etc/network/interfaces:

auto vti0
iface vti0 inet manual
  pre-up ip tunnel add vti0 local 198.51.100.14 remote 198.51.100.30 mode vti key 12
  post-down ip tunnel del vti0

auto vti1
iface vti1 inet manual
  pre-up ip tunnel add vti1 local 198.51.100.14 remote 198.51.100.46 mode vti key 13
  post-down ip tunnel del vti1

VPN Setup

The VPN is configured as usual with strongSwan. The only additional option 'mark' tells the VPN to use the key configured with the interfaces to divert the traffic through the tunnel interface. In the following section I will only show the configuration in /etc/ipsec.conf of the tunnel between A and B on router A:

conn A-B
  left=198.51.100.14
  leftsubnet=0.0.0.0/0
  leftid="routerA"
  right=198.51.100.30
  rightsubnet=0.0.0.0/0
  rightid="routerB"
  auto=start
  authby=secret
  keyexchange=ikev2
  mark=12

and, of course, the configuration of the preshared secret in /etc/ipsec.secrets:

routerA routerB : PSK "supersecret"

Additional Setup

Now only two additional changes to the default configuration have to be added. The kernel does a policy lookup. But with route based VPNs we don’t need the policy lookup any more. So we can disable the policy lookup of the kernel for our vti interfaces by adding the following lines to the sysctl.conf:

net.ipv4.conf.vti0.disable_policy=1
net.ipv4.conf.vti1.disable_policy=1

In strongSwan the IKE daemon also takes care of the routing. Since we do want to control the routing ourselves, we have to disable this feature in the service. The option can be found in the main section of the charon configuation file /etc/strongswan.d/charon.conf:

charon {
  install_routes = no
}

Routing

The last step is the routing. On router A the traffic for the network B has to be routed via interface vti0. The command

# ip route add 192.0.2.16/28 dev vti0

does the trick. First we add the routes between A, B, and C manually. Later on a routing service (BGP) will do this for us.

Now the network A can talk to network B. On the routers between A and B you only can see encrypted ESP packets.

Michael Schwartzkopff, 17.11.2018