1 // Copyright 2021 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/dedicated_web_transport_http3_client.h"
6
7 #include <string_view>
8 #include <vector>
9
10 #include "base/containers/contains.h"
11 #include "base/feature_list.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/metrics/field_trial_params.h"
14 #include "base/metrics/histogram_functions.h"
15 #include "base/task/single_thread_task_runner.h"
16 #include "net/base/address_list.h"
17 #include "net/base/port_util.h"
18 #include "net/base/url_util.h"
19 #include "net/http/http_network_session.h"
20 #include "net/log/net_log_values.h"
21 #include "net/proxy_resolution/configured_proxy_resolution_service.h"
22 #include "net/proxy_resolution/proxy_resolution_request.h"
23 #include "net/quic/address_utils.h"
24 #include "net/quic/crypto/proof_verifier_chromium.h"
25 #include "net/quic/quic_chromium_alarm_factory.h"
26 #include "net/spdy/spdy_http_utils.h"
27 #include "net/third_party/quiche/src/quiche/quic/core/http/web_transport_http3.h"
28 #include "net/third_party/quiche/src/quiche/quic/core/quic_connection.h"
29 #include "net/third_party/quiche/src/quiche/quic/core/quic_types.h"
30 #include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h"
31 #include "net/url_request/url_request_context.h"
32 #include "url/scheme_host_port.h"
33
34 namespace net {
35
36 namespace {
37
38 // From
39 // https://wicg.github.io/web-transport/#dom-quictransportconfiguration-server_certificate_fingerprints
40 constexpr int kCustomCertificateMaxValidityDays = 14;
41
42 // The time the client would wait for the server to acknowledge the session
43 // being closed.
44 constexpr base::TimeDelta kMaxCloseTimeout = base::Seconds(2);
45
46 // Enables custom congestion control for WebTransport over HTTP/3.
47 BASE_FEATURE(kWebTransportCongestionControl,
48 "WebTransportCongestionControl",
49 base::FEATURE_DISABLED_BY_DEFAULT);
50 constexpr base::FeatureParam<quic::CongestionControlType>::Option
51 kWebTransportCongestionControlAlgorithms[] = {
52 {quic::kCubicBytes, "CUBIC"},
53 {quic::kRenoBytes, "Reno"},
54 {quic::kBBR, "BBRv1"},
55 {quic::kBBRv2, "BBRv2"},
56 };
57 constexpr base::FeatureParam<quic::CongestionControlType>
58 kWebTransportCongestionControlAlgorithm{
59 &kWebTransportCongestionControl, /*name=*/"algorithm",
60 /*default_value=*/quic::kCubicBytes,
61 &kWebTransportCongestionControlAlgorithms};
62
HostsFromOrigins(std::set<HostPortPair> origins)63 std::set<std::string> HostsFromOrigins(std::set<HostPortPair> origins) {
64 std::set<std::string> hosts;
65 for (const auto& origin : origins) {
66 hosts.insert(origin.host());
67 }
68 return hosts;
69 }
70
71 // A version of WebTransportFingerprintProofVerifier that enforces
72 // Chromium-specific policies.
73 class ChromiumWebTransportFingerprintProofVerifier
74 : public quic::WebTransportFingerprintProofVerifier {
75 public:
76 using WebTransportFingerprintProofVerifier::
77 WebTransportFingerprintProofVerifier;
78
79 protected:
IsKeyTypeAllowedByPolicy(const quic::CertificateView & certificate)80 bool IsKeyTypeAllowedByPolicy(
81 const quic::CertificateView& certificate) override {
82 if (certificate.public_key_type() == quic::PublicKeyType::kRsa) {
83 return false;
84 }
85 return WebTransportFingerprintProofVerifier::IsKeyTypeAllowedByPolicy(
86 certificate);
87 }
88 };
89
CreateProofVerifier(const NetworkAnonymizationKey & anonymization_key,URLRequestContext * context,const WebTransportParameters & parameters)90 std::unique_ptr<quic::ProofVerifier> CreateProofVerifier(
91 const NetworkAnonymizationKey& anonymization_key,
92 URLRequestContext* context,
93 const WebTransportParameters& parameters) {
94 if (parameters.server_certificate_fingerprints.empty()) {
95 std::set<std::string> hostnames_to_allow_unknown_roots = HostsFromOrigins(
96 context->quic_context()->params()->origins_to_force_quic_on);
97 if (context->quic_context()->params()->webtransport_developer_mode) {
98 hostnames_to_allow_unknown_roots.insert("");
99 }
100 return std::make_unique<ProofVerifierChromium>(
101 context->cert_verifier(), context->transport_security_state(),
102 context->sct_auditing_delegate(),
103 std::move(hostnames_to_allow_unknown_roots), anonymization_key);
104 }
105
106 auto verifier =
107 std::make_unique<ChromiumWebTransportFingerprintProofVerifier>(
108 context->quic_context()->clock(), kCustomCertificateMaxValidityDays);
109 for (const quic::CertificateFingerprint& fingerprint :
110 parameters.server_certificate_fingerprints) {
111 bool success = verifier->AddFingerprint(fingerprint);
112 if (!success) {
113 DLOG(WARNING) << "Failed to add a certificate fingerprint: "
114 << fingerprint.fingerprint;
115 }
116 }
117 return verifier;
118 }
119
RecordNetLogQuicSessionClientStateChanged(NetLogWithSource & net_log,WebTransportState last_state,WebTransportState next_state,const std::optional<WebTransportError> & error)120 void RecordNetLogQuicSessionClientStateChanged(
121 NetLogWithSource& net_log,
122 WebTransportState last_state,
123 WebTransportState next_state,
124 const std::optional<WebTransportError>& error) {
125 net_log.AddEvent(
126 NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_STATE_CHANGED, [&] {
127 auto dict = base::Value::Dict()
128 .Set("last_state", WebTransportStateString(last_state))
129 .Set("next_state", WebTransportStateString(next_state));
130 if (error.has_value()) {
131 dict.Set("error",
132 base::Value::Dict()
133 .Set("net_error", error->net_error)
134 .Set("quic_error", static_cast<int>(error->quic_error))
135 .Set("details", error->details));
136 }
137 return dict;
138 });
139 }
140
141 // The stream associated with an extended CONNECT request for the WebTransport
142 // session.
143 class ConnectStream : public quic::QuicSpdyClientStream {
144 public:
ConnectStream(quic::QuicStreamId id,quic::QuicSpdyClientSession * session,quic::StreamType type,DedicatedWebTransportHttp3Client * client)145 ConnectStream(quic::QuicStreamId id,
146 quic::QuicSpdyClientSession* session,
147 quic::StreamType type,
148 DedicatedWebTransportHttp3Client* client)
149 : quic::QuicSpdyClientStream(id, session, type), client_(client) {}
150
~ConnectStream()151 ~ConnectStream() override { client_->OnConnectStreamDeleted(); }
152
OnInitialHeadersComplete(bool fin,size_t frame_len,const quic::QuicHeaderList & header_list)153 void OnInitialHeadersComplete(
154 bool fin,
155 size_t frame_len,
156 const quic::QuicHeaderList& header_list) override {
157 quic::QuicSpdyClientStream::OnInitialHeadersComplete(fin, frame_len,
158 header_list);
159 client_->OnHeadersComplete(response_headers());
160 }
161
OnClose()162 void OnClose() override {
163 quic::QuicSpdyClientStream::OnClose();
164 if (fin_received() && fin_sent()) {
165 // Clean close.
166 return;
167 }
168 if (stream_error() == quic::QUIC_STREAM_CONNECTION_ERROR) {
169 // If stream is closed due to the connection error, OnConnectionClosed()
170 // will populate the correct error details.
171 return;
172 }
173 client_->OnConnectStreamAborted();
174 }
175
OnWriteSideInDataRecvdState()176 void OnWriteSideInDataRecvdState() override {
177 quic::QuicSpdyClientStream::OnWriteSideInDataRecvdState();
178 client_->OnConnectStreamWriteSideInDataRecvdState();
179 }
180
181 private:
182 raw_ptr<DedicatedWebTransportHttp3Client> client_;
183 };
184
185 class DedicatedWebTransportHttp3ClientSession
186 : public quic::QuicSpdyClientSession {
187 public:
DedicatedWebTransportHttp3ClientSession(const quic::QuicConfig & config,const quic::ParsedQuicVersionVector & supported_versions,quic::QuicConnection * connection,const quic::QuicServerId & server_id,quic::QuicCryptoClientConfig * crypto_config,DedicatedWebTransportHttp3Client * client)188 DedicatedWebTransportHttp3ClientSession(
189 const quic::QuicConfig& config,
190 const quic::ParsedQuicVersionVector& supported_versions,
191 quic::QuicConnection* connection,
192 const quic::QuicServerId& server_id,
193 quic::QuicCryptoClientConfig* crypto_config,
194 DedicatedWebTransportHttp3Client* client)
195 : quic::QuicSpdyClientSession(config,
196 supported_versions,
197 connection,
198 server_id,
199 crypto_config),
200 client_(client) {}
201
OnSettingsFrame(const quic::SettingsFrame & frame)202 bool OnSettingsFrame(const quic::SettingsFrame& frame) override {
203 if (!quic::QuicSpdyClientSession::OnSettingsFrame(frame)) {
204 return false;
205 }
206 client_->OnSettingsReceived();
207 return true;
208 }
209
LocallySupportedWebTransportVersions() const210 quic::WebTransportHttp3VersionSet LocallySupportedWebTransportVersions()
211 const override {
212 quic::WebTransportHttp3VersionSet versions =
213 quic::WebTransportHttp3VersionSet(
214 {quic::WebTransportHttp3Version::kDraft02});
215 if (base::FeatureList::IsEnabled(features::kEnableWebTransportDraft07)) {
216 versions.Set(quic::WebTransportHttp3Version::kDraft07);
217 }
218 return versions;
219 }
220
LocalHttpDatagramSupport()221 quic::HttpDatagramSupport LocalHttpDatagramSupport() override {
222 return quic::HttpDatagramSupport::kRfcAndDraft04;
223 }
224
OnConnectionClosed(const quic::QuicConnectionCloseFrame & frame,quic::ConnectionCloseSource source)225 void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame,
226 quic::ConnectionCloseSource source) override {
227 quic::QuicSpdyClientSession::OnConnectionClosed(frame, source);
228 client_->OnConnectionClosed(frame.quic_error_code, frame.error_details,
229 source);
230 }
231
CreateConnectStream()232 ConnectStream* CreateConnectStream() {
233 if (!ShouldCreateOutgoingBidirectionalStream()) {
234 return nullptr;
235 }
236 std::unique_ptr<ConnectStream> stream =
237 std::make_unique<ConnectStream>(GetNextOutgoingBidirectionalStreamId(),
238 this, quic::BIDIRECTIONAL, client_);
239 ConnectStream* stream_ptr = stream.get();
240 ActivateStream(std::move(stream));
241 return stream_ptr;
242 }
243
OnDatagramProcessed(std::optional<quic::MessageStatus> status)244 void OnDatagramProcessed(std::optional<quic::MessageStatus> status) override {
245 client_->OnDatagramProcessed(
246 status.has_value() ? std::optional<quic::MessageStatus>(*status)
247 : std::optional<quic::MessageStatus>());
248 }
249
250 private:
251 raw_ptr<DedicatedWebTransportHttp3Client> client_;
252 };
253
254 class WebTransportVisitorProxy : public quic::WebTransportVisitor {
255 public:
WebTransportVisitorProxy(quic::WebTransportVisitor * visitor)256 explicit WebTransportVisitorProxy(quic::WebTransportVisitor* visitor)
257 : visitor_(visitor) {}
258
OnSessionReady()259 void OnSessionReady() override { visitor_->OnSessionReady(); }
OnSessionClosed(quic::WebTransportSessionError error_code,const std::string & error_message)260 void OnSessionClosed(quic::WebTransportSessionError error_code,
261 const std::string& error_message) override {
262 visitor_->OnSessionClosed(error_code, error_message);
263 }
OnIncomingBidirectionalStreamAvailable()264 void OnIncomingBidirectionalStreamAvailable() override {
265 visitor_->OnIncomingBidirectionalStreamAvailable();
266 }
OnIncomingUnidirectionalStreamAvailable()267 void OnIncomingUnidirectionalStreamAvailable() override {
268 visitor_->OnIncomingUnidirectionalStreamAvailable();
269 }
OnDatagramReceived(std::string_view datagram)270 void OnDatagramReceived(std::string_view datagram) override {
271 visitor_->OnDatagramReceived(datagram);
272 }
OnCanCreateNewOutgoingBidirectionalStream()273 void OnCanCreateNewOutgoingBidirectionalStream() override {
274 visitor_->OnCanCreateNewOutgoingBidirectionalStream();
275 }
OnCanCreateNewOutgoingUnidirectionalStream()276 void OnCanCreateNewOutgoingUnidirectionalStream() override {
277 visitor_->OnCanCreateNewOutgoingUnidirectionalStream();
278 }
279
280 private:
281 raw_ptr<quic::WebTransportVisitor> visitor_;
282 };
283
IsTerminalState(WebTransportState state)284 bool IsTerminalState(WebTransportState state) {
285 return state == WebTransportState::CLOSED ||
286 state == WebTransportState::FAILED;
287 }
288
289 // These values are persisted to logs. Entries should not be renumbered and
290 // numeric values should never be reused.
291 enum class NegotiatedHttpDatagramVersion {
292 kNone = 0,
293 kDraft04 = 1,
294 kRfc = 2,
295 kMaxValue = kRfc,
296 };
297
RecordNegotiatedHttpDatagramSupport(quic::HttpDatagramSupport support)298 void RecordNegotiatedHttpDatagramSupport(quic::HttpDatagramSupport support) {
299 NegotiatedHttpDatagramVersion negotiated;
300 switch (support) {
301 case quic::HttpDatagramSupport::kNone:
302 negotiated = NegotiatedHttpDatagramVersion::kNone;
303 break;
304 case quic::HttpDatagramSupport::kDraft04:
305 negotiated = NegotiatedHttpDatagramVersion::kDraft04;
306 break;
307 case quic::HttpDatagramSupport::kRfc:
308 negotiated = NegotiatedHttpDatagramVersion::kRfc;
309 break;
310 case quic::HttpDatagramSupport::kRfcAndDraft04:
311 NOTREACHED();
312 }
313 base::UmaHistogramEnumeration(
314 "Net.WebTransport.NegotiatedHttpDatagramVersion", negotiated);
315 }
316
WebTransportHttp3VersionString(quic::WebTransportHttp3Version version)317 const char* WebTransportHttp3VersionString(
318 quic::WebTransportHttp3Version version) {
319 switch (version) {
320 case quic::WebTransportHttp3Version::kDraft02:
321 return "draft-02";
322 case quic::WebTransportHttp3Version::kDraft07:
323 return "draft-07";
324 }
325 }
326
327 enum class NegotiatedWebTransportVersion {
328 kDraft02 = 0,
329 kDraft07 = 1,
330 kMaxValue = kDraft07,
331 };
332
RecordNegotiatedWebTransportVersion(quic::WebTransportHttp3Version version)333 void RecordNegotiatedWebTransportVersion(
334 quic::WebTransportHttp3Version version) {
335 NegotiatedWebTransportVersion negotiated;
336 switch (version) {
337 case quic::WebTransportHttp3Version::kDraft02:
338 negotiated = NegotiatedWebTransportVersion::kDraft02;
339 break;
340 case quic::WebTransportHttp3Version::kDraft07:
341 negotiated = NegotiatedWebTransportVersion::kDraft07;
342 break;
343 }
344 base::UmaHistogramEnumeration(
345 "Net.WebTransport.NegotiatedWebTransportVersion", negotiated);
346 }
347
AdjustSendAlgorithm(quic::QuicConnection & connection)348 void AdjustSendAlgorithm(quic::QuicConnection& connection) {
349 if (!base::FeatureList::IsEnabled(kWebTransportCongestionControl)) {
350 return;
351 }
352 connection.sent_packet_manager().SetSendAlgorithm(
353 kWebTransportCongestionControlAlgorithm.Get());
354 }
355
356 } // namespace
357
DedicatedWebTransportHttp3Client(const GURL & url,const url::Origin & origin,WebTransportClientVisitor * visitor,const NetworkAnonymizationKey & anonymization_key,URLRequestContext * context,const WebTransportParameters & parameters)358 DedicatedWebTransportHttp3Client::DedicatedWebTransportHttp3Client(
359 const GURL& url,
360 const url::Origin& origin,
361 WebTransportClientVisitor* visitor,
362 const NetworkAnonymizationKey& anonymization_key,
363 URLRequestContext* context,
364 const WebTransportParameters& parameters)
365 : url_(url),
366 origin_(origin),
367 anonymization_key_(anonymization_key),
368 context_(context),
369 visitor_(visitor),
370 quic_context_(context->quic_context()),
371 net_log_(NetLogWithSource::Make(context->net_log(),
372 NetLogSourceType::WEB_TRANSPORT_CLIENT)),
373 task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault().get()),
374 alarm_factory_(
375 std::make_unique<QuicChromiumAlarmFactory>(task_runner_,
376 quic_context_->clock())),
377 // TODO(vasilvv): proof verifier should have proper error reporting
378 // (currently, all certificate verification errors result in "TLS
379 // handshake error" even when more detailed message is available). This
380 // requires implementing ProofHandler::OnProofVerifyDetailsAvailable.
381 crypto_config_(
382 CreateProofVerifier(anonymization_key_, context, parameters),
383 /* session_cache */ nullptr) {
384 ConfigureQuicCryptoClientConfig(crypto_config_);
385 net_log_.BeginEvent(
386 NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_ALIVE, [&] {
387 base::Value::Dict dict;
388 dict.Set("url", url.possibly_invalid_spec());
389 dict.Set("network_anonymization_key",
390 anonymization_key.ToDebugString());
391 return dict;
392 });
393 }
394
~DedicatedWebTransportHttp3Client()395 DedicatedWebTransportHttp3Client::~DedicatedWebTransportHttp3Client() {
396 net_log_.EndEventWithNetErrorCode(
397 NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_ALIVE,
398 error_ ? error_->net_error : OK);
399 // |session_| owns this, so we need to make sure we release it before
400 // it gets dangling.
401 connection_ = nullptr;
402 }
403
Connect()404 void DedicatedWebTransportHttp3Client::Connect() {
405 if (state_ != WebTransportState::NEW ||
406 next_connect_state_ != CONNECT_STATE_NONE) {
407 NOTREACHED();
408 }
409
410 TransitionToState(WebTransportState::CONNECTING);
411 next_connect_state_ = CONNECT_STATE_INIT;
412 DoLoop(OK);
413 }
414
Close(const std::optional<WebTransportCloseInfo> & close_info)415 void DedicatedWebTransportHttp3Client::Close(
416 const std::optional<WebTransportCloseInfo>& close_info) {
417 CHECK(session());
418 base::TimeDelta probe_timeout = base::Microseconds(
419 connection_->sent_packet_manager().GetPtoDelay().ToMicroseconds());
420 // Wait for at least three PTOs similar to what's used in
421 // https://www.rfc-editor.org/rfc/rfc9000.html#name-immediate-close
422 base::TimeDelta close_timeout = std::min(3 * probe_timeout, kMaxCloseTimeout);
423 close_timeout_timer_.Start(
424 FROM_HERE, close_timeout,
425 base::BindOnce(&DedicatedWebTransportHttp3Client::OnCloseTimeout,
426 weak_factory_.GetWeakPtr()));
427 if (close_info.has_value()) {
428 session()->CloseSession(close_info->code, close_info->reason);
429 } else {
430 session()->CloseSession(0, "");
431 }
432 }
433
session()434 quic::WebTransportSession* DedicatedWebTransportHttp3Client::session() {
435 if (web_transport_session_ == nullptr)
436 return nullptr;
437 return web_transport_session_;
438 }
439
DoLoop(int rv)440 void DedicatedWebTransportHttp3Client::DoLoop(int rv) {
441 do {
442 ConnectState connect_state = next_connect_state_;
443 next_connect_state_ = CONNECT_STATE_NONE;
444 switch (connect_state) {
445 case CONNECT_STATE_INIT:
446 DCHECK_EQ(rv, OK);
447 rv = DoInit();
448 break;
449 case CONNECT_STATE_CHECK_PROXY:
450 DCHECK_EQ(rv, OK);
451 rv = DoCheckProxy();
452 break;
453 case CONNECT_STATE_CHECK_PROXY_COMPLETE:
454 rv = DoCheckProxyComplete(rv);
455 break;
456 case CONNECT_STATE_RESOLVE_HOST:
457 DCHECK_EQ(rv, OK);
458 rv = DoResolveHost();
459 break;
460 case CONNECT_STATE_RESOLVE_HOST_COMPLETE:
461 rv = DoResolveHostComplete(rv);
462 break;
463 case CONNECT_STATE_CONNECT:
464 DCHECK_EQ(rv, OK);
465 rv = DoConnect();
466 break;
467 case CONNECT_STATE_CONNECT_CONFIGURE:
468 rv = DoConnectConfigure(rv);
469 break;
470 case CONNECT_STATE_CONNECT_COMPLETE:
471 rv = DoConnectComplete();
472 break;
473 case CONNECT_STATE_SEND_REQUEST:
474 DCHECK_EQ(rv, OK);
475 rv = DoSendRequest();
476 break;
477 case CONNECT_STATE_CONFIRM_CONNECTION:
478 DCHECK_EQ(rv, OK);
479 rv = DoConfirmConnection();
480 break;
481 default:
482 NOTREACHED() << "Invalid state reached: " << connect_state;
483 }
484 } while (rv == OK && next_connect_state_ != CONNECT_STATE_NONE);
485
486 if (rv == OK || rv == ERR_IO_PENDING)
487 return;
488 SetErrorIfNecessary(rv);
489 TransitionToState(WebTransportState::FAILED);
490 }
491
DoInit()492 int DedicatedWebTransportHttp3Client::DoInit() {
493 if (!url_.is_valid())
494 return ERR_INVALID_URL;
495 if (url_.scheme_piece() != url::kHttpsScheme)
496 return ERR_DISALLOWED_URL_SCHEME;
497
498 if (!IsPortAllowedForScheme(url_.EffectiveIntPort(), url_.scheme_piece()))
499 return ERR_UNSAFE_PORT;
500
501 // TODO(vasilvv): check if QUIC is disabled by policy.
502
503 // Ensure that RFC 9000 is always supported.
504 supported_versions_ = quic::ParsedQuicVersionVector{
505 quic::ParsedQuicVersion::RFCv1(),
506 };
507 // Add other supported versions if available.
508 for (quic::ParsedQuicVersion& version :
509 quic_context_->params()->supported_versions) {
510 if (base::Contains(supported_versions_, version))
511 continue; // Skip as we've already added it above.
512 supported_versions_.push_back(version);
513 }
514 if (supported_versions_.empty()) {
515 DLOG(ERROR) << "Attempted using WebTransport with no compatible QUIC "
516 "versions available";
517 return ERR_NOT_IMPLEMENTED;
518 }
519
520 next_connect_state_ = CONNECT_STATE_CHECK_PROXY;
521 return OK;
522 }
523
DoCheckProxy()524 int DedicatedWebTransportHttp3Client::DoCheckProxy() {
525 next_connect_state_ = CONNECT_STATE_CHECK_PROXY_COMPLETE;
526 return context_->proxy_resolution_service()->ResolveProxy(
527 url_, /* method */ "CONNECT", anonymization_key_, &proxy_info_,
528 base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
529 base::Unretained(this)),
530 &proxy_resolution_request_, net_log_);
531 }
532
DoCheckProxyComplete(int rv)533 int DedicatedWebTransportHttp3Client::DoCheckProxyComplete(int rv) {
534 if (rv != OK)
535 return rv;
536
537 // If a proxy is configured, we fail the connection.
538 if (!proxy_info_.is_direct())
539 return ERR_TUNNEL_CONNECTION_FAILED;
540
541 next_connect_state_ = CONNECT_STATE_RESOLVE_HOST;
542 return OK;
543 }
544
DoResolveHost()545 int DedicatedWebTransportHttp3Client::DoResolveHost() {
546 next_connect_state_ = CONNECT_STATE_RESOLVE_HOST_COMPLETE;
547 HostResolver::ResolveHostParameters parameters;
548 resolve_host_request_ = context_->host_resolver()->CreateRequest(
549 url::SchemeHostPort(url_), anonymization_key_, net_log_, std::nullopt);
550 return resolve_host_request_->Start(base::BindOnce(
551 &DedicatedWebTransportHttp3Client::DoLoop, base::Unretained(this)));
552 }
553
DoResolveHostComplete(int rv)554 int DedicatedWebTransportHttp3Client::DoResolveHostComplete(int rv) {
555 if (rv != OK)
556 return rv;
557
558 DCHECK(resolve_host_request_->GetAddressResults());
559 next_connect_state_ = CONNECT_STATE_CONNECT;
560 return OK;
561 }
562
DoConnect()563 int DedicatedWebTransportHttp3Client::DoConnect() {
564 next_connect_state_ = CONNECT_STATE_CONNECT_CONFIGURE;
565
566 // TODO(vasilvv): consider unifying parts of this code with QuicSocketFactory
567 // (which currently has a lot of code specific to QuicChromiumClientSession).
568 socket_ = context_->GetNetworkSessionContext()
569 ->client_socket_factory->CreateDatagramClientSocket(
570 DatagramSocket::DEFAULT_BIND, net_log_.net_log(),
571 net_log_.source());
572 if (quic_context_->params()->enable_socket_recv_optimization)
573 socket_->EnableRecvOptimization();
574 socket_->UseNonBlockingIO();
575
576 IPEndPoint server_address =
577 *resolve_host_request_->GetAddressResults()->begin();
578 return socket_->ConnectAsync(
579 server_address, base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
580 base::Unretained(this)));
581 }
582
CreateConnection()583 void DedicatedWebTransportHttp3Client::CreateConnection() {
584 // Delete the objects in the same order they would be normally deleted by the
585 // destructor.
586 session_ = nullptr;
587 packet_reader_ = nullptr;
588
589 IPEndPoint server_address =
590 *resolve_host_request_->GetAddressResults()->begin();
591 quic::QuicConnectionId connection_id =
592 quic::QuicUtils::CreateRandomConnectionId(
593 quic_context_->random_generator());
594 auto connection = std::make_unique<quic::QuicConnection>(
595 connection_id, quic::QuicSocketAddress(),
596 ToQuicSocketAddress(server_address), quic_context_->helper(),
597 alarm_factory_.get(),
598 new QuicChromiumPacketWriter(socket_.get(), task_runner_),
599 /* owns_writer */ true, quic::Perspective::IS_CLIENT, supported_versions_,
600 connection_id_generator_);
601 connection_ = connection.get();
602 connection->SetMaxPacketLength(quic_context_->params()->max_packet_length);
603
604 session_ = std::make_unique<DedicatedWebTransportHttp3ClientSession>(
605 InitializeQuicConfig(*quic_context_->params()), supported_versions_,
606 connection.release(),
607 quic::QuicServerId(url_.host(), url_.EffectiveIntPort()), &crypto_config_,
608 this);
609 if (!original_supported_versions_.empty()) {
610 session_->set_client_original_supported_versions(
611 original_supported_versions_);
612 }
613
614 packet_reader_ = std::make_unique<QuicChromiumPacketReader>(
615 std::move(socket_), quic_context_->clock(), this,
616 kQuicYieldAfterPacketsRead,
617 quic::QuicTime::Delta::FromMilliseconds(
618 kQuicYieldAfterDurationMilliseconds),
619 quic_context_->params()->report_ecn, net_log_);
620
621 event_logger_ = std::make_unique<QuicEventLogger>(session_.get(), net_log_);
622 connection_->set_debug_visitor(event_logger_.get());
623 connection_->set_creator_debug_delegate(event_logger_.get());
624 AdjustSendAlgorithm(*connection_);
625
626 session_->Initialize();
627 packet_reader_->StartReading();
628
629 DCHECK(session_->WillNegotiateWebTransport());
630 session_->CryptoConnect();
631 }
632
DoConnectComplete()633 int DedicatedWebTransportHttp3Client::DoConnectComplete() {
634 if (!connection_->connected()) {
635 return ERR_QUIC_PROTOCOL_ERROR;
636 }
637 // Fail the connection if the received SETTINGS do not support WebTransport.
638 if (!session_->SupportsWebTransport()) {
639 return ERR_METHOD_NOT_SUPPORTED;
640 }
641 safe_to_report_error_details_ = true;
642 next_connect_state_ = CONNECT_STATE_SEND_REQUEST;
643 return OK;
644 }
645
DoConnectConfigure(int rv)646 int DedicatedWebTransportHttp3Client::DoConnectConfigure(int rv) {
647 if (rv != OK) {
648 return rv;
649 }
650
651 rv = socket_->SetReceiveBufferSize(kQuicSocketReceiveBufferSize);
652 if (rv != OK) {
653 return rv;
654 }
655
656 rv = socket_->SetDoNotFragment();
657 if (rv == ERR_NOT_IMPLEMENTED) {
658 rv = OK;
659 }
660 if (rv != OK) {
661 return rv;
662 }
663
664 rv = socket_->SetSendBufferSize(quic::kMaxOutgoingPacketSize * 20);
665 if (rv != OK) {
666 return rv;
667 }
668
669 next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
670 CreateConnection();
671 return ERR_IO_PENDING;
672 }
673
OnSettingsReceived()674 void DedicatedWebTransportHttp3Client::OnSettingsReceived() {
675 DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
676 // Wait until the SETTINGS parser is finished, and then send the request.
677 task_runner_->PostTask(
678 FROM_HERE, base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
679 weak_factory_.GetWeakPtr(), OK));
680 }
681
OnHeadersComplete(const quiche::HttpHeaderBlock & headers)682 void DedicatedWebTransportHttp3Client::OnHeadersComplete(
683 const quiche::HttpHeaderBlock& headers) {
684 http_response_info_ = std::make_unique<HttpResponseInfo>();
685 const int rv = SpdyHeadersToHttpResponse(headers, http_response_info_.get());
686 if (rv != OK) {
687 SetErrorIfNecessary(ERR_QUIC_PROTOCOL_ERROR);
688 TransitionToState(WebTransportState::FAILED);
689 return;
690 }
691 // TODO(vasilvv): add support for this header in downstream tests and remove
692 // this.
693 DCHECK(http_response_info_->headers);
694 http_response_info_->headers->RemoveHeader("sec-webtransport-http3-draft");
695
696 DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONFIRM_CONNECTION);
697 DoLoop(OK);
698 }
699
700 void DedicatedWebTransportHttp3Client::
OnConnectStreamWriteSideInDataRecvdState()701 OnConnectStreamWriteSideInDataRecvdState() {
702 task_runner_->PostTask(
703 FROM_HERE,
704 base::BindOnce(&DedicatedWebTransportHttp3Client::TransitionToState,
705 weak_factory_.GetWeakPtr(), WebTransportState::CLOSED));
706 }
707
OnConnectStreamAborted()708 void DedicatedWebTransportHttp3Client::OnConnectStreamAborted() {
709 SetErrorIfNecessary(session_ready_ ? ERR_FAILED : ERR_METHOD_NOT_SUPPORTED);
710 TransitionToState(WebTransportState::FAILED);
711 }
712
OnConnectStreamDeleted()713 void DedicatedWebTransportHttp3Client::OnConnectStreamDeleted() {
714 // `web_transport_session_` is owned by ConnectStream. Clear so that it
715 // doesn't get dangling.
716 web_transport_session_ = nullptr;
717 }
718
OnCloseTimeout()719 void DedicatedWebTransportHttp3Client::OnCloseTimeout() {
720 SetErrorIfNecessary(ERR_TIMED_OUT);
721 TransitionToState(WebTransportState::FAILED);
722 }
723
DoSendRequest()724 int DedicatedWebTransportHttp3Client::DoSendRequest() {
725 quic::QuicConnection::ScopedPacketFlusher scope(connection_);
726
727 DedicatedWebTransportHttp3ClientSession* session =
728 static_cast<DedicatedWebTransportHttp3ClientSession*>(session_.get());
729 ConnectStream* stream = session->CreateConnectStream();
730 if (stream == nullptr) {
731 return ERR_QUIC_PROTOCOL_ERROR;
732 }
733
734 quiche::HttpHeaderBlock headers;
735 DCHECK_EQ(url_.scheme(), url::kHttpsScheme);
736 headers[":scheme"] = url_.scheme();
737 headers[":method"] = "CONNECT";
738 headers[":authority"] = GetHostAndOptionalPort(url_);
739 headers[":path"] = url_.PathForRequest();
740 headers[":protocol"] = "webtransport";
741 headers["sec-webtransport-http3-draft02"] = "1";
742 headers["origin"] = origin_.Serialize();
743 stream->WriteHeaders(std::move(headers), /*fin=*/false, nullptr);
744
745 web_transport_session_ = stream->web_transport();
746 if (web_transport_session_ == nullptr) {
747 return ERR_METHOD_NOT_SUPPORTED;
748 }
749 stream->web_transport()->SetVisitor(
750 std::make_unique<WebTransportVisitorProxy>(this));
751
752 next_connect_state_ = CONNECT_STATE_CONFIRM_CONNECTION;
753 return ERR_IO_PENDING;
754 }
755
DoConfirmConnection()756 int DedicatedWebTransportHttp3Client::DoConfirmConnection() {
757 if (!session_ready_) {
758 return ERR_METHOD_NOT_SUPPORTED;
759 }
760
761 TransitionToState(WebTransportState::CONNECTED);
762 return OK;
763 }
764
TransitionToState(WebTransportState next_state)765 void DedicatedWebTransportHttp3Client::TransitionToState(
766 WebTransportState next_state) {
767 // Ignore all state transition requests if we have reached the terminal
768 // state.
769 if (IsTerminalState(state_)) {
770 DCHECK(IsTerminalState(next_state))
771 << "from: " << state_ << ", to: " << next_state;
772 return;
773 }
774
775 DCHECK_NE(state_, next_state);
776 const WebTransportState last_state = state_;
777 state_ = next_state;
778 RecordNetLogQuicSessionClientStateChanged(net_log_, last_state, next_state,
779 error_);
780 switch (next_state) {
781 case WebTransportState::CONNECTING:
782 DCHECK_EQ(last_state, WebTransportState::NEW);
783 break;
784
785 case WebTransportState::CONNECTED:
786 DCHECK_EQ(last_state, WebTransportState::CONNECTING);
787 visitor_->OnConnected(http_response_info_->headers);
788 break;
789
790 case WebTransportState::CLOSED:
791 DCHECK_EQ(last_state, WebTransportState::CONNECTED);
792 connection_->CloseConnection(quic::QUIC_NO_ERROR,
793 "WebTransport client terminated",
794 quic::ConnectionCloseBehavior::SILENT_CLOSE);
795 visitor_->OnClosed(close_info_);
796 break;
797
798 case WebTransportState::FAILED:
799 DCHECK(error_.has_value());
800 if (last_state == WebTransportState::CONNECTING) {
801 visitor_->OnConnectionFailed(*error_);
802 break;
803 }
804 DCHECK_EQ(last_state, WebTransportState::CONNECTED);
805 // Ensure the connection is properly closed before deleting it.
806 connection_->CloseConnection(
807 quic::QUIC_INTERNAL_ERROR,
808 "WebTransportState::ERROR reached but the connection still open",
809 quic::ConnectionCloseBehavior::SILENT_CLOSE);
810 visitor_->OnError(*error_);
811 break;
812
813 default:
814 NOTREACHED() << "Invalid state reached: " << next_state;
815 }
816 }
817
SetErrorIfNecessary(int error)818 void DedicatedWebTransportHttp3Client::SetErrorIfNecessary(int error) {
819 SetErrorIfNecessary(error, quic::QUIC_NO_ERROR, ErrorToString(error));
820 }
821
SetErrorIfNecessary(int error,quic::QuicErrorCode quic_error,std::string_view details)822 void DedicatedWebTransportHttp3Client::SetErrorIfNecessary(
823 int error,
824 quic::QuicErrorCode quic_error,
825 std::string_view details) {
826 if (!error_) {
827 error_ = WebTransportError(error, quic_error, details,
828 safe_to_report_error_details_);
829 }
830 }
831
OnSessionReady()832 void DedicatedWebTransportHttp3Client::OnSessionReady() {
833 CHECK(session_->SupportsWebTransport());
834
835 session_ready_ = true;
836
837 RecordNegotiatedWebTransportVersion(
838 *session_->SupportedWebTransportVersion());
839 RecordNegotiatedHttpDatagramSupport(session_->http_datagram_support());
840 net_log_.AddEvent(NetLogEventType::QUIC_SESSION_WEBTRANSPORT_SESSION_READY,
841 [&] {
842 base::Value::Dict dict;
843 dict.Set("http_datagram_version",
844 quic::HttpDatagramSupportToString(
845 session_->http_datagram_support()));
846 dict.Set("webtransport_http3_version",
847 WebTransportHttp3VersionString(
848 *session_->SupportedWebTransportVersion()));
849 return dict;
850 });
851 }
852
OnSessionClosed(quic::WebTransportSessionError error_code,const std::string & error_message)853 void DedicatedWebTransportHttp3Client::OnSessionClosed(
854 quic::WebTransportSessionError error_code,
855 const std::string& error_message) {
856 close_info_ = WebTransportCloseInfo(error_code, error_message);
857 task_runner_->PostTask(
858 FROM_HERE,
859 base::BindOnce(&DedicatedWebTransportHttp3Client::TransitionToState,
860 weak_factory_.GetWeakPtr(), WebTransportState::CLOSED));
861 }
862
863 void DedicatedWebTransportHttp3Client::
OnIncomingBidirectionalStreamAvailable()864 OnIncomingBidirectionalStreamAvailable() {
865 visitor_->OnIncomingBidirectionalStreamAvailable();
866 }
867
868 void DedicatedWebTransportHttp3Client::
OnIncomingUnidirectionalStreamAvailable()869 OnIncomingUnidirectionalStreamAvailable() {
870 visitor_->OnIncomingUnidirectionalStreamAvailable();
871 }
872
OnDatagramReceived(std::string_view datagram)873 void DedicatedWebTransportHttp3Client::OnDatagramReceived(
874 std::string_view datagram) {
875 visitor_->OnDatagramReceived(datagram);
876 }
877
878 void DedicatedWebTransportHttp3Client::
OnCanCreateNewOutgoingBidirectionalStream()879 OnCanCreateNewOutgoingBidirectionalStream() {
880 visitor_->OnCanCreateNewOutgoingBidirectionalStream();
881 }
882
883 void DedicatedWebTransportHttp3Client::
OnCanCreateNewOutgoingUnidirectionalStream()884 OnCanCreateNewOutgoingUnidirectionalStream() {
885 visitor_->OnCanCreateNewOutgoingUnidirectionalStream();
886 }
887
OnReadError(int result,const DatagramClientSocket * socket)888 bool DedicatedWebTransportHttp3Client::OnReadError(
889 int result,
890 const DatagramClientSocket* socket) {
891 SetErrorIfNecessary(result);
892 connection_->CloseConnection(quic::QUIC_PACKET_READ_ERROR,
893 ErrorToString(result),
894 quic::ConnectionCloseBehavior::SILENT_CLOSE);
895 return false;
896 }
897
OnPacket(const quic::QuicReceivedPacket & packet,const quic::QuicSocketAddress & local_address,const quic::QuicSocketAddress & peer_address)898 bool DedicatedWebTransportHttp3Client::OnPacket(
899 const quic::QuicReceivedPacket& packet,
900 const quic::QuicSocketAddress& local_address,
901 const quic::QuicSocketAddress& peer_address) {
902 session_->ProcessUdpPacket(local_address, peer_address, packet);
903 return connection_->connected();
904 }
905
HandleWriteError(int error_code,scoped_refptr<QuicChromiumPacketWriter::ReusableIOBuffer>)906 int DedicatedWebTransportHttp3Client::HandleWriteError(
907 int error_code,
908 scoped_refptr<QuicChromiumPacketWriter::ReusableIOBuffer> /*last_packet*/) {
909 return error_code;
910 }
911
OnWriteError(int error_code)912 void DedicatedWebTransportHttp3Client::OnWriteError(int error_code) {
913 SetErrorIfNecessary(error_code);
914 connection_->OnWriteError(error_code);
915 }
916
OnWriteUnblocked()917 void DedicatedWebTransportHttp3Client::OnWriteUnblocked() {
918 connection_->OnCanWrite();
919 }
920
OnConnectionClosed(quic::QuicErrorCode error,const std::string & error_details,quic::ConnectionCloseSource source)921 void DedicatedWebTransportHttp3Client::OnConnectionClosed(
922 quic::QuicErrorCode error,
923 const std::string& error_details,
924 quic::ConnectionCloseSource source) {
925 // If the session is already in a terminal state due to reasons other than
926 // connection close, we should ignore it; otherwise we risk re-entering the
927 // connection teardown process.
928 if (IsTerminalState(state_)) {
929 return;
930 }
931
932 if (!retried_with_new_version_ &&
933 session_->error() == quic::QUIC_INVALID_VERSION) {
934 retried_with_new_version_ = true;
935 DCHECK(original_supported_versions_.empty());
936 original_supported_versions_ = supported_versions_;
937 std::erase_if(
938 supported_versions_, [this](const quic::ParsedQuicVersion& version) {
939 return !base::Contains(
940 session_->connection()->server_supported_versions(), version);
941 });
942 if (!supported_versions_.empty()) {
943 // Since this is a callback from QuicConnection, we can't replace the
944 // connection object in this method; do it from the top of the event loop
945 // instead.
946 task_runner_->PostTask(
947 FROM_HERE,
948 base::BindOnce(&DedicatedWebTransportHttp3Client::CreateConnection,
949 weak_factory_.GetWeakPtr()));
950 return;
951 }
952 // If there are no supported versions, treat this as a regular error.
953 }
954
955 if (error == quic::QUIC_NO_ERROR) {
956 TransitionToState(WebTransportState::CLOSED);
957 return;
958 }
959
960 SetErrorIfNecessary(ERR_QUIC_PROTOCOL_ERROR, error, error_details);
961
962 if (state_ == WebTransportState::CONNECTING) {
963 DoLoop(OK);
964 return;
965 }
966
967 TransitionToState(WebTransportState::FAILED);
968 }
969
OnDatagramProcessed(std::optional<quic::MessageStatus> status)970 void DedicatedWebTransportHttp3Client::OnDatagramProcessed(
971 std::optional<quic::MessageStatus> status) {
972 visitor_->OnDatagramProcessed(status);
973 }
974
975 } // namespace net
976