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