fix(tools): 探针区分信令回复与打洞包,修掉打洞假阳性

问题:客户端反复注册引来的 PEER/WAIT 回复在 discover_peer 返回后才到达,
被接收线程当成「对方打进来了」,输出假的「✅ 打洞成功」。
实测形态:报告的对方地址实为信令服务器 47.98.245.66:47999,包长 22 字节
(真打洞包固定 16 字节 = PUNCHv1| + 8 字节时间戳)。

修复:
- 接收线程只认以 MAGIC 开头、且长度正确的包
- 显式排除信令服务器的 IP:端口
- 失败结论里报告被忽略的非打洞包数量,避免再次误读
- 验证记录补 4.5 节,写明 Rust 实现的对应判据
This commit is contained in:
yu 2026-10-23 18:02:14 +08:00
parent 2487e616c2
commit 18d12baba1
2 changed files with 37 additions and 3 deletions

View File

@ -174,8 +174,15 @@ class Probe:
self.stop = threading.Event()
self.first_src = None
self.packets = 0
self.junk = 0
def receiver(self):
def receiver(self, ignore=()):
"""接收线程。
只认「以 MAGIC 开头」且「来自对端而非信令服务器」的包。
信令服务器的 PEER/WAIT 回复绝不能算作打通——否则客户端反复注册引来的
回复会造成假阳性(已踩过:把服务器的 PEER 回复误判成打洞成功)。
"""
while not self.stop.is_set():
try:
data, src = self.sock.recvfrom(2048)
@ -188,6 +195,12 @@ class Probe:
if getattr(e, "winerror", None) == 10054:
continue
break
# ---- 过滤:非打洞包一律丢弃
if not data.startswith(MAGIC) or (ignore and src[:2] in ignore):
self.junk += 1
continue
if self.first_src is None:
self.first_src = src
print("\n ✅ 收到对方的包!")
@ -297,7 +310,13 @@ def main():
peer = found
print(" ✅ 配对成功,对方公网地址 = %s" % fmt_addr(found))
t = threading.Thread(target=probe.receiver, daemon=True)
# 信令服务器的回复(PEER/WAIT)绝不能算作打通,接收线程必须把它排除掉
ignore = set()
if args.server:
_sh, _sp, _sf = parse_addr(args.server)
ignore.add((_sh, _sp))
t = threading.Thread(target=probe.receiver, args=(ignore,), daemon=True)
t.start()
try:
@ -328,7 +347,10 @@ def main():
print(" 对方实际源地址 = %s" % fmt_addr(probe.first_src))
print(" 共收到 %d 个包" % probe.packets)
else:
print("结论:❌ 本轮没有收到对方的包")
print("结论:❌ 本轮没有收到对方的打洞包")
if probe.junk:
print(" (另外收到 %d 个非打洞包并已忽略:那是信令服务器的回复,不算打通)"
% probe.junk)
print(" 可能原因(按概率排序):")
print(" 1. 一端或两端是对称型 NAT / 运营商级 NAT(CGNAT) → 需要端口预测或中继")
print(" 2. 一方根本没发包(对方没启动、或地址填错/端口填错)")

View File

@ -99,6 +99,18 @@ sock.ioctl(socket.SIO_UDP_CONNRESET, False) # 1) 关闭该行为
> 注意:首次配置时误把安全组规则设成了 **TCP**,UDP 包仍被丢弃。协议类型必须选 UDP。
### 4.5 假阳性:信令回复被误判为"打通"
客户端在拿到对端地址前会反复注册,服务器的 `PEER` / `WAIT` 回复可能在 `discover_peer` 返回之后才到达。若接收线程不做区分,会把**服务器发来的回复**当成"对方打进来了",输出假的"✅ 打洞成功"。
实测踩到的形态:报告的"对方源地址"是**信令服务器的 IP:端口**(`47.98.245.66:47999`),包长 22 字节——而真正的打洞包固定是 **16 字节**(`PUNCHv1|` + 8 字节时间戳)。
**判据(Rust 实现必须照做)**:
1. 只认以协议魔数开头的包,长度必须是约定值
2. 显式排除信令服务器的 `IP:端口`
3. 结论里如果出现"非打洞包 N 个",说明收到了信令回复,**不能**据此判定成功
---
## 5. 尚未验证