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:
parent
2487e616c2
commit
18d12baba1
@ -174,8 +174,15 @@ class Probe:
|
|||||||
self.stop = threading.Event()
|
self.stop = threading.Event()
|
||||||
self.first_src = None
|
self.first_src = None
|
||||||
self.packets = 0
|
self.packets = 0
|
||||||
|
self.junk = 0
|
||||||
|
|
||||||
def receiver(self):
|
def receiver(self, ignore=()):
|
||||||
|
"""接收线程。
|
||||||
|
|
||||||
|
只认「以 MAGIC 开头」且「来自对端而非信令服务器」的包。
|
||||||
|
信令服务器的 PEER/WAIT 回复绝不能算作打通——否则客户端反复注册引来的
|
||||||
|
回复会造成假阳性(已踩过:把服务器的 PEER 回复误判成打洞成功)。
|
||||||
|
"""
|
||||||
while not self.stop.is_set():
|
while not self.stop.is_set():
|
||||||
try:
|
try:
|
||||||
data, src = self.sock.recvfrom(2048)
|
data, src = self.sock.recvfrom(2048)
|
||||||
@ -188,6 +195,12 @@ class Probe:
|
|||||||
if getattr(e, "winerror", None) == 10054:
|
if getattr(e, "winerror", None) == 10054:
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# ---- 过滤:非打洞包一律丢弃
|
||||||
|
if not data.startswith(MAGIC) or (ignore and src[:2] in ignore):
|
||||||
|
self.junk += 1
|
||||||
|
continue
|
||||||
|
|
||||||
if self.first_src is None:
|
if self.first_src is None:
|
||||||
self.first_src = src
|
self.first_src = src
|
||||||
print("\n ✅ 收到对方的包!")
|
print("\n ✅ 收到对方的包!")
|
||||||
@ -297,7 +310,13 @@ def main():
|
|||||||
peer = found
|
peer = found
|
||||||
print(" ✅ 配对成功,对方公网地址 = %s" % fmt_addr(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()
|
t.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -328,7 +347,10 @@ def main():
|
|||||||
print(" 对方实际源地址 = %s" % fmt_addr(probe.first_src))
|
print(" 对方实际源地址 = %s" % fmt_addr(probe.first_src))
|
||||||
print(" 共收到 %d 个包" % probe.packets)
|
print(" 共收到 %d 个包" % probe.packets)
|
||||||
else:
|
else:
|
||||||
print("结论:❌ 本轮没有收到对方的包")
|
print("结论:❌ 本轮没有收到对方的打洞包")
|
||||||
|
if probe.junk:
|
||||||
|
print(" (另外收到 %d 个非打洞包并已忽略:那是信令服务器的回复,不算打通)"
|
||||||
|
% probe.junk)
|
||||||
print(" 可能原因(按概率排序):")
|
print(" 可能原因(按概率排序):")
|
||||||
print(" 1. 一端或两端是对称型 NAT / 运营商级 NAT(CGNAT) → 需要端口预测或中继")
|
print(" 1. 一端或两端是对称型 NAT / 运营商级 NAT(CGNAT) → 需要端口预测或中继")
|
||||||
print(" 2. 一方根本没发包(对方没启动、或地址填错/端口填错)")
|
print(" 2. 一方根本没发包(对方没启动、或地址填错/端口填错)")
|
||||||
|
|||||||
12
打洞验证记录.md
12
打洞验证记录.md
@ -99,6 +99,18 @@ sock.ioctl(socket.SIO_UDP_CONNRESET, False) # 1) 关闭该行为
|
|||||||
|
|
||||||
> 注意:首次配置时误把安全组规则设成了 **TCP**,UDP 包仍被丢弃。协议类型必须选 UDP。
|
> 注意:首次配置时误把安全组规则设成了 **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. 尚未验证
|
## 5. 尚未验证
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user