PIA automatic port-forward update for Transmission daemon



  • Hi!

    This guide explains how to remotely update port number in Transmission daemon if PIA VPN restarts with new assigned forwarding port.

    I know that there is already a similar thread but it is quite old with several updates and variants of the solution. So i am sharing mine which takes slightly different approach. Yet main credits goes to original script author which i used as starting point.

    #Key points:
    -Working with pfSense 2.4.4-RELEASE-p3 + Transmission-daemon 2.94_3
    -Automatic NAT rule update upon PIA forward port change
    -Immediate port update of Transmission daemon without restart
    -pfSense updates the port remotely instead of transmission "fetching" it. No CRON job needed, no delay.
    -Key-based SSH connections.

    #Future ideas:
    -Email notifications in case of failure
    -Force-restart OpenVPN in case the the port gets closed from PIA side unnoticed (i never had such issue but few people faced such behavior)

    Hope this helps ... so here we go :]


    You need to have PIA VPN already configured. Use for example this guide

    I. pfSense side
    1.Enable SSH on pfSense
    System -> Advanced => tick "Enable Secure Shell"
    85d1ae08-9dc4-4986-8338-111ad412a287-image.png

    2.Create custom user
    Go to System -> User manager -> Add
    -Fill Username, password
    -Add "admins" group
    -Grant "WebCfg - All pages" and "User - System: Shell account access" priviledges
    -(Optional) generate SSH keys for your custom user
    3ccb6e6e-be2d-45c9-bd6e-f7af46d5f38d-image.png

    3.Install SUDO package
    -Go to System -> Package Manager => install SUDO package
    -Go to System -> sudo => create user permissions as bellow
    0a59318d-1fac-4b69-9cef-8a0dd38611d6-image.png

    4.Create Alias for port forward
    -Go to Firewall -> Aliases -> Ports
    -Create new port with name "Trans_Port"
    -Give it the current port (if you have it) or non-zero value
    b1685292-073a-4563-b9e7-91977e217a1c-image.png

    5.Create Alias for Transmission IP address
    -Go to Firewall -> Aliases -> IP
    -Create new port with name "Trans_IP"
    -Define IP or FQDN of your Transmisson daemon server
    89068de1-54a7-4475-9590-60848ddccfb4-image.png

    6.Create NAT rule for port-forward using the ALIAS instead of specific port/IP
    -Go to Firewall -> NAT
    -Create new rule like bellow (blue values could be different depending on your current VPN configuration)
    b6b5539f-1172-44db-bf8d-81c2e76bb7a3-image.png

    7.Generate SSH keys for enhanced security
    -SSH to the pfSense box with the user created in step 2.

    sudo su -
    #<enter your user password>
    #Enter an option: 8 for shell
    mkdir .ssh
    chmod 700 .ssh
    cd .ssh
    ssh-keygen -b 4096 -f ~/.ssh/id_rsa
    #When prompted for "Enter passphrase" just hit ENTER twice
    #Files id_rsa and id_rsa.pub will be generated.
    cat id_rsa.pub
    #Store the content of the file somewhere as it will be required later on
    

    8.Create custom devd config file
    -Still under root user from previous step do

    mkdir /usr/local/etc/devd
    cd /usr/local/etc/devd
    vi piaport.conf
    

    -paste following code and save ( :wq )

    notify 0 {
            match "system"          "IFNET";
            match "subsystem"       "(ovpnc1)";
            match "type"            "LINK_UP";
            action "logger $subsystem is UP";
            action "/home/custom/piaportforward/piaportfwd.sh";
    };
    
    notify 0 {
            match "system"          "IFNET";
            match "subsystem"       "(ovpnc1)";
            match "type"            "LINK_DOWN";
            action "logger $subsystem is DOWN";
    };
    

    Note: The "ovpnc1" is a technical name of the OpenVPN interface from within the pfSense UI
    f5515897-bed2-4718-bfbd-4093ab56c8a9-image.png

    9.Create the custom port-update script
    -Still under root user from previous step do

    mkdir /home/custom
    mkdir /home/custom/piaportforward
    cd /home/custom/piaportforward
    touch piaportfwd.sh
    chmod u+x piaportfwd.sh
    vi piaportfwd.sh
    

    -Paste the code bellow OR just unzip/upload the file [ piaportfwd.zip ] and chmod +x it.
    -No customization is necessary. Everything is fetched from config file.

    #!/bin/sh
    export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/root/bin
    
    # Vers: 1.2
    # Date: 1.2.2020
    
    ############# Update following if necessary ################
    
    # OpenVPN interface name
    OVPNIFACE='ovpnc1'
    
    # Alias name for port forward
    PORTALIAS='Trans_Port'
    
    # Alias name for Transmission IP
    IPALIAS='Trans_IP'
    
    # Email notification
    # TBD !!!
    
    ############## Other vars - do not touch ###################
    
    # pfSense config file and tempconfig location
    CONFFILE='/cf/conf/config.xml'
    TMPCONFFILE='/tmp/tmpconfig.xml'
    
    # Fetch remote Transmission IP from config
    TRANSIP=`xml sel -t -v "//alias[name=\"$IPALIAS\"]/address" $CONFFILE`
    
    ######################## MAIN CODE #########################
    
    # Wait for VPN interface to get fully UP
    sleep 10
    
    # Get the Unique ID of the client system
    # More details at: https://www.privateinternetaccess.com/forum/discussion/23431/new-pia-port-forwarding-api
    PIA_CLIENTID=`head -n 100 /dev/urandom | shasum -a 256 | tr -d " -"`
    logger "[PIA] Client-ID: $PIA_CLIENTID"
    
    # Request port-forward from PIA API
    PIA_PORT=$(curl --interface $OVPNIFACE "http://209.222.18.222:2000/\?client_id=$PIA_CLIENTID" 2>/dev/null | awk -F ':' '{ print $2 }'| awk -F '}' '{ print $1 }')
    
    if [ "$PIA_PORT" == "" ]; then
        PIA_PORT='0'
        logger "[PIA] Port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding."
        exit 0
    elif ! [ "$PIA_PORT" -eq "$PIA_PORT" ] 2> /dev/null; then
        logger "[PIA] Fatal error! Value $PIA_PORT is not a number. PIA API has most probably changed. Manual check necessary."
        # EMAIL
        exit 1
    elif [ "$PIA_PORT" -lt 1024 ] || [ "$PIA_PORT" -gt 65535  ]; then
        logger "[PIA] Fatal error! Value $PIA_PORT outside allowed port range. PIA API has most probably changed. Manual check necessary."
        # EMAIL
        exit 1
    fi
    logger "[PIA] Acquired forwarding port: $PIA_PORT"
    
    # Get current NAT port number using xmlstarlet to parse the config file.
    NATPORT=`xml sel -t -v "//alias[name=\"$PORTALIAS\"]/address" $CONFFILE`
    logger "[PIA] Current NAT rule port: $NATPORT"
    
    # If the acquired port is the same as already configured do not pointlessly reload config.
    if [ "$NATPORT" -eq "$PIA_PORT" ]; then
    	logger "[PIA] Acquired port $PIA_PORT equals the already configured port $NATPORT - no action required."
    	exit 0
    fi
    
    # If the port has changed update the tempconfig file.
    xml ed -u "//alias[name=\"$PORTALIAS\"]/address" -v $PIA_PORT $CONFFILE > $TMPCONFFILE
    
    # Validate the XML file just to ensure we don't nuke whole configuration
    xml val -q $TMPCONFFILE
    XMLVAL=$?
    if [ "$XMLVAL" -eq 1 ]; then
    	logger "[PIA] Fatal error! Updated tempconf file $TMPCONFFILE does not have valid XML format. Verify that the port alias is correct in script header and exists in pfSense Alias list"
      # EMAIL
    	exit 1
    fi
    
    # If the updated tempconfig is valid backup and replace the real config file.
    cp $CONFFILE ${CONFFILE}.bck 
    cp $TMPCONFFILE $CONFFILE
    
    # Force pfSense to re-read it's config and reload the rules.
    rm /tmp/config.cache
    /etc/rc.filter_configure
    logger "[PIA] New port $PIA_PORT udpated in pfSense config file."
    
    # Check if Transmission host is reachable
    ping -c1 -t1 -q $TRANSIP
    PINGRC=$?
    if [ "$PINGRC" -gt 0  ]; then
    	logger "[PIA] Error! Transmission host $TRANSIP is not reachable!"
      # EMAIL
    	exit 1
    fi
    
    # Update remote Transmission config with new port
    ssh transmission@${TRANSIP} "./transportupdate.sh ${PIA_PORT}"
    TRANSRC=$?
    
    if [ "$TRANSRC" -gt 0  ]; then
    	logger "[PIA] Error! Unable to remotely update Transmission port over SSH!"
      # EMAIL
    	exit 1
    fi
    logger "[PIA] New port successfully updated in remote Transmission system."
    
    exit 0
    
    

    -Disconnect form pfSense
    -(Optional) Disable SSH via WebUI under System -> Advanced => un-tick "Enable Secure Shell"

    II. Transmission host side
    -This part is for FreeBSD host systems but it will work just fine for any Linux host.
    -If there is something already configured on your side please read the steps anyway just to be sure there are no tiny difference.

    1.Enable SSH daemon
    -This is mainly for FreeBSD hosts (like FreeNAS) with Jails
    -Access the Jail via jexec and add following lines to /etc/rc.conf

    # Enable SSHd
    sshd_enable="YES"
    

    -Start sshd service by service sshd start

    2.Secure Transmission RPC Protocol
    -This is optional but recommended for security purpose
    -STOP the transmission daemon by service transmission stop
    -Edit /usr/local/etc/transmission/settings.json
    -Note that the location of settings.json may vary. The above path is from FeeBSD port.
    -Update/add following parameters. Replace username, password. Ensure that IP address of your pfSense is in whitelist.

    "rpc-authentication-required": true,
    "rpc-username": "SomeUserName",
    "rpc-password": "SomePassword",
    "rpc-whitelist": "127.0.0.1,10.10.10.1,10.10.10.5",
    

    -Start the transmission again service transmission start

    3.Create local port-update script
    -This needs to be done under transmission user, not as root!

    su - transmission
    touch ~/transportupdate.sh
    chmod u+x ~/transportupdate.sh
    vi ~/transportupdate.sh
    

    -Paste the code bellow OR just unzip/upload the file [ transportupdate.zip ] and chmod +x it.
    -UPDATE the USERNAME='username' and PASSWORD='password' at the beginning of the file as per the credentials configured in step II.2.

    #!/bin/sh
    export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/root/bin
    
    # Vers: 1.2
    # Date: 1.2.2020
    
    ############ Update these please #############
    
    # Transmission-remote WEB credentials
    USERNAME='username'
    PASSWORD='password'
    
    # Transmission-remote binary is usually under known environment location.
    # Validate the command is known by " which transmission-remote "
    # If the "transmission-remote" is not known try to " find / -name transmission-remote "
    # Then update the variable bellow with full-path to the binary
    TRANSREMOTE='transmission-remote'
    #TRANSREMOTE='/usr/local/bin/transmission-remote'
    
    ############ Rest of the code - do not touch #############
    
    # Port numbers
    NEWPORT="$1"
    
    # Verify that received new port is a valid number.
    if ! [ "$NEWPORT" -eq "$NEWPORT" ] 2> /dev/null; then
        logger "Non-numeric port ( $NEWPORT ) received from remote host. Aborting!"
        # EMAIL
        exit 1
    fi
    
    # Check if Transmission is running
    service transmission status
    TRANSSVCRC=$?
    if [ "$TRANSSVCRC" -gt 0  ]; then
      logger "Transmission service is not running. Port update aborted!"
      # EMAIL
    	exit 1
    else
      # Configure new port received from remote system
      $TRANSREMOTE --auth ${USERNAME}:${PASSWORD} -p ${NEWPORT}
      TRANSREMOTERC=$?
      if [ "$TRANSREMOTERC" -gt 0  ]; then
        logger "Error when calling transmission-remote binary. Port was NOT updated!"
        # EMAIL
    	 exit 1
      fi
      logger "Transmission port succesfully updated. New port is: ${NEWPORT}"
      exit 0
    fi
    
    

    4.Create/Upload public SSH key for pfSense connection
    -Still under transmission user

    mkdir ~/.ssh
    chmod 700 ~/.ssh
    cd ~/.ssh
    touch authorized_keys
    chmod 644 authorized_keys
    vi authorized_keys
    

    -Paste the content of id_rsa.pub generated in step I.7. and save ( :wq )

    5.Restart OpenVPN in pfSense
    3b0aef62-fca2-49c2-9520-d3c42e255eb0-image.png
    .
    -Wait for ~15secs and check Status -> System logs to see results
    da84729f-af4c-49ca-a2f7-c44125e731b2-image.png
    .
    -All OK, port changed
    f5fc0214-eca5-473a-a049-c87f1c0783f0-image.png
    .
    DONE !! :]



  • This is a great writeup - thanks for the effort you put into it.

    I'm getting an error with piaportfwd.sh and the xml command within it (I ran the command directly on the command line to show the problem with xml not being found);

    [2.4.5-RELEASE][root@router]: xml sel -t -v "//alias[name='Trans_IP'']/address" /cf/conf/config.xml
    xml: Command not found.
    

    Do you have any ideas on how to address this?



  • @dl_sdk Hi! Thanks, i did not realized the xmlstarlet is not pfSense default package. I guess I've installed it manually some time ago. Anyway the fix is simple:

    pkg install xmlstarlet
    

    Then relog your session and the script should work for you just fine.

    I'll update the guide above as well.
    //I can't do that "Error. You are only allowed to edit posts for 3600 second(s) after posting".



  • pkg install xmlstarlet worked!

    That lead to [PIA] Error! Unable to remotely update Transmission port over SSH! in the logs.

    I could ssh from pfsense to the transmission host without issue. On the transmission host, calling the transport update script, got;

    transmission@404-03:~$ ./transportupdate.sh 59227
    Unit transmission.service could not be found.
    

    Once I changed the service status to the below, things worked nicely.

    # Check if Transmission is running
    service transmission-daemon status
    

    Thank you once more for this.



  • @dl_sdk Hey, glad you figured it out. Yes the service name vary across distros/packages. In my case it is just a pkg inside BSD Jail. I could change that to get the real service name from service -e output but then it might fail on the service command itself.
    Anyway thanks for posting the command which works for you so other ppl can adjust the script if necessary.



  • if you have issues parsing the port from the API: i have noticed sometimes the query to the api is too fast, i added a sleep 15 before the API call in the script and now it seems to pull the port more reliably:

    # Get the Unique ID of the client system
    # More details at: https://www.privateinternetaccess.com/forum/discussion/23431>
    PIA_CLIENTID=`head -n 100 /dev/urandom | shasum -a 256 | tr -d " -"`
    logger "[PIA] Client-ID: $PIA_CLIENTID"
    
    sleep 15
    
    # Request port-forward from PIA API
    PIA_PORT=$(curl --interface $OVPNIFACE "http://209.222.18.222:2000/\?client_id=>
    
    
    

    hope it helps!



  • Seems to no longer be working with the move to "Next Gen" servers?



  • @Apocracy no it works, i am using it right now, i think right now only 2 or 3 servers support port forwarding, Canada and Germany don't work, they said they are working on a fix



  • This post is deleted!

Log in to reply