- add sources.
[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.backends import adb_commands
13
14
15 def _CheckOutput(*popenargs, **kwargs):
16   """Backport of subprocess.check_output to python 2.6"""
17   process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
18   output, _ = process.communicate()
19   retcode = process.poll()
20   if retcode:
21     cmd = kwargs.get('args')
22     if cmd is None:
23       cmd = popenargs[0]
24     error = subprocess.CalledProcessError(retcode, cmd)
25     error.output = output
26     raise error
27   return output
28
29
30 class RndisForwarderWithRoot(object):
31   """Forwards traffic using RNDIS. Assuming the device has root access.
32   """
33   _RNDIS_DEVICE = '/sys/class/android_usb/android0'
34
35   def __init__(self, adb):
36     """Args:
37          adb: an instance of AdbCommands
38     """
39     is_root_enabled = adb.Adb().EnableAdbRoot()
40     assert is_root_enabled, 'RNDIS forwarding could not enable root'
41     self._adb = adb.Adb()
42
43     self._host_port = 80
44     self._host_ip = None
45     self._device_ip = None
46     self._host_iface = None
47     self._device_iface = None
48     self._original_dns = None, None, None
49
50     assert self._IsRndisSupported(), 'Device does not have rndis!'
51     self._CheckConfigureNetwork()
52
53   def SetPorts(self, *port_pairs):
54     """Args:
55          port_pairs: Used for compatibility with Forwarder. RNDIS does not
56            support mapping so local_port must match remote_port in all pairs.
57     """
58     assert all(pair.remote_port == pair.local_port for pair in port_pairs), \
59            'Local and remote ports must be the same on all pairs with RNDIS.'
60     self._host_port = port_pairs[0].local_port
61
62   def OverrideDns(self):
63     """Overrides DNS on device to point at the host."""
64     self._original_dns = self._GetCurrentDns()
65     self._OverrideDns(self._device_iface, self._host_ip, self._host_ip)
66
67   def _IsRndisSupported(self):
68     """Checks that the device has RNDIS support in the kernel."""
69     return self._adb.FileExistsOnDevice(
70         '%s/f_rndis/device' % self._RNDIS_DEVICE)
71
72   def _WaitForDevice(self):
73     self._adb.Adb().SendCommand('wait-for-device')
74
75   def _FindDeviceRndisInterface(self):
76     """Returns the name of the RNDIS network interface if present."""
77     config = self._adb.RunShellCommand('netcfg')
78     interfaces = [line.split()[0] for line in config]
79     candidates = [iface for iface in interfaces if re.match('rndis|usb', iface)]
80     if candidates:
81       assert len(candidates) == 1, 'Found more than one rndis device!'
82       return candidates[0]
83
84   def _EnumerateHostInterfaces(self):
85     if sys.platform.startswith('linux'):
86       return _CheckOutput(['ip', 'addr']).splitlines()
87     elif sys.platform == 'darwin':
88       return _CheckOutput(['ifconfig']).splitlines()
89     raise Exception('Platform %s not supported!' % sys.platform)
90
91   def _FindHostRndisInterface(self):
92     """Returns the name of the host-side network interface."""
93     interface_list = self._EnumerateHostInterfaces()
94     ether_address = self._adb.GetFileContents(
95         '%s/f_rndis/ethaddr' % self._RNDIS_DEVICE)[0]
96     interface_name = None
97     for line in interface_list:
98       if not line.startswith(' '):
99         interface_name = line.split()[1].strip(':')
100       elif ether_address in line:
101         return interface_name
102
103   def _DisableRndis(self):
104     self._adb.RunShellCommand('setprop sys.usb.config adb')
105     self._WaitForDevice()
106
107   def _EnableRndis(self):
108     """Enables the RNDIS network interface."""
109     script_prefix = '/data/local/tmp/rndis'
110     # This could be accomplished via "svc usb setFunction rndis" but only on
111     # devices which have the "USB tethering" feature.
112     # Also, on some devices, it's necessary to go through "none" function.
113     script = """
114 trap '' HUP
115 trap '' TERM
116 trap '' PIPE
117
118 function manual_config() {
119   echo %(functions)s > %(dev)s/functions
120   echo 224 > %(dev)s/bDeviceClass
121   echo 1 > %(dev)s/enable
122   start adbd
123   setprop sys.usb.state %(functions)s
124 }
125
126 # This function kills adb transport, so it has to be run "detached".
127 function doit() {
128   setprop sys.usb.config none
129   while [ `getprop sys.usb.state` != "none" ]; do
130     sleep 1
131   done
132   manual_config
133   # For some combinations of devices and host kernels, adb won't work unless the
134   # interface is up, but if we bring it up immediately, it will break adb.
135   #sleep 1
136   #ifconfig rndis0 192.168.42.2 netmask 255.255.255.0 up
137   echo DONE >> %(prefix)s.log
138 }
139
140 doit &
141     """ % {'dev': self._RNDIS_DEVICE, 'functions': 'rndis,adb',
142            'prefix': script_prefix }
143     self._adb.SetFileContents('%s.sh' % script_prefix, script)
144     # TODO(szym): run via su -c if necessary.
145     self._adb.RunShellCommand('rm %s.log' % script_prefix)
146     self._adb.RunShellCommand('. %s.sh' % script_prefix)
147     self._WaitForDevice()
148     result = self._adb.GetFileContents('%s.log' % script_prefix)
149     assert any('DONE' in line for line in result), 'RNDIS script did not run!'
150
151   def _CheckEnableRndis(self, force):
152     """Enables the RNDIS network interface, retrying if necessary.
153     Args:
154       force: Disable RNDIS first, even if it appears already enabled.
155     Returns:
156       device_iface: RNDIS interface name on the device
157       host_iface: corresponding interface name on the host
158     """
159     for _ in range(3):
160       if not force:
161         device_iface = self._FindDeviceRndisInterface()
162         if device_iface:
163           host_iface = self._FindHostRndisInterface()
164           if host_iface:
165             return device_iface, host_iface
166       self._DisableRndis()
167       self._EnableRndis()
168       force = False
169     raise Exception('Could not enable RNDIS, giving up.')
170
171   def _GetHostAddresses(self, iface):
172     """Returns the IP addresses on host's interfaces, breaking out |iface|."""
173     interface_list = self._EnumerateHostInterfaces()
174     addresses = []
175     iface_address = None
176     found_iface = False
177     for line in interface_list:
178       if not line.startswith(' '):
179         found_iface = iface in line
180       match = re.search('(?<=inet )\S+', line)
181       if match:
182         address = match.group(0)
183         if found_iface:
184           assert not iface_address, (
185             'Found %s twice when parsing host interfaces.' % iface)
186           iface_address = address
187         else:
188           addresses.append(address)
189     return addresses, iface_address
190
191   def _GetDeviceAddresses(self, excluded_iface):
192     """Returns the IP addresses on all connected devices.
193     Excludes interface |excluded_iface| on the selected device.
194     """
195     my_device = self._adb.GetDevice()
196     addresses = []
197     for device in adb_commands.GetAttachedDevices():
198       adb = adb_commands.AdbCommands(device).Adb()
199       if device == my_device:
200         excluded = excluded_iface
201       else:
202         excluded = 'no interfaces excluded on other devices'
203       addresses += [line.split()[2] for line in adb.RunShellCommand('netcfg')
204                     if excluded not in line]
205     return addresses
206
207   def _ConfigureNetwork(self, device_iface, host_iface):
208     """Configures the |device_iface| to be on the same network as |host_iface|.
209     """
210     def _Ip2Long(addr):
211       return struct.unpack('!L', socket.inet_aton(addr))[0]
212
213     def _Long2Ip(value):
214       return socket.inet_ntoa(struct.pack('!L', value))
215
216     def _Length2Mask(length):
217       return 0xFFFFFFFF & ~((1 << (32 - length)) - 1)
218
219     def _IpPrefix2AddressMask(addr):
220       addr, masklen = addr.split('/')
221       return _Ip2Long(addr), _Length2Mask(int(masklen))
222
223     def _IsNetworkUnique(network, addresses):
224       return all((addr & mask != network & mask) for addr, mask in addresses)
225
226     def _NextUnusedAddress(network, netmask, used_addresses):
227       # Excludes '0' and broadcast.
228       for suffix in range(1, 0xFFFFFFFF & ~netmask):
229         candidate = network | suffix
230         if candidate not in used_addresses:
231           return candidate
232
233     addresses, host_address = self._GetHostAddresses(host_iface)
234
235     assert host_address, ('Interface %(iface)s was not configured.\n'
236       'To configure it automatically, add to /etc/network/interfaces:\n'
237       'auto %(iface)s\n'
238       'iface %(iface)s inet static\n'
239       '  address 192.168.<unique>.1\n'
240       '  netmask 255.255.255.0' % {'iface': host_iface})
241
242     addresses = [_IpPrefix2AddressMask(addr) for addr in addresses]
243     host_ip, netmask = _IpPrefix2AddressMask(host_address)
244
245     network = host_ip & netmask
246
247     if not _IsNetworkUnique(network, addresses):
248       logging.warning(
249         'The IP address configuration %s of %s is not unique!\n'
250         'Check your /etc/network/interfaces. If this overlap is intended,\n'
251         'you might need to use: ip rule add from <device_ip> lookup <table>\n'
252         'or add the interface to a bridge in order to route to this network.'
253         % (host_address, host_iface)
254       )
255
256     # Find unused IP address.
257     used_addresses = [addr for addr, _ in addresses]
258     used_addresses += [_IpPrefix2AddressMask(addr)[0]
259                        for addr in self._GetDeviceAddresses(device_iface)]
260     used_addresses += [host_ip]
261
262     device_ip = _NextUnusedAddress(network, netmask, used_addresses)
263     assert device_ip, ('The network %s on %s is full.' %
264                        (host_address, host_iface))
265
266     host_ip = _Long2Ip(host_ip)
267     device_ip = _Long2Ip(device_ip)
268     netmask = _Long2Ip(netmask)
269
270     # TODO(szym) run via su -c if necessary.
271     self._adb.RunShellCommand('ifconfig %s %s netmask %s up' %
272                               (device_iface, device_ip, netmask))
273     # Enabling the interface sometimes breaks adb.
274     self._WaitForDevice()
275     self._host_iface = host_iface
276     self._host_ip = host_ip
277     self._device_iface = device_iface
278     self._device_ip = device_ip
279
280   def _TestConnectivity(self):
281     with open(os.devnull, 'wb') as devnull:
282       return subprocess.call(['ping', '-q', '-c1', '-W1',
283                               '-I', self._host_iface, self._device_ip],
284                               stdout=devnull) == 0
285
286   def _CheckConfigureNetwork(self):
287     """Enables RNDIS and configures it, retrying until we have connectivity."""
288     force = False
289     for _ in range(3):
290       device_iface, host_iface = self._CheckEnableRndis(force)
291       self._ConfigureNetwork(device_iface, host_iface)
292       if self._TestConnectivity():
293         return
294       force = True
295     raise Exception('No connectivity, giving up.')
296
297   def _GetCurrentDns(self):
298     """Returns current gateway, dns1, and dns2."""
299     routes = self._adb.RunShellCommand('cat /proc/net/route')[1:]
300     routes = [route.split() for route in routes]
301     default_routes = [route[0] for route in routes if route[1] == '00000000']
302     return (
303       default_routes[0] if default_routes else None,
304       self._adb.RunShellCommand('getprop net.dns1')[0],
305       self._adb.RunShellCommand('getprop net.dns2')[0],
306     )
307
308   def _OverrideDns(self, iface, dns1, dns2):
309     """Overrides device's DNS configuration.
310
311     Args:
312       iface: name of the network interface to make default
313       dns1, dns2: nameserver IP addresses
314     """
315     if not iface:
316       return  # If there is no route, then nobody cares about DNS.
317     # DNS proxy in older versions of Android is configured via properties.
318     # TODO(szym): run via su -c if necessary.
319     self._adb.RunShellCommand('setprop net.dns1 ' + dns1)
320     self._adb.RunShellCommand('setprop net.dns2 ' + dns2)
321     dnschange = self._adb.RunShellCommand('getprop net.dnschange')[0]
322     if dnschange:
323       self._adb.RunShellCommand('setprop net.dnschange %s' %
324                                 (int(dnschange) + 1))
325     # Since commit 8b47b3601f82f299bb8c135af0639b72b67230e6 to frameworks/base
326     # the net.dns1 properties have been replaced with explicit commands for netd
327     self._adb.RunShellCommand('ndc netd resolver setifdns %s %s %s' %
328                               (iface, dns1, dns2))
329     # TODO(szym): if we know the package UID, we could setifaceforuidrange
330     self._adb.RunShellCommand('ndc netd resolver setdefaultif %s' % iface)
331
332   @property
333   def host_ip(self):
334     return self._host_ip
335
336   @property
337   def url(self):
338     # localhost and domains which resolve on the host's private network will not
339     # be resolved by the DNS proxy to the HTTP proxy.
340     return 'http://%s:%d' % (self._host_ip, self._host_port)
341
342   def Close(self):
343     self._OverrideDns(*self._original_dns)