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.
10 from telemetry
.internal
.util
import wpr_server
11 from telemetry
.internal
.util
import ts_proxy_server
12 from telemetry
.util
import wpr_modes
15 import platformsettings
18 class ArchiveDoesNotExistError(Exception):
19 """Raised when the archive path does not exist for replay mode."""
23 class ReplayAndBrowserPortsError(Exception):
24 """Raised an existing browser would get different remote replay ports."""
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.
35 def __init__(self
, platform_backend
):
36 self
._platform
_backend
= platform_backend
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
):
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.
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
:
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
))
68 return self
._wpr
_mode
is not None
71 def is_initialized(self
):
72 return self
._forwarder
is not None
76 return self
._platform
_backend
.forwarder_factory
.host_ip
79 def wpr_device_ports(self
):
81 return self
._forwarder
.port_pairs
.remote_ports
82 except AttributeError:
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.
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
()
109 """Undo changes in the target platform used for network control.
111 Implicitly stops replay if currently active.
114 self
._StopForwarder
()
115 self
._StopTsProxyServer
()
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
:
125 assert not self
.is_test_ca_installed
, 'Test CA is already installed'
126 if certutils
.openssl_import_error
:
128 'The OpenSSL module is unavailable. '
129 'Browsers may fall back to ignoring certificate errors.')
131 if not platformsettings
.HasSniSupport():
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.')
137 self
._wpr
_ca
_cert
_path
= os
.path
.join(tempfile
.mkdtemp(), 'testca.pem')
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.')
145 'Failed to install test certificate authority on target platform. '
146 'Browsers may fall back to ignoring certificate errors.')
149 def _RemoveTestCa(self
):
150 if not self
.is_test_ca_installed
:
153 self
._platform
_backend
.RemoveTestCa()
155 # Best effort cleanup - show the error and continue.
157 'Error trying to remove certificate authority from target platform.')
159 shutil
.rmtree(os
.path
.dirname(self
._wpr
_ca
_cert
_path
), ignore_errors
=True)
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
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.
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
:
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.)
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
):
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(
236 replay_options
=self
._ReplayCommandLineArgs
())
237 return self
._wpr
_server
.StartServer()
239 def _StopReplayServer(self
):
240 """Stop the replay server only."""
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
:
261 '--should_generate_certs',
262 '--https_root_ca_cert_path=%s' % self
._wpr
_ca
_cert
_path
])
265 def _StartTsProxyServer(self
, use_live_traffic
):
266 assert not self
._ts
_proxy
_server
, 'ts_proxy_server is already started'
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
276 return self
._forwarder
279 def ts_proxy_server(self
):
280 return self
._ts
_proxy
_server