tprasadtp/protonvpn-docker

[RFE] - Port Forwarding

triesmon opened this issue ยท 11 comments

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:
image

Code of Conduct & PII Redaction

  • I agree to follow this project's Code of Conduct
  • I have removed any sensitive personally identifying information(PII) and secrets from in this issue report.

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.

source: https://protonvpn.com/blog/port-forwarding/

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.

source: https://protonvpn.com/blog/port-forwarding/

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:

  1. natpmpc -a 0 0 udp 60
  2. natpmpc -a 0 0 tcp 60
  3. 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/

tsjk commented

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
tsjk commented

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.

tsjk commented

The end result was this:
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?

tsjk commented

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",
  ]