Change:
We already have in , but only with iptables support. It would be nice to have the nftables version available as well.
Being worked on in , I'm trying to test myself but couldn't make it to work yet.
I think the PR is ready now, testing welcome!
Maybe add an option for will be better?

Discussion:
listening_ip=${range} '') cfg.internalIPs} <ins> ${lib.optionalString (firewall == "nftables") '' upnp_table_name=miniupnpd upnp_nat_table_name=miniupnpd ''} </ins> ${cfg.appendConfig} ''; <ins> firewall = if config.networking.nftables.enable then "nftables" else "iptables"; miniupnpd = pkgs.miniupnpd.override { inherit firewall; }; firewallScripts = lib.optionals (firewall == "iptables") ([ "iptables"] ++ lib.optional (config.networking.enableIPv6) "ip6tables"); </ins> in { options = {

}; config = mkIf cfg.enable { <del> networking.firewall.extraCommands = '' ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface} ''; </del> <ins> networking.firewall.extraCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "n" (map (fw: '' EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_init.sh '') firewallScripts)); networking.firewall.extraStopCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "n" (map (fw: '' EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_removeall.sh '') firewallScripts)); </ins> <del> networking.firewall.extraStopCommands = '' ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface} ''; </del> <ins> networking.nftables = lib.mkIf (firewall == "nftables") { # see nft_init in ${miniupnpd-nftables}/etc/miniupnpd tables.miniupnpd = { family = "inet"; # The following is omitted because it's expected that the firewall is to be responsible for it. # # chain forward { #   type filter hook forward priority filter; policy drop; #   jump miniupnpd # } # # Otherwise, it quickly gets ugly with (potentially) two forward chains with "policy drop". # This means the chain "miniupnpd" never actually gets triggered and is simply there to satisfy # miniupnpd. If you're doing it yourself (without networking.firewall), the easiest way to get # it to work is adding a rule "ct status dnat accept" - this is what networking.firewall does. # If you don't want to simply accept forwarding for all "ct status dnat" packets, override # upnp_table_name with whatever your table is, create a chain "miniupnpd" in your table and # jump into it from your forward chain. content = '' chain miniupnpd {} chain prerouting_miniupnpd { type nat hook prerouting priority dstnat; policy accept; } chain postrouting_miniupnpd { type nat hook postrouting priority srcnat; policy accept; } ''; }; }; </ins> systemd.services.miniupnpd = { description = "MiniUPnP daemon"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { <del> ExecStart = "${pkgs.miniupnpd}/bin/miniupnpd -f ${configFile}"; </del> <ins> ExecStart = "${miniupnpd}/bin/miniupnpd -f ${configFile}"; </ins> PIDFile = "/run/miniupnpd.pid"; Type = "forking"; };

unbound = handleTest ./unbound.nix {}; unifi = handleTest ./unifi.nix {}; unit-php = handleTest ./web-servers/unit-php.nix {}; <del> upnp = handleTest ./upnp.nix {}; </del> <ins> upnp.iptables = handleTest ./upnp.nix { useNftables = false; }; upnp.nftables = handleTest ./upnp.nix { useNftables = true; }; </ins> uptermd = handleTest ./uptermd.nix {}; uptime-kuma = handleTest ./uptime-kuma.nix {}; usbguard = handleTest ./usbguard.nix {};

# this succeeds an external client will try to connect to the port # mapping. <del> import ./make-test-python.nix ({ pkgs, ... }: </del> <ins> import ./make-test-python.nix ({ pkgs, useNftables, ... }: </ins> let internalRouterAddress = "192.168.3.1";

networking.nat.enable = true; networking.nat.internalInterfaces = [ "eth2" ]; networking.nat.externalInterface = "eth1"; <ins> networking.nftables.enable = useNftables; </ins> networking.firewall.enable = true; networking.firewall.trustedInterfaces = [ "eth2" ]; networking.interfaces.eth1.ipv4.addresses = [

# Wait for network and miniupnpd. router.wait_for_unit("network-online.target") # $router.wait_for_unit("nat") <del> router.wait_for_unit("firewall.service") </del> <ins> router.wait_for_unit("${if useNftables then "nftables" else "firewall"}.service") </ins> router.wait_for_unit("miniupnpd") client1.wait_for_unit("network-online.target")

{ stdenv, lib, fetchurl, iptables-legacy, libuuid, openssl, pkg-config <del> , which, iproute2, gnused, coreutils, gawk, makeWrapper </del> <ins> , which, iproute2, gnused, coreutils, gnugrep, gawk, makeWrapper </ins> , nixosTests <ins> , firewall ? "iptables", nftables, libmnl, libnftnl </ins> }: let <del> scriptBinEnv = lib.makeBinPath [ which iproute2 iptables-legacy gnused coreutils gawk ]; </del> <ins> scriptBinEnv = lib.makeBinPath { iptables = [ # needed for dirname in ip{,6}tables_*.sh coreutils # used in miniupnpd_functions.sh: which iproute2 iptables-legacy gnused gnugrep gawk ]; nftables = [ # needed for dirname in nft_*.sh & cat in nft_init.sh coreutils # used in miniupnpd_functions.sh: which nftables ]; }.${firewall}; </ins> in stdenv.mkDerivation rec { pname = "miniupnpd";

sha256 = "sha256-b9cBn5Nv+IxB58gi9G8QtRvXLWZZePZYZIPedbMMNr8="; }; <del> buildInputs = [ iptables-legacy libuuid openssl ]; </del> <ins> buildInputs = [ iptables-legacy libuuid openssl ] ++ lib.optionals (firewall == "nftables") [ libmnl libnftnl ]; </ins> nativeBuildInputs= [ pkg-config makeWrapper ]; # ./configure is not a standard configure file, errors with: # Option not recognized : --prefix= dontAddPrefix = true; <ins> configureFlags = [ "--firewall=${firewall}" # allow using various config options "--ipv6" "--leasefile" "--regex" "--vendorcfg" # hardening "--portinuse" ]; </ins> installFlags = [ "PREFIX=$(out)" "INSTALLPREFIX=$(out)" ]; <del> postFixup = '' for script in $out/etc/miniupnpd/ip{,6}tables_{init,removeall}.sh do wrapProgram $script --set PATH '${scriptBinEnv}:$PATH' done ''; </del> <ins> postFixup = { # Ideally we'd prefer using system's config.firewall.package here for iptables, # however for some reason switching --prefix to --suffix breaks the script iptables = '' for script in $out/etc/miniupnpd/ip{,6}tables_{init,removeall}.sh do wrapProgram $script --prefix PATH : '${scriptBinEnv}:$PATH' done ''; nftables = '' for script in $out/etc/miniupnpd/nft_{delete_chain,flush,init,removeall}.sh do wrapProgram $script --suffix PATH : '${scriptBinEnv}:$PATH' done ''; }.${firewall}; </ins> passthru.tests = { bittorrent-integration = nixosTests.bittorrent;

description = "A daemon that implements the UPnP Internet Gateway Device (IGD) specification"; platforms = platforms.linux; license = licenses.bsd3; <ins> mainProgram = "miniupnpd"; </ins> }; }

miniupnpd = callPackage ../tools/networking/miniupnpd { }; <ins> miniupnpd-nftables = callPackage ../tools/networking/miniupnpd { firewall = "nftables"; }; </ins> miniball = callPackage ../development/libraries/miniball { }; minijail = callPackage ../tools/system/minijail { };