forked from quic-go/quic-go
http3: send SETTINGS_MAX_FIELD_SECTION_SIZE in the SETTINGS frame (#5431)
* http3/qlog: implement qlogging of SETTINGS_MAX_FIELD_SECTION_SIZE * http3: send SETTINGS_MAX_FIELD_SECTION_SIZE in the SETTINGS frame
This commit is contained in:
@@ -137,10 +137,15 @@ func (c *ClientConn) setupConn() error {
|
||||
b := make([]byte, 0, 64)
|
||||
b = quicvarint.Append(b, streamTypeControlStream)
|
||||
// send the SETTINGS frame
|
||||
b = (&settingsFrame{Datagram: c.enableDatagrams, Other: c.additionalSettings}).Append(b)
|
||||
b = (&settingsFrame{
|
||||
Datagram: c.enableDatagrams,
|
||||
Other: c.additionalSettings,
|
||||
MaxFieldSectionSize: int64(c.maxResponseHeaderBytes),
|
||||
}).Append(b)
|
||||
if c.conn.qlogger != nil {
|
||||
sf := qlog.SettingsFrame{
|
||||
Other: maps.Clone(c.additionalSettings),
|
||||
MaxFieldSectionSize: int64(c.maxResponseHeaderBytes),
|
||||
Other: maps.Clone(c.additionalSettings),
|
||||
}
|
||||
if c.enableDatagrams {
|
||||
sf.Datagram = pointer(true)
|
||||
|
||||
@@ -67,8 +67,14 @@ func testClientSettings(t *testing.T, enableDatagrams bool, other map[uint64]uin
|
||||
[]qlogwriter.Event{
|
||||
qlog.FrameCreated{
|
||||
StreamID: str.StreamID(),
|
||||
Raw: qlog.RawInfo{Length: 5},
|
||||
Frame: qlog.Frame{Frame: qlog.SettingsFrame{Datagram: datagramValue, Other: other}},
|
||||
Raw: qlog.RawInfo{Length: 10},
|
||||
Frame: qlog.Frame{
|
||||
Frame: qlog.SettingsFrame{
|
||||
MaxFieldSectionSize: defaultMaxResponseHeaderBytes,
|
||||
Datagram: datagramValue,
|
||||
Other: other,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
filterQlogEventsForFrame(eventRecorder.Events(qlog.FrameCreated{}), qlog.SettingsFrame{}),
|
||||
|
||||
@@ -30,9 +30,10 @@ func TestConnReceiveSettings(t *testing.T) {
|
||||
)
|
||||
b := quicvarint.Append(nil, streamTypeControlStream)
|
||||
sf := &settingsFrame{
|
||||
Datagram: true,
|
||||
ExtendedConnect: true,
|
||||
Other: map[uint64]uint64{1337: 42},
|
||||
MaxFieldSectionSize: 1234,
|
||||
Datagram: true,
|
||||
ExtendedConnect: true,
|
||||
Other: map[uint64]uint64{1337: 42},
|
||||
}
|
||||
b = sf.Append(b)
|
||||
controlStr, err := clientConn.OpenUniStream()
|
||||
@@ -61,7 +62,14 @@ func TestConnReceiveSettings(t *testing.T) {
|
||||
qlog.FrameParsed{
|
||||
StreamID: controlStr.StreamID(),
|
||||
Raw: qlog.RawInfo{Length: expectedLen, PayloadLength: expectedPayloadLen},
|
||||
Frame: qlog.Frame{Frame: qlog.SettingsFrame{Datagram: pointer(true), ExtendedConnect: pointer(true), Other: map[uint64]uint64{1337: 42}}},
|
||||
Frame: qlog.Frame{
|
||||
Frame: qlog.SettingsFrame{
|
||||
MaxFieldSectionSize: 1234,
|
||||
Datagram: pointer(true),
|
||||
ExtendedConnect: pointer(true),
|
||||
Other: map[uint64]uint64{1337: 42},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
filterQlogEventsForFrame(eventRecorder.Events(qlog.FrameParsed{}), qlog.SettingsFrame{}),
|
||||
|
||||
@@ -179,6 +179,8 @@ func (f *headersFrame) Append(b []byte) []byte {
|
||||
}
|
||||
|
||||
const (
|
||||
// SETTINGS_MAX_FIELD_SECTION_SIZE
|
||||
settingMaxFieldSectionSize = 0x6
|
||||
// Extended CONNECT, RFC 9220
|
||||
settingExtendedConnect = 0x8
|
||||
// HTTP Datagrams, RFC 9297
|
||||
@@ -186,10 +188,11 @@ const (
|
||||
)
|
||||
|
||||
type settingsFrame struct {
|
||||
Datagram bool // HTTP Datagrams, RFC 9297
|
||||
ExtendedConnect bool // Extended CONNECT, RFC 9220
|
||||
MaxFieldSectionSize int64 // SETTINGS_MAX_FIELD_SECTION_SIZE, -1 if not set
|
||||
|
||||
Other map[uint64]uint64 // all settings that we don't explicitly recognize
|
||||
Datagram bool // HTTP Datagrams, RFC 9297
|
||||
ExtendedConnect bool // Extended CONNECT, RFC 9220
|
||||
Other map[uint64]uint64 // all settings that we don't explicitly recognize
|
||||
}
|
||||
|
||||
func pointer[T any](v T) *T {
|
||||
@@ -207,10 +210,10 @@ func parseSettingsFrame(r *countingByteReader, l uint64, streamID quic.StreamID,
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
frame := &settingsFrame{}
|
||||
frame := &settingsFrame{MaxFieldSectionSize: -1}
|
||||
b := bytes.NewReader(buf)
|
||||
var settingsFrame qlog.SettingsFrame
|
||||
var readDatagram, readExtendedConnect bool
|
||||
settingsFrame := qlog.SettingsFrame{MaxFieldSectionSize: -1}
|
||||
var readMaxFieldSectionSize, readDatagram, readExtendedConnect bool
|
||||
for b.Len() > 0 {
|
||||
id, err := quicvarint.Read(b)
|
||||
if err != nil { // should not happen. We allocated the whole frame already.
|
||||
@@ -222,6 +225,13 @@ func parseSettingsFrame(r *countingByteReader, l uint64, streamID quic.StreamID,
|
||||
}
|
||||
|
||||
switch id {
|
||||
case settingMaxFieldSectionSize:
|
||||
if readMaxFieldSectionSize {
|
||||
return nil, fmt.Errorf("duplicate setting: %d", id)
|
||||
}
|
||||
readMaxFieldSectionSize = true
|
||||
frame.MaxFieldSectionSize = int64(val)
|
||||
settingsFrame.MaxFieldSectionSize = int64(val)
|
||||
case settingExtendedConnect:
|
||||
if readExtendedConnect {
|
||||
return nil, fmt.Errorf("duplicate setting: %d", id)
|
||||
@@ -274,6 +284,9 @@ func parseSettingsFrame(r *countingByteReader, l uint64, streamID quic.StreamID,
|
||||
func (f *settingsFrame) Append(b []byte) []byte {
|
||||
b = quicvarint.Append(b, 0x4)
|
||||
var l int
|
||||
if f.MaxFieldSectionSize >= 0 {
|
||||
l += quicvarint.Len(settingMaxFieldSectionSize) + quicvarint.Len(uint64(f.MaxFieldSectionSize))
|
||||
}
|
||||
for id, val := range f.Other {
|
||||
l += quicvarint.Len(id) + quicvarint.Len(val)
|
||||
}
|
||||
@@ -284,6 +297,10 @@ func (f *settingsFrame) Append(b []byte) []byte {
|
||||
l += quicvarint.Len(settingExtendedConnect) + quicvarint.Len(1)
|
||||
}
|
||||
b = quicvarint.Append(b, uint64(l))
|
||||
if f.MaxFieldSectionSize >= 0 {
|
||||
b = quicvarint.Append(b, settingMaxFieldSectionSize)
|
||||
b = quicvarint.Append(b, uint64(f.MaxFieldSectionSize))
|
||||
}
|
||||
if f.Datagram {
|
||||
b = quicvarint.Append(b, settingDatagram)
|
||||
b = quicvarint.Append(b, 1)
|
||||
|
||||
@@ -244,6 +244,11 @@ func TestParserSettingsFrameDuplicateSettings(t *testing.T) {
|
||||
num: settingExtendedConnect,
|
||||
val: 1,
|
||||
},
|
||||
{
|
||||
name: "max field section size",
|
||||
num: settingMaxFieldSectionSize,
|
||||
val: 1337,
|
||||
},
|
||||
{
|
||||
name: "datagram",
|
||||
num: settingDatagram,
|
||||
@@ -264,6 +269,42 @@ func TestParserSettingsFrameDuplicateSettings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserSettingsFrameMaxFieldSectionSize(t *testing.T) {
|
||||
t.Run("absent", func(t *testing.T) {
|
||||
testParserSettingsFrameMaxFieldSectionSize(t, false)
|
||||
})
|
||||
|
||||
t.Run("with value", func(t *testing.T) {
|
||||
testParserSettingsFrameMaxFieldSectionSize(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
func testParserSettingsFrameMaxFieldSectionSize(t *testing.T, present bool) {
|
||||
var settings []byte
|
||||
if present {
|
||||
settings = appendSetting(nil, settingMaxFieldSectionSize, 1337)
|
||||
}
|
||||
data := quicvarint.Append(nil, 4) // type byte
|
||||
data = quicvarint.Append(data, uint64(len(settings)))
|
||||
data = append(data, settings...)
|
||||
|
||||
fp := frameParser{r: bytes.NewReader(data)}
|
||||
f, err := fp.ParseNext(nil)
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, &settingsFrame{}, f)
|
||||
sf := f.(*settingsFrame)
|
||||
if present {
|
||||
require.EqualValues(t, 1337, sf.MaxFieldSectionSize)
|
||||
} else {
|
||||
require.EqualValues(t, -1, sf.MaxFieldSectionSize)
|
||||
}
|
||||
|
||||
fp = frameParser{r: bytes.NewReader(sf.Append(nil))}
|
||||
f2, err := fp.ParseNext(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, sf, f2)
|
||||
}
|
||||
|
||||
func TestParserSettingsFrameDatagram(t *testing.T) {
|
||||
t.Run("enabled", func(t *testing.T) {
|
||||
testParserSettingsFrameDatagram(t, true)
|
||||
|
||||
@@ -97,9 +97,10 @@ func (f *GoAwayFrame) encode(enc *jsontext.Encoder) error {
|
||||
}
|
||||
|
||||
type SettingsFrame struct {
|
||||
Datagram *bool
|
||||
ExtendedConnect *bool
|
||||
Other map[uint64]uint64
|
||||
MaxFieldSectionSize int64
|
||||
Datagram *bool
|
||||
ExtendedConnect *bool
|
||||
Other map[uint64]uint64
|
||||
}
|
||||
|
||||
func (f *SettingsFrame) encode(enc *jsontext.Encoder) error {
|
||||
@@ -109,6 +110,14 @@ func (f *SettingsFrame) encode(enc *jsontext.Encoder) error {
|
||||
h.WriteToken(jsontext.String("settings"))
|
||||
h.WriteToken(jsontext.String("settings"))
|
||||
h.WriteToken(jsontext.BeginArray)
|
||||
if f.MaxFieldSectionSize >= 0 {
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("name"))
|
||||
h.WriteToken(jsontext.String("settings_max_field_section_size"))
|
||||
h.WriteToken(jsontext.String("value"))
|
||||
h.WriteToken(jsontext.Uint(uint64(f.MaxFieldSectionSize)))
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
}
|
||||
if f.Datagram != nil {
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("name"))
|
||||
|
||||
@@ -91,8 +91,11 @@ func TestSettingsFrame(t *testing.T) {
|
||||
expected map[string]any
|
||||
}{
|
||||
{
|
||||
name: "datagram: true",
|
||||
frame: SettingsFrame{Datagram: pointer(true)},
|
||||
name: "datagram: true",
|
||||
frame: SettingsFrame{
|
||||
MaxFieldSectionSize: -1,
|
||||
Datagram: pointer(true),
|
||||
},
|
||||
expected: map[string]any{
|
||||
"frame_type": "settings",
|
||||
"settings": []map[string]any{{
|
||||
@@ -102,8 +105,11 @@ func TestSettingsFrame(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extended_connect: false",
|
||||
frame: SettingsFrame{ExtendedConnect: pointer(false)},
|
||||
name: "extended_connect: false",
|
||||
frame: SettingsFrame{
|
||||
MaxFieldSectionSize: -1,
|
||||
ExtendedConnect: pointer(false),
|
||||
},
|
||||
expected: map[string]any{
|
||||
"frame_type": "settings",
|
||||
"settings": []map[string]any{{
|
||||
@@ -113,8 +119,23 @@ func TestSettingsFrame(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datagram: false, extended_connect: false",
|
||||
frame: SettingsFrame{Datagram: pointer(false), ExtendedConnect: pointer(false)},
|
||||
name: "max_field_section_size",
|
||||
frame: SettingsFrame{MaxFieldSectionSize: 1337},
|
||||
expected: map[string]any{
|
||||
"frame_type": "settings",
|
||||
"settings": []map[string]any{{
|
||||
"name": "settings_max_field_section_size",
|
||||
"value": float64(1337),
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "datagram: false, extended_connect: false",
|
||||
frame: SettingsFrame{
|
||||
MaxFieldSectionSize: -1,
|
||||
Datagram: pointer(false),
|
||||
ExtendedConnect: pointer(false),
|
||||
},
|
||||
expected: map[string]any{
|
||||
"frame_type": "settings",
|
||||
"settings": []map[string]any{
|
||||
@@ -128,7 +149,10 @@ func TestSettingsFrame(t *testing.T) {
|
||||
// Only test a single unknown setting.
|
||||
// Testing multiple unknown settings doesn't add a lot of value,
|
||||
// and would require us to deal with non-deterministic map iteration order.
|
||||
frame: SettingsFrame{Other: map[uint64]uint64{0xdead: 0xbeef}},
|
||||
frame: SettingsFrame{
|
||||
MaxFieldSectionSize: -1,
|
||||
Other: map[uint64]uint64{0xdead: 0xbeef},
|
||||
},
|
||||
expected: map[string]any{
|
||||
"frame_type": "settings",
|
||||
"settings": []map[string]any{{
|
||||
|
||||
@@ -454,14 +454,16 @@ func (s *Server) handleConn(conn *quic.Conn) error {
|
||||
b := make([]byte, 0, 64)
|
||||
b = quicvarint.Append(b, streamTypeControlStream) // stream type
|
||||
b = (&settingsFrame{
|
||||
Datagram: s.EnableDatagrams,
|
||||
ExtendedConnect: true,
|
||||
Other: s.AdditionalSettings,
|
||||
MaxFieldSectionSize: int64(s.maxHeaderBytes()),
|
||||
Datagram: s.EnableDatagrams,
|
||||
ExtendedConnect: true,
|
||||
Other: s.AdditionalSettings,
|
||||
}).Append(b)
|
||||
if qlogger != nil {
|
||||
sf := qlog.SettingsFrame{
|
||||
ExtendedConnect: pointer(true),
|
||||
Other: maps.Clone(s.AdditionalSettings),
|
||||
MaxFieldSectionSize: int64(s.maxHeaderBytes()),
|
||||
ExtendedConnect: pointer(true),
|
||||
Other: maps.Clone(s.AdditionalSettings),
|
||||
}
|
||||
if s.EnableDatagrams {
|
||||
sf.Datagram = pointer(true)
|
||||
|
||||
Reference in New Issue
Block a user