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