Home | History | Annotate | Download | only in peerd_HandlesNameConflicts
      1 # Copyright 2014 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import dpkt
      6 import logging
      7 import time
      8 
      9 from autotest_lib.client.bin import test
     10 from autotest_lib.client.common_lib import error
     11 from autotest_lib.client.common_lib.cros.tendo import peerd_config
     12 from autotest_lib.client.cros import chrooted_avahi
     13 from autotest_lib.client.cros.netprotos import interface_host
     14 from autotest_lib.client.cros.netprotos import zeroconf
     15 from autotest_lib.client.cros.tendo import peerd_dbus_helper
     16 
     17 
     18 class peerd_HandlesNameConflicts(test.test):
     19     """Test that peerd can handle mDNS name conflicts."""
     20     version = 1
     21 
     22     CACHE_REFRESH_PERIOD_SECONDS = 5
     23     FAKE_HOST_HOSTNAME = 'a-test-host'
     24     TEST_TIMEOUT_SECONDS = 30
     25     TEST_SERVICE_ID = 'test-service-0'
     26     TEST_SERVICE_INFO = {'some_data': 'a value',
     27                           'other_data': 'another value'}
     28     INITIAL_MDNS_PREFIX = 'initial_mdns_prefix'
     29     SERBUS_SERVICE_ID = 'serbus'
     30     SERBUS_SERVICE_NAME = '_serbus'
     31     SERBUS_PROTOCOL = '_tcp'
     32     SERBUS_PORT = 0
     33 
     34 
     35     def reset_peerd(self):
     36         """Start up a peerd instance.
     37 
     38         This instance will have really verbose logging and will attempt
     39         to use a known MDNS prefix to start out.
     40 
     41         """
     42         self._peerd = peerd_dbus_helper.make_helper(
     43                 peerd_config.PeerdConfig(verbosity_level=3,
     44                                          mdns_prefix=self.INITIAL_MDNS_PREFIX))
     45 
     46 
     47     def initialize(self):
     48         # Make sure these are initiallized to None in case we throw
     49         # during self.initialize().
     50         self._chrooted_avahi = None
     51         self._peerd = None
     52         self._host = None
     53         self._zc_listener = None
     54         self._chrooted_avahi = chrooted_avahi.ChrootedAvahi()
     55         self._chrooted_avahi.start()
     56         self.reset_peerd()
     57         # Listen on our half of the interface pair for mDNS advertisements.
     58         self._host = interface_host.InterfaceHost(
     59                 self._chrooted_avahi.unchrooted_interface_name)
     60         self._zc_listener = zeroconf.ZeroconfDaemon(self._host,
     61                                                     self.FAKE_HOST_HOSTNAME)
     62         # The queries for hostname/dns_domain are IPCs and therefor relatively
     63         # expensive.  Do them just once.
     64         hostname = self._chrooted_avahi.hostname
     65         dns_domain = self._chrooted_avahi.dns_domain
     66         if not hostname or not dns_domain:
     67             raise error.TestFail('Failed to get hostname/domain from avahi.')
     68         self._dns_domain = dns_domain
     69         self._hostname = '%s.%s' % (hostname, dns_domain)
     70         self._last_cache_refresh_seconds = 0
     71 
     72 
     73     def cleanup(self):
     74         for obj in (self._chrooted_avahi,
     75                     self._host,
     76                     self._peerd):
     77             if obj is not None:
     78                 obj.close()
     79 
     80 
     81     def _get_PTR_prefix(self, service_id):
     82         ptr_name = '_%s._tcp.%s' % (service_id, self._dns_domain)
     83         found_records = self._zc_listener.cached_results(
     84                 ptr_name, dpkt.dns.DNS_PTR)
     85         if len(found_records) == 0:
     86             logging.debug('Found no PTR records for %s', ptr_name)
     87             return None
     88         if len(found_records) > 1:
     89             logging.debug('Found multiple PTR records for %s', ptr_name)
     90             return None
     91         unique_name = found_records[0].data
     92         expected_suffix = '.' + ptr_name
     93         if not unique_name.endswith(expected_suffix):
     94             logging.error('PTR record for "%s" points to odd name: "%s"',
     95                           ptr_name, unique_name)
     96             return None
     97         ptr_prefix = unique_name[0:-len(expected_suffix)]
     98         logging.debug('PTR record for "%s" points to service with name "%s"',
     99                       ptr_name, ptr_prefix)
    100         return ptr_prefix
    101 
    102 
    103     def _found_expected_PTR_records(self, forbidden_record_prefix):
    104         for service_id in (self.SERBUS_SERVICE_ID, self.TEST_SERVICE_ID):
    105             prefix = self._get_PTR_prefix(service_id)
    106             if prefix is None:
    107                 break
    108             if prefix == forbidden_record_prefix:
    109                 logging.debug('Ignoring service with conflicting prefix')
    110                 break
    111         else:
    112             return True
    113         delta_seconds = time.time() - self._last_cache_refresh_seconds
    114         if delta_seconds > self.CACHE_REFRESH_PERIOD_SECONDS:
    115             self._zc_listener.clear_cache()
    116             self._last_cache_refresh_seconds = time.time()
    117         return False
    118 
    119 
    120     def run_once(self):
    121         # Tell peerd about this exciting new service we have.
    122         self._peerd.expose_service(self.TEST_SERVICE_ID, self.TEST_SERVICE_INFO)
    123         # Wait for advertisements of that service to appear from avahi.
    124         # They should be prefixed with our special name, since there are no
    125         # conflicts.
    126         logging.info('Waiting to receive mDNS advertisements of '
    127                      'peerd services.')
    128         success, duration = self._host.run_until(
    129                 lambda: self._found_expected_PTR_records(None),
    130                 self.TEST_TIMEOUT_SECONDS)
    131         if not success:
    132             raise error.TestFail('Did not receive mDNS advertisements in time.')
    133         actual_prefix = self._get_PTR_prefix(self.SERBUS_SERVICE_ID)
    134         if actual_prefix != self.INITIAL_MDNS_PREFIX:
    135             raise error.TestFail('Expected initial mDNS advertisements to have '
    136                                  'a prefix=%s' % self.INITIAL_MDNS_PREFIX)
    137         logging.info('Found initial records advertised by peerd.')
    138         # Now register services with the same name, and restart peerd.
    139         # 1) The old instance of peerd should withdraw its services from Avahi
    140         # 2) The new instance of peerd should re-register services with Avahi
    141         # 3) Avahi should notice that the name.service_type.domain tuple
    142         #    conflicts with existing records, and signal this to peerd.
    143         # 4) Peerd should pick a new prefix and try again.
    144         self.reset_peerd()
    145         self._zc_listener.register_service(
    146                 self.INITIAL_MDNS_PREFIX,
    147                 self.SERBUS_SERVICE_NAME,
    148                 self.SERBUS_PROTOCOL,
    149                 self.SERBUS_PORT,
    150                 ['invalid=record'])
    151         self._peerd.expose_service(self.TEST_SERVICE_ID, self.TEST_SERVICE_INFO)
    152         run_until_predicate = lambda: self._found_expected_PTR_records(
    153                 self.INITIAL_MDNS_PREFIX)
    154         success, duration = self._host.run_until(run_until_predicate,
    155                                                  self.TEST_TIMEOUT_SECONDS)
    156         if not success:
    157             raise error.TestFail('Timed out waiting for peerd to change the '
    158                                  'record prefix.')
    159