316 lines
31 KiB
Plaintext
316 lines
31 KiB
Plaintext
Детектор свойств endpoint
|
||
========================
|
||
|
||
Владелец рабочего сокета единственный выбирает и публикует его профиль.
|
||
Помощники возвращают наблюдения и выполняют прямые испытания. Профиль относится
|
||
к протоколу, семейству, фактическому локальному IP:port и поколению сокета.
|
||
PUBLIC/NAT/PRIVATE/LOCAL — политика конфигурации, а не результат измерения.
|
||
source=ETCP означает измерение рабочего endpoint; source=STUN — резервную оценку
|
||
по его IP/netif через другой порт. Успешный ETCP имеет приоритет до явного сброса endpoint.
|
||
|
||
Модель: src/routing_layer/address_profile.h.
|
||
mapping: UNKNOWN, UNCHANGED, EIM, DEPENDENT.
|
||
inbound: UNKNOWN, OPEN, CONTACT_REQUIRED.
|
||
source: NONE, ETCP, STUN.
|
||
round/generation: локальные ID раунда и жизненного цикла endpoint; в NODEINFO не входят.
|
||
|
||
UNCHANGED означает совпадение наблюдаемых и локального адресов на испытанных
|
||
направлениях. Оно не доказывает свободный вход. EIM означает одинаковый внешний
|
||
endpoint на испытанных разных IP и портах; оно также не доказывает свободный вход.
|
||
DEPENDENT означает различие mapping на испытанных назначениях, без общего
|
||
внешнего адреса. Дальнейшее разделение ADM/APDM не используется для выбора NCD.
|
||
|
||
UDP-раунд
|
||
---------
|
||
|
||
1. Владелец A рассматривает узлы всех активных UTUN/CHAT-групп и выбирает одну группу
|
||
с маршрутами к наблюдателю B и отдельному помощнику C. Следующие хопы управления
|
||
должны быть READY. Прямые ETCP/UDP-линки к B/C не обязательны; B нужен доступный UDP endpoint.
|
||
Через проверяемый сокет S выполняет OBSERVE. Ответ содержит фактический
|
||
source IP:port, наблюдаемый B, и исходный тег S/generation/round/group.
|
||
2. A выбирает C по подписанному NODEINFO выбранной группы. Его рабочий UDP-сокет
|
||
задаёт интерфейс/семейство; C использует два отдельных probe_only порта,
|
||
чтобы помощь другому узлу не открывала фильтр его собственного рабочего endpoint.
|
||
C может находиться за NAT, независимо от политики PUBLIC/NAT и собственного профиля.
|
||
3. Управление RUN/RESULT идёт A -> B -> C через router в одной группе с подписью
|
||
и шифрованием. C сначала отправляет ADDRESS_CHECK с primary-порта к endpoint A,
|
||
реально наблюдавшемуся B. Аутентичный ingress на S до исходящего контакта подтверждает OPEN.
|
||
PONG возвращает наблюдаемый внешний endpoint C. При отсутствии PONG C узнаёт mapping
|
||
своего primary-порта через B, после чего выполняется контролируемый PRE из трёх проб.
|
||
4. Если внешний IP C совпадает с IP B или S ранее контактировал с C, A пробует
|
||
другие пары и группы. Совпадающие Node ID и разные порты одного IP не дают
|
||
независимого IP-наблюдения. При отсутствии второго IP результат неполный.
|
||
При выборе пары сначала сохраняется возможность свежего помощника; ранее контактировавшая
|
||
пара используется для mapping после исчерпания свежих. Отчёт о PONG не заменяет ingress на S.
|
||
5. После первого испытания S выполняет OBSERVE к primary C. При различии mapping
|
||
с B уже известен DEPENDENT. Иначе C отдельно измеряет внешний endpoint второго порта
|
||
через A; если фильтр A мешает — через B. S выполняет OBSERVE к этому endpoint.
|
||
Оба порта C скрыты из 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 через 30 минут.
|
||
Успешный профиль не протухает по таймеру. Ошибка перепроверки сохраняет прежний результат
|
||
и редкий период; неизвестный inbound при том же mapping/endpoint сохраняет прежний ETCP inbound.
|
||
Свойства разных источников не объединяются в один ETCP-профиль. Смена адреса отменяет
|
||
старый раунд, сбрасывает профиль и назначает скорую проверку. Таймеры используют monotonic timebase.
|
||
|
||
После таймаута NCD перепроверка назначается только для реально отправленных native UDP INIT
|
||
к OPEN endpoint из текущего подписанного NODEINFO. CONTACT допускает такой повод после
|
||
исчерпания цикла CM вместе с reverse, а UNKNOWN/приватный адрес и SOCKS5 его не дают.
|
||
Активный раунд не перезапускается; повторные ошибки объединяются, минимум пять минут
|
||
между такими перепроверками одного рабочего сокета. Последний успех сохраняется.
|
||
Начальные измерения имеют короткий приоритет перед фоновыми проверками; завершённый
|
||
частичный результат освобождает его, чтобы соседи могли выполнить свои наблюдения.
|
||
Если выбранная группа ещё доставляет управление пиру, ему передаётся подписанный
|
||
зашифрованный RECHECK размером 23 байта (команда/версия/socket ID/endpoint).
|
||
Пир проверяет членство отправителя и совпадение endpoint со своим текущим сокетом;
|
||
тот же лимит действует на удалённые запросы. ID раунда и время в NODEINFO не возвращаются.
|
||
|
||
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-наблюдения данного владельца.
|
||
Участники и ключи берутся из подписанного реестра выбранной группы. SOURCE читается
|
||
из проверенного router-заголовка, не из ETCP next hop. Управление занимает 136 байт,
|
||
не содержит списков исключений и копий ключей. Размер, версия, enum и ненулевые
|
||
group/generation/round/token проверяются до обработки. Числа BE, IP — сетевые байты.
|
||
|
||
На смене адреса, закрытии сокета, reset/DELETE транспорта и остановке ядра
|
||
отменяются собственные раунды и связанные helper jobs. Поздние ответы не
|
||
применяются к новому socket generation даже при повторном ID и IP:port.
|
||
Между попытками разных пар локальный статус cycling отличает паузу от завершения измерения.
|
||
Удаление выбранной группы отменяет её раунды, jobs и наблюдения до закрытия router-каналов;
|
||
другие группы и последний успешный профиль сохраняются. Детектор останавливается
|
||
до разборки групп, сокетов и соединений.
|
||
|
||
Публикация и NCD
|
||
---------------
|
||
|
||
Профиль публикуется только владельцем в общей подписанной записи NODEINFO.
|
||
Списки и их границы входят в подпись; mapping/inbound/source
|
||
сохраняются и загружаются вместе. node_addresses — производная проекция:
|
||
nat_mapping, nat_inbound, nat_source, address_kind.
|
||
При изменении формата старый кэш адресов пересоздаётся; старый NAT_TYPE не
|
||
интерпретируется как новая модель. При сокращении SOCKMETA старые snapshots инвалидируются
|
||
с сохранением timestamp; адреса и REALITY остаются bootstrap-сведениями. Записи чата не изменяются.
|
||
|
||
Общий 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/порт. Утечки/таймеры/сокеты проверяются.
|
||
Недостаточная UTUN-группа и достаточная CHAT-группа проверяются отдельно с TCP next hop
|
||
и отсутствием прямого транспорта A-C. Неудачные native INIT проверяют отсутствие
|
||
лишних перепроверок для UNKNOWN, ожидание reverse для CONTACT и пятиминутный лимит.
|
||
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.
|