Passes information about CONTINUATION frames through CallbackVisitor.
This is required for HTTP/2 frame flood detection.
PiperOrigin-RevId: 387882196
diff --git a/http2/adapter/callback_visitor.cc b/http2/adapter/callback_visitor.cc
index 7701cc6..3ecc064 100644
--- a/http2/adapter/callback_visitor.cc
+++ b/http2/adapter/callback_visitor.cc
@@ -72,11 +72,25 @@
size_t length,
uint8_t type,
uint8_t flags) {
+ QUICHE_VLOG(1) << "CallbackVisitor::OnFrameHeader(stream_id: " << stream_id
+ << ", len: " << length << ", type: " << int(type)
+ << ", flags: " << int(flags) << ")";
if (static_cast<FrameType>(type) == FrameType::CONTINUATION) {
// Treat CONTINUATION as HEADERS
QUICHE_DCHECK_EQ(current_frame_.hd.stream_id, stream_id);
current_frame_.hd.length += length;
current_frame_.hd.flags |= flags;
+ QUICHE_DLOG_IF(ERROR, length == 0) << "Empty CONTINUATION!";
+ // Still need to deliver the CONTINUATION to the begin frame callback.
+ nghttp2_frame_hd hd;
+ memset(&hd, 0, sizeof(hd));
+ hd.stream_id = stream_id;
+ hd.length = length;
+ hd.type = type;
+ hd.flags = flags;
+ if (callbacks_->on_begin_frame_callback) {
+ callbacks_->on_begin_frame_callback(nullptr, &hd, user_data_);
+ }
return;
}
// The general strategy is to clear |current_frame_| at the start of a new
@@ -88,9 +102,6 @@
current_frame_.hd.type = type;
current_frame_.hd.flags = flags;
if (callbacks_->on_begin_frame_callback) {
- QUICHE_VLOG(1) << "CallbackVisitor::OnFrameHeader(stream_id: " << stream_id
- << ", len: " << length << ", type: " << int(type)
- << ", flags: " << int(flags) << ")";
callbacks_->on_begin_frame_callback(nullptr, ¤t_frame_.hd,
user_data_);
}
diff --git a/http2/adapter/callback_visitor_test.cc b/http2/adapter/callback_visitor_test.cc
index ddf1f93..2cb31ed 100644
--- a/http2/adapter/callback_visitor_test.cc
+++ b/http2/adapter/callback_visitor_test.cc
@@ -22,6 +22,7 @@
PING,
GOAWAY,
WINDOW_UPDATE,
+ CONTINUATION,
};
// Tests connection-level events.
@@ -178,6 +179,41 @@
EXPECT_EQ(absl::string_view(metadata_dest, written), kExampleFrame);
}
+TEST(ClientCallbackVisitorUnitTest, HeadersWithContinuation) {
+ testing::StrictMock<MockNghttp2Callbacks> callbacks;
+ CallbackVisitor visitor(Perspective::kClient,
+ *MockNghttp2Callbacks::GetCallbacks(), &callbacks);
+
+ testing::InSequence seq;
+
+ // HEADERS on stream 1
+ EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, HEADERS, 0x0)));
+ visitor.OnFrameHeader(1, 23, HEADERS, 0x0);
+
+ EXPECT_CALL(callbacks,
+ OnBeginHeaders(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE)));
+ visitor.OnBeginHeadersForStream(1);
+
+ EXPECT_CALL(callbacks, OnHeader(_, ":status", "200", _));
+ visitor.OnHeaderForStream(1, ":status", "200");
+
+ EXPECT_CALL(callbacks, OnHeader(_, "server", "my-fake-server", _));
+ visitor.OnHeaderForStream(1, "server", "my-fake-server");
+
+ EXPECT_CALL(callbacks, OnBeginFrame(HasFrameHeader(1, CONTINUATION, 0x4)));
+ visitor.OnFrameHeader(1, 23, CONTINUATION, 0x4);
+
+ EXPECT_CALL(callbacks,
+ OnHeader(_, "date", "Tue, 6 Apr 2021 12:54:01 GMT", _));
+ visitor.OnHeaderForStream(1, "date", "Tue, 6 Apr 2021 12:54:01 GMT");
+
+ EXPECT_CALL(callbacks, OnHeader(_, "trailer", "x-server-status", _));
+ visitor.OnHeaderForStream(1, "trailer", "x-server-status");
+
+ EXPECT_CALL(callbacks, OnFrameRecv(IsHeaders(1, _, NGHTTP2_HCAT_RESPONSE)));
+ visitor.OnEndHeadersForStream(1);
+}
+
TEST(ServerCallbackVisitorUnitTest, ConnectionFrames) {
testing::StrictMock<MockNghttp2Callbacks> callbacks;
CallbackVisitor visitor(Perspective::kServer,
diff --git a/http2/adapter/nghttp2_adapter.cc b/http2/adapter/nghttp2_adapter.cc
index 7eb57f8..b786daa 100644
--- a/http2/adapter/nghttp2_adapter.cc
+++ b/http2/adapter/nghttp2_adapter.cc
@@ -98,6 +98,7 @@
int NgHttp2Adapter::Send() {
const int result = nghttp2_session_send(session_->raw_ptr());
if (result != 0) {
+ QUICHE_VLOG(1) << "nghttp2_session_send returned " << result;
visitor_.OnConnectionError();
}
return result;