Transparent IKEv2 VPN for a LXC container

Recently, I implemented an interesting scenario involving Linux networking and VPN. These were the requirements:

  • Unprivileged LXC container running on Debian 13 host:
    • All internet-bound traffic from the container is routed via remote VPN.
    • Container must not be able to communicate with any other networks the host might be directly connected to.
    • The container itself has no knowledge about the VPN.
    • If the VPN does not work, the container can’t communicate with internet services.
  • Debian 13 host:
    • strongSwan handles IPSec IKEv2 connections with the remote VPN service.
    • Network traffic originating from the host system is not routed via the VPN service.
  • Remote IPSec IKEv2 VPN service:
    • Not under our control.
    • Assigns a single virtual IPv4 address.
The initial network diagram

IPv4 ranges (RFC1918) used in the example

  • 192.168.99.0/24 – network the host is connected to
  • 192.168.222.0/24 – host-only network for the container(s)
  • 10.9.8.7/32 – VPN virtual IP

Let’s take a brief look at a base VPN configuration. It is very easy to configure a policy-based IKEv2 VPN for the host. Example swanctl.conf:

policy-based-vpn {
  version = 2

  local_addrs  = 192.168.99.98
  remote_addrs = remote-vpn-gateway.macadmin.cz
  vips = 0.0.0.0

  local {
    auth = pubkey
    id = vpn-client.macadmin.cz
  }

  remote {
      auth = pubkey
      id = remote-vpn-gateway.macadmin.cz
  }

  children {
    netvpn {
      remote_ts = 0.0.0.0/0
      start_action = trap
    }
  }
}

The challenge is how to make it work for the traffic originating from the container
and exclude all traffic originating from the host system. The host only network 192.168.222.0/24, the container is connected to, will have to be NATed behind a single IPv4 address to match the 1:1 expectation of the VPN service. First I tried to come up with a solution using policy-based VPN configuration, but after some thinking I figured a route-based VPN might be a better fit for this situation.

The plan:

  1. Create XFRM interface ipsec0 and assign IPSec policy with match-all-traffic selector 0.0.0.0/0.
  2. Create source routing table which will apply to all traffic originating from the host-only network and use it to route the traffic to ipsec0 interface.
  3. Use nftables to configure Source NAT on ipsec0.
  4. Make sure virtual IPv4 address assigned by the VPN server gets assigned to ipsec0 and not any another interface.
Continue reading “Transparent IKEv2 VPN for a LXC container”