Summary
trackedPacketConn.UnwrapPacketReader / UnwrapPacketWriter 的 upload/download 计数器映射与同文件 TCP 路径相反,导致使用 UDP PacketConn 的协议(Hysteria2、TUIC 的 UDP relay)流量上下行被互换计费。
根因
conntracker.go 里两套实现的方向语义本应一致:
- 入站方向"读"(从客户端来) = upload
- 入站方向"写"(发给客户端) = download
TCP 路径(trackedConn, line 669-680)写对了:
UnwrapReader() → &c.us.upload // 从入站读 = 用户上传 ✅
UnwrapWriter() → &c.us.download // 向入站写 = 用户下载 ✅
但 UDP 路径(trackedPacketConn, line 790-802)方向反了:
UnwrapPacketReader() → &c.us.download // ❌
UnwrapPacketWriter() → &c.us.upload // ❌
影响
- 协议:使用 N.PacketConn 的入站,主要是 Hysteria2、TUIC 的 UDP 数据通道
- 现象:用户面板看到的上下行数字与实际相反;按上行计费的策略(流量包、QoS、限速)作用到反向流量上
- 范围:仅 UDP 计费方向,不影响认证、归因或 TCP 流量
Fix
两行对调,使 UDP 路径与 TCP 路径方向一致:
func (c *trackedPacketConn) UnwrapPacketReader() (N.PacketReader, []N.CountFunc) {
if c.us == nil {
return c.PacketConn, nil
}
- return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.download)}
+ return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.upload)} // 从入站读 = 用户上传
}
func (c *trackedPacketConn) UnwrapPacketWriter() (N.PacketWriter, []N.CountFunc) {
if c.us == nil {
return c.PacketConn, nil
}
- return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.upload)}
+ return c.PacketConn, []N.CountFunc{c.makeCountFunc(&c.us.download)} // 向入站写 = 用户下载
}
Test plan
- 部署 patched 二进制,仅用 Hysteria2 客户端发起一次大流量下载(如 1GB 测速文件)
- 面板侧确认该用户记录 d(下行 / download)增加 ~1GB、u(上行 / upload)保持小量
- 反向验证:用客户端上传 1GB 文件,确认 u 增加而非 d
- TCP 路径回归测试(Shadowsocks / VMess 大文件下载)确认数字方向未变
Summary
trackedPacketConn.UnwrapPacketReader / UnwrapPacketWriter的 upload/download 计数器映射与同文件 TCP 路径相反,导致使用 UDP PacketConn 的协议(Hysteria2、TUIC 的 UDP relay)流量上下行被互换计费。根因
conntracker.go里两套实现的方向语义本应一致:TCP 路径(
trackedConn, line 669-680)写对了: