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 return;
313 }
314 base::UmaHistogramEnumeration(
315 "Net.WebTransport.NegotiatedHttpDatagramVersion", negotiated);
316 }
317
WebTransportHttp3VersionString(quic::WebTransportHttp3Version version)318 const char* WebTransportHttp3VersionString(
319 quic::WebTransportHttp3Version version) {
320 switch (version) {
321 case quic::WebTransportHttp3Version::kDraft02:
322 return "draft-02";
323 case quic::WebTransportHttp3Version::kDraft07:
324 return "draft-07";
325 }
326 }
327
328 enum class NegotiatedWebTransportVersion {
329 kDraft02 = 0,
330 kDraft07 = 1,
331 kMaxValue = kDraft07,
332 };
333
RecordNegotiatedWebTransportVersion(quic::WebTransportHttp3Version version)334 void RecordNegotiatedWebTransportVersion(
335 quic::WebTransportHttp3Version version) {
336 NegotiatedWebTransportVersion negotiated;
337 switch (version) {
338 case quic::WebTransportHttp3Version::kDraft02:
339 negotiated = NegotiatedWebTransportVersion::kDraft02;
340 break;
341 case quic::WebTransportHttp3Version::kDraft07:
342 negotiated = NegotiatedWebTransportVersion::kDraft07;
343 break;
344 }
345 base::UmaHistogramEnumeration(
346 "Net.WebTransport.NegotiatedWebTransportVersion", negotiated);
347 }
348
AdjustSendAlgorithm(quic::QuicConnection & connection)349 void AdjustSendAlgorithm(quic::QuicConnection& connection) {
350 if (!base::FeatureList::IsEnabled(kWebTransportCongestionControl)) {
351 return;
352 }
353 connection.sent_packet_manager().SetSendAlgorithm(
354 kWebTransportCongestionControlAlgorithm.Get());
355 }
356
357 } // namespace
358
DedicatedWebTransportHttp3Client(const GURL & url,const url::Origin & origin,WebTransportClientVisitor * visitor,const NetworkAnonymizationKey & anonymization_key,URLRequestContext * context,const WebTransportParameters & parameters)359 DedicatedWebTransportHttp3Client::DedicatedWebTransportHttp3Client(
360 const GURL& url,
361 const url::Origin& origin,
362 WebTransportClientVisitor* visitor,
363 const NetworkAnonymizationKey& anonymization_key,
364 URLRequestContext* context,
365 const WebTransportParameters& parameters)
366 : url_(url),
367 origin_(origin),
368 anonymization_key_(anonymization_key),
369 context_(context),
370 visitor_(visitor),
371 quic_context_(context->quic_context()),
372 net_log_(NetLogWithSource::Make(context->net_log(),
373 NetLogSourceType::WEB_TRANSPORT_CLIENT)),
374 task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault().get()),
375 alarm_factory_(
376 std::make_unique<QuicChromiumAlarmFactory>(task_runner_,
377 quic_context_->clock())),
378 // TODO(vasilvv): proof verifier should have proper error reporting
379 // (currently, all certificate verification errors result in "TLS
380 // handshake error" even when more detailed message is available). This
381 // requires implementing ProofHandler::OnProofVerifyDetailsAvailable.
382 crypto_config_(
383 CreateProofVerifier(anonymization_key_, context, parameters),
384 /* session_cache */ nullptr) {
385 ConfigureQuicCryptoClientConfig(crypto_config_);
386 net_log_.BeginEvent(
387 NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_ALIVE, [&] {
388 base::Value::Dict dict;
389 dict.Set("url", url.possibly_invalid_spec());
390 dict.Set("network_anonymization_key",
391 anonymization_key.ToDebugString());
392 return dict;
393 });
394 }
395
~DedicatedWebTransportHttp3Client()396 DedicatedWebTransportHttp3Client::~DedicatedWebTransportHttp3Client() {
397 net_log_.EndEventWithNetErrorCode(
398 NetLogEventType::QUIC_SESSION_WEBTRANSPORT_CLIENT_ALIVE,
399 error_ ? error_->net_error : OK);
400 // |session_| owns this, so we need to make sure we release it before
401 // it gets dangling.
402 connection_ = nullptr;
403 }
404
Connect()405 void DedicatedWebTransportHttp3Client::Connect() {
406 if (state_ != WebTransportState::NEW ||
407 next_connect_state_ != CONNECT_STATE_NONE) {
408 NOTREACHED();
409 return;
410 }
411
412 TransitionToState(WebTransportState::CONNECTING);
413 next_connect_state_ = CONNECT_STATE_INIT;
414 DoLoop(OK);
415 }
416
Close(const std::optional<WebTransportCloseInfo> & close_info)417 void DedicatedWebTransportHttp3Client::Close(
418 const std::optional<WebTransportCloseInfo>& close_info) {
419 CHECK(session());
420 base::TimeDelta probe_timeout = base::Microseconds(
421 connection_->sent_packet_manager().GetPtoDelay().ToMicroseconds());
422 // Wait for at least three PTOs similar to what's used in
423 // https://www.rfc-editor.org/rfc/rfc9000.html#name-immediate-close
424 base::TimeDelta close_timeout = std::min(3 * probe_timeout, kMaxCloseTimeout);
425 close_timeout_timer_.Start(
426 FROM_HERE, close_timeout,
427 base::BindOnce(&DedicatedWebTransportHttp3Client::OnCloseTimeout,
428 weak_factory_.GetWeakPtr()));
429 if (close_info.has_value()) {
430 session()->CloseSession(close_info->code, close_info->reason);
431 } else {
432 session()->CloseSession(0, "");
433 }
434 }
435
session()436 quic::WebTransportSession* DedicatedWebTransportHttp3Client::session() {
437 if (web_transport_session_ == nullptr)
438 return nullptr;
439 return web_transport_session_;
440 }
441
DoLoop(int rv)442 void DedicatedWebTransportHttp3Client::DoLoop(int rv) {
443 do {
444 ConnectState connect_state = next_connect_state_;
445 next_connect_state_ = CONNECT_STATE_NONE;
446 switch (connect_state) {
447 case CONNECT_STATE_INIT:
448 DCHECK_EQ(rv, OK);
449 rv = DoInit();
450 break;
451 case CONNECT_STATE_CHECK_PROXY:
452 DCHECK_EQ(rv, OK);
453 rv = DoCheckProxy();
454 break;
455 case CONNECT_STATE_CHECK_PROXY_COMPLETE:
456 rv = DoCheckProxyComplete(rv);
457 break;
458 case CONNECT_STATE_RESOLVE_HOST:
459 DCHECK_EQ(rv, OK);
460 rv = DoResolveHost();
461 break;
462 case CONNECT_STATE_RESOLVE_HOST_COMPLETE:
463 rv = DoResolveHostComplete(rv);
464 break;
465 case CONNECT_STATE_CONNECT:
466 DCHECK_EQ(rv, OK);
467 rv = DoConnect();
468 break;
469 case CONNECT_STATE_CONNECT_CONFIGURE:
470 rv = DoConnectConfigure(rv);
471 break;
472 case CONNECT_STATE_CONNECT_COMPLETE:
473 rv = DoConnectComplete();
474 break;
475 case CONNECT_STATE_SEND_REQUEST:
476 DCHECK_EQ(rv, OK);
477 rv = DoSendRequest();
478 break;
479 case CONNECT_STATE_CONFIRM_CONNECTION:
480 DCHECK_EQ(rv, OK);
481 rv = DoConfirmConnection();
482 break;
483 default:
484 NOTREACHED() << "Invalid state reached: " << connect_state;
485 rv = ERR_FAILED;
486 break;
487 }
488 } while (rv == OK && next_connect_state_ != CONNECT_STATE_NONE);
489
490 if (rv == OK || rv == ERR_IO_PENDING)
491 return;
492 SetErrorIfNecessary(rv);
493 TransitionToState(WebTransportState::FAILED);
494 }
495
DoInit()496 int DedicatedWebTransportHttp3Client::DoInit() {
497 if (!url_.is_valid())
498 return ERR_INVALID_URL;
499 if (url_.scheme_piece() != url::kHttpsScheme)
500 return ERR_DISALLOWED_URL_SCHEME;
501
502 if (!IsPortAllowedForScheme(url_.EffectiveIntPort(), url_.scheme_piece()))
503 return ERR_UNSAFE_PORT;
504
505 // TODO(vasilvv): check if QUIC is disabled by policy.
506
507 // Ensure that RFC 9000 is always supported.
508 supported_versions_ = quic::ParsedQuicVersionVector{
509 quic::ParsedQuicVersion::RFCv1(),
510 };
511 // Add other supported versions if available.
512 for (quic::ParsedQuicVersion& version :
513 quic_context_->params()->supported_versions) {
514 if (base::Contains(supported_versions_, version))
515 continue; // Skip as we've already added it above.
516 supported_versions_.push_back(version);
517 }
518 if (supported_versions_.empty()) {
519 DLOG(ERROR) << "Attempted using WebTransport with no compatible QUIC "
520 "versions available";
521 return ERR_NOT_IMPLEMENTED;
522 }
523
524 next_connect_state_ = CONNECT_STATE_CHECK_PROXY;
525 return OK;
526 }
527
DoCheckProxy()528 int DedicatedWebTransportHttp3Client::DoCheckProxy() {
529 next_connect_state_ = CONNECT_STATE_CHECK_PROXY_COMPLETE;
530 return context_->proxy_resolution_service()->ResolveProxy(
531 url_, /* method */ "CONNECT", anonymization_key_, &proxy_info_,
532 base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
533 base::Unretained(this)),
534 &proxy_resolution_request_, net_log_);
535 }
536
DoCheckProxyComplete(int rv)537 int DedicatedWebTransportHttp3Client::DoCheckProxyComplete(int rv) {
538 if (rv != OK)
539 return rv;
540
541 // If a proxy is configured, we fail the connection.
542 if (!proxy_info_.is_direct())
543 return ERR_TUNNEL_CONNECTION_FAILED;
544
545 next_connect_state_ = CONNECT_STATE_RESOLVE_HOST;
546 return OK;
547 }
548
DoResolveHost()549 int DedicatedWebTransportHttp3Client::DoResolveHost() {
550 next_connect_state_ = CONNECT_STATE_RESOLVE_HOST_COMPLETE;
551 HostResolver::ResolveHostParameters parameters;
552 resolve_host_request_ = context_->host_resolver()->CreateRequest(
553 url::SchemeHostPort(url_), anonymization_key_, net_log_, std::nullopt);
554 return resolve_host_request_->Start(base::BindOnce(
555 &DedicatedWebTransportHttp3Client::DoLoop, base::Unretained(this)));
556 }
557
DoResolveHostComplete(int rv)558 int DedicatedWebTransportHttp3Client::DoResolveHostComplete(int rv) {
559 if (rv != OK)
560 return rv;
561
562 DCHECK(resolve_host_request_->GetAddressResults());
563 next_connect_state_ = CONNECT_STATE_CONNECT;
564 return OK;
565 }
566
DoConnect()567 int DedicatedWebTransportHttp3Client::DoConnect() {
568 next_connect_state_ = CONNECT_STATE_CONNECT_CONFIGURE;
569
570 // TODO(vasilvv): consider unifying parts of this code with QuicSocketFactory
571 // (which currently has a lot of code specific to QuicChromiumClientSession).
572 socket_ = context_->GetNetworkSessionContext()
573 ->client_socket_factory->CreateDatagramClientSocket(
574 DatagramSocket::DEFAULT_BIND, net_log_.net_log(),
575 net_log_.source());
576 if (quic_context_->params()->enable_socket_recv_optimization)
577 socket_->EnableRecvOptimization();
578 socket_->UseNonBlockingIO();
579
580 IPEndPoint server_address =
581 *resolve_host_request_->GetAddressResults()->begin();
582 return socket_->ConnectAsync(
583 server_address, base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
584 base::Unretained(this)));
585 }
586
CreateConnection()587 void DedicatedWebTransportHttp3Client::CreateConnection() {
588 // Delete the objects in the same order they would be normally deleted by the
589 // destructor.
590 packet_reader_ = nullptr;
591 session_ = nullptr;
592
593 IPEndPoint server_address =
594 *resolve_host_request_->GetAddressResults()->begin();
595 quic::QuicConnectionId connection_id =
596 quic::QuicUtils::CreateRandomConnectionId(
597 quic_context_->random_generator());
598 auto connection = std::make_unique<quic::QuicConnection>(
599 connection_id, quic::QuicSocketAddress(),
600 ToQuicSocketAddress(server_address), quic_context_->helper(),
601 alarm_factory_.get(),
602 new QuicChromiumPacketWriter(socket_.get(), task_runner_),
603 /* owns_writer */ true, quic::Perspective::IS_CLIENT, supported_versions_,
604 connection_id_generator_);
605 connection_ = connection.get();
606 connection->SetMaxPacketLength(quic_context_->params()->max_packet_length);
607
608 session_ = std::make_unique<DedicatedWebTransportHttp3ClientSession>(
609 InitializeQuicConfig(*quic_context_->params()), supported_versions_,
610 connection.release(),
611 quic::QuicServerId(url_.host(), url_.EffectiveIntPort()), &crypto_config_,
612 this);
613 if (!original_supported_versions_.empty()) {
614 session_->set_client_original_supported_versions(
615 original_supported_versions_);
616 }
617
618 packet_reader_ = std::make_unique<QuicChromiumPacketReader>(
619 std::move(socket_), quic_context_->clock(), this,
620 kQuicYieldAfterPacketsRead,
621 quic::QuicTime::Delta::FromMilliseconds(
622 kQuicYieldAfterDurationMilliseconds),
623 net_log_);
624
625 event_logger_ = std::make_unique<QuicEventLogger>(session_.get(), net_log_);
626 connection_->set_debug_visitor(event_logger_.get());
627 connection_->set_creator_debug_delegate(event_logger_.get());
628 AdjustSendAlgorithm(*connection_);
629
630 session_->Initialize();
631 packet_reader_->StartReading();
632
633 DCHECK(session_->WillNegotiateWebTransport());
634 session_->CryptoConnect();
635 }
636
DoConnectComplete()637 int DedicatedWebTransportHttp3Client::DoConnectComplete() {
638 if (!connection_->connected()) {
639 return ERR_QUIC_PROTOCOL_ERROR;
640 }
641 // Fail the connection if the received SETTINGS do not support WebTransport.
642 if (!session_->SupportsWebTransport()) {
643 return ERR_METHOD_NOT_SUPPORTED;
644 }
645 safe_to_report_error_details_ = true;
646 next_connect_state_ = CONNECT_STATE_SEND_REQUEST;
647 return OK;
648 }
649
DoConnectConfigure(int rv)650 int DedicatedWebTransportHttp3Client::DoConnectConfigure(int rv) {
651 if (rv != OK) {
652 return rv;
653 }
654
655 rv = socket_->SetReceiveBufferSize(kQuicSocketReceiveBufferSize);
656 if (rv != OK) {
657 return rv;
658 }
659
660 rv = socket_->SetDoNotFragment();
661 if (rv == ERR_NOT_IMPLEMENTED) {
662 rv = OK;
663 }
664 if (rv != OK) {
665 return rv;
666 }
667
668 rv = socket_->SetSendBufferSize(quic::kMaxOutgoingPacketSize * 20);
669 if (rv != OK) {
670 return rv;
671 }
672
673 next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
674 CreateConnection();
675 return ERR_IO_PENDING;
676 }
677
OnSettingsReceived()678 void DedicatedWebTransportHttp3Client::OnSettingsReceived() {
679 DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
680 // Wait until the SETTINGS parser is finished, and then send the request.
681 task_runner_->PostTask(
682 FROM_HERE, base::BindOnce(&DedicatedWebTransportHttp3Client::DoLoop,
683 weak_factory_.GetWeakPtr(), OK));
684 }
685
OnHeadersComplete(const spdy::Http2HeaderBlock & headers)686 void DedicatedWebTransportHttp3Client::OnHeadersComplete(
687 const spdy::Http2HeaderBlock& headers) {
688 http_response_info_ = std::make_unique<HttpResponseInfo>();
689 const int rv = SpdyHeadersToHttpResponse(headers, http_response_info_.get());
690 if (rv != OK) {
691 SetErrorIfNecessary(ERR_QUIC_PROTOCOL_ERROR);
692 TransitionToState(WebTransportState::FAILED);
693 return;
694 }
695 // TODO(vasilvv): add support for this header in downstream tests and remove
696 // this.
697 DCHECK(http_response_info_->headers);
698 http_response_info_->headers->RemoveHeader("sec-webtransport-http3-draft");
699
700 DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONFIRM_CONNECTION);
701 DoLoop(OK);
702 }
703
704 void DedicatedWebTransportHttp3Client::
OnConnectStreamWriteSideInDataRecvdState()705 OnConnectStreamWriteSideInDataRecvdState() {
706 task_runner_->PostTask(
707 FROM_HERE,
708 base::BindOnce(&DedicatedWebTransportHttp3Client::TransitionToState,
709 weak_factory_.GetWeakPtr(), WebTransportState::CLOSED));
710 }
711
OnConnectStreamAborted()712 void DedicatedWebTransportHttp3Client::OnConnectStreamAborted() {
713 SetErrorIfNecessary(session_ready_ ? ERR_FAILED : ERR_METHOD_NOT_SUPPORTED);
714 TransitionToState(WebTransportState::FAILED);
715 }
716
OnConnectStreamDeleted()717 void DedicatedWebTransportHttp3Client::OnConnectStreamDeleted() {
718 // `web_transport_session_` is owned by ConnectStream. Clear so that it
719 // doesn't get dangling.
720 web_transport_session_ = nullptr;
721 }
722
OnCloseTimeout()723 void DedicatedWebTransportHttp3Client::OnCloseTimeout() {
724 SetErrorIfNecessary(ERR_TIMED_OUT);
725 TransitionToState(WebTransportState::FAILED);
726 }
727
DoSendRequest()728 int DedicatedWebTransportHttp3Client::DoSendRequest() {
729 quic::QuicConnection::ScopedPacketFlusher scope(connection_);
730
731 DedicatedWebTransportHttp3ClientSession* session =
732 static_cast<DedicatedWebTransportHttp3ClientSession*>(session_.get());
733 ConnectStream* stream = session->CreateConnectStream();
734 if (stream == nullptr) {
735 return ERR_QUIC_PROTOCOL_ERROR;
736 }
737
738 spdy::Http2HeaderBlock headers;
739 DCHECK_EQ(url_.scheme(), url::kHttpsScheme);
740 headers[":scheme"] = url_.scheme();
741 headers[":method"] = "CONNECT";
742 headers[":authority"] = GetHostAndOptionalPort(url_);
743 headers[":path"] = url_.PathForRequest();
744 headers[":protocol"] = "webtransport";
745 headers["sec-webtransport-http3-draft02"] = "1";
746 headers["origin"] = origin_.Serialize();
747 stream->WriteHeaders(std::move(headers), /*fin=*/false, nullptr);
748
749 web_transport_session_ = stream->web_transport();
750 if (web_transport_session_ == nullptr) {
751 return ERR_METHOD_NOT_SUPPORTED;
752 }
753 stream->web_transport()->SetVisitor(
754 std::make_unique<WebTransportVisitorProxy>(this));
755
756 next_connect_state_ = CONNECT_STATE_CONFIRM_CONNECTION;
757 return ERR_IO_PENDING;
758 }
759
DoConfirmConnection()760 int DedicatedWebTransportHttp3Client::DoConfirmConnection() {
761 if (!session_ready_) {
762 return ERR_METHOD_NOT_SUPPORTED;
763 }
764
765 TransitionToState(WebTransportState::CONNECTED);
766 return OK;
767 }
768
TransitionToState(WebTransportState next_state)769 void DedicatedWebTransportHttp3Client::TransitionToState(
770 WebTransportState next_state) {
771 // Ignore all state transition requests if we have reached the terminal
772 // state.
773 if (IsTerminalState(state_)) {
774 DCHECK(IsTerminalState(next_state))
775 << "from: " << state_ << ", to: " << next_state;
776 return;
777 }
778
779 DCHECK_NE(state_, next_state);
780 const WebTransportState last_state = state_;
781 state_ = next_state;
782 RecordNetLogQuicSessionClientStateChanged(net_log_, last_state, next_state,
783 error_);
784 switch (next_state) {
785 case WebTransportState::CONNECTING:
786 DCHECK_EQ(last_state, WebTransportState::NEW);
787 break;
788
789 case WebTransportState::CONNECTED:
790 DCHECK_EQ(last_state, WebTransportState::CONNECTING);
791 visitor_->OnConnected(http_response_info_->headers);
792 break;
793
794 case WebTransportState::CLOSED:
795 DCHECK_EQ(last_state, WebTransportState::CONNECTED);
796 connection_->CloseConnection(quic::QUIC_NO_ERROR,
797 "WebTransport client terminated",
798 quic::ConnectionCloseBehavior::SILENT_CLOSE);
799 visitor_->OnClosed(close_info_);
800 break;
801
802 case WebTransportState::FAILED:
803 DCHECK(error_.has_value());
804 if (last_state == WebTransportState::CONNECTING) {
805 visitor_->OnConnectionFailed(*error_);
806 break;
807 }
808 DCHECK_EQ(last_state, WebTransportState::CONNECTED);
809 // Ensure the connection is properly closed before deleting it.
810 connection_->CloseConnection(
811 quic::QUIC_INTERNAL_ERROR,
812 "WebTransportState::ERROR reached but the connection still open",
813 quic::ConnectionCloseBehavior::SILENT_CLOSE);
814 visitor_->OnError(*error_);
815 break;
816
817 default:
818 NOTREACHED() << "Invalid state reached: " << next_state;
819 break;
820 }
821 }
822
SetErrorIfNecessary(int error)823 void DedicatedWebTransportHttp3Client::SetErrorIfNecessary(int error) {
824 SetErrorIfNecessary(error, quic::QUIC_NO_ERROR, ErrorToString(error));
825 }
826
SetErrorIfNecessary(int error,quic::QuicErrorCode quic_error,std::string_view details)827 void DedicatedWebTransportHttp3Client::SetErrorIfNecessary(
828 int error,
829 quic::QuicErrorCode quic_error,
830 std::string_view details) {
831 if (!error_) {
832 error_ = WebTransportError(error, quic_error, details,
833 safe_to_report_error_details_);
834 }
835 }
836
OnSessionReady()837 void DedicatedWebTransportHttp3Client::OnSessionReady() {
838 CHECK(session_->SupportsWebTransport());
839
840 session_ready_ = true;
841
842 RecordNegotiatedWebTransportVersion(
843 *session_->SupportedWebTransportVersion());
844 RecordNegotiatedHttpDatagramSupport(session_->http_datagram_support());
845 net_log_.AddEvent(NetLogEventType::QUIC_SESSION_WEBTRANSPORT_SESSION_READY,
846 [&] {
847 base::Value::Dict dict;
848 dict.Set("http_datagram_version",
849 quic::HttpDatagramSupportToString(
850 session_->http_datagram_support()));
851 dict.Set("webtransport_http3_version",
852 WebTransportHttp3VersionString(
853 *session_->SupportedWebTransportVersion()));
854 return dict;
855 });
856 }
857
OnSessionClosed(quic::WebTransportSessionError error_code,const std::string & error_message)858 void DedicatedWebTransportHttp3Client::OnSessionClosed(
859 quic::WebTransportSessionError error_code,
860 const std::string& error_message) {
861 close_info_ = WebTransportCloseInfo(error_code, error_message);
862 task_runner_->PostTask(
863 FROM_HERE,
864 base::BindOnce(&DedicatedWebTransportHttp3Client::TransitionToState,
865 weak_factory_.GetWeakPtr(), WebTransportState::CLOSED));
866 }
867
868 void DedicatedWebTransportHttp3Client::
OnIncomingBidirectionalStreamAvailable()869 OnIncomingBidirectionalStreamAvailable() {
870 visitor_->OnIncomingBidirectionalStreamAvailable();
871 }
872
873 void DedicatedWebTransportHttp3Client::
OnIncomingUnidirectionalStreamAvailable()874 OnIncomingUnidirectionalStreamAvailable() {
875 visitor_->OnIncomingUnidirectionalStreamAvailable();
876 }
877
OnDatagramReceived(std::string_view datagram)878 void DedicatedWebTransportHttp3Client::OnDatagramReceived(
879 std::string_view datagram) {
880 visitor_->OnDatagramReceived(datagram);
881 }
882
883 void DedicatedWebTransportHttp3Client::
OnCanCreateNewOutgoingBidirectionalStream()884 OnCanCreateNewOutgoingBidirectionalStream() {
885 visitor_->OnCanCreateNewOutgoingBidirectionalStream();
886 }
887
888 void DedicatedWebTransportHttp3Client::
OnCanCreateNewOutgoingUnidirectionalStream()889 OnCanCreateNewOutgoingUnidirectionalStream() {
890 visitor_->OnCanCreateNewOutgoingUnidirectionalStream();
891 }
892
OnReadError(int result,const DatagramClientSocket * socket)893 bool DedicatedWebTransportHttp3Client::OnReadError(
894 int result,
895 const DatagramClientSocket* socket) {
896 SetErrorIfNecessary(result);
897 connection_->CloseConnection(quic::QUIC_PACKET_READ_ERROR,
898 ErrorToString(result),
899 quic::ConnectionCloseBehavior::SILENT_CLOSE);
900 return false;
901 }
902
OnPacket(const quic::QuicReceivedPacket & packet,const quic::QuicSocketAddress & local_address,const quic::QuicSocketAddress & peer_address)903 bool DedicatedWebTransportHttp3Client::OnPacket(
904 const quic::QuicReceivedPacket& packet,
905 const quic::QuicSocketAddress& local_address,
906 const quic::QuicSocketAddress& peer_address) {
907 session_->ProcessUdpPacket(local_address, peer_address, packet);
908 return connection_->connected();
909 }
910
HandleWriteError(int error_code,scoped_refptr<QuicChromiumPacketWriter::ReusableIOBuffer>)911 int DedicatedWebTransportHttp3Client::HandleWriteError(
912 int error_code,
913 scoped_refptr<QuicChromiumPacketWriter::ReusableIOBuffer> /*last_packet*/) {
914 return error_code;
915 }
916
OnWriteError(int error_code)917 void DedicatedWebTransportHttp3Client::OnWriteError(int error_code) {
918 SetErrorIfNecessary(error_code);
919 connection_->OnWriteError(error_code);
920 }
921
OnWriteUnblocked()922 void DedicatedWebTransportHttp3Client::OnWriteUnblocked() {
923 connection_->OnCanWrite();
924 }
925
OnConnectionClosed(quic::QuicErrorCode error,const std::string & error_details,quic::ConnectionCloseSource source)926 void DedicatedWebTransportHttp3Client::OnConnectionClosed(
927 quic::QuicErrorCode error,
928 const std::string& error_details,
929 quic::ConnectionCloseSource source) {
930 // If the session is already in a terminal state due to reasons other than
931 // connection close, we should ignore it; otherwise we risk re-entering the
932 // connection teardown process.
933 if (IsTerminalState(state_)) {
934 return;
935 }
936
937 if (!retried_with_new_version_ &&
938 session_->error() == quic::QUIC_INVALID_VERSION) {
939 retried_with_new_version_ = true;
940 DCHECK(original_supported_versions_.empty());
941 original_supported_versions_ = supported_versions_;
942 std::erase_if(
943 supported_versions_, [this](const quic::ParsedQuicVersion& version) {
944 return !base::Contains(
945 session_->connection()->server_supported_versions(), version);
946 });
947 if (!supported_versions_.empty()) {
948 // Since this is a callback from QuicConnection, we can't replace the
949 // connection object in this method; do it from the top of the event loop
950 // instead.
951 task_runner_->PostTask(
952 FROM_HERE,
953 base::BindOnce(&DedicatedWebTransportHttp3Client::CreateConnection,
954 weak_factory_.GetWeakPtr()));
955 return;
956 }
957 // If there are no supported versions, treat this as a regular error.
958 }
959
960 if (error == quic::QUIC_NO_ERROR) {
961 TransitionToState(WebTransportState::CLOSED);
962 return;
963 }
964
965 SetErrorIfNecessary(ERR_QUIC_PROTOCOL_ERROR, error, error_details);
966
967 if (state_ == WebTransportState::CONNECTING) {
968 DoLoop(OK);
969 return;
970 }
971
972 TransitionToState(WebTransportState::FAILED);
973 }
974
OnDatagramProcessed(std::optional<quic::MessageStatus> status)975 void DedicatedWebTransportHttp3Client::OnDatagramProcessed(
976 std::optional<quic::MessageStatus> status) {
977 visitor_->OnDatagramProcessed(status);
978 }
979
980 } // namespace net
981