You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
14 KiB
401 lines
14 KiB
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdio.h> |
|
#ifdef _WIN32 |
|
#include <winsock2.h> |
|
#include <ws2tcpip.h> |
|
#else |
|
#include <arpa/inet.h> |
|
#endif |
|
#include "../lib/platform_compat.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
#include "utun_instance.h" |
|
#include "etcp_api.h" |
|
#include "etcp.h" |
|
#include "etcp_connections.h" |
|
#include "route_node.h" |
|
#include "route_bgp.h" |
|
#include "route_ping.h" |
|
#include "../lib/u_async.h" |
|
|
|
struct route_ping_series_ctx { |
|
struct ETCP_CONN* reply_conn; |
|
uint64_t request_id; |
|
struct sockaddr_storage target_addr; |
|
uint8_t pubkey[SC_PUBKEY_SIZE]; |
|
struct ETCP_SOCKET* local_sock; |
|
uint8_t count_total; |
|
uint8_t count_sent; |
|
uint8_t count_ok; |
|
uint32_t sum_rtt; /* в 0.1 ms */ |
|
uint16_t timeout_ms; |
|
}; |
|
|
|
// ==================================== блок обработки запросов на удаленные пинги |
|
|
|
struct route_ping_pending { |
|
struct route_ping_pending* next; |
|
struct ROUTE_BGP* bgp; |
|
uint64_t request_id; |
|
route_ping_callback_t callback; |
|
void* arg; |
|
void* timeout_timer; |
|
uint8_t cancelled; |
|
struct ETCP_CONN* via_conn; // через кого отправили запрос (third_conn) |
|
}; |
|
|
|
// если удаленный узел долго не отвечает - вызываем коллбэк по таймауту |
|
static void route_ping_pending_timeout(void* arg) { |
|
struct route_ping_pending* p = (struct route_ping_pending*)arg; |
|
if (!p) return; |
|
p->timeout_timer = NULL; |
|
if (p->cancelled) { |
|
u_free(p); |
|
return; |
|
} |
|
struct ROUTE_BGP* bgp = p->bgp; |
|
struct route_ping_pending** cur = &bgp->ping_pending; |
|
while (*cur) { |
|
if (*cur == p) { |
|
*cur = p->next; |
|
break; |
|
} |
|
cur = &(*cur)->next; |
|
} |
|
if (p->callback) { |
|
p->callback(0, 0, 0, 0, p->arg); |
|
} |
|
u_free(p); |
|
} |
|
|
|
// прошел ответ "серия пигнов на удаленном узле завершена" |
|
void route_ping_handle_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len) { |
|
if (!bgp || !from_conn || !data || len < sizeof(struct BGP_PING_RESPONSE)) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); |
|
return; |
|
} |
|
const struct BGP_PING_RESPONSE* resp = (const struct BGP_PING_RESPONSE*)data; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "from=%s request_id=%016llx sent=%u ok=%u avg_rtt=%u", |
|
from_conn->log_name, (unsigned long long)resp->request_id, |
|
(unsigned)resp->count_sent, (unsigned)resp->count_ok, (unsigned)resp->avg_rtt); |
|
|
|
struct route_ping_pending** cur = &bgp->ping_pending; |
|
while (*cur) { |
|
struct route_ping_pending* p = *cur; |
|
if (p->request_id == resp->request_id) { |
|
*cur = p->next; |
|
if (p->timeout_timer) { |
|
uasync_cancel_timeout(bgp->instance->ua, p->timeout_timer); |
|
p->timeout_timer = NULL; |
|
} |
|
if (p->callback) { |
|
int success = (resp->count_ok > 0) ? 1 : 0; |
|
p->callback(success, resp->avg_rtt, resp->count_sent, resp->count_ok, p->arg); |
|
} |
|
u_free(p); |
|
return; |
|
} |
|
cur = &(*cur)->next; |
|
} |
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "request_id=%016llx not found in pending list", |
|
(unsigned long long)resp->request_id); |
|
} |
|
|
|
void route_ping_cancel_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) { |
|
if (!bgp || !conn) return; |
|
|
|
if (!bgp->ping_pending) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: ping_pending is NULL for %s", conn->log_name); |
|
return; |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: checking pending for %s", conn->log_name); |
|
|
|
int cancelled = 0; |
|
struct route_ping_pending** cur = &bgp->ping_pending; |
|
while (*cur) { |
|
struct route_ping_pending* p = *cur; |
|
struct nat_check_arg* na = (struct nat_check_arg*)p->arg; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, " checking pending req_id=%016llx via=%s link->etcp=%s", |
|
(unsigned long long)p->request_id, |
|
p->via_conn ? p->via_conn->log_name : "NULL", |
|
(na && na->link && na->link->etcp) ? na->link->etcp->log_name : "NULL"); |
|
|
|
if ((na && na->link && na->link->etcp == conn) || p->via_conn == conn) { |
|
*cur = p->next; |
|
if (p->timeout_timer) { |
|
uasync_cancel_timeout(bgp->instance->ua, p->timeout_timer); |
|
p->timeout_timer = NULL; |
|
} |
|
p->cancelled = 1; |
|
na->link = NULL; |
|
u_free(p); |
|
cancelled++; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_conn: cancelled req_id=%016llx", |
|
(unsigned long long)p->request_id); |
|
} else { |
|
cur = &(*cur)->next; |
|
} |
|
} |
|
|
|
if (cancelled == 0) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: no pending found for %s", conn->log_name); |
|
} |
|
} |
|
|
|
// ======================================================================== |
|
|
|
int route_ping_send_req_addr(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn, |
|
uint32_t target_ip, uint16_t target_port, |
|
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, |
|
uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg, |
|
const uint8_t* pubkey) { |
|
if (!bgp || !to_conn || count == 0 || timeout_ms == 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); |
|
return -1; |
|
} |
|
size_t pkt_size = pubkey ? sizeof(struct BGP_PING_REQUEST) : offsetof(struct BGP_PING_REQUEST, pubkey); |
|
struct BGP_PING_REQUEST* req_pkt = u_calloc(1, pkt_size); |
|
if (!req_pkt) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc failed"); |
|
return -2; |
|
} |
|
req_pkt->cmd = ETCP_ID_ROUTE_ENTRY; |
|
req_pkt->subcmd = ROUTE_SUBCMD_PING_REQ; |
|
req_pkt->request_id = bgp->next_ping_req_id++; |
|
req_pkt->count = count; |
|
req_pkt->interval_ms = interval_ms; |
|
req_pkt->timeout_ms = timeout_ms; |
|
/* IP уже в network byte order, порт конвертируем */ |
|
memcpy(req_pkt->target_ipv4, &target_ip, 4); |
|
req_pkt->target_port = htons(target_port); |
|
if (pubkey) { |
|
memcpy(req_pkt->pubkey, pubkey, SC_PUBKEY_SIZE); |
|
} |
|
|
|
struct ll_entry* e = queue_entry_new(0); |
|
if (!e) { |
|
u_free(req_pkt); |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "queue_entry_new failed"); |
|
return -3; |
|
} |
|
e->dgram = (uint8_t*)req_pkt; |
|
e->len = pkt_size; |
|
|
|
int ret = etcp_send(to_conn, e); |
|
if (ret != 0) { |
|
u_free(req_pkt); |
|
queue_entry_free(e); |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "etcp_send failed"); |
|
return -4; |
|
} |
|
|
|
struct route_ping_pending* pending = u_calloc(1, sizeof(struct route_ping_pending)); |
|
if (!pending) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "pending alloc failed"); |
|
return -5; |
|
} |
|
pending->bgp = bgp; |
|
pending->request_id = req_pkt->request_id; |
|
pending->callback = cb; |
|
pending->arg = arg; |
|
pending->via_conn = to_conn; |
|
pending->next = bgp->ping_pending; |
|
bgp->ping_pending = pending; |
|
|
|
pending->timeout_timer = uasync_set_timeout(bgp->instance->ua, wait_timeout_ms * 10, pending, route_ping_pending_timeout, "route_ping"); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%016llx ip=%s port=%u pubkey=%s", |
|
(unsigned long long)pending->request_id, |
|
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no"); |
|
return 0; |
|
} |
|
|
|
|
|
static void route_ping_series_finish(struct route_ping_series_ctx* ctx) { |
|
if (!ctx || !ctx->reply_conn) { |
|
u_free(ctx); |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "zero args"); |
|
return; |
|
} |
|
|
|
uint16_t avg_rtt = (ctx->count_ok > 0) |
|
? (uint16_t)(ctx->sum_rtt / ctx->count_ok) |
|
: (uint16_t)(ctx->timeout_ms * 10); /* таймаут как "плохо" */ |
|
|
|
/* Отправляем ответ по BGP */ |
|
struct BGP_PING_RESPONSE* resp = u_calloc(1, sizeof(struct BGP_PING_RESPONSE)); |
|
if (resp) { |
|
resp->cmd = ETCP_ID_ROUTE_ENTRY; |
|
resp->subcmd = ROUTE_SUBCMD_PING_RESP; |
|
resp->request_id = ctx->request_id; |
|
resp->count_sent = ctx->count_sent; |
|
resp->count_ok = ctx->count_ok; |
|
resp->avg_rtt = avg_rtt; |
|
|
|
struct ll_entry* e = queue_entry_new(0); |
|
if (e) { |
|
e->dgram = (uint8_t*)resp; |
|
e->len = sizeof(struct BGP_PING_RESPONSE); |
|
etcp_send(ctx->reply_conn, e); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, |
|
"PING series done request_id=%016llx sent=%u ok=%u avg_rtt=%u", |
|
(unsigned long long)ctx->request_id, |
|
(unsigned)ctx->count_sent, |
|
(unsigned)ctx->count_ok, |
|
(unsigned)avg_rtt); |
|
} else { |
|
u_free(resp); |
|
} |
|
} |
|
|
|
u_free(ctx); |
|
} |
|
|
|
/* Callback одного пинга из серии */ |
|
static void route_ping_single_cb(int success, |
|
uint16_t rtt, |
|
void* arg, |
|
uint64_t nonce, |
|
const uint8_t* resp_data, |
|
size_t resp_data_len) { |
|
(void)nonce; (void)resp_data; (void)resp_data_len; |
|
|
|
struct route_ping_series_ctx* ctx = (struct route_ping_series_ctx*)arg; |
|
if (!ctx) return; |
|
|
|
/* Обновляем статистику */ |
|
ctx->count_sent++; |
|
if (success) { |
|
ctx->count_ok++; |
|
ctx->sum_rtt += rtt; |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping response nonce=%llu", (unsigned long long)nonce); |
|
|
|
/* Если ещё не все пакеты отправлены — сразу шлём следующий */ |
|
if (ctx->count_sent < ctx->count_total) { |
|
int ret = etcp_send_ping_to_socket( |
|
ctx->reply_conn->instance, |
|
ctx->local_sock, |
|
ctx->pubkey, |
|
&ctx->target_addr, |
|
ctx->timeout_ms, |
|
route_ping_single_cb, |
|
ctx, |
|
NULL, 0); |
|
|
|
if (ret != 0) { |
|
/* Не смогли отправить следующий — завершаем серию досрочно */ |
|
route_ping_series_finish(ctx); |
|
} |
|
/* else: следующий пинг запущен, ждём его callback */ |
|
} else { |
|
/* Все пакеты обработаны */ |
|
route_ping_series_finish(ctx); |
|
} |
|
} |
|
|
|
// Обработчик PING_REQ (нам запросили "пропингуй узел и верни результат") |
|
void route_ping_handle_req(struct ROUTE_BGP* bgp, |
|
struct ETCP_CONN* from_conn, |
|
const uint8_t* data, |
|
size_t len) { |
|
if (!bgp || !from_conn || !data || len < offsetof(struct BGP_PING_REQUEST, pubkey)) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: bad args len=%zu", len); |
|
return; |
|
} |
|
|
|
const struct BGP_PING_REQUEST* req_pkt = (const struct BGP_PING_REQUEST*)data; |
|
|
|
/* Создаём контекст серии */ |
|
struct route_ping_series_ctx* ctx = u_calloc(1, sizeof(struct route_ping_series_ctx)); |
|
if (!ctx) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: alloc ctx failed"); |
|
return; |
|
} |
|
|
|
ctx->reply_conn = from_conn; |
|
ctx->request_id = req_pkt->request_id; |
|
ctx->count_total = req_pkt->count; |
|
ctx->timeout_ms = req_pkt->timeout_ms; |
|
if (len >= sizeof(struct BGP_PING_REQUEST)) { |
|
memcpy(ctx->pubkey, req_pkt->pubkey, SC_PUBKEY_SIZE); |
|
} |
|
|
|
/* Целевой адрес - в sockaddr храним в network byte order (как в пакете) */ |
|
struct sockaddr_in* sin = (struct sockaddr_in*)&ctx->target_addr; |
|
sin->sin_family = AF_INET; |
|
memcpy(&sin->sin_addr.s_addr, req_pkt->target_ipv4, 4); /* network byte order */ |
|
sin->sin_port = req_pkt->target_port; /* порт в network byte order */ |
|
|
|
/* Если target не указан — можно разрешить из nodeinfo */ |
|
/* |
|
if (sin->sin_addr.s_addr == 0 && sin->sin_port == 0) { |
|
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, req_pkt->node_id); |
|
if (nq) { |
|
const struct NODEINFO_IPV4_SOCKET* sockets; |
|
int count = get_node_v4_sockets(nq, &sockets); |
|
if (count > 0) { |
|
memcpy(&sin->sin_addr.s_addr, sockets[0].addr, 4); |
|
sin->sin_port = sockets[0].port; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "resolved target from nodeinfo: %s:%u", |
|
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port)); |
|
} |
|
} |
|
} |
|
*/ |
|
|
|
/* Ищем первый IPv4-сокет (как в старом коде) */ |
|
struct ETCP_SOCKET* ls = bgp->instance->etcp_sockets; |
|
while (ls) { |
|
if (ls->local_addr.ss_family == AF_INET) { |
|
ctx->local_sock = ls; |
|
break; |
|
} |
|
ls = ls->next; |
|
} |
|
|
|
if (!ctx->local_sock) { |
|
DEBUG_WARN(DEBUG_CATEGORY_BGP, "ping req: no IPv4 socket"); |
|
route_ping_series_finish(ctx); /* отправит 0/0 */ |
|
return; |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, |
|
"PING series start request_id=%016llx target=%s:%u count=%u timeout=%u", |
|
(unsigned long long)ctx->request_id, |
|
ip_to_str(&sin->sin_addr, AF_INET).str, |
|
ntohs(sin->sin_port), |
|
(unsigned)ctx->count_total, |
|
(unsigned)ctx->timeout_ms); |
|
|
|
/* Отладка: показываем что передаем в etcp_send_ping_to_socket */ |
|
{ |
|
struct sockaddr_in* dbg_sin = (struct sockaddr_in*)&ctx->target_addr; |
|
uint8_t* k = ctx->pubkey; |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "SEND_PING: family=%u addr=0x%08x port=%u pubkey=%02x%02x%02x%02x...%02x%02x", |
|
(unsigned)dbg_sin->sin_family, |
|
(unsigned)dbg_sin->sin_addr.s_addr, |
|
(unsigned)ntohs(dbg_sin->sin_port), |
|
k[0], k[1], k[2], k[3], k[60], k[61]); |
|
} |
|
|
|
/* Запускаем первый пинг (дальше цепочка через callback) */ |
|
int ret = etcp_send_ping_to_socket( |
|
bgp->instance, |
|
ctx->local_sock, |
|
ctx->pubkey, |
|
&ctx->target_addr, |
|
ctx->timeout_ms, |
|
route_ping_single_cb, |
|
ctx, |
|
NULL, 0); |
|
|
|
if (ret != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: cannot start first ping"); |
|
route_ping_series_finish(ctx); |
|
} |
|
}
|
|
|