Background
I've setup and been running IPsec/IKEv2 VPN so-called road-warrior scenario with strongSwan for a decade. Firstly setup on Entware. A couple of years later easily migrated the setup to EdgeRouter X (i.e. Debian).
Recent years' update in strongSwan such swanctl & xfrm interface, the UCI middleware and firewall4/nftables in OpenWrt (all new to me) made the migration of my old setup more challenging than anticipated this time around.
Available information is a bit messy, inaccurate and confusing. I've yet to see a comprehensive HOW-TO that works and sticks to best practice of latest technologies.
I spent a day on & off to pull this working setup together. Hope it will save others some time. And more and more users will deploy IPsec VPN. As far as I'm aware IPsec is the only solution with widely built-in clients on various OS platforms and will take advantage of crypto acceleration ASICs already supported by the platforms.
A few Conceptis First
strongSwan comes a long way. The old 'ipsec CLI' way of doing configuration is set for deprecation. The new 'swanctl' is the way forward.
The 'VTI interface' in strongSwan for setting up routes and firewall on the host is set for deprecation too. The replacement and better feature is known as 'xfrm interface'. The good old 'iptables policy based' routes and firewall seems still supported but I would not recommend for firewall already migrated to nftables.
So the chosen technologies in this guide is 'swanctl' and 'xfrm interface'. The latter makes so easy to work with nftables-based firewall such as OpenWrt's firewall4.
Pitfalls in OpenWrt
- Past information on the forum & documentation in OpenWrt are confusing and sometimes conflicting each other due to changing technologies. So cherrypick carefully.
- Package 'strongswan-minimal' (as of last check in Jul 2024) is for the old strongswan's 'ipsec CLI' way of doing the configuration. Unless you know that's specifically what you want, don't install. This will save you lot of trouble later on.
- If you want to try yourself from scratch, install package 'strongswan-full' instead. This will fulfil most pre-requisites but still some missing packages that you'll have to figure out. I've gone through this process. So you might not want to spend hours to repeat the journey.
- Don't install package 'ip-tiny'. Install package 'ip-full' instead. The installed-size of the full version is only 100KiB bigger. Why OpenWrt defaults to 'ip-tiny' or having two versions available is beyond my comprehension. It creates lot of confusion. At the minimum effort of change, I think package strongswan-full should be defined explicitly to depend on 'ip-full' instead. Package 'ip-tiny' provides the 'ip' utility that doesn't support
- 'ip xfrm' sub-command
- graceful reporting of empty routing table. Instead, show you a puzzling error (
Error: ipv4: FIB table does not exist. Dump terminated
). A common question on this forum but seems nobody had addressed the 'root cause' of the error from my brief search. Now you have it.
- package 'xfrm' is required to enable UCI to support 'xfrm interface' in /etc/config/network. None of the storngSwan package depends on package 'xfrm'. You have to manually install it.
Now we can start configuring our minimalistic setup of strongSwan...
Required Packages
- ip-full xfrm
- strongswan strongswan-charon strongswan-swanctl
- strongswan-mod-kernel-netlink strongswan-mod-kdf strongswan-mod-gmp strongswan-mod-random strongswan-mod-socket-default strongswan-mod-uci strongswan-mod-vici
- strongswan-mod-aes strongswan-mod-hmac strongswan-mod-pem strongswan-mod-pkcs1 strongswan-mod-pkcs7 strongswan-mod-sha1 strongswan-mod-sha2 strongswan-mod-x509
The second group of modules meet my requirement of crypto strength (as shown in config below). If you choose stronger or other ciphers, then some of the packages might not be required and additional packages will be required.
Here is the one liner version of the same list:
$ opkg install ip-full xfrm strongswan strongswan-charon strongswan-swanctl strongswan-mod-aes strongswan-mod-kdf strongswan-mod-gmp strongswan-mod-hmac strongswan-mod-kernel-netlink strongswan-mod-pem strongswan-mod-pkcs1 strongswan-mod-pkcs7 strongswan-mod-random strongswan-mod-sha1 strongswan-mod-sha2 strongswan-mod-socket-default strongswan-mod-x509 strongswan-mod-uci strongswan-mod-vici
Create the 'xfrm' Interface
Append the following code snippet to
/etc/config/network
# XFRM interface for IKEv2 IPsec VPN (strongSwan)
config interface 'xfrm0'
option proto 'xfrm'
option ifid '357'
# 'tunlink' must be an 'interface' defined in this config
# not the 'device' attached to the 'interface'
# e.g. 'wan' works but 'br-wan' doesn't on Banana Pi BPI-R4
option tunlink 'wan'
option mtu '1438'
option zone 'vpn'
# Purpose of this interface is to assign a static address to the host end of the 'xfrm0' tunnel
# This dummy interface, otherwise, is not used anywhere else whatsoever
config interface 'xfrm0_dummy'
option proto 'static'
option ifname '@xfrm0'
# last address in the subnet. Hope we won't have that many road-warriors to reach 254
# Adapt it to your own chosen private IP subnet
option ipaddr '172.31.0.254/24'
Note that the only linkage between this interface and our IPsec config is the "ifid". Nothing else. You can choose your own unique integer, need not be '357'.
Another trivia: inconsistently enough, "/etc/config/network" decided to use "ifid" as the keyword and "/etc/config/ipsec" (as you'll see in a moment) decide to use "if_id" as the keyword for the same thing...
Configure the Firewall (FW4)
Append the following code snippet to
/etc/config/firewall
config zone
option name 'vpn'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
option mtu_fix '1'
list network 'xfrm0'
config forwarding
option src 'vpn'
option dest 'wan'
config forwarding
option src 'vpn'
option dest 'lan'
config forwarding
option src 'wan'
option dest 'vpn'
config forwarding
option src 'lan'
option dest 'vpn'
Configure IKEv2 IPsec
Create /etc/config/ipsec with the following content
/etc/config/ipsec
config ipsec
list interface 'wan'
option zone 'vpn'
option debug '2'
config remote 'phaeo'
option enabled '1'
option gateway 'any'
option authentication_method 'pubkey'
option local_identifier 'the CN value given to MyVPNServerCert.pem'
option local_cert 'MyVPNServerCert.pem'
option local_key 'PrivateKeyToMyVPNServerCert.pem'
option ca_cert 'MySelfSignedCACert.pem'
option local_sendcert 'always' # swanctl init script needs to be patched to support this
option dpddelay '300s' # dpdaction must be set to non-default value in below tunnel section
option rekeytime '3h'
option keyingretries '0'
option mobike '1'
option fragmentation '1'
list crypto_proposal 'ike_proposal'
list tunnel 'roadwarrior'
list pools 'ipv4pool'
config crypto_proposal 'ike_proposal'
option encryption_algorithm 'aes256'
option hash_algorithm 'sha256'
option dh_group 'modp3072'
config tunnel 'roadwarrior'
list local_subnet '0.0.0.0/0'
list remote_subnet '172.31.0.0/24' # for decoration and easier human identification purpose only
option if_id '357'
option rekeytime '1h'
option startaction 'none'
option closeaction 'none'
option dpdaction 'clear' # must be set in order for above IKEv2 "remote" block to honor dpddelay setting
list crypto_proposal 'esp_proposal'
config crypto_proposal 'esp_proposal'
option encryption_algorithm 'aes128'
option hash_algorithm 'sha1'
# Undocumented in OpenWrt wiki, added by git commit 08a0f7
# https://github.com/openwrt/packages/commit/08a0f7bb94f5577d5c04dcd8e3691e8ee69f76f9
config pools 'ipv4pool'
option addrs '172.31.0.0/24' # actual IPv4 will be giving out to road-warriors
list netmask '255.255.255.0'
list dns '192.168.1.1' # the DNS server on your LAN
The swanctl.patch
This patch is not mine. It seems to be contributed by @aleks-mariusz as far as I'm aware. The finding in the patch is tremendous contribution. Why hasn't OpenWrt merged the patch btw ?
For the above '/etc/config/ipsec' to work, you will have to apply the patch. Otherwise, 'local_sendcert' won't be recognised as it's not supported in the stock OpenWrt version (as of check in OpenWrt snapshot in July 2024).
For preserving the exact formatting of the patch, and hence work 'out of the box', please get it from my GitHub with
$ cd /root
$ wget https://raw.githubusercontent.com/kvic-z/goodies-OpenWrt/main/swanctl.patch
To apply the patch
$ cd /
$ patch -p 1 < /root/swanctl.patch
A note on the crypto ciphers that I chose
They're from about 10 years ago. Some might suggest to choose stronger ciphers. IMHO, for the purpose of SOHO users, I think it's still good enough for today. It's very strong & compute intensive on the key exchange phase (that happens every 3 hours as configured above). And very light compute on actually encrypting the encapsulation. The idea is that by the time a less-than-state actor broke the symmetric key, my VPN tunnel already hops to a new key (that happens every hour as configured above). LOL
Generation of self-signed CA certificate and server certificate is covered in many tutorials. Hence, we'll skip here.
Conclusion
I'll have documented my 'difficult struggle' anyway. Instead of writing in Microsoft Word and keeping it private for enjoyment in a closed circle, I thought why not let me do it in Markdown and publish it on Discourse. Because I found concise & correct info/documentation is very lacking on OpenWrt wiki & forum. Ultimately I hope more people will use IPsec and find it easy to setup and joyful to use.
So this HOW-TO provides a setup that fits into latest OpenWrt release that uses
- strongSwan, swanctl & xfrm interface
- nftables & OpenWrt's firewall4
- UCI middleware in OpenWrt
References that provided valuable input to my research