1 # Copyright 2013 The Chromium 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.
12 from telemetry.core import util
13 from telemetry.core.backends import adb_commands
16 def _CheckOutput(*popenargs, **kwargs):
17 """Backport of subprocess.check_output to python 2.6"""
18 process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
19 output, _ = process.communicate()
20 retcode = process.poll()
22 cmd = kwargs.get('args')
25 error = subprocess.CalledProcessError(retcode, cmd)
31 class RndisForwarderWithRoot(object):
32 """Forwards traffic using RNDIS. Assuming the device has root access.
34 _RNDIS_DEVICE = '/sys/class/android_usb/android0'
35 _NETWORK_INTERFACES = '/etc/network/interfaces'
36 _TELEMETRY_MARKER = '# Added by Telemetry #'
38 def __init__(self, adb):
40 adb: an instance of AdbCommands
42 is_root_enabled = adb.Adb().EnableAdbRoot()
43 assert is_root_enabled, 'RNDIS forwarding requires a rooted device'
48 self._device_ip = None
49 self._host_iface = None
50 self._device_iface = None
51 self._original_dns = None, None, None
53 assert self._IsRndisSupported(), 'Device does not have rndis!'
54 self._CheckConfigureNetwork()
56 def SetPorts(self, *port_pairs):
58 port_pairs: Used for compatibility with Forwarder. RNDIS does not
59 support mapping so local_port must match remote_port in all pairs.
61 assert all(pair.remote_port == pair.local_port for pair in port_pairs), \
62 'Local and remote ports must be the same on all pairs with RNDIS.'
63 self._host_port = port_pairs[0].local_port
65 def OverrideDns(self):
66 """Overrides DNS on device to point at the host."""
67 self._original_dns = self._GetCurrentDns()
68 if not self._original_dns[0]:
69 # No default route. Install one via the host. This is needed because
70 # getaddrinfo in bionic uses routes to determine AI_ADDRCONFIG.
71 self._adb.RunShellCommand('route add default gw %s dev %s' %
72 (self._host_ip, self._device_iface))
73 self._OverrideDns(self._device_iface, self._host_ip, self._host_ip)
75 def _IsRndisSupported(self):
76 """Checks that the device has RNDIS support in the kernel."""
77 return self._adb.FileExistsOnDevice(
78 '%s/f_rndis/device' % self._RNDIS_DEVICE)
80 def _WaitForDevice(self):
81 self._adb.Adb().SendCommand('wait-for-device')
83 def _FindDeviceRndisInterface(self):
84 """Returns the name of the RNDIS network interface if present."""
85 config = self._adb.RunShellCommand('netcfg')
86 interfaces = [line.split()[0] for line in config]
87 candidates = [iface for iface in interfaces if re.match('rndis|usb', iface)]
89 assert len(candidates) == 1, 'Found more than one rndis device!'
92 def _EnumerateHostInterfaces(self):
93 if sys.platform.startswith('linux'):
94 return _CheckOutput(['ip', 'addr']).splitlines()
95 elif sys.platform == 'darwin':
96 return _CheckOutput(['ifconfig']).splitlines()
97 raise Exception('Platform %s not supported!' % sys.platform)
99 def _FindHostRndisInterface(self):
100 """Returns the name of the host-side network interface."""
101 interface_list = self._EnumerateHostInterfaces()
102 ether_address = self._adb.GetFileContents(
103 '%s/f_rndis/ethaddr' % self._RNDIS_DEVICE)[0]
104 interface_name = None
105 for line in interface_list:
106 if not line.startswith(' '):
107 interface_name = line.split()[1].strip(':')
108 elif ether_address in line:
109 return interface_name
111 def _WriteProtectedFile(self, path, contents):
112 subprocess.check_call(
113 ['sudo', 'bash', '-c', 'echo -e "%s" > %s' % (contents, path)])
115 def _DisableRndis(self):
116 self._adb.system_properties['sys.usb.config'] = 'adb'
117 self._WaitForDevice()
119 def _EnableRndis(self):
120 """Enables the RNDIS network interface."""
121 script_prefix = '/data/local/tmp/rndis'
122 # This could be accomplished via "svc usb setFunction rndis" but only on
123 # devices which have the "USB tethering" feature.
124 # Also, on some devices, it's necessary to go through "none" function.
130 function manual_config() {
131 echo %(functions)s > %(dev)s/functions
132 echo 224 > %(dev)s/bDeviceClass
133 echo 1 > %(dev)s/enable
135 setprop sys.usb.state %(functions)s
138 # This function kills adb transport, so it has to be run "detached".
140 setprop sys.usb.config none
141 while [ `getprop sys.usb.state` != "none" ]; do
145 # For some combinations of devices and host kernels, adb won't work unless the
146 # interface is up, but if we bring it up immediately, it will break adb.
148 #ifconfig rndis0 192.168.42.2 netmask 255.255.255.0 up
149 echo DONE >> %(prefix)s.log
153 """ % {'dev': self._RNDIS_DEVICE, 'functions': 'rndis,adb',
154 'prefix': script_prefix }
155 self._adb.SetFileContents('%s.sh' % script_prefix, script)
156 # TODO(szym): run via su -c if necessary.
157 self._adb.RunShellCommand('rm %s.log' % script_prefix)
158 self._adb.RunShellCommand('. %s.sh' % script_prefix)
159 self._WaitForDevice()
160 result = self._adb.GetFileContents('%s.log' % script_prefix)
161 assert any('DONE' in line for line in result), 'RNDIS script did not run!'
163 def _CheckEnableRndis(self, force):
164 """Enables the RNDIS network interface, retrying if necessary.
166 force: Disable RNDIS first, even if it appears already enabled.
168 device_iface: RNDIS interface name on the device
169 host_iface: corresponding interface name on the host
173 device_iface = self._FindDeviceRndisInterface()
175 host_iface = self._FindHostRndisInterface()
177 return device_iface, host_iface
181 raise Exception('Could not enable RNDIS, giving up.')
183 def _GetHostAddresses(self, iface):
184 """Returns the IP addresses on host's interfaces, breaking out |iface|."""
185 interface_list = self._EnumerateHostInterfaces()
189 for line in interface_list:
190 if not line.startswith(' '):
191 found_iface = iface in line
192 match = re.search('(?<=inet )\S+', line)
194 address = match.group(0)
196 assert not iface_address, (
197 'Found %s twice when parsing host interfaces.' % iface)
198 iface_address = address
200 addresses.append(address)
201 return addresses, iface_address
203 def _GetDeviceAddresses(self, excluded_iface):
204 """Returns the IP addresses on all connected devices.
205 Excludes interface |excluded_iface| on the selected device.
207 my_device = self._adb.GetDevice()
209 for device in adb_commands.GetAttachedDevices():
210 adb = adb_commands.AdbCommands(device).Adb()
211 if device == my_device:
212 excluded = excluded_iface
214 excluded = 'no interfaces excluded on other devices'
215 addresses += [line.split()[2] for line in adb.RunShellCommand('netcfg')
216 if excluded not in line]
219 def _ConfigureNetwork(self, device_iface, host_iface):
220 """Configures the |device_iface| to be on the same network as |host_iface|.
223 return struct.unpack('!L', socket.inet_aton(addr))[0]
226 return socket.inet_ntoa(struct.pack('!L', value))
228 def _Length2Mask(length):
229 return 0xFFFFFFFF & ~((1 << (32 - length)) - 1)
231 def _IpPrefix2AddressMask(addr):
232 addr, masklen = addr.split('/')
233 return _Ip2Long(addr), _Length2Mask(int(masklen))
235 def _IsNetworkUnique(network, addresses):
236 return all((addr & mask != network & mask) for addr, mask in addresses)
238 def _NextUnusedAddress(network, netmask, used_addresses):
239 # Excludes '0' and broadcast.
240 for suffix in range(1, 0xFFFFFFFF & ~netmask):
241 candidate = network | suffix
242 if candidate not in used_addresses:
245 interfaces = open(self._NETWORK_INTERFACES, 'r').read()
246 if 'auto ' + host_iface not in interfaces:
247 config = ('%(orig)s\n\n'
250 'iface %(iface)s inet static\n'
251 ' address 192.168.123.1\n' # Arbitrary IP.
252 ' netmask 255.255.255.0' % {'orig': interfaces,
253 'marker': self._TELEMETRY_MARKER,
254 'iface': host_iface})
255 self._WriteProtectedFile(self._NETWORK_INTERFACES, config)
256 subprocess.check_call(['sudo', '/etc/init.d/networking', 'restart'])
258 def HasHostAddress():
259 _, host_address = self._GetHostAddresses(host_iface)
260 return bool(host_address)
261 logging.info('Waiting for RNDIS connectivity...')
262 util.WaitFor(HasHostAddress, 10)
264 addresses, host_address = self._GetHostAddresses(host_iface)
265 assert host_address, 'Interface %s could not be configured.' % host_iface
267 addresses = [_IpPrefix2AddressMask(addr) for addr in addresses]
268 host_ip, netmask = _IpPrefix2AddressMask(host_address)
270 network = host_ip & netmask
272 if not _IsNetworkUnique(network, addresses):
274 'The IP address configuration %s of %s is not unique!\n'
275 'Check your /etc/network/interfaces. If this overlap is intended,\n'
276 'you might need to use: ip rule add from <device_ip> lookup <table>\n'
277 'or add the interface to a bridge in order to route to this network.'
278 % (host_address, host_iface)
281 # Find unused IP address.
282 used_addresses = [addr for addr, _ in addresses]
283 used_addresses += [_IpPrefix2AddressMask(addr)[0]
284 for addr in self._GetDeviceAddresses(device_iface)]
285 used_addresses += [host_ip]
287 device_ip = _NextUnusedAddress(network, netmask, used_addresses)
288 assert device_ip, ('The network %s on %s is full.' %
289 (host_address, host_iface))
291 host_ip = _Long2Ip(host_ip)
292 device_ip = _Long2Ip(device_ip)
293 netmask = _Long2Ip(netmask)
295 # TODO(szym) run via su -c if necessary.
296 self._adb.RunShellCommand('ifconfig %s %s netmask %s up' %
297 (device_iface, device_ip, netmask))
298 # Enabling the interface sometimes breaks adb.
299 self._WaitForDevice()
300 self._host_iface = host_iface
301 self._host_ip = host_ip
302 self._device_iface = device_iface
303 self._device_ip = device_ip
305 def _TestConnectivity(self):
306 with open(os.devnull, 'wb') as devnull:
307 return subprocess.call(['ping', '-q', '-c1', '-W1',
308 '-I', self._host_iface, self._device_ip],
311 def _CheckConfigureNetwork(self):
312 """Enables RNDIS and configures it, retrying until we have connectivity."""
315 device_iface, host_iface = self._CheckEnableRndis(force)
316 self._ConfigureNetwork(device_iface, host_iface)
317 if self._TestConnectivity():
320 raise Exception('No connectivity, giving up.')
322 def _GetCurrentDns(self):
323 """Returns current gateway, dns1, and dns2."""
324 routes = self._adb.RunShellCommand('cat /proc/net/route')[1:]
325 routes = [route.split() for route in routes]
326 default_routes = [route[0] for route in routes if route[1] == '00000000']
328 default_routes[0] if default_routes else None,
329 self._adb.system_properties['net.dns1'],
330 self._adb.system_properties['net.dns2'],
333 def _OverrideDns(self, iface, dns1, dns2):
334 """Overrides device's DNS configuration.
337 iface: name of the network interface to make default
338 dns1, dns2: nameserver IP addresses
341 return # If there is no route, then nobody cares about DNS.
342 # DNS proxy in older versions of Android is configured via properties.
343 # TODO(szym): run via su -c if necessary.
344 self._adb.system_properties['net.dns1'] = dns1
345 self._adb.system_properties['net.dns2'] = dns2
346 dnschange = self._adb.system_properties['net.dnschange']
348 self._adb.system_properties['net.dnschange'] = int(dnschange) + 1
349 # Since commit 8b47b3601f82f299bb8c135af0639b72b67230e6 to frameworks/base
350 # the net.dns1 properties have been replaced with explicit commands for netd
351 self._adb.RunShellCommand('ndc netd resolver setifdns %s %s %s' %
353 # TODO(szym): if we know the package UID, we could setifaceforuidrange
354 self._adb.RunShellCommand('ndc netd resolver setdefaultif %s' % iface)
362 # localhost and domains which resolve on the host's private network will not
363 # be resolved by the DNS proxy to the HTTP proxy.
364 return 'http://%s:%d' % (self._host_ip, self._host_port)
367 self._OverrideDns(*self._original_dns)