golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/qlog.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build go1.21 6 7 package quic 8 9 import ( 10 "context" 11 "encoding/hex" 12 "log/slog" 13 "net/netip" 14 "time" 15 ) 16 17 // Log levels for qlog events. 18 const ( 19 // QLogLevelFrame includes per-frame information. 20 // When this level is enabled, packet_sent and packet_received events will 21 // contain information on individual frames sent/received. 22 QLogLevelFrame = slog.Level(-6) 23 24 // QLogLevelPacket events occur at most once per packet sent or received. 25 // 26 // For example: packet_sent, packet_received. 27 QLogLevelPacket = slog.Level(-4) 28 29 // QLogLevelConn events occur multiple times over a connection's lifetime, 30 // but less often than the frequency of individual packets. 31 // 32 // For example: connection_state_updated. 33 QLogLevelConn = slog.Level(-2) 34 35 // QLogLevelEndpoint events occur at most once per connection. 36 // 37 // For example: connection_started, connection_closed. 38 QLogLevelEndpoint = slog.Level(0) 39 ) 40 41 func (c *Conn) logEnabled(level slog.Level) bool { 42 return logEnabled(c.log, level) 43 } 44 45 func logEnabled(log *slog.Logger, level slog.Level) bool { 46 return log != nil && log.Enabled(context.Background(), level) 47 } 48 49 // slogHexstring returns a slog.Attr for a value of the hexstring type. 50 // 51 // https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-04.html#section-1.1.1 52 func slogHexstring(key string, value []byte) slog.Attr { 53 return slog.String(key, hex.EncodeToString(value)) 54 } 55 56 func slogAddr(key string, value netip.Addr) slog.Attr { 57 return slog.String(key, value.String()) 58 } 59 60 func (c *Conn) logConnectionStarted(originalDstConnID []byte, peerAddr netip.AddrPort) { 61 if c.config.QLogLogger == nil || 62 !c.config.QLogLogger.Enabled(context.Background(), QLogLevelEndpoint) { 63 return 64 } 65 var vantage string 66 if c.side == clientSide { 67 vantage = "client" 68 originalDstConnID = c.connIDState.originalDstConnID 69 } else { 70 vantage = "server" 71 } 72 // A qlog Trace container includes some metadata (title, description, vantage_point) 73 // and a list of Events. The Trace also includes a common_fields field setting field 74 // values common to all events in the trace. 75 // 76 // Trace = { 77 // ? title: text 78 // ? description: text 79 // ? configuration: Configuration 80 // ? common_fields: CommonFields 81 // ? vantage_point: VantagePoint 82 // events: [* Event] 83 // } 84 // 85 // To map this into slog's data model, we start each per-connection trace with a With 86 // call that includes both the trace metadata and the common fields. 87 // 88 // This means that in slog's model, each trace event will also include 89 // the Trace metadata fields (vantage_point), which is a divergence from the qlog model. 90 c.log = c.config.QLogLogger.With( 91 // The group_id permits associating traces taken from different vantage points 92 // for the same connection. 93 // 94 // We use the original destination connection ID as the group ID. 95 // 96 // https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-04.html#section-3.4.6 97 slogHexstring("group_id", originalDstConnID), 98 slog.Group("vantage_point", 99 slog.String("name", "go quic"), 100 slog.String("type", vantage), 101 ), 102 ) 103 localAddr := c.endpoint.LocalAddr() 104 // https://www.ietf.org/archive/id/draft-ietf-quic-qlog-quic-events-03.html#section-4.2 105 c.log.LogAttrs(context.Background(), QLogLevelEndpoint, 106 "connectivity:connection_started", 107 slogAddr("src_ip", localAddr.Addr()), 108 slog.Int("src_port", int(localAddr.Port())), 109 slogHexstring("src_cid", c.connIDState.local[0].cid), 110 slogAddr("dst_ip", peerAddr.Addr()), 111 slog.Int("dst_port", int(peerAddr.Port())), 112 slogHexstring("dst_cid", c.connIDState.remote[0].cid), 113 ) 114 } 115 116 func (c *Conn) logConnectionClosed() { 117 if !c.logEnabled(QLogLevelEndpoint) { 118 return 119 } 120 err := c.lifetime.finalErr 121 trigger := "error" 122 switch e := err.(type) { 123 case *ApplicationError: 124 // TODO: Distinguish between peer and locally-initiated close. 125 trigger = "application" 126 case localTransportError: 127 switch err { 128 case errHandshakeTimeout: 129 trigger = "handshake_timeout" 130 default: 131 if e.code == errNo { 132 trigger = "clean" 133 } 134 } 135 case peerTransportError: 136 if e.code == errNo { 137 trigger = "clean" 138 } 139 default: 140 switch err { 141 case errIdleTimeout: 142 trigger = "idle_timeout" 143 case errStatelessReset: 144 trigger = "stateless_reset" 145 } 146 } 147 // https://www.ietf.org/archive/id/draft-ietf-quic-qlog-quic-events-03.html#section-4.3 148 c.log.LogAttrs(context.Background(), QLogLevelEndpoint, 149 "connectivity:connection_closed", 150 slog.String("trigger", trigger), 151 ) 152 } 153 154 func (c *Conn) logPacketDropped(dgram *datagram) { 155 c.log.LogAttrs(context.Background(), QLogLevelPacket, 156 "connectivity:packet_dropped", 157 ) 158 } 159 160 func (c *Conn) logLongPacketReceived(p longPacket, pkt []byte) { 161 var frames slog.Attr 162 if c.logEnabled(QLogLevelFrame) { 163 frames = c.packetFramesAttr(p.payload) 164 } 165 c.log.LogAttrs(context.Background(), QLogLevelPacket, 166 "transport:packet_received", 167 slog.Group("header", 168 slog.String("packet_type", p.ptype.qlogString()), 169 slog.Uint64("packet_number", uint64(p.num)), 170 slog.Uint64("flags", uint64(pkt[0])), 171 slogHexstring("scid", p.srcConnID), 172 slogHexstring("dcid", p.dstConnID), 173 ), 174 slog.Group("raw", 175 slog.Int("length", len(pkt)), 176 ), 177 frames, 178 ) 179 } 180 181 func (c *Conn) log1RTTPacketReceived(p shortPacket, pkt []byte) { 182 var frames slog.Attr 183 if c.logEnabled(QLogLevelFrame) { 184 frames = c.packetFramesAttr(p.payload) 185 } 186 dstConnID, _ := dstConnIDForDatagram(pkt) 187 c.log.LogAttrs(context.Background(), QLogLevelPacket, 188 "transport:packet_received", 189 slog.Group("header", 190 slog.String("packet_type", packetType1RTT.qlogString()), 191 slog.Uint64("packet_number", uint64(p.num)), 192 slog.Uint64("flags", uint64(pkt[0])), 193 slogHexstring("dcid", dstConnID), 194 ), 195 slog.Group("raw", 196 slog.Int("length", len(pkt)), 197 ), 198 frames, 199 ) 200 } 201 202 func (c *Conn) logPacketSent(ptype packetType, pnum packetNumber, src, dst []byte, pktLen int, payload []byte) { 203 var frames slog.Attr 204 if c.logEnabled(QLogLevelFrame) { 205 frames = c.packetFramesAttr(payload) 206 } 207 var scid slog.Attr 208 if len(src) > 0 { 209 scid = slogHexstring("scid", src) 210 } 211 c.log.LogAttrs(context.Background(), QLogLevelPacket, 212 "transport:packet_sent", 213 slog.Group("header", 214 slog.String("packet_type", ptype.qlogString()), 215 slog.Uint64("packet_number", uint64(pnum)), 216 scid, 217 slogHexstring("dcid", dst), 218 ), 219 slog.Group("raw", 220 slog.Int("length", pktLen), 221 ), 222 frames, 223 ) 224 } 225 226 // packetFramesAttr returns the "frames" attribute containing the frames in a packet. 227 // We currently pass this as a slog Any containing a []slog.Value, 228 // where each Value is a debugFrame that implements slog.LogValuer. 229 // 230 // This isn't tremendously efficient, but avoids the need to put a JSON encoder 231 // in the quic package or a frame parser in the qlog package. 232 func (c *Conn) packetFramesAttr(payload []byte) slog.Attr { 233 var frames []slog.Value 234 for len(payload) > 0 { 235 f, n := parseDebugFrame(payload) 236 if n < 0 { 237 break 238 } 239 payload = payload[n:] 240 switch f := f.(type) { 241 case debugFrameAck: 242 // The qlog ACK frame contains the ACK Delay field as a duration. 243 // Interpreting the contents of this field as a duration requires 244 // knowing the peer's ack_delay_exponent transport parameter, 245 // and it's possible for us to parse an ACK frame before we've 246 // received that parameter. 247 // 248 // We could plumb connection state down into the frame parser, 249 // but for now let's minimize the amount of code that needs to 250 // deal with this and convert the unscaled value into a scaled one here. 251 ackDelay := time.Duration(-1) 252 if c.peerAckDelayExponent >= 0 { 253 ackDelay = f.ackDelay.Duration(uint8(c.peerAckDelayExponent)) 254 } 255 frames = append(frames, slog.AnyValue(debugFrameScaledAck{ 256 ranges: f.ranges, 257 ackDelay: ackDelay, 258 })) 259 default: 260 frames = append(frames, slog.AnyValue(f)) 261 } 262 } 263 return slog.Any("frames", frames) 264 } 265 266 func (c *Conn) logPacketLost(space numberSpace, sent *sentPacket) { 267 c.log.LogAttrs(context.Background(), QLogLevelPacket, 268 "recovery:packet_lost", 269 slog.Group("header", 270 slog.String("packet_type", sent.ptype.qlogString()), 271 slog.Uint64("packet_number", uint64(sent.num)), 272 ), 273 ) 274 }