Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(746)

Side by Side Diff: tools/telemetry/telemetry/core/chrome/adb_commands.py

Issue 22286010: [telemetry] Implement Forwarder using RNDIS. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: add --android_rndis flag Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698