blob: 6254c713315c89b0487fe5075de9a9e95d5d1951 [file] [log] [blame]
// Copyright 2024 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/quic/quic_session_pool_proxy_job.h"
#include "base/memory/weak_ptr.h"
#include "net/base/completion_once_callback.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_handle.h"
#include "net/base/request_priority.h"
#include "net/base/trace_constants.h"
#include "net/base/tracing.h"
#include "net/log/net_log_with_source.h"
#include "net/quic/address_utils.h"
#include "net/quic/quic_context.h"
#include "net/quic/quic_crypto_client_config_handle.h"
#include "net/quic/quic_http_stream.h"
#include "net/quic/quic_session_pool.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_packet_writer.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
namespace net {
QuicSessionPool::ProxyJob::ProxyJob(
QuicSessionPool* pool,
quic::ParsedQuicVersion target_quic_version,
const QuicSessionAliasKey& key,
NetworkTrafficAnnotationTag proxy_annotation_tag,
const HttpUserAgentSettings* http_user_agent_settings,
std::unique_ptr<CryptoClientConfigHandle> client_config_handle,
RequestPriority priority,
int cert_verify_flags,
const NetLogWithSource& net_log)
: QuicSessionPool::Job::Job(
pool,
key,
std::move(client_config_handle),
priority,
NetLogWithSource::Make(
net_log.net_log(),
NetLogSourceType::QUIC_SESSION_POOL_PROXY_JOB)),
io_callback_(base::BindRepeating(&QuicSessionPool::ProxyJob::OnIOComplete,
base::Unretained(this))),
target_quic_version_(target_quic_version),
proxy_annotation_tag_(proxy_annotation_tag),
cert_verify_flags_(cert_verify_flags),
http_user_agent_settings_(http_user_agent_settings) {
DCHECK(!Job::key().session_key().proxy_chain().is_direct());
// The job relies on the the proxy to resolve DNS for the destination, so
// cannot determine protocol information from DNS. We must know the QUIC
// version already.
CHECK(target_quic_version.IsKnown())
<< "Cannot make QUIC proxy connections without a known QUIC version";
}
QuicSessionPool::ProxyJob::~ProxyJob() = default;
int QuicSessionPool::ProxyJob::Run(CompletionOnceCallback callback) {
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING) {
callback_ = std::move(callback);
}
return rv > 0 ? OK : rv;
}
void QuicSessionPool::ProxyJob::SetRequestExpectations(
QuicSessionRequest* request) {
// This Job does not do host resolution, but can notify when the session
// creation is finished.
const bool session_creation_finished =
session_attempt_ && session_attempt_->session_creation_finished();
if (!session_creation_finished) {
request->ExpectQuicSessionCreation();
}
}
void QuicSessionPool::ProxyJob::PopulateNetErrorDetails(
NetErrorDetails* details) const {
// First, prefer any error details reported from creating the session over
// which this job is carried.
if (net_error_details_.quic_connection_error != quic::QUIC_NO_ERROR) {
*details = net_error_details_;
return;
}
// Second, prefer to include error details from the session over which this
// job is carried, as any error in that session is "closer to" the client.
if (proxy_session_) {
proxy_session_->PopulateNetErrorDetails(details);
if (details->quic_connection_error != quic::QUIC_NO_ERROR) {
return;
}
}
// Finally, return the error from the session attempt.
if (!session_attempt_ || !session_attempt_->session()) {
return;
}
details->connection_info = QuicHttpStream::ConnectionInfoFromQuicVersion(
session_attempt_->session()->connection()->version());
details->quic_connection_error = session_attempt_->session()->error();
}
int QuicSessionPool::ProxyJob::DoLoop(int rv) {
do {
IoState state = io_state_;
io_state_ = STATE_NONE;
switch (state) {
case STATE_CREATE_PROXY_SESSION:
CHECK_EQ(OK, rv);
rv = DoCreateProxySession();
break;
case STATE_CREATE_PROXY_SESSION_COMPLETE:
rv = DoCreateProxySessionComplete(rv);
break;
case STATE_CREATE_PROXY_STREAM:
CHECK_EQ(OK, rv);
rv = DoCreateProxyStream();
break;
case STATE_CREATE_PROXY_STREAM_COMPLETE:
rv = DoCreateProxyStreamComplete(rv);
break;
case STATE_ATTEMPT_SESSION:
rv = DoAttemptSession();
break;
default:
NOTREACHED_IN_MIGRATION() << "io_state_: " << io_state_;
break;
}
} while (io_state_ != STATE_NONE && rv != ERR_IO_PENDING);
return rv;
}
void QuicSessionPool::ProxyJob::OnSessionAttemptComplete(int rv) {
CHECK_NE(rv, ERR_IO_PENDING);
if (!callback_.is_null()) {
std::move(callback_).Run(rv);
}
}
void QuicSessionPool::ProxyJob::OnIOComplete(int rv) {
rv = DoLoop(rv);
if (rv != ERR_IO_PENDING && !callback_.is_null()) {
std::move(callback_).Run(rv);
}
}
int QuicSessionPool::ProxyJob::DoCreateProxySession() {
io_state_ = STATE_CREATE_PROXY_SESSION_COMPLETE;
net_log().BeginEvent(NetLogEventType::QUIC_SESSION_POOL_PROXY_JOB_CONNECT);
const QuicSessionKey& session_key = key_.session_key();
auto [proxy_chain_prefix, last_proxy_server] =
session_key.proxy_chain().SplitLast();
auto last_server = last_proxy_server.host_port_pair();
url::SchemeHostPort destination(url::kHttpsScheme, last_server.host(),
last_server.port());
net_log_.BeginEventWithStringParams(
NetLogEventType::QUIC_SESSION_POOL_PROXY_JOB_CREATE_PROXY_SESSION,
"destination", destination.Serialize());
// 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();
proxy_session_request_ = std::make_unique<QuicSessionRequest>(pool_);
return proxy_session_request_->Request(
destination, quic_version, proxy_chain_prefix, proxy_annotation_tag_,
http_user_agent_settings_.get(), SessionUsage::kProxy,
session_key.privacy_mode(), priority(), session_key.socket_tag(),
session_key.network_anonymization_key(), session_key.secure_dns_policy(),
session_key.require_dns_https_alpn(), cert_verify_flags_,
GURL("https://" + last_server.ToString()), net_log(), &net_error_details_,
/*failed_on_default_network_callback=*/CompletionOnceCallback(),
io_callback_);
}
int QuicSessionPool::ProxyJob::DoCreateProxySessionComplete(int rv) {
net_log().EndEventWithNetErrorCode(
NetLogEventType::QUIC_SESSION_POOL_PROXY_JOB_CREATE_PROXY_SESSION, rv);
if (rv != 0) {
proxy_session_request_.reset();
return rv;
}
io_state_ = STATE_CREATE_PROXY_STREAM;
proxy_session_ = proxy_session_request_->ReleaseSessionHandle();
proxy_session_request_.reset();
return OK;
}
int QuicSessionPool::ProxyJob::DoCreateProxyStream() {
// Requiring confirmation here means more confidence that the underlying
// connection is working before building the proxy tunnel, at the cost of one
// more round-trip.
io_state_ = STATE_CREATE_PROXY_STREAM_COMPLETE;
return proxy_session_->RequestStream(/*requires_confirmation=*/true,
io_callback_, proxy_annotation_tag_);
}
int QuicSessionPool::ProxyJob::DoCreateProxyStreamComplete(int rv) {
if (rv != 0) {
return rv;
}
proxy_stream_ = proxy_session_->ReleaseStream();
DCHECK(proxy_stream_);
if (!proxy_stream_->IsOpen()) {
return ERR_CONNECTION_CLOSED;
}
io_state_ = STATE_ATTEMPT_SESSION;
return OK;
}
int QuicSessionPool::ProxyJob::DoAttemptSession() {
IPEndPoint local_address;
int rv = proxy_session_->GetSelfAddress(&local_address);
if (rv != 0) {
return rv;
}
IPEndPoint peer_address;
rv = proxy_session_->GetPeerAddress(&peer_address);
if (rv != 0) {
return rv;
}
session_attempt_ = std::make_unique<SessionAttempt>(
this, std::move(local_address), std::move(peer_address),
target_quic_version_, cert_verify_flags_, std::move(proxy_stream_),
http_user_agent_settings_);
return session_attempt_->Start(
base::BindOnce(&ProxyJob::OnSessionAttemptComplete, GetWeakPtr()));
}
} // namespace net