StrongSwan IKEv2 VPN on Raspberry pi
(Updated: )
Guide to set up road warrior VPN server (i.e. road warrior = mobile clients connecting to static server, vs e.g. site-to-site connection) using IKEv2 using strongswan on a raspberry pi. This guide is largely based on this digitalocean guide (digitalocean.com) combined with ready-made strongswan configurations (strongswan.org).
Update 20181224 : added algo VPN configurator
Update 20190223 : added cipher analysis / recommendation, clarified eth0 interface use on server, ESP/AH forwarding, added password generation one-liner.
Update 20200801 : minor fixes in commands.
Contents
Rationale for IKEv2/Strongswan ¶
I’ve decided to go for IKEv2 (wikipedia.org) for two main reasons:
- it’s natively supported by iOS and macOS and
- it only requires strongswan (strongswan.org) to operate. Two other options are 1) OpenVPN: requires non-native app/program to connect. 2) IPSEC/L2TP: requires xl2tpd on top of *swan.
After deciding on IKEv2, there are four main contenders for implementation. I chose strongswan (strongswan.org) because (serverfault.com)
- it’s available in the default Raspbian/debian apt repository and
- it appears newer than openswan.
Libreswan dropped out because it’s not available from the default apt repo, so it required compiling from source, which would’ve taken a long time on a raspberry pi. Softether (softether.org) also looks very promising (vpnxd.com) (thanks Sterk1!) as it supports a wide range of VPN protocols. Unfortunately like Libreswan this is also not available from the default apt repo.
Alternative guides ¶
I found some alternative guides/setups that could also work very well:
Automated installer scripts ¶
There are a few scripts that automate VPN setup. One requirement is that you trust the script to run as root, which I prefer not to. The advantage is that these scripts might have thought of more than I did, and are better maintained than static guides.
- algo (github.com) - automated script supporting IKEv2 among others, and explicitly not supporting weak keys (thanks GekkePrutser!)
- Automatic install of IPsec-L2TP/Libreswan setup (github.com) -- runs on libreswan compiled from source
Manual guides ¶
Other guides have instructions to install VPN manually:
- StrongSwan IKEv2 VPN on a Raspberry Pi (irmscher.bayern) -- similar guide
- Automatic install of IPsec-L2TP/Libreswan setup (github.com) -- this requires compiling from source and trusting someone else’s script (which I don’t)
- Openswan IPsec-L2TP (ritazh.com)
- https://vpnki.com/settings/linux/debian-l2tp-ipsec (vpnki.com)
- https://www.raspberrypi.org/forums/viewtopic.php?p=704519 (raspberrypi.org)
- https://openwrt.org/docs/ec/strongswan/roadwarrior (openwrt.org)
Target setup ¶
The target setup is as follows:
- My network is on
172.16.0.0/24,
- the router/DNS server is available on
172.16.0.1
, - the raspberry pi is at
${SERVER_FQDN}
. - I want the VPN clients to be on
172.16.1.0/24
and use the same DNS server. Alternatively instead of using virtual IPs, you can assign DHCP’ed IPs to VPN clients by settingVPN_SUBNET
to%dhcp
To this end, set the following variables:
SERVER_FQDN="my.router.com"
DNS_SERVER="172.16.0.1"
VPN_SUBNET="172.16.1.0/24"
VPN_IFACE="eth0"
SERVER_COUNTRY="NL"
YOUR_USERNAME="vpnuser"
YOUR_PASSWORD="vpnpassword"
Ciphers and security recommendations ¶
These (cisco.com) guides (cisco.com) have a nice overview of cipher security, from which I distilled this selection:
- Encryption: aes128, aes192, aes256, aes128gcm16, aes192gcm16, aes256gcm16
- Integrity: sha256, sha384, sha512
- Sign: modp2048, modp3072, modp4096, ecp256, ecp384, ecp521
When authenticating by password, use long passwords with at least the entropy content as the cipher strength (e.g. 128 bit for AES128).
The default ciphers on StrongSwan are reasonably OK, although unfortunately the broken MD5, SHA1, and 3DES are also included (perhaps for compatibility). Unfortunately, I haven’t found a way to remove support for specific ciphers, and removing the plugins from /etc/strongswan.d/charon
does not work.
There are two ways to fix this:
- Set secure ciphers server-side manually and explicitly
- Choose secure ciphers client-side
Server-side ciphers ¶
The relevant ipsec.conf settings (strongswan.org) are esp and ike.
ESP syntax is encryption-integrity[-dhgroup][-esnmode], for IKE this is encryption-integrity[-prf]-dhgroup. By using encryption-integrity-dhgroup, these are compatible
with each other. Unfortunately you have to list all permutations of the encryption, integrity, and signing ciphers. Don’t forget to add an exclamation mark to enforce these ciphers!
If you want to specifically enable ciphers on the server, you could use this python script (not tested!).
encrypt =['aes128', 'aes192','aes256','aes128gcm16','aes192gcm16','aes256gcm16']
integrity=['sha256','sha384','sha512']
sign = ['modp2048','modp3072','modp4096','ecp256','ecp384','ecp521']
ciphersuites = [e+'-'+i+'-'+s for e in encrypt for i in integrity for s in sign]
",".join(ciphersuites)+"!"
Client-side ciphers ¶
See my other post on creating iOS profiles (vanwerkhoven.org).
Speed ¶
Since this is running on a raspberry, speed is also relevant. I found these results on my RPi3B+ using openssl speed -evp
The 'numbers' are in 1000s of bytes per second processed.
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes 16384 bytes
md5 24634.10k 74728.55k 154655.66k 211365.59k 224220.50k 232265.05k
sha1 6816.60k 22071.29k 53680.45k 82852.25k 99040.45k 92405.76k
sha256 6001.71k 18437.16k 46062.42k 68029.68k 74043.08k 76436.82k
sha384 3025.30k 12140.95k 19447.72k 29114.71k 34839.29k 32287.40k
aes-128-cbc 30789.43k 43457.24k 47193.47k 47911.17k 48994.78k 48894.46k
aes-192-cbc 38234.35k 38777.04k 43397.33k 43725.83k 42692.50k 42976.49k
aes-256-cbc 33992.59k 36185.00k 35603.11k 38232.87k 37623.13k 35748.90k
aes-128-gcm 21008.15k 25713.49k 27408.30k 27992.41k 28466.52k 26858.84k
aes-192-gcm 19203.80k 23740.33k 25268.31k 24521.16k 21925.65k 22093.74k
aes-256-gcm 18081.80k 22149.27k 22574.48k 22588.82k 20678.59k 22074.42k
sign verify sign/s verify/s
256 bit ecdsa (nistp256) 0.0005s 0.0012s 1944.2 838.7
384 bit ecdsa (nistp384) 0.0204s 0.0109s 49.1 91.9
dsa 2048 bits 0.008810s 0.008716s 113.5 114.7
dsa4096 and ecdsap512 gave a segfault on openssl, not sure why.
Install required software ¶
Plugins are required to support EAP-MSCHAPv2 (strongswan-ikev2 might not be needed anymore as of Ubuntu 20.04). netfilter-persistent
is used to make iptables rules persistent.
sudo apt install strongswan strongswan-pki strongswan-ikev2 \
libcharon-extra-plugins libstrongswan-extra-plugins netfilter-persistent
Generate certificates and keys ¶
More or less directly copied from the guide mentioned above (digitalocean.com). Ideally run this as a trusted user in a directory that nobody else can access:
# ensure no other use can read this
mkdir ~/vpn-certs/
cd ~/vpn-certs/
chmod 700 .
ipsec pki --gen --type rsa --size 4096 --outform pem > server-root-key.pem
chmod 600 server-root-key.pem
ipsec pki --self --ca --lifetime 3650 \
--in server-root-key.pem \
--type rsa --dn "C=${SERVER_COUNTRY}, O=VPN Server, CN=VPN Server Root CA" \
--outform pem > server-root-ca.pem
ipsec pki --gen --type rsa --size 4096 --outform pem > vpn-server-key.pem
ipsec pki --pub --in vpn-server-key.pem \
--type rsa | ipsec pki --issue --lifetime 1825 \
--cacert server-root-ca.pem \
--cakey server-root-key.pem \
--dn "C=${SERVER_COUNTRY}, O=VPN Server, CN=${SERVER_FQDN}" \
--san ${SERVER_FQDN} \
--flag serverAuth --flag ikeIntermediate \
--outform pem > vpn-server-cert.pem
sudo cp ./vpn-server-cert.pem /etc/ipsec.d/certs/vpn-server-cert.pem
sudo cp ./vpn-server-key.pem /etc/ipsec.d/private/vpn-server-key.pem
sudo chown root /etc/ipsec.d/private/vpn-server-key.pem
sudo chgrp root /etc/ipsec.d/private/vpn-server-key.pem
sudo chmod 600 /etc/ipsec.d/private/vpn-server-key.pem
/etc/ipsec.conf ¶
# based on
# https://www.digitalocean.com/community/tutorials/how-to-set-up-an-ikev2-vpn-server-with-strongswan-on-ubuntu-16-04
# combined with improved cipher selection from
# https://wiki.strongswan.org/projects/strongswan/wiki/UsableExamples
# with improved cipher selection
config setup
charondebug="ike 1, knl 1, cfg 0"
uniqueids=no
conn ikev2-vpn
auto=add
compress=no
type=tunnel
keyexchange=ikev2
fragmentation=yes
forceencaps=yes
# Manually specify the ciphers here if you want.
#ike=aes192gcm16-aes128gcm16-prfsha256-ecp256-ecp521,aes192-sha256-modp3072
#esp=aes192gcm16-aes128gcm16-ecp256-modp3072,aes192-sha256-ecp256-modp3072
dpdaction=clear
dpddelay=300s
rekey=no
left=%any
leftid=@${SERVER_FQDN}
leftcert=/etc/ipsec.d/certs/vpn-server-cert.pem
leftsendcert=always
leftsubnet=0.0.0.0/0
right=%any
rightid=%any
rightauth=eap-mschapv2
rightdns=${DNS_SERVER}
rightsourceip=${VPN_SUBNET}
# alternatively, set rightsourceip=%dhcp to get DHCP'ed IPs from wherever the server is getting IPs from.
rightsendcert=never
eap_identity=%identity
If you want to select your own ciphers, read here (strongswan.org) on how to configure, here on what key length is recommended (keylength.com), and here for general security recommendations (strongswan.org).
/etc/ipsec.secrets ¶
${SERVER_FQDN} : RSA "/etc/ipsec.d/private/vpn-server-key.pem"
${YOUR_USERNAME} %any% : EAP "${YOUR_PASSWORD}"
N.B. Make sure your password is strong enough! To generate a strong password, use the following (source (strongswan.org)):
dd if=/dev/urandom count=1 bs=32 2>/dev/null | base64
This gives a 32-character length string of 6 bit printable characters (base64 is 6-bit) such that the password strength is 32*6 = 192bit.
iptables ¶
# ensure we don't lock our ssh session out
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# always accept loopback
sudo iptables -A INPUT -i lo -j ACCEPT
# accept ports 500 and 4500, required for IKEv2
sudo iptables -A INPUT -p udp --dport 500 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 4500 -j ACCEPT
# forward ESP
sudo iptables -A FORWARD --match policy --pol ipsec --dir in --proto esp \
-s ${VPN_SUBNET} -j ACCEPT
sudo iptables -A FORWARD --match policy --pol ipsec --dir out --proto esp \
-d ${VPN_SUBNET} -j ACCEPT
# more forwarding
sudo iptables -t nat -A POSTROUTING -s ${VPN_SUBNET} -o ${VPN_IFACE} -m policy \
--pol ipsec --dir out -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s ${VPN_SUBNET} -o ${VPN_IFACE} -j MASQUERADE
# fix fragmentation
sudo iptables -t mangle -A FORWARD --match policy --pol ipsec --dir in \
-s ${VPN_SUBNET} -o ${VPN_IFACE} -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss \
--mss 1361:1536 -j TCPMSS --set-mss 1360
# Drop everything we don't accept - for added security.
# NB only add if you know what you're doing,
# I accidentally locked out my grafana/influxdb setup here :p
# Also, for a router behind NAT I think this is superfluous.
#sudo iptables -A INPUT -j DROP
#sudo iptables -A FORWARD -j DROP
# make persistent
sudo netfilter-persistent save
sudo netfilter-persistent reload
To review your iptables rules, this StackExchange post explains how to view all iptables tables (stackexchange.com), by default only the filter table is shown:
iptables -vL -t filter
iptables -vL -t nat
iptables -vL -t mangle
iptables -vL -t raw
iptables -vL -t security
/etc/sysctl.conf ¶
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
# Do not accept ICMP redirects (prevent MITM attacks)
net.ipv4.conf.all.accept_redirects = 0
# Do not send ICMP redirects (we are not a router)
net.ipv4.conf.all.send_redirects = 0
# Not sure if this is required, I added it to be sure
net.ipv4.ip_no_pmtu_disc = 1
Run sysctl -p
after updating. If that doesn’t work, reboot.
Port forwarding ¶
Optionally (almost always), ensure port-forwarding on your home router is enabled such that UDP 4500 and 500 on the server are reachable from the internet. If using forceencaps (strongswan.org) as I did above, these are the only ports you need. If not, make sure you also forward protocols ESP (wikipedia.org) and AH (wikipedia.org), but be aware that NATing ESP and AH (internet-computer-security.com) is apparently not always supported.
Connecting clients ¶
Ensure you install server-root-ca.pem
on any clients. On macOS, after adding to keychain, make sure to trust it explicitly.
For iOS/macOS, Server and Remote ID are the same FQDN of your server.
After setting everything up, you’re done!
Troubleshooting ¶
- loading EAP_MSCHAPV2 method failed – ensure you have charon and strongswan plugins installed (libcharon-extra-plugins libstrongswan-extra-plugins)
- “Authentication failure” or immediate disconnect from iOS/macOS – this happens when you don’t explicitly install or trust the server certificate.
- loading EAP_DYNAMIC method failed -- this happens when you have multiple EAP authentication methods in IPSec.conf but no associated strongswan.conf solution. Note this is also listed explicitly in the example configuration file (if you read this
):
IF you need to support several EAP methods at the same time, you need to use eap-dynamic and not use any other conn with eap settings. Add the settings for the eap-dynamic plugin to your strongswan.conf file.
- unknown attribute type (25) -- this is not really an error
- received ESP_TFC_PADDING_NOT_SUPPORTED, not using ESPv3 TFC padding -- this is not an error either