How to set up a dynamic BGP IPsec VPN tunnel with strongSwan on Linux

Linux

This guide, coming from Google, explains how to use a dynamic IPsec VPN tunnel with strongSwan on Linux.

Step 1: Configure BIRD

/etc/bird/bird.conf

# Config example for bird 1.6 
#debug protocols all;

router id
169.254.2.2;

# Watch interface up/down events
protocol device
{
scan time
10;
}

# Import interface routes (Connected)
# (Not required in this example as kernel import all is used here to workaround the /32 on eth0 GCE VM setup)
#protocol direct {
# interface "*";
#}

# Sync routes to kernel
protocol kernel
{
learn
;
merge paths on
; # For ECMP
export filter {
krt_prefsrc
= 10.164.0.6; # Internal IP Address of the strongSwan VM.
accept
; # Sync all routes to kernel
};
import all; # Required due to /32 on GCE VMs for the static route below
}

# Configure a static route to make sure route exists
protocol
static {
# Network connected to eth0
route
10.164.0.0/20 recursive 10.164.0.1; # Network connected to eth0
# Or blackhole the aggregate
# route 10.164.0.0/20 blackhole;
}

# Prefix lists for routing security
# (Accept /24 as the most specific route)
define GCP_VPC_A_PREFIXES
= [ 192.168.0.0/16{16,24} ]; # VPC A address space
define LOCAL_PREFIXES
= [ 10.164.0.0/16{16,24} ]; # Local address space

# Filter received prefixes
filter gcp_vpc_a_in
{
if (net ~ GCP_VPC_A_PREFIXES) then accept;
else reject;
}

# Filter advertised prefixes
filter gcp_vpc_a_out
{
if (net ~ LOCAL_PREFIXES) then accept;
else reject;
}

template bgp gcp_vpc_a {
keepalive time
20;
hold time
60;
graceful restart aware
; # Cloud Router uses GR during maintenance
#multihop 3; # Required for Dedicated/Partner Interconnect

import filter gcp_vpc_a_in;
import limit 10 action warn; # restart | block | disable

export filter gcp_vpc_a_out;
export limit 10 action warn; # restart | block | disable
}

protocol bgp gcp_vpc_a_tun1
from gcp_vpc_a
{
local 169.254.2.2 as 65002;
neighbor
169.254.2.1 as 65000;
}

Step 2: Disable automatic routes in strongSwan

Routes are handled by BIRD, so you must disable automatic route creation in strongSwan.

/etc/strongswan.d/vti.conf

charon {
# We will handle routes by ourselves
install_routes
= no
}

Step 3: Create a script that will configure the VTI interface

This script is called every time a new tunnel is established, and it takes care of proper interface configuration, including MTU, etc.

/var/lib/strongswan/ipsec-vti.sh

#!/bin/bash
set -o nounset
set -o errexit

IP
=$(which ip)

PLUTO_MARK_OUT_ARR
=(${PLUTO_MARK_OUT//// })
PLUTO_MARK_IN_ARR
=(${PLUTO_MARK_IN//// })

VTI_TUNNEL_ID
=${1}
VTI_REMOTE
=${2}
VTI_LOCAL
=${3}

LOCAL_IF
="${PLUTO_INTERFACE}"
VTI_IF
="vti${VTI_TUNNEL_ID}"
# GCP's MTU is 1460, so it's hardcoded
GCP_MTU
="1460"
# ipsec overhead is 73 bytes, we need to compute new mtu.
VTI_MTU
=$((GCP_MTU-73))

case "${PLUTO_VERB}" in
up
-client)
$
{IP} link add ${VTI_IF} type vti local ${PLUTO_ME} remote ${PLUTO_PEER} okey ${PLUTO_MARK_OUT_ARR[0]} ikey ${PLUTO_MARK_IN_ARR[0]}
$
{IP} addr add ${VTI_LOCAL} remote ${VTI_REMOTE} dev "${VTI_IF}"
$
{IP} link set ${VTI_IF} up mtu ${VTI_MTU}

# Disable IPSEC Policy
sysctl
-w net.ipv4.conf.${VTI_IF}.disable_policy=1

# Enable loosy source validation, if possible. Otherwise disable validation.
sysctl
-w net.ipv4.conf.${VTI_IF}.rp_filter=2 || sysctl -w net.ipv4.conf.${VTI_IF}.rp_filter=0

# If you would like to use VTI for policy-based you shoud take care of routing by yourselv, e.x.
#if [[ "${PLUTO_PEER_CLIENT}" != "0.0.0.0/0" ]]; then
# ${IP} r add "${PLUTO_PEER_CLIENT}" dev "${VTI_IF}"
#fi
;;
down
-client)
$
{IP} tunnel del "${VTI_IF}"
;;
esac

# Enable IPv4 forwarding
sysctl
-w net.ipv4.ip_forward=1

# Disable IPSEC Encryption on local net
sysctl
-w net.ipv4.conf.${LOCAL_IF}.disable_xfrm=1
sysctl
-w net.ipv4.conf.${LOCAL_IF}.disable_policy=1

You should also make /var/lib/strongswan/ipsec-vti.sh executable by using the following command:

chmod +x /var/lib/strongswan/ipsec-vti.sh

Step 4: Configure IPsec credentials

Ensure that the following line is in the file:

/var/lib/strongswan/ipsec.secrets.inc

35.204.151.163 : PSK "secret"

Step 5: Configure IPsec connection

/var/lib/strongswan/ipsec.conf.inc

include /etc/ipsec.d/gcp.conf

/etc/ipsec.d/gcp.conf

conn %default
ikelifetime
=600m # 36,000 s
keylife
=180m # 10,800 s
rekeymargin
=3m
keyingtries
=3
keyexchange
=ikev2
mobike
=no
ike
=aes256gcm16-sha512-modp4096
esp
=aes256gcm16-sha512-modp8192
authby
=psk

conn net
-net
leftupdown
="/var/lib/strongswan/ipsec-vti.sh 0 169.254.2.1/30 169.254.2.2/30"
left
=35.204.200.153 # In case of NAT set to internal IP, e.x. 10.164.0.6
leftid
=35.204.200.153
leftsubnet
=0.0.0.0/0
leftauth
=psk
right
=35.204.151.163
rightid
=35.204.151.163
rightsubnet
=0.0.0.0/0
rightauth
=psk
type
=tunnel
# auto=add - means strongSwan won't try to initiate it
# auto=start - means strongSwan will try to establish connection as well
# Note that Google Cloud will also try to initiate the connection
auto=start
# dpdaction=restart - means strongSwan will try to reconnect if Dead Peer Detection spots
# a problem. Change to 'clear' if needed
dpdaction
=restart
# mark=%unique - We use this to mark VPN-related packets with iptables
# %unique