1 { config, lib, pkgs, ... }:
6 cfg = config.services.miniupnpd;
7 configFile = pkgs.writeText "miniupnpd.conf" ''
8 ext_ifname=${cfg.externalInterface}
9 enable_natpmp=${if cfg.natpmp then "yes" else "no"}
10 enable_upnp=${if cfg.upnp then "yes" else "no"}
12 ${concatMapStrings (range: ''
16 ${lib.optionalString (firewall == "nftables") ''
17 upnp_table_name=miniupnpd
18 upnp_nat_table_name=miniupnpd
23 firewall = if config.networking.nftables.enable then "nftables" else "iptables";
24 miniupnpd = pkgs.miniupnpd.override { inherit firewall; };
25 firewallScripts = lib.optionals (firewall == "iptables")
26 ([ "iptables"] ++ lib.optional (config.networking.enableIPv6) "ip6tables");
30 services.miniupnpd = {
31 enable = mkEnableOption "MiniUPnP daemon";
33 externalInterface = mkOption {
36 Name of the external interface.
40 internalIPs = mkOption {
41 type = types.listOf types.str;
42 example = [ "192.168.1.1/24" "enp1s0" ];
44 The IP address ranges to listen on.
48 natpmp = mkEnableOption "NAT-PMP support";
54 Whether to enable UPNP support.
58 appendConfig = mkOption {
62 Configuration lines appended to the MiniUPnP config.
68 config = mkIf cfg.enable {
69 networking.firewall.extraCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "\n" (map (fw: ''
70 EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_init.sh
71 '') firewallScripts));
73 networking.firewall.extraStopCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "\n" (map (fw: ''
74 EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_removeall.sh
75 '') firewallScripts));
77 networking.nftables = lib.mkIf (firewall == "nftables") {
78 # see nft_init in ${miniupnpd-nftables}/etc/miniupnpd
81 # The following is omitted because it's expected that the firewall is to be responsible for it.
84 # type filter hook forward priority filter; policy drop;
88 # Otherwise, it quickly gets ugly with (potentially) two forward chains with "policy drop".
89 # This means the chain "miniupnpd" never actually gets triggered and is simply there to satisfy
90 # miniupnpd. If you're doing it yourself (without networking.firewall), the easiest way to get
91 # it to work is adding a rule "ct status dnat accept" - this is what networking.firewall does.
92 # If you don't want to simply accept forwarding for all "ct status dnat" packets, override
93 # upnp_table_name with whatever your table is, create a chain "miniupnpd" in your table and
94 # jump into it from your forward chain.
97 chain prerouting_miniupnpd {
98 type nat hook prerouting priority dstnat; policy accept;
100 chain postrouting_miniupnpd {
101 type nat hook postrouting priority srcnat; policy accept;
107 systemd.services.miniupnpd = {
108 description = "MiniUPnP daemon";
109 after = [ "network.target" ];
110 wantedBy = [ "multi-user.target" ];
112 ExecStart = "${miniupnpd}/bin/miniupnpd -f ${configFile}";
113 PIDFile = "/run/miniupnpd.pid";