Double your speed : OPNsense + Wireguard + ProtonVPN + qBittorrent

Started by _Dave_, January 12, 2025, 12:12:09 AM

Previous topic - Next topic
Hi All,
After much consternation and gnashing of teeth, this OPNsense newbie has figured out how to achieve the following and would like to share it in case others find it helpful:

  • Multiple VPN Tunnels to ProtonVPN with load balancing for increased speed
  • Incoming WireGuard tunnel for qBittorrent use
  • Split tunneling for the qBittorrent client on Windows
  • Port forwarding open port from ProtonVPN to qBittorrent

There may be mistakes here or possibly something missing. If this doesn't work, if you find mistakes, or have suggestions, please comment!

In this guide, I will cover the following steps:
  • Setting up the official WireGuard client in Windows for split tunneling
  • Setting up an incoming tunnel in OPNsense
  • Configuring multiple tunnels to ProtonVPN with load balancing
  • Creating instances and gateways in OPNsense
  • Setting up firewall rules
  • Creating a shell script to get the open port from ProtonVPN, update firewall rules, and set the port in qBittorrent
  • Getting everything working


Part 1 - Setting Up the WireGuard Client in Windows

To enable split tunneling, we need the qBittorrent client to access the VPN while the rest of Windows uses the default gateway. This can be achieved by setting up the WireGuard Windows client and binding qBittorrent to the wg0 adapter created by WireGuard.

Alternatives such as Wiresock + WireSock UI (which targets the qBittorrent client specifically) or Proxifier exist, but WireGuard is the easiest to configure, so we will use it here.

Step 1: Download and Install the WireGuard Client
  • Download the WireGuard client installer from the official website.
  • Install the WireGuard client on your Windows machine.

Step 2: Generate the WireGuard Keys
  • Run the WireGuard client.
  • Press CTRL+N to create a new key pair.
  • Save the public and private keys. These will be referred to as "Windows public key" and "Windows private key."
  • Click "Cancel" after saving the keys.

Step 3: Create the wg0.conf File
  • Create a file named "wg0.conf" in your preferred location (e.g., Documents).
  • Use the following template for the file:
    [Interface]
    PrivateKey = <Windows private key> # Replace with the private key obtained above
    Address = 192.168.3.2/32 # Use a different network than your LAN. For example, if your router is 192.168.1.1, use 192.168.3.2 here.
    DNS = 10.2.0.1 # ProtonVPN's DNS and NAT-PMP server

    PostUp sets the route metric of the WireGuard adapter to a lower priority, enabling split tunneling.
    PostUp = powershell -command "$ii = (Get-NetAdapter -Name %WIREGUARD_TUNNEL_NAME%).ifIndex; route add 0.0.0.0 mask 0.0.0.0 0.0.0.0 IF $ii metric 9999"

    PreDown removes the route added above.
    PreDown = powershell -command "$ii = (Get-NetAdapter -Name %WIREGUARD_TUNNEL_NAME%).ifIndex; route delete 0.0.0.0 IF $ii"

    Table = off # Prevents the automatic addition of routes.

    [Peer]
    PublicKey = <OPNsense Public Key> # To be added later.
    AllowedIPs = 0.0.0.0/0, ::/0 # Allows traffic from qBittorrent.
    Endpoint = 192.168.3.1:666 # Gateway and port in OPNsense.
    PersistentKeepalive = 25

Step 4: Enable PowerShell Script Execution
  • To use the PostUp and PreDown hooks, allow script execution in PowerShell by running the following commands:
    Set-ExecutionPolicy RemoteSigned
    Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
  • Note: This reduces Windows' security level slightly.

Step 5: Enable Hooks in the WireGuard Client
  • Run the following commands in PowerShell to allow the WireGuard client to execute hooks:
    $regPath = 'HKLM:\Software\WireGuard'
    New-Item $regPath -Force | Out-Null
    New-ItemProperty $regPath -Name DangerousScriptExecution -Value 1 -Force | Out-Null


Part 2 - Creating an Incoming Tunnel for the WireGuard Windows Client

To set up the incoming tunnel, we need to generate another pair of WireGuard keys for OPNsense and configure the peer and instance on the OPNsense side.

Step 1: Generate the Host Keys
  • Open the WireGuard client on Windows.
  • Press CTRL+N to create a new key pair.
  • Save the generated private and public keys for OPNsense. These will be referenced as "Host public key" and "Host private key."

Step 2: Create the WireGuard Peer in OPNsense
  • Navigate to VPN > WireGuard > Peers in the OPNsense dashboard.
  • Click the "+" button to add a new peer.
  • Configure the peer with the following settings:
    • Name: WG_Host
    • Public Key: Enter the "Windows public key" generated in Part 1.
    • Endpoint Address: 192.168.1.1
    • Endpoint Port: 666
    • Allowed IPs: 0.0.0.0/0
  • Click "Save" to save the peer configuration.

Step 3: Create the WireGuard Instance in OPNsense
  • Navigate to VPN > WireGuard > Instances.
  • Click the "+" button to add a new instance.
  • Configure the instance with the following settings:
    • Enabled: Check this box.
    • Name: WG_Host
    • Public Key: Enter the "Host public key" you generated earlier.
    • Private Key: Enter the "Host private key" you generated earlier.
    • Peers: Select the "WG_Host" peer you created in Step 2.
  • Click "Save" and then "Apply" to activate the instance.

Step 4: Update the Windows WireGuard Configuration
  • Open the "wg0.conf" file from Part 1 in your preferred text editor.
  • Locate the "PublicKey" line in the "[Peer]" section.
  • Replace the existing public key value with the "Host public key" of the WG_Host WireGuard instance created in Step 3.
  • Save the file.


Part 3 - Create Multiple WireGuard Tunnels to ProtonVPN

Setting up multiple WireGuard tunnels to ProtonVPN involves downloading the configuration files and creating peers and instances in OPNsense. Follow these steps:

Step 1: Download the Configuration Files from ProtonVPN
  • Sign in to your ProtonVPN account at ProtonVPN.
  • Click on Downloads in the left panel and scroll down to "WireGuard Configuration."
  • Under "Select Platform," choose "Router."
  • Under "Select VPN Options":
    • For the first configuration file:
      • Choose "No filter."
      • Enable "NAT-PMP (Port Forwarding)."
    • For the remaining configuration file:
      • Choose "No filter."
      • Disable "NAT-PMP (Port Forwarding)."
  • Scroll down to a server with low utilization and select it. Use the SAME SERVER for all tunnels.
  • Click "Create" and then "Download" for each configuration file.
  • You should now have two configuration files in your Downloads folder, such as "wg-US-AZ-81.conf" and "wg-US-AZ-81(1).conf."

Step 2: Create the WireGuard Peers in OPNsense
  • Navigate to VPN > WireGuard > Peers in OPNsense.
  • Click "+" to add a new peer.
  • For the first peer:
    • Name: WG_Tunnel1_AZ_81_PF (or a similar descriptive name).
    • Public Key: Use the "PublicKey" from the first configuration file (e.g., wg-US-AZ-81.conf).
    • Allowed IPs: 0.0.0.0/0
    • Endpoint Address: Use the "Endpoint" IP address from the configuration file.
    • Port: 51820
  • For the second peer:
    • Name: WG_Tunnel2_AZ_81_2 (or a similar descriptive name).
    • Public Key: Use the "PublicKey" from the second configuration file, but change the first four characters to "zzzz."
    • Allowed IPs: 0.0.0.0/0
    • Endpoint Address: Use the "Endpoint" IP address from the configuration file (same as the first peer).
    • Port: 51820

Step 3: Create the WireGuard Instances in OPNsense
  • Navigate to VPN > WireGuard > Instances.
  • Click "+" to add a new instance.
  • For the first instance:
    • Enabled: Check this box.
    • Name: WG_Tunnel1_AZ_81_PF
    • Private Key: Use the "PrivateKey" from the first configuration file (e.g., wg-US-AZ-81.conf).
    • Listen Port: 51280
    • Tunnel Address: 10.2.0.2/32
    • Peers: Select WG_Tunnel1_AZ_81_PF.
  • For the second instance:
    • Enabled: Check this box.
    • Name: WG_Tunnel2_AZ_81_2
    • Private Key: Use the "PrivateKey" from the second configuration file (e.g., wg-US-AZ-81(1).conf).
    • Listen Port: 51281 (This must be different from the above 51820)
    • Tunnel Address: 10.2.0.2/32
    • Peers: Select WG_Tunnel2_AZ_81_2.
  • Click "Save" and "Apply."

Step 4: Correct the Duplicate Public Keys
  • Navigate to System > Configuration > Backups.
  • Click "Download Configuration" to save the configuration to your computer.
  • Open the downloaded file in a text editor.
  • Search for the "PublicKey" from the configuration file (e.g., wg-US-AZ-81.conf).
  • Find the "PublicKey" with the "zzzz" prefix and replace it with the original value.
  • Save the file and return to OPNsense.
  • Under Restore, select the edited configuration file and check "Reboot after a successful restore."
  • Click "Restore Configuration."


Part 4 - Creating Interfaces, Gateways, and Virtual IPs

To complete the setup, we need to create Interfaces to link to the tunnels, allowing us to configure Firewall rules and perform load balancing.

Step 1: Assign the Interfaces
  • Go to Interfaces > Assignments.
  • At the bottom, under "+ Assign a new interface," select the device associated with the first instance/peer (e.g., "opt1 (WireGuard - WG_Tunnel1_AZ_81_PF)").
  • Click "Add." The instance should now appear in the list of interfaces.
  • Repeat the process for the second interface (e.g., opt2 for the second tunnel).

Step 2: Configure the First Interface
  • Click on the first interface (highlighted in orange) in the interfaces list for opt1.
  • Enable the interface: Enable: checked.
  • Enter the following for the description: "WG_Tunnel1."
  • Check "Block bogon networks."
  • Click "Save."

Step 3: Configure the Second Interface
  • Repeat the same process for the second interface and set the description to "WG_Tunnel2."

Step 4: Create Gateways for Load Balancing
  • Go to System > Gateways > Configuration.
  • Click the "+" icon in the bottom right to add a new gateway.
  • Configure the gateway settings as follows:
    • Name: VPN_Gateway1
    • Description: VPN_Gateway1
    • Interface: select WG_Tunnel1.
    • Address Family: select IPv4.
    • IP Address: 10.2.1.1 (This is a virtual gateway IP for use later).
    • Monitor IP: 4.2.2.1 (A unique DNS server address for monitoring purposes).
  • Clone the gateway you just created by clicking the icon next to the trash icon.
  • Configure the cloned gateway with the following settings:
    • Name: VPN_Gateway2
    • Description: VPN_Gateway2
    • Interface: select WG_Tunnel2.
    • IP Address: 10.2.2.1
    • Monitor IP: 4.2.2.2 (A unique monitoring address).

Step 5: Configure Load Balancing Between the Tunnels
  • Go to System > Gateways > Group.
  • Click the "+" icon in the top right to add a new group.
  • Configure the group settings:
    • Group Name: WG_Gateway_Group
    • VPN_Gateway1 Tier: Tier 1
    • VPN_Gateway2 Tier: Tier 1
    • Trigger level: High Latency
    • Pool Options: Round Robin
    • Description: WireGuard gateway group

Step 6: Set Up Virtual IPs for the Tunnel Interfaces
  • Go to Interfaces > Virtual IPs > Settings.
  • Click the "+" icon at the bottom right to add a new virtual IP.
  • Configure the first Virtual IP as follows:
    • Mode: IP Alias
    • Interface: WG_Tunnel1
    • Network/Address: 10.2.1.1/32 (The IP address of the first gateway).
  • Clone the first Virtual IP, select WG_Tunnel2, and change the address to 10.2.2.1/32 (The second gateway IP address).
  • Click "Apply" at the bottom to finalize the configuration.


Part 5 - Firewall Rules

Before we create any rules, let's set up some Aliases to make rule creation easier and less error-prone:

Step 1: Create Aliases
  • VPN_DNS_server, Type: Host(s), Content: 10.2.0.1
  • VPN_Tunnel_Gateway, Type: Host(s), Content: 10.2.0.2
  • WG_Hosts_subnet, Type: Network(s), Content: 192.168.3.0/24
  • Torrent_client_address, Type: Host(s), Content: 192.168.3.2
  • WG_recv_port, Type: Port(s), Content: 666
  • NAT_PMP_Port, Type: Port(s), Content: 5351
  • Monitor_addresses, Type: Host(s), Content: 4.2.2.1, 4.2.2.2
  • Dynamic_UDP_Port, Type: Port(s), Content: 12345 (The port number doesn't matter as it will be overwritten later)
  • Dynamic_TCP_Port, Type: Port(s), Content: 12345 (The port number doesn't matter as it will be overwritten later)
  • Forwarded_Port, Type: Port(s), Content: 4 (The port number ProtonVPN is to forward to - can be any unused port)
  • qBittorrent_UI_PortPort, Type: Port(s), Content: 8080

Step 2: Create Interface Groups
  • Go to Firewall > Groups.
  • Click the "+" icon in the bottom right to create a new group.
  • Configure the group settings:
    • Name: WG_Gateway_Group
    • Members: WG_Tunnel1, WG_Tunnel2
  • Clone the above group and name it WG_Gateway_Group_H

Step 3: Create Port Forward Rules for NAT
  • Go to Firewall > NAT> Port Forward.
  • Click the "+" icon in the top right to create a new rule.
  • Configure these rules:
    • Interface: WG_Tunnel1, Protocol: UDP, Destination IP: VPN_Tunnel_Gateway, Destination Port: Forwarded_Port, NAT IP: Torrent_client_address, NAT Port: Dynamic_UDP_Port, Description: Port forward open UDP port from VPN, Filter Rule Association: None
    • Interface: WG_Tunnel1, Protocol: TCP, Destination IP: VPN_Tunnel_Gateway, Destination Port: Forwarded_Port, NAT IP: Torrent_client_address, NAT Port: Dynamic_TCP_Port, Description: Port forward open TCP port from VPN, Filter Rule Association: None

Step 4: Switch to Manual Outbound Rules for NAT
  • Go to Firewall > NAT> Outbound.
  • Add the following rules:
    • Interface: WAN, Source: This Firewall, NAT Address: WAN address, Description: Default outbound NAT for Firewall to WAN
    • Interface: WAN, Source: LAN net, NAT Address: WAN address, Description: Default outbound NAT for LAN to WAN
  • Apply the above rules, then select "Manual outbound NAT rule generation" at the top to disable automatic rules. The automatic rules that we are disabling are the same as the above two rules. (If you need the ISAKMP rules, you wouldn't be reading this :))

Step 5: Create Remaining Outbound Rules for traffic egress to VPN:
  • In Firewall > NAT> Outbound, create the following additional rules:
    • Interface: WG_Interfaces, Source: This Firewall, Destination: Monitor_addresses, Port: 53, NAT IP: VPN_DNS_server, Description: Allow Firewall monitoring of VPN
    • Interface: WG_Interfaces, Source: This Firewall, Destination: Monitor_addresses, NAT IP: VPN_Tunnel_Gateway, Description: Allow Firewall monitoring of VPN
    • Interface: WG_Interfaces_H, Source: Torrent_client_address, Protocol: UDP, Port: Dynamic_UDP_Port, Destination: *, NAT IP: VPN_Tunnel_Gateway, Static Port: Yes, Description: UDP port forward back to VPN
    • Interface: WG_Interfaces_H, Source: Torrent_client_address, Protocol: TCP, Port: Dynamic_TCP_Port, Destination: *, NAT IP: VPN_Tunnel_Gateway, Static Port: Yes, Description: TCP port forward back to VPN
    • Interface: WG_Interfaces_H, Source: WG_Hosts_subnet, Port: 53, Destination: VPN_DNS_server, Description: Allow VPN DNS queries
    • Interface: WG_Interfaces_H, Source: WG_Hosts_subnet, Destination: *, NAT IP: VPN_Tunnel_Gateway, Description: Allow VPN traffic

Step 6: Create LAN Rule for Windows WireGuard Client Handshake
  • Go to Firewall > Rules> Lan.
  • Add the following rule:
    • Protocol: UDP, Source: LAN net, Destination: *, Destination Port: WG_recv_port, Description: Allow WG_Host WG handshake

Step 7: Create WG_Host rules
  • Go to Firewall > Rules > WG_Host.
  • Click the "+" icon to add the following rules:
    • Direction: OUT, Protocol: TCP, Source: *, Destination: Torrent_client_address, Destination Port: qBittorrent_UI_Port, Description: Allow qBittorrent UI access
    • Direction: OUT, Protocol: UDP, Source: *, Destination: Torrent_client_address, Destination Port: Dynamic_UDP_Port, Description: Allow UDP port forward from VPN to qBittorrent
    • Direction: OUT, Protocol: TCP, Source: *, Destination: Torrent_client_address, Destination Port: Dynamic_TCP_Port, Description: Allow TCP port forward from VPN to qBittorrent
      • Click the Advanced features Show/Hide box
      • Under TCP flags select the checkbox for Any flags.
      • Under State Type select None
    • Protocol: UDP, Source: Torrent_client_address, Source Port: Dynamic_UDP_Port, Destination: *, Gateway: WG_Tunnel1, Description: Send WG_Host UDP packet response to VPN
    • Protocol: TCP, Source: Torrent_client_address, Source Port: Dynamic_TCP_Port, Destination: *, Gateway: WG_Gateway_Group, Description: Send WG_Host TCP packet response to VPN
      • Click the Advanced features Show/Hide box
      • Under TCP flags select the checkbox for Any flags.
      • Under State Type select sloppy state
    • Protocol: *, Source: WG_Hosts_subnet, Destination: *, Gateway: WG_Gateway_Group, Description: Load balance normal torrent traffic to VPN

Step 8: Create Rules for WG_Interfaces
  • Go to Firewall > Rules > WG_Interfaces.
  • Click the "+" icon to add the following rules:
    • Direction: OUT, Protocol: UDP, Source: VPN_DNS_server, Source Port: NAT_PMP_Port, Description: Allow NAT-PMP requests
    • Direction: OUT, Protocol: UDP, Source: VPN_Tunnel_Gateway, Destination: VPN_DNS_server, Destination Port: NAT_PMP_Port, Gateway: VPN_Gateway1, Description: Re-route NAT-PMP requests to Tunnel 1
    • Direction: OUT, Protocol: ICMP, Source: *, Destination: Monitor_addresses, Description: Allow monitor pings out to VPN
    • Direction: OUT, Protocol: ICMP, Source: Monitor_addresses, Destination: *, Description: Allow monitor pings out
    • Protocol: ICMP, Source: Monitor_addresses, Description: Allow monitor ping responses from VPN
    • Protocol: TCP/UDP, Source: VPN_DNS_server, Port: 53, Description: Allow DNS response from VPN
    • Direction: OUT, Protocol: TCP/UDP, Source: *, Destination: VPN_DNS_server, Port: 53, Description: Allow DNS to VPN

Step 9: Configure WG_Interfaces_H Rules
  • Go to Firewall > Rules > WG_Interfaces_H.
  • Click the "+" icon to add the following rules:
    • Direction: OUT, Protocol: UDP, Source: VPN_Tunnel_Gateway, Destination Port: Dynamic_UDP_Port, Gateway: VPN_Gateway1, Description: Allow UDP port forwards to VPN
    • Direction: OUT, Protocol: TCP, Source: VPN_Tunnel_Gateway, Destination Port: Dynamic_TCP_Port, Gateway: VPN_Gateway1, Description: Allow TCP port forwards to VPN
    • Protocol: TCP, Source: VPN_DNS_server, Source Port: NAT_PMP_Port, Description: Allow NAT-PMP response
    • Protocol: *, Source: WG_Hosts_subnet, Description: Allow normal VPN traffic to WG_Host
    • Direction: OUT, Protocol: *, Source: VPN_Tunnel_Gateway, Description: Allow all VPN out

Step 10: Create Rules for WG_Tunnel1
  • Go to Firewall > Rules > WG_Tunnel1.
  • Click the "+" icon to add the following rules:
    • Protocol: UDP, Source: VPN_DNS_server, Source Port: NAT_PMP_Port, Destination: *, Description: Allow NAT-PMP response in
    • Protocol: UDP, Source: *, Destination: Torrent_client_address, Destination Port: Dynamic_UDP_Port, Description: Send forwarded UDP traffic from VPN to WG_Host
    • Protocol: TCP, Source: *, Destination: Torrent_client_address, Destination Port: Dynamic_TCP_Port, Description: Send forwarded TCP traffic from VPN to WG_Host


Part 6 - Scripting

In this part, we will be setting up a script to run as a service that starts when the router is booted. The script will perform the following tasks:

  • Contact ProtonVPN using NAT-PMP to get the open port that is being forwarded
  • Update qBittorrent with the open port if needed
  • Update the Firewall aliases of Dynamic_UDP_Port and Dynamic_TCP_Port
  • Repeat this process every 45 seconds

Before we create the script, we will need to install a couple of commands that the shell script uses, create API keys for OPNsense, and configure qBittorrent's API.
We will need to SSH into the OPNsense shell for the next step. To do this:

  • Go to System > Settings > Administration, and enable "Secure Shell". Permit root user login and password login. You can revert these settings once everything is working properly.
  • Open a PowerShell window in Windows.
  • Run the following command, replacing the root username (if you have changed it) and the address of OPNsense:
C:\Windows\System32\OpenSSH\ssh.exe root@192.168.1.1
  • When the connection opens, you will see the OPNsense main menu. Select "8" to enter the shell.

The "natpmpc" command is used to get the open forwarded port from ProtonVPN using NAT-PMP. To install it, type the following commands:
cd /usr/ports/net/libnatpmp
make install clean
cd /usr
rm -rf ports

The script also uses "jq" to parse JSON responses. To install it, simply run the following command:
pkg install jq

Next, we need to create API keys for OPNsense, which are used by the script to change the Firewall aliases. You can obtain these keys by following these steps:
  • Go to System > Access > Users.
  • Edit the root user and scroll down to the "API keys" section.
  • Click "Add" to generate a new key, which will download a file with the "key" and "secret".

Additionally, we need to enable qBittorrent's API. Ensure that you are connected to ProtonVPN to avoid any issues during these steps.
  • Open qBittorrent and navigate to Tools > Options > Web UI.
  • Select 192.168.3.2 for the IP address and leave the port at 8080.
  • Create a username and password, and make sure to note these for later use.

Now that we have all the necessary information for the script, open a new file in your text editor and copy the following script into it:
#!/bin/sh

export PATH=$PATH:/usr/local/bin

# OPNsense
readonly GATEWAY="10.2.0.1"
readonly OPNSENSE_URL="https://192.168.1.1"
readonly TCP_ALIAS_NAME="Dynamic_TCP_Port"
readonly UDP_ALIAS_NAME="Dynamic_UDP_Port"
readonly FORWARDED_PORT_ALIAS_NAME="Forwarded_Port"
readonly API_KEY="<api key for OPNsense>"
readonly API_SECRET="<api secret for OPNsense>"

# qBittorrent
readonly QBT_URL="http://192.168.3.2:8080"
readonly QBT_USERNAME="<torrent client username>"
readonly QBT_PASSWORD="<torrent client password>"

# Function to log in to the torrent client and return session ID
login_torrent_client() {
  curl -s -k -X POST "$QBT_URL/api/v2/auth/login" \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d "username=$QBT_USERNAME&password=$QBT_PASSWORD" -i | \
    grep "set-cookie: SID" | awk -F'[=;]' '{print $2}' | tr -d ' '
}

# Function to get the current port from the torrent client
get_torrent_port() {
  local sid="$1"
  curl -s -k -X GET "$QBT_URL/api/v2/app/preferences" \
    -H "Cookie: SID=$sid" | jq -r '.listen_port'
}

# Function to set the port in the torrent client
set_torrent_port() {
  local port="$1"
  local sid="$2"
  curl -s -k -X POST "$QBT_URL/api/v2/app/setPreferences" \
    -H "Cookie: SID=$sid" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "json={\"listen_port\":$port}"
}

# Function to get the alias UUID from OPNsense
get_alias_uuid() {
  local alias_id="$1"
  response=$(curl -s -k -X GET "$OPNSENSE_URL/api/firewall/alias/getAliasUUID/$alias_id" -u "$API_KEY:$API_SECRET")
  echo "$response" | jq -r '.uuid'
}

# Function to get the current value of the alias
get_alias_value() {
  local uuid="$1"
  response=$(curl -s -k -X GET "$OPNSENSE_URL/api/firewall/alias/get" -u "$API_KEY:$API_SECRET")
  echo "$response" | jq -r --arg uuid "$uuid" '.alias.aliases.alias[$uuid].content | keys | .[0]'
}

# Function to set the alias value (forwarded port)
set_alias_value() {
  local uuid="$1"
  local forwarded_port="$2"
  curl -s -k -X POST "$OPNSENSE_URL/api/firewall/alias/setItem/$uuid" -u "$API_KEY:$API_SECRET" \
    -H "Content-Type: application/json" \
    -d "{\"alias\": {\"content\": \"$forwarded_port\"}}" >/dev/null 2>&1
}

# Function to apply the changes - this takes a few seconds to update
apply_changes() {
  curl -s -k -X POST "$OPNSENSE_URL/api/firewall/alias/reconfigure" -u "$API_KEY:$API_SECRET" -d '{}' >/dev/null 2>&1
}

# Get alias for forwarded port from OPNsense
FORWARDED_PORT_UUID=$(get_alias_uuid "$FORWARDED_PORT_ALIAS_NAME")
FORWARDED_PORT=$(get_alias_value "$FORWARDED_PORT_UUID")
#echo "Forwarded port is $FORWARDED_PORT."

# Initialize last public port variables
LAST_UDP_PORT=0
LAST_TCP_PORT=0

# Flag to check if any changes were made to OPNsense port aliases
alias_change_made=false

while true; do
  #echo "Last UDP Port is $LAST_UDP_PORT."
  #echo "Last TCP Port is $LAST_TCP_PORT."

  # Execute natpmpc command for UDP with the last public ports
  UDP_PUBLIC_PORT=$(natpmpc -g "$GATEWAY" -a "$LAST_UDP_PORT" "$FORWARDED_PORT" udp 60 | grep -o 'Mapped public port [0-9]*' | awk '{print $4}')

  # Execute natpmpc command for TCP with the last public port
  TCP_PUBLIC_PORT=$(natpmpc -g "$GATEWAY" -a "$LAST_TCP_PORT" "$FORWARDED_PORT" tcp 60 | grep -o 'Mapped public port [0-9]*' | awk '{print $4}')

  # Update the torrent client port if needed
  SID=$(login_torrent_client)
  if [ -n "$SID" ] && [ -n "$UDP_PUBLIC_PORT" ]; then
      CURRENT_TORRENT_PORT=$(get_torrent_port "$SID")
      if [ "$CURRENT_TORRENT_PORT" -ne "$UDP_PUBLIC_PORT" ]; then
          set_torrent_port "$UDP_PUBLIC_PORT" "$SID"
          echo "Updated torrent client port to $UDP_PUBLIC_PORT."
      fi
  fi

  # Check if both ports were successfully mapped
  if [ -n "$UDP_PUBLIC_PORT" ] && [ -n "$TCP_PUBLIC_PORT" ]; then
      # Update the UDP port if it's different from the last one
      if [ "$UDP_PUBLIC_PORT" -ne "$LAST_UDP_PORT" ]; then
          LAST_UDP_PORT="$UDP_PUBLIC_PORT"
          UDP_UUID=$(get_alias_uuid "$UDP_ALIAS_NAME")
          CURRENT_UDP_VALUE=$(get_alias_value "$UDP_UUID")
          if [ "$CURRENT_UDP_VALUE" != "$UDP_PUBLIC_PORT" ]; then
              set_alias_value "$UDP_UUID" "$UDP_PUBLIC_PORT"
              alias_change_made=true
          fi
      fi

      # Update the TCP port if it's different from the last one
      if [ "$TCP_PUBLIC_PORT" -ne "$LAST_TCP_PORT" ]; then
          LAST_TCP_PORT="$TCP_PUBLIC_PORT"
          TCP_UUID=$(get_alias_uuid "$TCP_ALIAS_NAME")
          CURRENT_TCP_VALUE=$(get_alias_value "$TCP_UUID")
          if [ "$CURRENT_TCP_VALUE" != "$TCP_PUBLIC_PORT" ]; then
              set_alias_value "$TCP_UUID" "$TCP_PUBLIC_PORT"
              alias_change_made=true
          fi
      fi

      # Apply changes only if updates were made to aliases
      if [ "$alias_change_made" = true ]; then
          apply_changes
          alias_change_made=false
          echo "OPNsense Aliases changed."
      fi
  else
      echo "Failed to map ports."
  fi

  # Wait 45 seconds before requesting another port - recommended by Proton VPN
  sleep 45
done

Now, edit the readonly variables for api keys and qBittorrent login at the top of the script to you own.

To place the script in OPNsense, follow these steps:

  • Go back to the OPNsense shell and type the following command:
cd /usr/local/bin
vi nat-pmp.sh
  • This will open the vi text editor with the cursor in the top left.
  • Go to the edited script in the text editor in Windows, select all, and copy it to the clipboard (CTRL+A, CTRL+C).
  • Return to the OPNsense shell, hit the "I" key (lowercase) to enter insert mode, right-click on the flashing cursor in the upper left, and you should see the script copied to the editor.
  • Press the Escape key to exit insert mode, then type ":wq" (without quotes) to save the file and exit the editor.

Next, make the script executable by running:
chmod +x /usr/local/bin/nat-pmp.sh

To test the script, you can run it manually with the following command:
/usr/local/bin/nat-pmp.sh

Now that the script is on the router, we need to create another script to set up a service that will run the above nat-pmp script at boot.
Create this service script in your Windows text editor:
#!/bin/sh

PROVIDE: nat_pmp
REQUIRE: NETWORKING
KEYWORD: shutdown
. /etc/rc.subr

name="nat_pmp"
rcvar="nat_pmp_enable"

command="/usr/local/bin/nat-pmp.sh"
pidfile="/var/run/${name}.pid"
start_cmd="${name}_start"
stop_cmd="${name}_stop"

nat_pmp_start() {
echo "Starting NAT-PMP script..."
/usr/sbin/daemon -p ${pidfile} -f ${command}
}

nat_pmp_stop() {
echo "Stopping NAT-PMP script..."
kill cat ${pidfile} && rm -f ${pidfile}
}

load_rc_config $name
run_rc_command "$1"

Once the service script is ready, go back to the OPNsense shell and run:
cd /usr/local/etc/rc.d
vi nat-pmp

Copy the service script from Windows to vi as before (press "i" to insert, right-click to paste, press ESC, and then type ":wq" to save and exit).

Make this script executable by running:
chmod +x /usr/local/etc/rc.d/nat-pmp

To add this service to OPNsense, execute:
echo "nat_pmp_enable=YES" >> /etc/rc.conf

Now, if you need to start or stop the service manually, you can use the following commands:
service nat-pmp start
service nat-pmp stop


Step 7 - Getting everything working

To ensure that the open port from ProtonVPN is being correctly retrieved, go to the OPNsense shell and type the following command, replacing the value 4 with the value of your Forwarded_Port alias:
natpmpc -g 10.2.0.1 -a 0 4 udp 60
You should see output that lists the "Mapped public port" followed by a port number.

Next, reboot the router to ensure all settings are applied correctly.

In the OPNsense UI, go to Firewall > Aliases. The script we created should have changed the values for "Dynamic_TCP_Port" and "Dynamic_UDP_Port" to something other than 12345. Additionally, all the Gateways in the OPNsense UI should now be showing green.

Open the WireGuard Windows client. Select "Add Tunnel" and select the wg0.conf file that you created in Step 1. Hit"Activate". After a few seconds, the "Latest handshake" should appear.

At this point, it would be a good idea to set up Proxifier so that you can use a web browser through the wg0 connection to test for leakage.

For testing with qBittorrent, ensure there are no torrents seeded that you don't want to be known. A large public torrent like the one at sdi-tool.org is good for this purpose.

You should now see the tunnels showing up in the OPNsense UI dashboard interface statistics, with traffic flowing to the WG_Tunnel1 and WG_Tunnel2 interfaces.

Now, open qBittorrent. Change the network interface in Tools > Options > Advanced to "wg0" and the Optional address to bind to to 192.168.3.2. Wait for 45 seconds. The port in Tools > Options > Connections should have been changed to the same port shown by the "natpmpc -g 10.2.0.1 -a 0 4 udp 60" command above.

You should see a green plug icon at the bottom, which indicates that there are incoming port forwards from ProtonVPN to qBittorrent.

Go to portchecker, type in the IP address and port given by the above "natpmpc" command. The port should show "open".

Go to ipleak.net and go down and activate "Torrent Address detection". Download the magnet link into qBittorrent and start the torrent. The website should show only VPN DNS servers listed under "DNS Addresses" and "Torrent Address detection". Leave this running for a while.

To verify, use Wireshark to ensure that there is no non-WireGuard torrent traffic on your normal interface. You can disconnect and reconnect your internet connection to test again.

When you're ready, enable your other torrents. You can add additional non port-forwarded tunnels at this time if your router can handle them. I take no responsibility for anything, have fun! :)

Wowwwww man!!!
this is insane, thank you for this sharing info. this is the issue I'm facing right now.

Its not working quite yet, I need to update the script and fix the port forwarding. I'll let you know when its fixed.

It should be fully working now if I translated the settings to the main post properly.