1*9c5db199SXin Li# Copyright 2014 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport logging 6*9c5db199SXin Liimport time 7*9c5db199SXin Li 8*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 9*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import avahi_utils 10*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import virtual_ethernet_pair 11*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.network import netblock 12*9c5db199SXin Lifrom autotest_lib.client.cros import network_chroot 13*9c5db199SXin Lifrom autotest_lib.client.cros import service_stopper 14*9c5db199SXin Lifrom autotest_lib.client.cros import tcpdump 15*9c5db199SXin Li 16*9c5db199SXin Li 17*9c5db199SXin Liclass ChrootedAvahi(object): 18*9c5db199SXin Li """Helper object to start up avahi in a network chroot. 19*9c5db199SXin Li 20*9c5db199SXin Li Creates a virtual ethernet pair to enable communication with avahi. 21*9c5db199SXin Li Does the necessary work to make avahi appear on DBus and allow it 22*9c5db199SXin Li to claim its canonical service name. 23*9c5db199SXin Li 24*9c5db199SXin Li """ 25*9c5db199SXin Li 26*9c5db199SXin Li SERVICES_TO_STOP = ['avahi'] 27*9c5db199SXin Li # This side has to be called something special to avoid shill touching it. 28*9c5db199SXin Li MONITOR_IF_IP = netblock.from_addr('10.9.8.1/24') 29*9c5db199SXin Li # We'll drop the Avahi side into our network namespace. 30*9c5db199SXin Li AVAHI_IF_IP = netblock.from_addr('10.9.8.2/24') 31*9c5db199SXin Li AVAHI_IF_NAME = 'pseudoethernet0' 32*9c5db199SXin Li TCPDUMP_FILE_PATH = '/var/log/peerd_dump.pcap' 33*9c5db199SXin Li AVAHI_CONFIG_FILE = 'etc/avahi/avahi-daemon.conf' 34*9c5db199SXin Li AVAHI_CONFIGS = { 35*9c5db199SXin Li AVAHI_CONFIG_FILE : 36*9c5db199SXin Li '[server]\n' 37*9c5db199SXin Li 'host-name-from-machine-id=yes\n' 38*9c5db199SXin Li 'browse-domains=\n' 39*9c5db199SXin Li 'use-ipv4=yes\n' 40*9c5db199SXin Li 'use-ipv6=no\n' 41*9c5db199SXin Li 'ratelimit-interval-usec=1000000\n' 42*9c5db199SXin Li 'ratelimit-burst=1000\n' 43*9c5db199SXin Li '[wide-area]\n' 44*9c5db199SXin Li 'enable-wide-area=no\n' 45*9c5db199SXin Li '[publish]\n' 46*9c5db199SXin Li 'publish-hinfo=no\n' 47*9c5db199SXin Li 'publish-workstation=no\n' 48*9c5db199SXin Li 'publish-aaaa-on-ipv4=no\n' 49*9c5db199SXin Li 'publish-a-on-ipv6=no\n' 50*9c5db199SXin Li '[rlimits]\n' 51*9c5db199SXin Li 'rlimit-core=0\n' 52*9c5db199SXin Li 'rlimit-data=4194304\n' 53*9c5db199SXin Li 'rlimit-fsize=1024\n' 54*9c5db199SXin Li 'rlimit-nofile=768\n' 55*9c5db199SXin Li 'rlimit-stack=4194304\n' 56*9c5db199SXin Li 'rlimit-nproc=10\n', 57*9c5db199SXin Li 58*9c5db199SXin Li 'etc/passwd' : 59*9c5db199SXin Li 'root:x:0:0:root:/root:/bin/bash\n' 60*9c5db199SXin Li 'avahi:*:238:238::/dev/null:/bin/false\n', 61*9c5db199SXin Li 62*9c5db199SXin Li 'etc/group' : 63*9c5db199SXin Li 'avahi:x:238:\n', 64*9c5db199SXin Li } 65*9c5db199SXin Li AVAHI_LOG_FILE = '/var/log/avahi.log' 66*9c5db199SXin Li AVAHI_PID_FILE = 'run/avahi-daemon/pid' 67*9c5db199SXin Li AVAHI_UP_TIMEOUT_SECONDS = 10 68*9c5db199SXin Li 69*9c5db199SXin Li 70*9c5db199SXin Li def __init__(self, unchrooted_interface_name='pseudoethernet1'): 71*9c5db199SXin Li """Construct a chrooted instance of Avahi. 72*9c5db199SXin Li 73*9c5db199SXin Li @param unchrooted_interface_name: string name of interface to leave 74*9c5db199SXin Li outside the network chroot. This interface will be connected 75*9c5db199SXin Li to the end Avahi is listening on. 76*9c5db199SXin Li 77*9c5db199SXin Li """ 78*9c5db199SXin Li self._unchrooted_interface_name = unchrooted_interface_name 79*9c5db199SXin Li self._services = None 80*9c5db199SXin Li self._vif = None 81*9c5db199SXin Li self._tcpdump = None 82*9c5db199SXin Li self._chroot = None 83*9c5db199SXin Li 84*9c5db199SXin Li 85*9c5db199SXin Li @property 86*9c5db199SXin Li def unchrooted_interface_name(self): 87*9c5db199SXin Li """Get the name of the end of the VirtualEthernetPair not in the chroot. 88*9c5db199SXin Li 89*9c5db199SXin Li The network chroot works by isolating avahi inside with one end of a 90*9c5db199SXin Li virtual ethernet pair. The outside world needs to interact with the 91*9c5db199SXin Li other end in order to talk to avahi. 92*9c5db199SXin Li 93*9c5db199SXin Li @return name of interface not inside the chroot. 94*9c5db199SXin Li 95*9c5db199SXin Li """ 96*9c5db199SXin Li return self._unchrooted_interface_name 97*9c5db199SXin Li 98*9c5db199SXin Li 99*9c5db199SXin Li @property 100*9c5db199SXin Li def avahi_interface_addr(self): 101*9c5db199SXin Li """@return string ip address of interface belonging to avahi.""" 102*9c5db199SXin Li return self.AVAHI_IF_IP.addr 103*9c5db199SXin Li 104*9c5db199SXin Li 105*9c5db199SXin Li @property 106*9c5db199SXin Li def hostname(self): 107*9c5db199SXin Li """@return string hostname claimed by avahi on |self.dns_domain|.""" 108*9c5db199SXin Li return avahi_utils.avahi_get_hostname() 109*9c5db199SXin Li 110*9c5db199SXin Li 111*9c5db199SXin Li @property 112*9c5db199SXin Li def dns_domain(self): 113*9c5db199SXin Li """@return string DNS domain in use by avahi (e.g. 'local').""" 114*9c5db199SXin Li return avahi_utils.avahi_get_domain_name() 115*9c5db199SXin Li 116*9c5db199SXin Li 117*9c5db199SXin Li def start(self): 118*9c5db199SXin Li """Start up the chrooted Avahi instance.""" 119*9c5db199SXin Li # Prevent weird interactions between services which talk to Avahi. 120*9c5db199SXin Li # TODO(wiley) Does Chrome need to die here as well? 121*9c5db199SXin Li self._services = service_stopper.ServiceStopper( 122*9c5db199SXin Li self.SERVICES_TO_STOP) 123*9c5db199SXin Li self._services.stop_services() 124*9c5db199SXin Li # We don't want Avahi talking to the real world, so give it a nice 125*9c5db199SXin Li # fake interface to use. We'll watch the other half of the pair. 126*9c5db199SXin Li self._vif = virtual_ethernet_pair.VirtualEthernetPair( 127*9c5db199SXin Li interface_name=self.unchrooted_interface_name, 128*9c5db199SXin Li peer_interface_name=self.AVAHI_IF_NAME, 129*9c5db199SXin Li interface_ip=self.MONITOR_IF_IP.netblock, 130*9c5db199SXin Li peer_interface_ip=self.AVAHI_IF_IP.netblock, 131*9c5db199SXin Li # Moving one end into the chroot causes errors. 132*9c5db199SXin Li ignore_shutdown_errors=True) 133*9c5db199SXin Li self._vif.setup() 134*9c5db199SXin Li if not self._vif.is_healthy: 135*9c5db199SXin Li raise error.TestError('Failed to setup virtual ethernet pair.') 136*9c5db199SXin Li # By default, take a packet capture of everything Avahi sends out. 137*9c5db199SXin Li self._tcpdump = tcpdump.Tcpdump(self.unchrooted_interface_name, 138*9c5db199SXin Li self.TCPDUMP_FILE_PATH) 139*9c5db199SXin Li # We're going to run Avahi in a network namespace to avoid interactions 140*9c5db199SXin Li # with the outside world. 141*9c5db199SXin Li self._chroot = network_chroot.NetworkChroot(self.AVAHI_IF_NAME, 142*9c5db199SXin Li self.AVAHI_IF_IP.addr, 143*9c5db199SXin Li self.AVAHI_IF_IP.prefix_len) 144*9c5db199SXin Li self._chroot.add_config_templates(self.AVAHI_CONFIGS) 145*9c5db199SXin Li self._chroot.add_root_directories(['etc/avahi', 'etc/avahi/services']) 146*9c5db199SXin Li self._chroot.add_copied_config_files(['etc/resolv.conf', 147*9c5db199SXin Li 'etc/avahi/hosts']) 148*9c5db199SXin Li self._chroot.add_startup_command( 149*9c5db199SXin Li '/usr/sbin/avahi-daemon --file=/%s >%s 2>&1' % 150*9c5db199SXin Li (self.AVAHI_CONFIG_FILE, self.AVAHI_LOG_FILE)) 151*9c5db199SXin Li self._chroot.bridge_dbus_namespaces() 152*9c5db199SXin Li self._chroot.startup() 153*9c5db199SXin Li # Wait for Avahi to come up, claim its DBus name, settle on a hostname. 154*9c5db199SXin Li start_time = time.time() 155*9c5db199SXin Li while time.time() - start_time < self.AVAHI_UP_TIMEOUT_SECONDS: 156*9c5db199SXin Li if avahi_utils.avahi_ping(): 157*9c5db199SXin Li break 158*9c5db199SXin Li time.sleep(0.2) 159*9c5db199SXin Li else: 160*9c5db199SXin Li raise error.TestFail('Avahi did not come up in time.') 161*9c5db199SXin Li 162*9c5db199SXin Li 163*9c5db199SXin Li def close(self): 164*9c5db199SXin Li """Clean up the chrooted Avahi instance.""" 165*9c5db199SXin Li if self._chroot: 166*9c5db199SXin Li # TODO(wiley) This is sloppy. Add a helper to move the logs over. 167*9c5db199SXin Li for line in self._chroot.get_log_contents().splitlines(): 168*9c5db199SXin Li logging.debug(line) 169*9c5db199SXin Li self._chroot.kill_pid_file(self.AVAHI_PID_FILE) 170*9c5db199SXin Li self._chroot.shutdown() 171*9c5db199SXin Li if self._tcpdump: 172*9c5db199SXin Li self._tcpdump.stop() 173*9c5db199SXin Li if self._vif: 174*9c5db199SXin Li self._vif.teardown() 175*9c5db199SXin Li if self._services: 176*9c5db199SXin Li self._services.restore_services() 177