Update sdk/platform-tools to version 26.0.0.
[android_tools.git] / sdk / platform-tools / systrace / catapult / telemetry / telemetry / internal / platform / network_controller_backend.py
blob052be072458065e5a75be8f4007c1a8569d1300d
1 # Copyright 2014 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.
5 import logging
6 import os
7 import shutil
8 import tempfile
10 from telemetry.internal.util import wpr_server
11 from telemetry.internal.util import ts_proxy_server
12 from telemetry.util import wpr_modes
14 import certutils
15 import platformsettings
18 class ArchiveDoesNotExistError(Exception):
19 """Raised when the archive path does not exist for replay mode."""
20 pass
23 class ReplayAndBrowserPortsError(Exception):
24 """Raised an existing browser would get different remote replay ports."""
25 pass
28 class NetworkControllerBackend(object):
29 """Control network settings and servers to simulate the Web.
31 Network changes include forwarding device ports to host platform ports.
32 Web Page Replay is used to record and replay HTTP/HTTPS responses.
33 """
35 def __init__(self, platform_backend):
36 self._platform_backend = platform_backend
37 self._wpr_mode = None
38 self._extra_wpr_args = None
39 self._archive_path = None
40 self._make_javascript_deterministic = None
41 self._forwarder = None
42 self._wpr_ca_cert_path = None
43 self._wpr_server = None
44 self._ts_proxy_server = None
45 self._port_pair = None
46 self._use_live_traffic = None
48 def InitializeIfNeeded(self, use_live_traffic):
49 """
50 This may, e.g., install test certificates and perform any needed setup
51 on the target platform.
53 After network interactions are over, clients should call the Close method.
54 """
55 if self._use_live_traffic is None:
56 self._use_live_traffic = use_live_traffic
57 assert self._use_live_traffic == use_live_traffic, (
58 'inconsistent state of use_live_traffic')
59 assert bool(self._ts_proxy_server) == bool(self._forwarder)
60 if self._ts_proxy_server:
61 return
62 local_port = self._StartTsProxyServer(self._use_live_traffic)
63 self._forwarder = self._platform_backend.forwarder_factory.Create(
64 self._platform_backend.GetPortPairForForwarding(local_port))
66 @property
67 def is_open(self):
68 return self._wpr_mode is not None
70 @property
71 def is_initialized(self):
72 return self._forwarder is not None
74 @property
75 def host_ip(self):
76 return self._platform_backend.forwarder_factory.host_ip
78 @property
79 def wpr_device_ports(self):
80 try:
81 return self._forwarder.port_pairs.remote_ports
82 except AttributeError:
83 return None
85 @property
86 def is_test_ca_installed(self):
87 return self._wpr_ca_cert_path is not None
89 def Open(self, wpr_mode, extra_wpr_args):
90 """Configure and prepare target platform for network control.
92 This may, e.g., install test certificates and perform any needed setup
93 on the target platform.
95 After network interactions are over, clients should call the Close method.
97 Args:
98 wpr_mode: a mode for web page replay; available modes are
99 wpr_modes.WPR_OFF, wpr_modes.APPEND, wpr_modes.WPR_REPLAY, or
100 wpr_modes.WPR_RECORD.
101 extra_wpr_args: an list of extra arguments for web page replay.
103 assert not self.is_open, 'Network controller is already open'
104 self._wpr_mode = wpr_mode
105 self._extra_wpr_args = extra_wpr_args
106 self._InstallTestCa()
108 def Close(self):
109 """Undo changes in the target platform used for network control.
111 Implicitly stops replay if currently active.
113 self.StopReplay()
114 self._StopForwarder()
115 self._StopTsProxyServer()
116 self._RemoveTestCa()
117 self._make_javascript_deterministic = None
118 self._archive_path = None
119 self._extra_wpr_args = None
120 self._wpr_mode = None
122 def _InstallTestCa(self):
123 if not self._platform_backend.supports_test_ca:
124 return
125 assert not self.is_test_ca_installed, 'Test CA is already installed'
126 if certutils.openssl_import_error:
127 logging.warning(
128 'The OpenSSL module is unavailable. '
129 'Browsers may fall back to ignoring certificate errors.')
130 return
131 if not platformsettings.HasSniSupport():
132 logging.warning(
133 'Web Page Replay requires SNI support (pyOpenSSL 0.13 or greater) '
134 'to generate certificates from a test CA. '
135 'Browsers may fall back to ignoring certificate errors.')
136 return
137 self._wpr_ca_cert_path = os.path.join(tempfile.mkdtemp(), 'testca.pem')
138 try:
139 certutils.write_dummy_ca_cert(*certutils.generate_dummy_ca_cert(),
140 cert_path=self._wpr_ca_cert_path)
141 self._platform_backend.InstallTestCa(self._wpr_ca_cert_path)
142 logging.info('Test certificate authority installed on target platform.')
143 except Exception:
144 logging.exception(
145 'Failed to install test certificate authority on target platform. '
146 'Browsers may fall back to ignoring certificate errors.')
147 self._RemoveTestCa()
149 def _RemoveTestCa(self):
150 if not self.is_test_ca_installed:
151 return
152 try:
153 self._platform_backend.RemoveTestCa()
154 except Exception:
155 # Best effort cleanup - show the error and continue.
156 logging.exception(
157 'Error trying to remove certificate authority from target platform.')
158 try:
159 shutil.rmtree(os.path.dirname(self._wpr_ca_cert_path), ignore_errors=True)
160 finally:
161 self._wpr_ca_cert_path = None
163 def StartReplay(self, archive_path, make_javascript_deterministic=False):
164 """Start web page replay from a given replay archive.
166 Starts as needed, and reuses if possible, the replay server on the host and
167 a forwarder from the host to the target platform.
169 Implementation details
170 ----------------------
172 The local host is where Telemetry is run. The remote is host where
173 the target application is run. The local and remote hosts may be
174 the same (e.g., testing a desktop browser) or different (e.g., testing
175 an android browser).
177 A replay server is started on the local host using the local ports, while
178 a forwarder ties the local to the remote ports.
180 Both local and remote ports may be zero. In that case they are determined
181 by the replay server and the forwarder respectively. Setting dns to None
182 disables DNS traffic.
184 Args:
185 archive_path: a path to a specific WPR archive.
186 make_javascript_deterministic: True if replay should inject a script
187 to make JavaScript behave deterministically (e.g., override Date()).
189 assert self.is_open, 'Network controller is not open'
190 if self._wpr_mode == wpr_modes.WPR_OFF:
191 return
192 if not archive_path:
193 # TODO(slamm, tonyg): Ideally, replay mode should be stopped when there is
194 # no archive path. However, if the replay server already started, and
195 # a file URL is tested with the
196 # telemetry.core.local_server.LocalServerController, then the
197 # replay server forwards requests to it. (Chrome is configured to use
198 # fixed ports fo all HTTP/HTTPS requests.)
199 return
200 if (self._wpr_mode == wpr_modes.WPR_REPLAY and
201 not os.path.exists(archive_path)):
202 raise ArchiveDoesNotExistError(
203 'Archive path does not exist: %s' % archive_path)
204 if (self._wpr_server is not None and
205 self._archive_path == archive_path and
206 self._make_javascript_deterministic == make_javascript_deterministic):
207 return # We may reuse the existing replay server.
209 self._archive_path = archive_path
210 self._make_javascript_deterministic = make_javascript_deterministic
211 local_ports = self._StartReplayServer()
212 self._ts_proxy_server.UpdateOutboundPorts(
213 http_port=local_ports.http, https_port=local_ports.https)
215 def _StopForwarder(self):
216 if self._forwarder:
217 self._forwarder.Close()
218 self._forwarder = None
220 def StopReplay(self):
221 """Stop web page replay.
223 Stops both the replay server and the forwarder if currently active.
225 self._StopReplayServer()
227 def _StartReplayServer(self):
228 """Start the replay server and return the started local_ports."""
229 self._StopReplayServer() # In case it was already running.
230 self._wpr_server = wpr_server.ReplayServer(
231 self._archive_path,
232 self.host_ip,
233 http_port=0,
234 https_port=0,
235 dns_port=None,
236 replay_options=self._ReplayCommandLineArgs())
237 return self._wpr_server.StartServer()
239 def _StopReplayServer(self):
240 """Stop the replay server only."""
241 if self._wpr_server:
242 self._wpr_server.StopServer()
243 self._wpr_server = None
245 def _StopTsProxyServer(self):
246 """Stop the replay server only."""
247 if self._ts_proxy_server:
248 self._ts_proxy_server.StopServer()
249 self._ts_proxy_server = None
251 def _ReplayCommandLineArgs(self):
252 wpr_args = list(self._extra_wpr_args)
253 if self._wpr_mode == wpr_modes.WPR_APPEND:
254 wpr_args.append('--append')
255 elif self._wpr_mode == wpr_modes.WPR_RECORD:
256 wpr_args.append('--record')
257 if not self._make_javascript_deterministic:
258 wpr_args.append('--inject_scripts=')
259 if self._wpr_ca_cert_path:
260 wpr_args.extend([
261 '--should_generate_certs',
262 '--https_root_ca_cert_path=%s' % self._wpr_ca_cert_path])
263 return wpr_args
265 def _StartTsProxyServer(self, use_live_traffic):
266 assert not self._ts_proxy_server, 'ts_proxy_server is already started'
267 host_ip = None
268 if not use_live_traffic:
269 host_ip = self.host_ip
270 self._ts_proxy_server = ts_proxy_server.TsProxyServer(host_ip=host_ip)
271 self._ts_proxy_server.StartServer()
272 return self._ts_proxy_server.port
274 @property
275 def forwarder(self):
276 return self._forwarder
278 @property
279 def ts_proxy_server(self):
280 return self._ts_proxy_server