qdm12 / gluetun

VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.
https://hub.docker.com/r/qmcgaw/gluetun
MIT License
7.43k stars 350 forks source link

Feature request: Pick the recommended NordVPN server to connect to. #719

Open Katorone opened 2 years ago

Katorone commented 2 years ago

What's the feature ๐Ÿง

When connecting to NordVPN, its API can be used to detect the recommended server for any region. This would avoid gluetun randomly picking already busy servers.

Created this ticket per our conversation: #

Extra information and references

This script emulates this behavior by checking the NordVPN API, then adjusting a .env -file used with docker-compose.

selectServer.sh :

#!/bin/bash
## NordVPN server selector
# This script uses nordvpn-server-find from https://github.com/mrzool/nordvpn-server-find to
# find the fastest/most available NordVPN server based on the IP of the requesting server.
# It uses the SERVER_HOSTNAME environmental variable to tell gluetun which server to use.
# If a new recommended server is found, the config is adjusted and the container is restarted.
# 
# In your docker-compose make sure to add:
#  SERVER_HOSTNAME=${NORDSERVER}
#
# Warning: REGION has to match the country of the fetched server.
#   'REGION=Netherlands', and SERVER_HOSTNAME='uk123.nordvpn.com' will result in:
#   ERROR vpn: failed finding a valid server connection: no server found
#   Check line 72 if you want to connect through a specific country.
# Also make sure that the protocol (default: udp) is the same in both files.
# 
# This script is compatible with .env files and uses them. If your .env file has a
# custom name, please edit the corresponding variable.
# 
# This script has only been tested on Debian 11.
# Run this script as the user that runs docker-compose, and/or crontab it
#
# Requirements:
#  bash >= 4.0
#  jq
#  git >= 1.8.5 
#  awk

# Directory that contains docker-compose.yml for gluetun
DOCKERDIR="/services/gluetun"
# Target directory where git repositories will be stored
# Each repository will get a subdirectory here.
GITDIR="${DOCKERDIR}/storage"
# .env file name
ENVFN=".env"
# Protocol: TCP or UDP
PROTO='UDP'

# nordvpn-server-find git repository
REPO="https://github.com/mrzool/nordvpn-server-find"
REPOTARGET="${GITDIR}/nordvpn-server-find"

# Make sure the directories exist.
mkdir -m 700 -p "${DOCKERDIR}"
mkdir -m 700 -p "${GITDIR}"

# Lowercase.
PROTO=${PROTO,,}
# Exit if any error occurs
set -e

# Create an empty .env file if it doesn't exist.
if [ ! -e "${DOCKERDIR}/${ENVFN}" ]; then
  touch "${DOCKERDIR}/${ENVFN}"
fi
ENVFNNORD="${DOCKERDIR}/${ENVFN}_NORDVPN"

if [ "$PROTO" = "tcp" ] || [ "$PROTO" = "udp" ]; then
  # Clone or pull the nordvpn-server-find repo
  if [ -d "${REPOTARGET}/.git" ]; then
    # Check if there are changes
    git -C "${REPOTARGET}" fetch origin
    if [ -n "$(git -C "${REPOTARGET}" status --porcelain)" ]; then
      git -C "${REPOTARGET}" pull
    fi
  else
    git -C "${GITDIR}" clone ${REPO};
  fi
  # Find the recommended server.
  chmod +x "${REPOTARGET}/nordvpn-server-find"
  # To find the recommended server from any country:
  #   RECOMMENDED=$(${REPOTARGET}/nordvpn-server-find -l uk -q)
  RECOMMENDED=$(${REPOTARGET}/nordvpn-server-find -r)
  # if the current recommended server isn't in .env_NORDVPN:
  if [ ! -e "${ENVFNNORD}" ] || [ $(grep -rc "${RECOMMENDED}" "${ENVFNNORD}") = 0 ]; then
    # rebuild .env_NORDVPN
    rm -f "${ENVFNNORD}"
    awk 1 "${DOCKERDIR}/${ENVFN}" > "${ENVFNNORD}"
    echo "NORDSERVER=${RECOMMENDED}" >> "${ENVFNNORD}"
    # log
    echo "New server selected: ${RECOMMENDED}"
    # restart the container
    docker-compose -f "${DOCKERDIR}/docker-compose.yml" --env-file "${ENVFNNORD}" down && \
    docker-compose -f "${DOCKERDIR}/docker-compose.yml" --env-file "${ENVFNNORD}" up -d

  fi
else
  echo "Invalid protocol: ${PROTO}"
  exit 0
fi

Submitted under the unlicense.

Example .env

COMPOSE_PROJECT_NAME=gluetun
NORDUSER=norduser@example.com
NORDPASS=superdupersecret

Example docker-compose.yml

# https://github.com/qdm12/gluetun
# https://github.com/qdm12/gluetun/wiki/NordVPN
# https://github.com/qdm12/gluetun/wiki/Custom-provider
# Check ip: 
#   docker-compose exec gluetun sh -c 'apk add curl > /dev/null && \
#     echo "VPN IP: $(curl -s "https://api.ipify.org")"' && \
#     echo "HOST IP: $(curl -s "https://api.ipify.org")"
version: "3.7"

networks:
  vpn-net:
    name: vpn-net
    driver: bridge
    driver_opts:
      com.docker.network.enable_ipv6: "false"
    ipam:
      driver: default
      config:
        - subnet: 10.169.69.0/24

services:
  gluetun:
  image: qmcgaw/gluetun
  container_name: gluetun
    networks:
      - vpn-net
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    expose:
      - 8888/tcp # HTTP proxy
      - 8388/tcp # Shadowsocks
      - 8388/udp # Shadowsocks
    volumes:
      - /services/gluetun/storage/config:/gluetun
    environment:
      - TZ=UTC
      - PUID=1004
      - PGID=1004
      - VPNSP=nordvpn
      - SERVER_HOSTNAME=${NORDSERVER:?Run selectServer.sh.}
      - OPENVPN_USER=${NORDUSER:?Add NORDUSER=username to the .env file.}
      - OPENVPN_PASSWORD=${NORDPASS:?Add NORDPASS=password to the .env file.}
      - REGION=United Kingdom
      - PROTOCOL=udp              # default=udp
      - OPENVPN_VERBOSITY=1       # default=1
      - OPENVPN_ROOT=no           # default=no
      - OPENVPN_IPV6=off          # default=off
      - DOT=on                    # default=on
      - DOT_PROVIDERS=cloudflare  # default=cloudflare
      - DOT_CACHING=on            # default=on
      - DOT_IPV6=off              # default=off
      - UPDATER_PERIOD=0          # default=24h

edit: docker-compose restart isn't enough to update the changed environmental variables to the container. Doing down/up instead. Also added a log line.

Katorone commented 2 years ago

One downside I've noticed with this method, is that gluetun will only have one server to try.

Every few hours I'm getting 'authentication failed' messages for no good reason:

gluetun    | 2021/11/21 10:54:37 INFO openvpn: SIGUSR1[soft,auth-failure] received, process restarting
gluetun    | 2021/11/21 10:54:47 INFO openvpn: TCP/UDP: Preserving recently used remote address: [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:54:47 INFO openvpn: UDP link local: (not bound)
gluetun    | 2021/11/21 10:54:47 INFO openvpn: UDP link remote: [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:54:49 INFO openvpn: [uk812.nordvpn.com] Peer Connection Initiated with [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:54:50 ERROR openvpn: AUTH: Received control message: AUTH_FAILED
gluetun    |
gluetun    | Your credentials might be wrong ๐Ÿคจ
gluetun    |
gluetun    | 2021/11/21 10:54:50 INFO openvpn: SIGUSR1[soft,auth-failure] received, process restarting
gluetun    | 2021/11/21 10:55:00 INFO openvpn: TCP/UDP: Preserving recently used remote address: [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:55:00 INFO openvpn: UDP link local: (not bound)
gluetun    | 2021/11/21 10:55:00 INFO openvpn: UDP link remote: [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:55:02 INFO openvpn: [uk812.nordvpn.com] Peer Connection Initiated with [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:55:03 ERROR openvpn: AUTH: Received control message: AUTH_FAILED
gluetun    |
gluetun    | Your credentials might be wrong ๐Ÿคจ
gluetun    |
gluetun    | 2021/11/21 10:55:03 INFO openvpn: SIGUSR1[soft,auth-failure] received, process restarting
gluetun    | 2021/11/21 10:55:13 INFO openvpn: TCP/UDP: Preserving recently used remote address: [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:55:13 INFO openvpn: UDP link local: (not bound)
gluetun    | 2021/11/21 10:55:13 INFO openvpn: UDP link remote: [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:55:13 INFO openvpn: [uk812.nordvpn.com] Peer Connection Initiated with [AF_INET]77.81.191.3:1194
gluetun    | 2021/11/21 10:55:14 INFO openvpn: Preserving previous TUN/TAP instance: tun0
gluetun    | 2021/11/21 10:55:14 INFO openvpn: Initialization Sequence Completed
gluetun    | 2021/11/21 10:55:14 INFO dns over tls: generate keytag query _ta-4a5c-4f66. NULL IN
gluetun    | 2021/11/21 10:55:14 INFO ip getter: Public IP address is 77.81.191.3 (United Kingdom, England, London)

In the next version of this script I'll try to make the API calls to nordvpn myself instead of relying on 'nordvpn-server-find', so I can work with script-friendly output and add multiple servers. (No ETA on that right now)

qdm12 commented 2 years ago

Yes no pressure. I need to setup some optional pre-VPN setup to interact with VPN APIs (for PIA's Wireguard and here, for NordVPN load API). Although that will leak out of the VPN at start, whereas the program is currently configured to block everything from the start.

Katorone commented 2 years ago

I've done some experimenting over the past week, and decided that my current method leads to an unstable connection. After all, why switch servers when the current one is just fine.

Because of this, I've decided to go about it another way, though the extend I can interact with the container is limited to docker events and docker logs.

What I'd like to do is:

One thing I've noticed is that nordvpn needs some time to update their credential server when a disconnect happens. The authentication failures in a previous post seems to be caused on their end. The issue here, is that we can't know if connecting to a server fails because of this delay, or if there's something wrong with the server itself. So ideally there should be a grace period while gluetun tries to reconnect before trying a new server.

Some countries are over-serviced by NordVPN, and I assume by other VPN providers as well. A good example of this is UK. Here we have a ton of servers that are below 10% utilization. My current script always finds a better server, even if it runs every 5 minutes. This causes a lot of disconnects and service interruption on the client side. A better method would be to only switch if the connection becomes unhealthy or the current server becomes worse then the configured score.

Using docker events I'm getting live notifications when the connection becomes healthy or unhealthy, and am able to restart the container. A grace time or parsing the live logs for an amount of reconnect attempts is possible as well.

My suggestion is to add a preference score to servers.json that any (internal) script can update. Gluetun could still connect to a random server when they have the same score (or no score). This would keep gluetun backwards compatible and scripts doing API calls completely optional. Since the API calls only happen after connecting to a random server, leakage is minimal as well. A server that takes too long to authenticate could get its score lowered (if it has one), the score can also be used to temporarily blacklist servers that went stale or are unreachable.

I'll post a script as soon as I have built & tested it. I'm tempted to switch to node because I'm more familiar with that, but because of the container environment (and bloat being evil) I'll keep it to bash & jq.

qdm12 commented 2 years ago

Good suggestion. Right now I'm still in the middle of a rather large configuration parsing refactor and I am busier than usual with common life things unfortunately ๐Ÿ˜… I would suggest you to not waste too much time on scripting it since it feels it has to be 'inside' gluetun. It would also require quite a few changes to the code base. There is more and more feature requests concerning pre-connection and while-connected actions, specific to each provider, so I better make those generic to fit each feature/vpn provider.

One last thing is I think it would be good to have the mapping hostname to load as a separate Nordvpn file, to separate concerns (I also want to split that servers.json by provider some day haha).

Y0ngg4n commented 2 years ago

Would be really nice if we could implement this. I get horrible download speeds with NordVPN sometimes

Schmill commented 1 year ago

Just to throw some thoughts into the pot with this one. Nord VPN offer "specialised servers" such as those specifically for P2P, obviously being able to specify that you want the p2p server would be an advantage, but currently I don't see anyway to do that?

zunami commented 1 year ago

Just to throw some thoughts into the pot with this one. Nord VPN offer "specialised servers" such as those specifically for P2P, obviously being able to specify that you want the p2p server would be an advantage, but currently I don't see anyway to do that?

have the same problem. in the wiki is unfortunately missing here the complete command option that controls nordvpn.

From the NordVPN Homepage for Linux To access the NordVPN client settings, type the nordvpn command in Terminal.

nordvpn login - Log in.
nordvpn connect or nordvpn c - Connect to VPN. To connect to specific servers, use nordvpn connect <country_code server_number> (eg. nordvpn connect uk715)
nordvpn disconnect or nordvpn d - Disconnect from VPN.
nordvpn c double_vpn - Connect to the closest Double VPN server.
nordvpn connect --group double_vpn <country_code> - Connect to a specific country using DoubleVPN servers.

nordvpn connect P2P - connect to a P2P server.
nordvpn connect The_Americas - connect to servers located in the Americas.
nordvpn connect Dedicated_IP - connect to a Dedicated IP server.

nordvpn set or nordvpn s - Set a configuration option.
Possible options:
nordvpn set threatprotectionlite on or off - Enable or disable [Threat Protection Lite](https://support.nordvpn.com/General-info/Features/1047407402/What-are-Threat-Protection-and-Threat-Protection-Lite.htm)
nordvpn set killswitch on or off - Enable or disable [Kill Switch](https://support.nordvpn.com/General-info/Features/1047407832/What-is-Kill-Switch-and-how-does-it-work.htm)
nordvpn set autoconnect on or off - Enable or disable auto-connect. You can set a specific server for automatic connection using nordvpn set autoconnect on country_code+server_number. Example: nordvpn set autoconnect on us2435.

nordvpn set notify on or off - Enable or disable notifications
nordvpn set dns 1.1.1.1 1.0.0.1 - Set custom DNS (you can set up a single DNS or two like shown in this command).
nordvpn set protocol udp or tcp - Switch between [UDP and TCP protocols](https://support.nordvpn.com/General-info/1047408102/What-are-the-pros-and-cons-of-TCP-and-UDP.htm)
nordvpn set obfuscate on or off - Enable or disable [Obfuscated Servers](https://support.nordvpn.com/General-info/Features/1047407962/What-do-the-different-NordVPN-server-categories-mean.htm).
nordvpn set technology - Set connection technology (OpenVPN or [NordLynx](https://support.nordvpn.com/Connectivity/Linux/1362931332/How-can-I-use-NordLynx-in-the-NordVPN-app-for-Linux.htm))
nordvpn set meshnet on - turn on [Meshnet](https://support.nordvpn.com/General-info/Features/1845333902/What-is-Meshnet.htm) on your device.

nordvpn whitelist add port 22 - Add a rule to whitelist a specified incoming port. You can also whitelist multiple ports โ€” just separate their numbers with a space.
nordvpn whitelist remove port 22 - Remove the rule to whitelist a specified port.
nordvpn whitelist add subnet 192.168.0.0/16 - Add a rule to whitelist a specified subnet.
nordvpn whitelist remove subnet 192.168.0.0/16  - Remove the rule to whitelist a specified subnet.

nordvpn account - See account information
nordvpn register - Register a new user account
nordvpn rate - Rate your last connection quality (1-5)
nordvpn settings - See the current settings.
nordvpn status - See the connection status.
nordvpn countries - See the country list.
nordvpn cities- See the city list. E.g.: nordvpn cities united_states  
nordvpn groups - See a list of available server groups.
nordvpn logout - Log out.
nordvpn help or nordvpn h - See the list of available commands or help for a specific command.
qdm12 commented 1 year ago

@Schmill

Nord VPN offer "specialised servers" such as those specifically for P2P, obviously being able to specify that you want the p2p server would be an advantage, but currently I don't see anyway to do that?

yes, opened #1643 for this (please subcribe to that one), although it's currently blocked by opened PR #1380. Actually that PR adds native Wireguard support for NordVPN, and I need testers so feel free to have a look at it! ๐Ÿ˜‰

@zunami

in the wiki is unfortunately missing here the complete command option that controls nordvpn.

There is no nordvpn program in gluetun that's why (and no intent to add it).

Back to this issue, #1380 now adds the API endpoint to get recommended servers so that helps development a bit further, but Gluetun is still not ready to do web exchanges before the VPN is up.

PilaScat commented 20 hours ago

news?