From b466d14fe469ccc390ac2eec7eca249dcfed8697 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 22 Apr 2026 18:16:49 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BB=D0=B5=20interface=5Faddr=20?= =?UTF-8?q?=D0=B2=20ETCP=5FSOCKET=20=D0=B8=20interface=5Fip/port=20=D0=B2?= =?UTF-8?q?=20BGP=5FNAT=5FCHECK=5FREQ?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - platform_compat: get_interface_ip_by_index() для получения IP по ifindex - ETCP_SOCKET: interface_addr заполняется из конфига/interface/default route - BGP_NAT_CHECK_REQ: добавлены interface_ip/interface_port (network byte order) - route_bgp_send_nat_check_req: отправляет interface_addr линка - route_bgp_handle_nat_check_req: принимает interface данные в sockaddr-заглушку --- lib/platform_compat.c | 28 ++++++ lib/platform_compat.h | 4 + nat.txt | 219 +++++++++++++++++++++++++++++++++++++++++ src/etcp_connections.c | 36 +++++++ src/etcp_connections.h | 1 + src/route_bgp.c | 34 ++++++- src/route_bgp.h | 2 + 7 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 nat.txt diff --git a/lib/platform_compat.c b/lib/platform_compat.c index b815c12b..fbfa3063 100644 --- a/lib/platform_compat.c +++ b/lib/platform_compat.c @@ -136,3 +136,31 @@ uint32_t get_default_route_ip(void) { return get_default_route_ip_linux(); #endif } + +#ifndef _WIN32 +uint32_t get_interface_ip_by_index(uint32_t netif_index) { + struct ifaddrs *ifaddr, *ifa; + if (getifaddrs(&ifaddr) == -1) return 0; + + uint32_t result = 0; + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) continue; + if (ifa->ifa_name == NULL) continue; + unsigned int idx = if_nametoindex(ifa->ifa_name); + if (idx != netif_index) continue; + if (ifa->ifa_addr->sa_family != AF_INET) continue; + + struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr; + result = addr->sin_addr.s_addr; + break; + } + + freeifaddrs(ifaddr); + return result; +} +#else +uint32_t get_interface_ip_by_index(uint32_t netif_index) { + (void)netif_index; + return 0; +} +#endif diff --git a/lib/platform_compat.h b/lib/platform_compat.h index b5b67d5b..ecc9e10d 100644 --- a/lib/platform_compat.h +++ b/lib/platform_compat.h @@ -159,5 +159,9 @@ int random_bytes(uint8_t *buffer, size_t len); // Returns IPv4 address in network byte order, 0 on error uint32_t get_default_route_ip(void); +// Get IPv4 address of interface by its index (ifindex) +// Returns IPv4 address in network byte order, 0 on error +uint32_t get_interface_ip_by_index(uint32_t netif_index); + #endif // PLATFORM_COMPAT_H diff --git a/nat.txt b/nat.txt new file mode 100644 index 00000000..167c8fd2 --- /dev/null +++ b/nat.txt @@ -0,0 +1,219 @@ +Диаграмма вызовов функций при определении типа NAT (NAT detection) +===================================================================== + +=== ИНИЦИАЛИЗАЦИЯ BGP === + +route_bgp_init(instance) + └── etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL) + └── При создании нового ETCP_CONN: + route_bgp_etcp_conn_cbk(conn, arg) + ├── etcp_conn_set_up_cbk(conn, route_bgp_on_conn_up, bgp) + └── etcp_conn_set_down_cbk(conn, route_bgp_on_conn_down, bgp) + + +=== УСТАНОВКА СОЕДИНЕНИЯ (сервер получает INIT_REQUEST) === + +etcp_connections_read_callback_socket(sock, arg) + └── Получает ETCP_INIT_REQUEST + ├── etcp_connection_create(instance, name) [если новый peer] + │ └── Вызывает instance->etcp_new_conn_cbk [route_bgp_etcp_conn_cbk] + ├── etcp_link_new(etcp, e_sock, remote_addr, is_server=1) + ├── Отправляет ETCP_INIT_RESPONSE (с NAT info) + ├── link->initialized = 1 + └── etcp_conn_ready(link->etcp) + └── conn->ready_cbk + └── etcp_on_up(etcp) + └── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp) + + +=== УСТАНОВКА СОЕДИНЕНИЯ (клиент получает INIT_RESPONSE) === + +etcp_connections_read_callback_socket(sock, arg) + └── Получает ETCP_INIT_RESPONSE + ├── Сохраняет NAT address: link->nat_ip, link->nat_port + ├── link->initialized = 1 + └── etcp_conn_ready(link->etcp) + └── etcp_on_up(etcp) + └── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp) + + +=== ЗАПУСК ПРОВЕРКИ NAT (при поднятии соединения) === + +route_bgp_on_conn_up(etcp, arg) + └── route_bgp_new_conn(conn) + ├── route_bgp_add_to_senders(bgp, conn) + └── Сканирует ВСЕ линки ВСЕХ соединений: + if (link->initialized && link->conn && link->nat_check_status < NAT_CHECK_IN_PROGRESS) + └── route_bgp_start_link_nat_check(bgp, link) + ├── if (nat_check_status == IN_PROGRESS) return + ├── route_bgp_find_third_node(bgp, exclude=link->etcp) + │ └── Ищет другой ETCP_CONN в senders_list + ├── Определяет target_ip/target_port: + │ ├── Если link->nat_ip != 0: использует nat_ip/nat_port + │ └── Иначе: использует link->remote_addr + ├── Проверяет is_local_subnet(target_ip) [если запрещено - skip] + ├── route_bgp_extract_nat_addr(conn, &nat_ip, &nat_port) + │ └── Ищет первый линк с nat_ip != 0 + ├── Создает nat_check_arg { link, nat_ip, nat_port } + └── route_ping_send_req_addr(bgp, third_conn, target_ip, target_port, + count=3, interval=500ms, timeout=1000ms, + wait_timeout=5000ms, + cb=nat_link_check_cb, arg=nat_check_arg, + pubkey=peer_pubkey) + + +=== ОТПРАВКА ЗАПРОСА НА ПИНГ (через third node) === + +route_ping_send_req_addr(bgp, to_conn, target_ip, target_port, ...) + ├── Создает BGP_PING_REQUEST { cmd, subcmd=PING_REQ, request_id, count, interval, timeout, target_ipv4, target_port, pubkey } + ├── etcp_send(to_conn, entry) [отправляет запрос third node] + ├── Создает route_ping_pending { request_id, callback=nat_link_check_cb, arg, timeout_timer } + └── Добавляет в bgp->ping_pending + + +=== ОБРАБОТКА PING_REQ (на third node) === + +route_bgp_receive_cbk(data, len, from_conn, bgp) + └── ROUTE_SUBCMD_PING_REQ + └── route_ping_handle_req(bgp, from_conn, data, len) + ├── Создает route_ping_series_ctx { reply_conn, request_id, target_addr, pubkey, local_sock, count_total } + ├── Находит первый IPv4 ETCP_SOCKET -> local_sock + └── etcp_send_ping_to_socket(instance, local_sock, pubkey, &target_addr, + timeout_ms, route_ping_single_cb, ctx, NULL, 0) + └── Отправляет ETCP пинг на target_addr + + +=== ОТВЕТ НА ПИНГ (целевой узел) === + +etcp_connections_read_callback_socket() + └── Получает ETCP пинг -> отправляет pong + + +=== ПОЛУЧЕНИЕ ОТВЕТА НА ПИНГ (third node) === + +route_ping_single_cb(success, rtt, arg, nonce, resp_data, resp_data_len) + ├── Обновляет статистику: count_sent++, count_ok++, sum_rtt += rtt + ├── Если count_sent < count_total: + │ └── etcp_send_ping_to_socket(...) [следующий пинг] + └── Иначе: + └── route_ping_series_finish(ctx) + ├── Вычисляет avg_rtt + └── Создает BGP_PING_RESPONSE { cmd, subcmd=PING_RESP, request_id, count_sent, count_ok, avg_rtt } + └── etcp_send(reply_conn, entry) [отправляет результат инициатору] + + +=== ПОЛУЧЕНИЕ PING_RESP (на инициаторе) === + +route_bgp_receive_cbk(data, len, from_conn, bgp) + └── ROUTE_SUBCMD_PING_RESP + └── route_ping_handle_resp(bgp, from_conn, data, len) + ├── Ищет route_ping_pending по request_id + ├── Отменяет timeout_timer + └── Вызывает callback: nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg) + + +=== CALLBACK ПРОВЕРКИ NAT === + +nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg) + ├── Определяет nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED + ├── link->nat_type = nat_type + ├── link->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED + └── route_bgp_send_nat_info(link->etcp, socket_id, nat_ip, nat_port, nat_type) + ├── Создает BGP_NAT_INFO { cmd, subcmd=NAT_INFO, socket_id, nat_ip[4], nat_port, nat_type } + └── etcp_send(conn, entry) [отправляет результат клиенту] + + +=== ПРИЕМ NAT_INFO (на клиенте) === + +route_bgp_receive_cbk(data, len, from_conn, bgp) + └── ROUTE_SUBCMD_NAT_INFO + └── route_bgp_handle_nat_info(bgp, from_conn, data, len) + ├── Ищет линки по remote_socket_id + ├── Устанавливает link->nat_type = info->nat_type + └── Логирует результат NAT detection + + +=== ЗАПРОС НА ПРОВЕРКУ NAT (NAT_CHECK_REQ - новый механизм) === + +route_bgp_send_nat_check_req(conn, socket_id) + ├── Создает BGP_NAT_CHECK_REQ { cmd, subcmd=NAT_CHECK_REQ, socket_id, interface_ip, interface_port } + │ ├── Ищет линк с remote_socket_id == socket_id + │ └── Берет interface_addr из линка -> interface_ip/interface_port + └── etcp_send(conn, entry) + +route_bgp_handle_nat_check_req(bgp, from_conn, data, len) + ├── Принимает BGP_NAT_CHECK_REQ + ├── Сохраняет interface_ip/interface_port в struct sockaddr_storage dummy + ├── Ищет линк с remote_socket_id == req->socket_id + └── Если линк найден и не в процессе проверки: + └── route_bgp_start_link_nat_check(bgp, target_link) + [далее по стандартному flow] + + +=== СВЯЗЬ СТРУКТУР === + +struct ETCP_SOCKET + ├── local_addr [адрес бинда из конфига] + ├── interface_addr [IP интерфейса + порт: интерфейс/default route/конфиг] + └── ... + +struct ETCP_LINK + ├── remote_addr [адрес удаленного узла] + ├── nat_ip [NAT IP, полученный из INIT_RESPONSE] + ├── nat_port [NAT port, полученный из INIT_RESPONSE] + ├── nat_type [OPEN/RESTRICTED, результат проверки] + ├── nat_check_status [NONE/WAITING/IN_PROGRESS/OPEN/RESTRICTED] + ├── initialized [0/1, флаг завершения handshake] + └── ... + +struct ETCP_CONN + ├── links [список ETCP_LINK] + ├── up_cbk [route_bgp_on_conn_up] + ├── down_cbk [route_bgp_on_conn_down] + ├── ready_cbk [вызывается при etcp_conn_ready] + ├── initialized [0/1, флаг готовности] + ├── links_up [0/1, есть ли активные линки] + └── ... + + +=== СТАТУСЫ NAT CHECK === + +NAT_CHECK_NONE = 0 [не проверялся] +NAT_CHECK_WAITING = 1 [ожидает] +NAT_CHECK_IN_PROGRESS = 2 [проверка запущена] +NAT_CHECK_OPEN = 3 [результат: OPEN] +NAT_CHECK_RESTRICTED = 4 [результат: RESTRICTED] + +NAT_TYPE_UNKNOWN = 0 +NAT_TYPE_OPEN = 1 +NAT_TYPE_RESTRICTED = 2 + +NAT_VERIFIED_UNKNOWN = 4 +NAT_VERIFIED_OPEN = 5 +NAT_VERIFIED_RESTRICTED = 6 +NAT_VERIFIED_DIRECT = 7 + + +=== СООБЩЕНИЯ ПРОТОКОЛА === + +ETCP_INIT_REQUEST (0x02) [клиент -> сервер] +ETCP_INIT_RESPONSE (0x03) [сервер -> клиент, включает NAT info] +ETCP_INIT_REQUEST_NOINIT (0x04) +ETCP_INIT_RESPONSE_NOINIT (0x05) +ETCP_PING (0x06) +ETCP_PONG (0x07) + +ROUTE_SUBCMD_NODEINFO (0x04) +ROUTE_SUBCMD_REQUEST_TABLE (0x05) +ROUTE_SUBCMD_WITHDRAW (0x06) +ROUTE_SUBCMD_PING_REQ (0x07) +ROUTE_SUBCMD_PING_RESP (0x08) +ROUTE_SUBCMD_NAT_INFO (0x09) +ROUTE_SUBCMD_NAT_CHECK_REQ (0x0A) + + +=== ПОЛНЫЙ FLOW (одна строка) === + +Handshake: etcp_connections_read_callback_socket -> INIT_REQUEST/RESPONSE -> link->initialized=1 -> etcp_conn_ready -> etcp_on_up -> route_bgp_on_conn_up -> route_bgp_new_conn -> route_bgp_start_link_nat_check -> route_ping_send_req_addr -> (через ETCP) -> route_ping_handle_req -> etcp_send_ping_to_socket -> (UDP) -> route_ping_single_cb -> route_ping_series_finish -> route_ping_handle_resp -> nat_link_check_cb -> route_bgp_send_nat_info -> route_bgp_handle_nat_info + +NAT_CHECK_REQ flow: route_bgp_send_nat_check_req -> BGP_NAT_CHECK_REQ -> route_bgp_handle_nat_check_req -> route_bgp_start_link_nat_check -> [стандартный ping flow] diff --git a/src/etcp_connections.c b/src/etcp_connections.c index fc2598a1..da05841d 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -550,6 +550,42 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Listen socket initialized: name=%s fd=%d addr=%s:%d", e_sock->name, e_sock->fd, ip_to_str(&sin6->sin6_addr, AF_INET6).str, ntohs(sin6->sin6_port)); } } + // Определяем interface_addr: IP интерфейса или из конфига + memset(&e_sock->interface_addr, 0, sizeof(e_sock->interface_addr)); + if (netif_index > 0) { + uint32_t if_ip = get_interface_ip_by_index(netif_index); + if (if_ip != 0) { + struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->interface_addr; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = if_ip; + } + } else if (ip && ip->ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)ip; + if (sin->sin_addr.s_addr == 0) { + uint32_t def_ip = get_default_route_ip(); + if (def_ip != 0) { + struct sockaddr_in* isin = (struct sockaddr_in*)&e_sock->interface_addr; + isin->sin_family = AF_INET; + isin->sin_addr.s_addr = def_ip; + } + } + } + if (((struct sockaddr_in*)&e_sock->interface_addr)->sin_addr.s_addr == 0) { + if (ip) memcpy(&e_sock->interface_addr, ip, sizeof(struct sockaddr_storage)); + } else { + // скопируем порт из конфига в interface_addr + if (ip && ip->ss_family == AF_INET) { + struct sockaddr_in* sin_cfg = (struct sockaddr_in*)ip; + struct sockaddr_in* sin_if = (struct sockaddr_in*)&e_sock->interface_addr; + sin_if->sin_port = sin_cfg->sin_port; + } + } + { + struct sockaddr_in* isin = (struct sockaddr_in*)&e_sock->interface_addr; + if (isin->sin_addr.s_addr != 0) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[ETCP] interface_addr for %s: %s:%d", e_sock->name, ip_to_str(&isin->sin_addr, AF_INET).str, ntohs(isin->sin_port)); + } + } e_sock->instance = instance; e_sock->errorcode = 0; e_sock->pkt_format_errors = 0; diff --git a/src/etcp_connections.h b/src/etcp_connections.h index 1030b6ab..cd5bad3f 100644 --- a/src/etcp_connections.h +++ b/src/etcp_connections.h @@ -73,6 +73,7 @@ struct ETCP_SOCKET { uint32_t local_defaultroute_ip; // auto-detected IPv4 for public servers (network byte order) uint8_t local_defaultroute_ip6[16]; // auto-detected IPv6 for public servers uint8_t only_local; // 1 = only local connections, no forwarding + struct sockaddr_storage interface_addr; // адрес интерфейса: если bind на интерфейс - его IP, если 0.0.0.0 без интерфейса - IP интерфейса через который идет default route, иначе - адрес из конфига }; // NAT check status diff --git a/src/route_bgp.c b/src/route_bgp.c index c1a02afb..e1211ee2 100644 --- a/src/route_bgp.c +++ b/src/route_bgp.c @@ -995,6 +995,14 @@ static void route_bgp_handle_nat_check_req(struct ROUTE_BGP* bgp, struct ETCP_CO const struct BGP_NAT_CHECK_REQ* req = (const struct BGP_NAT_CHECK_REQ*)data; uint8_t socket_id = req->socket_id; + struct sockaddr_storage dummy; + memset(&dummy, 0, sizeof(dummy)); + if (req->interface_ip != 0) { + struct sockaddr_in* sin = (struct sockaddr_in*)&dummy; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = req->interface_ip; + sin->sin_port = req->interface_port; + } struct ETCP_LINK* target_link = NULL; struct ETCP_LINK* l = from_conn->links; @@ -1007,8 +1015,11 @@ static void route_bgp_handle_nat_check_req(struct ROUTE_BGP* bgp, struct ETCP_CO } if (!target_link) { - DEBUG_WARN(DEBUG_CATEGORY_BGP, "NAT_CHECK_REQ from %s: no link with remote_socket_id=%u", - from_conn->log_name, socket_id); + struct sockaddr_in* sin = (struct sockaddr_in*)&dummy; + DEBUG_WARN(DEBUG_CATEGORY_BGP, "NAT_CHECK_REQ from %s: no link with remote_socket_id=%u interface=%s:%u", + from_conn->log_name, socket_id, + sin->sin_addr.s_addr ? ip_to_str(&sin->sin_addr, AF_INET).str : "none", + (unsigned)ntohs(sin->sin_port)); } else if (target_link->nat_check_status == NAT_CHECK_IN_PROGRESS) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NAT_CHECK_REQ from %s: check already in progress for socket_id=%u", from_conn->log_name, socket_id); @@ -1060,6 +1071,19 @@ void route_bgp_send_nat_check_req(struct ETCP_CONN* conn, uint8_t socket_id) { pkt->subcmd = ROUTE_SUBCMD_NAT_CHECK_REQ; pkt->socket_id = socket_id; + struct ETCP_LINK* l = conn->links; + while (l) { + if (l->remote_socket_id == socket_id) { + struct sockaddr_in* sin = (struct sockaddr_in*)&l->conn->interface_addr; + if (sin->sin_family == AF_INET) { + pkt->interface_ip = sin->sin_addr.s_addr; + pkt->interface_port = sin->sin_port; + } + break; + } + l = l->next; + } + struct ll_entry* e = queue_entry_new(0); if (!e) { u_free(pkt); @@ -1068,6 +1092,8 @@ void route_bgp_send_nat_check_req(struct ETCP_CONN* conn, uint8_t socket_id) { e->dgram = (uint8_t*)pkt; e->len = sizeof(struct BGP_NAT_CHECK_REQ); etcp_send(conn, e); - DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_send_nat_check_req to %s: socket_id=%u", - conn->log_name, socket_id); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_send_nat_check_req to %s: socket_id=%u interface=%s:%u", + conn->log_name, socket_id, + ip_to_str(&pkt->interface_ip, AF_INET).str, + (unsigned)ntohs(pkt->interface_port)); } diff --git a/src/route_bgp.h b/src/route_bgp.h index 0f8a2d0d..3b473f51 100644 --- a/src/route_bgp.h +++ b/src/route_bgp.h @@ -73,6 +73,8 @@ struct BGP_NAT_CHECK_REQ { uint8_t cmd; uint8_t subcmd; // ROUTE_SUBCMD_NAT_CHECK_REQ uint8_t socket_id; // ID сокета клиента, который надо проверить + uint32_t interface_ip; // interface_addr IP клиента (network byte order) + uint16_t interface_port; // interface_addr port клиента (network byte order) } __attribute__((packed)); struct route_ping_pending;