Open triesmon opened 2 years ago
Last time I checked, port forwarding was only available in the Windows Desktop version. Unless it has recently changed, it is not be possible to enable it in this service.
There are two features request for that :
Last time I checked, port forwarding was only available in the Windows Desktop version. Unless it has recently changed, it is not be possible to enable it in this service.
Port forwarding works under Linux, even with an OpenVPN client ;)
First of all, append +pmp
to your OpenVPN username.
Once connected to ProtonVPN networks, we should issue multiple commands:
natpmpc
alone, which confirms (or not) the availability of Port Forwarding
If it's available, then we must do:
natpmpc -a 0 0 udp 60
natpmpc -a 0 0 tcp 60
while true ; do date ; natpmpc -a 0 0 udp 60 && natpmpc -a 0 0 tcp 60 || { echo -e "ERROR with natpmpc command \a" ; break ; } ; sleep 45 ; done
Source: https://protonvpn.com/support/port-forwarding-manual-setup/
I used 7.2.3-debug-alexis
for my experiment here - as it uses Debian. Conveniently it was just created. Anyway, the following kind of works - given some extra tools such as natpmpc
and moreutils
.
docker-compose.yml
:
version: '2.3'
services:
protonwire:
build: .
container_name: protonwire
image: localhost:5000/protonwire:latest
init: true
restart: never
network_mode: bridge
environment:
DEBUG: "0"
KILL_SWITCH: "0"
PROTONVPN_SERVER: "xxx#N"
TZ: "Etc/UTC"
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.conf.all.rp_filter=2
- net.ipv6.conf.all.disable_ipv6=1
healthcheck:
test: ["CMD", "/bin/bash", "-c", "/usr/bin/protonwire check --container --silent || exit 1"]
interval: 120s
start_period: 20s
volumes:
- type: tmpfs
target: /tmp
- type: bind
source: ./private.key
target: /etc/protonwire/private-key
read_only: true
protonwire-natpmpc:
container_name: protonwire-natpmpc
image: localhost:5000/protonwire:latest
restart: never
depends_on:
protonwire:
condition: service_healthy
network_mode: service:protonwire
environment:
TZ: "Etc/UTC"
volumes:
- type: bind
source: /tmp/protonwire-natpmpc-log
target: /log
entrypoint:
- "/bin/bash"
- "-c"
command: >
"while true; do
{ date '+%Y-%m-%d %H:%M:%S'; natpmpc -a 1 0 udp 60 -g 10.2.0.1 && natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || break; } 2>&1 | ts '[%Y-%m-%dT%H:%M:%S] |' >> '/log/protonwire-natpmpc.log'; sleep 45 &
wait $!; [[ $(wc -l '/log/protonwire-natpmpc.log' | awk -F ' ' '{ print $1 }') -lt $((128 * 1024)) ]] || sed 1,$(( (128 * 1024) - (96 * 1024) ))d '/log/protonwire-natpmpc.log' | sponge '/log/protonwire-natpmpc.log';
done"
healthcheck:
test: ["CMD-SHELL", "[ $$(( $$(date '+%s') - $$(stat -c '%Y' '/log/protonwire-natpmpc.log') )) -lt 120 ] && /usr/bin/tail -n 23 '/log/protonwire-natpmpc.log' | grep -q -E 'Public IP address : [0-9]{1,3}(\\.[0-9]{1,3}){3}\\s*$$' && /usr/bin/tail -n 23 '/log/protonwire-natpmpc.log' | grep -q -E 'Mapped public port [1-9][0-9]* protocol UDP to local port 0 liftime 60\\s*$$' && /usr/bin/tail -n 23 '/log/protonwire-natpmpc.log' | grep -q -E 'Mapped public port [1-9][0-9]* protocol TCP to local port 0 liftime 60\\s*$$"]
interval: 120s
start_period: 30s
It seems to create a mapping. I tried to connect to it by throwing up a socat
web server (given socat
), like in a shell in the container
# socat \
-v -d -d \
TCP-LISTEN:<PORT_FROM_LOGS>,crlf,reuseaddr,fork \
SYSTEM:"
echo HTTP/1.1 200 OK;
echo Content-Type\: text/plain;
echo;
echo \"Server: \$SOCAT_SOCKADDR:\$SOCAT_SOCKPORT\";
echo \"Client: \$SOCAT_PEERADDR:\$SOCAT_PEERPORT\";
"
Seems to also work.
As does like (I actually use podman and podman-compose in the above):
$ podman run --rm --network container:protonwire -it ghcr.io/static-web-server/static-web-server:2 -p <PORT_FROM_LOGS> -g info
I guess one can use any container as a natpmpc
-helper. I just did what was easiest. Then one can maybe publish this ip and port via something easily accessible - or just share the logs with another container and parse them there.
The end result was this: https://github.com/tprasadtp/protonvpn-docker/compare/master...tsjk:protonvpn-docker:master
I'm using it for a service I run, and it seems ok.
@tsjk 7.3.0-alpha1 has all the tools you need to run it (though without the custom scripts from your fork). Though adding those requires bit more work would you be open to add helpers as a sub-commands to protonwire?
Sure. Let me work on it some more. The other day I realized that I really don't want this container to die and vanish (for whatever reason - I observed some fatal indexing error when metadata fails to refresh) as that will disable ALL networking in dependent containers. If the dependent containers have this and Tor via a socks port, for instance - the vpn temporarily going down is not a big problem. I'll tend to this issue first and then look at sub-commanding. I imagine looping the protonwire script with a signal handler.
Hello guys ! I managed to make port forwarding work starting from the "caddy proxy" docker compose example in the readme, using 7.3.0-alpha1 as you suggested. I did it in an ugly way because I suck at bash but it'll probably help some people anyways (and it's thus pretty simple to understand).
Here is the important part of my docker compose :
services:
protonvpn:
container_name: protonvpn
image: ghcr.io/tprasadtp/protonwire:7.3.0-alpha1
command: "sh /config/protonvpn-init.sh"
environment:
- WIREGUARD_PRIVATE_KEY=YOURKEY
- PROTONVPN_SERVER=YOURNODEURL
cap_add:
- NET_ADMIN
sysctls:
net.ipv4.conf.all.rp_filter: 2
net.ipv6.conf.all.disable_ipv6: 1
volumes:
- type: tmpfs
target: /tmp
- /local_path_to/protonvpn-port:/config/protonvpn-port
- /local_path_to/protonvpn-init.sh:/config/protonvpn-init.sh
ports:
- "yourport:yourport"
yourservice:
image: yourservice
container_name: yourservice
network_mode: service:protonvpn
volumes:
- /local_path_to/protonvpn-port:/config/protonvpn-port # Do whatever you want with this
restart: always
Here is the entry script for protonvpn :
/usr/bin/protonwire connect --container &
sleep 10
natpmpc -a 1 0 udp 60 -g 10.2.0.1
natpmpc -a 1 0 tcp 60 -g 10.2.0.1 | grep -oP 'public\ port\ \K\w+' > /config/protonvpn-port
echo "Port written to protonvpn-port file"
cat /config/protonvpn-port
while true ; do date ; natpmpc -a 1 0 udp 60 -g 10.2.0.1 && natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || { echo -e "ERROR with natpmpc command \a" ; break ; } ; sleep 45 ; done
Nothing crazy here, I start protonwire in the background, put in an arbitrary sleep because I didn't know how else I could wait for protonwire to connect successfully (there is probably a smart way to do this), and then just execute the natpmpc commands exactly like in the protonvpn documentation and extract the port number to a file on the host system via regexp.
After that I just retrieve the content of the protonvpn-port file in my other container and update my application with it.
Of course, if any error happens, everything goes to hell, it's a quick script for non critical applications, don't use it for anything important !
Here is the entry script for protonvpn :
/usr/bin/protonwire connect --container & sleep 10 natpmpc -a 1 0 udp 60 -g 10.2.0.1 natpmpc -a 1 0 tcp 60 -g 10.2.0.1 | grep -oP 'public\ port\ \K\w+' > /config/protonvpn-port echo "Port written to protonvpn-port file" cat /config/protonvpn-port while true ; do date ; natpmpc -a 1 0 udp 60 -g 10.2.0.1 && natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || { echo -e "ERROR with natpmpc command \a" ; break ; } ; sleep 45 ; done
i'm using @le-martre's solution for a k8s deployment:
command: ["/bin/bash", "-c"]
args:
[
"/usr/bin/protonwire connect --container & sleep 10; natpmpc -a 1 0 udp 60 -g 10.2.0.1; natpmpc -a 1 0 tcp 60 -g 10.2.0.1 | grep -oP 'public\\ port\\ \\K\\w+' > /config/protonvpn-port; echo \"Port written to protonvpn-port file\"; cat /config/protonvpn-port; while true; do date; natpmpc -a 1 0 udp 60 -g 10.2.0.1 && natpmpc -a 1 0 tcp 60 -g 10.2.0.1 || { echo -e \"ERROR with natpmpc command \\a\"; break; }; sleep 45; done",
]
Hello everyone,
I've made a few improvements to the scripts shared in this thread.
I am using on my Docker Compose as a multi-line command for now, perhaps if it gets bigger I will transform in a script with its own file.
To pass the port value to a secondary container, you can write to a shared file and use the healthcheck to control the initialization of the secondary container. In Docker Compose multi-line will be something like this;
services:
protonvpn:
<YOUR_OTHER_CONTAINER_CONFIGS>
volumes:
- ./natpmp.env:/tmp/natpmp.env:z
healthcheck:
test: protonwire healthcheck --silent --service-status-file || exit 1
interval: 60s
retries: 3
start_interval: 45s
timeout: 3s
entrypoint: ["/bin/sh","-c"]
command:
- |
/usr/bin/protonwire connect --service --p2p --kill-switch --check-interval 60 &
__check_connection () {
protonwire healthcheck --silent --service-status-file
}
__config_and_renew_port_forward () {
while true; do
natpmpc -a 1 0 udp 60 -g 10.2.0.1 > /dev/null || {
echo "[ERROR ][NATPMP] Command natpmpc has failed! Retrying in 3 seconds...";
sleep 3;
break;
};
natpmpc -a 1 0 tcp 60 -g 10.2.0.1 \
| grep -oP 'public\ port\ \K\w+' > /tmp/protonvpn-port-forward.txt;
echo "NATPMP_PORT=$(cat /tmp/protonvpn-port-forward.txt)" > /tmp/natpmp.env
echo "[SUCCESS ][NATPMP] Port forward successful! Will renew in 45 seconds...";
echo "[INFO ][NATPMP] Port forward enabled at port: $(cat /tmp/protonvpn-port-forward.txt)";
sleep 45;
done
}
until __check_connection; do
echo "[ERROR ][NATPMP] VPN connection NOT established! Retrying in 10 seconds..."
sleep 10
done
echo "[SUCCESS ][NATPMP] VPN connection established! Configuring it now..."
__config_and_renew_port_forward
other_container:
image: other_container
container_name: other_container
depends_on:
protonvpn:
condition: service_healthy
restart: true
env_file: ["./natpmp.env"]
Some details about the improvements.
First of all, the option --container
is deprecated as it is pointed here, --service
is the new one.
Second, instead of using the arbitrary sleep to check the connection, there is a command that we could utilize; protonwire healthcheck --silent --service-status-file
Lastly, the logs output is cleaner and it can be easily filtered directly with the docker logs
command OR if you are ingesting this logs into any observability platform like ELK, New Relic, etc... The command docker logs -f protonvpn | grep NATPMP
would produce this;
Finally, there are some corner cases that could still be improved. For example, when dealing with error control of the natpmpc
commands, and specially if the port changes and requires a restart of the secondary container. The latter could be solved if there is a way to instead of the ProtonVPN reconnecting when the connection drops there was a way to make the container restart, hence restarting the secondary container also. Maybe I will work on it in the future to address these.
Version of
protonvpn-docker
NA
Details about Feature/Enhancement
Having the ability to connect to a port forwarding server and maybe providing an endpoint or something in the logs that prints the currently enabled forwarded port would be useful.
Here's a reference to the feature in the desktop client:
Code of Conduct & PII Redaction