Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/net/http/internal/http2/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,19 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon
if conf.CountError != nil {
fr.countError = conf.CountError
}
fr.ReadMetaHeaders = hpack.NewDecoder(uint32(conf.MaxDecoderHeaderTableSize), nil)
// A decoder table size below the initial 4096 (RFC 7540, Section 6.5.2)
// can't be applied immediately: the client may keep using the initial
// size until it processes our SETTINGS frame (RFC 7540, Section 6.5.3),
// and it signals the reduction with a dynamic table size update at the
// beginning of the first header block following the settings
// acknowledgment (RFC 7541, Section 4.2). Start at the initial size and
// lower it when the client acknowledges our SETTINGS. See processSettings.
decoderTableSize := uint32(conf.MaxDecoderHeaderTableSize)
if decoderTableSize < initialHeaderTableSize {
sc.pendingDecoderTableSize = decoderTableSize
decoderTableSize = initialHeaderTableSize
}
fr.ReadMetaHeaders = hpack.NewDecoder(decoderTableSize, nil)
fr.MaxHeaderListSize = sc.maxHeaderListSize()
fr.MaxHeaderValueCount = sc.hs.MaxHeaderValueCount()
fr.SetMaxReadFrameSize(uint32(conf.MaxReadFrameSize))
Expand Down Expand Up @@ -443,6 +455,7 @@ type serverConn struct {
sawFirstSettings bool // got the initial SETTINGS frame after the preface
needToSendSettingsAck bool
unackedSettings int // how many SETTINGS have we sent without ACKs?
pendingDecoderTableSize uint32 // if non-zero, HPACK decoder table size to apply on SETTINGS ack
queuedControlFrames int // control frames in the writeSched queue
clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit)
advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
Expand Down Expand Up @@ -1614,6 +1627,17 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error {
// hang up on them anyway.
return sc.countError("ack_mystery", ConnectionError(ErrCodeProtocol))
}
if sc.pendingDecoderTableSize != 0 {
// The client has acknowledged our SETTINGS, so all header
// blocks it sends from now on were encoded with knowledge
// of our lower HEADER_TABLE_SIZE. It is now safe to apply
// the configured size to the decoder. The read goroutine
// is parked until readMore is called, so mutating the
// decoder here is race-free.
sc.framer.ReadMetaHeaders.SetAllowedMaxDynamicTableSize(sc.pendingDecoderTableSize)
sc.framer.ReadMetaHeaders.SetMaxDynamicTableSize(sc.pendingDecoderTableSize)
sc.pendingDecoderTableSize = 0
}
return nil
}
if f.NumSettings() > 100 || f.HasDuplicates() {
Expand Down
82 changes: 82 additions & 0 deletions src/net/http/internal/http2/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2857,6 +2857,88 @@ func testServer_MaxDecoderHeaderTableSize(t *testing.T) {
}
}

func TestServer_MaxDecoderHeaderTableSize_DeferredUntilAck(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
testServer_MaxDecoderHeaderTableSizeDeferred(t, true)
})
}

func TestServer_MaxDecoderHeaderTableSize_EnforcedAfterAck(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
testServer_MaxDecoderHeaderTableSizeDeferred(t, false)
})
}

// testServer_MaxDecoderHeaderTableSizeDeferred tests that a server
// configured with a decoder header table size below the protocol-initial
// 4096 bytes does not apply it until the client acknowledges the server's
// SETTINGS frame. Until then, the client's encoder may legitimately
// reference dynamic table entries under the initial 4096-byte table size
// (RFC 7540, Section 6.5.3; RFC 7541, Section 4.2).
func testServer_MaxDecoderHeaderTableSizeDeferred(t *testing.T, sizeUpdateAfterAck bool) {
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(h2 *http.HTTP2Config) {
h2.MaxDecoderHeaderTableSize = 1
})
defer st.Close()

st.writePreface()
st.writeSettings()
st.sync()
readFrame[*SettingsFrame](t, st)

// The server's initial WINDOW_UPDATE and its ack of our SETTINGS
// can arrive in either order.
st.wantUnorderedFrames(
func(f *SettingsFrame) bool {
if !f.IsAck() {
t.Fatalf("got second non-ack SETTINGS frame")
}
return true
},
func(f *WindowUpdateFrame) bool {
return true
},
)

// Send two requests whose header blocks use the dynamic table, before
// acknowledging the server's SETTINGS. The second request references
// entries added by the first. Our encoder is still permitted to use
// the initial 4096-byte table size, so both must succeed.
for _, id := range []uint32{1, 3} {
st.writeHeaders(HeadersFrameParam{
StreamID: id,
BlockFragment: st.encodeHeader("x-custom-header", "long-enough-value-to-index"),
EndStream: true,
EndHeaders: true,
})
st.wantHeaders(wantHeader{streamID: id, endStream: true})
}

// Acknowledge the server's SETTINGS. Header blocks we send from here
// on must respect the reduced table size.
st.writeSettingsAck()
st.sync()

if sizeUpdateAfterAck {
// A compliant encoder begins its next header block with a
// dynamic table size update. The request must succeed.
st.hpackEnc.SetMaxDynamicTableSize(1)
}
st.writeHeaders(HeadersFrameParam{
StreamID: 5,
BlockFragment: st.encodeHeader("x-custom-header", "long-enough-value-to-index"),
EndStream: true,
EndHeaders: true,
})
if sizeUpdateAfterAck {
st.wantHeaders(wantHeader{streamID: 5, endStream: true})
} else {
// A header block that keeps referencing the now-evicted
// 4096-byte table without a size update must be rejected.
st.wantGoAway(5, ErrCodeCompression)
}
}

func TestServer_MaxEncoderHeaderTableSize(t *testing.T) {
synctest.Test(t, testServer_MaxEncoderHeaderTableSize)
}
Expand Down
Loading