diff --git a/config.go b/config.go index 61c4097a617..bd74f58ce42 100644 --- a/config.go +++ b/config.go @@ -99,6 +99,10 @@ func populateConfig(config *Config) *Config { } else if maxIncomingUniStreams < 0 { maxIncomingUniStreams = 0 } + maxDatagrameFrameSize := config.MaxDatagramFrameSize + if maxDatagrameFrameSize == 0 { + maxDatagrameFrameSize = int64(protocol.DefaultMaxDatagramFrameSize) + } return &Config{ Versions: versions, @@ -117,6 +121,7 @@ func populateConfig(config *Config) *Config { StatelessResetKey: config.StatelessResetKey, TokenStore: config.TokenStore, EnableDatagrams: config.EnableDatagrams, + MaxDatagramFrameSize: maxDatagrameFrameSize, DisablePathMTUDiscovery: config.DisablePathMTUDiscovery, DisableVersionNegotiationPackets: config.DisableVersionNegotiationPackets, Tracer: config.Tracer, diff --git a/config_test.go b/config_test.go index a8574f8d472..ff660342d58 100644 --- a/config_test.go +++ b/config_test.go @@ -75,6 +75,8 @@ var _ = Describe("Config", func() { f.Set(reflect.ValueOf(true)) case "EnableDatagrams": f.Set(reflect.ValueOf(true)) + case "MaxDatagramFrameSize": + f.Set(reflect.ValueOf(int64(1280))) case "DisableVersionNegotiationPackets": f.Set(reflect.ValueOf(true)) case "DisablePathMTUDiscovery": diff --git a/datagram_queue.go b/datagram_queue.go index b1cbbf6dcc3..e23f79b0ee2 100644 --- a/datagram_queue.go +++ b/datagram_queue.go @@ -1,6 +1,8 @@ package quic import ( + "fmt" + "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/internal/utils" "github.com/lucas-clemente/quic-go/internal/wire" @@ -15,7 +17,7 @@ type datagramQueue struct { hasData func() - dequeued chan struct{} + dequeued chan error logger utils.Logger } @@ -25,7 +27,7 @@ func newDatagramQueue(hasData func(), logger utils.Logger) *datagramQueue { hasData: hasData, sendQueue: make(chan *wire.DatagramFrame, 1), rcvQueue: make(chan []byte, protocol.DatagramRcvQueueLen), - dequeued: make(chan struct{}), + dequeued: make(chan error), closed: make(chan struct{}), logger: logger, } @@ -42,18 +44,23 @@ func (h *datagramQueue) AddAndWait(f *wire.DatagramFrame) error { } select { - case <-h.dequeued: - return nil + case err := <-h.dequeued: + return err case <-h.closed: return h.closeErr } } // Get dequeues a DATAGRAM frame for sending. -func (h *datagramQueue) Get() *wire.DatagramFrame { +func (h *datagramQueue) Get(maxDatagramSize protocol.ByteCount, version protocol.VersionNumber) *wire.DatagramFrame { select { case f := <-h.sendQueue: - h.dequeued <- struct{}{} + datagramSize := f.Length(version) + if datagramSize > maxDatagramSize { + h.dequeued <- fmt.Errorf("datagram size %d exceed current limit of %d", datagramSize, maxDatagramSize) + return nil + } + h.dequeued <- nil return f default: return nil diff --git a/datagram_queue_test.go b/datagram_queue_test.go index 0ff7b96efa5..77ec0472493 100644 --- a/datagram_queue_test.go +++ b/datagram_queue_test.go @@ -3,6 +3,7 @@ package quic import ( "errors" + "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/internal/utils" "github.com/lucas-clemente/quic-go/internal/wire" @@ -23,7 +24,7 @@ var _ = Describe("Datagram Queue", func() { Context("sending", func() { It("returns nil when there's no datagram to send", func() { - Expect(queue.Get()).To(BeNil()) + Expect(queue.Get(protocol.DefaultMaxDatagramFrameSize, protocol.Version1)).To(BeNil()) }) It("queues a datagram", func() { @@ -36,11 +37,11 @@ var _ = Describe("Datagram Queue", func() { Eventually(queued).Should(HaveLen(1)) Consistently(done).ShouldNot(BeClosed()) - f := queue.Get() + f := queue.Get(protocol.DefaultMaxDatagramFrameSize, protocol.Version1) Expect(f).ToNot(BeNil()) Expect(f.Data).To(Equal([]byte("foobar"))) Eventually(done).Should(BeClosed()) - Expect(queue.Get()).To(BeNil()) + Expect(queue.Get(protocol.DefaultMaxDatagramFrameSize, protocol.Version1)).To(BeNil()) }) It("closes", func() { diff --git a/interface.go b/interface.go index 6381e9eddf9..6b6b195f9d4 100644 --- a/interface.go +++ b/interface.go @@ -298,8 +298,9 @@ type Config struct { DisableVersionNegotiationPackets bool // See https://datatracker.ietf.org/doc/draft-ietf-quic-datagram/. // Datagrams will only be available when both peers enable datagram support. - EnableDatagrams bool - Tracer logging.Tracer + EnableDatagrams bool + MaxDatagramFrameSize int64 + Tracer logging.Tracer } // ConnectionState records basic details about a QUIC connection diff --git a/internal/protocol/params.go b/internal/protocol/params.go index 4bc33e27461..4d3085fbf61 100644 --- a/internal/protocol/params.go +++ b/internal/protocol/params.go @@ -132,10 +132,10 @@ const MaxPostHandshakeCryptoFrameSize = 1000 // but must ensure that a maximum size ACK frame fits into one packet. const MaxAckFrameSize ByteCount = 1000 -// MaxDatagramFrameSize is the maximum size of a DATAGRAM frame as defined in +// DefaultMaxDatagramFrameSize is the maximum size of a DATAGRAM frame as defined in // https://datatracker.ietf.org/doc/draft-pauly-quic-datagram/. // The size is chosen such that a DATAGRAM frame fits into a QUIC packet. -const MaxDatagramFrameSize ByteCount = 1220 +const DefaultMaxDatagramFrameSize ByteCount = 1220 // DatagramRcvQueueLen is the length of the receive queue for DATAGRAM frames. // See https://datatracker.ietf.org/doc/draft-pauly-quic-datagram/. diff --git a/packet_packer.go b/packet_packer.go index 1d037ab28e7..efae6102527 100644 --- a/packet_packer.go +++ b/packet_packer.go @@ -596,7 +596,7 @@ func (p *packetPacker) composeNextPacket(maxFrameSize protocol.ByteCount, ackAll var hasDatagram bool if p.datagramQueue != nil { - if datagram := p.datagramQueue.Get(); datagram != nil { + if datagram := p.datagramQueue.Get(maxFrameSize, p.version); datagram != nil { payload.frames = append(payload.frames, ackhandler.Frame{ Frame: datagram, // set it to a no-op. Then we won't set the default callback, which would retransmit the frame. diff --git a/session.go b/session.go index 2568acfa47e..ceeaa71a95f 100644 --- a/session.go +++ b/session.go @@ -316,7 +316,10 @@ var newSession = func( RetrySourceConnectionID: retrySrcConnID, } if s.config.EnableDatagrams { - params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize + params.MaxDatagramFrameSize = protocol.ByteCount(s.config.MaxDatagramFrameSize) + if params.MaxDatagramFrameSize == 0 { + params.MaxDatagramFrameSize = protocol.DefaultMaxDatagramFrameSize + } } if s.tracer != nil { s.tracer.SentTransportParameters(params) @@ -440,7 +443,7 @@ var newClientSession = func( InitialSourceConnectionID: srcConnID, } if s.config.EnableDatagrams { - params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize + params.MaxDatagramFrameSize = protocol.ByteCount(s.config.MaxDatagramFrameSize) } if s.tracer != nil { s.tracer.SentTransportParameters(params) @@ -1415,7 +1418,7 @@ func (s *session) handleAckFrame(frame *wire.AckFrame, encLevel protocol.Encrypt } func (s *session) handleDatagramFrame(f *wire.DatagramFrame) error { - if f.Length(s.version) > protocol.MaxDatagramFrameSize { + if f.Length(s.version) > protocol.ByteCount(s.config.MaxDatagramFrameSize) { return &qerr.TransportError{ ErrorCode: qerr.ProtocolViolation, ErrorMessage: "DATAGRAM frame too large",