diff --git a/hosts/by-name/servo/net.nix b/hosts/by-name/servo/net.nix index f018b79f..63882b73 100644 --- a/hosts/by-name/servo/net.nix +++ b/hosts/by-name/servo/net.nix @@ -3,7 +3,7 @@ let portOpts = with lib; types.submodule { options = { - visibleTo.ovpn = mkOption { + visibleTo.ovpns = mkOption { type = types.bool; default = false; description = '' @@ -12,11 +12,81 @@ let }; }; }; + + bridgedWireguardNamespace = { name, ip4, peers, privateKeyFile, vethSubnet, vpnDns }: let + ip = "${pkgs.iproute2}/bin/ip"; + in-ns = "${ip} netns exec ${name}"; + iptables = "${pkgs.iptables}/bin/iptables"; + veth-host-ip = "${vethSubnet}.5"; # "x.y.z.5", "x.y.z.6" is legacy: some things elsewhere assume this for ovpns + veth-local-ip = "${vethSubnet}.6"; + bridgePort = port: proto: '' + ${in-ns} ${iptables} -A PREROUTING -t nat -p ${proto} --dport ${port} -m iprange --dst-range ${ip4} \ + -j DNAT --to-destination ${veth-host-ip} + ''; + bridgeStatements = lib.foldlAttrs + (acc: port: portCfg: acc ++ (builtins.map (bridgePort port) portCfg.protocol)) + [] + (lib.filterAttrs + (port: portCfg: portCfg.visibleTo."${name}") + config.sane.ports.ports + ) + ; + in { + inherit peers privateKeyFile; #< passthrough wireguard config + interfaceNamespace = name; + ips = [ "${ip4}/32" ]; + + preSetup = '' + ${ip} netns add ${name} || (test -e /run/netns/${name} && echo "${name} already exists") + ''; + + postSetup = '' + # 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 + ${ip} addr add ${veth-host-ip}/24 dev ${name}-veth-a + ${ip} link set ${name}-veth-a up + + # move veth-b into the namespace + ${ip} link set ${name}-veth-b netns ${name} + ${in-ns} ip addr add ${veth-local-ip}/24 dev ${name}-veth-b + ${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 ${veth-host-ip} lookup ${name} 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 ${veth-local-ip} dev ${name}-veth-a proto kernel src ${veth-host-ip} metric 1002 table ${name} + # give the default route lower priority + ${ip} rule add from all lookup local pref 100 + ${ip} rule del from all lookup local 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 ${vpnDns}:53 + '' + (lib.concatStringsSep "\n" bridgeStatements); + + 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 + ''; + }; in { options = with lib; { sane.ports.ports = mkOption { - # add the `visibleTo.ovpn` option + # add the `visibleTo.ovpns` option type = types.attrsOf portOpts; }; }; @@ -79,36 +149,13 @@ in # if you `systemctl restart wireguard-wg-ovpns`, make sure to also restart any other services in `NetworkNamespacePath = .../ovpns`. # TODO: why not create the namespace as a seperate operation (nix config for that?) networking.wireguard.enable = true; - networking.wireguard.interfaces.wg-ovpns = let - ip = "${pkgs.iproute2}/bin/ip"; - in-ns = "${ip} netns exec ovpns"; - iptables = "${pkgs.iptables}/bin/iptables"; - veth-host-ip = "10.0.1.5"; - veth-local-ip = "10.0.1.6"; - vpn-ip = "185.157.162.178"; - # DNS = 46.227.67.134, 192.165.9.158, 2a07:a880:4601:10f0:cd45::1, 2001:67c:750:1:cafe:cd45::1 - vpn-dns = "46.227.67.134"; - bridgePort = port: proto: '' - ${in-ns} ${iptables} -A PREROUTING -t nat -p ${proto} --dport ${port} -m iprange --dst-range ${vpn-ip} \ - -j DNAT --to-destination ${veth-host-ip} - ''; - bridgeStatements = lib.foldlAttrs - (acc: port: portCfg: acc ++ (builtins.map (bridgePort port) portCfg.protocol)) - [] - (lib.filterAttrs - (port: portCfg: portCfg.visibleTo.ovpn) - config.sane.ports.ports - ) - ; - in { + networking.wireguard.interfaces.wg-ovpns = bridgedWireguardNamespace { privateKeyFile = config.sops.secrets.wg_ovpns_privkey.path; # wg is active only in this namespace. # run e.g. ip netns exec ovpns # sudo ip netns exec ovpns ping www.google.com - interfaceNamespace = "ovpns"; - ips = [ - "185.157.162.178/32" - ]; + name = "ovpns"; + ip4 = "185.157.162.178"; peers = [ { publicKey = "SkkEZDCBde22KTs/Hc7FWvDBfdOCQA4YtBEuC3n5KGs="; @@ -126,48 +173,9 @@ in # dynamicEndpointRefreshRestartSeconds = 5; } ]; - preSetup = '' - ${ip} netns add ovpns || (test -e /run/netns/ovpns && echo "ovpns already exists") - ''; - postShutdown = '' - ${in-ns} ip link del ovpns-veth-b || echo "couldn't delete ovpns-veth-b" - ${ip} link del ovpns-veth-a || echo "couldn't delete ovpns-veth-a" - ${ip} netns delete ovpns || echo "couldn't delete ovpns" - # restore rules/routes - ${ip} rule del from ${veth-host-ip} lookup ovpns pref 50 || echo "couldn't delete init -> ovpns rule" - ${ip} route del default via ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns || echo "couldn't delete init -> ovpns route" - ${ip} rule add from all lookup local pref 0 - ${ip} rule del from all lookup local pref 100 - ''; - postSetup = '' - # DOCS: - # - some of this approach is described here: - # - iptables primer: - # create veth pair - ${ip} link add ovpns-veth-a type veth peer name ovpns-veth-b - ${ip} addr add ${veth-host-ip}/24 dev ovpns-veth-a - ${ip} link set ovpns-veth-a up - # mv veth-b into the ovpns namespace - ${ip} link set ovpns-veth-b netns ovpns - ${in-ns} ip addr add ${veth-local-ip}/24 dev ovpns-veth-b - ${in-ns} ip link set ovpns-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 ${veth-host-ip} lookup ovpns 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 ${veth-local-ip} dev ovpns-veth-a proto kernel src ${veth-host-ip} metric 1002 table ovpns - # give the default route lower priority - ${ip} rule add from all lookup local pref 100 - ${ip} rule del from all lookup local 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 ${vpn-dns}:53 - '' + (lib.concatStringsSep "\n" bridgeStatements); + vethSubnet = "10.0.1"; #< 10.0.1.x is used for forwarding traffic between the root namespace and the VPN namespace + vpnDns = "46.227.67.134"; #< DNS requests inside the namespace are forwarded here }; # create a new routing table that we can use to proxy traffic out of the root namespace diff --git a/hosts/by-name/servo/services/coturn.nix b/hosts/by-name/servo/services/coturn.nix index 2d1e6afe..ea8d927e 100644 --- a/hosts/by-name/servo/services/coturn.nix +++ b/hosts/by-name/servo/services/coturn.nix @@ -55,7 +55,7 @@ in # protocol = [ "tcp" "udp" ]; # # visibleTo.lan = true; # # visibleTo.wan = true; - # visibleTo.ovpn = true; # forward traffic from the VPN to the root NS + # visibleTo.ovpns = true; # forward traffic from the VPN to the root NS # description = "colin-stun-turn"; # }; # "5349" = { @@ -63,7 +63,7 @@ in # protocol = [ "tcp" ]; # # visibleTo.lan = true; # # visibleTo.wan = true; - # visibleTo.ovpn = true; + # visibleTo.ovpns = true; # description = "colin-stun-turn-over-tls"; # }; # } @@ -76,7 +76,7 @@ in # protocol = [ "tcp" "udp" ]; # # visibleTo.lan = true; # # visibleTo.wan = true; - # visibleTo.ovpn = true; + # visibleTo.ovpns = true; # description = "colin-turn-${builtins.toString count}-of-${builtins.toString numPorts}"; # }; # }) diff --git a/hosts/by-name/servo/services/nginx.nix b/hosts/by-name/servo/services/nginx.nix index a61f5d64..11cee4c7 100644 --- a/hosts/by-name/servo/services/nginx.nix +++ b/hosts/by-name/servo/services/nginx.nix @@ -18,7 +18,7 @@ in protocol = [ "tcp" ]; visibleTo.lan = true; visibleTo.wan = true; - visibleTo.ovpn = true; # so that letsencrypt can procure a cert for the mx record + visibleTo.ovpns = true; # so that letsencrypt can procure a cert for the mx record description = "colin-http-uninsane.org"; }; sane.ports.ports."443" = { diff --git a/hosts/by-name/servo/services/slskd.nix b/hosts/by-name/servo/services/slskd.nix index 6b00995d..29c781aa 100644 --- a/hosts/by-name/servo/services/slskd.nix +++ b/hosts/by-name/servo/services/slskd.nix @@ -22,8 +22,7 @@ sane.ports.ports."50300" = { protocol = [ "tcp" ]; - # not visible to WAN: i run this in a separate netns - visibleTo.ovpn = true; + # visibleTo.ovpns = true; #< not needed: it runs in the ovpns namespace description = "colin-soulseek"; }; diff --git a/hosts/by-name/servo/services/transmission.nix b/hosts/by-name/servo/services/transmission.nix index 4672f2a6..078c1252 100644 --- a/hosts/by-name/servo/services/transmission.nix +++ b/hosts/by-name/servo/services/transmission.nix @@ -197,7 +197,7 @@ lib.mkIf false #< TODO: re-enable once confident of sandboxing sane.dns.zones."uninsane.org".inet.CNAME."bt" = "native"; sane.ports.ports."51413" = { protocol = [ "tcp" "udp" ]; - visibleTo.ovpn = true; + # visibleTo.ovpns = true; #< not needed: it runs in the ovpns namespace description = "colin-bittorrent"; }; } diff --git a/hosts/by-name/servo/services/trust-dns.nix b/hosts/by-name/servo/services/trust-dns.nix index 3c3ee507..e25d7805 100644 --- a/hosts/by-name/servo/services/trust-dns.nix +++ b/hosts/by-name/servo/services/trust-dns.nix @@ -11,7 +11,7 @@ in protocol = [ "udp" "tcp" ]; visibleTo.lan = true; visibleTo.wan = true; - visibleTo.ovpn = true; + visibleTo.ovpns = true; description = "colin-dns-hosting"; };