Measure owned endpoint mapping and inbound reachability separately
This commit is contained in:
1 parent
b54307a0df
commit
ff69552a61
51 files changed
+2620
-2483
No files matched your search
@@ -159,7 +159,7 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
|
||||
- session_id: 32 бита, big-endian (должен совпасть с запросом клиента; если нет — клиент игнорирует)
|
||||
- mtu_local: uint16_t локальный MTU сервера
|
||||
- local_link_id: uint8_t идентификатор канала сервера
|
||||
- remote_socket_id: uint8_t (копируется из запроса)
|
||||
- remote_socket_id: uint8_t (ID серверного сокета; ID клиентского сокета известен серверу из запроса)
|
||||
- only_local: uint8_t флаг сервера
|
||||
- type: uint8_t тип сокета сервера
|
||||
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер — для NAT traversal)
|
||||
@@ -176,6 +176,12 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
|
||||
[0x07] [flags(1)] [node_id(8 BE)] [nonce(8 BE)] [resp_data_len(2 BE)] [response_data...] [padding...]
|
||||
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
|
||||
Ответ на PING. nonce копируется из запроса для сопоставления.
|
||||
Nonce генерируется CSPRNG. Проверяются также ключ отправителя, source IP:port,
|
||||
ID и поколение принимающего сокета. Флаг OBSERVE (0x04) запрашивает ответ
|
||||
address(19) || tag(25): family(4/6), IP(16), port(2, BE), NAT2UDP!(8),
|
||||
socket_id(1), generation(8, BE), round(8, BE).
|
||||
ADDRESS_CHECK (0x08) — аутентифицированное входящее испытание endpoint.
|
||||
Модель NAT и алгоритм владельца: doc/nat_detection.txt.
|
||||
|
||||
При получении init получатель пакета должен:
|
||||
- reset ETCP_LINK с этим ip_port если он есть
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
Детектор свойств endpoint
|
||||
========================
|
||||
|
||||
Владелец рабочего сокета единственный вычисляет и публикует его профиль.
|
||||
Помощники возвращают наблюдения и выполняют прямые испытания. Профиль относится
|
||||
к протоколу, семейству, фактическому локальному IP:port и поколению сокета.
|
||||
PUBLIC/NAT/PRIVATE/LOCAL — политика конфигурации, а не результат измерения.
|
||||
|
||||
Модель: src/routing_layer/address_profile.h.
|
||||
mapping: UNKNOWN, UNCHANGED, EIM, DEPENDENT.
|
||||
inbound: UNKNOWN, OPEN, CONTACT_REQUIRED.
|
||||
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 не меняется.
|
||||
@@ -13,12 +13,24 @@
|
||||
8-битный `ver` сохраняется как диагностический счётчик; свежесть определяется
|
||||
только timestamp.
|
||||
|
||||
Профиль endpoint в SOCKMETA содержит независимые `mapping` и `inbound`,
|
||||
`evidence_id` и `checked_at`. Последние относятся к измерению владельца,
|
||||
а не к версии всего NODEINFO: повтор записи или изменение имени не продлевает
|
||||
измерение. Новые поля профиля на проводе кодируются явными BE-байтами;
|
||||
границы списков metadata/адресов/REALITY также входят в каноническую подпись.
|
||||
Алгоритм и условия неопределённости описаны в `doc/nat_detection.txt`.
|
||||
|
||||
Таблица `node_snapshots` хранит полную сериализованную запись без группового
|
||||
окружения. `node_addresses` остаётся проекцией для поиска и отображения, а не
|
||||
источником для восстановления подписанной записи. При наличии сохранённого
|
||||
snapshot загрузка использует его. Локальная публикация сохраняется до рассылки;
|
||||
ошибка сохранения не разрешает публикацию новой версии.
|
||||
|
||||
В проекции адресов сохраняются `nat_mapping`, `nat_inbound`, `nat_evidence`,
|
||||
`nat_checked`, `address_kind`. Уникальность определяется endpoint и видом адреса,
|
||||
а не отображаемым классом NAT. Старый производный формат пересоздаётся;
|
||||
bootstrap-загрузка без подписанного snapshot не подтверждает NAT-профиль.
|
||||
|
||||
Timestamp 0 обозначает bootstrap-сведения без подписи (конфиг, приглашение,
|
||||
начальные адреса). Они могут помочь установить первое соединение, но не
|
||||
заменяют принятую подписанную запись. По BGP принимаются подписанные записи
|
||||
|
||||
Reference in new issue
Block a user