1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/http/http_proxy_connect_job.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <string>
10 #include <utility>
11
12 #include "base/metrics/field_trial.h"
13 #include "base/metrics/field_trial_param_associator.h"
14 #include "base/metrics/field_trial_params.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/test/metrics/histogram_tester.h"
20 #include "base/test/task_environment.h"
21 #include "base/time/time.h"
22 #include "build/build_config.h"
23 #include "net/base/host_port_pair.h"
24 #include "net/base/network_anonymization_key.h"
25 #include "net/base/proxy_chain.h"
26 #include "net/base/proxy_string_util.h"
27 #include "net/base/session_usage.h"
28 #include "net/base/test_proxy_delegate.h"
29 #include "net/cert/mock_cert_verifier.h"
30 #include "net/dns/mock_host_resolver.h"
31 #include "net/dns/public/secure_dns_policy.h"
32 #include "net/http/http_network_session.h"
33 #include "net/http/http_response_headers.h"
34 #include "net/http/http_server_properties.h"
35 #include "net/http/transport_security_state.h"
36 #include "net/nqe/network_quality_estimator_test_util.h"
37 #include "net/quic/quic_context.h"
38 #include "net/quic/quic_session_pool.h"
39 #include "net/socket/client_socket_handle.h"
40 #include "net/socket/connect_job_test_util.h"
41 #include "net/socket/socket_test_util.h"
42 #include "net/socket/socks_connect_job.h"
43 #include "net/socket/ssl_client_socket.h"
44 #include "net/socket/ssl_connect_job.h"
45 #include "net/socket/transport_connect_job.h"
46 #include "net/spdy/spdy_test_util_common.h"
47 #include "net/test/cert_test_util.h"
48 #include "net/test/gtest_util.h"
49 #include "net/test/test_data_directory.h"
50 #include "net/test/test_with_task_environment.h"
51 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
52 #include "testing/gmock/include/gmock/gmock.h"
53 #include "testing/gtest/include/gtest/gtest.h"
54 #include "url/gurl.h"
55 #include "url/scheme_host_port.h"
56
57 using ::testing::_;
58
59 namespace net {
60
61 namespace {
62
63 const char kEndpointHost[] = "www.endpoint.test";
64
65 enum HttpProxyType { HTTP, HTTPS, SPDY };
66
67 const char kHttpProxyHost[] = "httpproxy.example.test";
68 const char kHttpsProxyHost[] = "httpsproxy.example.test";
69 const char kQuicProxyHost[] = "quicproxy.example.test";
70 const char kHttpsNestedProxyHost[] = "last-hop-https-proxy.example.test";
71
72 const ProxyServer kHttpProxyServer{ProxyServer::SCHEME_HTTP,
73 HostPortPair(kHttpProxyHost, 80)};
74 const ProxyServer kHttpsProxyServer{ProxyServer::SCHEME_HTTPS,
75 HostPortPair(kHttpsProxyHost, 443)};
76 const ProxyServer kHttpsNestedProxyServer{
77 ProxyServer::SCHEME_HTTPS, HostPortPair(kHttpsNestedProxyHost, 443)};
78
79 const ProxyChain kHttpProxyChain{kHttpProxyServer};
80 const ProxyChain kHttpsProxyChain{kHttpsProxyServer};
81 const ProxyChain kHttpsNestedProxyChain{
82 {kHttpsProxyServer, kHttpsNestedProxyServer}};
83
84 constexpr char kTestHeaderName[] = "Foo";
85 // Note: `kTestSpdyHeaderName` should be a lowercase version of
86 // `kTestHeaderName`.
87 constexpr char kTestSpdyHeaderName[] = "foo";
88
89 // Match QuicStreamRequests' proxy chains.
90 MATCHER_P(QSRHasProxyChain,
91 proxy_chain,
92 base::StringPrintf("QuicStreamRequest %s ProxyChain %s",
93 negation ? "does not have" : "has",
94 proxy_chain.ToDebugString().c_str())) {
95 *result_listener << "where the proxy chain is "
96 << arg->session_key().proxy_chain().ToDebugString();
97 return arg->session_key().proxy_chain() == proxy_chain;
98 }
99
100 } // namespace
101
102 class HttpProxyConnectJobTestBase : public WithTaskEnvironment {
103 public:
HttpProxyConnectJobTestBase()104 HttpProxyConnectJobTestBase()
105 : WithTaskEnvironment(
106 base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
107 // Used a mock HostResolver that does not have a cache.
108 session_deps_.host_resolver = std::make_unique<MockHostResolver>(
109 /*default_result=*/MockHostResolverBase::RuleResolver::
110 GetLocalhostResult());
111
112 network_quality_estimator_ =
113 std::make_unique<TestNetworkQualityEstimator>();
114 session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
115 InitCommonConnectJobParams();
116 }
117
~HttpProxyConnectJobTestBase()118 virtual ~HttpProxyConnectJobTestBase() {
119 // Reset global field trial parameters to defaults values.
120 base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
121 HttpProxyConnectJob::UpdateFieldTrialParametersForTesting();
122 }
123
124 // This may only be called at the start of the test, before any ConnectJobs
125 // have been created.
InitCommonConnectJobParams()126 void InitCommonConnectJobParams() {
127 common_connect_job_params_ = std::make_unique<CommonConnectJobParams>(
128 session_->CreateCommonConnectJobParams());
129 // TODO(mmenke): Consider reworking this so it can be done through
130 // |session_deps_|.
131 common_connect_job_params_->proxy_delegate = proxy_delegate_.get();
132 common_connect_job_params_->network_quality_estimator =
133 network_quality_estimator_.get();
134 }
135
136 // This may only be called at the start of the test, before any ConnectJobs
137 // have been created.
InitProxyDelegate()138 void InitProxyDelegate() {
139 proxy_delegate_ = std::make_unique<TestProxyDelegate>();
140 proxy_delegate_->set_extra_header_name(kTestHeaderName);
141 InitCommonConnectJobParams();
142 }
143
144 protected:
145 std::unique_ptr<TestProxyDelegate> proxy_delegate_;
146
147 // These data providers may be pointed to by the socket factory in
148 // `session_deps_`.
149 std::unique_ptr<SSLSocketDataProvider> ssl_data_;
150 std::unique_ptr<SSLSocketDataProvider> old_ssl_data_;
151 std::unique_ptr<SSLSocketDataProvider> nested_second_proxy_ssl_data_;
152 std::unique_ptr<SequencedSocketData> data_;
153
154 SpdySessionDependencies session_deps_;
155 std::unique_ptr<HttpNetworkSession> session_;
156 std::unique_ptr<TestNetworkQualityEstimator> network_quality_estimator_;
157 std::unique_ptr<CommonConnectJobParams> common_connect_job_params_;
158 };
159
160 class HttpProxyConnectJobTest : public HttpProxyConnectJobTestBase,
161 public ::testing::TestWithParam<HttpProxyType> {
162 public:
163 // Initializes the field trial parameters for the field trial that determines
164 // connection timeout based on the network quality.
InitAdaptiveTimeoutFieldTrialWithParams(bool use_default_params,int ssl_http_rtt_multiplier,int non_ssl_http_rtt_multiplier,base::TimeDelta min_proxy_connection_timeout,base::TimeDelta max_proxy_connection_timeout)165 void InitAdaptiveTimeoutFieldTrialWithParams(
166 bool use_default_params,
167 int ssl_http_rtt_multiplier,
168 int non_ssl_http_rtt_multiplier,
169 base::TimeDelta min_proxy_connection_timeout,
170 base::TimeDelta max_proxy_connection_timeout) {
171 std::string trial_name = "NetAdaptiveProxyConnectionTimeout";
172 std::string group_name = "GroupName";
173
174 std::map<std::string, std::string> params;
175 if (!use_default_params) {
176 params["ssl_http_rtt_multiplier"] =
177 base::NumberToString(ssl_http_rtt_multiplier);
178 params["non_ssl_http_rtt_multiplier"] =
179 base::NumberToString(non_ssl_http_rtt_multiplier);
180 params["min_proxy_connection_timeout_seconds"] =
181 base::NumberToString(min_proxy_connection_timeout.InSeconds());
182 params["max_proxy_connection_timeout_seconds"] =
183 base::NumberToString(max_proxy_connection_timeout.InSeconds());
184 }
185 base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
186 EXPECT_TRUE(
187 base::AssociateFieldTrialParams(trial_name, group_name, params));
188 EXPECT_TRUE(base::FieldTrialList::CreateFieldTrial(trial_name, group_name));
189
190 // Force static global that reads the field trials to update.
191 HttpProxyConnectJob::UpdateFieldTrialParametersForTesting();
192 }
193
CreateHttpProxyParams(SecureDnsPolicy secure_dns_policy) const194 scoped_refptr<TransportSocketParams> CreateHttpProxyParams(
195 SecureDnsPolicy secure_dns_policy) const {
196 if (GetParam() != HTTP) {
197 return nullptr;
198 }
199 return base::MakeRefCounted<TransportSocketParams>(
200 kHttpProxyServer.host_port_pair(), NetworkAnonymizationKey(),
201 secure_dns_policy, OnHostResolutionCallback(),
202 /*supported_alpns=*/base::flat_set<std::string>());
203 }
204
CreateHttpsProxyParams(SecureDnsPolicy secure_dns_policy) const205 scoped_refptr<SSLSocketParams> CreateHttpsProxyParams(
206 SecureDnsPolicy secure_dns_policy) const {
207 if (GetParam() == HTTP) {
208 return nullptr;
209 }
210 return base::MakeRefCounted<SSLSocketParams>(
211 ConnectJobParams(base::MakeRefCounted<TransportSocketParams>(
212 kHttpsProxyServer.host_port_pair(), NetworkAnonymizationKey(),
213 secure_dns_policy, OnHostResolutionCallback(),
214 /*supported_alpns=*/base::flat_set<std::string>())),
215 HostPortPair(kHttpsProxyHost, 443), SSLConfig(),
216 NetworkAnonymizationKey());
217 }
218
219 // Returns a correctly constructed HttpProxyParams for a single HTTP or HTTPS
220 // proxy.
CreateParams(bool tunnel,SecureDnsPolicy secure_dns_policy)221 scoped_refptr<HttpProxySocketParams> CreateParams(
222 bool tunnel,
223 SecureDnsPolicy secure_dns_policy) {
224 ConnectJobParams params;
225 if (GetParam() == HTTP) {
226 params = ConnectJobParams(CreateHttpProxyParams(secure_dns_policy));
227 } else {
228 params = ConnectJobParams(CreateHttpsProxyParams(secure_dns_policy));
229 }
230 return base::MakeRefCounted<HttpProxySocketParams>(
231 std::move(params), HostPortPair(kEndpointHost, tunnel ? 443 : 80),
232 GetParam() == HTTP ? kHttpProxyChain : kHttpsProxyChain,
233 /*proxy_chain_index=*/0, tunnel, TRAFFIC_ANNOTATION_FOR_TESTS,
234 NetworkAnonymizationKey(), secure_dns_policy);
235 }
236
237 // Creates a correctly constructed `SSLSocketParams()` corresponding to the
238 // proxy server in `proxy_chain` at index `proxy_chain_index`.
CreateNestedHttpsProxyParams(bool tunnel,SecureDnsPolicy secure_dns_policy,const ProxyChain & proxy_chain,size_t proxy_chain_index) const239 scoped_refptr<SSLSocketParams> CreateNestedHttpsProxyParams(
240 bool tunnel,
241 SecureDnsPolicy secure_dns_policy,
242 const ProxyChain& proxy_chain,
243 size_t proxy_chain_index) const {
244 DCHECK_NE(GetParam(), HTTP);
245
246 const ProxyServer& proxy_server =
247 proxy_chain.GetProxyServer(proxy_chain_index);
248
249 if (proxy_chain_index != 0) {
250 // For all but the first hop in a multi-hop proxy, the SSLSocketParams
251 // should be created such that it tunnels over a direct encrypted
252 // connection made to the first hop (possibly via intermediate tunnels
253 // through other hops)... Build an HttpProxySocketParams for the
254 // previous hop that will establish this.
255 size_t previous_hop_proxy_chain_index = proxy_chain_index - 1;
256
257 return base::MakeRefCounted<SSLSocketParams>(
258 ConnectJobParams(CreateNestedParams(tunnel, secure_dns_policy,
259 proxy_chain,
260 previous_hop_proxy_chain_index)),
261 proxy_server.host_port_pair(), SSLConfig(),
262 NetworkAnonymizationKey());
263 }
264
265 // If we are creating the SSLSocketParams for the first hop, establish a
266 // direct encrypted connection to it.
267 return base::MakeRefCounted<SSLSocketParams>(
268 ConnectJobParams(base::MakeRefCounted<TransportSocketParams>(
269 proxy_server.host_port_pair(), NetworkAnonymizationKey(),
270 secure_dns_policy, OnHostResolutionCallback(),
271 /*supported_alpns=*/base::flat_set<std::string>())),
272 proxy_server.host_port_pair(), SSLConfig(), NetworkAnonymizationKey());
273 }
274
275 // Creates a correctly constructed `HttpProxySocketParams()` corresponding to
276 // the proxy server in `proxy_chain` at index `proxy_chain_index` (and set to
277 // create a CONNECT for either the next hop in the proxy or to
278 // `kEndpointHost`).
CreateNestedParams(bool tunnel,SecureDnsPolicy secure_dns_policy,const ProxyChain & proxy_chain,size_t proxy_chain_index) const279 scoped_refptr<HttpProxySocketParams> CreateNestedParams(
280 bool tunnel,
281 SecureDnsPolicy secure_dns_policy,
282 const ProxyChain& proxy_chain,
283 size_t proxy_chain_index) const {
284 DCHECK_NE(GetParam(), HTTP);
285 HostPortPair connect_host_port_pair;
286 scoped_refptr<SSLSocketParams> ssl_params = CreateNestedHttpsProxyParams(
287 tunnel, secure_dns_policy, proxy_chain, proxy_chain_index);
288 if (proxy_chain_index + 1 != proxy_chain.length()) {
289 // For all but the last hop in the proxy, what we CONNECT to is the next
290 // hop in the proxy.
291 size_t next_hop_proxy_chain_index = proxy_chain_index + 1;
292 const ProxyServer& next_hop_proxy_server =
293 proxy_chain.GetProxyServer(next_hop_proxy_chain_index);
294 connect_host_port_pair = next_hop_proxy_server.host_port_pair();
295 } else {
296 // If we aren't testing multi-hop proxies or this HttpProxySocketParams
297 // corresponds to the last hop, then we need to CONNECT to the
298 // destination site.
299 connect_host_port_pair = HostPortPair(kEndpointHost, tunnel ? 443 : 80);
300 }
301 return base::MakeRefCounted<HttpProxySocketParams>(
302 ConnectJobParams(std::move(ssl_params)), connect_host_port_pair,
303 proxy_chain, proxy_chain_index, tunnel, TRAFFIC_ANNOTATION_FOR_TESTS,
304 NetworkAnonymizationKey(), secure_dns_policy);
305 }
306
CreateConnectJobForHttpRequest(ConnectJob::Delegate * delegate,RequestPriority priority=DEFAULT_PRIORITY,SecureDnsPolicy secure_dns_policy=SecureDnsPolicy::kAllow)307 std::unique_ptr<HttpProxyConnectJob> CreateConnectJobForHttpRequest(
308 ConnectJob::Delegate* delegate,
309 RequestPriority priority = DEFAULT_PRIORITY,
310 SecureDnsPolicy secure_dns_policy = SecureDnsPolicy::kAllow) {
311 return CreateConnectJob(CreateParams(false /* tunnel */, secure_dns_policy),
312 delegate, priority);
313 }
314
CreateConnectJobForTunnel(ConnectJob::Delegate * delegate,RequestPriority priority=DEFAULT_PRIORITY,SecureDnsPolicy secure_dns_policy=SecureDnsPolicy::kAllow)315 std::unique_ptr<HttpProxyConnectJob> CreateConnectJobForTunnel(
316 ConnectJob::Delegate* delegate,
317 RequestPriority priority = DEFAULT_PRIORITY,
318 SecureDnsPolicy secure_dns_policy = SecureDnsPolicy::kAllow) {
319 return CreateConnectJob(CreateParams(true /* tunnel */, secure_dns_policy),
320 delegate, priority);
321 }
322
323 // Creates an HttpProxyConnectJob corresponding to `kHttpsNestedProxyChain`.
324 // This is done by working backwards through the proxy chain and creating
325 // socket params such that connect jobs will be created recursively with
326 // dependencies in the correct order (in other words, the inner-most connect
327 // job will establish a connection to the first proxy, and then that
328 // connection will get used to establish a connection to the second proxy, and
329 // finally a connection will be established to the destination).
CreateConnectJobForNestedProxyTunnel(ConnectJob::Delegate * delegate,RequestPriority priority=DEFAULT_PRIORITY,SecureDnsPolicy secure_dns_policy=SecureDnsPolicy::kAllow)330 std::unique_ptr<HttpProxyConnectJob> CreateConnectJobForNestedProxyTunnel(
331 ConnectJob::Delegate* delegate,
332 RequestPriority priority = DEFAULT_PRIORITY,
333 SecureDnsPolicy secure_dns_policy = SecureDnsPolicy::kAllow) {
334 size_t last_hop_proxy_server_index = kHttpsNestedProxyChain.length() - 1;
335 return CreateConnectJob(
336 CreateNestedParams(/*tunnel=*/true, secure_dns_policy,
337 kHttpsNestedProxyChain, last_hop_proxy_server_index),
338 delegate, priority);
339 }
340
CreateConnectJob(scoped_refptr<HttpProxySocketParams> http_proxy_socket_params,ConnectJob::Delegate * delegate,RequestPriority priority)341 std::unique_ptr<HttpProxyConnectJob> CreateConnectJob(
342 scoped_refptr<HttpProxySocketParams> http_proxy_socket_params,
343 ConnectJob::Delegate* delegate,
344 RequestPriority priority) {
345 return std::make_unique<HttpProxyConnectJob>(
346 priority, SocketTag(), common_connect_job_params_.get(),
347 std::move(http_proxy_socket_params), delegate, /*net_log=*/nullptr);
348 }
349
Initialize(base::span<const MockRead> reads,base::span<const MockWrite> writes,base::span<const MockRead> spdy_reads,base::span<const MockWrite> spdy_writes,IoMode connect_and_ssl_io_mode,bool two_ssl_proxies=false)350 void Initialize(base::span<const MockRead> reads,
351 base::span<const MockWrite> writes,
352 base::span<const MockRead> spdy_reads,
353 base::span<const MockWrite> spdy_writes,
354 IoMode connect_and_ssl_io_mode,
355 bool two_ssl_proxies = false) {
356 if (GetParam() == SPDY) {
357 data_ = std::make_unique<SequencedSocketData>(spdy_reads, spdy_writes);
358 } else {
359 data_ = std::make_unique<SequencedSocketData>(reads, writes);
360 }
361
362 data_->set_connect_data(MockConnect(connect_and_ssl_io_mode, OK));
363
364 session_deps_.socket_factory->AddSocketDataProvider(data_.get());
365
366 if (GetParam() != HTTP) {
367 // Keep the old ssl_data in case there is a draining socket.
368 old_ssl_data_.swap(ssl_data_);
369 ssl_data_ =
370 std::make_unique<SSLSocketDataProvider>(connect_and_ssl_io_mode, OK);
371 if (GetParam() == SPDY) {
372 InitializeSpdySsl(ssl_data_.get());
373 }
374 session_deps_.socket_factory->AddSSLSocketDataProvider(ssl_data_.get());
375 }
376
377 if (two_ssl_proxies) {
378 // For testing nested proxies we need another SSLSocketDataProvider
379 // corresponding to the SSL connection established to the second hop in
380 // the proxy.
381 nested_second_proxy_ssl_data_ =
382 std::make_unique<SSLSocketDataProvider>(connect_and_ssl_io_mode, OK);
383 if (GetParam() == SPDY) {
384 InitializeSpdySsl(nested_second_proxy_ssl_data_.get());
385 }
386 session_deps_.socket_factory->AddSSLSocketDataProvider(
387 nested_second_proxy_ssl_data_.get());
388 }
389 }
390
InitializeSpdySsl(SSLSocketDataProvider * ssl_data)391 void InitializeSpdySsl(SSLSocketDataProvider* ssl_data) {
392 ssl_data->next_proto = kProtoHTTP2;
393 }
394
395 // Return the timeout for establishing the lower layer connection. i.e., for
396 // an HTTP proxy, the TCP connection timeout, and for an HTTPS proxy, the
397 // TCP+SSL connection timeout. In many cases, this will return the return
398 // value of the "AlternateNestedConnectionTimeout()".
GetNestedConnectionTimeout()399 base::TimeDelta GetNestedConnectionTimeout() {
400 base::TimeDelta normal_nested_connection_timeout =
401 TransportConnectJob::ConnectionTimeout();
402 if (GetParam() != HTTP) {
403 normal_nested_connection_timeout +=
404 SSLConnectJob::HandshakeTimeoutForTesting();
405 }
406
407 // Doesn't actually matter whether or not this is for a tunnel - the
408 // connection timeout is the same, though it probably shouldn't be the
409 // same, since tunnels need an extra round trip.
410 base::TimeDelta alternate_connection_timeout =
411 HttpProxyConnectJob::AlternateNestedConnectionTimeout(
412 *CreateParams(true /* tunnel */, SecureDnsPolicy::kAllow),
413 network_quality_estimator_.get());
414
415 // If there's an alternate connection timeout, and it's less than the
416 // standard TCP+SSL timeout (Which is also applied by the nested connect
417 // jobs), return the alternate connection timeout. Otherwise, return the
418 // normal timeout.
419 if (!alternate_connection_timeout.is_zero() &&
420 alternate_connection_timeout < normal_nested_connection_timeout) {
421 return alternate_connection_timeout;
422 }
423
424 return normal_nested_connection_timeout;
425 }
426
427 protected:
428 SpdyTestUtil spdy_util_;
429
430 TestCompletionCallback callback_;
431 };
432
433 // All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY)
434 // and SPDY.
435 INSTANTIATE_TEST_SUITE_P(HttpProxyType,
436 HttpProxyConnectJobTest,
437 ::testing::Values(HTTP, HTTPS, SPDY));
438
TEST_P(HttpProxyConnectJobTest,NoTunnel)439 TEST_P(HttpProxyConnectJobTest, NoTunnel) {
440 InitProxyDelegate();
441 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
442 SCOPED_TRACE(io_mode);
443 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
444 base::HistogramTester histogram_tester;
445
446 Initialize(base::span<MockRead>(), base::span<MockWrite>(),
447 base::span<MockRead>(), base::span<MockWrite>(), io_mode);
448
449 TestConnectJobDelegate test_delegate;
450 std::unique_ptr<ConnectJob> connect_job =
451 CreateConnectJobForHttpRequest(&test_delegate);
452 test_delegate.StartJobExpectingResult(connect_job.get(), OK,
453 io_mode == SYNCHRONOUS);
454 EXPECT_EQ(proxy_delegate_->on_before_tunnel_request_call_count(), 0u);
455
456 // Proxies should not set any DNS aliases.
457 EXPECT_TRUE(test_delegate.socket()->GetDnsAliases().empty());
458
459 bool is_secure_proxy = GetParam() == HTTPS || GetParam() == SPDY;
460 histogram_tester.ExpectTotalCount(
461 "Net.HttpProxy.ConnectLatency.Insecure.Success",
462 is_secure_proxy ? 0 : 1);
463 histogram_tester.ExpectTotalCount(
464 "Net.HttpProxy.ConnectLatency.Secure.Success", is_secure_proxy ? 1 : 0);
465 }
466 }
467
468 // Pauses an HttpProxyConnectJob at various states, and check the value of
469 // HasEstablishedConnection().
TEST_P(HttpProxyConnectJobTest,HasEstablishedConnectionNoTunnel)470 TEST_P(HttpProxyConnectJobTest, HasEstablishedConnectionNoTunnel) {
471 session_deps_.host_resolver->set_ondemand_mode(true);
472
473 SequencedSocketData data;
474 data.set_connect_data(MockConnect(ASYNC, OK));
475 session_deps_.socket_factory->AddSocketDataProvider(&data);
476
477 // Set up SSL, if needed.
478 SSLSocketDataProvider ssl_data(ASYNC, OK);
479 switch (GetParam()) {
480 case HTTP:
481 // No SSL needed.
482 break;
483 case HTTPS:
484 // SSL negotiation is the last step in non-tunnel connections over HTTPS
485 // proxies, so pause there, to check the final state before completion.
486 ssl_data = SSLSocketDataProvider(SYNCHRONOUS, ERR_IO_PENDING);
487 session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
488 break;
489 case SPDY:
490 InitializeSpdySsl(&ssl_data);
491 session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
492 break;
493 }
494
495 TestConnectJobDelegate test_delegate;
496 std::unique_ptr<ConnectJob> connect_job =
497 CreateConnectJobForHttpRequest(&test_delegate);
498
499 // Connecting should run until the request hits the HostResolver.
500 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
501 EXPECT_FALSE(test_delegate.has_result());
502 EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests());
503 EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState());
504 EXPECT_FALSE(connect_job->HasEstablishedConnection());
505
506 // Once the HostResolver completes, the job should start establishing a
507 // connection, which will complete asynchronously.
508 session_deps_.host_resolver->ResolveOnlyRequestNow();
509 EXPECT_FALSE(test_delegate.has_result());
510 EXPECT_EQ(LOAD_STATE_CONNECTING, connect_job->GetLoadState());
511 EXPECT_FALSE(connect_job->HasEstablishedConnection());
512
513 switch (GetParam()) {
514 case HTTP:
515 case SPDY:
516 // Connection completes. Since no tunnel is established, the socket is
517 // returned immediately, and HasEstablishedConnection() is only specified
518 // to work before the ConnectJob completes.
519 EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk());
520 break;
521 case HTTPS:
522 base::RunLoop().RunUntilIdle();
523 EXPECT_FALSE(test_delegate.has_result());
524 EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, connect_job->GetLoadState());
525 EXPECT_TRUE(connect_job->HasEstablishedConnection());
526
527 // Unfortunately, there's no API to advance the paused SSL negotiation,
528 // so just end the test here.
529 }
530 }
531
532 // Pauses an HttpProxyConnectJob at various states, and check the value of
533 // HasEstablishedConnection().
TEST_P(HttpProxyConnectJobTest,HasEstablishedConnectionTunnel)534 TEST_P(HttpProxyConnectJobTest, HasEstablishedConnectionTunnel) {
535 session_deps_.host_resolver->set_ondemand_mode(true);
536
537 // HTTP proxy CONNECT request / response, with a pause during the read.
538 MockWrite http1_writes[] = {
539 MockWrite(ASYNC, 0,
540 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
541 "Host: www.endpoint.test:443\r\n"
542 "Proxy-Connection: keep-alive\r\n\r\n"),
543 };
544 MockRead http1_reads[] = {
545 // Pause at first read.
546 MockRead(ASYNC, ERR_IO_PENDING, 1),
547 MockRead(ASYNC, 2, "HTTP/1.1 200 Connection Established\r\n\r\n"),
548 };
549 SequencedSocketData http1_data(http1_reads, http1_writes);
550 http1_data.set_connect_data(MockConnect(ASYNC, OK));
551
552 // SPDY proxy CONNECT request / response, with a pause during the read.
553 spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect(
554 nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority,
555 HostPortPair(kEndpointHost, 443)));
556 MockWrite spdy_writes[] = {CreateMockWrite(req, 0)};
557 spdy::SpdySerializedFrame resp(
558 spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
559 MockRead spdy_reads[] = {
560 // Pause at first read.
561 MockRead(ASYNC, ERR_IO_PENDING, 1),
562 CreateMockRead(resp, 2, ASYNC),
563 MockRead(ASYNC, 0, 3),
564 };
565 SequencedSocketData spdy_data(spdy_reads, spdy_writes);
566 spdy_data.set_connect_data(MockConnect(ASYNC, OK));
567
568 // Will point to either the HTTP/1.x or SPDY data, depending on GetParam().
569 SequencedSocketData* sequenced_data = nullptr;
570
571 SSLSocketDataProvider ssl_data(ASYNC, OK);
572 ssl_data.ssl_info.cert =
573 ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
574 ASSERT_TRUE(ssl_data.ssl_info.cert);
575
576 switch (GetParam()) {
577 case HTTP:
578 sequenced_data = &http1_data;
579 break;
580 case HTTPS:
581 sequenced_data = &http1_data;
582 ssl_data.next_proto = NextProto::kProtoHTTP11;
583 session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
584 break;
585 case SPDY:
586 sequenced_data = &spdy_data;
587 InitializeSpdySsl(&ssl_data);
588 session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
589 break;
590 }
591
592 session_deps_.socket_factory->AddSocketDataProvider(sequenced_data);
593
594 TestConnectJobDelegate test_delegate;
595 std::unique_ptr<ConnectJob> connect_job =
596 CreateConnectJobForTunnel(&test_delegate);
597
598 // Connecting should run until the request hits the HostResolver.
599 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
600 EXPECT_FALSE(test_delegate.has_result());
601 EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests());
602 EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState());
603 EXPECT_FALSE(connect_job->HasEstablishedConnection());
604
605 // Once the HostResolver completes, the job should start establishing a
606 // connection, which will complete asynchronously.
607 session_deps_.host_resolver->ResolveOnlyRequestNow();
608 EXPECT_FALSE(test_delegate.has_result());
609 EXPECT_EQ(LOAD_STATE_CONNECTING, connect_job->GetLoadState());
610 EXPECT_FALSE(connect_job->HasEstablishedConnection());
611
612 // Run until the socket starts reading the proxy's handshake response.
613 sequenced_data->RunUntilPaused();
614 EXPECT_FALSE(test_delegate.has_result());
615 EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, connect_job->GetLoadState());
616 EXPECT_TRUE(connect_job->HasEstablishedConnection());
617
618 // Finish the read, and run the job until it's complete.
619 sequenced_data->Resume();
620 EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk());
621
622 // Proxies should not set any DNS aliases.
623 EXPECT_TRUE(test_delegate.socket()->GetDnsAliases().empty());
624
625 // Although the underlying proxy connection may use TLS or negotiate ALPN, the
626 // tunnel itself is a TCP connection to the origin and should not report these
627 // values.
628 SSLInfo ssl_info;
629 EXPECT_FALSE(test_delegate.socket()->GetSSLInfo(&ssl_info));
630 EXPECT_EQ(test_delegate.socket()->GetNegotiatedProtocol(),
631 NextProto::kProtoUnknown);
632 }
633
TEST_P(HttpProxyConnectJobTest,ProxyDelegateExtraHeaders)634 TEST_P(HttpProxyConnectJobTest, ProxyDelegateExtraHeaders) {
635 InitProxyDelegate();
636
637 ProxyServer proxy_server(
638 GetParam() == HTTP ? ProxyServer::SCHEME_HTTP : ProxyServer::SCHEME_HTTPS,
639 HostPortPair(GetParam() == HTTP ? kHttpProxyHost : kHttpsProxyHost,
640 GetParam() == HTTP ? 80 : 443));
641 std::string proxy_server_uri = ProxyServerToProxyUri(proxy_server);
642
643 std::string http1_request = base::StringPrintf(
644 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
645 "Host: www.endpoint.test:443\r\n"
646 "Proxy-Connection: keep-alive\r\n"
647 "%s: %s\r\n\r\n",
648 kTestHeaderName, proxy_server_uri.c_str());
649 MockWrite writes[] = {
650 MockWrite(ASYNC, 0, http1_request.c_str()),
651 };
652
653 const char kResponseHeaderName[] = "bar";
654 const char kResponseHeaderValue[] = "Response";
655 std::string http1_response = base::StringPrintf(
656 "HTTP/1.1 200 Connection Established\r\n"
657 "%s: %s\r\n\r\n",
658 kResponseHeaderName, kResponseHeaderValue);
659 MockRead reads[] = {
660 MockRead(ASYNC, 1, http1_response.c_str()),
661 };
662
663 const char* const kExtraRequestHeaders[] = {
664 kTestSpdyHeaderName,
665 proxy_server_uri.c_str(),
666 };
667 const char* const kExtraResponseHeaders[] = {
668 kResponseHeaderName,
669 kResponseHeaderValue,
670 };
671 spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect(
672 kExtraRequestHeaders, std::size(kExtraRequestHeaders) / 2, 1,
673 HttpProxyConnectJob::kH2QuicTunnelPriority,
674 HostPortPair(kEndpointHost, 443)));
675 MockWrite spdy_writes[] = {CreateMockWrite(req, 0)};
676 spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyGetReply(
677 kExtraResponseHeaders, std::size(kExtraResponseHeaders) / 2, 1));
678 MockRead spdy_reads[] = {
679 CreateMockRead(resp, 1, ASYNC),
680 MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2),
681 };
682
683 Initialize(reads, writes, spdy_reads, spdy_writes, ASYNC);
684
685 TestConnectJobDelegate test_delegate;
686 std::unique_ptr<ConnectJob> connect_job =
687 CreateConnectJobForTunnel(&test_delegate);
688 test_delegate.StartJobExpectingResult(connect_job.get(), OK,
689 false /* expect_sync_result */);
690
691 ASSERT_EQ(proxy_delegate_->on_tunnel_headers_received_call_count(), 1u);
692 proxy_delegate_->VerifyOnTunnelHeadersReceived(
693 ProxyChain(proxy_server), 0, kResponseHeaderName, kResponseHeaderValue);
694 }
695
696 // Test HTTP CONNECTs and SPDY CONNECTs through two proxies
697 // (HTTPS -> HTTPS -> HTTPS and SPDY -> SPDY -> HTTPS).
TEST_P(HttpProxyConnectJobTest,NestedProxyProxyDelegateExtraHeaders)698 TEST_P(HttpProxyConnectJobTest, NestedProxyProxyDelegateExtraHeaders) {
699 if (GetParam() == HTTP) {
700 return;
701 }
702 InitProxyDelegate();
703
704 const ProxyServer& first_hop_proxy_server =
705 kHttpsNestedProxyChain.GetProxyServer(/*chain_index=*/0);
706 const ProxyServer& second_hop_proxy_server =
707 kHttpsNestedProxyChain.GetProxyServer(/*chain_index=*/1);
708
709 std::string first_hop_proxy_server_uri =
710 ProxyServerToProxyUri(first_hop_proxy_server);
711 std::string second_hop_proxy_server_uri =
712 ProxyServerToProxyUri(second_hop_proxy_server);
713
714 std::string first_hop_http1_request = base::StringPrintf(
715 "CONNECT last-hop-https-proxy.example.test:443 HTTP/1.1\r\n"
716 "Host: last-hop-https-proxy.example.test:443\r\n"
717 "Proxy-Connection: keep-alive\r\n"
718 "%s: %s\r\n\r\n",
719 kTestHeaderName, first_hop_proxy_server_uri.c_str());
720 std::string second_hop_http1_request = base::StringPrintf(
721 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
722 "Host: www.endpoint.test:443\r\n"
723 "Proxy-Connection: keep-alive\r\n"
724 "%s: %s\r\n\r\n",
725 kTestHeaderName, second_hop_proxy_server_uri.c_str());
726
727 const char kResponseHeaderName[] = "bar";
728 std::string first_hop_http1_response = base::StringPrintf(
729 "HTTP/1.1 200 Connection Established\r\n"
730 "%s: %s\r\n\r\n",
731 kResponseHeaderName, first_hop_proxy_server_uri.c_str());
732
733 std::string second_hop_http1_response = base::StringPrintf(
734 "HTTP/1.1 200 Connection Established\r\n"
735 "%s: %s\r\n\r\n",
736 kResponseHeaderName, second_hop_proxy_server_uri.c_str());
737
738 MockWrite writes[] = {
739 MockWrite(ASYNC, 0, first_hop_http1_request.c_str()),
740 MockWrite(ASYNC, 2, second_hop_http1_request.c_str()),
741 };
742
743 MockRead reads[] = {
744 MockRead(ASYNC, 1, first_hop_http1_response.c_str()),
745 MockRead(ASYNC, 3, second_hop_http1_response.c_str()),
746 };
747
748 const char* const kFirstHopExtraRequestHeaders[] = {
749 kTestSpdyHeaderName,
750 first_hop_proxy_server_uri.c_str(),
751 };
752 const char* const kSecondHopExtraRequestHeaders[] = {
753 kTestSpdyHeaderName,
754 second_hop_proxy_server_uri.c_str(),
755 };
756 const char* const kFirstHopExtraResponseHeaders[] = {
757 kResponseHeaderName,
758 first_hop_proxy_server_uri.c_str(),
759 };
760 const char* const kSecondHopExtraResponseHeaders[] = {
761 kResponseHeaderName,
762 second_hop_proxy_server_uri.c_str(),
763 };
764
765 spdy::SpdySerializedFrame first_hop_req(spdy_util_.ConstructSpdyConnect(
766 kFirstHopExtraRequestHeaders, std::size(kFirstHopExtraRequestHeaders) / 2,
767 1, HttpProxyConnectJob::kH2QuicTunnelPriority,
768 second_hop_proxy_server.host_port_pair()));
769
770 spdy::SpdySerializedFrame first_hop_resp(spdy_util_.ConstructSpdyGetReply(
771 kFirstHopExtraResponseHeaders,
772 std::size(kFirstHopExtraResponseHeaders) / 2, 1));
773
774 // Use a new `SpdyTestUtil()` instance for the second hop response and request
775 // because otherwise, the serialized frames that get generated for these will
776 // use header compression and won't match what actually gets sent on the wire
777 // (where header compression doesn't affect these requests because they are
778 // associated with different streams).
779 SpdyTestUtil new_spdy_util;
780
781 spdy::SpdySerializedFrame second_hop_req(new_spdy_util.ConstructSpdyConnect(
782 kSecondHopExtraRequestHeaders,
783 std::size(kSecondHopExtraRequestHeaders) / 2, 1,
784 HttpProxyConnectJob::kH2QuicTunnelPriority,
785 HostPortPair(kEndpointHost, 443)));
786
787 // Since the second request and response are sent over the tunnel established
788 // previously, from a socket-perspective these need to be wrapped as data
789 // frames.
790 spdy::SpdySerializedFrame wrapped_second_hop_req(
791 spdy_util_.ConstructWrappedSpdyFrame(second_hop_req, 1));
792
793 spdy::SpdySerializedFrame second_hop_resp(new_spdy_util.ConstructSpdyGetReply(
794 kSecondHopExtraResponseHeaders,
795 std::size(kSecondHopExtraResponseHeaders) / 2, 1));
796
797 spdy::SpdySerializedFrame wrapped_second_hop_resp(
798 spdy_util_.ConstructWrappedSpdyFrame(second_hop_resp, 1));
799
800 MockWrite spdy_writes[] = {
801 CreateMockWrite(first_hop_req, 0),
802 CreateMockWrite(wrapped_second_hop_req, 2),
803 };
804 MockRead spdy_reads[] = {
805 CreateMockRead(first_hop_resp, 1, ASYNC),
806 // TODO(https://crbug.com/497228): We have to manually delay this read so
807 // that the higher-level SPDY stream doesn't get notified of an available
808 // read before the write it initiated (the second CONNECT) finishes,
809 // triggering a DCHECK.
810 MockRead(ASYNC, ERR_IO_PENDING, 3),
811 CreateMockRead(wrapped_second_hop_resp, 4, ASYNC),
812 MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5),
813 };
814
815 Initialize(reads, writes, spdy_reads, spdy_writes, ASYNC,
816 /*two_ssl_proxies=*/true);
817
818 TestConnectJobDelegate test_delegate;
819 std::unique_ptr<ConnectJob> connect_job =
820 CreateConnectJobForNestedProxyTunnel(&test_delegate);
821
822 if (GetParam() != SPDY) {
823 test_delegate.StartJobExpectingResult(connect_job.get(), OK,
824 /*expect_sync_result=*/false);
825 } else {
826 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
827
828 data_->RunUntilPaused();
829 base::RunLoop().RunUntilIdle();
830 data_->Resume();
831
832 EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk());
833 }
834 ASSERT_EQ(proxy_delegate_->on_tunnel_headers_received_call_count(), 2u);
835 proxy_delegate_->VerifyOnTunnelHeadersReceived(
836 kHttpsNestedProxyChain, /*chain_index=*/0, kResponseHeaderName,
837 first_hop_proxy_server_uri, /*call_index=*/0);
838 proxy_delegate_->VerifyOnTunnelHeadersReceived(
839 kHttpsNestedProxyChain, /*chain_index=*/1, kResponseHeaderName,
840 second_hop_proxy_server_uri, /*call_index=*/1);
841 }
842
843 // Test the case where auth credentials are not cached.
TEST_P(HttpProxyConnectJobTest,NeedAuth)844 TEST_P(HttpProxyConnectJobTest, NeedAuth) {
845 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
846 SCOPED_TRACE(io_mode);
847
848 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
849
850 MockWrite writes[] = {
851 MockWrite(io_mode, 0,
852 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
853 "Host: www.endpoint.test:443\r\n"
854 "Proxy-Connection: keep-alive\r\n\r\n"),
855 MockWrite(io_mode, 5,
856 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
857 "Host: www.endpoint.test:443\r\n"
858 "Proxy-Connection: keep-alive\r\n"
859 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
860 };
861 MockRead reads[] = {
862 // No credentials.
863 MockRead(io_mode, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
864 MockRead(io_mode, 2,
865 "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
866 MockRead(io_mode, 3, "Content-Length: 10\r\n\r\n"),
867 MockRead(io_mode, 4, "0123456789"),
868 MockRead(io_mode, 6, "HTTP/1.1 200 Connection Established\r\n\r\n"),
869 };
870
871 SpdyTestUtil spdy_util;
872 spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect(
873 nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority,
874 HostPortPair(kEndpointHost, 443)));
875 spdy::SpdySerializedFrame rst(
876 spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
877 spdy_util.UpdateWithStreamDestruction(1);
878
879 // After calling trans.RestartWithAuth(), this is the request we should
880 // be issuing -- the final header line contains the credentials.
881 const char* const kSpdyAuthCredentials[] = {
882 "proxy-authorization",
883 "Basic Zm9vOmJhcg==",
884 };
885 spdy::SpdySerializedFrame connect2(spdy_util.ConstructSpdyConnect(
886 kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 3,
887 HttpProxyConnectJob::kH2QuicTunnelPriority,
888 HostPortPair(kEndpointHost, 443)));
889
890 MockWrite spdy_writes[] = {
891 CreateMockWrite(connect, 0, io_mode),
892 CreateMockWrite(rst, 2, io_mode),
893 CreateMockWrite(connect2, 3, io_mode),
894 };
895
896 // The proxy responds to the connect with a 407, using a persistent
897 // connection.
898 const char kAuthStatus[] = "407";
899 const char* const kAuthChallenge[] = {
900 "proxy-authenticate",
901 "Basic realm=\"MyRealm1\"",
902 };
903 spdy::SpdySerializedFrame connect_auth_resp(
904 spdy_util.ConstructSpdyReplyError(kAuthStatus, kAuthChallenge,
905 std::size(kAuthChallenge) / 2, 1));
906
907 spdy::SpdySerializedFrame connect2_resp(
908 spdy_util.ConstructSpdyGetReply(nullptr, 0, 3));
909 MockRead spdy_reads[] = {
910 CreateMockRead(connect_auth_resp, 1, ASYNC),
911 CreateMockRead(connect2_resp, 4, ASYNC),
912 MockRead(ASYNC, OK, 5),
913 };
914
915 Initialize(reads, writes, spdy_reads, spdy_writes, io_mode);
916
917 TestConnectJobDelegate test_delegate;
918 std::unique_ptr<ConnectJob> connect_job =
919 CreateConnectJobForTunnel(&test_delegate);
920 ASSERT_EQ(ERR_IO_PENDING, connect_job->Connect());
921 // Auth callback is always invoked asynchronously when a challenge is
922 // observed.
923 EXPECT_EQ(0, test_delegate.num_auth_challenges());
924
925 test_delegate.WaitForAuthChallenge(1);
926 ASSERT_TRUE(test_delegate.auth_response_info().headers);
927 EXPECT_EQ(407, test_delegate.auth_response_info().headers->response_code());
928 std::string proxy_authenticate;
929 ASSERT_TRUE(test_delegate.auth_response_info().headers->EnumerateHeader(
930 nullptr, "Proxy-Authenticate", &proxy_authenticate));
931 EXPECT_EQ(proxy_authenticate, "Basic realm=\"MyRealm1\"");
932 ASSERT_TRUE(test_delegate.auth_controller());
933 EXPECT_FALSE(test_delegate.has_result());
934
935 test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar"));
936 test_delegate.RunAuthCallback();
937 // Per API contract, the request can not complete synchronously.
938 EXPECT_FALSE(test_delegate.has_result());
939
940 EXPECT_EQ(net::OK, test_delegate.WaitForResult());
941 EXPECT_EQ(1, test_delegate.num_auth_challenges());
942
943 // Close the H2 session to prevent reuse.
944 if (GetParam() == SPDY) {
945 session_->CloseAllConnections(ERR_FAILED, "Very good reason");
946 }
947 // Also need to clear the auth cache before re-running the test.
948 session_->http_auth_cache()->ClearAllEntries();
949 }
950 }
951
952 // Test the case where auth credentials are not cached and the first time
953 // credentials are sent, they are rejected.
TEST_P(HttpProxyConnectJobTest,NeedAuthTwice)954 TEST_P(HttpProxyConnectJobTest, NeedAuthTwice) {
955 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
956 SCOPED_TRACE(io_mode);
957
958 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
959
960 MockWrite writes[] = {
961 MockWrite(io_mode, 0,
962 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
963 "Host: www.endpoint.test:443\r\n"
964 "Proxy-Connection: keep-alive\r\n\r\n"),
965 MockWrite(io_mode, 2,
966 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
967 "Host: www.endpoint.test:443\r\n"
968 "Proxy-Connection: keep-alive\r\n"
969 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
970 MockWrite(io_mode, 4,
971 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
972 "Host: www.endpoint.test:443\r\n"
973 "Proxy-Connection: keep-alive\r\n"
974 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
975 };
976 MockRead reads[] = {
977 // No credentials.
978 MockRead(io_mode, 1,
979 "HTTP/1.1 407 Proxy Authentication Required\r\n"
980 "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
981 "Content-Length: 0\r\n\r\n"),
982 MockRead(io_mode, 3,
983 "HTTP/1.1 407 Proxy Authentication Required\r\n"
984 "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
985 "Content-Length: 0\r\n\r\n"),
986 MockRead(io_mode, 5, "HTTP/1.1 200 Connection Established\r\n\r\n"),
987 };
988
989 SpdyTestUtil spdy_util;
990 spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect(
991 nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority,
992 HostPortPair(kEndpointHost, 443)));
993 spdy::SpdySerializedFrame rst(
994 spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
995 spdy_util.UpdateWithStreamDestruction(1);
996
997 // After calling trans.RestartWithAuth(), this is the request we should
998 // be issuing -- the final header line contains the credentials.
999 const char* const kSpdyAuthCredentials[] = {
1000 "proxy-authorization",
1001 "Basic Zm9vOmJhcg==",
1002 };
1003 spdy::SpdySerializedFrame connect2(spdy_util.ConstructSpdyConnect(
1004 kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 3,
1005 HttpProxyConnectJob::kH2QuicTunnelPriority,
1006 HostPortPair(kEndpointHost, 443)));
1007 spdy::SpdySerializedFrame rst2(
1008 spdy_util.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
1009 spdy_util.UpdateWithStreamDestruction(3);
1010
1011 spdy::SpdySerializedFrame connect3(spdy_util.ConstructSpdyConnect(
1012 kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 5,
1013 HttpProxyConnectJob::kH2QuicTunnelPriority,
1014 HostPortPair(kEndpointHost, 443)));
1015 MockWrite spdy_writes[] = {
1016 CreateMockWrite(connect, 0, io_mode),
1017 CreateMockWrite(rst, 2, io_mode),
1018 CreateMockWrite(connect2, 3, io_mode),
1019 CreateMockWrite(rst2, 5, io_mode),
1020 CreateMockWrite(connect3, 6, io_mode),
1021 };
1022
1023 // The proxy responds to the connect with a 407, using a persistent
1024 // connection.
1025 const char kAuthStatus[] = "407";
1026 const char* const kAuthChallenge[] = {
1027 "proxy-authenticate",
1028 "Basic realm=\"MyRealm1\"",
1029 };
1030 spdy::SpdySerializedFrame connect_auth_resp(
1031 spdy_util.ConstructSpdyReplyError(kAuthStatus, kAuthChallenge,
1032 std::size(kAuthChallenge) / 2, 1));
1033 spdy::SpdySerializedFrame connect2_auth_resp(
1034 spdy_util.ConstructSpdyReplyError(kAuthStatus, kAuthChallenge,
1035 std::size(kAuthChallenge) / 2, 3));
1036 spdy::SpdySerializedFrame connect3_resp(
1037 spdy_util.ConstructSpdyGetReply(nullptr, 0, 5));
1038 MockRead spdy_reads[] = {
1039 CreateMockRead(connect_auth_resp, 1, ASYNC),
1040 CreateMockRead(connect2_auth_resp, 4, ASYNC),
1041 CreateMockRead(connect3_resp, 7, ASYNC),
1042 MockRead(ASYNC, OK, 8),
1043 };
1044
1045 Initialize(reads, writes, spdy_reads, spdy_writes, io_mode);
1046
1047 TestConnectJobDelegate test_delegate;
1048 std::unique_ptr<ConnectJob> connect_job =
1049 CreateConnectJobForTunnel(&test_delegate);
1050 ASSERT_EQ(ERR_IO_PENDING, connect_job->Connect());
1051 // Auth callback is always invoked asynchronously when a challenge is
1052 // observed.
1053 EXPECT_EQ(0, test_delegate.num_auth_challenges());
1054
1055 test_delegate.WaitForAuthChallenge(1);
1056 ASSERT_TRUE(test_delegate.auth_response_info().headers);
1057 EXPECT_EQ(407, test_delegate.auth_response_info().headers->response_code());
1058 std::string proxy_authenticate;
1059 ASSERT_TRUE(test_delegate.auth_response_info().headers->EnumerateHeader(
1060 nullptr, "Proxy-Authenticate", &proxy_authenticate));
1061 EXPECT_EQ(proxy_authenticate, "Basic realm=\"MyRealm1\"");
1062 EXPECT_FALSE(test_delegate.has_result());
1063
1064 test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar"));
1065 test_delegate.RunAuthCallback();
1066 // Per API contract, the auth callback can't be invoked synchronously.
1067 EXPECT_FALSE(test_delegate.auth_controller());
1068 EXPECT_FALSE(test_delegate.has_result());
1069
1070 test_delegate.WaitForAuthChallenge(2);
1071 ASSERT_TRUE(test_delegate.auth_response_info().headers);
1072 EXPECT_EQ(407, test_delegate.auth_response_info().headers->response_code());
1073 ASSERT_TRUE(test_delegate.auth_response_info().headers->EnumerateHeader(
1074 nullptr, "Proxy-Authenticate", &proxy_authenticate));
1075 EXPECT_EQ(proxy_authenticate, "Basic realm=\"MyRealm1\"");
1076 EXPECT_FALSE(test_delegate.has_result());
1077
1078 test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar"));
1079 test_delegate.RunAuthCallback();
1080 // Per API contract, the request can't complete synchronously.
1081 EXPECT_FALSE(test_delegate.has_result());
1082
1083 EXPECT_EQ(net::OK, test_delegate.WaitForResult());
1084 EXPECT_EQ(2, test_delegate.num_auth_challenges());
1085
1086 // Close the H2 session to prevent reuse.
1087 if (GetParam() == SPDY) {
1088 session_->CloseAllConnections(ERR_FAILED, "Very good reason");
1089 }
1090 // Also need to clear the auth cache before re-running the test.
1091 session_->http_auth_cache()->ClearAllEntries();
1092 }
1093 }
1094
1095 // Test the case where auth credentials are cached.
TEST_P(HttpProxyConnectJobTest,HaveAuth)1096 TEST_P(HttpProxyConnectJobTest, HaveAuth) {
1097 // Prepopulate auth cache.
1098 const std::u16string kFoo(u"foo");
1099 const std::u16string kBar(u"bar");
1100 url::SchemeHostPort proxy_scheme_host_port(
1101 GetParam() == HTTP ? GURL(std::string("http://") + kHttpProxyHost)
1102 : GURL(std::string("https://") + kHttpsProxyHost));
1103 session_->http_auth_cache()->Add(
1104 proxy_scheme_host_port, HttpAuth::AUTH_PROXY, "MyRealm1",
1105 HttpAuth::AUTH_SCHEME_BASIC, NetworkAnonymizationKey(),
1106 "Basic realm=MyRealm1", AuthCredentials(kFoo, kBar), "/");
1107
1108 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1109 SCOPED_TRACE(io_mode);
1110
1111 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1112
1113 MockWrite writes[] = {
1114 MockWrite(io_mode, 0,
1115 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1116 "Host: www.endpoint.test:443\r\n"
1117 "Proxy-Connection: keep-alive\r\n"
1118 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
1119 };
1120 MockRead reads[] = {
1121 MockRead(io_mode, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
1122 };
1123
1124 const char* const kSpdyAuthCredentials[] = {
1125 "proxy-authorization",
1126 "Basic Zm9vOmJhcg==",
1127 };
1128 SpdyTestUtil spdy_util;
1129 spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect(
1130 kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 1,
1131 HttpProxyConnectJob::kH2QuicTunnelPriority,
1132 HostPortPair(kEndpointHost, 443)));
1133
1134 MockWrite spdy_writes[] = {
1135 CreateMockWrite(connect, 0, ASYNC),
1136 };
1137
1138 spdy::SpdySerializedFrame connect_resp(
1139 spdy_util.ConstructSpdyGetReply(nullptr, 0, 1));
1140 MockRead spdy_reads[] = {
1141 // SpdySession starts trying to read from the socket as soon as it's
1142 // created, so this cannot be SYNCHRONOUS.
1143 CreateMockRead(connect_resp, 1, ASYNC),
1144 MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2),
1145 };
1146
1147 Initialize(reads, writes, spdy_reads, spdy_writes, io_mode);
1148
1149 TestConnectJobDelegate test_delegate;
1150 std::unique_ptr<ConnectJob> connect_job =
1151 CreateConnectJobForTunnel(&test_delegate);
1152 // SPDY operations always complete asynchronously.
1153 test_delegate.StartJobExpectingResult(
1154 connect_job.get(), OK, io_mode == SYNCHRONOUS && GetParam() != SPDY);
1155
1156 // Close the H2 session to prevent reuse.
1157 if (GetParam() == SPDY) {
1158 session_->CloseAllConnections(ERR_FAILED, "Very good reason");
1159 }
1160 }
1161 }
1162
TEST_P(HttpProxyConnectJobTest,HostResolutionFailure)1163 TEST_P(HttpProxyConnectJobTest, HostResolutionFailure) {
1164 session_deps_.host_resolver->rules()->AddSimulatedTimeoutFailure(
1165 kHttpProxyHost);
1166 session_deps_.host_resolver->rules()->AddSimulatedTimeoutFailure(
1167 kHttpsProxyHost);
1168
1169 TestConnectJobDelegate test_delegate;
1170 std::unique_ptr<ConnectJob> connect_job =
1171 CreateConnectJobForHttpRequest(&test_delegate, DEFAULT_PRIORITY);
1172 test_delegate.StartJobExpectingResult(connect_job.get(),
1173 ERR_PROXY_CONNECTION_FAILED,
1174 false /* expect_sync_result */);
1175 EXPECT_THAT(connect_job->GetResolveErrorInfo().error,
1176 test::IsError(ERR_DNS_TIMED_OUT));
1177 }
1178
TEST_P(HttpProxyConnectJobTest,RequestPriority)1179 TEST_P(HttpProxyConnectJobTest, RequestPriority) {
1180 // Make request hang during host resolution, so can observe priority there.
1181 session_deps_.host_resolver->set_ondemand_mode(true);
1182
1183 for (int initial_priority = MINIMUM_PRIORITY;
1184 initial_priority <= MAXIMUM_PRIORITY; ++initial_priority) {
1185 SCOPED_TRACE(initial_priority);
1186 for (int new_priority = MINIMUM_PRIORITY; new_priority <= MAXIMUM_PRIORITY;
1187 ++new_priority) {
1188 SCOPED_TRACE(new_priority);
1189 if (initial_priority == new_priority) {
1190 continue;
1191 }
1192 TestConnectJobDelegate test_delegate;
1193 std::unique_ptr<ConnectJob> connect_job = CreateConnectJobForHttpRequest(
1194 &test_delegate, static_cast<RequestPriority>(initial_priority));
1195 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
1196 EXPECT_FALSE(test_delegate.has_result());
1197
1198 MockHostResolverBase* host_resolver = session_deps_.host_resolver.get();
1199 size_t request_id = host_resolver->last_id();
1200 EXPECT_EQ(initial_priority, host_resolver->request_priority(request_id));
1201
1202 connect_job->ChangePriority(static_cast<RequestPriority>(new_priority));
1203 EXPECT_EQ(new_priority, host_resolver->request_priority(request_id));
1204
1205 connect_job->ChangePriority(
1206 static_cast<RequestPriority>(initial_priority));
1207 EXPECT_EQ(initial_priority, host_resolver->request_priority(request_id));
1208 }
1209 }
1210 }
1211
TEST_P(HttpProxyConnectJobTest,SecureDnsPolicy)1212 TEST_P(HttpProxyConnectJobTest, SecureDnsPolicy) {
1213 for (auto secure_dns_policy :
1214 {SecureDnsPolicy::kAllow, SecureDnsPolicy::kDisable}) {
1215 TestConnectJobDelegate test_delegate;
1216 std::unique_ptr<ConnectJob> connect_job = CreateConnectJobForHttpRequest(
1217 &test_delegate, DEFAULT_PRIORITY, secure_dns_policy);
1218
1219 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
1220 EXPECT_EQ(secure_dns_policy,
1221 session_deps_.host_resolver->last_secure_dns_policy());
1222 }
1223 }
1224
TEST_P(HttpProxyConnectJobTest,SpdySessionKeyDisableSecureDns)1225 TEST_P(HttpProxyConnectJobTest, SpdySessionKeyDisableSecureDns) {
1226 if (GetParam() != SPDY) {
1227 return;
1228 }
1229
1230 SSLSocketDataProvider ssl_data(ASYNC, OK);
1231 InitializeSpdySsl(&ssl_data);
1232 session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
1233
1234 // SPDY proxy CONNECT request / response, with a pause during the read.
1235 spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect(
1236 nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority,
1237 HostPortPair(kEndpointHost, 443)));
1238 MockWrite spdy_writes[] = {CreateMockWrite(req, 0)};
1239 spdy::SpdySerializedFrame resp(
1240 spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
1241 MockRead spdy_reads[] = {CreateMockRead(resp, 1), MockRead(ASYNC, 0, 2)};
1242 SequencedSocketData spdy_data(spdy_reads, spdy_writes);
1243 spdy_data.set_connect_data(MockConnect(ASYNC, OK));
1244 SequencedSocketData* sequenced_data = &spdy_data;
1245 session_deps_.socket_factory->AddSocketDataProvider(sequenced_data);
1246
1247 TestConnectJobDelegate test_delegate;
1248 std::unique_ptr<ConnectJob> connect_job = CreateConnectJobForTunnel(
1249 &test_delegate, DEFAULT_PRIORITY, SecureDnsPolicy::kDisable);
1250
1251 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
1252 EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk());
1253 EXPECT_TRUE(
1254 common_connect_job_params_->spdy_session_pool->FindAvailableSession(
1255 SpdySessionKey(kHttpsProxyServer.host_port_pair(),
1256 PRIVACY_MODE_DISABLED, ProxyChain::Direct(),
1257 SessionUsage::kProxy, SocketTag(),
1258 NetworkAnonymizationKey(), SecureDnsPolicy::kDisable,
1259 /*disable_cert_verification_network_fetches=*/true),
1260 /* enable_ip_based_pooling = */ false,
1261 /* is_websocket = */ false, NetLogWithSource()));
1262 EXPECT_FALSE(
1263 common_connect_job_params_->spdy_session_pool->FindAvailableSession(
1264 SpdySessionKey(kHttpsProxyServer.host_port_pair(),
1265 PRIVACY_MODE_DISABLED, ProxyChain::Direct(),
1266 SessionUsage::kProxy, SocketTag(),
1267 NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
1268 /*disable_cert_verification_network_fetches=*/true),
1269 /* enable_ip_based_pooling = */ false,
1270 /* is_websocket = */ false, NetLogWithSource()));
1271 }
1272
1273 // Make sure that HttpProxyConnectJob does not pass on its priority to its
1274 // SPDY session's socket request on Init, or on SetPriority.
TEST_P(HttpProxyConnectJobTest,SetSpdySessionSocketRequestPriority)1275 TEST_P(HttpProxyConnectJobTest, SetSpdySessionSocketRequestPriority) {
1276 if (GetParam() != SPDY) {
1277 return;
1278 }
1279 session_deps_.host_resolver->set_synchronous_mode(true);
1280
1281 // The SPDY CONNECT request should have a priority of kH2QuicTunnelPriority,
1282 // even though the ConnectJob's priority is set to HIGHEST after connection
1283 // establishment.
1284 spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect(
1285 nullptr /* extra_headers */, 0 /* extra_header_count */,
1286 1 /* stream_id */, HttpProxyConnectJob::kH2QuicTunnelPriority,
1287 HostPortPair(kEndpointHost, 443)));
1288 MockWrite spdy_writes[] = {CreateMockWrite(req, 0, ASYNC)};
1289 spdy::SpdySerializedFrame resp(
1290 spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
1291 MockRead spdy_reads[] = {CreateMockRead(resp, 1, ASYNC),
1292 MockRead(ASYNC, 0, 2)};
1293
1294 Initialize(base::span<MockRead>(), base::span<MockWrite>(), spdy_reads,
1295 spdy_writes, SYNCHRONOUS);
1296
1297 TestConnectJobDelegate test_delegate;
1298 std::unique_ptr<ConnectJob> connect_job =
1299 CreateConnectJobForTunnel(&test_delegate, IDLE);
1300 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
1301 EXPECT_FALSE(test_delegate.has_result());
1302
1303 connect_job->ChangePriority(HIGHEST);
1304
1305 // Wait for tunnel to be established. If the frame has a MEDIUM priority
1306 // instead of highest, the written data will not match what is expected, and
1307 // the test will fail.
1308 EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk());
1309 }
1310
TEST_P(HttpProxyConnectJobTest,TCPError)1311 TEST_P(HttpProxyConnectJobTest, TCPError) {
1312 // SPDY and HTTPS are identical, as they only differ once a connection is
1313 // established.
1314 if (GetParam() == SPDY) {
1315 return;
1316 }
1317 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1318 SCOPED_TRACE(io_mode);
1319 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1320 base::HistogramTester histogram_tester;
1321
1322 SequencedSocketData data;
1323 data.set_connect_data(MockConnect(io_mode, ERR_CONNECTION_CLOSED));
1324 session_deps_.socket_factory->AddSocketDataProvider(&data);
1325
1326 TestConnectJobDelegate test_delegate;
1327 std::unique_ptr<ConnectJob> connect_job =
1328 CreateConnectJobForHttpRequest(&test_delegate);
1329 test_delegate.StartJobExpectingResult(
1330 connect_job.get(), ERR_PROXY_CONNECTION_FAILED, io_mode == SYNCHRONOUS);
1331
1332 bool is_secure_proxy = GetParam() == HTTPS;
1333 histogram_tester.ExpectTotalCount(
1334 "Net.HttpProxy.ConnectLatency.Insecure.Error", is_secure_proxy ? 0 : 1);
1335 histogram_tester.ExpectTotalCount(
1336 "Net.HttpProxy.ConnectLatency.Secure.Error", is_secure_proxy ? 1 : 0);
1337 }
1338 }
1339
TEST_P(HttpProxyConnectJobTest,SSLError)1340 TEST_P(HttpProxyConnectJobTest, SSLError) {
1341 if (GetParam() == HTTP) {
1342 return;
1343 }
1344
1345 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1346 SCOPED_TRACE(io_mode);
1347 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1348 base::HistogramTester histogram_tester;
1349
1350 SequencedSocketData data;
1351 data.set_connect_data(MockConnect(io_mode, OK));
1352 session_deps_.socket_factory->AddSocketDataProvider(&data);
1353
1354 SSLSocketDataProvider ssl_data(io_mode, ERR_CERT_AUTHORITY_INVALID);
1355 if (GetParam() == SPDY) {
1356 InitializeSpdySsl(&ssl_data);
1357 }
1358 session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
1359
1360 TestConnectJobDelegate test_delegate;
1361 std::unique_ptr<ConnectJob> connect_job =
1362 CreateConnectJobForTunnel(&test_delegate);
1363 test_delegate.StartJobExpectingResult(connect_job.get(),
1364 ERR_PROXY_CERTIFICATE_INVALID,
1365 io_mode == SYNCHRONOUS);
1366
1367 histogram_tester.ExpectTotalCount(
1368 "Net.HttpProxy.ConnectLatency.Secure.Error", 1);
1369 histogram_tester.ExpectTotalCount(
1370 "Net.HttpProxy.ConnectLatency.Insecure.Error", 0);
1371 }
1372 }
1373
TEST_P(HttpProxyConnectJobTest,TunnelUnexpectedClose)1374 TEST_P(HttpProxyConnectJobTest, TunnelUnexpectedClose) {
1375 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1376 SCOPED_TRACE(io_mode);
1377 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1378
1379 MockWrite writes[] = {
1380 MockWrite(io_mode, 0,
1381 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1382 "Host: www.endpoint.test:443\r\n"
1383 "Proxy-Connection: keep-alive\r\n\r\n"),
1384 };
1385 MockRead reads[] = {
1386 MockRead(io_mode, 1, "HTTP/1.1 200 Conn"),
1387 MockRead(io_mode, ERR_CONNECTION_CLOSED, 2),
1388 };
1389 spdy::SpdySerializedFrame req(SpdyTestUtil().ConstructSpdyConnect(
1390 nullptr /*extra_headers */, 0 /*extra_header_count */,
1391 1 /* stream_id */, HttpProxyConnectJob::kH2QuicTunnelPriority,
1392 HostPortPair(kEndpointHost, 443)));
1393 MockWrite spdy_writes[] = {CreateMockWrite(req, 0, io_mode)};
1394 // Sync reads don't really work with SPDY, since it constantly reads from
1395 // the socket.
1396 MockRead spdy_reads[] = {
1397 MockRead(ASYNC, ERR_CONNECTION_CLOSED, 1),
1398 };
1399
1400 Initialize(reads, writes, spdy_reads, spdy_writes, io_mode);
1401
1402 TestConnectJobDelegate test_delegate;
1403 std::unique_ptr<ConnectJob> connect_job =
1404 CreateConnectJobForTunnel(&test_delegate);
1405
1406 if (GetParam() == SPDY) {
1407 // SPDY cannot process a headers block unless it's complete and so it
1408 // returns ERR_CONNECTION_CLOSED in this case. SPDY also doesn't return
1409 // this failure synchronously.
1410 test_delegate.StartJobExpectingResult(connect_job.get(),
1411 ERR_CONNECTION_CLOSED,
1412 false /* expect_sync_result */);
1413 } else {
1414 test_delegate.StartJobExpectingResult(connect_job.get(),
1415 ERR_RESPONSE_HEADERS_TRUNCATED,
1416 io_mode == SYNCHRONOUS);
1417 }
1418 }
1419 }
1420
TEST_P(HttpProxyConnectJobTest,Tunnel1xxResponse)1421 TEST_P(HttpProxyConnectJobTest, Tunnel1xxResponse) {
1422 // Tests that 1xx responses are rejected for a CONNECT request.
1423 if (GetParam() == SPDY) {
1424 // SPDY doesn't have 1xx responses.
1425 return;
1426 }
1427
1428 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1429 SCOPED_TRACE(io_mode);
1430 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1431
1432 MockWrite writes[] = {
1433 MockWrite(io_mode, 0,
1434 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1435 "Host: www.endpoint.test:443\r\n"
1436 "Proxy-Connection: keep-alive\r\n\r\n"),
1437 };
1438 MockRead reads[] = {
1439 MockRead(io_mode, 1, "HTTP/1.1 100 Continue\r\n\r\n"),
1440 MockRead(io_mode, 2, "HTTP/1.1 200 Connection Established\r\n\r\n"),
1441 };
1442
1443 Initialize(reads, writes, base::span<MockRead>(), base::span<MockWrite>(),
1444 io_mode);
1445
1446 TestConnectJobDelegate test_delegate;
1447 std::unique_ptr<ConnectJob> connect_job =
1448 CreateConnectJobForTunnel(&test_delegate);
1449 test_delegate.StartJobExpectingResult(connect_job.get(),
1450 ERR_TUNNEL_CONNECTION_FAILED,
1451 io_mode == SYNCHRONOUS);
1452 }
1453 }
1454
TEST_P(HttpProxyConnectJobTest,TunnelSetupError)1455 TEST_P(HttpProxyConnectJobTest, TunnelSetupError) {
1456 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1457 SCOPED_TRACE(io_mode);
1458 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1459
1460 MockWrite writes[] = {
1461 MockWrite(io_mode, 0,
1462 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1463 "Host: www.endpoint.test:443\r\n"
1464 "Proxy-Connection: keep-alive\r\n\r\n"),
1465 };
1466 MockRead reads[] = {
1467 MockRead(io_mode, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"),
1468 };
1469 SpdyTestUtil spdy_util;
1470 spdy::SpdySerializedFrame req(spdy_util.ConstructSpdyConnect(
1471 nullptr /* extra_headers */, 0 /* extra_header_count */,
1472 1 /* stream_id */, HttpProxyConnectJob::kH2QuicTunnelPriority,
1473 HostPortPair("www.endpoint.test", 443)));
1474 spdy::SpdySerializedFrame rst(
1475 spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
1476 MockWrite spdy_writes[] = {
1477 CreateMockWrite(req, 0, io_mode),
1478 CreateMockWrite(rst, 2, io_mode),
1479 };
1480 spdy::SpdySerializedFrame resp(spdy_util.ConstructSpdyReplyError(1));
1481 // Sync reads don't really work with SPDY, since it constantly reads from
1482 // the socket.
1483 MockRead spdy_reads[] = {
1484 CreateMockRead(resp, 1, ASYNC),
1485 MockRead(ASYNC, OK, 3),
1486 };
1487
1488 Initialize(reads, writes, spdy_reads, spdy_writes, io_mode);
1489
1490 TestConnectJobDelegate test_delegate;
1491 std::unique_ptr<ConnectJob> connect_job =
1492 CreateConnectJobForTunnel(&test_delegate, LOW);
1493 test_delegate.StartJobExpectingResult(
1494 connect_job.get(), ERR_TUNNEL_CONNECTION_FAILED,
1495 io_mode == SYNCHRONOUS && GetParam() != SPDY);
1496 // Need to close the session to prevent reuse in the next loop iteration.
1497 session_->spdy_session_pool()->CloseAllSessions();
1498 }
1499 }
1500
TEST_P(HttpProxyConnectJobTest,SslClientAuth)1501 TEST_P(HttpProxyConnectJobTest, SslClientAuth) {
1502 if (GetParam() == HTTP) {
1503 return;
1504 }
1505 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1506 SCOPED_TRACE(io_mode);
1507 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1508 base::HistogramTester histogram_tester;
1509
1510 SequencedSocketData socket_data(MockConnect(io_mode, OK),
1511 base::span<const MockRead>(),
1512 base::span<const MockWrite>());
1513 session_deps_.socket_factory->AddSocketDataProvider(&socket_data);
1514 SSLSocketDataProvider ssl_data(io_mode, ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
1515 if (GetParam() == SPDY) {
1516 InitializeSpdySsl(&ssl_data);
1517 }
1518 session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
1519
1520 // Redirects in the HTTPS case return errors, but also return sockets.
1521 TestConnectJobDelegate test_delegate;
1522 std::unique_ptr<ConnectJob> connect_job =
1523 CreateConnectJobForTunnel(&test_delegate);
1524 test_delegate.StartJobExpectingResult(connect_job.get(),
1525 ERR_SSL_CLIENT_AUTH_CERT_NEEDED,
1526 io_mode == SYNCHRONOUS);
1527
1528 histogram_tester.ExpectTotalCount(
1529 "Net.HttpProxy.ConnectLatency.Secure.Error", 1);
1530 histogram_tester.ExpectTotalCount(
1531 "Net.HttpProxy.ConnectLatency.Insecure.Error", 0);
1532 }
1533 }
1534
TEST_P(HttpProxyConnectJobTest,TunnelSetupRedirect)1535 TEST_P(HttpProxyConnectJobTest, TunnelSetupRedirect) {
1536 const std::string kRedirectTarget = "https://foo.google.com/";
1537
1538 for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) {
1539 SCOPED_TRACE(io_mode);
1540 session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS);
1541
1542 const std::string kResponseText =
1543 "HTTP/1.1 302 Found\r\n"
1544 "Location: " +
1545 kRedirectTarget +
1546 "\r\n"
1547 "Set-Cookie: foo=bar\r\n"
1548 "\r\n";
1549
1550 MockWrite writes[] = {
1551 MockWrite(io_mode, 0,
1552 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1553 "Host: www.endpoint.test:443\r\n"
1554 "Proxy-Connection: keep-alive\r\n\r\n"),
1555 };
1556 MockRead reads[] = {
1557 MockRead(io_mode, 1, kResponseText.c_str()),
1558 };
1559 SpdyTestUtil spdy_util;
1560 spdy::SpdySerializedFrame req(spdy_util.ConstructSpdyConnect(
1561 nullptr /* extra_headers */, 0 /* extra_header_count */, 1,
1562 DEFAULT_PRIORITY, HostPortPair(kEndpointHost, 443)));
1563 spdy::SpdySerializedFrame rst(
1564 spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
1565
1566 MockWrite spdy_writes[] = {
1567 CreateMockWrite(req, 0, io_mode),
1568 CreateMockWrite(rst, 3, io_mode),
1569 };
1570
1571 const char* const responseHeaders[] = {
1572 "location",
1573 kRedirectTarget.c_str(),
1574 "set-cookie",
1575 "foo=bar",
1576 };
1577 const int responseHeadersSize = std::size(responseHeaders) / 2;
1578 spdy::SpdySerializedFrame resp(spdy_util.ConstructSpdyReplyError(
1579 "302", responseHeaders, responseHeadersSize, 1));
1580 MockRead spdy_reads[] = {
1581 CreateMockRead(resp, 1, ASYNC),
1582 MockRead(ASYNC, 0, 2),
1583 };
1584
1585 Initialize(reads, writes, spdy_reads, spdy_writes, io_mode);
1586
1587 // Redirects during CONNECT returns an error.
1588 TestConnectJobDelegate test_delegate(
1589 TestConnectJobDelegate::SocketExpected::ON_SUCCESS_ONLY);
1590 std::unique_ptr<ConnectJob> connect_job =
1591 CreateConnectJobForTunnel(&test_delegate);
1592
1593 // H2 never completes synchronously.
1594 bool expect_sync_result = (io_mode == SYNCHRONOUS && GetParam() != SPDY);
1595
1596 // We don't trust 302 responses to CONNECT from proxies.
1597 test_delegate.StartJobExpectingResult(
1598 connect_job.get(), ERR_TUNNEL_CONNECTION_FAILED, expect_sync_result);
1599 EXPECT_FALSE(test_delegate.socket());
1600
1601 // Need to close the session to prevent reuse in the next loop iteration.
1602 session_->spdy_session_pool()->CloseAllSessions();
1603 }
1604 }
1605
1606 // Test timeouts in the case of an auth challenge and response.
TEST_P(HttpProxyConnectJobTest,TestTimeoutsAuthChallenge)1607 TEST_P(HttpProxyConnectJobTest, TestTimeoutsAuthChallenge) {
1608 // Wait until this amount of time before something times out.
1609 const base::TimeDelta kTinyTime = base::Microseconds(1);
1610
1611 enum class TimeoutPhase {
1612 CONNECT,
1613 PROXY_HANDSHAKE,
1614 SECOND_PROXY_HANDSHAKE,
1615
1616 NONE,
1617 };
1618
1619 const TimeoutPhase kTimeoutPhases[] = {
1620 TimeoutPhase::CONNECT,
1621 TimeoutPhase::PROXY_HANDSHAKE,
1622 TimeoutPhase::SECOND_PROXY_HANDSHAKE,
1623 TimeoutPhase::NONE,
1624 };
1625
1626 session_deps_.host_resolver->set_ondemand_mode(true);
1627
1628 MockWrite writes[] = {
1629 MockWrite(ASYNC, 0,
1630 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1631 "Host: www.endpoint.test:443\r\n"
1632 "Proxy-Connection: keep-alive\r\n\r\n"),
1633 MockWrite(ASYNC, 3,
1634 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1635 "Host: www.endpoint.test:443\r\n"
1636 "Proxy-Connection: keep-alive\r\n"
1637 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
1638 };
1639 MockRead reads[] = {
1640 // Pause before first response is read.
1641 MockRead(ASYNC, ERR_IO_PENDING, 1),
1642 MockRead(ASYNC, 2,
1643 "HTTP/1.1 407 Proxy Authentication Required\r\n"
1644 "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
1645 "Content-Length: 0\r\n\r\n"),
1646
1647 // Pause again before second response is read.
1648 MockRead(ASYNC, ERR_IO_PENDING, 4),
1649 MockRead(ASYNC, 5, "HTTP/1.1 200 Connection Established\r\n\r\n"),
1650 };
1651
1652 SpdyTestUtil spdy_util;
1653 spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect(
1654 nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority,
1655 HostPortPair(kEndpointHost, 443)));
1656 spdy::SpdySerializedFrame rst(
1657 spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
1658 spdy_util.UpdateWithStreamDestruction(1);
1659
1660 // After calling trans.RestartWithAuth(), this is the request we should
1661 // be issuing -- the final header line contains the credentials.
1662 const char* const kSpdyAuthCredentials[] = {
1663 "proxy-authorization",
1664 "Basic Zm9vOmJhcg==",
1665 };
1666 spdy::SpdySerializedFrame connect2(spdy_util.ConstructSpdyConnect(
1667 kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 3,
1668 HttpProxyConnectJob::kH2QuicTunnelPriority,
1669 HostPortPair(kEndpointHost, 443)));
1670 // This may be sent in some tests, either when tearing down a successful
1671 // connection, or on timeout.
1672 spdy::SpdySerializedFrame rst2(
1673 spdy_util.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL));
1674 MockWrite spdy_writes[] = {
1675 CreateMockWrite(connect, 0, ASYNC),
1676 CreateMockWrite(rst, 3, ASYNC),
1677 CreateMockWrite(connect2, 4, ASYNC),
1678 CreateMockWrite(rst2, 8, ASYNC),
1679 };
1680
1681 // The proxy responds to the connect with a 407, using a persistent
1682 // connection.
1683 const char kAuthStatus[] = "407";
1684 const char* const kAuthChallenge[] = {
1685 "proxy-authenticate",
1686 "Basic realm=\"MyRealm1\"",
1687 };
1688 spdy::SpdySerializedFrame connect_auth_resp(spdy_util.ConstructSpdyReplyError(
1689 kAuthStatus, kAuthChallenge, std::size(kAuthChallenge) / 2, 1));
1690 spdy::SpdySerializedFrame connect2_resp(
1691 spdy_util.ConstructSpdyGetReply(nullptr, 0, 3));
1692 MockRead spdy_reads[] = {
1693 // Pause before first response is read.
1694 MockRead(ASYNC, ERR_IO_PENDING, 1),
1695 CreateMockRead(connect_auth_resp, 2, ASYNC),
1696 // Pause again before second response is read.
1697 MockRead(ASYNC, ERR_IO_PENDING, 5),
1698 CreateMockRead(connect2_resp, 6, ASYNC),
1699 MockRead(ASYNC, OK, 7),
1700 };
1701
1702 for (TimeoutPhase timeout_phase : kTimeoutPhases) {
1703 SCOPED_TRACE(static_cast<int>(timeout_phase));
1704
1705 // Need to close the session to prevent reuse of a session from the last
1706 // loop iteration.
1707 session_->spdy_session_pool()->CloseAllSessions();
1708 // And clear the auth cache to prevent reusing cache entries.
1709 session_->http_auth_cache()->ClearAllEntries();
1710
1711 TestConnectJobDelegate test_delegate;
1712 std::unique_ptr<ConnectJob> connect_job =
1713 CreateConnectJobForTunnel(&test_delegate);
1714
1715 // Connecting should run until the request hits the HostResolver.
1716 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
1717 EXPECT_FALSE(test_delegate.has_result());
1718 EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests());
1719 EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState());
1720
1721 // Run until just before timeout.
1722 FastForwardBy(GetNestedConnectionTimeout() - kTinyTime);
1723 EXPECT_FALSE(test_delegate.has_result());
1724
1725 // Wait until timeout, if appropriate.
1726 if (timeout_phase == TimeoutPhase::CONNECT) {
1727 FastForwardBy(kTinyTime);
1728 ASSERT_TRUE(test_delegate.has_result());
1729 EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
1730 continue;
1731 }
1732
1733 // Add mock reads for socket needed in next step. Connect phase is timed out
1734 // before establishing a connection, so don't need them for
1735 // TimeoutPhase::CONNECT.
1736 Initialize(reads, writes, spdy_reads, spdy_writes, SYNCHRONOUS);
1737
1738 // Finish resolution.
1739 session_deps_.host_resolver->ResolveOnlyRequestNow();
1740 EXPECT_FALSE(test_delegate.has_result());
1741 EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL,
1742 connect_job->GetLoadState());
1743
1744 // Wait until just before negotiation with the tunnel should time out.
1745 FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime);
1746 EXPECT_FALSE(test_delegate.has_result());
1747
1748 if (timeout_phase == TimeoutPhase::PROXY_HANDSHAKE) {
1749 FastForwardBy(kTinyTime);
1750 ASSERT_TRUE(test_delegate.has_result());
1751 EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
1752 continue;
1753 }
1754
1755 data_->Resume();
1756 test_delegate.WaitForAuthChallenge(1);
1757 EXPECT_FALSE(test_delegate.has_result());
1758
1759 // ConnectJobs cannot timeout while showing an auth dialog.
1760 FastForwardBy(base::Days(1));
1761 EXPECT_FALSE(test_delegate.has_result());
1762
1763 // Send credentials
1764 test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar"));
1765 test_delegate.RunAuthCallback();
1766 EXPECT_FALSE(test_delegate.has_result());
1767
1768 FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime);
1769 EXPECT_FALSE(test_delegate.has_result());
1770
1771 if (timeout_phase == TimeoutPhase::SECOND_PROXY_HANDSHAKE) {
1772 FastForwardBy(kTinyTime);
1773 ASSERT_TRUE(test_delegate.has_result());
1774 EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
1775 continue;
1776 }
1777
1778 data_->Resume();
1779 EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk());
1780 }
1781 }
1782
1783 // Same as above, except test the case the first connection cannot be reused
1784 // once credentials are received.
TEST_P(HttpProxyConnectJobTest,TestTimeoutsAuthChallengeNewConnection)1785 TEST_P(HttpProxyConnectJobTest, TestTimeoutsAuthChallengeNewConnection) {
1786 // Proxy-Connection: Close doesn't make sense with H2.
1787 if (GetParam() == SPDY) {
1788 return;
1789 }
1790
1791 enum class TimeoutPhase {
1792 CONNECT,
1793 PROXY_HANDSHAKE,
1794 SECOND_CONNECT,
1795 SECOND_PROXY_HANDSHAKE,
1796
1797 // This has to be last for the H2 proxy case, since success will populate
1798 // the H2 session pool.
1799 NONE,
1800 };
1801
1802 const TimeoutPhase kTimeoutPhases[] = {
1803 TimeoutPhase::CONNECT, TimeoutPhase::PROXY_HANDSHAKE,
1804 TimeoutPhase::SECOND_CONNECT, TimeoutPhase::SECOND_PROXY_HANDSHAKE,
1805 TimeoutPhase::NONE,
1806 };
1807
1808 // Wait until this amount of time before something times out.
1809 const base::TimeDelta kTinyTime = base::Microseconds(1);
1810
1811 session_deps_.host_resolver->set_ondemand_mode(true);
1812
1813 MockWrite writes[] = {
1814 MockWrite(ASYNC, 0,
1815 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1816 "Host: www.endpoint.test:443\r\n"
1817 "Proxy-Connection: keep-alive\r\n\r\n"),
1818 };
1819 MockRead reads[] = {
1820 // Pause at read.
1821 MockRead(ASYNC, ERR_IO_PENDING, 1),
1822 MockRead(ASYNC, 2,
1823 "HTTP/1.1 407 Proxy Authentication Required\r\n"
1824 "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
1825 "Proxy-Connection: Close\r\n"
1826 "Content-Length: 0\r\n\r\n"),
1827 };
1828
1829 MockWrite writes2[] = {
1830 MockWrite(ASYNC, 0,
1831 "CONNECT www.endpoint.test:443 HTTP/1.1\r\n"
1832 "Host: www.endpoint.test:443\r\n"
1833 "Proxy-Connection: keep-alive\r\n"
1834 "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
1835 };
1836 MockRead reads2[] = {
1837 // Pause at read.
1838 MockRead(ASYNC, ERR_IO_PENDING, 1),
1839 MockRead(ASYNC, 2, "HTTP/1.1 200 Connection Established\r\n\r\n"),
1840 };
1841
1842 for (TimeoutPhase timeout_phase : kTimeoutPhases) {
1843 SCOPED_TRACE(static_cast<int>(timeout_phase));
1844
1845 // Need to clear the auth cache to prevent reusing cache entries.
1846 session_->http_auth_cache()->ClearAllEntries();
1847
1848 TestConnectJobDelegate test_delegate;
1849 std::unique_ptr<ConnectJob> connect_job =
1850 CreateConnectJobForTunnel(&test_delegate);
1851
1852 // Connecting should run until the request hits the HostResolver.
1853 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
1854 EXPECT_FALSE(test_delegate.has_result());
1855 EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests());
1856 EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState());
1857
1858 // Run until just before timeout.
1859 FastForwardBy(GetNestedConnectionTimeout() - kTinyTime);
1860 EXPECT_FALSE(test_delegate.has_result());
1861
1862 // Wait until timeout, if appropriate.
1863 if (timeout_phase == TimeoutPhase::CONNECT) {
1864 FastForwardBy(kTinyTime);
1865 ASSERT_TRUE(test_delegate.has_result());
1866 EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
1867 continue;
1868 }
1869
1870 // Add mock reads for socket needed in next step. Connect phase is timed out
1871 // before establishing a connection, so don't need them for
1872 // TimeoutPhase::CONNECT.
1873 Initialize(reads, writes, base::span<MockRead>(), base::span<MockWrite>(),
1874 SYNCHRONOUS);
1875
1876 // Finish resolution.
1877 session_deps_.host_resolver->ResolveOnlyRequestNow();
1878 EXPECT_FALSE(test_delegate.has_result());
1879 EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL,
1880 connect_job->GetLoadState());
1881
1882 // Wait until just before negotiation with the tunnel should time out.
1883 FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime);
1884 EXPECT_FALSE(test_delegate.has_result());
1885
1886 if (timeout_phase == TimeoutPhase::PROXY_HANDSHAKE) {
1887 FastForwardBy(kTinyTime);
1888 ASSERT_TRUE(test_delegate.has_result());
1889 EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
1890 continue;
1891 }
1892
1893 data_->Resume();
1894 test_delegate.WaitForAuthChallenge(1);
1895 EXPECT_FALSE(test_delegate.has_result());
1896
1897 // ConnectJobs cannot timeout while showing an auth dialog.
1898 FastForwardBy(base::Days(1));
1899 EXPECT_FALSE(test_delegate.has_result());
1900
1901 // Send credentials
1902 test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar"));
1903 test_delegate.RunAuthCallback();
1904 EXPECT_FALSE(test_delegate.has_result());
1905
1906 // Since the connection was not reusable, a new connection needs to be
1907 // established.
1908 base::RunLoop().RunUntilIdle();
1909 EXPECT_FALSE(test_delegate.has_result());
1910 EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests());
1911 EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState());
1912
1913 // Run until just before timeout.
1914 FastForwardBy(GetNestedConnectionTimeout() - kTinyTime);
1915 EXPECT_FALSE(test_delegate.has_result());
1916
1917 // Wait until timeout, if appropriate.
1918 if (timeout_phase == TimeoutPhase::SECOND_CONNECT) {
1919 FastForwardBy(kTinyTime);
1920 ASSERT_TRUE(test_delegate.has_result());
1921 EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
1922 continue;
1923 }
1924
1925 // Add mock reads for socket needed in next step. Connect phase is timed out
1926 // before establishing a connection, so don't need them for
1927 // TimeoutPhase::SECOND_CONNECT.
1928 Initialize(reads2, writes2, base::span<MockRead>(), base::span<MockWrite>(),
1929 SYNCHRONOUS);
1930
1931 // Finish resolution.
1932 session_deps_.host_resolver->ResolveOnlyRequestNow();
1933 EXPECT_FALSE(test_delegate.has_result());
1934 EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL,
1935 connect_job->GetLoadState());
1936
1937 // Wait until just before negotiation with the tunnel should time out.
1938 FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime);
1939 EXPECT_FALSE(test_delegate.has_result());
1940
1941 if (timeout_phase == TimeoutPhase::SECOND_PROXY_HANDSHAKE) {
1942 FastForwardBy(kTinyTime);
1943 ASSERT_TRUE(test_delegate.has_result());
1944 EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
1945 continue;
1946 }
1947
1948 data_->Resume();
1949 ASSERT_TRUE(test_delegate.has_result());
1950 EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk());
1951 }
1952 }
1953
TEST_P(HttpProxyConnectJobTest,ConnectionTimeoutNoNQE)1954 TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutNoNQE) {
1955 // Doesn't actually matter whether or not this is for a tunnel - the
1956 // connection timeout is the same, though it probably shouldn't be the same,
1957 // since tunnels need an extra round trip.
1958 base::TimeDelta alternate_connection_timeout =
1959 HttpProxyConnectJob::AlternateNestedConnectionTimeout(
1960 *CreateParams(true /* tunnel */, SecureDnsPolicy::kAllow),
1961 /*network_quality_estimator=*/nullptr);
1962
1963 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
1964 // On Android and iOS, when there's no NQE, there's a hard-coded alternate
1965 // proxy timeout.
1966 EXPECT_EQ(base::Seconds(10), alternate_connection_timeout);
1967 #else
1968 // On other platforms, there is not.
1969 EXPECT_EQ(base::TimeDelta(), alternate_connection_timeout);
1970 #endif
1971 }
1972
TEST_P(HttpProxyConnectJobTest,ConnectionTimeoutMin)1973 TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutMin) {
1974 // Set RTT estimate to a low value.
1975 base::TimeDelta rtt_estimate = base::Milliseconds(1);
1976 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
1977
1978 EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout());
1979
1980 // Test against a large value.
1981 EXPECT_GE(base::Minutes(10), GetNestedConnectionTimeout());
1982
1983 EXPECT_EQ(base::Seconds(8), GetNestedConnectionTimeout());
1984 }
1985
TEST_P(HttpProxyConnectJobTest,ConnectionTimeoutMax)1986 TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutMax) {
1987 // Set RTT estimate to a high value.
1988 base::TimeDelta rtt_estimate = base::Seconds(100);
1989 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
1990
1991 EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout());
1992
1993 // Test against a large value.
1994 EXPECT_GE(base::Minutes(10), GetNestedConnectionTimeout());
1995
1996 EXPECT_EQ(base::Seconds(30), GetNestedConnectionTimeout());
1997 }
1998
1999 // Tests the connection timeout values when the field trial parameters are
2000 // specified.
TEST_P(HttpProxyConnectJobTest,ConnectionTimeoutWithExperiment)2001 TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutWithExperiment) {
2002 // Timeout should be kMultiplier times the HTTP RTT estimate.
2003 const int kMultiplier = 4;
2004 const base::TimeDelta kMinTimeout = base::Seconds(8);
2005 const base::TimeDelta kMaxTimeout = base::Seconds(20);
2006
2007 InitAdaptiveTimeoutFieldTrialWithParams(false, kMultiplier, kMultiplier,
2008 kMinTimeout, kMaxTimeout);
2009 EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout());
2010
2011 base::TimeDelta rtt_estimate = base::Seconds(4);
2012 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2013 base::TimeDelta expected_connection_timeout = kMultiplier * rtt_estimate;
2014 EXPECT_EQ(expected_connection_timeout, GetNestedConnectionTimeout());
2015
2016 // Connection timeout should not exceed kMaxTimeout.
2017 rtt_estimate = base::Seconds(25);
2018 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2019 EXPECT_EQ(kMaxTimeout, GetNestedConnectionTimeout());
2020
2021 // Connection timeout should not be less than kMinTimeout.
2022 rtt_estimate = base::Seconds(0);
2023 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2024 EXPECT_EQ(kMinTimeout, GetNestedConnectionTimeout());
2025 }
2026
2027 // Tests the connection timeout values when the field trial parameters are
2028 // specified.
TEST_P(HttpProxyConnectJobTest,ConnectionTimeoutExperimentDifferentParams)2029 TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutExperimentDifferentParams) {
2030 // Timeout should be kMultiplier times the HTTP RTT estimate.
2031 const int kMultiplier = 3;
2032 const base::TimeDelta kMinTimeout = base::Seconds(2);
2033 const base::TimeDelta kMaxTimeout = base::Seconds(30);
2034
2035 InitAdaptiveTimeoutFieldTrialWithParams(false, kMultiplier, kMultiplier,
2036 kMinTimeout, kMaxTimeout);
2037 EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout());
2038
2039 base::TimeDelta rtt_estimate = base::Seconds(2);
2040 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2041 EXPECT_EQ(kMultiplier * rtt_estimate, GetNestedConnectionTimeout());
2042
2043 // A change in RTT estimate should also change the connection timeout.
2044 rtt_estimate = base::Seconds(7);
2045 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2046 EXPECT_EQ(kMultiplier * rtt_estimate, GetNestedConnectionTimeout());
2047
2048 // Connection timeout should not exceed kMaxTimeout.
2049 rtt_estimate = base::Seconds(35);
2050 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2051 EXPECT_EQ(kMaxTimeout, GetNestedConnectionTimeout());
2052
2053 // Connection timeout should not be less than kMinTimeout.
2054 rtt_estimate = base::Seconds(0);
2055 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2056 EXPECT_EQ(kMinTimeout, GetNestedConnectionTimeout());
2057 }
2058
TEST_P(HttpProxyConnectJobTest,ConnectionTimeoutWithConnectionProperty)2059 TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutWithConnectionProperty) {
2060 const int kSecureMultiplier = 3;
2061 const int kNonSecureMultiplier = 5;
2062 const base::TimeDelta kMinTimeout = base::Seconds(2);
2063 const base::TimeDelta kMaxTimeout = base::Seconds(30);
2064
2065 InitAdaptiveTimeoutFieldTrialWithParams(
2066 false, kSecureMultiplier, kNonSecureMultiplier, kMinTimeout, kMaxTimeout);
2067
2068 const base::TimeDelta kRttEstimate = base::Seconds(2);
2069 network_quality_estimator_->SetStartTimeNullHttpRtt(kRttEstimate);
2070 // By default, connection timeout should return the timeout for secure
2071 // proxies.
2072 if (GetParam() != HTTP) {
2073 EXPECT_EQ(kSecureMultiplier * kRttEstimate, GetNestedConnectionTimeout());
2074 } else {
2075 EXPECT_EQ(kNonSecureMultiplier * kRttEstimate,
2076 GetNestedConnectionTimeout());
2077 }
2078 }
2079
2080 // Tests the connection timeout values when the field trial parameters are not
2081 // specified.
TEST_P(HttpProxyConnectJobTest,ProxyPoolTimeoutWithExperimentDefaultParams)2082 TEST_P(HttpProxyConnectJobTest, ProxyPoolTimeoutWithExperimentDefaultParams) {
2083 InitAdaptiveTimeoutFieldTrialWithParams(true, 0, 0, base::TimeDelta(),
2084 base::TimeDelta());
2085 EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout());
2086
2087 // Timeout should be |http_rtt_multiplier| times the HTTP RTT
2088 // estimate.
2089 base::TimeDelta rtt_estimate = base::Milliseconds(10);
2090 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2091 // Connection timeout should not be less than the HTTP RTT estimate.
2092 EXPECT_LE(rtt_estimate, GetNestedConnectionTimeout());
2093
2094 // A change in RTT estimate should also change the connection timeout.
2095 rtt_estimate = base::Seconds(10);
2096 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2097 // Connection timeout should not be less than the HTTP RTT estimate.
2098 EXPECT_LE(rtt_estimate, GetNestedConnectionTimeout());
2099
2100 // Set RTT to a very large value.
2101 rtt_estimate = base::Minutes(60);
2102 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2103 EXPECT_GT(rtt_estimate, GetNestedConnectionTimeout());
2104
2105 // Set RTT to a very small value.
2106 rtt_estimate = base::Seconds(0);
2107 network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate);
2108 EXPECT_LT(rtt_estimate, GetNestedConnectionTimeout());
2109 }
2110
2111 // A Mock QuicSessionPool which can intercept calls to RequestSession.
2112 class MockQuicSessionPool : public QuicSessionPool {
2113 public:
MockQuicSessionPool(HttpServerProperties * http_server_properties,CertVerifier * cert_verifier,TransportSecurityState * transport_security_state,QuicContext * context)2114 explicit MockQuicSessionPool(HttpServerProperties* http_server_properties,
2115 CertVerifier* cert_verifier,
2116 TransportSecurityState* transport_security_state,
2117 QuicContext* context)
2118 : QuicSessionPool(/*net_log=*/nullptr,
2119 /*host_resolver=*/nullptr,
2120 /*ssl_config_service=*/nullptr,
2121 /*client_socket_factory=*/nullptr,
2122 http_server_properties,
2123 cert_verifier,
2124 transport_security_state,
2125 /*proxy_delegate=*/nullptr,
2126 /*sct_auditing_delegate=*/nullptr,
2127 /*socket_performance_watcher_factory=*/nullptr,
2128 /*quic_crypto_client_stream_factory=*/nullptr,
2129 context) {}
2130
2131 MockQuicSessionPool(const MockQuicSessionPool&) = delete;
2132 MockQuicSessionPool& operator=(const MockQuicSessionPool&) = delete;
2133
2134 ~MockQuicSessionPool() override = default;
2135
2136 // Requests are cancelled during test tear-down, so ignore those calls.
2137 MOCK_METHOD1(CancelRequest, void(QuicSessionRequest* request));
2138
2139 MOCK_METHOD(
2140 int,
2141 RequestSession,
2142 (const QuicSessionKey& session_key,
2143 url::SchemeHostPort destination,
2144 quic::ParsedQuicVersion quic_version,
2145 const std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
2146 const HttpUserAgentSettings* http_user_agent_settings,
2147 RequestPriority priority,
2148 bool use_dns_aliases,
2149 int cert_verify_flags,
2150 const GURL& url,
2151 const NetLogWithSource& net_log,
2152 QuicSessionRequest* request));
2153 };
2154
2155 class HttpProxyConnectQuicJobTest : public HttpProxyConnectJobTestBase,
2156 public testing::Test {
2157 public:
HttpProxyConnectQuicJobTest()2158 HttpProxyConnectQuicJobTest()
2159 : mock_quic_session_pool_(session_->http_server_properties(),
2160 session_->cert_verifier(),
2161 session_->context().transport_security_state,
2162 session_->context().quic_context) {
2163 common_connect_job_params_->quic_session_pool = &mock_quic_session_pool_;
2164 }
2165
2166 protected:
2167 MockQuicSessionPool mock_quic_session_pool_;
2168 };
2169
2170 // Test that a QUIC session is properly requested from the QuicSessionPool.
TEST_F(HttpProxyConnectQuicJobTest,RequestQuicProxy)2171 TEST_F(HttpProxyConnectQuicJobTest, RequestQuicProxy) {
2172 // Create params for a single-hop QUIC proxy. This consists of an
2173 // HttpProxySocketParams, an SSLSocketParams from which a few values are used,
2174 // and a TransportSocketParams which is totally unused but must be non-null.
2175 ProxyChain proxy_chain = ProxyChain::ForIpProtection({ProxyServer(
2176 ProxyServer::SCHEME_QUIC, HostPortPair(kQuicProxyHost, 443))});
2177 SSLConfig quic_ssl_config;
2178 scoped_refptr<HttpProxySocketParams> http_proxy_socket_params =
2179 base::MakeRefCounted<HttpProxySocketParams>(
2180 quic_ssl_config, HostPortPair(kEndpointHost, 443), proxy_chain,
2181 /*proxy_chain_index=*/0, /*tunnel=*/true,
2182 TRAFFIC_ANNOTATION_FOR_TESTS, NetworkAnonymizationKey(),
2183 SecureDnsPolicy::kAllow);
2184
2185 TestConnectJobDelegate test_delegate;
2186 auto connect_job = std::make_unique<HttpProxyConnectJob>(
2187 DEFAULT_PRIORITY, SocketTag(), common_connect_job_params_.get(),
2188 std::move(http_proxy_socket_params), &test_delegate,
2189 /*net_log=*/nullptr);
2190
2191 // Expect a session to be requested, and then leave it pending.
2192 EXPECT_CALL(mock_quic_session_pool_,
2193 RequestSession(_, _, _, _, _, _, _, _, _, _,
2194 QSRHasProxyChain(proxy_chain.Prefix(0))))
2195 .Times(1)
2196 .WillRepeatedly(testing::Return(ERR_IO_PENDING));
2197
2198 // Expect the request to be cancelled during test tear-down.
2199 EXPECT_CALL(mock_quic_session_pool_, CancelRequest).Times(1);
2200
2201 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
2202 }
2203
2204 // Test that a QUIC session is properly requested from the QuicSessionPool,
2205 // including a ProxyChain containing additional QUIC proxies, but excluding any
2206 // proxies later in the chain.
TEST_F(HttpProxyConnectQuicJobTest,RequestMultipleQuicProxies)2207 TEST_F(HttpProxyConnectQuicJobTest, RequestMultipleQuicProxies) {
2208 // Create params for a two-proxy QUIC proxy, as a prefix of a larger chain.
2209 ProxyChain proxy_chain = ProxyChain::ForIpProtection({
2210 ProxyServer(ProxyServer::SCHEME_QUIC, HostPortPair("qproxy1", 443)),
2211 // The proxy_chain_index points to this ProxyServer:
2212 ProxyServer(ProxyServer::SCHEME_QUIC, HostPortPair("qproxy2", 443)),
2213 ProxyServer(ProxyServer::SCHEME_HTTPS, HostPortPair("hproxy1", 443)),
2214 ProxyServer(ProxyServer::SCHEME_HTTPS, HostPortPair("hproxy2", 443)),
2215 });
2216 SSLConfig quic_ssl_config;
2217 scoped_refptr<HttpProxySocketParams> http_proxy_socket_params =
2218 base::MakeRefCounted<HttpProxySocketParams>(
2219 quic_ssl_config, HostPortPair(kEndpointHost, 443), proxy_chain,
2220 /*proxy_chain_index=*/1, /*tunnel=*/true,
2221 TRAFFIC_ANNOTATION_FOR_TESTS, NetworkAnonymizationKey(),
2222 SecureDnsPolicy::kAllow);
2223
2224 TestConnectJobDelegate test_delegate;
2225 auto connect_job = std::make_unique<HttpProxyConnectJob>(
2226 DEFAULT_PRIORITY, SocketTag(), common_connect_job_params_.get(),
2227 std::move(http_proxy_socket_params), &test_delegate,
2228 /*net_log=*/nullptr);
2229
2230 // Expect a session to be requested, and then leave it pending. The requested
2231 // QUIC session is to `qproxy2`, via proxy chain [`qproxy1`].
2232 EXPECT_CALL(mock_quic_session_pool_,
2233 RequestSession(_, _, _, _, _, _, _, _, _, _,
2234 QSRHasProxyChain(proxy_chain.Prefix(1))))
2235 .Times(1)
2236 .WillRepeatedly(testing::Return(ERR_IO_PENDING));
2237
2238 // Expect the request to be cancelled during test tear-down.
2239 EXPECT_CALL(mock_quic_session_pool_, CancelRequest).Times(1);
2240
2241 EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING));
2242 }
2243
2244 } // namespace net
2245