• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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