You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
27 KiB
290 lines
27 KiB
Детектор свойств endpoint |
|
======================== |
|
|
|
Владелец рабочего сокета единственный выбирает и публикует его профиль. |
|
Помощники возвращают наблюдения и выполняют прямые испытания. Профиль относится |
|
к протоколу, семейству, фактическому локальному IP:port и поколению сокета. |
|
PUBLIC/NAT/PRIVATE/LOCAL — политика конфигурации, а не результат измерения. |
|
source=ETCP означает измерение рабочего endpoint; source=STUN — резервную оценку |
|
по его IP/netif через другой порт. Измеренный ETCP имеет приоритет до истечения TTL. |
|
|
|
Модель: src/routing_layer/address_profile.h. |
|
mapping: UNKNOWN, UNCHANGED, EIM, DEPENDENT. |
|
inbound: UNKNOWN, OPEN, CONTACT_REQUIRED. |
|
source: NONE, ETCP, STUN. |
|
evidence_id: случайный 64-битный ID завершённого раунда владельца. |
|
checked_at: время измерения владельца, Unix microseconds. |
|
generation: случайный 64-битный ID жизненного цикла локального endpoint. |
|
|
|
UNCHANGED означает совпадение наблюдаемых и локального адресов на испытанных |
|
направлениях. Оно не доказывает свободный вход. EIM означает одинаковый внешний |
|
endpoint на испытанных разных IP и портах; оно также не доказывает свободный вход. |
|
DEPENDENT означает различие mapping на испытанных назначениях, без общего |
|
внешнего адреса. Дальнейшее разделение ADM/APDM не используется для выбора NCD. |
|
|
|
UDP-раунд |
|
--------- |
|
|
|
1. Владелец A выбирает живого наблюдателя B с доступным native UDP endpoint. |
|
Через проверяемый сокет S выполняет OBSERVE. Ответ содержит фактический |
|
source IP:port, наблюдаемый B, и исходный тег S/generation/round. |
|
2. Через уже установленный ETCP к B запрашивает поиск помощника C. |
|
B выбирает C из своих работающих UDP-пиров и возвращает его ключ и endpoint. |
|
Собственный подтверждённый NAT-профиль B/C не требуется: иначе bootstrap |
|
зависел бы от уже завершённой детекции. Пригодность подтверждают реальные пробы. |
|
3. Если внешний IP C совпадает с IP B, A исключает C и командует B искать |
|
следующего помощника. Совпадающие Node ID и разные порты одного IP не дают |
|
независимого IP-наблюдения. При отсутствии второго IP результат неполный. |
|
4. Для входящего испытания выбирается C, с IP которого S ранее не контактировал. |
|
Все команды C идут A -> B -> C. До первого испытания A не шлёт UDP к C. |
|
C отправляет три аутентифицированные ADDRESS_CHECK к endpoint из наблюдения B. |
|
A регистрирует фактический ingress на S; один отчёт о PONG не заменяет ingress. |
|
5. После завершения первого испытания S выполняет OBSERVE к основному порту C, |
|
затем к другому UDP-порту того же помощника. Вспомогательный порт принадлежит |
|
наблюдателю, скрыт из NODEINFO и исключён из NCD. Проверяемый S не меняется. |
|
6. Если первый ingress отсутствовал, C здоров и mapping к C совпал с mapping к B, |
|
после исходящего контакта выполняется второе входящее испытание от того же C. |
|
Контролируемый переход до/после контакта даёт CONTACT_REQUIRED. |
|
7. S повторяет исходное наблюдение у B. Изменение этого mapping во время раунда |
|
даёт UNKNOWN/address_changed и инвалидирует прежний общий внешний адрес. |
|
8. При совпадении наблюдений B/C/C-alt выбирается UNCHANGED или EIM; различие |
|
назначений даёт DEPENDENT. Первый чистый ingress даёт OPEN; подтверждённый |
|
переход после контакта при неизменном endpoint — CONTACT_REQUIRED. |
|
|
|
Если независимых от истории C больше нет, уже контактировавший C может измерить |
|
mapping. Входящая часть остаётся UNKNOWN/contact_history. Отказ проверки одного |
|
facet не превращается в ложный STRICT. Потери, ошибка отправки, отсутствие |
|
помощника, разрыв управления и неправильный ответ имеют отдельную причину. |
|
|
|
Контакты и повторы |
|
----------------- |
|
|
|
История IP назначения учитывает INIT, DATA, ACK, keepalive, PING/PONG и другие |
|
отправки с S в двух общих точках UDP-send. Любая неожиданная отправка к C до |
|
получения первого ingress делает входящее испытание загрязнённым. |
|
|
|
История записывается в nat_contacts общей SQLite один раз на новый IP. Ключ — |
|
семейство и фактический локальный IP:port. Перезапуск на том же endpoint её не |
|
очищает. Максимум 256 IP на endpoint; переполнение/ошибка сохранения отмечаются |
|
постоянным маркером неполной истории. Неизвестная история запрещает вывод OPEN |
|
по испытанию, которое может использовать старое разрешение фильтра. |
|
|
|
История характеризует наблюдаемый приложением трафик. Удаление БД, использование |
|
того же tuple другим процессом или изменение NAT вне наблюдаемого раунда нельзя |
|
превратить в доказательство полного отсутствия прежнего состояния. Результаты |
|
остаются подсказками для попыток, подтверждение связи даёт handshake с нужным ключом. |
|
|
|
Один активный раунд на S. Таймаут пробы — max(1 секунда, 4*RTT), не более 3 секунд; |
|
три попытки, бюджет раунда 30 секунд. Повтор после ошибки через 30 секунд или |
|
после появления подходящего транспорта; штатный refresh через 180 секунд. |
|
Срок фактов — 300 секунд. Таймеры владельца используют monotonic timebase; |
|
checked_at не обновляется повторным NODEINFO и не зависит от ответа сервера NTP. |
|
При несогласованных часах получатель использует осторожные попытки, не отказ. |
|
|
|
TCP и прокси |
|
------------- |
|
|
|
TCP inbound проверяется отдельным STCP handshake к объявленному настоящему |
|
listener с проверкой ключа владельца. Его target должен присутствовать в |
|
подписанном snapshot. Внешний порт исходящего TCP-соединения не рекламируется |
|
как listener. Без публичного кандидата профиль остаётся UNKNOWN/no_listener. |
|
UDP-факты не переносятся на TCP. Входящий TCP source port не определяет профиль |
|
listener удалённого клиента. SOCKS5 UDP не классифицируется как native endpoint. |
|
|
|
Корреляция и владение |
|
-------------------- |
|
|
|
Транспортный PONG сопоставляется по криптографически случайному nonce, ожидаемому |
|
ключу, source IP:port, ID и поколению принимающего сокета. Входящее испытание |
|
дополнительно проверяет владельца, round, step и token. Перед callback PING |
|
удаляется из pending. Отмена не вызывает callback и не интерпретирует void* arg |
|
как контекст другого типа. Произвольный выбор первого IPv4/TCP-сокета запрещён. |
|
|
|
Broker принимает RUN только для записанного им UDP-наблюдения данного владельца. |
|
Размер wire, версия, enum, границы списка исключений и ненулевые поколения/раунды |
|
проверяются до обработки. Числа новых сообщений — BE, IP — сетевые байты. |
|
owner_key и helper_key имеют разные поля и никогда не меняют значение по роли. |
|
|
|
На смене адреса, закрытии сокета, reset/DELETE транспорта и остановке ядра |
|
отменяются собственные раунды и связанные helper jobs. Поздние ответы не |
|
применяются к новому socket generation даже при повторном ID и IP:port. |
|
Детектор останавливается до разборки групп, сокетов и соединений. |
|
|
|
Публикация и NCD |
|
--------------- |
|
|
|
Профиль публикуется только владельцем в общей подписанной записи NODEINFO. |
|
Списки и их границы входят в подпись; mapping/inbound/evidence/checked_at |
|
сохраняются и загружаются вместе. node_addresses — производная проекция: |
|
nat_mapping, nat_inbound, nat_evidence, nat_checked, address_kind. |
|
При изменении формата старый кэш адресов пересоздаётся; старый NAT_TYPE не |
|
интерпретируется как новая модель. Подписанные snapshots и записи чата не удаляются. |
|
|
|
Общий NAT-адрес рекламируется только после EIM. DEPENDENT не рекламирует |
|
универсальный внешний endpoint. NTP.reachable не управляет рекламой адресов. |
|
Ошибки публикации логируются; детектор повторяет сохранение/публикацию. |
|
|
|
NCD различает OPEN, CONTACT и непроверенные публичные кандидаты. UNKNOWN допускает |
|
ограниченные попытки. Контактирующие EIM используют обычные встречные INIT-пробы |
|
NCD и reverse. Одинаковый внешний IP не запрещает hairpin и не доказывает общую |
|
LAN; частные адреса проверяются маршрутизацией и аутентификацией нужного пира. |
|
Профиль не закрывает уже работающий транспорт. Признак PUBLIC конфигурации |
|
может влиять на очередность попыток CM, но не назначает подтверждённый NAT-тип. |
|
Стриминг сохраняет raw NCD + REVERSE_REQ через группу, без routed DATA fallback. |
|
|
|
Проверки |
|
-------- |
|
|
|
test_route_ping: реальные UDP-наблюдения, эталонные байты IPv4/IPv6, неправильный |
|
round, аутентичный PONG чужого ключа, старое поколение, отмена без callback. |
|
test_nat_detection: разные socket ID, два рабочих UDP endpoint, TCP listener, |
|
замена помощника с тем же IP, повтор по старому контакту, подпись/SQLite профиля, |
|
закрытие S посреди раунда и повторный ID/порт. Утечки/таймеры/сокеты проверяются. |
|
test_sock_match: независимость политики от фактов, UNKNOWN, CONTACT, давность, |
|
частные адреса и разрешённая попытка hairpin. |
|
|
|
tests/stream_netns --nat-only: 16 настоящих uTun с 10 NAT, проверка всех UDP/TCP |
|
профилей и точного собственного внешнего адреса. --nat-check включает эту фазу |
|
до обычных JOIN/Merkle/BGP/streaming. Главный network namespace не меняется. |
|
|
|
Резервное определение типа NAT через STUN: реализация IPv4 2026-10-12 |
|
------------------------------------------------------------------ |
|
|
|
Уточнение пользователя: STUN группирует рабочие сокеты по одинаковым IP/netif, |
|
создаёт аналогичный UDP-сокет с другим портом и определяет тип NAT для группы. |
|
Когда ETCP не может выполнить проверку из-за нехватки узлов, используется STUN. |
|
Это заменяет прежнее предложение использовать STUN непосредственно на ETCP fd. |
|
Резервная схема реализована для native IPv4 UDP. IPv6 остаётся отдельным этапом. |
|
|
|
1. Реестр уникальных путей |
|
|
|
Ключ группы — (address family, фактический local IP, netif_index), без порта, |
|
имени и socket ID. Несколько рабочих портов одного IP/netif дают одну группу. |
|
Одинаковый IP на разных netif и разные IP одного netif — разные группы. |
|
netif=0 сохраняет смысл автоматической маршрутизации, не приравнивается |
|
произвольно к явно привязанному интерфейсу. Wildcard не становится отдельным IP: |
|
сначала нужен фактический адрес пути. Неопределённый путь получает UNKNOWN. |
|
|
|
Сканировать native UDP, исключать probe_only, SOCKS5 и only_local. TCP listener |
|
сохраняет отдельную проверку STCP: UDP-результат ему не присваивается. |
|
Начать с IPv4, уже поддержанного stun_nat; IPv6 требует отдельного этапа: |
|
XOR-MAPPED IPv6, IPv6 server/config/DNS и scope/interface, без переноса IPv4-фактов. |
|
|
|
Выбрать представителя группы, скопировать фактические bind IP/device/mark/FIB. |
|
Порты исходных сокетов не копировать; проба владеет собственным fd/reader. |
|
Разные mark/FIB/device внутри одного ключа означают неоднозначный путь: |
|
группа получает UNKNOWN/incompatible_route_policy с логом, а не случайный |
|
результат первого сокета. Ключ IP/netif при этом не усложняется. |
|
|
|
2. Измерение и кэш STUN |
|
|
|
Одна активная проба и один результат на группу, независимо от числа её сокетов. |
|
Тестовый порт случайный, отличается от рабочих портов. В пределах жизни ядра |
|
не переиспользовать ранее измеренные порты на этом IP/netif; при исчерпании |
|
не выполнять заведомо загрязнённое входящее испытание, вернуть явную причину. |
|
Самостоятельно закрывать только пробный fd, отменять его DNS и таймеры. |
|
|
|
STUN запускается при появлении группы, при наличии enabled и настроенных серверов. |
|
Разные имена серверов с одинаковым IP не считаются независимыми наблюдателями. |
|
На чистом тестовом порту сначала проверять filtering, потом mapping и стабильность |
|
исходного наблюдения. Переносить в кэш только завершённые пригодные свойства; |
|
потери, ошибки, отсутствие RFC5780 и сомнительный CHANGE дают UNKNOWN/причину. |
|
|
|
В результате хранить ключ группы, её generation, случайный evidence ID, |
|
test IP:port, фактические серверы, mapped адреса, mapping/filtering, причины, |
|
время измерения monotonic и Unix microseconds. TTL=300с, refresh=180с, |
|
повтор неполной проверки через 30с; темп новых транзакций остаётся ограниченным. |
|
Применение готового кэша к новому сокету не обновляет время измерения. |
|
|
|
3. Выбор результата NAT_DETECTION |
|
|
|
NAT_DETECTION остаётся единственным владельцем эффективного socket profile, |
|
nat_addr и публикации NODEINFO. STUN предоставляет кэш по ключу группы, |
|
NAT_DETECTION проверяет его готовность/инвалидацию раз в секунду; сам STUN |
|
не меняет профили рабочих сокетов. |
|
Реестр групп не держит указателей на рабочие сокеты: потребитель проверяет |
|
собственные socket generation и актуальную принадлежность группе перед применением. |
|
|
|
Хранить отдельно собственный результат ETCP и выбранный эффективный профиль. |
|
Добавить profile source: NONE / ETCP / STUN, в том числе в подпись, wire, |
|
SQLite и диагностические API. STUN означает резервную оценку по интерфейсу, |
|
ETCP — измерение рабочего endpoint. Подписывает публикацию только сам узел. |
|
|
|
Приоритет: действующий измеренный ETCP -> пригодный STUN при невозможности |
|
ETCP из-за отсутствия наблюдателя/независимого помощника -> UNKNOWN. |
|
Для STUN использовать реальные NO_OBSERVER/NO_HELPER, не общее число узлов: |
|
третий узел может быть offline, несовместимым или иметь тот же IP наблюдателя. |
|
После исчерпания замены таких помощников разрешать резервное применение. |
|
Пока STUN не готов, оставить UNKNOWN или ещё действующий профиль ETCP. |
|
|
|
Частичный, но измеренный ETCP не заменять целиком STUN: DEPENDENT с неизвестным |
|
ingress является полезным результатом. В первой версии не смешивать facets |
|
разных источников. Неудача нового раунда не стирает ещё действующий ETCP. |
|
Появление подходящего помощника ускоряет повтор ETCP; его успешный результат |
|
заменяет резервный без ожидания TTL. Не сбрасывать ETCP при завершении STUN. |
|
|
|
4. Преобразование и использование типа |
|
|
|
RFC5780 EIM -> EIM; отсутствие трансляции в согласованном измерении -> UNCHANGED; |
|
ADM/APDM и устойчивое расхождение обычных серверов -> DEPENDENT. |
|
EIF -> OPEN; ADF/APDF -> CONTACT_REQUIRED; неизвестный facet остаётся UNKNOWN. |
|
Это преобразование применяется с source=STUN и описывает резервную оценку. |
|
Обычный MATCH остаётся неполной диагностикой, сам по себе не назначает EIM. |
|
Для полного определения типа необходимы доступные серверы с RFC5780. |
|
|
|
Тип STUN помогает выбирать прямые/reverse попытки, но не доказывает их успех. |
|
Различное поведение NAT для рабочих и тестовых портов допустимо: источник |
|
оценки виден в логах и метаданных, реальные факты ETCP имеют приоритет. |
|
Не запрещать LAN/hairpin или закрывать работающую связь по резервной оценке. |
|
NCD не объявляет рабочий endpoint недоступным только на основании STUN. |
|
|
|
mapped IP:port тестового сокета никогда не копировать в nat_addr рабочего. |
|
Не подставлять туда рабочий local port: NAT может изменить его независимо. |
|
Общий внешний адрес рабочего сокета публиковать только с самостоятельным |
|
подтверждением его EIM/endpoint средствами ETCP. При наличии одного наблюдателя |
|
его адрес остаётся наблюдением для данного назначения, не общим NAT-адресом. |
|
|
|
5. Жизненный цикл и диагностика |
|
|
|
Добавление/удаление дубликата не повторяет STUN для неизменившейся группы. |
|
Исчезновение последнего рабочего сокета удаляет группу и отменяет пробу. |
|
Смена IP/interface/параметров маршрутизации инвалидирует соответствующий кэш; |
|
смена маршрута без смены IP и явный recheck запускают новое измерение. |
|
Generation относится к пути и раунду STUN, а не к имени представителя. |
|
Поздние ответы не применяются к пересозданной группе. Reload/stop отменяют всё |
|
до освобождения config/сокетов; порядок уничтожения не вызывает публикацию. |
|
|
|
INFO: создание/удаление группы, начало/результат пробы, выбор source, возврат |
|
к ETCP. DEBUG: объединённые дубликаты, transaction/generation/время/причины. |
|
WARN/ERROR: неподходящие помощники, неоднозначный путь, timeout/ошибка/несогласованность. |
|
В статусе доступны эффективный профиль, оба результата и причина выбора STUN. |
|
|
|
Этапы реализации: (1) группировка и API STUN по ключу IP/netif, |
|
(2) generation/TTL/обновление и завершение проб, |
|
(3) source в модели/подписи/wire/SQLite/статусе, |
|
(4) выбор резерва и возврат к ETCP, независимое хранение внешних адресов, |
|
(5) локальные и изолированные сетевые тесты. Изменять ETCP UDP demux не требуется. |
|
|
|
План тестов |
|
----------- |
|
|
|
- Два порта одного IP/netif: одна проба, применение резерва обоим сокетам. |
|
Разные IP/netif: отдельные пробы; разные mark/FIB внутри группы: явный UNKNOWN. |
|
- Смена представителя и удаление одного дубликата не теряют результат; удаление |
|
последнего, новая группа и поздний ответ проверяют владение и generation. |
|
- Нет узлов / один наблюдатель / все помощники за одним IP: STUN fallback. |
|
Живой независимый помощник: ETCP; появление/исчезновение, TTL и обе очередности |
|
завершения проверяют приоритет. Частичный ETCP не затирается полным STUN. |
|
- RFC5780 EIM/EIF, EIM/ADF, EIM/APDF, ADM/APDM; обычный MATCH; недоступные |
|
альтернативы, ignored CHANGE, DNS, потери, нестабильность и ограничение повторов. |
|
- NAT по-разному обрабатывает тестовый и рабочий порты: резерв помечен STUN, |
|
реальный ETCP его заменяет; тестовый mapped port не попадает в NODEINFO/NCD. |
|
- Смена маршрута без смены IP, refresh, reload, stop, callbacks и утечки. |
|
Общий evidence/time кэша не омолаживается при новом socket или NODEINFO. |
|
- В tests/stream_netns разместить RFC5780-серверы с двумя IP/двумя портами, |
|
сценарий двух uTun, затем добавить третьего. Проверить переключение источника |
|
типа при RTT/jitter/loss; всю сетевую настройку выполнять в отдельных namespace. |
|
|
|
Основания: https://www.rfc-editor.org/rfc/rfc5780#section-4.1 (выбор тестового порта |
|
и ограниченность переноса результата), #section-4.3/#section-4.4 (классы). |
|
Приоритет и применение резерва — согласованный проект uTun, не гарантия RFC.
|
|
|