Fix stale socket references in connectivity probes
This commit is contained in:
1 parent
8b0002e489
commit
e035f53ab6
4 files changed
+322
-47
No files matched your search
@@ -42,17 +42,17 @@ struct conn_probe_ctx {
|
||||
struct sockaddr_storage target_addr;
|
||||
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
|
||||
|
||||
struct ETCP_SOCKET* candidate_sockets[CONN_MAX_SOCKET_CANDIDATES];
|
||||
/* Снимок идентичностей: сокеты могут исчезнуть между асинхронными пингами. */
|
||||
struct { uint8_t id; uint64_t generation; } candidates[CONN_MAX_SOCKET_CANDIDATES];
|
||||
uint8_t candidate_count;
|
||||
uint8_t candidate_index;
|
||||
uint8_t invalidated; // часть кандидатов потеряна: отрицательный результат недостоверен
|
||||
|
||||
uint16_t best_across_sockets; // min RTT по всем сокетам
|
||||
uint8_t count_total; // 3 на серию (1 для TCP)
|
||||
uint8_t count_sent;
|
||||
uint8_t count_ok;
|
||||
uint16_t min_rtt; // min RTT в текущей серии
|
||||
uint16_t timeout_ms;
|
||||
void* ping_timer;
|
||||
uint8_t reality_set;
|
||||
struct reality_client_config reality;
|
||||
struct stcp_client* tcp_cli; // хендл влётного TCP-пинга (для отмены), NULL для UDP
|
||||
@@ -61,7 +61,8 @@ struct conn_probe_ctx {
|
||||
// ---- forward ----
|
||||
static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
|
||||
uint64_t nonce, const uint8_t* resp_data, size_t resp_data_len);
|
||||
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok);
|
||||
enum conn_probe_result { CONN_PROBE_FAILED, CONN_PROBE_REACHABLE, CONN_PROBE_INVALIDATED };
|
||||
static void conn_probe_finish(struct conn_probe_ctx* ctx, enum conn_probe_result result);
|
||||
static void conn_probe_start_series(struct conn_probe_ctx* ctx);
|
||||
|
||||
// ---- helpers ----
|
||||
@@ -147,36 +148,58 @@ static int conn_match_candidate_sockets(struct UTUN_INSTANCE* instance,
|
||||
|
||||
// ---- probe lifecycle ----
|
||||
|
||||
/* Заимствуем только живой UDP endpoint точного поколения, только на время текущего вызова uasync. */
|
||||
static struct ETCP_SOCKET* conn_probe_socket(const struct conn_probe_ctx* ctx) {
|
||||
if (!ctx->candidates[ctx->candidate_index].generation) return NULL;
|
||||
for (struct ETCP_SOCKET* sock = ctx->instance->etcp_sockets; sock; sock = sock->next) {
|
||||
if (sock->sock_id != ctx->candidates[ctx->candidate_index].id ||
|
||||
sock->generation != ctx->candidates[ctx->candidate_index].generation) continue;
|
||||
return !sock->is_tcp && !sock->probe_only && sock->fd != SOCKET_INVALID &&
|
||||
sock->local_addr.ss_family == ctx->target_addr.ss_family ? sock : NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Потеря локального endpoint не доказывает недоступность пира; старые RTT этой серии отбрасываем. */
|
||||
static void conn_probe_invalidate(struct conn_probe_ctx* ctx) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_BGP, "probe candidate invalidated group=%016llx node=%016llx target=%s "
|
||||
"socket=%u generation=%016llx candidate=%u/%u completed=%u/%u successful=%u",
|
||||
(unsigned long long)ctx->group->group_id, (unsigned long long)ctx->nq->node_id,
|
||||
sockaddr_storage_to_str(&ctx->target_addr).str, ctx->candidates[ctx->candidate_index].id,
|
||||
(unsigned long long)ctx->candidates[ctx->candidate_index].generation,
|
||||
ctx->candidate_index + 1, ctx->candidate_count, ctx->count_sent, ctx->count_total, ctx->count_ok);
|
||||
ctx->invalidated = 1;
|
||||
}
|
||||
|
||||
/* 0 — пинг принят, 1 — кандидат устарел, отрицательное значение — ошибка отправки. */
|
||||
static int conn_probe_send(struct conn_probe_ctx* ctx) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe send group=%016llx node=%016llx target=%s tcp=%u candidate=%u/%u attempt=%u/%u",
|
||||
(unsigned long long)ctx->group->group_id, (unsigned long long)ctx->nq->node_id,
|
||||
sockaddr_storage_to_str(&ctx->target_addr).str, ctx->is_tcp,
|
||||
ctx->candidate_index + 1, ctx->candidate_count, ctx->count_sent + 1, ctx->count_total);
|
||||
if (ctx->is_tcp) {
|
||||
return etcp_send_tcp_ping(ctx->instance, ctx->peer_pubkey, &ctx->target_addr,
|
||||
ctx->timeout_ms, conn_probe_single_cb, ctx, &ctx->tcp_cli, ctx->reality_set ? &ctx->reality : NULL);
|
||||
}
|
||||
struct ETCP_SOCKET* sock = ctx->candidate_sockets[ctx->candidate_index];
|
||||
struct ETCP_SOCKET* sock = conn_probe_socket(ctx);
|
||||
if (!sock) return 1;
|
||||
return etcp_send_ping_to_socket(ctx->instance, sock, ctx->peer_pubkey,
|
||||
&ctx->target_addr, ctx->timeout_ms,
|
||||
conn_probe_single_cb, ctx, NULL, 0, 0);
|
||||
}
|
||||
|
||||
static void conn_probe_start_series(struct conn_probe_ctx* ctx) {
|
||||
if (ctx->candidate_index >= ctx->candidate_count) {
|
||||
// все сокеты перебраны
|
||||
if (ctx->best_across_sockets != 65535) conn_probe_finish(ctx, 1);
|
||||
else conn_probe_finish(ctx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->count_sent = 0;
|
||||
ctx->count_ok = 0;
|
||||
ctx->min_rtt = 65535;
|
||||
|
||||
int ret = conn_probe_send(ctx);
|
||||
if (ret != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe: cannot start ping (tcp=%d)", ctx->is_tcp);
|
||||
ctx->count_sent = ctx->count_total; // simulate full failure
|
||||
while (ctx->candidate_index < ctx->candidate_count) {
|
||||
ctx->count_sent = 0; ctx->count_ok = 0; ctx->min_rtt = UINT16_MAX;
|
||||
int ret = conn_probe_send(ctx);
|
||||
if (!ret) return;
|
||||
if (ret == 1) conn_probe_invalidate(ctx);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe cannot start target=%s tcp=%u candidate=%u/%u error=%d",
|
||||
sockaddr_storage_to_str(&ctx->target_addr).str, ctx->is_tcp,
|
||||
ctx->candidate_index + 1, ctx->candidate_count, ret);
|
||||
ctx->candidate_index++;
|
||||
conn_probe_start_series(ctx);
|
||||
}
|
||||
conn_probe_finish(ctx, ctx->invalidated ? CONN_PROBE_INVALIDATED : CONN_PROBE_FAILED);
|
||||
}
|
||||
|
||||
static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
|
||||
@@ -184,20 +207,30 @@ static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
|
||||
(void)nonce; (void)resp_data; (void)resp_data_len;
|
||||
struct conn_probe_ctx* ctx = (struct conn_probe_ctx*)arg;
|
||||
if (!ctx) return;
|
||||
if (!ctx->nq) { conn_probe_finish(ctx, CONN_PROBE_FAILED); return; }
|
||||
|
||||
if (!ctx->is_tcp && !conn_probe_socket(ctx)) {
|
||||
conn_probe_invalidate(ctx);
|
||||
ctx->candidate_index++;
|
||||
conn_probe_start_series(ctx);
|
||||
return;
|
||||
}
|
||||
ctx->count_sent++;
|
||||
if (success) {
|
||||
ctx->count_ok++;
|
||||
if (rtt < ctx->min_rtt) ctx->min_rtt = rtt;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe ping: ok=%d rtt=%u sent=%d/%d tcp=%d",
|
||||
success, rtt, ctx->count_sent, ctx->count_total, ctx->is_tcp);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe result group=%016llx node=%016llx target=%s ok=%d rtt=%u completed=%u/%u tcp=%u",
|
||||
(unsigned long long)ctx->group->group_id, (unsigned long long)ctx->nq->node_id,
|
||||
sockaddr_storage_to_str(&ctx->target_addr).str, success, rtt, ctx->count_sent, ctx->count_total, ctx->is_tcp);
|
||||
|
||||
if (ctx->count_sent < ctx->count_total) {
|
||||
int ret = conn_probe_send(ctx);
|
||||
if (ret != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe: cannot continue ping (tcp=%d)", ctx->is_tcp);
|
||||
if (ret == 1) conn_probe_invalidate(ctx);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe cannot continue target=%s tcp=%u error=%d",
|
||||
sockaddr_storage_to_str(&ctx->target_addr).str, ctx->is_tcp, ret);
|
||||
ctx->count_sent = ctx->count_total; // force finish series
|
||||
ctx->count_ok = 0;
|
||||
} else return; // следующий пинг отправлен, ждём callback
|
||||
@@ -205,8 +238,7 @@ static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
|
||||
|
||||
// серия пингов завершена
|
||||
if (ctx->count_ok > 0) {
|
||||
if (ctx->min_rtt < ctx->best_across_sockets) ctx->best_across_sockets = ctx->min_rtt;
|
||||
conn_probe_finish(ctx, 1);
|
||||
conn_probe_finish(ctx, CONN_PROBE_REACHABLE);
|
||||
} else {
|
||||
// этот сокет не подошёл — пробуем следующий
|
||||
ctx->candidate_index++;
|
||||
@@ -214,42 +246,55 @@ static void conn_probe_single_cb(int success, uint16_t rtt, void* arg,
|
||||
}
|
||||
}
|
||||
|
||||
static void conn_probe_finish(struct conn_probe_ctx* ctx, int ok) {
|
||||
static void conn_probe_finish(struct conn_probe_ctx* ctx, enum conn_probe_result result) {
|
||||
if (!ctx) return;
|
||||
struct UTUN_INSTANCE* instance = ctx->instance;
|
||||
struct TOPO_GROUP* group = ctx->group;
|
||||
struct TOPO_GROUP_NODE* nq = ctx->nq;
|
||||
int done = 0;
|
||||
if (ctx->nq) {
|
||||
struct TOPO_CONNECTIVITY* c = &ctx->nq->connectivity;
|
||||
struct conn_probe_ctx** p = (struct conn_probe_ctx**)&c->probe_list;
|
||||
while (*p) { if (*p == ctx) { *p = ctx->next; break; } p = &(*p)->next; }
|
||||
|
||||
switch (ctx->addr_type) {
|
||||
int ok = result == CONN_PROBE_REACHABLE;
|
||||
if (result != CONN_PROBE_INVALIDATED) switch (ctx->addr_type) {
|
||||
case TOPO_ADDR_INTERFACE:
|
||||
c->interface_status = ok ? PROBE_RESULT_REACHABLE : (c->interface_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->interface_status);
|
||||
if (ok && ctx->best_across_sockets < c->interface_min_rtt) c->interface_min_rtt = ctx->best_across_sockets;
|
||||
if (ok && ctx->min_rtt < c->interface_min_rtt) c->interface_min_rtt = ctx->min_rtt;
|
||||
c->interface_probe_time = get_time_tb();
|
||||
c->real_status = ok ? PROBE_RESULT_REACHABLE : (c->real_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->real_status);
|
||||
if (ok && ctx->best_across_sockets < c->real_min_rtt) c->real_min_rtt = ctx->best_across_sockets;
|
||||
if (ok && ctx->min_rtt < c->real_min_rtt) c->real_min_rtt = ctx->min_rtt;
|
||||
c->real_probe_time = get_time_tb();
|
||||
break;
|
||||
case TOPO_ADDR_NAT:
|
||||
c->nat_status = ok ? PROBE_RESULT_REACHABLE : (c->nat_status == PROBE_RESULT_UNKNOWN ? PROBE_RESULT_UNREACHABLE : c->nat_status);
|
||||
if (ok && ctx->best_across_sockets < c->nat_min_rtt) c->nat_min_rtt = ctx->best_across_sockets;
|
||||
if (ok && ctx->min_rtt < c->nat_min_rtt) c->nat_min_rtt = ctx->min_rtt;
|
||||
c->nat_probe_time = get_time_tb();
|
||||
break;
|
||||
}
|
||||
|
||||
if (c->pending_count > 0) c->pending_count--;
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe pending underflow group=%016llx node=%016llx",
|
||||
(unsigned long long)group->group_id, (unsigned long long)nq->node_id);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "probe address complete group=%016llx node=%016llx target=%s result=%s remaining=%u",
|
||||
(unsigned long long)group->group_id, (unsigned long long)nq->node_id,
|
||||
sockaddr_storage_to_str(&ctx->target_addr).str,
|
||||
ok ? "reachable" : result == CONN_PROBE_INVALIDATED ? "invalidated" : "unreachable", c->pending_count);
|
||||
if (c->pending_count == 0) {
|
||||
c->probe_status = PROBE_STATUS_DONE;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe DONE for node %016llx: intf=%d nat=%d real=%d",
|
||||
(unsigned long long)ctx->nq->node_id,
|
||||
done = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe DONE group=%016llx node=%016llx: intf=%d nat=%d real=%d",
|
||||
(unsigned long long)group->group_id, (unsigned long long)nq->node_id,
|
||||
c->interface_status, c->nat_status, c->real_status);
|
||||
if (ctx->instance && ctx->instance->topo_sqlite_db)
|
||||
topo_node_sqlite_nodeinfo_updated(ctx->instance->topo_sqlite_db, ctx->nq->node_id);
|
||||
topo_fire_nodeinfo_cbk(ctx->instance, ctx->group, ctx->nq);
|
||||
}
|
||||
}
|
||||
|
||||
u_free(ctx);
|
||||
if (done) {
|
||||
if (instance->topo_sqlite_db) topo_node_sqlite_nodeinfo_updated(instance->topo_sqlite_db, nq->node_id);
|
||||
topo_fire_nodeinfo_cbk(instance, group, nq);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- public API ----
|
||||
@@ -292,7 +337,7 @@ static void route_connectivity_probe_deferred_cb(void* arg) {
|
||||
#endif
|
||||
|
||||
void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq) {
|
||||
if (!instance || !nq) return;
|
||||
if (!instance || !group || !nq) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe: missing instance/group/node"); return; }
|
||||
struct TOPO_NODE* ni = topo_node_registry_find(instance->topo_groups, nq->node_id);
|
||||
if (!ni) return;
|
||||
if (nq->node_id == instance->node_id) return; // не пингуем себя
|
||||
@@ -362,10 +407,11 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
|
||||
struct sockaddr_storage target; memset(&target, 0, sizeof(target));
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)⌖ sin->sin_family = AF_INET; sin->sin_addr.s_addr = ip; sin->sin_port = htons(port);
|
||||
|
||||
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx)); if (!ctx) continue;
|
||||
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx));
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe IPv4 context allocation failed"); continue; }
|
||||
ctx->instance = instance; ctx->group = group; ctx->nq = nq; ctx->addr_type = a->type; ctx->target_addr = target;
|
||||
memcpy(ctx->peer_pubkey, ni->public_key, SC_PUBKEY_SIZE);
|
||||
ctx->timeout_ms = g_probe_timeout_ms; ctx->best_across_sockets = 65535;
|
||||
ctx->timeout_ms = g_probe_timeout_ms;
|
||||
|
||||
if (a->protocol & TOPO_PROTO_TCP) {
|
||||
const struct TOPO_REALITY_SOCK *rs = topo_node_find_reality_sock(ni, a->socket_id);
|
||||
@@ -386,11 +432,13 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
|
||||
struct ETCP_SOCKET* candidates[CONN_MAX_SOCKET_CANDIDATES];
|
||||
int cand_count = conn_match_candidate_sockets(instance, a->type, tcfg, profile, &target, candidates, CONN_MAX_SOCKET_CANDIDATES);
|
||||
if (cand_count == 0) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for target %s:%u type=%d", ip_to_str(&ip, AF_INET).str, port, a->type); u_free(ctx); continue; }
|
||||
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
|
||||
for (int i = 0; i < cand_count; i++) {
|
||||
ctx->candidates[i].id = candidates[i]->sock_id;
|
||||
ctx->candidates[i].generation = candidates[i]->generation;
|
||||
}
|
||||
ctx->candidate_count = cand_count; ctx->candidate_index = 0; ctx->count_total = CONN_PROBE_COUNT;
|
||||
}
|
||||
ctx->next = (struct conn_probe_ctx*)nq->connectivity.probe_list; nq->connectivity.probe_list = ctx; pend++;
|
||||
conn_probe_start_series(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,10 +457,11 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)⌖ sin6->sin6_family = AF_INET6;
|
||||
memcpy(&sin6->sin6_addr, a6->addr, 16); sin6->sin6_port = htons(a6->port);
|
||||
|
||||
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx)); if (!ctx) continue;
|
||||
ctx->instance = instance; ctx->nq = nq; ctx->addr_type = a6->type; ctx->target_addr = target;
|
||||
struct conn_probe_ctx* ctx = u_calloc(1, sizeof(struct conn_probe_ctx));
|
||||
if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "probe IPv6 context allocation failed"); continue; }
|
||||
ctx->instance = instance; ctx->group = group; ctx->nq = nq; ctx->addr_type = a6->type; ctx->target_addr = target;
|
||||
memcpy(ctx->peer_pubkey, ni->public_key, SC_PUBKEY_SIZE);
|
||||
ctx->timeout_ms = g_probe_timeout_ms; ctx->best_across_sockets = 65535;
|
||||
ctx->timeout_ms = g_probe_timeout_ms;
|
||||
|
||||
if (a6->protocol & TOPO_PROTO_TCP) {
|
||||
const struct TOPO_REALITY_SOCK *rs = topo_node_find_reality_sock(ni, a6->socket_id);
|
||||
@@ -430,11 +479,13 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
|
||||
int cand_count = conn_match_candidate_sockets(instance, a6->type, CFG_SERVER_TYPE_UNKNOWN, NULL,
|
||||
&target, candidates, CONN_MAX_SOCKET_CANDIDATES);
|
||||
if (cand_count == 0) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "probe: no local sockets for IPv6 target type=%d", a6->type); u_free(ctx); continue; }
|
||||
memcpy(ctx->candidate_sockets, candidates, cand_count * sizeof(struct ETCP_SOCKET*));
|
||||
for (int i = 0; i < cand_count; i++) {
|
||||
ctx->candidates[i].id = candidates[i]->sock_id;
|
||||
ctx->candidates[i].generation = candidates[i]->generation;
|
||||
}
|
||||
ctx->candidate_count = cand_count; ctx->candidate_index = 0; ctx->count_total = CONN_PROBE_COUNT;
|
||||
}
|
||||
ctx->next = (struct conn_probe_ctx*)nq->connectivity.probe_list; nq->connectivity.probe_list = ctx; pend++;
|
||||
conn_probe_start_series(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,6 +498,13 @@ void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_G
|
||||
nq->connectivity.pending_count = pend;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "connectivity probe started for node %016llx: %d series pending",
|
||||
(unsigned long long)ni->node_id, pend);
|
||||
/* Все контексты учтены до отправки: отказ может синхронно завершить и освободить текущий. */
|
||||
struct conn_probe_ctx* ctx = nq->connectivity.probe_list;
|
||||
while (ctx) {
|
||||
struct conn_probe_ctx* next = ctx->next;
|
||||
conn_probe_start_series(ctx);
|
||||
ctx = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ struct TOPO_GROUP;
|
||||
* UDP: до 8 сокетов, серия CONN_PROBE_COUNT с каждого до первого успешного сокета;
|
||||
* TCP: один STCP ping с REALITY-параметрами при наличии. RTT — 0.1 мс, UINT16_MAX неизвестен.
|
||||
* До 16 уникальных IP:port на семейство. Собственный/спящий узел и текущие пробы пропускаются;
|
||||
* NAT/standby могут отложить старт. Результат — в nq->connectivity, завершение уведомляет topo callback. */
|
||||
* NAT/standby могут отложить старт. UDP-кандидат хранится как ID+поколение: исчезновение endpoint
|
||||
* отменяет его серию без отрицательного результата; новые сокеты попадут в следующий запуск.
|
||||
* Результат — в nq->connectivity, завершение уведомляет topo callback (может удалить nq). */
|
||||
void route_connectivity_probe_node(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* group,
|
||||
struct TOPO_GROUP_NODE* nq);
|
||||
|
||||
|
||||
Reference in new issue
Block a user