Exclude a local subnet from StrongSwan VPN

I have a computer with an extra, local-only Ethernet interface, with a private subnet. When a StrongSwan VPN is established, I cannot access that subnet.

This is the local 'left' configuration (established by algo):

conn ikev2-<rightip>
    fragmentation=yes
    rekey=no
    dpdaction=clear
    keyexchange=ikev2
    compress=no
    dpddelay=35s

    ike=aes128gcm16-prfsha512-ecp256!
    esp=aes128gcm16-ecp256!

    right=<rightip>
    rightid=<rightip>
    rightsubnet=0.0.0.0/0
    rightauth=pubkey

    leftsourceip=%config
    leftauth=pubkey
    leftcert=daves.crt
    leftfirewall=yes
    left=%defaultroute

    auto=add

The subnet in question is 10.0.0.0/24. %defaultroute resolves to an address in 192.168.0.0/24.

'left' and 'leftsubnet' don't look like the right options for this, but I see nothing better. I've tried setting leftsubnet to 10.0.0.0/24, and to !10.0.0.0/24.

How do I exclude a local subnet from a StronSwan VPN connection?

How do I inspect the route configuration of a connection?


You can set a passthrough policy.

UPDATE: as noted by @ecdsa, with strongswan >= 5.5.2 there's an easier method, look at the end if your version can use it.

Example with a few random IPs. Before any change and tunnel:

# ip route get 10.0.0.55
10.0.0.55 dev lxcbr0 src 10.0.0.77 uid 0 

After establishing tunnel, the problem:

# ip route get 10.0.0.55
10.0.0.55 via 192.168.0.1 dev eth0 table 220 src 192.168.0.44 uid 0 

Adding this configuration to /etc/ipsec.conf:

conn ignorelan
    left=127.0.0.1 #prevents usage in peers selection algorithm
    leftsubnet=10.0.0.0/24
    rightsubnet=10.0.0.0/24
    authby=never
    type=passthrough
    auto=route

and reloading it:

# ipsec reload

should activate it immediately. If it didn't (this time) you can do:

# ipsec route ignorelan
'ignorelan' shunt PASS policy installed

Anyway it should be used on any later restart. You now have (in addition to any tunnel):

# ipsec status 
Shunted Connections:
     ignorelan:  10.0.0.0/24 === 10.0.0.0/24 PASS

[...]

Now, tunnel being established or not, you get back the correct route (handled by strongswan, so in table 220, not (just) table main (anymore)):

# ip route get 10.0.0.55
10.0.0.55 dev lxcbr0 table 220 src 10.0.0.77 uid 0 
    cache 

With a tunnel up for 0.0.0.0/0, there would be a result similar to this in table 220:

# ip route show table 220
default via 192.168.0.1 dev eth0 proto static src 192.168.0.44 
10.0.0.0/24 dev lxcbr0 proto static src 10.0.0.77 
192.168.0.0/24 dev eth0 proto static src 192.168.0.44 

If you don't put routes matching reality (eg: wrong netmask) in the passthrough settings, expect wrong results for the "shunt" (like the expected source IP, but going through the wrong network card), so be careful.


UPDATE: the bypass-lan plugin is easier to use, especially with dynamic environments.

Instead of adding new conn entries, activate it (eg on Debian buster (not stable), edit /etc/strongswan.d/charon/bypass-lan.conf and set load=yes (ie: charon.plugins.bypass-lan.load=yes) ). By default every interface gets shunted, meaning a tunnel would establish, but wouldn't be used by default. So just set either interfaces_ignore or interfaces_use accordingly. You should set interfaces_ignore for the interface(s) you don't want to bypass tunnels or, else, set interfaces_use for the interface(s) you want to bypass tunnels. Eg:

interface_use = lxcbr0

Will get in this example after ipsec start, like the previous method:

# ip route show table 220
10.0.0.0/24 dev lxcbr0 proto static src 10.0.0.77

(but will also consider IPv6 routes related to this interface when IPv6 is in use).


Yet an other (hackish) method would be to bypass strongswan's table 220: instead of any strongswan settings, the same would be achieved (for these question and example) with:

ip rule add priority 219 to 10.0.0.0/24 lookup main

It will use the default (main) routing table with the target network instead of continuing to the next entry using strongswan's table 220.