Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFE] - Port Forwarding #125

Open
2 tasks done
triesmon opened this issue Jun 5, 2022 · 11 comments
Open
2 tasks done

[RFE] - Port Forwarding #125

triesmon opened this issue Jun 5, 2022 · 11 comments
Labels
api/upstream Upstream API bug/issue feature Feature requests s/keep-open Keep the issue /PR open and prevent from auto closing

Comments

@triesmon
Copy link

triesmon commented Jun 5, 2022

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.
@triesmon triesmon added the feature Feature requests label Jun 5, 2022
@gpascualg
Copy link

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/

@tprasadtp tprasadtp changed the title [RFE] - [RFE] - Port Forwarding Sep 24, 2022
@tprasadtp tprasadtp mentioned this issue Sep 24, 2022
2 tasks
@adriy-be
Copy link

adriy-be commented Jan 17, 2023

@Ludofloria
Copy link
Contributor

Ludofloria commented Mar 6, 2023

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/

@tprasadtp tprasadtp added the api/upstream Upstream API bug/issue label Apr 9, 2023
@tprasadtp tprasadtp added the s/keep-open Keep the issue /PR open and prevent from auto closing label Apr 10, 2023
@tsjk
Copy link

tsjk commented Oct 3, 2023

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
Copy link

tsjk commented Oct 3, 2023

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
Copy link

tsjk commented Oct 5, 2023

The end result was this:
master...tsjk:protonvpn-docker:master

I'm using it for a service I run, and it seems ok.

@tprasadtp
Copy link
Owner

@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
Copy link

tsjk commented Oct 7, 2023

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.

@le-martre
Copy link

le-martre commented Oct 13, 2023

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 !

@ianhundere
Copy link

ianhundere commented Nov 7, 2023

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

@ivan-pinatti
Copy link

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;

image

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api/upstream Upstream API bug/issue feature Feature requests s/keep-open Keep the issue /PR open and prevent from auto closing
Projects
None yet
Development

No branches or pull requests

9 participants