diff --git a/src/proxy/icmp_proxy.c b/src/proxy/icmp_proxy.c index 57b1001f..2a2e7ee2 100644 --- a/src/proxy/icmp_proxy.c +++ b/src/proxy/icmp_proxy.c @@ -10,16 +10,15 @@ #include "../lib/debug_config.h" #include "../lib/ll_queue.h" #include "../lib/mem.h" +#include "../lib/platform_compat.h" #include #include #include #include #ifndef _WIN32 -#include #include #include #include -#include #include #else #define ICMP_MINLEN 8 @@ -98,9 +97,22 @@ static int exit_send_echo(struct UTUN_INSTANCE* inst, uint64_t client_node_id, DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: sendto dst=0x%08x id=0x%04x seq=%u len=%zu", dst_ip, echo_id, echo_seq, icmp_len); struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}}; - ssize_t n = sendto(ctx->raw_sock, buf, icmp_len, 0, (struct sockaddr*)&addr, sizeof(addr)); + if (!ctx->expire_timer) { + ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->request_timeout_tb, ctx, req_expire_timer_cb, "icmp_expire"); + if (!ctx->expire_timer) { + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: cannot reserve expiry timer peer=%016llx", (unsigned long long)client_node_id); + u_free(buf); u_free(r); return -1; + } + } + ssize_t n; + do { n = sendto(ctx->raw_sock, buf, icmp_len, 0, (struct sockaddr*)&addr, sizeof(addr)); } while (n < 0 && errno == EINTR); u_free(buf); - if (n < 0) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: sendto failed: %s", strerror(errno)); u_free(r); return -1; } + if (n != (ssize_t)icmp_len) { + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: sendto failed dst=%08x sent=%zd expected=%zu error=%d", + dst_ip, n, icmp_len, n < 0 ? socket_get_error() : 0); + if (!ctx->pending) { uasync_cancel_timeout(ctx->ua, ctx->expire_timer); ctx->expire_timer = NULL; } + u_free(r); return -1; + } DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: sendto sent %zd bytes", n); r->client_node_id = client_node_id; r->dst_ip = dst_ip; r->orig_src_ip = orig_src_ip; @@ -109,8 +121,6 @@ static int exit_send_echo(struct UTUN_INSTANCE* inst, uint64_t client_node_id, if (payload_len > 0) memcpy(r->payload, payload, r->payload_len); r->sent_tb = get_time_tb(); r->next = ctx->pending; ctx->pending = r; - if (!ctx->expire_timer) - ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->request_timeout_tb, ctx, req_expire_timer_cb, "icmp_expire"); return 0; } @@ -181,9 +191,17 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry) uint8_t* payload = entry->dgram + ICMP_PROXY_RECV_HDR_SIZE; size_t payload_len = entry->len - ICMP_PROXY_RECV_HDR_SIZE; - if (ctx->raw_sock != SOCKET_INVALID) { + if (ctx->test_loopback) { + DEBUG_DEBUG(DEBUG_CATEGORY_PROXY, "icmp_proxy: explicit test echo peer=%016llx", (unsigned long long)client_node_id); + } else if (ctx->raw_sock != SOCKET_INVALID) { exit_send_echo(inst, client_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, payload_len); - } else if (ctx->test_loopback) { + goto drop; + } else { + DEBUG_WARN(DEBUG_CATEGORY_PROXY, "icmp_proxy: exit unavailable peer=%016llx dst=%08x", + (unsigned long long)client_node_id, dst_ip); + goto drop; + } + { DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: test loopback reply to 0x%08x", dst_ip); struct ll_entry* e = queue_entry_new(0); if (e) { @@ -197,11 +215,10 @@ static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry) memcpy(e->dgram + 12, &echo_seq, 2); if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len); e->len = ICMP_PROXY_HDR_SIZE + payload_len; - etcp_route_send(inst, TOPO_GROUP_UTUN, client_node_id, e, 0, 0); - } else queue_entry_free(e); - } - } else { - DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: no raw socket, dropping echo request to 0x%08x", dst_ip); + if (etcp_route_send(inst, TOPO_GROUP_UTUN, client_node_id, e, 0, 0) < 0) + DEBUG_WARN(DEBUG_CATEGORY_PROXY, "icmp_proxy: test reply router send failed"); + } else { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: test reply allocation failed"); queue_entry_free(e); } + } else DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: test reply entry allocation failed"); } drop: @@ -321,15 +338,27 @@ int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst, void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled) { struct icmp_proxy_ctx* ctx = inst ? inst->icmp_proxy : NULL; - if (ctx) ctx->test_loopback = enabled; + if (!ctx) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "icmp_proxy: test mode requires initialized context"); return; } + if (ctx->pending) { DEBUG_WARN(DEBUG_CATEGORY_PROXY, "icmp_proxy: cannot change test mode with pending requests"); return; } + if (enabled && ctx->raw_sock != SOCKET_INVALID) { + uasync_remove_socket_t(ctx->ua, ctx->raw_sock); ctx->raw_read_id = NULL; + socket_close_wrapper(ctx->raw_sock); ctx->raw_sock = SOCKET_INVALID; + } + ctx->test_loopback = enabled != 0; + DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: explicit test loopback=%d", ctx->test_loopback); } static void req_expire_timer_cb(void* arg) { struct icmp_proxy_ctx* ctx = (struct icmp_proxy_ctx*)arg; req_expire(ctx); ctx->expire_timer = NULL; - if (ctx->pending) + if (ctx->pending) { ctx->expire_timer = uasync_set_timeout(ctx->ua, ctx->request_timeout_tb, ctx, req_expire_timer_cb, "icmp_expire"); + if (!ctx->expire_timer) { + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: expiry timer failed, dropping all pending requests"); + while (ctx->pending) { struct icmp_request* r = ctx->pending; ctx->pending = r->next; u_free(r); } + } + } } static void req_expire(struct icmp_proxy_ctx* ctx) { @@ -345,35 +374,50 @@ static void req_expire(struct icmp_proxy_ctx* ctx) { // Публичные init/destroy // ==================================================================== int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) { - if (!inst) return -1; - if (inst->icmp_proxy) return 0; + if (!inst || !ua) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: invalid init inst=%p ua=%p", (void*)inst, (void*)ua); return -EINVAL; } + if (inst->icmp_proxy) { + if (inst->icmp_proxy->ua == ua) return 0; + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: init attempted with another event loop"); return -EINVAL; + } struct icmp_proxy_ctx* ctx = u_calloc(1, sizeof(struct icmp_proxy_ctx)); - if (!ctx) return -1; + if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: context allocation failed"); return -ENOMEM; } ctx->inst = inst; ctx->ua = ua; ctx->raw_sock = SOCKET_INVALID; ctx->request_timeout_tb = ICMP_TIMEOUT_TB; - ctx->is_exit = inst->tcp_proxy_server.enabled; ctx->test_loopback = 0; ctx->expire_timer = NULL; if (RAND_bytes((unsigned char*)&ctx->next_token, sizeof(ctx->next_token)) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: token initialization failed"); u_free(ctx); return -1; + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: token initialization failed"); u_free(ctx); return -EIO; + } + if (etcp_router_bind(inst, ETCP_RT_ID_ICMP_PROXY, icmp_proxy_recv_cb) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: router binding failed"); u_free(ctx); return -EIO; } inst->icmp_proxy = ctx; + DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: shared context ready node=%016llx", (unsigned long long)inst->node_id); + return 0; +} - if (ctx->is_exit) { - ctx->raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); - if (ctx->raw_sock == SOCKET_INVALID) - DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: raw socket(SOCK_RAW) failed: %s", strerror(errno)); - else { - socket_set_nonblocking(ctx->raw_sock); - DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: raw socket created fd=%d", ctx->raw_sock); - ctx->raw_read_id = uasync_add_socket_t(ua, ctx->raw_sock, raw_read_cb, NULL, NULL, "icmp_raw", ctx); - if (!ctx->raw_read_id) { socket_close_wrapper(ctx->raw_sock); ctx->raw_sock = SOCKET_INVALID; } - } +// Raw exit публикуется только после регистрации; отказ не затрагивает клиентский binding. +int icmp_proxy_enable_exit(struct UTUN_INSTANCE* inst) { + struct icmp_proxy_ctx* ctx = inst ? inst->icmp_proxy : NULL; + if (!ctx || !inst->tcp_proxy_server.enabled) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: exit requires context/server"); return -EINVAL; } + if (ctx->test_loopback || ctx->raw_sock != SOCKET_INVALID) return 0; + socket_t sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (sock == SOCKET_INVALID) { + int err = socket_get_error(); + DEBUG_WARN(DEBUG_CATEGORY_PROXY, "icmp_proxy: raw socket unavailable error=%d (%s)", err, socket_strerror(err)); + return -(err ? err : EIO); } - - etcp_router_bind(inst, ETCP_RT_ID_ICMP_PROXY, icmp_proxy_recv_cb); - ctx->initialized = 1; - DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy initialized (exit=%d raw_sock=%d)", ctx->is_exit, (int)ctx->raw_sock); + if (socket_set_nonblocking(sock) < 0) { + int err = socket_get_error(); + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: raw nonblocking failed error=%d", err); + socket_close_wrapper(sock); return -(err ? err : EIO); + } + void* read_id = uasync_add_socket_t(ctx->ua, sock, raw_read_cb, NULL, NULL, "icmp_raw", ctx); + if (!read_id) { + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "icmp_proxy: raw read registration failed"); socket_close_wrapper(sock); return -ENOMEM; + } + ctx->raw_sock = sock; ctx->raw_read_id = read_id; + DEBUG_INFO(DEBUG_CATEGORY_PROXY, "icmp_proxy: raw exit ready fd=%d", (int)sock); return 0; } diff --git a/src/proxy/icmp_proxy.h b/src/proxy/icmp_proxy.h index 504447d6..5a1c4f76 100644 --- a/src/proxy/icmp_proxy.h +++ b/src/proxy/icmp_proxy.h @@ -1,9 +1,9 @@ /* IPv4 ICMP echo через UTUN-router: exit отправляет ping через raw socket, сопоставляет reply * по wire id/seq и IP цели, восстанавливая исходные id/seq клиента. Запросы ограничены таймаутом. * init/destroy и callbacks — в uasync; inst владеет контекстом в inst->icmp_proxy. - * init создаёт raw socket, если server.enabled; recv_cb проверяет текущий server.enabled для REQUEST + * init регистрирует общий контекст; enable_exit отдельно открывает raw socket. recv_cb проверяет server.enabled для REQUEST * и via_node_id клиентского proxy для REPLY. IP/id/seq в API — network byte order. - * test_loopback возвращает виртуальные ответы только при отсутствии raw socket; флаг его не отключает. */ + * test_loopback явно переключает тестовый exit на виртуальные ответы, закрывая raw socket без pending-запросов. */ #ifndef ICMP_PROXY_H #define ICMP_PROXY_H @@ -49,8 +49,6 @@ struct icmp_request { }; struct icmp_proxy_ctx { - int initialized; // init завершён, binding запрошен - int is_exit; // снимок server.enabled при init (определяет создание raw socket) struct UTUN_INSTANCE* inst; // заимствованный instance-владелец struct UASYNC* ua; // заимствованный event loop socket_t raw_sock; // один SOCK_RAW для всего ICMP на exit @@ -58,15 +56,18 @@ struct icmp_proxy_ctx { struct icmp_request* pending; // собственные запросы, ожидающие ответа uint32_t next_token; // генератор wire id/seq со случайным начальным значением uint64_t request_timeout_tb; // период проверки и порог ожидания, default=50000 (5 с) - int test_loopback; // fallback при raw_sock==SOCKET_INVALID, только для тестов + int test_loopback; // явный виртуальный exit, только для тестов void* expire_timer; // периодический таймер очистки истёкших запросов }; // Создать контекст и bind ICMP service; повторный init не меняет существующий контекст и даёт 0. -// inst/ua живут до destroy. -1 = нет inst/памяти/случайного token, 0 = контекст создан. -// 0 не гарантирует raw socket на exit: ошибка socket/регистрации не проваливает init; bind не проверяется. +// inst/ua живут до destroy. 0 = контекст и binding готовы, <0 = ошибка без публикации нового контекста. +// Контекстом владеет instance; TCP client/server его не уничтожают. Raw socket здесь не создаётся. int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua); +// Запустить raw exit существующего контекста. 0 = готов (включая явный test_loopback), <0 = -errno. +// Отказ сохраняет клиентскую сторону и прежнее состояние; политику отсутствия прав выбирает instance. +int icmp_proxy_enable_exit(struct UTUN_INSTANCE* inst); // Снять binding, отменить таймер, закрыть raw socket, удалить pending и обнулить inst->icmp_proxy; NULL допустим. void icmp_proxy_destroy(struct UTUN_INSTANCE* inst); @@ -87,8 +88,8 @@ int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst, uint32_t dst_ip, uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq, const uint8_t* payload, size_t payload_len); -// Изменить тестовый fallback существующего контекста; без контекста ничего не делает. -// При наличии raw socket реальные echo-запросы имеют приоритет независимо от enabled. +// Явно включить тестовый loopback, закрыв raw socket; при pending-запросах изменение отклоняется с WARN. +// Без контекста ничего не делает; выключение loopback само по себе raw socket не открывает. void icmp_proxy_set_test_loopback(struct UTUN_INSTANCE* inst, int enabled); diff --git a/src/utun_instance.c b/src/utun_instance.c index 2f51947e..f54c3ef0 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -39,6 +39,8 @@ #include "transport_layer/node_conn_direct.h" #include "transport_layer/socket_monitor.h" #include "transport_layer/auto_socket.h" +#include "proxy/udp_proxy.h" +#include "proxy/icmp_proxy.h" #ifdef UTUN_HAVE_STANDBY #include "standby.h" #endif @@ -266,11 +268,10 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u if (g_topo_group_enabled && instance->topo_groups) etcp_router_bind(instance, ETCP_RT_ID_CONN_MGR, conn_mgr_router_recv_handler); - // TCP proxy server (exit node, optional) — must be before tcp_proxy_client so - // tcp_proxy_client_create can overwrite the handler if it has remote mappings + // TCP client/server имеют отдельные bindings; общими датаграммными контекстами владеет instance. #ifndef _WIN32 if (tcp_proxy_server_init(instance) != 0) { - DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "Failed to initialize tcp_proxy_server (non-fatal)"); + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "Failed to initialize TCP proxy exit"); return -1; } #endif @@ -298,6 +299,22 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u } else { instance->tcp_proxy_client = NULL; } + if (instance->tcp_proxy_server.enabled || (instance->tcp_proxy_client && instance->tcp_proxy_client->tun)) { + if (udp_proxy_init(instance, ua) < 0 || icmp_proxy_init(instance, ua) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "Failed to initialize shared proxy datagram contexts"); return -1; + } + if (instance->tcp_proxy_server.enabled) { + int ret = icmp_proxy_enable_exit(instance); + if (ret < 0) { + if (ret != -EPERM && ret != -EACCES && ret != -EPROTONOSUPPORT && ret != -EAFNOSUPPORT) { + DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "Failed to initialize ICMP exit: error=%d", -ret); return -1; + } + DEBUG_WARN(DEBUG_CATEGORY_PROXY, "ICMP exit disabled: %s; TCP/UDP and ICMP client remain available", strerror(-ret)); + } + } + DEBUG_INFO(DEBUG_CATEGORY_PROXY, "Proxy ready: tcp_exit=%d udp=1 icmp_client=1 icmp_exit=%d", + instance->tcp_proxy_server.enabled, instance->icmp_proxy->raw_sock != SOCKET_INVALID); + } #endif /* _WIN32 */ return 0; @@ -439,6 +456,15 @@ static void instance_destroy(struct UTUN_INSTANCE* instance, int owns_config) { } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] C done — control_srv"); +#ifndef _WIN32 + // Остановить producers/callbacks proxy, пока UTUN-группа, router и транспорт ещё живы. + if (instance->tcp_proxy_client) { tcp_proxy_client_destroy(instance->tcp_proxy_client); instance->tcp_proxy_client = NULL; } + tcp_proxy_server_destroy(instance); + udp_proxy_destroy(instance); + icmp_proxy_destroy(instance); + if (instance->ua) uasync_drain_immediate(instance->ua); +#endif + chat_service_stop(instance); utun_service_stop(instance); @@ -543,10 +569,6 @@ static void instance_destroy(struct UTUN_INSTANCE* instance, int owns_config) { tun_close(instance->tun); instance->tun = NULL; } -#ifndef _WIN32 - if (instance->tcp_proxy_client) { tcp_proxy_client_destroy(instance->tcp_proxy_client); instance->tcp_proxy_client = NULL; } - tcp_proxy_server_destroy(instance); -#endif routing_destroy(instance); if (instance->md.initialized) media_delivery_destroy(instance); media_async_destroy(instance->media_async); instance->media_async = NULL;