From 93cfef57ca0dcb8308209a32492eea67baa1c5e8 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 25 Apr 2021 19:10:15 +0700 Subject: [PATCH] expose a StatelessResetError --- errors.go | 1 + integrationtests/self/stateless_reset_test.go | 3 +-- internal/qerr/quic_error.go | 20 +++++++++++++++++ internal/qerr/quic_error_test.go | 22 +++++++++++++++++++ packet_handler_map.go | 15 +------------ packet_handler_map_test.go | 8 +++---- session.go | 6 ++--- session_test.go | 2 +- 8 files changed, 53 insertions(+), 24 deletions(-) diff --git a/errors.go b/errors.go index faa6bd6b..bd52a289 100644 --- a/errors.go +++ b/errors.go @@ -8,6 +8,7 @@ type ( TransportError = qerr.TransportError ApplicationError = qerr.ApplicationError VersionNegotiationError = qerr.VersionNegotiationError + StatelessResetError = qerr.StatelessResetError ) type ( diff --git a/integrationtests/self/stateless_reset_test.go b/integrationtests/self/stateless_reset_test.go index 86f3c893..790dc9bf 100644 --- a/integrationtests/self/stateless_reset_test.go +++ b/integrationtests/self/stateless_reset_test.go @@ -99,8 +99,7 @@ var _ = Describe("Stateless Resets", func() { _, serr = str.Read([]byte{0}) } Expect(serr).To(HaveOccurred()) - Expect(serr.Error()).To(ContainSubstring("received a stateless reset")) - + Expect(serr).To(MatchError(&quic.StatelessResetError{})) Expect(ln2.Close()).To(Succeed()) Eventually(acceptStopped).Should(BeClosed()) }) diff --git a/internal/qerr/quic_error.go b/internal/qerr/quic_error.go index f2739c5b..c4ad86bd 100644 --- a/internal/qerr/quic_error.go +++ b/internal/qerr/quic_error.go @@ -2,6 +2,7 @@ package qerr import ( "fmt" + "net" "github.com/lucas-clemente/quic-go/internal/protocol" ) @@ -109,3 +110,22 @@ func (e *VersionNegotiationError) Is(target error) bool { _, ok := target.(*VersionNegotiationError) return ok } + +// A StatelessResetError occurs when we receive a stateless reset. +type StatelessResetError struct { + Token protocol.StatelessResetToken +} + +var _ net.Error = &StatelessResetError{} + +func (e *StatelessResetError) Error() string { + return fmt.Sprintf("received a stateless reset with token %x", e.Token) +} + +func (e *StatelessResetError) Is(target error) bool { + _, ok := target.(*StatelessResetError) + return ok +} + +func (e *StatelessResetError) Timeout() bool { return false } +func (e *StatelessResetError) Temporary() bool { return true } diff --git a/internal/qerr/quic_error_test.go b/internal/qerr/quic_error_test.go index 48720e6f..325032f8 100644 --- a/internal/qerr/quic_error_test.go +++ b/internal/qerr/quic_error_test.go @@ -115,4 +115,26 @@ var _ = Describe("QUIC Errors", func() { }).Error()).To(Equal("no compatible QUIC version found (we support [0x2 0x3], server offered [0x4 0x5 0x6])")) }) }) + + Context("Stateless Reset errors", func() { + token := protocol.StatelessResetToken{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf} + + It("is a Stateless Reset error", func() { + Expect(errors.Is(&StatelessResetError{Token: token}, &StatelessResetError{})).To(BeTrue()) + }) + + It("has a string representation", func() { + Expect((&StatelessResetError{Token: token}).Error()).To(Equal("received a stateless reset with token 000102030405060708090a0b0c0d0e0f")) + }) + + It("is a net.Error", func() { + //nolint:gosimple // we need to assign to an interface here + var err error + err = &StatelessResetError{} + nerr, ok := err.(net.Error) + Expect(ok).To(BeTrue()) + Expect(nerr.Timeout()).To(BeFalse()) + Expect(nerr.Temporary()).To(BeTrue()) + }) + }) }) diff --git a/packet_handler_map.go b/packet_handler_map.go index 0d31bd83..5b4659d2 100644 --- a/packet_handler_map.go +++ b/packet_handler_map.go @@ -18,19 +18,6 @@ import ( "github.com/lucas-clemente/quic-go/logging" ) -type statelessResetErr struct { - token protocol.StatelessResetToken -} - -func (e *statelessResetErr) Error() string { - return fmt.Sprintf("received a stateless reset with token %x", e.token) -} - -func (e *statelessResetErr) Is(target error) bool { - _, ok := target.(*statelessResetErr) - return ok -} - type zeroRTTQueue struct { queue []*receivedPacket retireTimer *time.Timer @@ -435,7 +422,7 @@ func (h *packetHandlerMap) maybeHandleStatelessReset(data []byte) bool { copy(token[:], data[len(data)-16:]) if sess, ok := h.resetTokens[token]; ok { h.logger.Debugf("Received a stateless reset with token %#x. Closing session.", token) - go sess.destroy(&statelessResetErr{token: token}) + go sess.destroy(&StatelessResetError{Token: token}) return true } return false diff --git a/packet_handler_map_test.go b/packet_handler_map_test.go index 66882f55..48f1b91c 100644 --- a/packet_handler_map_test.go +++ b/packet_handler_map_test.go @@ -373,10 +373,10 @@ var _ = Describe("Packet Handler Map", func() { defer GinkgoRecover() defer close(destroyed) Expect(err).To(HaveOccurred()) - var resetErr *statelessResetErr + var resetErr *StatelessResetError Expect(errors.As(err, &resetErr)).To(BeTrue()) Expect(err.Error()).To(ContainSubstring("received a stateless reset")) - Expect(resetErr.token).To(Equal(token)) + Expect(resetErr.Token).To(Equal(token)) }) packetChan <- packetToRead{data: packet} Eventually(destroyed).Should(BeClosed()) @@ -393,10 +393,10 @@ var _ = Describe("Packet Handler Map", func() { packetHandler.EXPECT().destroy(gomock.Any()).Do(func(err error) { defer GinkgoRecover() Expect(err).To(HaveOccurred()) - var resetErr *statelessResetErr + var resetErr *StatelessResetError Expect(errors.As(err, &resetErr)).To(BeTrue()) Expect(err.Error()).To(ContainSubstring("received a stateless reset")) - Expect(resetErr.token).To(Equal(token)) + Expect(resetErr.Token).To(Equal(token)) close(destroyed) }) packetChan <- packetToRead{data: packet} diff --git a/session.go b/session.go index 180401ce..904396d6 100644 --- a/session.go +++ b/session.go @@ -1483,7 +1483,7 @@ func (s *session) handleCloseError(closeErr *closeError) { switch { case errors.Is(e, qerr.ErrIdleTimeout), errors.Is(e, qerr.ErrHandshakeTimeout), - errors.Is(e, &statelessResetErr{}), + errors.Is(e, &StatelessResetError{}), errors.Is(e, &VersionNegotiationError{}), errors.Is(e, &errCloseForRecreating{}), errors.Is(e, &qerr.ApplicationError{}), @@ -1503,7 +1503,7 @@ func (s *session) handleCloseError(closeErr *closeError) { if s.tracer != nil && !errors.Is(e, &errCloseForRecreating{}) { var ( - resetErr *statelessResetErr + resetErr *StatelessResetError vnErr *VersionNegotiationError transportErr *qerr.TransportError applicationErr *qerr.ApplicationError @@ -1514,7 +1514,7 @@ func (s *session) handleCloseError(closeErr *closeError) { case errors.Is(e, qerr.ErrHandshakeTimeout): s.tracer.ClosedConnection(logging.NewTimeoutCloseReason(logging.TimeoutReasonHandshake)) case errors.As(e, &resetErr): - s.tracer.ClosedConnection(logging.NewStatelessResetCloseReason(resetErr.token)) + s.tracer.ClosedConnection(logging.NewStatelessResetCloseReason(resetErr.Token)) case errors.As(e, &vnErr): s.tracer.ClosedConnection(logging.NewVersionNegotiationError(vnErr.Theirs)) case errors.As(e, &applicationErr): diff --git a/session_test.go b/session_test.go index 7000d810..ffd30fdc 100644 --- a/session_test.go +++ b/session_test.go @@ -658,7 +658,7 @@ var _ = Describe("Session", func() { streamManager.EXPECT().CloseWithError(gomock.Any()) sessionRunner.EXPECT().Remove(gomock.Any()).AnyTimes() cryptoSetup.EXPECT().Close() - sess.destroy(&statelessResetErr{token: token}) + sess.destroy(&StatelessResetError{Token: token}) }) })