blob: 278d9760ef1b1e468d43e0891430d8bff9ce3700 [file] [log] [blame]
# 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.')