Title: How to use Proton VPN port forwarding
Author: Solène
Date: 31 August 2024
Tags: network privacy security openbsd linux
Description: In this blog post, you will learn how to automate Proton
VPN port forwarding

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

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

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

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

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

### Without supervisord

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

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


```shell
#!/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
```

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