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; +}