Fix ETCP session recovery and router lifecycle; confirm chat invites before publishing
This commit is contained in:
1 parent
018c8e795d
commit
3131dcbb7c
67 files changed
+3005
-1249
No files matched your search
@@ -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 \
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+326
-428
File diff suppressed because it is too large.
Load diff
@@ -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)
|
||||
|
||||
@@ -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 таймеров, завершился без диагностических ошибок.
|
||||
@@ -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 пути). */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 в процессе, ждём завершения
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (при закрытии).
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) ─── */
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -20,6 +20,8 @@ cmd = 2 - запрос роутинг-таблицы (без данных)
|
||||
|
||||
*/
|
||||
|
||||
#define PKTNORM_MAX_DGRAM_SIZE 16384
|
||||
|
||||
// Структура для packer
|
||||
struct PKTNORM {
|
||||
// public:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user