Separate ICMP exit startup from shared context ownership

This commit is contained in:
evgeny committed 2026-10-24 13:23:58 +02:00
1 parent 051b13e161
commit 99be040488
3 files changed
+117 -50

No files matched your search

+78 -34
View File
@@ -10,16 +10,15 @@
#include "../lib/debug_config.h"
#include "../lib/ll_queue.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <openssl/rand.h>
#ifndef _WIN32
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#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;
}
+10 -9
View File
@@ -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);
+29 -7
View File
@@ -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;