Диагностика коллизии линков + детектор дубликата node_id + reality-конфиг autogen

This commit is contained in:
evgeny committed 2026-09-10 14:40:45 +03:00
1 parent c2882c533e
commit d886a2ff84
10 files changed
+487 -24

No files matched your search

+162 -4
View File
@@ -8,6 +8,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
@@ -103,6 +104,123 @@ static char* find_option(char *buf, size_t buf_len, const char *option) {
return NULL;
}
// Смещение байта сразу после ']' заголовка секции "[name]" (регистронезависимо), -1 если нет.
static long find_section_header(const char *buf, size_t buf_len, const char *name) {
size_t name_len = strlen(name);
size_t i = 0;
while (i < buf_len) {
if (buf[i] == '[' && i + name_len + 2 <= buf_len &&
strncasecmp(buf + i + 1, name, name_len) == 0 && buf[i + name_len + 1] == ']') {
return (long)(i + name_len + 2);
}
const char *nl = memchr(buf + i, '\n', buf_len - i);
if (!nl) break;
i = (size_t)(nl - buf) + 1;
}
return -1;
}
// Смещение строки, начинающейся с "option=", в диапазоне [start, end), -1 если нет.
static long find_option_in_range(const char *buf, size_t start, size_t end, const char *option) {
size_t olen = strlen(option);
size_t i = start;
while (i < end) {
if (i + olen <= end && strncmp(buf + i, option, olen) == 0 && buf[i + olen] == '=') {
return (long)i;
}
const char *nl = memchr(buf + i, '\n', end - i);
if (!nl) break;
i = (size_t)(nl - buf) + 1;
}
return -1;
}
// Заменить строку option=value внутри секции [section], либо вставить её сразу после
// строки заголовка секции (без пустых строк). Возвращает 0 / -1.
static int insert_or_replace_in_section(char **buf, size_t *buf_len, size_t *buf_capacity,
const char *section, const char *option, const char *value) {
if (!buf || !*buf || !buf_len || !buf_capacity || !section || !option || !value) return -1;
long hdr_off = find_section_header(*buf, *buf_len, section);
if (hdr_off < 0) return -1;
size_t sec_start = (size_t)hdr_off;
char *next_sec = memchr(*buf + sec_start, '[', *buf_len - sec_start);
size_t sec_end = next_sec ? (size_t)(next_sec - *buf) : *buf_len;
long opt_off = find_option_in_range(*buf, sec_start, sec_end, option);
if (opt_off >= 0) {
size_t opos = (size_t)opt_off;
char *nl = memchr(*buf + opos, '\n', sec_end - opos);
int has_trailing_nl = (nl != NULL);
size_t line_end = nl ? (size_t)(nl - *buf) + 1 : sec_end;
size_t old_len = line_end - opos;
char new_line[MAX_LINE_LEN];
int new_len = snprintf(new_line, sizeof(new_line), "%s=%s%s", option, value, has_trailing_nl ? "\n" : "");
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
long len_diff = (long)new_len - (long)old_len;
if ((long)*buf_len + len_diff + 1 > (long)*buf_capacity) {
*buf_capacity = *buf_len + len_diff + 1024;
char *new_buf = u_realloc(*buf, *buf_capacity);
if (!new_buf) return -1;
*buf = new_buf;
hdr_off = find_section_header(*buf, *buf_len, section);
if (hdr_off < 0) return -1;
sec_start = (size_t)hdr_off;
next_sec = memchr(*buf + sec_start, '[', *buf_len - sec_start);
sec_end = next_sec ? (size_t)(next_sec - *buf) : *buf_len;
opt_off = find_option_in_range(*buf, sec_start, sec_end, option);
if (opt_off < 0) return -1;
opos = (size_t)opt_off;
nl = memchr(*buf + opos, '\n', sec_end - opos);
has_trailing_nl = (nl != NULL);
line_end = nl ? (size_t)(nl - *buf) + 1 : sec_end;
}
memmove(*buf + opos + new_len, *buf + line_end, *buf_len - line_end + 1);
memcpy(*buf + opos, new_line, new_len);
*buf_len += len_diff;
} else {
size_t ins = sec_start;
int need_leading_nl = 0;
if (ins < *buf_len && (*buf)[ins] == '\n') {
ins += 1;
} else {
need_leading_nl = 1;
}
char new_line[MAX_LINE_LEN];
int new_len = snprintf(new_line, sizeof(new_line), "%s%s=%s\n", need_leading_nl ? "\n" : "", option, value);
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
if (*buf_len + (size_t)new_len + 1 > *buf_capacity) {
*buf_capacity = *buf_len + (size_t)new_len + 1024;
char *new_buf = u_realloc(*buf, *buf_capacity);
if (!new_buf) return -1;
*buf = new_buf;
hdr_off = find_section_header(*buf, *buf_len, section);
if (hdr_off < 0) return -1;
ins = (size_t)hdr_off;
need_leading_nl = 0;
if (ins < *buf_len && (*buf)[ins] == '\n') {
ins += 1;
} else {
need_leading_nl = 1;
}
new_len = snprintf(new_line, sizeof(new_line), "%s%s=%s\n", need_leading_nl ? "\n" : "", option, value);
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
}
memmove(*buf + ins + new_len, *buf + ins, *buf_len - ins + 1);
memcpy(*buf + ins, new_line, new_len);
*buf_len += new_len;
}
return 0;
}
static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_capacity, const char *option, const char *value) {
if (!buf || !*buf || !buf_len || !buf_capacity || !option || !value) return -1;
@@ -301,10 +419,42 @@ int config_ensure_keys_and_node_id(const char *filename) {
(unsigned long long)global->my_node_id, (unsigned long long)new_node_id, filename);
}
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Validation results - need_priv_key=%d, need_pub_key=%d, need_node_id=%d",
need_priv_key, need_pub_key, need_node_id);
// ── Step 4: reality-камуфляж — private_key и short_ids при enabled=1 ──
int need_reality_priv = 0, need_reality_ids = 0;
char new_reality_priv_hex[REALITY_AUTH_KEY_SIZE * 2 + 1] = {0};
char new_short_ids_str[4 * (REALITY_SHORT_ID_SIZE * 2) + 4] = {0};
if (!need_priv_key && !need_pub_key && !need_node_id) { free_config(config); u_free(file_buf); return 0; }
if (global->reality.enabled) {
if (!global->reality.has_private_key) {
need_reality_priv = 1;
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Reality private_key missing, generating new one in %s", filename);
uint8_t rpriv[REALITY_AUTH_KEY_SIZE], rpub[REALITY_AUTH_KEY_SIZE];
if (reality_generate_keypair(rpriv, rpub) != REALITY_OK) { free_config(config); u_free(file_buf); return -1; }
bytes_to_hex(rpriv, REALITY_AUTH_KEY_SIZE, new_reality_priv_hex, sizeof(new_reality_priv_hex));
}
if (global->reality.short_id_count == 0) {
need_reality_ids = 1;
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Reality short_ids missing, generating 4 ids in %s", filename);
char *p = new_short_ids_str;
size_t remain = sizeof(new_short_ids_str);
for (int i = 0; i < 4; i++) {
uint8_t sid[REALITY_SHORT_ID_SIZE];
if (random_bytes(sid, sizeof(sid)) != 0) { free_config(config); u_free(file_buf); return -1; }
char hex[REALITY_SHORT_ID_SIZE * 2 + 1];
bytes_to_hex(sid, REALITY_SHORT_ID_SIZE, hex, sizeof(hex));
int w = snprintf(p, remain, "%s%s", i ? "," : "", hex);
if (w < 0 || (size_t)w >= remain) { free_config(config); u_free(file_buf); return -1; }
p += w; remain -= (size_t)w;
}
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Validation results - need_priv_key=%d, need_pub_key=%d, need_node_id=%d, need_reality_priv=%d, need_reality_ids=%d",
need_priv_key, need_pub_key, need_node_id, need_reality_priv, need_reality_ids);
if (!need_priv_key && !need_pub_key && !need_node_id && !need_reality_priv && !need_reality_ids) {
free_config(config); u_free(file_buf); return 0;
}
char cfg_name[MAX_CONN_NAME_LEN]; snprintf(cfg_name, sizeof(cfg_name), "%s", global->name[0] ? global->name : "utun");
@@ -351,6 +501,14 @@ int config_ensure_keys_and_node_id(const char *filename) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing my_public_key to %s", filename);
if (insert_or_replace_option(&work_buf, &work_len, &buf_capacity, "my_public_key", new_pub_key) < 0) ret = -1;
}
if (need_reality_ids && ret == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing reality short_ids to %s", filename);
if (insert_or_replace_in_section(&work_buf, &work_len, &buf_capacity, "reality", "short_ids", new_short_ids_str) < 0) ret = -1;
}
if (need_reality_priv && ret == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Writing reality private_key to %s", filename);
if (insert_or_replace_in_section(&work_buf, &work_len, &buf_capacity, "reality", "private_key", new_reality_priv_hex) < 0) ret = -1;
}
}
if (ret == 0 && work_buf) {
@@ -358,7 +516,7 @@ int config_ensure_keys_and_node_id(const char *filename) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Failed to write updated config file: %s", filename);
ret = -1;
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Config file updated: %s (gen_priv=%d gen_pub=%d gen_nodeid=%d)", filename, need_priv_key, need_pub_key, need_node_id);
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Config file updated: %s (gen_priv=%d gen_pub=%d gen_nodeid=%d gen_reality_priv=%d gen_reality_ids=%d)", filename, need_priv_key, need_pub_key, need_node_id, need_reality_priv, need_reality_ids);
}
}
u_free(work_buf);
+13
View File
@@ -102,10 +102,21 @@ uint8_t cm_sock_v6_classify(const struct ETCP_SOCKET* s) {
return cm_classify_v6_addr(a6);
}
static void cm_warn_addr_busy(struct ETCP_CONN* conn, struct ETCP_SOCKET* s, struct sockaddr_storage* sa) {
struct ETCP_LINK* st = etcp_link_find_by_addr(s, sa, 0);
if (st && st->etcp != conn) {
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: addr %s busy by [%s] state=%d init=%d up=%d (adding for [%s] state=%d)",
sockaddr_storage_to_str(sa).str,
st->etcp->log_name, st->etcp->state, st->etcp->initialized, st->etcp->links_up,
conn->log_name, conn->state);
}
}
void cm_add_v4_link(struct ETCP_CONN* conn, const uint8_t* addr, uint16_t port, struct ETCP_SOCKET* s) {
struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
memcpy(&sin.sin_addr.s_addr, addr, 4); sin.sin_port = port;
struct sockaddr_storage sa; memcpy(&sa, &sin, sizeof(sin));
cm_warn_addr_busy(conn, s, &sa);
etcp_link_new(conn, s, &sa, 0);
}
@@ -114,6 +125,7 @@ void cm_add_v6_link(struct ETCP_CONN* conn, const uint8_t addr[16], uint16_t por
memcpy(&sin6.sin6_addr, addr, 16); sin6.sin6_port = htons(port);
if (cm_classify_v6_addr(addr) == CM_V6_LL) sin6.sin6_scope_id = s->netif_index;
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
cm_warn_addr_busy(conn, s, &sa);
etcp_link_new(conn, s, &sa, 0);
}
@@ -555,6 +567,7 @@ static void cm_direct_add_links(struct ETCP_CONN* conn, struct CONN_MGR_ENTRY* e
if (m->id != a->socket_id) continue;
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) { if (cm_nat_compatible(s, m->config_type, m->nat_type) && s->local_addr.ss_family == AF_INET) {
cm_warn_addr_busy(conn, s, &sa);
if (etcp_link_new(conn, s, &sa, 0)) { any = 1; v4_cnt++; } } s = s->next; }
break;
}
+14 -4
View File
@@ -689,6 +689,16 @@ void etcp_conn_queue_set_ready(struct ETCP_CONN* conn) {
queue_entry_free(conn->conn_queue_entry);
}
struct ll_entry* existing = queue_find_data_by_index(conn->instance->connections, (const uint8_t*)&conn->peer_node_id);
if (existing) {
struct conn_queue_entry* ece = (struct conn_queue_entry*)existing->data;
DEBUG_ERROR(DEBUG_CATEGORY_ETCP,
"[%s] queue_set_ready: DUPLICATE node_id key=0x%016llx already indexed by [%s] conn=%p state=%d (this=%p)",
conn->log_name, (unsigned long long)conn->peer_node_id,
ece->conn ? ece->conn->log_name : "?", (void*)(ece->conn),
ece->conn ? ece->conn->state : -1, (void*)conn);
}
struct ll_entry* qe = queue_entry_new(sizeof(struct conn_queue_entry));
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] failed to alloc queue entry for ready", conn->log_name); return; }
struct conn_queue_entry* ce = (struct conn_queue_entry*)qe->data;
@@ -1282,8 +1292,8 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) {
link->last_recv_updated=0;
if (dt<1000000) {
dgram->data[ptr++]=ETCP_SECTION_TIMESTAMP;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "[%s] KA-TX: ts_section dt=%llu tcp=%d",
link->etcp->log_name, (unsigned long long)dt, dgram->link->is_tcp);
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] KA-TX: ts_section dt=%llu tcp=%d",
link->etcp->log_name, (unsigned long long)dt, dgram->link->is_tcp);
uint16_t t=link->last_recv_timestamp + dt;
dgram->data[ptr++]=t;
@@ -1574,8 +1584,8 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) {
uint16_t ret_ts=data[1] | (data[2]<<8);// cur_ts=ret_ts = RTT
uint16_t new_rtt=cur_ts-ret_ts;
pkt->link->rtt_last=new_rtt;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "[%s] KA-RTT: rtt=%u cur=%u ret=%u dlen=%u",
etcp->log_name, new_rtt, cur_ts, ret_ts, pkt->data_len);
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] KA-RTT: rtt=%u cur=%u ret=%u dlen=%u",
etcp->log_name, new_rtt, cur_ts, ret_ts, pkt->data_len);
int recv_dt_tx1=data[3] | (data[4]<<8);// localtime удаленной стороны момента принятия пакета - timestamp этого пакета (на стороне отправителя, т.е. у нас)
+16 -5
View File
@@ -332,12 +332,23 @@ static int insert_link_queue(struct ETCP_SOCKET* e_sock, struct ETCP_LINK* link)
if (dup_qe) {
struct link_queue_entry* dup_lqe = (struct link_queue_entry*)dup_qe->data;
if (dup_lqe->link && dup_lqe->link->etcp != link->etcp) {
struct ETCP_CONN* new_c = link->etcp;
struct ETCP_CONN* old_c = dup_lqe->link->etcp;
uint64_t new_qkey = 0, old_qkey = 0;
if (new_c->conn_queue_entry) new_qkey = ((struct conn_queue_entry*)new_c->conn_queue_entry->data)->peer_node_id;
if (old_c->conn_queue_entry) old_qkey = ((struct conn_queue_entry*)old_c->conn_queue_entry->data)->peer_node_id;
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION,
"!!!!!!!!!!!! LINK ADDR COLLISION !!!!!!!!!!!! "
"addr=%s new_conn=%s(peer=0x%016llx) already_used_by=%s(peer=0x%016llx)",
"!!!!!!!!!!!! LINK ADDR COLLISION !!!!!!!!!!!! addr=%s "
"new: conn=%p [%s] peer=0x%016llx state=%d init=%d up=%d srv=%d tcp=%d qkey=0x%016llx "
"old: conn=%p [%s] peer=0x%016llx state=%d init=%d up=%d srv=%d tcp=%d qkey=0x%016llx",
sockaddr_storage_to_str(&link->remote_addr).str,
link->etcp->log_name, (unsigned long long)link->etcp->peer_node_id,
dup_lqe->link->etcp->log_name, (unsigned long long)dup_lqe->link->etcp->peer_node_id);
(void*)new_c, new_c->log_name, (unsigned long long)new_c->peer_node_id,
new_c->state, new_c->initialized, new_c->links_up, link->is_server, link->is_tcp,
(unsigned long long)new_qkey,
(void*)old_c, old_c->log_name, (unsigned long long)old_c->peer_node_id,
old_c->state, old_c->initialized, old_c->links_up,
dup_lqe->link->is_server, dup_lqe->link->is_tcp,
(unsigned long long)old_qkey);
return -1;
}
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "insert_link_queue: replacing stale DUP addr in [%s] old_link=%p", e_sock->name, dup_lqe->link);
@@ -2275,7 +2286,7 @@ int etcp_packet_decrypted(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
pkt->noencrypt_len=0;
pkt->link=link;
if (pkt->data_len && pkt->data[0] != ETCP_KEEPALIVE) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[%s] decrypt: code=%02x dlen=%u plen=%zu recv=%d sock=%p",
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,
link->recv_keepalive, (void*)e_sock);
}
+41
View File
@@ -85,6 +85,28 @@ static void ncd_registry_remove(struct UTUN_INSTANCE* inst, struct ncd_entry* en
while (*pp) { if (*pp == entry) { *pp = entry->next; return; } pp = &(*pp)->next; }
}
/* TEMP DEBUG: сканирует inst->connections и выводит conn'ы с peer_node_id==node_id,
* которые instance_find_conn мог не вернуть (key=0 или дубликат ключа). */
static void ncd_debug_scan_conns(struct UTUN_INSTANCE* inst, uint64_t node_id, const char* tag) {
struct ll_queue* q = inst->connections;
if (!q) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[ncd] %s node=0x%016llx: connections=NULL", tag, (unsigned long long)node_id); return; }
int found = 0;
struct ll_entry* e = q->head;
while (e) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (ce && ce->conn && ce->conn->peer_node_id == node_id) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG,
"[ncd] %s node=0x%016llx: conn=%p [%s] state=%d qkey=0x%016llx links=%p up=%d init=%d",
tag, (unsigned long long)node_id, (void*)ce->conn, ce->conn->log_name,
ce->conn->state, (unsigned long long)ce->peer_node_id, (void*)ce->conn->links,
ce->conn->links_up, ce->conn->initialized);
found = 1;
}
e = e->next;
}
if (!found) DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[ncd] %s node=0x%016llx: NO conn in queue", tag, (unsigned long long)node_id);
}
/* ═══════════ поиск узла ═══════════ */
/*
* Загружает информацию об узле (адреса, pubkey) для создания линков.
@@ -145,6 +167,17 @@ static int ncd_add_udp_link(struct ETCP_CONN* conn, struct ETCP_SOCKET* use_sock
return 0;
}
}
if (stale && stale->etcp != conn
&& !memcmp(conn->crypto_ctx.peer_public_key, stale->etcp->crypto_ctx.peer_public_key, SC_PUBKEY_SIZE))
{
DEBUG_ERROR(DEBUG_CATEGORY_ETCP,
"[ncd] add_udp_link: SAME-PUBKEY DUPLICATE conn at %s — new=%p [%s] state=%d / old=%p [%s] state=%d init=%d up=%d",
sockaddr_storage_to_str(sa).str,
(void*)conn, conn->log_name, conn->state,
(void*)stale->etcp, stale->etcp->log_name, stale->etcp->state,
stale->etcp->initialized, stale->etcp->links_up);
return 0;
}
return etcp_link_new(conn, use_sock, sa, 0) ? 1 : 0;
}
@@ -602,6 +635,12 @@ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id,
return NCD_REUSED;
}
if (conn) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[ncd] open node=0x%016llx: instance_find_conn=[%s] state=%d links=%p up=%d — NOT reused (state!=1), creating new",
(unsigned long long)node_id, conn->log_name, conn->state, (void*)conn->links, conn->links_up);
}
ncd_debug_scan_conns(inst, node_id, "open-new");
/* 3. Новое подключение */
struct TOPO_NODE* ni = ncd_lookup_node(inst, node_id);
if (!ni) {
@@ -756,6 +795,8 @@ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id,
return NCD_REUSED;
}}
ncd_debug_scan_conns(inst, node_id, "open_node-new");
/* 3. Новое подключение — используем переданный ni (временный, не владеем) */
{ char conn_name[MAX_CONN_NAME_LEN];
if (ni->node_name && ni->node_name[0]) strncpy(conn_name, ni->node_name, MAX_CONN_NAME_LEN - 1);