From 09a615ee6263118d71fc57a4dca9087cff6e09b5 Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 18 Jun 2024 09:32:47 +0000 Subject: [PATCH] netns: factor the netns setup/teardown into distinct services, rather than trying to piggyback network-local-commands idk what network-local-commands is about, nor network-pre.target. network-pre.target doesn't seem to actually be wanted by anything (?) --- modules/netns.nix | 101 ++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/modules/netns.nix b/modules/netns.nix index 61c9c95f..96493974 100644 --- a/modules/netns.nix +++ b/modules/netns.nix @@ -26,9 +26,10 @@ let }; }; mkNetNsConfig = name: opts: with opts; { - networking.localCommands = let - iptables = "${pkgs.iptables}/bin/iptables"; - in-ns = "ip netns exec ${name}"; + systemd.services."netns-${name}" = let + ip = lib.getExe' pkgs.iproute2 "ip"; + iptables = lib.getExe pkgs.iptables; + in-ns = "${ip} netns exec ${name}"; bridgePort = port: proto: '' ${in-ns} ${iptables} -A PREROUTING -t nat -p ${proto} --dport ${port} -m iprange --dst-range ${netnsPubIpv4} \ -j DNAT --to-destination ${hostVethIpv4} @@ -41,55 +42,59 @@ let config.sane.ports.ports ) ; - in '' - ip netns add ${name} || (test -e /run/netns/${name} && echo "${name} already exists") - # DOCS: - # - some of this approach is described here: - # - iptables primer: - # create veth pair - ip link add ${name}-veth-a type veth peer name ${name}-veth-b || echo "${name}-veth-{a,b} aleady exists" - ip addr add ${hostVethIpv4}/24 dev ${name}-veth-a || echo "${name}-veth-a aleady has IP address" - ip link set ${name}-veth-a up + in { + description = "create a network namespace which will selectively bridge traffic with the init namespace"; + # specifically, we need to set these up before wireguard-wg-*, + wantedBy = [ "network-pre.target" ]; + before = [ "network-pre.target" ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + script = '' + ${ip} netns add ${name} || (test -e /run/netns/${name} && echo "${name} already exists") + # DOCS: + # - some of this approach is described here: + # - iptables primer: + # create veth pair + ${ip} link add ${name}-veth-a type veth peer name ${name}-veth-b || echo "${name}-veth-{a,b} aleady exists" + ${ip} addr add ${hostVethIpv4}/24 dev ${name}-veth-a || echo "${name}-veth-a aleady has IP address" + ${ip} link set ${name}-veth-a up - # move veth-b into the namespace - ip link set ${name}-veth-b netns ${name} || echo "${name}-veth-b was already moved into its netns" - ${in-ns} ip addr add ${netnsVethIpv4}/24 dev ${name}-veth-b || echo "${name}-veth-b aleady has IP address" - ${in-ns} ip link set ${name}-veth-b up + # move veth-b into the namespace + ${ip} link set ${name}-veth-b netns ${name} || echo "${name}-veth-b was already moved into its netns" + ${in-ns} ${ip} addr add ${netnsVethIpv4}/24 dev ${name}-veth-b || echo "${name}-veth-b aleady has IP address" + ${in-ns} ${ip} link set ${name}-veth-b up - # make it so traffic originating from the host side of the veth - # is sent over the veth no matter its destination. - ip rule add from ${hostVethIpv4} lookup ${name} pref 50 || echo "${name} already has ip rules (pref 50)" + # make it so traffic originating from the host side of the veth + # is sent over the veth no matter its destination. + ${ip} rule add from ${hostVethIpv4} lookup ${name} pref 50 || echo "${name} already has ip rules (pref 50)" - # for traffic originating at the host veth to the WAN, use the veth as our gateway - # not sure if the metric 1002 matters. - ip route add default via ${netnsVethIpv4} dev ${name}-veth-a proto kernel src ${hostVethIpv4} metric 1002 table ${name} || \ - echo "${name} already has default route" - # give the default route lower priority - ip rule add from all lookup local pref 100 || echo "${name}: already has ip rules (pref 100)" - ip rule del from all lookup local pref 0 || echo "${name}: already removed ip rule of default lookup (pref 0)" + # for traffic originating at the host veth to the WAN, use the veth as our gateway + # not sure if the metric 1002 matters. + ${ip} route add default via ${netnsVethIpv4} dev ${name}-veth-a proto kernel src ${hostVethIpv4} metric 1002 table ${name} || \ + echo "${name} already has default route" + # give the default route lower priority + ${ip} rule add from all lookup local pref 100 || echo "${name}: already has ip rules (pref 100)" + ${ip} rule del from all lookup local pref 0 || echo "${name}: already removed ip rule of default lookup (pref 0)" - # in order to access DNS in this netns, we need to route it to the VPN's nameservers - # - alternatively, we could fix DNS servers like 1.1.1.1. - ${in-ns} ${iptables} -A OUTPUT -t nat -p udp --dport 53 -m iprange --dst-range 127.0.0.53 \ - -j DNAT --to-destination ${dns}:53 - '' + (lib.concatStringsSep "\n" bridgeStatements); + # in order to access DNS in this netns, we need to route it to the VPN's nameservers + # - alternatively, we could fix DNS servers like 1.1.1.1. + ${in-ns} ${iptables} -A OUTPUT -t nat -p udp --dport 53 -m iprange --dst-range 127.0.0.53 \ + -j DNAT --to-destination ${dns}:53 + '' + (lib.concatStringsSep "\n" bridgeStatements); + preStop = '' + ${in-ns} ${ip} link del ${name}-veth-b || echo "couldn't delete ${name}-veth-b" + ${ip} link del ${name}-veth-a || echo "couldn't delete ${name}-veth-a" + ${ip} netns delete ${name} || echo "couldn't delete ${name}" + # restore rules/routes + ${ip} rule del from ${hostVethIpv4} lookup ${name} pref 50 || echo "couldn't delete init -> ${name} rule" + ${ip} route del default via ${netnsVethIpv4} dev ${name}-veth-a proto kernel src ${hostVethIpv4} metric 1002 table ${name} || echo "couldn't delete init > ${name} route" + # FIXME: if there are other net namespaces active, changing the prefs here may break those! + ${ip} rule add from all lookup local pref 0 + ${ip} rule del from all lookup local pref 100 + ''; + }; - # postShutdown = '' - # ${in-ns} ip link del ${name}-veth-b || echo "couldn't delete ${name}-veth-b" - # ip link del ${name}-veth-a || echo "couldn't delete ${name}-veth-a" - # ip netns delete ${name} || echo "couldn't delete ${name}" - # # restore rules/routes - # ip rule del from ${veth-host-ip} lookup ${name} pref 50 || echo "couldn't delete init -> ${name} rule" - # ip route del default via ${veth-local-ip} dev ${name}-veth-a proto kernel src ${veth-host-ip} metric 1002 table ${name} || echo "couldn't delete init > #{name} route" - # ip rule add from all lookup local pref 0 - # ip rule del from all lookup local pref 100 - # ''; - - # specifically, we need to set these up before wireguard-wg-*, - # whose unit files are BEFORE "network.target", and therefore - # ordered ambiguously w.r.t. network-local-commands (a dep of "network.target"). - systemd.services.network-local-commands.wantedBy = [ "network-pre.target" ]; - systemd.services.network-local-commands.before = [ "network-pre.target" ]; + # for some reason network-pre doesn't actually get run before network.target by default?? systemd.targets.network-pre.wantedBy = [ "network.target" ]; systemd.targets.network-pre.before = [ "network.target" ]; @@ -116,7 +121,7 @@ in networking.localCommands = f.networking.localCommands; networking.iproute2.rttablesExtraConfig = f.networking.iproute2.rttablesExtraConfig; networking.iproute2.enable = f.networking.iproute2.enable; - systemd.services.network-local-commands = f.systemd.services.network-local-commands; + systemd.services = f.systemd.services; systemd.targets.network-pre = f.systemd.targets.network-pre; }; in take (sane-lib.mkTypedMerge take configs);