diff --git a/internal/mocks/tracer.go b/internal/mocks/tracer.go index a798d695..4ed0d14d 100644 --- a/internal/mocks/tracer.go +++ b/internal/mocks/tracer.go @@ -10,6 +10,7 @@ import ( gomock "github.com/golang/mock/gomock" protocol "github.com/lucas-clemente/quic-go/internal/protocol" + wire "github.com/lucas-clemente/quic-go/internal/wire" logging "github.com/lucas-clemente/quic-go/logging" ) @@ -48,6 +49,18 @@ func (mr *MockTracerMockRecorder) DroppedPacket(arg0, arg1, arg2, arg3 interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DroppedPacket", reflect.TypeOf((*MockTracer)(nil).DroppedPacket), arg0, arg1, arg2, arg3) } +// SentPacket mocks base method +func (m *MockTracer) SentPacket(arg0 net.Addr, arg1 *wire.Header, arg2 protocol.ByteCount, arg3 []logging.Frame) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SentPacket", arg0, arg1, arg2, arg3) +} + +// SentPacket indicates an expected call of SentPacket +func (mr *MockTracerMockRecorder) SentPacket(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SentPacket", reflect.TypeOf((*MockTracer)(nil).SentPacket), arg0, arg1, arg2, arg3) +} + // TracerForConnection mocks base method func (m *MockTracer) TracerForConnection(arg0 protocol.Perspective, arg1 protocol.ConnectionID) logging.ConnectionTracer { m.ctrl.T.Helper() diff --git a/logging/interface.go b/logging/interface.go index 11adb073..d5d251c4 100644 --- a/logging/interface.go +++ b/logging/interface.go @@ -68,6 +68,8 @@ const ( Encryption1RTT EncryptionLevel = protocol.Encryption1RTT // Encryption0RTT is the 0-RTT encryption level Encryption0RTT EncryptionLevel = protocol.Encryption0RTT + // EncryptionNone is no encryption + EncryptionNone EncryptionLevel = protocol.EncryptionUnspecified ) const ( @@ -85,6 +87,7 @@ type Tracer interface { // If nil is returned, tracing will be disabled for this connection. TracerForConnection(p Perspective, odcid ConnectionID) ConnectionTracer + SentPacket(net.Addr, *Header, ByteCount, []Frame) DroppedPacket(net.Addr, PacketType, ByteCount, PacketDropReason) } diff --git a/logging/mock_tracer_test.go b/logging/mock_tracer_test.go index 4d07bc24..55881a1e 100644 --- a/logging/mock_tracer_test.go +++ b/logging/mock_tracer_test.go @@ -7,6 +7,7 @@ package logging import ( gomock "github.com/golang/mock/gomock" protocol "github.com/lucas-clemente/quic-go/internal/protocol" + wire "github.com/lucas-clemente/quic-go/internal/wire" net "net" reflect "reflect" ) @@ -46,6 +47,18 @@ func (mr *MockTracerMockRecorder) DroppedPacket(arg0, arg1, arg2, arg3 interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DroppedPacket", reflect.TypeOf((*MockTracer)(nil).DroppedPacket), arg0, arg1, arg2, arg3) } +// SentPacket mocks base method +func (m *MockTracer) SentPacket(arg0 net.Addr, arg1 *wire.Header, arg2 protocol.ByteCount, arg3 []Frame) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SentPacket", arg0, arg1, arg2, arg3) +} + +// SentPacket indicates an expected call of SentPacket +func (mr *MockTracerMockRecorder) SentPacket(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SentPacket", reflect.TypeOf((*MockTracer)(nil).SentPacket), arg0, arg1, arg2, arg3) +} + // TracerForConnection mocks base method func (m *MockTracer) TracerForConnection(arg0 protocol.Perspective, arg1 protocol.ConnectionID) ConnectionTracer { m.ctrl.T.Helper() diff --git a/logging/multiplex.go b/logging/multiplex.go index bf9d9665..c9324942 100644 --- a/logging/multiplex.go +++ b/logging/multiplex.go @@ -32,6 +32,12 @@ func (m *tracerMultiplexer) TracerForConnection(p Perspective, odcid ConnectionI return newConnectionMultiplexer(connTracers...) } +func (m *tracerMultiplexer) SentPacket(remote net.Addr, hdr *Header, size ByteCount, frames []Frame) { + for _, t := range m.tracers { + t.SentPacket(remote, hdr, size, frames) + } +} + func (m *tracerMultiplexer) DroppedPacket(remote net.Addr, typ PacketType, size ByteCount, reason PacketDropReason) { for _, t := range m.tracers { t.DroppedPacket(remote, typ, size, reason) diff --git a/logging/multiplex_test.go b/logging/multiplex_test.go index 7702640a..56e3011c 100644 --- a/logging/multiplex_test.go +++ b/logging/multiplex_test.go @@ -56,6 +56,15 @@ var _ = Describe("Tracing", func() { }) It("traces the PacketSent event", func() { + remote := &net.UDPAddr{IP: net.IPv4(4, 3, 2, 1)} + hdr := &Header{DestConnectionID: ConnectionID{1, 2, 3}} + f := &MaxDataFrame{MaximumData: 1337} + tr1.EXPECT().SentPacket(remote, hdr, ByteCount(1024), []Frame{f}) + tr2.EXPECT().SentPacket(remote, hdr, ByteCount(1024), []Frame{f}) + tracer.SentPacket(remote, hdr, 1024, []Frame{f}) + }) + + It("traces the PacketDropped event", func() { remote := &net.UDPAddr{IP: net.IPv4(4, 3, 2, 1)} tr1.EXPECT().DroppedPacket(remote, PacketTypeRetry, ByteCount(1024), PacketDropDuplicate) tr2.EXPECT().DroppedPacket(remote, PacketTypeRetry, ByteCount(1024), PacketDropDuplicate) diff --git a/metrics/metrics.go b/metrics/metrics.go index 93c02273..25d3863f 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -5,6 +5,7 @@ import ( "net" "time" + "github.com/lucas-clemente/quic-go/internal/protocol" "github.com/lucas-clemente/quic-go/logging" "go.opencensus.io/stats" @@ -65,6 +66,16 @@ func (t *tracer) TracerForConnection(p logging.Perspective, _ logging.Connection return newConnTracer(t, p) } +func (t *tracer) SentPacket(_ net.Addr, hdr *logging.Header, _ protocol.ByteCount, _ []logging.Frame) { + stats.RecordWithTags( + context.Background(), + []tag.Mutator{ + tag.Upsert(keyPacketType, packetType(logging.PacketTypeFromHeader(hdr)).String()), + }, + sentPackets.M(1), + ) +} + func (t *tracer) DroppedPacket(net.Addr, logging.PacketType, logging.ByteCount, logging.PacketDropReason) { } diff --git a/qlog/qlog.go b/qlog/qlog.go index dfed2680..c3127445 100644 --- a/qlog/qlog.go +++ b/qlog/qlog.go @@ -37,6 +37,7 @@ func (t *tracer) TracerForConnection(p logging.Perspective, odcid protocol.Conne return nil } +func (t *tracer) SentPacket(net.Addr, *logging.Header, protocol.ByteCount, []logging.Frame) {} func (t *tracer) DroppedPacket(net.Addr, logging.PacketType, protocol.ByteCount, logging.PacketDropReason) { } diff --git a/server.go b/server.go index e297d336..c4fa4291 100644 --- a/server.go +++ b/server.go @@ -553,6 +553,9 @@ func (s *baseServer) sendRetry(remoteAddr net.Addr, hdr *wire.Header) error { // append the Retry integrity tag tag := handshake.GetRetryIntegrityTag(buf.Bytes(), hdr.DestConnectionID) buf.Write(tag[:]) + if s.config.Tracer != nil { + s.config.Tracer.SentPacket(remoteAddr, &replyHdr.Header, protocol.ByteCount(buf.Len()), nil) + } _, err = s.conn.WriteTo(buf.Bytes(), remoteAddr) return err } @@ -627,6 +630,9 @@ func (s *baseServer) sendError(remoteAddr net.Addr, hdr *wire.Header, sealer han replyHdr.Log(s.logger) wire.LogFrame(s.logger, ccf, true) + if s.config.Tracer != nil { + s.config.Tracer.SentPacket(remoteAddr, &replyHdr.Header, protocol.ByteCount(len(raw)), []logging.Frame{ccf}) + } _, err := s.conn.WriteTo(raw, remoteAddr) return err } @@ -638,6 +644,18 @@ func (s *baseServer) sendVersionNegotiationPacket(p *receivedPacket, hdr *wire.H s.logger.Debugf("Error composing Version Negotiation: %s", err) return } + if s.config.Tracer != nil { + s.config.Tracer.SentPacket( + p.remoteAddr, + &wire.Header{ + IsLongHeader: true, + DestConnectionID: hdr.SrcConnectionID, + SrcConnectionID: hdr.DestConnectionID, + }, + protocol.ByteCount(len(data)), + nil, + ) + } if _, err := s.conn.WriteTo(data, p.remoteAddr); err != nil { s.logger.Debugf("Error sending Version Negotiation: %s", err) } diff --git a/server_test.go b/server_test.go index 5b1492c5..aed385b7 100644 --- a/server_test.go +++ b/server_test.go @@ -261,6 +261,7 @@ var _ = Describe("Server", func() { Version: serv.config.Versions[0], }, make([]byte, protocol.MinInitialPacketSize)) packet.remoteAddr = raddr + tracer.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1) serv.handlePacket(packet) Eventually(done).Should(BeClosed()) }) @@ -284,6 +285,7 @@ var _ = Describe("Server", func() { Version: serv.config.Versions[0], }, make([]byte, protocol.MinInitialPacketSize)) packet.remoteAddr = raddr + tracer.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1) serv.handlePacket(packet) Eventually(done).Should(BeClosed()) }) @@ -379,6 +381,12 @@ var _ = Describe("Server", func() { Version: 0x42, }, make([]byte, protocol.MinInitialPacketSize)) packet.remoteAddr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337} + tracer.EXPECT().SentPacket(packet.remoteAddr, gomock.Any(), gomock.Any(), nil).Do(func(_ net.Addr, replyHdr *logging.Header, _ logging.ByteCount, _ []logging.Frame) { + Expect(replyHdr.IsLongHeader).To(BeTrue()) + Expect(replyHdr.Version).To(BeZero()) + Expect(replyHdr.SrcConnectionID).To(Equal(destConnID)) + Expect(replyHdr.DestConnectionID).To(Equal(srcConnID)) + }) serv.handlePacket(packet) var write mockPacketConnWrite Eventually(conn.dataWritten).Should(Receive(&write)) @@ -402,6 +410,12 @@ var _ = Describe("Server", func() { } packet := getPacket(hdr, make([]byte, protocol.MinInitialPacketSize)) packet.remoteAddr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337} + tracer.EXPECT().SentPacket(packet.remoteAddr, gomock.Any(), gomock.Any(), nil).Do(func(_ net.Addr, replyHdr *logging.Header, _ logging.ByteCount, _ []logging.Frame) { + Expect(replyHdr.Type).To(Equal(protocol.PacketTypeRetry)) + Expect(replyHdr.SrcConnectionID).ToNot(Equal(hdr.DestConnectionID)) + Expect(replyHdr.DestConnectionID).To(Equal(hdr.SrcConnectionID)) + Expect(replyHdr.Token).ToNot(BeEmpty()) + }) serv.handlePacket(packet) var write mockPacketConnWrite Eventually(conn.dataWritten).Should(Receive(&write)) @@ -429,6 +443,16 @@ var _ = Describe("Server", func() { packet := getPacket(hdr, make([]byte, protocol.MinInitialPacketSize)) packet.data = append(packet.data, []byte("coalesced packet")...) // add some garbage to simulate a coalesced packet packet.remoteAddr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1337} + tracer.EXPECT().SentPacket(packet.remoteAddr, gomock.Any(), gomock.Any(), gomock.Any()).Do(func(_ net.Addr, replyHdr *logging.Header, _ logging.ByteCount, frames []logging.Frame) { + Expect(replyHdr.Type).To(Equal(protocol.PacketTypeInitial)) + Expect(replyHdr.SrcConnectionID).To(Equal(hdr.DestConnectionID)) + Expect(replyHdr.DestConnectionID).To(Equal(hdr.SrcConnectionID)) + Expect(frames).To(HaveLen(1)) + Expect(frames[0]).To(BeAssignableToTypeOf(&wire.ConnectionCloseFrame{})) + ccf := frames[0].(*logging.ConnectionCloseFrame) + Expect(ccf.IsApplicationError).To(BeFalse()) + Expect(ccf.ErrorCode).To(Equal(qerr.InvalidToken)) + }) serv.handlePacket(packet) var write mockPacketConnWrite Eventually(conn.dataWritten).Should(Receive(&write)) @@ -740,6 +764,7 @@ var _ = Describe("Server", func() { p := getInitialWithRandomDestConnID() hdr, _, _, err := wire.ParsePacket(p.data, 0) Expect(err).ToNot(HaveOccurred()) + tracer.EXPECT().SentPacket(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) serv.handlePacket(p) var reject mockPacketConnWrite Eventually(conn.dataWritten).Should(Receive(&reject))