diff options
Diffstat (limited to 'modules/tunnel/ingress.nix')
| -rw-r--r-- | modules/tunnel/ingress.nix | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/modules/tunnel/ingress.nix b/modules/tunnel/ingress.nix new file mode 100644 index 0000000..a1260c8 --- /dev/null +++ b/modules/tunnel/ingress.nix @@ -0,0 +1,201 @@ +{ + config, + lib, + ... +}: + +let + inherit (lib) + imap0 + attrValues + mergeAttrsList + replaceString + concatImapStringsSep + ; + + definition = import ./definition.nix; + + inherit (definition) paths users mask; + + ownAddress = "10.123.X.1"; # ip of host running the ingress vpn (per-interface) + + addressFromTemplate = + index: template: prefix: + "${replaceString "X" (toString (index + 1)) template}/${toString prefix}"; + + ingressName = index: "tunnel-ingress${toString index}"; + egressName = "tunnel-egress0"; + egressAddress = "10.123.255.1/16"; # /16 encompasses all possible subnet addresses + egressMTU = 1400; + + egressHost = name: "${name}.rnrd.eu"; +in +{ + boot.kernel.sysctl = { + "net.ipv4.ip_forward" = 1; # allow ipv4 forwarding + }; + + networking.firewall = { + allowedUDPPorts = map (x: x.port) paths; + allowedTCPPorts = map (x: x.port) paths; + checkReversePath = "loose"; + }; + + age.secrets.ingress-key = { + file = ../../secrets/tunnel/ingress-key.age; + owner = "systemd-network"; + }; + + systemd.network = + let + mkNetdev = index: path: { + "10-${ingressName index}" = { + netdevConfig = { + Kind = "wireguard"; + Name = ingressName index; + }; + wireguardConfig = { + PrivateKeyFile = config.age.secrets.ingress-key.path; + ListenPort = path.port; + }; + wireguardPeers = map (user: { + PublicKey = user.key; + AllowedIPs = [ (addressFromTemplate index user.ip 32) ]; + }) (attrValues users); + }; + }; + + mkNetwork = index: path: { + "10-${ingressName index}" = { + name = ingressName index; + address = [ (addressFromTemplate index ownAddress 24) ]; + routingPolicyRules = [ + { + IncomingInterface = ingressName index; + Table = 100; + } + ]; + }; + }; + + ingressNetdevs = imap0 mkNetdev paths; + + ingressNetworks = imap0 mkNetwork paths; + egressNetworks = [ + { + "20-${egressName}" = { + name = egressName; + address = [ egressAddress ]; + networkConfig = { + IPv4ReversePathFilter = "loose"; + }; + linkConfig = { + ActivationPolicy = "up"; + RequiredForOnline = "no"; # does not count as online + MTUBytes = toString egressMTU; + }; + routes = [ + { + Destination = "0.0.0.0/0"; + Table = 100; + Scope = "link"; + } + ]; + }; + } + ]; + in + { + netdevs = mergeAttrsList ingressNetdevs; + networks = mergeAttrsList (ingressNetworks ++ egressNetworks); + }; + + # allow forwarding packets between egress and ingress, but avoid any snat, + # ip should always keep it's origin form, for correct egress routing. + # also adapt mss to outgoing mss value, so that we don't shatter packets. + networking.nftables.ruleset = + let + ingressInterfaces = concatImapStringsSep "\", \"" (i: _: ingressName (i - 1)) paths; + in + '' + table inet filter { + chain forward { + type filter hook forward priority 0; policy drop; + + tcp flags syn tcp option maxseg size set rt mtu + + iifname { "${ingressInterfaces}" } oifname "${egressName}" accept + iifname "${egressName}" oifname { "${ingressInterfaces}" } accept + } + } + ''; + + # sing-box is a vpn client supporting various protocols which will allow us + # to configure it in whichever way we want to avoid russian dpi. + # in this case, our communications crossing the borders are relying on vless. + services.sing-box = + let + inboundName = "tunnel-in"; + outboundName = egress: "tunnel-out-${egress}"; + in + { + enable = true; + settings = { + inbounds = [ + { + type = "tun"; + tag = inboundName; + interface_name = egressName; + address = [ egressAddress ]; + mtu = egressMTU; + stack = "gvisor"; + auto_route = false; # we route manually + strict_route = false; + endpoint_independent_nat = true; + } + ]; + + outbounds = map (path: { + type = "vless"; + flow = "xtls-rprx-vision"; + + server = egressHost path.egress; + server_port = 443; + + tag = outboundName path.egress; + uuid = path.info.uuid; + + tls = { + enabled = true; + server_name = "www.${mask}"; + + utls = { + enabled = true; + fingerprint = "chrome"; + }; + + reality = { + enabled = true; + public_key = path.info.public; + short_id = path.info.short; + }; + }; + }) paths; + + route = { + rules = imap0 (index: path: { + inbound = inboundName; + source_ip_cidr = [ (addressFromTemplate index "10.123.X.0" 24) ]; + outbound = outboundName path.egress; + }) paths; + + auto_detect_interface = true; + }; + + log = { + level = "debug"; + timestamp = true; + }; + }; + }; +} |
