docs: explain NAT detection through its main idea and steps
This commit is contained in:
1 parent
d70d6cb66a
commit
eb3dd7f237
1 file changed
+50
-145
+50
-145
@@ -1,161 +1,66 @@
|
||||
Детектор рабочего UDP endpoint
|
||||
==============================
|
||||
Как работает детектор NAT
|
||||
========================
|
||||
|
||||
Владелец сокета единственный выбирает и публикует его профиль. Он относится
|
||||
к протоколу, семейству, фактическому локальному IP:port и поколению сокета.
|
||||
PUBLIC/NAT/PRIVATE/LOCAL — политика конфигурации, не измеренный NAT-тип.
|
||||
Детектор выясняет две вещи: какой внешний IP:порт получает рабочий UDP-сокет
|
||||
при обращении к разным IP и проходят ли к нему пакеты от незнакомого IP.
|
||||
Проверяется именно сокет, через который работает приложение.
|
||||
|
||||
Независимые факты в src/routing_layer/address_profile.h:
|
||||
mapping: UNKNOWN, UNCHANGED, EIM, DEPENDENT;
|
||||
inbound: UNKNOWN, OPEN, CONTACT_REQUIRED;
|
||||
source: ETCP — измерен рабочий endpoint.
|
||||
Нужны три узла: A проверяет себя, B наблюдает его внешний адрес,
|
||||
C помогает проверить входящие пакеты. Команды и результаты передаются
|
||||
по уже работающей сети uTun, в том числе через TCP или промежуточные узлы.
|
||||
Сами тестовые пакеты идут напрямую по UDP.
|
||||
|
||||
Практические классы: strict — стабильные разные mapping к разным IP;
|
||||
eim open — одинаковый mapping и полученная чистая проба незнакомого входа;
|
||||
eim filtered — одинаковый mapping и вход, полученный только после контакта.
|
||||
UNCHANGED отличается от EIM отсутствием трансляции IP:port.
|
||||
DEPENDENT не означает запрета всех входящих. Дополнительные порты одного IP
|
||||
не сравниваются. Разные Node ID за одним IP не дают независимого испытания.
|
||||
OPEN подтверждается пакетом; CONTACT_REQUIRED — оценка по серии до/после:
|
||||
потерю первых UDP-проб нельзя абсолютно отличить от их блокировки.
|
||||
Таймаут, отсутствие помощника и противоречия не означают strict.
|
||||
Порядок проверки
|
||||
---------------
|
||||
|
||||
Участники и каналы
|
||||
------------------
|
||||
1. A отправляет пакет узлу B. Тот сообщает, с какого IP:порта его получил.
|
||||
Ответ идёт по управляющему соединению, поэтому блокировка прямого
|
||||
UDP-ответа не мешает узнать внешний адрес A.
|
||||
|
||||
S — рабочий сокет владельца A; B — наблюдатель; C — помощник с другим IP.
|
||||
Выбираются все активные UTUN/CHAT-группы с READY-маршрутами управления;
|
||||
прямые UDP-линки A-C не обязательны. Управление допускает транзит и TCP.
|
||||
Перебираются все подходящие UDP endpoint узла, включая адрес работающего линка.
|
||||
Таймаут одного адреса не исключает остальные адреса того же узла.
|
||||
2. Через B узел A просит C отправить несколько пакетов на этот внешний адрес.
|
||||
До этого A не должен отправлять ничего на IP узла C с проверяемого сокета.
|
||||
Приложение хранит историю таких отправок и учитывает новые во время проверки.
|
||||
На входящие тестовые пакеты A автоматически не отвечает.
|
||||
|
||||
OBSERVE принимается по UDP, фактический source IP:port возвращается через
|
||||
подписанный зашифрованный router-отчёт OBSERVATION. Прямой PONG не требуется.
|
||||
Получатель проверяет публичность source IP: локальный/немаршрутизируемый
|
||||
источник не подходит для интернет-проверки. OBSERVATION возвращает отказ,
|
||||
наблюдение не сохраняется; владелец переходит к следующему кандидату.
|
||||
Отдельный короткий UDP-обмен с B поддерживает mapping и контролирует входящий путь.
|
||||
Его отсутствие запрещает отрицательный вывод о filtering, но не стирает mapping.
|
||||
Обычное LAN-наблюдение INIT_RESPONSE сохраняется в линке и не сбрасывает
|
||||
интернет-профиль сокета. Локальный путь управления сам по себе допустим.
|
||||
3. Получатель проверяет настоящий адрес отправителя каждого тестового пакета.
|
||||
Локальный или другой непубличный IP не подходит для интернет-проверки.
|
||||
Для проверки незнакомого входа также не подходят IP узла B, собственный
|
||||
внешний IP узла A и IP, на который A уже отправлял пакеты.
|
||||
Если помощник не подходит, A выбирает другого. Разные узлы с одним IP
|
||||
не считаются разными источниками. Проверяются также подлинность пакета
|
||||
и принадлежность текущей попытке, чтобы чужой или старый пакет не влиял на результат.
|
||||
|
||||
У source-сокета C один отдельный probe_only UDP-сокет помощника. Он не входит
|
||||
в NODEINFO/NCD и не открывает фильтр собственного рабочего endpoint C.
|
||||
Дополнительного сравнительного порта нет.
|
||||
4. Затем A сам отправляет пакет C и узнаёт, какой внешний IP:порт видит C.
|
||||
Если входящие до этого не проходили и внешний адрес совпал с увиденным B,
|
||||
C повторяет отправку с того же IP:порта на исходный внешний адрес A.
|
||||
Так проверяется, открыл ли исходящий контакт путь для входящих.
|
||||
Параллельный обмен с B подтверждает, что UDP-связь в целом работает.
|
||||
|
||||
UDP-раунд
|
||||
---------
|
||||
5. A повторяет измерение внешнего адреса через B и C. Результат принимается,
|
||||
только если адрес для каждого направления остаётся стабильным.
|
||||
Сравниваются разные IP назначения; другие порты того же IP не проверяются.
|
||||
|
||||
1. S отправляет B односторонний OBSERVE; через управление получает M_B.
|
||||
2. A просит B делегировать C входящее испытание на M_B. B разрешает делегацию
|
||||
только для действительного записанного наблюдения этого владельца и раунда.
|
||||
3. C отправляет начальную серию входящих проб, без автоматического UDP-ответа.
|
||||
Затем через B измеряет mapping своего отдельного сокета; отчёт также идёт
|
||||
через router. Начальная серия могла уже подтвердить OPEN.
|
||||
4. Публичность и незнакомость проверяет получатель S по фактическому source IP.
|
||||
Локальный/немаршрутизируемый source немедленно исключает помощника из этой попытки.
|
||||
Проверка идентичности, round и token предшествует отказу; чужой или поздний
|
||||
пакет не может переключить кандидата. IP B,
|
||||
собственный внешний IP S (hairpin) и прежний исходящий контакт не подходят:
|
||||
A запрашивает другого помощника. Объявленный IP и Node ID не заменяют проверку.
|
||||
5. Если начального входа нет, C выполняет полную серию PRE до исходящего
|
||||
контакта S с C. После отчёта выдерживается окно доставки последней пробы:
|
||||
управляющее сообщение может обогнать UDP.
|
||||
6. S фиксирует границу контакта и отправляет OBSERVE на C. Через управление
|
||||
получает M_C. Source полученной аутентичной входящей пробы имеет приоритет
|
||||
перед предварительно объявленным endpoint помощника.
|
||||
7. При M_C = M_B, полном чистом PRE без входа и исправном контроле B выполняется
|
||||
POST: серия с того же endpoint C на исходный M_B. Source POST должен совпасть
|
||||
с endpoint, к которому обращался S. История проверяется до намеренного контакта,
|
||||
включая посторонние отправки во время PRE. Смена source не подтверждает filtering.
|
||||
8. S повторяет OBSERVE к B и C. Изменение наблюдений одного направления
|
||||
инвалидирует профиль и даёт address_changed. Этапы разных состояний не смешиваются.
|
||||
9. Стабильные M_B != M_C дают DEPENDENT; равенство — UNCHANGED/EIM.
|
||||
Чистый вход до контакта даёт OPEN. Полный PRE без входа, успешный POST
|
||||
и свежий прямой контроль B дают CONTACT_REQUIRED. Незавершённый ingress
|
||||
оставляет inbound UNKNOWN, сохраняя измеренный mapping.
|
||||
|
||||
Новый помощник получает новый round/token. Поздние пакеты предыдущей попытки
|
||||
не используются. После исчерпания свежих IP допускается mapping-раунд с прежним
|
||||
помощником, без обновления незнакомой входящей доступности.
|
||||
|
||||
История, время и отмена
|
||||
Что означает результат
|
||||
----------------------
|
||||
|
||||
История по IP учитывает INIT, DATA, ACK, keepalive, PING/PONG и другие отправки
|
||||
в двух общих точках UDP-send. Проверяется на момент первого ingress, не только
|
||||
на старте. Случайная отправка к C во время PRE делает опыт загрязнённым.
|
||||
Для POST сохраняется граница истории до намеренного OBSERVE к C.
|
||||
strict — B и C стабильно видят разные внешние IP:порты узла A.
|
||||
Единого внешнего адреса для подключения с любого IP нет.
|
||||
|
||||
nat_contacts общей SQLite хранит семейство и фактический локальный IP:port
|
||||
и один элемент на IP назначения. Перезапуск на том же endpoint историю не очищает.
|
||||
Максимум 256 IP; переполнение, повреждение и ошибка записи отмечают её неполной.
|
||||
Неполная история запрещает доказательство незнакомости. История описывает только
|
||||
наблюдаемый приложением трафик: удаление БД или чужое использование tuple
|
||||
не доказывает отсутствия состояния NAT.
|
||||
eim open — B и C видят одинаковый внешний IP:порт, и пакет от C пришёл
|
||||
ещё до исходящего контакта A с его IP.
|
||||
|
||||
Начальный раунд имеет короткий приоритет перед фоновыми NCD-пробами.
|
||||
Уже известные IP не блокируются; работающий транспорт сохраняется.
|
||||
Все операции выполняются в uasync-потоке. Один активный раунд на S.
|
||||
Ожидание пробы — max(1 секунда, 4*RTT к B), серия — три отправки.
|
||||
Цикл перебора ограничен 30 секундами. Истечение бюджета не назначает NAT-тип.
|
||||
Повтор неполного EIM/inbound — через 30 секунд; новый подходящий endpoint
|
||||
может разбудить раньше. Полный профиль обновляется через 30 минут.
|
||||
Неудачная перепроверка сохраняет прежний успех. Частичный результат того же
|
||||
mapping/endpoint сохраняет прежний inbound, но не переносит его с другого endpoint.
|
||||
eim filtered — внешний IP:порт одинаковый, серия пакетов до контакта не прошла,
|
||||
а после исходящего контакта пакеты с того же адреса C начали приходить.
|
||||
Это оценка: потерю UDP-пакетов нельзя абсолютно отличить от блокировки.
|
||||
|
||||
Смена адреса сбрасывает профиль и отменяет раунд. Закрытие сокета, удаление группы
|
||||
и остановка ядра отменяют UDP-пробы, heartbeat и helper jobs до закрытия транспорта.
|
||||
Generation и токены исключают применение позднего ответа к новому socket ID/fd.
|
||||
Неудачные реально отправленные native INIT к подписанному OPEN назначают recheck;
|
||||
CONTACT допускает его после исчерпания reverse. Ошибки объединяются, минимум
|
||||
пять минут между такими перепроверками рабочего сокета.
|
||||
Если внешний IP:порт совпадает с локальным, отдельно отмечается отсутствие
|
||||
трансляции адреса. Доступность входящих всё равно проверяется.
|
||||
|
||||
Wire v4
|
||||
-------
|
||||
Если данных недостаточно, соответствующая часть результата остаётся UNKNOWN.
|
||||
Известный тип NAT можно сохранить, даже если входящие проверить не удалось.
|
||||
Таймаут и отсутствие подходящих узлов сами по себе не означают strict.
|
||||
|
||||
Команды: NAT_RUN, NAT_RESULT, NAT_RECHECK, NAT_OBSERVATION.
|
||||
SOURCE/group берутся из проверенного router-заголовка, не из физического next hop.
|
||||
Разрешены только участники выбранной группы; управление подписано и зашифровано.
|
||||
|
||||
Тег OBSERVE: "NAT4UDP!", socket ID, generation, round, group.
|
||||
Односторонняя проба добавляет этап (1 байт) и token (8 байт).
|
||||
OBSERVATION проверяется по ожидаемому наблюдателю, socket ID/generation,
|
||||
group, round, этапу и token. Каждый повторный UDP-запрос получает новый token.
|
||||
Ingress дополнительно проверяет ключ C и фактический source endpoint.
|
||||
Wire содержит только uint8_t; числа BE, адреса — сетевые байты.
|
||||
|
||||
ETCP_PING_FLAG_NO_REPLY разрешён для OBSERVE/ADDRESS_CHECK с полным NAT-тегом.
|
||||
Он запрещает автоматический PONG: приём пробы не создаёт исходящий контакт.
|
||||
Обычные транспортные PING/PONG сохраняются. v3 несовместим с v4:
|
||||
участвующие в измерении узлы обновляются вместе.
|
||||
|
||||
Публикация, STUN и TCP
|
||||
---------------------
|
||||
|
||||
Владелец публикует профиль в подписанном NODEINFO. Универсальный внешний
|
||||
endpoint рекламируется после EIM; UNCHANGED использует собственный адрес.
|
||||
DEPENDENT не рекламирует универсальный NAT endpoint. NCD использует профиль
|
||||
для выбора попыток; работоспособность конкретной связи подтверждает handshake.
|
||||
|
||||
STUN другого порта остаётся диагностикой IP/netif и никогда не назначает
|
||||
профиль рабочему S. Результаты доступны через stun_nat API.
|
||||
UDP-профиль не переносится на TCP. TCP inbound проверяется STCP handshake
|
||||
к объявленному listener с проверкой ключа. Без публичного кандидата TCP
|
||||
остаётся UNKNOWN/no_listener. SOCKS UDP не классифицируется как native endpoint.
|
||||
|
||||
Проверки
|
||||
--------
|
||||
|
||||
test_route_ping: UDP-наблюдения, IPv4/IPv6 encoding, чужой ключ, неверный round,
|
||||
старое поколение и отмена без callback.
|
||||
test_nat_detection: несколько endpoint, одинаковые IP узлов, подпись/SQLite,
|
||||
TCP listener, транзитная CHAT-группа, recheck и teardown.
|
||||
test_nat_detection_faults (Linux): реальные UDP/router-пакеты, потерянные PONG,
|
||||
PRE/POST, поздний PRE после контакта, посторонняя отправка, пропавший отчёт,
|
||||
разные/нестабильные mapping, совпавший IP B, hairpin и перебор IP наблюдателя;
|
||||
локальные source OBSERVE/ingress, переход к другому помощнику, чужой token
|
||||
и сохранение интернет-профиля после наблюдения LAN.
|
||||
Только часы тестового poll ускоряются; сокеты, шифрование и протокол настоящие.
|
||||
tests/stream_netns/run.py --nat-only: 16 ядер, 6 настоящих NAT-шлюзов,
|
||||
EIM open/filtered/strict. Главный network namespace проверяется на неизменность.
|
||||
Соединения по LAN работают независимо и не сбрасывают интернет-профиль.
|
||||
Если доступны только локальные узлы, определить интернет-NAT нельзя.
|
||||
Результат относится к проверенному UDP-сокету: проверка STUN с другого порта
|
||||
и работа TCP не заменяют это измерение.
|
||||
Reference in new issue
Block a user