tests: verify STUN estimates and ETCP promotion in isolated networks

This commit is contained in:
evgeny committed 2026-10-17 18:48:06 +02:00
1 parent 8983026625
commit 7a40ba536e
2 files changed
+25 -12

No files matched your search

+5 -3
View File
@@ -32,9 +32,11 @@ python3 tests/stream_netns/run.py --stun-only --output /tmp/stun-interface-netns
По очереди проверяются n03 (cone), n14 (restricted) и n05 (dependent mapping).
Два рабочих порта одного IP/netif используют одну STUN-пробу. Сначала у владельца
нет ETCP-пиров, затем появляется один наблюдатель, затем независимый помощник.
Проверяются source=NONE до измерения рабочего порта и source=ETCP после него;
STUN остаётся отдельной диагностикой. Тестовый внешний порт не попадает в рекламу
рабочего сокета. JOIN/стриминг в этом режиме не запускаются.
Для EIM сначала проверяется предварительный source=STUN с inbound=UNKNOWN,
после измерения рабочего порта — source=ETCP с проверенной доступностью входящих.
Зависимый STUN-mapping не назначает предварительный EIM?. Один наблюдатель сохраняет
рабочий внешний адрес; тестовый внешний порт не попадает в рекламу рабочего сокета.
JOIN/стриминг в этом режиме не запускаются.
Случайные тестовые порты получают тот же класс NAT; правила для рабочего 32000 сохраняются.
Для испытания случайных портов активен один клиент каждого выбранного NAT-шлюза.
Расширенная классификация restricted/dependent включает реальные таймауты CHANGE,
+20 -9
View File
@@ -1,4 +1,4 @@
"""STUN другого порта остаётся диагностикой; рабочий профиль назначает независимый ETCP-раунд."""
"""STUN даёт предварительный EIM? того же IP/netif; ETCP уточняет профиль рабочего порта."""
import json
from pathlib import Path
@@ -51,9 +51,14 @@ def check_stun(scenario):
expected_filtering = {"EIF"} if inbound == "OPEN" else {"ADF", "APDF"}
if udp["stun"]["mapping"] not in expected_mapping or udp["stun"]["filtering"] not in expected_filtering:
raise RuntimeError(f"{owner_name}: STUN diagnostic class mismatch: {udp}")
if any(item["source"] != "NONE" or item["mapping"] != "UNKNOWN" or item["inbound"] != "UNKNOWN"
for item in (udp, duplicate)):
raise RuntimeError(f"{owner_name}: STUN diagnostics changed a working endpoint: {udp}, {duplicate}")
estimated = mapping == "EIM"
expected_source, expected_map = ("STUN", "EIM") if estimated else ("NONE", "UNKNOWN")
data = scenario.wait(f"stun-tentative-{owner_name}", lambda statistics:
all((profile(statistics[owner_name], name)["source"],
profile(statistics[owner_name], name)["mapping"],
profile(statistics[owner_name], name)["inbound"]) ==
(expected_source, expected_map, "UNKNOWN") for name in ("udp", "duplicate")), 5)
udp, duplicate = profile(data[owner_name], "udp"), profile(data[owner_name], "duplicate")
if udp["stun"]["evidence"] != duplicate["stun"]["evidence"] or udp["stun"]["members"] != 2 or udp["external"]:
raise RuntimeError(f"{owner_name}: duplicated probe or foreign external port: {udp}, {duplicate}")
for name in (observer_name,):
@@ -66,8 +71,12 @@ def check_stun(scenario):
agents[owner_name].rpc("nat_check", socket="udp", observer=agents[observer_name].identity)
data = scenario.wait(f"stun-no-helper-{owner_name}", lambda statistics:
profile(statistics[owner_name], "udp")["reason"] == "no_helper", 40)
if profile(data[owner_name], "udp")["source"] != "NONE":
raise RuntimeError(f"{owner_name}: single observer assigned an unmeasured endpoint profile")
partial = profile(data[owner_name], "udp")
expected_source = "STUN" if estimated else "ETCP"
if (partial["source"], partial["mapping"], partial["inbound"]) != (expected_source, expected_map, "UNKNOWN"):
raise RuntimeError(f"{owner_name}: single-observer profile mismatch: {partial}")
if not partial["external"]:
raise RuntimeError(f"{owner_name}: single observer did not preserve working external address")
agents[helper_name] = Agent(network, nodes[helper_name], Path(scenario.args.binary).resolve(), scenario.args.seed,
False, False, True)
agents[observer_name].rpc("nat_link", snapshot=agents[helper_name].rpc("snapshot")["snapshot"], socket="udp")
@@ -75,10 +84,12 @@ def check_stun(scenario):
agents[helper_name].identity in statistics[observer_name]["group_ready"] and
agents[observer_name].identity in statistics[helper_name]["group_ready"], 20)
agents[owner_name].rpc("nat_check", socket="udp", observer=agents[observer_name].identity)
data = scenario.wait(f"stun-etcp-promotion-{owner_name}", lambda statistics:
profile(statistics[owner_name], "udp")["source"] == "ETCP", 40)
measured = profile(data[owner_name], "udp")
expected = ("DEPENDENT", "UNKNOWN") if gateway.mode == "strict" else (mapping, inbound)
data = scenario.wait(f"stun-etcp-promotion-{owner_name}", lambda statistics:
(profile(statistics[owner_name], "udp")["source"],
profile(statistics[owner_name], "udp")["mapping"],
profile(statistics[owner_name], "udp")["inbound"]) == ("ETCP", *expected), 40)
measured = profile(data[owner_name], "udp")
if (measured["mapping"], measured["inbound"]) != expected:
raise RuntimeError(f"{owner_name}: ETCP class mismatch: {measured}")
verified[owner_name] = {"stun": udp, "etcp": measured}