From 18d12baba1c4e28b06f2d3e1e19dc14fcf8d6094 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Fri, 23 Oct 2026 18:02:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(tools):=20=E6=8E=A2=E9=92=88=E5=8C=BA?= =?UTF-8?q?=E5=88=86=E4=BF=A1=E4=BB=A4=E5=9B=9E=E5=A4=8D=E4=B8=8E=E6=89=93?= =?UTF-8?q?=E6=B4=9E=E5=8C=85=EF=BC=8C=E4=BF=AE=E6=8E=89=E6=89=93=E6=B4=9E?= =?UTF-8?q?=E5=81=87=E9=98=B3=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:客户端反复注册引来的 PEER/WAIT 回复在 discover_peer 返回后才到达, 被接收线程当成「对方打进来了」,输出假的「✅ 打洞成功」。 实测形态:报告的对方地址实为信令服务器 47.98.245.66:47999,包长 22 字节 (真打洞包固定 16 字节 = PUNCHv1| + 8 字节时间戳)。 修复: - 接收线程只认以 MAGIC 开头、且长度正确的包 - 显式排除信令服务器的 IP:端口 - 失败结论里报告被忽略的非打洞包数量,避免再次误读 - 验证记录补 4.5 节,写明 Rust 实现的对应判据 --- tools/punch_probe.py | 28 +++++++++++++++++++++++++--- 打洞验证记录.md | 12 ++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) 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. 尚未验证