Use the native IPsec stack to connect multiple networks over the internet.

I will connect three different networks over the internet using IPsec tunnels.

HostnameIP addressNetwork
antioch192.0.2.10172.16.32.0/20
cadmus192.0.2.20172.16.30.0/23
ignotus192.0.2.30172.16.48.0/20

Requirements

Install the strongSwan package.

$ sudo apt install strongswan

Remember to allow traffic on port 500 protocol UDP between above-mentioned nodes.

Generate secrets

I am using the OpenSSL utility to generate a base64-encoded secret.

$ openssl rand -base64 64 | paste --delimiters '' --serial 
FITSy9dGK9BlOOrOqOi3xRaWjMPgR9KQtT0GaPiBaKQ7LcYniHsdsSA78iEy8BmOGAXpkVi7Imp9dZeHfJPptA==

This solution is beautiful in its simplicity.

Store secrets

Define left (local) and right (remote) IP addresses and a secret for each tunnel. Notice that the secret needs to be the same on both sides.

antioch connects to ignotus and cadmus.

antioch$ sudo cat /etc/ipsec.secrets
# This file holds shared secrets or RSA private keys for authentication.

# RSA private key for this host, authenticating it to any other host
# which knows the public part.

192.0.2.10 192.0.2.30 : PSK "CfSzOwEc7A/IMg1AZnFG5ynMlm2cFL0Gy8Jt3K67edaPv2s5Zq+Zg4Lc+kaI7+0NygIkeXgYxe/XqXf1u8aXVQ=="
192.0.2.10 192.0.2.20 : PSK "Qc8okLcA28CLIx0ZKvRCznqpFKc2iHxqX9sMIe0hXV9HHgF9xRWzqm/pzKbZbwnajaOrqeKt5eAQ6Q6LjtqGVw=="

cadmus connects to antioch and ignotus.

cadmus$ sudo cat /etc/ipsec.secrets
# This file holds shared secrets or RSA private keys for authentication.

# RSA private key for this host, authenticating it to any other host
# which knows the public part.

192.0.2.20 192.0.2.10 : PSK "Qc8okLcA28CLIx0ZKvRCznqpFKc2iHxqX9sMIe0hXV9HHgF9xRWzqm/pzKbZbwnajaOrqeKt5eAQ6Q6LjtqGVw=="
192.0.2.20 192.0.2.30 : PSK "nJ3FlzqXFPwmlQAEK71jjfI6ynFGuzJbd+IAWYgl/rXs6s4+4ECgsJjPn0PH0BlAovQuJEY5p5WnrxRoix/6UQ=="

ignotus connects to antioch and cadmus.

ignotus$ sudo cat /etc/ipsec.secrets
# This file holds shared secrets or RSA private keys for authentication.

# RSA private key for this host, authenticating it to any other host
# which knows the public part.

192.0.2.30 192.0.2.10 : PSK "CfSzOwEc7A/IMg1AZnFG5ynMlm2cFL0Gy8Jt3K67edaPv2s5Zq+Zg4Lc+kaI7+0NygIkeXgYxe/XqXf1u8aXVQ=="
192.0.2.30 192.0.2.20 : PSK "nJ3FlzqXFPwmlQAEK71jjfI6ynFGuzJbd+IAWYgl/rXs6s4+4ECgsJjPn0PH0BlAovQuJEY5p5WnrxRoix/6UQ=="

IPsec configuration

Define left (local) and right (remote) IP addresses and corresponding subnets.

antioch connects to ignotus and cadmus.

antioch$ sudo cat /etc/ipsec.conf
conn antioch-to-ignotus
  authby=secret
  auto=route
  left=192.0.2.10
  leftsubnet=172.16.32.1/20
  right=192.0.2.30
  rightsubnet=172.16.48.1/20
  mobike=no
conn antioch-to-cadmus
  authby=secret
  auto=route
  left=192.0.2.10
  leftsubnet=172.16.32.1/20
  right=192.0.2.20
  rightsubnet=172.16.30.1/23
  mobike=no

cadmus connects to antioch and ignotus.

cadmus$ sudo cat /etc/ipsec.conf
conn cadmus-to-antioch
  authby=secret
  auto=route
  left=192.0.2.20
  leftsubnet=172.16.30.1/23
  right=192.0.2.10
  rightsubnet=172.16.32.1/20
  mobike=no
conn cadmus-to-ignotus
  authby=secret
  auto=route
  left=192.0.2.20
  leftsubnet=172.16.30.1/23
  right=192.0.2.30
  rightsubnet=172.16.48.1/20
  mobike=no

ignotus connects to antioch and cadmus.

ignotus$ sudo cat /etc/ipsec.conf
conn ignotus-to-antioch
  authby=secret
  auto=route
  left=192.0.2.30
  leftsubnet=172.16.48.1/20
  right=192.0.2.10
  rightsubnet=172.16.32.1/20
  mobike=no
conn ignotus-to-cadmus
  authby=secret
  auto=route
  left=192.0.2.30
  leftsubnet=172.16.48.1/20
  right=192.0.2.20
  rightsubnet=172.16.30.1/23
  mobike=no

Manage IPsec service

Start and enable service on each node.

$ systemctl enable --now strongswan-starter.service      
Created symlink /etc/systemd/system/multi-user.target.wants/strongswan-starter.service → /lib/systemd/system/strongswan-starter.service.

Inspect service status.

ignotus$ sudo systemctl status strongswan-starter.service 
● strongswan-starter.service - strongSwan IPsec IKEv1/IKEv2 daemon using ipsec.conf
     Loaded: loaded (/lib/systemd/system/strongswan-starter.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2020-11-26 21:09:25 UTC; 45s ago
   Main PID: 3955246 (starter)
      Tasks: 18 (limit: 38404)
     Memory: 4.4M
     CGroup: /system.slice/strongswan-starter.service
             ├─3955246 /usr/lib/ipsec/starter --daemon charon --nofork
             └─3955264 /usr/lib/ipsec/charon

Nov 26 21:10:04 ignotus charon[3955264]: 15[IKE] IKE_SA ignotus-to-cadmus[3] established between 192.0.2.30[192.0.2.30]...192.0.2.20[192.0.2.20]
Nov 26 21:10:04 ignotus charon[3955264]: 15[IKE] IKE_SA ignotus-to-cadmus[3] established between 192.0.2.30[192.0.2.30]...192.0.2.20[192.0.2.20]
Nov 26 21:10:04 ignotus charon[3955264]: 15[IKE] scheduling reauthentication in 10050s
Nov 26 21:10:04 ignotus charon[3955264]: 15[IKE] maximum IKE_SA lifetime 10590s
Nov 26 21:10:04 ignotus charon[3955264]: 15[CFG] selected proposal: ESP:AES_CBC_128/HMAC_SHA2_256_128/NO_EXT_SEQ
Nov 26 21:10:04 ignotus charon[3955264]: 15[IKE] CHILD_SA ignotus-to-cadmus{5} established with SPIs c3331638_i c7020e9a_o and TS 172.16.48.0/20 === 172.16.30.0/23
Nov 26 21:10:04 ignotus charon[3955264]: 15[IKE] CHILD_SA ignotus-to-cadmus{5} established with SPIs c3331638_i c7020e9a_o and TS 172.16.48.0/20 === 172.16.30.0/23
Nov 26 21:10:04 ignotus charon[3955264]: 15[ENC] generating IKE_AUTH response 1 [ IDr AUTH SA TSi TSr N(AUTH_LFT) ]
Nov 26 21:10:04 ignotus charon[3955264]: 15[NET] sending packet: from 192.0.2.30[500] to 192.0.2.20[500] (208 bytes)

Done.

Additional notes

Use ipsec utility to inspect and manage IPsec tunnels.

ignotus$ ipsec --help
ipsec command [arguments]

Commands:
        start|restart [arguments]
        update|reload|stop
        up|down|route|unroute <connectionname>
        down-srcip <start> [<end>]
        status|statusall [<connectionname>]
        listalgs|listpubkeys|listcerts [--utc]
        listcacerts|listaacerts|listocspcerts [--utc]
        listacerts|listgroups|listcainfos [--utc]
        listcrls|listocsp|listplugins|listall [--utc]
        listcounters|resetcounters [name]
        leases [<poolname> [<address>]]
        rereadsecrets|rereadcacerts|rereadaacerts
        rereadocspcerts|rereadacerts|rereadcrls|rereadall
        purgecerts|purgecrls|purgeike|purgeocsp
        scepclient|pki
        stroke
        version

Refer to the ipsec(8) man page for details.
Some commands have their own man pages, e.g. pki(1) or scepclient(8).

Inspect each connection as an example.

ignotus$ sudo ipsec statusall
Status of IKE charon daemon (strongSwan 5.8.2, Linux 5.4.0-54-generic, x86_64):
  uptime: 91 seconds, since Nov 26 21:09:25 2020
  malloc: sbrk 2703360, mmap 0, used 725472, free 1977888
  worker threads: 11 of 16 idle, 5/0/0/0 working, job queue: 0/0/0/0, scheduled: 7
  loaded plugins: charon aesni aes rc2 sha2 sha1 md5 mgf1 random nonce x509 revocation constraints pubkey pkcs1 pkcs7 pkcs8 pkcs12 pgp dnskey sshkey pem openssl fips-prf gmp agent xcbc hmac gcm drbg attr kernel-netlink resolve socket-default connmark stroke updown eap-mschapv2 xauth-generic counters
Listening IP addresses:
  192.0.2.30
  172.16.48.1
Connections:
ignotus-to-antioch:  192.0.2.30...192.0.2.10  IKEv1/2
ignotus-to-antioch:   local:  [192.0.2.30] uses pre-shared key authentication
ignotus-to-antioch:   remote: [192.0.2.10] uses pre-shared key authentication
ignotus-to-antioch:   child:  172.16.48.0/20 === 172.16.32.0/20 TUNNEL
ignotus-to-cadmus:  192.0.2.30...192.0.2.20  IKEv1/2
ignotus-to-cadmus:   local:  [192.0.2.30] uses pre-shared key authentication
ignotus-to-cadmus:   remote: [192.0.2.20] uses pre-shared key authentication
ignotus-to-cadmus:   child:  172.16.48.0/20 === 172.16.30.0/23 TUNNEL
Routed Connections:
ignotus-to-cadmus{2}:  ROUTED, TUNNEL, reqid 2
ignotus-to-cadmus{2}:   172.16.48.0/20 === 172.16.30.0/23
ignotus-to-antioch{1}:  ROUTED, TUNNEL, reqid 1
ignotus-to-antioch{1}:   172.16.48.0/20 === 172.16.32.0/20
Security Associations (2 up, 0 connecting):
ignotus-to-cadmus[3]: ESTABLISHED 52 seconds ago, 192.0.2.30[192.0.2.30]...192.0.2.20[192.0.2.20]
ignotus-to-cadmus[3]: IKEv2 SPIs: 25288d3b98dde4d6_i f88d38bed2635b45_r*, pre-shared key reauthentication in 2 hours
ignotus-to-cadmus[3]: IKE proposal: AES_CBC_128/HMAC_SHA2_256_128/PRF_AES128_XCBC/ECP_256
ignotus-to-cadmus{5}:  INSTALLED, TUNNEL, reqid 2, ESP SPIs: c3331638_i c7020e9a_o
ignotus-to-cadmus{5}:  AES_CBC_128/HMAC_SHA2_256_128, 58309 bytes_i (368 pkts, 0s ago), 166072 bytes_o (420 pkts, 0s ago), rekeying in 42 minutes
ignotus-to-cadmus{5}:   172.16.48.0/20 === 172.16.30.0/23
ignotus-to-antioch[1]: ESTABLISHED 90 seconds ago, 192.0.2.30[192.0.2.30]...192.0.2.10[192.0.2.10]
ignotus-to-antioch[1]: IKEv2 SPIs: d4ae22aaf88b0bab_i e8a8007af4fdcb26_r*, pre-shared key reauthentication in 2 hours
ignotus-to-antioch[1]: IKE proposal: AES_CBC_128/HMAC_SHA2_256_128/PRF_AES128_XCBC/ECP_256
ignotus-to-antioch{3}:  INSTALLED, TUNNEL, reqid 1, ESP SPIs: cc91bd1d_i c64aaa36_o
ignotus-to-antioch{3}:  AES_CBC_128/HMAC_SHA2_256_128, 1550 bytes_i (20 pkts, 2s ago), 13783 bytes_o (27 pkts, 2s ago), rekeying in 40 minutes
ignotus-to-antioch{3}:   172.16.48.0/20 === 172.16.32.0/20

Inspect routes.

$ ip r get 172.16.32.1
172.16.32.1 via 192.0.2.10 dev eth0 table 220 src 172.16.48.1 uid 0
    cache
$ ip r show table 220
172.16.30.0/23 via 192.0.2.20 dev eth0 proto static src 172.16.48.1
172.16.32.0/20 via 192.0.2.10 dev eth0 proto static src 172.16.48.1