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