Have server-side OgHttp2Session send a GOAWAY on connection error.
OgHttp2Session::LatchErrorAndNotify() means the connection is not long for this
world, so this CL updates the method to send a (server-side) GOAWAY.
PiperOrigin-RevId: 403179565
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index e1c540d..34a39eb 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -1438,13 +1438,18 @@
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0));
EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::INTERNAL_ERROR)));
int send_result = adapter->Send();
// Some bytes should have been serialized.
EXPECT_EQ(0, send_result);
- // SETTINGS and SETTINGS ack
+ // SETTINGS, SETTINGS ack, and GOAWAY
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::SETTINGS}));
+ spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::GOAWAY}));
}
// Exercises the case when a visitor chooses to reject a frame based solely on
@@ -1487,13 +1492,18 @@
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0));
EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::INTERNAL_ERROR)));
int send_result = adapter->Send();
// Some bytes should have been serialized.
EXPECT_EQ(0, send_result);
- // SETTINGS and SETTINGS ack
+ // SETTINGS, SETTINGS ack, and GOAWAY
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::SETTINGS}));
+ spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::GOAWAY}));
}
TEST(OgHttp2AdapterServerTest, ServerRejectsBeginningOfData) {
@@ -1547,13 +1557,18 @@
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0));
EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::INTERNAL_ERROR)));
int send_result = adapter->Send();
// Some bytes should have been serialized.
EXPECT_EQ(0, send_result);
- // SETTINGS and SETTINGS ack.
+ // SETTINGS, SETTINGS ack, and GOAWAY.
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::SETTINGS}));
+ spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::GOAWAY}));
}
TEST(OgHttp2AdapterServerTest, ServerRejectsStreamData) {
@@ -1607,13 +1622,18 @@
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0));
EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::INTERNAL_ERROR)));
int send_result = adapter->Send();
// Some bytes should have been serialized.
EXPECT_EQ(0, send_result);
- // SETTINGS and SETTINGS ack.
+ // SETTINGS, SETTINGS ack, and GOAWAY.
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::SETTINGS}));
+ spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::GOAWAY}));
}
// Exercises a naive mutually recursive test client and server. This test fails
@@ -1732,13 +1752,18 @@
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x0, 0));
EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(GOAWAY, 0, _, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(GOAWAY, 0, _, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
int send_result = adapter->Send();
// Some bytes should have been serialized.
EXPECT_EQ(0, send_result);
- // SETTINGS and SETTINGS ack.
+ // SETTINGS, SETTINGS ack, and GOAWAY.
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
- spdy::SpdyFrameType::SETTINGS}));
+ spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::GOAWAY}));
}
} // namespace
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 1f8fcf2..8c5e6c8 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -17,6 +17,7 @@
namespace {
using ConnectionError = Http2VisitorInterface::ConnectionError;
+using SpdyFramerError = Http2DecoderAdapter::SpdyFramerError;
// #define OGHTTP2_DEBUG_TRACE 1
@@ -146,6 +147,47 @@
std::function<void()> f_;
};
+Http2ErrorCode GetHttp2ErrorCode(SpdyFramerError error) {
+ switch (error) {
+ case SpdyFramerError::SPDY_NO_ERROR:
+ return Http2ErrorCode::NO_ERROR;
+ case SpdyFramerError::SPDY_INVALID_STREAM_ID:
+ case SpdyFramerError::SPDY_INVALID_CONTROL_FRAME:
+ case SpdyFramerError::SPDY_INVALID_PADDING:
+ case SpdyFramerError::SPDY_INVALID_DATA_FRAME_FLAGS:
+ case SpdyFramerError::SPDY_UNEXPECTED_FRAME:
+ return Http2ErrorCode::PROTOCOL_ERROR;
+ case SpdyFramerError::SPDY_CONTROL_PAYLOAD_TOO_LARGE:
+ case SpdyFramerError::SPDY_INVALID_CONTROL_FRAME_SIZE:
+ case SpdyFramerError::SPDY_OVERSIZED_PAYLOAD:
+ return Http2ErrorCode::FRAME_SIZE_ERROR;
+ case SpdyFramerError::SPDY_DECOMPRESS_FAILURE:
+ case SpdyFramerError::SPDY_HPACK_INDEX_VARINT_ERROR:
+ case SpdyFramerError::SPDY_HPACK_NAME_LENGTH_VARINT_ERROR:
+ case SpdyFramerError::SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR:
+ case SpdyFramerError::SPDY_HPACK_NAME_TOO_LONG:
+ case SpdyFramerError::SPDY_HPACK_VALUE_TOO_LONG:
+ case SpdyFramerError::SPDY_HPACK_NAME_HUFFMAN_ERROR:
+ case SpdyFramerError::SPDY_HPACK_VALUE_HUFFMAN_ERROR:
+ case SpdyFramerError::SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE:
+ case SpdyFramerError::SPDY_HPACK_INVALID_INDEX:
+ case SpdyFramerError::SPDY_HPACK_INVALID_NAME_INDEX:
+ case SpdyFramerError::SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED:
+ case SpdyFramerError::
+ SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK:
+ case SpdyFramerError::
+ SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING:
+ case SpdyFramerError::SPDY_HPACK_TRUNCATED_BLOCK:
+ case SpdyFramerError::SPDY_HPACK_FRAGMENT_TOO_LONG:
+ case SpdyFramerError::SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT:
+ return Http2ErrorCode::COMPRESSION_ERROR;
+ case SpdyFramerError::SPDY_INTERNAL_FRAMER_ERROR:
+ case SpdyFramerError::SPDY_STOP_PROCESSING:
+ case SpdyFramerError::LAST_ERROR:
+ return Http2ErrorCode::INTERNAL_ERROR;
+ }
+}
+
} // namespace
void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() {
@@ -301,7 +343,8 @@
QUICHE_DLOG(INFO) << "Preface doesn't match! Expected: ["
<< absl::CEscape(remaining_preface_) << "], actual: ["
<< absl::CEscape(bytes) << "]";
- LatchErrorAndNotify(ConnectionError::kInvalidConnectionPreface);
+ LatchErrorAndNotify(Http2ErrorCode::PROTOCOL_ERROR,
+ ConnectionError::kInvalidConnectionPreface);
return -1;
}
remaining_preface_.remove_prefix(min_size);
@@ -380,7 +423,8 @@
}
}
if (result < 0) {
- LatchErrorAndNotify(ConnectionError::kSendError);
+ LatchErrorAndNotify(Http2ErrorCode::INTERNAL_ERROR,
+ ConnectionError::kSendError);
return result;
} else if (!serialized_prefix_.empty()) {
return 0;
@@ -416,7 +460,8 @@
spdy::SpdySerializedFrame frame = framer_.SerializeFrame(*frame_ptr);
const int64_t result = visitor_.OnReadyToSend(absl::string_view(frame));
if (result < 0) {
- LatchErrorAndNotify(ConnectionError::kSendError);
+ LatchErrorAndNotify(Http2ErrorCode::INTERNAL_ERROR,
+ ConnectionError::kSendError);
return false;
} else if (result == 0) {
// Write blocked.
@@ -653,12 +698,13 @@
}
}
-void OgHttp2Session::OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+void OgHttp2Session::OnError(SpdyFramerError error,
std::string detailed_error) {
QUICHE_VLOG(1) << "Error: "
<< http2::Http2DecoderAdapter::SpdyFramerErrorToString(error)
<< " details: " << detailed_error;
- LatchErrorAndNotify(ConnectionError::kParseError);
+ // TODO(diannahu): Consider propagating `detailed_error`.
+ LatchErrorAndNotify(GetHttp2ErrorCode(error), ConnectionError::kParseError);
}
void OgHttp2Session::OnCommonHeader(spdy::SpdyStreamId stream_id,
@@ -811,7 +857,8 @@
const auto new_stream_id = static_cast<Http2StreamId>(stream_id);
if (new_stream_id <= highest_processed_stream_id_) {
// A new stream ID lower than the watermark is a connection error.
- LatchErrorAndNotify(ConnectionError::kInvalidNewStreamId);
+ LatchErrorAndNotify(Http2ErrorCode::PROTOCOL_ERROR,
+ ConnectionError::kInvalidNewStreamId);
return;
}
CreateStream(stream_id);
@@ -873,7 +920,8 @@
stream_id, spdy::ERROR_CODE_INTERNAL_ERROR));
}
} else if (result == Http2VisitorInterface::HEADER_CONNECTION_ERROR) {
- LatchErrorAndNotify(ConnectionError::kHeaderError);
+ LatchErrorAndNotify(Http2ErrorCode::INTERNAL_ERROR,
+ ConnectionError::kHeaderError);
}
}
@@ -1048,7 +1096,8 @@
}
}
-void OgHttp2Session::LatchErrorAndNotify(ConnectionError error) {
+void OgHttp2Session::LatchErrorAndNotify(Http2ErrorCode error_code,
+ ConnectionError error) {
if (latched_error_) {
// Do not kick a connection when it is down.
return;
@@ -1057,6 +1106,11 @@
latched_error_ = true;
visitor_.OnConnectionError(error);
decoder_.StopProcessing();
+ if (IsServerSession()) {
+ EnqueueFrame(absl::make_unique<spdy::SpdyGoAwayIR>(
+ highest_processed_stream_id_, TranslateErrorCode(error_code),
+ ConnectionErrorToString(error)));
+ }
}
} // namespace adapter
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index af17230..31488f4 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -280,7 +280,10 @@
// Returns true if the session can create a new stream.
bool CanCreateStream() const;
- void LatchErrorAndNotify(Http2VisitorInterface::ConnectionError error);
+ // Informs the visitor of the connection `error` and stops processing on the
+ // connection. If server-side, also sends a GOAWAY with `error_code`.
+ void LatchErrorAndNotify(Http2ErrorCode error_code,
+ Http2VisitorInterface::ConnectionError error);
// Receives events when inbound frames are parsed.
Http2VisitorInterface& visitor_;