Home | History | Annotate | Download | only in cros
      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 = '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