Copilot commented on code in PR #3690:
URL: https://github.com/apache/brpc/pull/3690#discussion_r4421643595
##########
src/brpc/policy/http2_rpc_protocol.cpp:
##########
@@ -155,19 +144,13 @@ static int WriteAck(Socket* s, const void* data, size_t
n) {
butil::IOBuf sendbuf;
sendbuf.append(data, n);
Socket::WriteOptions wopt;
- // These writes historically ignored EOVERCROWDED so control replies
- // could always be queued, but that defeats the explicit memory bound of
- // -socket_max_unwritten_bytes: a peer flooding PING/SETTINGS frames
- // while withholding TCP reads makes this process buffer ack frames
- // without limit. Respect the bound by default; callers treat a failed
- // ack write as a connection error and close the overcrowded connection.
- wopt.ignore_eovercrowded = FLAGS_h2_ack_ignore_eovercrowded;
+ wopt.ignore_eovercrowded = true;
Review Comment:
Forced `ignore_eovercrowded = true` for HTTP/2 control-plane acks
(PING/SETTINGS/RST/GOAWAY/WINDOW_UPDATE) bypasses the
`-socket_max_unwritten_bytes` memory bound and can reintroduce unbounded
buffering under a peer that floods control frames while not reading. Consider
restoring the prior behavior (respect the bound by default) and/or keeping a
guarded flag, but defaulting to bounded behavior.
##########
src/brpc/policy/http2_rpc_protocol.cpp:
##########
@@ -590,11 +594,7 @@ ParseResult H2Context::Consume(
LOG(WARNING) << "Fail to send GOAWAY to " << *_socket;
return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
}
- // https://tools.ietf.org/html/rfc7540#section-5.4.1
- // A connection error is unrecoverable: close the connection
- // after sending GOAWAY instead of continuing to parse (and
- // buffer) whatever the misbehaving peer keeps sending.
- return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
+ return MakeMessage(NULL);
Review Comment:
After sending a connection-level GOAWAY, the code now returns
`MakeMessage(NULL)` (i.e., keeps parsing/accepting more frames) instead of
forcing connection teardown. For protocol violations, continuing to parse input
from a misbehaving peer can become an unbounded buffering/CPU sink. Consider
restoring the previous behavior: treat a connection error as unrecoverable and
stop parsing/close the connection after GOAWAY.
##########
src/brpc/policy/http2_rpc_protocol.cpp:
##########
@@ -797,26 +760,18 @@ H2ParseResult H2StreamContext::OnContinuation(
<< ", stream_id=" << frame_head.stream_id;
return MakeH2Error(H2_PROTOCOL_ERROR);
}
- // See the same check in H2StreamContext::OnHeaders().
- if (_rejected_error != H2_NO_ERROR) {
- return MakeH2Error(_rejected_error, stream_id());
- }
if (_stream_ended) {
return OnEndStream();
}
}
- return MakeH2Message(nullptr);
+ return MakeH2Message(NULL);
}
H2ParseResult H2Context::OnData(
butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) {
uint32_t frag_size = frame_head.payload_size;
uint8_t pad_length = 0;
if (frame_head.flags & H2_FLAGS_PADDED) {
Review Comment:
This regresses validation for PADDED DATA frames: if `payload_size == 0` and
PADDED is set, `--frag_size` underflows (uint32_t) and can make subsequent size
checks behave incorrectly. Restore the explicit `payload_size == 0` (or
`frag_size == 0`) guard before decrementing.
##########
src/brpc/policy/http2_rpc_protocol.cpp:
##########
@@ -1076,12 +1045,10 @@ H2ParseResult H2Context::OnGoAway(
LOG(ERROR) << "Invalid flags=" << h.flags;
return MakeH2Error(H2_PROTOCOL_ERROR);
}
- // Last-Stream-ID and Error Code precede the Additional Debug Data.
- // The reserved bit of Last-Stream-ID is ignored on receipt.
- const int last_stream_id = static_cast<int>(LoadUint32(it) & 0x7FFFFFFF);
- const H2Error ALLOW_UNUSED h2_error = static_cast<H2Error>(LoadUint32(it));
// Skip Additional Debug Data
it.forward(h.payload_size - 8);
+ const int last_stream_id = static_cast<int>(LoadUint32(it));
+ const H2Error ALLOW_UNUSED h2_error = static_cast<H2Error>(LoadUint32(it));
Review Comment:
GOAWAY parsing is incorrect: the first 8 bytes of the payload are
`Last-Stream-ID` and `Error Code`, followed by optional debug data. Forwarding
`(payload_size - 8)` before reading causes the code to read from the debug-data
tail (or past the payload) rather than the fixed header fields. Read the two
uint32s first (masking the reserved bit of Last-Stream-ID with `0x7FFFFFFF`),
then forward the remaining debug data.
##########
src/brpc/policy/http2_rpc_protocol.cpp:
##########
@@ -1222,10 +1189,6 @@ void H2Context::DeferWindowUpdate(int64_t size) {
SaveUint32(winbuf + FRAME_HEAD_SIZE, conn_wu);
if (WriteAck(_socket, winbuf, sizeof(winbuf)) != 0) {
LOG(WARNING) << "Fail to send WINDOW_UPDATE";
- // Retry on a later DATA frame instead of silently losing
- // the window bytes (the peer would stall otherwise).
- _deferred_window_update.fetch_add(
- conn_wu, butil::memory_order_relaxed);
}
Review Comment:
On WINDOW_UPDATE write failure, the deferred window bytes are now dropped.
That can permanently stall the peer (it will never regain window it expects),
causing hung streams/connections. Either restore re-adding the bytes back to
`_deferred_window_update` on write failure, or treat a failed WINDOW_UPDATE
write as a connection error that closes the socket so callers don't hang.
##########
src/brpc/policy/http2_rpc_protocol.cpp:
##########
@@ -1338,13 +1300,6 @@ void H2StreamContext::SetState(H2StreamState state) {
int H2StreamContext::ConsumeHeaders(butil::IOBufBytesIterator& it) {
HPacker& hpacker = _conn_ctx->hpacker();
HttpHeader& h = header();
- // https://tools.ietf.org/html/rfc7540#section-10.5.1
- // Bound the cumulative decoded size of the header list. Without this
- // check nothing limits header bytes (-max_body_size covers DATA only)
- // and 1-byte HPACK indexed references to a large dynamic-table entry
- // amplify a small HEADERS/CONTINUATION frame ~4000x ("HPACK bomb").
- const uint32_t max_header_list_size =
- _conn_ctx->_unack_local_settings.max_header_list_size;
while (it) {
Review Comment:
This change removes multiple HTTP/2 header safety limits (e.g., max decoded
header list size / \"HPACK bomb\" mitigation, max header count enforcement, and
the prior stream-level rejection mechanism that continued decoding to keep the
dynamic table in sync). The new behavior can allow a single stream to drive
unbounded memory/cpu via excessive headers or oversized header blocks. Please
reintroduce: (1) a decoded header-list-size bound (RFC 7540/9113 10.5.1), (2)
header count limiting, and (3) stream-level rejection that keeps decoding but
avoids accumulating headers, so one bad stream doesn't force a connection-level
failure.
##########
src/brpc/details/hpack.h:
##########
@@ -108,6 +108,14 @@ class HPacker : public Describable {
void Encode(butil::IOBufAppender* out, const Header& header)
{ return Encode(out, header, HPackOptions()); }
+ // Encode header as a literal without consulting or mutating the dynamic
+ // table (RFC 7541 section 6.2.2 / 6.2.3). Safe to use for trailers whose
+ // transmission may be deferred behind flow-controlled DATA frames while
+ // other streams continue to encode HEADERS on the same connection.
Review Comment:
The comment says \"encode header as a literal\", but the implementation in
`EncodeWithoutDynamicTable` may emit an indexed representation (0x80) when the
header matches the static table. That's still correct for the goal (no dynamic
table interaction), but the wording is inaccurate. Update the comment to
reflect that it encodes without consulting/mutating the dynamic table and may
use static-table indexed encoding when possible.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]