429 lines
15 KiB
C
429 lines
15 KiB
C
#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 "topo_node.h"
|
|
#include "topo_group.h"
|
|
#include "route_ping.h"
|
|
#include "../lib/u_async.h"
|
|
|
|
struct route_ping_series_ctx {
|
|
struct ETCP_CONN* reply_conn;
|
|
uint32_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 NAT_DETECTION* nd;
|
|
uint32_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 NAT_DETECTION* nd = p->nd;
|
|
struct route_ping_pending** cur = &nd->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 NAT_DETECTION* nd, struct ETCP_CONN* from_conn,
|
|
const uint8_t* data, size_t len) {
|
|
if (!nd || !from_conn || !data || len < sizeof(struct NATDET_PING_RESP)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args");
|
|
return;
|
|
}
|
|
const struct NATDET_PING_RESP* resp = (const struct NATDET_PING_RESP*)data;
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "from=%s request_id=%08x sent=%u ok=%u avg_rtt=%u",
|
|
from_conn->log_name, (unsigned)resp->request_id,
|
|
(unsigned)resp->count_sent, (unsigned)resp->count_ok, (unsigned)resp->avg_rtt);
|
|
topo_node_ping_update_rtt(nd->inst->topo_groups, from_conn->peer_node_id, resp->avg_rtt);
|
|
|
|
struct route_ping_pending** cur = &nd->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(nd->inst->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=%08x not found in pending list",
|
|
(unsigned)resp->request_id);
|
|
}
|
|
|
|
void route_ping_cancel_for_conn(struct NAT_DETECTION* nd, struct ETCP_CONN* conn) {
|
|
if (!nd || !conn) return;
|
|
|
|
if (!nd->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 = &nd->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=%08x via=%s link->etcp=%s",
|
|
(unsigned)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;
|
|
uint32_t rid = p->request_id;
|
|
if (p->timeout_timer) {
|
|
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer);
|
|
p->timeout_timer = NULL;
|
|
}
|
|
p->cancelled = 1;
|
|
if (na) na->link = NULL;
|
|
u_free(na);
|
|
u_free(p);
|
|
cancelled++;
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_conn: cancelled req_id=%08x", (unsigned)rid);
|
|
} else {
|
|
cur = &(*cur)->next;
|
|
}
|
|
}
|
|
|
|
if (cancelled == 0) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "cancel_for_conn: no pending found for %s", conn->log_name);
|
|
}
|
|
}
|
|
|
|
void route_ping_cancel_for_link(struct NAT_DETECTION* nd, struct ETCP_LINK* link) {
|
|
if (!nd || !link) return;
|
|
struct route_ping_pending** cur = &nd->ping_pending;
|
|
while (*cur) {
|
|
struct route_ping_pending* p = *cur;
|
|
struct nat_check_arg* na = (struct nat_check_arg*)p->arg;
|
|
if (na && na->link == link) {
|
|
*cur = p->next;
|
|
uint32_t rid = p->request_id;
|
|
if (p->timeout_timer) {
|
|
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer);
|
|
p->timeout_timer = NULL;
|
|
}
|
|
p->cancelled = 1;
|
|
na->link = NULL;
|
|
u_free(na);
|
|
u_free(p);
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "cancel_for_link: cancelled req_id=%08x link=%p", (unsigned)rid, (void*)link);
|
|
} else {
|
|
cur = &(*cur)->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void route_ping_destroy_pending(struct NAT_DETECTION* nd) {
|
|
if (!nd) return;
|
|
struct route_ping_pending* p = nd->ping_pending;
|
|
while (p) {
|
|
struct route_ping_pending* next = p->next;
|
|
if (p->timeout_timer) {
|
|
uasync_cancel_timeout(nd->inst->ua, p->timeout_timer);
|
|
p->timeout_timer = NULL;
|
|
}
|
|
p->cancelled = 1;
|
|
u_free(p);
|
|
p = next;
|
|
}
|
|
nd->ping_pending = NULL;
|
|
}
|
|
|
|
// ========================================================================
|
|
|
|
int route_ping_send_req_addr(struct NAT_DETECTION* nd, struct TOPO_GROUP* group,
|
|
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) {
|
|
(void)group;
|
|
if (!nd || !to_conn || count == 0 || timeout_ms == 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args");
|
|
return -1;
|
|
}
|
|
size_t pkt_size = pubkey ? sizeof(struct NATDET_PING_REQ) : offsetof(struct NATDET_PING_REQ, pubkey);
|
|
struct NATDET_PING_REQ* req_pkt = u_calloc(1, pkt_size);
|
|
if (!req_pkt) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc failed");
|
|
return -2;
|
|
}
|
|
req_pkt->cmd = ETCP_ID_NAT_DETECTION;
|
|
req_pkt->subcmd = NATDET_SUBCMD_PING_REQ;
|
|
uint32_t new_req_id = nd->next_ping_req_id++;
|
|
req_pkt->request_id = new_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;
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "request_id=%08x ip=%s port=%u pubkey=%s",
|
|
(unsigned)new_req_id,
|
|
ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port, pubkey ? "yes" : "no");
|
|
|
|
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->nd = nd;
|
|
pending->request_id = new_req_id;
|
|
pending->callback = cb;
|
|
pending->arg = arg;
|
|
pending->via_conn = to_conn;
|
|
pending->next = nd->ping_pending;
|
|
nd->ping_pending = pending;
|
|
|
|
pending->timeout_timer = uasync_set_timeout(nd->inst->ua, wait_timeout_ms * 10, pending,
|
|
route_ping_pending_timeout, "route_ping");
|
|
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 NATDET_PING_RESP* resp = u_calloc(1, sizeof(struct NATDET_PING_RESP));
|
|
if (resp) {
|
|
resp->cmd = ETCP_ID_NAT_DETECTION;
|
|
resp->subcmd = NATDET_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 NATDET_PING_RESP);
|
|
if (etcp_send(ctx->reply_conn, e) != 0) {
|
|
queue_dgram_free(e); queue_entry_free(e);
|
|
} else {
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP,
|
|
"PING series done %s request_id=%08x sent=%u ok=%u avg_rtt=%u",
|
|
ctx->reply_conn->log_name,
|
|
(unsigned)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_DEBUG(DEBUG_CATEGORY_BGP, "ping cb: ok=%d rtt=%u sent=%d/%d nonce=%016llx",
|
|
success, rtt, ctx->count_sent, ctx->count_total, (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, 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 NAT_DETECTION* nd, struct TOPO_GROUP* group,
|
|
struct ETCP_CONN* from_conn,
|
|
const uint8_t* data,
|
|
size_t len) {
|
|
if (!from_conn || !nd || !data || len < offsetof(struct NATDET_PING_REQ, pubkey)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: bad args len=%zu", len);
|
|
return;
|
|
}
|
|
|
|
const struct NATDET_PING_REQ* req_pkt = (const struct NATDET_PING_REQ*)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 (group) {
|
|
struct TOPO_GROUP_NODE* req_nq = topo_node_find_by_id(group, from_conn->peer_node_id);
|
|
if (req_nq) req_nq->connectivity.ping_req_time = get_time_tb();
|
|
}
|
|
}
|
|
if (len >= sizeof(struct NATDET_PING_REQ)) {
|
|
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 */
|
|
|
|
/* Ищем первый IPv4-сокет */
|
|
struct ETCP_SOCKET* ls = (group ? group->instance : nd->inst)->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=%08x target=%s:%u count=%u timeout=%u",
|
|
(unsigned)ctx->request_id, ip_to_str(&sin->sin_addr, AF_INET).str,
|
|
ntohs(sin->sin_port), (unsigned)ctx->count_total, (unsigned)ctx->timeout_ms);
|
|
|
|
/* Запускаем первый пинг (дальше цепочка через callback) */
|
|
int ret = etcp_send_ping_to_socket(
|
|
(group ? group->instance : nd->inst),
|
|
ctx->local_sock,
|
|
ctx->pubkey,
|
|
&ctx->target_addr,
|
|
ctx->timeout_ms,
|
|
route_ping_single_cb,
|
|
ctx,
|
|
NULL, 0, 0);
|
|
|
|
if (ret != 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "ping req: cannot start first ping");
|
|
route_ping_series_finish(ctx);
|
|
}
|
|
}
|