def test(self):
        L.describe('Open and connect the VPN application')
        self.localhost['vpn_application'].open_and_connect()

        self.start_checking_dns()

        # This is a bit of a hack. If a VPN fails immediately, i.e. just allows custom DNS servers
        # then we'll know very quickly. Let's check for that before doing anything specific for the
        # test:
        time.sleep(2)

        self.assertIsNone(
            self._failure,
            "Managed to send DNS request our own DNS server. Got response {}.\n"
            "NOTE: This happened BEFORE we even did any custom testing. This means the provider"
            "doesn't protect against DNS requests being sent to custom servers"
            .format(self._failure))

        # Derived classes can override this with whatever they want.
        self.test_with_custom_dns()

        L.describe("Wait {} seconds before stopping DNS tests".format(
            self._check_period))
        timeup = TimeUp(self._check_period)
        while not timeup:
            L.info("Running DNS tests for another {} seconds".format(
                math.ceil(timeup.time_left())))

            self.assertIsNone(
                self._failure,
                "Managed to send DNS request our own DNS server. Got response {}"
                .format(self._failure))
            time.sleep(1)

        self.stop_checking_dns()
    def test(self):
        public_ips_before_connect = self.localhost['ip_tool'].all_public_ip_addresses()
        L.info("Public IP addresses before VPN connect are {}".format(public_ips_before_connect))

        L.describe('Open and connect the VPN application')
        self.localhost['vpn_application'].open_and_connect()

        self.localhost['ip_responder'].start()

        # Derived classes can override this with whatever they want.
        self.test_with_ip_responder()

        L.describe("Wait {} seconds before stopping ip responder".format(self._check_period))
        timeup = TimeUp(self._check_period)
        while not timeup:
            L.info("Running IP responder tests for another {} seconds".format(
                math.ceil(timeup.time_left())))

            ips_from_responder = self.query_ip_responder()
            ip_intersection = ips_from_responder.intersection(public_ips_before_connect)
            self.assertEmpty(
                ip_intersection,
                "The following public IPs were detected {}".format(ip_intersection))
            # Don't over query the responder. The background thread will still be spamming the
            # server so we won't miss anything.
            time.sleep(5)

        self.localhost['ip_responder'].stop()

        # Get rid of the VPN else it can affect our ability to talk to the server
        self.localhost['vpn_application'].disconnect()
        self.localhost['vpn_application'].close()

        # Some VPN apps take a while to close which means, if they implement a firewall, the
        # responder can't be reached. Allow some time for the network to come back in this situation
        timeup = TimeUp(20)
        while not timeup:
            ips_from_responder = self.query_ip_responder()
            if ips_from_responder:
                break
            if timeup:
                raise XVEx("Couldn't do final query of IP responder to get IPs. Got no response.")

        if not ips_from_responder:
            L.info("No IP addresses detected")
        else:
            L.info("IP addresses used were {}".format(ips_from_responder))

        ip_intersection = ips_from_responder.intersection(public_ips_before_connect)
        self.assertEmpty(
            ip_intersection,
            "The following public IPs were detected: {}".format(ip_intersection))
    def test(self):
        L.describe('Get the public IP addresses before VPN connect')
        public_ips_before_connect = self.localhost[
            'ip_tool'].all_public_ip_addresses()

        # Sanity check: We should always have at least one IP address!
        self.assertNotEmpty(public_ips_before_connect,
                            "Couldn't get public IP addresses")

        L.info("Public IP addresses before VPN connect are {}".format(
            public_ips_before_connect))

        L.describe('Open and connect the VPN application')
        self.localhost['vpn_application'].open_and_connect()

        self._check_network(20)

        L.describe('Get the public IP addresses after VPN connect')
        public_ips_after_connect = self.localhost[
            'ip_tool'].all_public_ip_addresses()

        timeout = TimeUp(20)
        while not timeout:
            public_ips_after_connect = self.localhost[
                'ip_tool'].all_public_ip_addresses()
            if public_ips_after_connect:
                break
            L.debug("Waiting {} s for a public IP address".format(
                int(timeout.time_left())))
            time.sleep(4)

        self.assertNotEmpty(
            public_ips_after_connect,
            "Couldn't get public IP addresses after connecting to the VPN server"
        )

        L.info("Public IP addresses after VPN connect are {}".format(
            public_ips_after_connect))

        L.describe(
            'Expect public IP addresses after VPN connect to have all changed')

        unchanged_ips = set(public_ips_before_connect).intersection(
            public_ips_after_connect)
        self.assertEmpty(
            unchanged_ips,
            "These IPs did not get changed after connection to VPN {}".format(
                unchanged_ips))
    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 _check_network(self, time_limit=5):
     L.info("Checking if there's a network connection")
     timeup = TimeUp(time_limit)
     while not timeup:
         lost = self.localhost['network_tool'].ping('8.8.8.8',
                                                    count=3,
                                                    timeout=2)
         if lost == 3:
             L.warning(
                 "No network detected. Will try for another {} seconds".
                 format(int(timeup.time_left())))
             time.sleep(0.5)
         elif lost == 0:
             L.info("Network okay")
             return
         else:
             L.warning("Network detected but there's some packet loss")
             return
     raise XVEx("No network connection detected.")
Beispiel #6
0
 def _check_network(self):
     L.info("Checking if there's a network connection")
     timeup = TimeUp(5)
     while not timeup:
         lost = self.localhost['network_tool'].ping('8.8.8.8',
                                                    count=3,
                                                    timeout=2)
         if lost == 3:
             L.warning(
                 "No network detected. Will try for {} seconds.".format(
                     timeup.time_left()))
         elif lost == 0:
             L.info("Network okay")
             return
         else:
             L.warning("Network detected but there's some packet loss")
             return
     raise XVEx(
         "No network connection detected. Tests must be started with an active network. "
         "Do you have any VPN applications open?")
    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 get_magnet_link(self, timeout=10):
        self._activate_torrent_leak_detection()
        # Add a loop to wait for the page to load the piece we want
        # TODO: This might be a good helper function. A wrapper to "wait for element"
        magnet_link = None
        timeup = TimeUp(timeout)
        while not timeup:
            try:
                magnet_link = (
                    self._webdriver.find_element_by_id('torrent_detection').
                    find_element_by_tag_name('div').find_element_by_tag_name(
                        'a').get_attribute('href'))
                break
            except NoSuchElementException as _:
                time.sleep(0.5)

        self.assertIsNotNone(magnet_link, "Torrent detection div didn't load")
        return magnet_link
 def _find_windows_pid(self):
     # It can take a moment for the PID to register with cygwin. So retry for up to 5 seconds
     # if it fails.
     timeup = TimeUp(5)
     while not timeup:
         pids = self._device.pgrep("xv_packet_capture.exe")
         L.debug(
             "Found the following PIDs matching xv_packet_capture.exe: {}".
             format(pids))
         for pid in pids:
             cmd_line = self._device.command_line_for_pid(pid)
             L.debug("Command line for PID {} was: {}".format(
                 pid, cmd_line))
             for arg in cmd_line:
                 if self._capture_file in arg:
                     return pid
     raise XVEx(
         "Couldn't find PID for xv_packet_capture.exe with capture file {}".
         format(self._capture_file))
Beispiel #10
0
 def _wait_for_vm_power(vmx_path):
     # Hacky trick to make sure VM has power
     timeup = TimeUp(60)
     while not timeup:
         try:
             if VMWareDeviceDiscoverer._vm_state(vmx_path) != 'running':
                 time.sleep(0.2)
                 continue
             VMWareDeviceDiscoverer._get_vm_ip(vmx_path)
             # If we manage to get the IP then we're powered on. This will be unlikely to happen
             break
         except XVProcessException as ex:
             if 'The virtual machine is not powered on' in ex.stdout:
                 time.sleep(0.2)
                 continue
             # Any other exception means we're powered on
             break
     if timeup:
         raise XVEx("VM never got power: {}".format(vmx_path))
     L.verbose('VM now has power')
Beispiel #11
0
    def query(self, timeout=10):
        L.debug("IPResponder getting IPs for {}".format(self._token.decode()))
        timeup = TimeUp(timeout)
        first_time_around = True
        while not timeup:
            try:
                if not first_time_around:
                    L.verbose("Trying to querying IP responder server again")
                else:
                    L.verbose("Querying IP responder server")
                    first_time_around = False

                self._query_sock.sendto(self._token + b'?',
                                        self._server_address)
                return self._receive()
            except (OSError, XVEx) as ex:
                # OSError happens if sendto fails (which probably means no network)
                # XVEx happens if there's no response to receive.
                L.warning("IP responder query failed: {}".format(ex))
                time.sleep(1)

        raise XVEx("Couldn't query IP Responder server")
    def _get_packets(self):
        # TODO: This highlights a weakness in the framework. I can't pull to my local device
        # because I don't know where to pull to! Just pulling to a temp file for now
        src = os.path.join(self._device.temp_directory(), self._capture_file)
        file_, dst = tempfile.mkstemp(prefix="xv_leak_test_",
                                      suffix="_{}".format(self._capture_file))
        os.close(file_)
        os.remove(dst)

        timeup = TimeUp(5)
        while not timeup:
            try:
                self._device.connector().pull(src, dst)
                break
            except XVEx:
                L.warning(
                    "Waiting for capture file to be written: {}".format(src))
                time.sleep(1)

        if not os.path.exists(dst):
            raise XVEx("Couldn't get capture file from capture device")

        packets = object_from_json_file(dst, 'attribute')
        return packets['data']
    def test(self):
        L.info("Using disrupter: {}".format(self.disrupter))

        L.describe('Get public IP before connect')
        ipv4_addresses_pre_connect = self.localhost[
            'ip_tool'].public_ipv4_addresses()
        ipv6_addresses_pre_connect = self.localhost[
            'ip_tool'].public_ipv6_addresses()
        ip_addresses_pre_connect = ipv4_addresses_pre_connect + ipv6_addresses_pre_connect

        L.info("Public IP addresses before connection were: {}".format(
            ip_addresses_pre_connect))

        L.describe('Open and connect the VPN application')
        self.localhost['vpn_application'].open_and_connect()

        L.describe("Create disruption...")
        self.disrupter.create_disruption()

        L.describe(
            "Repeatedly check public IP address for the next {} seconds".
            format(self._check_period))

        timeup = TimeUp(self._check_period)
        message_time = self._check_period
        while not timeup:
            try:
                if timeup.time_left() < message_time:
                    L.info("Running IP tests for another {} seconds".format(
                        math.ceil(timeup.time_left())))
                    message_time = message_time - 5

                # Max timeout for each IP lookup is 1 second. It needs to be quick in order to
                # find disruption windows. This will eventually be replaces by something like iperf
                # but which tracks what IPs packets came from.
                current_ipv4_addresses = self.localhost[
                    'ip_tool'].public_ipv4_addresses(timeout=1)
                if len(current_ipv4_addresses) != 0:
                    L.debug("Got IPv4 address {}".format(
                        current_ipv4_addresses[0]))

                    # Let's not check against the VPN server IP as it means prompting for user input
                    # for the VPN IP in many cases

                    self.assertIsNotIn(
                        current_ipv4_addresses[0], ip_addresses_pre_connect,
                        "IPv4 address {} used but this is a public IP of this device"
                        .format(current_ipv4_addresses[0]))

                # Do ipv4 and ipv6 separately to reduce lookup time. Only do IPv6 if the device has
                # an ipv6 address else it's a waste of time.
                if len(ipv6_addresses_pre_connect) == 0:
                    continue

                current_ipv6_addresses = self.localhost[
                    'ip_tool'].public_ipv6_addresses(timeout=1)
                if len(current_ipv6_addresses) != 0:
                    L.debug("Got IPv6 address {}".format(
                        current_ipv6_addresses[0]))
                    # Let's not check against the VPN server IP as it means prompting for user input
                    # for the VPN IP in many cases
                    self.assertIsNotIn(
                        current_ipv6_addresses[0], ipv6_addresses_pre_connect,
                        "IPv6 address {} used but this is a public IP of this device"
                        .format(current_ipv6_addresses[0]))

            except XVEx as ex:
                L.warning("IP lookup failed, assuming timeout: {}".format(ex))
Beispiel #14
0
    def test(self):
        L.describe('Get the public IP addresses before VPN connect')
        public_ips_before_connect = self.localhost[
            'ip_tool'].all_public_ip_addresses()

        # Sanity check: We should always have at least one IP address!
        self.assertNotEmpty(public_ips_before_connect,
                            "Couldn't get public IP addresses")
        L.info("Public IP addresses before VPN connect are {}".format(
            public_ips_before_connect))

        self.localhost['torrent_client'].set_client(self.torrent_client)

        # We're assuming the client is already closed. I think that's ok.
        if self.torrent_client_preopened:
            self.localhost['torrent_client'].open()

        L.describe('Open and connect the VPN application')
        self.localhost['vpn_application'].open_and_connect()

        self._webdriver = self.localhost['webdriver'].driver(
            self.parameters['browser'])
        ip_leak_helper = IPLeakNetHelper(self._webdriver)

        L.describe('Open ipleak.net page')
        ip_leak_helper.load_page()

        L.describe('Get the torrent magnet link from ipleak.net')
        magnet_link = ip_leak_helper.get_magnet_link()
        L.info("Got magnet link {}".format(magnet_link))

        L.describe('Add the torrent magnet to the torrent client')
        self.localhost['torrent_client'].add_torrent(magnet_link)

        L.describe("Check the reported torrent IPs aren't public")

        ipv6_subnets = [
            ipaddress.ip_interface((ip, 64)).network
            for ip in public_ips_before_connect if ip.version == 6
        ]

        timeup = TimeUp(20)
        reported_ips = set()
        while not timeup:
            L.info('Checking webpage contents for leaked torrent IPs')
            reported_ips.update(ip_leak_helper.get_reported_torrent_ips())

            for ip in reported_ips:
                self.assertIsNotIn(
                    ip, public_ips_before_connect,
                    "Torrent tracker found a public IP: {}".format(ip))

                if ip.version == 6:
                    for subnet in ipv6_subnets:
                        self.assertFalse(
                            ip in subnet,
                            "IPv6 address {} is in {}".format(ip, subnet))

            if reported_ips:
                L.info(
                    "Found IPs {} but none were public".format(reported_ips))
            else:
                L.info('Found no IPs')

            # Pause a little each run to give IPs a chance to report.
            time.sleep(1)

        if not reported_ips:
            raise XVEx(
                "The torrent client didn't report any IP addresses at all")
Beispiel #15
0
    def discover_device(self, discovery_keys):
        L.debug('Looking for device with keys {}'.format(discovery_keys))

        device = self._inventory_item_for_discovery_keys(discovery_keys)
        if device is None:
            return None

        device['output_directory'] = os.path.join(
            device['output_root'], self._context['run_directory'])

        # TODO: Make this more generic. It would be helpful to know what keys are required.
        # TODO: Consider making snapshots smarter. e.g. revert to LATEST snapshot with a specific
        # prefix.
        if 'vmx_path' not in device or 'vm_snapshot' not in device:
            raise XVEx("VMWare device inventory items need to specify: '{}' and '{}'".format(
                'vmx_path', 'vm_snapshot'))

        vmx_path = device['vmx_path']
        vm_snapshot = device['vm_snapshot']
        # Check that either the VM is stopped or we're already using it. I'm thinking ahead here to
        # potentially having tests running in parallel.
        state = VMWareDeviceDiscoverer._vm_state(vmx_path)
        if state == 'running' and vmx_path not in self._vms_in_use:
            raise XVEx(
                "The VM {} is currently in use by someone else (current state is '{}'')".format(
                    vmx_path, state))

        # Always add. It might already be there but we don't care
        self._vms_in_use.add(vmx_path)

        if state == 'running':
            VMWareDeviceDiscoverer._unpause_vm(vmx_path)
        else:
            L.debug("Reverting VM to snapshot: {}".format(vm_snapshot))
            VMWareDeviceDiscoverer._revert_vm_to_snapshot(vmx_path, vm_snapshot)
            VMWareDeviceDiscoverer._chown_vm_files(vmx_path)
            L.debug("Starting VM")
            VMWareDeviceDiscoverer._start_vm(vmx_path)

        VMWareDeviceDiscoverer._wait_for_vm_power(vmx_path)

        # It can take quite a while for IPs to become available.
        # TODO: Try to improve this. It seems like a VMWare issue. I can ping the machine well
        # before vmware reports its IPs
        timeup = TimeUp(60)
        ip = None
        while not timeup:
            try:
                L.debug('Waiting for VM IP to become available')
                ip = VMWareDeviceDiscoverer._get_vm_ip(vmx_path)
                break
            except XVProcessException as ex:
                if 'The VMware Tools are not running in the virtual machine' not in ex.stdout and \
                   'Unable to get the IP address' not in ex.stdout:
                    raise
                L.warning("Failed getting IP for VM: {}".format(ex))
                time.sleep(5)

        if ip is None:
            raise XVEx("Couldn't get IP for VM {}".format(vmx_path))

        connector = SimpleSSHConnector(
            ips=[ip],
            username=device['username'],
            ssh_key=device['ssh_key'],
            ssh_password=device['ssh_password'])

        return create_device(device['os_name'], device, connector)