Fix ETCP session recovery and router lifecycle; confirm chat invites before publishing

This commit is contained in:
evgeny committed 2026-09-27 02:51:47 +03:00
1 parent 018c8e795d
commit 3131dcbb7c
67 files changed
+3005 -1249

No files matched your search

+4
View File
@@ -30,6 +30,8 @@ utun_CORE_SOURCES = \
tun_freebsd.c \
tun_windows.c \
transport_layer/etcp.c \
transport_layer/etcp_session.c \
transport_layer/etcp_session.h \
transport_layer/etcp_connections.c \
transport_layer/etcp_keepalive.c \
transport_layer/etcp_bbr.c \
@@ -129,6 +131,8 @@ libutun_a_SOURCES = \
tun_freebsd.c \
tun_windows.c \
transport_layer/etcp.c \
transport_layer/etcp_session.c \
transport_layer/etcp_session.h \
transport_layer/etcp_connections.c \
transport_layer/etcp_keepalive.c \
transport_layer/etcp_bbr.c \
+1 -1
View File
@@ -49,7 +49,7 @@ extern "C" {
#define CHAT_EVT_BGP_INFO 26 /* текст: "rtt:XXms hop:X via bgp: [XXXX] name1 [XXXX] name2" */
#define CHAT_EVT_CONNECTING_NODES 27 /* [ch_id_len:1][ch_id:var][count:2][node_id:8]* */
#define CHAT_EVT_ADMIN_KEY_RECEIVED 28 /* [ch_id_len:1][ch_id:var][from_node_id:8] — получен канальный ключ (стал совладельцем) */
#define CHAT_EVT_INVITE_LINK_READY 29 /* [ch_id_len:1][ch_id:var][link_len:2][link:var] — пустой link = ошибка */
#define CHAT_EVT_INVITE_LINK_READY 29 /* [ch_id_len:1][ch_id:var][request_id:8][link_len:2][link:var] — пустой link = ошибка */
#define CHAT_EVT_INVITE_CANDIDATES 30 /* [ch_id_len:1][ch_id:var][auto_node_id:8][count:2][node_id:8]* — достижимые узлы для invite */
#define CHAT_EVT_CHANNEL_DELETED 31 /* [ch_id_len:1][ch_id:var] — канал удалён локально */
#define CHAT_EVT_DM_MSG_RECEIVED 32 /* [conv_id_len:1][conv_id:var][author_node_id:8] */
+64 -26
View File
@@ -64,10 +64,18 @@ struct headless_client {
int send_offset;
int subscribed;
int closing;
struct headless_invite* invites;
struct headless_control* hc;
struct headless_client* next;
};
struct headless_invite {
struct headless_invite* next;
struct headless_client* client;
struct chat_invite_build* pending;
int id;
};
struct headless_control {
struct UASYNC* ua;
struct UTUN_INSTANCE* inst;
@@ -76,6 +84,7 @@ struct headless_control {
socket_t listen_fd;
void* listen_sock_id;
int running;
void* cleanup_timer;
};
static struct headless_control* hc_of(struct UTUN_INSTANCE* inst) {
@@ -258,7 +267,9 @@ static void hc_close_client(struct headless_client* cli) {
struct headless_control* hc = cli ? cli->hc : NULL;
if (!cli || cli->closing) return;
cli->closing = 1;
while (cli->invites) chat_invite_build_cancel(cli->invites->pending);
if (hc && hc->ua && cli->socket_id) uasync_remove_socket_t(hc->ua, cli->fd);
cli->socket_id = NULL;
if (cli->fd != SOCKET_INVALID) { socket_close_wrapper(cli->fd); cli->fd = SOCKET_INVALID; }
}
@@ -273,6 +284,7 @@ static void client_read_callback(socket_t fd, void* arg) {
for (ssize_t i = 0; i < r; i++) {
if (tmp[i] == '\n') {
if (cli->recv_len > 0) { cli->recv_buf[cli->recv_len] = '\0'; hc_handle_command(cli, cli->recv_buf); cli->recv_len = 0; }
if (cli->closing) return;
} else if (cli->recv_len < RECV_BUF_SIZE - 1) {
cli->recv_buf[cli->recv_len++] = tmp[i];
}
@@ -448,6 +460,34 @@ static void hc_handle_attach(struct headless_client* cli, int id, const char* js
send_response(cli, id, resp, NULL);
}
static void hc_invite_ready(void* arg, int result, const char* link) {
struct headless_invite* req = arg;
struct headless_client* cli = req->client;
struct headless_invite** p = &cli->invites;
while (*p && *p != req) p = &(*p)->next;
if (*p) *p = req->next;
int id = req->id;
u_free(req);
if (cli->closing) return;
if (result != CHAT_JOIN_OK) {
send_response(cli, id, NULL, result == CHAT_JOIN_TIMEOUT ? "invite registration timed out" : "invite registration failed");
return;
}
struct InviteData d; char err[128];
if (invite_link_decode(link, strlen(link), &d, err, sizeof(err)) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: cannot decode confirmed invite: %s", err);
send_response(cli, id, NULL, "failed to decode built link");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_HEADLESS, "headless: invite confirmed ch=%llu node=%016llx addrs=%d",
(unsigned long long)d.channelId, (unsigned long long)d.nodeId, (int)d.addrCount);
char resp[1280];
snprintf(resp, sizeof(resp),
"{\"link\":\"%s\",\"channel_id\":%llu,\"node_id\":\"0x%016llx\",\"addrs\":%d}",
link, (unsigned long long)d.channelId, (unsigned long long)d.nodeId, (int)d.addrCount);
send_response(cli, id, resp, NULL);
}
static void hc_handle_invite(struct headless_client* cli, int id, const char* json) {
struct headless_control* hc = cli->hc;
char ch[64], node_id_str[32];
@@ -462,26 +502,16 @@ static void hc_handle_invite(struct headless_client* cli, int id, const char* js
if (json_get_str(json, "node_id", node_id_str, sizeof(node_id_str)) == 0)
target_node_id = strtoull(node_id_str, NULL, 16);
char link[1024];
if (chat_invite_build_link(hc->inst, channel_id, target_node_id, NULL, link, sizeof(link)) < 0) {
send_response(cli, id, NULL, "failed to build invite link (see log)");
return;
struct headless_invite* req = u_calloc(1, sizeof(*req));
if (!req) {
DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: invite allocation failed");
send_response(cli, id, NULL, "out of memory"); return;
}
struct InviteData d; char err[128];
if (invite_link_decode(link, strlen(link), &d, err, sizeof(err)) < 0) {
send_response(cli, id, NULL, "failed to decode built link");
return;
}
DEBUG_INFO((int)DEBUG_CATEGORY_HEADLESS, "headless: invite ch=%s target=0x%016llx node=0x%016llx addrs=%d",
ch, (unsigned long long)target_node_id, (unsigned long long)d.nodeId, (int)d.addrCount);
char resp[1280];
snprintf(resp, sizeof(resp),
"{\"link\":\"%s\",\"channel_id\":%llu,\"node_id\":\"0x%016llx\",\"addrs\":%d}",
link, (unsigned long long)d.channelId, (unsigned long long)d.nodeId, (int)d.addrCount);
send_response(cli, id, resp, NULL);
req->client = cli; req->id = id;
req->pending = chat_invite_build_link(hc->inst, channel_id, target_node_id, NULL, hc_invite_ready, req);
if (!req->pending) { u_free(req); send_response(cli, id, NULL, "failed to build invite link (see log)"); return; }
req->next = cli->invites;
cli->invites = req;
}
static void hc_handle_invite_nodes(struct headless_client* cli, int id, const char* json) {
@@ -768,7 +798,7 @@ static const char* cmd_names[] = { "ping", "status", "channels", "members", "mes
static hc_cmd_fn cmd_handlers[] = { hc_handle_ping, hc_handle_status, hc_handle_channels, hc_handle_members, hc_handle_messages, hc_handle_send, hc_handle_attach, hc_handle_invite, hc_handle_invite_nodes, hc_handle_connect, hc_handle_create_channel, hc_handle_invite_to, hc_handle_subscribe, hc_handle_dm_start, hc_handle_dm_list, hc_handle_dm_messages, hc_handle_dm_send, hc_handle_call_start, hc_handle_call_accept, hc_handle_call_decline, hc_handle_call_hangup, hc_handle_radio_on, hc_handle_radio_off, hc_handle_radio_subscribers, hc_handle_quit };
static void hc_handle_command(struct headless_client* cli, const char* json) {
if (!cli || !json) return;
if (!cli || cli->closing || !json) return;
int id = json_get_int(json, "id", 0);
const char* cs = json_get_cmd(json);
if (!cs) { send_response(cli, id, NULL, "missing cmd"); return; }
@@ -810,6 +840,7 @@ static void accept_callback(socket_t fd, void* arg) {
static void hc_cleanup(void* arg) {
struct headless_control* hc = (struct headless_control*)arg;
hc->cleanup_timer = NULL;
struct headless_client** prev = &hc->clients;
while (*prev) {
if ((*prev)->closing) {
@@ -819,7 +850,8 @@ static void hc_cleanup(void* arg) {
u_free(dead); hc->client_count--;
} else prev = &(*prev)->next;
}
if (hc->ua) uasync_set_timeout(hc->ua, 5000, hc, hc_cleanup, "hc_cleanup");
hc->cleanup_timer = uasync_set_timeout(hc->ua, 5000, hc, hc_cleanup, "hc_cleanup");
if (!hc->cleanup_timer) DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: cleanup timer allocation failed");
}
/* ── Init / Destroy ── */
@@ -857,7 +889,11 @@ int chat_headless_control_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
if (!hc->listen_sock_id) { socket_close_wrapper(hc->listen_fd); hc->listen_fd = SOCKET_INVALID; return -1; }
chat_event_set_handler(inst, hc_on_chat_event);
uasync_set_timeout(ua, 5000, hc, hc_cleanup, "hc_cleanup");
hc->cleanup_timer = uasync_set_timeout(ua, 5000, hc, hc_cleanup, "hc_cleanup");
if (!hc->cleanup_timer) {
DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: cleanup timer allocation failed");
chat_headless_control_destroy(inst); return -1;
}
hc->running = 1;
DEBUG_INFO((int)DEBUG_CATEGORY_HEADLESS, "headless: listening on %s:%d", bind_ip, port);
@@ -866,13 +902,15 @@ int chat_headless_control_init(struct UASYNC* ua, struct UTUN_INSTANCE* inst,
void chat_headless_control_destroy(struct UTUN_INSTANCE* inst) {
struct headless_control* hc = hc_of(inst);
if (!hc || !hc->running) return;
if (!hc) return;
hc->running = 0;
chat_event_set_handler(inst, NULL);
if (hc->cleanup_timer) uasync_cancel_timeout(hc->ua, hc->cleanup_timer);
if (inst->chat_event_handler == hc_on_chat_event) chat_event_set_handler(inst, NULL);
if (hc->listen_sock_id && hc->ua) uasync_remove_socket_t(hc->ua, hc->listen_fd);
if (hc->listen_fd != SOCKET_INVALID) { socket_close_wrapper(hc->listen_fd); hc->listen_fd = SOCKET_INVALID; }
struct headless_client* c = hc->clients;
while (c) { struct headless_client* n = c->next; if (c->fd != SOCKET_INVALID) socket_close_wrapper(c->fd); u_free(c); c = n; }
hc->clients = NULL; hc->client_count = 0;
while (c) { struct headless_client* n = c->next; hc_close_client(c); u_free(c); c = n; }
inst->headless = NULL;
u_free(hc);
DEBUG_INFO((int)DEBUG_CATEGORY_HEADLESS, "headless: destroyed");
}
+146 -36
View File
@@ -58,10 +58,24 @@ static void _keys_purge_expired(struct UTUN_INSTANCE* inst) {
}
}
static void _keys_put(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key, uint64_t inviter_id) {
static int _keys_put(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key, uint64_t inviter_id) {
_keys_purge_expired(inst);
if (!channel_id || !join_key || !inviter_id) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid key registration", CJ_ID);
return -1;
}
for (struct join_key_entry* e = inst->join_keys; e; e = e->next) {
if (e->channel_id != channel_id || e->join_key != join_key) continue;
if (e->inviter_id != inviter_id) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration conflicts with existing inviter ch=%llu", CJ_ID,
(unsigned long long)channel_id);
return -1;
}
e->expiry_tb = _now_tb() + (uint64_t)JOIN_KEY_TTL_SECONDS * 10000ULL;
return 0;
}
struct join_key_entry* e = u_malloc(sizeof(*e));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key alloc failed", CJ_ID); return; }
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key alloc failed", CJ_ID); return -1; }
e->join_key = join_key; e->channel_id = channel_id; e->inviter_id = inviter_id;
e->expiry_tb = _now_tb() + (uint64_t)JOIN_KEY_TTL_SECONDS * 10000ULL;
e->next = inst->join_keys;
@@ -69,6 +83,7 @@ static void _keys_put(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key stored key=%016llx ch=%llu inviter=%016llx ttl=%ds",
CJ_ID, (unsigned long long)join_key, (unsigned long long)channel_id,
(unsigned long long)inviter_id, JOIN_KEY_TTL_SECONDS);
return 0;
}
static struct join_key_entry* _keys_find(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) {
@@ -208,6 +223,69 @@ void chat_join_process_request(struct UTUN_INSTANCE* inst, uint64_t group_id,
CJ_ID, (unsigned long long)m.node_id, ch_id, (unsigned long long)from_node, (unsigned long long)join_key);
}
/* Ожидание прикладного ACK: надёжная доставка router сама повторяет потерянные пакеты. */
struct chat_join_registration {
struct chat_join_registration* next;
struct UTUN_INSTANCE* inst;
uint64_t channel_id, target_node_id, join_key;
void* timer;
chat_join_registered_fn callback;
void* arg;
};
static int send_key_control(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target,
uint64_t key, uint8_t subcmd, uint8_t status) {
struct ll_entry* entry = ll_alloc_lldgram(subcmd == JOIN_SUBCMD_KEY_REGISTER_ACK ? 11 : 10);
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: key control allocation failed", CJ_ID);
return -1;
}
entry->dgram[0] = ETCP_RT_ID_JOIN;
entry->len = entry->memlen;
entry->dgram[1] = subcmd;
memcpy(entry->dgram + 2, &key, 8);
if (subcmd == JOIN_SUBCMD_KEY_REGISTER_ACK) entry->dgram[10] = status;
int rc = etcp_route_send(inst, channel_id, target, entry, 0, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: key control send failed cmd=%02x ch=%llu peer=%016llx rc=%d",
CJ_ID, subcmd, (unsigned long long)channel_id, (unsigned long long)target, rc);
}
return rc;
}
static void registration_finish(struct chat_join_registration* req, int result) {
struct chat_join_registration** p = &req->inst->join_registrations;
while (*p && *p != req) p = &(*p)->next;
if (*p) *p = req->next;
if (req->timer) uasync_cancel_timeout(req->inst->ua, req->timer);
if (result == CHAT_JOIN_OK) {
if (chat_join_store_local_key(req->inst, req->channel_id, req->join_key) != 0) result = CHAT_JOIN_ERROR;
}
if (result == CHAT_JOIN_OK || result == CHAT_JOIN_CANCELLED) {
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration %s ch=%llu peer=%016llx key=%016llx", CJ_ID,
result == CHAT_JOIN_OK ? "confirmed" : "cancelled", (unsigned long long)req->channel_id,
(unsigned long long)req->target_node_id, (unsigned long long)req->join_key);
} else {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration failed ch=%llu peer=%016llx key=%016llx result=%d", CJ_ID,
(unsigned long long)req->channel_id, (unsigned long long)req->target_node_id,
(unsigned long long)req->join_key, result);
}
chat_join_registered_fn callback = req->callback;
void* arg = req->arg;
u_free(req);
callback(arg, result);
}
static void registration_timer(void* arg) {
struct chat_join_registration* req = arg;
req->timer = NULL;
registration_finish(req, req->target_node_id == req->inst->node_id ? CHAT_JOIN_OK : CHAT_JOIN_TIMEOUT);
}
void chat_join_cancel_registration(struct chat_join_registration* req) {
if (req) registration_finish(req, CHAT_JOIN_CANCELLED);
}
/* ── etcp_router recv ── */
static void join_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
@@ -221,6 +299,12 @@ static void join_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
const uint8_t* d = entry->dgram;
uint64_t from_node; memcpy(&from_node, d + ROUTER_SVC_SRC_OFF, 8);
uint64_t group_id; memcpy(&group_id, d + ROUTER_SVC_GROUP_OFF, 8);
uint8_t required = ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED;
if (from_node != inst->node_id && (d[ROUTER_SVC_FLAGS_OFF] & required) != required) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: unprotected join packet rejected from=%016llx ch=%llu", CJ_ID,
(unsigned long long)from_node, (unsigned long long)group_id);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
const uint8_t* payload = d + ROUTER_SVC_PAYLOAD_OFF;
size_t plen = entry->len - ROUTER_SVC_PAYLOAD_OFF;
uint8_t subcmd = payload[0];
@@ -230,9 +314,28 @@ static void join_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (subcmd == JOIN_SUBCMD_KEY_REGISTER) {
/* payload: [subcmd:1][join_key:8] */
if (plen < 1 + 8) { queue_dgram_free(entry); queue_entry_free(entry); return; }
uint64_t join_key; memcpy(&join_key, payload + 1, 8);
_keys_put(inst, group_id, join_key, from_node);
if (plen != 9) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: malformed KEY_REGISTER len=%zu", CJ_ID, plen);
} else {
uint64_t key; memcpy(&key, payload + 1, 8);
int rc = _keys_put(inst, group_id, key, from_node);
send_key_control(inst, group_id, from_node, key, JOIN_SUBCMD_KEY_REGISTER_ACK, rc ? 1 : 0);
}
} else if (subcmd == JOIN_SUBCMD_KEY_REGISTER_ACK) {
if (plen != 10 || payload[9] > 1) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: malformed KEY_REGISTER_ACK len=%zu", CJ_ID, plen);
} else {
uint64_t key; memcpy(&key, payload + 1, 8);
struct chat_join_registration* req = inst->join_registrations;
while (req && (req->channel_id != group_id || req->target_node_id != from_node || req->join_key != key)) req = req->next;
/* Освобождаем пакет до callback: владелец может закрыть instance. */
int result = payload[9] ? CHAT_JOIN_ERROR : CHAT_JOIN_OK;
queue_dgram_free(entry); queue_entry_free(entry);
if (req) registration_finish(req, result);
else DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "%s: late/unmatched registration ACK ch=%llu peer=%016llx", CJ_ID,
(unsigned long long)group_id, (unsigned long long)from_node);
return;
}
} else if (subcmd == JOIN_SUBCMD_REQUEST_FWD) {
/* payload: [subcmd:1][join_key:8][member...] */
if (plen < 1 + 8) { queue_dgram_free(entry); queue_entry_free(entry); return; }
@@ -249,6 +352,7 @@ static void join_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
int chat_join_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
inst->join_stopping = 0;
if (etcp_router_bind(inst, ETCP_RT_ID_JOIN, join_recv_cb) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: etcp_router_bind failed", CJ_ID);
return -1;
@@ -259,45 +363,52 @@ int chat_join_init(struct UTUN_INSTANCE* inst) {
void chat_join_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
etcp_router_unbind(inst, ETCP_RT_ID_JOIN);
inst->join_stopping = 1;
if (inst->router_bindings.callbacks[ETCP_RT_ID_JOIN]) etcp_router_unbind(inst, ETCP_RT_ID_JOIN);
while (inst->join_registrations) chat_join_cancel_registration(inst->join_registrations);
_keys_free_all(inst);
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: destroyed", CJ_ID);
}
/* ── Публичные операции ── */
void chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t target_node_id, uint64_t join_key) {
if (!inst || channel_id == 0 || join_key == 0) return;
int chat_join_store_local_key(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) {
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: local registration without instance", CJ_ID); return -1; }
return _keys_put(inst, channel_id, join_key, inst->node_id);
}
/* инвайтер всегда хранит выданный ключ локально — для верификации REQUEST_FWD */
_keys_put(inst, channel_id, join_key, inst->node_id);
if (target_node_id == inst->node_id || target_node_id == 0) {
return;
struct chat_join_registration* chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t target_node_id, uint64_t join_key, chat_join_registered_fn callback, void* arg) {
if (!inst || !inst->ua || inst->join_stopping || !channel_id || !join_key || !callback) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid registration arguments", CJ_ID);
return NULL;
}
uint8_t payload[1 + 8];
payload[0] = JOIN_SUBCMD_KEY_REGISTER;
memcpy(payload + 1, &join_key, 8);
struct ll_entry* entry = queue_entry_new(0);
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: register_key queue_entry_new failed", CJ_ID); return; }
entry->dgram = u_malloc(1 + sizeof(payload));
if (!entry->dgram) { queue_entry_free(entry); return; }
entry->dgram[0] = ETCP_RT_ID_JOIN;
memcpy(entry->dgram + 1, payload, sizeof(payload));
entry->len = 1 + (uint16_t)sizeof(payload);
int rc = etcp_route_send(inst, channel_id, target_node_id, entry, 1, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: register_key etcp_route_send FAILED rc=%d ch=%llu → 0x%016llx",
CJ_ID, rc, (unsigned long long)channel_id, (unsigned long long)target_node_id);
queue_dgram_free(entry); queue_entry_free(entry);
return;
for (struct chat_join_registration* r = inst->join_registrations; r; r = r->next) {
if (r->channel_id == channel_id && r->join_key == join_key) {
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration already pending ch=%llu", CJ_ID, (unsigned long long)channel_id);
return NULL;
}
}
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: join-key registered ch=%llu → connection=0x%016llx key=%016llx",
CJ_ID, (unsigned long long)channel_id, (unsigned long long)target_node_id, (unsigned long long)join_key);
struct chat_join_registration* req = u_calloc(1, sizeof(*req));
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration allocation failed", CJ_ID); return NULL; }
req->inst = inst; req->channel_id = channel_id; req->join_key = join_key;
req->target_node_id = target_node_id ? target_node_id : inst->node_id;
req->callback = callback; req->arg = arg;
int local = req->target_node_id == inst->node_id;
req->timer = uasync_set_timeout(inst->ua, local ? 0 : JOIN_REGISTER_TIMEOUT_TB, req, registration_timer, "join_register");
if (!req->timer) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration timer allocation failed", CJ_ID);
u_free(req); return NULL;
}
if (!local && send_key_control(inst, channel_id, req->target_node_id, join_key, JOIN_SUBCMD_KEY_REGISTER, 0) != 0) {
uasync_cancel_timeout(inst->ua, req->timer);
u_free(req); return NULL;
}
req->next = inst->join_registrations;
inst->join_registrations = req;
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: registration pending ch=%llu peer=%016llx key=%016llx", CJ_ID,
(unsigned long long)channel_id, (unsigned long long)req->target_node_id, (unsigned long long)join_key);
return req;
}
uint64_t chat_join_lookup_inviter(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) {
@@ -336,7 +447,6 @@ int chat_join_forward_request(struct UTUN_INSTANCE* inst, uint64_t channel_id,
if (rc != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: forward etcp_route_send FAILED rc=%d ch=%llu → 0x%016llx",
CJ_ID, rc, (unsigned long long)channel_id, (unsigned long long)inviter_id);
queue_dgram_free(entry); queue_entry_free(entry);
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: join request forwarded ch=%llu → inviter=0x%016llx joiner=0x%016llx",
+17 -4
View File
@@ -6,6 +6,8 @@
* инвайтер A connection-узел C джойнер J
* │ KEY_REGISTER(key,ch) │ │
* ├──────etcp_router─────────▶│ хранит {key→A, 10 мин} │
* │◀── KEY_REGISTER_ACK ──────┤ │
* │ теперь ссылка готова │ │
* │ │◀─── JOIN_INFO_REQ(key) ──────┤ (J подключился к C по ссылке)
* │ │── JOIN_INFO_RESP ───────────▶│ (ch_x25519, ch_ed25519, ch_sig, C-мембер)
* │ │◀─── JOIN_REQUEST ────────────┤ (key + полный набор мембера J)
@@ -40,8 +42,14 @@ extern "C" {
/* subcommands (первый байт payload etcp_router) */
#define JOIN_SUBCMD_KEY_REGISTER 0x01 /* инвайтер → connection: {join_key:8} */
#define JOIN_SUBCMD_REQUEST_FWD 0x02 /* connection → инвайтер: {join_key:8, member...} */
#define JOIN_SUBCMD_KEY_REGISTER_ACK 0x03 /* connection → инвайтер: {join_key:8, status:1}; 0=stored, 1=rejected */
#define JOIN_KEY_TTL_SECONDS 600 /* 10 минут хранения ключа на connection-узле */
#define JOIN_REGISTER_TIMEOUT_TB 50000 /* 5 секунд на подтверждение */
enum chat_join_result { CHAT_JOIN_OK = 0, CHAT_JOIN_ERROR = -1, CHAT_JOIN_TIMEOUT = -2, CHAT_JOIN_CANCELLED = -3 };
struct chat_join_registration;
typedef void (*chat_join_registered_fn)(void* arg, int result);
/* Полный набор мембера джойнера (без подписи дерева — её ставит инвайтер). */
struct join_member_data {
@@ -68,10 +76,15 @@ int chat_join_init(struct UTUN_INSTANCE* inst);
/* Деинициализация. */
void chat_join_destroy(struct UTUN_INSTANCE* inst);
/* Инвайтер: зарегистрировать join-ключ на connection-узле.
* target==self → храним локально (инвайтер и есть connection-узел). */
void chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t target_node_id, uint64_t join_key);
/* Локальное приглашение (инвайтер == connection). 0=stored, -1=ошибка. */
int chat_join_store_local_key(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key);
/* Все вызовы в uasync-потоке. NULL=ошибка запуска, callback не вызывается.
* Иначе callback ровно один раз, после возврата register_key (также для self).
* Handle действителен до callback. cancel/destroy вызывают callback синхронно с CANCELLED. */
struct chat_join_registration* chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id,
uint64_t target_node_id, uint64_t join_key, chat_join_registered_fn callback, void* arg);
void chat_join_cancel_registration(struct chat_join_registration* registration);
/* Connection-узел: найти инвайтера по ключу. 0 = нет/истёк. */
uint64_t chat_join_lookup_inviter(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key);
+10 -5
View File
@@ -207,7 +207,8 @@ static void chat_sync_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
const uint8_t* d = entry->dgram;
size_t dlen = entry->len;
uint64_t group_id = be64toh(*(const uint64_t*)(d + 1));
uint64_t group_id; memcpy(&group_id, d + 1, sizeof(group_id));
group_id = be64toh(group_id);
uint8_t type = d[9];
char ch_id[64];
if (cs_group_id_to_ch_id(cs->inst, group_id, ch_id, sizeof(ch_id)) != 0) {
@@ -946,8 +947,9 @@ void chat_sync_connect_from_invite(struct UTUN_INSTANCE* inst, uint64_t channel_
(long long)channel_id, (long long)node_id, addr_count, password ? "yes" : "no");
#endif
#ifndef __ANDROID__
uint64_t pubkey_prefix; memcpy(&pubkey_prefix, pubkey_bin, sizeof(pubkey_prefix));
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invite start ch=%llu node=0x%016llx pubkey=%016llx... addrs=%d pass=%s",
CS_ID, channel_id, node_id, *(const uint64_t*)pubkey_bin, addr_count, password && password[0] ? "yes" : "no");
CS_ID, (unsigned long long)channel_id, (unsigned long long)node_id, (unsigned long long)pubkey_prefix, addr_count, password && password[0] ? "yes" : "no");
#endif
struct cm_invite_wrap* w = u_malloc(sizeof(struct cm_invite_wrap));
@@ -1150,9 +1152,11 @@ static void cs_handle_join_info_resp(struct chat_sync* cs, uint64_t peer,
uint64_t c_signed_by; memcpy(&c_signed_by, p, 8); p += 8;
const uint8_t* c_signature = p; p += 64;
uint64_t ch_x_prefix, ch_ed_prefix;
memcpy(&ch_x_prefix, ch_x25519, sizeof(ch_x_prefix)); memcpy(&ch_ed_prefix, ch_ed, sizeof(ch_ed_prefix));
DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN_INFO_RESP from=%016llx owner=%016llx ch_x=%016llx ch_ed=%016llx name=%s conn_node=%016llx",
CS_ID, (unsigned long long)peer, (unsigned long long)owner,
*(const uint64_t*)ch_x25519, *(const uint64_t*)ch_ed, name, (unsigned long long)c_node_id);
(unsigned long long)ch_x_prefix, (unsigned long long)ch_ed_prefix, name, (unsigned long long)c_node_id);
/* verify channel signature */
uint8_t vmsg[1024]; size_t vlen = 0;
@@ -1450,8 +1454,9 @@ static void cs_handle_channel_invite(struct chat_sync* cs, uint64_t peer,
/* Save channel and start join handshake */
int cprc = topo_node_sqlite_channel_put(cs->inst->topo_sqlite_db,
ch_id, name, owner, x25519, NULL, ed_pub, NULL, ch_sig);
uint64_t ch_x_prefix; memcpy(&ch_x_prefix, x25519, sizeof(ch_x_prefix));
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INVITE channel_put rc=%d ch=%s ch_x25519=%016llx",
CS_ID, cprc, ch_id, *(const uint64_t*)x25519);
CS_ID, cprc, ch_id, (unsigned long long)ch_x_prefix);
chat_core_ensure_channel_ready(cs->inst, ch_id);
/* Initiate join: connect back and send JOIN_INFO_REQ с зарегистрированным ключом */
cs->pending_join_key = join_key;
@@ -1522,7 +1527,7 @@ static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* in
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: join_key generation failed ch=%s", CS_ID, ch_id);
return -1;
}
chat_join_register_key(inst, strtoull(ch_id, NULL, 10), 0, join_key);
if (chat_join_store_local_key(inst, strtoull(ch_id, NULL, 10), join_key) != 0) return -1;
uint8_t buf[1024]; size_t boff = 0;
buf[boff++] = CS_MSG_CHANNEL_INVITE;
+58 -26
View File
@@ -263,8 +263,8 @@ static int collect_node_addrs_from_db(struct UTUN_INSTANCE* inst, uint64_t node_
/* ── публичный API ── */
int chat_invite_build_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target_node_id,
const char* password, char* out, size_t out_size) {
static int prepare_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target_node_id,
const char* password, char* out, size_t out_size, uint64_t* target, uint64_t* key) {
struct chat_core_ctx* cc = CC(inst);
if (!out || out_size == 0) return -1;
out[0] = '\0';
@@ -347,7 +347,8 @@ int chat_invite_build_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint
}
/* ── регистрация join-ключа на connection-узле (или локально, если target==self) ── */
chat_join_register_key(inst, channel_id, target_nid, data.join_key);
*target = target_nid;
*key = data.join_key;
return 0;
}
@@ -363,6 +364,39 @@ int chat_invite_best_node(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint6
return 0;
}
struct chat_invite_build {
struct chat_join_registration* registration;
chat_invite_ready_fn callback;
void* arg;
char link[1024];
};
static void invite_registered(void* arg, int result) {
struct chat_invite_build* req = arg;
req->registration = NULL;
req->callback(req->arg, result, result == CHAT_JOIN_OK ? req->link : NULL);
u_free(req);
}
struct chat_invite_build* chat_invite_build_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target_node_id,
const char* password, chat_invite_ready_fn callback, void* arg) {
if (!callback) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: missing callback"); return NULL; }
struct chat_invite_build* req = u_calloc(1, sizeof(*req));
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: allocation failed"); return NULL; }
uint64_t target, key;
if (prepare_link(inst, channel_id, target_node_id, password, req->link, sizeof(req->link), &target, &key) != 0) {
u_free(req); return NULL;
}
req->callback = callback; req->arg = arg;
req->registration = chat_join_register_key(inst, channel_id, target, key, invite_registered, req);
if (!req->registration) { u_free(req); return NULL; }
return req;
}
void chat_invite_build_cancel(struct chat_invite_build* req) {
if (req && req->registration) chat_join_cancel_registration(req->registration);
}
int chat_invite_candidate_nodes(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t* out_ids, int max_ids) {
struct chat_core_ctx* cc = CC(inst);
if (!out_ids || max_ids <= 0) return -1;
@@ -433,35 +467,33 @@ void chat_invite_candidates_trampoline(void* arg) {
u_free(evt);
}
void chat_invite_build_link_trampoline(void* arg) {
struct chat_invite_build_req* req = (struct chat_invite_build_req*)arg;
if (!req) return;
uint64_t channel_id = strtoull(req->ch_id, NULL, 10);
uint64_t target = req->target_node_id;
char password[INVITE_PASS_MAX];
password[0] = '\0';
const char* pass = NULL;
if (req->password[0]) {
strncpy(password, req->password, sizeof(password) - 1);
password[sizeof(password) - 1] = '\0';
pass = password;
}
char link[1024];
int ok = chat_invite_build_link(req->inst, channel_id, target, pass, link, sizeof(link));
static void gui_invite_ready(void* arg, int result, const char* link) {
struct chat_invite_build_req* req = arg;
req->inst->invite_gui_request = NULL;
if (result == CHAT_JOIN_CANCELLED) { u_free(req); return; }
size_t ch_len = strlen(req->ch_id);
size_t link_len = (ok == 0) ? strlen(link) : 0;
size_t evt_sz = 1 + ch_len + 2 + link_len;
size_t link_len = link ? strlen(link) : 0;
size_t evt_sz = 1 + ch_len + 8 + 2 + link_len;
uint8_t* evt = u_malloc(evt_sz);
if (!evt) { u_free(req); return; }
if (!evt) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: event allocation failed"); u_free(req); return; }
evt[0] = (uint8_t)ch_len;
memcpy(evt + 1, req->ch_id, ch_len);
uint16_t u16 = (uint16_t)link_len;
memcpy(evt + 1 + ch_len, &u16, 2);
if (link_len) memcpy(evt + 1 + ch_len + 2, link, link_len);
memcpy(evt + 1 + ch_len, &req->request_id, 8);
memcpy(evt + 1 + ch_len + 8, &u16, 2);
if (link_len) memcpy(evt + 1 + ch_len + 10, link, link_len);
chat_event_post(req->inst, CHAT_EVT_INVITE_LINK_READY, evt, (int)evt_sz);
u_free(evt);
u_free(req);
}
void chat_invite_build_link_trampoline(void* arg) {
struct chat_invite_build_req* req = arg;
if (!req) return;
if (!req->inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: GUI request without instance"); u_free(req); return; }
if (req->inst->invite_gui_request) chat_invite_build_cancel(req->inst->invite_gui_request->pending);
req->inst->invite_gui_request = req;
req->pending = chat_invite_build_link(req->inst, strtoull(req->ch_id, NULL, 10), req->target_node_id,
req->password[0] ? req->password : NULL, gui_invite_ready, req);
if (!req->pending) gui_invite_ready(req, CHAT_JOIN_ERROR, NULL);
}
+11 -3
View File
@@ -16,14 +16,20 @@ extern "C" {
#endif
struct UTUN_INSTANCE;
struct chat_invite_build;
typedef void (*chat_invite_ready_fn)(void* arg, int result, const char* link);
/* Собрать invite-ссылку для канала.
* target_node_id = 0 → auto (лучший достижимый мембер, иначе self).
* target_node_id != 0 → конкретный узел (должен быть достижимым мембером канала).
* password = NULL → без пароля.
* Возвращает 0 и заполняет out, либо -1 (детали в логе). */
int chat_invite_build_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target_node_id,
const char* password, char* out, size_t out_size);
* NULL=ошибка запуска, callback не вызывается. Иначе callback после возврата,
* ровно один раз, result из enum chat_join_result. link доступен только внутри callback,
* только при успехе — после подтверждения сохранения ключа на выбранном узле.
* Handle действителен до callback; cancel/destroy завершают запрос синхронно с CANCELLED. */
struct chat_invite_build* chat_invite_build_link(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t target_node_id,
const char* password, chat_invite_ready_fn callback, void* arg);
void chat_invite_build_cancel(struct chat_invite_build* request);
/* Список достижимых узлов-кандидатов канала (включая self) — тот же фильтр, что в auto.
* Возвращает количество (0..max_ids), либо -1. */
@@ -37,8 +43,10 @@ int chat_invite_best_node(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint6
struct chat_invite_build_req {
struct UTUN_INSTANCE* inst;
char ch_id[64];
uint64_t request_id; /* идентификатор запроса UI, возвращается в событии */
uint64_t target_node_id; /* 0 = auto */
char password[128];
struct chat_invite_build* pending; /* внутреннее поле trampoline */
};
void chat_invite_build_link_trampoline(void* arg);
+1 -1
View File
@@ -1211,7 +1211,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
struct ll_entry* re = instance->router_conns->head;
while (re) {
struct ETCP_ROUTER_CONN* rc = (struct ETCP_ROUTER_CONN*)re;
rsp->router.total_inflight += (uint32_t)(rc->tx_seq - rc->tx_acked);
rsp->router.total_inflight += (uint32_t)queue_entry_count(rc->inflight_q);
if (rc->send_q) rsp->router.total_send_q += (uint32_t)queue_entry_count(rc->send_q);
if (rc->recv_q) rsp->router.total_recv_q += (uint32_t)queue_entry_count(rc->recv_q);
rsp->router.pkts_sent += rc->c_pkts_sent;
+51 -2
View File
@@ -31,6 +31,7 @@
#define RADIO_DEDUP_MAX 128 /* ёмкость кэша дедупликации (≈2.5с @ 50 кадров/с) */
#define RADIO_DEDUP_TTL_TB 20000 /* 2с — окно дедупликации (совпадает с таймаутом источника) */
#define RADIO_TALKER_TIMEOUT_TB 20000 /* 2с без кадров — считаем передачу завершённой (FIN потерян/пир отвалился) */
/* Запись кэша дедупликации: ключ — (src, stream, seq), tb — момент прихода. */
struct radio_seen {
@@ -52,7 +53,8 @@ struct radio_ctx {
uint32_t c_nosub_dropped; /* не отправлено: за соседом нет подписчиков */
uint32_t c_bad_dropped; /* дропнуто некорректных пакетов */
uint32_t c_dup_dropped; /* дропнуто дублей (src,stream,seq) пришедших по другому пути */
struct radio_talker { uint64_t src; uint16_t stream; } talkers[RADIO_MAX_TALKERS]; /* активные говорящие */
struct radio_talker { uint64_t src; uint16_t stream; uint64_t last_tb; } talkers[RADIO_MAX_TALKERS]; /* активные говорящие */
void* talker_timer; /* периодический sweep зависших говорящих (self-rearming) */
struct radio_seen seen[RADIO_DEDUP_MAX]; /* кэш дедупликации */
int seen_count;
};
@@ -62,6 +64,8 @@ struct radio_instance {
void* frame_arg;
};
static void radio_talker_timer_cb(void* arg);
static struct radio_ctx* radio_of(struct TOPO_GROUP* group) {
return group ? group->radio : NULL;
}
@@ -80,6 +84,9 @@ int radio_init(struct TOPO_GROUP* group) {
ctx->stream_id = (uint16_t)(get_time_tb() & 0xFFFF);
group->radio = ctx;
ctx->talker_timer = uasync_set_timeout(group->instance->ua, RADIO_TALKER_TIMEOUT_TB, ctx, radio_talker_timer_cb, "radio_talker");
if (!ctx->talker_timer) DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "%s: init talker_timer failed grp=%016llx", RADIO_ID, (unsigned long long)group->group_id);
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "%s: init ok grp=%016llx", RADIO_ID, (unsigned long long)group->group_id);
return 0;
}
@@ -87,6 +94,7 @@ int radio_init(struct TOPO_GROUP* group) {
void radio_destroy(struct TOPO_GROUP* group) {
if (!group || !group->radio) return;
struct radio_ctx* ctx = group->radio;
if (ctx->talker_timer) { uasync_cancel_timeout(group->instance->ua, ctx->talker_timer); ctx->talker_timer = NULL; }
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "%s: destroy grp=%016llx (tx=%u rx=%u fwd=%u loop=%u nosub=%u bad=%u dup=%u)",
RADIO_ID, (unsigned long long)group->group_id,
ctx->c_tx_frames, ctx->c_rx_frames, ctx->c_fwd_frames,
@@ -190,8 +198,10 @@ static void radio_talk_event(struct TOPO_GROUP* group, uint64_t src, uint16_t st
/* зафиксировать начало передачи: пост on=1, если это новый поток источника */
static void radio_talker_on(struct radio_ctx* ctx, uint64_t src, uint16_t stream) {
uint64_t now = get_time_tb();
for (int i = 0; i < RADIO_MAX_TALKERS; i++) {
if (ctx->talkers[i].src == src) {
ctx->talkers[i].last_tb = now;
if (ctx->talkers[i].stream != stream) {
ctx->talkers[i].stream = stream;
radio_talk_event(ctx->group, src, stream, 1);
@@ -203,6 +213,7 @@ static void radio_talker_on(struct radio_ctx* ctx, uint64_t src, uint16_t stream
if (ctx->talkers[i].src == 0) {
ctx->talkers[i].src = src;
ctx->talkers[i].stream = stream;
ctx->talkers[i].last_tb = now;
radio_talk_event(ctx->group, src, stream, 1);
return;
}
@@ -216,11 +227,35 @@ static void radio_talker_off(struct radio_ctx* ctx, uint64_t src) {
radio_talk_event(ctx->group, src, ctx->talkers[i].stream, 0);
ctx->talkers[i].src = 0;
ctx->talkers[i].stream = 0;
ctx->talkers[i].last_tb = 0;
return;
}
}
}
/* Снять говорящих, от которых дольше RADIO_TALKER_TIMEOUT_TB нет кадров:
* FIN потерян/не был отправлен (пир отвалился, выключил рацию во время разговора).
* Аналог таймаута источника в radio_audio.c — гарантирует, что индикация «говорит» гаснет. */
static void radio_talkers_sweep(struct radio_ctx* ctx) {
uint64_t now = get_time_tb();
for (int i = 0; i < RADIO_MAX_TALKERS; i++) {
if (!ctx->talkers[i].src) continue;
if (now - ctx->talkers[i].last_tb > RADIO_TALKER_TIMEOUT_TB) {
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: talker timeout src=%016llx (last frame %.0fms ago)",
RADIO_ID, (unsigned long long)ctx->talkers[i].src,
(double)(now - ctx->talkers[i].last_tb) / 10.0);
radio_talker_off(ctx, ctx->talkers[i].src);
}
}
}
static void radio_talker_timer_cb(void* arg) {
struct radio_ctx* ctx = (struct radio_ctx*)arg;
if (!ctx || !ctx->group) return;
radio_talkers_sweep(ctx);
ctx->talker_timer = uasync_set_timeout(ctx->group->instance->ua, RADIO_TALKER_TIMEOUT_TB, ctx, radio_talker_timer_cb, "radio_talker");
}
/* Дедупликация: вернуть 1, если (src,stream,seq) уже видели (дубль по другому пути),
* иначе зафиксировать в кэше и вернуть 0. Лениво чистит просроченные записи. */
static int radio_seen_dup(struct radio_ctx* ctx, uint64_t src, uint16_t stream, uint16_t seq, uint64_t now) {
@@ -383,6 +418,15 @@ void radio_set_active(struct UTUN_INSTANCE* inst, uint64_t group_id, int on) {
if (!inst || !inst->topo_groups) return;
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, group_id);
if (!group) { DEBUG_WARN(DEBUG_CATEGORY_RADIO, "%s: set_active group 0x%016llx not found", RADIO_ID, (unsigned long long)group_id); return; }
if (!on) {
/* выключение прослушивания: снять всех активных говорящих — индикация должна погаснуть
* сразу, а не дожидаться FIN/таймаута */
struct radio_ctx* ctx = radio_of(group);
if (ctx) {
for (int i = 0; i < RADIO_MAX_TALKERS; i++)
if (ctx->talkers[i].src) radio_talker_off(ctx, ctx->talkers[i].src);
}
}
topo_group_set_radio(group, on);
}
@@ -427,7 +471,12 @@ int radio_talk_send(struct UTUN_INSTANCE* inst, uint64_t group_id, const uint8_t
if (!group) return -1;
struct radio_ctx* ctx = radio_of(group);
if (!ctx) return -1;
if (!ctx->talking) radio_talk_begin(inst, group_id);
if (!ctx->talking) {
/* burst не открыт: хвостовой кадр, пришедший после radio_talk_end (гонка релиза PTT) —
* не переоткрываем передачу, иначе получится burst без FIN и «говорит» зависнет у пиров. */
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: send dropped: no open burst grp=%016llx", RADIO_ID, (unsigned long long)group_id);
return 0;
}
uint64_t self = inst->node_id;
uint64_t hops[1] = { self };
+12
View File
@@ -252,6 +252,7 @@ void cm_ncd_callback(struct NODE_CONN_DIRECT* ncd_h, enum ncd_event ncd_ev, void
if (!entry || !entry->mgr || entry->state == CONN_MGR_STATE_DISCONNECTED) return;
switch (ncd_ev) {
case NCD_EVENT_UP:
entry->ncd_down_notified = 0;
if (entry->state == CONN_MGR_STATE_CONNECTED) return;
{ int won = entry->reverse_active ? CONN_TYPE_REVERSE : CONN_TYPE_DIRECT;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "link: ETCP handshake OK 0x%016llx — %s (reverse_active=%d indirect_active=%d)",
@@ -303,7 +304,18 @@ void cm_ncd_callback(struct NODE_CONN_DIRECT* ncd_h, enum ncd_event ncd_ev, void
if (entry->reverse_active || entry->indirect_active) return;
cm_start_parallel(entry);
break;
case NCD_EVENT_CLOSED:
if (entry->ncd_handle == ncd_h) entry->ncd_handle = NULL;
node_conn_direct_close(ncd_h);
if (entry->state != CONN_MGR_STATE_CONNECTED) {
cm_ncd_callback(NULL, NCD_EVENT_TIMEOUT, entry);
return;
}
entry->state = CONN_MGR_STATE_FAILED; entry->conn_type = CONN_TYPE_NONE;
if (!entry->ncd_down_notified) cm_deliver_event(entry, CONN_EVENT_DOWN);
return;
case NCD_EVENT_DOWN:
entry->ncd_down_notified = 1;
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "link: ETCP link DROPPED to 0x%016llx",
(unsigned long long)entry->node_id);
cm_deliver_event(entry, CONN_EVENT_DOWN);
+19 -13
View File
@@ -55,6 +55,15 @@ void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uin
{ struct CONN_MGR_CANDIDATE t = mgr->best_candidates[i]; mgr->best_candidates[i]=mgr->best_candidates[i-1]; mgr->best_candidates[i-1]=t; }
}
/* Маршрут может вести через посредника. Для прямой пробы нужны transport peer и его живой линк. */
static struct ETCP_LINK* cm_active_direct_link(struct TOPO_GROUP* group, uint64_t node_id) {
struct ETCP_CONN* conn = instance_find_conn(group->instance,node_id);
if (!conn || conn->state != 1 || conn->close_requested) return NULL;
for (struct ETCP_LINK* link = conn->links; link; link = link->next)
if (link->initialized && link->link_status) return link;
return NULL;
}
/* ═══════ bg_ping ═══════ */
static void cm_bg_ping_noop_cb(int s, uint16_t r, void* a, uint64_t n, const uint8_t* d, size_t l) { (void)s;(void)r;(void)a;(void)n;(void)d;(void)l; }
@@ -124,9 +133,7 @@ void cm_bg_ping_timer_cb(void* arg) {
if (e) {
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e;
if (nq->node_id != mgr->instance->node_id) {
int has_active = 0;
struct ETCP_CONN* dc = topo_group_find_conn_for_node(group, nq->node_id);
if (dc && dc->links) { struct ETCP_LINK* l = dc->links; while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; } }
int has_active = cm_active_direct_link(group,nq->node_id) != NULL;
if (!has_active) {
struct TOPO_NODE* bg_ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id);
if (bg_ni) cm_bg_ping_to_node(mgr, bg_ni);
@@ -141,10 +148,7 @@ rearm:
/* ═══════ candidate_ping ═══════ */
static int cm_has_active_conn(struct TOPO_GROUP* g, uint64_t nid) {
struct ETCP_CONN* c = topo_group_find_conn_for_node(g, nid);
if (!c || !c->links) return 0;
struct ETCP_LINK* l = c->links; while (l) { if (l->initialized && l->link_status) return 1; l = l->next; }
return 0;
return cm_active_direct_link(g,nid) != NULL;
}
/* Обновление кандидатов-посредников: удаляет протухших (>30с без свежих RTT),
@@ -182,18 +186,20 @@ void cm_candidate_ping_timer_cb(void* arg) {
&& (now - cm_get_node_max_probe_time(nq)) >= CONN_MGR_CANDIDATE_STALE_TB)
{ DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: candidate 0x%016llx stale, removing", (unsigned long long)nid);
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (--mgr->best_candidate_count - i) * sizeof(mgr->best_candidates[0])); i--; continue; }
if (cm_has_active_conn(mgr->group, nid)) {
struct ETCP_CONN* dc = topo_group_find_conn_for_node(mgr->group, nid);
struct ETCP_LINK* link = cm_active_direct_link(mgr->group,nid);
if (link) {
struct ETCP_CONN* dc = link->etcp;
struct TOPO_NODE* ni = topo_node_registry_find(mgr->instance->topo_groups, nq->node_id);
/* пир в SLEEP-фазе — прямое соединение живо, пинг будит его: пропускаем */
if (ni && dc && dc->links && !dc->peer_sleep_phase) {
if (dc->links->is_tcp) {
if (ni && !dc->peer_sleep_phase) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP,"candidate probe: direct peer=%016llx link=%u",(unsigned long long)nid,link->local_link_id);
if (link->is_tcp) {
struct stcp_client** slot = NULL;
for (uint8_t i = 0; i < CONN_MGR_MAX_CANDIDATES; i++)
if (!mgr->cand_tcp_pings[i]) { slot = &mgr->cand_tcp_pings[i]; break; }
if (slot) etcp_send_tcp_ping(mgr->instance, ni->public_key, &dc->links->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_cand_tcp_ping_cb, slot, slot);
if (slot) etcp_send_tcp_ping(mgr->instance, ni->public_key, &link->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_cand_tcp_ping_cb, slot, slot);
} else
etcp_send_ping(mgr->instance, ni->public_key, &dc->links->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0);
etcp_send_ping(mgr->instance, ni->public_key, &link->remote_addr, CONN_PROBE_TIMEOUT_MS, cm_bg_ping_noop_cb, NULL, NULL, 0);
}
} else route_connectivity_probe_node(mgr->instance, mgr->group, nq);
}
+2
View File
@@ -42,6 +42,7 @@ enum CONN_MGR_STATE {
CONN_MGR_STATE_DISCONNECTED = 0,
CONN_MGR_STATE_CONNECTING = 1,
CONN_MGR_STATE_CONNECTED = 2,
CONN_MGR_STATE_FAILED = 3,
};
enum CM_TRY_STATE {
@@ -103,6 +104,7 @@ struct CONN_MGR_ENTRY {
uint8_t state, conn_type;
uint64_t last_traffic_tb;
struct CONN_MGR_HANDLE* handles; struct NODE_CONN_DIRECT* ncd_handle;
uint8_t ncd_down_notified;
uint64_t intermediaries[3]; uint8_t intermediariy_count, rr_idx;
uint8_t local_scan_state, main_connect_state, db_loaded;
uint8_t reverse_active, indirect_active, indirect_ready;
File diff suppressed because it is too large. Load diff
+23 -11
View File
@@ -4,9 +4,11 @@
// Подпись/шифрование вынесены в автономный модуль route_crypto (encode/decode).
//
// Формат SVC_ROUTE пакета:
// [cmd:1] [group_id:8] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [timestamp:2] [reset_id:8] [payload...]
// [cmd:1] [group_id:8] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [timestamp:2] [reset_id:8]
// [peer_reset_id:8] [challenge:8] [payload...]
// flags: bit7=START, bit6=RST, bit3=SIGNED, bit2=ENCRYPTED, bit1=CLOSE
// reset_id — эпоха сеанса: пишется только в START/RST, в data/ACK = 0
// reset_id/peer_reset_id — идентификаторы отправителя/получателя во всех пакетах.
// START=HELLO, RST=CHALLENGE, START|RST=CONFIRM; challenge используется только в handshake.
// ACK-пакет: тот же заголовок, seq=rx_seq, payload_len=0
#ifndef ETCP_ROUTER_H
#define ETCP_ROUTER_H
@@ -33,10 +35,12 @@ struct SVC_ROUTE_HDR {
uint8_t svc_id; // идентификатор сервиса
uint8_t flags; // bit7=START, bit6=RST, bit3=SIGNED, bit2=ENCRYPTED, bit1=CLOSE
uint16_t timestamp; // часы отправителя (0.1ms), для RTT; ACK: эхо последнего data-пакета
uint64_t reset_id; // эпоха сеанса (native order, как group_id/seq); только START/RST, в data/ACK = 0
uint64_t reset_id; // случайный идентификатор локального экземпляра router_conn
uint64_t peer_reset_id; // идентификатор экземпляра получателя (0 только в HELLO)
uint64_t challenge; // случайный запрос/эхо для подтверждения актуальности пира
};
#pragma pack(pop)
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 41
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 57
#define SVC_ROUTE_MAX_BINDINGS 256
// Единый формат доставки сервисной кодограммы (router → сервис):
@@ -103,6 +107,7 @@ struct ETCP_ROUTER_CONN {
uint8_t last_recv_updated; // флаг: есть данные для эха в ACK
uint32_t tx_seq; // следующий seq для отправки
uint32_t tx_sent; // граница фактически отправленных пакетов (ACK <= tx_sent)
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
uint32_t tx_acked; // сколько наших пакетов подтвердил remote (для inflight)
uint32_t last_sent_ack_seq; // последний отправленный ACK (= rx_seq на момент отправки)
@@ -116,17 +121,23 @@ struct ETCP_ROUTER_CONN {
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
struct queue_waiter_handle send_waiter; // waiter на send_input_q (по 1 пакету, round-robin)
struct ll_queue* send_waiter_q; // очередь регистрации, не результат повторного поиска маршрута
uint32_t retrans_seq; // следующий seq текущего прохода ретрансмитов
uint32_t retrans_end; // конец прохода (исключительно)
void* watchdog_timer; // DEBUG-watchdog: медленная проверка инварианта drain
uint8_t send_blocked; // 1 = inflight полон, ждём ack
uint8_t no_route; // 1 = нет BGP-маршрута, передача приостановлена
void* no_route_timer; // таймер 20ms проверки появления маршрута
uint8_t no_ack_count; // счётчик последовательных ретрансмиссий без ACK
uint8_t closed; // 1 = в процессе закрытия, таймеры игнорируют
uint64_t reset_id; // метка эпохи сеанса (случайная при создании; master авторитет, slave принимает эпоху мастера)
uint64_t peer_reset_id; // последняя увиденная эпоха сеанса пира (0 = ещё не синхронизированы)
uint8_t start_sent; // 0 = нужно отправить START в первом data
uint8_t peer_sync_done; // 0 = rx_seq ещё не синхронизирован с удалённым seq (авто-создание/рестарт)
uint8_t send_restart_pending; // 1 = RST принят и отправка переинициализирована (игнор повторных RST до первого ACK)
uint64_t reset_id; // собственный идентификатор; не меняется при рестарте пира
uint64_t peer_reset_id; // подтверждённый идентификатор пира (0 = handshake не завершён)
uint64_t pending_peer_id; // кандидат, ещё не имеющий права менять состояние сессии
uint64_t pending_challenge;
uint64_t challenge_sent_tb;
void* handshake_timer;
uint8_t start_sent; // получен первый ACK данных
uint8_t peer_sync_done; // 1 = завершено подтверждение peer ID
// Ретрансмиты: inflight очередь
struct ll_queue* inflight_q; // хеш по seq (4 байта), непрерывный блок без SACK
@@ -200,6 +211,7 @@ int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn c
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q).
// Принимает владение entry во всех исходах, включая ошибку.
// mode — битовая маска ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (см. route_crypto.h), 0 = обычный пакет.
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id, struct ll_entry* entry, int force, int mode);
@@ -236,14 +248,14 @@ int etcp_router_send_q_has_room(struct UTUN_INSTANCE* inst, uint64_t group_id, u
// 4. Служебные функции (для каркаса: etcp.c, topo_group.c)
// ====================================================================
// Сбросить ретрансмиты для всех router_conn к узлу (без колбэков, без CLOSE)
// Отвязать отправку от сбрасываемого транспорта; сохранить inflight и ретрансмиты
// Используется при etcp_conn_reinit чтобы избежать гонки с очисткой ETCP-очередей
void etcp_router_pause_retrans_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
// Закрыть все router_conn для указанного (group_id, remote_node_id) (peer умер)
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id);
// Сбросить состояние роутера для конкретного peer+svc в группе (перезапуск удалённой стороны).
// Начать новую локальную сессию для конкретного peer+svc в группе и уведомить сервис.
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id, uint8_t svc_id);
// Удалить все транзитные очереди соединения (вызывается при закрытии ETCP_CONN)
+80 -119
View File
@@ -1,141 +1,102 @@
# ETCP Router (etcp_router)
# ETCP Router: протокол и проверка транспорта
## 1. Назначение
Сервисный слой маршрутизации поверх ETCP — упрощённый TCP с восстановлением порядка доставки, дедупликацией и контролем inflight. Мультиплексирует до 256 сервисов (`svc_id`) на одном ETCP-соединении. Поддерживает transit (многошаговую маршрутизацию через промежуточные узлы), ретрансмиты, minRTT-измерения и подписи Ed25519.
Логическое соединение определяется `(group_id, remote_node_id, svc_id)`. Роутер обеспечивает порядок, дедупликацию и повторную отправку между конечными узлами; физический ETCP и транзитные узлы могут меняться. Формат изменён без обратной совместимости.
## 2. Как пользоваться
## Wire-формат
### Инициализация
```c
// В utun_instance_init():
etcp_router_init(inst);
Packed `SVC_ROUTE_HDR`, 57 байт:
// Зарегистрировать обработчик сервиса (svc_id 0..255)
etcp_router_bind(inst, svc_id, my_service_callback);
```
`cmd:1 | group:8 | dst:8 | src:8 | seq:4 | svc:1 | flags:1 | timestamp:2 | reset_id:8 | peer_reset_id:8 | challenge:8`
### Отправка
```c
// Простой способ: сформировать ll_entry с svc_id в первом байте
struct ll_entry* e = queue_entry_new(0);
e->dgram = u_malloc(1 + payload_len);
e->dgram[0] = svc_id;
memcpy(e->dgram + 1, payload, payload_len);
e->len = 1 + payload_len;
etcp_route_send(inst, group_id, dst_node_id, e, force, mode); // mode: 0 / ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT
Числовое представление полей сохраняет существующий формат проекта (native endian). Отдельной версии/согласования формата нет; узлы должны обновляться совместно.
// Или через существующий ROUTER_CONN:
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, group_id, remote_node_id, svc_id);
etcp_router_conn_send(rconn, data, len, mode);
```
`reset_id` — случайный ненулевой ID локального экземпляра соединения; `peer_reset_id` — ID получателя. DATA, ACK и CLOSE принимаются только при совпадении обоих ID с текущей парой. Смена физического транспорта пару не меняет.
### Приём
```c
void my_service_callback(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry) { /* соединение закрыто */ return; }
uint8_t svc_id = entry->dgram[0];
uint64_t from = *(uint64_t*)(entry->dgram + ROUTER_SVC_SRC_OFF); // remote_node_id
uint8_t rx_flags = entry->dgram[ROUTER_SVC_FLAGS_OFF]; // был ли SIGNED/ENCRYPTED на wire
// ... данные начиная с entry->dgram[ROUTER_SVC_PAYLOAD_OFF] ...
queue_dgram_free(entry);
queue_entry_free(entry);
}
```
| Пакет | Флаги | Payload | Поля |
|---|---|---|---|
| HELLO | START (0x80) | нет | собственный ID, peer=0, challenge=0 |
| CHALLENGE | RST (0x40) | нет | оба ID, случайный ненулевой challenge |
| CONFIRM | START|RST | нет | оба ID, эхо challenge |
| DATA | 0 либо SIGNED/ENCRYPTED | непустой | seq, оба ID, challenge=0 |
| ACK | 0 | нет | seq=следующий ожидаемый, оба ID, challenge=0 |
| CLOSE | CLOSE (0x02) | нет | оба ID, challenge=0 |
### Backpressure
```c
// На send_q (inflight переполнен):
struct queue_waiter_handle h;
etcp_router_on_send_ready(inst, group_id, node_id, svc_id, &h, my_ready_cb, my_arg);
// Когда send_q освободится — вызовется my_ready_cb
```
Незнакомый ID сам по себе не сбрасывает рабочую сессию. Получатель выдаёт свежий challenge и меняет peer ID только после соответствующего CONFIRM. CHALLENGE не меняет рабочую сессию: отправляется CONFIRM и, если нужно, встречный challenge. Потерянные сообщения handshake восстанавливаются повторением HELLO через 300 ms; выдача новых challenge ограничена тем же интервалом.
### Подпись / шифрование (модуль route_crypto)
```c
// mode — битовая маска, применяется в начале функции отправки (route_crypto_encode),
// при приёме проверяется/расшифровывается перед вызовом коллбэка (route_crypto_decode).
#include "route_crypto.h"
etcp_route_send(inst, group_id, dst, e, force, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT);
```
Первичное согласование сохраняет накопленные исходящие данные. Подтверждённый рестарт пира сбрасывает seq/очереди и уведомляет сервис; собственный ID сохраняется. Явный `etcp_router_conn_restart` создаёт новый собственный ID. Сброс выполняется до уведомления, поэтому данные, отправленные сервисом из callback, сохраняются. Сервис должен заново выполнить прикладную синхронизацию после уведомления; гарантии доставки не продолжаются через явный сброс сессии.
## 3. API
Challenge защищает от случайных старых/переставленных управляющих пакетов, но не является end-to-end аутентификацией против злонамеренного транзитного узла. Управление опирается на защиту физических ETCP-соединений. Подпись/шифрование DATA включаются вызывающим сервисом через `ROUTE_CRYPTO_SIGN` / `ROUTE_CRYPTO_ENCRYPT`.
### Ключевые структуры
## Очереди и подтверждения
**SVC_ROUTE_HDR** (33 байта) — заголовок пакета:
`cmd(1) + group_id(8) + dst_node_id(8) + src_node_id(8) + seq(4) + svc_id(1) + flags(1) + timestamp(2)`
- `send_q`: plaintext + seq + crypto mode, до 64 пакетов при обычной отправке. Кодирование происходит при отправке после handshake, с актуальной парой ID.
- `tx_seq`: следующий выделяемый seq; `tx_sent`: граница фактически отправленного; `tx_acked`: граница подтверждённого.
- `inflight_q`: финальные wire-копии, обычно до 256 пакетов. Копия создаётся до передачи в ETCP; ошибка не теряет pending-пакет.
- ACK принимается только в модульном интервале `(tx_acked, tx_sent]`. Дубликаты, старые и выходящие за границу ACK не продлевают ожидание прогресса. Удаляются существующие inflight-записи, а не перебираются все номера до произвольного ACK.
- Новые DATA и ретрансмиты идут по одному через waiter физической `send_input_q`. При смене пути отменяется waiter на очереди, где он действительно был зарегистрирован, включая уже запланированный callback.
- Каждые 300 ms без прогресса планируется проход повторной отправки. После 17 попыток без ACK соединение закрывается. Отсутствующий маршрут проверяется каждые 20 ms; очередь сохраняется. Пока первичный handshake не завершён, HELLO повторяется без отдельного таймаута.
- Watchdog 500 ms восстанавливает отправку и переносит waiter при смене заблокированного пути.
- Сброс физического ETCP сохраняет router inflight и seq. Восстановление самого ETCP-handshake остаётся обязанностью транспортного слоя.
- Crypto decode/проверка подписи выполняются до помещения в reorder и изменения rx_seq. Повреждённый пакет не подтверждается как доставленный.
- DATA выдаются сервису строго по seq; входящие дубликаты повторно проверяются после извлечения из очереди.
- Wire-пакет ограничен `PKTNORM_MAX_DGRAM_SIZE` (16384), включая заголовок и crypto overhead. Пустой payload запрещён: он используется для управления.
Флаги: `ROUTER_FLAG_START` (0x80), `ROUTER_FLAG_RST` (0x40), `ROUTER_FLAG_SIGNED` (0x08), `ROUTER_FLAG_ENCRYPTED` (0x04), `ROUTER_FLAG_CLOSE` (0x02).
## Сервисный API
**Формат доставки сервису** (router → callback):
`[svc_id:1][src_node_id:8][dst_node_id:8][rx_flags:1][payload...]` (ROUTER_SVC_HDR_SIZE = 18).
`etcp_router_conn_send(rconn, data, len, mode)` копирует payload; 0 означает принятие в очередь, а не end-to-end доставку. При backpressure производитель ждёт через `etcp_router_on_send_ready`; отменяет ожидание через `etcp_router_cancel_send_ready`. Handle должен жить до callback/отмены. Режим `force` низкоуровневого `etcp_route_send` обходит порог очереди и не предназначен для непрерывного потока.
**ETCP_ROUTER_CONN** — состояние логического подключения (group_id + remote_node_id + svc_id):
- `tx_seq`, `rx_seq`, `tx_acked` — seq-нумерация для порядка и inflight
- `recv_q` — reorder-очередь с хеш-индексом по seq (восстановление порядка)
- `send_q` — очередь ожидания при полном inflight (backpressure)
- `send_waiter` — waiter на send_input_q (по 1 пакету, round-robin между сервисами)
- `watchdog_timer` — медленная проверка инварианта drain (страховка от заклинивания)
- `inflight_q` — копии финальных отправленных пакетов для ретрансмита (хеш по seq)
- `incoming_q` — FIFO между сетевым приёмом и recv_q (защита от гонок)
- `rtt`, `rtt_jitter`, `minrtt` — измерения задержки
- `inflight_limit` — текущий лимит пакетов в полёте (minrtt_probe снижает до 4)
- `no_ack_count` — последовательные ретрансмиты без ACK (при 17 — закрытие)
- `start_sent`, `peer_sync_done` — синхронизация после (пере)подключения
- `closed` — флаг закрытия (игнорирование таймеров)
`etcp_router_bind` регистрирует callback, владеющий переданным ll_entry. Формат доставки — 26 байт заголовка:
**ROUTER_INFLIGHT** — запись в inflight_q: `seq, last_sent_tb, send_count, dgram*` (финальный wire-пакет)
`svc:1 | src:8 | dst:8 | rx_flags:1 | group:8 | payload...`
**TRANSIT_QUEUE** — per (group_id, src, dst) пара на промежуточном узле:
- `q` — FIFO транзитных пакетов
- `waiter` — backpressure на send_input_q next_hop'а
- `conn` — ETCP_CONN следующего шага
Читать невыравненные числовые поля нужно через `memcpy`. Заголовок без payload означает уведомление о закрытии/сбросе. После обработки освобождаются `queue_dgram_free(entry)` и `queue_entry_free(entry)`.
### Внутренняя архитектура
```
Приём: сеть → incoming_q (FIFO) → router_incoming_q_cb → recv_q (хеш по seq)
↓
router_try_assembly → deliver → route_crypto_decode → cb
Callback может закрыть соединение. Освобождение send_q отложено за пределы callback производителя; отправка проверяет, не закрылся/перезапустился ли rconn. При уничтожении реестр удаляется ровно один раз для каждого соединения.
Отправка: router_enqueue_send → router_build_packet (seq + route_crypto_encode) → send_q (FIFO, backpressure)
router_send_kick → waiter на send_input_q → router_send_drain_cb (по 1 пакету, round-robin)
→ etcp_send + inflight_q (копия финального пакета)
↓
router_track_inflight_state + retrans_schedule
ACK: периодический (10ms) + idle (500ms) → router_send_ack(rx_seq)
```
## Проверки и диагностика
### Константы
| Константа | Значение | Описание |
|---|---|---|
| `ROUTER_MAX_INFLIGHT` | 256 | Макс. пакетов в полёте |
| `ROUTER_ACK_INTERVAL_TB` | 100 | ACK интервал 10ms (0.1ms timebase) |
| `ROUTER_ACK_IDLE_TB` | 5000 | Idle таймаут 500ms |
| `ROUTER_RETRANS_TIMEOUT_TB` | 3000 | Таймаут ретрансмита 300ms |
| `ROUTER_NO_ACK_MAX_RETRANS` | 17 | Макс. ретрансмитов без ACK (≈5s) |
| `ROUTER_MAX_SEND_Q_PACKETS` | 64 | Порог backpressure send_q |
| `ROUTER_SEND_WATCHDOG_TB` | 5000 | Watchdog проверка инварианта drain (500ms) |
`tests/test_etcp_router_faults` перехватывает SVC_ROUTE между настоящими роутерами, очередями и UASYNC-таймерами. Физическая очередь тестовая: нижележащий ETCP не скрывает потери от роутера. Проверяются:
### Функции
| Функция | Описание |
|---|---|
| `etcp_router_init(inst)` | Инициализация: bind ETCP_ID_SVC_ROUTE, создание router_conns |
| `etcp_router_destroy(inst)` | Деинициализация: unbind + close_all + free |
| `etcp_router_bind(inst, svc_id, cb)` | Зарегистрировать обработчик сервиса |
| `etcp_router_unbind(inst, svc_id)` | Удалить обработчик сервиса |
| `etcp_route_send(inst, group, dst, entry, force, mode)` | Отправить пакет (loopback/transit/direct); mode = ROUTE_CRYPTO_SIGN/ENCRYPT |
| `etcp_router_conn_get(inst, group, remote, svc_id)` | Найти или создать ROUTER_CONN |
| `etcp_router_conn_send(rconn, data, len, mode)` | Отправить данные с авто-seq и inflight-контролем |
| `etcp_router_conn_close(rconn)` | Закрыть seq-подключение (CLOSE + уведомление сервиса) |
| `etcp_router_conn_close_all_for_node(inst, group, node_id)` | Закрыть все conn к узлу |
| `etcp_router_conn_restart(inst, group, remote, svc_id)` | Сброс состояния (перезапуск удалённой стороны) |
| `etcp_router_pause_retrans_for_node(inst, node_id)` | Сбросить ретрансмиты без закрытия (для conn reinit) |
| `etcp_router_set_max_inflight(rconn, new_max)` | Установить рабочий max_inflight |
| `etcp_router_on_send_ready/cancel_send_ready(inst, group, node, svc, h, cb, arg)` | Backpressure на send_q |
| `etcp_router_transit_queues_destroy(conn)` | Удалить все транзитные очереди ETCP_CONN |
1. Потери DATA/ACK, перестановка, дубликаты, четыре потока (два направления × два сервиса), backpressure и ограниченность физической очереди.
2. Потеря HELLO/CHALLENGE/CONFIRM в обоих направлениях.
3. Старые, повторные, слишком большие ACK и ACK ещё не отправленного пакета.
4. Сохранение inflight при сбросе транспорта.
5. Старые DATA/ACK/CLOSE/HELLO/CONFIRM после смены сессии.
6. Переполнение uint32 seq.
7. Закрытие после смены пути при зарегистрированном и отложенном waiter.
8. Закрытие из callback приёма.
9. Смена маршрута при занятой старой очереди без новой отправки.
10. Исчезновение и восстановление маршрута.
11. Таймаут при непрерывных повторных ACK без прогресса.
12. Одновременный рестарт обоих концов.
13. Граничные размеры payload, crypto overhead, SIZE_MAX.
14. Закрытие из callback производителя при освобождении send_q.
### Модуль route_crypto (пред/пост-обработка пакета)
| Функция | Описание |
|---|---|
| `route_crypto_encode(inst, peer, base, len, mode, &out, &len)` | Шифрование/подпись в начале отправки → финальный пакет |
| `route_crypto_decode(inst, wire, len, &out, &len, &rx_flags)` | Проверка подписи/расшифровка перед коллбэком; ошибка → дроп |
Успех передачи требует совпадения содержимого, порядка и количества, финальных ACK, пустых send/inflight. Завершение проверяет освобождение таймеров. Unit-набор включает 34 проверки, в том числе криптографию и повторную доставку после неверной подписи.
Сетевой `test_etcp_router` использует UDP/dummynet, backpressure и сброс с восстановлением ETCP-handshake. `test_etcp_router_reconnect` проверяет a–b–c и перезапуск b, по 500 пакетов до/после, с ожиданием последних ACK в каждой фазе и фиксированным seed.
Переменная `ROUTER_TEST_LOG=/tmp/router.log` включает подробный файл. Рабочая категория — `etcp_route`; DEBUG показывает handshake, пару эпох, ретрансмиты и смену состояния; поштучные ACK/DATA оставлены на TRACE. Ошибочные ветки не скрыты. В fault-тесте WARN/ERROR для намеренно неверных ACK/размеров и искусственного таймаута ожидаемы.
Предыдущий этап проверки роутера: 14 fault-сценариев и 34 unit-проверки прошли; fault-набор также прошёл ASan/UBSan/LeakSanitizer (инструментированы router, route_crypto и ll_queue, остальная библиотека обычная). UDP-тест: 440/440 доставлено и подтверждено. Транзитный: 1000/1000 доставлено и подтверждено. Это не доказательство отсутствия всех ошибок стека.
## Lifecycle транспорта и восстановление
ETCP имеет единую точку финального освобождения после DELETE, завершения callback и освобождения последней ссылки. Запрос закрытия идемпотентен и сразу запрещает новые send/ref_take.
NCD-entry удерживает одну ссылку ETCP. При DELETE запись сначала исключается из реестра, затем владельцы получают CLOSED; старые handles безопасно закрываются после создания нового соединения с тем же node_id. Закрытые callbacks/handles удаляются после внешней рассылки. Отложенный начальный UP отменяется при DOWN/close. При удалении сокета обход использует снимок ссылок ETCP, поскольку DOWN может удалить NCD-entry.
Recovery удерживает TOPO_NODE для каждого уникального кандидата и освобождает ссылку при любом завершении. При успехе BGP сначала получает собственный NCD-handle. `test_topo_recovery` удаляет B и проверяет самостоятельное подключение A↔C и доставку 1000 сообщений без тестового reconnect.
`etcp_session.c` согласует эпохи физического транспорта через HELLO/CHALLENGE/CONFIRM поверх аутентифицированного UDP/STCP. Каждый DATA/ACK содержит пару эпох; старые кадры отбрасываются до обработки. Сброс потока не генерирует ложный DOWN. REINIT повторно запускает обмен таблицами BGP. Формат и таймауты описаны в `doc/etcp_protocol.txt`.
Дополнительные проверки:
- `test_etcp_lifecycle`: DELETE/refcount, повторное закрытие, закрытие из callback, FIN_WAIT, отмена начального UP, удаление сокета, владение recovery.
- `test_etcp_session`: реальные crypto/ingress/таймеры; reset любой стороны, одновременный reset, потери всех видов control, повторный reset, отказ одного из двух линков, timeout, старые DATA/ACK/control, неверные размеры.
- `test_etcp_router_tcp`: тот же контроль содержимого, порядка, дубликатов, backpressure и финальных ACK по STCP; обе стороны сбрасываются одновременно.
Диагностика по запросу: `SESSION_TEST_LOG=/tmp/session.log`, `LIFECYCLE_TEST_LOG=/tmp/lifecycle.log`, `ROUTER_TEST_LOG=/tmp/router.log`, `BGP_TEST_LOG=/tmp/bgp.log`. `ROUTER_TEST_RESET=client|server|both` выбирает сторону reset; `ROUTER_TEST_TCP=1` включает STCP. Ожидаемые ошибки отрицательных сценариев не скрываются.
Дополнительно исправлен монитор conn_mgr: наличие маршрута через посредника не означает прямого соединения. Фоновый PING выбирает транспорт по node_id конечного peer и живой линк именно этого транспорта. Проверка BGP-треугольника выдерживает полный период фоновых проб и требует отсутствия ошибок входящих пакетов до внесения потерь. На старом monitor эта проверка воспроизводимо падает; с исправлением проходит.
Итоговая проверка текущих исправлений: 16 регрессионных executable-тестов прошли (router unit/faults/lifecycle/session, UDP, TCP, reconnect, recovery, STCP traffic, NCD, три conn_mgr, normalizer/ETCP, BGP triangle). Семь наборов дополнительно прошли ASan/UBSan/LeakSanitizer: lifecycle, session, router faults, UDP router, TCP router, autonomous recovery, BGP triangle. Инструментированы ETCP core/API/connections/session, NCD, router, recovery, topo_group/connect, conn_mgr core/monitor и ll_queue; остальные библиотечные объекты обычные. Отрицательные тесты намеренно вызывают ошибки; после принудительного удаления транспорта возможен отказ приёма запоздалой UDP-датаграммы без существующего линка. Финальный BGP-прогон, включая фоновые пробы и cleanup таймеров, завершился без диагностических ошибок.
+42 -44
View File
@@ -95,7 +95,7 @@ static void topo_group_send_table_complete(struct TOPO_GROUP* group, struct ETCP
if (etcp_send(conn, e) != 0) { u_free(req); queue_entry_free(e); }
}
static void topo_group_add_to_senders(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
static int topo_group_add_to_senders(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
static bool topo_group_should_send_to(const struct TOPO_GROUP_NODE* nq, uint64_t target_id);
static void topo_group_send_full_table(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
static void topo_group_handle_request_table(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
@@ -262,7 +262,8 @@ static void topo_group_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry*
queue_dgram_free(entry); queue_entry_free(entry); return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "BGP recv NODEINFO from=%s nid=%016llx ver=%d grp=%016llx", from_conn->log_name, (unsigned long long)((struct TOPOMSG_NODEINFO_PKT*)data)->node.node_id, ((struct TOPOMSG_NODEINFO_PKT*)data)->node.ver, (unsigned long long)((struct TOPOMSG_NODEINFO_PKT*)data)->node.group_id);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP,"BGP recv %s from=%s group=%016llx len=%u",group_subcmd_name(subcmd),
from_conn->log_name,(unsigned long long)pkt_group_id,entry->len);
if (subcmd == TOPO_SUBCMD_NODEINFO) { nodeinfo_dump_log(data, entry->len); topo_group_process_nodeinfo(group, from_conn, data, entry->len); }
else if (subcmd == TOPO_SUBCMD_WITHDRAW) topo_group_process_withdraw(group, from_conn, data, entry->len);
@@ -290,6 +291,20 @@ static void topo_group_conn_status(struct ETCP_CONN* conn, int status, void* arg
struct TOPO_GROUPS* groups = (struct TOPO_GROUPS*)arg;
if (!conn || !groups) return;
if (status == ETCP_CONN_STATUS_REINIT) {
// Сброс normalizer мог уничтожить TABLE_REQUEST/NODEINFO. Повторяем обмен, сохраняя членство и пути.
for (struct ll_entry* e = groups->group_list->head; e; e = e->next) {
struct TOPO_GROUP* g = (struct TOPO_GROUP*)e;
for (struct ll_entry* se = g->senders_list->head; se; se = se->next) {
if (((struct TOPO_GROUP_CONN_ITEM*)se->data)->conn != conn) continue;
DEBUG_INFO(DEBUG_CATEGORY_BGP,"BGP session resync: peer=%016llx group=%016llx",
(unsigned long long)conn->peer_node_id,(unsigned long long)g->group_id);
topo_group_send_join_group(g,conn); topo_group_send_table_request(g,conn);
break;
}
}
return;
}
if (status == ETCP_CONN_STATUS_UP && conn->peer_node_id) {
int is_client = conn->instance->config && config_peer_in_clients(conn->instance->config, conn->crypto_ctx.peer_public_key);
int explicit_conn = conn->bgp_ready_cbk != NULL;
@@ -680,10 +695,10 @@ void topo_groups_set_node_updated_cb(struct TOPO_GROUPS* groups, topo_node_updat
}
/* Добавляет пира в группу при UP: дедуп, добавление в senders, запрос таблицы. */
void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
if (!group || !conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return; }
if (!conn->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance is NULL"); return; }
if (!conn->instance->rt) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance->rt is NULL"); return; }
int topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
if (!group || !conn) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid args"); return -1; }
if (!conn->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance is NULL"); return -1; }
if (!conn->instance->rt) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance->rt is NULL"); return -1; }
/* дедуп: тот же conn стреляет ETCP_CONN_STATUS_UP дважды (UDP-линк, затем TCP-линк).
* Если conn уже в senders_list — повторно не обрабатываем, иначе active_conn_count
@@ -692,13 +707,12 @@ void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
while (se) {
if (((struct TOPO_GROUP_CONN_ITEM*)se->data)->conn == conn) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group_new_conn: conn already in senders_list, skip double UP (%s)", conn->log_name);
return;
return 0;
}
se = se->next;
}
}
topo_recovery_cancel_for_node(group, conn->peer_node_id);
struct TOPO_GROUP_NODE* peer_nq = topo_node_find_by_id(group, conn->peer_node_id);
if (peer_nq) {
@@ -710,30 +724,14 @@ void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_group_new_conn: ADD conn=%p peer=%016llx state=%d grp=%016llx ch=%s",
(void*)conn, (unsigned long long)conn->peer_node_id, conn->state,
(unsigned long long)group->group_id, group->channel_id);
topo_group_add_to_senders(group, conn);
/* BGP — владелец conn: держим NCD-handle (если ещё нет), чтобы соединение
* не рвалось другими владельцами (conn_mgr) при их закрытии. cb=NULL —
* события BGP уже получает через conn-status callback. */
{ struct ll_entry* se = group->senders_list->head;
while (se) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)se->data;
if (item->conn == conn) {
if (item->handle == NULL) {
node_conn_direct_open(conn->instance, conn->peer_node_id, NULL, NULL, &item->handle, NULL);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "topo_group_new_conn: BGP handle acquired node=0x%016llx h=%p",
(unsigned long long)conn->peer_node_id, (void*)item->handle);
}
break;
}
se = se->next;
}
}
if (topo_group_add_to_senders(group, conn) != 0) return -1;
topo_recovery_cancel_for_node(group, conn->peer_node_id);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id);
topo_group_send_table_request(group, conn);
topo_group_connect_on_up(group, conn);
return 0;
}
/* Обрабатывает DOWN пира: чистит пути, каскадно удаляет узлы, шлёт withdraw. */
@@ -772,7 +770,8 @@ void topo_group_remove_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
nodes_removed++;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node removed from group: node=%016llx grp=%016llx type=%u ch=%s (link_down)",
(unsigned long long)key, (unsigned long long)group->group_id, group->group_type, group->channel_id);
etcp_router_conn_close_all_for_node(group->instance, group->group_id, key);
// REINIT временно убирает путь, но end-to-end данные роутера остаются неподтверждёнными.
if (!conn->reinit_pending) etcp_router_conn_close_all_for_node(group->instance, group->group_id, key);
uint64_t wd_src = (key == conn->peer_node_id) ? conn->instance->node_id : conn->peer_node_id;
topo_group_broadcast_withdraw(group, key, wd_src, NULL);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Removed node %016llx after link down", (unsigned long long)key);
@@ -1169,23 +1168,22 @@ void topo_group_send_nodeinfo(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE*
}
/* Добавляет conn в senders_list (дедуп), если его там ещё нет. */
static void topo_group_add_to_senders(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
if (!group || !conn || !group->senders_list) return;
bool already = false;
struct ll_entry* e = group->senders_list->head;
while (e) { if (((struct TOPO_GROUP_CONN_ITEM*)e->data)->conn == conn) { already = true; break; } e = e->next; }
if (!already) {
struct ll_entry* item_entry = queue_entry_new(sizeof(struct TOPO_GROUP_CONN_ITEM));
if (item_entry) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)item_entry->data;
item->conn = conn;
item->handle = NULL;
queue_data_put(group->senders_list, item_entry);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "senders_add conn=%p peer=%016llx name='%s' grp=%016llx",
(void*)conn, (unsigned long long)conn->peer_node_id, conn->log_name,
(unsigned long long)group->group_id);
}
static int topo_group_add_to_senders(struct TOPO_GROUP* group, struct ETCP_CONN* conn) {
if (!group || !conn || !group->senders_list) return -1;
for (struct ll_entry* e = group->senders_list->head; e; e = e->next)
if (((struct TOPO_GROUP_CONN_ITEM*)e->data)->conn == conn) return 0;
struct ll_entry* e = queue_entry_new(sizeof(struct TOPO_GROUP_CONN_ITEM));
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "senders_add: allocation failed"); return -1; }
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)e->data;
item->conn = conn; item->handle = NULL;
if (node_conn_direct_open(conn->instance, conn->peer_node_id, NULL, NULL, &item->handle, NULL) == NCD_ERR) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "senders_add: cannot acquire NCD handle peer=%016llx", (unsigned long long)conn->peer_node_id);
queue_entry_free(e); return -1;
}
queue_data_put(group->senders_list, e);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "senders_add: acquired peer=%016llx group=%016llx",
(unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id);
return 0;
}
/* Решает, надо ли слать узел пиру (не шлём, если пир уже есть в hop_list пути). */
+1 -1
View File
@@ -281,7 +281,7 @@ void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id);
*
* Вызывается при ETCP on_up.
*/
void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
int topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn);
/**
* @brief Удаляет conn из senders_list, очищает paths во всех nodes, отправляет withdraw если node unreachable.
+5 -3
View File
@@ -358,7 +358,7 @@ static void tgc_release_handle(struct TOPO_GROUP_CONNECT* gc, struct NODE_CONN_D
static void tgc_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg) {
struct TOPO_GROUP_CONNECT* gc = (struct TOPO_GROUP_CONNECT*)arg;
struct ETCP_CONN* conn = node_conn_direct_get_conn(h);
uint64_t node_id = conn ? conn->peer_node_id : 0;
uint64_t node_id = node_conn_direct_node_id(h);
int ok = (event == NCD_EVENT_UP);
@@ -366,9 +366,11 @@ static void tgc_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void*
/* запуск BGP: узел соединён с группой (topo_group_new_conn берёт владение NCD-handle) */
if (conn) topo_group_new_conn(gc->group, conn);
tgc_release_handle(gc, h);
} else if (event == NCD_EVENT_TIMEOUT) {
} else {
// Каждая попытка даёт один результат. Поздние DOWN/CLOSED не должны уменьшать pending повторно.
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(gc->group, node_id);
if (nq) nq->handle = NULL;
tgc_release_handle(gc, h);
}
switch (gc->phase) {
@@ -645,7 +647,7 @@ static void tgc_once_cb(struct NODE_CONN_DIRECT* h, enum ncd_event ev, void* arg
DEBUG_WARN(DEBUG_CATEGORY_BGP, "%s: once-connect TIMEOUT node=0x%016llx ch=%s",
TGC_ID, (unsigned long long)c->node_id, c->group->channel_id);
node_conn_direct_close(h);
} else if (ev == NCD_EVENT_DOWN) {
} else if (ev == NCD_EVENT_DOWN || ev == NCD_EVENT_CLOSED) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "%s: once-connect DOWN node=0x%016llx ch=%s",
TGC_ID, (unsigned long long)c->node_id, c->group->channel_id);
node_conn_direct_close(h);
+2 -2
View File
@@ -380,7 +380,7 @@ static void tgi_ncd_cb(struct NODE_CONN_DIRECT* h, enum ncd_event ev, void* arg)
if (!qe->dgram) { queue_entry_free(qe); tgi_fail(p); return; }
memcpy(qe->dgram, &req, sizeof(req)); qe->len = sizeof(req);
if (etcp_send(conn, qe) != 0) { u_free(qe->dgram); queue_entry_free(qe); tgi_fail(p); }
} else if (ev == NCD_EVENT_TIMEOUT) {
} else if (ev == NCD_EVENT_TIMEOUT || ev == NCD_EVENT_CLOSED) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "invite: ncd TIMEOUT connecting to 0x%016llx", (unsigned long long)p->node_id);
tgi_fail(p);
}
@@ -520,7 +520,7 @@ static void tgi_to_channel_cb(struct NODE_CONN_DIRECT* h, enum ncd_event ev, voi
if (g && conn) topo_group_new_conn(g, conn);
node_conn_direct_close(h);
u_free(c);
} else if (ev == NCD_EVENT_TIMEOUT) {
} else if (ev == NCD_EVENT_TIMEOUT || ev == NCD_EVENT_CLOSED) {
node_conn_direct_force_close(h);
u_free(c);
}
+31 -16
View File
@@ -31,12 +31,20 @@ static struct TOPO_RECOVERY_CTX* topo_recovery_ctx_alloc(struct TOPO_GROUP* grou
ctx->instance = group->instance; ctx->group = group; ctx->next_hop_id = next_hop_id;
ctx->capacity = RECOVERY_CAPACITY_INIT;
ctx->nodes = u_calloc(ctx->capacity, sizeof(struct TOPO_RECOVERY_NODE));
if (!ctx->nodes) { u_free(ctx); return NULL; }
if (!ctx->nodes) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "recovery: nodes allocation failed"); u_free(ctx); return NULL; }
return ctx;
}
static void topo_recovery_remove_candidate(struct TOPO_RECOVERY_CTX* ctx, size_t index) {
uint64_t id = ctx->nodes[index].node_id;
ctx->nodes[index] = ctx->nodes[--ctx->count];
topo_node_registry_unref(ctx->instance->topo_groups, id);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "recovery: release candidate=%016llx remaining=%zu", (unsigned long long)id, ctx->count);
}
static void topo_recovery_ctx_free(struct TOPO_RECOVERY_CTX* ctx) {
if (!ctx) return;
while (ctx->count) topo_recovery_remove_candidate(ctx, ctx->count - 1);
u_free(ctx->nodes);
u_free(ctx);
}
@@ -60,22 +68,27 @@ static void topo_recovery_try_next(struct TOPO_RECOVERY_CTX* ctx);
static void topo_recovery_callback(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg) {
struct TOPO_RECOVERY_CTX* ctx = (struct TOPO_RECOVERY_CTX*)arg;
struct ETCP_CONN* conn = node_conn_direct_get_conn(h);
uint64_t node_id = conn ? conn->peer_node_id : 0;
uint64_t node_id = node_conn_direct_node_id(h);
if (node_id != ctx->current_node_id) return;
if (ctx->connect_timer) { uasync_cancel_timeout(ctx->instance->ua, ctx->connect_timer); ctx->connect_timer = NULL; }
for (size_t i = 0; i < ctx->count; i++)
if (ctx->nodes[i].node_id == node_id) { ctx->nodes[i] = ctx->nodes[ctx->count - 1]; ctx->count--; break; }
ctx->current_node_id = 0;
ctx->current_handle = NULL;
if (event == NCD_EVENT_UP) {
/* владение переходит к topo_group_new_conn (item->handle), connect-фазу закрываем */
if (conn) topo_group_new_conn(ctx->group, conn);
node_conn_direct_close(h);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: next=%016llx reconnected to %016llx", (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id);
} else {
node_conn_direct_close(h);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: next=%016llx connect to %016llx failed: %d", (unsigned long long)ctx->next_hop_id, (unsigned long long)node_id, event);
if (event == NCD_EVENT_UP && conn) {
// Передача владения может отменить другие recovery. Свой ctx исключаем из списка заранее.
topo_recovery_ctx_remove(ctx->group, ctx);
if (topo_group_new_conn(ctx->group, conn) == 0) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: reconnected node=%016llx", (unsigned long long)node_id);
node_conn_direct_close(h);
topo_recovery_ctx_free(ctx);
return;
}
DEBUG_WARN(DEBUG_CATEGORY_BGP, "recovery: BGP ownership transfer failed node=%016llx", (unsigned long long)node_id);
ctx->next = ctx->group->recovery_list; ctx->group->recovery_list = ctx;
}
node_conn_direct_close(h);
for (size_t i = 0; i < ctx->count; i++)
if (ctx->nodes[i].node_id == node_id) { topo_recovery_remove_candidate(ctx, i); break; }
DEBUG_INFO(DEBUG_CATEGORY_BGP, "recovery: attempt finished node=%016llx event=%d", (unsigned long long)node_id, event);
topo_recovery_try_next(ctx);
}
@@ -90,7 +103,7 @@ static void topo_recovery_timeout_cb(void* arg) {
ctx->current_handle = NULL;
}
for (size_t i = 0; i < ctx->count; i++)
if (ctx->nodes[i].node_id == node_id) { ctx->nodes[i] = ctx->nodes[ctx->count - 1]; ctx->count--; break; }
if (ctx->nodes[i].node_id == node_id) { topo_recovery_remove_candidate(ctx, i); break; }
ctx->current_node_id = 0;
topo_recovery_try_next(ctx);
}
@@ -125,7 +138,7 @@ static void topo_recovery_try_next(struct TOPO_RECOVERY_CTX* ctx) {
if (node_conn_direct_open(ctx->instance, node_id, topo_recovery_callback, ctx,
&ctx->current_handle, NULL) == NCD_ERR) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "recovery: node_conn_direct_open %016llx failed", (unsigned long long)node_id);
*best = ctx->nodes[ctx->count - 1]; ctx->count--;
topo_recovery_remove_candidate(ctx, (size_t)(best - ctx->nodes));
ctx->current_node_id = 0;
continue;
}
@@ -143,7 +156,7 @@ static void topo_recovery_try_next(struct TOPO_RECOVERY_CTX* ctx) {
void topo_recovery_add_node(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq, uint64_t next_hop) {
if (!group || !nq) return;
struct TOPO_NODE* ni = topo_node_registry_find(group->instance->topo_groups, nq->node_id);
if (!ni) return;
if (!ni) { DEBUG_WARN(DEBUG_CATEGORY_BGP, "recovery: missing candidate=%016llx", (unsigned long long)nq->node_id); return; }
uint64_t node_id = nq->node_id;
struct TOPO_RECOVERY_CTX* ctx = topo_recovery_find_by_next_hop(group, next_hop);
@@ -154,15 +167,17 @@ void topo_recovery_add_node(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* nq
group->recovery_list = ctx;
}
for (size_t i = 0; i < ctx->count; i++) if (ctx->nodes[i].node_id == node_id) return;
uint16_t rtt = topo_get_chain_rtt(nq);
if (ctx->count >= ctx->capacity) {
size_t new_cap = ctx->capacity * 2;
struct TOPO_RECOVERY_NODE* new_nodes = u_realloc(ctx->nodes, new_cap * sizeof(struct TOPO_RECOVERY_NODE));
if (!new_nodes) return;
if (!new_nodes) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "recovery: candidate allocation failed"); return; }
ctx->nodes = new_nodes; ctx->capacity = new_cap;
}
topo_node_registry_ref(group->instance->topo_groups, node_id);
ctx->nodes[ctx->count].node_id = node_id;
ctx->nodes[ctx->count].min_rtt = rtt;
ctx->nodes[ctx->count].is_next_hop = (uint8_t)(node_id == next_hop ? 1 : 0);
+63 -57
View File
@@ -2,6 +2,7 @@
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_session.h"
#include "etcp_connect.h"
#include "etcp_debug.h"
#include "call_ring.h"
@@ -292,7 +293,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
void etcp_fire_conn_status(struct ETCP_CONN* etcp, int status) {
if (!etcp || !etcp->instance) return;
if (etcp->state == 2 && status != ETCP_CONN_STATUS_DELETE) return; // при state==2 допустим только DELETE
static const char* names[] = { "NEW", "UP", "DOWN", "DELETE" };
static const char* names[] = { "NEW", "UP", "DOWN", "DELETE", "REINIT" };
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "[%s] Connection status changed to %s", etcp->log_name,
(status >= 0 && status < (int)(sizeof(names)/sizeof(names[0]))) ? names[status] : "?");
etcp->callbacks_running++;
@@ -303,14 +304,21 @@ void etcp_fire_conn_status(struct ETCP_CONN* etcp, int status) {
void etcp_cbk_fire(struct ETCP_CONN* conn, int event) {
if (!conn) return;
if (conn->state == 2) return; // после DELETE per-connection события не идут
static const char* names[] = { "INIT", "REINIT", "UP", "DOWN", "NODE_CHANGED" };
if (conn->state == 2 && event != ETCP_CBK_EVENT_DELETE) return;
static const char* names[] = { "INIT", "REINIT", "UP", "DOWN", "NODE_CHANGED", "DELETE" };
int idx = 0, e = event; while (e >>= 1) idx++;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] callback event: %s", conn->log_name, (idx >= 0 && idx < (int)(sizeof(names)/sizeof(names[0]))) ? names[idx] : "?");
conn->callbacks_running++;
struct etcp_cbk_entry* cbe = conn->cbks;
while (cbe) { struct etcp_cbk_entry* n = cbe->next; if (cbe->event_mask & event) cbe->fn(conn, event, cbe->arg); cbe = n; }
while (cbe) { struct etcp_cbk_entry* n = cbe->next; if (cbe->fn && (cbe->event_mask & event)) cbe->fn(conn, event, cbe->arg); cbe = n; }
conn->callbacks_running--;
if (!conn->callbacks_running) {
struct etcp_cbk_entry** pp = &conn->cbks;
while (*pp) {
struct etcp_cbk_entry* e = *pp;
if (!e->fn) { *pp = e->next; u_free(e); } else pp = &e->next;
}
}
}
static void etcp_on_up(struct ETCP_CONN* etcp) {
@@ -394,22 +402,37 @@ static void etcp_connection_free_deferred(void* arg) {
* запрошено внутри цепочки коллбэков (callbacks_running) — синхронное закрытие
* в этом случае освободило бы conn/линки, которые коллбэк-цепочка ещё использует. */
static void etcp_connection_close_deferred(void* arg) {
etcp_connection_close((struct ETCP_CONN*)arg);
struct ETCP_CONN* conn = arg;
conn->close_token = NULL;
etcp_connection_close(conn);
}
// Единственная точка планирования free: release внутри DELETE не опережает phase 1.
static void etcp_connection_maybe_free(struct ETCP_CONN* conn) {
if (!conn->delete_complete || conn->ref_count || conn->callbacks_running || conn->free_scheduled) return;
conn->free_scheduled = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] final free scheduled", conn->log_name);
uasync_call_soon(conn->instance->ua, conn, etcp_connection_free_deferred);
}
// Close connection: phase 1 detach + deferred phase 2 cleanup
void etcp_connection_close(struct ETCP_CONN* etcp) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
if (!etcp) return;
if (etcp->state == 2) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] already deleted", etcp->log_name); return; }
if (etcp->state == 2 || etcp->close_running) return;
callring_put(CR_CCLOSE_IN, (uintptr_t)etcp, 0);
etcp->close_requested = 1;
if (etcp->callbacks_running) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] close requested inside callback chain — deferring", etcp->log_name);
uasync_call_soon(etcp->instance->ua, etcp, etcp_connection_close_deferred);
if (etcp->close_token) return;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] close requested inside callback chain — deferring", etcp->log_name);
etcp->close_token = uasync_call_soon(etcp->instance->ua, etcp, etcp_connection_close_deferred);
return;
}
etcp->close_running = 1;
if (etcp->close_token) { uasync_call_soon_cancel(etcp->instance->ua, etcp->close_token); etcp->close_token = NULL; }
etcp_session_cancel(etcp);
// === PHASE 1: detach from external world ===
/* Финальный DOWN если соединение ещё UP (переход UP→DOWN до state=2):
@@ -427,6 +450,7 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
}
if (etcp->peer_phase_timer) { uasync_cancel_timeout(etcp->instance->ua, etcp->peer_phase_timer); etcp->peer_phase_timer = NULL; }
etcp_cbk_fire(etcp, ETCP_CBK_EVENT_DELETE);
etcp_fire_conn_status(etcp, ETCP_CONN_STATUS_DELETE);
DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] close: state=2 conn=%p peer=%016llx (DELETE fired)", etcp->log_name, (void*)etcp, (unsigned long long)etcp->peer_node_id);
@@ -456,15 +480,15 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
}
// === PHASE 2: deferred resource cleanup (only if no outstanding refs) ===
if (etcp->ref_count == 0)
uasync_call_soon(etcp->instance->ua, etcp, etcp_connection_free_deferred);
// else: cleanup deferred until last etcp_conn_ref_free()
etcp->delete_complete = 1;
etcp->close_running = 0;
etcp_connection_maybe_free(etcp);
}
// Take a reference on connection. Returns -1 if already deleted (state==2).
int etcp_conn_ref_take(struct ETCP_CONN* conn) {
if (!conn) return -1;
if (conn->state == 2) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] ref_take on deleted conn", conn->log_name); return -1; }
if (conn->state == 2 || conn->close_requested) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] ref_take on deleted conn", conn->log_name); return -1; }
conn->ref_count++;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] ref_take ref_count=%d", conn->log_name, conn->ref_count);
return 0;
@@ -476,8 +500,7 @@ void etcp_conn_ref_free(struct ETCP_CONN* conn) {
if (conn->ref_count <= 0) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] ref_free underflow ref_count=%d", conn->log_name, conn->ref_count); return; }
conn->ref_count--;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] ref_free ref_count=%d state=%d", conn->log_name, conn->ref_count, conn->state);
if (conn->ref_count == 0 && conn->state == 2)
uasync_call_soon(conn->instance->ua, conn, etcp_connection_free_deferred);
etcp_connection_maybe_free(conn);
}
// Reset connection
@@ -577,31 +600,9 @@ void etcp_conn_reset(struct ETCP_CONN* etcp) {
etcp->tx_state=ETCP_TX_STATE_DATA_WAIT;
int up=0;
struct ETCP_LINK* link = etcp->links;
while (link) {
if (link->link_status) up=1;
link = link->next;
}
if (up) {
if (etcp->links_up==0) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "reset conn: set link up");
etcp->links_up=1;
etcp_on_up(etcp);
} else {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "reset conn: set link down/up");
etcp_on_down(etcp, NULL);
etcp_on_up(etcp);
}
} else {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "links not up - skip on_down/on_up");
if (etcp->links_up) {
etcp->links_up=0;
etcp->setup_start_tb = get_time_tb();
etcp_on_down(etcp, NULL);
}
}
// Сброс потока не меняет состояние физических линков. Владельцы получают REINIT
// после согласования сессии; DOWN/UP принадлежат только переходам link_status.
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] stream reset: keeping links_up=%u", etcp->log_name, etcp->links_up);
clear_queue(etcp->input_send_q);
clear_queue(etcp->input_wait_ack);
@@ -622,12 +623,19 @@ void etcp_links_reset(struct ETCP_CONN* etcp) {// Если сбой в обме
}
void etcp_conn_reinit(struct ETCP_CONN* etcp, const char* reason) {
etcp_conn_reinit_id(etcp, reason, etcp ? etcp->reset_id : 0);
etcp_conn_fatal_reinit(etcp, reason);
}
// Фатальный ресет (normalizer desync / bad fragment size): меняем эпоху reset_id
void etcp_conn_fatal_reinit(struct ETCP_CONN* etcp, const char* reason) {
uint64_t new_id; random_bytes((uint8_t*)&new_id, 8);
if (!etcp || etcp->close_requested) return;
uint64_t new_id;
do {
if (random_bytes((uint8_t*)&new_id,8) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP,"[%s] reset epoch RNG failed",etcp->log_name); return;
}
} while (!new_id || new_id == etcp->reset_id);
etcp_session_start(etcp);
etcp_conn_reinit_id(etcp, reason, new_id);
}
@@ -672,29 +680,25 @@ void etcp_conn_reinit_id(struct ETCP_CONN* etcp, const char* reason, uint64_t re
void etcp_conn_apply_peer_reset_id(struct ETCP_CONN* conn, uint64_t peer_reset_id) {
if (!conn || peer_reset_id == 0) return; /* 0 = неизвестно (STCP-сервер ещё не создал conn) */
if (peer_reset_id == conn->peer_reset_id) return; // uid пира не изменился
uint64_t old = conn->peer_reset_id;
conn->peer_reset_id = peer_reset_id;
if (old != 0 && conn->got_initial_pkt) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] peer reset id changed to %016llx — resetting self",
conn->log_name, (unsigned long long)peer_reset_id);
etcp_conn_reinit(conn, "peer reset epoch");
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] peer reset id=%016llx (clean, no reset)",
conn->log_name, (unsigned long long)peer_reset_id);
}
if (!conn->peer_reset_id) conn->peer_reset_id = peer_reset_id;
else etcp_session_observe(conn, peer_reset_id); // INIT не доказывает свежесть новой эпохи
}
// внутренняя функция. Вызывается один раз когда первый линк готов.
void etcp_conn_ready(struct ETCP_CONN* conn) {
if (!conn) return;
if (conn->state == 2) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] conn_ready on deleted conn", conn->log_name); return; }
if (conn->initialized) return; // already ready
if (conn->initialized || conn->session_required) return; // ready or awaiting authenticated confirmation
conn->initialized = 1;
conn->reset_done = 1;
if (conn->tx_state == 0) { conn->tx_state = ETCP_TX_STATE_DATA_WAIT; }
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] Connection ready", conn->log_name);
// При асинхронном session handshake физический линк мог стать UP раньше готовности потока.
for (struct ETCP_LINK* link = conn->links; link; link = link->next)
if (link->initialized && link->link_status) conn->links_up = 1;
etcp_conn_queue_set_ready(conn);
// Реинит уже готового соединения (state!=0): etcp_conn_queue_set_ready не сработал
@@ -703,6 +707,7 @@ void etcp_conn_ready(struct ETCP_CONN* conn) {
if (conn->reinit_pending) {
conn->reinit_pending = 0;
etcp_cbk_fire(conn, ETCP_CBK_EVENT_REINIT);
etcp_fire_conn_status(conn, ETCP_CONN_STATUS_REINIT);
}
}
@@ -744,7 +749,8 @@ void etcp_conn_queue_set_ready(struct ETCP_CONN* conn) {
conn->log_name, conn->state, (unsigned long long)conn->peer_node_id);
etcp_cbk_fire(conn, ETCP_CBK_EVENT_INIT);
if (conn->reinit_pending) { conn->reinit_pending = 0; etcp_cbk_fire(conn, ETCP_CBK_EVENT_REINIT); }
if (conn->reinit_pending) { conn->reinit_pending = 0; etcp_cbk_fire(conn, ETCP_CBK_EVENT_REINIT);
etcp_fire_conn_status(conn, ETCP_CONN_STATUS_REINIT); }
if (conn->links_up) etcp_on_up(conn);
}
@@ -1242,9 +1248,9 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
int data_len=0;
if (inf_pkt) data_len=inf_pkt->ll.len;
int remain_len = link->mtu
- 28 /*udp headers*/
- (link->remote_addr.ss_family == AF_INET6 ? IP_UDP_OVERHEAD_V6 : IP_UDP_OVERHEAD_V4)
- SC_NONCE_SIZE - SC_TAG_SIZE - SC_CRC32_SIZE
- (ETCP_ENCRYPTED_HDR_SIZE + ETCP_ACK_BASE_SIZE)
- (ETCP_ENCRYPTED_HDR_SIZE + ETCP_SESSION_HEADER_SIZE + ETCP_ACK_BASE_SIZE)
- (1 + sizeof(inf_pkt->seq)) /*payload hdr*/
- data_len;
// DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "remain_len= %d pl=%d", remain_len, data_len);
@@ -1278,7 +1284,7 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
dgram->data[1]=(ptr - 8) / 8;
int max_enc = link->mtu - 28 - SC_NONCE_SIZE - SC_TAG_SIZE - SC_CRC32_SIZE - ETCP_ENCRYPTED_HDR_SIZE;
int max_enc = link->mtu - WIRE_OVERHEAD(link->remote_addr.ss_family);
if (max_enc > PACKET_DATA_SIZE) max_enc = PACKET_DATA_SIZE;
if (max_enc < 0) max_enc = 0;
uint8_t burst_flags = 0; int pkt_sz_pos = 0;
@@ -1879,7 +1885,7 @@ void etcp_update_mtu(struct ETCP_CONN* etcp) {
int max_udp = IP_UDP_OVERHEAD_V4;
for (struct ETCP_LINK* l = etcp->links; l; l = l->next)
if (l->remote_addr.ss_family == AF_INET6) max_udp = IP_UDP_OVERHEAD_V6;
etcp->normalizer->frag_size = etcp->mtu - ACK_REZERV - max_udp - UDP_SC_HDR_SIZE;
etcp->normalizer->frag_size = etcp->mtu - ACK_REZERV - max_udp - UDP_SC_HDR_SIZE - ETCP_SESSION_HEADER_SIZE;
}
}
}
+11 -2
View File
@@ -197,13 +197,22 @@ struct ETCP_CONN {
uint8_t routing_exchange_active; // 0-не активен, 1-надо инициировать (клиент), 2-обмен идёт, 3-завершён, 4-пропущен (нет BGP)
uint8_t got_initial_pkt; //
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
uint64_t reset_id; // метка эпохи ресета (генерируется при локальном ресете, master авторитет)
uint64_t peer_reset_id; // последняя увиденная эпоха ресета пира
uint64_t reset_id; // локальная эпоха потока (меняется только при локальном reset)
uint64_t peer_reset_id; // подтверждённая эпоха потока пира
uint64_t session_candidate, session_challenge, session_started;
void* session_timer;
uint8_t session_required; // только подтверждение challenge может завершить локальный reset
uint8_t tx_state; // 0 - n/a, 1 - data_wait (queues empty), 2 - link_wait (link busy)
uint8_t links_up; // 0 - канал не готов для передачи, 1 - канал готов для передачи (хотя бы один линк не down)
uint8_t reset_done; // 0 - рукопожатие не завершено (реинит разрешён), 1 - соединение стабильно (реинит заблокирован)
int callbacks_running; // счётчик вложенности колбэк-цепочек (>0 — внутри цепочки, etcp_connection_close откладывается)
uint8_t close_requested; // новые операции запрещены, phase 1 может ждать выхода из callback
uint8_t close_running;
uint8_t delete_complete;
uint8_t free_scheduled;
void* close_token;
// Unified callback chain with event mask (init/reinit/up/down/node_changed)
struct etcp_cbk_entry* cbks;
uint8_t reinit_pending; // 1 = reinit в процессе, ждём завершения
+8 -2
View File
@@ -34,7 +34,13 @@ static void etcp_cbk_remove_from_chain(struct etcp_cbk_entry** head, etcp_cbk_fn
}
void etcp_conn_add_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg, int event_mask) { if (conn) etcp_cbk_add_to_chain(&conn->cbks, fn, arg, event_mask); }
void etcp_conn_remove_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) { if (conn) etcp_cbk_remove_from_chain(&conn->cbks, fn, arg); }
void etcp_conn_remove_cbk(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg) {
if (!conn) return;
if (!conn->callbacks_running) { etcp_cbk_remove_from_chain(&conn->cbks, fn, arg); return; }
for (struct etcp_cbk_entry* e = conn->cbks; e; e = e->next) {
if (e->fn == fn && e->arg == arg) { e->fn = NULL; e->arg = NULL; e->event_mask = 0; return; }
}
}
void etcp_conn_update_cbk_mask(struct ETCP_CONN* conn, etcp_cbk_fn fn, void* arg, int event_mask) {
if (!conn || !fn) return;
struct etcp_cbk_entry* p = conn->cbks;
@@ -171,7 +177,7 @@ int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
/* Владение entry: при успехе entry уходит в очередь (может быть освобождён
* синхронно); при ошибке (-1) entry НЕ освобождается — вызывающий владеет. */
if (!conn || !entry || !conn->send_input_q || conn->state == 2) return -1;
if (!conn || !entry || !conn->send_input_q || conn->state == 2 || conn->close_requested) return -1;
return queue_data_put(conn->send_input_q, entry);
}
+3 -1
View File
@@ -94,6 +94,7 @@ extern "C" {
#define ETCP_CONN_STATUS_NEW 0 // соединение создано
#define ETCP_CONN_STATUS_UP 1 // соединение поднялось
#define ETCP_CONN_STATUS_DOWN 2 // соединение упало
#define ETCP_CONN_STATUS_REINIT 4 // новая сессия на существующем соединении
#define ETCP_CONN_STATUS_DELETE 3 // соединение удалено
// Forward declarations
@@ -141,6 +142,7 @@ struct TOPO_GROUP_NODE;
#define ETCP_CBK_EVENT_UP (1 << 2)
#define ETCP_CBK_EVENT_DOWN (1 << 3)
#define ETCP_CBK_EVENT_NODE_CHANGED (1 << 4)
#define ETCP_CBK_EVENT_DELETE (1 << 5) // терминальное событие, state==2
typedef void (*etcp_cbk_fn)(struct ETCP_CONN* conn, int event, void* arg);
struct etcp_cbk_entry {
@@ -339,7 +341,7 @@ int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id);
* @param fn Коллбэк (сигнатура etcp_conn_status_fn)
* @param arg Пользовательский аргумент
*
* @note status = ETCP_CONN_STATUS_NEW / UP / DOWN / DELETE.
* @note status = ETCP_CONN_STATUS_NEW / UP / DOWN / DELETE / REINIT.
* Строгая последовательность NEW -> UP <-> DOWN -> DELETE; DELETE —
* единственный статус, отправляемый при state==2 (при закрытии).
*/
+77 -42
View File
@@ -53,6 +53,12 @@ static const char* nat_type_str(uint8_t nat_type) {
* Только static-функции, используемые раньше своего определения.
* Не-static функции уже объявлены в etcp_connections.h. */
static void tcp_link_close_cb(struct stcp_link *sl, int err, void *arg);
/* Wire fields may start at any byte offset. */
static uint16_t wire_read16(const void* p) { uint16_t v; memcpy(&v,p,2); return be16toh(v); }
static uint64_t wire_read64(const void* p) { uint64_t v; memcpy(&v,p,8); return be64toh(v); }
static void wire_write16(void* p, uint16_t v) { v = htobe16(v); memcpy(p,&v,2); }
static void wire_write64(void* p, uint64_t v) { v = htobe64(v); memcpy(p,&v,8); }
static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset, uint8_t collision);
static void etcp_link_init_timer_cbk(void* arg);
static void burst_resp_timeout_cb(void* arg);
@@ -104,7 +110,7 @@ void tcp_server_on_link(struct stcp_link *link, struct ETCP_SOCKET *tcp_sock) {
struct ETCP_CONN *conn = instance_find_conn(inst, node_id);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "tcp_server_on_link: %s conn=%p peer=0x%016llx pubkey=%016llx",
conn ? "existing" : "NEW", (void*)conn, (unsigned long long)node_id, *(const uint64_t*)pubkey);
conn ? "existing" : "NEW", (void*)conn, (unsigned long long)node_id, (unsigned long long)wire_read64(pubkey));
if (!conn) {
conn = etcp_connection_create(inst, NULL);
@@ -204,11 +210,11 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset, uint8_t c
struct ETCP_INIT_REQUEST_PKT* req = (struct ETCP_INIT_REQUEST_PKT*)dgram->data;
req->code = reset ? ETCP_INIT_REQUEST : ETCP_INIT_REQUEST_NOINIT;
*(uint64_t*)req->node_id = htobe64(link->etcp->instance->node_id);
*(uint64_t*)req->reset_id = htobe64(link->etcp->reset_id);
*(uint16_t*)req->mtu = htobe16(link->mtu_local);
*(uint16_t*)req->keepalive = htobe16(link->etcp->instance->keepalive_interval);
*(uint16_t*)req->recovery = htobe16(link->recovery_interval / 100);
wire_write64(req->node_id,link->etcp->instance->node_id);
wire_write64(req->reset_id,link->etcp->reset_id);
wire_write16(req->mtu,link->mtu_local);
wire_write16(req->keepalive,link->etcp->instance->keepalive_interval);
wire_write16(req->recovery,link->recovery_interval / 100);
req->link_id = link->local_link_id;
req->socket_id = link->conn ? link->conn->sock_id : 0;
req->only_local = link->conn ? link->conn->only_local : 0;
@@ -220,11 +226,11 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset, uint8_t c
} else if (link->conn && link->conn->interface_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&link->conn->interface_addr;
memcpy(req->src_ipv4, &sin->sin_addr.s_addr, 4);
*(uint16_t*)req->src_port = sin->sin_port;
memcpy(req->src_port,&sin->sin_port,2);
} else if (link->conn && link->conn->interface_addr.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&link->conn->interface_addr;
memset(req->src_ipv4, 0, 4);
*(uint16_t*)req->src_port = sin6->sin6_port;
memcpy(req->src_port,&sin6->sin6_port,2);
} else {
memset(req->src_ipv4, 0, 4);
memset(req->src_port, 0, 2);
@@ -1233,6 +1239,7 @@ void etcp_link_enter_ready_tcp(struct ETCP_LINK *link) {
if (!link->mtu_remote) link->mtu_remote = link->mtu;
etcp->tcp_link_count++;
if (link->tcp_link) { const uint8_t* ed = stcp_link_get_peer_ed25519_pubkey(link->tcp_link); if (ed) memcpy(etcp->peer_ed25519_pubkey, ed, SC_PUBKEY_SIZE); }
if (!etcp->peer_reset_id) etcp_session_start(etcp); // сервер создаёт ETCP_CONN после STCP handshake
if (etcp->initialized == 0) etcp_conn_ready(etcp);
if (etcp->state == 2) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "enter_ready_tcp: conn DELETED after conn_ready, link=%p", (void*)link); return; }
/* в standby анонс спячки вместо обычного flagless-keepalive */
@@ -1319,6 +1326,18 @@ static void tcp_link_close_cb(struct stcp_link *sl, int err, void *arg) {
}
}
// Единый plaintext для UDP/STCP. Эпохи DATA/ACK подписываются вместе с содержимым.
static size_t etcp_encode_plain(struct ETCP_DGRAM* dgram, uint8_t* buf) {
size_t offset = 3, len = dgram->data_len - dgram->noencrypt_len;
memcpy(buf, &dgram->timestamp, 3);
if (len && dgram->data[0] <= 1) {
etcp_session_encode(buf+offset, ETCP_SESSION_DATA, dgram->link->etcp->reset_id, dgram->link->etcp->peer_reset_id);
offset += ETCP_SESSION_HEADER_SIZE;
}
memcpy(buf+offset, dgram->data, len);
return offset+len;
}
// Отправка ETCP_DGRAM по TCP: сборка буфера (timestamp+flag_up+data) и передача в stcp_link_send.
static int etcp_tcp_send(struct ETCP_DGRAM* dgram) {
struct ETCP_LINK *link = dgram->link;
@@ -1326,17 +1345,12 @@ static int etcp_tcp_send(struct ETCP_DGRAM* dgram) {
link->pkt_sent_since_keepalive = 1;
dgram->flag_up = 1;
dgram->timestamp = get_current_timestamp();
size_t total_len = 3 + dgram->data_len;
uint8_t *buf = u_malloc(total_len);
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] TCP send: malloc %zu failed", link->etcp->log_name, total_len); return -1; }
memcpy(buf, &dgram->timestamp, 2);
buf[2] = dgram->flag_up;
if (dgram->data_len) memcpy(buf + 3, dgram->data, dgram->data_len);
uint8_t buf[PACKET_DATA_SIZE + ETCP_SESSION_HEADER_SIZE + 3];
size_t total_len = etcp_encode_plain(dgram, buf);
int rc;
if (link->send_hook) rc = (int)link->send_hook(0, buf, total_len, NULL, 0, link, link->send_hook_ctx);
else if (link->tcp_link) rc = stcp_link_send(link->tcp_link, buf, total_len) == 0 ? (int)dgram->data_len : -1;
else rc = -1;
u_free(buf);
if (rc > 0) link->total_encrypted += (size_t)rc;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] TCP send: link=%d total=%zu dlen=%d rc=%d via=%s",
link->etcp->log_name, link->local_link_id, total_len, dgram->data_len, rc,
@@ -1379,7 +1393,9 @@ int etcp_encrypt_send(struct ETCP_DGRAM* dgram) {
dgram->link->total_encrypted += dgram->data_len;
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO)) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "Before encryption", dgram->data, dgram->data_len);
sc_encrypt(sc, (uint8_t*)&dgram->timestamp, 3 + len, enc_buf, &enc_buf_len);
uint8_t plain[PACKET_DATA_SIZE + ETCP_SESSION_HEADER_SIZE + 3];
size_t plain_len = etcp_encode_plain(dgram, plain);
sc_encrypt(sc, plain, plain_len, enc_buf, &enc_buf_len);
if (enc_buf_len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "eencryption failed for node %016llx", (unsigned long long)dgram->link->etcp->instance->node_id);
dgram->link->send_errors++; errcode=2; goto es_err; }
@@ -1422,7 +1438,9 @@ static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, struct ETCP_SOCKET* e_so
size_t enc_buf_len = 0;
dgram->timestamp = get_current_timestamp();
dgram->flag_up = 1;
sc_encrypt(sc, (uint8_t*)&dgram->timestamp, 3 + len, enc_buf, &enc_buf_len);
uint8_t plain[PACKET_DATA_SIZE + ETCP_SESSION_HEADER_SIZE + 3];
size_t plain_len = etcp_encode_plain(dgram, plain);
sc_encrypt(sc, plain, plain_len, enc_buf, &enc_buf_len);
if (enc_buf_len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "encryption failed for ping");
return -1;
@@ -1677,14 +1695,14 @@ static int handle_ping(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
return 7;
}
uint8_t flags = pkt->data[1];
uint64_t peer_id = be64toh(*(uint64_t*)(pkt->data + 2));
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 10));
uint16_t ulen = be16toh(*(uint16_t*)(pkt->data + 18));
uint64_t peer_id = wire_read64(pkt->data + 2);
uint64_t nonce = wire_read64(pkt->data + 10);
uint16_t ulen = wire_read16(pkt->data + 18);
if (ulen > pkt->data_len - 20) ulen = (uint16_t)(pkt->data_len - 20);
const uint8_t* udata = (ulen > 0) ? (pkt->data + 20) : NULL;
if ((flags & ETCP_PING_FLAG_SEND_RTT) && ulen >= 2) {
uint16_t rtt_val = be16toh(*(uint16_t*)udata);
uint16_t rtt_val = wire_read16(udata);
topo_node_ping_update_rtt(e_sock->instance->topo_groups, peer_id, rtt_val);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "PING rtt=%u from peer=%016llx", (unsigned)rtt_val, (unsigned long long)peer_id);
}
@@ -1741,11 +1759,11 @@ static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
return 7;
}
uint8_t flags = pkt->data[1];
uint64_t nonce = be64toh(*(uint64_t*)(pkt->data + 10));
uint64_t nonce = wire_read64(pkt->data + 10);
uint16_t ulen = 0;
const uint8_t* udata = NULL;
if (pkt->data_len >= 20) {
ulen = be16toh(*(uint16_t*)(pkt->data + 18));
ulen = wire_read16(pkt->data + 18);
if (ulen > 0 && pkt->data_len >= 20 + ulen) {
udata = pkt->data + 20;
}
@@ -1770,7 +1788,7 @@ static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "PONG matched nonce=%016llx rtt=%u want_rtt=%d",
(unsigned long long)nonce, (unsigned)rtt, (flags & ETCP_PING_FLAG_WANT_RTT) ? 1 : 0);
uint64_t pong_peer_id = be64toh(*(uint64_t*)(pkt->data + 2));
uint64_t pong_peer_id = wire_read64(pkt->data + 2);
topo_node_ping_update_rtt(e_sock->instance->topo_groups, pong_peer_id, rtt);
ctx->cb(1, rtt, ctx->arg, nonce, udata, ulen);
@@ -1811,8 +1829,8 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "send init_response without reset");
resp->code = ETCP_INIT_RESPONSE_NOINIT; // 0x05 - without reset
}
*(uint64_t*)resp->node_id = htobe64(e_sock->instance->node_id);
*(uint64_t*)resp->reset_id = htobe64(conn->reset_id);
wire_write64(resp->node_id,e_sock->instance->node_id);
wire_write64(resp->reset_id,conn->reset_id);
resp->mtu[0]=link->mtu_local>>8;
resp->mtu[1]=link->mtu_local;
@@ -1887,7 +1905,7 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk
}
memcpy(resp->ed25519_pubkey, e_sock->instance->my_ed25519_pubkey, SC_PUBKEY_SIZE);
resp->device_type = e_sock->instance->client_type;
*(uint16_t*)resp->keepalive = htobe16(e_sock->instance->keepalive_interval);
wire_write16(resp->keepalive,e_sock->instance->keepalive_interval);
pkt->noencrypt_len=0;
pkt->link=link;
link->recv_keepalive = 1;
@@ -1919,6 +1937,11 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk
etcp_encrypt_send(pkt);
memory_pool_free(e_sock->instance->pkt_pool, pkt);
if (!link->is_server) {
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] link %u accepted incoming INIT: client -> server",
link->etcp->log_name, link->local_link_id);
link->is_server = 1;
}
link->initialized = 1;
{ int old_state = link->link_state; link->link_state = LINK_STATE_CONNECTED; etcp_fire_link_status_cbk(link, old_state, link->link_status); }
if (link->init_timer) {
@@ -1946,11 +1969,11 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
// ETCP_INIT_RESPONSE (0x03) - reset entire ETCP_CONN
// ETCP_INIT_RESPONSE_NOINIT (0x05) - no reset
struct ETCP_INIT_RESPONSE_PKT* resp = (struct ETCP_INIT_RESPONSE_PKT*)pkt->data;
uint64_t server_node_id = be64toh(*(uint64_t*)resp->node_id);
uint64_t resp_reset_id = be64toh(*(uint64_t*)resp->reset_id);
uint64_t server_node_id = wire_read64(resp->node_id);
uint64_t resp_reset_id = wire_read64(resp->reset_id);
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "INIT_RESPONSE rid=%016llx", (unsigned long long)resp_reset_id);
etcp_conn_apply_peer_reset_id(link->etcp, resp_reset_id);
link->mtu_remote = be16toh(*(uint16_t*)resp->mtu);
link->mtu_remote = wire_read16(resp->mtu);
if (link->mtu_remote > PACKET_DATA_MAX_MTU) link->mtu_remote = PACKET_DATA_MAX_MTU;
link->mtu = link->mtu_local < link->mtu_remote ? link->mtu_local : link->mtu_remote;
etcp_update_mtu(link->etcp);
@@ -1964,7 +1987,7 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
if (pkt_len >= ETCP_INIT_RESP_SIZE) {
uint32_t new_nat_ip;
memcpy(&new_nat_ip, resp->peer_ipv4, 4);
uint16_t new_nat_port = be16toh(*(uint16_t*)resp->peer_port);
uint16_t new_nat_port = wire_read16(resp->peer_port);
// Check if NAT address changed
if (link->nat_ip == 0 && link->nat_port == 0) {
@@ -2120,7 +2143,7 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp: DECRYPT FAIL on existing link. rc=%d link=%p log=%s from=%s sess=%d link_state=%d keepalive=%d enc_errs=%zu my_pub=%016llx peer_pub=%016llx seskey=%02x%02x%02x%02x",
dec_rc, link, link->etcp->log_name, sockaddr_storage_to_str(&addr).str, link->etcp->crypto_ctx.session_ready,
link->link_state, link->recv_keepalive, link->encrypt_errors,
*(uint64_t*)link->etcp->crypto_ctx.pk->public_key, *(uint64_t*)link->etcp->crypto_ctx.peer_public_key,
(unsigned long long)wire_read64(link->etcp->crypto_ctx.pk->public_key), (unsigned long long)wire_read64(link->etcp->crypto_ctx.peer_public_key),
link->etcp->crypto_ctx.session_key[0], link->etcp->crypto_ctx.session_key[1],
link->etcp->crypto_ctx.session_key[2], link->etcp->crypto_ctx.session_key[3]);
} else {
@@ -2192,7 +2215,7 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
pkt->data_len=pkt_len-3;
pkt->noencrypt_len=0;
uint8_t code = pkt->data[0];
uint64_t peer_id = be64toh(*(uint64_t*)(pkt->data + 2));
uint64_t peer_id = wire_read64(pkt->data + 2);
if (code == ETCP_PING) {
#ifdef UTUN_HAVE_STANDBY
{ char who[64]; snprintf(who, sizeof(who), "addr=%s", sockaddr_storage_to_str(&addr).str); standby_log_rx("udp ping", who); }
@@ -2260,10 +2283,10 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
}
struct ETCP_INIT_REQUEST_PKT* req = (struct ETCP_INIT_REQUEST_PKT*)pkt->data;
peer_id = be64toh(*(uint64_t*)req->node_id);
uint64_t reset_id = be64toh(*(uint64_t*)req->reset_id);
uint16_t mtu = be16toh(*(uint16_t*)req->mtu);
uint16_t src_port = be16toh(*(uint16_t*)req->src_port);
peer_id = wire_read64(req->node_id);
uint64_t reset_id = wire_read64(req->reset_id);
uint16_t mtu = wire_read16(req->mtu);
uint16_t src_port = wire_read16(req->src_port);
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "INIT received: peer=0x%016llx mtu=%u link=%u sock=%u rid=%016llx src=%s",
(unsigned long long)peer_id, mtu, req->link_id, req->socket_id,
(unsigned long long)reset_id, sockaddr_storage_to_str(&addr).str);
@@ -2309,7 +2332,7 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
errorcode=5;
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "node_changed: different pubkey from %s claiming peer=0x%016llx — conn=%s old_pub=%016llx new_pub=%016llx",
sockaddr_storage_to_str(&addr).str, (unsigned long long)peer_id, conn->log_name,
*(uint64_t*)conn->crypto_ctx.peer_public_key, *(uint64_t*)sc.peer_public_key);
(unsigned long long)wire_read64(conn->crypto_ctx.peer_public_key), (unsigned long long)wire_read64(sc.peer_public_key));
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "peer key mismatch for node %016llx, firing node_changed callbacks", (unsigned long long)peer_id);
conn->callbacks_running++;
etcp_cbk_fire(conn, ETCP_CBK_EVENT_NODE_CHANGED);
@@ -2406,7 +2429,7 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
if (req->collision) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] INIT collision=1 from peer=%016llx — remote is master, becoming slave",
conn->log_name, (unsigned long long)peer_id);
etcp_conn_reinit(conn, "collision yield");
// Коллизия INIT меняет роль линка, но не эпоху надёжного потока.
send_reset = 1;
} else {
// Check if WE have an outbound (master) link on this conn
@@ -2428,7 +2451,7 @@ static void etcp_process_packet(struct ETCP_SOCKET* e_sock, uint8_t* data,
if (conn->got_initial_pkt) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] INIT_REQUEST on existing link: code=0x%02x got_init=%d → reinit",
conn->log_name, req->code, conn->got_initial_pkt);
etcp_conn_reinit(conn, "duplicate INIT");
// Повтор INIT не сбрасывает поток. Новая эпоха требует session challenge.
}
send_reset = 0;
} else {
@@ -2456,7 +2479,7 @@ create_new_link:
if (req->collision) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] INIT collision=1 from peer=%016llx — remote is master, becoming slave",
conn->log_name, (unsigned long long)peer_id);
etcp_conn_reinit(conn, "collision yield");
// Коллизия INIT меняет роль линка, но не эпоху надёжного потока.
send_reset = 1;
} else {
// Check if WE have an outbound (master) link
@@ -2485,7 +2508,7 @@ create_new_link:
etcp_update_mtu(link->etcp);
uint32_t req_src_ip; memcpy(&req_src_ip, req->src_ipv4, 4);
uint16_t req_src_port = be16toh(*(uint16_t*)req->src_port);
uint16_t req_src_port = wire_read16(req->src_port);
memcpy(conn->peer_ed25519_pubkey, req->ed25519_pubkey, SC_PUBKEY_SIZE);
@@ -2514,6 +2537,18 @@ int etcp_packet_decrypted(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
pkt->data_len=pkt_len-3;
pkt->noencrypt_len=0;
pkt->link=link;
if (!pkt->data_len) { DEBUG_WARN(DEBUG_CATEGORY_ETCP,"empty decrypted ETCP packet"); return 46; }
if (etcp_session_receive(link->etcp,pkt->data,pkt->data_len)) {
memory_pool_free(link->etcp->instance->pkt_pool,pkt); return 0;
}
if (pkt->data[0] == ETCP_SESSION_DATA || pkt->data[0] <= 1) {
if (!etcp_session_accept_data(link->etcp,pkt->data,pkt->data_len)) {
memory_pool_free(link->etcp->instance->pkt_pool,pkt); return 0;
}
pkt->data_len -= ETCP_SESSION_HEADER_SIZE;
memmove(pkt->data,pkt->data+ETCP_SESSION_HEADER_SIZE,pkt->data_len);
pkt_len -= ETCP_SESSION_HEADER_SIZE;
}
if (pkt->data_len && pkt->data[0] != ETCP_KEEPALIVE) {
DEBUG_DEBUG(DEBUG_CATEGORY_CRYPTO, "[%s] decrypt: code=%02x dlen=%u plen=%zu recv=%d sock=%p",
link->etcp->log_name, pkt->data[0], pkt->data_len, pkt_len,
+3 -2
View File
@@ -9,6 +9,7 @@ extern "C" {
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
#include "secure_channel.h"
#include "etcp_session.h"
#include "reality.h"
#include "utun_instance.h"
#include "etcp_bbr.h"
@@ -19,8 +20,8 @@ extern "C" {
#define IP_UDP_OVERHEAD_V4 (20 + 8) // IP(20)+UDP(8)
#define IP_UDP_OVERHEAD_V6 (40 + 8) // IP(40)+UDP(8)
#define SC_ENCRYPT_OVERHEAD (ETCP_ENCRYPTED_HDR_SIZE + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE) // 3+13+4+16=36
#define UDP_SC_HDR_SIZE (SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE + 5) // 13+16+4+5=38 — sc overhead без ETCP_HDR + etcp framing
#define SC_ENCRYPT_OVERHEAD (ETCP_ENCRYPTED_HDR_SIZE + ETCP_SESSION_HEADER_SIZE + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE)
#define UDP_SC_HDR_SIZE (SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE + 5)
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
#define WIRE_OVERHEAD(fam) ((fam) == AF_INET6 ? IP_UDP_OVERHEAD_V6 + SC_ENCRYPT_OVERHEAD : IP_UDP_OVERHEAD_V4 + SC_ENCRYPT_OVERHEAD)
+124
View File
@@ -0,0 +1,124 @@
#include "etcp_session.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "../lib/debug_config.h"
#include "../lib/platform_compat.h"
#include <string.h>
#define SESSION_RETRY_TB 5000
#define SESSION_TIMEOUT_TB 100000
static uint64_t read_id(const uint8_t* p) { uint64_t v; memcpy(&v,p,8); return be64toh(v); }
static void write_id(uint8_t* p, uint64_t v) { v = htobe64(v); memcpy(p,&v,8); }
void etcp_session_encode(uint8_t* data, uint8_t code, uint64_t sender, uint64_t target) {
data[0] = code; write_id(data+1,sender); write_id(data+9,target);
}
static void session_send(struct ETCP_CONN* c, uint8_t code, uint64_t peer, uint64_t cookie) {
for (struct ETCP_LINK* link = c->links; link; link = link->next) {
if (!link->initialized) continue;
struct ETCP_DGRAM* pkt = memory_pool_alloc(c->instance->pkt_pool);
if (!pkt) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP,"[%s] session: packet allocation failed",c->log_name); return; }
pkt->link = link; pkt->data_len = ETCP_SESSION_CONTROL_SIZE; pkt->noencrypt_len = 0;
etcp_session_encode(pkt->data,code,c->reset_id,peer); write_id(pkt->data+17,cookie);
if (etcp_encrypt_send(pkt) < 0)
DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] session: send failed code=%u link=%u",c->log_name,code,link->local_link_id);
memory_pool_free(c->instance->pkt_pool,pkt);
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session TX code=%u local=%016llx peer=%016llx cookie=%016llx",
c->log_name,code,(unsigned long long)c->reset_id,(unsigned long long)peer,(unsigned long long)cookie);
}
void etcp_session_cancel(struct ETCP_CONN* c) {
if (c->session_timer) { uasync_cancel_timeout(c->instance->ua,c->session_timer); c->session_timer = NULL; }
c->session_candidate = c->session_challenge = 0;
}
static void session_tick(void* arg) {
struct ETCP_CONN* c = arg;
c->session_timer = NULL;
if (c->close_requested) return;
if (get_time_tb() - c->session_started >= SESSION_TIMEOUT_TB) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] session confirmation timeout required=%u candidate=%016llx",c->log_name,
c->session_required,(unsigned long long)c->session_candidate);
etcp_session_cancel(c);
if (c->session_required) etcp_connection_close(c);
return;
}
if (c->session_required) session_send(c,ETCP_SESSION_HELLO,c->peer_reset_id,0);
if (c->session_candidate) session_send(c,ETCP_SESSION_CHALLENGE,c->session_candidate,c->session_challenge);
c->session_timer = uasync_set_timeout(c->instance->ua,SESSION_RETRY_TB,c,session_tick,"etcp_session");
}
static void session_schedule(struct ETCP_CONN* c) {
if (c->session_timer) return;
c->session_started = get_time_tb();
c->session_timer = uasync_set_timeout(c->instance->ua,1,c,session_tick,"etcp_session");
}
void etcp_session_start(struct ETCP_CONN* c) {
etcp_session_cancel(c); c->session_required = 1;
DEBUG_INFO(DEBUG_CATEGORY_ETCP,"[%s] session negotiation started local=%016llx peer=%016llx",c->log_name,
(unsigned long long)c->reset_id,(unsigned long long)c->peer_reset_id);
session_schedule(c);
}
void etcp_session_observe(struct ETCP_CONN* c, uint64_t peer_epoch) {
if (!peer_epoch || c->close_requested) return;
if (c->session_candidate != peer_epoch) {
c->session_candidate = peer_epoch;
do {
if (random_bytes((uint8_t*)&c->session_challenge,8) != 0) {
c->session_candidate = 0;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP,"[%s] session challenge RNG failed",c->log_name); return;
}
} while (!c->session_challenge);
}
session_schedule(c);
}
int etcp_session_receive(struct ETCP_CONN* c, const uint8_t* data, size_t len) {
if (!len || data[0] < ETCP_SESSION_HELLO || data[0] > ETCP_SESSION_CONFIRM) return 0;
if (len != ETCP_SESSION_CONTROL_SIZE || c->close_requested) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] invalid session control size=%zu",c->log_name,len); return 1;
}
uint64_t peer = read_id(data+1), target = read_id(data+9), cookie = read_id(data+17);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session RX code=%u peer=%016llx target=%016llx cookie=%016llx",
c->log_name,data[0],(unsigned long long)peer,(unsigned long long)target,(unsigned long long)cookie);
if (!peer) { DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] session: zero peer epoch",c->log_name); return 1; }
if (data[0] == ETCP_SESSION_HELLO) {
etcp_session_observe(c,peer);
if (c->session_challenge) session_send(c,ETCP_SESSION_CHALLENGE,peer,c->session_challenge);
} else if (target != c->reset_id || !cookie) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session: stale target or empty cookie",c->log_name);
} else if (data[0] == ETCP_SESSION_CHALLENGE) {
session_send(c,ETCP_SESSION_CONFIRM,peer,cookie);
if (c->session_required || peer != c->peer_reset_id) {
etcp_session_observe(c,peer);
if (c->session_challenge) session_send(c,ETCP_SESSION_CHALLENGE,peer,c->session_challenge);
}
} else if (peer == c->session_candidate && cookie == c->session_challenge) {
etcp_session_cancel(c);
if (peer != c->peer_reset_id) etcp_conn_reinit_id(c,"confirmed peer epoch",c->reset_id);
c->peer_reset_id = peer; c->session_required = 0;
DEBUG_INFO(DEBUG_CATEGORY_ETCP,"[%s] session confirmed local=%016llx peer=%016llx",c->log_name,
(unsigned long long)c->reset_id,(unsigned long long)peer);
etcp_conn_ready(c);
} else {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] session: stale confirmation",c->log_name);
}
return 1;
}
int etcp_session_accept_data(struct ETCP_CONN* c, const uint8_t* data, size_t len) {
if (len <= ETCP_SESSION_HEADER_SIZE || data[0] != ETCP_SESSION_DATA || data[ETCP_SESSION_HEADER_SIZE] > 1) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP,"[%s] invalid session data size=%zu",c->log_name,len); return 0;
}
uint64_t peer = read_id(data+1), target = read_id(data+9);
if (peer != c->peer_reset_id || target != c->reset_id || c->reinit_pending || !c->initialized) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP,"[%s] discard stale data peer=%016llx target=%016llx pending=%u",c->log_name,
(unsigned long long)peer,(unsigned long long)target,c->reinit_pending);
return 0;
}
return 1;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef ETCP_SESSION_H
#define ETCP_SESSION_H
#include <stddef.h>
#include <stdint.h>
struct ETCP_CONN;
#define ETCP_SESSION_DATA 0x09
#define ETCP_SESSION_HELLO 0x0a
#define ETCP_SESSION_CHALLENGE 0x0b
#define ETCP_SESSION_CONFIRM 0x0c
#define ETCP_SESSION_HEADER_SIZE 17
#define ETCP_SESSION_CONTROL_SIZE 25
/* Authenticated link control, independent of the reliable stream and its normalizer. */
void etcp_session_start(struct ETCP_CONN* conn);
void etcp_session_observe(struct ETCP_CONN* conn, uint64_t peer_epoch);
void etcp_session_cancel(struct ETCP_CONN* conn);
int etcp_session_receive(struct ETCP_CONN* conn, const uint8_t* data, size_t len);
void etcp_session_encode(uint8_t* data, uint8_t code, uint64_t sender, uint64_t target);
int etcp_session_accept_data(struct ETCP_CONN* conn, const uint8_t* data, size_t len);
#endif
+149 -232
View File
@@ -38,6 +38,9 @@ struct ncd_entry {
uint64_t node_id;
struct ETCP_CONN* conn;
struct UASYNC* ua;
struct UTUN_INSTANCE* inst;
unsigned dispatch_depth;
uint8_t closed;
int handle_count;
struct NODE_CONN_DIRECT* handles; /* связный список всех handle'ов */
@@ -55,6 +58,7 @@ struct NODE_CONN_DIRECT {
ncd_callback cb;
void* cb_arg;
struct NODE_CONN_DIRECT* next;
uint8_t closed;
void* deliver_up_token; /* handle uasync_call_soon отложенной доставки UP (для отмены при close) */
};
@@ -65,7 +69,10 @@ static void ncd_deliver_up_cb(void* arg);
static void ncd_init_cb(struct ETCP_CONN* conn, int event, void* arg);
static void ncd_up_cb(struct ETCP_CONN* conn, int event, void* arg);
static void ncd_down_cb(struct ETCP_CONN* conn, int event, void* arg);
static void ncd_deferred_close(void* arg);
static void ncd_delete_cb(struct ETCP_CONN* conn, int event, void* arg);
static void ncd_finish(struct ncd_entry* entry, int close_conn);
static void ncd_collect(struct ncd_entry* entry);
static void ncd_cancel_deliver_up(struct NODE_CONN_DIRECT* h, struct UASYNC* ua);
static void ncd_deferred_close_conn(void* arg);
/* ═══════════ реестр ═══════════ */
@@ -77,11 +84,15 @@ static void ncd_deferred_close_conn(void* arg);
static struct ncd_entry* ncd_registry_find(struct UTUN_INSTANCE* inst, uint64_t node_id) {
struct ncd_entry* e = (struct ncd_entry*)inst->ncd_registry;
while (e) { if (e->node_id == node_id) return e; e = e->next; }
while (e) { if (e->node_id == node_id && !e->closed && !e->conn->close_requested && e->conn->state != 2) return e; e = e->next; }
return NULL;
}
static void ncd_registry_add(struct UTUN_INSTANCE* inst, struct ncd_entry* entry) {
static int ncd_registry_add(struct UTUN_INSTANCE* inst, struct ncd_entry* entry) {
entry->inst = inst;
if (etcp_conn_ref_take(entry->conn) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] register closing conn"); return -1; }
etcp_conn_add_cbk(entry->conn, ncd_delete_cb, entry, ETCP_CBK_EVENT_DELETE);
entry->next = (struct ncd_entry*)inst->ncd_registry; inst->ncd_registry = entry;
return 0;
}
static void ncd_registry_remove(struct UTUN_INSTANCE* inst, struct ncd_entry* entry) {
struct ncd_entry** pp = (struct ncd_entry**)&inst->ncd_registry;
@@ -263,36 +274,41 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
*/
static void ncd_event_dispatch(struct ncd_entry* entry, enum ncd_event event) {
if (!entry) return;
if (!entry || (entry->closed && event != NCD_EVENT_CLOSED)) return;
switch (event) {
case NCD_EVENT_UP:
if (entry->up) return;
if (entry->timed_out) DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] connected after timeout node=%016llx",
(unsigned long long)entry->node_id);
entry->timed_out = 0;
if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; }
entry->up = 1;
break;
entry->up = 1; break;
case NCD_EVENT_DOWN:
if (!entry->up) return;
entry->up = 0;
break;
entry->up = 0; break;
case NCD_EVENT_TIMEOUT:
if (entry->timed_out) return;
entry->timed_out = 1;
entry->connect_timer = NULL;
break;
entry->timed_out = 1; entry->connect_timer = NULL; break;
case NCD_EVENT_CLOSED: entry->up = 0; break;
}
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] event=%s node=0x%016llx handles=%d",
event == NCD_EVENT_UP ? "UP" : event == NCD_EVENT_DOWN ? "DOWN" : "TIMEOUT",
(unsigned long long)entry->node_id, entry->handle_count);
struct NODE_CONN_DIRECT* h = entry->handles;
while (h) { struct NODE_CONN_DIRECT* next = h->next; if (h->cb) h->cb(h, event, h->cb_arg); h = next; }
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] event=%d node=%016llx handles=%d depth=%u", event,
(unsigned long long)entry->node_id, entry->handle_count, entry->dispatch_depth);
entry->dispatch_depth++;
// Закрытые элементы не освобождаются до выхода из внешней рассылки. Новые добавляются в голову.
for (struct NODE_CONN_DIRECT* h = entry->handles; h; h = h->next) {
if (event != NCD_EVENT_UP) ncd_cancel_deliver_up(h, entry->ua);
if (!h->closed && h->cb && (!entry->closed || event == NCD_EVENT_CLOSED)) h->cb(h, event, h->cb_arg);
}
entry->dispatch_depth--;
ncd_collect(entry);
}
/* ═══════════ ETCP коллбэки (прокидывают в ncd_event_dispatch) ═══════════ */
/*
* ETCP сообщил что соединение работает: INIT — handshake завершён,
* UP — линки восстановились после DOWN. Транслируем в NCD_EVENT_UP.
* Не транслируем если уже сработал таймаут подключения (timed_out)
* или conn в состоянии fin_wait (ждём закрытия).
* TIMEOUT сообщает об истечении срока первой попытки, но удерживаемый handle
* продолжает следить за транспортом (в том числе при REVERSE). FIN_WAIT подавляет UP.
* Если INIT пришёл а линков нет — UP не доставляем: ncd_up_cb сам
* доставит когда линки поднимутся. Так гарантируем что пользователь
* получает UP только когда conn реально готов отправлять данные.
@@ -300,7 +316,7 @@ static void ncd_event_dispatch(struct ncd_entry* entry, enum ncd_event event) {
static void ncd_init_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event;
struct ncd_entry* entry = (struct ncd_entry*)arg;
if (!entry || entry->timed_out || conn->fin_wait) return;
if (!entry || conn->fin_wait) return;
if (conn->links_up > 0) ncd_event_dispatch(entry, NCD_EVENT_UP);
}
static void ncd_up_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)event;
@@ -308,36 +324,56 @@ static void ncd_up_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)even
if (!entry || conn->fin_wait) return;
ncd_event_dispatch(entry, NCD_EVENT_UP);
}
/*
* Отложенное (uasync_call_soon) уничтожение ncd_entry вместе с conn.
* Нельзя вызывать напрямую из ETCP-коллбэка: conn может использоваться
* после возврата из коллбэка. Поэтому очистка откладывается на следующий цикл событий.
* Снимает все ETCP-коллбэки, удаляет из реестра, закрывает conn, освобождает память.
*/
static void ncd_deferred_close(void* arg) {
struct ncd_entry* entry = (struct ncd_entry*)arg;
struct ETCP_CONN* conn = entry->conn;
// Каждый отложенный запрос удерживает ETCP до завершения callback, в том числе при повторном close.
static void ncd_deferred_close_conn(void* arg) {
struct ETCP_CONN* conn = arg;
etcp_connection_close(conn);
etcp_conn_ref_free(conn);
}
static void ncd_schedule_close(struct ETCP_CONN* conn) {
if (!conn || conn->close_requested || conn->state == 2) return;
if (etcp_conn_ref_take(conn) != 0) return;
conn->close_requested = 1;
uasync_call_soon(conn->instance->ua, conn, ncd_deferred_close_conn);
}
static void ncd_collect(struct ncd_entry* entry) {
if (entry->dispatch_depth) return;
struct NODE_CONN_DIRECT** pp = &entry->handles;
while (*pp) {
struct NODE_CONN_DIRECT* h = *pp;
if (h->closed) { *pp = h->next; u_free(h); } else pp = &h->next;
}
if (entry->closed && !entry->handle_count) {
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] free node=%016llx", (unsigned long long)entry->node_id);
u_free(entry);
}
}
// Сначала исключение из реестра и отсоединение транспорта, затем уведомление владельцев.
static void ncd_finish(struct ncd_entry* entry, int close_conn) {
if (entry->closed) return;
entry->closed = 1;
ncd_registry_remove(entry->inst, entry);
if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; }
if (entry->fin_wait_timer) { uasync_cancel_timeout(entry->ua, entry->fin_wait_timer); entry->fin_wait_timer = NULL; }
struct ETCP_CONN* conn = entry->conn; entry->conn = NULL;
if (conn) {
conn->fin_wait = 0; conn->fin_wait_clear_cb = NULL; conn->fin_wait_clear_arg = NULL;
etcp_conn_remove_cbk(conn, ncd_init_cb, entry);
etcp_conn_remove_cbk(conn, ncd_up_cb, entry);
etcp_conn_remove_cbk(conn, ncd_down_cb, entry);
ncd_registry_remove(conn->instance, entry);
etcp_connection_close(conn);
etcp_conn_remove_cbk(conn, ncd_delete_cb, entry);
if (close_conn) ncd_schedule_close(conn);
etcp_conn_ref_free(conn);
}
u_free(entry);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] detached node=%016llx handles=%d", (unsigned long long)entry->node_id, entry->handle_count);
ncd_event_dispatch(entry, NCD_EVENT_CLOSED); // collect может освободить entry
}
/*
* Отложенное закрытие conn без ncd_entry.
* Используется когда пришёл CLOSE от пира, а ncd_entry для этого пира уже нет
* (закрыли раньше — например другой модуль уже удалил все handle'ы).
*/
static void ncd_deferred_close_conn(void* arg) {
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg;
callring_put(CR_NCD_DEFER, (uintptr_t)conn, 0);
if (conn) etcp_connection_close(conn);
static void ncd_delete_cb(struct ETCP_CONN* conn, int event, void* arg) {
(void)conn; (void)event; ncd_finish(arg, 0);
}
/*
* ETCP сообщил что все линки упали.
* Если conn в fin_wait и handle'ов нет — peer подтвердил закрытие,
@@ -350,7 +386,7 @@ static void ncd_down_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)ev
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] DOWN during fin_wait, cleaning up node=0x%016llx", (unsigned long long)entry->node_id);
conn->fin_wait = 0; conn->fin_wait_clear_cb = NULL; conn->fin_wait_clear_arg = NULL;
if (entry->fin_wait_timer) { uasync_cancel_timeout(entry->ua, entry->fin_wait_timer); entry->fin_wait_timer = NULL; }
uasync_call_soon(entry->ua, entry, ncd_deferred_close);
ncd_finish(entry, 1);
return;
}
ncd_event_dispatch(entry, NCD_EVENT_DOWN);
@@ -359,8 +395,8 @@ static void ncd_down_cb(struct ETCP_CONN* conn, int event, void* arg) { (void)ev
/* ═══════════ таймер подключения ═══════════ */
/*
* Таймер первого подключения истёк — соединение не установилось за etcp_connect_timeout_tb.
* Транслирует NCD_EVENT_TIMEOUT всем handle'ам. После этого init/up коллбэки
* больше не транслируются (entry->timed_out = 1).
* Транслирует NCD_EVENT_TIMEOUT один раз. Владелец может закрыть handle или
* оставить его для позднего UP; окончательное завершение обозначается CLOSED.
*/
static void ncd_connect_timeout_cb(void* arg) {
@@ -383,11 +419,13 @@ static void ncd_connect_timeout_cb(void* arg) {
static void ncd_deliver_up_cb(void* arg) {
struct NODE_CONN_DIRECT* h = (struct NODE_CONN_DIRECT*)arg;
h->deliver_up_token = NULL; /* токен consumed — close больше не отменит освобождённый узел */
if (h->cb) h->cb(h, NCD_EVENT_UP, h->cb_arg);
if (!h->closed && !h->entry->closed && h->entry->up && !h->entry->timed_out && h->cb)
h->cb(h, NCD_EVENT_UP, h->cb_arg);
}
/* Планирует отложенную доставку UP и сохраняет токен для возможной отмены при close. */
static void ncd_schedule_deliver_up(struct UASYNC* ua, struct NODE_CONN_DIRECT* h) {
ncd_cancel_deliver_up(h, ua);
h->deliver_up_token = uasync_call_soon(ua, h, ncd_deliver_up_cb);
}
@@ -427,13 +465,7 @@ static void ncd_fin_wait_timeout_cb(void* arg) {
entry->fin_wait_timer = NULL;
if (!entry->conn->fin_wait) return;
DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] fin_wait timeout, force close node=0x%016llx", (unsigned long long)entry->node_id);
entry->conn->fin_wait = 0; entry->conn->fin_wait_clear_cb = NULL; entry->conn->fin_wait_clear_arg = NULL;
etcp_conn_remove_cbk(entry->conn, ncd_init_cb, entry);
etcp_conn_remove_cbk(entry->conn, ncd_up_cb, entry);
etcp_conn_remove_cbk(entry->conn, ncd_down_cb, entry);
etcp_connection_close(entry->conn);
ncd_registry_remove(entry->conn->instance, entry);
u_free(entry);
ncd_finish(entry, 1);
}
/* ═══════════ приём CLOSE / KEEP_ALIVE ═══════════ */
@@ -457,13 +489,17 @@ static void ncd_recv_control_handler(struct ETCP_CONN* conn, struct ll_entry* e)
struct ncd_control_msg* msg = (struct ncd_control_msg*)e->dgram;
uint64_t sender_id = msg->node_id;
struct ncd_entry* entry = ncd_registry_find(conn->instance, sender_id);
if (sender_id != conn->peer_node_id || (entry && entry->conn != conn)) {
DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] control from wrong transport node=%016llx", (unsigned long long)sender_id);
queue_dgram_free(e); queue_entry_free(e); return;
}
switch (msg->subcmd) {
case NCD_SUBCMD_CLOSE:
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] recv CLOSE from 0x%016llx", (unsigned long long)sender_id);
if (!entry) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] no entry for CLOSE sender 0x%016llx, deferred close conn", (unsigned long long)sender_id);
if (conn) uasync_call_soon(conn->instance->ua, conn, ncd_deferred_close_conn);
ncd_schedule_close(conn);
break;
}
if (entry->handle_count > 0) {
@@ -479,12 +515,7 @@ static void ncd_recv_control_handler(struct ETCP_CONN* conn, struct ll_entry* e)
}
} else {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] deferred close entry for CLOSE from 0x%016llx", (unsigned long long)sender_id);
if (entry->conn && entry->conn->conn_queue && entry->conn->conn_queue_entry) {
queue_remove_data(entry->conn->conn_queue, entry->conn->conn_queue_entry);
queue_entry_free(entry->conn->conn_queue_entry);
entry->conn->conn_queue_entry = NULL; entry->conn->conn_queue = NULL;
}
uasync_call_soon(entry->ua, entry, ncd_deferred_close);
ncd_finish(entry, 1);
}
break;
case NCD_SUBCMD_KEEP_ALIVE:
@@ -531,7 +562,9 @@ static void ncd_revive_entry(struct ncd_entry* entry, struct UTUN_INSTANCE* inst
if (!entry) return;
if (entry->conn && entry->conn->links_up > 0) {
entry->timed_out = 0;
ncd_event_dispatch(entry, NCD_EVENT_UP);
entry->up = 1;
for (struct NODE_CONN_DIRECT* h = entry->handles; h; h = h->next)
if (!h->closed) ncd_schedule_deliver_up(entry->ua, h);
return;
}
struct ETCP_LINK* l = entry->conn ? entry->conn->links : NULL;
@@ -605,7 +638,7 @@ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id,
/* 2. Ищем conn через instance_find_conn (входящее / созданное etcp_connect) */
struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
if (conn && conn->state == 1) {
if (conn && conn->state == 1 && !conn->close_requested) {
/* снять fin_wait если был */
if (conn->fin_wait) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open REUSED clearing fin_wait (existing conn) node=0x%016llx", (unsigned long long)node_id);
@@ -615,11 +648,11 @@ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id,
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] alloc entry failed node=0x%016llx", (unsigned long long)node_id); return NCD_ERR; }
entry->node_id = node_id; entry->conn = conn; entry->ua = inst->ua;
entry->up = (uint8_t)(conn->links_up ? 1 : 0);
ncd_registry_add(inst, entry);
if (ncd_registry_add(inst, entry) < 0) { u_free(entry); return NCD_ERR; }
struct NODE_CONN_DIRECT* h = u_calloc(1, sizeof(*h));
if (!h) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] alloc handle failed node=0x%016llx", (unsigned long long)node_id);
ncd_registry_remove(inst, entry); u_free(entry); return NCD_ERR; }
ncd_finish(entry, 0); return NCD_ERR; }
h->entry = entry; h->cb = cb; h->cb_arg = cb_arg;
h->next = entry->handles; entry->handles = h;
entry->handle_count = 1;
@@ -691,11 +724,13 @@ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id,
etcp_connection_close(conn); topo_node_registry_unref(inst->topo_groups, ni->node_id); return NCD_ERR;
}
entry->node_id = node_id; entry->conn = conn; entry->ua = inst->ua; entry->up = 0;
ncd_registry_add(inst, entry);
if (ncd_registry_add(inst, entry) < 0) {
u_free(entry); etcp_connection_close(conn); topo_node_registry_unref(inst->topo_groups, ni->node_id); return NCD_ERR;
}
struct NODE_CONN_DIRECT* h = u_calloc(1, sizeof(*h));
if (!h) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] alloc handle failed node=0x%016llx", (unsigned long long)node_id);
ncd_registry_remove(inst, entry); u_free(entry); etcp_connection_close(conn); topo_node_registry_unref(inst->topo_groups, ni->node_id); return NCD_ERR; }
ncd_finish(entry, 0); etcp_connection_close(conn); topo_node_registry_unref(inst->topo_groups, ni->node_id); return NCD_ERR; }
h->entry = entry; h->cb = cb; h->cb_arg = cb_arg;
h->next = entry->handles; entry->handles = h;
entry->handle_count = 1;
@@ -762,7 +797,7 @@ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
/* 2. Ищем conn через instance_find_conn */
{ struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
if (conn && conn->state != 2) {
if (conn && conn->state != 2 && !conn->close_requested) {
if (conn->fin_wait) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] open_node REUSED clearing fin_wait (existing conn) node=0x%016llx", (unsigned long long)node_id);
conn->fin_wait = 0; conn->fin_wait_clear_cb = NULL; conn->fin_wait_clear_arg = NULL;
@@ -771,11 +806,11 @@ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] open_node alloc entry failed node=0x%016llx", (unsigned long long)node_id); return NCD_ERR; }
entry->node_id = node_id; entry->conn = conn; entry->ua = inst->ua;
entry->up = (uint8_t)(conn->links_up ? 1 : 0);
ncd_registry_add(inst, entry);
if (ncd_registry_add(inst, entry) < 0) { u_free(entry); return NCD_ERR; }
struct NODE_CONN_DIRECT* h = u_calloc(1, sizeof(*h));
if (!h) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] open_node alloc handle failed node=0x%016llx", (unsigned long long)node_id);
ncd_registry_remove(inst, entry); u_free(entry); return NCD_ERR; }
ncd_finish(entry, 0); return NCD_ERR; }
h->entry = entry; h->cb = cb; h->cb_arg = cb_arg;
h->next = entry->handles; entry->handles = h;
entry->handle_count = 1;
@@ -836,11 +871,11 @@ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
etcp_connection_close(conn); return NCD_ERR;
}
entry->node_id = node_id; entry->conn = conn; entry->ua = inst->ua; entry->up = 0;
ncd_registry_add(inst, entry);
if (ncd_registry_add(inst, entry) < 0) { u_free(entry); etcp_connection_close(conn); return NCD_ERR; }
struct NODE_CONN_DIRECT* h = u_calloc(1, sizeof(*h));
if (!h) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] open_node alloc handle failed node=0x%016llx", (unsigned long long)node_id);
ncd_registry_remove(inst, entry); u_free(entry); etcp_connection_close(conn); return NCD_ERR; }
ncd_finish(entry, 0); etcp_connection_close(conn); return NCD_ERR; }
h->entry = entry; h->cb = cb; h->cb_arg = cb_arg;
h->next = entry->handles; entry->handles = h;
entry->handle_count = 1;
@@ -876,161 +911,36 @@ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
* просто без наших handle'ов.
* - Если пир не ответит — conn закрывается форсированно по таймауту.
*/
void node_conn_direct_close(struct NODE_CONN_DIRECT* h) {
if (!h) return;
struct ncd_entry* entry = h->entry;
if (!entry) { u_free(h); return; }
ncd_cancel_deliver_up(h, entry->ua);
uint64_t node_id = entry->node_id;
struct ETCP_CONN* conn = entry->conn;
/* Удаляем handle из списка */
struct NODE_CONN_DIRECT** pp = &entry->handles;
int found = 0;
while (*pp) { if (*pp == h) { *pp = h->next; found = 1; break; } pp = &(*pp)->next; }
if (!found) {
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] close: h=%p NOT in handles list node=0x%016llx (double-close?)",
(void*)h, (unsigned long long)node_id);
}
entry->handle_count--;
if (entry->handle_count < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] FATAL: handle_count=%d < 0 h=%p node=0x%016llx",
entry->handle_count, (void*)h, (unsigned long long)node_id);
h->entry = NULL; u_free(h); return;
}
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] close h=%p node=0x%016llx remaining=%d", (void*)h, (unsigned long long)node_id, entry->handle_count);
if (entry->handle_count == 0) {
if (entry->handles) {
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] FATAL: handle_count=0 but handles non-empty node=0x%016llx",
(unsigned long long)node_id);
entry->handles = NULL;
}
h->cb = NULL; h->cb_arg = NULL;
if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; }
if (conn && conn->state != 2 && !entry->timed_out && conn->links_up > 0) {
/* устанавливаем fin_wait, отправляем CLOSE, ставим таймер */
conn->fin_wait = 1;
conn->fin_wait_clear_cb = ncd_fin_wait_cancelled;
conn->fin_wait_clear_arg = entry;
{ struct ncd_control_msg msg; msg.cmd = ETCP_RT_ID_NCD_CONTROL; msg.subcmd = NCD_SUBCMD_CLOSE;
msg.node_id = conn->instance->node_id;
struct ll_entry* qe = queue_entry_new(0);
if (qe) { qe->dgram = u_malloc(sizeof(msg));
if (qe->dgram) { memcpy(qe->dgram, &msg, sizeof(msg)); qe->len = sizeof(msg);
if (etcp_send(conn, qe) != 0) { queue_dgram_free(qe); queue_entry_free(qe); }
else DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] sent CLOSE to 0x%016llx", (unsigned long long)node_id); }
else queue_entry_free(qe); }
}
entry->fin_wait_timer = uasync_set_timeout(entry->ua, NCD_FIN_WAIT_TIMEOUT_TB, entry, ncd_fin_wait_timeout_cb, "ncd_fin_wait");
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] fin_wait started node=0x%016llx", (unsigned long long)node_id);
} else if (conn && conn->state != 2) {
/* conn никогда не поднялся (timed_out) — graceful CLOSE не нужен, чистим сразу */
conn->fin_wait = 0;
conn->fin_wait_clear_cb = NULL; conn->fin_wait_clear_arg = NULL;
etcp_conn_remove_cbk(conn, ncd_init_cb, entry);
etcp_conn_remove_cbk(conn, ncd_up_cb, entry);
etcp_conn_remove_cbk(conn, ncd_down_cb, entry);
if (conn->conn_queue && conn->conn_queue_entry) {
queue_remove_data(conn->conn_queue, conn->conn_queue_entry);
queue_entry_free(conn->conn_queue_entry);
conn->conn_queue_entry = NULL; conn->conn_queue = NULL;
}
/* закрываем conn синхронно чтобы линки сразу ушли из socket->links_queue:
* иначе в том же тике новый open того же узла создаст дубль-conn и коллизию адресов.
* u_free(conn) всё равно отложен (phase 2 в etcp_connection_close), UAF нет. */
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] close: node=0x%016llx state=%d timed_out=%d cb_running=%d links_up=%d -> %s",
(unsigned long long)node_id, conn->state, entry->timed_out, conn->callbacks_running,
conn->links_up,
conn->callbacks_running ? "deferred" : "sync");
if (conn->callbacks_running) uasync_call_soon(entry->ua, conn, ncd_deferred_close_conn);
else etcp_connection_close(conn);
ncd_registry_remove(conn->instance, entry);
u_free(entry);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] close: conn never came up, immediate cleanup node=0x%016llx",
(unsigned long long)node_id);
} else {
DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] close: no conn for entry, leaking entry=%p", entry);
}
}
h->entry = NULL;
u_free(h);
}
/*
* Немедленное жёсткое закрытие handle. Никакого CLOSE/KEEP_ALIVE — сразу
* снимает ETCP-коллбэки и (если последний handle) закрывает conn.
*
* Используется при разрушении CM entry чтобы гарантировать что коллбэки
* не вызовутся в уже освобождённую память. После force_close никакие
* NCD-события не будут доставлены.
*/
void node_conn_direct_force_close(struct NODE_CONN_DIRECT* h) {
if (!h) return;
if (!h->entry) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] force_close: h=%p h->entry=NULL", h); u_free(h); return; }
static void ncd_handle_close(struct NODE_CONN_DIRECT* h, int force) {
if (!h || h->closed) return;
struct ncd_entry* entry = h->entry;
ncd_cancel_deliver_up(h, entry->ua);
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] force_close h=%p entry=%p node=0x%016llx handles=%d",
h, entry, (unsigned long long)entry->node_id, entry->handle_count);
uint64_t node_id = entry->node_id;
struct ETCP_CONN* conn = entry->conn;
struct NODE_CONN_DIRECT** pp = &entry->handles;
while (*pp) { if (*pp == h) { *pp = h->next; break; } pp = &(*pp)->next; }
h->closed = 1; h->cb = NULL; h->cb_arg = NULL;
entry->handle_count--;
if (entry->handle_count < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] FATAL: force_close handle_count=%d < 0 h=%p node=0x%016llx",
entry->handle_count, (void*)h, (unsigned long long)node_id);
h->entry = NULL; u_free(h); return;
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] handle close node=%016llx remaining=%d force=%d",
(unsigned long long)entry->node_id, entry->handle_count, force);
if (entry->handle_count || entry->closed) { ncd_collect(entry); return; }
struct ETCP_CONN* conn = entry->conn;
if (force || !conn || conn->close_requested || conn->state == 2 || entry->timed_out || !conn->links_up) {
ncd_finish(entry, 1); return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close node=0x%016llx remaining=%d",
(unsigned long long)node_id, entry->handle_count);
if (entry->handle_count == 0) {
if (entry->handles) {
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] FATAL: force_close handle_count=0 but handles non-empty node=0x%016llx",
(unsigned long long)node_id);
entry->handles = NULL;
if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; }
conn->fin_wait = 1; conn->fin_wait_clear_cb = ncd_fin_wait_cancelled; conn->fin_wait_clear_arg = entry;
struct ncd_control_msg msg = { ETCP_RT_ID_NCD_CONTROL, NCD_SUBCMD_CLOSE, entry->inst->node_id };
struct ll_entry* e = ll_alloc_lldgram(sizeof(msg));
if (!e) DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] CLOSE allocation failed");
else {
memcpy(e->dgram, &msg, sizeof(msg)); e->len = sizeof(msg);
if (etcp_send(conn,e) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] CLOSE send failed"); queue_dgram_free(e); queue_entry_free(e);
}
h->cb = NULL; h->cb_arg = NULL;
if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; }
if (entry->fin_wait_timer) { uasync_cancel_timeout(entry->ua, entry->fin_wait_timer); entry->fin_wait_timer = NULL; }
if (conn && conn->state != 2 && conn->instance) {
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: cleaning conn=%p", conn);
conn->fin_wait = 0;
conn->fin_wait_clear_cb = NULL; conn->fin_wait_clear_arg = NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_init_cb");
etcp_conn_remove_cbk(conn, ncd_init_cb, entry);
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_up_cb");
etcp_conn_remove_cbk(conn, ncd_up_cb, entry);
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing ncd_down_cb");
etcp_conn_remove_cbk(conn, ncd_down_cb, entry);
if (conn->conn_queue && conn->conn_queue_entry) {
queue_remove_data(conn->conn_queue, conn->conn_queue_entry);
queue_entry_free(conn->conn_queue_entry);
conn->conn_queue_entry = NULL; conn->conn_queue = NULL;
}
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: scheduling deferred close");
if (conn->state != 2) uasync_call_soon(entry->ua, conn, ncd_deferred_close_conn);
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: removing from registry");
ncd_registry_remove(conn->instance, entry);
}
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: freeing entry=%p", entry);
u_free(entry);
}
h->entry = NULL;
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: freeing h=%p", h);
u_free(h);
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] force_close: done");
entry->fin_wait_timer = uasync_set_timeout(entry->ua, NCD_FIN_WAIT_TIMEOUT_TB, entry, ncd_fin_wait_timeout_cb, "ncd_fin_wait");
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] fin_wait node=%016llx", (unsigned long long)entry->node_id);
ncd_collect(entry);
}
void node_conn_direct_close(struct NODE_CONN_DIRECT* h) { ncd_handle_close(h, 0); }
void node_conn_direct_force_close(struct NODE_CONN_DIRECT* h) { ncd_handle_close(h, 1); }
/*
* Сменить или сбросить (cb=NULL) callback на уже открытом handle.
@@ -1052,8 +962,12 @@ void node_conn_direct_transfer(struct NODE_CONN_DIRECT* h, ncd_callback new_cb,
* Прямой доступ к ETCP_CONN из handle.
* Нужен для отправки данных (etcp_send), проверки статуса, и т.д.
*/
uint64_t node_conn_direct_node_id(const struct NODE_CONN_DIRECT* h) {
return h && h->entry ? h->entry->node_id : 0;
}
struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h) {
if (!h || !h->entry) return NULL;
if (!h || h->closed || !h->entry || h->entry->closed) return NULL;
return h->entry->conn;
}
@@ -1193,22 +1107,25 @@ int ncd_add_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock) {
int ncd_remove_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock) {
if (!inst || !sock) return 0;
int removed = 0;
struct ncd_entry* entry = (struct ncd_entry*)inst->ncd_registry;
while (entry) {
struct ETCP_CONN* conn = entry->conn;
if (conn) {
struct ETCP_LINK* link = conn->links;
while (link) {
struct ETCP_LINK* next = link->next;
if (link->conn == sock) {
etcp_link_close(link);
removed++;
}
link = next;
}
size_t count = 0;
for (struct ncd_entry* e = inst->ncd_registry; e; e = e->next) count++;
if (!count) return 0;
struct ETCP_CONN** conns = u_calloc(count,sizeof(*conns));
if (!conns) { DEBUG_ERROR(DEBUG_CATEGORY_NCD,"[ncd] remove_socket_links: snapshot allocation failed"); return -1; }
size_t held = 0;
for (struct ncd_entry* e = inst->ncd_registry; e; e = e->next)
if (etcp_conn_ref_take(e->conn) == 0) conns[held++] = e->conn;
// DOWN callback может удалить текущий и следующий entry. Снимок удерживает только транспорт.
for (size_t i = 0; i < held; i++) {
struct ETCP_LINK* link = conns[i]->links;
while (link) {
struct ETCP_LINK* next = link->next;
if (link->conn == sock) { etcp_link_close(link); removed++; }
link = next;
}
entry = entry->next;
etcp_conn_ref_free(conns[i]);
}
u_free(conns);
if (removed > 0) {
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] remove_socket_links socket=%s closed=%d", sock->name, removed);
}
+6 -3
View File
@@ -14,8 +14,8 @@
* - chat_core / topo_group — связь с узлами канала/группы
* - любой модуль, кому нужно надёжное ETCP-соединение с конкретным node_id
*
* События (UP/DOWN/TIMEOUT) доставляются через единый callback асинхронно
* (uasync_call_soon), что исключает рекурсию и проблемы с порядком инициализации.
* Переходы UP/DOWN/TIMEOUT/CLOSED доставляются синхронно. Закрытие любого handle
* из callback безопасно. Начальное UP при open готового conn откладывается через uasync_call_soon.
*/
#include <stdint.h>
@@ -37,7 +37,8 @@ struct NODE_CONN_DIRECT;
enum ncd_event {
NCD_EVENT_UP = 0, /* соединение поднялось (линки работают) */
NCD_EVENT_DOWN = 1, /* соединение упало */
NCD_EVENT_TIMEOUT = 2, /* таймаут первого подключения */
NCD_EVENT_CLOSED = 3, /* транспорт окончательно удалён; get_conn=NULL, владелец закрывает handle */
NCD_EVENT_TIMEOUT = 2, /* срок первой попытки истёк; удерживаемый handle может получить поздний UP */
};
typedef void (*ncd_callback)(struct NODE_CONN_DIRECT* h, enum ncd_event event, void* arg);
@@ -81,6 +82,8 @@ void node_conn_direct_set_callback(struct NODE_CONN_DIRECT* h, ncd_callback cb,
* должен забыть указатель. Соединение не трогается. */
void node_conn_direct_transfer(struct NODE_CONN_DIRECT* h, ncd_callback new_cb, void* new_cb_arg);
uint64_t node_conn_direct_node_id(const struct NODE_CONN_DIRECT* h);
struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h);
/* ─── Протокол CLOSE / KEEP_ALIVE (ETCP_RT_ID_NCD_CONTROL = 0x12) ─── */
+2 -2
View File
@@ -42,7 +42,7 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
int max_udp = IP_UDP_OVERHEAD_V4;
for (struct ETCP_LINK* l = etcp->links; l; l = l->next)
if (l->remote_addr.ss_family == AF_INET6) max_udp = IP_UDP_OVERHEAD_V6;
pn->frag_size = etcp->mtu - ACK_REZERV - max_udp - UDP_SC_HDR_SIZE;
pn->frag_size = etcp->mtu - ACK_REZERV - max_udp - UDP_SC_HDR_SIZE - ETCP_SESSION_HEADER_SIZE;
int min_frag = max_udp + UDP_SC_HDR_SIZE + ACK_REZERV;
if (etcp->mtu < min_frag || pn->frag_size > etcp->mtu) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "MTU %d too small (min %d)", etcp->mtu, min_frag);
@@ -380,7 +380,7 @@ static void pn_unpacker_cb(struct ll_queue* q, void* arg) {
// DEBUG_INFO(DEBUG_CATEGORY_ETCP, "new fragment pkt_len=%d (at %d)", part_size, ptr);
ptr += 2;
if (part_size<1 || part_size>16384) {
if (part_size<1 || part_size>PKTNORM_MAX_DGRAM_SIZE) {
pn->logic_errors++;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "PART_SIZE ERROR!!! %d", part_size);
etcp_conn_fatal_reinit(pn->etcp, "bad fragment size");
+2
View File
@@ -20,6 +20,8 @@ cmd = 2 - запрос роутинг-таблицы (без данных)
*/
#define PKTNORM_MAX_DGRAM_SIZE 16384
// Структура для packer
struct PKTNORM {
// public:
+3
View File
@@ -210,6 +210,9 @@ struct UTUN_INSTANCE {
struct chat_core_ctx* chat_core; // chat_core.c (was g_cc)
struct chat_sync* chat_sync; // chat_sync.c (was g_cs)
struct join_key_entry* join_keys; // chat_join.c (was g_keys; собственный init/destroy)
struct chat_join_registration* join_registrations;
int join_stopping;
struct chat_invite_build_req* invite_gui_request;
struct dm_state* dm; // dm/dm_core.c (was g_dm)
struct dm_mb_state* dm_mailbox; // dm/dm_mailbox.c (was g_mb)
struct call_ctx* call; // call/call.c — P2P audio call