losurdo: transmission: use transmission_4
[sourcephile-nix.git] / nixos / modules / services / networking / upnpc.nix
index abe9e20e66d92ed92c0790a37b3868ecee9114a1..3ff2053ca6fa316bd1080dc93aaeb40c3144fe8f 100644 (file)
@@ -1,7 +1,7 @@
 { pkgs, lib, config, ... }:
 with lib;
 let
-  inherit (config.users) users;
+  inherit (config.users) users groups;
   cfg = config.services.upnpc;
   getInfo = ''
     while IFS=: read -r k v; do
@@ -17,107 +17,160 @@ let
   '';
 in
 {
-options.services.upnpc = {
-  redirections = mkOption {
-    description = "UPnP redirections to request.";
-    default = [];
-    type = types.listOf (types.submodule ({config, ...}: {
-      options.externalPort = mkOption {
-        description = "External port to open on the redirecting device.";
-        type = types.port;
-      };
-      options.internalPort = mkOption {
-        description = "Internal port, target of the redirection.";
-        type = types.port;
-        default = config.externalPort;
-      };
-      options.protocol = mkOption {
-        description = "Protocol to redirect.";
-        type = with types; enum ["TCP" "UDP"];
-        default = "TCP";
-      };
-      options.description = mkOption {
-        description = "Description of the port mapping";
-        type = types.str;
-        default = "";
-      };
-      options.duration = mkOption {
-        description = "Duration of the redirection, in seconds. 0 means indefinitely.";
-        type = types.int;
-        default = 0;
-      };
-      options.maintainPeriod = mkOption {
-        description = "Period (in seconds) between runs to maintain the redirection.";
-        type = with types; nullOr int;
-        default = if config.duration > 0 then config.duration / 2 else null;
-        defaultText = "if duration > 0 then duration / 2 else null";
-      };
-      options.override = mkOption {
-        description = "Try to override the redirection in case of conflict in mapping entry.";
-        type = types.bool;
-        default = true;
-      };
-      options.service = mkOption {
-        description = "Configuration specific to the systemd service handling this UPnP redirecting.";
-        type = types.attrs;
-        default = {};
-      };
-    }));
+  options.services.upnpc = {
+    enable = mkEnableOption "UPnP redirections";
+    redirections = mkOption {
+      description = "UPnP redirections to request.";
+      default = [ ];
+      type = types.listOf (types.submodule ({ config, ... }: {
+        options.externalPort = mkOption {
+          description = "External port to open on the redirecting device.";
+          type = types.port;
+        };
+        options.internalPort = mkOption {
+          description = "Internal port, target of the redirection.";
+          type = types.port;
+          default = config.externalPort;
+        };
+        options.protocol = mkOption {
+          description = "Protocol to redirect.";
+          type = with types; enum [ "TCP" "UDP" ];
+          default = "TCP";
+        };
+        options.description = mkOption {
+          description = "Description of the port mapping";
+          type = types.str;
+          default = "";
+        };
+        options.duration = mkOption {
+          description = "Duration of the redirection, in seconds. 0 means indefinitely.";
+          type = types.int;
+          default = 0;
+        };
+        options.maintainPeriod = mkOption {
+          description = "Period (in seconds) between runs to maintain the redirection.";
+          type = with types; nullOr int;
+          default = if config.duration > 0 then config.duration / 2 else null;
+          defaultText = "if duration > 0 then duration / 2 else null";
+        };
+        options.override = mkOption {
+          description = "Try to override the redirection in case of conflict in mapping entry.";
+          type = types.bool;
+          default = true;
+        };
+        options.service = mkOption {
+          description = "Configuration specific to the systemd service handling this UPnP redirecting.";
+          type = types.attrs;
+          default = { };
+        };
+      }));
+    };
   };
-};
-config = {
-  systemd.services = listToAttrs (map (r:
-    nameValuePair "upnpc-${toString r.internalPort}" (mkMerge [
-      { description = "UPnP ${toString r.internalPort}";
-        after = [ "network-online.target" ];
-        #wantedBy = [ "multi-user.target" ];
-        path = [ pkgs.miniupnpc ];
-        serviceConfig = {
-          Type = if r.maintainPeriod == null then "oneshot" else "simple";
-          RemainAfterExit = r.maintainPeriod == null;
-          ExecStart = pkgs.writeShellScript "upnpc-start-${toString r.internalPort}" ''
-            set -eu
-            redirect () {
-              result=
-              while IFS= read -r line; do
-                echo >&2 -E "$line"
-                case $line in
-                  (*" is redirected to internal $localIP:${toString r.internalPort}"*) result=ok ;;
-                  (*ConflictInMappingEntry*) result=conflict ;;
-                esac
-              done <<EOF
-            $(upnpc -u "$desc" ${optionalString (r.description != "") "-e \"${r.description}\""} \
-              -a "$localIP" ${toString r.internalPort} ${toString r.externalPort} ${r.protocol} ${toString r.duration} 2>&1)
-            EOF
-            }
-            while true; do
-              ${getInfo}
-              redirect
-              ${optionalString r.override ''
-                test "$result" != conflict || {
-                  upnpc -u "$desc" -d ${toString r.externalPort} ${r.protocol}
-                  redirect
+  config = mkIf cfg.enable {
+    systemd.services = listToAttrs (map
+      (r:
+        nameValuePair "upnpc-${toString r.internalPort}" (mkMerge [
+          {
+            description = "UPnP ${toString r.internalPort}";
+            after = [ "network-pre.target" ];
+            #wantedBy = [ "multi-user.target" ];
+            path = [ pkgs.miniupnpc ];
+            serviceConfig = {
+              Type = if r.maintainPeriod == null then "oneshot" else "simple";
+              RemainAfterExit = r.maintainPeriod == null;
+              ExecStart = pkgs.writeShellScript "upnpc-start-${toString r.internalPort}" ''
+                set -eu
+                redirect () {
+                  result=
+                  while IFS= read -r line; do
+                    echo >&2 -E "$line"
+                    case $line in
+                      (*" is redirected to internal $localIP:${toString r.internalPort}"*) result=ok ;;
+                      (*ConflictInMappingEntry*) result=conflict ;;
+                    esac
+                  done <<EOF
+                $(upnpc -u "$desc" ${optionalString (r.description != "") "-e \"${r.description}\""} \
+                  -a "$localIP" ${toString r.internalPort} ${toString r.externalPort} ${r.protocol} ${toString r.duration} 2>&1)
+                EOF
                 }
-              ''}
-              case $result in
-                (ok) ${if r.maintainPeriod == null then "break" else "sleep " + toString r.maintainPeriod} ;;
-                (*) exit 1 ;;
-              esac
-            done
-          '';
-          ExecStop = "${pkgs.miniupnpc}/bin/upnpc -d ${toString r.externalPort} ${r.protocol}";
-          Restart = "on-failure";
-          RestartSec = mkDefault r.maintainPeriod;
-          DynamicUser = true;
-          User = users."upnpc".name;
-        };
-      }
-      r.service
-    ])
-    ) cfg.redirections);
+                while true; do
+                  ${getInfo}
+                  redirect
+                  ${optionalString r.override ''
+                    test "$result" != conflict || {
+                      upnpc -u "$desc" -d ${toString r.externalPort} ${r.protocol}
+                      redirect
+                    }
+                  ''}
+                  case $result in
+                    (ok) ${if r.maintainPeriod == null then "break" else "sleep " + toString r.maintainPeriod} ;;
+                    (*) exit 1 ;;
+                  esac
+                done
+              '';
+              ExecStop = "${pkgs.miniupnpc}/bin/upnpc -d ${toString r.externalPort} ${r.protocol}";
+              Restart = "on-failure";
+              DynamicUser = true;
+              User = users."upnpc".name;
+            } // lib.optionalAttrs (r.maintainPeriod != null) {
+              RestartSec = mkDefault r.maintainPeriod;
+            };
+          }
+          r.service
+        ])
+      )
+      cfg.redirections);
+
+    environment.systemPackages = [ pkgs.miniupnpc ];
 
-  # This enables to match on the uid in the firewall.
-  users.users."upnpc".isSystemUser = true;
-};
-meta.maintainers = with maintainers; [ julm ];
+    # This enables to match on the uid in the firewall.
+    users.users."upnpc" = {
+      isSystemUser = true;
+      group = groups."upnpc".name;
+    };
+    users.groups."upnpc" = { };
+    networking.nftables.ruleset =
+      lib.optionalString (cfg.redirections != [ ]) ''
+        table inet filter {
+          # A set containing the udp port(s) to which SSDP replies are allowed.
+          set upnpc-ssdp {
+            type inet_service
+            timeout 5s
+          }
+          chain input-net {
+            # Create a rule for accepting any SSDP packets going to a remembered port.
+            udp dport @upnpc-ssdp counter accept comment "SSDP answer"
+          }
+          chain output-net {
+            skuid ${users.upnpc.name} \
+              tcp dport ssdp \
+              counter accept \
+              comment "SSDP automatic opening"
+            skuid ${users.upnpc.name} \
+              ip daddr 239.255.255.250 udp dport ssdp \
+              set add udp sport @upnpc-ssdp \
+              comment "SSDP automatic opening"
+            skuid ${users.upnpc.name} \
+              ip daddr 239.255.255.250 udp dport ssdp \
+              counter accept \
+              comment "SSDP"
+          }
+        }
+      '' + lib.optionalString config.networking.enableIPv6 ''
+        table inet filter {
+          chain output-net {
+            skuid ${users.upnpc.name} \
+              ip6 daddr { FF02::C, FF05::C, FF08::C, FF0E::C } \
+              udp dport ssdp \
+              set add udp sport @upnpc-ssdp \
+              comment "SSDP automatic opening"
+            skuid ${users.upnpc.name} \
+              ip6 daddr { FF02::C, FF05::C, FF08::C, FF0E::C } \
+              udp dport ssdp \
+              counter accept comment "SSDP"
+          }
+        }
+      '';
+  };
+  meta.maintainers = with maintainers; [ julm ];
 }