51 changed files with 2621 additions and 2484 deletions
@ -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 не меняется. |
||||
@ -0,0 +1,51 @@
|
||||
/* Профиль конкретного endpoint: изменение адреса и доступность входа независимы.
|
||||
* checked_at — время измерения владельца в Unix microseconds, не версия NODEINFO. |
||||
* Профиль помогает выбирать попытки; работоспособность подтверждает транспорт. */ |
||||
#ifndef ADDRESS_PROFILE_H |
||||
#define ADDRESS_PROFILE_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
enum address_mapping { |
||||
ADDRESS_MAP_UNKNOWN, |
||||
ADDRESS_MAP_UNCHANGED, |
||||
ADDRESS_MAP_EIM, |
||||
ADDRESS_MAP_DEPENDENT |
||||
}; |
||||
|
||||
enum address_inbound { |
||||
ADDRESS_IN_UNKNOWN, |
||||
ADDRESS_IN_OPEN, |
||||
ADDRESS_IN_CONTACT |
||||
}; |
||||
|
||||
#define ADDRESS_PROFILE_TTL_US UINT64_C(300000000) |
||||
|
||||
struct address_profile { |
||||
uint8_t mapping; |
||||
uint8_t inbound; |
||||
uint64_t evidence_id; |
||||
uint64_t checked_at; |
||||
}; |
||||
|
||||
static inline const char *address_mapping_name(uint8_t value) { |
||||
static const char *const names[] = {"UNKNOWN", "UNCHANGED", "EIM", "DEPENDENT"}; |
||||
return value <= ADDRESS_MAP_DEPENDENT ? names[value] : "INVALID"; |
||||
} |
||||
|
||||
static inline const char *address_inbound_name(uint8_t value) { |
||||
static const char *const names[] = {"UNKNOWN", "OPEN", "CONTACT_REQUIRED"}; |
||||
return value <= ADDRESS_IN_CONTACT ? names[value] : "INVALID"; |
||||
} |
||||
|
||||
static inline int address_profile_valid(const struct address_profile *profile) { |
||||
return profile && profile->mapping <= ADDRESS_MAP_DEPENDENT && profile->inbound <= ADDRESS_IN_CONTACT && |
||||
((!profile->mapping && !profile->inbound) || (profile->evidence_id && profile->checked_at)); |
||||
} |
||||
|
||||
static inline int address_profile_fresh(const struct address_profile *profile, uint64_t now_us) { |
||||
return profile && profile->evidence_id && profile->checked_at && now_us >= profile->checked_at && |
||||
now_us - profile->checked_at <= ADDRESS_PROFILE_TTL_US; |
||||
} |
||||
|
||||
#endif |
||||
File diff suppressed because it is too large
Load Diff
@ -1,161 +1,61 @@
|
||||
/**
|
||||
* @file nat_detection.h |
||||
* @brief NAT-детекция через третьего пира (STUN-like ping). |
||||
* |
||||
* Самодостаточный модуль на своём ETCP_ID (0x02): |
||||
* сам биндится, сам подписывается на conn_up/conn_down, |
||||
* ищет третий узел прямо в instance->connections, |
||||
* обновляет ETCP_SOCKET/loc_node и broadcast-ит NODEINFO. |
||||
*/ |
||||
/* Владелец измеряет собственный endpoint. Пиры передают наблюдения и выполняют
|
||||
* прямые пробы; управление проверяющим идёт через явно выбранного посредника. |
||||
* Все операции и callbacks выполняются в uasync-потоке ядра. */ |
||||
#ifndef NAT_DETECTION_H |
||||
#define NAT_DETECTION_H |
||||
|
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
#include "../lib/socket_compat.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include "secure_channel.h" |
||||
|
||||
struct UTUN_INSTANCE; |
||||
struct TOPO_GROUP; |
||||
struct ETCP_CONN; |
||||
struct ETCP_LINK; |
||||
struct route_ping_pending; |
||||
|
||||
/* ====== Sub-команды протокола ====== */ |
||||
|
||||
#define NATDET_SUBCMD_PING_REQ 0x07 // сервер → третий узел: "пропингуй IP:port"
|
||||
#define NATDET_SUBCMD_PING_RESP 0x08 // третий узел → сервер: результат пинга
|
||||
#define NATDET_SUBCMD_NAT_INFO 0x09 // сервер → клиент: "твой NAT — EIM/STRICT/DIRECT"
|
||||
#define NATDET_SUBCMD_NAT_CHECK_REQ 0x0A // клиент → сервер: "проверь мой NAT, вот мой interface_addr"
|
||||
|
||||
/* ====== Wire-формат сообщений ====== */ |
||||
|
||||
/** Сервер → третий узел: запрос серии ETCP-пингов на target_ip:target_port */ |
||||
struct NATDET_PING_REQ { |
||||
uint8_t cmd; // ETCP_ID_NAT_DETECTION
|
||||
uint8_t subcmd; // NATDET_SUBCMD_PING_REQ
|
||||
uint16_t reserved; |
||||
uint32_t request_id; // для корреляции запрос/ответ
|
||||
uint8_t count; // число пингов в серии
|
||||
uint8_t socket_id; // сокет для отправки (на третьем узле)
|
||||
uint16_t interval_ms; // интервал между пингами
|
||||
uint16_t timeout_ms; // таймаут одного пинга
|
||||
uint8_t target_ipv4[4]; // IP цели (network byte order)
|
||||
uint16_t target_port; // порт цели (network byte order)
|
||||
uint8_t pubkey[SC_PUBKEY_SIZE]; // опционально: pubkey цели для шифрования пинга
|
||||
} __attribute__((packed)); |
||||
struct ETCP_SOCKET; |
||||
struct NAT_DETECTION; |
||||
|
||||
/** Третий узел → сервер: агрегированная статистика серии пингов */ |
||||
struct NATDET_PING_RESP { |
||||
uint8_t cmd; |
||||
uint8_t subcmd; // NATDET_SUBCMD_PING_RESP
|
||||
uint16_t reserved; |
||||
uint32_t request_id; |
||||
uint8_t count_sent; // всего отправлено пингов
|
||||
uint8_t count_ok; // успешно (получен PONG)
|
||||
uint16_t avg_rtt; // средний RTT в 0.1ms
|
||||
} __attribute__((packed)); |
||||
|
||||
/** Сервер → клиент: результат определения типа NAT */ |
||||
struct NATDET_MSG_NAT_INFO { |
||||
uint8_t cmd; |
||||
uint8_t subcmd; // NATDET_SUBCMD_NAT_INFO
|
||||
uint8_t socket_id; // ID сокета клиента
|
||||
uint8_t nat_ip[4]; // NAT-адрес клиента (network byte order)
|
||||
uint16_t nat_port; // NAT-порт клиента (network byte order)
|
||||
uint8_t nat_type; // NAT_TYPE_EIM / NAT_TYPE_STRICT / NAT_TYPE_DIRECT
|
||||
} __attribute__((packed)); |
||||
|
||||
/** Клиент → сервер: запрос на проверку NAT для указанного сокета */ |
||||
struct NATDET_MSG_NAT_CHECK_REQ { |
||||
uint8_t cmd; |
||||
uint8_t subcmd; // NATDET_SUBCMD_NAT_CHECK_REQ
|
||||
uint8_t socket_id; // ID сокета который надо проверить
|
||||
uint32_t interface_ip; // IP клиента на интерфейсе (network byte order)
|
||||
uint16_t interface_port; // порт клиента на интерфейсе (network byte order)
|
||||
} __attribute__((packed)); |
||||
|
||||
/* ====== Callback для завершения пинга ====== */ |
||||
|
||||
typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, |
||||
uint8_t count_sent, uint8_t count_ok, void* arg); |
||||
|
||||
/* ====== Контекст NAT-детекции (один на UTUN_INSTANCE) ====== */ |
||||
|
||||
struct NAT_DETECTION { |
||||
struct UTUN_INSTANCE* inst; |
||||
struct route_ping_pending* ping_pending; // список ожидающих PING_RESP ответов
|
||||
uint32_t next_ping_req_id; // монотонный счётчик request_id
|
||||
uint8_t allow_nat_check_local; // разрешить чек для loopback (тесты)
|
||||
enum nat_check_reason { |
||||
NAT_REASON_NONE, NAT_REASON_NO_OBSERVER, NAT_REASON_NO_HELPER, NAT_REASON_CONTROL_LOST, |
||||
NAT_REASON_SEND_ERROR, NAT_REASON_TIMEOUT, NAT_REASON_ADDRESS_CHANGED, NAT_REASON_CONTACT_HISTORY, |
||||
NAT_REASON_PROXY, NAT_REASON_NO_LISTENER, NAT_REASON_CANCELLED, NAT_REASON_INVALID_OBSERVATION, NAT_REASON_DEPENDENT_MAPPING |
||||
}; |
||||
|
||||
/** Аргумент, передаваемый в callback nat_link_check_cb */ |
||||
struct nat_check_arg { |
||||
struct ETCP_LINK* link; |
||||
uint32_t nat_ip; |
||||
uint16_t nat_port; |
||||
struct nat_check_status { |
||||
uint64_t generation, round; |
||||
uint64_t observer, helper; |
||||
uint32_t rounds, helper_reselections; |
||||
uint8_t phase, reason, history_complete; |
||||
}; |
||||
|
||||
/* ==================== ПУБЛИЧНОЕ API ==================== */ |
||||
|
||||
/* -------- Жизненный цикл -------- */ |
||||
|
||||
/** Создать модуль: etcp_bind на 0x02, подписка на new_conn (on_up + on_down). */ |
||||
struct NAT_DETECTION* nat_detection_create(struct UTUN_INSTANCE* inst); |
||||
|
||||
/** Уничтожить: отписаться от new_conn, etcp_unbind, очистить pending пинги.
|
||||
* Вызывается из topo_groups_destroy(). */ |
||||
|
||||
void nat_detection_destroy(struct NAT_DETECTION* nd); |
||||
|
||||
/* -------- Служебные: вызываются другими модулями в ответ на события ETCP -------- */ |
||||
|
||||
/** Линк поднялся (INIT handshake завершён) — запустить для него NAT-чек.
|
||||
* Вызывается из etcp_connections: handle_init_response_server(). */ |
||||
|
||||
void nat_detection_link_ready(struct NAT_DETECTION* nd, struct ETCP_LINK* link); |
||||
|
||||
/** Отменить все pending NAT-пинги для соединения и сбросить nat_check_status.
|
||||
* Вызывается из etcp.c при реините и etcp_connections.c при смене адреса линка. */ |
||||
|
||||
void nat_detection_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); |
||||
|
||||
/** Отправить клиенту NAT_INFO — результат DIRECT detection (публичный IP без пинга).
|
||||
* Вызывается из etcp_connections: handle_init_response_server(). */ |
||||
|
||||
void nat_detection_send_nat_info(struct NAT_DETECTION* nd, struct ETCP_CONN* to_conn, |
||||
uint8_t socket_id, uint32_t nat_ip, uint16_t nat_port, |
||||
uint8_t nat_type); |
||||
|
||||
/* -------- Control API -------- */ |
||||
|
||||
/** Принудительная перепроверка NAT для всех линков (etcpmon команда "nat").
|
||||
* Вызывается из control_server. */ |
||||
|
||||
void nat_detection_request_check_all(struct NAT_DETECTION* nd); |
||||
|
||||
/* -------- Тестовые хелперы -------- */ |
||||
|
||||
/** Разрешить NAT-чек для локальных подсетей (127.x, 10.x, 192.168.x).
|
||||
* По умолчанию запрещено — только для тестов где узлы на loopback. */ |
||||
|
||||
void nat_detection_set_allow_local(struct NAT_DETECTION* nd, int allow); |
||||
|
||||
/** Отправить STUN-пинг через BGP-посредника (to_conn) к target_ip:target_port.
|
||||
* pubkey — опционально (NULL = взять из nodeinfo). |
||||
* count пингов, интервал interval_ms, таймаут одного timeout_ms, общий wait_timeout_ms. */ |
||||
|
||||
int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, |
||||
struct ETCP_CONN* to_conn, |
||||
uint32_t target_ip, uint16_t target_port, |
||||
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, |
||||
uint16_t wait_timeout_ms, route_ping_callback_t cb, |
||||
void* arg, const uint8_t* pubkey); |
||||
struct NAT_DETECTION *nat_detection_create(struct UTUN_INSTANCE *instance); |
||||
void nat_detection_destroy(struct NAT_DETECTION *detector); |
||||
void nat_detection_link_ready(struct NAT_DETECTION *detector, struct ETCP_LINK *link); |
||||
void nat_detection_cancel_for_conn(struct NAT_DETECTION *detector, struct ETCP_CONN *connection); |
||||
void nat_detection_socket_removed(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket); |
||||
void nat_detection_note_send(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, |
||||
const struct sockaddr_storage *destination); |
||||
void nat_detection_observed_by_peer(struct NAT_DETECTION *detector, struct ETCP_LINK *link, |
||||
const struct sockaddr_storage *observed); |
||||
void nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, |
||||
const uint8_t peer_key[32], const struct sockaddr_storage *source, |
||||
const uint8_t *tag, size_t length); |
||||
void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, |
||||
const uint8_t peer_key[32], const struct sockaddr_storage *source, |
||||
const uint8_t *tag, size_t length); |
||||
void nat_detection_request_check_all(struct NAT_DETECTION *detector); |
||||
int nat_detection_request_check_socket(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, uint64_t observer); |
||||
void nat_detection_set_allow_local(struct NAT_DETECTION *detector, int allow); |
||||
/* При ручном режиме помощники продолжают работать, раунды запускает request_check_all. */ |
||||
void nat_detection_set_automatic(struct NAT_DETECTION *detector, int automatic); |
||||
int nat_detection_get_status(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, struct nat_check_status *status); |
||||
const char *nat_check_reason_name(uint8_t reason); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif /* NAT_DETECTION_H */ |
||||
|
||||
#endif |
||||
|
||||
@ -1,428 +1,77 @@
|
||||
#include <stdlib.h> |
||||
#include "route_ping.h" |
||||
|
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#ifdef _WIN32 |
||||
#include <winsock2.h> |
||||
#include <ws2tcpip.h> |
||||
#else |
||||
#include <arpa/inet.h> |
||||
#endif |
||||
#include "../lib/platform_compat.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/mem.h" |
||||
#include "utun_instance.h" |
||||
#include "etcp_api.h" |
||||
#include "etcp.h" |
||||
#include "etcp_connections.h" |
||||
#include "topo_node.h" |
||||
#include "topo_group.h" |
||||
#include "route_ping.h" |
||||
#include "../lib/u_async.h" |
||||
|
||||
struct route_ping_series_ctx { |
||||
struct ETCP_CONN* reply_conn; |
||||
uint32_t request_id; |
||||
struct sockaddr_storage target_addr; |
||||
uint8_t pubkey[SC_PUBKEY_SIZE]; |
||||
struct ETCP_SOCKET* local_sock; |
||||
uint8_t count_total; |
||||
uint8_t count_sent; |
||||
uint8_t count_ok; |
||||
uint32_t sum_rtt; /* в 0.1 ms */ |
||||
uint16_t timeout_ms; |
||||
}; |
||||
|
||||
// ==================================== блок обработки запросов на удаленные пинги
|
||||
|
||||
struct route_ping_pending { |
||||
struct route_ping_pending* next; |
||||
struct NAT_DETECTION* nd; |
||||
uint32_t request_id; |
||||
route_ping_callback_t callback; |
||||
void* arg; |
||||
void* timeout_timer; |
||||
uint8_t cancelled; |
||||
struct ETCP_CONN* via_conn; // через кого отправили запрос (third_conn)
|
||||
}; |
||||
|
||||
// если удаленный узел долго не отвечает - вызываем коллбэк по таймауту
|
||||
static void route_ping_pending_timeout(void* arg) { |
||||
struct route_ping_pending* p = (struct route_ping_pending*)arg; |
||||
if (!p) return; |
||||
p->timeout_timer = NULL; |
||||
if (p->cancelled) { |
||||
u_free(p); |
||||
return; |
||||
} |
||||
struct NAT_DETECTION* nd = p->nd; |
||||
struct route_ping_pending** cur = &nd->ping_pending; |
||||
while (*cur) { |
||||
if (*cur == p) { |
||||
*cur = p->next; |
||||
break; |
||||
} |
||||
cur = &(*cur)->next; |
||||
} |
||||
if (p->callback) { |
||||
p->callback(0, 0, 0, 0, p->arg); |
||||
} |
||||
u_free(p); |
||||
} |
||||
|
||||
// прошел ответ "серия пигнов на удаленном узле завершена"
|
||||
void route_ping_handle_resp(struct NAT_DETECTION* nd, struct ETCP_CONN* from_conn, |
||||
const uint8_t* data, size_t len) { |
||||
if (!nd || !from_conn || !data || len < sizeof(struct NATDET_PING_RESP)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); |
||||
return; |
||||
} |
||||
const struct NATDET_PING_RESP* resp = (const struct NATDET_PING_RESP*)data; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "from=%s request_id=%08x sent=%u ok=%u avg_rtt=%u", |
||||
from_conn->log_name, (unsigned)resp->request_id, |
||||
(unsigned)resp->count_sent, (unsigned)resp->count_ok, (unsigned)resp->avg_rtt); |
||||
topo_node_ping_update_rtt(nd->inst->topo_groups, from_conn->peer_node_id, resp->avg_rtt); |
||||
|
||||
struct route_ping_pending** cur = &nd->ping_pending; |
||||
while (*cur) { |
||||
struct route_ping_pending* p = *cur; |
||||
if (p->request_id == resp->request_id) { |
||||
*cur = p->next; |
||||
if (p->timeout_timer) { |
||||
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer); |
||||
p->timeout_timer = NULL; |
||||
} |
||||
if (p->callback) { |
||||
int success = (resp->count_ok > 0) ? 1 : 0; |
||||
p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, p->arg); |
||||
} |
||||
u_free(p); |
||||
return; |
||||
} |
||||
cur = &(*cur)->next; |
||||
} |
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "request_id=%08x not found in pending list", |
||||
(unsigned)resp->request_id); |
||||
} |
||||
|
||||
void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn) { |
||||
if (!nd || !conn) return; |
||||
|
||||
if (!nd->ping_pending) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: ping_pending is NULL for %s", conn->log_name); |
||||
return; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: checking pending for %s", conn->log_name); |
||||
|
||||
int cancelled = 0; |
||||
struct route_ping_pending** cur = &nd->ping_pending; |
||||
while (*cur) { |
||||
struct route_ping_pending* p = *cur; |
||||
struct nat_check_arg* na = (struct nat_check_arg*)p->arg; |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, " checking pending req_id=%08x via=%s link->etcp=%s", |
||||
(unsigned)p->request_id, |
||||
p->via_conn ? p->via_conn->log_name : "NULL", |
||||
(na && na->link && na->link->etcp) ? na->link->etcp->log_name : "NULL"); |
||||
|
||||
if ((na && na->link && na->link->etcp == conn) || p->via_conn == conn) { |
||||
*cur = p->next; |
||||
uint32_t rid = p->request_id; |
||||
if (p->timeout_timer) { |
||||
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer); |
||||
p->timeout_timer = NULL; |
||||
} |
||||
p->cancelled = 1; |
||||
if (na) na->link = NULL; |
||||
u_free(na); |
||||
u_free(p); |
||||
cancelled++; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_conn: cancelled req_id=%08x", (unsigned)rid); |
||||
} else { |
||||
cur = &(*cur)->next; |
||||
} |
||||
} |
||||
|
||||
if (cancelled == 0) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: no pending found for %s", conn->log_name); |
||||
} |
||||
static void write64(uint8_t *out, uint64_t value) { |
||||
for (unsigned i = 0; i < 8; i++) out[i] = (uint8_t)(value >> (56 - i * 8)); |
||||
} |
||||
|
||||
void route_ping_cancel_for_link(struct NAT_DETECTION* nd, struct ETCP_LINK* link) { |
||||
if (!nd || !link) return; |
||||
struct route_ping_pending** cur = &nd->ping_pending; |
||||
while (*cur) { |
||||
struct route_ping_pending* p = *cur; |
||||
struct nat_check_arg* na = (struct nat_check_arg*)p->arg; |
||||
if (na && na->link == link) { |
||||
*cur = p->next; |
||||
uint32_t rid = p->request_id; |
||||
if (p->timeout_timer) { |
||||
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer); |
||||
p->timeout_timer = NULL; |
||||
} |
||||
p->cancelled = 1; |
||||
na->link = NULL; |
||||
u_free(na); |
||||
u_free(p); |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_link: cancelled req_id=%08x link=%p", (unsigned)rid, (void*)link); |
||||
} else { |
||||
cur = &(*cur)->next; |
||||
} |
||||
} |
||||
int route_ping_addr_encode(uint8_t out[ROUTE_ADDR_WIRE_SIZE], const struct sockaddr_storage *address) { |
||||
if (!out || !address) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "address encode: missing argument"); return -1; } |
||||
memset(out, 0, ROUTE_ADDR_WIRE_SIZE); |
||||
if (address->ss_family == AF_INET) { |
||||
const struct sockaddr_in *v4 = (const struct sockaddr_in *)address; |
||||
out[0] = 4; memcpy(out + 1, &v4->sin_addr, 4); memcpy(out + 17, &v4->sin_port, 2); |
||||
} else if (address->ss_family == AF_INET6) { |
||||
const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *)address; |
||||
out[0] = 6; memcpy(out + 1, &v6->sin6_addr, 16); memcpy(out + 17, &v6->sin6_port, 2); |
||||
} else { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "address encode: unsupported family=%d", address->ss_family); return -1; } |
||||
return 0; |
||||
} |
||||
|
||||
void route_ping_destroy_pending(struct NAT_DETECTION* nd) { |
||||
if (!nd) return; |
||||
struct route_ping_pending* p = nd->ping_pending; |
||||
while (p) { |
||||
struct route_ping_pending* next = p->next; |
||||
if (p->timeout_timer) { |
||||
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer); |
||||
p->timeout_timer = NULL; |
||||
} |
||||
p->cancelled = 1; |
||||
u_free(p); |
||||
p = next; |
||||
} |
||||
nd->ping_pending = NULL; |
||||
int route_ping_addr_decode(const uint8_t in[ROUTE_ADDR_WIRE_SIZE], struct sockaddr_storage *address) { |
||||
if (!in || !address) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "address decode: missing argument"); return -1; } |
||||
memset(address, 0, sizeof(*address)); |
||||
if (in[0] == 4) { |
||||
struct sockaddr_in *v4 = (struct sockaddr_in *)address; |
||||
v4->sin_family = AF_INET; memcpy(&v4->sin_addr, in + 1, 4); memcpy(&v4->sin_port, in + 17, 2); |
||||
static const uint8_t zero[12]; |
||||
if (memcmp(in + 5, zero, 12)) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "address decode: noncanonical IPv4"); return -1; } |
||||
} else if (in[0] == 6) { |
||||
struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)address; |
||||
v6->sin6_family = AF_INET6; memcpy(&v6->sin6_addr, in + 1, 16); memcpy(&v6->sin6_port, in + 17, 2); |
||||
} else { DEBUG_WARN(DEBUG_CATEGORY_NAT, "address decode: invalid family=%u", in[0]); return -1; } |
||||
if (!in[17] && !in[18]) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "address decode: zero port"); return -1; } |
||||
return 0; |
||||
} |
||||
|
||||
// ========================================================================
|
||||
|
||||
int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, |
||||
struct ETCP_CONN* to_conn, |
||||
uint32_t target_ip, uint16_t target_port, |
||||
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, |
||||
uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, |
||||
const uint8_t* pubkey) { |
||||
(void)group; |
||||
if (!nd || !to_conn || count == 0 || timeout_ms == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); |
||||
return -1; |
||||
} |
||||
size_t pkt_size = pubkey ? sizeof(struct NATDET_PING_REQ) : offsetof(struct NATDET_PING_REQ, pubkey); |
||||
struct NATDET_PING_REQ* req_pkt = u_calloc(1, pkt_size); |
||||
if (!req_pkt) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc failed"); |
||||
return -2; |
||||
} |
||||
req_pkt->cmd = ETCP_ID_NAT_DETECTION; |
||||
req_pkt->subcmd = NATDET_SUBCMD_PING_REQ; |
||||
uint32_t new_req_id = nd->next_ping_req_id++; |
||||
req_pkt->request_id = new_req_id; |
||||
req_pkt->count = count; |
||||
req_pkt->interval_ms = interval_ms; |
||||
req_pkt->timeout_ms = timeout_ms; |
||||
/* IP уже в network byte order, порт конвертируем */ |
||||
memcpy(req_pkt->target_ipv4, &target_ip, 4); |
||||
req_pkt->target_port = htons(target_port); |
||||
if (pubkey) { |
||||
memcpy(req_pkt->pubkey, pubkey, SC_PUBKEY_SIZE); |
||||
} |
||||
|
||||
struct ll_entry* e = queue_entry_new(0); |
||||
if (!e) { |
||||
u_free(req_pkt); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "queue_entry_new failed"); |
||||
return -3; |
||||
int route_ping_addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b, int include_port) { |
||||
if (!a || !b || a->ss_family != b->ss_family) return 0; |
||||
if (a->ss_family == AF_INET) { |
||||
const struct sockaddr_in *av = (const struct sockaddr_in *)a, *bv = (const struct sockaddr_in *)b; |
||||
return av->sin_addr.s_addr == bv->sin_addr.s_addr && (!include_port || av->sin_port == bv->sin_port); |
||||
} |
||||
e->dgram = (uint8_t*)req_pkt; |
||||
e->len = pkt_size; |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%08x ip=%s port=%u pubkey=%s", |
||||
(unsigned)new_req_id, |
||||
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no"); |
||||
|
||||
int ret = etcp_send(to_conn, e); |
||||
if (ret != 0) { |
||||
u_free(req_pkt); |
||||
queue_entry_free(e); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send failed"); |
||||
return -4; |
||||
if (a->ss_family == AF_INET6) { |
||||
const struct sockaddr_in6 *av = (const struct sockaddr_in6 *)a, *bv = (const struct sockaddr_in6 *)b; |
||||
return !memcmp(&av->sin6_addr, &bv->sin6_addr, 16) && (!include_port || av->sin6_port == bv->sin6_port); |
||||
} |
||||
|
||||
struct route_ping_pending* pending = u_calloc(1, sizeof(struct route_ping_pending)); |
||||
if (!pending) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "pending alloc failed"); |
||||
return -5; |
||||
} |
||||
pending->nd = nd; |
||||
pending->request_id = new_req_id; |
||||
pending->callback = cb; |
||||
pending->arg = arg; |
||||
pending->via_conn = to_conn; |
||||
pending->next = nd->ping_pending; |
||||
nd->ping_pending = pending; |
||||
|
||||
pending->timeout_timer = uasync_set_timeout(nd->inst->ua, wait_timeout_ms * 10, pending, |
||||
route_ping_pending_timeout, "route_ping"); |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
static void route_ping_series_finish(struct route_ping_series_ctx* ctx) { |
||||
if (!ctx || !ctx->reply_conn) { |
||||
u_free(ctx); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "zero args"); |
||||
return; |
||||
} |
||||
|
||||
uint16_t avg_rtt = (ctx->count_ok > 0) |
||||
? (uint16_t)(ctx->sum_rtt / ctx->count_ok) |
||||
: (uint16_t)(ctx->timeout_ms * 10); /* таймаут как "плохо" */ |
||||
|
||||
/* Отправляем ответ по BGP */ |
||||
struct NATDET_PING_RESP* resp = u_calloc(1, sizeof(struct NATDET_PING_RESP)); |
||||
if (resp) { |
||||
resp->cmd = ETCP_ID_NAT_DETECTION; |
||||
resp->subcmd = NATDET_SUBCMD_PING_RESP; |
||||
resp->request_id = ctx->request_id; |
||||
resp->count_sent = ctx->count_sent; |
||||
resp->count_ok = ctx->count_ok; |
||||
resp->avg_rtt = avg_rtt; |
||||
|
||||
struct ll_entry* e = queue_entry_new(0); |
||||
if (e) { |
||||
e->dgram = (uint8_t*)resp; |
||||
e->len = sizeof(struct NATDET_PING_RESP); |
||||
if (etcp_send(ctx->reply_conn, e) != 0) { |
||||
queue_dgram_free(e); queue_entry_free(e); |
||||
} else { |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, |
||||
"PING series done %s request_id=%08x sent=%u ok=%u avg_rtt=%u", |
||||
ctx->reply_conn->log_name, |
||||
(unsigned)ctx->request_id, |
||||
(unsigned)ctx->count_sent, |
||||
(unsigned)ctx->count_ok, |
||||
(unsigned)avg_rtt); |
||||
} |
||||
} else { |
||||
u_free(resp); |
||||
} |
||||
} |
||||
|
||||
u_free(ctx); |
||||
void route_ping_make_tag(uint8_t out[ROUTE_OBSERVE_TAG_SIZE], const struct ETCP_SOCKET *socket, uint64_t round) { |
||||
memcpy(out, "NAT2UDP!", 8); out[8] = socket->sock_id; |
||||
write64(out + 9, socket->generation); write64(out + 17, round); |
||||
} |
||||
|
||||
/* Callback одного пинга из серии */ |
||||
static void route_ping_single_cb(int success, |
||||
uint16_t rtt, |
||||
void* arg, |
||||
uint64_t nonce, |
||||
const uint8_t* resp_data, |
||||
size_t resp_data_len) { |
||||
(void)nonce; (void)resp_data; (void)resp_data_len; |
||||
|
||||
struct route_ping_series_ctx* ctx = (struct route_ping_series_ctx*)arg; |
||||
if (!ctx) return; |
||||
|
||||
/* Обновляем статистику */ |
||||
ctx->count_sent++; |
||||
if (success) { |
||||
ctx->count_ok++; |
||||
ctx->sum_rtt += rtt; |
||||
} |
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "ping cb: ok=%d rtt=%u sent=%d/%d nonce=%016llx", |
||||
success, rtt, ctx->count_sent, ctx->count_total, (unsigned long long)nonce); |
||||
|
||||
/* Если ещё не все пакеты отправлены — сразу шлём следующий */ |
||||
if (ctx->count_sent < ctx->count_total) { |
||||
int ret = etcp_send_ping_to_socket( |
||||
ctx->reply_conn->instance, |
||||
ctx->local_sock, |
||||
ctx->pubkey, |
||||
&ctx->target_addr, |
||||
ctx->timeout_ms, |
||||
route_ping_single_cb, |
||||
ctx, |
||||
NULL, 0, 0); |
||||
|
||||
if (ret != 0) { |
||||
/* Не смогли отправить следующий — завершаем серию досрочно */ |
||||
route_ping_series_finish(ctx); |
||||
} |
||||
/* else: следующий пинг запущен, ждём его callback */ |
||||
} else { |
||||
/* Все пакеты обработаны */ |
||||
route_ping_series_finish(ctx); |
||||
} |
||||
int route_ping_observe(struct ETCP_SOCKET *socket, const uint8_t peer_key[SC_PUBKEY_SIZE], |
||||
const struct sockaddr_storage *peer_address, uint64_t round, int timeout_ms, |
||||
etcp_ping_callback_t callback, void *arg) { |
||||
if (!socket || !socket->generation || !round) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "observe: invalid endpoint/round"); return -1; } |
||||
uint8_t tag[ROUTE_OBSERVE_TAG_SIZE]; route_ping_make_tag(tag, socket, round); |
||||
return etcp_send_ping_to_socket(socket->instance, socket, peer_key, peer_address, timeout_ms, |
||||
callback, arg, tag, sizeof(tag), ETCP_PING_FLAG_OBSERVE); |
||||
} |
||||
|
||||
// Обработчик PING_REQ (нам запросили "пропингуй узел и верни результат")
|
||||
void route_ping_handle_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, |
||||
struct ETCP_CONN* from_conn, |
||||
const uint8_t* data, |
||||
size_t len) { |
||||
if (!from_conn || !nd || !data || len < offsetof(struct NATDET_PING_REQ, pubkey)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: bad args len=%zu", len); |
||||
return; |
||||
} |
||||
|
||||
const struct NATDET_PING_REQ* req_pkt = (const struct NATDET_PING_REQ*)data; |
||||
|
||||
/* Создаём контекст серии */ |
||||
struct route_ping_series_ctx* ctx = u_calloc(1, sizeof(struct route_ping_series_ctx)); |
||||
if (!ctx) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: alloc ctx failed"); |
||||
return; |
||||
} |
||||
|
||||
ctx->reply_conn = from_conn; |
||||
ctx->request_id = req_pkt->request_id; |
||||
ctx->count_total = req_pkt->count; |
||||
ctx->timeout_ms = req_pkt->timeout_ms; |
||||
|
||||
{ |
||||
if (group) { |
||||
struct TOPO_GROUP_NODE* req_nq = topo_node_find_by_id(group, from_conn->peer_node_id); |
||||
if (req_nq) req_nq->connectivity.ping_req_time = get_time_tb(); |
||||
} |
||||
} |
||||
if (len >= sizeof(struct NATDET_PING_REQ)) { |
||||
memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE); |
||||
int route_ping_observation(const uint8_t *data, size_t length, const struct ETCP_SOCKET *socket, |
||||
uint64_t round, struct sockaddr_storage *observed) { |
||||
if (!data || !socket || length != ROUTE_ADDR_WIRE_SIZE + ROUTE_OBSERVE_TAG_SIZE) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "observation: invalid size=%zu", length); return -1; |
||||
} |
||||
|
||||
/* Целевой адрес - в sockaddr храним в network byte order (как в пакете) */ |
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&ctx->target_addr; |
||||
sin->sin_family = AF_INET; |
||||
memcpy(&sin->sin_addr.s_addr, req_pkt->target_ipv4, 4); /* network byte order */ |
||||
sin->sin_port = req_pkt->target_port; /* порт в network byte order */ |
||||
|
||||
/* Ищем первый IPv4-сокет */ |
||||
struct ETCP_SOCKET* ls = (group ? group->instance : nd->inst)->etcp_sockets; |
||||
while (ls) { |
||||
if (ls->local_addr.ss_family == AF_INET) { |
||||
ctx->local_sock = ls; |
||||
break; |
||||
} |
||||
ls = ls->next; |
||||
} |
||||
|
||||
if (!ctx->local_sock) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "ping req: no IPv4 socket"); |
||||
route_ping_series_finish(ctx); /* отправит 0/0 */ |
||||
return; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "PING series start request_id=%08x target=%s:%u count=%u timeout=%u", |
||||
(unsigned)ctx->request_id, ip_to_str(&sin->sin_addr, AF_INET).str, |
||||
ntohs(sin->sin_port), (unsigned)ctx->count_total, (unsigned)ctx->timeout_ms); |
||||
|
||||
/* Запускаем первый пинг (дальше цепочка через callback) */ |
||||
int ret = etcp_send_ping_to_socket( |
||||
(group ? group->instance : nd->inst), |
||||
ctx->local_sock, |
||||
ctx->pubkey, |
||||
&ctx->target_addr, |
||||
ctx->timeout_ms, |
||||
route_ping_single_cb, |
||||
ctx, |
||||
NULL, 0, 0); |
||||
|
||||
if (ret != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: cannot start first ping"); |
||||
route_ping_series_finish(ctx); |
||||
uint8_t tag[ROUTE_OBSERVE_TAG_SIZE]; route_ping_make_tag(tag, socket, round); |
||||
if (memcmp(data + ROUTE_ADDR_WIRE_SIZE, tag, sizeof(tag))) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "observation: stale endpoint generation/round"); return -1; |
||||
} |
||||
return route_ping_addr_decode(data, observed); |
||||
} |
||||
|
||||
@ -1,56 +1,33 @@
|
||||
/**
|
||||
* @file route_ping.h |
||||
* @brief Удалённый пинг через BGP-посредника. |
||||
* |
||||
* A просит B пропинговать C и вернуть сводную статистику: |
||||
* A --PING_REQ--> B --ETCP ping--> C |
||||
* A <--PING_RESP-- B (sent/ok/avg_rtt) |
||||
* |
||||
* Промежуточный узел задаётся ЯВНО в to_conn — вызывающий сам выбирает, |
||||
* через кого слать запрос. Автоподбор посредника (nat_check_find_third_node) |
||||
* находится в nat_detection.c, не здесь. |
||||
*/ |
||||
/* Прямые UDP-наблюдения с указанного рабочего сокета. Управление помощниками —
|
||||
* nat_detection; этот модуль не вычисляет NAT-тип и не выбирает чужой сокет. */ |
||||
#ifndef ROUTE_PING_H |
||||
#define ROUTE_PING_H |
||||
|
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
#include "etcp_connections.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include "nat_detection.h" |
||||
|
||||
/** Запросить у to_conn серию пингов на target_ip:target_port.
|
||||
* cb вызывается всегда ровно 1 раз: |
||||
* success=1 если хоть один пинг успешен (PONG получен) |
||||
* success=0 при общем таймауте или все пинги провалились |
||||
* @param to_conn промежуточный узел — выбирается вызывающим явно */ |
||||
int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, |
||||
struct ETCP_CONN* to_conn, |
||||
uint32_t target_ip, uint16_t target_port, |
||||
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, |
||||
uint16_t wait_timeout_ms, route_ping_callback_t cb, |
||||
void* arg, const uint8_t* pubkey); |
||||
#define ROUTE_ADDR_WIRE_SIZE 19 |
||||
#define ROUTE_OBSERVE_TAG_SIZE 25 |
||||
|
||||
/** Отменить все ожидающие ответы (при остановке NAT_DETECTION). */ |
||||
void route_ping_destroy_pending(struct NAT_DETECTION* nd); |
||||
int route_ping_addr_encode(uint8_t out[ROUTE_ADDR_WIRE_SIZE], const struct sockaddr_storage *address); |
||||
int route_ping_addr_decode(const uint8_t in[ROUTE_ADDR_WIRE_SIZE], struct sockaddr_storage *address); |
||||
int route_ping_addr_equal(const struct sockaddr_storage *a, const struct sockaddr_storage *b, int include_port); |
||||
|
||||
/** Отменить pending для conn (при разрыве/реините соединения). */ |
||||
void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn); |
||||
|
||||
/** Отменить pending для конкретного линка (при закрытии линка без разрыва conn). */ |
||||
void route_ping_cancel_for_link(struct NAT_DETECTION* nd, struct ETCP_LINK* link); |
||||
|
||||
/** Входящий PING_REQ — мы посредник: пингуем цель, шлём PING_RESP. */ |
||||
void route_ping_handle_req(struct NAT_DETECTION* nd, struct TOPO_GROUP* group, |
||||
struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); |
||||
|
||||
/** Входящий PING_RESP — мы инициатор: находим pending по request_id, вызываем cb. */ |
||||
void route_ping_handle_resp(struct NAT_DETECTION* nd, |
||||
struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); |
||||
/* Ответ: address(19) || tag(25), все числа BE, IP — неизменённые сетевые байты. */ |
||||
int route_ping_observe(struct ETCP_SOCKET *socket, const uint8_t peer_key[SC_PUBKEY_SIZE], |
||||
const struct sockaddr_storage *peer_address, uint64_t round, int timeout_ms, |
||||
etcp_ping_callback_t callback, void *arg); |
||||
int route_ping_observation(const uint8_t *data, size_t length, const struct ETCP_SOCKET *socket, |
||||
uint64_t round, struct sockaddr_storage *observed); |
||||
void route_ping_make_tag(uint8_t out[ROUTE_OBSERVE_TAG_SIZE], const struct ETCP_SOCKET *socket, uint64_t round); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif // ROUTE_PING_H
|
||||
|
||||
#endif |
||||
|
||||
@ -1,62 +1,28 @@
|
||||
# route_ping — удалённый пинг через BGP (NAT-детекция, проверка живости) |
||||
|
||||
## 1. Назначение |
||||
Механизм запроса удалённого пинга через BGP-канал. Узел A отправляет узлу B TOPO_PING_REQ |
||||
с IP:port цели, узел B выполняет серию пингов (`etcp_send_ping_to_socket`) и возвращает |
||||
TOPO_PING_RESP с результатами (count_ok, avg_rtt). |
||||
|
||||
Используется: |
||||
- `route_connectivity` — для зондирования связности (прямые пинги, без BGP) |
||||
- `topo_group` — для NAT-детекции через промежуточный узел (third-party ping) |
||||
- Общий механизм проверки живости адресов |
||||
|
||||
## 2. Как пользоваться |
||||
```c |
||||
// Отправить запрос пинга адреса target_ip:target_port через промежуточный узел to_conn |
||||
int ret = route_ping_send_req_addr(group, to_conn, |
||||
target_ip, target_port, |
||||
count=2, interval_ms=100, timeout_ms=500, |
||||
wait_timeout_ms=2000, callback, arg, pubkey); |
||||
``` |
||||
|
||||
Промежуточный узел (to_conn) получает запрос → выполняет серию `etcp_send_ping_to_socket()` |
||||
→ собирает статистику → отправляет ответ. |
||||
|
||||
Ключевой нюанс: `wait_timeout_ms` задаёт таймаут ожидания ответа на локальной стороне. |
||||
Если ответ не приходит вовремя, `route_ping_pending_timeout` вызывает callback с `success=0`. |
||||
|
||||
## 3. API |
||||
|
||||
### Протокольные структуры (packed, wire-format) |
||||
| Структура | Поля | |
||||
|-----------|------| |
||||
| `TOPO_PING_REQ` | `cmd=ETCP_ID_TOPO_ENTRY`, `subcmd=0x07`, `request_id`, `count`, `interval_ms`, `timeout_ms`, `target_ipv4`, `target_port`, `pubkey` (опционально) | |
||||
| `TOPO_PING_RESP` | `cmd`, `subcmd=0x08`, `request_id`, `count_sent`, `count_ok`, `avg_rtt` (0.1ms) | |
||||
|
||||
### Внутренние структуры |
||||
| Структура | Назначение | |
||||
|-----------|------------| |
||||
| `route_ping_series_ctx` | Контекст серии пингов (на отвечающей стороне): `reply_conn`, `request_id`, `target_addr`, `local_sock`, счётчики, статистика | |
||||
| `route_ping_pending` | Ожидающий запрос (на запрашивающей стороне): `request_id`, `callback`, `timeout_timer`, `via_conn` | |
||||
|
||||
### Функции |
||||
| Функция | Назначение | |
||||
|---------|------------| |
||||
| `route_ping_send_req_addr(group, to_conn, target_ip, target_port, count, interval, timeout, wait_timeout, cb, arg, pubkey)` | Отправить TOPO_PING_REQ через BGP, зарегистрировать callback | |
||||
| `route_ping_handle_req(group, from_conn, data, len)` | Обработать входящий PING_REQ: найти локальный IPv4-сокет, запустить серию `etcp_send_ping_to_socket()`, по завершении отправить PING_RESP | |
||||
| `route_ping_handle_resp(group, from_conn, data, len)` | Обработать PING_RESP: найти pending по `request_id`, отменить таймер, вызвать callback | |
||||
| `route_ping_destroy_pending(group)` | Очистить весь список pending (при уничтожении BGP) | |
||||
| `route_ping_cancel_for_conn(group, conn)` | Отменить pending запросы, связанные с конкретным `ETCP_CONN` (при разрыве) | |
||||
|
||||
### Callback |
||||
```c |
||||
typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, |
||||
uint8_t count_sent, uint8_t count_ok, void* arg); |
||||
``` |
||||
Вызывается при получении ответа или по таймауту (`success=0`). |
||||
|
||||
### Зависимости |
||||
- `src/topo_group.h` — BGP-канал (список `ping_pending`, `next_ping_req_id`) |
||||
- `src/etcp_api.h` — `etcp_send()` для отправки через BGP |
||||
- `src/etcp_connections.h` — `etcp_send_ping_to_socket()` для собственно пинга |
||||
- `src/topo_node.h` — `topo_node_find_by_id()` |
||||
# route_ping — наблюдение рабочего UDP endpoint |
||||
|
||||
Модуль кодирует адреса и выполняет прямое UDP-наблюдение через конкретный |
||||
`ETCP_SOCKET`. Выбор B/C, пересылка управляющих сообщений и классификация |
||||
профиля принадлежат `nat_detection`; алгоритм — в `doc/nat_detection.txt`. |
||||
|
||||
`route_ping_observe(socket, peer_key, peer_address, round, timeout_ms, callback, arg)` |
||||
передаёт PING с `ETCP_PING_FLAG_OBSERVE`. PONG содержит фактически наблюдаемый |
||||
source IP:port и исходный тег. Профиль не меняется от единственного ответа. |
||||
`route_ping_observation` проверяет размер, ID/поколение сокета и round. |
||||
|
||||
Адрес занимает 19 байт: family(1: 4/6), IP(16: IPv4 + 12 нулей либо IPv6), |
||||
port(2, BE). Тег занимает 25 байт: `NAT2UDP!`(8), socket_id(1), |
||||
generation(8, BE), round(8, BE). Неправильные family, скрытые IPv4-байты |
||||
и нулевой порт отвергаются. Новый wire не использует native packed integers. |
||||
|
||||
В транспорте nonce генерируется CSPRNG; PONG должен соответствовать ожидаемому |
||||
ключу, source IP:port и принимающему socket generation. Pending снимается |
||||
до callback. `etcp_cancel_pings(instance, callback, arg)` отменяет только |
||||
совпадающие запросы без callback; arg остаётся собственностью вызывающего. |
||||
|
||||
`ETCP_PING_FLAG_ADDRESS_CHECK` обозначает прямое входящее испытание. Его |
||||
receipt фиксируется на рабочем сокете владельца до отправки PONG. Потеря |
||||
обратного PONG поэтому не превращает полученный ingress в отрицательный результат. |
||||
UDP-результаты не используются для TCP listener и SOCKS5 endpoint. |
||||
|
||||
Проверки: `tests/test_route_ping.c`, `tests/test_nat_detection.c`, |
||||
изолированный стенд `tests/stream_netns/run.py --nat-only`. |
||||
|
||||
@ -0,0 +1,82 @@
|
||||
#ifndef NAT_FIXTURE_H |
||||
#define NAT_FIXTURE_H |
||||
|
||||
#include <assert.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include "test_net.h" |
||||
#include "utun_instance.h" |
||||
#include "config_parser.h" |
||||
#include "transport_layer/etcp.h" |
||||
#include "transport_layer/node_conn_direct.h" |
||||
#include "routing_layer/topo_group.h" |
||||
#include "routing_layer/topo_node_sqlite.h" |
||||
#include "routing_layer/nat_detection.h" |
||||
#include "routing_layer/route_ping.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/mem.h" |
||||
|
||||
struct nat_fixture { |
||||
struct UASYNC *ua; |
||||
struct UTUN_INSTANCE *nodes[8]; |
||||
struct NODE_CONN_DIRECT *handles[24]; |
||||
unsigned node_count, handle_count; |
||||
size_t allocations; |
||||
}; |
||||
|
||||
static inline void nat_fixture_init(struct nat_fixture *fixture) { |
||||
memset(fixture, 0, sizeof(*fixture)); fixture->allocations = u_get_allocated_count(); |
||||
fixture->ua = uasync_create(); assert(fixture->ua); |
||||
} |
||||
|
||||
static inline struct UTUN_INSTANCE *nat_fixture_node(struct nat_fixture *fixture, const char *ip, |
||||
const struct SC_MYKEYS *keys, const char *extra) { |
||||
struct SC_MYKEYS generated; |
||||
if (!keys) { assert(sc_generate_keypair(&generated) == SC_OK); keys = &generated; } |
||||
char private_key[65], public_key[65], config[4096]; |
||||
for (unsigned i = 0; i < 32; i++) { |
||||
snprintf(private_key + i * 2, 3, "%02x", keys->private_key[i]); |
||||
snprintf(public_key + i * 2, 3, "%02x", keys->public_key[i]); |
||||
} |
||||
snprintf(config, sizeof(config), "[global]\nmy_private_key=%s\nmy_public_key=%s\nauto_sockets=no\n" |
||||
"[ntp]\nenabled=no\n[server:udp]\naddr=%s:0\ntype=public\n[allowed_keys]\nallow_all=1\n%s", |
||||
private_key, public_key, ip, extra ? extra : ""); |
||||
struct UTUN_INSTANCE *node = utun_instance_create_from_str(fixture->ua, config); |
||||
assert(node && fixture->node_count < 8 && utun_core_start(node) == 0); |
||||
nat_detection_set_allow_local(node->nat_det, 1); |
||||
fixture->nodes[fixture->node_count++] = node; return node; |
||||
} |
||||
|
||||
/* Без групповой автосвязности: A контактирует только с явно указанным B.
|
||||
* NCD переносит настоящую подписанную запись, handshake идёт по UDP. */ |
||||
static inline struct ETCP_CONN *nat_fixture_connect(struct nat_fixture *fixture, struct UTUN_INSTANCE *from, |
||||
const char *source, struct UTUN_INSTANCE *to) { |
||||
assert(fixture->handle_count < 24); |
||||
struct TOPO_NODE *peer = topo_node_registry_find(to->topo_groups, to->node_id); assert(peer); |
||||
struct NODE_CONN_DIRECT **handle = &fixture->handles[fixture->handle_count++]; |
||||
assert(node_conn_direct_open_node(from, to->node_id, NULL, NULL, handle, peer, |
||||
test_etcp_bound_socket(from, source)) != NCD_ERR); |
||||
uint64_t end = get_time_tb() + 50000; |
||||
struct ETCP_CONN *connection = NULL; |
||||
while (get_time_tb() < end) { |
||||
connection = node_conn_direct_get_conn(*handle); |
||||
if (connection && connection->links_up) return connection; |
||||
uasync_poll(fixture->ua, 10); |
||||
} |
||||
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "fixture connection timed out from=%016llx to=%016llx", |
||||
(unsigned long long)from->node_id, (unsigned long long)to->node_id); |
||||
abort(); |
||||
} |
||||
|
||||
static inline void nat_fixture_cleanup(struct nat_fixture *fixture) { |
||||
for (unsigned i = 0; i < fixture->handle_count; i++) node_conn_direct_force_close(fixture->handles[i]); |
||||
for (unsigned i = 0; i < fixture->node_count; i++) utun_instance_destroy(fixture->nodes[i]); |
||||
uasync_poll(fixture->ua, 0); |
||||
assert(fixture->ua->timer_alloc_count == fixture->ua->timer_free_count); |
||||
assert(fixture->ua->socket_alloc_count == fixture->ua->socket_free_count); |
||||
uasync_destroy(fixture->ua, 0); |
||||
if (u_get_allocated_count() != fixture->allocations) u_report_unfreed_blocks(); |
||||
assert(u_get_allocated_count() == fixture->allocations); |
||||
} |
||||
|
||||
#endif |
||||
@ -0,0 +1,107 @@
|
||||
"""Проверка детектора uTun на настоящем NAT; контроль не создаёт контакт с помощником.""" |
||||
|
||||
import json |
||||
|
||||
from topology import NATS, NODES |
||||
|
||||
|
||||
def profile(statistics, protocol): |
||||
return next(item for item in statistics["address_profiles"] if item["socket"] == protocol) |
||||
|
||||
|
||||
def check_profiles(scenario): |
||||
agents = scenario.agents |
||||
public = [node for node in NODES if not node.nat] |
||||
transport = "tcp" if scenario.args.tcp_only else "udp" |
||||
connected = set() |
||||
|
||||
def connect(source, target): |
||||
pair = (source.name, target.name) |
||||
if pair in connected: |
||||
return |
||||
snapshot = agents[target.name].rpc("snapshot")["snapshot"] |
||||
agents[source.name].rpc("nat_link", snapshot=snapshot, socket=transport) |
||||
target_id = agents[target.name].identity |
||||
scenario.wait(f"nat-link-{source.name}-{target.name}", |
||||
lambda data: target_id in data[source.name]["direct_up"], 20) |
||||
connected.add(pair) |
||||
|
||||
def store_owner(owner, observer): |
||||
agents[observer.name].rpc("nat_store", snapshot=agents[owner.name].rpc("snapshot")["snapshot"]) |
||||
|
||||
def measure(owner, observer, protocol): |
||||
before = profile(agents[owner.name].rpc("stats"), protocol) |
||||
agents[owner.name].rpc("nat_check", socket=protocol, observer=agents[observer.name].identity) |
||||
data = scenario.wait(f"nat-profile-{owner.name}-{protocol}", lambda statistics: |
||||
profile(statistics[owner.name], protocol)["rounds"] > before["rounds"] and |
||||
profile(statistics[owner.name], protocol)["phase"] == 0, 40) |
||||
return profile(data[owner.name], protocol) |
||||
|
||||
# Отсутствие второго наблюдателя не превращается в STRICT/DIRECT. |
||||
first, second = public[:2] |
||||
connect(first, second) |
||||
if not scenario.args.tcp_only: |
||||
incomplete = measure(first, second, "udp") |
||||
if (incomplete["mapping"], incomplete["inbound"], incomplete["reason"]) != ("UNKNOWN", "UNKNOWN", "no_helper"): |
||||
raise RuntimeError(f"NAT no-helper test: {incomplete}") |
||||
|
||||
verified = {} |
||||
for owner in public + [node for node in NODES if node.nat]: |
||||
observer = second if owner.name == first.name else first |
||||
if not scenario.args.tcp_only: |
||||
contacts = set(profile(agents[owner.name].rpc("stats"), "udp")["contacts"]) |
||||
helper = next((node for node in public if node.name not in {owner.name, observer.name} and node.ip not in contacts), None) |
||||
if not helper: |
||||
raise RuntimeError(f"{owner.name}: no independent helper remains before cold measurement; contacts={contacts}") |
||||
connect(observer, helper) |
||||
connect(owner, observer) |
||||
store_owner(owner, observer) |
||||
protocols = ["tcp"] if scenario.args.tcp_only else ["udp", "tcp"] if scenario.args.tcp else ["udp"] |
||||
observed = {} |
||||
for protocol in protocols: |
||||
if protocol == "tcp" and owner.nat: |
||||
# Приватный TCP listener не имеет обнаруженного публичного endpoint. |
||||
agents[owner.name].rpc("nat_check", socket="tcp", observer=agents[observer.name].identity) |
||||
data = scenario.wait(f"nat-private-tcp-{owner.name}", lambda statistics: |
||||
profile(statistics[owner.name], "tcp")["reason"] == "no_listener", 5) |
||||
actual = profile(data[owner.name], "tcp") |
||||
expected = ("UNKNOWN", "UNKNOWN") |
||||
else: |
||||
actual = measure(owner, observer, protocol) |
||||
if protocol == "tcp" or not owner.nat: |
||||
expected = ("UNCHANGED", "OPEN") |
||||
else: |
||||
gateway = next(item for item in NATS if item.name == owner.nat) |
||||
expected = {"cone": ("EIM", "OPEN"), "restricted": ("EIM", "CONTACT_REQUIRED"), |
||||
"strict": ("DEPENDENT", "UNKNOWN")}[gateway.mode] |
||||
if (actual["mapping"], actual["inbound"]) != expected: |
||||
raise RuntimeError(f"{owner.name}/{protocol}: expected {expected}, detector returned {actual}") |
||||
if expected[0] == "EIM": |
||||
gateway = next(item for item in NATS if item.name == owner.nat) |
||||
external = f"{gateway.ip}:{owner.mapped_port}" |
||||
elif expected[0] == "UNCHANGED": |
||||
external = f"{owner.ip}:32000" |
||||
else: |
||||
external = "" |
||||
if actual["external"] != external: |
||||
raise RuntimeError(f"{owner.name}/{protocol}: foreign/invalid external endpoint: {actual}, expected {external}") |
||||
observed[protocol] = actual |
||||
print(f"NAT {owner.name}/{protocol}: {actual['mapping']} / {actual['inbound']}" |
||||
f" external={actual['external'] or '-'} reason={actual['reason']}", flush=True) |
||||
verified[owner.name] = observed |
||||
(scenario.network.directory / "nat-profiles.json").write_text(json.dumps(verified, indent=2) + "\n") |
||||
|
||||
# Повтор на использовавшемся порту сохраняет mapping, но не обновляет OPEN |
||||
# по контактировавшим IP. Проверка запускается после всех холодных измерений. |
||||
first_profile = verified[first.name].get("udp") |
||||
if first_profile: |
||||
again = measure(first, second, "udp") |
||||
if again["mapping"] != "UNCHANGED" or again["inbound"] not in {"OPEN", "UNKNOWN"}: |
||||
raise RuntimeError(f"{first.name}: warm measurement produced invalid classification: {again}") |
||||
if again["inbound"] == "UNKNOWN" and again["reason"] != "contact_history": |
||||
raise RuntimeError(f"{first.name}: UNKNOWN without contact-history explanation: {again}") |
||||
|
||||
result = {"nodes_checked": len(verified), "protocols": protocols, "no_helper_checked": not scenario.args.tcp_only, |
||||
"warm_recheck_checked": bool(first_profile), "verified": verified} |
||||
(scenario.network.directory / "nat-result.json").write_text(json.dumps(result, indent=2) + "\n") |
||||
return result |
||||
@ -1,527 +1,125 @@
|
||||
/**
|
||||
* @file test_nat_detection.c |
||||
* @brief Интеграционный тест детекции типа NAT через три узла |
||||
* |
||||
* Топология: C1 -> S <- C2 |
||||
* - S получает NODEINFO от C1 (direct peer) |
||||
* - S использует C2 как третий узел для NAT-пинга C1 |
||||
* - C2 пингует C1, результат возвращается S |
||||
* - S отправляет NAT_INFO C1 |
||||
* - Проверяем корректность заполнения всех NAT-полей |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include "test_utils.h" |
||||
#include "test_net.h" |
||||
#include "etcp.h" |
||||
#include "etcp_connections.h" |
||||
#include "../src/config_parser.h" |
||||
#include "../src/config_updater.h" |
||||
#include "../src/utun_instance.h" |
||||
#include "routing.h" |
||||
#include "topo_group.h" |
||||
#include "route_ping.h" |
||||
#include "nat_detection.h" |
||||
#include "topo_node.h" |
||||
#include "../src/tun_if.h" |
||||
#include "secure_channel.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
|
||||
#define TEST_TIMEOUT_MS 5000 |
||||
static uint64_t g_node_id_s = 0; |
||||
static uint64_t g_node_id_c1 = 0; |
||||
static uint64_t g_node_id_c2 = 0; |
||||
|
||||
static struct UTUN_INSTANCE* inst_s = NULL; |
||||
static struct UTUN_INSTANCE* inst_c1 = NULL; |
||||
static struct UTUN_INSTANCE* inst_c2 = NULL; |
||||
static struct UASYNC* ua = NULL; |
||||
static int test_timed_out = 0; |
||||
static void* test_timeout_id = NULL; |
||||
|
||||
static char temp_dir[] = "/tmp/utun_nat_test_XXXXXX"; |
||||
static char config_s[256]; |
||||
static char config_c1[256]; |
||||
static char config_c2[256]; |
||||
|
||||
static struct { |
||||
int done; |
||||
int success; |
||||
uint16_t avg_rtt; |
||||
uint8_t count_sent; |
||||
uint8_t count_ok; |
||||
} nat_ping_result; |
||||
|
||||
static int write_config(const char* path, const char* content) { |
||||
FILE* f = fopen(path, "w"); |
||||
if (!f) return -1; |
||||
fprintf(f, "%s", content); |
||||
fclose(f); |
||||
return 0; |
||||
} |
||||
|
||||
static char* get_pubkey_from_config(const char* path) { |
||||
struct utun_config* cfg = parse_config(path); |
||||
if (!cfg) return NULL; |
||||
char* pub = strdup(cfg->global.my_public_key_hex); |
||||
free_config(cfg); |
||||
return pub; |
||||
} |
||||
|
||||
static int create_temp_configs(void) { |
||||
if (test_mkdtemp(temp_dir) != 0) { |
||||
fprintf(stderr, "Failed to create temp directory\n"); |
||||
return -1; |
||||
} |
||||
snprintf(config_s, sizeof(config_s), "%s/s.conf", temp_dir); |
||||
snprintf(config_c1, sizeof(config_c1), "%s/c1.conf", temp_dir); |
||||
snprintf(config_c2, sizeof(config_c2), "%s/c2.conf", temp_dir); |
||||
|
||||
const char* tpl_s = |
||||
"[global]\n" |
||||
"tun_ip=10.100.0.1/24\n" |
||||
"tun_ifname=tun100\n" |
||||
"\n" |
||||
"[server:test_s]\n" |
||||
"addr=127.0.0.1:0\n" |
||||
"type=public\n" |
||||
"\n" |
||||
"[allowed_keys]\n" |
||||
"allow_all=1\n"; |
||||
if (write_config(config_s, tpl_s) != 0) return -1; |
||||
if (config_ensure_keys_and_node_id(config_s) != 0) return -1; |
||||
char* pub_s = get_pubkey_from_config(config_s); |
||||
if (!pub_s) return -1; |
||||
|
||||
const char* tpl_c2 = |
||||
"[global]\n" |
||||
"tun_ip=10.100.0.3/24\n" |
||||
"tun_ifname=tun103\n" |
||||
"\n" |
||||
"[server:test_c2]\n" |
||||
"addr=127.0.0.1:0\n" |
||||
"type=public\n" |
||||
"\n" |
||||
"[client:to_s]\n" |
||||
"keepalive=1\n" |
||||
"peer_public_key=%s\n" |
||||
"link=test_c2:127.0.0.1:0\n"; |
||||
char tpl_c2_full[4096]; |
||||
snprintf(tpl_c2_full, sizeof(tpl_c2_full), tpl_c2, pub_s); |
||||
if (write_config(config_c2, tpl_c2_full) != 0) { free(pub_s); return -1; } |
||||
if (config_ensure_keys_and_node_id(config_c2) != 0) { free(pub_s); return -1; } |
||||
char* pub_c2 = get_pubkey_from_config(config_c2); |
||||
if (!pub_c2) { free(pub_s); return -1; } |
||||
|
||||
const char* tpl_c1 = |
||||
"[global]\n" |
||||
"tun_ip=10.100.0.2/24\n" |
||||
"tun_ifname=tun102\n" |
||||
"\n" |
||||
"[server:test_c1]\n" |
||||
"addr=127.0.0.1:0\n" |
||||
"type=nat\n" |
||||
"\n" |
||||
"[client:to_s]\n" |
||||
"keepalive=1\n" |
||||
"peer_public_key=%s\n" |
||||
"link=test_c1:127.0.0.1:0\n"; |
||||
char tpl_c1_full[4096]; |
||||
snprintf(tpl_c1_full, sizeof(tpl_c1_full), tpl_c1, pub_s); |
||||
free(pub_s); |
||||
if (write_config(config_c1, tpl_c1_full) != 0) { free(pub_c2); return -1; } |
||||
if (config_ensure_keys_and_node_id(config_c1) != 0) { free(pub_c2); return -1; } |
||||
free(pub_c2); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void cleanup_temp_configs(void) { |
||||
test_unlink(config_s); |
||||
test_unlink(config_c1); |
||||
test_unlink(config_c2); |
||||
test_rmdir(temp_dir); |
||||
} |
||||
|
||||
static int has_initialized_link(struct UTUN_INSTANCE* inst) { |
||||
if (!inst || !inst->connections) return 0; |
||||
struct ll_entry* entry = inst->connections->head; |
||||
while (entry) { |
||||
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; |
||||
struct ETCP_CONN* conn = ce->conn; |
||||
struct ETCP_LINK* link = conn->links; |
||||
while (link) { |
||||
if (link->initialized) return 1; |
||||
link = link->next; |
||||
} |
||||
entry = entry->next; |
||||
} |
||||
return 0; |
||||
/* Детектор владеет профилем сокета; помощник с IP наблюдателя заменяется.
|
||||
* Настоящие ETCP/NCD/PING/STCP, без групповой автосвязности и без подмены времени. */ |
||||
#include "nat_fixture.h" |
||||
#include "ntp_time.h" |
||||
|
||||
static void wait_profile(struct nat_fixture *fixture, struct ETCP_SOCKET *socket, uint8_t mapping, uint8_t inbound) { |
||||
uint64_t end = get_time_tb() + 180000; |
||||
while (get_time_tb() < end) { |
||||
if (socket->address_profile.evidence_id && socket->address_profile.mapping == mapping && |
||||
socket->address_profile.inbound == inbound) return; |
||||
uasync_poll(fixture->ua, 10); |
||||
} |
||||
struct nat_check_status status = {0}; |
||||
nat_detection_get_status(socket->instance->nat_det, socket, &status); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "profile wait failed socket=%s mapping=%s inbound=%s phase=%u reason=%s", |
||||
socket->name, address_mapping_name(socket->address_profile.mapping), |
||||
address_inbound_name(socket->address_profile.inbound), status.phase, nat_check_reason_name(status.reason)); |
||||
abort(); |
||||
} |
||||
|
||||
static struct ETCP_LINK* find_link_to_node(struct UTUN_INSTANCE* inst, uint64_t node_id) { |
||||
if (!inst || !inst->connections) return NULL; |
||||
struct ll_entry* entry = queue_find_data_by_index(inst->connections, (const uint8_t*)&node_id); |
||||
return entry ? ((struct conn_queue_entry*)entry->data)->conn->links : NULL; |
||||
} |
||||
|
||||
static void test_timeout_cb(void* arg) { |
||||
(void)arg; |
||||
test_timed_out = 1; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "test_nat_detection: overall test timeout"); |
||||
} |
||||
|
||||
static void nat_ping_resp_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg) { |
||||
(void)arg; |
||||
nat_ping_result.done = 1; |
||||
nat_ping_result.success = success; |
||||
nat_ping_result.avg_rtt = avg_rtt; |
||||
nat_ping_result.count_sent = count_sent; |
||||
nat_ping_result.count_ok = count_ok; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "nat_ping_resp_cb: success=%d avg_rtt=%u sent=%u ok=%u", |
||||
success, (unsigned)avg_rtt, (unsigned)count_sent, (unsigned)count_ok); |
||||
static void check_snapshot(struct UTUN_INSTANCE *owner, struct ETCP_SOCKET *socket) { |
||||
struct TOPO_NODE *node = topo_node_registry_find(owner->topo_groups, owner->node_id); |
||||
assert(node && topo_node_verify(node) == 0); |
||||
struct TOPO_SOCKMETA4 *meta = node->v4_sock_meta; |
||||
while (meta && meta->id != socket->sock_id) meta = meta->next; |
||||
assert(meta && meta->address_profile.evidence_id == socket->address_profile.evidence_id && |
||||
meta->address_profile.checked_at == socket->address_profile.checked_at && |
||||
meta->address_profile.mapping == socket->address_profile.mapping && |
||||
meta->address_profile.inbound == socket->address_profile.inbound); |
||||
struct address_profile saved = meta->address_profile; |
||||
meta->address_profile.checked_at++; |
||||
assert(topo_node_verify(node) < 0); |
||||
meta->address_profile = saved; |
||||
meta->address_profile.inbound = ADDRESS_IN_CONTACT; |
||||
assert(topo_node_verify(node) < 0); |
||||
meta->address_profile = saved; assert(topo_node_verify(node) == 0); |
||||
|
||||
sqlite3_stmt *statement = NULL; |
||||
assert(sqlite3_prepare_v2(owner->topo_sqlite_db, |
||||
"SELECT nat_mapping,nat_inbound,nat_evidence,nat_checked FROM node_addresses WHERE node_id=? AND socket_id=?", |
||||
-1, &statement, NULL) == SQLITE_OK); |
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)owner->node_id); sqlite3_bind_int(statement, 2, socket->sock_id); |
||||
assert(sqlite3_step(statement) == SQLITE_ROW); |
||||
assert(sqlite3_column_int(statement, 0) == saved.mapping && sqlite3_column_int(statement, 1) == saved.inbound); |
||||
assert((uint64_t)sqlite3_column_int64(statement, 2) == saved.evidence_id); |
||||
assert((uint64_t)sqlite3_column_int64(statement, 3) == saved.checked_at); |
||||
sqlite3_finalize(statement); |
||||
} |
||||
|
||||
int main(void) { |
||||
int test_result = 1; |
||||
debug_config_init(); |
||||
debug_set_level(DEBUG_LEVEL_INFO); |
||||
debug_set_categories(DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ROUTING); |
||||
utun_instance_set_tun_init_enabled(0); |
||||
|
||||
if (create_temp_configs() != 0) { |
||||
fprintf(stderr, "Failed to create temp configs\n"); |
||||
return 1; |
||||
} |
||||
|
||||
ua = uasync_create(); |
||||
if (!ua) { |
||||
cleanup_temp_configs(); |
||||
return 1; |
||||
} |
||||
|
||||
inst_s = utun_instance_create(ua, config_s); |
||||
inst_c1 = utun_instance_create(ua, config_c1); |
||||
inst_c2 = utun_instance_create(ua, config_c2); |
||||
if (!inst_s || !inst_c1 || !inst_c2) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create instances"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
test_etcp_set_peer(inst_c1, "to_s", inst_s, "test_s"); |
||||
test_etcp_set_peer(inst_c2, "to_s", inst_s, "test_s"); |
||||
// Разрешаем NAT check для localhost (для теста)
|
||||
|
||||
if (utun_instance_init(inst_s) != 0 || utun_instance_init(inst_c1) != 0 || utun_instance_init(inst_c2) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to init connections"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
if (topo_groups_get_default(inst_s->topo_groups)) topo_group_set_nat_check_local(topo_groups_get_default(inst_s->topo_groups), 1); |
||||
|
||||
g_node_id_s = inst_s->node_id; g_node_id_c1 = inst_c1->node_id; g_node_id_c2 = inst_c2->node_id; |
||||
|
||||
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb, "test_nat"); |
||||
|
||||
// 1. Wait for links C1->S and C2->S to initialize
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Waiting for ETCP links to initialize..."); |
||||
// Wait for links to be initialized instead of fixed 100 iterations
|
||||
while (!test_timed_out) { |
||||
if (has_initialized_link(inst_c1) && has_initialized_link(inst_c2)) { |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Links C1->S and C2->S initialized"); |
||||
break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
} |
||||
if (test_timed_out) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Timeout waiting for links"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// 2. Wait for BGP exchange so S learns about C1 and C2
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (S learns C1 and C2)..."); |
||||
int bgp_wait_cycles = 0; |
||||
while (!test_timed_out && bgp_wait_cycles < 500) { |
||||
if (topo_groups_get_default(inst_s->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c1) != NULL && |
||||
topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c2) != NULL) { |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "S learned about C1 and C2"); |
||||
break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
if (!topo_groups_get_default(inst_s->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c1) == NULL || |
||||
topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c2) == NULL) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "S did not learn about C1/C2 in time"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// Verify C1 socket has config type before NAT detection
|
||||
{ |
||||
struct ETCP_SOCKET* s = inst_c1->etcp_sockets; |
||||
if (!s || s->type != CFG_SERVER_TYPE_NAT) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: C1 socket type=%d, expected CFG_SERVER_TYPE_NAT(%d) before detection", |
||||
s ? (int)s->type : -1, CFG_SERVER_TYPE_NAT); |
||||
goto cleanup; |
||||
} |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "C1 socket has config NAT type before detection"); |
||||
} |
||||
|
||||
// 3. Wait for NAT detection to complete for C1 - check link on server
|
||||
struct ETCP_LINK* link_sc1 = find_link_to_node(inst_s, g_node_id_c1); |
||||
if (!link_sc1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "No link to C1 on server"); |
||||
goto cleanup; |
||||
} |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for NAT detection to complete for C1..."); |
||||
bgp_wait_cycles = 0; |
||||
while (!test_timed_out && bgp_wait_cycles < 1500) { |
||||
if (link_sc1->nat_check_status == NAT_CHECK_EIM) { |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection completed for C1: EIM"); |
||||
break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
if (!link_sc1 || link_sc1->nat_check_status != NAT_CHECK_EIM) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NAT detection did not complete for C1"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// 4. Wait for C1 to receive NAT_INFO (verified type)
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for C1 to receive NAT_INFO..."); |
||||
bgp_wait_cycles = 0; |
||||
while (!test_timed_out && bgp_wait_cycles < 500) { |
||||
struct ETCP_SOCKET* sock = inst_c1->etcp_sockets; |
||||
int found = 0; |
||||
while (sock) { |
||||
if (sock->nat_type == NAT_VERIFIED_EIM) { found = 1; break; } |
||||
sock = sock->next; |
||||
} |
||||
if (found) { |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "C1 received NAT_INFO"); |
||||
/* Даем время для получения NODEINFO с обновленным local_node */ |
||||
for (int i = 0; i < 50 && !test_timed_out; i++) { |
||||
uasync_poll(ua, 10); |
||||
} |
||||
break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
|
||||
// 5. Verify all NAT fields on server
|
||||
struct TOPO_GROUP_NODE* node_c1 = topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c1); |
||||
if (!node_c1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "TOPO_GROUP_NODE for C1 disappeared"); |
||||
goto cleanup; |
||||
} |
||||
if (link_sc1->nat_check_status != NAT_CHECK_EIM) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: nat_check_status=%d, expected EIM(%d)", |
||||
(int)link_sc1->nat_check_status, NAT_CHECK_EIM); |
||||
goto cleanup; |
||||
} |
||||
if (link_sc1->nat_type != NAT_TYPE_EIM) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: nat_type=%d, expected EIM(%d)", |
||||
(int)link_sc1->nat_type, NAT_TYPE_EIM); |
||||
goto cleanup; |
||||
} |
||||
if (link_sc1->nat_ip == 0 || link_sc1->nat_port == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: nat_ip/port not set (ip=%08x port=%u)", |
||||
(unsigned)link_sc1->nat_ip, (unsigned)link_sc1->nat_port); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// Verify link nat_type on server
|
||||
struct ETCP_CONN* conn_sc1 = NULL; |
||||
{ struct ll_entry* e = inst_s->connections->head; |
||||
while (e) { |
||||
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data; |
||||
if (ce->conn->peer_node_id == g_node_id_c1) { conn_sc1 = ce->conn; break; } |
||||
e = e->next; |
||||
} } |
||||
if (!link_sc1 || link_sc1->nat_type != NAT_TYPE_EIM) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: link nat_type not EIM on server"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// Verify socket nat_type on C1
|
||||
struct ETCP_SOCKET* sock_c1 = NULL; |
||||
struct ETCP_SOCKET* sock = inst_c1->etcp_sockets; |
||||
while (sock) { |
||||
if (sock->nat_type == NAT_VERIFIED_EIM) { |
||||
sock_c1 = sock; |
||||
break; |
||||
} |
||||
sock = sock->next; |
||||
} |
||||
if (!sock_c1) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: no socket with NAT_VERIFIED_EIM on C1"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// Verify local_node socket update on C1 (received NAT_INFO updated nodeinfo)
|
||||
if (topo_groups_get_default(inst_c1->topo_groups) && topo_groups_get_default(inst_c1->topo_groups)->local_node) { |
||||
struct TOPO_NODE* lni = topo_node_registry_find(inst_c1->topo_groups, topo_groups_get_default(inst_c1->topo_groups)->local_node->node_id); |
||||
if (lni) { |
||||
int found = 0; |
||||
for (const struct TOPO_SOCKMETA4* m = lni->v4_sock_meta; m; m = m->next) { |
||||
if (m->id == sock_c1->sock_id) { |
||||
if (m->nat_type != NAT_VERIFIED_EIM) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: local_node socket meta not updated on C1: nat_type=%u expected=%u", m->nat_type, NAT_VERIFIED_EIM); |
||||
goto cleanup; |
||||
} |
||||
found = 1; break; |
||||
} |
||||
} |
||||
if (!found) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: socket not found in local_node meta on C1"); goto cleanup; } |
||||
// Verify NAT addr in flat address list
|
||||
int nat_found = 0; |
||||
for (const struct TOPO_ADDR4* a = lni->v4_addrs; a; a = a->next) { |
||||
if (a->type == TOPO_ADDR_NAT && a->socket_id == sock_c1->sock_id) { |
||||
uint32_t addr_ip; memcpy(&addr_ip, a->addr, 4); |
||||
if (addr_ip != link_sc1->nat_ip || a->port != link_sc1->nat_port) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: NAT addr mismatch in local_node: addr=%08x nat_ip=%08x port=%u nat_port=%u", |
||||
addr_ip, link_sc1->nat_ip, a->port, link_sc1->nat_port); |
||||
goto cleanup; |
||||
} |
||||
nat_found = 1; break; |
||||
} |
||||
} |
||||
if (!nat_found) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: NAT addr not found in local_node flat addr list on C1"); goto cleanup; } |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "local_node meta+addr check PASSED on C1"); |
||||
} |
||||
} |
||||
|
||||
// Wait for C2 to learn updated C1 nodeinfo (with verified NAT type)
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for C2 to learn updated C1 nodeinfo..."); |
||||
bgp_wait_cycles = 0; |
||||
int c2_verified_nat = 0; |
||||
struct TOPO_GROUP_NODE* node_c1_on_c2 = NULL; |
||||
struct TOPO_NODE* cni = NULL; |
||||
while (!test_timed_out && bgp_wait_cycles < 500) { |
||||
node_c1_on_c2 = topo_groups_get_default(inst_c2->topo_groups) ? topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), g_node_id_c1) : NULL; |
||||
cni = node_c1_on_c2 ? topo_node_registry_find(inst_c2->topo_groups, node_c1_on_c2->node_id) : NULL; |
||||
if (node_c1_on_c2 && cni) { |
||||
for (const struct TOPO_SOCKMETA4* m = cni->v4_sock_meta; m; m = m->next) { |
||||
if (m->nat_type == NAT_VERIFIED_EIM) { c2_verified_nat = 1; break; } |
||||
} |
||||
if (c2_verified_nat) break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
if (!c2_verified_nat) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: C2 did not receive updated C1 nodeinfo with verified NAT"); |
||||
goto cleanup; |
||||
} |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "C2 received updated C1 nodeinfo with verified NAT"); |
||||
|
||||
// 6. Separate STUN check via explicit route_ping_send_req_addr
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Performing explicit STUN ping from S via C2 to C1..."); |
||||
struct ETCP_CONN* conn_sc2 = NULL; |
||||
struct ll_entry* entry = inst_s->connections->head; |
||||
while (entry) { |
||||
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; |
||||
if (ce->conn->peer_node_id == g_node_id_c2) { conn_sc2 = ce->conn; break; } |
||||
entry = entry->next; |
||||
} |
||||
if (!conn_sc2) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: S has no connection to C2"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
memset(&nat_ping_result, 0, sizeof(nat_ping_result)); |
||||
/* nat_ip уже в network byte order, API тоже принимает NBO */ |
||||
uint32_t target_ip_net = link_sc1->nat_ip; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "TEST: sending ping with pubkey, nat_ip_net=0x%08x nat_port=%u", |
||||
link_sc1->nat_ip, link_sc1->nat_port); |
||||
int ret = route_ping_send_req_addr(inst_s->nat_det, topo_groups_get_default(inst_s->topo_groups), conn_sc2, |
||||
target_ip_net, link_sc1->nat_port, |
||||
3, 10, 200, 3000, nat_ping_resp_cb, NULL, |
||||
node_c1_on_c2 ? topo_node_registry_find(inst_c2->topo_groups, node_c1_on_c2->node_id)->public_key : NULL); |
||||
if (ret != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr failed: %d", ret); |
||||
goto cleanup; |
||||
} |
||||
|
||||
bgp_wait_cycles = 0; |
||||
while (!test_timed_out && bgp_wait_cycles < 500 && !nat_ping_result.done) { |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
if (!nat_ping_result.done) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: STUN ping timeout"); |
||||
goto cleanup; |
||||
} |
||||
if (!nat_ping_result.success || nat_ping_result.count_ok == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: STUN ping failed success=%d ok=%u", |
||||
nat_ping_result.success, (unsigned)nat_ping_result.count_ok); |
||||
goto cleanup; |
||||
} |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "STUN check PASSED"); |
||||
|
||||
// 7. Test route_ping_send_req_addr WITHOUT embedded pubkey (C2 should resolve pubkey from node_id)
|
||||
// First wait for C2 to learn C1's nodeinfo via BGP exchange from S
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for C2 to learn C1 nodeinfo..."); |
||||
bgp_wait_cycles = 0; |
||||
while (!test_timed_out && bgp_wait_cycles < 500) { |
||||
if (topo_groups_get_default(inst_c2->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), g_node_id_c1) != NULL) { |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "C2 learned C1 nodeinfo"); |
||||
break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
if (!topo_groups_get_default(inst_c2->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), g_node_id_c1) == NULL) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: C2 did not learn C1 nodeinfo in time"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Testing route ping without embedded pubkey..."); |
||||
memset(&nat_ping_result, 0, sizeof(nat_ping_result)); |
||||
/* nat_ip уже в network byte order, API тоже принимает NBO */ |
||||
target_ip_net = link_sc1->nat_ip; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "TEST: sending ping NO pubkey, nat_ip_net=0x%08x nat_port=%u", |
||||
link_sc1->nat_ip, link_sc1->nat_port); |
||||
ret = route_ping_send_req_addr(inst_s->nat_det, topo_groups_get_default(inst_s->topo_groups), conn_sc2, |
||||
target_ip_net, link_sc1->nat_port, |
||||
3, 10, 200, 3000, nat_ping_resp_cb, NULL, |
||||
cni->public_key); |
||||
if (ret != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr (no pubkey) failed: %d", ret); |
||||
goto cleanup; |
||||
} |
||||
|
||||
bgp_wait_cycles = 0; |
||||
while (!test_timed_out && bgp_wait_cycles < 500 && !nat_ping_result.done) { |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
if (!nat_ping_result.done) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: no-pubkey ping timeout"); |
||||
goto cleanup; |
||||
} |
||||
if (!nat_ping_result.success || nat_ping_result.count_ok == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: no-pubkey ping failed success=%d ok=%u", |
||||
nat_ping_result.success, (unsigned)nat_ping_result.count_ok); |
||||
goto cleanup; |
||||
} |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "No-pubkey ping check PASSED"); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "NAT detection test PASSED"); |
||||
test_result = 0; |
||||
|
||||
cleanup: |
||||
if (test_timeout_id) uasync_cancel_timeout(ua, test_timeout_id); |
||||
if (inst_s) utun_instance_destroy(inst_s); |
||||
if (inst_c1) utun_instance_destroy(inst_c1); |
||||
if (inst_c2) utun_instance_destroy(inst_c2); |
||||
if (ua) uasync_destroy(ua, 0); |
||||
cleanup_temp_configs(); |
||||
return test_result; |
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_NAT, DEBUG_LEVEL_DEBUG); |
||||
struct nat_fixture fixture; nat_fixture_init(&fixture); |
||||
struct SC_MYKEYS first, second; |
||||
assert(sc_generate_keypair(&first) == SC_OK && sc_generate_keypair(&second) == SC_OK); |
||||
if (sc_derive_node_id_from_pubkey(first.public_key) > sc_derive_node_id_from_pubkey(second.public_key)) { |
||||
struct SC_MYKEYS swap = first; first = second; second = swap; |
||||
} |
||||
struct UTUN_INSTANCE *owner = nat_fixture_node(&fixture, "127.0.0.2", NULL, |
||||
"[server:second]\naddr=127.0.0.5:0\ntype=public\n" |
||||
"[server:tcp]\naddr=127.0.0.2:0\ntype=public\ntransport=tcp\n"); |
||||
struct UTUN_INSTANCE *observer = nat_fixture_node(&fixture, "127.0.0.3", NULL, NULL); |
||||
struct UTUN_INSTANCE *same_ip = nat_fixture_node(&fixture, "127.0.0.3", &first, NULL); |
||||
struct UTUN_INSTANCE *helper = nat_fixture_node(&fixture, "127.0.0.4", &second, NULL); |
||||
struct ETCP_SOCKET *socket = test_etcp_bound_socket(owner, "udp"); |
||||
struct ETCP_SOCKET *second_socket = test_etcp_bound_socket(owner, "second"); |
||||
struct ETCP_SOCKET *tcp = test_etcp_bound_socket(owner, "tcp"); |
||||
struct ETCP_SOCKET *observer_socket = test_etcp_bound_socket(observer, "udp"); |
||||
assert(socket->sock_id != observer_socket->sock_id); |
||||
|
||||
/* B уже знает обоих помощниках, но A не отправлял UDP ни одному из них. */ |
||||
nat_fixture_connect(&fixture, observer, "udp", same_ip); |
||||
nat_fixture_connect(&fixture, observer, "udp", helper); |
||||
struct ETCP_CONN *connection = nat_fixture_connect(&fixture, owner, "udp", observer); |
||||
assert(connection->links && connection->links->remote_socket_id == observer_socket->sock_id); |
||||
assert(connection->links->local_as_seen_by_peer.ss_family == AF_INET); |
||||
assert(route_ping_addr_equal(&connection->links->local_as_seen_by_peer, &socket->local_addr, 1)); |
||||
/* Подписанная запись A нужна независимому испытанию настоящего TCP listener. */ |
||||
nat_fixture_connect(&fixture, observer, "udp", owner); |
||||
owner->ntp.reachable = 0; /* ни измерение, ни публикация не требуют интернет-NTP */ |
||||
nat_detection_request_check_all(owner->nat_det); |
||||
wait_profile(&fixture, socket, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); |
||||
wait_profile(&fixture, second_socket, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); |
||||
wait_profile(&fixture, tcp, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); |
||||
|
||||
struct nat_check_status status; |
||||
assert(nat_detection_get_status(owner->nat_det, socket, &status) == 0); |
||||
assert(status.helper_reselections > 0 && status.helper == helper->node_id && status.observer == observer->node_id); |
||||
assert(socket->address_profile.evidence_id != second_socket->address_profile.evidence_id); |
||||
check_snapshot(owner, socket); |
||||
check_snapshot(owner, second_socket); |
||||
check_snapshot(owner, tcp); |
||||
|
||||
/* Профиль B не заимствует профиль A, даже когда транспорт переиспользуется в обе стороны. */ |
||||
assert(!route_ping_addr_equal(&socket->local_addr, &observer_socket->local_addr, 1)); |
||||
assert(!route_ping_addr_equal(&socket->local_addr, &test_etcp_bound_socket(helper, "udp")->local_addr, 1)); |
||||
|
||||
/* Повторная проверка не объявляет старый контакт независимым. */ |
||||
uint64_t old_evidence = socket->address_profile.evidence_id; |
||||
nat_detection_request_check_all(owner->nat_det); |
||||
uint64_t end = get_time_tb() + 180000; |
||||
while (get_time_tb() < end && socket->address_profile.evidence_id == old_evidence) uasync_poll(fixture.ua, 10); |
||||
assert(socket->address_profile.evidence_id != old_evidence); |
||||
assert(socket->address_profile.mapping == ADDRESS_MAP_UNCHANGED && socket->address_profile.inbound == ADDRESS_IN_UNKNOWN); |
||||
|
||||
/* Закрытие endpoint посреди запроса и переиспользование ID/порта: старые ответы не применяются. */ |
||||
nat_detection_request_check_all(owner->nat_det); |
||||
end = get_time_tb() + 20000; |
||||
do { uasync_poll(fixture.ua, 0); nat_detection_get_status(owner->nat_det, socket, &status); } |
||||
while (!status.phase && get_time_tb() < end); |
||||
assert(status.phase); |
||||
uint8_t old_id = socket->sock_id; uint64_t old_generation = socket->generation; |
||||
struct CFG_SERVER recreated; memset(&recreated, 0, sizeof(recreated)); |
||||
recreated.ip = socket->local_addr; recreated.type = CFG_SERVER_TYPE_PUBLIC; recreated.mtu = 1500; recreated.fib = -1; |
||||
snprintf(recreated.name, sizeof(recreated.name), "recreated"); |
||||
etcp_socket_remove(socket); |
||||
owner->next_socket_id = old_id; |
||||
socket = etcp_socket_add(owner, &recreated); |
||||
assert(socket && socket->sock_id == old_id && socket->generation != old_generation); |
||||
end = get_time_tb() + 2000; |
||||
while (get_time_tb() < end) uasync_poll(fixture.ua, 10); |
||||
assert(socket->address_profile.mapping == ADDRESS_MAP_UNKNOWN && socket->address_profile.inbound == ADDRESS_IN_UNKNOWN); |
||||
|
||||
nat_fixture_cleanup(&fixture); |
||||
return 0; |
||||
} |
||||
|
||||
@ -1,341 +1,98 @@
|
||||
/**
|
||||
* @file test_route_ping.c |
||||
* @brief Интеграционный тест route_ping через три инстанса |
||||
* |
||||
* Топология: A -> B -> C |
||||
* - A отправляет запрос route_ping на B для узла C |
||||
* - B пингует C через ETCP ping |
||||
* - C отвечает PONG |
||||
* - B отправляет BGP_PING_RESPONSE на A |
||||
* - A получает ответ через route_ping_send_req callback |
||||
*/ |
||||
/* Настоящие UDP-наблюдения и отклонение PONG чужого ключа/поколения. */ |
||||
#include "nat_fixture.h" |
||||
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include "test_utils.h" |
||||
#include "test_net.h" |
||||
#include "etcp.h" |
||||
#include "etcp_connections.h" |
||||
#include "../src/config_parser.h" |
||||
#include "../src/config_updater.h" |
||||
#include "../src/utun_instance.h" |
||||
#include "routing.h" |
||||
#include "topo_group.h" |
||||
#include "route_ping.h" |
||||
#include "../src/tun_if.h" |
||||
#include "secure_channel.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
struct result { int done, success; uint8_t data[128]; size_t length; }; |
||||
|
||||
#define TEST_TIMEOUT_MS 10000 |
||||
static uint64_t g_node_id_a = 0; |
||||
static uint64_t g_node_id_b = 0; |
||||
static uint64_t g_node_id_c = 0; |
||||
|
||||
static struct UTUN_INSTANCE* inst_a = NULL; |
||||
static struct UTUN_INSTANCE* inst_b = NULL; |
||||
static struct UTUN_INSTANCE* inst_c = NULL; |
||||
static struct UASYNC* ua = NULL; |
||||
static int test_timed_out = 0; |
||||
static void* test_timeout_id = NULL; |
||||
|
||||
static char temp_dir[] = "/tmp/utun_route_ping_test_XXXXXX"; |
||||
static char config_a[256]; |
||||
static char config_b[256]; |
||||
static char config_c[256]; |
||||
|
||||
static struct { |
||||
int done; |
||||
int success; |
||||
uint16_t avg_rtt; |
||||
uint8_t count_sent; |
||||
uint8_t count_ok; |
||||
} ping_result; |
||||
|
||||
static int write_config(const char* path, const char* content) { |
||||
FILE* f = fopen(path, "w"); |
||||
if (!f) return -1; |
||||
fprintf(f, "%s", content); |
||||
fclose(f); |
||||
return 0; |
||||
} |
||||
|
||||
static char* get_pubkey_from_config(const char* path) { |
||||
struct utun_config* cfg = parse_config(path); |
||||
if (!cfg) return NULL; |
||||
char* pub = strdup(cfg->global.my_public_key_hex); |
||||
free_config(cfg); |
||||
return pub; |
||||
} |
||||
|
||||
static int create_temp_configs(void) { |
||||
if (test_mkdtemp(temp_dir) != 0) { |
||||
fprintf(stderr, "Failed to create temp directory\n"); |
||||
return -1; |
||||
} |
||||
snprintf(config_a, sizeof(config_a), "%s/a.conf", temp_dir); |
||||
snprintf(config_b, sizeof(config_b), "%s/b.conf", temp_dir); |
||||
snprintf(config_c, sizeof(config_c), "%s/c.conf", temp_dir); |
||||
|
||||
// 1. Config C (no keys)
|
||||
const char* tpl_c = |
||||
"[global]\n" |
||||
"tun_ip=10.100.0.3/24\n" |
||||
"tun_ifname=tun102\n" |
||||
"\n" |
||||
"[server:test_c]\n" |
||||
"addr=127.0.0.1:0\n" |
||||
"type=public\n" |
||||
"\n" |
||||
"[allowed_keys]\n" |
||||
"allow_all=1\n"; |
||||
if (write_config(config_c, tpl_c) != 0) return -1; |
||||
if (config_ensure_keys_and_node_id(config_c) != 0) return -1; |
||||
char* pub_c = get_pubkey_from_config(config_c); |
||||
if (!pub_c) return -1; |
||||
|
||||
// 2. Config B (no own keys, but needs peer_public_key of C)
|
||||
char tpl_b[4096]; |
||||
snprintf(tpl_b, sizeof(tpl_b), |
||||
"[global]\n" |
||||
"tun_ip=10.100.0.2/24\n" |
||||
"tun_ifname=tun101\n" |
||||
"\n" |
||||
"[server:test_b]\n" |
||||
"addr=127.0.0.1:0\n" |
||||
"type=public\n" |
||||
"\n" |
||||
"[allowed_keys]\n" |
||||
"allow_all=1\n" |
||||
"\n" |
||||
"[client:to_c]\n" |
||||
"keepalive=1\n" |
||||
"peer_public_key=%s\n" |
||||
"link=test_b:127.0.0.1:0\n", |
||||
pub_c); |
||||
free(pub_c); |
||||
if (write_config(config_b, tpl_b) != 0) return -1; |
||||
if (config_ensure_keys_and_node_id(config_b) != 0) return -1; |
||||
char* pub_b = get_pubkey_from_config(config_b); |
||||
if (!pub_b) return -1; |
||||
|
||||
// 3. Config A (no own keys, but needs peer_public_key of B)
|
||||
char tpl_a[4096]; |
||||
snprintf(tpl_a, sizeof(tpl_a), |
||||
"[global]\n" |
||||
"tun_ip=10.100.0.1/24\n" |
||||
"tun_ifname=tun100\n" |
||||
"\n" |
||||
"[server:test_a]\n" |
||||
"addr=127.0.0.1:0\n" |
||||
"type=public\n" |
||||
"\n" |
||||
"[allowed_keys]\n" |
||||
"allow_all=1\n" |
||||
"\n" |
||||
"[client:to_b]\n" |
||||
"keepalive=1\n" |
||||
"peer_public_key=%s\n" |
||||
"link=test_a:127.0.0.1:0\n", |
||||
pub_b); |
||||
free(pub_b); |
||||
if (write_config(config_a, tpl_a) != 0) return -1; |
||||
if (config_ensure_keys_and_node_id(config_a) != 0) return -1; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void cleanup_temp_configs(void) { |
||||
test_unlink(config_a); |
||||
test_unlink(config_b); |
||||
test_unlink(config_c); |
||||
test_rmdir(temp_dir); |
||||
} |
||||
|
||||
static int has_initialized_link(struct UTUN_INSTANCE* inst) { |
||||
if (!inst || !inst->connections) return 0; |
||||
struct ll_entry* entry = inst->connections->head; |
||||
while (entry) { |
||||
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; |
||||
struct ETCP_CONN* conn = ce->conn; |
||||
struct ETCP_LINK* link = conn->links; |
||||
while (link) { |
||||
if (link->initialized) return 1; |
||||
link = link->next; |
||||
} |
||||
entry = entry->next; |
||||
} |
||||
return 0; |
||||
static void ping_done(int success, uint16_t rtt, void *arg, uint64_t nonce, const uint8_t *data, size_t length) { |
||||
(void)rtt; (void)nonce; |
||||
struct result *result = arg; result->done++; result->success = success; result->length = length; |
||||
assert(length <= sizeof(result->data)); if (length) memcpy(result->data, data, length); |
||||
} |
||||
|
||||
static struct ETCP_CONN* find_conn_to_peer(struct UTUN_INSTANCE* inst, uint64_t peer_node_id) { |
||||
if (!inst || !inst->connections) return NULL; |
||||
struct ll_entry* entry = queue_find_data_by_index(inst->connections, (const uint8_t*)&peer_node_id); |
||||
return entry ? ((struct conn_queue_entry*)entry->data)->conn : NULL; |
||||
static void wait_result(struct nat_fixture *fixture, struct result *result) { |
||||
uint64_t end = get_time_tb() + 30000; |
||||
while (!result->done && get_time_tb() < end) uasync_poll(fixture->ua, 10); |
||||
assert(result->done == 1); |
||||
} |
||||
|
||||
static void test_timeout_cb(void* arg) { |
||||
(void)arg; |
||||
test_timed_out = 1; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "test_route_ping: overall test timeout"); |
||||
/* Аутентичный PONG от другого узла с известным nonce. Его source совпадает с
|
||||
* target ожидающего запроса, поэтому ключ пира обязан проверяться отдельно. */ |
||||
static void send_foreign_pong(struct UTUN_INSTANCE *sender, struct ETCP_SOCKET *destination, uint64_t nonce) { |
||||
uint8_t plain[23] = {0}, encrypted[256], salt[8], obfuscated[32]; |
||||
plain[2] = 1; plain[3] = ETCP_PONG; |
||||
for (unsigned i = 0; i < 8; i++) { |
||||
plain[5 + i] = (uint8_t)(sender->node_id >> (56 - i * 8)); |
||||
plain[13 + i] = (uint8_t)(nonce >> (56 - i * 8)); |
||||
} |
||||
sc_context_t crypto; assert(sc_init_ctx(&crypto, &sender->my_keys) == SC_OK); |
||||
assert(sc_set_peer_public_key(&crypto, destination->instance->my_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK); |
||||
size_t length = 0; assert(sc_encrypt(&crypto, plain, sizeof(plain), encrypted, &length) == SC_OK); |
||||
assert(random_bytes(salt, sizeof(salt)) == 0); |
||||
sc_obfuscate_pubkey(salt, destination->instance->my_keys.public_key, sender->my_keys.public_key, obfuscated); |
||||
memcpy(encrypted + length, salt, sizeof(salt)); memcpy(encrypted + length + sizeof(salt), obfuscated, sizeof(obfuscated)); |
||||
length += sizeof(salt) + sizeof(obfuscated); |
||||
struct ETCP_SOCKET *socket = test_etcp_bound_socket(sender, "udp"); |
||||
assert(socket_sendto(socket->fd, encrypted, length, (const struct sockaddr *)&destination->local_addr, |
||||
sizeof(struct sockaddr_in)) == (ssize_t)length); |
||||
} |
||||
|
||||
static void ping_resp_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg) { |
||||
(void)arg; |
||||
ping_result.done = 1; |
||||
ping_result.success = success; |
||||
ping_result.avg_rtt = avg_rtt; |
||||
ping_result.count_sent = count_sent; |
||||
ping_result.count_ok = count_ok; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping_resp_cb: success=%d avg_rtt=%u sent=%u ok=%u", |
||||
success, (unsigned)avg_rtt, (unsigned)count_sent, (unsigned)count_ok); |
||||
static void address_encoding(void) { |
||||
struct sockaddr_storage address = {0}, decoded; |
||||
struct sockaddr_in *v4 = (struct sockaddr_in *)&address; |
||||
v4->sin_family = AF_INET; assert(inet_pton(AF_INET, "198.51.100.7", &v4->sin_addr) == 1); v4->sin_port = htons(1300); |
||||
uint8_t wire[ROUTE_ADDR_WIRE_SIZE]; |
||||
static const uint8_t expected[19] = {4,198,51,100,7,0,0,0,0,0,0,0,0,0,0,0,0,5,20}; |
||||
assert(route_ping_addr_encode(wire, &address) == 0 && !memcmp(wire, expected, sizeof(wire))); |
||||
assert(route_ping_addr_decode(wire, &decoded) == 0 && route_ping_addr_equal(&address, &decoded, 1)); |
||||
wire[5] = 1; assert(route_ping_addr_decode(wire, &decoded) < 0); /* скрытые байты IPv4 запрещены */ |
||||
memset(&address, 0, sizeof(address)); |
||||
struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)&address; |
||||
v6->sin6_family = AF_INET6; v6->sin6_port = htons(0xabcd); |
||||
assert(inet_pton(AF_INET6, "2001:db8::1234", &v6->sin6_addr) == 1); |
||||
assert(route_ping_addr_encode(wire, &address) == 0 && wire[0] == 6 && wire[17] == 0xab && wire[18] == 0xcd); |
||||
assert(route_ping_addr_decode(wire, &decoded) == 0 && route_ping_addr_equal(&address, &decoded, 1)); |
||||
wire[17] = wire[18] = 0; assert(route_ping_addr_decode(wire, &decoded) < 0); |
||||
} |
||||
|
||||
int main(void) { |
||||
int test_result = 1; |
||||
debug_config_init(); |
||||
debug_set_level(DEBUG_LEVEL_INFO); |
||||
debug_set_categories(DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ROUTING); |
||||
utun_instance_set_tun_init_enabled(0); |
||||
|
||||
if (create_temp_configs() != 0) { |
||||
fprintf(stderr, "Failed to create temp configs\n"); |
||||
return 1; |
||||
} |
||||
|
||||
ua = uasync_create(); |
||||
if (!ua) { |
||||
cleanup_temp_configs(); |
||||
return 1; |
||||
} |
||||
|
||||
inst_a = utun_instance_create(ua, config_a); |
||||
inst_b = utun_instance_create(ua, config_b); |
||||
inst_c = utun_instance_create(ua, config_c); |
||||
if (!inst_a || !inst_b || !inst_c) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create instances"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
test_etcp_set_peer(inst_a, "to_b", inst_b, "test_b"); |
||||
test_etcp_set_peer(inst_b, "to_c", inst_c, "test_c"); |
||||
if (utun_instance_init(inst_a) != 0 || utun_instance_init(inst_b) != 0 || utun_instance_init(inst_c) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to init connections"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
g_node_id_a = inst_a->node_id; g_node_id_b = inst_b->node_id; g_node_id_c = inst_c->node_id; |
||||
|
||||
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb, "test_route_ping"); |
||||
|
||||
// 1. Wait for links A->B and B->C to initialize
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Waiting for ETCP links to initialize..."); |
||||
while (!test_timed_out) { |
||||
if (has_initialized_link(inst_a) && has_initialized_link(inst_b)) { |
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Links initialized"); |
||||
break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
} |
||||
if (test_timed_out) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Timeout waiting for links"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// 2. Wait for BGP exchange so B learns C (registry has C's nodeinfo + addresses)
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (B learns C)..."); |
||||
int bgp_wait_cycles = 0; |
||||
while (!test_timed_out && bgp_wait_cycles < 500) { |
||||
struct TOPO_NODE* ni_c = topo_node_registry_find(inst_b->topo_groups, g_node_id_c); |
||||
if (ni_c && ni_c->v4_addrs) { |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "B learned about C"); |
||||
break; |
||||
} |
||||
uasync_poll(ua, 10); |
||||
bgp_wait_cycles++; |
||||
} |
||||
if (!topo_node_registry_find(inst_b->topo_groups, g_node_id_c)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "B did not learn about C in time"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// 3. Get target info for C and send route ping request from A to B
|
||||
struct ETCP_CONN* conn_ab = find_conn_to_peer(inst_a, g_node_id_b); |
||||
if (!conn_ab) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "A has no connection to B"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
/* Получаем target IP:port из nodeinfo узла C */ |
||||
/* addr[] в network byte order, port в host byte order */ |
||||
uint32_t target_ip = 0; |
||||
uint16_t target_port = 0; |
||||
struct TOPO_GROUP_NODE* nq = topo_groups_get_default(inst_b->topo_groups) ? topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), g_node_id_c) : NULL; |
||||
struct TOPO_NODE* ni = nq ? topo_node_registry_find(inst_b->topo_groups, nq->node_id) : NULL; |
||||
if (nq && ni) { |
||||
const struct TOPO_ADDR4* a = ni->v4_addrs; |
||||
if (a) { |
||||
memcpy(&target_ip, a->addr, 4); |
||||
target_port = a->port; |
||||
} |
||||
} |
||||
if (target_ip == 0 || target_port == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Cannot resolve target for node C"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending route ping request A->B for C (%s:%u)", |
||||
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port); |
||||
memset(&ping_result, 0, sizeof(ping_result)); |
||||
int ret = route_ping_send_req_addr(inst_a->nat_det, topo_groups_get_default(inst_a->topo_groups), conn_ab, target_ip, target_port, |
||||
3, // count
|
||||
10, // interval_ms
|
||||
200, // timeout_ms
|
||||
5000,// wait_timeout_ms
|
||||
ping_resp_cb, NULL, |
||||
ni ? ni->public_key : NULL); |
||||
if (ret != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr failed: %d", ret); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// 4. Wait for response
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for route ping response..."); |
||||
while (!test_timed_out && !ping_result.done) { |
||||
uasync_poll(ua, 10); |
||||
} |
||||
|
||||
if (test_timed_out) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Timeout waiting for ping response"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
// 5. Verify results
|
||||
if (!ping_result.success) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ping reported failure (success=0)"); |
||||
goto cleanup; |
||||
} |
||||
if (ping_result.count_ok == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ping count_ok is zero"); |
||||
goto cleanup; |
||||
} |
||||
if (ping_result.avg_rtt == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ping avg_rtt is zero"); |
||||
goto cleanup; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route ping test PASSED: sent=%u ok=%u avg_rtt=%u", |
||||
(unsigned)ping_result.count_sent, (unsigned)ping_result.count_ok, (unsigned)ping_result.avg_rtt); |
||||
test_result = 0; |
||||
|
||||
cleanup: |
||||
if (test_timeout_id) uasync_cancel_timeout(ua, test_timeout_id); |
||||
if (inst_a) utun_instance_destroy(inst_a); |
||||
if (inst_b) utun_instance_destroy(inst_b); |
||||
if (inst_c) utun_instance_destroy(inst_c); |
||||
if (ua) uasync_destroy(ua, 0); |
||||
cleanup_temp_configs(); |
||||
return test_result; |
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_NAT, DEBUG_LEVEL_DEBUG); |
||||
address_encoding(); |
||||
struct nat_fixture fixture; nat_fixture_init(&fixture); |
||||
struct UTUN_INSTANCE *owner = nat_fixture_node(&fixture, "127.0.0.2", NULL, NULL); |
||||
struct UTUN_INSTANCE *observer = nat_fixture_node(&fixture, "127.0.0.3", NULL, NULL); |
||||
struct UTUN_INSTANCE *foreign = nat_fixture_node(&fixture, "127.0.0.4", NULL, NULL); |
||||
struct ETCP_SOCKET *socket = test_etcp_bound_socket(owner, "udp"); |
||||
struct ETCP_SOCKET *peer = test_etcp_bound_socket(observer, "udp"); |
||||
const uint64_t round = UINT64_C(0x0102030405060708); |
||||
struct result result = {0}; |
||||
assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, round, 500, ping_done, &result) == 0); |
||||
wait_result(&fixture, &result); |
||||
struct sockaddr_storage observed; |
||||
assert(result.success && route_ping_observation(result.data, result.length, socket, round, &observed) == 0); |
||||
assert(route_ping_addr_equal(&observed, &socket->local_addr, 1)); |
||||
assert(route_ping_observation(result.data, result.length, socket, round + 1, &observed) < 0); |
||||
assert(socket->address_profile.mapping == ADDRESS_MAP_UNKNOWN); /* один ответ не назначает NAT-тип */ |
||||
|
||||
memset(&result, 0, sizeof(result)); |
||||
struct ETCP_SOCKET *untrusted = test_etcp_bound_socket(foreign, "udp"); |
||||
assert(route_ping_observe(socket, observer->my_keys.public_key, &untrusted->local_addr, |
||||
round + 1, 300, ping_done, &result) == 0); |
||||
assert(owner->pending_pings); |
||||
send_foreign_pong(foreign, socket, owner->pending_pings->nonce); |
||||
wait_result(&fixture, &result); |
||||
assert(!result.success && !owner->pending_pings); |
||||
|
||||
memset(&result, 0, sizeof(result)); |
||||
assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, round + 2, 300, ping_done, &result) == 0); |
||||
uint64_t generation = socket->generation; socket->generation = generation == UINT64_MAX ? generation - 1 : generation + 1; |
||||
wait_result(&fixture, &result); assert(!result.success); socket->generation = generation; |
||||
|
||||
memset(&result, 0, sizeof(result)); |
||||
assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, round + 3, 500, ping_done, &result) == 0); |
||||
etcp_cancel_pings(owner, ping_done, &result); assert(!owner->pending_pings); |
||||
uint64_t end = get_time_tb() + 6000; |
||||
while (get_time_tb() < end) uasync_poll(fixture.ua, 10); |
||||
assert(!result.done); |
||||
nat_fixture_cleanup(&fixture); |
||||
return 0; |
||||
} |
||||
|
||||
@ -1,88 +1,46 @@
|
||||
/**
|
||||
* @file test_sock_match.c |
||||
* @brief Unit-тесты классификации сокетов и сопоставления адресов (sock_match). |
||||
* |
||||
* Тестирует только чистую логику sock_role_of / sock_target_class / sock_match |
||||
* без сети и без UTUN_INSTANCE — с синтетическими struct sock_view и адресами. |
||||
*/ |
||||
#include <stdio.h> |
||||
/* Политика не заменяет измерение; неопределённость разрешает ограниченную пробу. */ |
||||
#include <assert.h> |
||||
#include <string.h> |
||||
#include "../src/routing_layer/sock_match.h" |
||||
#include "../src/config_parser.h" |
||||
#include "../src/transport_layer/etcp_connections.h" |
||||
#include "../src/routing_layer/topo_node.h" |
||||
#include "routing_layer/sock_match.h" |
||||
#include "routing_layer/topo_node.h" |
||||
#include "config_parser.h" |
||||
#include "../lib/platform_compat.h" |
||||
|
||||
static int fails = 0; |
||||
static int checks = 0; |
||||
|
||||
#define CHECK(cond, msg) do { checks++; if (!(cond)) { fails++; printf("FAIL: %s\n", msg); } } while (0) |
||||
|
||||
static struct sock_view mk_view(uint8_t role, uint8_t has_inet, uint8_t has_priv, uint8_t is_eim, uint32_t pub4) { |
||||
struct sock_view v; memset(&v, 0, sizeof(v)); |
||||
v.role = role; v.has_inet = has_inet; v.has_priv = has_priv; v.is_eim = is_eim; v.public_ip4 = pub4; |
||||
return v; |
||||
} |
||||
|
||||
/* Классификация IPv4-адреса (network byte order): публичный/маршрутизируемый или нет.
|
||||
* Адрес задаём «логически» (старший байт = первый октет) и переводим в s_addr через htonl. */ |
||||
static void test_ip_is_public(void) { |
||||
CHECK(ip_is_public(htonl(0x08080808u)) == 1, "8.8.8.8 public"); |
||||
CHECK(ip_is_public(htonl(0x01020304u)) == 1, "1.2.3.4 public"); |
||||
CHECK(ip_is_public(htonl(0xC0A80101u)) == 0, "192.168.1.1 private"); |
||||
CHECK(ip_is_public(htonl(0x0A000001u)) == 0, "10.0.0.1 private"); |
||||
CHECK(ip_is_public(htonl(0xAC100001u)) == 0, "172.16.0.1 private"); |
||||
CHECK(ip_is_public(htonl(0xAC1F0001u)) == 0, "172.31.0.1 private"); |
||||
CHECK(ip_is_public(htonl(0x64400001u)) == 0, "100.64.0.1 CGNAT"); |
||||
CHECK(ip_is_public(htonl(0x7F000001u)) == 0, "127.0.0.1 loopback"); |
||||
CHECK(ip_is_public(htonl(0xA9FE0101u)) == 0, "169.254.1.1 link-local"); |
||||
CHECK(ip_is_public(htonl(0x00000000u)) == 0, "0.0.0.0"); |
||||
CHECK(ip_is_public(htonl(0xE0000001u)) == 0, "224.0.0.1 multicast"); |
||||
CHECK(ip_is_public(htonl(0xFFFFFFFFu)) == 0, "255.255.255.255 reserved"); |
||||
} |
||||
|
||||
static void test_target_class(void) { |
||||
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN) == SOCK_TC_PUB, "public INTERFACE -> PUB"); |
||||
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_UNKNOWN, NAT_TYPE_UNKNOWN) == SOCK_TC_PUB, "unknown -> PUB"); |
||||
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "local -> PRIV"); |
||||
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PRIVATE, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "private -> PRIV"); |
||||
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM) == SOCK_TC_PUB, "nat+EIM NAT-addr -> PUB"); |
||||
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM) == SOCK_TC_PRIV, "nat+EIM INTERFACE -> PRIV"); |
||||
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_DIRECT) == SOCK_TC_PUB, "nat+DIRECT NAT-addr -> PUB"); |
||||
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT) == SOCK_TC_UNREACH, "nat+STRICT NAT-addr -> UNREACH"); |
||||
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT) == SOCK_TC_PRIV, "nat+STRICT INTERFACE -> PRIV"); |
||||
CHECK(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN) == SOCK_TC_UNREACH, "nat+unknown NAT-addr -> UNREACH"); |
||||
CHECK(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_TYPE_UNKNOWN) == SOCK_TC_PRIV, "nat+unknown INTERFACE -> PRIV"); |
||||
} |
||||
|
||||
static void test_match(void) { |
||||
uint32_t pub = 0x01020304u; /* произвольный "публичный" IP */ |
||||
struct sock_view direct = mk_view(SOCK_ROLE_DIRECT, 1, 0, 0, pub); |
||||
struct sock_view natout = mk_view(SOCK_ROLE_NAT, 1, 1, 0, 0); |
||||
struct sock_view local = mk_view(SOCK_ROLE_LOCAL, 0, 1, 0, 0); |
||||
|
||||
/* наш DIRECT (публичный) */ |
||||
CHECK(sock_match(&direct, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 1, "DIRECT->PUB ok"); |
||||
CHECK(sock_match(&direct, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == 0, "DIRECT->PRIV нет приватного интерфейса"); |
||||
/* наш NAT (outbound-only) */ |
||||
CHECK(sock_match(&natout, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 1, "NAT->PUB ok"); |
||||
CHECK(sock_match(&natout, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT, 0) == 1, "NAT->PRIV (есть приватный)"); |
||||
/* наш LOCAL (без интернета) */ |
||||
CHECK(sock_match(&local, TOPO_ADDR_NAT, CFG_SERVER_TYPE_PUBLIC, NAT_TYPE_UNKNOWN, pub) == 0, "LOCAL->PUB нет интернета"); |
||||
CHECK(sock_match(&local, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, NAT_TYPE_UNKNOWN, 0) == 1, "LOCAL->PRIV ок (LAN)"); |
||||
/* UNREACH цель */ |
||||
CHECK(sock_match(&direct, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_STRICT, pub) == 0, "строгий NAT-адрес недостижим"); |
||||
/* hairpin: оба EIM, одинаковый публичный IP */ |
||||
struct sock_view eim = mk_view(SOCK_ROLE_DIRECT, 1, 1, 1, pub); |
||||
CHECK(sock_match(&eim, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, pub) == 0, "hairpin (одинаковый публичный IP)"); |
||||
CHECK(sock_match(&eim, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, NAT_VERIFIED_EIM, 0x05060708u) == 1, "EIM разные публичные IP -> ok"); |
||||
} |
||||
#include "../lib/debug_config.h" |
||||
|
||||
int main(void) { |
||||
test_ip_is_public(); |
||||
test_target_class(); |
||||
test_match(); |
||||
if (fails) { printf("RESULT: %d/%d failed\n", fails, checks); return 1; } |
||||
printf("RESULT: all %d checks passed\n", checks); |
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); |
||||
const uint64_t now = UINT64_C(1700000000000000); |
||||
const uint32_t public_ip = htonl(UINT32_C(0xc633640b)); |
||||
const uint32_t private_ip = htonl(UINT32_C(0x0a400102)); |
||||
struct address_profile unknown = {0}; |
||||
struct address_profile open = {.mapping = ADDRESS_MAP_EIM, .inbound = ADDRESS_IN_OPEN, .evidence_id = 1, .checked_at = now}; |
||||
struct address_profile contact = open; contact.inbound = ADDRESS_IN_CONTACT; |
||||
struct address_profile dependent = open; dependent.mapping = ADDRESS_MAP_DEPENDENT; dependent.inbound = ADDRESS_IN_UNKNOWN; |
||||
|
||||
assert(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PUBLIC, &unknown, 1, now) == SOCK_TC_PROBE); |
||||
assert(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PUBLIC, &open, 0, now) == SOCK_TC_PRIV); |
||||
assert(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &open, 1, now) == SOCK_TC_PUB); |
||||
assert(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &contact, 1, now) == SOCK_TC_CONTACT); |
||||
assert(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &unknown, 1, now) == SOCK_TC_PROBE); |
||||
assert(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &dependent, 1, now) == SOCK_TC_UNREACH); |
||||
assert(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, &dependent, 0, now) == SOCK_TC_PRIV); |
||||
assert(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_LOCAL, &open, 1, now) == SOCK_TC_PRIV); |
||||
assert(sock_target_class(TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_PRIVATE, &open, 1, now) == SOCK_TC_PRIV); |
||||
assert(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &open, 1, now + ADDRESS_PROFILE_TTL_US + 1) == SOCK_TC_PROBE); |
||||
assert(sock_target_class(TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &open, 1, now - 1) == SOCK_TC_PROBE); |
||||
|
||||
struct sock_view outgoing = {.has_inet = 1, .has_priv = 1, .now_us = now, .public_ip4 = public_ip}; |
||||
struct sock_view local = {.has_priv = 1, .now_us = now}; |
||||
assert(sock_match(&outgoing, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &open, public_ip)); |
||||
assert(sock_match(&outgoing, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &contact, public_ip)); |
||||
/* Совпадение публичного IP разрешает проверку hairpin. */ |
||||
outgoing.address_profile = open; |
||||
assert(sock_match(&outgoing, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &open, public_ip)); |
||||
assert(!sock_match(&local, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &open, public_ip)); |
||||
assert(sock_match(&local, TOPO_ADDR_INTERFACE, CFG_SERVER_TYPE_NAT, &dependent, private_ip)); |
||||
assert(!sock_match(&outgoing, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &dependent, public_ip)); |
||||
outgoing.now_us = now + ADDRESS_PROFILE_TTL_US + 1; |
||||
assert(sock_match(&outgoing, TOPO_ADDR_NAT, CFG_SERVER_TYPE_NAT, &open, public_ip)); |
||||
/* Смена времени/давности снижает уверенность, сохраняя возможность реального handshake. */ |
||||
return 0; |
||||
} |
||||
|
||||
Loading…
Reference in new issue