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