Help setting up port-forwarding with Transmission and ProtonVPN on NixOS

Yes, it was the IPv6!

If anyone runs into the same issue, my final working setup is:

# Transmission peer ports don't work when IPv6 is enabled.
networking.enableIPv6 = false;

services.transmission = {
  enable = true;
  openRPCPort = true;
  settings = {
    port-forwarding-enabled = false;
    rpc-bind-address = "0.0.0.0";
    rpc-whitelist = "127.0.0.*,192.168.*.*";
  };
};

systemd.timers."transmission-port-forwarding" = {
  wantedBy = ["timers.target"];
  timerConfig = {
    OnBootSec = "45s";
    OnUnitActiveSec = "45s";
    Unit = "transmission-port-forwarding.service";
  };
};

systemd.services."transmission-port-forwarding" = {
  serviceConfig = {
    Type = "oneshot";
    User = "root";
  };
  script = ''
    set -u

    renew_port() {
      protocol="$1"
      port_file="$HOME/.local/state/transmission-$protocol-port"

      result="$(${pkgs.libnatpmp}/bin/natpmpc -a 1 0 "$protocol" 60 -g 10.2.0.1)"
      echo "$result"

      new_port="$(echo "$result" | ${pkgs.ripgrep}/bin/rg --only-matching --replace '$1' 'Mapped public port (\d+) protocol ... to local port 0 lifetime 60')"
      old_port="$(cat "$port_file")"
      echo "Mapped new $protocol port $new_port, old one was $old_port."
      echo "$new_port" >"$port_file"

      if ${pkgs.iptables}/bin/iptables -C INPUT -p "$protocol" --dport "$new_port" -j ACCEPT
      then
        echo "New $protocol port $new_port already open, not opening again."
      else
        echo "Opening new $protocol port $new_port."
        ${pkgs.iptables}/bin/iptables -I INPUT -p "$protocol" --dport "$new_port" -j ACCEPT
      fi

      if [ "$protocol" = tcp ]
      then
        echo "Telling transmission to listen on peer port $new_port."
        ${pkgs.transmission}/bin/transmission-remote --port "$new_port"
      fi

      if [ "$new_port" -eq "$old_port" ]
      then
        echo "New $protocol port $new_port is the same as old port $old_port, not closing old port."
      else
        if ${pkgs.iptables}/bin/iptables -C INPUT -p "$protocol" --dport "$old_port" -j ACCEPT
        then
          echo "Closing old $protocol port $old_port."
          ${pkgs.iptables}/bin/iptables -D INPUT -p "$protocol" --dport "$old_port" -j ACCEPT
        else
          echo "Old $protocol port $old_port not open, not attempting to close."
        fi
      fi
    }

    renew_port udp
    renew_port tcp
  '';
};
1 Like