Separate ICMP exit startup from shared context ownership
This commit is contained in:
1 parent
051b13e161
commit
99be040488
3 files changed
+117
-50
No files matched your search
+78
-34
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user