1 From 6805353e349165acb54e59abff10c53ea9069da7 Mon Sep 17 00:00:00 2001
 
   2 From: Julien Moutinho <julm+nixpkgs@sourcephile.fr>
 
   3 Date: Sun, 17 Jan 2021 15:28:13 +0100
 
   4 Subject: [PATCH 1/2] nixos/netns: init module to manage network namespaces
 
   7  nixos/modules/module-list.nix               |   1 +
 
   8  nixos/modules/services/networking/netns.nix | 133 ++++++++++++++++++++
 
   9  2 files changed, 134 insertions(+)
 
  10  create mode 100644 nixos/modules/services/networking/netns.nix
 
  12 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
 
  13 index 79f5c22f5b..a350346754 100644
 
  14 --- a/nixos/modules/module-list.nix
 
  15 +++ b/nixos/modules/module-list.nix
 
  17    ./services/networking/netclient.nix
 
  18    ./services/networking/networkd-dispatcher.nix
 
  19    ./services/networking/networkmanager.nix
 
  20 +  ./services/networking/netns.nix
 
  21    ./services/networking/nextdns.nix
 
  22    ./services/networking/nftables.nix
 
  23    ./services/networking/nghttpx/default.nix
 
  24 diff --git a/nixos/modules/services/networking/netns.nix b/nixos/modules/services/networking/netns.nix
 
  26 index 0000000000..eb05d0c0fd
 
  28 +++ b/nixos/modules/services/networking/netns.nix
 
  39 +  cfg = config.services.netns;
 
  40 +  inherit (config) networking;
 
  41 +  # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
 
  44 +    lib.concatMapStrings (s: if lib.isList s then "-" else s) (
 
  45 +      builtins.split "[^a-zA-Z0-9_.\\-]+" name
 
  49 +  options.services.netns = {
 
  50 +    namespaces = mkOption {
 
  52 +        Network namespaces to create.
 
  54 +        Other services can join a network namespace named `netns` with:
 
  56 +        PrivateNetwork=true;
 
  57 +        JoinsNamespaceOf="netns-''${netns}.service";
 
  60 +        So can `iproute2` with: `ip -n ''${netns}`
 
  63 +        You should usually create (or update via your VPN configuration's up script)
 
  64 +        a file named `/etc/netns/''${netns}/resolv.conf`
 
  65 +        that will be bind-mounted by `ip -n ''${netns}` onto `/etc/resolv.conf`,
 
  66 +        which you'll also want to configure in the services joining this network namespace:
 
  68 +        BindReadOnlyPaths = ["/etc/netns/''${netns}/resolv.conf:/etc/resolv.conf"];
 
  73 +      type = types.attrsOf (
 
  75 +          options.nftables = mkOption {
 
  76 +            description = "Nftables ruleset within the network namespace.";
 
  78 +            default = networking.nftables.ruleset;
 
  79 +            defaultText = "config.networking.nftables.ruleset";
 
  81 +          options.sysctl = options.boot.kernel.sysctl // {
 
  82 +            description = "sysctl within the network namespace.";
 
  83 +            default = config.boot.kernel.sysctl;
 
  84 +            defaultText = literalMD "config.boot.kernel.sysctl";
 
  86 +          options.service = mkOption {
 
  87 +            description = "Systemd configuration specific to this netns service";
 
  96 +    systemd.services = mapAttrs' (
 
  98 +      nameValuePair "netns-${escapeUnitName name}" (mkMerge [
 
 100 +          description = "${name} network namespace";
 
 101 +          before = [ "network.target" ];
 
 104 +            RemainAfterExit = true;
 
 105 +            # Let systemd create the netns so that PrivateNetwork=true
 
 106 +            # with JoinsNamespaceOf="netns-${name}.service" works.
 
 107 +            PrivateNetwork = true;
 
 108 +            # PrivateNetwork=true implies PrivateMounts=true by default,
 
 109 +            # which would prevent the persisting and sharing of /var/run/netns/$name
 
 110 +            # causing `ip netns exec $name $SHELL` outside of this service to fail with:
 
 111 +            # Error: Peer netns reference is invalid.
 
 112 +            # As `stat -f -c %T /var/run/netns/$name` would not be "nsfs" in those mntns.
 
 113 +            # See https://serverfault.com/questions/961504/cannot-create-nested-network-namespace
 
 114 +            PrivateMounts = false;
 
 117 +                # Register the netns with a binding mount to /var/run/netns/$name to keep it alive,
 
 118 +                # and make sure resolv.conf can be used in BindReadOnlyPaths=
 
 119 +                # For propagating changes in that file to the services bind mounting it,
 
 120 +                # updating must not remove the file, but only truncate it.
 
 121 +                (pkgs.writeShellScript "ip-netns-attach" ''
 
 122 +                  ${pkgs.iproute2}/bin/ip netns attach ${escapeShellArg name} $$
 
 123 +                  mkdir -p /etc/netns/${escapeShellArg name}
 
 124 +                  touch /etc/netns/${escapeShellArg name}/resolv.conf
 
 127 +                # Bringing the loopback interface is almost always a good thing.
 
 128 +                "${pkgs.iproute2}/bin/ip link set dev lo up"
 
 130 +                # Use --ignore because some keys may no longer exist in that new namespace,
 
 131 +                # like net.ipv6.conf.eth0.addr_gen_mode or net.core.rmem_max
 
 133 +                  ${pkgs.procps}/bin/sysctl --ignore -p ${
 
 134 +                    pkgs.writeScript "sysctl" (
 
 137 +                          n: v: optionalString (v != null) "${n}=${if v == false then "0" else toString v}\n"
 
 145 +              # Load the nftables ruleset of this netns.
 
 146 +              optional networking.nftables.enable (
 
 147 +                pkgs.writeScript "nftables-ruleset" ''
 
 148 +                  #!${pkgs.nftables}/bin/nft -f
 
 153 +            # Unregister the netns from the tracking mecanism of iproute2.
 
 154 +            ExecStop = "${pkgs.iproute2}/bin/ip netns delete ${escapeShellArg name}";
 
 160 +    meta.maintainers = with lib.maintainers; [ julm ];