| # Copyright 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import dpkt |
| import logging |
| import time |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib.cros.tendo import peerd_config |
| from autotest_lib.client.cros import chrooted_avahi |
| from autotest_lib.client.cros.netprotos import interface_host |
| from autotest_lib.client.cros.netprotos import zeroconf |
| from autotest_lib.client.cros.tendo import peerd_dbus_helper |
| |
| |
| class peerd_HandlesNameConflicts(test.test): |
| """Test that peerd can handle mDNS name conflicts.""" |
| version = 1 |
| |
| CACHE_REFRESH_PERIOD_SECONDS = 5 |
| FAKE_HOST_HOSTNAME = 'a-test-host' |
| TEST_TIMEOUT_SECONDS = 30 |
| TEST_SERVICE_ID = 'test-service-0' |
| TEST_SERVICE_INFO = {'some_data': 'a value', |
| 'other_data': 'another value'} |
| INITIAL_MDNS_PREFIX = 'initial_mdns_prefix' |
| SERBUS_SERVICE_ID = 'serbus' |
| SERBUS_SERVICE_NAME = '_serbus' |
| SERBUS_PROTOCOL = '_tcp' |
| SERBUS_PORT = 0 |
| |
| |
| def reset_peerd(self): |
| """Start up a peerd instance. |
| |
| This instance will have really verbose logging and will attempt |
| to use a known MDNS prefix to start out. |
| |
| """ |
| self._peerd = peerd_dbus_helper.make_helper( |
| peerd_config.PeerdConfig(verbosity_level=3, |
| mdns_prefix=self.INITIAL_MDNS_PREFIX)) |
| |
| |
| def initialize(self): |
| # Make sure these are initiallized to None in case we throw |
| # during self.initialize(). |
| self._chrooted_avahi = None |
| self._peerd = None |
| self._host = None |
| self._zc_listener = None |
| self._chrooted_avahi = chrooted_avahi.ChrootedAvahi() |
| self._chrooted_avahi.start() |
| self.reset_peerd() |
| # Listen on our half of the interface pair for mDNS advertisements. |
| self._host = interface_host.InterfaceHost( |
| self._chrooted_avahi.unchrooted_interface_name) |
| self._zc_listener = zeroconf.ZeroconfDaemon(self._host, |
| self.FAKE_HOST_HOSTNAME) |
| # The queries for hostname/dns_domain are IPCs and therefor relatively |
| # expensive. Do them just once. |
| hostname = self._chrooted_avahi.hostname |
| dns_domain = self._chrooted_avahi.dns_domain |
| if not hostname or not dns_domain: |
| raise error.TestFail('Failed to get hostname/domain from avahi.') |
| self._dns_domain = dns_domain |
| self._hostname = '%s.%s' % (hostname, dns_domain) |
| self._last_cache_refresh_seconds = 0 |
| |
| |
| def cleanup(self): |
| for obj in (self._chrooted_avahi, |
| self._host, |
| self._peerd): |
| if obj is not None: |
| obj.close() |
| |
| |
| def _get_PTR_prefix(self, service_id): |
| ptr_name = '_%s._tcp.%s' % (service_id, self._dns_domain) |
| found_records = self._zc_listener.cached_results( |
| ptr_name, dpkt.dns.DNS_PTR) |
| if len(found_records) == 0: |
| logging.debug('Found no PTR records for %s', ptr_name) |
| return None |
| if len(found_records) > 1: |
| logging.debug('Found multiple PTR records for %s', ptr_name) |
| return None |
| unique_name = found_records[0].data |
| expected_suffix = '.' + ptr_name |
| if not unique_name.endswith(expected_suffix): |
| logging.error('PTR record for "%s" points to odd name: "%s"', |
| ptr_name, unique_name) |
| return None |
| ptr_prefix = unique_name[0:-len(expected_suffix)] |
| logging.debug('PTR record for "%s" points to service with name "%s"', |
| ptr_name, ptr_prefix) |
| return ptr_prefix |
| |
| |
| def _found_expected_PTR_records(self, forbidden_record_prefix): |
| for service_id in (self.SERBUS_SERVICE_ID, self.TEST_SERVICE_ID): |
| prefix = self._get_PTR_prefix(service_id) |
| if prefix is None: |
| break |
| if prefix == forbidden_record_prefix: |
| logging.debug('Ignoring service with conflicting prefix') |
| break |
| else: |
| return True |
| delta_seconds = time.time() - self._last_cache_refresh_seconds |
| if delta_seconds > self.CACHE_REFRESH_PERIOD_SECONDS: |
| self._zc_listener.clear_cache() |
| self._last_cache_refresh_seconds = time.time() |
| return False |
| |
| |
| def run_once(self): |
| # Tell peerd about this exciting new service we have. |
| self._peerd.expose_service(self.TEST_SERVICE_ID, self.TEST_SERVICE_INFO) |
| # Wait for advertisements of that service to appear from avahi. |
| # They should be prefixed with our special name, since there are no |
| # conflicts. |
| logging.info('Waiting to receive mDNS advertisements of ' |
| 'peerd services.') |
| success, duration = self._host.run_until( |
| lambda: self._found_expected_PTR_records(None), |
| self.TEST_TIMEOUT_SECONDS) |
| if not success: |
| raise error.TestFail('Did not receive mDNS advertisements in time.') |
| actual_prefix = self._get_PTR_prefix(self.SERBUS_SERVICE_ID) |
| if actual_prefix != self.INITIAL_MDNS_PREFIX: |
| raise error.TestFail('Expected initial mDNS advertisements to have ' |
| 'a prefix=%s' % self.INITIAL_MDNS_PREFIX) |
| logging.info('Found initial records advertised by peerd.') |
| # Now register services with the same name, and restart peerd. |
| # 1) The old instance of peerd should withdraw its services from Avahi |
| # 2) The new instance of peerd should re-register services with Avahi |
| # 3) Avahi should notice that the name.service_type.domain tuple |
| # conflicts with existing records, and signal this to peerd. |
| # 4) Peerd should pick a new prefix and try again. |
| self.reset_peerd() |
| self._zc_listener.register_service( |
| self.INITIAL_MDNS_PREFIX, |
| self.SERBUS_SERVICE_NAME, |
| self.SERBUS_PROTOCOL, |
| self.SERBUS_PORT, |
| ['invalid=record']) |
| self._peerd.expose_service(self.TEST_SERVICE_ID, self.TEST_SERVICE_INFO) |
| run_until_predicate = lambda: self._found_expected_PTR_records( |
| self.INITIAL_MDNS_PREFIX) |
| success, duration = self._host.run_until(run_until_predicate, |
| self.TEST_TIMEOUT_SECONDS) |
| if not success: |
| raise error.TestFail('Timed out waiting for peerd to change the ' |
| 'record prefix.') |