diff --git a/integrationtests/self/stateless_reset_test.go b/integrationtests/self/stateless_reset_test.go index 34cf5236..4521e96e 100644 --- a/integrationtests/self/stateless_reset_test.go +++ b/integrationtests/self/stateless_reset_test.go @@ -98,7 +98,8 @@ var _ = Describe("Stateless Resets", func() { if serr == nil { _, serr = str.Read([]byte{0}) } - Expect(serr).To(MatchError("INTERNAL_ERROR: received a stateless reset")) + Expect(serr).To(HaveOccurred()) + Expect(serr.Error()).To(ContainSubstring("INTERNAL_ERROR: received a stateless reset")) Expect(ln2.Close()).To(Succeed()) Eventually(acceptStopped).Should(BeClosed()) diff --git a/packet_handler_map.go b/packet_handler_map.go index cb907a2d..1daec42b 100644 --- a/packet_handler_map.go +++ b/packet_handler_map.go @@ -4,7 +4,7 @@ import ( "crypto/hmac" "crypto/rand" "crypto/sha256" - "errors" + "fmt" "hash" "net" "sync" @@ -15,6 +15,16 @@ import ( "github.com/lucas-clemente/quic-go/internal/wire" ) +type statelessResetErr struct { + token *[16]byte +} + +func (e statelessResetErr) StatelessResetToken() *[16]byte { return e.token } + +func (e statelessResetErr) Error() string { + return fmt.Sprintf("received a stateless reset with token %x", *e.token) +} + // The packetHandlerMap stores packetHandlers, identified by connection ID. // It is used: // * by the server to store sessions @@ -285,7 +295,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(errors.New("received a stateless reset")) + go sess.destroy(&statelessResetErr{token: &token}) return true } return false diff --git a/packet_handler_map_test.go b/packet_handler_map_test.go index 05b985fc..4580747e 100644 --- a/packet_handler_map_test.go +++ b/packet_handler_map_test.go @@ -233,7 +233,11 @@ var _ = Describe("Packet Handler Map", func() { packet := append([]byte{0x40} /* short header packet */, make([]byte, 50)...) packet = append(packet, token[:]...) destroyed := make(chan struct{}) - packetHandler.EXPECT().destroy(errors.New("received a stateless reset")).Do(func(error) { + packetHandler.EXPECT().destroy(gomock.Any()).Do(func(err error) { + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(&statelessResetErr{})) + Expect(err.Error()).To(ContainSubstring("received a stateless reset")) + Expect(*err.(*statelessResetErr).StatelessResetToken()).To(Equal(token)) close(destroyed) }) conn.dataToRead <- packet @@ -248,7 +252,11 @@ var _ = Describe("Packet Handler Map", func() { packet := append([]byte{0x40} /* short header packet */, make([]byte, 50)...) packet = append(packet, token[:]...) destroyed := make(chan struct{}) - packetHandler.EXPECT().destroy(errors.New("received a stateless reset")).Do(func(error) { + packetHandler.EXPECT().destroy(gomock.Any()).Do(func(err error) { + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(&statelessResetErr{})) + Expect(err.Error()).To(ContainSubstring("received a stateless reset")) + Expect(*err.(*statelessResetErr).StatelessResetToken()).To(Equal(token)) close(destroyed) }) conn.dataToRead <- packet diff --git a/qlog/event.go b/qlog/event.go index 78b867c8..97a7d78c 100644 --- a/qlog/event.go +++ b/qlog/event.go @@ -138,6 +138,19 @@ func (e eventRetryReceived) MarshalJSONObject(enc *gojay.Encoder) { enc.ObjectKey("header", e.Header) } +type eventStatelessResetReceived struct { + Token *[16]byte +} + +func (e eventStatelessResetReceived) Category() category { return categoryTransport } +func (e eventStatelessResetReceived) Name() string { return "packet_received" } +func (e eventStatelessResetReceived) IsNil() bool { return false } + +func (e eventStatelessResetReceived) MarshalJSONObject(enc *gojay.Encoder) { + enc.StringKey("packet_type", PacketTypeStatelessReset.String()) + enc.StringKey("stateless_reset_token", fmt.Sprintf("%x", *e.Token)) +} + type eventPacketBuffered struct { PacketType PacketType } diff --git a/qlog/qlog.go b/qlog/qlog.go index 947369c9..7c3e2217 100644 --- a/qlog/qlog.go +++ b/qlog/qlog.go @@ -25,6 +25,7 @@ type Tracer interface { SentPacket(hdr *wire.ExtendedHeader, packetSize protocol.ByteCount, ack *wire.AckFrame, frames []wire.Frame) ReceivedRetry(*wire.Header) ReceivedPacket(hdr *wire.ExtendedHeader, packetSize protocol.ByteCount, frames []wire.Frame) + ReceivedStatelessReset(token *[16]byte) BufferedPacket(PacketType) DroppedPacket(PacketType, protocol.ByteCount, PacketDropReason) UpdatedMetrics(rttStats *congestion.RTTStats, cwnd protocol.ByteCount, bytesInFLight protocol.ByteCount, packetsInFlight int) @@ -205,6 +206,12 @@ func (t *tracer) ReceivedRetry(hdr *wire.Header) { }) } +func (t *tracer) ReceivedStatelessReset(token *[16]byte) { + t.recordEvent(&eventStatelessResetReceived{ + Token: token, + }) +} + func (t *tracer) BufferedPacket(packetType PacketType) { t.recordEvent(&eventPacketBuffered{PacketType: packetType}) } diff --git a/qlog/qlog_test.go b/qlog/qlog_test.go index bf98f8ea..72091f11 100644 --- a/qlog/qlog_test.go +++ b/qlog/qlog_test.go @@ -309,6 +309,17 @@ var _ = Describe("Tracer", func() { Expect(ev).ToNot(HaveKey("frames")) }) + It("records a received Retry packet", func() { + tracer.ReceivedStatelessReset(&[16]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}) + entry := exportAndParseSingle() + Expect(entry.Time).To(BeTemporally("~", time.Now(), 10*time.Millisecond)) + Expect(entry.Category).To(Equal("transport")) + Expect(entry.Name).To(Equal("packet_received")) + ev := entry.Event + Expect(ev).To(HaveKeyWithValue("packet_type", "stateless_reset")) + Expect(ev).To(HaveKeyWithValue("stateless_reset_token", "00112233445566778899aabbccddeeff")) + }) + It("records buffered packets", func() { tracer.BufferedPacket(PacketTypeHandshake) entry := exportAndParseSingle() diff --git a/qlog/types.go b/qlog/types.go index c3557e6c..baffaedd 100644 --- a/qlog/types.go +++ b/qlog/types.go @@ -90,6 +90,8 @@ const ( PacketTypeVersionNegotiation // PacketType1RTT is a 1-RTT packet PacketType1RTT + // PacketTypeStatelessReset is a stateless reset + PacketTypeStatelessReset // PacketTypeNotDetermined is the packet type when it could not be determined PacketTypeNotDetermined ) @@ -106,6 +108,8 @@ func (t PacketType) String() string { return "0RTT" case PacketTypeVersionNegotiation: return "version_negotiation" + case PacketTypeStatelessReset: + return "stateless_reset" case PacketType1RTT: return "1RTT" case PacketTypeNotDetermined: diff --git a/qlog/types_test.go b/qlog/types_test.go index 1f13d5a2..9d0b9260 100644 --- a/qlog/types_test.go +++ b/qlog/types_test.go @@ -24,6 +24,7 @@ var _ = Describe("Types", func() { Expect(PacketTypeHandshake.String()).To(Equal("handshake")) Expect(PacketType0RTT.String()).To(Equal("0RTT")) Expect(PacketType1RTT.String()).To(Equal("1RTT")) + Expect(PacketTypeStatelessReset.String()).To(Equal("stateless_reset")) Expect(PacketTypeRetry.String()).To(Equal("retry")) Expect(PacketTypeVersionNegotiation.String()).To(Equal("version_negotiation")) Expect(PacketTypeNotDetermined.String()).To(BeEmpty()) diff --git a/session.go b/session.go index ec1842f2..c6d1faf9 100644 --- a/session.go +++ b/session.go @@ -1149,6 +1149,9 @@ func (s *session) handleCloseError(closeErr closeError) { if closeErr.err == nil { closeErr.err = qerr.NewApplicationError(0, "") } + if statelessReset, ok := closeErr.err.(interface{ StatelessResetToken() *[16]byte }); ok && s.qlogger != nil { + s.qlogger.ReceivedStatelessReset(statelessReset.StatelessResetToken()) + } var quicErr *qerr.QuicError var ok bool