diff --git a/tools/punch_probe.py b/tools/punch_probe.py index 6c8abdf..c7342c6 100644 --- a/tools/punch_probe.py +++ b/tools/punch_probe.py @@ -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. 一方根本没发包(对方没启动、或地址填错/端口填错)") diff --git a/打洞验证记录.md b/打洞验证记录.md index 7de01f8..a2471c5 100644 --- a/打洞验证记录.md +++ b/打洞验证记录.md @@ -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. 尚未验证