Skip to content

Commit 0e478a2

Browse files
elindseyneild
authored andcommitted
http2: add SETTINGS_HEADER_TABLE_SIZE support
Add support for handling of SETTINGS_HEADER_TABLESIZE in SETTINGS frames. Add http2.Transport.MaxDecoderHeaderTableSize to set the advertised table size for new client connections. Add http2.Transport.MaxEncoderHeaderTableSize to cap the accepted size for new client connections. Add http2.Server.MaxDecoderHeaderTableSize and MaxEncoderHeaderTableSize to do the same on the server. Fixes golang/go#29356 Fixes golang/go#56054 Change-Id: I16ae0f84b8527dc1e09dfce081e9f408fd514513 Reviewed-on: https://go-review.googlesource.com/c/net/+/435899 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Joedian Reid <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Damien Neil <[email protected]>
1 parent a2d827a commit 0e478a2

File tree

5 files changed

+258
-12
lines changed

5 files changed

+258
-12
lines changed

http2/hpack/encode.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
116116
e.dynTab.setMaxSize(v)
117117
}
118118

119+
// MaxDynamicTableSize returns the current dynamic header table size.
120+
func (e *Encoder) MaxDynamicTableSize() (v uint32) {
121+
return e.dynTab.maxSize
122+
}
123+
119124
// SetMaxDynamicTableSizeLimit changes the maximum value that can be
120125
// specified in SetMaxDynamicTableSize to v. By default, it is set to
121126
// 4096, which is the same size of the default dynamic header table

http2/server.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ type Server struct {
9898
// the HTTP/2 spec's recommendations.
9999
MaxConcurrentStreams uint32
100100

101+
// MaxDecoderHeaderTableSize optionally specifies the http2
102+
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
103+
// informs the remote endpoint of the maximum size of the header compression
104+
// table used to decode header blocks, in octets. If zero, the default value
105+
// of 4096 is used.
106+
MaxDecoderHeaderTableSize uint32
107+
108+
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
109+
// header compression table used for encoding request headers. Received
110+
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
111+
// the default value of 4096 is used.
112+
MaxEncoderHeaderTableSize uint32
113+
101114
// MaxReadFrameSize optionally specifies the largest frame
102115
// this server is willing to read. A valid value is between
103116
// 16k and 16M, inclusive. If zero or otherwise invalid, a
@@ -170,6 +183,20 @@ func (s *Server) maxConcurrentStreams() uint32 {
170183
return defaultMaxStreams
171184
}
172185

186+
func (s *Server) maxDecoderHeaderTableSize() uint32 {
187+
if v := s.MaxDecoderHeaderTableSize; v > 0 {
188+
return v
189+
}
190+
return initialHeaderTableSize
191+
}
192+
193+
func (s *Server) maxEncoderHeaderTableSize() uint32 {
194+
if v := s.MaxEncoderHeaderTableSize; v > 0 {
195+
return v
196+
}
197+
return initialHeaderTableSize
198+
}
199+
173200
// maxQueuedControlFrames is the maximum number of control frames like
174201
// SETTINGS, PING and RST_STREAM that will be queued for writing before
175202
// the connection is closed to prevent memory exhaustion attacks.
@@ -394,7 +421,6 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
394421
advMaxStreams: s.maxConcurrentStreams(),
395422
initialStreamSendWindowSize: initialWindowSize,
396423
maxFrameSize: initialMaxFrameSize,
397-
headerTableSize: initialHeaderTableSize,
398424
serveG: newGoroutineLock(),
399425
pushEnabled: true,
400426
sawClientPreface: opts.SawClientPreface,
@@ -424,12 +450,13 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
424450
sc.flow.add(initialWindowSize)
425451
sc.inflow.add(initialWindowSize)
426452
sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
453+
sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize())
427454

428455
fr := NewFramer(sc.bw, c)
429456
if s.CountError != nil {
430457
fr.countError = s.CountError
431458
}
432-
fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
459+
fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil)
433460
fr.MaxHeaderListSize = sc.maxHeaderListSize()
434461
fr.SetMaxReadFrameSize(s.maxReadFrameSize())
435462
sc.framer = fr
@@ -559,7 +586,6 @@ type serverConn struct {
559586
streams map[uint32]*stream
560587
initialStreamSendWindowSize int32
561588
maxFrameSize int32
562-
headerTableSize uint32
563589
peerMaxHeaderListSize uint32 // zero means unknown (default)
564590
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
565591
writingFrame bool // started writing a frame (on serve goroutine or separate)
@@ -864,6 +890,7 @@ func (sc *serverConn) serve() {
864890
{SettingMaxFrameSize, sc.srv.maxReadFrameSize()},
865891
{SettingMaxConcurrentStreams, sc.advMaxStreams},
866892
{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
893+
{SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()},
867894
{SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())},
868895
},
869896
})
@@ -1661,7 +1688,6 @@ func (sc *serverConn) processSetting(s Setting) error {
16611688
}
16621689
switch s.ID {
16631690
case SettingHeaderTableSize:
1664-
sc.headerTableSize = s.Val
16651691
sc.hpackEncoder.SetMaxDynamicTableSize(s.Val)
16661692
case SettingEnablePush:
16671693
sc.pushEnabled = s.Val != 0

http2/server_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,6 +2736,43 @@ func TestServerWithH2Load(t *testing.T) {
27362736
}
27372737
}
27382738

2739+
func TestServer_MaxDecoderHeaderTableSize(t *testing.T) {
2740+
wantHeaderTableSize := uint32(initialHeaderTableSize * 2)
2741+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
2742+
s.MaxDecoderHeaderTableSize = wantHeaderTableSize
2743+
})
2744+
defer st.Close()
2745+
2746+
var advHeaderTableSize *uint32
2747+
st.greetAndCheckSettings(func(s Setting) error {
2748+
switch s.ID {
2749+
case SettingHeaderTableSize:
2750+
advHeaderTableSize = &s.Val
2751+
}
2752+
return nil
2753+
})
2754+
2755+
if advHeaderTableSize == nil {
2756+
t.Errorf("server didn't advertise a header table size")
2757+
} else if got, want := *advHeaderTableSize, wantHeaderTableSize; got != want {
2758+
t.Errorf("server advertised a header table size of %d, want %d", got, want)
2759+
}
2760+
}
2761+
2762+
func TestServer_MaxEncoderHeaderTableSize(t *testing.T) {
2763+
wantHeaderTableSize := uint32(initialHeaderTableSize / 2)
2764+
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
2765+
s.MaxEncoderHeaderTableSize = wantHeaderTableSize
2766+
})
2767+
defer st.Close()
2768+
2769+
st.greet()
2770+
2771+
if got, want := st.sc.hpackEncoder.MaxDynamicTableSize(), wantHeaderTableSize; got != want {
2772+
t.Errorf("server encoder is using a header table size of %d, want %d", got, want)
2773+
}
2774+
}
2775+
27392776
// Issue 12843
27402777
func TestServerDoS_MaxHeaderListSize(t *testing.T) {
27412778
st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {})

http2/transport.go

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ type Transport struct {
118118
// to mean no limit.
119119
MaxHeaderListSize uint32
120120

121+
// MaxDecoderHeaderTableSize optionally specifies the http2
122+
// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
123+
// informs the remote endpoint of the maximum size of the header compression
124+
// table used to decode header blocks, in octets. If zero, the default value
125+
// of 4096 is used.
126+
MaxDecoderHeaderTableSize uint32
127+
128+
// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
129+
// header compression table used for encoding request headers. Received
130+
// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
131+
// the default value of 4096 is used.
132+
MaxEncoderHeaderTableSize uint32
133+
121134
// StrictMaxConcurrentStreams controls whether the server's
122135
// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
123136
// globally. If false, new TCP connections are created to the
@@ -293,10 +306,11 @@ type ClientConn struct {
293306
lastActive time.Time
294307
lastIdle time.Time // time last idle
295308
// Settings from peer: (also guarded by wmu)
296-
maxFrameSize uint32
297-
maxConcurrentStreams uint32
298-
peerMaxHeaderListSize uint64
299-
initialWindowSize uint32
309+
maxFrameSize uint32
310+
maxConcurrentStreams uint32
311+
peerMaxHeaderListSize uint64
312+
peerMaxHeaderTableSize uint32
313+
initialWindowSize uint32
300314

301315
// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
302316
// Write to reqHeaderMu to lock it, read from it to unlock.
@@ -681,6 +695,20 @@ func (t *Transport) expectContinueTimeout() time.Duration {
681695
return t.t1.ExpectContinueTimeout
682696
}
683697

698+
func (t *Transport) maxDecoderHeaderTableSize() uint32 {
699+
if v := t.MaxDecoderHeaderTableSize; v > 0 {
700+
return v
701+
}
702+
return initialHeaderTableSize
703+
}
704+
705+
func (t *Transport) maxEncoderHeaderTableSize() uint32 {
706+
if v := t.MaxEncoderHeaderTableSize; v > 0 {
707+
return v
708+
}
709+
return initialHeaderTableSize
710+
}
711+
684712
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
685713
return t.newClientConn(c, t.disableKeepAlives())
686714
}
@@ -724,12 +752,13 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
724752
if t.CountError != nil {
725753
cc.fr.countError = t.CountError
726754
}
727-
cc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
755+
maxHeaderTableSize := t.maxDecoderHeaderTableSize()
756+
cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil)
728757
cc.fr.MaxHeaderListSize = t.maxHeaderListSize()
729758

730-
// TODO: SetMaxDynamicTableSize, SetMaxDynamicTableSizeLimit on
731-
// henc in response to SETTINGS frames?
732759
cc.henc = hpack.NewEncoder(&cc.hbuf)
760+
cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize())
761+
cc.peerMaxHeaderTableSize = initialHeaderTableSize
733762

734763
if t.AllowHTTP {
735764
cc.nextStreamID = 3
@@ -747,6 +776,9 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
747776
if max := t.maxHeaderListSize(); max != 0 {
748777
initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max})
749778
}
779+
if maxHeaderTableSize != initialHeaderTableSize {
780+
initialSettings = append(initialSettings, Setting{ID: SettingHeaderTableSize, Val: maxHeaderTableSize})
781+
}
750782

751783
cc.bw.Write(clientPreface)
752784
cc.fr.WriteSettings(initialSettings...)
@@ -2773,8 +2805,10 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
27732805
cc.cond.Broadcast()
27742806

27752807
cc.initialWindowSize = s.Val
2808+
case SettingHeaderTableSize:
2809+
cc.henc.SetMaxDynamicTableSize(s.Val)
2810+
cc.peerMaxHeaderTableSize = s.Val
27762811
default:
2777-
// TODO(bradfitz): handle more settings? SETTINGS_HEADER_TABLE_SIZE probably.
27782812
cc.vlogf("Unhandled Setting: %v", s)
27792813
}
27802814
return nil

http2/transport_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4223,6 +4223,150 @@ func TestTransportRequestsStallAtServerLimit(t *testing.T) {
42234223
ct.run()
42244224
}
42254225

4226+
func TestTransportMaxDecoderHeaderTableSize(t *testing.T) {
4227+
ct := newClientTester(t)
4228+
var reqSize, resSize uint32 = 8192, 16384
4229+
ct.tr.MaxDecoderHeaderTableSize = reqSize
4230+
ct.client = func() error {
4231+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
4232+
cc, err := ct.tr.NewClientConn(ct.cc)
4233+
if err != nil {
4234+
return err
4235+
}
4236+
_, err = cc.RoundTrip(req)
4237+
if err != nil {
4238+
return err
4239+
}
4240+
if got, want := cc.peerMaxHeaderTableSize, resSize; got != want {
4241+
return fmt.Errorf("peerHeaderTableSize = %d, want %d", got, want)
4242+
}
4243+
return nil
4244+
}
4245+
ct.server = func() error {
4246+
buf := make([]byte, len(ClientPreface))
4247+
_, err := io.ReadFull(ct.sc, buf)
4248+
if err != nil {
4249+
return fmt.Errorf("reading client preface: %v", err)
4250+
}
4251+
f, err := ct.fr.ReadFrame()
4252+
if err != nil {
4253+
return err
4254+
}
4255+
sf, ok := f.(*SettingsFrame)
4256+
if !ok {
4257+
ct.t.Fatalf("wanted client settings frame; got %v", f)
4258+
_ = sf // stash it away?
4259+
}
4260+
var found bool
4261+
err = sf.ForeachSetting(func(s Setting) error {
4262+
if s.ID == SettingHeaderTableSize {
4263+
found = true
4264+
if got, want := s.Val, reqSize; got != want {
4265+
return fmt.Errorf("received SETTINGS_HEADER_TABLE_SIZE = %d, want %d", got, want)
4266+
}
4267+
}
4268+
return nil
4269+
})
4270+
if err != nil {
4271+
return err
4272+
}
4273+
if !found {
4274+
return fmt.Errorf("missing SETTINGS_HEADER_TABLE_SIZE setting")
4275+
}
4276+
if err := ct.fr.WriteSettings(Setting{SettingHeaderTableSize, resSize}); err != nil {
4277+
ct.t.Fatal(err)
4278+
}
4279+
if err := ct.fr.WriteSettingsAck(); err != nil {
4280+
ct.t.Fatal(err)
4281+
}
4282+
4283+
for {
4284+
f, err := ct.fr.ReadFrame()
4285+
if err != nil {
4286+
return err
4287+
}
4288+
switch f := f.(type) {
4289+
case *HeadersFrame:
4290+
var buf bytes.Buffer
4291+
enc := hpack.NewEncoder(&buf)
4292+
enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
4293+
ct.fr.WriteHeaders(HeadersFrameParam{
4294+
StreamID: f.StreamID,
4295+
EndHeaders: true,
4296+
EndStream: true,
4297+
BlockFragment: buf.Bytes(),
4298+
})
4299+
return nil
4300+
}
4301+
}
4302+
}
4303+
ct.run()
4304+
}
4305+
4306+
func TestTransportMaxEncoderHeaderTableSize(t *testing.T) {
4307+
ct := newClientTester(t)
4308+
var peerAdvertisedMaxHeaderTableSize uint32 = 16384
4309+
ct.tr.MaxEncoderHeaderTableSize = 8192
4310+
ct.client = func() error {
4311+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
4312+
cc, err := ct.tr.NewClientConn(ct.cc)
4313+
if err != nil {
4314+
return err
4315+
}
4316+
_, err = cc.RoundTrip(req)
4317+
if err != nil {
4318+
return err
4319+
}
4320+
if got, want := cc.henc.MaxDynamicTableSize(), ct.tr.MaxEncoderHeaderTableSize; got != want {
4321+
return fmt.Errorf("henc.MaxDynamicTableSize() = %d, want %d", got, want)
4322+
}
4323+
return nil
4324+
}
4325+
ct.server = func() error {
4326+
buf := make([]byte, len(ClientPreface))
4327+
_, err := io.ReadFull(ct.sc, buf)
4328+
if err != nil {
4329+
return fmt.Errorf("reading client preface: %v", err)
4330+
}
4331+
f, err := ct.fr.ReadFrame()
4332+
if err != nil {
4333+
return err
4334+
}
4335+
sf, ok := f.(*SettingsFrame)
4336+
if !ok {
4337+
ct.t.Fatalf("wanted client settings frame; got %v", f)
4338+
_ = sf // stash it away?
4339+
}
4340+
if err := ct.fr.WriteSettings(Setting{SettingHeaderTableSize, peerAdvertisedMaxHeaderTableSize}); err != nil {
4341+
ct.t.Fatal(err)
4342+
}
4343+
if err := ct.fr.WriteSettingsAck(); err != nil {
4344+
ct.t.Fatal(err)
4345+
}
4346+
4347+
for {
4348+
f, err := ct.fr.ReadFrame()
4349+
if err != nil {
4350+
return err
4351+
}
4352+
switch f := f.(type) {
4353+
case *HeadersFrame:
4354+
var buf bytes.Buffer
4355+
enc := hpack.NewEncoder(&buf)
4356+
enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
4357+
ct.fr.WriteHeaders(HeadersFrameParam{
4358+
StreamID: f.StreamID,
4359+
EndHeaders: true,
4360+
EndStream: true,
4361+
BlockFragment: buf.Bytes(),
4362+
})
4363+
return nil
4364+
}
4365+
}
4366+
}
4367+
ct.run()
4368+
}
4369+
42264370
func TestAuthorityAddr(t *testing.T) {
42274371
tests := []struct {
42284372
scheme, authority string

0 commit comments

Comments
 (0)