177 lines
6.8 KiB
Python
177 lines
6.8 KiB
Python
# 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 logging
|
|
import time
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib.cros import avahi_utils
|
|
from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
|
|
from autotest_lib.client.common_lib.cros.network import netblock
|
|
from autotest_lib.client.cros import network_chroot
|
|
from autotest_lib.client.cros import service_stopper
|
|
from autotest_lib.client.cros import tcpdump
|
|
|
|
|
|
class ChrootedAvahi(object):
|
|
"""Helper object to start up avahi in a network chroot.
|
|
|
|
Creates a virtual ethernet pair to enable communication with avahi.
|
|
Does the necessary work to make avahi appear on DBus and allow it
|
|
to claim its canonical service name.
|
|
|
|
"""
|
|
|
|
SERVICES_TO_STOP = ['avahi']
|
|
# This side has to be called something special to avoid shill touching it.
|
|
MONITOR_IF_IP = netblock.from_addr('10.9.8.1/24')
|
|
# We'll drop the Avahi side into our network namespace.
|
|
AVAHI_IF_IP = netblock.from_addr('10.9.8.2/24')
|
|
AVAHI_IF_NAME = 'pseudoethernet0'
|
|
TCPDUMP_FILE_PATH = '/var/log/peerd_dump.pcap'
|
|
AVAHI_CONFIG_FILE = 'etc/avahi/avahi-daemon.conf'
|
|
AVAHI_CONFIGS = {
|
|
AVAHI_CONFIG_FILE :
|
|
'[server]\n'
|
|
'host-name-from-machine-id=yes\n'
|
|
'browse-domains=\n'
|
|
'use-ipv4=yes\n'
|
|
'use-ipv6=no\n'
|
|
'ratelimit-interval-usec=1000000\n'
|
|
'ratelimit-burst=1000\n'
|
|
'[wide-area]\n'
|
|
'enable-wide-area=no\n'
|
|
'[publish]\n'
|
|
'publish-hinfo=no\n'
|
|
'publish-workstation=no\n'
|
|
'publish-aaaa-on-ipv4=no\n'
|
|
'publish-a-on-ipv6=no\n'
|
|
'[rlimits]\n'
|
|
'rlimit-core=0\n'
|
|
'rlimit-data=4194304\n'
|
|
'rlimit-fsize=1024\n'
|
|
'rlimit-nofile=768\n'
|
|
'rlimit-stack=4194304\n'
|
|
'rlimit-nproc=10\n',
|
|
|
|
'etc/passwd' :
|
|
'root:x:0:0:root:/root:/bin/bash\n'
|
|
'avahi:*:238:238::/dev/null:/bin/false\n',
|
|
|
|
'etc/group' :
|
|
'avahi:x:238:\n',
|
|
}
|
|
AVAHI_LOG_FILE = '/var/log/avahi.log'
|
|
AVAHI_PID_FILE = 'run/avahi-daemon/pid'
|
|
AVAHI_UP_TIMEOUT_SECONDS = 10
|
|
|
|
|
|
def __init__(self, unchrooted_interface_name='pseudoethernet1'):
|
|
"""Construct a chrooted instance of Avahi.
|
|
|
|
@param unchrooted_interface_name: string name of interface to leave
|
|
outside the network chroot. This interface will be connected
|
|
to the end Avahi is listening on.
|
|
|
|
"""
|
|
self._unchrooted_interface_name = unchrooted_interface_name
|
|
self._services = None
|
|
self._vif = None
|
|
self._tcpdump = None
|
|
self._chroot = None
|
|
|
|
|
|
@property
|
|
def unchrooted_interface_name(self):
|
|
"""Get the name of the end of the VirtualEthernetPair not in the chroot.
|
|
|
|
The network chroot works by isolating avahi inside with one end of a
|
|
virtual ethernet pair. The outside world needs to interact with the
|
|
other end in order to talk to avahi.
|
|
|
|
@return name of interface not inside the chroot.
|
|
|
|
"""
|
|
return self._unchrooted_interface_name
|
|
|
|
|
|
@property
|
|
def avahi_interface_addr(self):
|
|
"""@return string ip address of interface belonging to avahi."""
|
|
return self.AVAHI_IF_IP.addr
|
|
|
|
|
|
@property
|
|
def hostname(self):
|
|
"""@return string hostname claimed by avahi on |self.dns_domain|."""
|
|
return avahi_utils.avahi_get_hostname()
|
|
|
|
|
|
@property
|
|
def dns_domain(self):
|
|
"""@return string DNS domain in use by avahi (e.g. 'local')."""
|
|
return avahi_utils.avahi_get_domain_name()
|
|
|
|
|
|
def start(self):
|
|
"""Start up the chrooted Avahi instance."""
|
|
# Prevent weird interactions between services which talk to Avahi.
|
|
# TODO(wiley) Does Chrome need to die here as well?
|
|
self._services = service_stopper.ServiceStopper(
|
|
self.SERVICES_TO_STOP)
|
|
self._services.stop_services()
|
|
# We don't want Avahi talking to the real world, so give it a nice
|
|
# fake interface to use. We'll watch the other half of the pair.
|
|
self._vif = virtual_ethernet_pair.VirtualEthernetPair(
|
|
interface_name=self.unchrooted_interface_name,
|
|
peer_interface_name=self.AVAHI_IF_NAME,
|
|
interface_ip=self.MONITOR_IF_IP.netblock,
|
|
peer_interface_ip=self.AVAHI_IF_IP.netblock,
|
|
# Moving one end into the chroot causes errors.
|
|
ignore_shutdown_errors=True)
|
|
self._vif.setup()
|
|
if not self._vif.is_healthy:
|
|
raise error.TestError('Failed to setup virtual ethernet pair.')
|
|
# By default, take a packet capture of everything Avahi sends out.
|
|
self._tcpdump = tcpdump.Tcpdump(self.unchrooted_interface_name,
|
|
self.TCPDUMP_FILE_PATH)
|
|
# We're going to run Avahi in a network namespace to avoid interactions
|
|
# with the outside world.
|
|
self._chroot = network_chroot.NetworkChroot(self.AVAHI_IF_NAME,
|
|
self.AVAHI_IF_IP.addr,
|
|
self.AVAHI_IF_IP.prefix_len)
|
|
self._chroot.add_config_templates(self.AVAHI_CONFIGS)
|
|
self._chroot.add_root_directories(['etc/avahi', 'etc/avahi/services'])
|
|
self._chroot.add_copied_config_files(['etc/resolv.conf',
|
|
'etc/avahi/hosts'])
|
|
self._chroot.add_startup_command(
|
|
'/usr/sbin/avahi-daemon --file=/%s >%s 2>&1' %
|
|
(self.AVAHI_CONFIG_FILE, self.AVAHI_LOG_FILE))
|
|
self._chroot.bridge_dbus_namespaces()
|
|
self._chroot.startup()
|
|
# Wait for Avahi to come up, claim its DBus name, settle on a hostname.
|
|
start_time = time.time()
|
|
while time.time() - start_time < self.AVAHI_UP_TIMEOUT_SECONDS:
|
|
if avahi_utils.avahi_ping():
|
|
break
|
|
time.sleep(0.2)
|
|
else:
|
|
raise error.TestFail('Avahi did not come up in time.')
|
|
|
|
|
|
def close(self):
|
|
"""Clean up the chrooted Avahi instance."""
|
|
if self._chroot:
|
|
# TODO(wiley) This is sloppy. Add a helper to move the logs over.
|
|
for line in self._chroot.get_log_contents().splitlines():
|
|
logging.debug(line)
|
|
self._chroot.kill_pid_file(self.AVAHI_PID_FILE)
|
|
self._chroot.shutdown()
|
|
if self._tcpdump:
|
|
self._tcpdump.stop()
|
|
if self._vif:
|
|
self._vif.teardown()
|
|
if self._services:
|
|
self._services.restore_services()
|