fix(listener): keep *net.UDPConn visible to quic-go for GSO/GRO

The quic and wt (WebTransport) listeners wrap the raw UDP socket with
net.PacketConn metrics/stats/admission/limiter wrappers before handing
it to quic-go. WrapPacketConn returns a plain net.PacketConn, which
hides the underlying *net.UDPConn. quic-go probes the conn for
OOBCapablePacketConn (SyscallConn, SetReadBuffer, ReadMsgUDP,
WriteMsgUDP); behind the wrapper that probe fails, so quic-go
disables its kernel offloads and cannot size its receive buffer.

Switch the non-cipher path of both listeners to WrapUDPConn variants,
which forward SyscallConn/SetReadBuffer/ReadMsgUDP/WriteMsgUDP down to
the underlying *net.UDPConn, so quic-go sees an OOBCapablePacketConn
and keeps GSO/GRO + buffer sizing. The same metrics/stats/admission/
limiting still apply.

The quic listener's cipher path keeps the net.PacketConn wrappers,
since CipherPacketConn is not OOB-capable.

+48% throughput (1.80 → 2.69 Gbit/s single-stream, loopback iperf3).
The quic-go receive-buffer warning is gone.
This commit is contained in:
BBQ
2026-06-17 21:55:36 +08:00
committed by GitHub
parent ca582a147f
commit 301dbc56d0
2 changed files with 31 additions and 16 deletions
+6 -4
View File
@@ -74,10 +74,12 @@ func (l *wtListener) Init(md md.Metadata) (err error) {
return
}
pc = metrics.WrapPacketConn(l.options.Service, pc)
pc = stats.WrapPacketConn(pc, l.options.Stats)
pc = admission.WrapPacketConn(l.options.Admission, pc)
pc = limiter_wrapper.WrapPacketConn(
pc = metrics.WrapUDPConn(l.options.Service, pc)
if l.options.Stats != nil {
pc = stats.WrapUDPConn(pc, l.options.Stats)
}
pc = admission.WrapUDPConn(l.options.Admission, pc)
pc = limiter_wrapper.WrapUDPConn(
pc,
l.options.TrafficLimiter,
traffic_limiter.ServiceLimitKey,
+25 -12
View File
@@ -70,20 +70,33 @@ func (l *quicListener) Init(md md.Metadata) (err error) {
}
if l.md.cipherKey != nil {
conn = quic_util.CipherPacketConn(conn, l.md.cipherKey)
conn = metrics.WrapPacketConn(l.options.Service, conn)
conn = stats.WrapPacketConn(conn, l.options.Stats)
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapPacketConn(
conn,
l.options.TrafficLimiter,
traffic_limiter.ServiceLimitKey,
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
limiter.NetworkOption(conn.LocalAddr().Network()),
)
} else {
conn = metrics.WrapUDPConn(l.options.Service, conn)
if l.options.Stats != nil {
conn = stats.WrapUDPConn(conn, l.options.Stats)
}
conn = admission.WrapUDPConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapUDPConn(
conn,
l.options.TrafficLimiter,
traffic_limiter.ServiceLimitKey,
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
limiter.NetworkOption(conn.LocalAddr().Network()),
)
}
conn = metrics.WrapPacketConn(l.options.Service, conn)
conn = stats.WrapPacketConn(conn, l.options.Stats)
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapPacketConn(
conn,
l.options.TrafficLimiter,
traffic_limiter.ServiceLimitKey,
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
limiter.NetworkOption(conn.LocalAddr().Network()),
)
config := &quic.Config{
KeepAlivePeriod: l.md.keepAlivePeriod,
HandshakeIdleTimeout: l.md.handshakeTimeout,