File: tracer.go

package info (click to toggle)
golang-github-lucas-clemente-quic-go 0.54.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,312 kB
  • sloc: sh: 54; makefile: 7
file content (148 lines) | stat: -rw-r--r-- 4,386 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package metrics

import (
	"errors"
	"fmt"
	"net"

	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/quic-go/quic-go/internal/qerr"
	"github.com/quic-go/quic-go/logging"

	"github.com/prometheus/client_golang/prometheus"
)

const metricNamespace = "quicgo"

func getIPVersion(addr net.Addr) string {
	udpAddr, ok := addr.(*net.UDPAddr)
	if !ok {
		return ""
	}
	if udpAddr.IP.To4() != nil {
		return "ipv4"
	}
	return "ipv6"
}

var (
	connsRejected = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "server_connections_rejected_total",
			Help:      "Connections Rejected",
		},
		[]string{"ip_version", "reason"},
	)
	packetDropped = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "server_received_packets_dropped_total",
			Help:      "packets dropped",
		},
		[]string{"ip_version", "reason"},
	)
)

// NewTracer creates a new tracer using the default Prometheus registerer.
// The Tracer returned from this function can be used to collect metrics for
// events happening before the establishment of a QUIC connection.
// It can be set on the Tracer field of quic.Transport.
func NewTracer() *logging.Tracer {
	return NewTracerWithRegisterer(prometheus.DefaultRegisterer)
}

// NewTracerWithRegisterer creates a new tracer using a given Prometheus registerer.
func NewTracerWithRegisterer(registerer prometheus.Registerer) *logging.Tracer {
	for _, c := range [...]prometheus.Collector{
		connsRejected,
		packetDropped,
	} {
		if err := registerer.Register(c); err != nil {
			if ok := errors.As(err, &prometheus.AlreadyRegisteredError{}); !ok {
				panic(err)
			}
		}
	}

	return &logging.Tracer{
		SentPacket: func(addr net.Addr, hdr *logging.Header, _ logging.ByteCount, frames []logging.Frame) {
			tags := getStringSlice()
			defer putStringSlice(tags)

			var reason string
			//nolint:exhaustive // we only care about Retry and Initial packets here
			switch hdr.Type {
			case protocol.PacketTypeRetry:
				reason = "retry"
			case protocol.PacketTypeInitial:
				var ccf *logging.ConnectionCloseFrame
				for _, f := range frames {
					cc, ok := f.(*logging.ConnectionCloseFrame)
					if ok {
						ccf = cc
						break
					}
				}
				// This should never happen. We only send Initials before creating the connection in order to
				// reject a connection attempt.
				if ccf == nil {
					return
				}
				if ccf.IsApplicationError {
					//nolint:exhaustive // Only a few error codes applicable.
					switch qerr.TransportErrorCode(ccf.ErrorCode) {
					case qerr.ConnectionRefused:
						reason = "connection_refused"
					case qerr.InvalidToken:
						reason = "invalid_token"
					default:
						// This shouldn't happen, the server doesn't send CONNECTION_CLOSE frames with different errors.
						reason = fmt.Sprintf("transport_error: %d", ccf.ErrorCode)
					}
				} else {
					// This shouldn't happen, the server doesn't send application-level CONNECTION_CLOSE frames.
					reason = "application_error"
				}
			}
			*tags = append(*tags, getIPVersion(addr))
			*tags = append(*tags, reason)
			connsRejected.WithLabelValues(*tags...).Inc()
		},
		SentVersionNegotiationPacket: func(addr net.Addr, _, _ logging.ArbitraryLenConnectionID, _ []logging.Version) {
			tags := getStringSlice()
			defer putStringSlice(tags)

			*tags = append(*tags, getIPVersion(addr))
			*tags = append(*tags, "version_negotiation")
			connsRejected.WithLabelValues(*tags...).Inc()
		},
		DroppedPacket: func(addr net.Addr, pt logging.PacketType, _ logging.ByteCount, reason logging.PacketDropReason) {
			tags := getStringSlice()
			defer putStringSlice(tags)

			var dropReason string
			//nolint:exhaustive // Only a few drop reasons applicable.
			switch reason {
			case logging.PacketDropDOSPrevention:
				if pt == logging.PacketType0RTT {
					dropReason = "0rtt_dos_prevention"
				} else {
					dropReason = "dos_prevention"
				}
			case logging.PacketDropHeaderParseError:
				dropReason = "header_parsing"
			case logging.PacketDropPayloadDecryptError:
				dropReason = "payload_decrypt"
			case logging.PacketDropUnexpectedPacket:
				dropReason = "unexpected_packet"
			default:
				dropReason = "unknown"
			}

			*tags = append(*tags, getIPVersion(addr))
			*tags = append(*tags, dropReason)
			packetDropped.WithLabelValues(*tags...).Inc()
		},
	}
}