9.7 KiB
Независимая 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 показывает происхождение оценки.
Конфигурация
[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 и два порта на серверной стороне, доступные во всех четырёх комбинациях.
- Обычный Binding получает внешний адрес и альтернативный endpoint.
- CHANGE-REQUEST с флагами IP+port проверяет EIF.
- Если ответ не пришёл, контрольный Binding проверяет живость сервера и стабильность mapping.
- CHANGE-REQUEST только port различает ADF и APDF; после таймаута снова выполняется контрольный Binding.
- Перед публикацией ADF/APDF альтернативные endpoints проверяются обычными Binding-запросами. Недоступный альтернативный endpoint даёт UNKNOWN, а не ложную строгую фильтрацию.
- После filtering проверяется mapping: альтернативный IP с первоначальным портом, затем при необходимости альтернативный IP с альтернативным портом. Сравнение различает EIM, ADM и APDM.
- Второй сервер и повтор первого проверяют независимую пару и стабильность результата.
Все 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, STUN RFC 8489.