Skip to content

Instantly share code, notes, and snippets.

@oniGino
Created January 2, 2026 00:35
Show Gist options
  • Select an option

  • Save oniGino/5dd9386eb9cfc7bc625afac28521b18b to your computer and use it in GitHub Desktop.

Select an option

Save oniGino/5dd9386eb9cfc7bc625afac28521b18b to your computer and use it in GitHub Desktop.
ProtonVPN UPNP Port Forwarding Script
#!/bin/bash
# CONFIGURATION
GATEWAY="10.2.0.1"
LIFETIME=60
SLEEP_TIME=45
NFT_TABLE="inet main"
NFT_CHAIN="INPUT"
NFT_COMMENT="UPnP-Loop"
added_tcp_rule=0
added_udp_rule=0
tcp_port=""
udp_port=""
print_log() {
local msg="$1"
local now
now=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$now] $msg"
}
add_nft_rule() {
local proto="$1"
local port="$2"
print_log "Adding nft rule for $proto port $port"
nft add rule "$NFT_TABLE" "$NFT_CHAIN" "$proto" dport "$port" accept comment "$NFT_COMMENT"
transmission-remote --port $port
}
cleanup_upnp_rules() {
# Delete all rules having our comment, regardless of proto or port
print_log "Deleting all nft $NFT_TABLE $NFT_CHAIN rules with comment \"$NFT_COMMENT\""
handles=$(nft --json list chain "$NFT_TABLE" "$NFT_CHAIN" 2>/dev/null | \
jq -r --arg comment "$NFT_COMMENT" \
'.nftables[].rule | select(.comment == $comment) | .handle')
for h in $handles; do
print_log "Deleting rule with handle $h"
nft delete rule "$NFT_TABLE" "$NFT_CHAIN" handle "$h"
done
print_log "Cleanup complete. Exiting."
}
trap cleanup_upnp_rules EXIT
forward_port() {
local proto="$1"
forward_port_output=$(natpmpc -a 1 0 $proto $LIFETIME -g $GATEWAY 2>&1)
forward_port_status=$?
forward_port_port=$(echo "$forward_port_output" | awk "/Mapped public port/ && /${proto^^}/ {print \$4}")
}
while true; do
print_log "Requesting port mapping..."
# Forward UDP
forward_port "udp"
udp_status=$forward_port_status
udp_port_new=$forward_port_port
udp_output=$forward_port_output
if [ "$udp_status" -ne 0 ]; then
print_log "ERROR: natpmpc UDP mapping failed (status: $udp_status)"
print_log "Output: $udp_output"
break
fi
if [ "$added_udp_rule" -eq 0 ] && [ -n "$udp_port_new" ]; then
udp_port="$udp_port_new"
add_nft_rule "udp" "$udp_port"
added_udp_rule=1
fi
# Forward TCP
forward_port "tcp"
tcp_status=$forward_port_status
tcp_port_new=$forward_port_port
tcp_output=$forward_port_output
if [ "$tcp_status" -ne 0 ]; then
print_log "ERROR: natpmpc TCP mapping failed (status: $tcp_status)"
print_log "Output: $tcp_output"
break
fi
if [ "$added_tcp_rule" -eq 0 ] && [ -n "$tcp_port_new" ]; then
tcp_port="$tcp_port_new"
add_nft_rule "tcp" "$tcp_port"
added_tcp_rule=1
fi
print_log "UDP mapped to: $udp_port"
print_log "TCP mapped to: $tcp_port"
print_log "Sleeping for $SLEEP_TIME seconds..."
sleep $SLEEP_TIME
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment