# TODO: Dir creation and file permissions in nix { pkgs, config, lib, ... }: with lib; let cfg = config.util-nixarr.upnp; in { options.util-nixarr.upnp = { enable = mkEnableOption "Enable port forwarding using UPNP."; openTcpPorts = mkOption { type = with types; listOf port; default = []; description = '' What TCP ports to open using UPNP. ''; example = [46382 38473]; }; openUdpPorts = mkOption { type = with types; listOf port; default = []; description = '' What UDP ports to open using UPNP. ''; example = [46382 38473]; }; }; config = mkIf cfg.enable { # UPNPC firewall access, if not set, then upnpc will fail with "No IGD # UPnP Device found !" # # Alternatively, I also tried allowing all traffic from the router. But # I assume that the official way is cleaner/more secure: # ```nix # networking.firewall.extraCommands = '' # iptables -I INPUT -p udp -s 192.168.1.1 -j ACCEPT # iptables -I OUTPUT -p udp -d 192.168.1.1 -j ACCEPT # ''; # ``` # # See: # https://github.com/miniupnp/miniupnp/blob/8ced59d384de13689d3b1c32405bcb562030b241/miniupnpc/README # # TODO: Understand this properly networking.firewall.extraCommands = '' # Rules for IPv4: ${pkgs.ipset}/bin/ipset -exist create upnp hash:ip,port timeout 3 iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT iptables -A INPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j ACCEPT # Rules for IPv6: ${pkgs.ipset}/bin/ipset -exist create upnp6 hash:ip,port timeout 3 family inet6 ip6tables -A OUTPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist ip6tables -A OUTPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist ip6tables -A INPUT -p udp -m set --match-set upnp6 dst,dst -j ACCEPT ip6tables -A INPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j ACCEPT ip6tables -A INPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j ACCEPT ''; systemd = { services.upnpc = let upnp-ports = pkgs.writeShellApplication { name = "upnp-ports"; runtimeInputs = with pkgs; [miniupnpc]; text = ( strings.concatMapStrings (x: "upnpc -r ${builtins.toString x} UDP" + "\n") cfg.openUpdPorts ++ strings.concatMapStrings (x: "upnpc -r ${builtins.toString x} TCP" + "\n") cfg.openTcpPorts ++ ''echo "Successfully requested upnp ports to be opened".'' ); }; in mkIf cfg.enable { enable = true; description = "Sets port on router"; script = "${upnp-ports}/bin/upnp-ports"; serviceConfig = { User = "root"; Type = "oneshot"; }; }; timers = { upnpc = mkIf cfg.enable { description = "Sets port on router"; wantedBy = ["timers.target"]; timerConfig = { OnCalendar = "hourly"; Persistent = "true"; # Run service immediately if last window was missed RandomizedDelaySec = "5m"; # Run service OnCalendar +- 1h }; }; }; }; }; }