Browse Source

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.
stream-multiview
evgeny 1 week ago
parent
commit
72e2c89e3b
  1. 8
      doc/service_lifecycle.md
  2. 48
      src/control_server.c
  3. 54
      src/transport_layer/auto_socket.c
  4. 23
      src/transport_layer/etcp_connections.c
  5. 15
      src/transport_layer/etcp_keepalive.c
  6. 3
      src/transport_layer/etcp_keepalive.h
  7. 9
      src/tun_freebsd.c
  8. 18
      src/tun_if.c
  9. 11
      src/tun_linux.c
  10. 339
      src/tun_route.c
  11. 20
      src/tun_route.h
  12. 22
      src/tun_route_doc.md
  13. 26
      src/tun_windows.c
  14. 72
      src/utun_instance.c
  15. 4
      src/utun_instance.h
  16. 7
      tests/Makefile.am
  17. 9
      tests/test_etcp_padding.c
  18. 30
      tests/test_etcp_server_lifecycle.c
  19. 194
      tests/test_tun_route.c

8
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, маршрутизатор, общая репликация, время, мониторинг |

48
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);
}

54
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) {

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

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

3
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.

9
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) {

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

11
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

339
src/tun_route.c

@ -9,6 +9,7 @@
#include <string.h>
#include <errno.h>
#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;
}

20
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
}

22
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 <net>/<prefix> dev <ifname>` через `system()`
3. `tun_route_flush`: `ip route flush dev <ifname>`, при неудаче — парсинг `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 <ifname>`; UTUN stop/rollback используют список владения, а не flush.
**Windows:**
1. `netsh interface ip add/delete route` через `system()` (первичный метод)

26
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;

72
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;

4
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

7
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)

9
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);

30
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);

194
tests/test_tun_route.c

@ -0,0 +1,194 @@
/* Реальный bind netlink при работающем мониторе; управляемые ACK и откат запуска UTUN. */
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <poll.h>
#include <net/if.h>
#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;
}
Loading…
Cancel
Save