# Независимая STUN-диагностика NAT Модуль `src/transport_layer/stun_nat.c/h` измеряет NAT на уникальных путях существующих IPv4 UDP-сокетов. Ключ группы — семейство, фактический IP и netif; имена и рабочие порты не участвуют. Для каждой группы создаётся один собственный неблокирующий UDP-сокет с тем же локальным IP, привязкой к устройству и Linux SO_MARK / FreeBSD FIB. Тестовый порт выбирается случайно из 49152–65535. Модуль сам регистрирует чтение в uasync, принимает STUN и закрывает свой сокет после измерения. STUN не меняет исходный ETCP fd, его порт и обработчики. NAT_DETECTION использует его оценку типа при NO_OBSERVER/NO_HELPER, помечает source=STUN и публикует подписанный профиль. Действующий результат source=ETCP имеет приоритет, включая частичный. Мapped endpoint тестового порта не переносится в nat_addr или NODEINFO рабочего сокета. Поведение NAT на рабочем порту может отличаться; source показывает происхождение оценки. ## Конфигурация ```ini [stun] enabled=yes server=stun-a.example.org:3478 server=stun-b.example.org:3478 # Дополнительные строки — резервные серверы. [debug] nat=debug ``` Без `enabled=yes` модуль выключен. Без порта используется 3478. Список сохраняет порядок конфига. При включении нужны минимум две строки `server`; для измерения выбираются два ответивших сервера с различными IP. Совпадающие IP после DNS пропускаются. DNS асинхронный, с использованием существующего IPv4 A-резолвера. IPv6, SOCKS UDP и `only_local` пропускаются с явным статусом; TCP не проверяется. ## Результат `stun_nat_get_result(instance, "имя исходного сокета", &result)` копирует локальный снимок: Имя сначала разрешается в текущий рабочий сокет, затем в группу. API ядра `stun_nat_get_socket_result(instance, socket, &result)` дополнительно проверяет текущие IP/netif и параметры маршрутизации. Несколько рабочих портов получают общий снимок. - `local` — тестовый IP:port; `servers[2]` и `mapped[2]` — реальные серверы и наблюдавшиеся внешние адреса; - `translated`: -1 неизвестно, 0 трансляция не наблюдалась, 1 внешний адрес отличается; - `mapping`: UNKNOWN, MATCH, DEPENDENT, EIM, ADM, APDM; - `filtering`: UNKNOWN, EIF, ADF, APDF; - причины неполного результата, статус, время измерения и обнаруженная поддержка RFC 5780. - ключ группы, generation, evidence_id, measured_at_us, количество рабочих сокетов и признак refresh. Обычный STUN даёт MATCH при одинаковых адресах и DEPENDENT при разных. MATCH не доказывает EIM, а обычные Binding-ответы не позволяют определить filtering. Первоначальный сервер опрашивается повторно: смена его mapping во время проверки инвалидирует классификацию как нестабильную. ## RFC 5780 Корректные OTHER-ADDRESS и RESPONSE-ORIGIN включают автоматическую попытку Behavior Discovery. Нужны две публичные IP и два порта на серверной стороне, доступные во всех четырёх комбинациях. 1. Обычный Binding получает внешний адрес и альтернативный endpoint. 2. CHANGE-REQUEST с флагами IP+port проверяет EIF. 3. Если ответ не пришёл, контрольный Binding проверяет живость сервера и стабильность mapping. 4. CHANGE-REQUEST только port различает ADF и APDF; после таймаута снова выполняется контрольный Binding. 5. Перед публикацией ADF/APDF альтернативные endpoints проверяются обычными Binding-запросами. Недоступный альтернативный endpoint даёт UNKNOWN, а не ложную строгую фильтрацию. 6. После filtering проверяется mapping: альтернативный IP с первоначальным портом, затем при необходимости альтернативный IP с альтернативным портом. Сравнение различает EIM, ADM и APDM. 7. Второй сервер и повтор первого проверяют независимую пару и стабильность результата. Все filtering-тесты выполняются до открывающих фильтр отправок на альтернативные endpoints. Собственные предыдущие STUN-отправки учитываются: при пересечении адресов следующего сервера сомнительный тест filtering пропускается. ERROR 420 и игнорирование CHANGE-REQUEST дают UNKNOWN. Аутентификация STUN не реализована: требующий её сервер фиксируется как ошибка, затем используется резервный. Запрос содержит случайный 96-битный transaction ID и FINGERPRINT. Проверяются источник ответа, transaction ID, границы/выравнивание атрибутов, XOR-MAPPED-ADDRESS, RESPONSE-ORIGIN и FINGERPRINT при его наличии. Повторы одной транзакции сохраняют ID: RTO от 500 мс удваивается, всего семь отправок, последнее ожидание 8 секунд. Новые транзакции всех проб ограничены десятью в секунду; ожидания не блокируют event loop. ## Жизненный цикл и проверки Ядро запускает модуль при `utun_core_start`; остановка уничтожает его до сетевых ресурсов и config. Перезагрузка конфига отменяет старые DNS/таймеры и создаёт новый модуль. Раз в три секунды выполняется локальная сверка групп: новые/изменённые проверяются, группа удаляется после исчезновения последнего рабочего сокета. Смена имени/представителя или удаление дубликата не теряет кэш. Несовпадение mark/FIB/device внутри одного ключа даёт UNKNOWN с причиной incompatible routing policy. TTL результата 300 секунд, refresh 180 секунд; неполные/неудачные проверки повторяются через 30 секунд. Во время штатного refresh доступен прежний результат до TTL; его возраст не обновляется. Использованные тестовые порты не переиспользуются в течение жизни ядра, включая reload STUN. История портов освобождается только при окончательном destroy ядра. `stun_nat_recheck(instance)` явно повторяет измерение на новых портах, в том числе после смены маршрута без смены IP. Публичные операции выполняются в uasync-потоке экземпляра. INFO содержит начало измерения и итог с адресами, классификацией и причинами UNKNOWN. DEBUG содержит этапы, transaction ID, повторы, RTT, DNS и очистку; WARN/ERROR описывают аномалии. Linux-тест `test_stun_nat` использует реальные локальные UDP-сокеты и ускоренные часы: обычный STUN, все классы RFC 5780, недоступные альтернативы, потеря запроса, неверные/поздние ответы, резервные серверы, DNS, нестабильный mapping, отмена и повторная проверка, изменение состава сокетов. Каждый сценарий проверяет неизменность исходного ETCP-сокета и освобождение таймеров. Ссылки: [RFC 5780](https://www.rfc-editor.org/rfc/rfc5780), [STUN RFC 8489](https://www.rfc-editor.org/rfc/rfc8489).