def run(self, credentials, settings, update, sync, purge, auto_connect, kill_switch): self.setup() if credentials: self.credentials.save_new_credentials() if settings: self.settings.save_new_settings() if update: self.get_configs() if sync: if self.sync_servers(): networkmanager.reload_connections() elif purge: networkmanager.remove_autoconnect() networkmanager.remove_killswitch(paths.KILLSWITCH) self.purge_active_connections() if auto_connect: self.enable_auto_connect(auto_connect[0], auto_connect[1], auto_connect[2]) if kill_switch: networkmanager.set_killswitch(paths.KILLSWITCH)
def import_config(self, file_path: str, username: str, password: str) -> bool: updated = False imported = False if self.remove_legacy_files(): self.logger.info("Removed legacy files") if not os.path.isfile(file_path): self.logger.error("Configuration file '%s' does not exist.", file_path) return None # remove all old connections and any auto-connect, until a better import routine is added if self.remove_active_connections(): updated = True if networkmanager.remove_autoconnect(): updated = True dns_list = self.settings.get_custom_dns_servers() connection_name = os.path.splitext(os.path.basename(file_path))[0] if networkmanager.import_connection(file_path, connection_name, username, password, dns_list): updated = True imported = True self.active_servers[IMPORTED_SERVER_KEY] = { 'name': connection_name, 'domain': '<' + connection_name + '>', 'score': -1, 'load': -1, 'latency': -1, } self.save_active_servers(self.active_servers, paths.ACTIVE_SERVERS) if updated: networkmanager.reload_connections() return imported
def sync_servers(self, preserve_vpn, slow_mode): updated = False username = self.credentials.get_username() password = self.credentials.get_password() # Check if there are custom DNS servers specified in the settings before loading the defaults dns_list = self.settings.get_custom_dns_servers() if not self.configs_exist(): self.logger.warning("No OpenVPN configuration files found.") if not self.get_configs(): sys.exit(1) self.logger.info("Checking for new connections to import...") server_list = nordapi.get_server_list(sort_by_load=True) if server_list: valid_server_list = self.get_valid_servers(server_list) if valid_server_list: if not preserve_vpn: # If there's a kill-switch in place, we need to temporarily remove it, otherwise it will kill out network when disabling an active VPN below # Disconnect active Nord VPNs, so we get a more reliable benchmark show_warning = False if networkmanager.remove_killswitch(): show_warning = True warning_string = "Kill-switch" if networkmanager.disconnect_active_vpn(self.active_servers): if show_warning: warning_string = "Active VPN(s) and " + warning_string else: show_warning = True warning_string = "Active VPN(s)" if show_warning: self.logger.warning("%s disabled for accurate benchmarking. Your connection is not secure until these are re-enabled.", warning_string) else: self.logger.warning("Active VPN preserved. This may give unreliable results!") if slow_mode: self.logger.info("Benchmarking slow mode enabled.") num_servers = len(valid_server_list) self.logger.info("Benchmarking %i servers...", num_servers) start = timer() ping_attempts = self.settings.get_ping_attempts() # We are going to be multiprocessing within a class instance, so this needs getting outside of the multiprocessing valid_protocols = self.settings.get_protocols() valid_categories = self.settings.get_categories() best_servers, num_success = benchmarking.get_best_servers(valid_server_list, ping_attempts, valid_protocols, valid_categories, slow_mode) end = timer() if num_success == 0: self.logger.error("Benchmarking failed to test any servers. Your network may be blocking large-scale ICMP requests. Exiting.") sys.exit(1) else: percent_success = round(num_success / num_servers * 100, 2) self.logger.info("Benchmarked %i servers successfully (%0.2f%%). Took %0.2f seconds.", num_success, percent_success, end - start) if percent_success < 90.0: self.logger.warning("A large quantity of tests failed. Your network may be unreliable, or blocking large-scale ICMP requests. Syncing in slow mode (-s) may fix this.") # remove all old connections and any auto-connect, until a better sync routine is added if self.remove_active_connections(): updated = True if networkmanager.remove_autoconnect(): updated = True self.logger.info("Adding new connections...") new_connections = 0 for key in best_servers.keys(): imported = True name = best_servers[key]['name'] if not self.connection_exists(name): domain = best_servers[key]['domain'] protocol = key[2] file_path = self.get_ovpn_path(domain, protocol) if file_path: if networkmanager.import_connection(file_path, name, username, password, dns_list): updated = True new_connections += 1 else: imported = False else: self.logger.warning("Could not find a configuration file for %s. Skipping.", name) # If the connection already existed, or the import was successful, add the server combination to the active servers if imported: self.active_servers[key] = best_servers[key] self.save_active_servers(self.active_servers, paths.ACTIVE_SERVERS) if new_connections > 0: self.logger.info("%i new connections added.", new_connections) else: self.logger.info("No new connections added.") return updated else: self.logger.error("No servers found matching your settings. Review your settings and try again.") sys.exit(1) else: self.logger.error("Could not fetch the server list from NordVPN. Check your Internet connectivity.") sys.exit(1)
def __init__(self): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(title="commands", help="Each command has its own help page, which can be accessed via nordnm <COMMAND> --help", metavar='') # Kill-switch and auto-connect are repeated, to allow their use with or without the sync command. # TODO: Find out if there's a way to re-use the attributes so they don't need to be manually repeated parser.add_argument("-v", "--version", help="Display the package version.", action="store_true") parser.add_argument("-k", "--kill-switch", help="Sets a network kill-switch, to disable the active network interface when an active VPN connection disconnects.", action="store_true") parser.add_argument("-a", "--auto-connect", nargs=3, metavar=("[COUNTRY_CODE]", "[VPN_CATEGORY]", "[PROTOCOL]"), help="Configure NetworkManager to auto-connect to the chosen server type. Takes country code, category and protocol.") remove_parser = subparsers.add_parser("remove", aliases=['r'], help="Remove active connections, auto-connect, kill-switch, data, mac settings or all.") remove_parser.add_argument("--all", dest="remove_all", help="Remove all connections, enabled features and local data.", action="store_true") remove_parser.add_argument("-c", "--connections", dest="remove_c", help="Remove all active connections and auto-connect.", action="store_true") remove_parser.add_argument("-a", "--auto-connect", dest="remove_ac", help="Remove the active auto-connect feature.", action="store_true") remove_parser.add_argument("-k", "--kill-switch", dest="remove_ks", help="Remove the active kill-switch feature.", action="store_true") remove_parser.add_argument("-d", "--data", dest="remove_d", help="Remove existing local data (VPN Configs, Credentials & Settings).", action="store_true") remove_parser.add_argument("-m", "--mac-settings", dest="remove_m", help="Remove existing MAC Address settings configured by nordnm.", action="store_true") remove_parser.set_defaults(remove=True) update_parser = subparsers.add_parser('update', aliases=['u'], help='Update a specified setting.') update_parser.add_argument('-c', '--credentials', help='Update your existing saved credentials.', action='store_true') update_parser.add_argument('-s', '--settings', help='Update your existing saved settings.', action='store_true') update_parser.set_defaults(update=True) list_parser = subparsers.add_parser('list', aliases=['l'], help="List the specified information.") list_parser.add_argument('--active-servers', help='Display a list of the active servers currently synchronised.', action='store_true', default=False) list_parser.add_argument('--countries', help='Display a list of the available NordVPN countries.', action='store_true', default=False) list_parser.add_argument('--categories', help='Display a list of the available NordVPN categories..', action='store_true', default=False) list_parser.set_defaults(list=True) sync_parser = subparsers.add_parser('sync', aliases=['s'], help="Synchronise the optimal servers (based on load and latency) to NetworkManager.") sync_parser.add_argument('-s', '--slow-mode', help="Run benchmarking in 'slow mode'. May increase benchmarking success by pinging servers at a slower rate.", action='store_true') sync_parser.add_argument('-p', '--preserve-vpn', help="When provided, synchronising will preserve any active VPN instead of disabling it for more accurate benchmarking.", action='store_true') sync_parser.add_argument('-n', '--no-update', help='Do not download the latest OpenVPN configurations from NordVPN.', action='store_true', default=False) sync_parser.add_argument("-k", "--kill-switch", help="Sets a network kill-switch, to disable the active network interface when an active VPN connection disconnects.", action="store_true") sync_parser.add_argument('-a', '--auto-connect', nargs=3, metavar=('[COUNTRY_CODE]', '[VPN_CATEGORY]', '[PROTOCOL]'), help='Configure NetworkManager to auto-connect to the chosen server type. Takes country code, category and protocol.') sync_parser.set_defaults(sync=True) import_parser = subparsers.add_parser('import', aliases=['i'], help="Import an OpenVPN config file to NetworkManager.") import_parser.add_argument("config_file", metavar='CONFIG_FILE', help="The OpenVPN config file to be imported.") import_parser.add_argument("-k", "--kill-switch", help="Sets a network kill-switch, to disable the active network interface when an active VPN connection disconnects.", action="store_true") import_parser.add_argument('-a', '--auto-connect', help='Configure NetworkManager to auto-connect to the the imported config.', action="store_true", dest="auto_connect_imported", default=False) import_parser.add_argument('-u', '--username', required=True, help="Specify the username used for the OpenVPN config.", metavar="USERNAME") import_parser.add_argument('-p', '--password', required=True, help="Specify the password used for the OpenVPN config.", metavar="PASSWORD") import_parser.set_defaults(import_config=True) # For reference: https://blogs.gnome.org/thaller/category/networkmanager/ mac_parser = subparsers.add_parser('mac', aliases=['m'], help="Global NetworkManager MAC address preferences. This command will affect ALL NetworkManager connections permanently.") mac_parser.add_argument('-r', '--random', help="A randomised MAC addresss will be generated on each connect.", action='store_true') mac_parser.add_argument('-s', '--stable', help="Use a stable, hashed MAC address on connect.", action='store_true') mac_parser.add_argument('-e', '--explicit', help="Specify a MAC address to use on connect.", nargs=1, metavar='"MAC_ADDRESS"') mac_parser.add_argument('--preserve', help="Don't change the current MAC address upon connection.", action='store_true') mac_parser.add_argument('--permanent', help="Use the permanent MAC address of the device on connect.", action='store_true') mac_parser.set_defaults(mac=True) self.logger = logging.getLogger(__name__) self.active_servers = {} try: args = parser.parse_args() except Exception: parser.print_help() sys.exit(1) # Count the number of arguments provided arg_count = 0 for arg in vars(args): if getattr(args, arg): arg_count += 1 if arg_count == 0: parser.print_help() sys.exit(1) if "version" in args and args.version: print(__version__) sys.exit(1) self.print_splash() # Check for commands that should be run on their own if "remove" in args and args.remove: removed = False if not args.remove_c and not args.remove_d and not args.remove_ac and not args.remove_ks and not args.remove_m and not args.remove_all: remove_parser.print_help() sys.exit(1) if args.remove_all: # Removing all, so set all args to True args.remove_ks = True args.remove_ac = True args.remove_c = True args.remove_d = True args.remove_m = True elif args.remove_c: # We need to remove the auto-connect if we are removing all connections args.remove_ac = True if args.remove_ks: if networkmanager.remove_killswitch(): removed = True if args.remove_ac: if networkmanager.remove_autoconnect(): removed = True if args.remove_c: # Get the active servers, since self.setup() hasn't run if os.path.isfile(paths.ACTIVE_SERVERS): self.active_servers = self.load_active_servers(paths.ACTIVE_SERVERS) if self.remove_active_connections(): removed = True if args.remove_d: if self.remove_data(): removed = True if args.remove_m: if networkmanager.remove_global_mac_address(): removed = True if removed: networkmanager.reload_connections() else: self.logger.info("Nothing to remove.") sys.exit(0) elif "list" in args and args.list: if not args.countries and not args.categories and not args.active_servers: list_parser.print_help() sys.exit(1) if args.categories: self.print_categories() if args.countries: self.print_countries() if args.active_servers: self.print_active_servers() sys.exit(0) elif "mac" in args and args.mac: value = None if args.random: value = "random" elif args.stable: value = "stable" elif args.explicit: value = args.explicit[0] elif args.preserve: value = "preserve" elif args.permanent: value = "permanent" if value: if networkmanager.set_global_mac_address(value): networkmanager.restart() else: mac_parser.print_help() # Now that arguments that don't need to be disturbed by setup() are over, do setup() self.setup() if "update" in args and args.update: if not args.credentials and not args.settings: update_parser.print_help() sys.exit(1) if args.credentials: self.credentials.save_new_credentials() if args.settings: self.settings.save_new_settings() sys.exit(0) # Now check for commands that can be chained... if "sync" in args and args.sync: # Take the inverse of no_update arg as update parameter self.sync(not args.no_update, args.preserve_vpn, args.slow_mode) if "import_config" in args and args.import_config: if not self.import_config(args.config_file, args.username, args.password): sys.exit(1) if args.auto_connect_imported: self.enable_auto_connect(*IMPORTED_SERVER_KEY) if args.kill_switch: networkmanager.set_killswitch() if args.auto_connect: country_code = args.auto_connect[0] category = args.auto_connect[1] protocol = args.auto_connect[2] self.enable_auto_connect(country_code, category, protocol) sys.exit(0)
def sync_servers(self): updated = False username = self.credentials.get_username() password = self.credentials.get_password() dns_list = nordapi.get_nameservers() self.logger.info("Checking for new connections to import...") if self.configs_exist(): server_list = nordapi.get_server_list(sort_by_load=True) if server_list: valid_server_list = self.get_valid_servers(server_list) if valid_server_list: # If there's a kill-switch in place, we need to temporarily remove it, otherwise it will kill out network when disabling an active VPN below # Disconnect active Nord VPNs, so we get a more reliable benchmark show_warning = False if networkmanager.remove_killswitch(paths.KILLSWITCH): show_warning = True warning_string = "Kill-switch" if networkmanager.disconnect_active_vpn( self.active_servers): if show_warning: warning_string = "Active VPN(s) and " + warning_string else: show_warning = True warning_string = "Active VPN(s)" if show_warning: self.logger.warning( "%s disabled for accurate benchmarking. Your connection is not secure until these are re-enabled.", warning_string) self.logger.info("Benchmarking servers...") start = timer() ping_attempts = self.settings.get_ping_attempts( ) # We are going to be multiprocessing within a class instance, so this needs getting outside of the multiprocessing valid_protocols = self.settings.get_protocols() best_servers = benchmarking.get_best_servers( valid_server_list, ping_attempts, valid_protocols) end = timer() self.logger.info( "Benchmarking complete. Took %0.2f seconds.", end - start) # Purge all old connections and any auto-connect, until a better sync routine is added if self.purge_active_connections(): updated = True if networkmanager.remove_autoconnect(): updated = True self.logger.info("Adding new connections...") new_connections = 0 for key in best_servers.keys(): imported = True name = best_servers[key]['name'] if not self.connection_exists(name): domain = best_servers[key]['domain'] protocol = key[2] file_path = self.get_ovpn_path(domain, protocol) if file_path: if networkmanager.import_connection( file_path, name, username, password, dns_list): updated = True new_connections += 1 else: imported = False else: self.logger.warning( "Could not find a configuration file for %s. Skipping.", name) # If the connection already existed, or the import was successful, add the server combination to the active servers if imported: self.active_servers[key] = best_servers[key] self.save_active_servers(self.active_servers, paths.ACTIVE_SERVERS) if new_connections > 0: self.logger.info("%i new connections added.", new_connections) else: self.logger.info("No new connections added.") return updated else: self.logger.error( "No servers found matching your settings. Review your settings and try again." ) sys.exit(1) else: self.logger.error( "Could not fetch the server list from NordVPN. Check your Internet connectivity." ) sys.exit(1) else: self.logger.error( "Can't find any OpenVPN configuration files. Please run --update before syncing." ) sys.exit(1)
def sync_servers(self, preserve_vpn): # remove legacy for file_path in paths.LEGACY_FILES: try: os.remove(file_path) except FileNotFoundError: pass log.info("Checking for new connections to import...") server_list = nordapi.get_server_list(sort_by_load=True) if not server_list: log.error( "Could not fetch the server list from NordVPN. Check your Internet connectivity." ) sys.exit(1) server_list = [s for s in server_list if self.is_valid_server(s)] if not server_list: log.error( "No servers found matching your settings. Review your settings and try again." ) sys.exit(1) if preserve_vpn: log.warning( "Active VPN preserved. This may give unreliable results!") else: # If there's a kill-switch in place, we need to temporarily remove it, # otherwise it will kill out network when disabling an active VPN below # Disconnect active Nord VPNs, so we get a more reliable benchmark warnings = { 'Kill-switch': networkmanager.remove_killswitch(), 'Active VPN(s)': networkmanager.disconnect_active_vpn(self.active_servers), } if any(warnings.values()): log.warning( f"{', '.join(warnings.keys())} disabled for accurate benchmarking. " f"Your connection is not secure until these are re-enabled." ) # remove all old connections and any auto-connect, until a better sync routine is added self.active_servers = {} networkmanager.remove_autoconnect() log.info("Adding new connections...") new_servers = {} for key, server in self.get_best_servers(server_list).items(): if self.connection_exists(server['name']): new_servers[key] = server continue file_path = self.get_ovpn_path(server['domain'], key[2]) if not file_path: log.warning( f"Could not find a configuration file for {server['name']}. Skipping." ) continue networkmanager.import_connection(file_path, server['name'], self.credentials.get_username(), self.credentials.get_password(), nordapi.get_nameservers()) new_servers[key] = server if len(new_servers) > 0: self.active_servers = {**self.active_servers, **new_servers} log.info(f"{len(new_servers)} new connections added.") else: log.info("No new connections added.")
def remove_auto_connect(): networkmanager.remove_autoconnect()