10 cfg = config.services.netns;
11 inherit (config) networking;
12 # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
15 lib.concatMapStrings (s: if lib.isList s then "-" else s) (
16 builtins.split "[^a-zA-Z0-9_.\\-]+" name
20 options.services.netns = {
21 namespaces = mkOption {
22 description = mdDoc ''
23 Network namespaces to create.
25 Other services can join a network namespace named `netns` with:
28 JoinsNamespaceOf="netns-''${netns}.service";
31 So can `iproute` with: `ip -n ''${netns}`
34 You should usually create (or update via your VPN configuration's up script)
35 a file named `/etc/netns/''${netns}/resolv.conf`
36 that will be bind-mounted by `ip -n ''${netns}` onto `/etc/resolv.conf`,
37 which you'll also want to configure in the services joining this network namespace:
39 BindReadOnlyPaths = ["/etc/netns/''${netns}/resolv.conf:/etc/resolv.conf"];
44 type = types.attrsOf (
46 options.nftables = mkOption {
47 description = mdDoc "Nftables ruleset within the network namespace.";
49 default = networking.nftables.ruleset;
50 defaultText = "config.networking.nftables.ruleset";
52 options.sysctl = options.boot.kernel.sysctl // {
53 description = mdDoc "sysctl within the network namespace.";
54 default = config.boot.kernel.sysctl;
55 defaultText = literalMD "config.boot.kernel.sysctl";
57 options.service = mkOption {
58 description = mdDoc "Systemd configuration specific to this netns service";
67 systemd.services = mapAttrs' (
69 nameValuePair "netns-${escapeUnitName name}" (mkMerge [
71 description = "${name} network namespace";
72 before = [ "network.target" ];
75 RemainAfterExit = true;
76 # Explanation: let systemd create the netns
77 # so that PrivateNetwork=true
78 # with JoinsNamespaceOf="netns-${name}.service" works.
79 PrivateNetwork = true;
80 # Explanation: using PrivateMounts=true would prevent
81 # the sharing of the mount bind /var/run/netns/$name
82 # done by `ip netns attach`,
83 # causing outside `ip netns exec $name $SHELL` to fail with:
84 # Error: Peer netns reference is invalid.
85 PrivateMounts = false;
87 # Explanation: for convenience, register the netns
88 # to the tracking mecanism of iproute,
89 # and make sure resolv.conf can be used in BindReadOnlyPaths=
90 # For propagating changes in that file to the services bind mounting it,
91 # updating must not remove the file, but only truncate it.
92 (pkgs.writeShellScript "ip-netns-attach" ''
93 ${pkgs.iproute}/bin/ip netns attach ${escapeShellArg name} $$
94 mkdir -p /etc/netns/${escapeShellArg name}
95 touch /etc/netns/${escapeShellArg name}/resolv.conf
98 # Explanation: bringing the loopback interface is almost always a good thing.
99 "${pkgs.iproute}/bin/ip link set dev lo up"
101 # Explanation: use --ignore because some keys
102 # may no longer exist in that new namespace,
103 # like net.ipv6.conf.eth0.addr_gen_mode or net.core.rmem_max .
105 ${pkgs.procps}/bin/sysctl --ignore -p ${
106 pkgs.writeScript "sysctl" (
109 n: v: optionalString (v != null) "${n}=${if v == false then "0" else toString v}\n"
117 # Load the nftables ruleset of this netns.
118 optional networking.nftables.enable (
119 pkgs.writeScript "nftables-ruleset" ''
120 #!${pkgs.nftables}/bin/nft -f
125 # Unregister the netns from the tracking mecanism of iproute.
126 ExecStop = "${pkgs.iproute}/bin/ip netns delete ${escapeShellArg name}";
132 meta.maintainers = with lib.maintainers; [ julm ];