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