diff --git a/src/chat/chat_headless_control.c b/src/chat/chat_headless_control.c index 589945a7..6a58a17d 100644 --- a/src/chat/chat_headless_control.c +++ b/src/chat/chat_headless_control.c @@ -501,18 +501,20 @@ static void hc_handle_invite_to(struct headless_client* cli, int id, const char* int port = (int)strtol(colon + 1, NULL, 10); if (port <= 0 || port > 65535) { send_response(cli, id, NULL, "invalid port"); return; } - uint8_t addrs_data[21]; int addrs_len = 0; + uint8_t addrs_data[22]; int addrs_len = 0; struct in_addr a4; struct in6_addr a6; if (inet_pton(AF_INET, ip, &a4) == 1) { - addrs_data[0] = 4; addrs_data[1] = 0; addrs_data[2] = (uint8_t)proto; - memcpy(addrs_data + 3, &a4.s_addr, 4); - addrs_data[7] = (uint8_t)(port >> 8); addrs_data[8] = (uint8_t)(port & 0xFF); - addrs_len = 9; + addrs_data[0] = 0; /* reality-префикс: has=0 */ + addrs_data[1] = 4; addrs_data[2] = 0; addrs_data[3] = (uint8_t)proto; + memcpy(addrs_data + 4, &a4.s_addr, 4); + addrs_data[8] = (uint8_t)(port >> 8); addrs_data[9] = (uint8_t)(port & 0xFF); + addrs_len = 10; } else if (inet_pton(AF_INET6, ip, &a6) == 1) { - addrs_data[0] = 6; addrs_data[1] = 0; addrs_data[2] = (uint8_t)proto; - memcpy(addrs_data + 3, &a6, 16); - addrs_data[19] = (uint8_t)(port >> 8); addrs_data[20] = (uint8_t)(port & 0xFF); - addrs_len = 21; + addrs_data[0] = 0; /* reality-префикс: has=0 */ + addrs_data[1] = 6; addrs_data[2] = 0; addrs_data[3] = (uint8_t)proto; + memcpy(addrs_data + 4, &a6, 16); + addrs_data[20] = (uint8_t)(port >> 8); addrs_data[21] = (uint8_t)(port & 0xFF); + addrs_len = 22; } else { send_response(cli, id, NULL, "invalid addr ip"); return; } DEBUG_INFO((int)DEBUG_CATEGORY_HEADLESS, "headless: invite_to ch=%s node=0x%016llx addr=%s:%d proto=%d", diff --git a/src/chat/chat_status.c b/src/chat/chat_status.c index 1728e7ac..d693da01 100644 --- a/src/chat/chat_status.c +++ b/src/chat/chat_status.c @@ -316,7 +316,7 @@ void chat_core_collect_conn_metrics_trampoline(void* arg) { #define MEMBER_DETAIL_MAX_LINKS 16 #define MEMBER_DETAIL_MAX_SOCKS 16 -#define MEMBER_DETAIL_LINK_SIZE 63 +#define MEMBER_DETAIL_LINK_SIZE 64 #define MEMBER_DETAIL_SOCK_SIZE 48 /* Повторяет Qt: name.lastIndexOf("_v") → name.mid(3, idx - 3) */ @@ -434,6 +434,7 @@ static void collect_member_detail(uint64_t node_id) { if (lk->conn) strncpy(sn, lk->conn->name, 19); memcpy(p, sn, 20); p += 20; } + *p++ = lk->reality_set ? 1 : 0; } *p++ = sock_count; diff --git a/src/chat/chat_sync.c b/src/chat/chat_sync.c index 8411e537..66059ab4 100644 --- a/src/chat/chat_sync.c +++ b/src/chat/chat_sync.c @@ -719,11 +719,46 @@ struct chat_invite { uint64_t node_id; uint8_t pubkey[32]; uint8_t* addrs_data; + int addrs_data_len; int addr_count; }; struct cm_invite_wrap { struct chat_invite inv; }; +/* Парсит reality-префикс addrs_data: has(1) + [short_id(8) pubkey(32) ver(3) snlen(1) sn(N)]. + Сдвигает *ap за префикс. Возвращает 1, если reality присутствует (и params заполнены). */ +static int invite_reality_parse(const uint8_t** ap, const uint8_t* end, + uint8_t short_id[8], uint8_t pubkey[32], uint8_t ver[3], + char* sname, size_t sname_sz) { + const uint8_t* p = *ap; + if (p + 1 > end) return 0; + if (!*p++) { *ap = p; return 0; } + if (p + 8 + 32 + 3 + 1 > end) { *ap = end; return 0; } + memcpy(short_id, p, 8); p += 8; + memcpy(pubkey, p, 32); p += 32; + memcpy(ver, p, 3); p += 3; + uint8_t snlen = *p++; + if (snlen > (uint8_t)(sname_sz - 1)) snlen = (uint8_t)(sname_sz - 1); + if (p + snlen > end) { *ap = end; return 0; } + memcpy(sname, p, snlen); sname[snlen] = '\0'; p += snlen; + *ap = p; + return 1; +} + +/* Добавляет reality-сокет узла с node-global параметрами. */ +static void invite_reality_sock_append(struct TOPO_NODE* ni, uint8_t socket_id, + const uint8_t short_id[8], const uint8_t pubkey[32], + const uint8_t ver[3], const char* sname) { + struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r)); + if (!r) return; + r->socket_id = socket_id; + memcpy(r->short_id, short_id, 8); + memcpy(r->server_pubkey, pubkey, 32); + memcpy(r->version, ver, 3); + snprintf(r->server_name, sizeof(r->server_name), "%s", sname); + r->next = ni->reality_socks; ni->reality_socks = r; +} + static void cm_invite_trampoline(void* arg) { struct cm_invite_wrap* w = (struct cm_invite_wrap*)arg; struct chat_invite* inv = &w->inv; @@ -736,27 +771,28 @@ static void cm_invite_trampoline(void* arg) { if (!ni) { u_free(w); return; } ni->node_id = node_id; memcpy(ni->public_key, inv->pubkey, 32); const uint8_t* ap = inv->addrs_data; - for (int i = 0; i < inv->addr_count; i++) { + const uint8_t* ap_end = inv->addrs_data + inv->addrs_data_len; + uint8_t r_short_id[8], r_pubkey[32], r_ver[3]; char r_sname[TOPO_REALITY_SNAME_MAX]; + int has_reality = invite_reality_parse(&ap, ap_end, r_short_id, r_pubkey, r_ver, r_sname, sizeof(r_sname)); + for (int i = 0; i < inv->addr_count && ap + 3 <= ap_end; i++) { uint8_t family = *ap++; uint8_t sock_id = *ap++; uint8_t proto = *ap++; - uint16_t port = 0; - if (family == 4) { port = ((uint16_t)ap[4] << 8) | ap[5]; } - else if (family == 6) { port = ((uint16_t)ap[16] << 8) | ap[17]; } - const char* proto_str = proto == 1 ? "UDP" : proto == 2 ? "TCP" : proto == 3 ? "UDP+TCP" : "?"; - if (family == 4) { + if (family == 4 && ap + 6 <= ap_end) { struct TOPO_ADDR4* a4 = u_calloc(1, sizeof(*a4)); if (!a4) break; memcpy(a4->addr, ap, 4); ap += 4; a4->port = ((uint16_t)ap[0] << 8) | ap[1]; ap += 2; - a4->type = TOPO_ADDR_NAT; a4->protocol = proto; + a4->type = TOPO_ADDR_NAT; a4->protocol = proto; a4->socket_id = sock_id; a4->next = ni->v4_addrs; ni->v4_addrs = a4; - } else if (family == 6) { + if (has_reality && (proto & 0x04)) invite_reality_sock_append(ni, sock_id, r_short_id, r_pubkey, r_ver, r_sname); + } else if (family == 6 && ap + 18 <= ap_end) { struct TOPO_ADDR6* a6 = u_calloc(1, sizeof(*a6)); if (!a6) break; memcpy(a6->addr, ap, 16); ap += 16; a6->port = ((uint16_t)ap[0] << 8) | ap[1]; ap += 2; - a6->type = TOPO_ADDR_NAT; a6->protocol = proto; + a6->type = TOPO_ADDR_NAT; a6->protocol = proto; a6->socket_id = sock_id; a6->next = ni->v6_addrs; ni->v6_addrs = a6; - } else { ap += 18; } + if (has_reality && (proto & 0x04)) invite_reality_sock_append(ni, sock_id, r_short_id, r_pubkey, r_ver, r_sname); + } else { break; } } { char buf[512]; int off = 0; int n4 = 0, n6 = 0; @@ -836,6 +872,7 @@ void chat_sync_connect_from_invite(uint64_t channel_id, uint64_t node_id, inv->addrs_data = u_malloc(addrs_sz); if (!inv->addrs_data) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: connect_from_invite malloc(%zu) failed", CS_ID, addrs_sz); u_free(inv); return; } memcpy(inv->addrs_data, addrs_data, addrs_sz); + inv->addrs_data_len = (int)addrs_sz; inv->addr_count = addr_count; g_cs->pending_invite_ch_id = channel_id; @@ -1563,6 +1600,8 @@ static void cs_invite_trampoline(void* arg) { const uint8_t* ap = w->addrs_data; const uint8_t* ap_end = ap + w->addrs_len; + uint8_t r_short_id[8], r_pubkey[32], r_ver[3]; char r_sname[TOPO_REALITY_SNAME_MAX]; + int has_reality = invite_reality_parse(&ap, ap_end, r_short_id, r_pubkey, r_ver, r_sname, sizeof(r_sname)); for (int i = 0; i < w->addr_count && ap + 1 <= ap_end; i++) { uint8_t family = *ap++; uint8_t sock_id = *ap++; uint8_t proto = *ap++; if (family == 4 && ap + 6 <= ap_end) { @@ -1570,15 +1609,17 @@ static void cs_invite_trampoline(void* arg) { if (!a4) break; memcpy(a4->addr, ap, 4); ap += 4; a4->port = ((uint16_t)ap[0] << 8) | ap[1]; ap += 2; - a4->type = TOPO_ADDR_NAT; a4->protocol = proto; + a4->type = TOPO_ADDR_NAT; a4->protocol = proto; a4->socket_id = sock_id; a4->next = ni->v4_addrs; ni->v4_addrs = a4; + if (has_reality && (proto & 0x04)) invite_reality_sock_append(ni, sock_id, r_short_id, r_pubkey, r_ver, r_sname); } else if (family == 6 && ap + 18 <= ap_end) { struct TOPO_ADDR6* a6 = u_calloc(1, sizeof(*a6)); if (!a6) break; memcpy(a6->addr, ap, 16); ap += 16; a6->port = ((uint16_t)ap[0] << 8) | ap[1]; ap += 2; - a6->type = TOPO_ADDR_NAT; a6->protocol = proto; + a6->type = TOPO_ADDR_NAT; a6->protocol = proto; a6->socket_id = sock_id; a6->next = ni->v6_addrs; ni->v6_addrs = a6; + if (has_reality && (proto & 0x04)) invite_reality_sock_append(ni, sock_id, r_short_id, r_pubkey, r_ver, r_sname); } else { break; } } diff --git a/src/chat/invite_build.c b/src/chat/invite_build.c index 93966f4f..ed7f67e0 100644 --- a/src/chat/invite_build.c +++ b/src/chat/invite_build.c @@ -172,14 +172,15 @@ static int collect_self_addrs(struct InviteAddr* out, int max_cnt) { for (s = g_cc.inst->etcp_sockets; s && cnt < max_cnt; s = s->next) { if (!s->is_tcp) continue; struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr; + uint8_t proto = INVITE_PROTO_TCP | (s->reality_enabled ? INVITE_PROTO_REALITY : 0); if (sa->ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)sa; - out[cnt].family = 4; out[cnt].socketId = s->sock_id; out[cnt].proto = INVITE_PROTO_TCP; + out[cnt].family = 4; out[cnt].socketId = s->sock_id; out[cnt].proto = proto; memcpy(out[cnt].address, &sin->sin_addr, 4); out[cnt].port = ntohs(sin->sin_port); cnt++; } else if (sa->ss_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; - out[cnt].family = 6; out[cnt].socketId = s->sock_id; out[cnt].proto = INVITE_PROTO_TCP; + out[cnt].family = 6; out[cnt].socketId = s->sock_id; out[cnt].proto = proto; memcpy(out[cnt].address, &sin6->sin6_addr, 16); out[cnt].port = ntohs(sin6->sin6_port); cnt++; } @@ -187,17 +188,39 @@ static int collect_self_addrs(struct InviteAddr* out, int max_cnt) { return cnt; } +/* reality-параметры self-узла из конфига (node-global, как в topo_node_update_my_addresses). */ +static void collect_self_reality(struct InviteReality* out) { + memset(out, 0, sizeof(*out)); + if (!g_cc.inst || !g_cc.inst->config) return; + const struct reality_config* rc = &g_cc.inst->config->global.reality; + if (!rc->enabled || !rc->has_private_key) return; + int any = 0; + for (struct ETCP_SOCKET* s = g_cc.inst->etcp_sockets; s; s = s->next) + if (s->is_tcp && s->reality_enabled) { any = 1; break; } + if (!any) return; + uint8_t pub[32]; + if (reality_pubkey_from_priv(rc->private_key, pub) != REALITY_OK) return; + out->has = 1; + if (rc->short_id_count > 0) memcpy(out->short_id, rc->short_ids[0], 8); + else memcpy(out->short_id, rc->short_id, 8); + memcpy(out->server_pubkey, pub, 32); + memcpy(out->version, rc->version, 3); + strncpy(out->server_name, rc->server_name, INVITE_SNAME_MAX - 1); + out->server_name[INVITE_SNAME_MAX - 1] = '\0'; +} + /* ── Фаза 4b: адреса узла из БД ── */ -static int collect_node_addrs_from_db(uint64_t node_id, struct InviteAddr* out, int max_cnt, int dir_only) { +static int collect_node_addrs_from_db(uint64_t node_id, struct InviteAddr* out, int max_cnt, int dir_only, + struct InviteReality* reality) { char sql[256]; if (dir_only) snprintf(sql, sizeof(sql), - "SELECT family,socket_id,protocol,address,port FROM node_addresses" + "SELECT family,socket_id,protocol,address,port,options FROM node_addresses" " WHERE node_id=? AND addr_type=%d", ADDR_TYPE_DIRECT); else snprintf(sql, sizeof(sql), - "SELECT family,socket_id,protocol,address,port FROM node_addresses WHERE node_id=?"); + "SELECT family,socket_id,protocol,address,port,options FROM node_addresses WHERE node_id=?"); sqlite3_stmt* st = NULL; if (sqlite3_prepare_v2(g_cc.db, sql, -1, &st, NULL) != SQLITE_OK) return 0; @@ -215,6 +238,17 @@ static int collect_node_addrs_from_db(uint64_t node_id, struct InviteAddr* out, memset(out[cnt].address, 0, sizeof(out[cnt].address)); if (addr && addr_len >= ip_len) memcpy(out[cnt].address, addr, (size_t)ip_len); out[cnt].port = (uint16_t)sqlite3_column_int(st, 4); + if (reality && !reality->has && (out[cnt].proto & INVITE_PROTO_REALITY) + && sqlite3_column_type(st, 5) == SQLITE_BLOB + && sqlite3_column_bytes(st, 5) == TOPO_ADDR_REALITY_OPTS_SIZE) { + const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(st, 5); + reality->has = 1; + memcpy(reality->short_id, o->short_id, 8); + memcpy(reality->server_pubkey, o->server_pubkey, 32); + memcpy(reality->version, o->version, 3); + memcpy(reality->server_name, o->server_name, INVITE_SNAME_MAX); + reality->server_name[INVITE_SNAME_MAX - 1] = '\0'; + } cnt++; } sqlite3_finalize(st); @@ -284,9 +318,10 @@ int chat_invite_build_link(uint64_t channel_id, uint64_t target_node_id, /* ── адреса ── */ if (use_self) { data.addrCount = (uint8_t)collect_self_addrs(data.addrs, INVITE_ADDR_MAX); + collect_self_reality(&data.reality); } else { int dir_only = has_both_public ? 1 : 0; - data.addrCount = (uint8_t)collect_node_addrs_from_db(target_nid, data.addrs, INVITE_ADDR_MAX, dir_only); + data.addrCount = (uint8_t)collect_node_addrs_from_db(target_nid, data.addrs, INVITE_ADDR_MAX, dir_only, &data.reality); } if (data.addrCount == 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "invite_build: no addrs for node=0x%016llx", diff --git a/src/chat/invite_link.c b/src/chat/invite_link.c index 54560a36..3cbd7b68 100644 --- a/src/chat/invite_link.c +++ b/src/chat/invite_link.c @@ -63,12 +63,13 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteData* out int off = 0; uint8_t ver = raw[off++]; - if (ver != INVITE_LINK_VERSION && ver != 0x02) { + if (ver != INVITE_LINK_VERSION && ver != 0x02 && ver != 0x01) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "unsupported version 0x%02x", ver); return -1; } out->password[0] = '\0'; out->password_len = 0; + out->reality.has = 0; - if (ver == 0x02) { + if (ver == 0x02 || ver == INVITE_LINK_VERSION) { if (off + 1 > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at pass_len"); return -1; } uint8_t plen = raw[off++]; if (plen > 0) { @@ -89,6 +90,22 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteData* out for (int i = 0; i < 8; i++) jk = (jk << 8) | raw[off++]; out->join_key = jk; + if (ver == INVITE_LINK_VERSION) { + if (off + 1 > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at reality"); return -1; } + if (raw[off++]) { + if (off + 8 + 32 + 3 + 1 > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at reality params"); return -1; } + memcpy(out->reality.short_id, raw + off, 8); off += 8; + memcpy(out->reality.server_pubkey, raw + off, 32); off += 32; + memcpy(out->reality.version, raw + off, 3); off += 3; + uint8_t snlen = raw[off++]; + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + if (off + snlen > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at reality server_name"); return -1; } + memcpy(out->reality.server_name, raw + off, snlen); out->reality.server_name[snlen] = '\0'; + off += snlen; + out->reality.has = 1; + } + } + memset(out->pubkey, 0, INVITE_PUBKEY_SIZE); out->addrCount = 0; @@ -117,10 +134,25 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteData* out int invite_serialize_addrs(const struct InviteData* data, uint8_t* buf, size_t buf_size) { if (!data || !buf) return -1; - size_t needed = 0; + size_t needed = 1; + if (data->reality.has) { + size_t snlen = strlen(data->reality.server_name); + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + needed += 8 + 32 + 3 + 1 + snlen; + } for (int i = 0; i < (int)data->addrCount; i++) needed += (data->addrs[i].family == 6) ? 21U : 9U; if (needed > buf_size) return -1; size_t pos = 0; + buf[pos++] = data->reality.has ? 1 : 0; + if (data->reality.has) { + memcpy(buf + pos, data->reality.short_id, 8); pos += 8; + memcpy(buf + pos, data->reality.server_pubkey, 32); pos += 32; + memcpy(buf + pos, data->reality.version, 3); pos += 3; + size_t snlen = strlen(data->reality.server_name); + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + buf[pos++] = (uint8_t)snlen; + memcpy(buf + pos, data->reality.server_name, snlen); pos += snlen; + } for (int i = 0; i < (int)data->addrCount; i++) { const struct InviteAddr* a = &data->addrs[i]; int ip_len = (a->family == 6) ? 16 : 4; @@ -134,16 +166,28 @@ int invite_serialize_addrs(const struct InviteData* data, uint8_t* buf, size_t b int invite_link_encode(const struct InviteData* data, const char* password, char* out, size_t out_size) { if (!data || !out || data->pubkey[0] == 0) return -1; if (data->addrCount == 0) return -1; - int has_pass = (password && password[0]) ? 1 : 0; uint8_t raw[4096]; size_t pos = 0; - raw[pos++] = has_pass ? 0x02 : INVITE_LINK_VERSION; - if (has_pass) { - size_t plen = strlen(password); + raw[pos++] = INVITE_LINK_VERSION; + { + size_t plen = (password && password[0]) ? strlen(password) : 0; if (plen > INVITE_PASS_MAX - 1) return -1; - raw[pos++] = (uint8_t)plen; memcpy(raw + pos, password, plen); pos += plen; + raw[pos++] = (uint8_t)plen; + if (plen) { memcpy(raw + pos, password, plen); pos += plen; } } for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)((data->channelId >> (i * 8)) & 0xFF); for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)((data->join_key >> (i * 8)) & 0xFF); + + raw[pos++] = data->reality.has ? 1 : 0; + if (data->reality.has) { + memcpy(raw + pos, data->reality.short_id, 8); pos += 8; + memcpy(raw + pos, data->reality.server_pubkey, 32); pos += 32; + memcpy(raw + pos, data->reality.version, 3); pos += 3; + size_t snlen = strlen(data->reality.server_name); + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + raw[pos++] = (uint8_t)snlen; + memcpy(raw + pos, data->reality.server_name, snlen); pos += snlen; + } + uint8_t pk[32]; memcpy(pk, data->pubkey, 32); int ia = 0; diff --git a/src/chat/invite_link.h b/src/chat/invite_link.h index 30ce0fa3..14811738 100644 --- a/src/chat/invite_link.h +++ b/src/chat/invite_link.h @@ -10,13 +10,15 @@ #include #include -#define INVITE_LINK_VERSION 0x01 +#define INVITE_LINK_VERSION 0x03 #define INVITE_PUBKEY_SIZE 32 #define INVITE_ADDR_MAX 32 #define INVITE_PASS_MAX 128 +#define INVITE_SNAME_MAX 64 #define INVITE_PROTO_UDP 0x01 #define INVITE_PROTO_TCP 0x02 +#define INVITE_PROTO_REALITY 0x04 /* флаг reality-камуфляжа поверх TCP (reality-TCP = 0x06) */ #ifdef __cplusplus extern "C" { @@ -30,6 +32,15 @@ struct InviteAddr { uint16_t port; }; +/* Reality-параметры узла (node-global: одинаковы для всех reality-сокетов). */ +struct InviteReality { + uint8_t has; /* 1 = reality присутствует в ссылке */ + uint8_t short_id[8]; + uint8_t server_pubkey[32]; + uint8_t version[3]; + char server_name[INVITE_SNAME_MAX]; +}; + struct InviteData { uint64_t channelId; uint64_t join_key; /* 64-битный ключ входа (генерируется инвайтером, хранится на connection-узле 10 мин) */ @@ -39,6 +50,7 @@ struct InviteData { struct InviteAddr addrs[INVITE_ADDR_MAX]; char password[INVITE_PASS_MAX]; uint8_t password_len; + struct InviteReality reality; }; int invite_link_decode(const char* link, size_t link_len, struct InviteData* out, diff --git a/src/config_parser.c b/src/config_parser.c index 6f8743bd..ec4bbf78 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1631,20 +1631,23 @@ void print_chatserver_invite_link(const struct utun_config *config, const struct uint8_t proto; } addrs[32]; int addr_count = 0; + int has_reality = 0; for (const struct CFG_SERVER *s = servers; s && addr_count < 32; s = s->next) { + uint8_t proto = (s->transport == 1) ? 0x02 : 0x01; + if (s->transport == 1 && s->reality_enabled) { proto |= 0x04; has_reality = 1; } if (s->ip.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip; addrs[addr_count].family = 4; memcpy(addrs[addr_count].addr, &sin->sin_addr, 4); addrs[addr_count].port = ntohs(sin->sin_port); - addrs[addr_count].proto = (s->transport == 1) ? 0x02 : 0x01; + addrs[addr_count].proto = proto; addr_count++; } else if (s->ip.ss_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&s->ip; addrs[addr_count].family = 6; memcpy(addrs[addr_count].addr, &sin6->sin6_addr, 16); addrs[addr_count].port = ntohs(sin6->sin6_port); - addrs[addr_count].proto = (s->transport == 1) ? 0x02 : 0x01; + addrs[addr_count].proto = proto; addr_count++; } } @@ -1653,18 +1656,37 @@ void print_chatserver_invite_link(const struct utun_config *config, const struct return; } + /* reality-параметры (node-global) из секции [reality] */ + uint8_t r_pubkey[32]; + int r_valid = 0; + if (has_reality && g->reality.enabled && g->reality.has_private_key + && reality_pubkey_from_priv(g->reality.private_key, r_pubkey) == REALITY_OK) + r_valid = 1; + /* Build binary blob */ uint8_t raw[4096]; size_t pos = 0; - int has_pass = g->chatserver_join_password[0] ? 1 : 0; - raw[pos++] = has_pass ? 0x02 : 0x01; /* version */ - if (has_pass) { - size_t plen = strlen(g->chatserver_join_password); + raw[pos++] = 0x03; /* version */ + { size_t plen = g->chatserver_join_password[0] ? strlen(g->chatserver_join_password) : 0; raw[pos++] = (uint8_t)plen; - memcpy(raw + pos, g->chatserver_join_password, plen); pos += plen; - } + if (plen) { memcpy(raw + pos, g->chatserver_join_password, plen); pos += plen; } } /* channel_id = 0 (8 bytes BE) */ for (int i = 0; i < 8; i++) raw[pos++] = 0; + /* join_key = 0 (8 bytes BE) */ + for (int i = 0; i < 8; i++) raw[pos++] = 0; + /* reality-блок */ + raw[pos++] = r_valid ? 1 : 0; + if (r_valid) { + if (g->reality.short_id_count > 0) memcpy(raw + pos, g->reality.short_ids[0], 8); + else memcpy(raw + pos, g->reality.short_id, 8); + pos += 8; + memcpy(raw + pos, r_pubkey, 32); pos += 32; + memcpy(raw + pos, g->reality.version, 3); pos += 3; + size_t snlen = strlen(g->reality.server_name); + if (snlen > 63) snlen = 63; + raw[pos++] = (uint8_t)snlen; + memcpy(raw + pos, g->reality.server_name, snlen); pos += snlen; + } int ia = 0; while (ia < addr_count) { diff --git a/src/routing_layer/topo_group_invite.c b/src/routing_layer/topo_group_invite.c index 831489d0..3ba368cc 100644 --- a/src/routing_layer/topo_group_invite.c +++ b/src/routing_layer/topo_group_invite.c @@ -495,7 +495,7 @@ int topo_group_invite_join(struct UTUN_INSTANCE* inst, uint64_t group_id, for (const struct TOPO_ADDR4* a = gni->v4_addrs; a; a = a->next) aoff += snprintf(addrs + aoff, sizeof(addrs) - (size_t)aoff, " %d.%d.%d.%d:%d(%s)", a->addr[0], a->addr[1], a->addr[2], a->addr[3], (int)a->port, - a->protocol == TOPO_PROTO_UDP ? "UDP" : a->protocol == TOPO_PROTO_TCP ? "TCP" : "?"); + a->protocol == TOPO_PROTO_UDP ? "UDP" : a->protocol == TOPO_PROTO_TCP ? "TCP" : (a->protocol & TOPO_PROTO_REALITY) ? "TCP+REALITY" : "?"); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "invite: starting to 0x%016llx via%s timeout=%dms", (unsigned long long)nid, addrs, TGI_TIMEOUT_MS); } diff --git a/src/routing_layer/topo_node.c b/src/routing_layer/topo_node.c index b6bf98a8..6c604c98 100644 --- a/src/routing_layer/topo_node.c +++ b/src/routing_layer/topo_node.c @@ -654,7 +654,7 @@ void topo_node_dump_all(struct TOPO_GROUP* group) { }} { const char* atn[] = {"INTERFACE","NAT","REAL"}; int ai = 0; struct TOPO_ADDR4* a = ni->v4_addrs; while (a) { - const char* proto = (a->protocol & TOPO_PROTO_TCP) ? "tcp" : (a->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; + const char* proto = (a->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a->protocol & TOPO_PROTO_TCP) ? "tcp" : (a->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; DEBUG_INFO(DEBUG_CATEGORY_BGP, " v4addr[%d]: %s:%u %s sock=%u proto=%s", ai++, ip_to_str(a->addr, AF_INET).str, a->port, (a->type<=2)?atn[a->type]:"?", a->socket_id, proto); a = a->next; @@ -664,7 +664,7 @@ void topo_node_dump_all(struct TOPO_GROUP* group) { sm6 = sm6->next; }} { int ai6 = 0; struct TOPO_ADDR6* a6 = ni->v6_addrs; while (a6) { - const char* proto = (a6->protocol & TOPO_PROTO_TCP) ? "tcp" : (a6->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; + const char* proto = (a6->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a6->protocol & TOPO_PROTO_TCP) ? "tcp" : (a6->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; DEBUG_INFO(DEBUG_CATEGORY_BGP, " v6addr[%d]: %s:%u type=%u sock=%u proto=%s", ai6++, ip_to_str(a6->addr, AF_INET6).str, a6->port, a6->type, a6->socket_id, proto); a6 = a6->next; @@ -742,7 +742,7 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) { FMT_ADD(" v4sock[%d]: id=%u config=%u nat=%u\n", si++, sm->id, sm->config_type, sm->nat_type); sm = sm->next; }} { const char* atn[] = {"INTERFACE","NAT","REAL"}; struct TOPO_ADDR4* a = ni->v4_addrs; int ai = 0; while (a) { - const char* proto = (a->protocol & TOPO_PROTO_TCP) ? "tcp" : (a->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; + const char* proto = (a->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a->protocol & TOPO_PROTO_TCP) ? "tcp" : (a->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; FMT_ADD(" v4addr[%d]: %s:%u %s sock=%u proto=%s\n", ai++, ip_to_str(a->addr, AF_INET).str, a->port, (a->type<=2)?atn[a->type]:"?", a->socket_id, proto); a = a->next; @@ -751,7 +751,7 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) { FMT_ADD(" v6sock[%d]: id=%u config=%u nat=%u\n", s6i++, sm6->id, sm6->config_type, sm6->nat_type); sm6 = sm6->next; }} { struct TOPO_ADDR6* a6 = ni->v6_addrs; int a6i = 0; while (a6) { - const char* proto = (a6->protocol & TOPO_PROTO_TCP) ? "tcp" : (a6->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; + const char* proto = (a6->protocol & TOPO_PROTO_REALITY) ? "tcp+reality" : (a6->protocol & TOPO_PROTO_TCP) ? "tcp" : (a6->protocol & TOPO_PROTO_UDP) ? "udp" : "?"; FMT_ADD(" v6addr[%d]: %s:%u type=%u sock=%u proto=%s\n", a6i++, ip_to_str(a6->addr, AF_INET6).str, a6->port, a6->type, a6->socket_id, proto); a6 = a6->next; @@ -976,6 +976,8 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) { if (ts->type == CFG_SERVER_TYPE_PRIVATE) { ts = ts->next; continue; } /* private скрыт, LOCAL рекламируем (LAN) */ struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr; if (!addr || !addr->ss_family) { ts = ts->next; continue; } + const int is_reality = r_valid && ts->reality_enabled; + uint8_t proto = TOPO_PROTO_TCP | (is_reality ? TOPO_PROTO_REALITY : 0); if (addr->ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)addr; { struct TOPO_SOCKMETA4* sm = memory_pool_alloc(instance->topo_groups->v4_sock_meta_pool); @@ -983,7 +985,7 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) { sm->next = m4_head; m4_head = sm; } { struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool); memcpy(a->addr, &sin->sin_addr.s_addr, 4); a->port = ntohs(sin->sin_port); - a->type = TOPO_ADDR_INTERFACE; a->socket_id = ts->sock_id; a->protocol = TOPO_PROTO_TCP; + a->type = TOPO_ADDR_INTERFACE; a->socket_id = ts->sock_id; a->protocol = proto; a->next = v4_head; v4_head = a; } } else if (addr->ss_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr; @@ -992,10 +994,10 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) { sm6->next = m6_head; m6_head = sm6; } { struct TOPO_ADDR6* a6 = memory_pool_alloc(instance->topo_groups->v6_addr_pool); memcpy(a6->addr, &sin6->sin6_addr, 16); a6->port = ntohs(sin6->sin6_port); - a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = ts->sock_id; a6->protocol = TOPO_PROTO_TCP; + a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = ts->sock_id; a6->protocol = proto; a6->next = v6_head; v6_head = a6; } } - if (r_valid && ts->reality_enabled) { + if (is_reality) { struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(struct TOPO_REALITY_SOCK)); if (!r) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "reality_sock alloc failed"); } else { diff --git a/src/routing_layer/topo_node.h b/src/routing_layer/topo_node.h index 160988f3..3ddf9364 100644 --- a/src/routing_layer/topo_node.h +++ b/src/routing_layer/topo_node.h @@ -42,8 +42,9 @@ struct UTUN_INSTANCE; #define TOPO_ADDR_NAT 1 #define TOPO_ADDR_REAL 2 -#define TOPO_PROTO_UDP 0x01 -#define TOPO_PROTO_TCP 0x02 +#define TOPO_PROTO_UDP 0x01 +#define TOPO_PROTO_TCP 0x02 +#define TOPO_PROTO_REALITY 0x04 /* флаг reality-камуфляжа поверх TCP (reality-TCP = 0x06) */ #define ADDR_TYPE_NETIF 0 #define ADDR_TYPE_DIRECT 1 @@ -156,6 +157,15 @@ struct TOPO_REALITY_SOCK { char server_name[TOPO_REALITY_SNAME_MAX]; }; +/* Reality-параметры адреса для персистенции в node_addresses.options (BLOB, фиксированный размер). */ +#define TOPO_ADDR_REALITY_OPTS_SIZE 107 +struct TOPO_ADDR_REALITY_OPTS { + uint8_t short_id[8]; + uint8_t server_pubkey[32]; + uint8_t version[3]; + char server_name[TOPO_REALITY_SNAME_MAX]; +}; + struct _topo_head { struct _topo_head* next; }; static inline int topo_list_count(struct _topo_head* head) { int n = 0; while (head) { n++; head = head->next; } return n; } diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 0f75a1cd..b3d419e4 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -86,6 +86,21 @@ static void smeta6_lookup(struct TOPO_SOCKMETA6* head, uint8_t sid, uint8_t* cfg if (m->id == sid) { *cfg = m->config_type; *nat = m->nat_type; return; } } +/* reality-параметры сокета → фиксированный blob для node_addresses.options. + Возвращает 1 и заполняет opts, если у сокета есть reality; иначе 0. */ +static int reality_opts_build(struct TOPO_NODE* ni, uint8_t sid, struct TOPO_ADDR_REALITY_OPTS* opts) { + if (!ni || !opts) return 0; + for (const struct TOPO_REALITY_SOCK* r = ni->reality_socks; r; r = r->next) { + if (r->socket_id != sid) continue; + memcpy(opts->short_id, r->short_id, 8); + memcpy(opts->server_pubkey, r->server_pubkey, 32); + memcpy(opts->version, r->version, 3); + memcpy(opts->server_name, r->server_name, TOPO_REALITY_SNAME_MAX); + return 1; + } + return 0; +} + /* ── Персистенция адресов узла (источник истины — BGP/topo_group) ── */ int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni) { @@ -109,17 +124,19 @@ int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* sqlite3_stmt* is = NULL; if (sqlite3_prepare_v2(db, - "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type,nat_type,socket_id)" - " VALUES(?,?,?,?,?,?,?,?,?)", -1, &is, NULL) != SQLITE_OK) { + "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type,nat_type,socket_id,options)" + " VALUES(?,?,?,?,?,?,?,?,?,?)", -1, &is, NULL) != SQLITE_OK) { sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); return -1; } int written = 0; + struct TOPO_ADDR_REALITY_OPTS ropts; for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) { uint8_t cfg, nat; smeta4_lookup(ni->v4_sock_meta, a->socket_id, &cfg, &nat); int at = (a->type == TOPO_ADDR_INTERFACE) ? addr_type_from_ip(4, a->addr) : addr_type_classify(cfg, nat); + int has_reality = (a->protocol & TOPO_PROTO_REALITY) && reality_opts_build(ni, a->socket_id, &ropts); sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id); sqlite3_bind_int(is, 2, 4); sqlite3_bind_int(is, 3, (int)a->protocol); @@ -129,6 +146,8 @@ int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* sqlite3_bind_int(is, 7, (int)cfg); sqlite3_bind_int(is, 8, (int)nat); sqlite3_bind_int(is, 9, (int)a->socket_id); + if (has_reality) sqlite3_bind_blob(is, 10, &ropts, sizeof(ropts), SQLITE_STATIC); + else sqlite3_bind_null(is, 10); if (sqlite3_step(is) != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "addrs_put: INSERT failed node=%016llx fam=4 sock=%d at=%d cfg=%d nat=%d addr=%d.%d.%d.%d:%d: %s", (unsigned long long)node_id, a->socket_id, at, cfg, nat, @@ -140,6 +159,7 @@ int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* uint8_t cfg, nat; smeta6_lookup(ni->v6_sock_meta, a->socket_id, &cfg, &nat); int at = (a->type == TOPO_ADDR_INTERFACE) ? ADDR_TYPE_NETIF : addr_type_classify(cfg, nat); + int has_reality = (a->protocol & TOPO_PROTO_REALITY) && reality_opts_build(ni, a->socket_id, &ropts); sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id); sqlite3_bind_int(is, 2, 6); sqlite3_bind_int(is, 3, (int)a->protocol); @@ -149,6 +169,8 @@ int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* sqlite3_bind_int(is, 7, (int)cfg); sqlite3_bind_int(is, 8, (int)nat); sqlite3_bind_int(is, 9, (int)a->socket_id); + if (has_reality) sqlite3_bind_blob(is, 10, &ropts, sizeof(ropts), SQLITE_STATIC); + else sqlite3_bind_null(is, 10); if (sqlite3_step(is) != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "addrs_put: INSERT failed node=%016llx fam=6 sock=%d at=%d cfg=%d nat=%d: %s", (unsigned long long)node_id, a->socket_id, at, cfg, nat, sqlite3_errmsg(db)); @@ -255,6 +277,7 @@ int topo_node_sqlite_init(sqlite3* db) { " config_type INTEGER DEFAULT 0," " nat_type INTEGER DEFAULT 0," " socket_id INTEGER NOT NULL DEFAULT 0," + " options BLOB," // reality-параметры (TOPO_ADDR_REALITY_OPTS) " created_at INTEGER DEFAULT (unixepoch())" ");" "CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);" @@ -284,6 +307,29 @@ int topo_node_sqlite_init(sqlite3* db) { return -1; } + /* migration: добавить колонку options в уже существующую node_addresses (idempotent) */ + { + sqlite3_stmt* cs = NULL; + int has_options = 0; + if (sqlite3_prepare_v2(db, "PRAGMA table_info(node_addresses)", -1, &cs, NULL) == SQLITE_OK) { + while (sqlite3_step(cs) == SQLITE_ROW) { + const char* cn = (const char*)sqlite3_column_text(cs, 1); + if (cn && strcmp(cn, "options") == 0) { has_options = 1; break; } + } + sqlite3_finalize(cs); + } + if (!has_options) { + char* aerr = NULL; + int arc = sqlite3_exec(db, "ALTER TABLE node_addresses ADD COLUMN options BLOB", NULL, NULL, &aerr); + if (arc != SQLITE_OK) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "topo_node_sqlite_init: ALTER TABLE ADD options failed: %s", aerr ? aerr : "?"); + if (aerr) sqlite3_free(aerr); + } else { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite_init: migrated node_addresses (added options column)"); + } + } + } + /* backfill: вывести deleted из adm_tags (idempotent, самовосстановление) */ { sqlite3_stmt* s = NULL; @@ -945,11 +991,12 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr struct TOPO_ADDR4* v4_head = NULL; struct TOPO_ADDR4* v4_tail = NULL; struct TOPO_SOCKMETA4* m4_head = NULL; struct TOPO_SOCKMETA4* m4_tail = NULL; + struct TOPO_REALITY_SOCK* r_head = NULL; struct TOPO_REALITY_SOCK* r_tail = NULL; uint8_t sock_id_counter = 0; { sqlite3_stmt* s = NULL; if (sqlite3_prepare_v2(db, - "SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type FROM node_addresses" + "SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type,options FROM node_addresses" " WHERE node_id=? AND family=4 ORDER BY addr_type", -1, &s, NULL) == SQLITE_OK) { sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); @@ -983,6 +1030,22 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr else { m4_tail->next = m4; m4_tail = m4; } } } + if (sqlite3_column_type(s, 7) == SQLITE_BLOB + && sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE + && (proto & TOPO_PROTO_REALITY)) { + const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7); + struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r)); + if (r) { + r->socket_id = sock_id; + memcpy(r->short_id, o->short_id, 8); + memcpy(r->server_pubkey, o->server_pubkey, 32); + memcpy(r->version, o->version, 3); + memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX); + r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0; + if (!r_head) r_head = r_tail = r; + else { r_tail->next = r; r_tail = r; } + } + } } sqlite3_finalize(s); } @@ -993,7 +1056,7 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr { sqlite3_stmt* s = NULL; if (sqlite3_prepare_v2(db, - "SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type FROM node_addresses" + "SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type,options FROM node_addresses" " WHERE node_id=? AND family=6 ORDER BY addr_type", -1, &s, NULL) == SQLITE_OK) { sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); @@ -1027,6 +1090,22 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr else { m6_tail->next = m6; m6_tail = m6; } } } + if (sqlite3_column_type(s, 7) == SQLITE_BLOB + && sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE + && (proto6 & TOPO_PROTO_REALITY)) { + const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7); + struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r)); + if (r) { + r->socket_id = sock_id; + memcpy(r->short_id, o->short_id, 8); + memcpy(r->server_pubkey, o->server_pubkey, 32); + memcpy(r->version, o->version, 3); + memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX); + r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0; + if (!r_head) r_head = r_tail = r; + else { r_tail->next = r; r_tail = r; } + } + } } sqlite3_finalize(s); } @@ -1043,6 +1122,7 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr ni->v6_addrs = v6_head; ni->v4_sock_meta = m4_head; ni->v6_sock_meta = m6_head; + ni->reality_socks = r_head; DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx loaded from DB name=%s v4=%d v6=%d", (unsigned long long)node_id, name ? name : "", diff --git a/src/transport_layer/node_conn_direct.c b/src/transport_layer/node_conn_direct.c index aa0a8a99..ee87b2c2 100644 --- a/src/transport_layer/node_conn_direct.c +++ b/src/transport_layer/node_conn_direct.c @@ -1098,6 +1098,7 @@ static int ncd_add_tcp_link_random(struct ETCP_CONN* conn, struct TOPO_NODE* ni, struct ETCP_LINK* tlink = etcp_link_new(conn, sock, &sa, 0); if (!tlink) return 0; tlink->is_tcp = 1; + topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); return 1; } @@ -1120,6 +1121,7 @@ static int ncd_add_tcp_link_random(struct ETCP_CONN* conn, struct TOPO_NODE* ni, struct ETCP_LINK* tlink = etcp_link_new(conn, sock, &sa, 0); if (!tlink) return 0; tlink->is_tcp = 1; + topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); return 1; } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt index 6a5f6d0e..7902f22b 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt @@ -4,19 +4,28 @@ import android.util.Base64 import com.utun.chat.data.LogManager import java.security.MessageDigest -const val INVITE_LINK_VERSION: Int = 0x01 +const val INVITE_LINK_VERSION: Int = 0x03 const val INVITE_PUBKEY_SIZE: Int = 32 +const val INVITE_SNAME_MAX: Int = 64 const val INVITE_PROTO_UDP: Int = 0x01 const val INVITE_PROTO_TCP: Int = 0x02 -const val INVITE_PROTO_BOTH: Int = 0x03 +const val INVITE_PROTO_REALITY: Int = 0x04 // флаг reality-камуфляжа поверх TCP (reality-TCP = 0x06) + +data class InviteReality( + val has: Boolean, + val shortId: ByteArray, + val serverPubkey: ByteArray, + val version: ByteArray, + val serverName: String +) data class InviteAddr( val family: Int, // 4 or 6 val address: ByteArray, val port: Int, val socketId: Int, - val proto: Int // INVITE_PROTO_UDP/TCP/BOTH + val proto: Int // INVITE_PROTO_* ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -32,13 +41,26 @@ data class InviteData( val joinKey: Long, val pubkey: ByteArray, val nodeId: Long, - val addrs: List + val addrs: List, + val password: String = "", + val reality: InviteReality? = null ) { - /** Serialize addresses for C bridge: family(1) + socketId(1) + proto(1) + address(4|16) + port(2 BE) */ + /** Serialize addresses for C bridge: reality-префикс + family(1)+socketId(1)+proto(1)+address(4|16)+port(2 BE) */ fun serializeAddrs(): ByteArray { - val size = addrs.fold(0) { acc, a -> acc + if (a.family == 4) 9 else 21 } + var size = 1 + if (reality != null && reality.has) size += 8 + 32 + 3 + 1 + reality.serverName.toByteArray(Charsets.UTF_8).let { minOf(it.size, INVITE_SNAME_MAX - 1) } + size = addrs.fold(size) { acc, a -> acc + if (a.family == 4) 9 else 21 } val buf = ByteArray(size) var pos = 0 + buf[pos] = (if (reality != null && reality.has) 1 else 0).toByte(); pos++ + if (reality != null && reality.has) { + System.arraycopy(reality.shortId, 0, buf, pos, 8); pos += 8 + System.arraycopy(reality.serverPubkey, 0, buf, pos, 32); pos += 32 + System.arraycopy(reality.version, 0, buf, pos, 3); pos += 3 + val sn = reality.serverName.toByteArray(Charsets.UTF_8).let { if (it.size > INVITE_SNAME_MAX - 1) it.copyOf(INVITE_SNAME_MAX - 1) else it } + buf[pos] = sn.size.toByte(); pos++ + System.arraycopy(sn, 0, buf, pos, sn.size); pos += sn.size + } for (a in addrs) { buf[pos] = a.family.toByte(); pos++ buf[pos] = a.socketId.toByte(); pos++ @@ -85,13 +107,25 @@ object InviteLink { } var off = 0 - if ((raw[off].toInt() and 0xFF) != INVITE_LINK_VERSION) { + val ver = raw[off].toInt() and 0xFF + if (ver != INVITE_LINK_VERSION && ver != 0x02 && ver != 0x01) { LogManager.addLog("ERROR", "INVITE", "decode: wrong version 0x%02x expected 0x%02x".format( raw[off].toInt() and 0xFF, INVITE_LINK_VERSION)) return null } off++ + var password = "" + if (ver == 0x02 || ver == INVITE_LINK_VERSION) { + if (off >= raw.size) return null + val plen = raw[off].toInt() and 0xFF; off++ + if (plen > 0) { + if (off + plen > raw.size) return null + password = String(raw.copyOfRange(off, off + plen), Charsets.UTF_8) + off += plen + } + } + var chId = 0L for (i in 0 until 8) { if (off + i >= raw.size) return null @@ -106,6 +140,24 @@ object InviteLink { } off += 8 + var reality: InviteReality? = null + if (ver == INVITE_LINK_VERSION) { + if (off >= raw.size) return null + val has = (raw[off].toInt() and 0xFF) != 0; off++ + if (has) { + if (off + 8 + 32 + 3 + 1 > raw.size) return null + val shortId = raw.copyOfRange(off, off + 8); off += 8 + val serverPubkey = raw.copyOfRange(off, off + 32); off += 32 + val version = raw.copyOfRange(off, off + 3); off += 3 + var snlen = raw[off].toInt() and 0xFF; off++ + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1 + if (off + snlen > raw.size) return null + val serverName = String(raw.copyOfRange(off, off + snlen), Charsets.UTF_8) + off += snlen + reality = InviteReality(has = true, shortId = shortId, serverPubkey = serverPubkey, version = version, serverName = serverName) + } + } + LogManager.addLog("DEBUG", "INVITE", "decode: chId=0x%016x joinKey=0x%016x".format(chId, joinKey)) var pubkey: ByteArray? = null @@ -143,14 +195,15 @@ object InviteLink { val pkHex = pubkey.take(8).joinToString("") { "%02x".format(it) } LogManager.addLog("INFO", "INVITE", "decode: ch=0x%016x node=0x%016x pk=%s... addrs=%d".format( chId, nodeId, pkHex, addrs.size)) - return InviteData(channelId = chId, joinKey = joinKey, pubkey = pubkey, nodeId = nodeId, addrs = addrs) + return InviteData(channelId = chId, joinKey = joinKey, pubkey = pubkey, nodeId = nodeId, + addrs = addrs, password = password, reality = reality) } fun addrToString(addr: InviteAddr): String { - val protoStr = when (addr.proto) { - INVITE_PROTO_UDP -> "UDP" - INVITE_PROTO_TCP -> "TCP" - INVITE_PROTO_BOTH -> "UDP+TCP" + val protoStr = when { + (addr.proto and INVITE_PROTO_REALITY) != 0 -> "TCP+REALITY" + addr.proto == INVITE_PROTO_UDP -> "UDP" + addr.proto == INVITE_PROTO_TCP -> "TCP" else -> "?${addr.proto}" } val ip = if (addr.family == 4 && addr.address.size == 4) { diff --git a/tools/chatgui-android/libutun_lite/invite_link_c.c b/tools/chatgui-android/libutun_lite/invite_link_c.c index 74dae855..c43b3fe9 100644 --- a/tools/chatgui-android/libutun_lite/invite_link_c.c +++ b/tools/chatgui-android/libutun_lite/invite_link_c.c @@ -72,14 +72,15 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteDataC* ou int off = 0; uint8_t ver = raw[off++]; - if (ver != INVITE_LINK_VERSION && ver != 0x02) { + if (ver != INVITE_LINK_VERSION && ver != 0x02 && ver != 0x01) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "unsupported version 0x%02x", ver); return -1; } out->password[0] = '\0'; out->password_len = 0; + out->reality.has = 0; - /* v2: password field before addresses */ - if (ver == 0x02) { + /* v2/v3: password field before addresses */ + if (ver == 0x02 || ver == INVITE_LINK_VERSION) { if (off + 1 > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at pass_len"); return -1; @@ -114,6 +115,22 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteDataC* ou jk = (jk << 8) | raw[off++]; out->join_key = jk; + /* v3: reality-блок */ + if (ver == INVITE_LINK_VERSION) { + if (off + 1 > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at reality"); return -1; } + if (raw[off++]) { + if (off + 8 + 32 + 3 + 1 > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at reality params"); return -1; } + memcpy(out->reality.short_id, raw + off, 8); off += 8; + memcpy(out->reality.server_pubkey, raw + off, 32); off += 32; + memcpy(out->reality.version, raw + off, 3); off += 3; + uint8_t snlen = raw[off++]; + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + if (off + snlen > raw_len) { if (error_buf && error_buf_size) snprintf(error_buf, error_buf_size, "truncated at reality server_name"); return -1; } + memcpy(out->reality.server_name, raw + off, snlen); out->reality.server_name[snlen] = '\0'; off += snlen; + out->reality.has = 1; + } + } + memset(out->pubkey, 0, INVITE_PUBKEY_SIZE); out->addrCount = 0; @@ -158,11 +175,26 @@ int invite_link_decode(const char* link, size_t link_len, struct InviteDataC* ou int invite_serialize_addrs(const struct InviteDataC* data, uint8_t* buf, size_t buf_size) { if (!data || !buf) return -1; - size_t needed = 0; + size_t needed = 1; + if (data->reality.has) { + size_t snlen = strlen(data->reality.server_name); + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + needed += 8 + 32 + 3 + 1 + snlen; + } for (int i = 0; i < (int)data->addrCount; i++) needed += (data->addrs[i].family == 6) ? 21U : 9U; if (needed > buf_size) return -1; size_t pos = 0; + buf[pos++] = data->reality.has ? 1 : 0; + if (data->reality.has) { + memcpy(buf + pos, data->reality.short_id, 8); pos += 8; + memcpy(buf + pos, data->reality.server_pubkey, 32); pos += 32; + memcpy(buf + pos, data->reality.version, 3); pos += 3; + size_t snlen = strlen(data->reality.server_name); + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + buf[pos++] = (uint8_t)snlen; + memcpy(buf + pos, data->reality.server_name, snlen); pos += snlen; + } for (int i = 0; i < (int)data->addrCount; i++) { const struct InviteAddrC* a = &data->addrs[i]; int ip_len = (a->family == 6) ? 16 : 4; @@ -180,18 +212,29 @@ int invite_link_encode(const struct InviteDataC* data, const char* password, cha if (!data || !out || data->pubkey[0] == 0) return -1; if (data->addrCount == 0) return -1; - int has_pass = (password && password[0]) ? 1 : 0; uint8_t raw[4096]; size_t pos = 0; - raw[pos++] = has_pass ? 0x02 : INVITE_LINK_VERSION; - if (has_pass) { - size_t plen = strlen(password); + raw[pos++] = INVITE_LINK_VERSION; + { + size_t plen = (password && password[0]) ? strlen(password) : 0; if (plen > INVITE_PASS_MAX - 1) return -1; raw[pos++] = (uint8_t)plen; - memcpy(raw + pos, password, plen); pos += plen; + if (plen) { memcpy(raw + pos, password, plen); pos += plen; } } for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)((data->channelId >> (i * 8)) & 0xFF); for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)((data->join_key >> (i * 8)) & 0xFF); + + raw[pos++] = data->reality.has ? 1 : 0; + if (data->reality.has) { + memcpy(raw + pos, data->reality.short_id, 8); pos += 8; + memcpy(raw + pos, data->reality.server_pubkey, 32); pos += 32; + memcpy(raw + pos, data->reality.version, 3); pos += 3; + size_t snlen = strlen(data->reality.server_name); + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + raw[pos++] = (uint8_t)snlen; + memcpy(raw + pos, data->reality.server_name, snlen); pos += snlen; + } + uint8_t pk[32]; memcpy(pk, data->pubkey, 32); int ia = 0; diff --git a/tools/chatgui-android/libutun_lite/invite_link_c.h b/tools/chatgui-android/libutun_lite/invite_link_c.h index 873a1900..f5c3b879 100644 --- a/tools/chatgui-android/libutun_lite/invite_link_c.h +++ b/tools/chatgui-android/libutun_lite/invite_link_c.h @@ -4,14 +4,15 @@ #include #include -#define INVITE_LINK_VERSION 0x01 +#define INVITE_LINK_VERSION 0x03 #define INVITE_PUBKEY_SIZE 32 #define INVITE_ADDR_MAX 32 #define INVITE_PASS_MAX 128 +#define INVITE_SNAME_MAX 64 #define INVITE_PROTO_UDP 0x01 #define INVITE_PROTO_TCP 0x02 -#define INVITE_PROTO_BOTH 0x03 +#define INVITE_PROTO_REALITY 0x04 /* флаг reality-камуфляжа поверх TCP (reality-TCP = 0x06) */ #ifdef __cplusplus extern "C" { @@ -25,6 +26,15 @@ struct InviteAddrC { uint16_t port; }; +/* Reality-параметры узла (node-global: одинаковы для всех reality-сокетов). */ +struct InviteRealityC { + uint8_t has; + uint8_t short_id[8]; + uint8_t server_pubkey[32]; + uint8_t version[3]; + char server_name[INVITE_SNAME_MAX]; +}; + struct InviteDataC { uint64_t channelId; uint64_t join_key; /* 64-битный ключ входа (генерируется инвайтером) */ @@ -34,6 +44,7 @@ struct InviteDataC { struct InviteAddrC addrs[INVITE_ADDR_MAX]; char password[INVITE_PASS_MAX]; /* empty = no password (v1 compat) */ uint8_t password_len; + struct InviteRealityC reality; }; /* decode utun://base64 link string, returns 0 on success, -1 on error. diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h index 50fcc0b3..cacd8382 100644 --- a/tools/chatgui/db/db_manager.h +++ b/tools/chatgui/db/db_manager.h @@ -52,7 +52,7 @@ struct AccountRow { struct NodeAddr { quint64 nodeId = 0; int family = 0; - int protocol = 0; // битмаска: 1=UDP, 2=TCP, 3=TCP+UDP + int protocol = 0; // битмаска: 1=UDP, 2=TCP, 4=REALITY (6=TCP+REALITY) QByteArray address; quint16 port = 0; int rtt = 0; diff --git a/tools/chatgui/src/accountlist.cpp b/tools/chatgui/src/accountlist.cpp index 8e9c4437..e976466d 100644 --- a/tools/chatgui/src/accountlist.cpp +++ b/tools/chatgui/src/accountlist.cpp @@ -312,6 +312,7 @@ void AccountList::onMemberDetailData(uint64_t nodeId, const uint8_t* data, int l lk.addr = QByteArray((const char*)p + 25, 16); memcpy(&lk.port, p + 41, 2); memcpy(lk.sockName, p + 43, 20); lk.sockName[19] = '\0'; + lk.isReality = p[63] != 0; p += MEMBER_DETAIL_LINK_SIZE; m_snapLinks.append(lk); } @@ -367,14 +368,17 @@ void AccountList::updateDetailDisplay() { /* ── Body ── */ QStringList lines; + auto protoName = [](int protocol) -> QString { + if (protocol & 0x04) return (protocol & 0x02) ? "TCP+REALITY" : "REALITY"; + switch (protocol) { case 1: return "UDP"; case 2: return "TCP"; default: return "?"; } + }; + if (!m_detailAddrs.isEmpty()) { lines.append("── Addresses ──"); for (const auto& a : m_detailAddrs) { - QString proto; - switch (a.protocol) { case 1: proto = "UDP"; break; case 2: proto = "TCP"; break; default: proto = "?"; break; } lines.append(QString(" [sock%1] %2 %3 %4:%5 %6") .arg(a.socket_id) - .arg(proto) + .arg(protoName(a.protocol)) .arg(a.ifname) .arg(addrStr(a.addr.constData(), a.family)) .arg(a.port) @@ -389,7 +393,7 @@ void AccountList::updateDetailDisplay() { for (const auto& da : m_detailAddrs) { if (da.socket_id == s.sockId) { a = &da; break; } } - QString proto = a ? (a->protocol == 2 ? "TCP" : "UDP") : "?"; + QString proto = a ? protoName(a->protocol) : "?"; QString ip = a ? addrStr(a->addr.constData(), a->family) : "?"; lines.append(QString(" [%1/%2] %3:%4 %5") .arg(s.linksUp).arg(s.linksDown) @@ -404,7 +408,7 @@ void AccountList::updateDetailDisplay() { QString c = up ? "#4CAF50" : "#E53935"; lines.append(QString("● %2 %3:%4 %5 %6:%7") .arg(c) - .arg(lk.isTcp ? "TCP" : "UDP") + .arg(lk.isReality ? "TCP+REALITY" : (lk.isTcp ? "TCP" : "UDP")) .arg(addrStr(lk.localAddr.constData(), lk.localFamily)).arg(lk.localPort) .arg(lk.isServer ? "\u25c0" : "\u25b6") .arg(addrStr(lk.addr.constData(), lk.family)).arg(lk.port)); diff --git a/tools/chatgui/src/accountlist.h b/tools/chatgui/src/accountlist.h index a51bce05..fee63dea 100644 --- a/tools/chatgui/src/accountlist.h +++ b/tools/chatgui/src/accountlist.h @@ -36,6 +36,7 @@ struct MemberLinkSnap { QByteArray addr; uint16_t port; char sockName[20]; + bool isReality; }; struct MemberSockSnap { diff --git a/tools/chatgui/src/invite_link.cpp b/tools/chatgui/src/invite_link.cpp index b6c7b717..8bd9af6b 100644 --- a/tools/chatgui/src/invite_link.cpp +++ b/tools/chatgui/src/invite_link.cpp @@ -82,14 +82,14 @@ InviteData decodeInviteLink(const QString& link) { int off = 0; uint8_t ver = data[off++]; - if (ver != INVITE_LINK_VERSION && ver != 0x02) { + if (ver != INVITE_LINK_VERSION && ver != 0x02 && ver != 0x01) { d.error = QStringLiteral("unsupported version 0x%1").arg(ver, 2, 16, QLatin1Char('0')); GUI_ERROR("decodeInviteLink: unsupported version %d", ver); return d; } - /* v2: password field before channel_id */ - if (ver == 0x02) { + /* v2/v3: password field before channel_id */ + if (ver == 0x02 || ver == INVITE_LINK_VERSION) { if (off >= len) { d.error = QStringLiteral("truncated at pass_len"); return d; @@ -127,6 +127,23 @@ InviteData decodeInviteLink(const QString& link) { jk = (jk << 8) | data[off++]; d.joinKey = jk; + /* v3: reality-блок (node-global параметры) */ + if (ver == INVITE_LINK_VERSION) { + if (off >= len) { d.error = QStringLiteral("truncated at reality"); return d; } + if (data[off++] != 0) { + if (off + 8 + 32 + 3 + 1 > len) { d.error = QStringLiteral("truncated at reality params"); return d; } + d.reality.shortId = QByteArray(reinterpret_cast(data + off), 8); off += 8; + d.reality.serverPubkey = QByteArray(reinterpret_cast(data + off), 32); off += 32; + d.reality.version = QByteArray(reinterpret_cast(data + off), 3); off += 3; + uint8_t snlen = data[off++]; + if (snlen > INVITE_SNAME_MAX - 1) snlen = INVITE_SNAME_MAX - 1; + if (off + snlen > len) { d.error = QStringLiteral("truncated at reality server_name"); return d; } + d.reality.serverName = QString::fromUtf8(reinterpret_cast(data + off), snlen); + off += snlen; + d.reality.has = true; + } + } + bool firstBlock = true; while (off + 1 <= len) { uint8_t header = data[off++]; @@ -169,3 +186,25 @@ InviteData decodeInviteLink(const QString& link) { return d; } + +QByteArray serializeInviteAddrs(const InviteData& d) { + QByteArray buf; + buf.append(static_cast(d.reality.has ? 1 : 0)); + if (d.reality.has) { + buf.append(d.reality.shortId.leftJustified(8, '\0', true)); + buf.append(d.reality.serverPubkey.leftJustified(32, '\0', true)); + buf.append(d.reality.version.leftJustified(3, '\0', true)); + QByteArray sn = d.reality.serverName.toUtf8().left(INVITE_SNAME_MAX - 1); + buf.append(static_cast(sn.size())); + buf.append(sn); + } + for (const auto& a : d.addrs) { + buf.append(static_cast(a.family)); + buf.append(static_cast(a.socketId)); + buf.append(static_cast(a.proto)); + buf.append(a.address); + buf.append(static_cast((a.port >> 8) & 0xFF)); + buf.append(static_cast(a.port & 0xFF)); + } + return buf; +} diff --git a/tools/chatgui/src/invite_link.h b/tools/chatgui/src/invite_link.h index 9d40cd8e..17e21243 100644 --- a/tools/chatgui/src/invite_link.h +++ b/tools/chatgui/src/invite_link.h @@ -6,17 +6,29 @@ #include #include "../db/db_manager.h" -#define INVITE_LINK_VERSION 0x01 +#define INVITE_LINK_VERSION 0x03 -#define INVITE_PROTO_UDP 0x01 -#define INVITE_PROTO_TCP 0x02 +#define INVITE_PROTO_UDP 0x01 +#define INVITE_PROTO_TCP 0x02 +#define INVITE_PROTO_REALITY 0x04 // флаг reality-камуфляжа поверх TCP (reality-TCP = 0x06) + +#define INVITE_SNAME_MAX 64 struct InviteAddr { int family; // 4=IPv4, 6=IPv6 QByteArray address; quint16 port; uint8_t socketId; // socket_id из ETCP - uint8_t proto; // INVITE_PROTO_UDP/TCP/BOTH + uint8_t proto; // INVITE_PROTO_* +}; + +/* Reality-параметры узла (node-global: одинаковы для всех reality-сокетов). */ +struct InviteReality { + bool has = false; + QByteArray shortId; // 8 байт + QByteArray serverPubkey; // 32 байта + QByteArray version; // 3 байта + QString serverName; }; struct InviteData { @@ -26,6 +38,7 @@ struct InviteData { QList addrs; QString password; QString error; + InviteReality reality; }; QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey, @@ -33,3 +46,6 @@ QString encodeInviteLink(uint64_t channelId, const QByteArray& pubkey, const QString& password = QString()); InviteData decodeInviteLink(const QString& link); + +/* reality-префикс + адреса: has(1) [short_id(8) pubkey(32) ver(3) snlen(1) sn(N)] addrs*(family sockId proto addr port) */ +QByteArray serializeInviteAddrs(const InviteData& d); diff --git a/tools/chatgui/src/inviteby.cpp b/tools/chatgui/src/inviteby.cpp index 4c2775df..a8d24b56 100644 --- a/tools/chatgui/src/inviteby.cpp +++ b/tools/chatgui/src/inviteby.cpp @@ -179,15 +179,7 @@ void InviteByLinkDialog::onSendInvite() { m_channelId.toUtf8().constData(), targetNodeId, d.addrs.size()); /* Save addresses to DB */ - QByteArray addrsBuf; - for (const auto& a : d.addrs) { - addrsBuf.append(static_cast(a.family)); - addrsBuf.append(static_cast(a.socketId)); - addrsBuf.append(static_cast(a.proto)); - addrsBuf.append(a.address); - addrsBuf.append(static_cast((a.port >> 8) & 0xFF)); - addrsBuf.append(static_cast(a.port & 0xFF)); - } + QByteArray addrsBuf = serializeInviteAddrs(d); chat_sync_invite_to_channel_with_addrs( chat_core_get_inst(), m_channelId.toUtf8().constData(), targetNodeId, diff --git a/tools/chatgui/src/joindialog.cpp b/tools/chatgui/src/joindialog.cpp index a559bbff..2817e2a7 100644 --- a/tools/chatgui/src/joindialog.cpp +++ b/tools/chatgui/src/joindialog.cpp @@ -188,15 +188,7 @@ void JoinDialog::onConnectClicked() { setStatus("Connecting to peer..."); - QByteArray addrsBuf; - for (const auto& a : d.addrs) { - addrsBuf.append(static_cast(a.family)); - addrsBuf.append(static_cast(a.socketId)); - addrsBuf.append(static_cast(a.proto)); - addrsBuf.append(a.address); - addrsBuf.append(static_cast((a.port >> 8) & 0xFF)); - addrsBuf.append(static_cast(a.port & 0xFF)); - } + QByteArray addrsBuf = serializeInviteAddrs(d); int addrCount = d.addrs.size(); const char* pass = d.password.isEmpty() ? NULL : d.password.toUtf8().constData(); diff --git a/tools/chatgui/src/nodespage.cpp b/tools/chatgui/src/nodespage.cpp index 980fe869..2780c043 100644 --- a/tools/chatgui/src/nodespage.cpp +++ b/tools/chatgui/src/nodespage.cpp @@ -38,6 +38,7 @@ QString NodesPage::addrStr(const void* addr, int family) { } QString NodesPage::protoStr(int protocol) { + if (protocol & 0x04) return (protocol & 0x02) ? "TCP+REALITY" : "REALITY"; switch (protocol) { case 1: return "UDP"; case 2: return "TCP"; diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index 9cef0ae7..97370e7e 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -40,7 +40,7 @@ struct TOPO_GROUP_NODE; #define GUI_EVT_INVITE_CANDIDATES 30 /* data: [ch_id_len:1][ch_id:var][auto_node_id:8][count:2][node_id:8]* — достижимые узлы для invite */ /* Размеры элементов пакета GUI_EVT_MEMBER_DETAIL (должны совпадать с chat_status.c) */ -#define MEMBER_DETAIL_LINK_SIZE 63 +#define MEMBER_DETAIL_LINK_SIZE 64 #define MEMBER_DETAIL_MAX_LINKS 16 #define MEMBER_DETAIL_MAX_SOCKS 16 #define MEMBER_DETAIL_SOCK_SIZE 48