OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 """Brings in Chrome Android's android_commands module, which itself is a | 4 """Brings in Chrome Android's android_commands module, which itself is a |
5 thin(ish) wrapper around adb.""" | 5 thin(ish) wrapper around adb.""" |
6 import logging | |
6 import os | 7 import os |
8 import re | |
9 import socket | |
10 import struct | |
11 import subprocess | |
12 import sys | |
7 | 13 |
8 from telemetry.core import util | 14 from telemetry.core import util |
9 | 15 |
10 # This is currently a thin wrapper around Chrome Android's | 16 # This is currently a thin wrapper around Chrome Android's |
11 # build scripts, located in chrome/build/android. This file exists mainly to | 17 # build scripts, located in chrome/build/android. This file exists mainly to |
12 # deal with locating the module. | 18 # deal with locating the module. |
13 | 19 |
14 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android') | 20 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android') |
15 try: | 21 try: |
16 from pylib import android_commands # pylint: disable=F0401 | 22 from pylib import android_commands # pylint: disable=F0401 |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
158 def __init__(self, adb, *port_pairs): | 164 def __init__(self, adb, *port_pairs): |
159 self._adb = adb.Adb() | 165 self._adb = adb.Adb() |
160 self._host_port = port_pairs[0].local_port | 166 self._host_port = port_pairs[0].local_port |
161 | 167 |
162 new_port_pairs = [(port_pair.local_port, port_pair.remote_port) | 168 new_port_pairs = [(port_pair.local_port, port_pair.remote_port) |
163 for port_pair in port_pairs] | 169 for port_pair in port_pairs] |
164 | 170 |
165 self._port_pairs = new_port_pairs | 171 self._port_pairs = new_port_pairs |
166 forwarder.Forwarder.Map(new_port_pairs, self._adb) | 172 forwarder.Forwarder.Map(new_port_pairs, self._adb) |
167 | 173 |
168 @staticmethod | |
169 def _GetBuildType(): | |
170 assert HasForwarder() | |
171 return 'Debug' if HasForwarder('Debug') else 'Release' | |
172 | |
173 @property | 174 @property |
174 def url(self): | 175 def url(self): |
175 return 'http://localhost:%i' % self._host_port | 176 return 'http://localhost:%i' % self._host_port |
176 | 177 |
177 def Close(self): | 178 def Close(self): |
178 for (device_port, _) in self._port_pairs: | 179 for (device_port, _) in self._port_pairs: |
179 forwarder.Forwarder.UnmapDevicePort(device_port, self._adb) | 180 forwarder.Forwarder.UnmapDevicePort(device_port, self._adb) |
181 | |
182 | |
183 class RndisForwarderWithRoot(object): | |
tonyg
2013/08/22 23:55:03
This is meaty enough that it I think it warrants i
szym
2013/08/23 15:33:41
Done.
| |
184 """Forwards traffic using RNDIS. Assuming the device has root access. | |
185 """ | |
186 _RNDIS_DEVICE = '/sys/class/android_usb/android0' | |
187 | |
188 def __init__(self, adb, *port_pairs): | |
189 """Args: | |
190 adb: an instance of AdbCommands | |
191 port_pairs: Used for compatibility with Forwarder. RNDIS does not | |
192 support mapping so local_port must match remote_port in all pairs. | |
193 """ | |
194 assert adb.IsRootEnabled(), 'Root must be enabled to use RNDIS forwarding.' | |
195 assert all(pair.remote_port == pair.local_port for pair in port_pairs), \ | |
196 'Local and remote ports must be the same on all pairs with RNDIS.' | |
197 self._adb = adb.Adb() | |
198 if port_pairs: | |
199 self._host_port = port_pairs[0].local_port | |
200 | |
201 self._host_ip = None | |
202 self._device_ip = None | |
203 self._host_iface = None | |
204 self._device_iface = None | |
205 self._original_dns = None, None, None | |
206 | |
207 assert self._IsRndisSupported(), 'Device does not have rndis!' | |
208 self._CheckConfigureNetwork() | |
209 | |
210 def OverrideDns(self): | |
tonyg
2013/08/22 23:55:03
Where did we land on this? I thought we were going
szym
2013/08/23 15:33:41
Right now, AdbCommands._override_dns = False.
| |
211 """Overrides DNS on device to point at the host.""" | |
212 self._original_dns = self._GetCurrentDns() | |
213 self._OverrideDns(self._device_iface, self._host_ip, self._host_ip) | |
214 | |
215 def _IsRndisSupported(self): | |
216 """Checks that the device has RNDIS support in the kernel.""" | |
217 return self._adb.FileExistsOnDevice( | |
218 '%s/f_rndis/device' % self._RNDIS_DEVICE) | |
219 | |
220 def _WaitForDevice(self): | |
221 self._adb.Adb().SendCommand('wait-for-device') | |
222 | |
223 def _FindDeviceRndisInterface(self): | |
224 """Returns the name of the RNDIS network interface if present.""" | |
225 config = self._adb.RunShellCommand('netcfg') | |
226 candidates = [line.split()[0] for line in config if 'rndis' in line] | |
227 if candidates: | |
228 assert len(candidates) == 1, 'Found more than one rndis device!' | |
229 return candidates[0] | |
230 | |
231 def _EnumerateHostInterfaces(self): | |
232 if sys.platform.startswith('linux'): | |
233 return subprocess.check_output(['ip', 'addr']).splitlines() | |
tonyg
2013/08/22 23:55:03
Unfortunately, the bots run python 2.6 and check_o
szym
2013/08/23 15:33:41
Makes me wonder why. Python 2.6.X expires (no furt
szym
2013/08/26 14:43:06
Done.
| |
234 if sys.platform.startswith('darwin'): | |
tonyg
2013/08/22 23:55:03
elif and you can just use == here.
szym
2013/08/26 14:43:06
Done.
| |
235 return subprocess.check_output(['ifconfig']).splitlines() | |
236 raise Exception('Platform %s not supported!' % sys.platform) | |
237 | |
238 def _FindHostRndisInterface(self): | |
239 """Returns the name of the host-side network interface.""" | |
240 interface_list = self._EnumerateHostInterfaces() | |
241 ether_address = self._adb.GetFileContents( | |
242 '%s/f_rndis/ethaddr' % self._RNDIS_DEVICE)[0] | |
243 interface_name = None | |
244 for line in interface_list: | |
245 if not line.startswith(' '): | |
246 interface_name = line.split()[1].strip(':') | |
247 elif ether_address in line: | |
248 return interface_name | |
249 | |
250 def _DisableRndis(self): | |
251 self._adb.RunShellCommand('setprop sys.usb.config adb') | |
252 self._WaitForDevice() | |
253 | |
254 def _EnableRndis(self): | |
255 """Enables the RNDIS network interface.""" | |
256 script_prefix = '/data/local/tmp/rndis' | |
257 # This could be accomplished via "svc usb setFunction rndis" but only on | |
258 # devices which have the "USB tethering" feature. | |
259 # Also, on some devices, it's necessary to go through "none" function. | |
260 script = """ | |
261 trap '' HUP | |
262 trap '' TERM | |
263 trap '' PIPE | |
264 | |
265 function manual_config() { | |
266 echo %(functions)s > %(dev)s/functions | |
267 echo 224 > %(dev)s/bDeviceClass | |
268 echo 1 > %(dev)s/enable | |
269 start adbd | |
270 setprop sys.usb.state %(functions)s | |
271 } | |
272 | |
273 # This function kills adb transport, so it has to be run "detached". | |
274 function doit() { | |
275 setprop sys.usb.config none | |
276 while [ `getprop sys.usb.state` != "none" ]; do | |
277 sleep 1 | |
278 done | |
279 manual_config | |
280 # For some combinations of devices and host kernels, adb won't work unless the | |
281 # interface is up, but if we bring it up immediately, it will break adb. | |
282 #sleep 1 | |
283 #ifconfig rndis0 192.168.42.2 netmask 255.255.255.0 up | |
284 echo DONE >> %(prefix)s.log | |
285 } | |
286 | |
287 doit & | |
288 """ % {'dev': self._RNDIS_DEVICE, 'functions': 'rndis,adb', | |
289 'prefix': script_prefix } | |
290 self._adb.SetFileContents('%s.sh' % script_prefix, script) | |
291 # TODO(szym): run via su -c if necessary. | |
292 self._adb.RunShellCommand('rm %s.log' % script_prefix) | |
293 self._adb.RunShellCommand('. %s.sh' % script_prefix) | |
294 self._WaitForDevice() | |
295 result = self._adb.GetFileContents('%s.log' % script_prefix) | |
296 assert any('DONE' in line for line in result), 'RNDIS script did not run!' | |
297 | |
298 def _CheckEnableRndis(self, force): | |
299 """Enables the RNDIS network interface, retrying if necessary. | |
300 Args: | |
301 force: Disable RNDIS first, even if it appears already enabled. | |
302 Returns: | |
303 device_iface: RNDIS interface name on the device | |
304 host_iface: corresponding interface name on the host | |
305 """ | |
306 for _ in range(3): | |
307 if not force: | |
308 device_iface = self._FindDeviceRndisInterface() | |
309 if device_iface: | |
310 host_iface = self._FindHostRndisInterface() | |
311 if host_iface: | |
312 return device_iface, host_iface | |
313 self._DisableRndis() | |
314 self._EnableRndis() | |
315 force = False | |
316 raise Exception('Could not enable RNDIS, giving up.') | |
317 | |
318 def _GetHostAddresses(self, iface): | |
319 """Returns the IP addresses on host's interfaces, breaking out |iface|.""" | |
320 interface_list = self._EnumerateHostInterfaces() | |
321 addresses = [] | |
322 iface_address = None | |
323 found_iface = False | |
324 for line in interface_list: | |
325 if not line.startswith(' '): | |
326 found_iface = iface in line | |
327 match = re.search('(?<=inet )\S+', line) | |
328 if match: | |
329 address = match.group(0) | |
330 if found_iface: | |
331 assert not iface_address, ( | |
332 'Found %s twice when parsing host interfaces.' % iface) | |
333 iface_address = address | |
334 else: | |
335 addresses.append(address) | |
336 return addresses, iface_address | |
337 | |
338 def _GetDeviceAddresses(self, excluded_iface): | |
339 """Returns the IP addresses on all connected devices. | |
340 Excludes interface |excluded_iface| on the selected device. | |
341 """ | |
342 my_device = self._adb.GetDevice() | |
343 addresses = [] | |
344 for device in GetAttachedDevices(): | |
345 adb = android_commands.AndroidCommands(device) | |
346 if device == my_device: | |
347 excluded = excluded_iface | |
348 else: | |
349 excluded = 'no interfaces excluded on other devices' | |
350 addresses += [line.split()[2] for line in adb.RunShellCommand('netcfg') | |
351 if excluded not in line] | |
352 return addresses | |
353 | |
354 def _ConfigureNetwork(self, device_iface, host_iface): | |
355 """Configures the |device_iface| to be on the same network as |host_iface|. | |
356 """ | |
357 def _Ip2Long(addr): | |
358 return struct.unpack('!L', socket.inet_aton(addr))[0] | |
359 | |
360 def _Long2Ip(value): | |
361 return socket.inet_ntoa(struct.pack('!L', value)) | |
362 | |
363 def _Length2Mask(length): | |
364 return 0xFFFFFFFF & ~((1 << (32 - length)) - 1) | |
365 | |
366 def _IpPrefix2AddressMask(addr): | |
367 addr, masklen = addr.split('/') | |
368 return _Ip2Long(addr), _Length2Mask(int(masklen)) | |
369 | |
370 def _IsNetworkUnique(network, addresses): | |
371 return all((addr & mask != network & mask) for addr, mask in addresses) | |
372 | |
373 def _NextUnusedAddress(network, netmask, used_addresses): | |
374 # Excludes '0' and broadcast. | |
375 for suffix in range(1, 0xFFFFFFFF & ~netmask): | |
376 candidate = network | suffix | |
377 if candidate not in used_addresses: | |
378 return candidate | |
379 | |
380 addresses, host_address = self._GetHostAddresses(host_iface) | |
381 | |
382 assert host_address, ('Interface %(iface)s was not configured.\n' | |
383 'To configure it automatically, add to /etc/network/interfaces:\n' | |
384 'auto %(iface)s\n' | |
385 'iface %(iface)s\n' | |
386 ' address 192.168.<unique>.1\n' | |
387 ' netmask 255.255.255.0' % {'iface': host_iface}) | |
388 | |
389 addresses = [_IpPrefix2AddressMask(addr) for addr in addresses] | |
390 host_ip, netmask = _IpPrefix2AddressMask(host_address) | |
391 | |
392 network = host_ip & netmask | |
393 | |
394 if not _IsNetworkUnique(network, addresses): | |
395 logging.warning( | |
396 'The IP address configuration %s of %s is not unique!\n' | |
397 'Check your /etc/network/interfaces. If this overlap is intended,\n' | |
398 'you might need to use: ip rule add from <device_ip> lookup <table>\n' | |
399 'or add the interface to a bridge in order to route to this network.' | |
400 % (host_address, host_iface) | |
401 ) | |
402 | |
403 # Find unused IP address. | |
404 used_addresses = [addr for addr, _ in addresses] | |
405 used_addresses += [_IpPrefix2AddressMask(addr)[0] | |
406 for addr in self._GetDeviceAddresses(device_iface)] | |
407 used_addresses += [host_ip] | |
408 | |
409 device_ip = _NextUnusedAddress(network, netmask, used_addresses) | |
410 assert device_ip, ('The network %s on %s is full.' % | |
411 (host_address, host_iface)) | |
412 | |
413 host_ip = _Long2Ip(host_ip) | |
414 device_ip = _Long2Ip(device_ip) | |
415 netmask = _Long2Ip(netmask) | |
416 | |
417 # TODO(szym) run via su -c if necessary. | |
418 self._adb.RunShellCommand('ifconfig %s %s netmask %s up' % | |
419 (device_iface, device_ip, netmask)) | |
420 # Enabling the interface sometimes breaks adb. | |
421 self._WaitForDevice() | |
422 self._host_iface = host_iface | |
423 self._host_ip = host_ip | |
424 self._device_iface = device_iface | |
425 self._device_ip = device_ip | |
426 | |
427 def _TestConnectivity(self): | |
428 with open(os.devnull, 'wb') as devnull: | |
429 return subprocess.call(['ping', '-q', '-c1', '-W1', | |
430 '-I', self._host_iface, self._device_ip], | |
431 stdout=devnull) == 0 | |
432 | |
433 def _CheckConfigureNetwork(self): | |
434 """Enables RNDIS and configures it, retrying until we have connectivity.""" | |
435 force = False | |
436 for _ in range(3): | |
437 device_iface, host_iface = self._CheckEnableRndis(force) | |
438 self._ConfigureNetwork(device_iface, host_iface) | |
439 if self._TestConnectivity(): | |
440 return | |
441 force = True | |
442 raise Exception('No connectivity, giving up.') | |
443 | |
444 def _GetCurrentDns(self): | |
445 """Returns current gateway, dns1, and dns2.""" | |
446 routes = self._adb.RunShellCommand('cat /proc/net/route')[1:] | |
447 routes = [route.split() for route in routes] | |
448 default_routes = [route[0] for route in routes if route[1] == '00000000'] | |
449 return ( | |
450 default_routes[0] if default_routes else None, | |
451 self._adb.RunShellCommand('getprop net.dns1')[0], | |
452 self._adb.RunShellCommand('getprop net.dns2')[0], | |
453 ) | |
454 | |
455 def _OverrideDns(self, iface, dns1, dns2): | |
456 """Overrides device's DNS configuration. | |
457 | |
458 Args: | |
459 iface: name of the network interface to make default | |
460 dns1, dns2: nameserver IP addresses | |
461 """ | |
462 if not iface: | |
463 return # If there is no route, then nobody cares about DNS. | |
464 # DNS proxy in older versions of Android is configured via properties. | |
465 # TODO(szym): run via su -c if necessary. | |
466 self._adb.RunShellCommand('setprop net.dns1 ' + dns1) | |
467 self._adb.RunShellCommand('setprop net.dns2 ' + dns2) | |
468 dnschange = self._adb.RunShellCommand('getprop net.dnschange')[0] | |
469 if dnschange: | |
470 self._adb.RunShellCommand('setprop net.dnschange %s' % | |
471 (int(dnschange) + 1)) | |
472 # Since commit 8b47b3601f82f299bb8c135af0639b72b67230e6 to frameworks/base | |
473 # the net.dns1 properties have been replaced with explicit commands for netd | |
474 self._adb.RunShellCommand('ndc netd resolver setifdns %s %s %s' % | |
475 (iface, dns1, dns2)) | |
476 # TODO(szym): if we know the package UID, we could setifaceforuidrange | |
477 self._adb.RunShellCommand('ndc netd resolver setdefaultif %s' % iface) | |
478 | |
479 @property | |
480 def host_ip(self): | |
481 return self._host_ip | |
482 | |
483 @property | |
484 def url(self): | |
485 # localhost and domains which resolve on the host's private network will not | |
486 # be resolved by the DNS proxy to the HTTP proxy. | |
487 return 'http://%s:%d' % (self._host_ip, self._host_port) | |
488 | |
489 def Close(self): | |
490 self._OverrideDns(*self._original_dns) | |
OLD | NEW |