diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 4176da2c8cb..980961225c9 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -112,6 +112,32 @@ let
Determines whether to add allowed IPs as routes or not.
'';
};
+
+ socketNamespace = mkOption {
+ default = null;
+ type = with types; nullOr str;
+ example = "container";
+ description = ''The pre-existing network namespace in which the
+ WireGuard interface is created, and which retains the socket even if the
+ interface is moved via . When
+ null, the interface is created in the init namespace.
+ See documentation.
+ '';
+ };
+
+ interfaceNamespace = mkOption {
+ default = null;
+ type = with types; nullOr str;
+ example = "init";
+ description = ''The pre-existing network namespace the WireGuard
+ interface is moved to. The special value init means
+ the init namespace. When null, the interface is not
+ moved.
+ See documentation.
+ '';
+ };
};
};
@@ -239,6 +265,10 @@ let
if peer.presharedKey != null
then pkgs.writeText "wg-psk" peer.presharedKey
else peer.presharedKeyFile;
+ src = interfaceCfg.socketNamespace;
+ dst = interfaceCfg.interfaceNamespace;
+ ip = nsWrap "ip" src dst;
+ wg = nsWrap "wg" src dst;
in nameValuePair "wireguard-${interfaceName}-peer-${unitName}"
{
description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
@@ -255,16 +285,16 @@ let
};
script = let
- wg_setup = "wg set ${interfaceName} peer ${peer.publicKey}" +
+ wg_setup = "${wg} set ${interfaceName} peer ${peer.publicKey}" +
optionalString (psk != null) " preshared-key ${psk}" +
optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" +
optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" +
optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}";
route_setup =
- optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+ optionalString interfaceCfg.allowedIPsAsRoutes
(concatMapStringsSep "\n"
(allowedIP:
- "ip route replace ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+ "${ip} route replace ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
) peer.allowedIPs);
in ''
${wg_setup}
@@ -272,13 +302,13 @@ let
'';
postStop = let
- route_destroy = optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+ route_destroy = optionalString interfaceCfg.allowedIPsAsRoutes
(concatMapStringsSep "\n"
(allowedIP:
- "ip route delete ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+ "${ip} route delete ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
) peer.allowedIPs);
in ''
- wg set ${interfaceName} peer ${peer.publicKey} remove
+ ${wg} set ${interfaceName} peer ${peer.publicKey} remove
${route_destroy}
'';
};
@@ -287,6 +317,13 @@ let
# exactly one way to specify the private key must be set
#assert (values.privateKey != null) != (values.privateKeyFile != null);
let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
+ src = values.socketNamespace;
+ dst = values.interfaceNamespace;
+ ipPreMove = nsWrap "ip" src null;
+ ipPostMove = nsWrap "ip" src dst;
+ wg = nsWrap "wg" src dst;
+ ns = if dst == "init" then "1" else dst;
+
in
nameValuePair "wireguard-${name}"
{
@@ -307,26 +344,33 @@ let
${values.preSetup}
- ip link add dev ${name} type wireguard
+ ${ipPreMove} link add dev ${name} type wireguard
+ ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) "${ipPreMove} link set ${name} netns ${ns}"}
${concatMapStringsSep "\n" (ip:
- "ip address add ${ip} dev ${name}"
+ "${ipPostMove} address add ${ip} dev ${name}"
) values.ips}
- wg set ${name} private-key ${privKey} ${
+ ${wg} set ${name} private-key ${privKey} ${
optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"}
- ip link set up dev ${name}
+ ${ipPostMove} link set up dev ${name}
${values.postSetup}
'';
postStop = ''
- ip link del dev ${name}
+ ${ipPostMove} link del dev ${name}
${values.postShutdown}
'';
};
+ nsWrap = cmd: src: dst:
+ let
+ nsList = filter (ns: ns != null) [ src dst ];
+ ns = last nsList;
+ in
+ if (length nsList > 0 && ns != "init") then "ip netns exec ${ns} ${cmd}" else cmd;
in
{
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 04323317a99..dc7225456b6 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -280,6 +280,7 @@ in
virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
wireguard = handleTest ./wireguard {};
wireguard-generated = handleTest ./wireguard/generated.nix {};
+ wireguard-namespaces = handleTest ./wireguard/namespaces.nix {};
wordpress = handleTest ./wordpress.nix {};
xautolock = handleTest ./xautolock.nix {};
xdg-desktop-portal = handleTest ./xdg-desktop-portal.nix {};
diff --git a/nixos/tests/wireguard/namespaces.nix b/nixos/tests/wireguard/namespaces.nix
new file mode 100644
index 00000000000..94f993d9475
--- /dev/null
+++ b/nixos/tests/wireguard/namespaces.nix
@@ -0,0 +1,80 @@
+let
+ listenPort = 12345;
+ socketNamespace = "foo";
+ interfaceNamespace = "bar";
+ node = {
+ networking.wireguard.interfaces.wg0 = {
+ listenPort = listenPort;
+ ips = [ "10.10.10.1/24" ];
+ privateKeyFile = "/etc/wireguard/private";
+ generatePrivateKeyFile = true;
+ };
+ };
+
+in
+
+import ../make-test.nix ({ pkgs, ...} : {
+ name = "wireguard-with-namespaces";
+ meta = with pkgs.stdenv.lib.maintainers; {
+ maintainers = [ asymmetric ];
+ };
+
+ nodes = {
+ # interface should be created in the socketNamespace
+ # and not moved from there
+ peer0 = pkgs.lib.attrsets.recursiveUpdate node {
+ networking.wireguard.interfaces.wg0 = {
+ preSetup = ''
+ ip netns add ${socketNamespace}
+ '';
+ inherit socketNamespace;
+ };
+ };
+ # interface should be created in the init namespace
+ # and moved to the interfaceNamespace
+ peer1 = pkgs.lib.attrsets.recursiveUpdate node {
+ networking.wireguard.interfaces.wg0 = {
+ preSetup = ''
+ ip netns add ${interfaceNamespace}
+ '';
+ inherit interfaceNamespace;
+ };
+ };
+ # interface should be created in the socketNamespace
+ # and moved to the interfaceNamespace
+ peer2 = pkgs.lib.attrsets.recursiveUpdate node {
+ networking.wireguard.interfaces.wg0 = {
+ preSetup = ''
+ ip netns add ${socketNamespace}
+ ip netns add ${interfaceNamespace}
+ '';
+ inherit socketNamespace interfaceNamespace;
+ };
+ };
+ # interface should be created in the socketNamespace
+ # and moved to the init namespace
+ peer3 = pkgs.lib.attrsets.recursiveUpdate node {
+ networking.wireguard.interfaces.wg0 = {
+ preSetup = ''
+ ip netns add ${socketNamespace}
+ '';
+ inherit socketNamespace;
+ interfaceNamespace = "init";
+ };
+ };
+ };
+
+ testScript = ''
+ startAll();
+
+ $peer0->waitForUnit("wireguard-wg0.service");
+ $peer1->waitForUnit("wireguard-wg0.service");
+ $peer2->waitForUnit("wireguard-wg0.service");
+ $peer3->waitForUnit("wireguard-wg0.service");
+
+ $peer0->succeed("ip -n ${socketNamespace} link show wg0");
+ $peer1->succeed("ip -n ${interfaceNamespace} link show wg0");
+ $peer2->succeed("ip -n ${interfaceNamespace} link show wg0");
+ $peer3->succeed("ip link show wg0");
+ '';
+})