Before we create any rules, let's set up some Aliases to make rule creation easier and less error-prone:
Part 6 - ScriptingIn 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:
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 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="<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
}
# 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
# Get session id from qBittorrent
SID=$(login_torrent_client)
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
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 "$UDP_PUBLIC_PORT" ]; then
set_torrent_port "$UDP_PUBLIC_PORT" "$SID"
echo "Updated torrent client port to $UDP_PUBLIC_PORT."
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 (https://sdi-tool.org/download/) 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 (https://portchecker.co/check-v0), type in the IP address and port given by the above "natpmpc" command. The port should show "open".
Go to ipleak.net (https://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! :)