1# Copyright (c) 2013 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 os
7import sys
8import tempfile
9import time
10
11from autotest_lib.client.bin import test
12from autotest_lib.client.common_lib import error, utils
13from autotest_lib.client.cros import p2p_utils
14from autotest_lib.client.cros.netprotos import cros_p2p, zeroconf
15
16
17class p2p_ServeFiles(test.test):
18    """The P2P Server class tester.
19
20    This class runs the p2p service (p2p-server and p2p-http-server) and checks
21    that the DUT is serving the shared files on the network.
22    """
23    version = 1
24
25    def setup(self):
26        self.job.setup_dep(['lansim'])
27
28
29    def initialize(self):
30        dep = 'lansim'
31        dep_dir = os.path.join(self.autodir, 'deps', dep)
32        logging.info('lansim is at %s', dep_dir)
33        self.job.install_pkg(dep, 'dep', dep_dir)
34
35        # Import the lansim modules installed on lansim/build/
36        sys.path.append(os.path.join(dep_dir, 'build'))
37
38        self._p2p = p2p_utils.P2PServerOverTap()
39        self._sim = None
40
41
42    def cleanup(self):
43        # Work around problem described in the chromium:364583 bug.
44        time.sleep(1)
45        self._join_simulator()
46        self._p2p.cleanup()
47
48
49    def _join_simulator(self):
50        """Stops the simulator and logs any exception generated there."""
51        if not self._sim:
52            return
53        self._sim.stop()
54        self._sim.join()
55        if self._sim.error:
56            logging.error('SimulatorThread exception: %r', self._sim.error)
57            logging.error(self._sim.traceback)
58
59
60    def _dut_ready(self, p2pcli):
61        # Lookup the DUT on the mDNS network.
62        peers = p2pcli.get_peers()
63        if not peers:
64            return False
65        peer_name, hostname, ips, port = peers[0]
66        # Get the files shared by the DUT.
67        files = p2pcli.get_peer_files(peer_name)
68        if not files:
69            return False
70        return peer_name, hostname, ips, port, files
71
72
73    def _p2p_fetch(self, host, port, filename):
74        """Fetch a file from a p2p-http-server.
75
76        @return: A str with the contents of the responde if the request
77        succeeds or an integer value with the error code returned by curl
78        otherwise.
79        """
80        fd, tempfn = tempfile.mkstemp(prefix='p2p-fetch')
81        ret = utils.run(
82                'curl', args=['http://%s:%s/%s' % (host, port, filename)],
83                timeout=20., ignore_timeout=False, ignore_status=True,
84                stdout_tee=open(tempfn, 'w'), stderr_tee=sys.stdout)
85        with os.fdopen(fd) as f:
86            output = f.read()
87        os.unlink(tempfn)
88
89        if ret is None:
90            return None
91        if ret.exit_status != 0:
92            return ret.exit_status
93        return output
94
95
96    def run_once(self):
97        from lansim import simulator, host
98
99        # Setup the environment where avahi-daemon runs during the test.
100        try:
101            self._p2p.setup(dumpdir=self.job.resultdir)
102        except:
103            logging.exception('Failed to start tested services.')
104            raise
105
106        # Share a file on the DUT.
107        content = open('/dev/urandom').read(16*1024)
108        with open(os.path.join(p2p_utils.P2P_SHARE_PATH, 'file.p2p'), 'w') as f:
109            f.write(content)
110
111        self._sim = simulator.SimulatorThread(self._p2p.tap)
112        # Create a single fake peer that will be sending the multicast requests.
113        peer = host.SimpleHost(self._sim, '94:EB:2C:00:00:61', '169.254.10.55')
114
115        # Run a userspace implementation of avahi + p2p-client on the fake
116        # host. This will use the P2P services exported by the DUT.
117        zero = zeroconf.ZeroconfDaemon(peer, 'peer')
118        p2pcli = cros_p2p.CrosP2PClient(zero)
119
120        self._sim.start()
121
122        # Force a request from the client before waiting for the DUT's response.
123        self._sim.run_on_simulator(lambda: p2pcli.start_query())
124
125        # Wait up to 30 seconds until the DUT is ready sharing the files.
126        res = self._sim.wait_for_condition(lambda: self._dut_ready(p2pcli),
127                                           timeout=30.)
128        self._sim.run_on_simulator(lambda: p2pcli.stop_query())
129
130        if not res:
131            raise error.TestFail('The DUT failed to announce the shared files '
132                                 'after 30 seconds.')
133
134        # DUT's p2p-http-server is running on hostname:port.
135        peer_name, hostname, ips, port, files = res
136
137        if len(files) != 1 or files[0] != ('file', len(content)) or (
138                len(ips) != 1) or ips[0] != self._p2p.tap.addr:
139            logging.error('peer_name = %r', peer_name)
140            logging.error('hostname = %r', hostname)
141            logging.error('ips = %r', ips)
142            logging.error('port = %r', port)
143            logging.error('files = %r', files)
144            raise error.TestFail('The DUT announced an erroneous file.')
145
146        # Check we can't download directly from localhost.
147        for host_ip in (ips[0], '127.0.0.1'):
148            ret = self._p2p_fetch(host_ip, port, 'file')
149            if ret != 7: # curl's exit code 7 is "Failed to connect to host."
150                logging.error('curl returned: %s', repr(ret)[:100])
151                raise error.TestFail(
152                        "The DUT didn't block a request from localhost using "
153                        "the address %s." % host_ip)
154
155        # Check we can download if the connection comes from a peer on the
156        # network. To achieve this, we forward the tester's TCP traffic through
157        # a fake host on lansim.
158        self._sim.run_on_simulator(lambda: peer.tcp_forward(1234, ips[0], port))
159
160        ret = self._p2p_fetch(peer.ip_addr, 1234, 'file')
161        if ret != content:
162            logging.error('curl returned: %s', repr(ret)[:100])
163            raise error.TestFail(
164                    "The DUT didn't serve the file request from %s " %
165                    peer.id_addr)
166