About me: My name is Solène Rapenne, pronouns she/her. I like learning and sharing knowledge. Hobbies: '(BSD OpenBSD Qubes OS Lisp cmdline gaming security QubesOS internet-stuff). I love percent and lambda characters. Qubes OS core team member, former OpenBSD developer solene@. No AI is involved in this blog.

Contact me: solene at dataswamp dot org or @solene@bsd.network (mastodon).

I'm a freelance OpenBSD, FreeBSD, Linux and Qubes OS consultant, this includes DevOps, DevSecOps, technical writing or documentation work. If you enjoy this blog, you can sponsor my open source work financially so I can write this blog and contribute to Free Software as my daily job.

How to use Proton VPN port forwarding

Written by Solène, on 31 August 2024.
Tags: #network #privacy #security #openbsd #linux

Comments on Fediverse/Mastodon

1. Introduction §

If you use Proton VPN with the paid plan, you have access to their port forwarding feature. It allows you to expose a TCP and/or UDP port of your machine on the public IP of your current VPN connection.

This can be useful for multiple use cases, let's see how to use it on Linux and OpenBSD.

Proton VPN documentation: port forwarding setup

If you do not have a privacy need with regard to the service you need to expose to the Internet, renting a cheap VPS is a better solution: cheaper price, stable public IP, no weird script for port forwarding, use of standard ports allowed, reverse DNS, etc...

2. Feature explanation §

Proton VPN port forwarding feature is not really practical, at least not as practical as doing a port forwarding with your local router. The NAT is done using NAT-PMP protocol (an alternative to UPnP), you will be given a random port number for 60 seconds. The random port number is the same for TCP and UDP.

Wikipedia page about NAT Port Mapping Protocol

There is a NAT PMPC client named natpmpc (available almost everywhere as a package) that need to run in an infinite loop to renew the port lease before it expires.

This is rather not practical for multiple reasons:

  • you get a random port assigned, so you must configure your daemon every time
  • the lease renewal script must run continuously
  • if something wrong happens (script failing, short network failure) that prevent renewing the lease, you will get a new random port

Although it has shortcomings, it is a useful feature that was dropped by other VPN providers because of abuses.

3. Setup §

Let me share a script I am using on Linux and OpenBSD that does the following:

  • get the port number
  • reconfigure the daemon using the port forwarding feature
  • infinite loop renewing the lease

You can run the script from supervisord (a process manager) to restart it upon failure.

Supervisor official project website

In the example, the Java daemon I2P will be used to demonstrate the configuration update using sed after being assigned the port number.

3.1. OpenBSD §

Install the package natpmpd to get the NAT-PMP client.

Create a script with the following content, and make it executable:

#!/bin/sh

PORT=$(natpmpc -a 1 0 udp 60 -g 10.2.0.1 | awk '/Mapped public/ { print $4 }')

# check if the current port is correct
grep "$PORT" /var/i2p/router.config || /etc/rc.d/i2p stop

# update the port in I2P config
sed -i -E "s,(^i2np.udp.port).*,\1=$PORT, ; s,(^i2np.udp.internalPort).*,\1=$PORT," /var/i2p/router.config

# make sure i2p is started (in case it was stopped just before)
/etc/rc.d/i2p start

while true
do
    date # use for debug only
    natpmpc -a 1 0 udp 60 -g 10.2.0.1 && natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || { echo "error Failure natpmpc $(date)"; break ; }
    sleep 45
done

The script will search for the port number in I2P configuration, stop the service if the port is not found. Then the port line is modified with sed (in all cases, it does not matter much). Finally, i2p is started, this will only do something in case i2p was stopped before, otherwise nothing happens.

Then, in an infinite loop with a 45 seconds frequency, there is a renewal of the TCP and UDP port forwarding happening. If something wrong happens, the script exits.

3.1.1. Using supervisord §

If you want to use supervisord to start the script at boot and maintain it running, install the package supervisor and create the file /etc/supervisord.d/nat.ini with the following content:

[program:natvpn]
command=/etc/supervisord.d/continue_nat.sh ; choose the path of your script
autorestart=unexpected ; when to restart if exited after running (def: unexpected)

Enable supervisord at boot, start it and verify it started (a configuration error prevents it from starting):

rcctl enable supervisord
rcctl start supervisord
rcctl check supervisord

3.1.2. Without supervisord §

Open a shell as root and execute the script and keep the terminal opened, or run it in a tmux session.

3.2. Linux §

The setup is exactly the same as for OpenBSD, just make sure the package providing natpmpc is installed.

Depending on your distribution, if you want to automate the script running / restart, you can run it from a systemd service with auto restart on failure, or use supervisord as explained above.

If you use a different network namespace, just make sure to prefix the commands using the VPN with ip netns exec vpn.

Here is the same example as above but using a network namespace named "vpn" to start i2p service and do the NAT query.

#!/bin/sh

PORT=$(ip netns exec vpn natpmpc -a 1 0 udp 60 -g 10.2.0.1 | awk '/Mapped public/ { print $4 }')

FILE=/var/i2p/.i2p/router.config

grep "$PORT" $FILE || sudo -u i2p /var/i2p/i2prouter stop
sed -i -E "s,(^i2np.udp.port).*,\1=$PORT, ; s,(^i2np.udp.internalPort).*,\1=$PORT," $FILE

ip netns exec vpn sudo -u i2p /var/i2p/i2prouter start

while true
do
    date
    ip netns exec vpn natpmpc -a 1 0 udp 60 -g 10.2.0.1 && ip netns exec vpn natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || { echo "error Failure natpmpc $(date)"; break ; }
    sleep 45
done

4. Conclusion §

Proton VPN port forwarding feature is useful when need to expose a local network service on a public IP. Automating it is required to make it work efficiently due to the unusual implementation.