1 // Copyright 2024 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/quic/quic_session_pool_proxy_job.h"
6
7 #include "base/memory/weak_ptr.h"
8 #include "net/base/completion_once_callback.h"
9 #include "net/base/network_change_notifier.h"
10 #include "net/base/network_handle.h"
11 #include "net/base/request_priority.h"
12 #include "net/base/trace_constants.h"
13 #include "net/base/tracing.h"
14 #include "net/log/net_log_with_source.h"
15 #include "net/quic/address_utils.h"
16 #include "net/quic/quic_context.h"
17 #include "net/quic/quic_crypto_client_config_handle.h"
18 #include "net/quic/quic_http_stream.h"
19 #include "net/quic/quic_session_pool.h"
20 #include "net/spdy/multiplexed_session_creation_initiator.h"
21 #include "net/third_party/quiche/src/quiche/quic/core/quic_packet_writer.h"
22 #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
23
24 namespace net {
25
ProxyJob(QuicSessionPool * pool,quic::ParsedQuicVersion target_quic_version,QuicSessionAliasKey key,NetworkTrafficAnnotationTag proxy_annotation_tag,MultiplexedSessionCreationInitiator session_creation_initiator,const HttpUserAgentSettings * http_user_agent_settings,std::unique_ptr<CryptoClientConfigHandle> client_config_handle,RequestPriority priority,int cert_verify_flags,const NetLogWithSource & net_log)26 QuicSessionPool::ProxyJob::ProxyJob(
27 QuicSessionPool* pool,
28 quic::ParsedQuicVersion target_quic_version,
29 QuicSessionAliasKey key,
30 NetworkTrafficAnnotationTag proxy_annotation_tag,
31 MultiplexedSessionCreationInitiator session_creation_initiator,
32 const HttpUserAgentSettings* http_user_agent_settings,
33 std::unique_ptr<CryptoClientConfigHandle> client_config_handle,
34 RequestPriority priority,
35 int cert_verify_flags,
36 const NetLogWithSource& net_log)
37 : QuicSessionPool::Job::Job(
38 pool,
39 std::move(key),
40 std::move(client_config_handle),
41 priority,
42 NetLogWithSource::Make(
43 net_log.net_log(),
44 NetLogSourceType::QUIC_SESSION_POOL_PROXY_JOB)),
45 io_callback_(base::BindRepeating(&QuicSessionPool::ProxyJob::OnIOComplete,
46 base::Unretained(this))),
47 target_quic_version_(target_quic_version),
48 proxy_annotation_tag_(proxy_annotation_tag),
49 session_creation_initiator_(session_creation_initiator),
50 cert_verify_flags_(cert_verify_flags),
51 http_user_agent_settings_(http_user_agent_settings) {
52 DCHECK(!Job::key().session_key().proxy_chain().is_direct());
53 // The job relies on the the proxy to resolve DNS for the destination, so
54 // cannot determine protocol information from DNS. We must know the QUIC
55 // version already.
56 CHECK(target_quic_version.IsKnown())
57 << "Cannot make QUIC proxy connections without a known QUIC version";
58 }
59
60 QuicSessionPool::ProxyJob::~ProxyJob() = default;
61
Run(CompletionOnceCallback callback)62 int QuicSessionPool::ProxyJob::Run(CompletionOnceCallback callback) {
63 int rv = DoLoop(OK);
64 if (rv == ERR_IO_PENDING) {
65 callback_ = std::move(callback);
66 }
67
68 return rv > 0 ? OK : rv;
69 }
70
SetRequestExpectations(QuicSessionRequest * request)71 void QuicSessionPool::ProxyJob::SetRequestExpectations(
72 QuicSessionRequest* request) {
73 // This Job does not do host resolution, but can notify when the session
74 // creation is finished.
75 const bool session_creation_finished =
76 session_attempt_ && session_attempt_->session_creation_finished();
77 if (!session_creation_finished) {
78 request->ExpectQuicSessionCreation();
79 }
80 }
81
PopulateNetErrorDetails(NetErrorDetails * details) const82 void QuicSessionPool::ProxyJob::PopulateNetErrorDetails(
83 NetErrorDetails* details) const {
84 // First, prefer any error details reported from creating the session over
85 // which this job is carried.
86 if (net_error_details_.quic_connection_error != quic::QUIC_NO_ERROR) {
87 *details = net_error_details_;
88 return;
89 }
90
91 // Second, prefer to include error details from the session over which this
92 // job is carried, as any error in that session is "closer to" the client.
93 if (proxy_session_) {
94 proxy_session_->PopulateNetErrorDetails(details);
95 if (details->quic_connection_error != quic::QUIC_NO_ERROR) {
96 return;
97 }
98 }
99
100 // Finally, return the error from the session attempt.
101 if (session_attempt_) {
102 session_attempt_->PopulateNetErrorDetails(details);
103 }
104 }
105
DoLoop(int rv)106 int QuicSessionPool::ProxyJob::DoLoop(int rv) {
107 do {
108 IoState state = io_state_;
109 io_state_ = STATE_NONE;
110 switch (state) {
111 case STATE_CREATE_PROXY_SESSION:
112 CHECK_EQ(OK, rv);
113 rv = DoCreateProxySession();
114 break;
115 case STATE_CREATE_PROXY_SESSION_COMPLETE:
116 rv = DoCreateProxySessionComplete(rv);
117 break;
118 case STATE_CREATE_PROXY_STREAM:
119 CHECK_EQ(OK, rv);
120 rv = DoCreateProxyStream();
121 break;
122 case STATE_CREATE_PROXY_STREAM_COMPLETE:
123 rv = DoCreateProxyStreamComplete(rv);
124 break;
125 case STATE_ATTEMPT_SESSION:
126 rv = DoAttemptSession();
127 break;
128 default:
129 NOTREACHED() << "io_state_: " << io_state_;
130 }
131 } while (io_state_ != STATE_NONE && rv != ERR_IO_PENDING);
132 return rv;
133 }
134
OnSessionAttemptComplete(int rv)135 void QuicSessionPool::ProxyJob::OnSessionAttemptComplete(int rv) {
136 CHECK_NE(rv, ERR_IO_PENDING);
137 if (!callback_.is_null()) {
138 std::move(callback_).Run(rv);
139 }
140 }
141
OnIOComplete(int rv)142 void QuicSessionPool::ProxyJob::OnIOComplete(int rv) {
143 rv = DoLoop(rv);
144 if (rv != ERR_IO_PENDING && !callback_.is_null()) {
145 std::move(callback_).Run(rv);
146 }
147 }
148
DoCreateProxySession()149 int QuicSessionPool::ProxyJob::DoCreateProxySession() {
150 io_state_ = STATE_CREATE_PROXY_SESSION_COMPLETE;
151
152 net_log().BeginEvent(NetLogEventType::QUIC_SESSION_POOL_PROXY_JOB_CONNECT);
153
154 const QuicSessionKey& session_key = key_.session_key();
155 auto [proxy_chain_prefix, last_proxy_server] =
156 session_key.proxy_chain().SplitLast();
157 auto last_server = last_proxy_server.host_port_pair();
158 url::SchemeHostPort destination(url::kHttpsScheme, last_server.host(),
159 last_server.port());
160
161 net_log_.BeginEventWithStringParams(
162 NetLogEventType::QUIC_SESSION_POOL_PROXY_JOB_CREATE_PROXY_SESSION,
163 "destination", destination.Serialize());
164
165 // Select the default QUIC version for the session to the proxy, since there
166 // is no DNS or Alt-Svc information to use.
167 quic::ParsedQuicVersion quic_version = SupportedQuicVersionForProxying();
168
169 // In order to support connection re-use in multi-proxy chains, without
170 // sacrificing partitioning, use an empty NAK for connections to a proxy that
171 // are carrying a connection to another proxy. For example, given chain
172 // [proxy1, proxy2, proxy3], the connections to proxy1 and proxy2 need not be
173 // partitioned and can use an empty NAK. This situation is identified by the
174 // session usage of the tunneled connection being kProxy.
175 bool use_empty_nak = false;
176 if (!base::FeatureList::IsEnabled(net::features::kPartitionProxyChains) &&
177 session_key.session_usage() == SessionUsage::kProxy) {
178 use_empty_nak = true;
179 }
180
181 proxy_session_request_ = std::make_unique<QuicSessionRequest>(pool_);
182 return proxy_session_request_->Request(
183 destination, quic_version, proxy_chain_prefix, proxy_annotation_tag_,
184 http_user_agent_settings_.get(), SessionUsage::kProxy,
185 session_key.privacy_mode(), priority(), session_key.socket_tag(),
186 use_empty_nak ? NetworkAnonymizationKey()
187 : session_key.network_anonymization_key(),
188 session_key.secure_dns_policy(), session_key.require_dns_https_alpn(),
189 cert_verify_flags_, GURL("https://" + last_server.ToString()), net_log(),
190 &net_error_details_, session_creation_initiator_,
191 /*failed_on_default_network_callback=*/CompletionOnceCallback(),
192 io_callback_);
193 }
194
DoCreateProxySessionComplete(int rv)195 int QuicSessionPool::ProxyJob::DoCreateProxySessionComplete(int rv) {
196 net_log().EndEventWithNetErrorCode(
197 NetLogEventType::QUIC_SESSION_POOL_PROXY_JOB_CREATE_PROXY_SESSION, rv);
198 if (rv != 0) {
199 proxy_session_request_.reset();
200 return rv;
201 }
202 io_state_ = STATE_CREATE_PROXY_STREAM;
203 proxy_session_ = proxy_session_request_->ReleaseSessionHandle();
204 proxy_session_request_.reset();
205
206 return OK;
207 }
208
DoCreateProxyStream()209 int QuicSessionPool::ProxyJob::DoCreateProxyStream() {
210 // Requiring confirmation here means more confidence that the underlying
211 // connection is working before building the proxy tunnel, at the cost of one
212 // more round-trip.
213 io_state_ = STATE_CREATE_PROXY_STREAM_COMPLETE;
214 return proxy_session_->RequestStream(/*requires_confirmation=*/true,
215 io_callback_, proxy_annotation_tag_);
216 }
217
DoCreateProxyStreamComplete(int rv)218 int QuicSessionPool::ProxyJob::DoCreateProxyStreamComplete(int rv) {
219 if (rv != 0) {
220 return rv;
221 }
222 proxy_stream_ = proxy_session_->ReleaseStream();
223
224 DCHECK(proxy_stream_);
225 if (!proxy_stream_->IsOpen()) {
226 return ERR_CONNECTION_CLOSED;
227 }
228
229 io_state_ = STATE_ATTEMPT_SESSION;
230 return OK;
231 }
232
DoAttemptSession()233 int QuicSessionPool::ProxyJob::DoAttemptSession() {
234 IPEndPoint local_address;
235 int rv = proxy_session_->GetSelfAddress(&local_address);
236 if (rv != 0) {
237 return rv;
238 }
239
240 IPEndPoint peer_address;
241 rv = proxy_session_->GetPeerAddress(&peer_address);
242 if (rv != 0) {
243 return rv;
244 }
245
246 session_attempt_ = std::make_unique<QuicSessionAttempt>(
247 this, std::move(local_address), std::move(peer_address),
248 target_quic_version_, cert_verify_flags_, std::move(proxy_stream_),
249 http_user_agent_settings_, session_creation_initiator_);
250
251 return session_attempt_->Start(
252 base::BindOnce(&ProxyJob::OnSessionAttemptComplete, GetWeakPtr()));
253 }
254
255 } // namespace net
256