diff --git a/http3/frames.go b/http3/frames.go index 454e5f94..90b22f6b 100644 --- a/http3/frames.go +++ b/http3/frames.go @@ -88,11 +88,18 @@ func (f *headersFrame) Append(b []byte) []byte { return quicvarint.Append(b, f.Length) } -const settingDatagram = 0x33 +const ( + // Extended CONNECT, RFC 9220 + settingExtendedConnect = 0x8 + // HTTP Datagrams, RFC 9297 + settingDatagram = 0x33 +) type settingsFrame struct { - Datagram bool - 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 parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) { @@ -108,7 +115,7 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) { } frame := &settingsFrame{} b := bytes.NewReader(buf) - var readDatagram bool + var readDatagram, readExtendedConnect bool for b.Len() > 0 { id, err := quicvarint.Read(b) if err != nil { // should not happen. We allocated the whole frame already. @@ -120,13 +127,22 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) { } switch id { + case settingExtendedConnect: + if readExtendedConnect { + return nil, fmt.Errorf("duplicate setting: %d", id) + } + readExtendedConnect = true + if val != 0 && val != 1 { + return nil, fmt.Errorf("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: %d", val) + } + frame.ExtendedConnect = val == 1 case settingDatagram: if readDatagram { return nil, fmt.Errorf("duplicate setting: %d", id) } readDatagram = true if val != 0 && val != 1 { - return nil, fmt.Errorf("invalid value for H3_DATAGRAM: %d", val) + return nil, fmt.Errorf("invalid value for SETTINGS_H3_DATAGRAM: %d", val) } frame.Datagram = val == 1 default: @@ -151,11 +167,18 @@ func (f *settingsFrame) Append(b []byte) []byte { if f.Datagram { l += quicvarint.Len(settingDatagram) + quicvarint.Len(1) } + if f.ExtendedConnect { + l += quicvarint.Len(settingExtendedConnect) + quicvarint.Len(1) + } b = quicvarint.Append(b, uint64(l)) if f.Datagram { b = quicvarint.Append(b, settingDatagram) b = quicvarint.Append(b, 1) } + if f.ExtendedConnect { + b = quicvarint.Append(b, settingExtendedConnect) + b = quicvarint.Append(b, 1) + } for id, val := range f.Other { b = quicvarint.Append(b, id) b = quicvarint.Append(b, val) diff --git a/http3/frames_test.go b/http3/frames_test.go index 04e583d2..fbb1d752 100644 --- a/http3/frames_test.go +++ b/http3/frames_test.go @@ -127,8 +127,8 @@ var _ = Describe("Frames", func() { } }) - Context("H3_DATAGRAM", func() { - It("reads the H3_DATAGRAM value", func() { + Context("HTTP Datagrams", func() { + It("reads the SETTINGS_H3_DATAGRAM value", func() { settings := quicvarint.Append(nil, settingDatagram) settings = quicvarint.Append(settings, 1) data := quicvarint.Append(nil, 4) // type byte @@ -141,7 +141,7 @@ var _ = Describe("Frames", func() { Expect(sf.Datagram).To(BeTrue()) }) - It("rejects duplicate H3_DATAGRAM entries", func() { + It("rejects duplicate SETTINGS_H3_DATAGRAM entries", func() { settings := quicvarint.Append(nil, settingDatagram) settings = quicvarint.Append(settings, 1) settings = quicvarint.Append(settings, settingDatagram) @@ -153,23 +153,67 @@ var _ = Describe("Frames", func() { Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingDatagram))) }) - It("rejects invalid values for the H3_DATAGRAM entry", func() { + It("rejects invalid values for the SETTINGS_H3_DATAGRAM entry", func() { settings := quicvarint.Append(nil, settingDatagram) settings = quicvarint.Append(settings, 1337) data := quicvarint.Append(nil, 4) // type byte data = quicvarint.Append(data, uint64(len(settings))) data = append(data, settings...) _, err := parseNextFrame(bytes.NewReader(data), nil) - Expect(err).To(MatchError("invalid value for H3_DATAGRAM: 1337")) + Expect(err).To(MatchError("invalid value for SETTINGS_H3_DATAGRAM: 1337")) }) - It("writes the H3_DATAGRAM setting", func() { + It("writes the SETTINGS_H3_DATAGRAM setting", func() { sf := &settingsFrame{Datagram: true} frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil) Expect(err).ToNot(HaveOccurred()) Expect(frame).To(Equal(sf)) }) }) + + Context("Extended Connect", func() { + It("reads the SETTINGS_ENABLE_CONNECT_PROTOCOL value", func() { + settings := quicvarint.Append(nil, settingExtendedConnect) + settings = quicvarint.Append(settings, 1) + data := quicvarint.Append(nil, 4) // type byte + data = quicvarint.Append(data, uint64(len(settings))) + data = append(data, settings...) + f, err := parseNextFrame(bytes.NewReader(data), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(f).To(BeAssignableToTypeOf(&settingsFrame{})) + sf := f.(*settingsFrame) + Expect(sf.ExtendedConnect).To(BeTrue()) + }) + + It("rejects duplicate SETTINGS_ENABLE_CONNECT_PROTOCOL entries", func() { + settings := quicvarint.Append(nil, settingExtendedConnect) + settings = quicvarint.Append(settings, 1) + settings = quicvarint.Append(settings, settingExtendedConnect) + settings = quicvarint.Append(settings, 1) + data := quicvarint.Append(nil, 4) // type byte + data = quicvarint.Append(data, uint64(len(settings))) + data = append(data, settings...) + _, err := parseNextFrame(bytes.NewReader(data), nil) + Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingExtendedConnect))) + }) + + It("rejects invalid values for the SETTINGS_ENABLE_CONNECT_PROTOCOL entry", func() { + settings := quicvarint.Append(nil, settingExtendedConnect) + settings = quicvarint.Append(settings, 1337) + data := quicvarint.Append(nil, 4) // type byte + data = quicvarint.Append(data, uint64(len(settings))) + data = append(data, settings...) + _, err := parseNextFrame(bytes.NewReader(data), nil) + Expect(err).To(MatchError("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: 1337")) + }) + + It("writes the SETTINGS_ENABLE_CONNECT_PROTOCOL setting", func() { + sf := &settingsFrame{ExtendedConnect: true} + frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(frame).To(Equal(sf)) + }) + }) }) Context("hijacking", func() { diff --git a/http3/server.go b/http3/server.go index 2d5437c9..c17b16b5 100644 --- a/http3/server.go +++ b/http3/server.go @@ -451,7 +451,11 @@ func (s *Server) handleConn(conn quic.Connection) error { } b := make([]byte, 0, 64) b = quicvarint.Append(b, streamTypeControlStream) // stream type - b = (&settingsFrame{Datagram: s.EnableDatagrams, Other: s.AdditionalSettings}).Append(b) + b = (&settingsFrame{ + Datagram: s.EnableDatagrams, + ExtendedConnect: true, + Other: s.AdditionalSettings, + }).Append(b) str.Write(b) go s.handleUnidirectionalStreams(conn)