diff --git a/tests/stream_netns/README.md b/tests/stream_netns/README.md index 0c2d3d67..9feffafa 100644 --- a/tests/stream_netns/README.md +++ b/tests/stream_netns/README.md @@ -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, diff --git a/tests/stream_netns/stun_scenario.py b/tests/stream_netns/stun_scenario.py index 07e6ef6d..dd84accf 100644 --- a/tests/stream_netns/stun_scenario.py +++ b/tests/stream_netns/stun_scenario.py @@ -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}