18 KiB
Подписанная запись узла
TOPO_NODE.timestamp — версия всей подписанной записи, а не отдельного адреса.
Узел-владелец назначает Unix microseconds, строго больше предыдущей сохранённой
версии. Имя, тип, активность, адреса, метаданные сокетов, REALITY и подпись
заменяются вместе. Timestamp входит в подпись. Групповые пути и подсети
остаются в TOPO_GROUP_NODE и не являются частью общей записи узла.
В NODEINFO секции подсетей присутствуют только при TOPO_FLAG_SEND_SUBNETS.
Без флага оба счётчика подсетей равны нулю; противоречащий этому заголовок отклоняется.
Parser проверяет точную длину пакета и проходит все присутствующие секции независимо
от запрошенных out_*: например, out_subnets=NULL отключает создание объектов подсетей,
но сохраняет проверку префиксов и правильное положение следующего hop-list.
Выходы выдаются только после полного успеха; при ошибке они обнулены, частичные объекты
освобождены. Подпись и допустимость пути проверяются после структурного разбора.
Получатель проверяет подпись до изменения реестра или маршрутов. Более свежая
запись заменяет старую целиком, включая пустые списки адресов. Старые записи
игнорируются; разные подписанные записи с одинаковым timestamp — конфликт.
Повторное BGP-объявление может обновить путь, но не содержимое общей записи.
8-битный ver сохраняется как диагностический счётчик; свежесть определяется
только timestamp.
Локальный узел группы готовится до удаления прежнего local_node: сравниваются сами адреса
и префиксы подсетей, включая замену при прежнем количестве. При отказе памяти, подписи или
конфликте IPv4-маршрутов прежние узел и маршруты сохраняются. Подсети CHAT не публикуются.
topo_group_update_my_nodeinfo возвращает число IPv4-подсетей, для CHAT — 0, при ошибке — -1;
рассылку подготовленного NODEINFO выполняет вызывающий.
Живая телефонная занятость передаётся без изменения структуры NODEINFO: в существующем
flags бит 0x04 означает известное состояние, 0x08 — занятость звонком.
Это групповые флаги TOPO_GROUP_NODE.phone_status, как флаг рации 0x02;
они не входят в подпись общей записи и используются только для цвета трубки в GUI.
Без разрешения Android, при выключенной публикации и после ухода узла статус неизвестен.
Решение о начале вызова принимает адресат по локальному состоянию ОС, а не по BGP-подсказке.
Изменения публикуются обычной новой версией NODEINFO во всех CHAT-группах; отдельного опроса и таймера нет.
Профиль endpoint в SOCKMETA содержит независимые mapping и inbound, источник source.
ID и время измерения не передаются: последний успешный профиль действует до
явного изменения endpoint или подтверждённого нового результата. Повторная
проверка с тем же результатом не меняет NODEINFO. Все поля SOCKMETA — отдельные байты;
границы списков metadata/адресов/REALITY также входят в каноническую подпись.
Алгоритм и условия неопределённости описаны в doc/nat_detection.txt.
source=ETCP обозначает результат детектора рабочего endpoint. Одно UDP-наблюдение может дать
адрес TOPO_ADDR_NAT при mapping=UNKNOWN, inbound=UNKNOWN: это NAT-кандидат
для пробного подключения, а не адрес интерфейса или подтверждённый открытый вход.
Общий внешний адрес подтверждает EIM; strict не публикует универсальный NAT-адрес.
Source входит в подпись; SOCKMETA занимает 5 байт. source=STUN означает предварительный
EIM? для того же локального IP/netif с неизвестным входом; измерение ETCP приоритетно.
Владелец снимает STUN-оценку после TTL. Внешний адрес тестового порта не публикуется:
NAT-кандидат по-прежнему требует наблюдения рабочего сокета.
Для TCP достаточно одного публичного пира: внешний IP с неизвестным входящим
портом публикуется как TOPO_ADDR_NAT с port=0, mapping=UNKNOWN, inbound=UNKNOWN.
NCD пропускает нулевые порты. Непубличный локальный TCP IP сразу означает NAT;
совпавший с наблюдением публичный IP даёт UNCHANGED без подтверждения открытого входа.
Таблица node_snapshots хранит полную сериализованную запись без группового
окружения. node_addresses остаётся проекцией для поиска и отображения, а не
источником для восстановления подписанной записи. При наличии сохранённого
snapshot загрузка использует его. Локальная публикация сохраняется до рассылки;
ошибка сохранения не разрешает публикацию новой версии.
В проекции адресов сохраняются nat_mapping, nat_inbound, nat_source, address_kind.
Уникальность определяется endpoint и видом адреса,
а не отображаемым классом NAT. Старый производный формат пересоздаётся;
bootstrap-загрузка без подписанного snapshot не подтверждает NAT-профиль.
Такая загрузка требует хотя бы одного адреса; отказ SQL/памяти освобождает все частичные списки
и возвращает NULL. Наличие валидного snapshot с пустым списком адресов, напротив, допустимо.
При удалении старых nat_evidence/nat_checked адреса и REALITY сохраняются для bootstrap,
старые профили сбрасываются, сериализованные snapshots инвалидируются.
Максимальные timestamp snapshots сохраняются, чтобы новая локальная публикация
не откатывала версию даже при коррекции часов. Чат и ключи миграция не изменяет.
Timestamp 0 обозначает bootstrap-сведения без подписи (конфиг, приглашение, начальные адреса). Они могут помочь установить первое соединение, но не заменяют принятую подписанную запись. По BGP принимаются подписанные записи с ненулевым timestamp. Формат NODEINFO изменён без обратной совместимости.
После принятия BGP-записи node_conn_direct_update_node применяет адреса к
существующему NCD-соединению. Отсутствующее соединение этот вызов не создаёт.
Путь подготавливается до изменения общего реестра и групповых подсетей: отказ
памяти сохраняет прежнюю запись, ссылки и пути. После commit IPv4-таблица
перестраивается для этого узла, включая удаление старых подсетей при пустом
новом наборе. Конфликт подсетей или OOM установки маршрутов логируется,
новый набор не устанавливается, прежние маршруты удаляются; ошибка запрещает
READY сессии. Принятая подписанная запись и пути при этом могут сохраниться.
Линки дополняются как при UP, так и до установления соединения; повторная
доставка не создаёт дубликаты UDP/TCP. Работающие линки не закрываются только
из-за смены списка адресов. NCD удерживает ссылку на общую запись, пока
обслуживает соединение, и использует её при восстановлении.
Для обхода адресов NCD делает отдельную копию: callbacks транспорта могут заменить
списки в общем реестре до завершения обхода. Удержание ссылки на запись сохраняет
сам объект, но не старые списки. Обновление, открытие и добавление нового сокета
используют общий подбор разрешённых пар: family, UDP/TCP, локальная политика,
NAT-профиль и обязательные метаданные REALITY. IPv6 link-local требует netif_index.
Новый сокет обслуживает NCD-транспорты вне FIN_WAIT; случайный выбор одного адреса не используется.
node_conn_direct_open_node сверяет переданную запись с реестром/БД. Более
свежая подписанная запись принимается целиком, старая уступает уже известной.
Bootstrap-адреса не перекрывают подписанную запись даже при DOWN.
LAN discovery предоставляет отдельные адреса подключения и не меняет подписанный снимок.
Открытое объявление рабочих сокетов и ответ на проверку имеют Ed25519-подпись;
публичные ключи в этих пакетах отсутствуют. Получатель использует известный ключ
из registry/SQLite, а неизвестное устройство остаётся только в списке discovery.
Подписываются весь заголовок local_transport (magic/version/type, длина, IDs/эпохи, request=0) и тело: nonce(16), family(4/6), IP(16), количество сокетов, затем socket_id/protocol/port(BE)/generation(BE) каждого сокета. IPv4 дополнен нулевыми байтами. Подписанный IP обязан совпасть с источником UDP; рабочий порт отдельный от discovery-порта. Публикуются принимающие IP/interface обычные сокеты, без PRIVATE, probe и SOCKS UDP; до 32 endpoint на один IP, без фрагментации.
Объявление само не разрешает NCD использовать адрес. Только для узла без прямого UP и живого BGP-пути во всех группах получатель отправляет случайный nonce; подписанный ответ связывает его с обоими node_id и эпохами discovery. Ответ принимается однократно в течение 3 секунд. Это проверка свежести без часов/NTP: старые подписи, эпохи или случайные поколения сокетов сами по себе свежесть не доказывают. После проверки адрес действует 30 секунд по монотонным часам и только на интерфейсе приёма (включая IPv6 scope). Повтор объявления/ответа не продлевает срок без нового nonce.
NCD использует эти кандидаты при проверке совместимости, открытии/обновлении и появлении локального сокета. Если сохранены только ключи, идентичность загружается из nodes без требования старого адреса. LAN не создаёт NCD handle автоматически. Неподтверждённые ETCP-пробы очищаются после истечения кандидата/остановки discovery; после успешного handshake линк живёт по обычным правилам транспорта. REALITY требует метаданных из известного снимка; прямые LAN TCP-пробы при глобальном SOCKS выключены. Диагностика: discovery для подписи/nonce/адресов, etcp для NCD-проб и их удаления.
ID узла проверяется по public_key. Ошибка запуска возвращает NULL handle и не снимает прежний FIN_WAIT. Отдельно принятый snapshot и уже добавленные линки при частичной ошибке могут сохраниться; новый handle публикуется после подготовки ресурсов. Явный specific_sock задаёт локальную политику, сохраняя проверки family/протокола/цели/REALITY; автоматическое появление сокета использует обычную политику подбора.
Конфигурационные подключения UTUN используют один адаптер
init_client_connections для старта и reload. Все адреса, TCP и REALITY
передаются NCD; адаптер не создаёт ETCP-линки. Новый набор handles открывается
до освобождения старого; при ошибке старые handles сохраняются. Конфигурация
удерживает один handle на клиента, независимо от количества его адресов.
Включение конфигурационного пира в UTUN-группу выполняет callback NCD UP.
Само событие транспортного UP не включает соединение в UTUN-группу автоматически.
Входящий JOIN остаётся явным запросом
участия. При пересоздании UTUN-группы учитываются её конфигурационные handles.
Удаление клиента отправляет LEAVE_GROUP и освобождает удержание UTUN-группы
с обеих сторон. Другие владельцы NCD сохраняют транспорт. Поздние NODEINFO и
WITHDRAW от отсоединённого UTUN-пира игнорируются, чтобы не вернуть его маршруты.
LEAVE ждёт backpressure через queue waiter и временно удерживает собственный
NCD handle. Закрытие транспорта отменяет ожидание; новое участие в группе
делает ожидающий LEAVE устаревшим, и он не отправляется.
Причина удаления пира передаётся явно: только потеря транспорта запускает
recovery каскадных маршрутов. Локальный/удалённый LEAVE и удаление битой
member-записи не должны восстанавливать намеренно закрытое участие.
REALITY использует полное SNI до 255 символов; размер поля в записи узла согласован с транспортом. Если локального TCP listen-сокета нет, NCD может создать исходящий TCP-линк с выбором локального адреса операционной системой.