tests: finish cold UDP detection before opening additional TCP links

This commit is contained in:
evgeny committed 2026-10-17 21:22:22 +02:00
1 parent d2a8e6fda7
commit 2513e9ae81
1 file changed
+44 -39
+44 -39
View File
@@ -64,7 +64,11 @@ def check_profiles(scenario):
print(f"NAT routed private helper: {private_helper.name} through {second.name}, group={actual['group']}", flush=True)
verified = {}
protocols = ["tcp"] if scenario.args.tcp_only else ["udp", "tcp"] if scenario.args.tcp else ["udp"]
for owner in public + [node for node in NODES if node.nat]:
verified[owner.name] = {}
if scenario.args.tcp_only:
continue
observer = second if owner.name == first.name else first
if not scenario.args.tcp_only:
agents[owner.name].rpc("nat_check", socket="udp", observer=agents[observer.name].identity)
@@ -80,45 +84,46 @@ def check_profiles(scenario):
connect(observer, helper)
connect(owner, observer)
store_owner(owner, observer)
protocols = ["tcp"] if scenario.args.tcp_only else ["udp", "tcp"] if scenario.args.tcp else ["udp"]
observed = {}
for protocol in protocols:
if protocol == "tcp":
# Только один действующий TCP-пир, без испытания входящих и без сравнения портов.
connect(owner, observer, "tcp")
data = scenario.wait(f"nat-tcp-ip-{owner.name}", lambda statistics:
profile(statistics[owner.name], "tcp")["source"] == "ETCP" and
bool(profile(statistics[owner.name], "tcp")["external"]), 10)
actual = profile(data[owner.name], "tcp")
expected = ("UNKNOWN" if owner.nat else "UNCHANGED", "UNKNOWN")
if actual["rounds"] or int(actual["helper"]):
raise RuntimeError(f"{owner.name}: TCP IP detection unexpectedly used a helper round: {actual}")
else:
actual = measure(owner, observer, protocol)
if not owner.nat:
expected = ("UNCHANGED", "OPEN")
else:
gateway = next(item for item in NATS if item.name == owner.nat)
expected = {"cone": ("EIM", "OPEN"), "restricted": ("EIM", "CONTACT_REQUIRED"),
"strict": ("DEPENDENT", "UNKNOWN")}[gateway.mode]
if (actual["mapping"], actual["inbound"]) != expected:
raise RuntimeError(f"{owner.name}/{protocol}: expected {expected}, detector returned {actual}")
if protocol == "tcp":
ip = next(item for item in NATS if item.name == owner.nat).ip if owner.nat else owner.ip
external = f"{ip}:0"
elif expected[0] == "EIM":
gateway = next(item for item in NATS if item.name == owner.nat)
external = f"{gateway.ip}:{owner.mapped_port}"
elif expected[0] == "UNCHANGED":
external = f"{owner.ip}:32000"
else:
external = ""
if actual["external"] != external:
raise RuntimeError(f"{owner.name}/{protocol}: foreign/invalid external endpoint: {actual}, expected {external}")
observed[protocol] = actual
print(f"NAT {owner.name}/{protocol}: {actual['mapping']} / {actual['inbound']}"
f" external={actual['external'] or '-'} reason={actual['reason']}", flush=True)
verified[owner.name] = observed
actual = measure(owner, observer, "udp")
if not owner.nat:
expected = ("UNCHANGED", "OPEN")
else:
gateway = next(item for item in NATS if item.name == owner.nat)
expected = {"cone": ("EIM", "OPEN"), "restricted": ("EIM", "CONTACT_REQUIRED"),
"strict": ("DEPENDENT", "UNKNOWN")}[gateway.mode]
if (actual["mapping"], actual["inbound"]) != expected:
raise RuntimeError(f"{owner.name}/udp: expected {expected}, detector returned {actual}")
if expected[0] == "EIM":
gateway = next(item for item in NATS if item.name == owner.nat)
external = f"{gateway.ip}:{owner.mapped_port}"
elif expected[0] == "UNCHANGED":
external = f"{owner.ip}:32000"
else:
external = ""
if actual["external"] != external:
raise RuntimeError(f"{owner.name}/udp: foreign/invalid external endpoint: {actual}, expected {external}")
verified[owner.name]["udp"] = actual
print(f"NAT {owner.name}/udp: {actual['mapping']} / {actual['inbound']}"
f" external={actual['external'] or '-'} reason={actual['reason']}", flush=True)
(scenario.network.directory / "nat-profiles.json").write_text(json.dumps(verified, indent=2) + "\n")
# Дополнительные TCP-линки создаются после холодных UDP-измерений: NCD может запустить и UDP-попытки.
if "tcp" in protocols:
for owner in public + [node for node in NODES if node.nat]:
observer = second if owner.name == first.name else first
connect(owner, observer, "tcp")
data = scenario.wait(f"nat-tcp-ip-{owner.name}", lambda statistics:
profile(statistics[owner.name], "tcp")["source"] == "ETCP" and
bool(profile(statistics[owner.name], "tcp")["external"]), 10)
actual = profile(data[owner.name], "tcp")
expected = ("UNKNOWN" if owner.nat else "UNCHANGED", "UNKNOWN")
ip = next(item for item in NATS if item.name == owner.nat).ip if owner.nat else owner.ip
if (actual["mapping"], actual["inbound"], actual["external"]) != (*expected, f"{ip}:0"):
raise RuntimeError(f"{owner.name}/tcp: expected {expected}, IP={ip}:0, detector returned {actual}")
if actual["rounds"] or int(actual["helper"]):
raise RuntimeError(f"{owner.name}: TCP IP detection unexpectedly used a helper round: {actual}")
verified[owner.name]["tcp"] = actual
print(f"NAT {owner.name}/tcp: {actual['mapping']} / {actual['inbound']} external={actual['external']}", flush=True)
(scenario.network.directory / "nat-profiles.json").write_text(json.dumps(verified, indent=2) + "\n")
# Повтор на использовавшемся порту сохраняет mapping, но не обновляет OPEN