tprasadtp / protonvpn-docker

ProtonVPN Wireguard Docker Image. Supports ARMv8 (64-bit ) and x86 (64-Bit).
GNU General Public License v3.0
274 stars 24 forks source link

Using ProtonVPN-Docker as a 'proxy' docker results in timeouts on ports #15

Closed LovelessCodes closed 2 years ago

LovelessCodes commented 3 years ago

Trying this configuration on docker-compose, when trying to access the opened ports for the web interface of in this case, sabnzbd, it basically loads forever and timeouts.

version: "3.8"
services:
  sabnzbd:
    image: ghcr.io/linuxserver/sabnzbd
    container_name: sabnzbd
    network_mode: service:protonvpn
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Amsterdam
      - UMASK_SET=022
      - WEBUI_PORT=8080
    volumes:
      - /plex:/config
      - /plex/downloads:/downloads
    depends_on:
      - sabnzbd-vpn
    restart: unless-stopped
  protonvpn:
    image: ghcr.io/tprasadtp/protonvpn:latest
    container_name: sabnzbd-vpn
    environment:
      - PROTONVPN_USERNAME=REDACTED
      - PROTONVPN_PASSWORD=REDACTED
      - PROTONVPN_TIER=2
      - PROTONVPN_COUNTRY=NL
      - DEBUG=1
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    ports:
      - 8080:8080

The VPN connects fine and the health checks seem fine as well - I've also tried to exclude my IP through the PROTONVPN_EXCLUDE_CIDRS env. variable. The Web Interface can connect for a second before the VPN gets connected. Both containers have the same IP when trying to curl ifconfig.io - obviously getting a VPN IP. I'm trying to access the Web Interface from outside the LAN and have opened the ports and have tested that it works without the ProtonVPN Docker. I must disclose that I'm a complete "newb" to Docker and only recently discovered it, so I'm in no way an expert.

tprasadtp commented 3 years ago

Try adding your docker network CIDRs to exclude list. Possibly related to #11.

oatmealm commented 3 years ago

The CIDR is not the issue. I have a similar setup with the CIDR set correctly to my private local network, as required, yet I still see the same problem and I can't connect to the internet via the container.

tprasadtp commented 3 years ago

@oatmealm Could you please check it with 4.x release? I think it was created by enabling killswitch in the config file.

killswitch = 1

and because protonvpn-cli uses iptables -P INPUT DROP when killswitch=1, this resulted in dropped traffic and hence timeout on ports :grin: :facepalm: :facepalm:

Try this docker-compose config and let me know if it still results in timeouts

version: '3.4'
services:
  protonvpn:
    container_name: protonvpn
    environment:
      PROTONVPN_COUNTRY: NL
      PROTONVPN_USERNAME: ${PROTONVPN_USERNAME}
      PROTONVPN_PASSWORD: ${PROTONVPN_PASSWORD}
      PROTONVPN_TIER: ${PROTONVPN_TIER:-0}
    image: ghcr.io/tprasadtp/protonvpn:latest
    restart: unless-stopped
    networks:
      - internet
      - proxy
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    expose:
      - 8000
  pyload:
    depends_on:
      - protonvpn
    container_name: pyload
    environment:
      TZ: "Europe/Berlin"
      PGID: "1000"
      PUID: "1000"
    image: linuxserver/pyload:latest
    restart: unless-stopped
    userns_mode: host
    network_mode: service:protonvpn
    volumes:
      - config:/config
      - ./downloads/:/downloads/:rw
volumes:
  config:
networks:
  internet:
  proxy:
    internal: true

Be sure to pull the latest image and export your credentials as env variables first.

oatmealm commented 3 years ago

Will get to it ASAP. Thanks!

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.

maclarel commented 3 years ago

I'm hitting this as well, with or without kill switch usage. I'll get a brief blip of connectivity to whatever service is being exposed (qBittorrent in my case) and once the VPN connections I get timeouts.

If I disconnect the VPN from in the container, it is immediately available again.

I believe, to some extent, this may be unavoidable as the routes pushed by the VPN only allow outbound traffic over the VPN interface:

# ip route
0.0.0.0/1 via 10.20.0.1 dev proton0
default via 192.168.240.1 dev eth0
10.20.0.0/16 dev proton0 proto kernel scope link src 10.20.0.3
128.0.0.0/1 via 10.20.0.1 dev proton0
172.20.0.0/16 dev eth1 proto kernel scope link src 172.20.0.2
185.159.158.58 via 192.168.240.1 dev eth0
192.168.240.0/20 dev eth0 proto kernel scope link src 192.168.240.2

vs the following when the VPN is not connected

# ip route
default via 192.168.240.1 dev eth0
172.20.0.0/16 dev eth1 proto kernel scope link src 172.20.0.2
192.168.240.0/20 dev eth0 proto kernel scope link src 192.168.240.2

Any attempts internally to the protonvpn container on the exposed port (8080 in this case) work fine due to 192.168.240.0/20 dev eth0 proto kernel scope link src 192.168.240.2 but any connection attempt from outside of the container will fail as soon as the VPN connection due to the appended 0.0.0.0/1 via 10.20.0.1 dev proton0 grabbing all traffic prior to the default route.

HOWEVER if I append a new route specifically for my LAN traffic (10.0.1.0/24 in my case) I'm again able to access the exposed service. In my case, this consists of running ip route append 10.0.1.0/24 via 192.168.240.1 dev eth0. Unfortunately, I'm not aware of a way to add this in a persist method short of forking this repo and adding it to my own dockerfile, which is less than ideal...

The approach I'm going to play around with now is basically building a new image off of your image at ghcr.io/tprasadtp/protonvpn:latest that adds the route I need with a new image. Again, less than ideal, but it should work for the time being and I'm not sure there's a good option for this otherwise that wouldn't involve a fair bit of work on your side to programmatically generate routes based on environment variables or something. This is a waste of time.

It turns out that the split tunneling feature of ProtonVPN does this for us, we just need to tell it which subnet to split traffic for:

root@ca53b575b2aa:/# protonvpn configure
What do you want to change?

1) Username and Password
2) ProtonVPN Plan
3) Default Protocol
4) DNS Management
5) Kill Switch
6) Split Tunneling
7) Purge Configuration

Please enter your choice or leave empty to quit: 6

Enable split tunneling? [y/N]: y
Please enter an IP or CIDR to exclude from VPN.
Or leave empty to stop: 10.0.1.0/24
Please enter an IP or CIDR to exclude from VPN.
Or leave empty to stop:

Split tunneling configuration updated.

root@ca53b575b2aa:/# protonvpn disconnect
Disconnected.
root@ca53b575b2aa:/# protonvpn reconnect
Connecting to IS#2 via UDP...
Connected!
root@ca53b575b2aa:/# ip route
0.0.0.0/1 via 10.16.0.1 dev proton0
default via 172.29.0.1 dev eth0
10.0.1.0/24 via 172.29.0.1 dev eth0
10.16.0.0/16 dev proton0 proto kernel scope link src 10.16.0.7
128.0.0.0/1 via 10.16.0.1 dev proton0
169.254.169.254 via 172.29.0.1 dev eth0
169.254.170.2 via 172.29.0.1 dev eth0
172.29.0.0/16 dev eth0 proto kernel scope link src 172.29.0.2
172.30.0.0/16 dev eth1 proto kernel scope link src 172.30.0.2
185.159.158.101 via 172.29.0.1 dev eth0

Now to figure out how to make this persist and/or expose it in an environment variable...

And in an epic facepalm moment, this is already documented and exposed functionality with PROTONVPN_EXCLUDE_CIDRS, and this absolutely solves the problem - PROTONVPN_EXCLUDE_CIDRS: 10.0.1.0/24. 🤦

@oatmealm @lovelesscodes this may solve it for you too.

tprasadtp commented 3 years ago

Hey @maclarel try this and let me know if you still have timeout issues.

Debugging with default compose file

  1. Stop and delete existing containers named protonvpn and pyload (if any).
  2. Please export credentials as environment variables PROTONVPN_USERNAME and PROTONVPN_PASSWORD and optionally PROTONVPN_TIER. This depends on your shell. Please consult your shell's manpage/docs for how to export environment variables.
  3. Get your debugging [compose file][] and create a temporary folder required to save pyload downloads.
    curl -sSfLO https://raw.githubusercontent.com/tprasadtp/protonvpn-docker/master/k8s/docker-compose.yml
    mkdir -p ./downloads
  4. Try to run downloaded [compose file][]
    docker-compose up
  5. We intentionally do not expose in this config, pyload to outside world. depending on your system and runtime constraints IP address of your container will vary. To obtain IP address/URL of your pyload service,run
    docker inspect --format='{{range .NetworkSettings.Networks}}{{printf "http://%s:8000\\n" .IPAddress}}{{end}}' protonvpn
  6. Visit the link in your browser.
  7. If for some reason you get timeout errors, please specify output of following when running inside protonvpn container via docker exec -it protonvpn bash.
    • ip r
    • curl -sSfL ipinfo.io

Runtime configuration

  1. I am running with following configuration

    • Version of protonvpn-docker :
    • Host architecture:
  2. I have following connected containers:

    • Image:
    • Version(Optional):

Also, can you complete the following template? You can copy the contents below in your response.

Docker configuration

Redacted PII & Credential validation

maclarel commented 3 years ago

Hey @tprasadtp thanks for getting back to me so quickly here! 🎉

I think I have this all figured out at this point as outlined at the end of my post above. With that said, I'd be happy to test some changes if you think there's an easier way... It's not immediately apparent from the linked docker-compose.yml what would be changing here - could you let me know what to look for?

tprasadtp commented 3 years ago

@maclarel The compose file specified runs pyload which is plain, boring and simple. Thus it acts like a common denominator in debugging and makes easier for everyone to reproduce the issue as everyone may not be comfortable running a P2P image.

That being said original author of this issue mentioned that exclude cidrs did not work for them. However it was before 4.x releases. Please let me know if your setup is working as you intended.

maclarel commented 3 years ago

That being said original author of this issue mentioned that exclude cidrs did not work for them. However it was before 4.x releases. Please let me know if your setup is working as you intended.

Aha! Gotcha, yeah PROTONVPN_EXCLUDE_CIDRS is working perfectly for me with the latest container. As it turns out, forcing qBitorrent to bind to the proton0 network interface also gives it a software level "kill switch" so it gets around the issues with the ProtonVPN implementation since that interface vanishes entirely if it's not connected!

I wonder if the OP here wasn't formatting the PROTONVPN_EXCLUDE_CIDRS values properly? I know the init scripts do some validation on those values... maybe they were getting dumped? Could also be that this was fixed elsewhere and that's why they haven't responded 😄

tprasadtp commented 3 years ago

Perfect! @maclarel The reason I did not include proton0 interface in the docs is because

  1. Not all software provide a way to bind to an interface. For me, better way would be to handle it in the container init phase. In k8s you can achieve it with init containers, though I did not include it in the docs because It involves a special init container which was very specific to my setup.
  2. We used to match release tags with upstream and they would rename the interface between patch releases. Now that is no longer the case, and we are pinning upstream release, I think it can be included. (Perfect for a PR :wink: :wink: )

To answer your second question, I have not received feedback from the original author of this issue. They were using pre 3.1 release which indeed had this issue.

I will leave this issue pinned as it is helpful for other uses who might have the similar issue.

oatmealm commented 3 years ago

Hi there! Sorry for the long delay... I can confirm I have tested it and it seems to work with tinyproxy. Thanks!