{
  config,
  lib,
  ...
}: {
  options.mj.services.friendlyport = with lib.types; {
    ports = lib.mkOption {
      type = listOf (submodule {
        options = {
          subnets = lib.mkOption {type = listOf str;};
          tcp = lib.mkOption {
            type = listOf int;
            default = [];
          };
          udp = lib.mkOption {
            type = listOf int;
            default = [];
          };
        };
      });
    };
  };

  config = let
    inherit (config.mj.services.friendlyport) ports;

    mkAdd = proto: subnets: ints: let
      subnetsS = builtins.concatStringsSep "," subnets;
      intsS = builtins.concatStringsSep "," (map builtins.toString ints);
    in
      if builtins.length ints == 0
      then ""
      else "iptables -A INPUT -p ${proto} --match multiport --dports ${intsS} --source ${subnetsS} -j ACCEPT";

    startTCP = map (attr: mkAdd "tcp" attr.subnets attr.tcp) ports;
    startUDP = map (attr: mkAdd "udp" attr.subnets attr.udp) ports;

    # TODO: when stopping the firewall, systemd uses the old ports. So this is a two-phase process.
    # How to stop the old one and start the new one?
    mkDel = proto: subnets: ints: let
      subnetsS = builtins.concatStringsSep "," subnets;
      intsS = builtins.concatStringsSep "," (map builtins.toString ints);
    in
      if builtins.length ints == 0
      then ""
      else "iptables -D INPUT -p ${proto} --match multiport --dports ${intsS} --source ${subnetsS} -j ACCEPT || :";

    stopTCP = map (attr: mkDel "tcp" attr.subnets attr.tcp) ports;
    stopUDP = map (attr: mkDel "udp" attr.subnets attr.udp) ports;
  in {
    networking.firewall.extraCommands = lib.concatLines (startTCP ++ startUDP);
    networking.firewall.extraStopCommands = lib.concatLines (stopTCP ++ stopUDP);
  };
}