thrnz / docker-wireguard-pia

A Docker container for using Wireguard with PIA.
283 stars 54 forks source link

Dynamically forward remote port to fixed local port #72

Open lehmanju opened 1 year ago

lehmanju commented 1 year ago

To further simplify automatic reconfiguration of incoming ports, consider adding following snippet to one of the scripts:

# Map port to another fixed port
if [ -n "$MAP_PORT" ]; then
  iptables -A INPUT -i wg0 -p tcp --dport "$MAP_PORT" -j ACCEPT
  iptables -A INPUT -i wg0 -p udp --dport "$MAP_PORT" -j ACCEPT
  echo "$(date): Allowing incoming traffic on map port $MAP_PORT"
  iptables -A PREROUTING -t nat -i wg0 -p tcp --dport "$1" -j REDIRECT --to-port "$MAP_PORT"
  iptables -A PREROUTING -t nat -i wg0 -p udp --dport "$1" -j REDIRECT --to-port "$MAP_PORT"
  echo $(date): Forwarding traffic from port "$1" to "$MAP_PORT"
fi

This would allow other containers to listen on a fixed port while PIA changes the remote port dynamically.

BastionNtB commented 11 months ago

I don't think this will work, though I would desperately want it too.

Assuming you're torrenting, clients will report to the tracker the port your client is using. If your client is using 1000 for example, and you've mapped it to the true outbound port of 47209, clients trying to connect with you will try using port 1000, not the outbound port that was forwarded. But at that time no one would be able to connect.

jimger commented 3 months ago

I had the same idea as @lehmanju but can confirm that what probably @BastionNtB says is true. Was trying to set QBittorrent's port to a static one and forward the forwarded port there, but couldn't work...

BastionNtB commented 3 months ago

This is the script I made to set the QB port after a successful connection. Not sure if this helps, cause the original question was port related, but not necessarily attempting to set the port.

I have a volume bind with this script and call "PORT_SCRIPT" environment variable in the docker-wireguard config.

PORT_SCRIPT='/pia-shared/setqbtport.sh'

The script itself will have multiple attempts to connect, so it will essentially wait till it is able to connect; should QB take longer to load. It will also keep track of the original and new port so it won't keep changing it if it's not needed for whatever reason. Do keep in mind I planned for this to work with authentication, but ended up with auth bypass. Not sure if that was because it wouldn't work or whatnot (been awhile since I've had to mess with it), but you may need to bypass auth as well unless the script can be fixed. If you do end up using it as is, the script will work out of the box assuming the environment is similar (and QB has bypass auth for localhost/127.0.0.1 enabled). I didn't share it before because I was unhappy with how I ended up making the script to function, and wanted to make it more cleaner, but alas, never got around to it... Hope it helps someone.

#!/bin/bash

# Check if the correct number of arguments is provided
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <new_port>"
    exit 1
fi

# Output Variables
cookie_file="/tmp/qb-cookies.txt"
logging_path="/pia-shared"
logging_name="qbiport-$(date '+%Y-%m-%d').log"
log_file="/${logging_path}/${logging_name}"

# Set qBittorrent web server URL and API endpoint
QBT_URL="http://127.0.0.1:8080/"
QBT_API="${QBT_URL}api/v2/"
QBT_LOGINENDPOINT="auth/login"
QBT_SETPFENDPOINT="app/setPreferences"
QBT_GETPFENDPOINT="app/preferences"

# Set qBittorrent web server credentials - comment out if qbittorrent is set to bypass auth.
#QBT_WEBUIUSER="your_username"
#QBT_WEBUIPASS="your_password"

# Set the new port number from the command-line argument
NEW_PORT=$1

# Function to log messages
log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$log_file"
}

log "Port Script has started, will attempt to change port to ${NEW_PORT}"

# Check if QBT_WEBUIUSER and QBT_WEBUIPASS are undefined
if [ -z "$QBT_WEBUIUSER" ] && [ -z "$QBT_WEBUIPASS" ]; then
    AUTHREQUIRED=false
    log "No authentication will be used for connection."
else
    AUTHREQUIRED=true
    log "Authentication will be used for connection."
fi

# Function to check status of qBittorrent web server
check_qbit_status() {
    local max_time=60
    curl --location "${QBT_URL}" \
         --include \
         --silent \
         --max-time $max_time \
         | sed -n -E 's/HTTP\/.+ ([0-9]+).*/\1/p'
}

# Function to authenticate with qBittorrent web server
new_auth_qbit() {
    local max_time=10
    curl --location "${QBT_API}${QBT_LOGINENDPOINT}" \
         --include \
         --silent \
         --max-time $max_time \
         --header "Referer: ${QBT_URL}"\
         --cookie-jar $cookie_file \
         --data "username=${QBT_WEBUIUSER}&password=${QBT_WEBUIPASS}" \
         | sed -n -E 's/HTTP\/.+ ([0-9]+).*/\1/p'
}

# Function to get the current port from qBittorrent web server
get_qbit_port() {
    local max_time=10
    curl --location "${QBT_API}${QBT_GETPFENDPOINT}" \
         --silent \
         --max-time $max_time \
         --header "Referer: ${QBT_URL}"\
         --cookie-jar $cookie_file \
         | jq '.listen_port'
}

# Function to set the new port in qBittorrent web server
set_qbit_port() {
    local max_time=10
    curl --location "${QBT_API}${QBT_SETPFENDPOINT}" \
         --include \
         --silent \
         --max-time $max_time \
         --header "Referer: ${QBT_URL}"\
         --cookie-jar $cookie_file \
         --data "json={\"listen_port\":${NEW_PORT}}" \
         | sed -n -E 's/HTTP\/.+ ([0-9]+).*/\1/p'
}

log "Attempting connection with ${QBT_URL}..."
retry_count=0
max_retry=300
while true; do
    response=$(check_qbit_status)
    if [[ $response -eq 200 ]]; then
        log "Connection successful. (http: ${response})"
        break
    elif ((retry_count++ == $max_retry)); then
        log "Failed to reach ${QBT_URL} within ${max_retry} tries. Exiting . . ."
        exit 1
    else
        log "${QBT_URL} responded with \"${response}\" expected '200', retrying (${retry_count}/${max_retry}) . . ."
        sleep 10
    fi
done

# Authenticate with qBittorrent web server
if $AUTHREQUIRED; then
    retry_count=0
    max_retry=3
    while true; do
        log "Attempting authentication ${QBT_API}${QBT_SETPFENDPOINT}"
        response=$(new_auth_qbit)
        if [[ $response -eq 200 ]]; then
            log "Authentication successful. (http: ${response})"
            break
        elif ((retry_count++ == $max_retry)); then
            log "Failed to reach ${QBT_URL} within ${max_retry} tries. Exiting . . ."
            exit 1
        else
            log "${QBT_URL} responded with \"${response}\" expected '200', retrying (${retry_count}/${max_retry}) . . ."
            sleep 10
        fi
    done
fi

log "Fetching current port for confirmation of change . . ."
retry_count=0
max_retry=6
while true; do
    CURRENT_PORT=$(get_qbit_port)
    if [[ $CURRENT_PORT =~ ^[0-9]+$ ]]; then
        log "Detected port number: ${CURRENT_PORT}"
    else
        log "${QBT_URL} responded with ${CURRENT_PORT} expected a number pattern . . ."
        sleep 10
    fi

    if [[ $CURRENT_PORT != $NEW_PORT ]]; then
        log "Attempting to change port from ${CURRENT_PORT} to ${NEW_PORT} . . ."
        response=$(set_qbit_port)
        if [[ $response -eq 200 ]]; then
            log "Port change request successful, checking for port change.. (http: ${response})"
            sleep 10
        elif ((retry_count++ == $max_retry)); then
            log "Failed to set port from ${CURRENT_PORT} to ${NEW_PORT} within ${max_retry} tries. Exiting . . ."
            exit 1
        else
            log "${QBT_URL} responded with http \"${response}\" expected 200, retrying (${retry_count}/${max_retry}) . . ."
            sleep 10
        fi
    else
        log "Listen port is correctly set."
        break
    fi
done

# Clean up temporary files
rm -f $cookie_file
jimger commented 3 months ago

This is the script I made to set the QB port after a successful connection. Not sure if this helps, cause the original question was port related, but not necessarily attempting to set the port.

I have a volume bind with this script and call "PORT_SCRIPT" environment variable in the docker-wireguard config.

PORT_SCRIPT='/pia-shared/setqbtport.sh'

The script itself will have multiple attempts to connect, so it will essentially wait till it is able to connect; should QB take longer to load. It will also keep track of the original and new port so it won't keep changing it if it's not needed for whatever reason. Do keep in mind I planned for this to work with authentication, but ended up with auth bypass. Not sure if that was because it wouldn't work or whatnot (been awhile since I've had to mess with it), but you may need to bypass auth as well unless the script can be fixed. If you do end up using it as is, the script will work out of the box assuming the environment is similar (and QB has bypass auth for localhost/127.0.0.1 enabled). I didn't share it before because I was unhappy with how I ended up making the script to function, and wanted to make it more cleaner, but alas, never got around to it... Hope it helps someone.

#!/bin/bash

# Check if the correct number of arguments is provided
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <new_port>"
    exit 1
fi

# Output Variables
cookie_file="/tmp/qb-cookies.txt"
logging_path="/pia-shared"
logging_name="qbiport-$(date '+%Y-%m-%d').log"
log_file="/${logging_path}/${logging_name}"

# Set qBittorrent web server URL and API endpoint
QBT_URL="http://127.0.0.1:8080/"
QBT_API="${QBT_URL}api/v2/"
QBT_LOGINENDPOINT="auth/login"
QBT_SETPFENDPOINT="app/setPreferences"
QBT_GETPFENDPOINT="app/preferences"

# Set qBittorrent web server credentials - comment out if qbittorrent is set to bypass auth.
#QBT_WEBUIUSER="your_username"
#QBT_WEBUIPASS="your_password"

# Set the new port number from the command-line argument
NEW_PORT=$1

# Function to log messages
log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$log_file"
}

log "Port Script has started, will attempt to change port to ${NEW_PORT}"

# Check if QBT_WEBUIUSER and QBT_WEBUIPASS are undefined
if [ -z "$QBT_WEBUIUSER" ] && [ -z "$QBT_WEBUIPASS" ]; then
    AUTHREQUIRED=false
    log "No authentication will be used for connection."
else
    AUTHREQUIRED=true
    log "Authentication will be used for connection."
fi

# Function to check status of qBittorrent web server
check_qbit_status() {
    local max_time=60
    curl --location "${QBT_URL}" \
         --include \
         --silent \
         --max-time $max_time \
         | sed -n -E 's/HTTP\/.+ ([0-9]+).*/\1/p'
}

# Function to authenticate with qBittorrent web server
new_auth_qbit() {
    local max_time=10
    curl --location "${QBT_API}${QBT_LOGINENDPOINT}" \
         --include \
         --silent \
         --max-time $max_time \
         --header "Referer: ${QBT_URL}"\
         --cookie-jar $cookie_file \
         --data "username=${QBT_WEBUIUSER}&password=${QBT_WEBUIPASS}" \
         | sed -n -E 's/HTTP\/.+ ([0-9]+).*/\1/p'
}

# Function to get the current port from qBittorrent web server
get_qbit_port() {
    local max_time=10
    curl --location "${QBT_API}${QBT_GETPFENDPOINT}" \
         --silent \
         --max-time $max_time \
         --header "Referer: ${QBT_URL}"\
         --cookie-jar $cookie_file \
         | jq '.listen_port'
}

# Function to set the new port in qBittorrent web server
set_qbit_port() {
    local max_time=10
    curl --location "${QBT_API}${QBT_SETPFENDPOINT}" \
         --include \
         --silent \
         --max-time $max_time \
         --header "Referer: ${QBT_URL}"\
         --cookie-jar $cookie_file \
         --data "json={\"listen_port\":${NEW_PORT}}" \
         | sed -n -E 's/HTTP\/.+ ([0-9]+).*/\1/p'
}

log "Attempting connection with ${QBT_URL}..."
retry_count=0
max_retry=300
while true; do
    response=$(check_qbit_status)
    if [[ $response -eq 200 ]]; then
        log "Connection successful. (http: ${response})"
        break
    elif ((retry_count++ == $max_retry)); then
        log "Failed to reach ${QBT_URL} within ${max_retry} tries. Exiting . . ."
        exit 1
    else
        log "${QBT_URL} responded with \"${response}\" expected '200', retrying (${retry_count}/${max_retry}) . . ."
        sleep 10
    fi
done

# Authenticate with qBittorrent web server
if $AUTHREQUIRED; then
    retry_count=0
    max_retry=3
    while true; do
        log "Attempting authentication ${QBT_API}${QBT_SETPFENDPOINT}"
        response=$(new_auth_qbit)
        if [[ $response -eq 200 ]]; then
            log "Authentication successful. (http: ${response})"
            break
        elif ((retry_count++ == $max_retry)); then
            log "Failed to reach ${QBT_URL} within ${max_retry} tries. Exiting . . ."
            exit 1
        else
            log "${QBT_URL} responded with \"${response}\" expected '200', retrying (${retry_count}/${max_retry}) . . ."
            sleep 10
        fi
    done
fi

log "Fetching current port for confirmation of change . . ."
retry_count=0
max_retry=6
while true; do
    CURRENT_PORT=$(get_qbit_port)
    if [[ $CURRENT_PORT =~ ^[0-9]+$ ]]; then
        log "Detected port number: ${CURRENT_PORT}"
    else
        log "${QBT_URL} responded with ${CURRENT_PORT} expected a number pattern . . ."
        sleep 10
    fi

    if [[ $CURRENT_PORT != $NEW_PORT ]]; then
        log "Attempting to change port from ${CURRENT_PORT} to ${NEW_PORT} . . ."
        response=$(set_qbit_port)
        if [[ $response -eq 200 ]]; then
            log "Port change request successful, checking for port change.. (http: ${response})"
            sleep 10
        elif ((retry_count++ == $max_retry)); then
            log "Failed to set port from ${CURRENT_PORT} to ${NEW_PORT} within ${max_retry} tries. Exiting . . ."
            exit 1
        else
            log "${QBT_URL} responded with http \"${response}\" expected 200, retrying (${retry_count}/${max_retry}) . . ."
            sleep 10
        fi
    else
        log "Listen port is correctly set."
        break
    fi
done

# Clean up temporary files
rm -f $cookie_file

Cheers tbh later on I found: https://github.com/thrnz/docker-wireguard-pia/issues/26#issuecomment-925874550 Which actually works great... Cheers :)

BastionNtB commented 3 months ago

Glad you got it working! I found that one too, but I didn't like that it wouldn't wait long enough, didn't have logic to detect certain conditions, or have more robust logging to show what was going on, which is why mine ended up being longer.

jimger commented 3 months ago

Glad you got it working! I found that one too, but I didn't like that it wouldn't wait long enough, didn't have logic to detect certain conditions, or have more robust logging to show what was going on, which is why mine ended up being longer.

Fair, though to be honest, I just tried, but although when no authenication it is fine, it fails to authenicate with user/pass provided for some reason.... I modified the script to "echo" the user and the pass that I pass through env variables and they seem correct :(

ScottESanDiego commented 3 months ago

FWIW, I ended up just making another tiny Docker container that runs alongside PIA and qBittorrent, using inotifywait to watch for the port changes. Seems to work reliably for me.

https://github.com/ScottESanDiego/qbittorrent-porthelper