Task:

  • A Linux machine (let’s call it router) connects to a strongswan server (IPsec IKEv2)
  • Some other machines register ip router as the default route and work via VPN (transparently for them)

How to set up the server – nothing special, for example, according to this article: . How to connect 2 networks is also clear: https://selectel.ru/blog/tutorials/how-to-set-up-vpn-ipsec / (but we don’t need the entire internal network to be visible from the vpn server, we would like nat and access rules).

If you use a regular client (requires access from a local computer, but does not provide access from a local network), you can enable https://docs.strongswan.org/docs/5.9/plugins/bypass-lan.html the plugin is so that when the VPN is turned on, this machine continues to see the local network.

There is another option - to run a proxy server (squid) on the router and distribute a VPN connection through it, but I don’t want to load a weak machine.

The note describes how to set up an XFRM client (Route-based VPN) and everything you need around. The VTI client was originally planned, but there is more magic there than in XFRM (it seems like XFRM is preferable).

We configure it for SUSE MicroOS. For other distributors, the details may be different. For example, ip forward is enabled here by default, you do not need to change this setting.

Disable SELinux to begin with (in principle, you can still suffer and turn it on, but not now):

cat >/etc/selinux/config <<EOF
SELINUX=permissive
SELINUXTYPE=targeted
EOF

# да, оно просто так не отключается, нужно еще grub править
vim /etc/default/grub
# изменить: enforcing=1 -> enforcing=0
transactional-update grub.cfg
reboot

We put the packages:

transactional-update pkg in strongswan nftables
reboot

We put the certificate from the server in `/etc/ipsec.d/certs/cacert.pem'.

/etc/ipsec.conf is outdated and does not contain new parameters (if_id_in). Therefore, we use the swan configuration files. 192.168.3.0/24 – the internal network in the examples. 10.10.10.0/24 – vpn network configured on the vpn server (rightsourceip=10.10.10.0/24).

Setting up a vpn:

vim /etc/strongswan.conf
# add 2 params inside charon to disable automatic routes (our script will do job):
# install_routes = no
# install_virtual_ip = no
# (может быть можно через какие-то параметры настроить, чтобы оно само правильно формировалось, но мне удалось это решить ручными скриптами)

cat >/etc/swanctl/conf.d/ikev2-do.conf <<EOF
connections {
    ikev2-myconn {
        include /etc/swanctl/conf.d/ike_sa_default.conf
        version = 2
        if_id_in = %unique
        if_id_out = %unique
        # it can be any IP, sever can provide another but it's required to have some IP here
        vips = 10.10.10.4
        remote_addrs = myremote.example.com
        children {
            ikev2-do {
                include /etc/swanctl/conf.d/child_sa_default.conf
                start_action = start
                local_ts = dynamic
                remote_ts = 0.0.0.0/0
                updown = /usr/local/bin/rw-updown.sh
            }
        }
        local-0 {
            auth = eap-mschapv2
            id = mi-vpn-client
        }
        remote-0 {
            auth = pubkey
            id = @myremote.example.com
        }
    }
}
secrets {
    eap-mi-vpn-client {
        secret = "myPassword!"
        id-0 = myUser1
    }
}
EOF

cat >/usr/local/bin/rw-updown.sh <<EOF
#!/bin/bash
set -eEuo pipefail

XFRM_IF="xfrm${PLUTO_UNIQUEID}"

case "${PLUTO_VERB}" in
    up-client)
        echo "vpn rw-updown" ${PLUTO_VERB} ${PLUTO_UNIQUEID} ${XFRM_IF} ${PLUTO_ME} ${PLUTO_PEER} ${PLUTO_IF_ID_IN} ${PLUTO_IF_ID_OUT}
        ip link add ${XFRM_IF} type xfrm dev lo if_id ${PLUTO_IF_ID_IN}
        ip link set dev ${XFRM_IF} mtu 1418
        ip addr add ${PLUTO_MY_CLIENT} dev ${XFRM_IF}
        ip link set ${XFRM_IF} up
        ip route add default dev ${XFRM_IF}
        nft add rule nat postrouting ip saddr 192.168.3.0/24 oifname "${XFRM_IF}" masquerade
        ;;
    down-client)
	echo down
	    # NOTE: folling command is planned but not supported yet by nft
	    # nft delete rule nat postrouting ip saddr 192.168.3.0/24 oifname "xfrm1" masquerade
	    nft flush chain nat postrouting
	    	    
	    ip route delete default
        ip link del ${XFRM_IF}
        ;;
esac
EOF
chmod +x /usr/local/bin/rw-updown.sh

And routing (disable the default router - it will be from the vpn and set up direct access to the vpn server):

nmcli connection modify ens18 ipv4.never-default true
nmcli connection modify ens18 +ipv4.routes "<ip address of myremote.example.com> 192.168.3.1"

A little firewall (for vpn, only nat (empty chain), the rest can not be configured):

cat >/etc/nftables.conf <<EOF
#!/usr/sbin/nft -f

flush ruleset

# ipv4 + ipv6
table inet filter {
    set wanted_tcp_ports {
        type inet_service
        flags interval
        elements = { 22 }
    }

    chain base_checks {
        ## another set, this time for connection tracking states.
        # allow established/related connections
        ct state {established, related} accept;

        # early drop of invalid connections
        ct state invalid drop;
    }

    chain input {
        type filter hook input priority 0; policy drop;

        # allow from loopback
        iif "lo" accept;

        jump base_checks;

        # allow icmp and igmp
        ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, packet-too-big, time-exceeded, parameter-problem, destination-unreachable, packet-too-big, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert, mld2-listener-report } accept;
        ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, router-solicitation, router-advertisement, time-exceeded, parameter-problem } accept;
        ip protocol igmp accept;
        tcp dport @wanted_tcp_ports accept

        # for testing reject with logging
        counter log prefix "[nftables] input reject " reject;
    }
    chain forward {
        type filter hook forward priority 0; policy accept;
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}
table ip nat {
	chain postrouting {
		type nat hook postrouting priority srcnat; policy accept;
	}
}
EOF
chmod +x /etc/nftables.conf

Starting (yes, there is no built-in nftables starter):

cat >/etc/systemd/system/nftables.service <<EOF
[Unit]
Description=Initialises nftab rules

[Service]
Type=oneshot
ExecStart=/etc/nftables.conf

[Install]
WantedBy=default.target
EOF

systemctl daemon-reload
systemctl enable --now nftables.service
systemctl enable --now strongswan
reboot

And different commands to check how everything is set up:

swanctl -l
swanctl -L
ip a l
ip -s link show xfrm1
ip -d link show xfrm1
ip rule ls
ip r l table 0
ip route show table lan
ip route show table vpn
nft list ruleset
nmcli connection show ens18
curl https://myip.ru/index_small.php

Somehow, too many settings and knowledge are needed for these settings, but it didn’t work out easier.