Perform header-based draft version negotiation in WebTransport over HTTP/3.
The client-side validation can be disabled, which we will need to do in Chrome until the WPT server supports it.
PiperOrigin-RevId: 407210837
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index af54b06..54fda98 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -877,6 +877,8 @@
bool QuicSpdySession::ShouldNegotiateDatagramContexts() { return false; }
+bool QuicSpdySession::ShouldValidateWebTransportVersion() const { return true; }
+
bool QuicSpdySession::WillNegotiateWebTransport() {
return LocalHttpDatagramSupport() != HttpDatagramSupport::kNone &&
version().UsesHttp3() && ShouldNegotiateWebTransport();
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 9f95075..50603de 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -471,6 +471,11 @@
// created WebTransport sessions over HTTP/3.
virtual bool ShouldNegotiateDatagramContexts();
+ // Indicates whether the client should check that the
+ // `Sec-Webtransport-Http3-Draft` header is valid.
+ // TODO(vasilvv): remove this once this is enabled in Chromium.
+ virtual bool ShouldValidateWebTransportVersion() const;
+
protected:
// Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
// CreateOutgoingUnidirectionalStream() with QuicSpdyStream return type to
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 20cebee..bf86955 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -583,6 +583,8 @@
headers.OnHeader(":protocol", "webtransport");
if (session_.http_datagram_support() == HttpDatagramSupport::kDraft00) {
headers.OnHeader("datagram-flow-id", absl::StrCat(session_id));
+ } else {
+ headers.OnHeader("sec-webtransport-http3-draft02", "1");
}
stream->OnStreamHeaderList(/*fin=*/true, 0, headers);
if (session_.http_datagram_support() != HttpDatagramSupport::kDraft00) {
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 55265de..7cb50c0 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -288,6 +288,12 @@
header_block["sec-use-datagram-contexts"] = "?1";
}
+ if (web_transport_ != nullptr &&
+ spdy_session_->http_datagram_support() != HttpDatagramSupport::kDraft00 &&
+ spdy_session_->perspective() == Perspective::IS_SERVER) {
+ header_block["sec-webtransport-http3-draft"] = "draft02";
+ }
+
size_t bytes_written =
WriteHeadersImpl(std::move(header_block), fin, std::move(ack_listener));
if (!VersionUsesHttp3(transport_version()) && fin) {
@@ -1233,6 +1239,7 @@
std::string method;
std::string protocol;
absl::optional<QuicDatagramStreamId> flow_id;
+ bool version_indicated = false;
for (const auto& header : header_list_) {
const std::string& header_name = header.first;
const std::string& header_value = header.second;
@@ -1265,12 +1272,29 @@
}
flow_id = flow_id_out;
}
+ if (header_name == "sec-webtransport-http3-draft02") {
+ if (header_value != "1") {
+ QUIC_DLOG(ERROR) << ENDPOINT
+ << "Rejecting WebTransport due to invalid value of "
+ "Sec-Webtransport-Http3-Draft02 header";
+ return;
+ }
+ version_indicated = true;
+ }
}
if (method != "CONNECT" || protocol != "webtransport") {
return;
}
+ if (!version_indicated &&
+ spdy_session_->http_datagram_support() != HttpDatagramSupport::kDraft00) {
+ QUIC_DLOG(ERROR)
+ << ENDPOINT
+ << "WebTransport request rejected due to missing version header.";
+ return;
+ }
+
if (spdy_session_->http_datagram_support() == HttpDatagramSupport::kDraft00) {
if (!flow_id.has_value()) {
QUIC_DLOG(ERROR)
@@ -1318,6 +1342,8 @@
if (spdy_session_->http_datagram_support() == HttpDatagramSupport::kDraft00) {
headers["datagram-flow-id"] = absl::StrCat(id());
+ } else {
+ headers["sec-webtransport-http3-draft02"] = "1";
}
web_transport_ = std::make_unique<WebTransportHttp3>(
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index ce48043..d75ea97 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -3109,6 +3109,7 @@
headers_[":method"] = "CONNECT";
headers_[":protocol"] = "webtransport";
+ headers_["sec-webtransport-http3-draft02"] = "1";
stream_->OnStreamHeadersPriority(
spdy::SpdyStreamPrecedence(kV3HighestPriority));
diff --git a/quic/core/http/web_transport_http3.cc b/quic/core/http/web_transport_http3.cc
index 128c44e..c2d02f0 100644
--- a/quic/core/http/web_transport_http3.cc
+++ b/quic/core/http/web_transport_http3.cc
@@ -179,6 +179,7 @@
QUIC_DVLOG(1) << ENDPOINT
<< "Received WebTransport headers from server without "
"a valid status code, rejecting.";
+ rejection_reason_ = WebTransportHttp3RejectionReason::kNoStatusCode;
return;
}
bool valid_status = status_code >= 200 && status_code <= 299;
@@ -187,8 +188,32 @@
<< "Received WebTransport headers from server with "
"status code "
<< status_code << ", rejecting.";
+ rejection_reason_ = WebTransportHttp3RejectionReason::kWrongStatusCode;
return;
}
+ bool should_validate_version =
+ session_->http_datagram_support() != HttpDatagramSupport::kDraft00 &&
+ session_->ShouldValidateWebTransportVersion();
+ if (should_validate_version) {
+ auto draft_version_it = headers.find("sec-webtransport-http3-draft");
+ if (draft_version_it == headers.end()) {
+ QUIC_DVLOG(1) << ENDPOINT
+ << "Received WebTransport headers from server without "
+ "a draft version, rejecting.";
+ rejection_reason_ =
+ WebTransportHttp3RejectionReason::kMissingDraftVersion;
+ return;
+ }
+ if (draft_version_it->second != "draft02") {
+ QUIC_DVLOG(1) << ENDPOINT
+ << "Received WebTransport headers from server with "
+ "an unknown draft version ("
+ << draft_version_it->second << "), rejecting.";
+ rejection_reason_ =
+ WebTransportHttp3RejectionReason::kUnsupportedDraftVersion;
+ return;
+ }
+ }
}
QUIC_DVLOG(1) << ENDPOINT << "WebTransport session " << id_ << " ready.";
diff --git a/quic/core/http/web_transport_http3.h b/quic/core/http/web_transport_http3.h
index cd446c1..542b941 100644
--- a/quic/core/http/web_transport_http3.h
+++ b/quic/core/http/web_transport_http3.h
@@ -23,6 +23,14 @@
class QuicSpdySession;
class QuicSpdyStream;
+enum class WebTransportHttp3RejectionReason {
+ kNone,
+ kNoStatusCode,
+ kWrongStatusCode,
+ kMissingDraftVersion,
+ kUnsupportedDraftVersion,
+};
+
// A session of WebTransport over HTTP/3. The session is owned by
// QuicSpdyStream object for the CONNECT stream that established it.
//
@@ -96,6 +104,9 @@
absl::string_view close_details) override;
bool close_received() const { return close_received_; }
+ WebTransportHttp3RejectionReason rejection_reason() const {
+ return rejection_reason_;
+ }
private:
// Notifies the visitor that the connection has been closed. Ensures that the
@@ -123,6 +134,8 @@
bool close_received_ = false;
bool close_notified_ = false;
+ WebTransportHttp3RejectionReason rejection_reason_ =
+ WebTransportHttp3RejectionReason::kNone;
// Those are set to default values, which are used if the session is not
// closed cleanly using an appropriate capsule.
WebTransportSessionError error_code_ = 0;