def pingable(self): if not self.enabled(): L.verbose("{} not enabled".format(self.name())) return False ips = self.ip_addresses() if not ips: L.verbose("{} has no IP address".format(self.name())) return False cmd = ['ping', '-n', '1', '-w', '2', '-S', ips[0].exploded, '8.8.8.8'] try: output = check_subprocess(cmd)[0] if 'Received = 1' in output: return True else: # Consider this a real error and propagate. It's likely a code issue. raise XVEx( "Don't know how to parse ping output: {}".format(output)) except XVProcessException as ex: if 'Lost = 1' in ex.stderr: L.debug("Couldn't ping on adapter {}".format(self)) return False L.warning("Ping failed unexpectedly with error '{}'. " "Assuming adapter {} un-pingable.".format( ex.stderr, self)) return False
def protocol(self): info = self._vpn_info() if info is not None and info.protocol: return info.protocol L.warning("Couldn't determine the VPN protocol. Will fallback to default method") return super().protocol()
def dns_server_ips(self): info = self._vpn_info() if info is not None and info.dns_server_ips: return info.dns_server_ips if not self._config.get('strict', False): dns_servers_after_connect = set( self._device['dns_tool'].known_servers()) L.debug( "Inferring VPN DNS servers. DNS before connect: {}, DNS after connect: {}" .format(self._dns_servers_before_connect, dns_servers_after_connect)) for server in self._dns_servers_before_connect: dns_servers_after_connect.discard(server) if dns_servers_after_connect: L.warning( "Inferring VPN DNS server IPs from System Configuration. " "This is likely correct, but can be prevented by specifying " "the 'strict' keyword in the VPN configuration.") return list(dns_servers_after_connect) L.warning("Couldn't find DNS servers by inspecting system.") return super().dns_server_ips()
def detect(self): L.debug("Trying to determine if we're using the l2tp protocol") vpn_info = VPNInfo() # Assume that no-process definitively means no openvpn if not self._l2tp_processes(): L.debug('Not using l2tp') return None L.info("Detected l2tp as the current VPN protocol") vpn_info.vpn_processes = self._l2tp_processes() vpn_info.protocol = 'l2tp' # TODO: Do tunnel_interface vpn_info.vpn_server_ip = self._get_vpn_server_ip() vpn_info.dns_server_ips = [] vpn_info.tunnel_interface = None if vpn_info.vpn_server_ip == ipaddress.ip_address('127.0.0.1'): L.warning( "Got vpn server IP 127.0.0.1. The app is likely using a proxy. Removing this IP" ) vpn_info.vpn_server_ip = None # Prevent anyone who's holding on to this class from using stale data. self._reset() L.debug( "Detected the following info about openvpn: {}".format(vpn_info)) return vpn_info
def configure(self): skip_settings = self._config.get('skip_settings', True) settings = self._config.get('settings', {}) human_readable = settings.get('human_readable', None) if human_readable is not None: L.describe("VPN application should be configured to {}".format( human_readable)) elif settings: L.describe( "VPN application should be configured to {}".format(settings)) if not settings: L.warning( "No settings specified for VPN application. Using current settings." ) return elif skip_settings: L.warning( "Configuring VPN application skipped as skip_settings=True") return msg = "" for key, value in list(settings.items()): msg += " {} => {}\n".format(key, value) message_and_await_enter( "Configure the VPN application with the following settings:\n{}". format(msg))
def known_servers(self): L.warning( 'TODO: Implement DNS server discovery for Linux. Currently we just return the DNS ' 'server used for a DNSserver') dns_servers = [self.lookup()[0]] self._check_current_dns_server_is_known(dns_servers) return dns_servers
def _vpn_server_ip_from_route(self): L.debug("Attempting to get VPN server IP from routing table") routes = self._device['route'].get_v4_routes() if not routes or not self._routes_before_connect: return None potential_routes = [] for route in routes: if route in self._routes_before_connect: continue dest_ip = route.destination_ip() if dest_ip and route.gateway_ip() and not dest_ip.is_private: potential_routes.append(route) if len(potential_routes) == 1: ip = potential_routes[0].destination_ip() L.debug("Got VPN server IP {} from routing table".format(ip)) return ip elif len(potential_routes) >= 1: L.warning( "Tried to get VPN server from route table but found more than one " "candidate:\n{}".format(potential_routes)) else: L.warning( "Couldn't find any routes which look like they're for the VPN server" ) return None
def dns_server_ips(self): info = self._vpn_info() if info is not None and info.dns_server_ips: return info.dns_server_ips L.warning("Couldn't find VPN DNS server IPs. Will fallback to default method") return super().dns_server_ips()
def _webrtc_ips(self, url_base): url = self._url(url_base) L.describe("Open ICE detection webpage {}".format(url)) self._webdriver.get(url) # TODO: This isn't working properly. Also, try to automate if self._ask_perms: message_and_await_enter( "Please grant the browser permissions to use audio/video") # Not in love with this but not sure we have a lot of choice. There's no way to know whether # the javascript code has done searching for IPs TODO: Verify that L.describe("Wait for {} seconds for IPs to report".format( WebRTCICEHelper.TIME_TO_WAIT_FOR_IPS)) time.sleep(WebRTCICEHelper.TIME_TO_WAIT_FOR_IPS) if 'xv_leak_tools local ICE IP detection' not in self._webdriver.title: raise XVEx("Got unexpected webpage title: {}".format( self._webdriver.title)) L.describe( "Check ICE detection webpage doesn't show our public IP addresses") inner_html = self._webdriver.find_element_by_id( 'ice_ips').get_attribute('innerHTML') if not inner_html: raise XVEx('ice_ips div in webrtc page contained no inner_html') json_reponse = json.loads(inner_html) if not json_reponse['webrtc_supported']: L.warning( 'WebRTC not supported for this browser. Reporting no ips.') return [] return [ipaddress.ip_address(ip) for ip in json_reponse['ips']]
def vpn_processes(self): info = self._vpn_info() if info is not None and info.vpn_processes: return info.vpn_processes L.warning("Couldn't find VPN processes. Will fallback to default method") return super().vpn_processes()
def report_info(self): info = super().report_info() try: info += self._connector_helper.check_command(['systeminfo'])[0] except XVProcessException as ex: L.warning("Couldn't get OS info from systeminfo:\n{}".format(ex)) return info
def _start_server(self): L.warning("The server must be started manually :(") if self._device.connector().execute(['lsof', '-t', '-i', ':4444'])[0]: message_and_await_enter("Start the Selenium server") while self._device.connector().execute( ['lsof', '-t', '-i', ':4444'])[0]: L.debug("Waiting for Selenium") time.sleep(0.5) self._server_running = True
def close_app(self, app_path, root=False): # pylint: disable=unused-argument pname = windows_path_split(app_path)[1] pids = [str(pid) for pid in self.pgrep(pname)] if len(pids) > 1: L.warning( "Closing all pids {} associated to application {}".format( ', '.join(pids), pname)) for pid in pids: self.kill_process(pid)
def __del__(self): if self._no_disable: return L.debug("Releasing pfctl reference {}".format(self._token)) try: self._pfctl(['-X', self._token]) except XVProcessException as ex: # This can happen as some providers will aggressively tear down the firewall when they # disconnect (using pfctl -d) and thus remove our reference (token) to the firewall. L.warning("Failed to terminate pf firewall: {}".format(ex))
def vpn_server_ip(self): info = self._vpn_info() if info is not None and info.vpn_server_ip: return info.vpn_server_ip ip = self._vpn_server_ip_from_route() if ip: return ip L.warning("Couldn't find VPN server IP. Will fallback to default method") return super().vpn_server_ip()
def _get_public_ip_addresses(self, url, prog): stdout = self._url_getter(url)[0] matches = prog.match(stdout) if not matches: L.warning( "Couldn't determine public IP address using {}. Got response\n{}" .format(url, stdout)) return [] return [ipaddress.ip_address(matches.group(1))]
def _close_vpn_applications(self): for application in self._vpn_applications: for pid in self._device.pgrep(application): # Warn the user because ideally tests should start on a clean machine L.warning("Cleanup killing processes for application {}".format(application)) self._device.kill_process(pid) for application in self._unkillable_applications: if self._device.pgrep(application): L.warning("Application {} is open but I don't know how to kill it".format( application))
def _windump_index(guid, windump_lines): # Try to parse something # 1.\Device\NPF_{0F888A67-F8CF-4B5E-8812-5473E4A1D365} (ExpressVPN Tap Adapter) for line in windump_lines: match = _PROG_WINDUMP.match(line) if not match: continue if match.group(2) == guid: return match.group(1) L.warning( "Couldn't find windump interface index for adapter with GUID {}". format(guid)) return "0"
def _get_packets(self): dst = os.path.join(self._device.temp_directory(), self._capture_file) timeup = TimeUp(5) while not timeup: if os.path.exists(dst): break L.warning("Waiting for capture file to be written: {}".format(dst)) time.sleep(1) if not os.path.exists(dst): raise XVEx("Couldn't get capture file from capture device") return object_from_json_file(dst, 'attribute')['data']
def build(self, device, config): if not isinstance(device, DesktopDevice): raise ComponentNotSupported( "Can't create firewall tool for : {}".format(device.os_name())) if device.os_name() == 'macos': return MacOSFirewall(device, config) elif device.os_name() == 'windows': return WindowsFirewall(device, config) elif device.os_name() == 'linux': L.warning("Firewall not implemented for linux yet") raise ComponentNotSupported( "Firewall not implemented for linux yet")
def _kill_server(self): if self._server_running: L.debug("Killing Selenium server") selenium_process = self._device.connector().execute( ['lsof', '-t', '-i', ':4444']) if not selenium_process[0]: selenium_pid = selenium_process[1].strip() self._device.connector().execute(['kill', selenium_pid]) self._server_running = False else: L.warning( "Couldn't find the Selenium server process; try killing Java" )
def _sort_services(services, service_order): sorted_services = [None] * len(service_order) for service in services: try: sorted_services[service_order.index(service.id())] = service except ValueError as _: L.warning( "Couldn't determine service order. Couldn't find order for service {}" .format(service.name())) # It's possible that we had more service order items than services, in which case we strip # them. Let's hope it can't be the other way around (presumably we'll have already got an # exception in that case!) return [service for service in sorted_services if service is not None]
def discover_device(self, discovery_keys): if 'device_id' not in discovery_keys or discovery_keys['device_id'] != 'localhost': return None if len(discovery_keys) != 1: L.warning( "Only the 'device_id' discovery key is valid for discovering localhost. The others " "will be ignored: {}".format(discovery_keys)) if current_os() == 'windows': connector = WindowsLocalShellConnector() else: connector = LocalShellConnector() return create_device(current_os(), self._localhost_config, connector)
def punch_hole_in_firewall(ips): if current_os() == 'macos': pf = PFCtl() ip_list = '{ ' + ', '.join(ips) + ' }' pf.set_rules([ "pass in quick from {} no state".format(ip_list), "pass out quick to {} no state".format(ip_list) ]) elif current_os() == 'windows': L.warning( "Ignoring option to open up firewall for {} on Windows".format( ', '.join(ips))) else: raise XVEx('Editing the firewall is only supported for PF/macOS')
def dns_server_ips(self): if not self._tap_adapter_name: L.debug( "Don't know TAP adapter name for VPN application {}. Falling back to default " "method to get DNS server IPs".format(self._config["name"])) return super().dns_server_ips() adapter = self._device['network_tool'].adapter_by_name(self._tap_adapter_name) if adapter: return adapter.dns_servers() L.warning( "Couldn't find TAP adapter '{}' for VPN application '{}'. Falling back to default " "method to get DNS server IPs".format(self._tap_adapter_name, self._config["name"])) return super().dns_server_ips()
def _protocol_from_config_file(self): L.debug( "Trying to determine if openvpn is using UDP or TCP by looking at the config file") if not self._config_file(): L.debug("Couldn't get config file so can't establish which protocol is being used") return None with open(self._config_file()) as file_: for line in file_.readlines(): for protocol in OpenVPNDetector.UDP_TCP: if protocol in line: L.debug("Detected protocol {} from the config file".format(protocol)) return protocol L.warning("Couldn't determine openvpn protocol from config file {}".format( self._config_file())) return None
def report_info(self): info = super().report_info() commands = [ ['uname', '-a'], ['lsb_release', '-a'], ['lscpu'], ] for command in commands: try: info += self._connector_helper.check_command(command)[0] except XVProcessException as ex: L.warning( "Couldn't get system info using command {}:\n{}".format( command, ex)) return info
def test(self): L.info("Using disrupter: {}".format(self.disrupter)) L.describe('Find all known DNS servers before connecting to VPN') dns_servers_before_connect = self.localhost['dns_tool'].known_servers() L.info( "All known DNS servers are: {}".format(dns_servers_before_connect)) L.describe('Open and connect the VPN application') self.localhost['vpn_application'].open_and_connect() vpn_dns_servers = self.localhost['vpn_application'].dns_server_ips() L.info("VPN DNS servers are: {}".format(vpn_dns_servers)) self._check_network(time_limit=20) L.describe('Check DNS server used was a VPN DNS server') self.dns_helper.dns_server_is_vpn_server(dns_servers_before_connect, vpn_dns_servers) L.describe("Create disruption...") self.disrupter.create_disruption() L.describe( "Do some DNS lookups for the next {} seconds and check the DNS server used is a VPN " "DNS Server".format(self._check_period)) timeup = TimeUp(self._check_period) message_time = self._check_period while not timeup: if timeup.time_left() < message_time: L.info("Doing DNS lookup tests for another {} seconds".format( math.ceil(timeup.time_left()))) message_time = message_time - 5 try: # Max timeout for each DNS lookup is 2 seconds. timeout = min(timeup.time_left(), 2) self.dns_helper.dns_server_is_vpn_server( dns_servers_before_connect, vpn_dns_servers, timeout=timeout) except XVEx as ex: L.warning("DNS lookup failed, assuming no leak: {}".format(ex))
def _get_ips(self, msg, max_ips=0): msg = msg + " Press enter after each one. When finished, just press enter." ips = [] while 1: ip_string = message_and_await_string(msg) if not ip_string: break try: ips.append(ipaddress.ip_address(ip_string)) if max_ips != 0 and len(ips) == max_ips: break except (ipaddress.AddressValueError, ValueError) as ex: L.warning( "{}: invalid IP address. Please re-enter.".format(ex)) if not ips: L.warning('User did not provide any valid IP addresses') return ips
def _clean_parameters(self, parameters): supported_parameters = self.supported_parameters() for name, _ in list(parameters.items()): if name not in supported_parameters: L.warning( "Parameter {} not supported for test {}. It will be ignored" .format(name, self.__class__.__name__)) continue for name, supported_parameter in list(supported_parameters.items()): if name not in parameters: if supported_parameter.required: raise XVEx("Parameter {} is mandatory for test {}".format( name, self.__class__.__name__)) else: L.debug("Defaulting test parameter '{}' to {}".format( name, copy.deepcopy(supported_parameter.default))) parameters[name] = supported_parameter.default return parameters