Connect your Linux machine to a VPN Gateway using strongSwan

In this blog post I’ll show you how to connect your local machine to a remote VPN server using the IKEv2 and IPSec protocol. Instead of the deprecated ipsec.conf we’ll use the modern swanctl.conf.

Why IPSec/IKEv2?

IKEv2 offers high speed and good data security with a stable connection. The protocol is one of the best.

strongSwan provides an open-source implementation of IPSec. strongSwan works on Linux, Android, FrreBSD, macOS, iOs, and Windows.

The tool natively supports forwarding and split-tunneling, thus enabling you to selectively route your traffic through the VPN connection.

Why is that useful?
With split-tunneling you can exclude your local subnets (your home network, or local Docker bridge) from the VPN gateway.
Now you can connect your local machine to the VPN server, but still have access to your wifi-connected printer.

1. Installation

Usually, the Arch wiki is a mine of gold. Unfortunately, the wiki solely describes how to setup a connection with ipsec.conf and ipsec starter.

The newly available swanctl and vici plugin provide a better experience in combination with systemd and strongSwan’s plugins.

In this article, I’ll show you a sample ipsec.conf with pre-shared keys (EAP), and how to migrate the configuration to swanctl.

We’ll assume that you have access to a remote VPN server, either your own implementation or a commercial provider like NordVPN.

We’ll use yay to install strongSwan:

yay -S strongswan

2. Configuration

1. ipsec/swanctl

Example ipsec.conf with username and password (NordVPN uses a different approach, see below):

conn vpn
  keyexchange=ikev2
  dpdaction=clear
  dpddelay=300s
  eap_identity="<your-username>"
  leftauth=eap-mschapv2
  left=%defaultroute
  leftsourceip=%config
  right=<server.name.com>
  rightauth=pubkey
  rightsubnet=0.0.0.0/0
  rightid=%any
  type=tunnel
  auto=add

Here’s how the configuration translates to swanctl.conf (on your machine: /etc/swanctl/swanctl.conf or similar):

connections {
    vpn {
        version = 2
        proposals = aes192gcm16-aes128gcm16-prfsha256-ecp256-ecp521,aes192-sha256-modp3072,default
        rekey_time = 0s
        fragmentation = yes
        dpd_delay = 300s
        local_addrs = %defaultroute
        remote_addrs = <server.name.com>
        vips=0.0.0.0,::
        local {
            auth = eap-mschapv2
            eap_id = "<your-username>"
        }
        remote {
            auth = pubkey
            id = %any
        }
        children {
            vpn {
                remote_ts = 0.0.0.0/0,::/0
                rekey_time = 0s
                dpd_action = clear
                esp_proposals = aes192gcm16-aes128gcm16-prfsha256-ecp256-modp3072,aes192-sha256-ecp256-modp3072,default
            }
        }
    }

secrets {
    eap-vpn {
        id = "<your-username>"
        secret = "<your-password>"
    }
}

Don’t forget to replace the remote_addr with the real server name.
Replace <your-username> and <your-password>, too.

Here’s another example configuration where we use a username and certifictate instead of username/password in the ipsec.conf (NordVPN):

conn NordVPN
  keyexchange=ikev2
  dpdaction=clear
  dpddelay=300s
  eap_identity="<your-username>"
  leftauth=eap-mschapv2
  left=%defaultroute
  leftsourceip=%config
  right=<server.name.com>
  rightauth=pubkey
  rightsubnet=0.0.0.0/0
  rightid=%any
  rightca=/etc/ipsec.d/cacerts/NordVPN.pem
  type=tunnel
  auto=add

For etc/swanctl/swanctl.conf:

connections {
    nordvpn {
        version = 2
        proposals = aes192gcm16-aes128gcm16-prfsha256-ecp256-ecp521,aes192-sha256-modp3072,default
        rekey_time = 0s
        fragmentation = yes
        dpd_delay = 300s
        local_addrs = %defaultroute
        remote_addrs = <server.name.com>
        vips=0.0.0.0,::
        local {
            auth = eap-mschapv2
            eap_id = "<your-username>"
        }
        remote {
            auth = pubkey
            cacerts=/etc/ipsec.d/cacerts/NordVPN.pem
            id = %any
        }
        children {
            nordvpn {
                remote_ts = 0.0.0.0/0,::/0
                rekey_time = 0s
                dpd_action = clear
                esp_proposals = aes192gcm16-aes128gcm16-prfsha256-ecp256-modp3072,aes192-sha256-ecp256-modp3072,default
            }
        }
    }

2. Constraints Plugin

You might also want to disable the constraints plugin:

sudo sed -i 's/load = yes/load = no/g' /etc/strongswan.d/charon/constraints.conf

Make sure that your strongSwan basic configuration respects that setting (/etc/strongswan.conf):

## strongswan.conf - strongSwan configuration file
#
## Refer to the strongswan.conf(5) manpage for details
#
## Configuration changes should be made in the included files

charon-systemd {
  threads = 16
  plugins {
  	include strongswan.d/charon/*.conf
	}
}
include strongswan.d/*.conf

3. Get Certificates

Your local machine needs a certificate for the VPN server.

With NordVPN you have to download their certificate:

sudo wget https://downloads.nordvpn.com/certificates/root.der -O /etc/ipsec.d/cacerts/NordVPN.der
sudo openssl x509 -inform der -in /etc/ipsec.d/cacerts/NordVPN.der -out /etc/ipsec.d/cacerts/NordVPN.pem

With other providers it might suffice to link the standard OpenSSL certificates with the IPSec certs:

sudo rmdir /etc/ipsec.d/cacerts
sudo ln -s /etc/ssl/certs /etc/ipsec.d/cacerts

4. Restart strongSwan

strongSwan has a systemd script:

sudo systemctl restart strongswan

You can also enable the script for starting strongSwan on boot:

sudo systemctl enable strongswan

You can use the tool via the swanctl command line utility. For example:

## starts the connection and the remote children setup
sudo swanctl -i -c <name-of-children-connection>
## stops the complete connection
sudo swanctl -t -i <name-of-the-connection>

Example:

sudo swanctl -i -c nordvpn

5. Test Connection

In your browser, go to ipleak.net.

Further Reading