Port forwarding for transmission

This commit is contained in:
Zuckerberg 2023-03-12 19:50:29 -06:00
parent 7159868b57
commit 42c0dcae2d
2 changed files with 94 additions and 46 deletions

View File

@ -10,10 +10,9 @@
# TODO handle potential errors (or at least print status, success, and failures to the console) # TODO handle potential errors (or at least print status, success, and failures to the console)
# TODO handle 2 month limit for port # TODO handle 2 month limit for port
# TODO handle VPN container with different name # TODO handle VPN container with different name
# TODO parameterize names of systemd services so that multiple wg VPNs could coexist in theory easier # - TODO parameterize names of systemd services so that multiple wg VPNs could coexist in theory easier
# - TODO implement this module such that the wireguard VPN doesn't have to live in a container
# TODO add some variance to the port forward timer # TODO add some variance to the port forward timer
# TODO allow not forwarding a port
# TODO implement this module such that the wireguard VPN doesn't have to live in a container
# TODO look at wg-gen script for example of looking up a random server in a region and connect to that (user should not need to specify IP addr) # TODO look at wg-gen script for example of looking up a random server in a region and connect to that (user should not need to specify IP addr)
with builtins; with builtins;
@ -27,9 +26,22 @@ let
# PIA_TOKEN only lasts 24hrs # PIA_TOKEN only lasts 24hrs
PIA_TOKEN=`curl -s -u "$PIA_USER:$PIA_PASS" https://www.privateinternetaccess.com/gtoken/generateToken | jq -r '.token'` PIA_TOKEN=`curl -s -u "$PIA_USER:$PIA_PASS" https://www.privateinternetaccess.com/gtoken/generateToken | jq -r '.token'`
''; '';
refreshPIAPort = ''
signature=`sed '1q;d' /tmp/${cfg.interfaceName}-port-renewal`
payload=`sed '2q;d' /tmp/${cfg.interfaceName}-port-renewal`
bind_port_response=`curl -Gs -m 5 --connect-to "${cfg.serverHostname}::${cfg.serverIp}:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "payload=$payload" --data-urlencode "signature=$signature" "https://${cfg.serverHostname}:19999/bindPort"`
'';
portForwarding = cfg.forwardPortForTransmission || cfg.forwardedPort != null;
in { in {
options.pia.wireguard = { options.pia.wireguard = {
enable = lib.mkEnableOption "Enable private internet access"; enable = lib.mkEnableOption "Enable private internet access";
wireguardListenPort = lib.mkOption {
type = lib.types.port;
description = "The port wireguard listens on for this VPN connection";
default = 51820;
};
serverHostname = lib.mkOption { serverHostname = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "zurich406"; default = "zurich406";
@ -43,30 +55,36 @@ in {
default = "piaw"; default = "piaw";
}; };
forwardedPort = lib.mkOption { forwardedPort = lib.mkOption {
type = lib.types.port; type = lib.types.nullOr lib.types.port;
description = "The port to redirect port forwarded TCP VPN traffic too"; description = "The port to redirect port forwarded TCP VPN traffic too";
default = 15050; default = null;
}; };
# TODO allow disabling this forwardPortForTransmission = lib.mkEnableOption "PIA port forwarding for transmission should be performed.";
portForwarding = lib.mkEnableOption "Enables PIA port fowarding";
# TODO implement this such that the wireguard VPN doesn't have to live in a container
useInVPNContainer = lib.mkEnableOption "Configures the PIA WG VPN for use in the VPN container";
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.forwardPortForTransmission != (cfg.forwardedPort != null);
message = ''
The PIA forwarded port cannot simultaneously be used by transmission and redirected to another port.
'';
}
];
vpn-container.mounts = [ "/tmp/${cfg.interfaceName}.conf" "/tmp/${cfg.interfaceName}-address.conf" ]; vpn-container.mounts = [ "/tmp/${cfg.interfaceName}.conf" "/tmp/${cfg.interfaceName}-address.conf" ];
containers.vpn.interfaces = [ cfg.interfaceName ]; containers.vpn.interfaces = [ cfg.interfaceName ];
# TODO: while this is much better than "loose" networking, it seems to have issues with firewall restarts
# allow traffic for wireguard interface to pass since wireguard trips up rpfilter # allow traffic for wireguard interface to pass since wireguard trips up rpfilter
# networking.firewall = { # networking.firewall = {
# extraCommands = '' # extraCommands = ''
# ip46tables -t raw -I nixos-fw-rpfilter -p udp -m udp --sport 51820 -j RETURN # ip46tables -t raw -I nixos-fw-rpfilter -p udp -m udp --sport ${toString cfg.wireguardListenPort} -j RETURN
# ip46tables -t raw -I nixos-fw-rpfilter -p udp -m udp --dport 51820 -j RETURN # ip46tables -t raw -I nixos-fw-rpfilter -p udp -m udp --dport ${toString cfg.wireguardListenPort} -j RETURN
# ''; # '';
# extraStopCommands = '' # extraStopCommands = ''
# ip46tables -t raw -D nixos-fw-rpfilter -p udp -m udp --sport 51820 -j RETURN || true # ip46tables -t raw -D nixos-fw-rpfilter -p udp -m udp --sport ${toString cfg.wireguardListenPort} -j RETURN || true
# ip46tables -t raw -D nixos-fw-rpfilter -p udp -m udp --dport 51820 -j RETURN || true # ip46tables -t raw -D nixos-fw-rpfilter -p udp -m udp --dport ${toString cfg.wireguardListenPort} -j RETURN || true
# ''; # '';
# }; # };
networking.firewall.checkReversePath = "loose"; networking.firewall.checkReversePath = "loose";
@ -98,11 +116,14 @@ in {
${getPIAToken} ${getPIAToken}
# generate wireguard keys
privKey=$(wg genkey) privKey=$(wg genkey)
pubKey=$(echo "$privKey" | wg pubkey) pubKey=$(echo "$privKey" | wg pubkey)
# authorize our WG keys with the PIA server we are about to connect to
wireguard_json=`curl -s -G --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "pt=$PIA_TOKEN" --data-urlencode "pubkey=$pubKey" https://$WG_HOSTNAME:1337/addKey` wireguard_json=`curl -s -G --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "pt=$PIA_TOKEN" --data-urlencode "pubkey=$pubKey" https://$WG_HOSTNAME:1337/addKey`
# create wg-quick config file
rm -f /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf rm -f /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
touch /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf touch /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
chmod 700 /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf chmod 700 /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
@ -110,13 +131,15 @@ in {
[Interface] [Interface]
# Address = $(echo "$wireguard_json" | jq -r '.peer_ip') # Address = $(echo "$wireguard_json" | jq -r '.peer_ip')
PrivateKey = $privKey PrivateKey = $privKey
ListenPort = 51820 ListenPort = ${toString cfg.wireguardListenPort}
[Peer] [Peer]
PersistentKeepalive = 25 PersistentKeepalive = 25
PublicKey = $(echo "$wireguard_json" | jq -r '.server_key') PublicKey = $(echo "$wireguard_json" | jq -r '.server_key')
AllowedIPs = 0.0.0.0/0 AllowedIPs = 0.0.0.0/0
Endpoint = $WG_SERVER_IP:$(echo "$wireguard_json" | jq -r '.server_port') Endpoint = $WG_SERVER_IP:$(echo "$wireguard_json" | jq -r '.server_port')
" >> /tmp/${cfg.interfaceName}.conf " >> /tmp/${cfg.interfaceName}.conf
# create file storing the VPN ip address PIA assigned to us
echo "$wireguard_json" | jq -r '.peer_ip' >> /tmp/${cfg.interfaceName}-address.conf echo "$wireguard_json" | jq -r '.peer_ip' >> /tmp/${cfg.interfaceName}-address.conf
# Create wg interface now so it inherits from the namespace with internet access # Create wg interface now so it inherits from the namespace with internet access
@ -128,7 +151,9 @@ in {
''; '';
preStop = '' preStop = ''
# cleanup wireguard interface
ip link del ${cfg.interfaceName} ip link del ${cfg.interfaceName}
rm -f /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
''; '';
}; };
@ -148,24 +173,26 @@ in {
script = '' script = ''
# pseudo calls wg-quick # pseudo calls wg-quick
# wg-quick down /tmp/${cfg.interfaceName}.conf # Near equivalent of "wg-quick up /tmp/${cfg.interfaceName}.conf"
# cannot actually call wg-quick because the interface has to be already created at this point # cannot actually call wg-quick because the interface has to be already
# created before the container taken ownership of the interface
# assumes wg interface was already created # Thus, assumes wg interface was already created:
# ip link add ${cfg.interfaceName} type wireguard # ip link add ${cfg.interfaceName} type wireguard
myaddress=`cat /tmp/${cfg.interfaceName}-address.conf` myaddress=`cat /tmp/${cfg.interfaceName}-address.conf`
wg setconf ${cfg.interfaceName} /tmp/${cfg.interfaceName}.conf wg setconf ${cfg.interfaceName} /tmp/${cfg.interfaceName}.conf
ip -4 address add $myaddress dev ${cfg.interfaceName} ip -4 address add $myaddress dev ${cfg.interfaceName}
ip link set mtu 1420 up dev ${cfg.interfaceName} ip link set mtu 1420 up dev ${cfg.interfaceName}
wg set ${cfg.interfaceName} fwmark 51820 wg set ${cfg.interfaceName} fwmark ${toString cfg.wireguardListenPort}
ip -4 route add 0.0.0.0/0 dev ${cfg.interfaceName} table 51820 ip -4 route add 0.0.0.0/0 dev ${cfg.interfaceName} table ${toString cfg.wireguardListenPort}
# TODO is this needed? # TODO is this needed?
ip -4 rule add not fwmark 51820 table 51820 ip -4 rule add not fwmark ${toString cfg.wireguardListenPort} table ${toString cfg.wireguardListenPort}
ip -4 rule add table main suppress_prefixlength 0 ip -4 rule add table main suppress_prefixlength 0
# sysctl -q net.ipv4.conf.all.src_valid_mark=1
# The rest of the script is only for only for port forwarding skip if not needed
if [ ${lib.boolToString portForwarding} == false ]; then exit 0; fi
# Reserve port # Reserve port
${getPIAToken} ${getPIAToken}
@ -185,30 +212,55 @@ in {
echo $signature >> /tmp/${cfg.interfaceName}-port-renewal echo $signature >> /tmp/${cfg.interfaceName}-port-renewal
echo $payload >> /tmp/${cfg.interfaceName}-port-renewal echo $payload >> /tmp/${cfg.interfaceName}-port-renewal
# redirect the fowarded port # The first port refresh triggers the port to be actually allocated
# iptables -A INPUT -i ${cfg.interfaceName} -p tcp --dport $port -j ACCEPT ${refreshPIAPort}
# iptables -A INPUT -i ${cfg.interfaceName} -p udp --dport $port -j ACCEPT
# iptables -A INPUT -i ${cfg.interfaceName} -p tcp --dport ${toString cfg.forwardedPort} -j ACCEPT ${lib.optionalString (cfg.forwardedPort != null) ''
# iptables -A INPUT -i ${cfg.interfaceName} -p udp --dport ${toString cfg.forwardedPort} -j ACCEPT # redirect the fowarded port
# iptables -A PREROUTING -t nat -i ${cfg.interfaceName} -p tcp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort} iptables -A INPUT -i ${cfg.interfaceName} -p tcp --dport $port -j ACCEPT
# iptables -A PREROUTING -t nat -i ${cfg.interfaceName} -p udp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort} iptables -A INPUT -i ${cfg.interfaceName} -p udp --dport $port -j ACCEPT
iptables -A INPUT -i ${cfg.interfaceName} -p tcp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -A INPUT -i ${cfg.interfaceName} -p udp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -A PREROUTING -t nat -i ${cfg.interfaceName} -p tcp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
iptables -A PREROUTING -t nat -i ${cfg.interfaceName} -p udp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
''}
${lib.optionalString cfg.forwardPortForTransmission ''
# assumes no auth needed for transmission
curlout=$(curl localhost:9091/transmission/rpc 2>/dev/null)
regex='X-Transmission-Session-Id\: (\w*)'
if [[ $curlout =~ $regex ]]; then
sessionId=''${BASH_REMATCH[1]}
else
exit 1
fi
# set the port in transmission
data='{"method": "session-set", "arguments": { "peer-port" :'$port' } }'
curl http://localhost:9091/transmission/rpc -d "$data" -H "X-Transmission-Session-Id: $sessionId"
''}
''; '';
preStop = '' preStop = ''
wg-quick down /tmp/${cfg.interfaceName}.conf wg-quick down /tmp/${cfg.interfaceName}.conf
# stop redirecting the forwarded port # The rest of the script is only for only for port forwarding skip if not needed
# iptables -D INPUT -i ${cfg.interfaceName} -p tcp --dport $port -j ACCEPT if [ ${lib.boolToString portForwarding} == false ]; then exit 0; fi
# iptables -D INPUT -i ${cfg.interfaceName} -p udp --dport $port -j ACCEPT
# iptables -D INPUT -i ${cfg.interfaceName} -p tcp --dport ${toString cfg.forwardedPort} -j ACCEPT ${lib.optionalString (cfg.forwardedPort != null) ''
# iptables -D INPUT -i ${cfg.interfaceName} -p udp --dport ${toString cfg.forwardedPort} -j ACCEPT # stop redirecting the forwarded port
# iptables -D PREROUTING -t nat -i ${cfg.interfaceName} -p tcp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort} iptables -D INPUT -i ${cfg.interfaceName} -p tcp --dport $port -j ACCEPT
# iptables -D PREROUTING -t nat -i ${cfg.interfaceName} -p udp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort} iptables -D INPUT -i ${cfg.interfaceName} -p udp --dport $port -j ACCEPT
iptables -D INPUT -i ${cfg.interfaceName} -p tcp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -D INPUT -i ${cfg.interfaceName} -p udp --dport ${toString cfg.forwardedPort} -j ACCEPT
iptables -D PREROUTING -t nat -i ${cfg.interfaceName} -p tcp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
iptables -D PREROUTING -t nat -i ${cfg.interfaceName} -p udp --dport $port -j REDIRECT --to-port ${toString cfg.forwardedPort}
''}
''; '';
}; };
vpn-container.config.systemd.services.pia-vpn-wireguard-forward-port = { vpn-container.config.systemd.services.pia-vpn-wireguard-forward-port = {
enable = cfg.portForwarding; enable = portForwarding;
description = "PIA VPN WireGuard Tunnel Port Forwarding"; description = "PIA VPN WireGuard Tunnel Port Forwarding";
after = [ "pia-vpn-wireguard.service" ]; after = [ "pia-vpn-wireguard.service" ];
requires = [ "pia-vpn-wireguard.service" ]; requires = [ "pia-vpn-wireguard.service" ];
@ -219,15 +271,11 @@ in {
Type = "oneshot"; Type = "oneshot";
}; };
script = '' script = refreshPIAPort;
signature=`sed '1q;d' /tmp/${cfg.interfaceName}-port-renewal`
payload=`sed '2q;d' /tmp/${cfg.interfaceName}-port-renewal`
bind_port_response=`curl -Gs -m 5 --connect-to "${cfg.serverHostname}::${cfg.serverIp}:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "payload=$payload" --data-urlencode "signature=$signature" "https://${cfg.serverHostname}:19999/bindPort"`
'';
}; };
vpn-container.config.systemd.timers.pia-vpn-wireguard-forward-port = { vpn-container.config.systemd.timers.pia-vpn-wireguard-forward-port = {
enable = cfg.portForwarding; enable = portForwarding;
partOf = [ "pia-vpn-wireguard-forward-port.service" ]; partOf = [ "pia-vpn-wireguard-forward-port.service" ];
wantedBy = [ "timers.target" ]; wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "*:0/10"; # 10 minutes timerConfig.OnCalendar = "*:0/10"; # 10 minutes

View File

@ -44,7 +44,7 @@ in
config = mkIf cfg.enable { config = mkIf cfg.enable {
pia.wireguard.enable = !cfg.useOpenVPN; pia.wireguard.enable = !cfg.useOpenVPN;
pia.wireguard.portForwarding = !cfg.useOpenVPN; pia.wireguard.forwardPortForTransmission = !cfg.useOpenVPN;
containers.${cfg.containerName} = { containers.${cfg.containerName} = {
ephemeral = true; ephemeral = true;