Chromium Code Reviews| 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 |