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 <dneil@google.com>
Reviewed-by: Joedian Reid <joedian@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Damien Neil <dneil@google.com>
diff --git a/http2/hpack/encode.go b/http2/hpack/encode.go
index 6886dc1..46219da 100644
--- a/http2/hpack/encode.go
+++ b/http2/hpack/encode.go
@@ -116,6 +116,11 @@
 	e.dynTab.setMaxSize(v)
 }
 
+// MaxDynamicTableSize returns the current dynamic header table size.
+func (e *Encoder) MaxDynamicTableSize() (v uint32) {
+	return e.dynTab.maxSize
+}
+
 // SetMaxDynamicTableSizeLimit changes the maximum value that can be
 // specified in SetMaxDynamicTableSize to v. By default, it is set to
 // 4096, which is the same size of the default dynamic header table
diff --git a/http2/server.go b/http2/server.go
index d8a17aa..e35a76c 100644
--- a/http2/server.go
+++ b/http2/server.go
@@ -98,6 +98,19 @@
 	// the HTTP/2 spec's recommendations.
 	MaxConcurrentStreams uint32
 
+	// MaxDecoderHeaderTableSize optionally specifies the http2
+	// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
+	// informs the remote endpoint of the maximum size of the header compression
+	// table used to decode header blocks, in octets. If zero, the default value
+	// of 4096 is used.
+	MaxDecoderHeaderTableSize uint32
+
+	// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
+	// header compression table used for encoding request headers. Received
+	// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
+	// the default value of 4096 is used.
+	MaxEncoderHeaderTableSize uint32
+
 	// MaxReadFrameSize optionally specifies the largest frame
 	// this server is willing to read. A valid value is between
 	// 16k and 16M, inclusive. If zero or otherwise invalid, a
@@ -170,6 +183,20 @@
 	return defaultMaxStreams
 }
 
+func (s *Server) maxDecoderHeaderTableSize() uint32 {
+	if v := s.MaxDecoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
+func (s *Server) maxEncoderHeaderTableSize() uint32 {
+	if v := s.MaxEncoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
 // maxQueuedControlFrames is the maximum number of control frames like
 // SETTINGS, PING and RST_STREAM that will be queued for writing before
 // the connection is closed to prevent memory exhaustion attacks.
@@ -394,7 +421,6 @@
 		advMaxStreams:               s.maxConcurrentStreams(),
 		initialStreamSendWindowSize: initialWindowSize,
 		maxFrameSize:                initialMaxFrameSize,
-		headerTableSize:             initialHeaderTableSize,
 		serveG:                      newGoroutineLock(),
 		pushEnabled:                 true,
 		sawClientPreface:            opts.SawClientPreface,
@@ -424,12 +450,13 @@
 	sc.flow.add(initialWindowSize)
 	sc.inflow.add(initialWindowSize)
 	sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
+	sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize())
 
 	fr := NewFramer(sc.bw, c)
 	if s.CountError != nil {
 		fr.countError = s.CountError
 	}
-	fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
+	fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil)
 	fr.MaxHeaderListSize = sc.maxHeaderListSize()
 	fr.SetMaxReadFrameSize(s.maxReadFrameSize())
 	sc.framer = fr
@@ -559,7 +586,6 @@
 	streams                     map[uint32]*stream
 	initialStreamSendWindowSize int32
 	maxFrameSize                int32
-	headerTableSize             uint32
 	peerMaxHeaderListSize       uint32            // zero means unknown (default)
 	canonHeader                 map[string]string // http2-lower-case -> Go-Canonical-Case
 	writingFrame                bool              // started writing a frame (on serve goroutine or separate)
@@ -864,6 +890,7 @@
 			{SettingMaxFrameSize, sc.srv.maxReadFrameSize()},
 			{SettingMaxConcurrentStreams, sc.advMaxStreams},
 			{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
+			{SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()},
 			{SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())},
 		},
 	})
@@ -1661,7 +1688,6 @@
 	}
 	switch s.ID {
 	case SettingHeaderTableSize:
-		sc.headerTableSize = s.Val
 		sc.hpackEncoder.SetMaxDynamicTableSize(s.Val)
 	case SettingEnablePush:
 		sc.pushEnabled = s.Val != 0
diff --git a/http2/server_test.go b/http2/server_test.go
index 757bd29..3760871 100644
--- a/http2/server_test.go
+++ b/http2/server_test.go
@@ -2736,6 +2736,43 @@
 	}
 }
 
+func TestServer_MaxDecoderHeaderTableSize(t *testing.T) {
+	wantHeaderTableSize := uint32(initialHeaderTableSize * 2)
+	st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
+		s.MaxDecoderHeaderTableSize = wantHeaderTableSize
+	})
+	defer st.Close()
+
+	var advHeaderTableSize *uint32
+	st.greetAndCheckSettings(func(s Setting) error {
+		switch s.ID {
+		case SettingHeaderTableSize:
+			advHeaderTableSize = &s.Val
+		}
+		return nil
+	})
+
+	if advHeaderTableSize == nil {
+		t.Errorf("server didn't advertise a header table size")
+	} else if got, want := *advHeaderTableSize, wantHeaderTableSize; got != want {
+		t.Errorf("server advertised a header table size of %d, want %d", got, want)
+	}
+}
+
+func TestServer_MaxEncoderHeaderTableSize(t *testing.T) {
+	wantHeaderTableSize := uint32(initialHeaderTableSize / 2)
+	st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) {
+		s.MaxEncoderHeaderTableSize = wantHeaderTableSize
+	})
+	defer st.Close()
+
+	st.greet()
+
+	if got, want := st.sc.hpackEncoder.MaxDynamicTableSize(), wantHeaderTableSize; got != want {
+		t.Errorf("server encoder is using a header table size of %d, want %d", got, want)
+	}
+}
+
 // Issue 12843
 func TestServerDoS_MaxHeaderListSize(t *testing.T) {
 	st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {})
diff --git a/http2/transport.go b/http2/transport.go
index 46dda4d..91f4370 100644
--- a/http2/transport.go
+++ b/http2/transport.go
@@ -118,6 +118,19 @@
 	// to mean no limit.
 	MaxHeaderListSize uint32
 
+	// MaxDecoderHeaderTableSize optionally specifies the http2
+	// SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
+	// informs the remote endpoint of the maximum size of the header compression
+	// table used to decode header blocks, in octets. If zero, the default value
+	// of 4096 is used.
+	MaxDecoderHeaderTableSize uint32
+
+	// MaxEncoderHeaderTableSize optionally specifies an upper limit for the
+	// header compression table used for encoding request headers. Received
+	// SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
+	// the default value of 4096 is used.
+	MaxEncoderHeaderTableSize uint32
+
 	// StrictMaxConcurrentStreams controls whether the server's
 	// SETTINGS_MAX_CONCURRENT_STREAMS should be respected
 	// globally. If false, new TCP connections are created to the
@@ -293,10 +306,11 @@
 	lastActive      time.Time
 	lastIdle        time.Time // time last idle
 	// Settings from peer: (also guarded by wmu)
-	maxFrameSize          uint32
-	maxConcurrentStreams  uint32
-	peerMaxHeaderListSize uint64
-	initialWindowSize     uint32
+	maxFrameSize           uint32
+	maxConcurrentStreams   uint32
+	peerMaxHeaderListSize  uint64
+	peerMaxHeaderTableSize uint32
+	initialWindowSize      uint32
 
 	// reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
 	// Write to reqHeaderMu to lock it, read from it to unlock.
@@ -681,6 +695,20 @@
 	return t.t1.ExpectContinueTimeout
 }
 
+func (t *Transport) maxDecoderHeaderTableSize() uint32 {
+	if v := t.MaxDecoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
+func (t *Transport) maxEncoderHeaderTableSize() uint32 {
+	if v := t.MaxEncoderHeaderTableSize; v > 0 {
+		return v
+	}
+	return initialHeaderTableSize
+}
+
 func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
 	return t.newClientConn(c, t.disableKeepAlives())
 }
@@ -724,12 +752,13 @@
 	if t.CountError != nil {
 		cc.fr.countError = t.CountError
 	}
-	cc.fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
+	maxHeaderTableSize := t.maxDecoderHeaderTableSize()
+	cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil)
 	cc.fr.MaxHeaderListSize = t.maxHeaderListSize()
 
-	// TODO: SetMaxDynamicTableSize, SetMaxDynamicTableSizeLimit on
-	// henc in response to SETTINGS frames?
 	cc.henc = hpack.NewEncoder(&cc.hbuf)
+	cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize())
+	cc.peerMaxHeaderTableSize = initialHeaderTableSize
 
 	if t.AllowHTTP {
 		cc.nextStreamID = 3
@@ -747,6 +776,9 @@
 	if max := t.maxHeaderListSize(); max != 0 {
 		initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max})
 	}
+	if maxHeaderTableSize != initialHeaderTableSize {
+		initialSettings = append(initialSettings, Setting{ID: SettingHeaderTableSize, Val: maxHeaderTableSize})
+	}
 
 	cc.bw.Write(clientPreface)
 	cc.fr.WriteSettings(initialSettings...)
@@ -2773,8 +2805,10 @@
 			cc.cond.Broadcast()
 
 			cc.initialWindowSize = s.Val
+		case SettingHeaderTableSize:
+			cc.henc.SetMaxDynamicTableSize(s.Val)
+			cc.peerMaxHeaderTableSize = s.Val
 		default:
-			// TODO(bradfitz): handle more settings? SETTINGS_HEADER_TABLE_SIZE probably.
 			cc.vlogf("Unhandled Setting: %v", s)
 		}
 		return nil
diff --git a/http2/transport_test.go b/http2/transport_test.go
index 9eaf7bf..ee852b6 100644
--- a/http2/transport_test.go
+++ b/http2/transport_test.go
@@ -4223,6 +4223,150 @@
 	ct.run()
 }
 
+func TestTransportMaxDecoderHeaderTableSize(t *testing.T) {
+	ct := newClientTester(t)
+	var reqSize, resSize uint32 = 8192, 16384
+	ct.tr.MaxDecoderHeaderTableSize = reqSize
+	ct.client = func() error {
+		req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
+		cc, err := ct.tr.NewClientConn(ct.cc)
+		if err != nil {
+			return err
+		}
+		_, err = cc.RoundTrip(req)
+		if err != nil {
+			return err
+		}
+		if got, want := cc.peerMaxHeaderTableSize, resSize; got != want {
+			return fmt.Errorf("peerHeaderTableSize = %d, want %d", got, want)
+		}
+		return nil
+	}
+	ct.server = func() error {
+		buf := make([]byte, len(ClientPreface))
+		_, err := io.ReadFull(ct.sc, buf)
+		if err != nil {
+			return fmt.Errorf("reading client preface: %v", err)
+		}
+		f, err := ct.fr.ReadFrame()
+		if err != nil {
+			return err
+		}
+		sf, ok := f.(*SettingsFrame)
+		if !ok {
+			ct.t.Fatalf("wanted client settings frame; got %v", f)
+			_ = sf // stash it away?
+		}
+		var found bool
+		err = sf.ForeachSetting(func(s Setting) error {
+			if s.ID == SettingHeaderTableSize {
+				found = true
+				if got, want := s.Val, reqSize; got != want {
+					return fmt.Errorf("received SETTINGS_HEADER_TABLE_SIZE = %d, want %d", got, want)
+				}
+			}
+			return nil
+		})
+		if err != nil {
+			return err
+		}
+		if !found {
+			return fmt.Errorf("missing SETTINGS_HEADER_TABLE_SIZE setting")
+		}
+		if err := ct.fr.WriteSettings(Setting{SettingHeaderTableSize, resSize}); err != nil {
+			ct.t.Fatal(err)
+		}
+		if err := ct.fr.WriteSettingsAck(); err != nil {
+			ct.t.Fatal(err)
+		}
+
+		for {
+			f, err := ct.fr.ReadFrame()
+			if err != nil {
+				return err
+			}
+			switch f := f.(type) {
+			case *HeadersFrame:
+				var buf bytes.Buffer
+				enc := hpack.NewEncoder(&buf)
+				enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
+				ct.fr.WriteHeaders(HeadersFrameParam{
+					StreamID:      f.StreamID,
+					EndHeaders:    true,
+					EndStream:     true,
+					BlockFragment: buf.Bytes(),
+				})
+				return nil
+			}
+		}
+	}
+	ct.run()
+}
+
+func TestTransportMaxEncoderHeaderTableSize(t *testing.T) {
+	ct := newClientTester(t)
+	var peerAdvertisedMaxHeaderTableSize uint32 = 16384
+	ct.tr.MaxEncoderHeaderTableSize = 8192
+	ct.client = func() error {
+		req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
+		cc, err := ct.tr.NewClientConn(ct.cc)
+		if err != nil {
+			return err
+		}
+		_, err = cc.RoundTrip(req)
+		if err != nil {
+			return err
+		}
+		if got, want := cc.henc.MaxDynamicTableSize(), ct.tr.MaxEncoderHeaderTableSize; got != want {
+			return fmt.Errorf("henc.MaxDynamicTableSize() = %d, want %d", got, want)
+		}
+		return nil
+	}
+	ct.server = func() error {
+		buf := make([]byte, len(ClientPreface))
+		_, err := io.ReadFull(ct.sc, buf)
+		if err != nil {
+			return fmt.Errorf("reading client preface: %v", err)
+		}
+		f, err := ct.fr.ReadFrame()
+		if err != nil {
+			return err
+		}
+		sf, ok := f.(*SettingsFrame)
+		if !ok {
+			ct.t.Fatalf("wanted client settings frame; got %v", f)
+			_ = sf // stash it away?
+		}
+		if err := ct.fr.WriteSettings(Setting{SettingHeaderTableSize, peerAdvertisedMaxHeaderTableSize}); err != nil {
+			ct.t.Fatal(err)
+		}
+		if err := ct.fr.WriteSettingsAck(); err != nil {
+			ct.t.Fatal(err)
+		}
+
+		for {
+			f, err := ct.fr.ReadFrame()
+			if err != nil {
+				return err
+			}
+			switch f := f.(type) {
+			case *HeadersFrame:
+				var buf bytes.Buffer
+				enc := hpack.NewEncoder(&buf)
+				enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
+				ct.fr.WriteHeaders(HeadersFrameParam{
+					StreamID:      f.StreamID,
+					EndHeaders:    true,
+					EndStream:     true,
+					BlockFragment: buf.Bytes(),
+				})
+				return nil
+			}
+		}
+	}
+	ct.run()
+}
+
 func TestAuthorityAddr(t *testing.T) {
 	tests := []struct {
 		scheme, authority string