diff --git a/doc/nat_detection.txt b/doc/nat_detection.txt index d4312a79..98b06057 100644 --- a/doc/nat_detection.txt +++ b/doc/nat_detection.txt @@ -91,6 +91,8 @@ facet не превращается в ложный STRICT. Потери, оши исчерпания цикла CM вместе с reverse, а UNKNOWN/приватный адрес и SOCKS5 его не дают. Активный раунд не перезапускается; повторные ошибки объединяются, минимум пять минут между такими перепроверками одного рабочего сокета. Последний успех сохраняется. +Начальные измерения имеют короткий приоритет перед фоновыми проверками; завершённый +частичный результат освобождает его, чтобы соседи могли выполнить свои наблюдения. Если выбранная группа ещё доставляет управление пиру, ему передаётся подписанный зашифрованный RECHECK размером 23 байта (команда/версия/socket ID/endpoint). Пир проверяет членство отправителя и совпадение endpoint со своим текущим сокетом; diff --git a/tests/stream_netns/nat_scenario.py b/tests/stream_netns/nat_scenario.py index a4c597ca..8093225e 100644 --- a/tests/stream_netns/nat_scenario.py +++ b/tests/stream_netns/nat_scenario.py @@ -69,6 +69,10 @@ def check_profiles(scenario): if not scenario.args.tcp_only: contacts = set(profile(agents[owner.name].rpc("stats"), "udp")["contacts"]) helper = next((node for node in public if node.name not in {owner.name, observer.name} and node.ip not in contacts), None) + initial = profile(agents[owner.name].rpc("stats"), "udp") + if not helper and initial["source"] == "ETCP" and initial["mapping"] != "UNKNOWN": + # Автоматическая детекция могла уже завершиться, пока узел помогал предыдущему owner. + helper = next(node for node in public if node.name not in {owner.name, observer.name}) if not helper: raise RuntimeError(f"{owner.name}: no independent helper remains before cold measurement; contacts={contacts}") connect(observer, helper)