{ lib, config, pkgs, ... }: let cfg = config.networking.vpn-netns; inherit (cfg.portForwarding) leaseDuration updateDuration endpoint; forwardedServices = lib.filterAttrs ( _: value: value.enable && value.portForwarding.enable ) cfg.encapsulatedServices; assignCount = { acc, temporaryPort }: name: value: { acc = acc ++ [ { inherit name temporaryPort; inherit (value.portForwarding) updateScript; } ]; temporaryPort = temporaryPort + 1; }; forwardedServicesWithTmpPort = lib.foldlAttrs assignCount { acc = [ ]; temporaryPort = cfg.portForwarding.temporaryPortRange.from; } forwardedServices; serviceList = lib.mapAttrsToList (name: _: name + ".service") forwardedServices; in lib.mkIf (forwardedServices != { } && cfg.portForwarding.enable) { assertions = [ { assertion = forwardedServicesWithTmpPort.temporaryPort <= cfg.portForwarding.temporaryPortRange.to + 1; message = '' vpn forwarding: not enough temporary ports. Increase the range of vpn.endpoint.temporaryPortRange. ''; } ]; systemd = { services.natpmpc-lease = { description = "Request VPN port forwarding leases."; wantedBy = serviceList; after = [ "wireguard.target" ]; wants = [ "wireguard.target" ]; # preStart = "sleep 3"; path = with pkgs; [ libnatpmp iptables ]; script = lib.concatMapStrings ( { temporaryPort, name, updateScript, }: let temporaryPort' = toString temporaryPort; leaseDuration' = toString leaseDuration; in '' natpmpc -g ${endpoint} -a 1 ${temporaryPort'} udp ${leaseDuration'} > /dev/null PORT=$(natpmpc -g ${endpoint} -a 1 ${temporaryPort'} tcp ${leaseDuration'} | grep -oP "Mapped public port \K\d+") iptables -t nat -C PREROUTING -p udp --dport ${temporaryPort'} -j REDIRECT --to-port "$PORT" 2>/dev/null || ( iptables -t nat -A PREROUTING -p tcp --dport ${temporaryPort'} -j REDIRECT --to-port "$PORT"; iptables -t nat -A PREROUTING -p udp --dport ${temporaryPort'} -j REDIRECT --to-port "$PORT" ) echo "Changing settings for service ${name} with port $PORT, if needed" ${updateScript} '' ) forwardedServicesWithTmpPort.acc; serviceConfig = { Type = "oneshot"; TimeoutStartSec = 60; # toString (2 * updateDuration); NetworkNamespacePath = "/var/run/netns/${cfg.interfaceNamespace}"; }; }; timers.natpmpc-lease = { wantedBy = [ "timers.target" ]; timerConfig = { OnUnitActiveSec = toString (leaseDuration - updateDuration); Persistent = "yes"; }; }; }; }