tests: verify TCP IP-only detection across real NAT gateways

This commit is contained in:
evgeny committed 2026-10-17 19:52:39 +02:00
1 parent 62aee9f01c
commit b588f8e9f4
2 files changed
+22 -14

No files matched your search

+5 -3
View File
@@ -50,10 +50,12 @@ JOIN/стриминг в этом режиме не запускаются.
до первого исходящего контакта рабочего сокета с помощником.
Проверяются отдельно mapping и inbound каждого UDP endpoint, точный собственный
внешний IP:port, отсутствие ложного STRICT без помощника и корректность TCP listener.
внешний IP:port и отсутствие ложного STRICT без помощника.
Ожидания: public — UNCHANGED/OPEN, cone — EIM/OPEN, restricted — EIM/CONTACT_REQUIRED,
strict — DEPENDENT без универсального внешнего адреса. TCP за NAT без port forwarding
остаётся UNKNOWN/no_listener. Результаты: `nat-profiles.json`, `nat-result.json`;
strict — DEPENDENT без универсального внешнего адреса. Для TCP один настоящий TCP-пир
сообщает внешний IP: public — UNCHANGED/UNKNOWN, за NAT — UNKNOWN/UNKNOWN с source=ETCP.
Во всех TCP-наблюдениях внешний порт 0, раундов с помощником нет; тип NAT и вход не проверяются.
Результаты: `nat-profiles.json`, `nat-result.json`;
обычная телеметрия также включает `address_profiles`, контакты и причину неполного измерения.
Полная baseline-проверка длится 120 секунд после старта доставки, плюс калибровка и JOIN. Она покрывает оборот 16-битного timestamp и несколько циклов проб. Короткая проверка:
+17 -11
View File
@@ -18,12 +18,12 @@ def check_profiles(scenario):
for agent in agents.values():
agent.rpc("nat_auto", enabled=1)
def connect(source, target):
pair = (source.name, target.name)
def connect(source, target, protocol=transport):
pair = (source.name, target.name, protocol)
if pair in connected:
return
snapshot = agents[target.name].rpc("snapshot")["snapshot"]
agents[source.name].rpc("nat_link", snapshot=snapshot, socket=transport)
agents[source.name].rpc("nat_link", snapshot=snapshot, socket=protocol)
target_id = agents[target.name].identity
source_id = agents[source.name].identity
scenario.wait(f"nat-link-{source.name}-{target.name}",
@@ -83,16 +83,19 @@ def check_profiles(scenario):
protocols = ["tcp"] if scenario.args.tcp_only else ["udp", "tcp"] if scenario.args.tcp else ["udp"]
observed = {}
for protocol in protocols:
if protocol == "tcp" and owner.nat:
# Приватный TCP listener не имеет обнаруженного публичного endpoint.
agents[owner.name].rpc("nat_check", socket="tcp", observer=agents[observer.name].identity)
data = scenario.wait(f"nat-private-tcp-{owner.name}", lambda statistics:
profile(statistics[owner.name], "tcp")["reason"] == "no_listener", 5)
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", "UNKNOWN")
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 protocol == "tcp" or not owner.nat:
if not owner.nat:
expected = ("UNCHANGED", "OPEN")
else:
gateway = next(item for item in NATS if item.name == owner.nat)
@@ -100,7 +103,10 @@ def check_profiles(scenario):
"strict": ("DEPENDENT", "UNKNOWN")}[gateway.mode]
if (actual["mapping"], actual["inbound"]) != expected:
raise RuntimeError(f"{owner.name}/{protocol}: expected {expected}, detector returned {actual}")
if expected[0] == "EIM":
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":