Skip to content

Commit b67a0f0

Browse files
neildgopherbot
authored andcommitted
http2: send correct LastStreamID in stream-caused GOAWAY
When closing a connection because a stream contained a request we didn't like (for example, because the request headers exceed the maximum we will accept), set the LastStreamID in the GOAWAY frame to include the offending stream. This informs the client that retrying the request is unlikely to succeed, and avoids retry loops. This change requires passing the stream ID of the offending stream from Framer.ReadFrame up to the caller. The most sensible way to do this would probably be in the error. However, ReadFrame currently returns a defined error type for connection-ending errors (ConnectionError), and that type is a uint32 with no place to put the stream ID. Rather than changing the returned errors, ReadFrame now returns an error along with a non-nil Frame containing the stream ID, when a stream is responsible for a connection-ending error. For golang/go#66668 Change-Id: Iba07ccbd70ab4939aa56903605474d01703ac6e4 Reviewed-on: https://go-review.googlesource.com/c/net/+/576756 Reviewed-by: Jonathan Amsterdam <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Auto-Submit: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent a130fcc commit b67a0f0

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

http2/frame.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ func terminalReadFrameError(err error) bool {
490490
// returned error is ErrFrameTooLarge. Other errors may be of type
491491
// ConnectionError, StreamError, or anything else from the underlying
492492
// reader.
493+
//
494+
// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
495+
// indicates the stream responsible for the error.
493496
func (fr *Framer) ReadFrame() (Frame, error) {
494497
fr.errDetail = nil
495498
if fr.lastFrame != nil {
@@ -1521,7 +1524,7 @@ func (fr *Framer) maxHeaderStringLen() int {
15211524
// readMetaFrame returns 0 or more CONTINUATION frames from fr and
15221525
// merge them into the provided hf and returns a MetaHeadersFrame
15231526
// with the decoded hpack values.
1524-
func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
1527+
func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) {
15251528
if fr.AllowIllegalReads {
15261529
return nil, errors.New("illegal use of AllowIllegalReads with ReadMetaHeaders")
15271530
}
@@ -1592,7 +1595,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
15921595
}
15931596
// It would be nice to send a RST_STREAM before sending the GOAWAY,
15941597
// but the structure of the server's frame writer makes this difficult.
1595-
return nil, ConnectionError(ErrCodeProtocol)
1598+
return mh, ConnectionError(ErrCodeProtocol)
15961599
}
15971600

15981601
// Also close the connection after any CONTINUATION frame following an
@@ -1604,11 +1607,11 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
16041607
}
16051608
// It would be nice to send a RST_STREAM before sending the GOAWAY,
16061609
// but the structure of the server's frame writer makes this difficult.
1607-
return nil, ConnectionError(ErrCodeProtocol)
1610+
return mh, ConnectionError(ErrCodeProtocol)
16081611
}
16091612

16101613
if _, err := hdec.Write(frag); err != nil {
1611-
return nil, ConnectionError(ErrCodeCompression)
1614+
return mh, ConnectionError(ErrCodeCompression)
16121615
}
16131616

16141617
if hc.HeadersEnded() {
@@ -1625,7 +1628,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
16251628
mh.HeadersFrame.invalidate()
16261629

16271630
if err := hdec.Close(); err != nil {
1628-
return nil, ConnectionError(ErrCodeCompression)
1631+
return mh, ConnectionError(ErrCodeCompression)
16291632
}
16301633
if invalid != nil {
16311634
fr.errDetail = invalid

http2/server.go

+5
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,11 @@ func (sc *serverConn) processFrameFromReader(res readFrameResult) bool {
14821482
sc.goAway(ErrCodeFlowControl)
14831483
return true
14841484
case ConnectionError:
1485+
if res.f != nil {
1486+
if id := res.f.Header().StreamID; id > sc.maxClientStreamID {
1487+
sc.maxClientStreamID = id
1488+
}
1489+
}
14851490
sc.logf("http2: server connection error from %v: %v", sc.conn.RemoteAddr(), ev)
14861491
sc.goAway(ErrCode(ev))
14871492
return true // goAway will handle shutdown

http2/server_test.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -4818,9 +4818,17 @@ func TestServerContinuationFlood(t *testing.T) {
48184818
if err != nil {
48194819
break
48204820
}
4821-
switch f.(type) {
4821+
switch f := f.(type) {
48224822
case *HeadersFrame:
48234823
t.Fatalf("received HEADERS frame; want GOAWAY and a closed connection")
4824+
case *GoAwayFrame:
4825+
// We might not see the GOAWAY (see below), but if we do it should
4826+
// indicate that the server processed this request so the client doesn't
4827+
// attempt to retry it.
4828+
if got, want := f.LastStreamID, uint32(1); got != want {
4829+
t.Errorf("received GOAWAY with LastStreamId %v, want %v", got, want)
4830+
}
4831+
48244832
}
48254833
}
48264834
// We expect to have seen a GOAWAY before the connection closes,

0 commit comments

Comments
 (0)