qlog: use PathEndpointInfo in connection_started (#5368)

* qlog: use PathEndpointInfo in connection_started

* correctly deal with dual-stack addresses
This commit is contained in:
Marten Seemann
2025-10-11 13:55:38 +08:00
committed by GitHub
parent ae909aeb72
commit 115e8ee5d7
5 changed files with 176 additions and 47 deletions

View File

@@ -413,12 +413,7 @@ var newClientConnection = func(
if addr, ok := conn.RemoteAddr().(*net.UDPAddr); ok {
destAddr = addr
}
s.qlogger.RecordEvent(qlog.StartedConnection{
SrcAddr: srcAddr,
DestAddr: destAddr,
SrcConnectionID: srcConnID,
DestConnectionID: destConnID,
})
s.qlogger.RecordEvent(startedConnectionEvent(srcAddr, destAddr))
}
s.connIDManager = newConnIDManager(
destConnID,
@@ -1658,12 +1653,7 @@ func (c *Conn) handleUnpackedLongHeaderPacket(
if addr, ok := c.conn.RemoteAddr().(*net.UDPAddr); ok {
destAddr = addr
}
c.qlogger.RecordEvent(qlog.StartedConnection{
SrcAddr: srcAddr,
DestAddr: destAddr,
SrcConnectionID: packet.hdr.SrcConnectionID,
DestConnectionID: packet.hdr.DestConnectionID,
})
c.qlogger.RecordEvent(startedConnectionEvent(srcAddr, destAddr))
}
}
}

View File

@@ -1,6 +1,8 @@
package quic
import (
"net"
"net/netip"
"slices"
"github.com/quic-go/quic-go/internal/ackhandler"
@@ -268,3 +270,53 @@ func toQlogPacketType(pt protocol.PacketType) qlog.PacketType {
}
return qpt
}
func toPathEndpointInfo(addr *net.UDPAddr) qlog.PathEndpointInfo {
if addr == nil {
return qlog.PathEndpointInfo{}
}
var info qlog.PathEndpointInfo
if addr.IP == nil || addr.IP.To4() != nil {
addrPort := netip.AddrPortFrom(netip.AddrFrom4([4]byte(addr.IP.To4())), uint16(addr.Port))
if addrPort.IsValid() {
info.IPv4 = addrPort
}
} else {
addrPort := netip.AddrPortFrom(netip.AddrFrom16([16]byte(addr.IP.To16())), uint16(addr.Port))
if addrPort.IsValid() {
info.IPv6 = addrPort
}
}
return info
}
// startedConnectionEvent builds a StartedConnection event using consistent logic
// for both endpoints. If the local address is unspecified (e.g., dual-stack
// listener), it selects the family based on the remote address and uses the
// unspecified address of that family with the local port.
func startedConnectionEvent(local, remote *net.UDPAddr) qlog.StartedConnection {
var localInfo, remoteInfo qlog.PathEndpointInfo
if remote != nil {
remoteInfo = toPathEndpointInfo(remote)
}
if local != nil {
if local.IP == nil || local.IP.IsUnspecified() {
// Choose local family based on the remote address family.
if remote != nil && remote.IP.To4() != nil {
ap := netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), uint16(local.Port))
if ap.IsValid() {
localInfo.IPv4 = ap
}
} else if remote != nil && remote.IP.To16() != nil && remote.IP.To4() == nil {
ap := netip.AddrPortFrom(netip.AddrFrom16([16]byte{}), uint16(local.Port))
if ap.IsValid() {
localInfo.IPv6 = ap
}
}
} else {
localInfo = toPathEndpointInfo(local)
}
}
return qlog.StartedConnection{Local: localInfo, Remote: remoteInfo}
}

View File

@@ -1,6 +1,8 @@
package quic
import (
"net"
"net/netip"
"testing"
"github.com/quic-go/quic-go/internal/wire"
@@ -70,3 +72,72 @@ func TestConnectionLoggingOtherFrames(t *testing.T) {
f := toQlogFrame(&wire.MaxDataFrame{MaximumData: 1234})
require.Equal(t, &qlog.MaxDataFrame{MaximumData: 1234}, f.Frame)
}
func TestConnectionLoggingStartedConnectionEvent(t *testing.T) {
tests := []struct {
name string
local *net.UDPAddr
remote *net.UDPAddr
wantLocalIP string
wantLocalPort uint16
wantRemote netip.AddrPort
}{
{
name: "unspecified local, remote IPv4 -> 0.0.0.0",
local: &net.UDPAddr{Port: 58451},
remote: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 6121},
wantLocalIP: "0.0.0.0",
wantLocalPort: 58451,
wantRemote: netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 6121),
},
{
name: "unspecified local, remote IPv6 -> ::",
local: &net.UDPAddr{Port: 4242},
remote: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 6121},
wantLocalIP: "::",
wantLocalPort: 4242,
wantRemote: func() netip.AddrPort { a, _ := netip.ParseAddr("2001:db8::1"); return netip.AddrPortFrom(a, 6121) }(),
},
{
name: "specified local IPv4",
local: &net.UDPAddr{IP: net.IPv4(192, 168, 1, 10), Port: 9999},
remote: &net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 1234},
wantLocalIP: "192.168.1.10",
wantLocalPort: 9999,
wantRemote: netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 1234),
},
{
name: "specified local IPv6",
local: &net.UDPAddr{IP: net.ParseIP("fe80::1"), Port: 999},
remote: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 6121},
wantLocalIP: "fe80::1",
wantLocalPort: 999,
wantRemote: func() netip.AddrPort { a, _ := netip.ParseAddr("2001:db8::1"); return netip.AddrPortFrom(a, 6121) }(),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ev := startedConnectionEvent(tc.local, tc.remote)
var gotIP string
var gotPort uint16
if ev.Local.IPv4.IsValid() {
gotIP = ev.Local.IPv4.Addr().String()
gotPort = ev.Local.IPv4.Port()
} else if ev.Local.IPv6.IsValid() {
gotIP = ev.Local.IPv6.Addr().String()
gotPort = ev.Local.IPv6.Port()
}
require.Equal(t, tc.wantLocalIP, gotIP)
require.Equal(t, tc.wantLocalPort, gotPort)
var gotRemote netip.AddrPort
if ev.Remote.IPv4.IsValid() {
gotRemote = ev.Remote.IPv4
} else if ev.Remote.IPv6.IsValid() {
gotRemote = ev.Remote.IPv6
}
require.Equal(t, tc.wantRemote, gotRemote)
})
}
}

View File

@@ -3,7 +3,6 @@ package qlog
import (
"errors"
"fmt"
"net"
"net/netip"
"time"
@@ -56,11 +55,33 @@ func (i RawInfo) encode(enc *jsontext.Encoder) error {
return h.err
}
type PathEndpointInfo struct {
IPv4 netip.AddrPort
IPv6 netip.AddrPort
}
func (p PathEndpointInfo) encode(enc *jsontext.Encoder) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if p.IPv4.IsValid() {
h.WriteToken(jsontext.String("ip_v4"))
h.WriteToken(jsontext.String(p.IPv4.Addr().String()))
h.WriteToken(jsontext.String("port_v4"))
h.WriteToken(jsontext.Int(int64(p.IPv4.Port())))
}
if p.IPv6.IsValid() {
h.WriteToken(jsontext.String("ip_v6"))
h.WriteToken(jsontext.String(p.IPv6.Addr().String()))
h.WriteToken(jsontext.String("port_v6"))
h.WriteToken(jsontext.Int(int64(p.IPv6.Port())))
}
h.WriteToken(jsontext.EndObject)
return h.err
}
type StartedConnection struct {
SrcAddr *net.UDPAddr
DestAddr *net.UDPAddr
SrcConnectionID ConnectionID
DestConnectionID ConnectionID
Local PathEndpointInfo
Remote PathEndpointInfo
}
func (e StartedConnection) Name() string { return "transport:connection_started" }
@@ -68,25 +89,14 @@ func (e StartedConnection) Name() string { return "transport:connection_started"
func (e StartedConnection) Encode(enc *jsontext.Encoder, _ time.Time) error {
h := encoderHelper{enc: enc}
h.WriteToken(jsontext.BeginObject)
if e.SrcAddr.IP.To4() != nil {
h.WriteToken(jsontext.String("ip_version"))
h.WriteToken(jsontext.String("ipv4"))
} else {
h.WriteToken(jsontext.String("ip_version"))
h.WriteToken(jsontext.String("ipv6"))
h.WriteToken(jsontext.String("local"))
if err := e.Local.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("remote"))
if err := e.Remote.encode(enc); err != nil {
return err
}
h.WriteToken(jsontext.String("src_ip"))
h.WriteToken(jsontext.String(e.SrcAddr.IP.String()))
h.WriteToken(jsontext.String("src_port"))
h.WriteToken(jsontext.Int(int64(e.SrcAddr.Port)))
h.WriteToken(jsontext.String("dst_ip"))
h.WriteToken(jsontext.String(e.DestAddr.IP.String()))
h.WriteToken(jsontext.String("dst_port"))
h.WriteToken(jsontext.Int(int64(e.DestAddr.Port)))
h.WriteToken(jsontext.String("src_cid"))
h.WriteToken(jsontext.String(e.SrcConnectionID.String()))
h.WriteToken(jsontext.String("dst_cid"))
h.WriteToken(jsontext.String(e.DestConnectionID.String()))
h.WriteToken(jsontext.EndObject)
return h.err
}

View File

@@ -3,7 +3,6 @@ package qlog
import (
"bytes"
"encoding/json"
"net"
"net/netip"
"testing"
"time"
@@ -58,21 +57,28 @@ func decode(t *testing.T, data string) (string, map[string]any) {
}
func TestStartedConnection(t *testing.T) {
var localInfo, remoteInfo PathEndpointInfo
localInfo.IPv4 = netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 13, 37}), 42)
ip, err := netip.ParseAddr("2001:db8::1")
require.NoError(t, err)
remoteInfo.IPv6 = netip.AddrPortFrom(ip, 24)
name, ev := testEventEncoding(t, &StartedConnection{
SrcAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 13, 37), Port: 42},
DestAddr: &net.UDPAddr{IP: net.IPv4(192, 168, 12, 34), Port: 24},
SrcConnectionID: protocol.ParseConnectionID([]byte{1, 2, 3, 4}),
DestConnectionID: protocol.ParseConnectionID([]byte{5, 6, 7, 8}),
Local: localInfo,
Remote: remoteInfo,
})
require.Equal(t, "transport:connection_started", name)
require.Equal(t, "ipv4", ev["ip_version"])
require.Equal(t, "192.168.13.37", ev["src_ip"])
require.Equal(t, float64(42), ev["src_port"])
require.Equal(t, "192.168.12.34", ev["dst_ip"])
require.Equal(t, float64(24), ev["dst_port"])
require.Equal(t, "01020304", ev["src_cid"])
require.Equal(t, "05060708", ev["dst_cid"])
local, ok := ev["local"].(map[string]any)
require.True(t, ok)
require.Equal(t, "192.168.13.37", local["ip_v4"])
require.Equal(t, float64(42), local["port_v4"])
remote, ok := ev["remote"].(map[string]any)
require.True(t, ok)
require.Equal(t, "2001:db8::1", remote["ip_v6"])
require.Equal(t, float64(24), remote["port_v6"])
}
func TestVersionInformation(t *testing.T) {