From 72e2c89e3be83fee49b754bf023ef556e8f2bf57 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Oct 2026 14:24:32 +0200 Subject: [PATCH] Fix sockless TCP keepalive and require complete UTUN initialization Receive keepalive through the owning ETCP link, preserve RX deadlines, and report routing metrics without TUN. Use independent netlink requests and track installed routes for rollback. Reject incomplete resource initialization and release partially constructed cores while preserving caller config ownership. Validated startup failure and rollback, padded keepalive, standby, STCP traffic, transport lifecycle, dynamic sockets, and daemon startup. Real TUN/netlink checks passed in an isolated Linux network namespace; modified modules passed FreeBSD compilation checks. --- doc/service_lifecycle.md | 8 + src/control_server.c | 48 ++-- src/transport_layer/auto_socket.c | 54 ++-- src/transport_layer/etcp_connections.c | 23 +- src/transport_layer/etcp_keepalive.c | 15 +- src/transport_layer/etcp_keepalive.h | 3 +- src/tun_freebsd.c | 9 +- src/tun_if.c | 18 +- src/tun_linux.c | 11 +- src/tun_route.c | 339 +++++++++---------------- src/tun_route.h | 20 +- src/tun_route_doc.md | 22 +- src/tun_windows.c | 26 +- src/utun_instance.c | 72 +++--- src/utun_instance.h | 4 +- tests/Makefile.am | 7 +- tests/test_etcp_padding.c | 9 +- tests/test_etcp_server_lifecycle.c | 30 ++- tests/test_tun_route.c | 194 ++++++++++++++ 19 files changed, 539 insertions(+), 373 deletions(-) create mode 100644 tests/test_tun_route.c diff --git a/doc/service_lifecycle.md b/doc/service_lifecycle.md index 56919360..60ce8866 100644 --- a/doc/service_lifecycle.md +++ b/doc/service_lifecycle.md @@ -27,6 +27,14 @@ utun_instance_destroy(inst); сервиса откатывает созданные им ресурсы. `utun_instance_stop()` останавливает цикл приложения; освобождение ядра выполняет `utun_instance_destroy()`. +Успех запуска требует успешной инициализации всех обязательных ресурсов: заданных +listen-сокетов, мониторинга, TUN и всех `route_subnet` при включённом TUN. Частичный +набор сокетов или маршрутов — ошибка запуска. `utun_started` устанавливается только +после завершения всех шагов. Системные маршруты хранятся отдельным списком успешно +созданных записей; stop и откат удаляют только этот список, в обратном порядке. +Маршрут, на добавлении которого ядро вернуло ошибку, не становится собственностью +UTUN и при откате не удаляется. Ошибка одного удаления не прекращает очистку остальных. + | Владелец | Ресурсы | |---|---| | Ядро | Идентичность узла, реестр узлов и групп, SQLite, транспортные сокеты, NCD, маршрутизатор, общая репликация, время, мониторинг | diff --git a/src/control_server.c b/src/control_server.c index 88b85528..0a0a4979 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -1169,7 +1169,8 @@ static void send_metrics(struct control_server* server, struct control_client* c rsp->etcp.busy_memory_blocks = (uint32_t)(u_get_allocated_count() - memory_pool_get_total_free_blocks()); - /* Fill TUN metrics */ + /* TUN отсутствует в режиме прокси; маршруты и BGP работают независимо. */ + memset(&rsp->tun, 0, sizeof(rsp->tun)); if (instance->tun) { rsp->tun.bytes_read = instance->tun->bytes_read; rsp->tun.bytes_written = instance->tun->bytes_written; @@ -1178,38 +1179,25 @@ static void send_metrics(struct control_server* server, struct control_client* c rsp->tun.read_errors = instance->tun->read_errors; rsp->tun.write_errors = instance->tun->write_errors; - /* Routing statistics */ - rsp->tun.routed_packets = instance->routed_packets; - rsp->tun.dropped_packets = instance->dropped_packets; - /* TUN queues */ rsp->tun.tun_in_q_packets = (uint32_t)queue_entry_count(instance->tun->input_queue); rsp->tun.tun_in_q_bytes = (uint32_t)queue_total_bytes(instance->tun->input_queue); rsp->tun.tun_out_q_packets = (uint32_t)queue_entry_count(instance->tun->output_queue); rsp->tun.tun_out_q_bytes = (uint32_t)queue_total_bytes(instance->tun->output_queue); - - /* Routing table */ - if (instance->rt) { - rsp->tun.rt_count = (uint32_t)instance->rt->count; - rsp->tun.rt_local = (uint32_t)instance->rt->stats.local_routes; - rsp->tun.rt_learned = (uint32_t)instance->rt->stats.learned_routes; - } else { - rsp->tun.rt_count = 0; - rsp->tun.rt_local = 0; - rsp->tun.rt_learned = 0; - } + } - /* BGP routing */ - { struct TOPO_GROUP* g = topo_groups_get_default(instance->topo_groups); - if (g) { - rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(g->senders_list); - rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(g->nodes); - } else { - rsp->tun.rt_bgp_senders = 0; - rsp->tun.rt_bgp_nodes = 0; - } } - } else { - memset(&rsp->tun, 0, sizeof(rsp->tun)); + /* Статистика маршрутизации не зависит от наличия TUN. */ + rsp->tun.routed_packets = instance->routed_packets; + rsp->tun.dropped_packets = instance->dropped_packets; + if (instance->rt) { + rsp->tun.rt_count = (uint32_t)instance->rt->count; + rsp->tun.rt_local = (uint32_t)instance->rt->stats.local_routes; + rsp->tun.rt_learned = (uint32_t)instance->rt->stats.learned_routes; + } + struct TOPO_GROUP* group = topo_groups_get_default(instance->topo_groups); + if (group) { + rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(group->senders_list); + rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(group->nodes); } /* Fill router congestion metrics — aggregate over all router_conns */ @@ -1300,11 +1288,13 @@ static void send_metrics(struct control_server* server, struct control_client* c /* Log and send response */ if (server->log_file) { log_hex_data(server->log_file, "TX", buffer, rsp_size); - fprintf(server->log_file, "%llu: [LOG] Sent RSP_METRICS rtt=%u bytes=%llu links=%d\n", + fprintf(server->log_file, "%llu: [LOG] Sent RSP_METRICS rtt=%u bytes=%llu links=%d tun=%d " + "routes=%u local=%u learned=%u bgp_senders=%u bgp_nodes=%u\n", (unsigned long long)get_timestamp_ms(), rsp->etcp.rtt_last, (unsigned long long)rsp->etcp.bytes_sent_total, - links_count); + links_count, instance->tun != NULL, rsp->tun.rt_count, rsp->tun.rt_local, rsp->tun.rt_learned, + rsp->tun.rt_bgp_senders, rsp->tun.rt_bgp_nodes); fflush(server->log_file); } diff --git a/src/transport_layer/auto_socket.c b/src/transport_layer/auto_socket.c index f5d61800..3175c07e 100644 --- a/src/transport_layer/auto_socket.c +++ b/src/transport_layer/auto_socket.c @@ -155,9 +155,9 @@ static struct stcp_server* create_iface_tcp_socket(struct AUTO_SOCKET* as, uint3 int family, uint8_t type, struct ETCP_SOCKET** out_sock); static void remove_iface_sockets(struct AUTO_SOCKET* as, uint32_t ifindex); static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, int* out_has_v4, int* out_has_v6, uint8_t* out_v4_type, uint8_t* out_v6_type); -static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname); -static void auto_socket_scan_all(struct AUTO_SOCKET* as); -static void auto_socket_reconcile_config(struct AUTO_SOCKET* as); +static int reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname); +static int auto_socket_scan_all(struct AUTO_SOCKET* as); +static int auto_socket_reconcile_config(struct AUTO_SOCKET* as); /* ─── DB helpers (port persistence) ─── * Сохраняют порт сокета в SQLite чтобы переиспользовать его при перезапуске utun. @@ -655,9 +655,9 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, * Полное сканирование: обходит все UP-интерфейсы (не loopback) и для каждого * вызывает reconcile_iface. Используется при старте и при ручном запросе * пересканирования (auto_socket_on_network_change). */ -static void auto_socket_scan_all(struct AUTO_SOCKET* as) { +static int auto_socket_scan_all(struct AUTO_SOCKET* as) { struct ifaddrs* ifa_list = NULL; - if (getifaddrs(&ifa_list) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] getifaddrs: %s", strerror(errno)); return; } + if (getifaddrs(&ifa_list) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] getifaddrs: %s", strerror(errno)); return -1; } int up_count = 0; char up_names[64][IFNAMSIZ]; @@ -671,15 +671,17 @@ static void auto_socket_scan_all(struct AUTO_SOCKET* as) { for (int i = 0; i < up_count; i++) { if (strcmp(up_names[i], ifa->ifa_name) == 0) { found = 1; break; } } if (found) continue; - if (up_count >= 64) { DEBUG_WARN(DEBUG_CATEGORY_AS, "[as] too many interfaces, cap at 64"); break; } + if (up_count >= 64) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] too many interfaces, cap at 64"); freeifaddrs(ifa_list); return -1; } strncpy(up_names[up_count], ifa->ifa_name, IFNAMSIZ - 1); up_names[up_count][IFNAMSIZ - 1] = 0; up_count++; } freeifaddrs(ifa_list); + int result = 0; for (int i = 0; i < up_count; i++) - reconcile_iface(as, if_nametoindex(up_names[i]), up_names[i]); + if (reconcile_iface(as, if_nametoindex(up_names[i]), up_names[i]) < 0) result = -1; + return result; } #else /* _WIN32 */ @@ -690,11 +692,12 @@ static int win_collect_up_ifaces(uint32_t* out_ifindex, int max_count) { ULONG size = 0; ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; DWORD rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &size); - if (rv != ERROR_BUFFER_OVERFLOW) return 0; + if (rv == ERROR_NO_DATA) return 0; + if (rv != ERROR_BUFFER_OVERFLOW) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] GetAdaptersAddresses size failed: %lu", rv); return -1; } adapters = (PIP_ADAPTER_ADDRESSES)u_malloc(size); - if (!adapters) return 0; + if (!adapters) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] adapter list allocation failed"); return -1; } rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size); - if (rv != NO_ERROR) { u_free(adapters); return 0; } + if (rv != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] GetAdaptersAddresses failed: %lu", rv); u_free(adapters); return -1; } int count = 0; for (PIP_ADAPTER_ADDRESSES a = adapters; a && count < max_count; a = a->Next) { @@ -750,14 +753,16 @@ static int scan_and_classify_iface(uint32_t ifindex, const char* ifname, return 0; } -static void auto_socket_scan_all(struct AUTO_SOCKET* as) { +static int auto_socket_scan_all(struct AUTO_SOCKET* as) { uint32_t up[64]; int up_count = win_collect_up_ifaces(up, 64); + if (up_count < 0) return -1; + int result = 0; for (int i = 0; i < up_count; i++) { char ifname[IF_NAMESIZE]; if (!if_indextoname(up[i], ifname)) snprintf(ifname, sizeof(ifname), "if%u", up[i]); - reconcile_iface(as, up[i], ifname); + if (reconcile_iface(as, up[i], ifname) < 0) result = -1; } /* prune: удаляем сокеты адаптеров, которых больше нет в UP-списке (или они DOWN) */ @@ -769,6 +774,7 @@ static void auto_socket_scan_all(struct AUTO_SOCKET* as) { if (!found) { remove_iface_sockets(as, ifa->netif_index); continue; } pp = &ifa->next; } + return result; } #endif /* _WIN32 */ @@ -923,7 +929,7 @@ static struct auto_sock_iface* reconcile_get_ifa(struct AUTO_SOCKET* as, uint32_ struct auto_sock_iface* ifa = as->ifaces; while (ifa) { if (ifa->netif_index == ifindex) return ifa; ifa = ifa->next; } ifa = u_calloc(1, sizeof(*ifa)); - if (!ifa) return NULL; + if (!ifa) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] interface record allocation failed: %s", ifname); return NULL; } ifa->netif_index = ifindex; snprintf(ifa->ifname, sizeof(ifa->ifname), "%s", ifname); ifa->next = as->ifaces; @@ -956,13 +962,13 @@ static void reconcile_prune_ifa(struct AUTO_SOCKET* as, struct auto_sock_iface* * * В конце удаляет запись auto_sock_iface если все сокеты отсутствуют, * и оповещает GUI если были изменения. */ -static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname) { - if (ifindex == 0) return; +static int reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char* ifname) { + if (ifindex == 0) { DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] invalid interface index: %s", ifname); return -1; } int changed = 0; int has_v4 = 0, has_v6 = 0; uint8_t v4_need_type = CFG_SERVER_TYPE_NAT, v6_need_type = CFG_SERVER_TYPE_NAT; - if (scan_and_classify_iface(ifindex, ifname, &has_v4, &has_v6, &v4_need_type, &v6_need_type) < 0) return; + if (scan_and_classify_iface(ifindex, ifname, &has_v4, &has_v6, &v4_need_type, &v6_need_type) < 0) return -1; /* Фильтр авто-режима: интерфейс без default-маршрута не может достать до * публичных пиров — не создаём на нём сокеты (иначе только sendto ENETUNREACH). */ @@ -1100,8 +1106,12 @@ static void reconcile_iface(struct AUTO_SOCKET* as, uint32_t ifindex, const char } } + int complete = (!has_v4 || (ifa && ifa->v4_udp && ifa->v4_tcp)) && (!has_v6 || (ifa && ifa->v6_udp && ifa->v6_tcp)); + if (!complete) DEBUG_ERROR(DEBUG_CATEGORY_AS, "[as] incomplete socket initialization iface=%s index=%u v4=%d v6=%d", + ifname, ifindex, has_v4, has_v6); reconcile_prune_ifa(as, ifa, ifindex, ifname); if (changed) auto_socket_post_sockets_changed(as); + return complete ? 0 : -1; } /* ═══════════ Platform monitoring ═══════════ */ @@ -1300,7 +1310,7 @@ int auto_socket_init(struct UTUN_INSTANCE* inst) { /* Android-режим: источник сокетов — [server] секции конфига (генерирует Kotlin). * Системный мониторинг и таблица портов в БД не нужны. */ if (as->mode == CFG_AUTO_SOCKETS_ANDROID) { - auto_socket_reconcile_config(as); + if (auto_socket_reconcile_config(as) < 0) { auto_socket_destroy(inst); return -1; } DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized (android mode)"); return 0; } @@ -1319,7 +1329,7 @@ int auto_socket_init(struct UTUN_INSTANCE* inst) { if (auto_socket_init_monitor(as) < 0) { u_free(as); inst->auto_socket_state = NULL; return -1; } - auto_socket_scan_all(as); + if (auto_socket_scan_all(as) < 0) { auto_socket_destroy(inst); return -1; } DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] initialized"); return 0; @@ -1401,9 +1411,10 @@ static void remove_config_socket(struct AUTO_SOCKET* as, struct ETCP_SOCKET* soc /* Сверяет inst->config->servers с inst->etcp_sockets и устраняет расхождения: * удаляет сокеты, которых нет/которые изменились (проход 1), создаёт отсутствующие (проход 2). */ -static void auto_socket_reconcile_config(struct AUTO_SOCKET* as) { +static int auto_socket_reconcile_config(struct AUTO_SOCKET* as) { struct UTUN_INSTANCE* inst = as->instance; - if (!inst || !inst->config) return; + if (!inst || !inst->config) return -1; + int result = 0; /* Тип сокета определяем по фактическому адресу (не доверяем type из конфига): * публичный адрес → PUBLIC, приватный/link-local → NAT. */ @@ -1452,10 +1463,11 @@ static void auto_socket_reconcile_config(struct AUTO_SOCKET* as) { DEBUG_INFO(DEBUG_CATEGORY_AS, "[as] created socket %s (%s) addr=%s", s->name, s->transport ? "tcp" : "udp", sockaddr_storage_to_str(&s->ip).str); ncd_add_socket_links(inst, created); - } + } else result = -1; } auto_socket_post_sockets_changed(as); + return result; } void auto_socket_reload_config(struct UTUN_INSTANCE* inst, struct utun_config* new_cfg) { diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 95b366d6..502af04c 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -2634,7 +2634,7 @@ int etcp_packet_decrypted(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, } #endif - if (etcp_keepalive_on_recv(e_sock, pkt, link, pkt_len)) { + if (etcp_keepalive_on_recv(pkt)) { return 0; } @@ -2699,7 +2699,7 @@ int etcp_create_server_socket(struct UTUN_INSTANCE* instance, struct CFG_SERVER* // Создание всех listen-сокетов из конфига ([server] секции); автосокеты пропускаются. // Вызывается до topo_group_init() чтобы заполнить etcp_sockets для nodeinfo. -// Возврат: 0 = OK, 1 = частичный успех (часть сокетов не создана), -1 = фатально (нет сокетов). +// Возврат: 0 = все сокеты созданы, -1 = хотя бы один обязательный сокет не создан. int init_sockets(struct UTUN_INSTANCE* instance) { DEBUG_TRACE(DEBUG_CATEGORY_ETCP, ""); if (!instance || !instance->config) return -1; @@ -2792,8 +2792,10 @@ int init_sockets(struct UTUN_INSTANCE* instance) { server = server->next; } - if (success_count == 0 && fail_count > 0) return -1; // All failed - fatal - if (fail_count > 0) return 1; // Partial failure - non-fatal warning + if (fail_count) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "Configured socket initialization failed: opened=%d failed=%d", success_count, fail_count); + return -1; + } return 0; // All OK } @@ -2903,22 +2905,19 @@ int init_connections(struct UTUN_INSTANCE* instance) { // If sockets already exist (created by init_sockets), check stored status if (instance->etcp_sockets || instance->stcp_servers) { - if (instance->socket_init_status == 1) { - socket_result = 1; - } + socket_result = instance->socket_init_status; } else { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Sockets not initialized, calling init_sockets()"); socket_result = init_sockets(instance); instance->socket_init_status = socket_result; - if (socket_result < 0) { - return -1; - } + } + if (socket_result != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "Core transport cannot start after socket initialization failure: result=%d", socket_result); + return -1; } if (node_conn_direct_init(instance) < 0) return -1; etcp_keepalive_register(instance); - // Return 1 if there was a partial socket initialization error - if (socket_result == 1) return 1; return 0; } diff --git a/src/transport_layer/etcp_keepalive.c b/src/transport_layer/etcp_keepalive.c index ff42f9b2..521e25fe 100644 --- a/src/transport_layer/etcp_keepalive.c +++ b/src/transport_layer/etcp_keepalive.c @@ -507,10 +507,13 @@ static void ka_peer_arm_phase(struct ETCP_CONN* etcp) { etcp, ka_peer_phase_timer_cb, "peer_phase"); } -int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, - struct ETCP_LINK* link, size_t pkt_len) { - (void)pkt_len; - if (!e_sock || !pkt || !link) return 0; +int etcp_keepalive_on_recv(struct ETCP_DGRAM* pkt) { + if (!pkt || !pkt->link || !pkt->link->etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_KEEPALIVE, "keepalive receive without packet/link/connection pkt=%p", (void*)pkt); + return 0; + } + struct ETCP_LINK* link = pkt->link; + struct UTUN_INSTANCE* instance = link->etcp->instance; uint8_t code = pkt->data[0]; if (code == ETCP_KEEPALIVE) { @@ -519,7 +522,7 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, if (pkt->data_len < body_len || pkt->data_len - body_len > ETCP_PADDING_MAX) { DEBUG_WARN(DEBUG_CATEGORY_KEEPALIVE, "[%s] invalid keepalive size=%u flags=%02x body=%zu", link->etcp->log_name, pkt->data_len, flags, body_len); - memory_pool_free(e_sock->instance->pkt_pool, pkt); + memory_pool_free(instance->pkt_pool, pkt); return 1; } uint16_t peer_period = pkt->data[2] | ((uint16_t)pkt->data[3] << 8); @@ -578,7 +581,7 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, link->keepalive_recv_count++; etcp_keepalive_watch_rx(link); - memory_pool_free(e_sock->instance->pkt_pool, pkt); + memory_pool_free(instance->pkt_pool, pkt); return 1; } diff --git a/src/transport_layer/etcp_keepalive.h b/src/transport_layer/etcp_keepalive.h index a47c2fcd..4f6f2b94 100644 --- a/src/transport_layer/etcp_keepalive.h +++ b/src/transport_layer/etcp_keepalive.h @@ -69,8 +69,7 @@ void etcp_keepalive_watch_rx(struct ETCP_LINK* link); // --- новое --- // Обработка keepalive-пакетов (ETCP_KEEPALIVE) из etcp_packet_decrypted. // Возвращает 1 если пакет обработан (и освобождён), 0 — если это не keepalive. -int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, - struct ETCP_LINK* link, size_t pkt_len); +int etcp_keepalive_on_recv(struct ETCP_DGRAM* pkt); // Подписка на фактический режим/фазу standby; без duty-cycle — на активность клиента. // Вызывается из init_connections. diff --git a/src/tun_freebsd.c b/src/tun_freebsd.c index 6c0a526d..47f2daed 100644 --- a/src/tun_freebsd.c +++ b/src/tun_freebsd.c @@ -328,20 +328,25 @@ int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str } // Set MTU if (tun_set_mtu(tun->ifname, mtu) < 0) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set MTU %d on %s", mtu, tun->ifname); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set MTU %d on %s", mtu, tun->ifname); + goto fail; } // Получаем ifindex tun->ifindex = if_nametoindex(tun->ifname); if (tun->ifindex == 0) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to get ifindex for %s", tun->ifname); + goto fail; } else { DEBUG_INFO(DEBUG_CATEGORY_TUN, "Interface %s has index %d", tun->ifname, tun->ifindex); } // Set non-blocking mode if (fcntl(tun->fd, F_SETFL, O_NONBLOCK) < 0) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set non-blocking mode: %s", strerror(errno)); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set non-blocking mode: %s", strerror(errno)); + goto fail; } return 0; +fail: + tun_platform_cleanup(tun); return -1; } // Platform-specific cleanup for FreeBSD void tun_platform_cleanup(struct tun_if* tun) { diff --git a/src/tun_if.c b/src/tun_if.c index c23f8a97..ab49d240 100644 --- a/src/tun_if.c +++ b/src/tun_if.c @@ -120,12 +120,12 @@ static void tun_input_queue_callback(struct ll_queue* q, void* arg) // =================================================================== struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) { - if (!ua || !config) return NULL; + if (!ua || !config) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN initialization without event loop/config"); return NULL; } int test_mode = config->global.tun_test_mode; struct tun_if* tun = u_calloc(1, sizeof(struct tun_if)); - if (!tun) return NULL; + if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN allocation failed"); return NULL; } tun->ua = ua; tun->test_mode = test_mode; @@ -139,6 +139,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) if (config->global.tun_ip.family == AF_INET) { snprintf(ip_str, sizeof(ip_str), "%s/32", ip_to_str(&config->global.tun_ip.addr.v4, AF_INET).str); } else { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Unsupported TUN IP family=%d", config->global.tun_ip.family); u_free(tun); return NULL; } @@ -183,7 +184,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) if (poll_fd >= 0) { tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, "tun", tun); if (!tun->socket_id) goto fail; - } + } else { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s has no poll descriptor", tun->ifname); goto fail; } } #endif #ifdef _WIN32 @@ -195,7 +196,6 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) if (!tun->stop_event) goto fail; tun->read_thread = CreateThread(NULL, 0, tun_read_thread_proc, tun, 0, NULL); if (!tun->read_thread) { - CloseHandle(tun->stop_event); goto fail; } } @@ -207,6 +207,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) return tun; fail: + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s initialization failed; releasing device and queues", tun->ifname); if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; } #ifdef _WIN32 if (tun->stop_event) CloseHandle(tun->stop_event); @@ -220,6 +221,7 @@ fail: if (tun->input_queue) queue_free(tun->input_queue); if (tun->pool) memory_pool_destroy(tun->pool); if (!test_mode) tun_platform_cleanup(tun); + else if (tun->fd >= 0) close(tun->fd); u_free(tun); return NULL; } @@ -229,12 +231,12 @@ fail: // =================================================================== struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* ip_addr_str, int mtu, int test_mode) { - if (!ua || !ifname || !ip_addr_str) return NULL; + if (!ua || !ifname || !ip_addr_str) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Invalid NAT TUN initialization arguments"); return NULL; } (void)mtu; int tun_mtu = TUN_MTU_DEFAULT; struct tun_if* tun = u_calloc(1, sizeof(struct tun_if)); - if (!tun) return NULL; + if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN allocation failed"); return NULL; } tun->ua = ua; tun->test_mode = test_mode; @@ -282,7 +284,7 @@ struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* i if (poll_fd >= 0) { tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, "tun", tun); if (!tun->socket_id) goto fail2; - } + } else { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN %s has no poll descriptor", tun->ifname); goto fail2; } } #endif @@ -291,11 +293,13 @@ struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* i return tun; fail2: + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN %s initialization failed; releasing device and queues", tun->ifname); if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; } if (tun->output_queue) queue_free(tun->output_queue); if (tun->input_queue) queue_free(tun->input_queue); if (tun->pool) memory_pool_destroy(tun->pool); if (!test_mode) tun_platform_cleanup(tun); + else if (tun->fd >= 0) close(tun->fd); u_free(tun); return NULL; } diff --git a/src/tun_linux.c b/src/tun_linux.c index 581e7da0..d4f0bafc 100644 --- a/src/tun_linux.c +++ b/src/tun_linux.c @@ -188,7 +188,8 @@ int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str // Set MTU if (tun_set_mtu(tun->ifname, mtu) < 0) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set MTU %d on %s: %s", mtu, tun->ifname, strerror(errno)); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set MTU %d on %s: %s", mtu, tun->ifname, strerror(errno)); + goto fail; } // Bring interface up @@ -202,15 +203,19 @@ int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str tun->ifindex = if_nametoindex(tun->ifname); if (tun->ifindex == 0) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to get ifindex for %s", tun->ifname); + goto fail; } // Set non-blocking mode int flags = fcntl(tun->fd, F_GETFL, 0); - if (flags >= 0) { - fcntl(tun->fd, F_SETFL, flags | O_NONBLOCK); + if (flags < 0 || fcntl(tun->fd, F_SETFL, flags | O_NONBLOCK) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set non-blocking mode on %s: %s", tun->ifname, strerror(errno)); + goto fail; } return 0; +fail: + tun_platform_cleanup(tun); return -1; } // Platform-specific cleanup for Linux diff --git a/src/tun_route.c b/src/tun_route.c index 1b8ffb88..278ca5ef 100644 --- a/src/tun_route.c +++ b/src/tun_route.c @@ -9,6 +9,7 @@ #include #include #include "../lib/mem.h" +#include "../lib/u_async.h" // Platform detection #ifdef _WIN32 @@ -47,164 +48,94 @@ struct nl_req { char buf[256]; }; -static int netlink_route(int ifindex, uint32_t network, uint8_t prefix_len, int cmd, int flags) { - int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (fd < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create netlink socket: %s", strerror(errno)); - return -1; - } - - // Bind to receive responses - struct sockaddr_nl addr; - memset(&addr, 0, sizeof(addr)); - addr.nl_family = AF_NETLINK; - addr.nl_pid = getpid(); - addr.nl_groups = 0; - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to bind netlink socket: %s", strerror(errno)); - close(fd); +/* Отдельный сокет на запрос: ядро назначает port ID независимо от сокетов мониторинга. */ +static int netlink_route(uint32_t ifindex, const char* ifname, uint32_t network, uint8_t prefix_len, int cmd) { + if (!ifindex && ifname && ifname[0]) ifindex = if_nametoindex(ifname); + if (!ifindex || prefix_len > 32) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Invalid route interface=%s index=%u prefix=%u", ifname ? ifname : "?", ifindex, prefix_len); return -1; } - - // Prepare destination address (kernel) - struct sockaddr_nl dest; - memset(&dest, 0, sizeof(dest)); - dest.nl_family = AF_NETLINK; - dest.nl_pid = 0; // kernel - dest.nl_groups = 0; - - struct nl_req req; - memset(&req, 0, sizeof(req)); - + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create route netlink socket: %s", strerror(errno)); return -1; } + const char* stage = "bind"; + struct sockaddr_nl addr = { .nl_family = AF_NETLINK }; + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) goto fail; + socklen_t addr_len = sizeof(addr); + stage = "getsockname"; + if (getsockname(fd, (struct sockaddr*)&addr, &addr_len) < 0) goto fail; + struct sockaddr_nl dest = { .nl_family = AF_NETLINK }; + struct nl_req req = {0}; req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); - req.nl.nlmsg_type = cmd; // RTM_NEWROUTE or RTM_DELROUTE - req.nl.nlmsg_flags = flags | NLM_F_REQUEST | NLM_F_ACK; // Request ACK from kernel - req.nl.nlmsg_seq = 1; - req.nl.nlmsg_pid = getpid(); - - req.rt.rtm_family = AF_INET; - req.rt.rtm_table = RT_TABLE_MAIN; - req.rt.rtm_protocol = RTPROT_STATIC; - req.rt.rtm_scope = RT_SCOPE_UNIVERSE; - req.rt.rtm_type = RTN_UNICAST; - req.rt.rtm_dst_len = prefix_len; - - // Add destination address attribute - struct rtattr *rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nl.nlmsg_len)); - rta->rta_type = RTA_DST; - rta->rta_len = RTA_LENGTH(4); - memcpy(RTA_DATA(rta), &network, 4); - req.nl.nlmsg_len = NLMSG_ALIGN(req.nl.nlmsg_len) + RTA_ALIGN(rta->rta_len); - - // Add output interface attribute - rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nl.nlmsg_len)); - rta->rta_type = RTA_OIF; - rta->rta_len = RTA_LENGTH(4); - memcpy(RTA_DATA(rta), &ifindex, 4); - req.nl.nlmsg_len = NLMSG_ALIGN(req.nl.nlmsg_len) + RTA_ALIGN(rta->rta_len); - - if (sendto(fd, &req, req.nl.nlmsg_len, 0, (struct sockaddr *)&dest, sizeof(dest)) < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to send netlink message: %s", strerror(errno)); - close(fd); - return -1; - } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Netlink message sent: seq=%u pid=%u len=%d", - req.nl.nlmsg_seq, req.nl.nlmsg_pid, req.nl.nlmsg_len); - - // Wait for response with timeout using poll - struct pollfd pfd = { .fd = fd, .events = POLLIN }; - int poll_ret = poll(&pfd, 1, 1000); // 1 second timeout - if (poll_ret < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "poll() failed: %s", strerror(errno)); - close(fd); - return -1; - } else if (poll_ret == 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Timeout waiting for netlink response (sent %d bytes)", req.nl.nlmsg_len); - close(fd); - return -1; - } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Poll returned: revents=%d", pfd.revents); - - // Receive response - char reply[4096]; - ssize_t len = recv(fd, reply, sizeof(reply), 0); - if (len < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to receive netlink response: %s", strerror(errno)); - close(fd); - return -1; - } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Received netlink response: len=%d", (int)len); - - struct nlmsghdr *nl_hdr = (struct nlmsghdr *)reply; - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Netlink msg: type=%d seq=%d pid=%d", - nl_hdr->nlmsg_type, nl_hdr->nlmsg_seq, nl_hdr->nlmsg_pid); - - if (nl_hdr->nlmsg_type == NLMSG_ERROR) { - struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nl_hdr); - if (err->error < 0) { - errno = -err->error; - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Netlink error: %s", strerror(errno)); - close(fd); - return -1; + req.nl.nlmsg_type = cmd; req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + if (cmd == RTM_NEWROUTE) req.nl.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + req.nl.nlmsg_seq = 1; req.nl.nlmsg_pid = addr.nl_pid; + req.rt.rtm_family = AF_INET; req.rt.rtm_table = RT_TABLE_MAIN; + req.rt.rtm_protocol = RTPROT_STATIC; req.rt.rtm_scope = RT_SCOPE_UNIVERSE; + req.rt.rtm_type = RTN_UNICAST; req.rt.rtm_dst_len = prefix_len; + uint32_t wire_network = htonl(network); + struct rtattr* attr = (struct rtattr*)((uint8_t*)&req + NLMSG_ALIGN(req.nl.nlmsg_len)); + attr->rta_type = RTA_DST; attr->rta_len = RTA_LENGTH(sizeof(wire_network)); + memcpy(RTA_DATA(attr), &wire_network, sizeof(wire_network)); req.nl.nlmsg_len += RTA_ALIGN(attr->rta_len); + attr = (struct rtattr*)((uint8_t*)&req + NLMSG_ALIGN(req.nl.nlmsg_len)); + attr->rta_type = RTA_OIF; attr->rta_len = RTA_LENGTH(sizeof(ifindex)); + memcpy(RTA_DATA(attr), &ifindex, sizeof(ifindex)); req.nl.nlmsg_len += RTA_ALIGN(attr->rta_len); + stage = "send"; + ssize_t sent; + do { sent = sendto(fd, &req, req.nl.nlmsg_len, 0, (struct sockaddr*)&dest, sizeof(dest)); } while (sent < 0 && errno == EINTR); + if (sent < 0) goto fail; + if ((size_t)sent != req.nl.nlmsg_len) { errno = EIO; goto fail; } + DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Route request cmd=%d port_id=%u seq=%u ifindex=%u prefix=%u", + cmd, addr.nl_pid, req.nl.nlmsg_seq, ifindex, prefix_len); + uint64_t deadline = get_time_tb() + 10000; + for (;;) { + stage = "wait for ACK"; + uint64_t now = get_time_tb(); + if (now >= deadline) { errno = ETIMEDOUT; goto fail; } + struct pollfd pollfd = { .fd = fd, .events = POLLIN }; + int ready = poll(&pollfd, 1, (int)((deadline - now + 9) / 10)); + if (ready < 0 && errno == EINTR) continue; + if (ready < 0) goto fail; + if (!ready) { errno = ETIMEDOUT; goto fail; } + if (!(pollfd.revents & POLLIN)) { errno = EIO; goto fail; } + uint32_t reply[1024]; + struct sockaddr_nl source = {0}; socklen_t source_len = sizeof(source); + stage = "receive ACK"; + ssize_t received = recvfrom(fd, reply, sizeof(reply), MSG_DONTWAIT | MSG_TRUNC, (struct sockaddr*)&source, &source_len); + if (received < 0 && (errno == EINTR || errno == EAGAIN)) continue; + if (received < 0) goto fail; + if ((size_t)received > sizeof(reply) || source_len != sizeof(source) || source.nl_family != AF_NETLINK || source.nl_pid) { + errno = EPROTO; goto fail; } + size_t remaining = (size_t)received; + for (struct nlmsghdr* hdr = (struct nlmsghdr*)reply; NLMSG_OK(hdr, remaining); hdr = NLMSG_NEXT(hdr, remaining)) { + if (hdr->nlmsg_seq != req.nl.nlmsg_seq) continue; + if (hdr->nlmsg_type != NLMSG_ERROR || hdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + errno = EPROTO; goto fail; + } + const struct nlmsgerr* error = (const struct nlmsgerr*)NLMSG_DATA(hdr); + if (error->error) { errno = error->error < 0 ? -error->error : EPROTO; goto fail; } + DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Route ACK success cmd=%d port_id=%u seq=%u ifindex=%u", + cmd, addr.nl_pid, hdr->nlmsg_seq, ifindex); + close(fd); return 0; + } + if (remaining) { errno = EPROTO; goto fail; } } - - close(fd); - return 0; -} - -// Fallback: use ip route command -static int ip_route_cmd(const char *ifname, uint32_t network, uint8_t prefix_len, const char *cmd) { - struct in_addr addr; - addr.s_addr = network; - - char cmdline[256]; - snprintf(cmdline, sizeof(cmdline), "ip route %s %s/%d dev %s >/dev/null 2>&1", - cmd, ip_to_str(&addr, AF_INET).str, prefix_len, ifname); - - int ret = system(cmdline); - return (ret == 0) ? 0 : -1; -} - -int tun_route_add(uint32_t ifindex, const char *ifname, uint32_t network, uint8_t prefix_len) { - if (ifindex == 0 && (!ifname || !ifname[0])) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Interface index and name are both invalid"); - return -1; - } - - network = htonl(network); - - // Try netlink first - if (ifindex != 0 && netlink_route(ifindex, network, prefix_len, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL) == 0) { - return 0; +fail: { + int error = errno; + struct in_addr ip = { .s_addr = htonl(network) }; + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Route %s %s/%u via %s index=%u failed at %s: %s", + cmd == RTM_NEWROUTE ? "add" : "delete", ip_to_str(&ip, AF_INET).str, prefix_len, + ifname ? ifname : "?", ifindex, stage, strerror(error)); + close(fd); errno = error; return -1; } - - // Fallback to ip route command - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Netlink failed, trying ip route command for ifindex %u", ifindex); - const char *dev = ifname ? ifname : ""; - return ip_route_cmd(dev, ntohl(network), prefix_len, "add"); } -int tun_route_del(uint32_t ifindex, const char *ifname, uint32_t network, uint8_t prefix_len) { - if (ifindex == 0 && (!ifname || !ifname[0])) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "ifindex and ifname are both invalid"); - return -1; - } - - network = htonl(network); - - // Try netlink first - if (ifindex != 0 && netlink_route(ifindex, network, prefix_len, RTM_DELROUTE, 0) == 0) { - return 0; - } +int tun_route_add(uint32_t ifindex, const char* ifname, uint32_t network, uint8_t prefix_len) { + return netlink_route(ifindex, ifname, network, prefix_len, RTM_NEWROUTE); +} - // Fallback to ip route command - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Netlink failed, trying ip route command for ifindex %u", ifindex); - const char *dev = ifname ? ifname : ""; - return ip_route_cmd(dev, ntohl(network), prefix_len, "del"); +int tun_route_del(uint32_t ifindex, const char* ifname, uint32_t network, uint8_t prefix_len) { + return netlink_route(ifindex, ifname, network, prefix_len, RTM_DELROUTE); } int tun_route_flush(const char *ifname) { @@ -225,23 +156,6 @@ snprintf(cmdline, sizeof(cmdline), return (ret == 0) ? 0 : -1; } -int tun_route_del_all(uint32_t ifindex, const char *ifname, struct CFG_ROUTE_ENTRY *routes) { - int count = 0; - struct CFG_ROUTE_ENTRY *entry = routes; - - while (entry) { - uint32_t network = ntohl(entry->ip.addr.v4.s_addr); - if (entry->ip.family == AF_INET) { - if (tun_route_del(ifindex, ifname, network, entry->netmask) == 0) { - count++; - } - } - entry = entry->next; - } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Deleted %d routes via ifindex=%lu", count, (unsigned long)ifindex); - return count; -} #elif defined(_WIN32) @@ -367,23 +281,6 @@ int tun_route_flush(const char *ifname) { return 0; } -int tun_route_del_all(uint32_t ifindex, const char *ifname, struct CFG_ROUTE_ENTRY *routes) { - int count = 0; - struct CFG_ROUTE_ENTRY *entry = routes; - - while (entry) { - uint32_t network = ntohl(entry->ip.addr.v4.s_addr); - if (entry->ip.family == AF_INET) { - if (tun_route_del(ifindex, ifname, network, entry->netmask) == 0) { - count++; - } - } - entry = entry->next; - } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Deleted %d routes via ifindex=%lu", count, (unsigned long)ifindex); - return count; -} #else @@ -510,48 +407,50 @@ int tun_route_flush(const char *ifname) { return (ret == 0) ? 0 : -1; } -int tun_route_del_all(uint32_t ifindex, const char *ifname, struct CFG_ROUTE_ENTRY *routes) { - int count = 0; - struct CFG_ROUTE_ENTRY *entry = routes; - - while (entry) { - uint32_t network = ntohl(entry->ip.addr.v4.s_addr); - if (entry->ip.family == AF_INET) { - if (tun_route_del(ifindex, ifname, network, entry->netmask) == 0) { - count++; - } - } - entry = entry->next; - } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Deleted %d routes via ifindex=%lu", count, (unsigned long)ifindex); - return count; -} #endif // Platform-independent functions -int tun_route_add_all(uint32_t ifindex, const char *ifname, struct CFG_ROUTE_ENTRY *routes) { - int count = 0; - struct CFG_ROUTE_ENTRY *entry = routes; - - while (entry) { - uint32_t network = ntohl(entry->ip.addr.v4.s_addr); - if (entry->ip.family == AF_INET) { - if (tun_route_add(ifindex, ifname, network, entry->netmask) == 0) { - struct in_addr addr; - addr.s_addr = entry->ip.addr.v4.s_addr; - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added route %s/%d via %s", ip_to_str(&addr, AF_INET).str, entry->netmask, ifname); - count++; - } else { - struct in_addr addr; - addr.s_addr = entry->ip.addr.v4.s_addr; - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add route %s/%d via %s", ip_to_str(&addr, AF_INET).str, entry->netmask, ifname); - } +int tun_route_add_all(uint32_t ifindex, const char* ifname, const struct CFG_ROUTE_ENTRY* routes, struct tun_route** installed) { + if (!installed || *installed) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Route installation requires an empty owned list"); return -1; + } + unsigned count = 0; + for (const struct CFG_ROUTE_ENTRY* entry = routes; entry; entry = entry->next) { + if (entry->ip.family != AF_INET) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Unsupported route family=%d via %s", entry->ip.family, ifname ? ifname : "?"); + return -1; } - entry = entry->next; + struct tun_route* route = u_calloc(1, sizeof(*route)); + if (!route) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Route ownership allocation failed"); return -1; } + route->network = ntohl(entry->ip.addr.v4.s_addr); route->prefix_len = entry->netmask; + if (tun_route_add(ifindex, ifname, route->network, route->prefix_len) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add route %s/%u via %s; rolling back %u installed routes", + ip_to_str(&entry->ip.addr.v4, AF_INET).str, route->prefix_len, ifname ? ifname : "?", count); + u_free(route); return -1; + } + route->next = *installed; *installed = route; count++; + DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added route %s/%u via %s", + ip_to_str(&entry->ip.addr.v4, AF_INET).str, route->prefix_len, ifname ? ifname : "?"); } - - return count; + DEBUG_INFO(DEBUG_CATEGORY_TUN, "All %u configured routes installed via %s", count, ifname ? ifname : "?"); + return 0; +} + +int tun_route_del_all(uint32_t ifindex, const char* ifname, struct tun_route** routes) { + if (!routes) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Route removal without owned list"); return -1; } + int result = 0; + while (*routes) { + struct tun_route* route = *routes; *routes = route->next; + struct in_addr addr = { .s_addr = htonl(route->network) }; + if (tun_route_del(ifindex, ifname, route->network, route->prefix_len) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to delete owned route %s/%u via %s", + ip_to_str(&addr, AF_INET).str, route->prefix_len, ifname ? ifname : "?"); + result = -1; + } else DEBUG_INFO(DEBUG_CATEGORY_TUN, "Deleted owned route %s/%u via %s", + ip_to_str(&addr, AF_INET).str, route->prefix_len, ifname ? ifname : "?"); + u_free(route); + } + return result; } diff --git a/src/tun_route.h b/src/tun_route.h index bbc52e5f..87958a42 100644 --- a/src/tun_route.h +++ b/src/tun_route.h @@ -11,6 +11,13 @@ extern "C" { struct CFG_ROUTE_ENTRY; +/* Маршрут, успешно созданный сервисом; список принадлежит сервису, а не конфигу. */ +struct tun_route { + struct tun_route* next; + uint32_t network; // host byte order + uint8_t prefix_len; +}; + /** * @brief Add system route for a subnet via TUN interface * @param ifindex Interface index @@ -32,13 +39,13 @@ int tun_route_add(uint32_t ifindex, const char *ifname, uint32_t network, uint8_ int tun_route_del(uint32_t ifindex, const char *ifname, uint32_t network, uint8_t prefix_len); /** - * @brief Delete all system routes from config entries + * @brief Delete and release all routes owned by the service * @param ifindex Interface index * @param ifname Interface name (e.g., "tun0") - * @param routes Linked list of route entries to delete - * @return Number of routes deleted + * @param routes Owned list; cleared after all deletion attempts + * @return 0 on complete success, -1 if any deletion failed */ -int tun_route_del_all(uint32_t ifindex, const char *ifname, struct CFG_ROUTE_ENTRY *routes); +int tun_route_del_all(uint32_t ifindex, const char *ifname, struct tun_route** routes); /** * @brief Delete all system routes via specified interface @@ -52,9 +59,10 @@ int tun_route_flush(const char *ifname); * @param ifindex Interface index * @param ifname Interface name (e.g., "tun0") - for logging only * @param routes Linked list of route entries - * @return Number of routes added successfully + * @param installed Initially empty owned list; retains successful additions on error for rollback + * @return 0 on complete success, -1 on error */ -int tun_route_add_all(uint32_t ifindex, const char *ifname, struct CFG_ROUTE_ENTRY *routes); +int tun_route_add_all(uint32_t ifindex, const char *ifname, const struct CFG_ROUTE_ENTRY* routes, struct tun_route** installed); #ifdef __cplusplus } diff --git a/src/tun_route_doc.md b/src/tun_route_doc.md index 0ae7d17d..67c8bc21 100644 --- a/src/tun_route_doc.md +++ b/src/tun_route_doc.md @@ -9,16 +9,22 @@ tun_route_add(ifindex, "tun0", 0x0A000000, 8); // 10.0.0.0/8 // Добавить все маршруты из конфига -tun_route_add_all(ifindex, "tun0", cfg.routes); +struct tun_route* installed = NULL; +int result = tun_route_add_all(ifindex, "tun0", cfg.routes, &installed); // Удалить все маршруты из конфига -tun_route_del_all(ifindex, "tun0", cfg.routes); +tun_route_del_all(ifindex, "tun0", &installed); // также при result < 0 // Удалить все маршруты через интерфейс (очистка) tun_route_flush("tun0"); ``` -**Network передаётся в host byte order** (внутри конвертируется в network byte order). `tun_route_add_all`/`tun_route_del_all` проходят по связному списку `CFG_ROUTE_ENTRY` и вызывают `tun_route_add`/`tun_route_del` для каждого элемента. +**Network передаётся в host byte order** (внутри конвертируется в network byte order). +`tun_route_add_all` читает `CFG_ROUTE_ENTRY`, выделяет запись владения до операции +и сохраняет только успешно добавленные маршруты. При ошибке возвращает `-1`, +оставляя созданный список для отката. `tun_route_del_all` удаляет и освобождает +этот список независимо от дальнейших изменений конфига. IPv6 пока не реализован: +его присутствие в `route_subnet` возвращает ошибку, а не молча пропускается. **Root-права:** требуются для изменения системной таблицы маршрутизации. @@ -27,16 +33,16 @@ tun_route_flush("tun0"); ### Функции - **`tun_route_add(ifindex, ifname, network, prefix_len)`** — добавляет маршрут к подсети `network/prefix_len` через интерфейс `ifindex`. Пытается использовать нативный механизм платформы, при неудаче — fallback на shell-команду - **`tun_route_del(ifindex, ifname, network, prefix_len)`** — удаляет аналогичный маршрут -- **`tun_route_add_all(ifindex, ifname, routes)`** — массовое добавление маршрутов из связного списка `CFG_ROUTE_ENTRY`. Возвращает количество успешно добавленных -- **`tun_route_del_all(ifindex, ifname, routes)`** — массовое удаление. Возвращает количество удалённых +- **`tun_route_add_all(ifindex, ifname, routes, installed)`** — `0`, если установлены все маршруты, `-1` при первой ошибке; `installed` изначально пуст +- **`tun_route_del_all(ifindex, ifname, installed)`** — удаляет принадлежащие сервису маршруты; `0` при полном успехе, `-1` при любой ошибке; обнуляет список - **`tun_route_flush(ifname)`** — удаление всех маршрутов, привязанных к интерфейсу `ifname`, без привязки к конфигу ### Платформенные механизмы **Linux:** -1. Netlink socket (`AF_NETLINK`, `NETLINK_ROUTE`): сообщения `RTM_NEWROUTE`/`RTM_DELROUTE` с атрибутами `RTA_DST` (адрес сети) и `RTA_OIF` (индекс интерфейса). Ожидание ACK от ядра через `poll` с таймаутом 1с -2. Fallback: `ip route add/del / dev ` через `system()` -3. `tun_route_flush`: `ip route flush dev `, при неудаче — парсинг `ip route show` и удаление по одному +1. Netlink socket (`AF_NETLINK`, `NETLINK_ROUTE`) на одну операцию с автоматически назначенным port ID, без подписок. Работает независимо от уже открытых сокетов мониторинга. +2. `RTM_NEWROUTE`/`RTM_DELROUTE` с `RTA_DST` и `RTA_OIF`; успех только после корректного ACK ядра с совпавшим sequence и `error=0`. Общий срок ожидания — 1с, включая EINTR и посторонние ответы. Add/delete не используют shell-fallback. +3. `tun_route_flush`: отдельный API `ip route flush dev `; UTUN stop/rollback используют список владения, а не flush. **Windows:** 1. `netsh interface ip add/delete route` через `system()` (первичный метод) diff --git a/src/tun_windows.c b/src/tun_windows.c index 64ef5a68..803af048 100644 --- a/src/tun_windows.c +++ b/src/tun_windows.c @@ -125,22 +125,23 @@ static int wintun_configure_interface(WINTUN_ADAPTER_HANDLE adapter, const char* InitializeIpInterfaceEntry(&ifRow); ifRow.Family = AF_INET; ifRow.InterfaceLuid = luid; - if (GetIpInterfaceEntry(&ifRow) == NO_ERROR) { - ifRow.NlMtu = mtu; - SetIpInterfaceEntry(&ifRow); - } + err = GetIpInterfaceEntry(&ifRow); + if (err != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "GetIpInterfaceEntry failed: %lu", err); return -1; } + ifRow.NlMtu = mtu; + err = SetIpInterfaceEntry(&ifRow); + if (err != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "SetIpInterfaceEntry MTU failed: %lu", err); return -1; } } // Bring UP NET_IFINDEX idx = 0; - if (ConvertInterfaceLuidToIndex(&luid, &idx) == NO_ERROR) { - MIB_IFROW ifr = {0}; - ifr.dwIndex = idx; - if (GetIfEntry(&ifr) == NO_ERROR) { - ifr.dwAdminStatus = MIB_IF_ADMIN_STATUS_UP; - SetIfEntry(&ifr); - } - } + err = ConvertInterfaceLuidToIndex(&luid, &idx); + if (err != NO_ERROR || !idx) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to get Wintun index: %lu", err); return -1; } + MIB_IFROW ifr = {0}; ifr.dwIndex = idx; + err = GetIfEntry(&ifr); + if (err != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "GetIfEntry failed: %lu", err); return -1; } + ifr.dwAdminStatus = MIB_IF_ADMIN_STATUS_UP; + err = SetIfEntry(&ifr); + if (err != NO_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "SetIfEntry UP failed: %lu", err); return -1; } return 0; } @@ -187,6 +188,7 @@ int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str } else { tun->ifindex = 0; DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to get interface index from LUID"); + tun_platform_cleanup(tun); return -1; } return 0; diff --git a/src/utun_instance.c b/src/utun_instance.c index c0c6b910..7d56d015 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -60,6 +60,7 @@ // Forward declarations static uint32_t get_dest_ip(const uint8_t *packet, size_t len); static int mkdir_recursive(const char* path); +static void instance_destroy(struct UTUN_INSTANCE* instance, int owns_config); // Global instance for signal handlers static struct UTUN_INSTANCE *g_instance = NULL; @@ -173,8 +174,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u struct ll_entry *entry = queue_entry_new(sizeof(struct NETWORK_ENTRY)); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Failed to allocate network entry for %s", net->name); - net = net->next; - continue; + return -1; } struct NETWORK_ENTRY *ne = (struct NETWORK_ENTRY*)entry->data; ne->id = net->id & 0x00FFFFFFFFFFFFFFULL; @@ -195,6 +195,9 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u instance->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET), "ack_pool"); instance->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool"); instance->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool"); + if (!instance->ack_pool || !instance->data_pool || !instance->pkt_pool) { + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Core memory pool initialization failed"); return -1; + } // Create routing module if (routing_create(instance) != 0) { @@ -209,13 +212,14 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u return -1; } instance->nat_det = nat_detection_create(instance); - if (!instance->nat_det) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NAT detection creation failed"); } + if (!instance->nat_det) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NAT detection creation failed"); return -1; } if (config->global.db_path[0]) mkdir_recursive(config->global.db_path); if (g_topo_group_enabled) { instance->topo_groups = topo_groups_init(instance); if (!instance->topo_groups) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to initialize BGP module"); + return -1; } else { DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized"); @@ -225,8 +229,9 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u instance->topo_groups = NULL; } - socket_monitor_init(instance); - auto_socket_init(instance); + if (socket_monitor_init(instance) < 0 || auto_socket_init(instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "Socket monitoring/automatic socket initialization failed"); return -1; + } if (g_topo_group_enabled && instance->topo_groups) topo_node_update_my_addresses(instance); @@ -235,7 +240,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u // Initialize firewall fw_init(&instance->fw); if (fw_load_rules(&instance->fw, &config->global) != 0) { - DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Failed to load firewall rules"); + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to load firewall rules"); return -1; } else { DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Firewall initialized: %d rules, bypass_all=%d", instance->fw.count, instance->fw.bypass_all); @@ -331,10 +336,8 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config // Initialize using common function if (instance_init_common(instance, ua, config) != 0) { - // Cleanup on error - etcp_padding_stop(&instance->padding); - if (instance->config) free_config(instance->config); - u_free(instance); + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Core creation failed; releasing all initialized resources"); + instance_destroy(instance, 1); return NULL; } @@ -372,9 +375,8 @@ struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct // Initialize using common function (config ownership transferred to instance) if (instance_init_common(instance, ua, config) != 0) { - // Cleanup on error - caller still owns config since we failed - etcp_padding_stop(&instance->padding); - u_free(instance); + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Core creation failed; releasing resources and preserving caller config"); + instance_destroy(instance, 0); return NULL; } @@ -396,7 +398,7 @@ struct UTUN_INSTANCE* utun_instance_create_from_str(struct UASYNC* ua, const cha } // Destroy instance and cleanup resources -void utun_instance_destroy(struct UTUN_INSTANCE *instance) { +static void instance_destroy(struct UTUN_INSTANCE* instance, int owns_config) { if (!instance) return; DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Starting cleanup for instance %p node=0x%016llx ua=%p", @@ -494,7 +496,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { /* Phase K: ETCP connections */ { - struct ll_entry* entry = instance->connections->head; + struct ll_entry* entry = instance->connections ? instance->connections->head : NULL; int cc = 0; while (entry) { struct ll_entry* next = entry->next; @@ -526,8 +528,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { /* Phase M: TUN + proxy + routing */ if (instance->tun) { - if (instance->tun->ifindex && instance->route_subnets) - tun_route_del_all(instance->tun->ifindex, instance->tun->ifname, instance->route_subnets); + if (instance->tun_routes) tun_route_del_all(instance->tun->ifindex, instance->tun->ifname, &instance->tun_routes); tun_close(instance->tun); instance->tun = NULL; } @@ -558,7 +559,8 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] O done — networks"); /* Phase P: config + pools */ - if (instance->config) { free_config(instance->config); instance->config = NULL; } + if (instance->config && owns_config) free_config(instance->config); + instance->config = NULL; // Cleanup packet pool (ensure no leak if stop wasn't called) if (instance->pkt_pool) { @@ -625,6 +627,10 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INSTANCE_DESTROY] Instance destroyed completely"); } +void utun_instance_destroy(struct UTUN_INSTANCE* instance) { + instance_destroy(instance, 1); +} + // Stop instance void utun_instance_stop(struct UTUN_INSTANCE *instance) { if (!instance) return; @@ -669,7 +675,7 @@ int utun_core_start(struct UTUN_INSTANCE *instance) { DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "init_connections() returned: %d", conn_result); - if (conn_result < 0) { + if (conn_result != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize connections, error=%d", conn_result); return -1; } @@ -679,16 +685,17 @@ int utun_core_start(struct UTUN_INSTANCE *instance) { // Initialize control server if configured if (instance->config->global.control_sock.ss_family != 0) { instance->control_srv = (struct control_server*)u_calloc(1, sizeof(struct control_server)); - if (instance->control_srv) { - if (control_server_init(instance->control_srv, instance->ua, instance, - &instance->config->global.control_sock, 8) != 0) { - DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Failed to initialize control server, continuing without monitoring"); - u_free(instance->control_srv); - instance->control_srv = NULL; - } else { - DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Control server initialized successfully"); - } + if (!instance->control_srv) { + DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control server allocation failed"); return -1; + } + if (control_server_init(instance->control_srv, instance->ua, instance, + &instance->config->global.control_sock, 8) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Configured control socket initialization failed"); + control_server_shutdown(instance->control_srv); + u_free(instance->control_srv); instance->control_srv = NULL; + return -1; } + DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Control server initialized successfully"); } // Start the main loop @@ -732,9 +739,8 @@ int utun_service_start(struct UTUN_INSTANCE* instance) { } DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname); if (config->route_subnets) { - int added = tun_route_add_all(instance->tun->ifindex, instance->tun->ifname, config->route_subnets); - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added %d system routes for TUN interface", added); - instance->route_subnets = config->route_subnets; + if (tun_route_add_all(instance->tun->ifindex, instance->tun->ifname, config->route_subnets, &instance->tun_routes) < 0) + goto fail; } } else { DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup"); @@ -758,11 +764,9 @@ void utun_service_stop(struct UTUN_INSTANCE* instance) { if (instance->nat_tr.initialized) nat_transport_destroy(instance); if (instance->router_bindings.callbacks[ETCP_RT_ID_DATA]) etcp_router_unbind(instance, ETCP_RT_ID_DATA); if (instance->tun) { - if (instance->tun->ifindex && instance->route_subnets) - tun_route_del_all(instance->tun->ifindex, instance->tun->ifname, instance->route_subnets); + if (instance->tun_routes) tun_route_del_all(instance->tun->ifindex, instance->tun->ifname, &instance->tun_routes); tun_close(instance->tun); instance->tun = NULL; } - instance->route_subnets = NULL; while (instance->config_conn_handles) { struct CONFIG_CONN_HANDLE* owner = instance->config_conn_handles; instance->config_conn_handles = owner->next; diff --git a/src/utun_instance.h b/src/utun_instance.h index ce089d97..c337d84f 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -134,8 +134,8 @@ struct UTUN_INSTANCE { // TUN interface struct tun_if* tun; - // Заимствованный список системных маршрутов из config; нужен для stop UTUN. - struct CFG_ROUTE_ENTRY* route_subnets; + // Принадлежащие UTUN системные маршруты; независимы от изменений config. + struct tun_route* tun_routes; // только успешно установленные системные маршруты UTUN struct ROUTE_TABLE* rt; // таблица маршрутов подсетей struct TOPO_GROUPS* topo_groups; // Groups module for topology exchange diff --git a/tests/Makefile.am b/tests/Makefile.am index 4e005b6b..4f142a1a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -91,7 +91,8 @@ check_PROGRAMS += \ test_etcp_padding \ test_ll_queue_regressions \ test_standby \ - test_standby_transport + test_standby_transport \ + test_tun_route endif # Короткие интеграционные проверки (до 1с). @@ -829,6 +830,10 @@ test_services_SOURCES = test_services.c test_services_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/routing_layer -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/lib test_services_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) +test_tun_route_SOURCES = test_tun_route.c +test_tun_route_LDADD = $(LIBUTUN) $(COMMON_LIBS) +test_tun_route_LDFLAGS = -Wl,--wrap=sendto -Wl,--wrap=recvfrom -Wl,--wrap=poll -Wl,--wrap=tun_platform_init + test_member_files_SOURCES = test_member_files.c test_member_files_CFLAGS = $(test_services_CFLAGS) test_member_files_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) diff --git a/tests/test_etcp_padding.c b/tests/test_etcp_padding.c index 916f8310..5edf8d20 100644 --- a/tests/test_etcp_padding.c +++ b/tests/test_etcp_padding.c @@ -67,9 +67,9 @@ static ssize_t keepalive_hook(socket_t fd, const void* buf, size_t len, const st CHECK(plain_len == 3 + 4 + (size_t)expected_keepalive_padding); check_zeroes(plain + 3 + 4, expected_keepalive_padding); struct ETCP_DGRAM* pkt = memory_pool_alloc(sender->pkt_pool); CHECK(pkt); - memcpy(pkt->data, plain + 3, plain_len - 3); pkt->data_len = plain_len - 3; + memcpy(pkt->data, plain + 3, plain_len - 3); pkt->data_len = plain_len - 3; pkt->link = link; uint32_t before = link->keepalive_recv_count; - CHECK(etcp_keepalive_on_recv(sender->etcp_sockets, pkt, link, plain_len) == 1); + CHECK(etcp_keepalive_on_recv(pkt) == 1); CHECK(link->keepalive_recv_count == before + 1); keepalive_packets++; return len; @@ -214,9 +214,9 @@ int main(void) { CHECK(keepalive_packets == 2); struct ETCP_DGRAM* malformed = memory_pool_alloc(sender->pkt_pool); CHECK(malformed); memset(malformed->data, 0, 7); malformed->data[0] = ETCP_KEEPALIVE; - malformed->data[1] = KA_FLAG_SLEEP; malformed->data_len = 7; + malformed->data[1] = KA_FLAG_SLEEP; malformed->data_len = 7; malformed->link = &link; uint32_t received = link.keepalive_recv_count; - CHECK(etcp_keepalive_on_recv(sender->etcp_sockets, malformed, &link, 10) == 1); + CHECK(etcp_keepalive_on_recv(malformed) == 1); CHECK(link.keepalive_recv_count == received && !link.ka_peer_sleeping); puts("PASS keepalive: encrypted padding boundaries and receive parsing"); @@ -246,6 +246,7 @@ int main(void) { etcp_conn_ref_free(conn); etcp_connection_close(peer_conn); etcp_connection_close(sibling); uasync_poll(ua, 0); fail_timer = 1; CHECK(etcp_connection_create(sender, "padding-allocation-failure") == NULL); + uasync_drain_immediate(ua); // завершить deferred free до снимка числа живых блоков size_t allocations = u_get_allocated_count(); CHECK(utun_instance_create_from_config(ua, sender->config) == NULL); CHECK(u_get_allocated_count() == allocations); diff --git a/tests/test_etcp_server_lifecycle.c b/tests/test_etcp_server_lifecycle.c index 554632c9..7942404a 100644 --- a/tests/test_etcp_server_lifecycle.c +++ b/tests/test_etcp_server_lifecycle.c @@ -160,13 +160,34 @@ static void check_silent_peer(int tcp) { teardown(); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS silent incoming peer tcp=%d", tcp); } +/* Исходящий TCP без listen-сокета: keepalive с padding проходит общий RX и продлевает deadline. */ +static void check_sockless_tcp_keepalive(void) { + setup(); struct ETCP_CONN* conn = ready_conn(1); struct ETCP_LINK* link = conn->links; + link->conn = NULL; link->is_server = 0; + etcp_keepalive_watch_rx(link); + const unsigned padding[] = { 0, 10, 11, 13, ETCP_PADDING_MAX }; + for (unsigned i = 0; i < sizeof(padding) / sizeof(padding[0]); i++) { + test_clock_run_for(inst.ua, 10000); CHECK(conn->links_up && !conn->down_timer); + struct ETCP_DGRAM* pkt = memory_pool_alloc(inst.pkt_pool); CHECK(pkt); + memset(pkt, 0, sizeof(*pkt) + 4 + padding[i]); pkt->flag_up = 1; + pkt->data[0] = ETCP_KEEPALIVE; pkt->data[2] = 200; + CHECK(etcp_packet_decrypted(NULL, pkt, link, 7 + padding[i]) == 0); + CHECK(link->keepalive_recv_count == i + 1 && link->rx_deadline_tb > get_time_tb()); + CHECK(!link->burst_recv_received && !conn->recv_q->count && !conn->down_timer); + } + test_clock_run_for(inst.ua, 10000); CHECK(conn->links_up && !conn->down_timer); + test_clock_run_for(inst.ua, 15000); CHECK(!conn->links_up && conn->down_timer); + teardown(); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS TCP without listen socket: padded RX refreshes deadline; silence closes link"); +} + /* Сон не даёт преждевременный DOWN, но локальные фазовые таймеры не продлевают жизнь пира. */ static void check_sleep(void) { setup(); struct ETCP_CONN* conn = ready_conn(1); struct ETCP_LINK* link = conn->links; struct ETCP_DGRAM* pkt = memory_pool_alloc(inst.pkt_pool); CHECK(pkt); - memset(pkt, 0, sizeof(*pkt) + 8); pkt->data_len = 8; pkt->data[0] = ETCP_KEEPALIVE; pkt->data[1] = KA_FLAG_SLEEP; + memset(pkt, 0, sizeof(*pkt) + 8); pkt->link = link; pkt->data_len = 8; + pkt->data[0] = ETCP_KEEPALIVE; pkt->data[1] = KA_FLAG_SLEEP; pkt->data[2] = 0xe8; pkt->data[3] = 3; pkt->data[4] = 5; pkt->data[6] = 200; // 1 s period, 0.5 s ACTIVE, 20 s SLEEP - CHECK(etcp_keepalive_on_recv(&sock, pkt, link, 11) == 1); + CHECK(etcp_keepalive_on_recv(pkt) == 1); CHECK(link->ka_peer_sleeping && !link->keepalive_timer && link->rx_timeout_timer); test_clock_run_for(inst.ua, 60000); CHECK(conn->links_up && !conn->down_timer); struct NODE_CONN_DIRECT* handle = NULL; @@ -198,12 +219,12 @@ static void check_wake_deadline(void) { struct ETCP_DGRAM* pkt = memory_pool_alloc(inst.pkt_pool); CHECK(pkt); memset(pkt, 0, sizeof(*pkt) + 8); pkt->data_len = 8; pkt->data[0] = ETCP_KEEPALIVE; pkt->data[1] = KA_FLAG_SLEEP; pkt->data[2] = 0xe8; pkt->data[3] = 3; pkt->data[6] = 200; - CHECK(etcp_keepalive_on_recv(&sock, pkt, link, 11) == 1); + pkt->link = link; CHECK(etcp_keepalive_on_recv(pkt) == 1); test_clock_run_for(inst.ua, 10000); CHECK(conn->links_up && link->ka_peer_sleeping); pkt = memory_pool_alloc(inst.pkt_pool); CHECK(pkt); memset(pkt, 0, sizeof(*pkt) + 4); pkt->data_len = 4; pkt->data[0] = ETCP_KEEPALIVE; pkt->data[2] = 200; link->last_recv_local_time = get_time_tb(); - CHECK(etcp_keepalive_on_recv(&sock, pkt, link, 7) == 1); CHECK(!link->ka_peer_sleeping); + pkt->link = link; CHECK(etcp_keepalive_on_recv(pkt) == 1); CHECK(!link->ka_peer_sleeping); test_clock_run_for(inst.ua, 10000); CHECK(conn->links_up); test_clock_run_for(inst.ua, 20000); CHECK(!conn->links_up && conn->down_timer && !deleted); teardown(); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS wake replaces long sleep deadline with normal timeout"); @@ -238,6 +259,7 @@ int main(void) { check_release_before_deadline(); CHECK(u_get_allocated_count() == baseline); check_silent_peer(0); CHECK(u_get_allocated_count() == baseline); check_silent_peer(1); CHECK(u_get_allocated_count() == baseline); + check_sockless_tcp_keepalive(); CHECK(u_get_allocated_count() == baseline); check_sleep(); CHECK(u_get_allocated_count() == baseline); check_wake_deadline(); CHECK(u_get_allocated_count() == baseline); check_remote_down(); CHECK(u_get_allocated_count() == baseline); diff --git a/tests/test_tun_route.c b/tests/test_tun_route.c new file mode 100644 index 00000000..9621a615 --- /dev/null +++ b/tests/test_tun_route.c @@ -0,0 +1,194 @@ +/* Реальный bind netlink при работающем мониторе; управляемые ACK и откат запуска UTUN. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utun_instance.h" +#include "tun_route.h" +#include "tun_if.h" +#include "chat/chat_core.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" + +static int mock = 1, route_fd = -1, pending, additions, deletions, fail_add, fail_delete, bad_ack; +static uint32_t added[8], removed[8], request_seq; +static uint16_t request_cmd; +static int ack_error; +static int fail_tun; + +int __real_tun_platform_init(struct tun_if*, const char*, const char*, int); +int __wrap_tun_platform_init(struct tun_if* tun, const char* name, const char* ip, int mtu) { + if (!fail_tun) return __real_tun_platform_init(tun, name, ip, mtu); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Injected TUN initialization failure"); return -1; +} + +ssize_t __real_sendto(int, const void*, size_t, int, const struct sockaddr*, socklen_t); +ssize_t __wrap_sendto(int fd, const void* buf, size_t len, int flags, const struct sockaddr* dest, socklen_t alen) { + if (!mock || dest->sa_family != AF_NETLINK) return __real_sendto(fd, buf, len, flags, dest, alen); + const struct nlmsghdr* hdr = buf; + assert(len >= NLMSG_LENGTH(sizeof(struct rtmsg)) && hdr->nlmsg_len == len); + struct sockaddr_nl bound = {0}; socklen_t size = sizeof(bound); + assert(getsockname(fd, (struct sockaddr*)&bound, &size) == 0 && hdr->nlmsg_pid == bound.nl_pid); + const struct rtmsg* rt = NLMSG_DATA(hdr); assert(rt->rtm_family == AF_INET); + int remaining = RTM_PAYLOAD(hdr); uint32_t network = 0; + for (struct rtattr* attr = RTM_RTA(rt); RTA_OK(attr, remaining); attr = RTA_NEXT(attr, remaining)) { + if (attr->rta_type == RTA_DST) { assert(RTA_PAYLOAD(attr) == 4); memcpy(&network, RTA_DATA(attr), 4); } + } + route_fd = fd; request_seq = hdr->nlmsg_seq; request_cmd = hdr->nlmsg_type; pending = 1; ack_error = 0; + if (request_cmd == RTM_NEWROUTE) { + assert(additions < 8); added[additions++] = ntohl(network); + if (additions == fail_add) ack_error = -EEXIST; + } else { + assert(request_cmd == RTM_DELROUTE && deletions < 8); removed[deletions++] = ntohl(network); + if (deletions == fail_delete) ack_error = -EPERM; + } + return len; +} + +int __real_poll(struct pollfd*, nfds_t, int); +int __wrap_poll(struct pollfd* fds, nfds_t n, int timeout) { + if (!mock || n != 1 || fds[0].fd != route_fd) return __real_poll(fds, n, timeout); + if (!pending) return 0; + fds[0].revents = POLLIN; return 1; +} + +ssize_t __real_recvfrom(int, void*, size_t, int, struct sockaddr*, socklen_t*); +ssize_t __wrap_recvfrom(int fd, void* buf, size_t len, int flags, struct sockaddr* source, socklen_t* alen) { + if (!mock || fd != route_fd || !pending) return __real_recvfrom(fd, buf, len, flags, source, alen); + struct { struct nlmsghdr hdr; struct nlmsgerr error; } reply = {0}; + reply.hdr.nlmsg_len = sizeof(reply); reply.hdr.nlmsg_type = bad_ack == 1 ? RTM_NEWROUTE : NLMSG_ERROR; + reply.hdr.nlmsg_seq = request_seq + (bad_ack == 3); reply.error.error = ack_error; + struct sockaddr_nl kernel = { .nl_family = AF_NETLINK, .nl_pid = bad_ack == 4 ? 99 : 0 }; + assert(len >= sizeof(reply) && *alen >= sizeof(kernel)); + memcpy(source, &kernel, sizeof(kernel)); *alen = sizeof(kernel); + memcpy(buf, &reply, sizeof(reply)); pending = 0; + if (bad_ack == 2) { ((struct nlmsghdr*)buf)->nlmsg_len = sizeof(struct nlmsghdr); return sizeof(struct nlmsghdr); } + return sizeof(reply); +} + +static void reset(void) { + additions = deletions = fail_add = fail_delete = bad_ack = pending = 0; route_fd = -1; +} + +static struct UTUN_INSTANCE* create(struct UASYNC* ua, int test) { + const char* config = test ? + "[global]\ntun_enabled=yes\ntun_test_mode=1\ntun_ifname=lo\ntun_ip=198.51.100.1\n" + "[routing]\nmy_subnet=198.51.100.1/32\n" + "[server:local]\naddr=127.0.0.1:0\ntype=private\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n" : + "[global]\ntun_enabled=yes\ntun_ifname=utest\ntun_ip=198.51.100.1\n" + "[routing]\nmy_subnet=198.51.100.1/32\n" + "[server:local]\naddr=127.0.0.1:0\ntype=private\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n"; + struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); assert(inst); + inst->config->global.db_sync_enabled = 0; + assert(utun_core_start(inst) == 0 && inst->socket_monitor); + return inst; +} + +static struct CFG_ROUTE_ENTRY* config_route(struct UTUN_INSTANCE* inst, uint32_t network) { + struct CFG_ROUTE_ENTRY* entry = u_calloc(1, sizeof(*entry)); assert(entry); + entry->ip.family = AF_INET; entry->ip.addr.v4.s_addr = htonl(network); entry->netmask = 24; + struct CFG_ROUTE_ENTRY** tail = &inst->config->route_subnets; + while (*tail) tail = &(*tail)->next; + *tail = entry; return entry; +} + +static void destroy(struct UTUN_INSTANCE* inst, struct UASYNC* ua) { + utun_instance_destroy(inst); uasync_drain_immediate(ua); uasync_destroy(ua, 0); +} + +static void check_mock(void) { + struct UASYNC* ua = uasync_create(); assert(ua); + struct UTUN_INSTANCE* inst = create(ua, 1); + assert(chat_service_start(inst) == 0); void* chat = inst->chat_core; sqlite3* db = inst->topo_sqlite_db; + config_route(inst, 0xc0000200); config_route(inst, 0xcb007100); + reset(); fail_tun = 1; inst->config->global.tun_test_mode = 0; + assert(utun_service_start(inst) < 0 && !inst->utun_started && !inst->tun && !additions && !deletions); + assert(inst->chat_core == chat && inst->chat_started && inst->core_started); + fail_tun = 0; inst->config->global.tun_test_mode = 1; + reset(); fail_add = 2; + assert(utun_service_start(inst) < 0 && !inst->utun_started && !inst->tun && !inst->tun_routes); + assert(additions == 2 && deletions == 1 && added[0] == 0xc0000200 && removed[0] == added[0]); + assert(!topo_groups_get_default(inst->topo_groups) && inst->chat_core == chat && inst->chat_started && inst->topo_sqlite_db == db); + DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS second route failure rolls back only first owned route and preserves chat/core"); + reset(); fail_add = 1; + assert(utun_service_start(inst) < 0 && additions == 1 && !deletions && !inst->utun_started); + reset(); assert(utun_service_start(inst) == 0 && inst->utun_started && additions == 2 && inst->tun_routes); + struct tun_route* owned = inst->tun_routes; + assert(utun_service_start(inst) == 0 && inst->tun_routes == owned && additions == 2); + inst->config->route_subnets->ip.addr.v4.s_addr = htonl(0xc6336400); + fail_delete = 1; utun_service_stop(inst); + assert(!inst->tun_routes && !inst->tun && !inst->utun_started && deletions == 2); + assert(removed[0] == 0xcb007100 && removed[1] == 0xc0000200); + utun_service_stop(inst); assert(deletions == 2); + DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS stop uses owned snapshot, continues after deletion error and is idempotent"); + for (int mode = 1; mode <= 4; mode++) { + reset(); bad_ack = mode; + assert(tun_route_add(0, "lo", 0xc0000200, 24) < 0); + } + destroy(inst, ua); +} + +/* Ошибка второго listen-сокета отклоняет весь запуск, включая начальный auto_sockets=android. */ +static void check_socket_failure(int automatic) { + int blocker = socket(AF_INET, SOCK_STREAM, 0); assert(blocker >= 0); + struct sockaddr_in address = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK) }; + assert(bind(blocker, (struct sockaddr*)&address, sizeof(address)) == 0 && listen(blocker, 1) == 0); + socklen_t len = sizeof(address); assert(getsockname(blocker, (struct sockaddr*)&address, &len) == 0); + char config[512]; + snprintf(config, sizeof(config), "[global]\ntun_enabled=no\nauto_sockets=%s\n" + "[server:first]\naddr=127.0.0.1:0\ntype=private\n" + "[server:blocked]\naddr=127.0.0.1:%u\ntransport=tcp\ntype=private\n", automatic ? "android" : "no", + ntohs(address.sin_port)); + struct UASYNC* ua = uasync_create(); assert(ua); + assert(!utun_instance_create_from_str(ua, config)); + uasync_drain_immediate(ua); uasync_destroy(ua, 0); close(blocker); + DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS second socket failure rejects startup and cleans opened sockets automatic=%d", automatic); +} + +static void check_control_failure(void) { + int blocker = socket(AF_INET, SOCK_STREAM, 0); assert(blocker >= 0); + struct sockaddr_in address = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK) }; + assert(bind(blocker, (struct sockaddr*)&address, sizeof(address)) == 0 && listen(blocker, 1) == 0); + socklen_t len = sizeof(address); assert(getsockname(blocker, (struct sockaddr*)&address, &len) == 0); + char config[256]; + snprintf(config, sizeof(config), "[global]\ntun_enabled=no\n[server:local]\naddr=127.0.0.1:0\ntype=private\n" + "[control]\nip=127.0.0.1\nport=%u\ncontrol_allow=127.0.0.1/32\n", ntohs(address.sin_port)); + struct UASYNC* ua = uasync_create(); assert(ua); + struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); assert(inst); + assert(utun_core_start(inst) < 0 && !inst->core_started && !inst->running && !inst->control_srv); + destroy(inst, ua); close(blocker); + DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS configured control socket failure rejects core startup and releases resources"); +} + +static void check_real(void) { + mock = 0; + struct UASYNC* ua = uasync_create(); assert(ua); + struct UTUN_INSTANCE* inst = create(ua, 0); + config_route(inst, 0xc0000200); config_route(inst, 0xcb007100); + assert(utun_service_start(inst) == 0 && inst->tun_routes && inst->utun_started); + assert(system("ip -4 route get 192.0.2.1 | grep -q 'dev utest'") == 0); + utun_service_stop(inst); assert(!inst->tun_routes && !inst->utun_started); + assert(system("test -z \"$(ip -4 route show 192.0.2.0/24)\"") == 0); + unsigned loopback = if_nametoindex("lo"); assert(loopback); + assert(tun_route_add(loopback, "lo", 0xcb007100, 24) == 0); + assert(utun_service_start(inst) < 0 && !inst->tun && !inst->tun_routes && !inst->utun_started); + assert(system("test -z \"$(ip -4 route show 192.0.2.0/24)\"") == 0); + assert(system("ip -4 route get 203.0.113.1 | grep -q 'dev lo'") == 0); + assert(tun_route_del(loopback, "lo", 0xcb007100, 24) == 0); + destroy(inst, ua); + DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS real netlink/TUN with running monitor: add, lookup, stop, conflict and partial rollback"); +} + +int main(int argc, char** argv) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_TUN, DEBUG_LEVEL_DEBUG); + utun_instance_set_tun_init_enabled(1); + size_t baseline = u_get_allocated_count(); + if (argc == 2 && !strcmp(argv[1], "--real")) check_real(); + else { check_mock(); check_socket_failure(0); check_socket_failure(1); check_control_failure(); } + assert(u_get_allocated_count() == baseline); return 0; +}