Upstream version 5.34.98.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / backends / android_rndis.py
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.
4 import logging
5 import os
6 import re
7 import socket
8 import struct
9 import subprocess
10 import sys
11
12 from telemetry.core import util
13 from telemetry.core.backends import adb_commands
14
15
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()
21   if retcode:
22     cmd = kwargs.get('args')
23     if cmd is None:
24       cmd = popenargs[0]
25     error = subprocess.CalledProcessError(retcode, cmd)
26     error.output = output
27     raise error
28   return output
29
30
31 class RndisForwarderWithRoot(object):
32   """Forwards traffic using RNDIS. Assuming the device has root access.
33   """
34   _RNDIS_DEVICE = '/sys/class/android_usb/android0'
35   _NETWORK_INTERFACES = '/etc/network/interfaces'
36   _TELEMETRY_MARKER = '# Added by Telemetry #'
37
38   def __init__(self, adb):
39     """Args:
40          adb: an instance of AdbCommands
41     """
42     is_root_enabled = adb.Adb().EnableAdbRoot()
43     assert is_root_enabled, 'RNDIS forwarding requires a rooted device'
44     self._adb = adb.Adb()
45
46     self._host_port = 80
47     self._host_ip = None
48     self._device_ip = None
49     self._host_iface = None
50     self._device_iface = None
51     self._original_dns = None, None, None
52
53     assert self._IsRndisSupported(), 'Device does not have rndis!'
54     self._CheckConfigureNetwork()
55
56   def SetPorts(self, *port_pairs):
57     """Args:
58          port_pairs: Used for compatibility with Forwarder. RNDIS does not
59            support mapping so local_port must match remote_port in all pairs.
60     """
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
64
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)
74
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)
79
80   def _WaitForDevice(self):
81     self._adb.Adb().SendCommand('wait-for-device')
82
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)]
88     if candidates:
89       assert len(candidates) == 1, 'Found more than one rndis device!'
90       return candidates[0]
91
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)
98
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
110
111   def _WriteProtectedFile(self, path, contents):
112     subprocess.check_call(
113         ['sudo', 'bash', '-c', 'echo -e "%s" > %s' % (contents, path)])
114
115   def _DisableRndis(self):
116     self._adb.system_properties['sys.usb.config'] = 'adb'
117     self._WaitForDevice()
118
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.
125     script = """
126 trap '' HUP
127 trap '' TERM
128 trap '' PIPE
129
130 function manual_config() {
131   echo %(functions)s > %(dev)s/functions
132   echo 224 > %(dev)s/bDeviceClass
133   echo 1 > %(dev)s/enable
134   start adbd
135   setprop sys.usb.state %(functions)s
136 }
137
138 # This function kills adb transport, so it has to be run "detached".
139 function doit() {
140   setprop sys.usb.config none
141   while [ `getprop sys.usb.state` != "none" ]; do
142     sleep 1
143   done
144   manual_config
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.
147   #sleep 1
148   #ifconfig rndis0 192.168.42.2 netmask 255.255.255.0 up
149   echo DONE >> %(prefix)s.log
150 }
151
152 doit &
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!'
162
163   def _CheckEnableRndis(self, force):
164     """Enables the RNDIS network interface, retrying if necessary.
165     Args:
166       force: Disable RNDIS first, even if it appears already enabled.
167     Returns:
168       device_iface: RNDIS interface name on the device
169       host_iface: corresponding interface name on the host
170     """
171     for _ in range(3):
172       if not force:
173         device_iface = self._FindDeviceRndisInterface()
174         if device_iface:
175           host_iface = self._FindHostRndisInterface()
176           if host_iface:
177             return device_iface, host_iface
178       self._DisableRndis()
179       self._EnableRndis()
180       force = False
181     raise Exception('Could not enable RNDIS, giving up.')
182
183   def _GetHostAddresses(self, iface):
184     """Returns the IP addresses on host's interfaces, breaking out |iface|."""
185     interface_list = self._EnumerateHostInterfaces()
186     addresses = []
187     iface_address = None
188     found_iface = False
189     for line in interface_list:
190       if not line.startswith(' '):
191         found_iface = iface in line
192       match = re.search('(?<=inet )\S+', line)
193       if match:
194         address = match.group(0)
195         if found_iface:
196           assert not iface_address, (
197             'Found %s twice when parsing host interfaces.' % iface)
198           iface_address = address
199         else:
200           addresses.append(address)
201     return addresses, iface_address
202
203   def _GetDeviceAddresses(self, excluded_iface):
204     """Returns the IP addresses on all connected devices.
205     Excludes interface |excluded_iface| on the selected device.
206     """
207     my_device = self._adb.GetDevice()
208     addresses = []
209     for device in adb_commands.GetAttachedDevices():
210       adb = adb_commands.AdbCommands(device).Adb()
211       if device == my_device:
212         excluded = excluded_iface
213       else:
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]
217     return addresses
218
219   def _ConfigureNetwork(self, device_iface, host_iface):
220     """Configures the |device_iface| to be on the same network as |host_iface|.
221     """
222     def _Ip2Long(addr):
223       return struct.unpack('!L', socket.inet_aton(addr))[0]
224
225     def _Long2Ip(value):
226       return socket.inet_ntoa(struct.pack('!L', value))
227
228     def _Length2Mask(length):
229       return 0xFFFFFFFF & ~((1 << (32 - length)) - 1)
230
231     def _IpPrefix2AddressMask(addr):
232       addr, masklen = addr.split('/')
233       return _Ip2Long(addr), _Length2Mask(int(masklen))
234
235     def _IsNetworkUnique(network, addresses):
236       return all((addr & mask != network & mask) for addr, mask in addresses)
237
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:
243           return candidate
244
245     interfaces = open(self._NETWORK_INTERFACES, 'r').read()
246     if 'auto ' + host_iface not in interfaces:
247       config = ('%(orig)s\n\n'
248                 '%(marker)s\n'
249                 'auto %(iface)s\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'])
257
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)
263
264     addresses, host_address = self._GetHostAddresses(host_iface)
265     assert host_address, 'Interface %s could not be configured.' % host_iface
266
267     addresses = [_IpPrefix2AddressMask(addr) for addr in addresses]
268     host_ip, netmask = _IpPrefix2AddressMask(host_address)
269
270     network = host_ip & netmask
271
272     if not _IsNetworkUnique(network, addresses):
273       logging.warning(
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)
279       )
280
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]
286
287     device_ip = _NextUnusedAddress(network, netmask, used_addresses)
288     assert device_ip, ('The network %s on %s is full.' %
289                        (host_address, host_iface))
290
291     host_ip = _Long2Ip(host_ip)
292     device_ip = _Long2Ip(device_ip)
293     netmask = _Long2Ip(netmask)
294
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
304
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],
309                               stdout=devnull) == 0
310
311   def _CheckConfigureNetwork(self):
312     """Enables RNDIS and configures it, retrying until we have connectivity."""
313     force = False
314     for _ in range(3):
315       device_iface, host_iface = self._CheckEnableRndis(force)
316       self._ConfigureNetwork(device_iface, host_iface)
317       if self._TestConnectivity():
318         return
319       force = True
320     raise Exception('No connectivity, giving up.')
321
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']
327     return (
328       default_routes[0] if default_routes else None,
329       self._adb.system_properties['net.dns1'],
330       self._adb.system_properties['net.dns2'],
331     )
332
333   def _OverrideDns(self, iface, dns1, dns2):
334     """Overrides device's DNS configuration.
335
336     Args:
337       iface: name of the network interface to make default
338       dns1, dns2: nameserver IP addresses
339     """
340     if not iface:
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']
347     if 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' %
352                               (iface, dns1, dns2))
353     # TODO(szym): if we know the package UID, we could setifaceforuidrange
354     self._adb.RunShellCommand('ndc netd resolver setdefaultif %s' % iface)
355
356   @property
357   def host_ip(self):
358     return self._host_ip
359
360   @property
361   def url(self):
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)
365
366   def Close(self):
367     self._OverrideDns(*self._original_dns)
368     self._DisableRndis()