android: member list screen + link status dots, ipv6 autoconnect fixes

Android chatgui:
- New MemberListScreen with member list, detail card, edit dialog
- Link status dots (green/red/grey) per address in detail card (1s poll)
- Self-node name coloured green
- ChatScreen: people icon in TopAppBar

IPv6 autoconnect fixes (3 bugs in conn_mgr/topo):
- topo_node_sqlite_node_load: load v6 addresses from DB
- topo_node_registry_store: merge v6_addrs into existing node
- cm_sock_v6_classify: wildcard [::] treated as CM_V6_DIR
- cm_add_v6_link: htons(port) was missing

Memory fixes:
- strdup→u_strdup in bridge (7 instances)
- u_realloc cap update before success check (3 instances)
- char sql[256]→[512] for truncated SELECT

Chat member hash now includes addresses for change detection

Linux chatgui:
- Link status dots in AccountList detail panel (1s poll)
- Self-node name in green in member list
This commit is contained in:
evgeny committed 2026-08-01 23:40:58 +03:00
1 parent 6719a1e23c
commit 50156e12af
19 files changed
+1022 -23

No files matched your search

+25 -1
View File
@@ -126,8 +126,32 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
const char* atags = (const char*)sqlite3_column_text(stmt, 7);
const uint8_t* atsig = (const uint8_t*)sqlite3_column_blob(stmt, 8);
if (!x25 || !ed) continue;
uint8_t addrs[2048]; int addr_off = 0; int addr_count = 0;
sqlite3_stmt* as = NULL;
sqlite3_prepare_v2(db,
"SELECT family, socket_id, address, port FROM node_addresses WHERE node_id=? AND addr_type=0"
" ORDER BY family, address, port", -1, &as, NULL);
if (as) {
sqlite3_bind_int64(as, 1, (sqlite3_int64)nid);
while (sqlite3_step(as) == SQLITE_ROW && addr_off < (int)sizeof(addrs) - 8) {
int fam = sqlite3_column_int(as, 0);
int sid = sqlite3_column_int(as, 1);
addrs[addr_off++] = (uint8_t)fam;
addrs[addr_off++] = (uint8_t)sid;
int ip_sz = fam == 4 ? 4 : 16;
memcpy(addrs + addr_off, sqlite3_column_blob(as, 2), (size_t)ip_sz);
addr_off += ip_sz;
uint16_t p = (uint16_t)sqlite3_column_int(as, 3);
addrs[addr_off++] = (uint8_t)(p >> 8);
addrs[addr_off++] = (uint8_t)(p & 0xFF);
addr_count++;
}
sqlite3_finalize(as);
}
uint8_t mh[MT_HASH_SIZE];
_compute_member_hash(nid, x25, ed, sig, jts, usig, uts, NULL, 0, atags, atsig, mh);
_compute_member_hash(nid, x25, ed, sig, jts, usig, uts, addrs, addr_count, atags, atsig, mh);
EVP_DigestUpdate(sha_ctx, mh, MT_HASH_SIZE);
count++;
}
+5 -2
View File
@@ -94,7 +94,10 @@ uint8_t cm_classify_v6_addr(const uint8_t addr[16]) {
uint8_t cm_sock_v6_classify(const struct ETCP_SOCKET* s) {
if (s->local_addr.ss_family != AF_INET6) return CM_V6_OTH;
return cm_classify_v6_addr(((const struct sockaddr_in6*)&s->local_addr)->sin6_addr.s6_addr);
const uint8_t* a6 = ((const struct sockaddr_in6*)&s->local_addr)->sin6_addr.s6_addr;
static const uint8_t z[16];
if (!memcmp(a6, z, 16)) return CM_V6_DIR;
return cm_classify_v6_addr(a6);
}
void cm_add_v4_link(struct ETCP_CONN* conn, const uint8_t* addr, uint16_t port, struct ETCP_SOCKET* s) {
@@ -106,7 +109,7 @@ void cm_add_v4_link(struct ETCP_CONN* conn, const uint8_t* addr, uint16_t port,
void cm_add_v6_link(struct ETCP_CONN* conn, const uint8_t addr[16], uint16_t port, struct ETCP_SOCKET* s) {
struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6;
memcpy(&sin6.sin6_addr, addr, 16); sin6.sin6_port = port;
memcpy(&sin6.sin6_addr, addr, 16); sin6.sin6_port = htons(port);
if (cm_classify_v6_addr(addr) == CM_V6_LL) { struct sockaddr_in6* our = (struct sockaddr_in6*)&s->local_addr; sin6.sin6_scope_id = our->sin6_scope_id; }
struct sockaddr_storage sa; memcpy(&sa, &sin6, sizeof(sin6));
etcp_link_new(conn, s, &sa, 0);
+2
View File
@@ -107,6 +107,8 @@ struct TOPO_NODE* topo_node_registry_store(struct TOPO_GROUPS* groups, struct TO
struct TOPO_NODE* existing = topo_node_registry_find(groups, ni->node_id);
if (existing) {
topo_node_ref(existing);
if (!existing->v4_addrs && ni->v4_addrs) { existing->v4_addrs = ni->v4_addrs; ni->v4_addrs = NULL; }
if (!existing->v6_addrs && ni->v6_addrs) { existing->v6_addrs = ni->v6_addrs; ni->v6_addrs = NULL; }
topo_node_free_raw(groups, ni);
return existing;
}
+37 -6
View File
@@ -154,17 +154,17 @@ int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_GROUPS* groups, struct TO
/* delete stale addresses for socket_ids no longer present */
{
char del_sql[256];
char del_sql[512];
if (sock_count > 0) {
int pos = snprintf(del_sql, sizeof(del_sql),
"DELETE FROM node_addresses WHERE node_id=? AND socket_id NOT IN (");
"DELETE FROM node_addresses WHERE node_id=? AND addr_type!=0 AND socket_id NOT IN (");
for (uint8_t i = 0; i < sock_count; i++) {
if (i > 0) pos += snprintf(del_sql + pos, sizeof(del_sql) - (size_t)pos, ",");
pos += snprintf(del_sql + pos, sizeof(del_sql) - (size_t)pos, "%d", (int)sock_ids[i]);
}
pos += snprintf(del_sql + pos, sizeof(del_sql) - (size_t)pos, ") AND socket_id != 0");
} else {
snprintf(del_sql, sizeof(del_sql), "DELETE FROM node_addresses WHERE node_id=? AND socket_id != 0");
snprintf(del_sql, sizeof(del_sql), "DELETE FROM node_addresses WHERE node_id=? AND addr_type!=0 AND socket_id != 0");
}
sqlite3_stmt* del_stmt = NULL;
if (sqlite3_prepare_v2(db, del_sql, -1, &del_stmt, NULL) == SQLITE_OK) {
@@ -757,7 +757,35 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
sqlite3_finalize(s);
}
}
if (!v4_head) { u_free(name); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx has no IPv4/UDP addresses", (unsigned long long)node_id); return NULL; }
/* IPv6 addresses */
struct TOPO_ADDR6* v6_head = NULL; struct TOPO_ADDR6* v6_tail = NULL;
{
sqlite3_stmt* s = NULL;
if (sqlite3_prepare_v2(db,
"SELECT address,port,addr_type,socket_id FROM node_addresses"
" WHERE node_id=? AND family=6 AND protocol=1 ORDER BY addr_type",
-1, &s, NULL) == SQLITE_OK) {
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
while (sqlite3_step(s) == SQLITE_ROW) {
const void* addr = sqlite3_column_blob(s, 0);
if (!addr || sqlite3_column_bytes(s, 0) < 16) continue;
int port = sqlite3_column_int(s, 1);
int at = sqlite3_column_int(s, 2);
int sock_id_raw = sqlite3_column_int(s, 3);
uint8_t sock_id = (uint8_t)(sock_id_raw ? sock_id_raw : ++sock_id_counter);
uint8_t type = (at == ADDR_TYPE_NETIF) ? TOPO_ADDR_INTERFACE : TOPO_ADDR_NAT;
struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool);
if (!a6) continue;
memset(a6, 0, sizeof(*a6));
memcpy(a6->addr, addr, 16); a6->port = (uint16_t)port;
a6->type = type; a6->socket_id = sock_id; a6->protocol = TOPO_PROTO_UDP;
if (!v6_head) v6_head = v6_tail = a6;
else { v6_tail->next = a6; v6_tail = a6; }
}
sqlite3_finalize(s);
}
}
if (!v4_head && !v6_head) { u_free(name); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx has no addresses", (unsigned long long)node_id); return NULL; }
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
if (!ni) { u_free(name); return NULL; }
@@ -765,9 +793,12 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
memcpy(ni->public_key, pubkey, 32); memcpy(ni->ed25519_public_key, ed_pubkey, 32);
ni->node_name = name;
ni->v4_addrs = v4_head;
ni->v6_addrs = v6_head;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx loaded from DB name=%s v4_addrs=%d",
(unsigned long long)node_id, name ? name : "", topo_list_count((struct _topo_head*)v4_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 : "",
topo_list_count((struct _topo_head*)v4_head),
topo_list_count((struct _topo_head*)v6_head));
return ni;
}