Setting up WireGuard in a network namespace for selectively routing traffic through VPN

You’re exactly right @mth, the fundamental issue was running wg.service inside the newly created wg namespace. The service needs to run in the default namespace. Thank you very much for your help!

I’ll include a summary here for those who may find this post later.

Overall I had to make two changes:

  1. Remove the NetworkNamespacePath line from wg.service’s serviceConfig
  2. Update the wg setconf command to run via ip netns exec, since it runs after wg0 is moved to the wg namespace.

My working config is thus:

{ config, pkgs, ... }:
  systemd.services."netns@" = {
    description = "%I network namespace";
    before = [ "network.target" ];
    serviceConfig = {
      Type = "oneshot";
      RemainAfterExit = true;
      ExecStart = "${pkgs.iproute}/bin/ip netns add %I";
      ExecStop = "${pkgs.iproute}/bin/ip netns del %I";
    };
  };

  systemd.services.wg = {
    description = "wg network interface";
    bindsTo = [ "netns@wg.service" ];
    requires = [ "network-online.target" ];
    after = [ "netns@wg.service" ];
    serviceConfig = {
      Type = "oneshot";
      RemainAfterExit = true;
      ExecStart = with pkgs; writers.writeBash "wg-up" ''
        set -e
        ${iproute}/bin/ip link add wg0 type wireguard
        ${iproute}/bin/ip link set wg0 netns wg
        ${iproute}/bin/ip -n wg address add <ipv4 VPN addr/cidr> dev wg0
        ${iproute}/bin/ip -n wg -6 address add <ipv6 VPN addr/cidr> dev wg0
        ${iproute}/bin/ip netns exec wg \
          ${wireguard}/bin/wg setconf wg0 /root/myVPNprovider.conf
        ${iproute}/bin/ip -n wg link set wg0 up
        ${iproute}/bin/ip -n wg route add default dev wg0
        ${iproute}/bin/ip -n wg -6 route add default dev wg0
      '';
      ExecStop = with pkgs; writers.writeBash "wg-down" ''
        ${iproute}/bin/ip -n wg route del default dev wg0
        ${iproute}/bin/ip -n wg -6 route del default dev wg0
        ${iproute}/bin/ip -n wg link del wg0
      '';
    };
  };

With this updated config, services can now be defined that will be exclusively routed through the wg0 interface. These new services can utilize the NetworkNamespacePath option to run inside the wg namespace. The basic skeleton is:

systemd.services.myWireguardOnlyService = {
  description = "Service that will only have access to the wg0 interface"
  bindsTo = [ "netns@wg.service" ];
  requires = [ "network-online.target" ];
  after = [ "wg.service" ];
  serviceConfig = {
    ...
    NetworkNamespacePath = "/var/run/netns/wg";
    ...
  };
}

I’m sure this could be refactored and cleaned up a bit, but it will serve my needs nicely for now. :slight_smile:

5 Likes