Verouderd: Geef je headless (Raspberry Pi) VPN een boost met ProtonVPN en WireGuard

Verouderd: Beheer ProtonVPN moeiteloos op uw headless apparaat zoals een Raspberry Pi met dit Python-script. Maak automatisch verbinding met de snelste server met behulp van WireGuard voor optimale snelheid en veiligheid. Dit is om fout 422 te verhelpen met behulp van de protonvpn-cli-client, die op dit moment niet werkt.

een jaar geleden   •   18 min lezen

Door Remco Loup.
Foto door Hector Bermudez / Unsplash
Inhoudsopgave

De API:https://api.protonvpn.ch/vpn/logicalsis volgens Proton Support officieel stopgezet. Het enige wat het nu teruggeeft is:

{"Code":0,"Error":"Invalid access token","Details":{}}

Dit bericht is nu helaas achterhaald. Iemand is een petitie gestart om deze functie terug te krijgen.

Hoe kunnen we Proton VPN verbeteren?
  • 27 stemmen
  • 4 reacties

Toegang tot API opnieuw inschakelen

Tot voor kort konden gebruikers toegang krijgen tot de API (https://api.protonvpn.ch/vpn/logicals) om servers te filteren op basis van verschillende criteria (bijvoorbeeld land en serverbelasting) om ervoor te zorgen dat ze verbinding maakten met een server die niet overbelast was. Deze mogelijkheid is de afgelopen dagen verwijderd. De ondersteuning antwoordde met het volgende...

De verouderde gids die vanaf 06/2025 niet meer functioneert Om historische redenen.

Toen ProtonVPN zijn command-line interface (CLI) tool stopzette, hadden gebruikers van headless systemen zoals Raspberry Pi geen eenvoudige methode meer om VPN-verbindingen te beheren. Zonder grafische gebruikersinterface is het handmatig wisselen van VPN-servers een omslachtige taak geworden. Ook is het onmogelijk geworden om de snelste VPN te selecteren uit een pool van servers.

Om dit probleem op te lossen, heb ik een Python-script ontwikkeld dat automatisch de snelste ProtonVPN-server selecteert op basis van latentie en belasting, en vervolgens uw WireGuard-configuratie voor u bijwerkt. Deze handleiding leidt u door het instellen van WireGuard voor ProtonVPN, het installeren van het script en het automatiseren van uw VPN-verbinding, zodat uw Raspberry Pi altijd verbonden is met de beste server.

Waarom heb ik dit Protonss-script nodig?

Dit Python-script automatiseert het proces om uw Raspberry Pi te verbinden met de snelste beschikbare ProtonVPN-server, met WireGuard als VPN-protocol. Het controleert de latentie en belasting van meerdere VPN-servers via de API van ProtonVPN, selecteert de server met de beste prestaties, werkt uw WireGuard-configuratie bij met de nieuwe serverinformatie en maakt alleen opnieuw verbinding met uw VPN als er een aanzienlijk snellere server wordt gevonden (niemand houdt immers van opnieuw verbinden). Dit script registreert ook het hele proces voor eenvoudigere probleemoplossing.

  1. Geen handmatig wisselen van VPN meer: zonder de CLI van ProtonVPN zou het wisselen naar de snelste server handmatig moeten gebeuren. Dit script automatiseert het proces door regelmatig de beschikbare servers te controleren en de verbinding bij te werken op basis van realtime prestaties.
  2. Optimaliseert de verbindingssnelheid: het script zorgt ervoor dat u altijd verbonden bent met de VPN-server met de laagste latentie en belasting. Dit is vooral handig voor het behouden van snelle verbindingen in landen met snelle of weinig belaste ProtonVPN-servers.
  3. Vermindert downtime: Door het herverbindingsproces te automatiseren, helpt het script om mogelijke downtime als gevolg van slechte serverprestaties of trage verbindingen tot een minimum te beperken.
  4. Op maat gemaakt voor Raspberry Pi: Als u een headless Raspberry Pi (zonder monitor) gebruikt, is dit script perfect voor het automatiseren van VPN-taken zonder dat u voortdurend toezicht hoeft te houden.
  5. Beveiliging en privacy: Door ervoor te zorgen dat uw Raspberry Pi altijd verbinding maakt met een VPN, worden uw gegevens en online activiteiten beter beschermd, vooral als u de Pi gebruikt als torrent- of netwerkapparaat.

Kortom, dit script automatiseert het vinden van de optimale ProtonVPN-server, waardoor hogere snelheden, meer betrouwbaarheid en gemak worden geboden voor headless systemen zoals een Raspberry Pi.

Laten we erin duiken!

Eerst moet je WireGuard installeren, een snel, lichtgewicht VPN-protocol dat we zullen gebruiken om verbinding te maken met onze ProtonVPN-server.

# Werk eerst uw pakketlijst bij en installeer WireGuard:
sudo apt update
sudo apt install wireguard curl resolvconf

Genereer een WireGuard-configuratie vanuit ProtonVPN

  • Log in op uw ProtonVPN-account op hun website.
  • Ga naar het gedeelte 'Downloads' in uw dashboard.
  • Genereer een nieuw configuratiebestand onder "WireGuard-configuraties".
  • Als je torrents gebruikt, schakel dan NAT-PMP in.

Sla dit configuratiebestand op je Raspberry Pi op:

# Sla het configuratiebestand op uw Raspberry Pi op:
# Plaats het in de WireGuard-map en hernoem het voor het gemak:
sudo nano /etc/wireguard/protonvpn.conf

Zodra u het configuratiebestand in de juiste map hebt geplaatst, controleert u de verbinding.

# Controleer de verbinding met ProtonVPN:
sudo wg-quick up protonvpn

# Dit zou een ander IP-adres moeten opleveren dan dat van uw internetprovider.
curl ifconfig.me 

# Controleer de verbinding met ProtonVPN:
sudo wg-quick down protonvpn

curl ifconfig.me 

Als deze twee IP-adressen van ifconfig.me verschillend zijn, bent u verbonden met een VPN. Zelfs als u uw IP-adres niet kent, omdat het een DHCP-adres of iets anders is.

Onze Python-omgeving voor Protonss installeren.

Bij het ontwikkelen en uitvoeren van Python-scripts op uw Raspberry Pi kunt u het beste een virtuele Python-omgeving gebruiken. Met een virtuele omgeving kunt u Python-pakketten in een geïsoleerde omgeving installeren en beheren, zodat de standaard Python-configuratie van uw systeem ongewijzigd blijft.

Op deze manier hebben wijzigingen in uw omgeving alleen invloed op het project waaraan u werkt, zoals het installeren van specifieke pakketten zoals verzoeken—zonder conflicten te veroorzaken met andere Python-projecten of afhankelijkheden op systeemniveau.

Hier volgt hoe u een virtuele omgeving voor ons script kunt instellen en gebruiken:

  1. Virtuele omgeving installeren: Installeer eerst de python3-venv pakket, waarmee u virtuele omgevingen kunt maken en beheren.
sudo apt install -y python3-venv python3-full
  1. Creëer een virtuele omgeving: Na installatie kunt u een virtuele omgeving aanmaken. Hierdoor wordt een geïsoleerde omgeving gegenereerd in de door u opgegeven map (in dit geval ~/protonss).
python3 -m venv ~/protonss
  1. Activeer de virtuele omgeving: Nadat u de omgeving hebt aangemaakt, moet u deze activeren. Hierdoor wordt uw systeem geïnstrueerd om Python en pakketten uit de virtuele omgeving te gebruiken in plaats van de Python-installatie die op het hele systeem is geïnstalleerd.
bron ~/protonss/bin/activate
  • Nu de omgeving actief is, kan elk Python-pakket dat u installeert (zoals verzoeken) zal in deze geïsoleerde omgeving worden geïnstalleerd.
pip install requests

Deze virtuele omgeving in de toekomst gebruiken: Telkens wanneer u Python-scripts wilt uitvoeren die afhankelijk zijn van deze virtuele omgeving, moet u deze opnieuw activeren met behulp van de bron ~/venv/bin/activate commando. Eenmaal geactiveerd, worden alle Python-commando's of -pakketten die u gebruikt in deze omgeving uitgevoerd.

Ons ProtonSS-script installeren om automatisch verbinding te maken met de snelste VPN.

We maken een configuratiebestand aan om alle variabelen op te slaan die het Python-script zal gebruiken. U kunt bijvoorbeeld het pad naar uw WireGuard-configuratie, logbestand en het aantal pings om de latentie te testen definiëren.

cd ~/protonss

Voorbeeld config.ini:

nano config.ini
[Instellingen]
# Pad naar uw WireGuard-configuratiebestand
wg_config_path = /etc/wireguard/protonvpn.conf

# Pad naar uw logbestand
log_file = /var/log/wireguard_updater.log

# Aantal pings om de latentie te meten
ping_count = 3

# Maximaal verschil in score vóór herverbinding 0 - 100
# De gemiddelde score van servers lijkt tussen 10 en 40 te liggen. Als u deze waarde te hoog instelt, worden er geen servers gewisseld.
score_threshold = 5.0

# Door komma's gescheiden lijst met voorkeurslanden
preferred_countries = US

# Door komma's gescheiden lijst met voorkeurssteden
# Indien opgegeven, moeten servers zich in deze steden bevinden (tenzij ze zich in een land bevinden zonder opgegeven stad)
# Voorkeurssteden om verbinding mee te maken. Indien opgegeven, moeten servers zich in deze steden bevinden
# (tenzij ze zich in een land bevinden zonder opgegeven stad).
# Voorbeeld: preferred_cities = Phoenix, Los Angeles, Seattle
# Hiermee kunt u zich richten op specifieke steden binnen uw voorkeurslanden.
# Als dit veld leeg wordt gelaten, komen alle steden binnen de voorkeurslanden in aanmerking.
# Opmerking: Stadsnamen zijn hoofdlettergevoelig en moeten exact overeenkomen met de naamgeving van ProtonVPN.
# Meerdere steden moeten worden gescheiden door komma's.
preferred_cities =Phoenix

# Gewicht voor latentie in de serverscore
# Latentie is subjectief, aangezien dit waarschijnlijk wordt gemeten via de vpn en alleen meetelt als serverlatentie.
# Het controleren van de latentie zonder vpn wordt niet aanbevolen, aangezien de proton-servers zonder vpn niet zullen reageren.
# De waarde kan niet hoger zijn dan 1. 0,3 komt overeen met 30% van de score_treshold.
latency_weight = 0,3

# Gewicht voor serverbelasting in de serverscore
# Belasting is de beste factor. 0,7 komt overeen met 70% van het gewicht van de score_threshold. Dit kan niet hoger zijn dan 1.
load_weight = 0,7

# Ping-servers in- of uitschakelen (True of False)
# Aangezien ping mogelijk niet betrouwbaar genoeg is om van server te blijven wisselen, kan dit hier worden uitgeschakeld.
ping_enabled = True

# Specificeer de interface die moet worden gebruikt voor ping-tests. Door deze optie in te stellen, kunt u pingen naar de snelste vpn-server.
# Dit zorgt voor de laagste latentie VPN vanaf uw locatie.
ping_interface = eth0

[Functies]
# Kies welk type servers u wilt gebruiken.
# Opties: p2p, plus, secure_core, tor.
# Standaard is p2p indien niet gespecificeerd.
feature_type = p2p

# Uitleg van de functies:
# Elke server heeft precies ÉÉN functienummer:
# 1, 17 = Secure Core-servers
# 2 = TOR-servers
# 0, 8, 16, 24 = Alleen Plus-servers
# 4, 12, 20, 28 = P2P-servers (dit zijn ook Plus-servers)

U kunt de variabelen naar behoefte aanpassen. Bijvoorbeeld door voorkeurslanden Als je dit veld leeg laat, gaat het script heel veel servers checken, waardoor het heel lang duurt voordat het klaar is!

Maak het Python-script

Nu gaan we het Python-script maken dat uw VPN-verbinding automatiseert. We moeten eerst een paar stappen voorbereiden.

De eerste regel van het script moet een shebang zijn, die ervoor zorgt dat het script altijd in de juiste Python-omgeving wordt uitgevoerd:

bron ~/protonss/bin/activate
welke python

Hiermee krijgt u het blokje dat u nodig hebt om de eerste regel van dit script in te voeren.

/home/pi/venv/bin/python

Wijzig het script door een shebang: Voeg de volgende regel helemaal bovenaan uw Python-script toe. Vervang /home/{gebruiker}/venv/bin/python met het daadwerkelijke pad dat je kreeg toen je het welke python commando eerder. Zorg ervoor dat je de #! aan het begin van de regel – dit vertelt het systeem welke interpreter moet worden gebruikt om het script uit te voeren. We hebben het absolute pad nodig, dus ~/ werkt niet.

nano protonvpn_auto_connect.py
#!/home/{user}/protonss/bin/python

import requests
import subprocess
import os
import sys
import configparser
import logging
import re
from datetime import datetime
import math
import shutil

# Get the directory of the current script
script_dir = os.path.dirname(os.path.abspath(__file__))

# Load the config file from the same directory as the script
config = configparser.ConfigParser()
config.read(os.path.join(script_dir, 'config.ini'))

WG_CONFIG_PATH = config.get('Settings', 'wg_config_path')
LOG_FILE = config.get('Settings', 'log_file')
PING_COUNT = config.getint('Settings', 'ping_count')
SCORE_THRESHOLD = config.getfloat('Settings', 'score_threshold', fallback=5.0)
PREFERRED_COUNTRIES = config.get('Settings', 'preferred_countries', fallback='').split(',')
PREFERRED_CITIES = config.get('Settings', 'preferred_cities', fallback='').split(',')
PING_INTERFACE = config.get('Settings', 'ping_interface', fallback=None)

# Weighting factors for latency and load from the config
LATENCY_WEIGHT = config.getfloat('Settings', 'latency_weight', fallback=0.3)
LOAD_WEIGHT = config.getfloat('Settings', 'load_weight', fallback=0.7)

# Ping option from config
PING_ENABLED = config.getboolean('Settings', 'ping_enabled', fallback=True)

# Clean up country codes (remove spaces and convert to uppercase)
PREFERRED_COUNTRIES = [country.strip().upper() for country in PREFERRED_COUNTRIES if country.strip()]

# Clean up city names (remove spaces)
PREFERRED_CITIES = [city.strip() for city in PREFERRED_CITIES if city.strip()]

# Set up logging
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.info('--- ProtonVPN Auto Connect Script Start ---')

# Feature definitions
FEATURE_SECURE_CORE = [1, 17]  # Secure Core servers
FEATURE_TOR = [2]         # TOR servers
FEATURE_PLUS = [0, 8, 16, 24]  # Plus-only servers
FEATURE_P2P = [4, 12, 20, 28]    # P2P servers (these are also Plus servers)
# check https://api.protonmail.ch/vpn/logicals for more info. 
# be aware that the secure core and tor server are very limited so check if one is available for your country as stated in the config.ini file.
# FREE servers are not included in the api response. so they are already left out.

def get_feature_description(features):
    # If no feature type is specified in config.ini, default to P2P
    # Features is a number representing server capabilities
    if not features:  # If features is 0 or None
        return "P2P"  # Default to P2P
    elif features in FEATURE_SECURE_CORE:
        return "Secure Core"
    elif features in FEATURE_TOR:
        return "TOR" 
    elif features in FEATURE_P2P:
        return "P2P"
    elif features in FEATURE_PLUS:
        return "Plus"
    else:
        return "P2P"  # Default to P2P for any unrecognized feature

def get_wanted_features():
    """Get the wanted feature type based on config priorities"""
    feature_type = config.get('Features', 'feature_type', fallback='p2p').lower()
    
    if feature_type == 'secure_core':
        return FEATURE_SECURE_CORE
    elif feature_type == 'tor':
        return FEATURE_TOR
    elif feature_type == 'plus':
        return FEATURE_PLUS
    else:  # Default to P2P
        return FEATURE_P2P

# Function to fetch the list of servers from the ProtonVPN API
def get_servers(full_list=False):
    try:
        url = 'https://api.protonmail.ch/vpn/logicals'
        response = requests.get(url)
        response.raise_for_status()
        data = response.json()
        logging.info("API response data received.")
        return data['LogicalServers']
    except requests.RequestException as e:
        logging.error(f'Error fetching server list: {e}')
        sys.exit(1)
    except ValueError as e:
        logging.error(f'Error parsing JSON: {e}')
        sys.exit(1)

# Function to filter P2P servers and servers in preferred countries
def filter_servers(servers):
    filtered_servers = []
    
    # Get feature preferences from config (nu maar één type)
    wanted_features = get_wanted_features()
    
    logging.info(f"Filtering servers with preferences - Countries: {PREFERRED_COUNTRIES or 'Any'}, "
                f"Cities: {PREFERRED_CITIES or 'Any'}, Features: {[get_feature_description(f) for f in wanted_features]}")
    
    for server in servers:
        if not isinstance(server, dict):
            logging.error(f"Unexpected type for server: {type(server)} - Value: {server}")
            continue
            
        features = server.get('Features', 0)
        country = server['ExitCountry']
        city = server.get('City', '')

        # Check if server features match our wanted features
        if features in wanted_features:
            # Check country and city preferences
            if country in PREFERRED_COUNTRIES:
                if not PREFERRED_CITIES or city in PREFERRED_CITIES:
                    filtered_servers.append(server)
                    logging.info(f"Server matched - Country: {country}, City: {city}, "
                              f"Features: {get_feature_description(features)}")
            
    if not filtered_servers:
        logging.warning(f"No servers found matching filters. Countries: {PREFERRED_COUNTRIES}, "
                      f"Cities: {PREFERRED_CITIES}, Features: {[get_feature_description(f) for f in wanted_features]}")
    else:
        logging.info(f"Found {len(filtered_servers)} matching servers")
        
    return filtered_servers

# Function to measure latency by pinging the server (optional)
def get_latency(hostname):
    if not PING_ENABLED:
        logging.info(f"Ping disabled, skipping latency check for {hostname}.")
        return 0  # Return 0 latency if ping is disabled

    latencies = []
    for i in range(PING_COUNT):
        try:
            # Add the interface option if it's specified
            ping_command = ['ping', '-c', '1', '-W', '1', hostname]
            if PING_INTERFACE:
                ping_command.insert(1, '-I')
                ping_command.insert(2, PING_INTERFACE)

            result = subprocess.run(ping_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            if result.returncode == 0:
                output = result.stdout.decode()
                latency_line = [line for line in output.split('\n') if 'time=' in line][0]
                latency = float(latency_line.split('time=')[1].split(' ms')[0])
                latencies.append(latency)
            else:
                latencies.append(float('inf'))
        except Exception as e:
            latencies.append(float('inf'))
            logging.error(f'Ping error for {hostname}: {e}')

    # Return the lowest (best) latency value
    best_latency = min(latencies)
    
    # Handle inf latency
    if math.isinf(best_latency):
        logging.warning(f"Latency for {hostname} is inf. Assigning high latency value.")
        best_latency = 1000  # Assign a high latency value

    return best_latency

# Function to select the best server based on a weighted combination of latency and load
def select_best_server(servers):
    best_server = None
    best_score = float('inf')
    
    for server in servers:
        server_load = server.get('Load')
        
        # Handle inf load
        if server_load is None or math.isinf(server_load):
            logging.warning(f"Server {server['Name']} has inf load. Assigning high load value.")
            server_load = 100  # Assign a high load value if it's inf or None
        else:
            server_load = int(server_load)
        
        for srv in server['Servers']:
            exit_ip = srv.get('ExitIP')
            hostname = exit_ip

            # Get latency, only if ping is enabled
            latency = get_latency(hostname)

            # Combine latency and load using the configured weights
            score = (latency * LATENCY_WEIGHT) + (server_load * LOAD_WEIGHT)
            
            logging.info(f"Server {server['Name']} - Latency: {latency:.2f} ms, Load: {server_load}%, Score: {score:.2f}")
            
            if score < best_score:
                best_score = score
                best_server = server
                best_server_instance = srv

    return best_server, best_server_instance, best_score

# Function to read the current server IP from the WireGuard configuration
def get_current_server_ip():
    try:
        with open(WG_CONFIG_PATH, 'r') as f:
            config_text = f.read()
        # Extract the current Endpoint IP
        match = re.search(r'Endpoint\s*=\s*([\d\.]+):\d+', config_text)
        if match:
            logging.info(f"Current server IP found in WireGuard configuration: {match.group(1)}")
            return match.group(1)
        else:
            logging.warning('No current Endpoint IP found in WireGuard configuration.')
            return None
    except Exception as e:
        logging.error(f'Error reading the WireGuard configuration: {e}')
        return None

# Function to find the current server's performance in the live server data
def get_current_server_performance(servers, current_ip):
    if current_ip is None:
        logging.warning('Current server IP is None, skipping performance check.')
        return None, None, float('inf')
    
    for server in servers:
        for srv in server['Servers']:
            if srv['ExitIP'] == current_ip:
                server_load = server.get('Load')
                if server_load is None or math.isinf(server_load):
                    logging.warning(f"Server {server['Name']} (IP: {current_ip}) has inf load. Assigning high load value.")
                    server_load = 100  # Assign a high load value
                else:
                    server_load = int(server_load)

                latency = get_latency(current_ip) if PING_ENABLED else 0
                score = (latency * LATENCY_WEIGHT) + (server_load * LOAD_WEIGHT)

                logging.info(f"Current server {server['Name']} - Latency: {latency:.2f} ms, Load: {server_load}%, Score: {score:.2f}")
                return server, srv, score
    logging.warning(f"Current server with IP {current_ip} not found in live data.")
    return None, None, float('inf')

# Function to find the current server in the full ProtonVPN list if not found in filtered data
def find_current_server_full_list(current_ip):
    servers = get_servers(full_list=True)
    return get_current_server_performance(servers, current_ip)

# Function to update the WireGuard configuration file
def update_wg_config(server, server_instance, best_score):
    try:
        endpoint_ip = server_instance['EntryIP']
        endpoint_port = 51820  # Default WireGuard port
        endpoint = f"{endpoint_ip}:{endpoint_port}"
        public_key = server_instance['X25519PublicKey']
        server_name = server['Name']

        # Read the current WireGuard configuration
        with open(WG_CONFIG_PATH, 'r') as f:
            config_text = f.read()

        # Strip comments that start with # under [Peer] before PublicKey
        config_text = re.sub(r'#.*\n', '', config_text, flags=re.M)

        # Update the Endpoint in the configuration
        config_text = re.sub(r'Endpoint\s*=.*', f'Endpoint = {endpoint}', config_text)

        # Update the PublicKey in the configuration
        config_text = re.sub(r'PublicKey\s*=.*', f'PublicKey = {public_key}', config_text)

        # Add the new comment indicating the chosen server and score
        config_text = re.sub(r'\[Peer\]\n', f'[Peer]\n# {server_name} - Auto-generated by ProtonVPN Auto Connect - Score: {best_score:.2f}\n', config_text)

        # Write the updated configuration back to the file
        with open(WG_CONFIG_PATH, 'w') as f:
            f.write(config_text)
        os.chmod(WG_CONFIG_PATH, 0o600)

        logging.info(f"WireGuard configuration updated with server: {server_name} ({server['ExitCountry']}), Endpoint: {endpoint}, PublicKey: {public_key}, Score: {best_score:.2f}")
    except Exception as e:
        logging.error(f'Error updating the WireGuard configuration: {e}')
        sys.exit(1)

# Function to restart the WireGuard interface
def is_resolvconf_available():
    """Check if resolvconf is available on the system."""
    return shutil.which("resolvconf") is not None

def is_wg_quick_available():
    """Check if wg-quick is available on the system."""
    return shutil.which("wg-quick") is not None

def restart_wg():
    interface_name = os.path.basename(WG_CONFIG_PATH).split('.')[0]
    
    # Check if wg-quick is available before proceeding
    if not is_wg_quick_available():
        logging.error("wg-quick not found. Ensure WireGuard is installed correctly.")
        sys.exit(1)

    try:
        # Check if resolvconf is available before proceeding
        if not is_resolvconf_available():
            logging.warning("resolvconf not found, DNS settings may not be updated correctly.")

        # Bring down the interface
        subprocess.run(['wg-quick', 'down', interface_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

        # Bring up the interface
        subprocess.run(['wg-quick', 'up', interface_name], check=True)
        logging.info(f'WireGuard interface {interface_name} started.')

    except subprocess.CalledProcessError as e:
        logging.error(f'Error starting the WireGuard interface: {e}')
        sys.exit(1)

# Main function
def main():
    logging.info('--- Starting server selection process ---')
    
    # Fetch the list of servers
    servers = get_servers()
    if not isinstance(servers, list):
        logging.error(f'Unexpected type for servers: {type(servers)}')
        sys.exit(1)

    # Filter the servers
    filtered_servers = filter_servers(servers)
    if not filtered_servers:
        logging.error('No suitable servers available after filtering.')
        sys.exit(1)

    # Get the current server's IP address from the WireGuard configuration
    current_ip = get_current_server_ip()

    # Get the current server's performance if it exists
    current_server, current_server_instance, current_score = get_current_server_performance(filtered_servers, current_ip)

    # If the current server is not found, check the full list of servers
    if current_server is None:
        logging.info(f"Current server not found in filtered list, checking full server list...")
        current_server, current_server_instance, current_score = find_current_server_full_list(current_ip)

    # Select the best server based on weighted latency and load
    best_server, best_server_instance, best_score = select_best_server(filtered_servers)
    if not best_server:
        logging.error('No suitable server found.')
        sys.exit(1)

    if current_server:
        logging.info(f"Current server {current_server['Name']} (IP: {current_ip}) - Score: {current_score:.2f}")
    
    logging.info(f"Selected best server {best_server['Name']} - Score: {best_score:.2f}")
    
    if current_server and current_score <= best_score + SCORE_THRESHOLD:
        logging.info(f"Current server {current_server['Name']} is within threshold. No switch necessary.")
    else:
        logging.info(f"Switching to server {best_server['Name']} due to better performance.")
        update_wg_config(best_server, best_server_instance, best_score)
        restart_wg()

    logging.info('--- ProtonVPN Auto Connect Script End ---')

if __name__ == '__main__':
    main()

Het script testen

chmod +x protonvpn_auto_connect.py

Zodra het script is opgeslagen, geef je het uitvoeringsrechten en voer je het uit.

sudo ./protonvpn_auto_connect.py
cat /var/log/wireguard_updater.log
tail -f /var/log/wireguard_updater.log

Je kunt het logbestand met tail of cat bekijken om de uitvoer in de logs te controleren. Tail heeft de voorkeur, omdat je het kunt blijven bekijken terwijl je script wordt uitgevoerd. Open gewoon een tweede ssh met tail om het te blijven bekijken.

25-09-2024 22:19:10 [INFO] --- ProtonVPN Auto Connect Script Start ---
25-09-2024 22:19:10 [INFO] --- Server selectieproces starten ---
25-09-2024 22:19:11 [INFO] API-responsgegevens ontvangen.
25-09-2024 22:19:11 [INFO] Huidige server-IP gevonden in WireGuard-configuratie: 79.135.105.20
25-09-2024 22:19:11 [WAARSCHUWING] Huidige server met IP 79.135.105.20 niet gevonden in gefilterde gegevens.
25-09-2024 22:19:11 [INFO] Huidige server niet gevonden in gefilterde lijst, volledige serverlijst wordt gecontroleerd...
25-09-2024 22:19:12 [INFO] API-responsgegevens ontvangen.
25-09-2024 22:19:13 [INFO] Huidige server BH#1 - Latentie: 26,90 ms, belasting: 10%, score: 15,07
25-09-2024 22:19:14 [INFO] Server AO#1 - Latentie: 27,10 ms, belasting: 7%, score: 13,03
25-09-2024 22:19:15 [INFO] Server AO#2 - Latentie: 26,60 ms, belasting: 14%, score: 17,78
25-09-2024 22:19:16 [INFO] Server AO#3 - Latentie: 27,10 ms, belasting: 9%, score: 14,43
25-09-2024 22:19:17 [INFO] Server AO#4 - Latentie: 26,60 ms, belasting: 16%, score: 19,18
25-09-2024 22:19:17 [INFO] Huidige server BH#1 (IP: 79.135.105.20) - Score: 15,07
25-09-2024 22:19:17 [INFO] Beste server geselecteerd AO#1 - Score: 13,03
25-09-2024 22:19:17 [INFO] Huidige server BH#1 valt binnen de drempelwaarde. Schakelen is niet nodig.
25-09-2024 22:19:17 [INFO] --- ProtonVPN Auto Connect Script Einde ---
sudo cat /etc/wireguard/protonvpn.conf

Je kunt ook controleren of alles werkt door het bestand protonvpn.conf te controleren of door je externe IP-adres te controleren.

[Peer]
# BH#3 - Automatisch gegenereerd door ProtonVPN Auto Connect - Score: 7,74

U ziet dan iets als hierboven. Deze opmerking wordt bijgewerkt wanneer er verbinding wordt gemaakt met een andere VPN en het IP-adres binnen de peer natuurlijk ook verandert.

curl ifconfig.me #dit geeft het externe ip-adres weer in de cli.

Als extra stap kunt u ook uw externe IP-adres controleren. Dit zou iets anders moeten zijn dan het IP-adres dat door uw provider wordt verstrekt.

Dit script elk uur uitvoeren.

sudo crontab -e
0 * * * * cd /home/{gebruiker}/protonss && /home/{gebruiker}/protonss/protonvpn_auto_connect.py >> /home/{gebruiker}/protonss/protonvpn_cron.log 2>&1

Hier hebben we het zo ingesteld dat het elk uur wordt uitgevoerd, maar je kunt het plannen zoals je wilt.

sudo crontab -l

Sla de crontab op en controleer of cron is geïnstalleerd.

Optioneel: blokkeer specifiek verkeer als je VPN niet werkt

Als u ervoor wilt zorgen dat er geen verkeer uw apparaat verlaat als de VPN-verbinding uitvalt, kunt u gebruikmaken van iptables het verkeer blokkeren wanneer de WireGuard-interface niet werkt.

sudo apt-get install iptables

Controleer of u iptables hebt geïnstalleerd of installeer het met bovenstaande opdracht.

ifconfig -a
protonvpn: flags=209<UP,POINTOPOINT,RUNNING,NOARP>  mtu 1420
        inet 10.2.1.3  netmask 255.255.255.255  destination 10.2.1.3
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (UNSPEC)
        RX packets 612  bytes 101768 (99.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 765  bytes 101144 (98.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Controleer eerst de naam van uw netwerkinterface. Als u uw bestand protonvpn.conf hebt genoemd, moet het protonvpn worden genoemd.

sudo iptables -A OUTPUT -p tcp --dport 51413 ! -o protonvpn -j REJECT --reject-with icmp-port-unreachable
sudo iptables -A OUTPUT -p udp --dport 51413 ! -o protonvpn -j REJECT --reject-with icmp-port-unreachable

Je kunt elke regel toevoegen aan je iptables. De twee onderstaande regels zorgen ervoor dat poort 51413 wordt geweigerd wanneer de netwerkinterface protonvpn niet beschikbaar is. Er is een regel voor UDP- en TCP-verkeer.

sudo iptables -L OUTPUT -v -n

Nadat u alle vereiste regels hebt toegevoegd, kunt u ze controleren met deze opdracht .

sudo apt-get install iptables-persistent

En maak deze regels permanent, zodat u ze niet bij elke herstart opnieuw hoeft toe te voegen.

sudo netfilter-persistent opslaan

Conclusie

Met dit Python-script wordt uw Raspberry Pi altijd verbonden met de snelste beschikbare ProtonVPN-server, zodat u zonder handmatige tussenkomst verzekerd bent van de best mogelijke VPN-prestaties. Of u uw Pi nu gebruikt voor netwerken, torrenting of algemene online beveiliging, deze automatisering zorgt voor optimale prestaties en betrouwbare privacybescherming.

Update - 20-12-2024

Preferred_cities toegevoegd als optie om de selectie van het land VS te verkorten. Dat omvatte meer dan 2000 servers die anders gecontroleerd moesten worden. De prestatiecontrole kan op deze manier in ieder geval iets korter worden :-).

En ik heb ontdekt wat elke Feature-sectie voor elke server in de API bevat. Ik zal dit hieronder uitleggen. Ik heb mijn code aangepast om hiermee correct te kunnen werken. Houd er rekening mee dat de configuratie die u eerder hebt gemaakt, moet overeenkomen met het type server waarmee u verbinding maakt. Als uw configuratie niet is geconfigureerd om verbinding te maken met een Secure Core, selecteer dan ook geen Secure Core in dit script. Houd rekening met de aangemaakte ProtonVPN-configuratie.

Uitleg over de functie:

Elke server heeft precies ÉÉN functienummer:

  • 1, 17 = Secure Core-servers
  • 2 = TOR-servers
  • 0, 8, 16, 24 = Alleen Plus-servers
  • 4, 12, 20, 28 = P2P-servers (dit zijn blijkbaar ook Plus-servers)
        {
            "Name": "US-AZ#105",
            "EntryCountry": "US",
            "ExitCountry": "US",
            "Domain": "node-us-260.protonvpn.net",
            "Tier": 2,
            "Features": 12,

Na wat meer onderzoek naar vragen over de gratis servers van ProtonVPN, lijkt het erop dat ProtonVPN de openbare API-toegang tot hun gratis server-eindpunten heeft verwijderd. Het /vpn/servers-eindpunt dat voorheen informatie over gratis servers zou moeten geven, geeft nu een 404-foutmelding:

{    "Code": 404,    "Error": "Path not found",    "Details": {}}

Momenteel is alleen het Plus-server-eindpunt toegankelijk via het openbare 

API:url = 'https://api.protonmail.ch/vpn/logicals'

Dit is waarschijnlijk een bewuste wijziging door ProtonVPN. Helaas kan dit script de gratis servers dus niet ondersteunen.

Vertel het verder

Blijf lezen