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

Version history
2025-06-25 (v1.1.0):
  • Changed NAP-PMP call to use the same private port as the public port
  • Use only a single alias to track the forwarded port since UDP and TCP forwarded ports are the same
  • Simplified the main script to use the single alias
  • Simplified firewall rules
2025-01-17 (v1.0.1):
  • Updated script behavior to log into qBittorrent only at startup or when the session expires to reduces clutter in qBittorrent logs.

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

    # 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"

    # 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.
  • Important: the servers must have P2P enabled. The first config file must have port forwarding enabled and the remaining config files port forwarding must be disabled.
  • 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
  • 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
  • Forwarded_Port, Type: Port(s), Content: 12345 (The port ProtonVPN forwards - can by anything as it will be overwritten by the main script)
  • qBittorrent_UI_Port, 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 Rule for DNAT (Destination 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: TCP/UDP, Destination IP: VPN_Tunnel_Gateway, Destination Port: Forwarded_Port, NAT IP: Torrent_client_address, NAT Port: Forwarded_Port, Description: Port forward port from VPN, Filter Rule Association: None

Step 4: Switch to Manual Outbound Rules for SNAT (Source 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 SNAT Rules for traffic egress to VPN:
  • In Firewall > NAT> Outbound, create the following additional rules in order above the two rules you created in step 4:
    • Interface: WG_Interfaces, Source: This Firewall, Destination: Monitor_addresses, Destination Port: 53, NAT Address: VPN_DNS_server, Description: Allow Firewall monitoring of VPN
    • Interface: WG_Interfaces, Source: This Firewall, Destination: Monitor_addresses, NAT Address: VPN_Tunnel_Gateway, Description: Allow Firewall monitoring of VPN
    • Interface: WG_Interfaces_H, Source: WG_Hosts_subnet, Destination Port: 53, NAT Address: VPN_DNS_server, Description: Allow VPN DNS queries
    • Interface: WG_Interfaces_H, Source: WG_Hosts_subnet, Destination Port: any, NAT Address: 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 and place it above the other LAN rules:
    • Protocol: UDP, Source: LAN net, 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: IPv4 TCP, Destination: Torrent_client_address, Destination Port: qBittorrent_UI_Port, Description: Allow qBittorrent UI access
    • Direction: OUT, Protocol: IPv4 ICMP, Destination: Torrent_client_address, Description: Allow pings from torrent peers
    • Direction: OUT, Protocol: IPv4/TCP/UDP, Destination: Torrent_client_address, Destination Port: Forwarded_Port, Description: Pass port forwards from VPN to qBittorrent - REMOVE STATE
      • Click the Advanced features Show/Hide box
      • Under TCP flags select the checkbox for Any flags.
      • Under State Type select None
    • Protocol: IPv4 TCP, Source: Torrent_client_address, Source Port: Forwarded_Port, Gateway: VPN_Gateway1, Description: Pass TCP port forward responses to VPN - REMOVE STATE
      • Click the Advanced features Show/Hide box
      • Under TCP flags select the checkbox for Any flags.
      • Under State Type select None
    • Protocol: IPv4, Source: WG_Hosts_subnet, 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: IPv4 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
    • Protocol: IPv4 TCP/UDP, Source: VPN_DNS_server, Source Port: 53, Description: Allow DNS response from VPN
    • Direction: OUT, Protocol: IPv4 TCP/UDP, Destination: VPN_DNS_server, Destination Port: 53, Description: Allow DNS to VPN
    • Protocol: IPv4 ICMP, Source: VPN_DNS_server, Destination: VPN_Tunnel_Gateway, Description: Allow pings from ProtonVPN

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: IPv4 TCP, Source: VPN_Tunnel_Gateway, Source Port: Forwarded_Port, Gateway: VPN_Gateway1, Description: Allow TCP port forwarded responses back out to VPN
    • Direction: OUT, Protocol: IPv4 any, Source: VPN_Tunnel_Gateway, Gateway: VPN_Gateway_Group, Description: Allow responses back out to VPN
    • Protocol: IPv4 TCP/UDP, Destination: Torrent_client_address, Destination Port: Forwarded_Port, Description: Pass port forwards to VPN

Step 10: Create Rules for WG_Tunnel1
  • Go to Firewall > Rules > WG_Tunnel1.
  • Click the "+" icon to add the following rules:
    • Protocol: IPv4 TCP/UDP, Destination: Torrent_client_address, Destination Port: Forwarded_Port, Description: Pass forwarded 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 the Firewall aliase Forwarded_Port if it has changed
  • Update qBittorrent with the open port if needed
  • 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:
opnsense-code ports
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 VPN_GATEWAY="10.2.0.1"
readonly OPNSENSE_URL="https://192.168.1.1"
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="<qBittorrent client username>"
readonly QBT_PASSWORD="<qBittorrent 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"
  local port=$(curl -s -k -X GET "$QBT_URL/api/v2/app/preferences" -H "Cookie: SID=$sid" 2>/dev/null | jq -r '.listen_port' 2>/dev/null)
  if ! echo "$port" | grep -qE '^[0-9]+$'; then
    echo ""
  else
    echo "$port"
  fi
}

# 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
}

# Initialize last public port variable
LAST_PUBLIC_PORT=1

# Get session id from qBittorrent
SID=$(login_torrent_client)

while true; do
  #echo "Last public port was $LAST_PUBLIC_PORT."

  # Retry NAT-PMP requests until UDP and TCP ports match
  while true; do
      UDP_PUBLIC_PORT=$(natpmpc -g "$VPN_GATEWAY" -a "$LAST_PUBLIC_PORT" 0 udp 60 | grep -o 'Mapped public port [0-9]*' | awk '{print $4}')
      TCP_PUBLIC_PORT=$(natpmpc -g "$VPN_GATEWAY" -a "$LAST_PUBLIC_PORT" 0 tcp 60 | grep -o 'Mapped public port [0-9]*' | awk '{print $4}')
      if [ -n "$UDP_PUBLIC_PORT" ] && [ -n "$TCP_PUBLIC_PORT" ] && [ "$UDP_PUBLIC_PORT" -eq "$TCP_PUBLIC_PORT" ]; then
          PUBLIC_PORT="$UDP_PUBLIC_PORT"
          break
      else
          echo "UDP ($UDP_PUBLIC_PORT) and TCP ($TCP_PUBLIC_PORT) ports differ or missing, retrying..."
          sleep 1
      fi
  done

  # Update the port in OPNsense if it's different from the last one
  if [ "$PUBLIC_PORT" -ne "$LAST_PUBLIC_PORT" ]; then
  LAST_PUBLIC_PORT="$PUBLIC_PORT"
  ALIAS_UUID=$(get_alias_uuid "$FORWARDED_PORT_ALIAS_NAME")
  CURRENT_PORT_VALUE=$(get_alias_value "$ALIAS_UUID")
  if [ "$CURRENT_PORT_VALUE" != "$PUBLIC_PORT" ]; then
  set_alias_value "$ALIAS_UUID" "$PUBLIC_PORT"
  apply_changes
  echo "OPNsense forwarded port alias changed."
  fi
  fi

  # Update the torrent client port if needed
  CURRENT_TORRENT_PORT=$(get_torrent_port "$SID")
  if [ -z "$CURRENT_TORRENT_PORT" ]; then
      SID=$(login_torrent_client)  # Re-log if session has expired
      CURRENT_TORRENT_PORT=$(get_torrent_port "$SID")
  fi
  if [ "$CURRENT_TORRENT_PORT" -ne "$PUBLIC_PORT" ]; then
      set_torrent_port "$PUBLIC_PORT" "$SID"
      echo "Updated torrent client port to $PUBLIC_PORT."
  fi

  # Wait 45 seconds before requesting another port forward - 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 your 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:
natpmpc -g 10.2.0.1 -a 1 0 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 value for "Forwarded_Port" to something other than 12345. 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. BTW this is a good tool so if you are not bandwidth constrained consider seeding it.

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 1 0 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. You may see DNS requests as some windows programs will send DNS requests to all interfaces.

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.

Just facing the issue, thanks for this awesome tutorial.
I've one problem tho as I can't find
Quote/usr/ports/net/libnatpmp
in my OPNSense.
Did you install anything beforehand?

Yep, forgot a step, thanks for catching it.
Before
/usr/ports/net/libnatpmp
do
opnsense-code ports

Thanks for this guide.  I have a question though.  How should the firewall rules look like if wireguard and ProtonVPN is running exlusively in a client machine only?

I'm not sure I'm following. In order for this to work, you would not be using ProtonVPN on the client machine. Wireguard is running on the client machine to connect to the router and do split tunnel, and for qBittorrent to use it.

@_Dave_
That's a great guide, many thanks. QQ: to whom the 4.2.2.1 & 4.2.2.2 IP addresses belong to?

4.2.2.1 though 4.2.2.6 belong to Layer 3 Communications DNS servers. They don't advertise them as public but I have never had an issue with them.

and one more question: could you explain what the Virtual IPs are for ?

Tia.

Virtual IP's are assigned to the gateways for each tunnel so that we can distinguish between them. OPNsense will load balance between them, and those IP's will be used to send/receive from the VPN. Without them, when we get a response from the VPN, we wouldn't know what tunnel to send the response back to.

Great guide!

I've followed the guide mostly, deviating slightly as I'm not using Windows. I can't get the 2 VPN connections to work at the same time. I can get either 1 of the 2 I've selected by themselves but not both at the same time. Any ideas where to start troubleshooting?

I am assuming that the status on the dashboard for both gateways are green and you are getting handshakes for both tunnels? Try adding allow all rules with logging and seeing if they are relevant. Use packet capture on the interfaces to see if your getting traffic both ways. ChatGPT also _may_ be helpful. You only have one tunnel with port forwarding enabled, the other is disabled, and both are the same ProtonVPN server?

I have updated the NAP-PMP call to use the same private port as the public port. This allowed me to simplify the main script and firewall rules.