1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
|
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_proxy_connect_job.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <variant>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "http_proxy_client_socket.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/http_user_agent_settings.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_chain.h"
#include "net/base/session_usage.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/log/net_log_source_type.h"
#include "net/log/net_log_with_source.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/quic/quic_context.h"
#include "net/quic/quic_http_utils.h"
#include "net/quic/quic_proxy_client_socket.h"
#include "net/quic/quic_session_key.h"
#include "net/quic/quic_session_pool.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/next_proto.h"
#include "net/socket/ssl_client_socket.h"
#include "net/socket/ssl_connect_job.h"
#include "net/socket/transport_client_socket_pool.h"
#include "net/socket/transport_connect_job.h"
#include "net/spdy/multiplexed_session_creation_initiator.h"
#include "net/spdy/spdy_proxy_client_socket.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/spdy/spdy_stream.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
namespace net {
namespace {
// HttpProxyConnectJobs will time out after this many seconds. Note this is in
// addition to the timeout for the transport socket.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
constexpr base::TimeDelta kHttpProxyConnectJobTunnelTimeout = base::Seconds(10);
#else
constexpr base::TimeDelta kHttpProxyConnectJobTunnelTimeout = base::Seconds(30);
#endif
class HttpProxyTimeoutExperiments {
public:
HttpProxyTimeoutExperiments() { Init(); }
~HttpProxyTimeoutExperiments() = default;
void Init() {
min_proxy_connection_timeout_ =
base::Seconds(GetInt32Param("min_proxy_connection_timeout_seconds", 8));
max_proxy_connection_timeout_ = base::Seconds(
GetInt32Param("max_proxy_connection_timeout_seconds", 30));
ssl_http_rtt_multiplier_ = GetInt32Param("ssl_http_rtt_multiplier", 10);
non_ssl_http_rtt_multiplier_ =
GetInt32Param("non_ssl_http_rtt_multiplier", 5);
DCHECK_LT(0, ssl_http_rtt_multiplier_);
DCHECK_LT(0, non_ssl_http_rtt_multiplier_);
DCHECK_LE(base::TimeDelta(), min_proxy_connection_timeout_);
DCHECK_LE(base::TimeDelta(), max_proxy_connection_timeout_);
DCHECK_LE(min_proxy_connection_timeout_, max_proxy_connection_timeout_);
}
base::TimeDelta min_proxy_connection_timeout() const {
return min_proxy_connection_timeout_;
}
base::TimeDelta max_proxy_connection_timeout() const {
return max_proxy_connection_timeout_;
}
int32_t ssl_http_rtt_multiplier() const { return ssl_http_rtt_multiplier_; }
int32_t non_ssl_http_rtt_multiplier() const {
return non_ssl_http_rtt_multiplier_;
}
private:
// Returns the value of the parameter |param_name| for the field trial
// "NetAdaptiveProxyConnectionTimeout". If the value of the parameter is
// unavailable, then |default_value| is available.
static int32_t GetInt32Param(const std::string& param_name,
int32_t default_value) {
int32_t param;
if (!base::StringToInt(base::GetFieldTrialParamValue(
"NetAdaptiveProxyConnectionTimeout", param_name),
¶m)) {
return default_value;
}
return param;
}
// For secure proxies, the connection timeout is set to
// |ssl_http_rtt_multiplier_| times the HTTP RTT estimate. For insecure
// proxies, the connection timeout is set to |non_ssl_http_rtt_multiplier_|
// times the HTTP RTT estimate. In either case, the connection timeout
// is clamped to be between |min_proxy_connection_timeout_| and
// |max_proxy_connection_timeout_|.
base::TimeDelta min_proxy_connection_timeout_;
base::TimeDelta max_proxy_connection_timeout_;
int32_t ssl_http_rtt_multiplier_;
int32_t non_ssl_http_rtt_multiplier_;
};
HttpProxyTimeoutExperiments* GetProxyTimeoutExperiments() {
static HttpProxyTimeoutExperiments proxy_timeout_experiments;
return &proxy_timeout_experiments;
}
// Make a URL for a proxy, for use in proxy auth challenges.
GURL MakeProxyUrl(const HttpProxySocketParams& params) {
const bool is_https = params.is_over_ssl() || params.is_over_quic();
return GURL((is_https ? "https://" : "http://") +
params.proxy_server().host_port_pair().ToString());
}
} // namespace
HttpProxySocketParams::HttpProxySocketParams(
ConnectJobParams nested_params,
const HostPortPair& endpoint,
const ProxyChain& proxy_chain,
size_t proxy_chain_index,
bool tunnel,
const NetworkTrafficAnnotationTag traffic_annotation,
const NetworkAnonymizationKey& network_anonymization_key,
SecureDnsPolicy secure_dns_policy)
: HttpProxySocketParams(std::move(nested_params),
std::nullopt,
endpoint,
proxy_chain,
proxy_chain_index,
tunnel,
std::move(traffic_annotation),
network_anonymization_key,
secure_dns_policy) {}
HttpProxySocketParams::HttpProxySocketParams(
SSLConfig quic_ssl_config,
const HostPortPair& endpoint,
const ProxyChain& proxy_chain,
size_t proxy_chain_index,
bool tunnel,
const NetworkTrafficAnnotationTag traffic_annotation,
const NetworkAnonymizationKey& network_anonymization_key,
SecureDnsPolicy secure_dns_policy)
: HttpProxySocketParams(std::nullopt,
std::move(quic_ssl_config),
endpoint,
proxy_chain,
proxy_chain_index,
tunnel,
std::move(traffic_annotation),
network_anonymization_key,
secure_dns_policy) {}
HttpProxySocketParams::HttpProxySocketParams(
std::optional<ConnectJobParams> nested_params,
std::optional<SSLConfig> quic_ssl_config,
const HostPortPair& endpoint,
const ProxyChain& proxy_chain,
size_t proxy_chain_index,
bool tunnel,
const NetworkTrafficAnnotationTag traffic_annotation,
const NetworkAnonymizationKey& network_anonymization_key,
SecureDnsPolicy secure_dns_policy)
: nested_params_(std::move(nested_params)),
quic_ssl_config_(std::move(quic_ssl_config)),
endpoint_(endpoint),
proxy_chain_(proxy_chain),
proxy_chain_index_(proxy_chain_index),
tunnel_(tunnel),
network_anonymization_key_(network_anonymization_key),
traffic_annotation_(traffic_annotation),
secure_dns_policy_(secure_dns_policy) {
DCHECK(!proxy_chain_.is_direct());
DCHECK(proxy_chain_.IsValid());
CHECK(proxy_chain_index_ < proxy_chain_.length());
// This is either a connection to an HTTP proxy,an SSL proxy, or a QUIC proxy.
DCHECK(nested_params_ || quic_ssl_config_);
DCHECK(!(nested_params_ && quic_ssl_config_));
// Only supports proxy endpoints without scheme for now.
// TODO(crbug.com/40181080): Handle scheme.
if (is_over_transport()) {
DCHECK(std::holds_alternative<HostPortPair>(
nested_params_->transport()->destination()));
} else if (is_over_ssl() && nested_params_->ssl()->GetConnectionType() ==
SSLSocketParams::ConnectionType::DIRECT) {
DCHECK(std::holds_alternative<HostPortPair>(
nested_params_->ssl()->GetDirectConnectionParams()->destination()));
}
}
HttpProxySocketParams::~HttpProxySocketParams() = default;
std::unique_ptr<HttpProxyConnectJob> HttpProxyConnectJob::Factory::Create(
RequestPriority priority,
const SocketTag& socket_tag,
const CommonConnectJobParams* common_connect_job_params,
scoped_refptr<HttpProxySocketParams> params,
ConnectJob::Delegate* delegate,
const NetLogWithSource* net_log) {
return std::make_unique<HttpProxyConnectJob>(
priority, socket_tag, common_connect_job_params, std::move(params),
delegate, net_log);
}
HttpProxyConnectJob::HttpProxyConnectJob(
RequestPriority priority,
const SocketTag& socket_tag,
const CommonConnectJobParams* common_connect_job_params,
scoped_refptr<HttpProxySocketParams> params,
ConnectJob::Delegate* delegate,
const NetLogWithSource* net_log)
: ConnectJob(priority,
socket_tag,
base::TimeDelta() /* The socket takes care of timeouts */,
common_connect_job_params,
delegate,
net_log,
NetLogSourceType::HTTP_PROXY_CONNECT_JOB,
NetLogEventType::HTTP_PROXY_CONNECT_JOB_CONNECT),
params_(std::move(params)),
http_auth_controller_(
params_->tunnel()
? base::MakeRefCounted<HttpAuthController>(
HttpAuth::AUTH_PROXY,
MakeProxyUrl(*params_),
params_->network_anonymization_key(),
common_connect_job_params->http_auth_cache,
common_connect_job_params->http_auth_handler_factory,
host_resolver())
: nullptr) {}
HttpProxyConnectJob::~HttpProxyConnectJob() = default;
const RequestPriority HttpProxyConnectJob::kH2QuicTunnelPriority =
DEFAULT_PRIORITY;
LoadState HttpProxyConnectJob::GetLoadState() const {
switch (next_state_) {
case STATE_TRANSPORT_CONNECT_COMPLETE:
return nested_connect_job_->GetLoadState();
case STATE_HTTP_PROXY_CONNECT:
case STATE_HTTP_PROXY_CONNECT_COMPLETE:
case STATE_SPDY_PROXY_CREATE_STREAM:
case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
case STATE_QUIC_PROXY_CREATE_SESSION:
case STATE_QUIC_PROXY_CREATE_STREAM:
case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
case STATE_RESTART_WITH_AUTH:
case STATE_RESTART_WITH_AUTH_COMPLETE:
return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
// This state shouldn't be possible to be called in.
case STATE_TRANSPORT_CONNECT:
NOTREACHED();
case STATE_BEGIN_CONNECT:
case STATE_NONE:
// May be possible for this method to be called after an error, shouldn't
// be called after a successful connect.
break;
}
return LOAD_STATE_IDLE;
}
bool HttpProxyConnectJob::HasEstablishedConnection() const {
if (has_established_connection_) {
return true;
}
// It's possible the nested connect job has established a connection, but
// hasn't completed yet (For example, an SSLConnectJob may be negotiating
// SSL).
if (nested_connect_job_) {
return nested_connect_job_->HasEstablishedConnection();
}
return false;
}
ResolveErrorInfo HttpProxyConnectJob::GetResolveErrorInfo() const {
return resolve_error_info_;
}
bool HttpProxyConnectJob::IsSSLError() const {
return ssl_cert_request_info_ != nullptr;
}
scoped_refptr<SSLCertRequestInfo> HttpProxyConnectJob::GetCertRequestInfo() {
return ssl_cert_request_info_;
}
void HttpProxyConnectJob::OnConnectJobComplete(int result, ConnectJob* job) {
DCHECK_EQ(nested_connect_job_.get(), job);
DCHECK_EQ(next_state_, STATE_TRANSPORT_CONNECT_COMPLETE);
OnIOComplete(result);
}
void HttpProxyConnectJob::OnNeedsProxyAuth(
const HttpResponseInfo& response,
HttpAuthController* auth_controller,
base::OnceClosure restart_with_auth_callback,
ConnectJob* job) {
// None of the nested ConnectJob used by this class can encounter auth
// challenges. Instead, the challenges are returned by the ProxyClientSocket
// implementations after nested_connect_job_ has already established a
// connection.
NOTREACHED();
}
Error HttpProxyConnectJob::OnDestinationDnsAliasesResolved(
const std::set<std::string>& aliases,
ConnectJob* job) {
// Do nothing and return OK when DNS aliases for HTTP proxy hostnames since
// higher-level layers will not take action on these.
return OK;
}
base::TimeDelta HttpProxyConnectJob::AlternateNestedConnectionTimeout(
const HttpProxySocketParams& params,
const NetworkQualityEstimator* network_quality_estimator) {
base::TimeDelta default_alternate_timeout;
// On Android and iOS, a default proxy connection timeout is used instead of
// the actual TCP/SSL timeouts of nested jobs.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
default_alternate_timeout = kHttpProxyConnectJobTunnelTimeout;
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
bool is_https = params.proxy_server().is_https();
if (!network_quality_estimator) {
return default_alternate_timeout;
}
std::optional<base::TimeDelta> http_rtt_estimate =
network_quality_estimator->GetHttpRTT();
if (!http_rtt_estimate) {
return default_alternate_timeout;
}
int32_t multiplier =
is_https ? GetProxyTimeoutExperiments()->ssl_http_rtt_multiplier()
: GetProxyTimeoutExperiments()->non_ssl_http_rtt_multiplier();
base::TimeDelta timeout = multiplier * http_rtt_estimate.value();
// Ensure that connection timeout is between
// |min_proxy_connection_timeout_| and |max_proxy_connection_timeout_|.
return std::clamp(
timeout, GetProxyTimeoutExperiments()->min_proxy_connection_timeout(),
GetProxyTimeoutExperiments()->max_proxy_connection_timeout());
}
base::TimeDelta HttpProxyConnectJob::TunnelTimeoutForTesting() {
return kHttpProxyConnectJobTunnelTimeout;
}
void HttpProxyConnectJob::UpdateFieldTrialParametersForTesting() {
GetProxyTimeoutExperiments()->Init();
}
int HttpProxyConnectJob::ConnectInternal() {
DCHECK_EQ(next_state_, STATE_NONE);
next_state_ = STATE_BEGIN_CONNECT;
return DoLoop(OK);
}
ProxyServer::Scheme HttpProxyConnectJob::GetProxyServerScheme() const {
return params_->proxy_server().scheme();
}
void HttpProxyConnectJob::OnIOComplete(int result) {
int rv = DoLoop(result);
if (rv != ERR_IO_PENDING) {
// May delete |this|.
NotifyDelegateOfCompletion(rv);
}
}
void HttpProxyConnectJob::RestartWithAuthCredentials() {
DCHECK(transport_socket_);
DCHECK_EQ(STATE_NONE, next_state_);
// Always do this asynchronously, to avoid re-entrancy.
next_state_ = STATE_RESTART_WITH_AUTH;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
weak_ptr_factory_.GetWeakPtr(), OK));
}
int HttpProxyConnectJob::DoLoop(int result) {
DCHECK_NE(next_state_, STATE_NONE);
int rv = result;
do {
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_BEGIN_CONNECT:
DCHECK_EQ(OK, rv);
rv = DoBeginConnect();
break;
case STATE_TRANSPORT_CONNECT:
DCHECK_EQ(OK, rv);
rv = DoTransportConnect();
break;
case STATE_TRANSPORT_CONNECT_COMPLETE:
rv = DoTransportConnectComplete(rv);
break;
case STATE_HTTP_PROXY_CONNECT:
DCHECK_EQ(OK, rv);
rv = DoHttpProxyConnect();
break;
case STATE_HTTP_PROXY_CONNECT_COMPLETE:
rv = DoHttpProxyConnectComplete(rv);
break;
case STATE_SPDY_PROXY_CREATE_STREAM:
DCHECK_EQ(OK, rv);
rv = DoSpdyProxyCreateStream();
break;
case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
rv = DoSpdyProxyCreateStreamComplete(rv);
break;
case STATE_QUIC_PROXY_CREATE_SESSION:
DCHECK_EQ(OK, rv);
rv = DoQuicProxyCreateSession();
break;
case STATE_QUIC_PROXY_CREATE_STREAM:
rv = DoQuicProxyCreateStream(rv);
break;
case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
rv = DoQuicProxyCreateStreamComplete(rv);
break;
case STATE_RESTART_WITH_AUTH:
DCHECK_EQ(OK, rv);
rv = DoRestartWithAuth();
break;
case STATE_RESTART_WITH_AUTH_COMPLETE:
rv = DoRestartWithAuthComplete(rv);
break;
default:
NOTREACHED() << "bad state";
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
return rv;
}
int HttpProxyConnectJob::DoBeginConnect() {
connect_start_time_ = base::TimeTicks::Now();
ResetTimer(
AlternateNestedConnectionTimeout(*params_, network_quality_estimator()));
switch (GetProxyServerScheme()) {
case ProxyServer::SCHEME_QUIC:
next_state_ = STATE_QUIC_PROXY_CREATE_SESSION;
// QUIC connections are always considered to have been established.
// |has_established_connection_| is only used to start retries if a
// connection hasn't been established yet, and QUIC has its own connection
// establishment logic.
has_established_connection_ = true;
break;
case ProxyServer::SCHEME_HTTP:
case ProxyServer::SCHEME_HTTPS:
next_state_ = STATE_TRANSPORT_CONNECT;
break;
default:
NOTREACHED();
}
return OK;
}
int HttpProxyConnectJob::DoTransportConnect() {
ProxyServer::Scheme scheme = GetProxyServerScheme();
if (scheme == ProxyServer::SCHEME_HTTP) {
nested_connect_job_ = std::make_unique<TransportConnectJob>(
priority(), socket_tag(), common_connect_job_params(),
params_->transport_params(), this, &net_log());
} else {
DCHECK_EQ(scheme, ProxyServer::SCHEME_HTTPS);
DCHECK(params_->is_over_ssl());
// Skip making a new connection if we have an existing HTTP/2 session.
if (params_->tunnel() &&
common_connect_job_params()->spdy_session_pool->FindAvailableSession(
CreateSpdySessionKey(), /*enable_ip_based_pooling=*/false,
/*is_websocket=*/false, net_log())) {
next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
return OK;
}
nested_connect_job_ = std::make_unique<SSLConnectJob>(
priority(), socket_tag(), common_connect_job_params(),
params_->ssl_params(), this, &net_log());
}
next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
return nested_connect_job_->Connect();
}
int HttpProxyConnectJob::DoTransportConnectComplete(int result) {
resolve_error_info_ = nested_connect_job_->GetResolveErrorInfo();
ProxyServer::Scheme scheme = GetProxyServerScheme();
if (result != OK) {
// Only record latency for connections to the first proxy in a chain.
if (params_->proxy_chain_index() == 0) {
EmitConnectLatency(NextProto::kProtoUnknown,
params_->proxy_server().scheme(),
HttpConnectResult::kError,
base::TimeTicks::Now() - connect_start_time_);
}
if (IsCertificateError(result)) {
DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
// TODO(rch): allow the user to deal with proxy cert errors in the
// same way as server cert errors.
return ERR_PROXY_CERTIFICATE_INVALID;
}
if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
ssl_cert_request_info_ = nested_connect_job_->GetCertRequestInfo();
if (params_->proxy_chain().is_multi_proxy() && !ssl_cert_request_info_) {
// When multi-proxy chains are in use, it's possible that a client auth
// cert is requested by the first proxy after the transport connection
// to it has been established. When this occurs,
// ERR_SSL_CLIENT_AUTH_CERT_NEEDED will get passed back to the parent
// SSLConnectJob and then to the parent HttpProxyConnectJob, but the SSL
// cert request info won't have been set up for the parent
// HttpProxyConnectJob to use it in this method. Fail gracefully when
// this case is encountered.
// TODO(crbug.com/40284947): Investigate whether changes are
// needed to support making the SSL cert request info available here in
// the case described above. Just returning `result` here makes the
// behavior for multi-proxy chains match that of single-proxy chains
// (where the proxied request fails with ERR_SSL_CLIENT_AUTH_CERT_NEEDED
// and no `SSLCertRequestInfo` is available from the corresponding
// `ResponseInfo`), though, so it could be that no further action is
// needed here.
return result;
}
DCHECK(ssl_cert_request_info_);
ssl_cert_request_info_->is_proxy = true;
return result;
}
// If this transport connection was attempting to be made through other
// proxies, prefer to propagate errors from attempting to establish the
// previous proxy connection(s) instead of returning
// `ERR_PROXY_CONNECTION_FAILED`. For instance, if the attempt to connect to
// the first proxy resulted in `ERR_PROXY_HTTP_1_1_REQUIRED`, return that so
// that the whole job will be restarted using HTTP/1.1.
if (params_->proxy_chain_index() != 0) {
return result;
}
return ERR_PROXY_CONNECTION_FAILED;
}
NextProto next_proto = nested_connect_job_->socket()->GetNegotiatedProtocol();
// Only record latency for connections to the first proxy in a chain.
if (params_->proxy_chain_index() == 0) {
EmitConnectLatency(next_proto, params_->proxy_server().scheme(),
HttpConnectResult::kSuccess,
base::TimeTicks::Now() - connect_start_time_);
}
has_established_connection_ = true;
if (!params_->tunnel()) {
// If not tunneling, this is an HTTP URL being fetched directly over the
// proxy. Return the underlying socket directly. The caller will handle the
// ALPN protocol, etc., from here. Clear the DNS aliases to match the other
// proxy codepaths.
SetSocket(nested_connect_job_->PassSocket(),
/*dns_aliases=*/std::set<std::string>());
return result;
}
// Establish a tunnel over the proxy by making a CONNECT request. HTTP/1.1 and
// HTTP/2 handle CONNECT differently.
if (next_proto == NextProto::kProtoHTTP2) {
DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
} else {
next_state_ = STATE_HTTP_PROXY_CONNECT;
}
return result;
}
int HttpProxyConnectJob::DoHttpProxyConnect() {
DCHECK(params_->tunnel());
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
// Reset the timer to just the length of time allowed for HttpProxy handshake
// so that a fast TCP connection plus a slow HttpProxy failure doesn't take
// longer to timeout than it should.
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
// Add a HttpProxy connection on top of the tcp socket.
transport_socket_ = std::make_unique<HttpProxyClientSocket>(
nested_connect_job_->PassSocket(), GetUserAgent(), params_->endpoint(),
params_->proxy_chain(), params_->proxy_chain_index(),
http_auth_controller_, common_connect_job_params()->proxy_delegate,
params_->traffic_annotation());
nested_connect_job_.reset();
return transport_socket_->Connect(base::BindOnce(
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
}
int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) {
// Always inform caller of auth requests asynchronously.
if (result == ERR_PROXY_AUTH_REQUESTED) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&HttpProxyConnectJob::OnAuthChallenge,
weak_ptr_factory_.GetWeakPtr()));
return ERR_IO_PENDING;
}
if (result == ERR_HTTP_1_1_REQUIRED) {
return ERR_PROXY_HTTP_1_1_REQUIRED;
}
// In TLS 1.2 with False Start or TLS 1.3, alerts from the server rejecting
// our client certificate are received at the first Read(), not Connect(), so
// the error mapping in DoTransportConnectComplete does not apply. Repeat the
// mapping here.
if (result == ERR_BAD_SSL_CLIENT_AUTH_CERT) {
return ERR_PROXY_CONNECTION_FAILED;
}
if (result == OK) {
SetSocket(std::move(transport_socket_), /*dns_aliases=*/std::nullopt);
}
return result;
}
int HttpProxyConnectJob::DoSpdyProxyCreateStream() {
DCHECK(params_->tunnel());
DCHECK(params_->is_over_ssl());
// Reset the timer to just the length of time allowed for HttpProxy handshake
// so that a fast TCP connection plus a slow HttpProxy failure doesn't take
// longer to timeout than it should.
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
SpdySessionKey key = CreateSpdySessionKey();
base::WeakPtr<SpdySession> spdy_session =
common_connect_job_params()->spdy_session_pool->FindAvailableSession(
key, /* enable_ip_based_pooling = */ false,
/* is_websocket = */ false, net_log());
// It's possible that a session to the proxy has recently been created
if (spdy_session) {
nested_connect_job_.reset();
} else {
// Create a session direct to the proxy itself
base::expected<base::WeakPtr<SpdySession>, int> spdy_session_result =
common_connect_job_params()
->spdy_session_pool->CreateAvailableSessionFromSocket(
key, nested_connect_job_->PassSocket(),
nested_connect_job_->connect_timing(), net_log(),
SpdySessionInitiator::kHttpProxyConnectJob);
nested_connect_job_.reset();
if (!spdy_session_result.has_value()) {
return spdy_session_result.error();
}
spdy_session = std::move(spdy_session_result.value());
}
next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE;
spdy_stream_request_ = std::make_unique<SpdyStreamRequest>();
return spdy_stream_request_->StartRequest(
SPDY_BIDIRECTIONAL_STREAM, spdy_session,
GURL("https://" + params_->endpoint().ToString()),
false /* no early data */, kH2QuicTunnelPriority, socket_tag(),
spdy_session->net_log(),
base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
base::Unretained(this)),
params_->traffic_annotation());
}
int HttpProxyConnectJob::DoSpdyProxyCreateStreamComplete(int result) {
if (result < 0) {
// See the comment in DoHttpProxyConnectComplete(). HTTP/2 proxies will
// typically also fail here, as a result of SpdyProxyClientSocket::Connect()
// below, but the error may surface out of SpdyStreamRequest if there were
// enough requests in parallel that stream creation became asynchronous.
if (result == ERR_BAD_SSL_CLIENT_AUTH_CERT) {
result = ERR_PROXY_CONNECTION_FAILED;
}
spdy_stream_request_.reset();
return result;
}
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
base::WeakPtr<SpdyStream> stream = spdy_stream_request_->ReleaseStream();
spdy_stream_request_.reset();
DCHECK(stream.get());
// |transport_socket_| will set itself as |stream|'s delegate.
transport_socket_ = std::make_unique<SpdyProxyClientSocket>(
stream, params_->proxy_chain(), params_->proxy_chain_index(),
GetUserAgent(), params_->endpoint(), net_log(), http_auth_controller_,
common_connect_job_params()->proxy_delegate);
return transport_socket_->Connect(base::BindOnce(
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
}
int HttpProxyConnectJob::DoQuicProxyCreateSession() {
DCHECK(params_->tunnel());
DCHECK(!common_connect_job_params()->quic_supported_versions->empty());
const SSLConfig& ssl_config = params_->quic_ssl_config().value();
// Reset the timer to just the length of time allowed for HttpProxy handshake
// so that a fast QUIC connection plus a slow tunnel setup doesn't take longer
// to timeout than it should.
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
next_state_ = STATE_QUIC_PROXY_CREATE_STREAM;
const HostPortPair& proxy_server = params_->proxy_server().host_port_pair();
quic_session_request_ = std::make_unique<QuicSessionRequest>(
common_connect_job_params()->quic_session_pool);
// Select the default QUIC version for the session to the proxy, since there
// is no DNS or Alt-Svc information to use.
quic::ParsedQuicVersion quic_version = SupportedQuicVersionForProxying();
// The QuicSessionRequest will handle connecting to any proxies earlier in the
// chain to this one, but expects a ProxyChain containing only QUIC proxies.
ProxyChain quic_proxies =
params_->proxy_chain().Prefix(params_->proxy_chain_index());
// The ConnectJobParamsFactory ensures that this prefix is all QUIC proxies.
for (const ProxyServer& ps : quic_proxies.proxy_servers()) {
CHECK(ps.is_quic());
}
return quic_session_request_->Request(
// TODO(crbug.com/40181080) Pass the destination directly once it's
// converted to contain scheme.
url::SchemeHostPort(url::kHttpsScheme, proxy_server.host(),
proxy_server.port()),
quic_version, quic_proxies, params_->traffic_annotation(),
http_user_agent_settings(), SessionUsage::kProxy, ssl_config.privacy_mode,
kH2QuicTunnelPriority, socket_tag(), params_->network_anonymization_key(),
params_->secure_dns_policy(),
/*require_dns_https_alpn=*/false, ssl_config.GetCertVerifyFlags(),
GURL("https://" + proxy_server.ToString()), net_log(),
&quic_net_error_details_, MultiplexedSessionCreationInitiator::kUnknown,
/*management_config=*/std::nullopt,
/*failed_on_default_network_callback=*/CompletionOnceCallback(),
base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
base::Unretained(this)));
}
int HttpProxyConnectJob::DoQuicProxyCreateStream(int result) {
if (result < 0) {
quic_session_request_.reset();
return result;
}
next_state_ = STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE;
quic_session_ = quic_session_request_->ReleaseSessionHandle();
quic_session_request_.reset();
return quic_session_->RequestStream(
false,
base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
base::Unretained(this)),
params_->traffic_annotation());
}
int HttpProxyConnectJob::DoQuicProxyCreateStreamComplete(int result) {
if (result < 0) {
return result;
}
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
std::unique_ptr<QuicChromiumClientStream::Handle> quic_stream =
quic_session_->ReleaseStream();
uint8_t urgency = ConvertRequestPriorityToQuicPriority(kH2QuicTunnelPriority);
quic_stream->SetPriority(quic::QuicStreamPriority(
quic::HttpStreamPriority{urgency, kDefaultPriorityIncremental}));
transport_socket_ = std::make_unique<QuicProxyClientSocket>(
std::move(quic_stream), std::move(quic_session_), params_->proxy_chain(),
params_->proxy_chain_index(), GetUserAgent(), params_->endpoint(),
net_log(), http_auth_controller_,
common_connect_job_params()->proxy_delegate);
return transport_socket_->Connect(base::BindOnce(
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
}
int HttpProxyConnectJob::DoRestartWithAuth() {
DCHECK(transport_socket_);
// Start the timeout timer again.
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
next_state_ = STATE_RESTART_WITH_AUTH_COMPLETE;
return transport_socket_->RestartWithAuth(base::BindOnce(
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
}
int HttpProxyConnectJob::DoRestartWithAuthComplete(int result) {
DCHECK_NE(ERR_IO_PENDING, result);
if (result == OK && !transport_socket_->IsConnected()) {
result = ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
}
// If the connection could not be reused to attempt to send proxy auth
// credentials, try reconnecting. Do not reset the HttpAuthController in this
// case; the server may, for instance, send "Proxy-Connection: close" and
// expect that each leg of the authentication progress on separate
// connections.
bool reconnect = result == ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
// If auth credentials were sent but the connection was closed, the server may
// have timed out while the user was selecting credentials. Retry once.
if (!has_restarted_ &&
(result == ERR_CONNECTION_CLOSED || result == ERR_CONNECTION_RESET ||
result == ERR_CONNECTION_ABORTED ||
result == ERR_SOCKET_NOT_CONNECTED)) {
reconnect = true;
has_restarted_ = true;
// Release any auth state bound to the connection. The new connection will
// start the current scheme and identity from scratch.
if (http_auth_controller_) {
http_auth_controller_->OnConnectionClosed();
}
}
if (reconnect) {
// Attempt to create a new one.
transport_socket_.reset();
next_state_ = STATE_BEGIN_CONNECT;
return OK;
}
// If not reconnecting, treat the result as the result of establishing a
// tunnel through the proxy. This is important in the case another auth
// challenge is seen.
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
return result;
}
void HttpProxyConnectJob::ChangePriorityInternal(RequestPriority priority) {
// Do not set the priority on |spdy_stream_request_| or
// |quic_session_request_|, since those should always use
// kH2QuicTunnelPriority.
if (nested_connect_job_) {
nested_connect_job_->ChangePriority(priority);
}
if (transport_socket_) {
transport_socket_->SetStreamPriority(priority);
}
}
void HttpProxyConnectJob::OnTimedOutInternal() {
// Only record latency for connections to the first proxy in a chain.
if (next_state_ == STATE_TRANSPORT_CONNECT_COMPLETE &&
params_->proxy_chain_index() == 0) {
EmitConnectLatency(NextProto::kProtoUnknown,
params_->proxy_server().scheme(),
HttpConnectResult::kTimedOut,
base::TimeTicks::Now() - connect_start_time_);
}
}
void HttpProxyConnectJob::OnAuthChallenge() {
// Stop timer while potentially waiting for user input.
ResetTimer(base::TimeDelta());
NotifyDelegateOfProxyAuth(
*transport_socket_->GetConnectResponseInfo(),
transport_socket_->GetAuthController().get(),
base::BindOnce(&HttpProxyConnectJob::RestartWithAuthCredentials,
weak_ptr_factory_.GetWeakPtr()));
}
std::string HttpProxyConnectJob::GetUserAgent() const {
if (!http_user_agent_settings()) {
return std::string();
}
return http_user_agent_settings()->GetUserAgent();
}
SpdySessionKey HttpProxyConnectJob::CreateSpdySessionKey() const {
// Construct the SpdySessionKey using a ProxyChain that corresponds to what we
// are sending the CONNECT to. For the first proxy server use
// `ProxyChain::Direct()`, and for the others use a proxy chain containing all
// proxy servers that we have already connected through.
std::vector<ProxyServer> intermediate_proxy_servers;
for (size_t proxy_index = 0; proxy_index < params_->proxy_chain_index();
++proxy_index) {
intermediate_proxy_servers.push_back(
params_->proxy_chain().GetProxyServer(proxy_index));
}
ProxyChain session_key_proxy_chain(std::move(intermediate_proxy_servers));
if (params_->proxy_chain_index() == 0) {
DCHECK(session_key_proxy_chain.is_direct());
}
// Note that `disable_cert_network_fetches` must be true for proxies to avoid
// deadlock. See comment on
// `SSLConfig::disable_cert_verification_network_fetches`.
return SpdySessionKey(
params_->proxy_server().host_port_pair(), PRIVACY_MODE_DISABLED,
session_key_proxy_chain, SessionUsage::kProxy, socket_tag(),
params_->network_anonymization_key(), params_->secure_dns_policy(),
/*disable_cert_verification_network_fetches=*/true);
}
// static
void HttpProxyConnectJob::EmitConnectLatency(NextProto http_version,
ProxyServer::Scheme scheme,
HttpConnectResult result,
base::TimeDelta latency) {
std::string_view http_version_piece;
switch (http_version) {
case NextProto::kProtoUnknown:
// fall through to assume Http1
case NextProto::kProtoHTTP11:
http_version_piece = "Http1";
break;
case NextProto::kProtoHTTP2:
http_version_piece = "Http2";
break;
case NextProto::kProtoQUIC:
http_version_piece = "Http3";
break;
default:
NOTREACHED();
}
std::string_view scheme_piece;
switch (scheme) {
case ProxyServer::SCHEME_HTTP:
scheme_piece = "Http";
break;
case ProxyServer::SCHEME_HTTPS:
scheme_piece = "Https";
break;
case ProxyServer::SCHEME_QUIC:
scheme_piece = "Quic";
break;
case ProxyServer::SCHEME_INVALID:
case ProxyServer::SCHEME_SOCKS4:
case ProxyServer::SCHEME_SOCKS5:
default:
NOTREACHED();
}
std::string_view result_piece;
switch (result) {
case HttpConnectResult::kSuccess:
result_piece = "Success";
break;
case HttpConnectResult::kError:
result_piece = "Error";
break;
case HttpConnectResult::kTimedOut:
result_piece = "TimedOut";
break;
default:
NOTREACHED();
}
std::string histogram =
base::StrCat({"Net.HttpProxy.ConnectLatency.", http_version_piece, ".",
scheme_piece, ".", result_piece});
base::UmaHistogramMediumTimes(histogram, latency);
}
} // namespace net
|