blob: 5a76f80afbd08c507d7d723ab93a09b6d2ec38a8 [file] [log] [blame]
Christopher Wiley4bb434d2014-10-01 11:52:25 -07001# 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
5import logging
6import time
7
8from autotest_lib.client.common_lib import error
Christopher Wileyb1a5bdb2015-02-02 14:56:18 -08009from autotest_lib.client.common_lib.cros import avahi_utils
Peter Qiudb63b1e2015-01-28 16:51:45 -080010from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
Christopher Wileyb1a5bdb2015-02-02 14:56:18 -080011from autotest_lib.client.common_lib.cros.network import netblock
Christopher Wiley4bb434d2014-10-01 11:52:25 -070012from autotest_lib.client.cros import network_chroot
13from autotest_lib.client.cros import service_stopper
14from autotest_lib.client.cros import tcpdump
Christopher Wiley4bb434d2014-10-01 11:52:25 -070015
16
17class 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
Christopher Wiley610b2672014-10-23 16:19:16 -070026 SERVICES_TO_STOP = ['avahi']
Christopher Wiley4bb434d2014-10-01 11:52:25 -070027 # This side has to be called something special to avoid shill touching it.
Christopher Wiley684878e2014-12-18 14:32:48 -080028 MONITOR_IF_IP = netblock.from_addr('10.9.8.1/24')
Christopher Wiley4bb434d2014-10-01 11:52:25 -070029 # We'll drop the Avahi side into our network namespace.
Christopher Wiley684878e2014-12-18 14:32:48 -080030 AVAHI_IF_IP = netblock.from_addr('10.9.8.2/24')
Christopher Wiley4bb434d2014-10-01 11:52:25 -070031 AVAHI_IF_NAME = 'pseudoethernet0'
Christopher Wileye3445652014-10-14 12:16:12 -070032 TCPDUMP_FILE_PATH = '/var/log/peerd_dump.pcap'
Christopher Wiley4bb434d2014-10-01 11:52:25 -070033 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'
Mike Frysinger898bd552017-04-10 23:56:36 -040066 AVAHI_PID_FILE = 'run/avahi-daemon/pid'
Christopher Wiley4bb434d2014-10-01 11:52:25 -070067 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(
Ben Chan630d6b42015-02-13 18:14:45 -0800149 '/usr/sbin/avahi-daemon --file=/%s >%s 2>&1' %
Christopher Wiley4bb434d2014-10-01 11:52:25 -0700150 (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()