diff --git a/nord-checker.py b/nord-checker.py index 2fb0343..49e5fe7 100644 --- a/nord-checker.py +++ b/nord-checker.py @@ -6,17 +6,20 @@ import datetime import requests import re import sys -from psql_utils import connect_to_database, update_or_insert_ip -from ovpn_downloader import download_and_extract_ovpn_configs, download_ovpn_if_needed +from psql_utils import connect_to_database, update_or_insert_ip, should_skip_file from openvpn_manager import establish_vpn_connection, is_vpn_active, disconnect_vpn, get_external_ip +from ovpn_template import DEFAULT_OVPN_CONFIG # Variables CREDENTIAL_FILE = "nord.creds" -OVPN_DIR = "ovpn_configs/ovpn_tcp" +TEMP_OVPN_DIR = "/tmp/ovpn_configs" # Directory for temporary OVPN files LOG_FILE = "openvpn.log" # This is not used anymore RESULT_FILE = "vpnlist.txt" PING_URL = "https://health.ext.ben.io/ping/a1a55915-1051-48ed-bd13-ea3f1717a3ee" +# NordVPN API endpoint (updated to get all servers) +NORDVPN_API_URL = "https://api.nordvpn.com/v1/servers?limit=16384" + # Define DEBUG_MODE DEBUG_MODE = False if len(sys.argv) > 1 and sys.argv[1] == "-d": @@ -27,7 +30,7 @@ def debug_print(message): print(f"{datetime.datetime.now()} + [Debug Mode]:{message}") try: - requests.get(PING_URL, timeout=10) + requests.get(PING_URL, auth=('local', 'local'), timeout=10) print(f"{datetime.datetime.now()} [Main Script]: Ping sent to: %s" % PING_URL) except requests.RequestException as e: # Log ping failure here... @@ -64,7 +67,17 @@ def is_script_running(): print(f"{datetime.datetime.now()} [Main Script]: Error checking for running instances: {e}") return False - +# Function to get a list of all NordVPN servers +def get_nordvpn_servers(): + """Fetches a list of all NordVPN servers from the API.""" + try: + response = requests.get(NORDVPN_API_URL) + response.raise_for_status() # Raise an exception for bad status codes + servers = response.json() + return servers + except requests.exceptions.RequestException as e: + print(f"{datetime.datetime.now()} [Main Script]: Error fetching NordVPN servers: {e}") + return [] # Get current IP before starting (using get_external_ip) @@ -91,76 +104,85 @@ except subprocess.CalledProcessError: # Connect to PostgreSQL database conn, cursor = connect_to_database() -# Download and extract OVPN configurations, and get the list of files to check -debug_print(f"{datetime.datetime.now()} [Main Script]: Downloading and extracting OVPN configurations...") -ovpn_files_to_check = download_and_extract_ovpn_configs(cursor, conn, debug_print=debug_print) +# Get the list of recommended NordVPN servers +servers = get_nordvpn_servers() -total_files = len(ovpn_files_to_check) -debug_print(f"{datetime.datetime.now()} [Main Script]: Found {total_files} OVPN files to check.") -print(f"{datetime.datetime.now()} [Main Script]: Found {total_files} OVPN files to check.") +# Create the temporary directory if it doesn't exist +if not os.path.exists(TEMP_OVPN_DIR): + os.makedirs(TEMP_OVPN_DIR) -# Process each OVPN file -FILE_NUM = 1 -for OVPN_FILE in ovpn_files_to_check: - OVPN_FILENAME = os.path.basename(OVPN_FILE) +# Process each server +server_count = len(servers) +for index, server in enumerate(servers): + # Check if the server uses OpenVPN TCP Dedicated technology + technologies = server.get('technologies', []) + skip_server = any(tech.get('identifier') == 'openvpn_dedicated_tcp' for tech in technologies) - debug_print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Processing OVPN file...") + if skip_server: + server_name = server.get('hostname') + debug_print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Skipping OpenVPN TCP Dedicated server.") + continue # Skip to the next server - # Check if the OVPN file exists, download if needed - file_exists = download_ovpn_if_needed(OVPN_FILENAME, cursor, conn, debug_print=debug_print) + server_name = server.get('hostname') + server_ip = server.get('station') # Get the server IP address + server_load = server.get('load', 'N/A') # Get server load, default to 'N/A' if not available + server_status = server.get('status', 'N/A') # Get server status, default to 'N/A' if not available - if not file_exists: - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Skipping, file not found and removed from database.") - FILE_NUM += 1 + debug_print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Load: {server_load}, Status: {server_status} Processing server... ") + + # Construct the temporary OVPN filename + ovpn_filename = f"{server_name}.tcp.ovpn" + ovpn_filepath = os.path.join(TEMP_OVPN_DIR, ovpn_filename) + + # Check if the server should be skipped based on last check time + if should_skip_file(cursor, ovpn_filename, index+1, server_count, debug_print=debug_print): + debug_print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Skipping server, checked within the last 3 days.") continue - # Establish VPN connection - connection_successful, connection_message = establish_vpn_connection(f"{OVPN_DIR}/{OVPN_FILE}", CREDENTIAL_FILE, LOG_FILE, debug_print=debug_print) + # Create temporary OVPN file from template + with open(ovpn_filepath, "w") as f: + f.write(DEFAULT_OVPN_CONFIG.format(server_ip=server_ip, server_cn=server_name)) + # Establish VPN connection + connection_successful, connection_message = establish_vpn_connection( + ovpn_filepath, CREDENTIAL_FILE, LOG_FILE, debug_print=debug_print + ) if connection_successful: - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: {connection_message}") - if not connection_successful: - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Error starting OpenVPN connection:{connection_message}") + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: {connection_message}") - # Wait for a random time between 1 and 4 seconds - sleep_time = random.randint(8, 15) - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Waiting for {sleep_time} seconds before the next connection...") - time.sleep(sleep_time) - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Resuming...") + # Check if the VPN connection is active + try: + subprocess.run(["pgrep", "-f", ovpn_filename], check=True, capture_output=True) - continue # Skip to the next OVPN file + # Check external IP (with validation and retries) + external_ip = get_external_ip() - # Check if the VPN connection is active (using the function from openvpn_manager.py) - try: - subprocess.run(["pgrep", "-f", OVPN_FILENAME], check=True, capture_output=True) + if external_ip is None: + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Unable to get external IP. Skipping this server.") + continue - # Check external IP (with validation and retries) - external_ip = get_external_ip() + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: External IP via VPN: {external_ip}") - if external_ip is None: - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Unable to get external IP. Skipping this OVPN file.") - continue + # Update or insert into database + update_or_insert_ip(cursor, conn, ovpn_filename, external_ip) - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: External IP via VPN: {external_ip}") + except subprocess.CalledProcessError: + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: VPN connection failed to establish.") + else: + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Error starting OpenVPN connection: {connection_message}") - # Update or insert into database - update_or_insert_ip(cursor, conn, OVPN_FILENAME, external_ip) - - except subprocess.CalledProcessError: - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: VPN connection failed to establish.") - - # Disconnect VPN + # Disconnect VPN disconnect_vpn() - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: VPN disconnected.") + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: VPN disconnected.") # Wait for a random time between 5 and 8 seconds sleep_time = random.randint(5, 8) - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Waiting for {sleep_time} seconds before the next connection...") + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Waiting for {sleep_time} seconds before the next connection...") time.sleep(sleep_time) - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Resuming...") + print(f"{datetime.datetime.now()} [{index+1}/{server_count}] [{server_name}]: Resuming...") - # Increment file number - FILE_NUM += 1 + # Clean up the temporary OVPN file + os.remove(ovpn_filepath) # Close the database connection cursor.close() diff --git a/ovpn_template.py b/ovpn_template.py new file mode 100644 index 0000000..87570ce --- /dev/null +++ b/ovpn_template.py @@ -0,0 +1,83 @@ +# ovpn_template.py +DEFAULT_OVPN_CONFIG = """ +client +dev tun +proto tcp +remote {server_ip} 443 +resolv-retry infinite +remote-random +nobind +tun-mtu 1500 +tun-mtu-extra 32 +mssfix 1450 +persist-key +persist-tun +ping 15 +ping-restart 0 +ping-timer-rem +reneg-sec 0 +comp-lzo no +verify-x509-name CN={server_cn} +remote-cert-tls server +auth-user-pass +verb 3 +pull +fast-io +cipher AES-256-CBC +auth SHA512 + +-----BEGIN CERTIFICATE----- +MIIFCjCCAvKgAwIBAgIBATANBgkqhkiG9w0BAQ0FADA5MQswCQYDVQQGEwJQQTEQ +MA4GA1UEChMHTm9yZFZQTjEYMBYGA1UEAxMPTm9yZFZQTiBSb290IENBMB4XDTE2 +MDEwMTAwMDAwMFoXDTM1MTIzMTIzNTk1OVowOTELMAkGA1UEBhMCUEExEDAOBgNV +BAoTB05vcmRWUE4xGDAWBgNVBAMTD05vcmRWUE4gUm9vdCBDQTCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMkr/BYhyo0F2upsIMXwC6QvkZps3NN2/eQF +kfQIS1gql0aejsKsEnmY0Kaon8uZCTXPsRH1gQNgg5D2gixdd1mJUvV3dE3y9FJr +XMoDkXdCGBodvKJyU6lcfEVF6/UxHcbBguZK9UtRHS9eJYm3rpL/5huQMCppX7kU +eQ8dpCwd3iKITqwd1ZudDqsWaU0vqzC2H55IyaZ/5/TnCk31Q1UP6BksbbuRcwOV +skEDsm6YoWDnn/IIzGOYnFJRzQH5jTz3j1QBvRIuQuBuvUkfhx1FEwhwZigrcxXu +MP+QgM54kezgziJUaZcOM2zF3lvrwMvXDMfNeIoJABv9ljw969xQ8czQCU5lMVmA +37ltv5Ec9U5hZuwk/9QO1Z+d/r6Jx0mlurS8gnCAKJgwa3kyZw6e4FZ8mYL4vpRR +hPdvRTWCMJkeB4yBHyhxUmTRgJHm6YR3D6hcFAc9cQcTEl/I60tMdz33G6m0O42s +Qt/+AR3YCY/RusWVBJB/qNS94EtNtj8iaebCQW1jHAhvGmFILVR9lzD0EzWKHkvy +WEjmUVRgCDd6Ne3eFRNS73gdv/C3l5boYySeu4exkEYVxVRn8DhCxs0MnkMHWFK6 +MyzXCCn+JnWFDYPfDKHvpff/kLDobtPBf+Lbch5wQy9quY27xaj0XwLyjOltpiST +LWae/Q4vAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqG +SIb3DQEBDQUAA4ICAQC9fUL2sZPxIN2mD32VeNySTgZlCEdVmlq471o/bDMP4B8g +nQesFRtXY2ZCjs50Jm73B2LViL9qlREmI6vE5IC8IsRBJSV4ce1WYxyXro5rmVg/ +k6a10rlsbK/eg//GHoJxDdXDOokLUSnxt7gk3QKpX6eCdh67p0PuWm/7WUJQxH2S +DxsT9vB/iZriTIEe/ILoOQF0Aqp7AgNCcLcLAmbxXQkXYCCSB35Vp06u+eTWjG0/ +pyS5V14stGtw+fA0DJp5ZJV4eqJ5LqxMlYvEZ/qKTEdoCeaXv2QEmN6dVqjDoTAo +k0t5u4YRXzEVCfXAC3ocplNdtCA72wjFJcSbfif4BSC8bDACTXtnPC7nD0VndZLp ++RiNLeiENhk0oTC+UVdSc+n2nJOzkCK0vYu0Ads4JGIB7g8IB3z2t9ICmsWrgnhd +NdcOe15BincrGA8avQ1cWXsfIKEjbrnEuEk9b5jel6NfHtPKoHc9mDpRdNPISeVa +wDBM1mJChneHt59Nh8Gah74+TM1jBsw4fhJPvoc7Atcg740JErb904mZfkIEmojC +VPhBHVQ9LHBAdM8qFI2kRK0IynOmAZhexlP/aT/kpEsEPyaZQlnBn3An1CRz8h0S +PApL8PytggYKeQmRhl499+6jLxcZ2IegLfqq41dzIjwHwTMplg+1pKIOVojpWA== +-----END CERTIFICATE----- + +key-direction 1 + +# +# 2048 bit OpenVPN static key +# +-----BEGIN OpenVPN Static key V1----- +e685bdaf659a25a200e2b9e39e51ff03 +0fc72cf1ce07232bd8b2be5e6c670143 +f51e937e670eee09d4f2ea5a6e4e6996 +5db852c275351b86fc4ca892d78ae002 +d6f70d029bd79c4d1c26cf14e9588033 +cf639f8a74809f29f72b9d58f9b8f5fe +fc7938eade40e9fed6cb92184abb2cc1 +0eb1a296df243b251df0643d53724cdb +5a92a1d6cb817804c4a9319b57d53be5 +80815bcfcb2df55018cc83fc43bc7ff8 +2d51f9b88364776ee9d12fc85cc7ea5b +9741c4f598c485316db066d52db4540e +212e1518a9bd4828219e24b20d88f598 +a196c9de96012090e333519ae18d3509 +9427e7b372d348d352dc4c85e18cd4b9 +3f8a56ddb2e64eb67adfc9b337157ff4 +-----END OpenVPN Static key V1----- + +""" diff --git a/psql_utils.py b/psql_utils.py index 060ee25..a5330a0 100644 --- a/psql_utils.py +++ b/psql_utils.py @@ -23,7 +23,7 @@ def connect_to_database(): cursor = conn.cursor() return conn, cursor -def should_skip_file(cursor, filename): +def should_skip_file(cursor, filename, index, server_count, debug_print): """ Checks if the given OVPN file should be skipped based on the last check time in the database. Returns True if it should be skipped, False otherwise. @@ -33,7 +33,7 @@ def should_skip_file(cursor, filename): row = cursor.fetchone() if row is None: - print(f"No record found for {filename} in the database.") + debug_print(f"{datetime.datetime.now()} [{index}/{server_count}] [{filename}]: Skipping OpenVPN TCP Dedicated server.") return False # No record found, so don't skip last_check = row[0]