liyujie
2025-08-28 786ff4f4ca2374bdd9177f2e24b503d43e7a3b93
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# 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()