#!/usr/bin/env python3 import subprocess import requests import argparse import json import random PRIVACY_FRIENDLY_COUNTRIES = ['Sweden', 'Switzerland', 'Germany', 'Finland', 'Netherlands', 'Norway'] TAILSCALE_ARGS = [ '--exit-node-allow-lan-access', # '--stateful-filtering=false', '--accept-dns', '--accept-routes', '--auto-update' ] def get_current_exit_node(): result = subprocess.run(['tailscale', 'status', '--json'], capture_output=True, text=True) if result.returncode != 0: raise Exception("Failed to get Tailscale status") status = json.loads(result.stdout) current_exit_node = status.get('Peer', {}).get('Tailnet', {}).get('ExitNode', {}).get('Name') return current_exit_node def set_exit_node(): # Get the suggested exit node result = subprocess.run(['tailscale', 'exit-node', 'suggest'], capture_output=True, text=True) exit_node = '' for line in result.stdout.splitlines(): if 'Suggested exit node' in line: exit_node = line.split(': ')[1].strip() break print(f"Suggested exit node: {exit_node}") # Set the exit node with additional arguments cmd = ['tailscale', 'set', f'--exit-node={exit_node}'] + TAILSCALE_ARGS subprocess.run(cmd, check=True) # Verify the exit node response = requests.get('https://am.i.mullvad.net/json') exit_node_info = response.json() exit_node_hostname = exit_node_info.get('mullvad_exit_ip_hostname') print(f"Current exit node hostname: {exit_node_hostname}") # Get the part before the first '.' in the exit_node exit_node_short = exit_node.split('.')[0] # Verify that the exit_node_short and exit_node_hostname are equal if exit_node_short == exit_node_hostname: print("Exit node set successfully!") else: print("Failed to set exit node!") def unset_exit_node(): # Unset the exit node cmd = ['tailscale', 'set', '--exit-node='] + TAILSCALE_ARGS subprocess.run(cmd, check=True) print("Exit node unset successfully!") def start_exit_node(): current_exit_node = get_current_exit_node() if current_exit_node: print(f"Already connected to exit node: {current_exit_node}") else: set_exit_node() def get_random_privacy_friendly_exit_node(): result = subprocess.run(['tailscale', 'exit-node', 'list'], capture_output=True, text=True) if result.returncode != 0: raise Exception("Failed to list Tailscale exit nodes") exit_nodes = [] for line in result.stdout.splitlines(): parts = line.split() if len(parts) > 3 and parts[2] in PRIVACY_FRIENDLY_COUNTRIES: exit_nodes.append(parts[1]) if not exit_nodes: raise Exception("No privacy-friendly exit nodes available") return random.choice(exit_nodes) def set_random_privacy_friendly_exit_node(): exit_node = get_random_privacy_friendly_exit_node() print(f"Selected random privacy-friendly exit node: {exit_node}") # Set the exit node with additional arguments cmd = ['tailscale', 'set', f'--exit-node={exit_node}'] + TAILSCALE_ARGS subprocess.run(cmd, check=True) # Verify the exit node response = requests.get('https://am.i.mullvad.net/json') exit_node_info = response.json() exit_node_hostname = exit_node_info.get('mullvad_exit_ip_hostname') print(f"Current exit node hostname: {exit_node_hostname}") # Get the part before the first '.' in the exit_node exit_node_short = exit_node.split('.')[0] # Verify that the exit_node_short and exit_node_hostname are equal if exit_node_short == exit_node_hostname: print("Exit node set successfully!") else: print("Failed to set exit node!") if __name__ == "__main__": parser = argparse.ArgumentParser(description='Manage VPN exit nodes.') parser.add_argument('action', choices=['start', 'stop', 'new', 'shh'], help='Action to perform: start, stop, new, or shh') args = parser.parse_args() if args.action == 'start': start_exit_node() elif args.action == 'stop': unset_exit_node() elif args.action == 'new': set_exit_node() elif args.action == 'shh': set_random_privacy_friendly_exit_node()