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