diff --git a/http3/client.go b/http3/client.go index b8378c6f..d408fa66 100644 --- a/http3/client.go +++ b/http3/client.go @@ -167,8 +167,19 @@ func (c *client) handleUnidirectionalStreams() { c.session.CloseWithError(quic.ErrorCode(errorFrameError), "") return } - if _, ok := f.(*settingsFrame); !ok { + sf, ok := f.(*settingsFrame) + if !ok { c.session.CloseWithError(quic.ErrorCode(errorMissingSettings), "") + return + } + if !sf.Datagram { + return + } + // If datagram support was enabled on our side as well as on the server side, + // we can expect it to have been negotiated both on the transport and on the HTTP/3 layer. + // Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT). + if c.opts.EnableDatagram && !c.session.ConnectionState().SupportsDatagrams { + c.session.CloseWithError(quic.ErrorCode(errorSettingsError), "missing QUIC Datagram support") } }() } diff --git a/http3/client_test.go b/http3/client_test.go index 5d5733e2..655fad97 100644 --- a/http3/client_test.go +++ b/http3/client_test.go @@ -328,6 +328,33 @@ var _ = Describe("Client", func() { Expect(err).To(MatchError("done")) Eventually(done).Should(BeClosed()) }) + + It("errors when the server advertises datagram support (and we enabled support for it)", func() { + client.opts.EnableDatagram = true + buf := &bytes.Buffer{} + utils.WriteVarInt(buf, streamTypeControlStream) + (&settingsFrame{Datagram: true}).Write(buf) + controlStr := mockquic.NewMockStream(mockCtrl) + controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes() + sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) { + return controlStr, nil + }) + sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) { + <-testDone + return nil, errors.New("test done") + }) + sess.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false}) + done := make(chan struct{}) + sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, reason string) { + defer GinkgoRecover() + Expect(code).To(BeEquivalentTo(errorSettingsError)) + Expect(reason).To(Equal("missing QUIC Datagram support")) + close(done) + }) + _, err := client.RoundTrip(request) + Expect(err).To(MatchError("done")) + Eventually(done).Should(BeClosed()) + }) }) Context("Doing requests", func() { diff --git a/http3/server.go b/http3/server.go index bd30b2dc..dd4dcf29 100644 --- a/http3/server.go +++ b/http3/server.go @@ -301,8 +301,19 @@ func (s *Server) handleUnidirectionalStreams(sess quic.EarlySession) { sess.CloseWithError(quic.ErrorCode(errorFrameError), "") return } - if _, ok := f.(*settingsFrame); !ok { + sf, ok := f.(*settingsFrame) + if !ok { sess.CloseWithError(quic.ErrorCode(errorMissingSettings), "") + return + } + if !sf.Datagram { + return + } + // If datagram support was enabled on our side as well as on the client side, + // we can expect it to have been negotiated both on the transport and on the HTTP/3 layer. + // Note: ConnectionState() will block until the handshake is complete (relevant when using 0-RTT). + if s.EnableDatagrams && !sess.ConnectionState().SupportsDatagrams { + sess.CloseWithError(quic.ErrorCode(errorSettingsError), "missing QUIC Datagram support") } }(str) } diff --git a/http3/server_test.go b/http3/server_test.go index 89fe0ecb..98596776 100644 --- a/http3/server_test.go +++ b/http3/server_test.go @@ -304,6 +304,32 @@ var _ = Describe("Server", func() { s.handleConn(sess) Eventually(done).Should(BeClosed()) }) + + It("errors when the client advertises datagram support (and we enabled support for it)", func() { + s.EnableDatagrams = true + buf := &bytes.Buffer{} + utils.WriteVarInt(buf, streamTypeControlStream) + (&settingsFrame{Datagram: true}).Write(buf) + controlStr := mockquic.NewMockStream(mockCtrl) + controlStr.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes() + sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) { + return controlStr, nil + }) + sess.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) { + <-testDone + return nil, errors.New("test done") + }) + sess.EXPECT().ConnectionState().Return(quic.ConnectionState{SupportsDatagrams: false}) + done := make(chan struct{}) + sess.EXPECT().CloseWithError(gomock.Any(), gomock.Any()).Do(func(code quic.ErrorCode, reason string) { + defer GinkgoRecover() + Expect(code).To(BeEquivalentTo(errorSettingsError)) + Expect(reason).To(Equal("missing QUIC Datagram support")) + close(done) + }) + s.handleConn(sess) + Eventually(done).Should(BeClosed()) + }) }) Context("stream- and connection-level errors", func() {