12 changed files with 681 additions and 29 deletions
@ -0,0 +1,421 @@
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <arpa/inet.h> |
||||
#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" |
||||
|
||||
struct route_ping_sock_ctx { |
||||
struct ETCP_SOCKET* local_sock; |
||||
uint16_t avg_rtt; // средний RTT в 0.1ms
|
||||
uint8_t count_sent; |
||||
uint8_t count_ok; |
||||
}; |
||||
|
||||
struct route_ping_req { |
||||
struct ETCP_CONN* reply_conn; |
||||
uint64_t request_id; |
||||
struct NODEINFO_Q* target_node; |
||||
uint8_t socket_count; |
||||
uint8_t completed_count; |
||||
uint16_t timeout_ms; |
||||
struct route_ping_sock_ctx sockets[0]; |
||||
}; |
||||
|
||||
struct route_ping_series { |
||||
struct route_ping_req* req; |
||||
struct route_ping_sock_ctx* sock_ctx; |
||||
struct ETCP_SOCKET* local_sock; |
||||
struct sockaddr_storage target_addr; |
||||
uint8_t pubkey[SC_PUBKEY_SIZE]; |
||||
uint8_t count_total; |
||||
uint8_t count_sent; |
||||
uint8_t count_ok; |
||||
uint32_t rtt_sum; |
||||
uint16_t timeout_ms; |
||||
uint16_t interval_ms; |
||||
void* next_timer; |
||||
}; |
||||
|
||||
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; |
||||
}; |
||||
|
||||
static void route_ping_send_resp(struct route_ping_req* req, uint16_t avg_rtt); |
||||
static void route_ping_next(void* arg); |
||||
static void route_ping_finish(struct route_ping_req* req); |
||||
|
||||
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; |
||||
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); |
||||
} |
||||
|
||||
static void route_ping_series_free(struct route_ping_series* series) { |
||||
if (!series) return; |
||||
if (series->next_timer) { |
||||
uasync_cancel_timeout(series->req->reply_conn->instance->ua, series->next_timer); |
||||
series->next_timer = NULL; |
||||
} |
||||
u_free(series); |
||||
} |
||||
|
||||
static void route_ping_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* series = (struct route_ping_series*)arg; |
||||
if (!series) return; |
||||
series->count_sent++; |
||||
if (success) { |
||||
series->count_ok++; |
||||
series->rtt_sum += rtt; |
||||
} |
||||
if (series->count_sent < series->count_total && series->req->reply_conn) { |
||||
struct UTUN_INSTANCE* inst = series->req->reply_conn->instance; |
||||
series->next_timer = uasync_set_timeout(inst->ua, series->interval_ms * 10, series, route_ping_next); |
||||
return; |
||||
} |
||||
// серия завершена
|
||||
if (series->count_ok > 0) { |
||||
series->sock_ctx->avg_rtt = (uint16_t)(series->rtt_sum / series->count_ok); |
||||
} else { |
||||
series->sock_ctx->avg_rtt = series->timeout_ms * 10; // максимум при полном провале
|
||||
} |
||||
series->sock_ctx->count_sent = series->count_sent; |
||||
series->sock_ctx->count_ok = series->count_ok; |
||||
series->req->completed_count++; |
||||
if (series->req->completed_count >= series->req->socket_count) { |
||||
route_ping_finish(series->req); |
||||
} |
||||
u_free(series); |
||||
} |
||||
|
||||
static void route_ping_next(void* arg) { |
||||
struct route_ping_series* series = (struct route_ping_series*)arg; |
||||
if (!series) return; |
||||
series->next_timer = NULL; |
||||
if (!series->req->reply_conn) { |
||||
u_free(series); |
||||
return; |
||||
} |
||||
int ret = etcp_send_ping_to_socket(series->req->reply_conn->instance, series->local_sock, |
||||
series->pubkey, &series->target_addr, |
||||
series->timeout_ms, route_ping_cb, series, NULL, 0); |
||||
if (ret != 0) { |
||||
// не удалось отправить, считаем как fail и продолжаем/завершаем
|
||||
series->count_sent++; |
||||
if (series->count_sent < series->count_total) { |
||||
struct UTUN_INSTANCE* inst = series->req->reply_conn->instance; |
||||
series->next_timer = uasync_set_timeout(inst->ua, series->interval_ms * 10, series, route_ping_next); |
||||
} else { |
||||
series->sock_ctx->avg_rtt = series->timeout_ms * 10; |
||||
series->sock_ctx->count_sent = series->count_sent; |
||||
series->sock_ctx->count_ok = series->count_ok; |
||||
series->req->completed_count++; |
||||
if (series->req->completed_count >= series->req->socket_count) { |
||||
route_ping_finish(series->req); |
||||
} |
||||
u_free(series); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void route_ping_finish(struct route_ping_req* req) { |
||||
if (!req) return; |
||||
uint32_t best_metric = 0xFFFFFFFF; |
||||
uint16_t best_avg_rtt = 0; |
||||
uint8_t best_count_sent = 0; |
||||
uint8_t best_count_ok = 0; |
||||
struct ETCP_SOCKET* best_sock = NULL; |
||||
for (uint8_t i = 0; i < req->socket_count; i++) { |
||||
struct route_ping_sock_ctx* sc = &req->sockets[i]; |
||||
uint8_t loss = (sc->count_sent > 0) ? ((sc->count_sent - sc->count_ok) * 100 / sc->count_sent) : 0; |
||||
uint32_t metric = (uint32_t)sc->avg_rtt * (loss * 3 + 1); |
||||
if (metric < best_metric) { |
||||
best_metric = metric; |
||||
best_avg_rtt = sc->avg_rtt; |
||||
best_count_sent = sc->count_sent; |
||||
best_count_ok = sc->count_ok; |
||||
best_sock = sc->local_sock; |
||||
} |
||||
} |
||||
if (req->target_node) { |
||||
req->target_node->last_ping_time = get_time_tb(); |
||||
req->target_node->last_rtt = best_avg_rtt; |
||||
req->target_node->best_socket = best_sock; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_ping_finish: node=%016llx best_rtt=%u best_sock=%p metric=%u", |
||||
(unsigned long long)req->target_node->node.node_id, (unsigned)best_avg_rtt, |
||||
(void*)best_sock, (unsigned)best_metric); |
||||
} |
||||
route_ping_send_resp(req, best_avg_rtt); |
||||
u_free(req); |
||||
} |
||||
|
||||
static void route_ping_send_resp(struct route_ping_req* req, uint16_t avg_rtt) { |
||||
if (!req || !req->reply_conn) return; |
||||
struct BGP_PING_RESPONSE* resp = u_calloc(1, sizeof(struct BGP_PING_RESPONSE)); |
||||
if (!resp) return; |
||||
resp->cmd = ETCP_ID_ROUTE_ENTRY; |
||||
resp->subcmd = ROUTE_SUBCMD_PING_RESP; |
||||
resp->request_id = req->request_id; |
||||
// суммируем по всем сокетам для отчёта
|
||||
uint8_t total_sent = 0, total_ok = 0; |
||||
for (uint8_t i = 0; i < req->socket_count; i++) { |
||||
total_sent += req->sockets[i].count_sent; |
||||
total_ok += req->sockets[i].count_ok; |
||||
} |
||||
resp->count_sent = total_sent; |
||||
resp->count_ok = total_ok; |
||||
resp->avg_rtt = avg_rtt; |
||||
struct ll_entry* e = queue_entry_new(0); |
||||
if (!e) { |
||||
u_free(resp); |
||||
return; |
||||
} |
||||
e->dgram = (uint8_t*)resp; |
||||
e->len = sizeof(struct BGP_PING_RESPONSE); |
||||
etcp_send(req->reply_conn, e); |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_ping_send_resp: request_id=%016llx sent=%u ok=%u avg_rtt=%u", |
||||
(unsigned long long)req->request_id, (unsigned)total_sent, (unsigned)total_ok, (unsigned)avg_rtt); |
||||
} |
||||
|
||||
int route_ping_send_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn, uint64_t node_id, |
||||
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, |
||||
uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg) { |
||||
if (!bgp || !to_conn || count == 0 || timeout_ms == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req: invalid args"); |
||||
return -1; |
||||
} |
||||
struct BGP_PING_REQUEST* req_pkt = u_calloc(1, sizeof(struct BGP_PING_REQUEST)); |
||||
if (!req_pkt) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req: 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->node_id = node_id; |
||||
req_pkt->count = count; |
||||
req_pkt->interval_ms = interval_ms; |
||||
req_pkt->timeout_ms = timeout_ms; |
||||
|
||||
struct ll_entry* e = queue_entry_new(0); |
||||
if (!e) { |
||||
u_free(req_pkt); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req: queue_entry_new failed"); |
||||
return -3; |
||||
} |
||||
e->dgram = (uint8_t*)req_pkt; |
||||
e->len = sizeof(struct BGP_PING_REQUEST); |
||||
|
||||
int ret = etcp_send(to_conn, e); |
||||
if (ret != 0) { |
||||
u_free(req_pkt); |
||||
queue_entry_free(e); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req: 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, "route_ping_send_req: pending alloc failed"); |
||||
return -5; |
||||
} |
||||
pending->bgp = bgp; |
||||
pending->request_id = req_pkt->request_id; |
||||
pending->callback = cb; |
||||
pending->arg = arg; |
||||
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); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_ping_send_req: request_id=%016llx node=%016llx count=%u interval=%u timeout=%u wait=%u", |
||||
(unsigned long long)pending->request_id, (unsigned long long)node_id, |
||||
(unsigned)count, (unsigned)interval_ms, (unsigned)timeout_ms, (unsigned)wait_timeout_ms); |
||||
return 0; |
||||
} |
||||
|
||||
void route_ping_destroy_pending(struct ROUTE_BGP* bgp) { |
||||
if (!bgp) return; |
||||
while (bgp->ping_pending) { |
||||
struct route_ping_pending* p = bgp->ping_pending; |
||||
bgp->ping_pending = p->next; |
||||
if (p->timeout_timer) { |
||||
uasync_cancel_timeout(bgp->instance->ua, p->timeout_timer); |
||||
p->timeout_timer = NULL; |
||||
} |
||||
u_free(p); |
||||
} |
||||
} |
||||
|
||||
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 < sizeof(struct BGP_PING_REQUEST)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_handle_req: invalid args"); |
||||
return; |
||||
} |
||||
const struct BGP_PING_REQUEST* req_pkt = (const struct BGP_PING_REQUEST*)data; |
||||
struct NODEINFO_Q* target = route_bgp_get_node(bgp, req_pkt->node_id); |
||||
if (!target) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_ping_handle_req: node %016llx not found", |
||||
(unsigned long long)req_pkt->node_id); |
||||
// отправляем ответ с нулями
|
||||
struct route_ping_req* req = u_calloc(1, sizeof(struct route_ping_req)); |
||||
if (req) { |
||||
req->reply_conn = from_conn; |
||||
req->request_id = req_pkt->request_id; |
||||
req->socket_count = 0; |
||||
route_ping_send_resp(req, 0); |
||||
u_free(req); |
||||
} |
||||
return; |
||||
} |
||||
const struct NODEINFO_IPV4_SOCKET* sockets; |
||||
int sock_count = get_node_v4_sockets(target, &sockets); |
||||
if (sock_count <= 0 || !sockets) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_ping_handle_req: no IPv4 sockets for node %016llx", |
||||
(unsigned long long)req_pkt->node_id); |
||||
struct route_ping_req* req = u_calloc(1, sizeof(struct route_ping_req)); |
||||
if (req) { |
||||
req->reply_conn = from_conn; |
||||
req->request_id = req_pkt->request_id; |
||||
req->socket_count = 0; |
||||
route_ping_send_resp(req, 0); |
||||
u_free(req); |
||||
} |
||||
return; |
||||
} |
||||
// считаем подходящие локальные сокеты AF_INET
|
||||
uint8_t local_count = 0; |
||||
struct ETCP_SOCKET* ls = bgp->instance->etcp_sockets; |
||||
while (ls) { |
||||
if (ls->local_addr.ss_family == AF_INET) local_count++; |
||||
ls = ls->next; |
||||
} |
||||
if (local_count == 0) { |
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "route_ping_handle_req: no local IPv4 sockets"); |
||||
struct route_ping_req* req = u_calloc(1, sizeof(struct route_ping_req)); |
||||
if (req) { |
||||
req->reply_conn = from_conn; |
||||
req->request_id = req_pkt->request_id; |
||||
req->socket_count = 0; |
||||
route_ping_send_resp(req, 0); |
||||
u_free(req); |
||||
} |
||||
return; |
||||
} |
||||
struct route_ping_req* req = u_calloc(1, sizeof(struct route_ping_req) + local_count * sizeof(struct route_ping_sock_ctx)); |
||||
if (!req) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_handle_req: alloc failed"); |
||||
return; |
||||
} |
||||
req->reply_conn = from_conn; |
||||
req->request_id = req_pkt->request_id; |
||||
req->target_node = target; |
||||
req->timeout_ms = req_pkt->timeout_ms; |
||||
req->socket_count = local_count; |
||||
// заполняем target_addr из первого сокета узла (можно расширить на все)
|
||||
struct sockaddr_storage target_addr; |
||||
memset(&target_addr, 0, sizeof(target_addr)); |
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&target_addr; |
||||
sin->sin_family = AF_INET; |
||||
memcpy(&sin->sin_addr, sockets[0].addr, 4); |
||||
sin->sin_port = htons(sockets[0].port); |
||||
ls = bgp->instance->etcp_sockets; |
||||
uint8_t idx = 0; |
||||
while (ls) { |
||||
if (ls->local_addr.ss_family == AF_INET) { |
||||
req->sockets[idx].local_sock = ls; |
||||
struct route_ping_series* series = u_calloc(1, sizeof(struct route_ping_series)); |
||||
if (series) { |
||||
series->req = req; |
||||
series->sock_ctx = &req->sockets[idx]; |
||||
series->local_sock = ls; |
||||
series->target_addr = target_addr; |
||||
memcpy(series->pubkey, target->node.public_key, SC_PUBKEY_SIZE); |
||||
series->count_total = req_pkt->count; |
||||
series->count_sent = 0; |
||||
series->count_ok = 0; |
||||
series->rtt_sum = 0; |
||||
series->timeout_ms = req_pkt->timeout_ms; |
||||
series->interval_ms = req_pkt->interval_ms; |
||||
int ret = etcp_send_ping_to_socket(bgp->instance, ls, target->node.public_key, |
||||
&target_addr, req_pkt->timeout_ms, route_ping_cb, series, NULL, 0); |
||||
if (ret != 0) { |
||||
// сразу считаем fail
|
||||
series->sock_ctx->avg_rtt = req_pkt->timeout_ms * 10; |
||||
series->sock_ctx->count_sent = req_pkt->count; |
||||
req->completed_count++; |
||||
u_free(series); |
||||
} |
||||
} else { |
||||
req->sockets[idx].avg_rtt = req_pkt->timeout_ms * 10; |
||||
req->sockets[idx].count_sent = req_pkt->count; |
||||
req->completed_count++; |
||||
} |
||||
idx++; |
||||
} |
||||
ls = ls->next; |
||||
} |
||||
if (req->completed_count >= req->socket_count) { |
||||
route_ping_finish(req); |
||||
} |
||||
} |
||||
|
||||
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, "route_ping_handle_resp: invalid args"); |
||||
return; |
||||
} |
||||
const struct BGP_PING_RESPONSE* resp = (const struct BGP_PING_RESPONSE*)data; |
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_ping_handle_resp: 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, "route_ping_handle_resp: request_id=%016llx not found in pending list", |
||||
(unsigned long long)resp->request_id); |
||||
} |
||||
@ -0,0 +1,45 @@
|
||||
#ifndef ROUTE_PING_H |
||||
#define ROUTE_PING_H |
||||
|
||||
#include <stdint.h> |
||||
#include <stddef.h> |
||||
#include "route_bgp.h" |
||||
|
||||
#define ROUTE_SUBCMD_PING_REQ 0x07 |
||||
#define ROUTE_SUBCMD_PING_RESP 0x08 |
||||
|
||||
struct BGP_PING_REQUEST { |
||||
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
|
||||
uint8_t subcmd; // ROUTE_SUBCMD_PING_REQ
|
||||
uint64_t request_id; // для корреляции
|
||||
uint64_t node_id; // целевой узел (должен быть известен в nodes)
|
||||
uint8_t count; // число пингов
|
||||
uint16_t interval_ms; // интервал между пингами
|
||||
uint16_t timeout_ms; // таймаут одного пинга
|
||||
} __attribute__((packed)); |
||||
|
||||
struct BGP_PING_RESPONSE { |
||||
uint8_t cmd; |
||||
uint8_t subcmd; // ROUTE_SUBCMD_PING_RESP
|
||||
uint64_t request_id; |
||||
uint8_t count_sent; |
||||
uint8_t count_ok; |
||||
uint16_t avg_rtt; // средний RTT в 0.1ms
|
||||
uint8_t reserved[4]; |
||||
} __attribute__((packed)); |
||||
|
||||
typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg); |
||||
|
||||
// Отправить запрос пинга через BGP, ожидать ответа с таймаутом
|
||||
int route_ping_send_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* to_conn, uint64_t node_id, |
||||
uint8_t count, uint16_t interval_ms, uint16_t timeout_ms, |
||||
uint16_t wait_timeout_ms, route_ping_callback_t cb, void* arg); |
||||
|
||||
// Очистить список ожидающих запросов (при уничтожении BGP)
|
||||
void route_ping_destroy_pending(struct ROUTE_BGP* bgp); |
||||
|
||||
// Обработчики, вызываемые из route_bgp_receive_cbk
|
||||
void route_ping_handle_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); |
||||
void route_ping_handle_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from_conn, const uint8_t* data, size_t len); |
||||
|
||||
#endif // ROUTE_PING_H
|
||||
Loading…
Reference in new issue