Browse Source

refactor: TCP socket addresses — sync to chat DB, topo_node, config file single source

- chat: collect_my_addrs extends to TCP + IPv6, chat_core_sync_my_addresses writes TCP addrs
- topo_node: replace CFG_SERVER with TCP_SOCKET for TCP addresses, simplify NAT logic
- etcp_connections: add IPv6 interface addr resolution for TCP sockets
- Android: INI config file as single source of truth for servers, remove JSON/Gson
- Android: filter temporary IPv6 addresses, fix restart with socket cleanup
- instance_lite: sync addresses after socket creation, proper restart cleanup
topo_upd
evgeny 2 months ago
parent
commit
ddf696bcf6
  1. 62
      src/chat/chat_channel.c
  2. 2
      src/chat/chat_core.c
  3. 58
      src/chat/chat_profile.c
  4. 118
      src/routing_layer/topo_node.c
  5. 34
      src/transport_layer/etcp_connections.c
  6. 4
      tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt
  7. 188
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt
  8. 4
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt
  9. 15
      tools/chatgui-android/libutun_lite/instance_lite.c

62
src/chat/chat_channel.c

@ -18,6 +18,7 @@
#include "../../lib/ll_queue.h"
#include "../../lib/platform_compat.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/etcp_connections.h"
#include <openssl/sha.h>
#include <openssl/evp.h>
@ -52,24 +53,66 @@ static int channel_sign_join(const uint8_t* ch_x25519, const uint8_t* ch_ed25519
/* ─── сбор адресов текущего узла (для member_sync_put) ─── */
static int collect_my_addrs(uint8_t* buf, int max_addrs) {
int count = 0;
static int collect_my_addrs(uint8_t* buf, int max_buf_sz) {
int pos = 0, count = 0;
struct ETCP_SOCKET* s = g_cc.inst->etcp_sockets;
while (s && count < max_addrs) {
while (s) {
struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : NULL;
if (!sa) sa = s->local_addr.ss_family ? &s->local_addr : NULL;
if (sa && sa->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
buf[count * 8] = 4;
buf[count * 8 + 1] = s->sock_id;
memcpy(buf + count * 8 + 2, &sin->sin_addr, 4);
if (pos + 8 > max_buf_sz) break;
buf[pos++] = 4; buf[pos++] = s->sock_id;
memcpy(buf + pos, &sin->sin_addr, 4); pos += 4;
uint16_t port = ntohs(sin->sin_port);
buf[count * 8 + 6] = (uint8_t)(port >> 8);
buf[count * 8 + 7] = (uint8_t)(port & 0xFF);
buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF);
count++;
uint8_t* ip = (uint8_t*)&sin->sin_addr;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] collect_my_addrs UDP sock=%d %d.%d.%d.%d:%d",
CC_ID, s->sock_id, ip[0], ip[1], ip[2], ip[3], port);
} else if (sa && sa->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
if (pos + 20 > max_buf_sz) break;
buf[pos++] = 6; buf[pos++] = s->sock_id;
memcpy(buf + pos, &sin6->sin6_addr, 16); pos += 16;
uint16_t port = ntohs(sin6->sin6_port);
buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF);
count++;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] collect_my_addrs UDP v6 sock=%d port=%d",
CC_ID, s->sock_id, port);
}
s = s->next;
}
{ struct TCP_SOCKET* ts = g_cc.inst->tcp_sockets;
while (ts) {
struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr;
if (addr && addr->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)addr;
if (pos + 8 > max_buf_sz) break;
buf[pos++] = 4; buf[pos++] = ts->sock_id;
memcpy(buf + pos, &sin->sin_addr, 4); pos += 4;
uint16_t port = ntohs(sin->sin_port);
buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF);
count++;
uint8_t* ip = (uint8_t*)&sin->sin_addr;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] collect_my_addrs TCP sock=%d %d.%d.%d.%d:%d",
CC_ID, ts->sock_id, ip[0], ip[1], ip[2], ip[3], port);
} else if (addr && addr->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
if (pos + 20 > max_buf_sz) break;
buf[pos++] = 6; buf[pos++] = ts->sock_id;
memcpy(buf + pos, &sin6->sin6_addr, 16); pos += 16;
uint16_t port = ntohs(sin6->sin6_port);
buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF);
count++;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] collect_my_addrs TCP v6 sock=%d port=%d",
CC_ID, ts->sock_id, port);
}
ts = ts->next; }
}
return count;
}
@ -140,7 +183,7 @@ void chat_core_create_channel(struct chat_channel_create* req) {
myid, g_cc.inst->my_keys.public_key,
join_ts, g_cc.inst->my_ed25519_privkey, join_sig);
uint8_t my_addrs[256]; int my_addr_cnt = collect_my_addrs(my_addrs, 16);
uint8_t my_addrs[256]; int my_addr_cnt = collect_my_addrs(my_addrs, (int)sizeof(my_addrs));
char juser3[256]; snprintf(juser3, sizeof(juser3), "{\"name\":\"%s\"}",
g_cc.inst->name[0] ? g_cc.inst->name : "");
@ -151,6 +194,7 @@ void chat_core_create_channel(struct chat_channel_create* req) {
DEBUG_ERROR(DEBUG_CATEGORY_DB_SYNC, "%s: member_sync_put(self) FAILED ch=%s rc=%d",
CC_ID, req->channel_id, mrc);
}
chat_core_sync_my_addresses();
}
chat_event_post(CHAT_EVT_CHANNEL_UPDATED, data, 1 + ch_id_len);

2
src/chat/chat_core.c

@ -135,8 +135,6 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) {
g_cc.initialized = 1;
chat_core_sync_my_addresses();
/* create media_files table */
media_index_init(g_cc.db);

58
src/chat/chat_profile.c

@ -12,6 +12,7 @@
#include "../utun_instance.h"
#include "../transport_layer/etcp.h"
#include "../transport_layer/etcp_connections.h"
#include "../transport_layer/secure_channel.h"
#include "../ntp_time.h"
#include "../../lib/u_async.h"
@ -113,7 +114,7 @@ void chat_core_sync_my_addresses(void) {
}
struct ETCP_SOCKET* sock = g_cc.inst->etcp_sockets;
int sock_count = 0, written = 0;
int sock_count = 0, written = 0, tcp_written = 0;
while (sock) {
sock_count++;
int sock_family = sock->local_addr.ss_family ? sock->local_addr.ss_family : 0;
@ -149,13 +150,56 @@ void chat_core_sync_my_addresses(void) {
}
sqlite3_finalize(ins);
if (sock_count == 0)
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] etcp_sockets is NULL — NO sockets, addresses NOT written!", CC_ID);
else if (written == 0)
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] %d sockets found but 0 addresses written (all have no interface_addr nor local_addr)", CC_ID, sock_count);
{ sqlite3_stmt* tcp_ins = NULL;
sqlite3_prepare_v2(g_cc.db,
"INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)"
" VALUES(?,?,2,?,?,0,?)", -1, &tcp_ins, NULL);
if (tcp_ins) {
struct TCP_SOCKET* ts = g_cc.inst->tcp_sockets;
int tcp_count = 0;
while (ts) { if (ts->type == CFG_SERVER_TYPE_PRIVATE || ts->type == CFG_SERVER_TYPE_LOCAL) { ts = ts->next; continue; }
tcp_count++;
struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr;
if (!addr || !addr->ss_family) { ts = ts->next; continue; }
if (addr->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)addr;
sqlite3_bind_int64(tcp_ins, 1, (sqlite3_int64)g_cc.my_node_id);
sqlite3_bind_int(tcp_ins, 2, 4);
sqlite3_bind_blob(tcp_ins, 3, &sin->sin_addr, 4, SQLITE_STATIC);
sqlite3_bind_int(tcp_ins, 4, (int)ntohs(sin->sin_port));
sqlite3_bind_int(tcp_ins, 5, (int)ts->sock_id);
sqlite3_step(tcp_ins); sqlite3_reset(tcp_ins);
uint8_t* ip = (uint8_t*)&sin->sin_addr;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] INSERT TCP my_node=0x%016llx sock=%d %d.%d.%d.%d:%d",
CC_ID, (unsigned long long)g_cc.my_node_id, ts->sock_id, ip[0], ip[1], ip[2], ip[3], (int)ntohs(sin->sin_port));
tcp_written++;
} else if (addr->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
sqlite3_bind_int64(tcp_ins, 1, (sqlite3_int64)g_cc.my_node_id);
sqlite3_bind_int(tcp_ins, 2, 6);
sqlite3_bind_blob(tcp_ins, 3, &sin6->sin6_addr, 16, SQLITE_STATIC);
sqlite3_bind_int(tcp_ins, 4, (int)ntohs(sin6->sin6_port));
sqlite3_bind_int(tcp_ins, 5, (int)ts->sock_id);
sqlite3_step(tcp_ins); sqlite3_reset(tcp_ins);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] INSERT TCP v6 my_node=0x%016llx sock=%d port=%d",
CC_ID, (unsigned long long)g_cc.my_node_id, ts->sock_id, (int)ntohs(sin6->sin6_port));
tcp_written++;
}
ts = ts->next; }
sqlite3_finalize(tcp_ins);
if (tcp_written > 0)
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] TCP DONE: %d addresses written, my_node=0x%016llx",
CC_ID, tcp_written, (unsigned long long)g_cc.my_node_id);
}
}
if (sock_count == 0 && tcp_written == 0)
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] no sockets found — NO addresses written!", CC_ID);
else if (written == 0 && tcp_written == 0)
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] %d sockets found but 0 addresses written", CC_ID, sock_count);
else
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] DONE: %d of %d sockets written, my_node=0x%016llx",
CC_ID, written, sock_count, (unsigned long long)g_cc.my_node_id);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "%s: [ADDR_SYNC] DONE: %d UDP + %d TCP = %d total, my_node=0x%016llx",
CC_ID, written, tcp_written, written + tcp_written, (unsigned long long)g_cc.my_node_id);
sqlite3_stmt* chk = NULL;
sqlite3_prepare_v2(g_cc.db, "SELECT COUNT(*) FROM node_addresses WHERE node_id=? AND addr_type=0", -1, &chk, NULL);

118
src/routing_layer/topo_node.c

@ -14,6 +14,7 @@
#include "route_lib.h"
#include "etcp_debug.h"
#include "topo_node_sqlite.h"
#include "../transport_layer/etcp_connections.h"
#include "../lib/u_async.h"
void topo_node_ref(struct TOPO_NODE* ni) {
@ -603,26 +604,19 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
if (group->group_type == TOPO_GROUP_TYPE_CHAT) { vc = 0; vc6 = 0; }
int sock_count = 0, addr_count = 0, sock6_count = 0, addr6_count = 0;
int tcp4_count = 0, tcp6_count = 0;
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
while (e_sock) {
if (e_sock->type == CFG_SERVER_TYPE_PRIVATE || e_sock->type == CFG_SERVER_TYPE_LOCAL) { e_sock = e_sock->next; continue; }
if (e_sock->local_addr.ss_family == AF_INET) {
sock_count++; addr_count++;
if (e_sock->nat_addr.ss_family == AF_INET) {
struct sockaddr_in* ns = (struct sockaddr_in*)&e_sock->nat_addr;
struct sockaddr_in* ls = (struct sockaddr_in*)&e_sock->local_addr;
struct sockaddr_in* ifs = (struct sockaddr_in*)&e_sock->interface_addr;
uint32_t ref_ip = ls->sin_addr.s_addr ? ls->sin_addr.s_addr : ifs->sin_addr.s_addr;
uint16_t ref_port = ntohs(ls->sin_addr.s_addr ? ls->sin_port : ifs->sin_port);
if (ns->sin_addr.s_addr != 0 && e_sock->nat_type != NAT_VERIFIED_STRICT && (ns->sin_addr.s_addr != ref_ip || ntohs(ns->sin_port) != ref_port))
addr_count++;
}
if (e_sock->nat_type == NAT_VERIFIED_DIRECT || ((e_sock->type == CFG_SERVER_TYPE_PUBLIC || e_sock->type == CFG_SERVER_TYPE_UNKNOWN) && e_sock->nat_type < NAT_VERIFIED_UNKNOWN)) addr_count++;
} else if (e_sock->local_addr.ss_family == AF_INET6) { sock6_count++; addr6_count++; }
if (e_sock->local_addr.ss_family == AF_INET) { sock_count++; addr_count++; }
else if (e_sock->local_addr.ss_family == AF_INET6) { sock6_count++; addr6_count++; }
e_sock = e_sock->next;
}
{ struct CFG_SERVER* srv = instance->config->servers;
while (srv) { if (srv->transport) { if (srv->ip.ss_family == AF_INET) addr_count++; else if (srv->ip.ss_family == AF_INET6) addr6_count++; } srv = srv->next; }
{ struct TCP_SOCKET* ts = instance->tcp_sockets;
while (ts) { if (ts->type == CFG_SERVER_TYPE_PRIVATE || ts->type == CFG_SERVER_TYPE_LOCAL) { ts = ts->next; continue; }
if (ts->interface_addr.ss_family == AF_INET || ts->local_addr.ss_family == AF_INET) { tcp4_count++; addr_count++; }
else if (ts->interface_addr.ss_family == AF_INET6 || ts->local_addr.ss_family == AF_INET6) { tcp6_count++; addr6_count++; }
ts = ts->next; }
}
int changed = 1; uint8_t old_ver = 0;
@ -693,25 +687,19 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
struct sockaddr_in* local_sin = (struct sockaddr_in*)&e_sock->local_addr;
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
int use_local = (local_sin->sin_addr.s_addr != 0);
uint32_t ref_ip = use_local ? local_sin->sin_addr.s_addr : if_sin->sin_addr.s_addr;
uint16_t ref_port = ntohs(use_local ? local_sin->sin_port : if_sin->sin_port);
int nat_verified = (e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0 && e_sock->nat_type != NAT_VERIFIED_STRICT);
int nat_differs = (nat_verified && (nat_sin->sin_addr.s_addr != ref_ip || ntohs(nat_sin->sin_port) != ref_port));
{ struct TOPO_ADDR4* a = memory_pool_alloc(group->instance->topo_groups->v4_addr_pool);
if (nat_verified) {
memcpy(a->addr, &nat_sin->sin_addr.s_addr, 4); a->port = ntohs(nat_sin->sin_port);
a->type = TOPO_ADDR_NAT; a->socket_id = e_sock->sock_id | 1;
} else {
int use_local = (local_sin->sin_addr.s_addr != 0);
memcpy(a->addr, use_local ? &local_sin->sin_addr.s_addr : &if_sin->sin_addr.s_addr, 4);
a->port = ntohs(use_local ? local_sin->sin_port : if_sin->sin_port);
a->type = (!nat_differs && nat_verified) ? TOPO_ADDR_NAT : TOPO_ADDR_INTERFACE;
a->socket_id = (a->type == TOPO_ADDR_NAT) ? (e_sock->sock_id | 1) : e_sock->sock_id;
a->type = TOPO_ADDR_INTERFACE; a->socket_id = e_sock->sock_id;
}
a->protocol = TOPO_PROTO_UDP;
a->next = ni->v4_addrs; ni->v4_addrs = a; }
if (nat_differs) {
struct TOPO_ADDR4* a = memory_pool_alloc(group->instance->topo_groups->v4_addr_pool);
memcpy(a->addr, &nat_sin->sin_addr.s_addr, 4); a->port = ntohs(nat_sin->sin_port);
a->type = TOPO_ADDR_NAT; a->socket_id = e_sock->sock_id | 1; a->protocol = TOPO_PROTO_UDP;
a->next = ni->v4_addrs; ni->v4_addrs = a;
}
} else if (e_sock->local_addr.ss_family == AF_INET6) {
{ struct TOPO_SOCKMETA6* sm6 = memory_pool_alloc(group->instance->topo_groups->v6_sock_meta_pool);
sm6->id = e_sock->sock_id; sm6->config_type = e_sock->type; sm6->nat_type = e_sock->nat_type;
@ -724,20 +712,24 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
}
e_sock = e_sock->next;
}
{ struct CFG_SERVER* srv = instance->config->servers;
while (srv) { if (srv->transport && srv->ip.ss_family == AF_INET) {
struct sockaddr_in* tcp_sin = (struct sockaddr_in*)&srv->ip;
struct TOPO_ADDR4* a = memory_pool_alloc(group->instance->topo_groups->v4_addr_pool);
memcpy(a->addr, &tcp_sin->sin_addr.s_addr, 4); a->port = ntohs(tcp_sin->sin_port);
a->type = TOPO_ADDR_INTERFACE; a->socket_id = 0; a->protocol = TOPO_PROTO_TCP;
a->next = ni->v4_addrs; ni->v4_addrs = a;
} else if (srv->transport && srv->ip.ss_family == AF_INET6) {
struct sockaddr_in6* tcp_sin6 = (struct sockaddr_in6*)&srv->ip;
struct TOPO_ADDR6* a6 = memory_pool_alloc(group->instance->topo_groups->v6_addr_pool);
memcpy(a6->addr, &tcp_sin6->sin6_addr, 16); a6->port = ntohs(tcp_sin6->sin6_port);
a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = 0; a6->protocol = TOPO_PROTO_TCP;
a6->next = ni->v6_addrs; ni->v6_addrs = a6;
} srv = srv->next; }
{ struct TCP_SOCKET* ts = instance->tcp_sockets;
while (ts) { if (ts->type == CFG_SERVER_TYPE_PRIVATE || ts->type == CFG_SERVER_TYPE_LOCAL) { ts = ts->next; continue; }
struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr;
if (!addr || !addr->ss_family) { ts = ts->next; continue; }
if (addr->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)addr;
{ struct TOPO_ADDR4* a = memory_pool_alloc(group->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->next = ni->v4_addrs; ni->v4_addrs = a; }
} else if (addr->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
{ struct TOPO_ADDR6* a6 = memory_pool_alloc(group->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->next = ni->v6_addrs; ni->v6_addrs = a6; }
}
ts = ts->next; }
}
if (group->group_type != TOPO_GROUP_TYPE_CHAT && (vc || vc6)) {
@ -758,8 +750,8 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
lq->subnets = r;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4s=%d v4a=%d v6s=%d v6a=%d v4sub=%d v6sub=%d ver=%d",
sock_count, addr_count, sock6_count, addr6_count, vc, vc6, ni->ver);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my_nodeinfo updated: v4s=%d v4a=%d v6s=%d v6a=%d tcp4=%d tcp6=%d v4sub=%d v6sub=%d ver=%d",
sock_count, addr_count, sock6_count, addr6_count, tcp4_count, tcp6_count, vc, vc6, ni->ver);
if (group->group_type != TOPO_GROUP_TYPE_CHAT && instance->rt && !route_insert(instance->rt, lq))
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "failed to insert local routes");
} else {
@ -793,24 +785,19 @@ void topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
struct sockaddr_in* local_sin = (struct sockaddr_in*)&e_sock->local_addr;
struct sockaddr_in* if_sin = (struct sockaddr_in*)&e_sock->interface_addr;
struct sockaddr_in* nat_sin = (struct sockaddr_in*)&e_sock->nat_addr;
int use_local = (local_sin->sin_addr.s_addr != 0);
uint32_t ref_ip = use_local ? local_sin->sin_addr.s_addr : if_sin->sin_addr.s_addr;
uint16_t ref_port = ntohs(use_local ? local_sin->sin_port : if_sin->sin_port);
int nat_verified = (e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0 && e_sock->nat_type != NAT_VERIFIED_STRICT);
int nat_differs = (nat_verified && (nat_sin->sin_addr.s_addr != ref_ip || ntohs(nat_sin->sin_port) != ref_port));
{ struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool);
if (nat_verified) {
memcpy(a->addr, &nat_sin->sin_addr.s_addr, 4); a->port = ntohs(nat_sin->sin_port);
a->type = TOPO_ADDR_NAT; a->socket_id = e_sock->sock_id | 1;
} else {
int use_local = (local_sin->sin_addr.s_addr != 0);
memcpy(a->addr, use_local ? &local_sin->sin_addr.s_addr : &if_sin->sin_addr.s_addr, 4);
a->port = ntohs(use_local ? local_sin->sin_port : if_sin->sin_port);
a->type = (!nat_differs && nat_verified) ? TOPO_ADDR_NAT : TOPO_ADDR_INTERFACE;
a->socket_id = (a->type == TOPO_ADDR_NAT) ? (e_sock->sock_id | 1) : e_sock->sock_id;
a->type = TOPO_ADDR_INTERFACE; a->socket_id = e_sock->sock_id;
}
a->protocol = TOPO_PROTO_UDP;
a->next = ni->v4_addrs; ni->v4_addrs = a; }
if (nat_differs) {
struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool);
memcpy(a->addr, &nat_sin->sin_addr.s_addr, 4); a->port = ntohs(nat_sin->sin_port);
a->type = TOPO_ADDR_NAT; a->socket_id = e_sock->sock_id | 1; a->protocol = TOPO_PROTO_UDP;
a->next = ni->v4_addrs; ni->v4_addrs = a;
}
} else if (e_sock->local_addr.ss_family == AF_INET6) {
{ struct TOPO_SOCKMETA6* sm6 = memory_pool_alloc(instance->topo_groups->v6_sock_meta_pool);
sm6->id = e_sock->sock_id; sm6->config_type = e_sock->type; sm6->nat_type = e_sock->nat_type;
@ -823,6 +810,25 @@ void topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
}
e_sock = e_sock->next;
}
{ struct TCP_SOCKET* ts = instance->tcp_sockets;
while (ts) { if (ts->type == CFG_SERVER_TYPE_PRIVATE || ts->type == CFG_SERVER_TYPE_LOCAL) { ts = ts->next; continue; }
struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr;
if (!addr || !addr->ss_family) { ts = ts->next; continue; }
if (addr->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)addr;
{ 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->next = ni->v4_addrs; ni->v4_addrs = a; }
} else if (addr->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr;
{ 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->next = ni->v6_addrs; ni->v6_addrs = a6; }
}
ts = ts->next; }
}
ni->ver = (ni->ver % 255) + 1;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my addresses updated, new ver=%d", ni->ver);

34
src/transport_layer/etcp_connections.c

@ -784,18 +784,46 @@ struct TCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SER
rs->sin_family = AF_INET; rs->sin_port = htons(53);
inet_pton(AF_INET, "8.8.8.8", &rs->sin_addr);
struct sockaddr_storage local;
if (get_outgoing_local_ip(&server->ip, &remote, &local) == 0)
if (get_outgoing_local_ip(&server->ip, &remote, &local) == 0) {
ts->interface_addr = local;
((struct sockaddr_in*)&ts->interface_addr)->sin_port = sin->sin_port;
}
}
} else if (server->ip.ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&server->ip;
memcpy(&ts->local_addr, sin6, sizeof(*sin6));
ts->interface_addr = ts->local_addr;
if (server->type == CFG_SERVER_TYPE_PUBLIC && memcmp(&sin6->sin6_addr, &in6addr_any, sizeof(struct in6_addr)) == 0) {
uint16_t v6if = (server->netif_index > 0) ? server->netif_index : get_default_route_netif_index(AF_INET6);
if (v6if > 0) {
uint8_t v6addr[16];
int got = get_interface_ipv6_addr_nl(v6if, 1, v6addr);
if (got != 0) got = get_interface_ipv6_addr_nl(v6if, 0, v6addr);
if (got == 0) {
struct sockaddr_in6* if6 = (struct sockaddr_in6*)&ts->interface_addr;
if6->sin6_family = AF_INET6; memcpy(&if6->sin6_addr, v6addr, 16);
}
}
if (ts->interface_addr.ss_family == 0) {
struct sockaddr_storage remote; memset(&remote, 0, sizeof(remote));
struct sockaddr_in6* rs6 = (struct sockaddr_in6*)&remote;
rs6->sin6_family = AF_INET6; rs6->sin6_port = htons(53);
inet_pton(AF_INET6, "2001:4860:4860::8888", &rs6->sin6_addr);
if (get_outgoing_local_ip(&server->ip, &remote, &ts->interface_addr) == 0)
((struct sockaddr_in6*)&ts->interface_addr)->sin6_port = sin6->sin6_port;
else
ts->interface_addr = ts->local_addr;
}
}
}
ts->next = instance->tcp_sockets;
instance->tcp_sockets = ts;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_socket_add: %s type=%d sock_id=%u", ts->name, ts->type, ts->sock_id);
{ char loc_str[64] = "none", if_str[64] = "none";
if (ts->local_addr.ss_family) snprintf(loc_str, sizeof(loc_str), "%s", sockaddr_storage_to_str(&ts->local_addr).str);
if (ts->interface_addr.ss_family) snprintf(if_str, sizeof(if_str), "%s", sockaddr_storage_to_str(&ts->interface_addr).str);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "tcp_socket_add: %s type=%d sock_id=%u local=%s iface=%s",
ts->name, ts->type, ts->sock_id, loc_str, if_str); }
return ts;
}
@ -1406,8 +1434,6 @@ static void send_init_response(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pk
if (g) {
topo_group_update_my_nodeinfo(g->instance, g);
if (g->local_node && g->senders_list) {
struct TOPO_NODE* vni = topo_node_registry_find(g->instance->topo_groups, g->local_node->node_id);
if (vni) vni->ver = (vni->ver % 255) + 1;
struct ll_entry* se = g->senders_list->head;
while (se) {
struct TOPO_GROUP_CONN_ITEM* item = (struct TOPO_GROUP_CONN_ITEM*)se->data;

4
tools/chatgui-android/app/src/main/java/com/utun/chat/ChatApplication.kt

@ -35,10 +35,8 @@ class ChatApplication : Application() {
/* First launch config */
if (configProvider.isFirstLaunch()) {
kotlinx.coroutines.runBlocking {
LogManager.addLog("INFO", "APP", "First launch — configuring")
configProvider.detectInterfaces()
LogManager.addLog("INFO", "APP", "First launch — marking launched")
configProvider.markLaunched()
LogManager.addLog("INFO", "APP", "First launch done, ${configProvider.loadServers().size} interfaces detected")
}
}
}

188
tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt

@ -7,8 +7,6 @@ import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.first
@ -19,6 +17,7 @@ import java.net.InetAddress
import java.net.NetworkInterface
import java.io.File
import java.util.UUID
import java.util.concurrent.ThreadLocalRandom
private val Context.dataStore by preferencesDataStore("utun_config")
@ -35,7 +34,6 @@ class ConfigProvider(private val context: Context) {
private val debugCategories = stringPreferencesKey("debug_categories")
private val logUdpIp = stringPreferencesKey("log_udp_ip")
private val logUdpPort = intPreferencesKey("log_udp_port")
private val serversJson = stringPreferencesKey("servers")
private val chatStorageAutoload = intPreferencesKey("chat_storage_autoload")
private val chatStorageAutoloadMaxsizeMb = intPreferencesKey("chat_storage_autoload_maxsize_mb")
private val chatStorageMaxsizeGb = intPreferencesKey("chat_storage_maxsize_gb")
@ -46,9 +44,6 @@ class ConfigProvider(private val context: Context) {
private val chatMediaDownloadMaxPeers = intPreferencesKey("chat_media_download_max_peers")
private val firstLaunchDone = booleanPreferencesKey("first_launch_done")
private val gson = Gson()
private val random = java.util.concurrent.ThreadLocalRandom.current()
private fun keysFilePath(): String {
val dir = runBlocking { context.dataStore.data.first()[dbPath] } ?: context.filesDir.absolutePath
return "$dir/keys.conf"
@ -95,19 +90,6 @@ class ConfigProvider(private val context: Context) {
fun getString(key: String): String? = runBlocking {
when {
key.startsWith("servers.") -> {
val parts = key.removePrefix("servers.").split(".", limit = 2)
val idx = parts[0].toIntOrNull() ?: return@runBlocking null
val field = parts.getOrNull(1) ?: return@runBlocking null
val list = loadServers()
val srv = list.getOrNull(idx) ?: return@runBlocking null
when (field) {
"bindIp" -> srv.bindIp
"protocol" -> srv.protocol
else -> null
}
}
key == "servers.count" -> loadServers().size.toString()
key == "node.private_key" -> readKeysFromFile()?.second ?: context.dataStore.data.first()[nodePrivateKey]
key == "node.public_key" -> readKeysFromFile()?.first ?: context.dataStore.data.first()[nodePublicKey]
key == "node.name" -> context.dataStore.data.first()[nodeName] ?: getDeviceName()
@ -123,18 +105,6 @@ class ConfigProvider(private val context: Context) {
fun getInt(key: String): Int = runBlocking {
when {
key.startsWith("servers.") -> {
val parts = key.removePrefix("servers.").split(".", limit = 2)
val idx = parts[0].toIntOrNull() ?: return@runBlocking 0
val field = parts.getOrNull(1) ?: return@runBlocking 0
val list = loadServers()
val srv = list.getOrNull(idx) ?: return@runBlocking 0
when (field) {
"port" -> srv.port
else -> 0
}
}
key == "servers.count" -> loadServers().size
key == "node.listen_port" -> context.dataStore.data.first()[nodeListenPort] ?: 12345
key == "control.port" -> context.dataStore.data.first()[controlPort] ?: 9999
key == "log_udp.port" -> context.dataStore.data.first()[logUdpPort] ?: 9999
@ -191,6 +161,20 @@ class ConfigProvider(private val context: Context) {
// fc00::/7 → fc00-fdff, 2000::/3 → 2000-3fff
return hi in 0xFC00..0xFDFF || hi in 0x2000..0x3FFF
}
// 0x20 = IFA_F_TEMPORARY в /proc/net/if_inet6
fun isTemporaryV6(ifName: String, ip: String): Boolean {
val hexAddr = ip.split("%")[0].replace(":", "").uppercase().padStart(32, '0')
return try {
File("/proc/net/if_inet6").useLines { lines ->
lines.any { line ->
val parts = line.trim().split("\\s+".toRegex(), limit = 6)
parts.size >= 6 && parts[0] == hexAddr && parts[5] == ifName &&
parts[4].toIntOrNull(16)?.let { (it and 0x20) != 0 } == true
}
}
} catch (_: Exception) { false }
}
}
/* ── First launch ── */
@ -207,6 +191,7 @@ class ConfigProvider(private val context: Context) {
suspend fun detectInterfaces(): List<ServerEntry> {
val servers = mutableListOf<ServerEntry>()
val seen = mutableSetOf<String>()
try {
val nis = NetworkInterface.getNetworkInterfaces()
while (nis.hasMoreElements()) {
@ -216,68 +201,88 @@ class ConfigProvider(private val context: Context) {
val addr = a.address
val ip = when {
addr is Inet4Address -> addr.hostAddress
addr is Inet6Address && isUsableV6(addr) -> addr.hostAddress?.let { it.split("%")[0] }
addr is Inet6Address && isUsableV6(addr) && !isTemporaryV6(ni.name, addr.hostAddress ?: "") -> addr.hostAddress?.let { it.split("%")[0] }
else -> null
} ?: continue
val key = "${ni.name}/${if (ip.contains(":")) "v6" else "v4"}"
if (!seen.add(key)) continue
servers.add(ServerEntry(
id = UUID.randomUUID().toString(),
ifName = ni.name,
bindIp = ip,
protocol = "tcp",
port = random.nextInt(2000, 10000) // 2000-9999
port = ThreadLocalRandom.current().nextInt(2000, 10000)
))
}
}
} catch (e: Exception) {
LogManager.addLog("ERROR", "CFG", "detectInterfaces failed: ${e.message}")
}
saveServers(servers)
return servers
}
suspend fun saveServers(servers: List<ServerEntry>) {
val json = gson.toJson(servers)
context.dataStore.edit { prefs -> prefs[serversJson] = json }
}
/* ── Config file as single source of truth for servers ── */
fun loadServers(): List<ServerEntry> = runBlocking {
val json = context.dataStore.data.first()[serversJson] ?: "[]"
try {
gson.fromJson(json, object : TypeToken<List<ServerEntry>>() {}.type) ?: emptyList()
} catch (e: Exception) {
emptyList()
}
fun configFilePath(): String {
val dir = getString("db.path") ?: context.filesDir.absolutePath
return "$dir/utun.cfg"
}
/** Return all currently available network interface names (for dropdown) */
fun getAvailableInterfaces(): List<Pair<String, String>> = runBlocking {
val list = mutableListOf<Pair<String, String>>()
fun loadServersFromConfig(): List<ServerEntry> {
val file = File(configFilePath())
if (!file.exists()) return emptyList()
data class RawSrv(val ifName: String, val family: String, val port: Int, val protocol: String)
val raw = mutableListOf<RawSrv>()
try {
val nis = NetworkInterface.getNetworkInterfaces()
while (nis.hasMoreElements()) {
val ni = nis.nextElement()
if (!ni.isUp || ni.isLoopback) continue
for (a in ni.interfaceAddresses) {
val addr = a.address
val ip = when {
addr is Inet4Address -> addr.hostAddress
addr is Inet6Address && isUsableV6(addr) -> addr.hostAddress?.let { it.split("%")[0] }
else -> null
} ?: continue
list.add(ni.name to ip)
val lines = file.readLines()
var ifName = ""; var family = ""; var addrPort = 0; var proto = "tcp"; var sectionOpen = false
for (line in lines) {
val trimmed = line.trim()
if (trimmed.startsWith("[server:") && trimmed.endsWith("]")) {
if (sectionOpen && ifName.isNotEmpty() && addrPort > 0)
raw.add(RawSrv(ifName, family, addrPort, proto))
val section = trimmed.removeSurrounding("[", "]").removePrefix("server:")
val lastUnderscore = section.lastIndexOf('_')
if (lastUnderscore > 0) {
ifName = section.substring(0, lastUnderscore)
family = section.substring(lastUnderscore + 1)
} else { ifName = ""; family = "" }
addrPort = 0; proto = "tcp"; sectionOpen = true
} else if (trimmed.startsWith("addr=")) {
val addr = trimmed.removePrefix("addr=")
val colonIdx = addr.lastIndexOf(':')
if (colonIdx >= 0) addrPort = addr.substring(colonIdx + 1).toIntOrNull() ?: 0
} else if (trimmed.startsWith("transport=")) {
proto = if (trimmed.removePrefix("transport=") == "udp") "udp" else "tcp"
}
}
if (sectionOpen && ifName.isNotEmpty() && addrPort > 0)
raw.add(RawSrv(ifName, family, addrPort, proto))
} catch (_: Exception) { return emptyList() }
val liveIps = getAvailableInterfaces()
return raw.map { rs ->
val isV6 = rs.family == "v6"
val realIp = liveIps.firstOrNull { (ifn, ip) ->
ifn == rs.ifName && (if (isV6) ip.contains(":") else !ip.contains(":"))
}?.second
ServerEntry(UUID.randomUUID().toString(), rs.ifName,
realIp ?: if (isV6) "::" else "0.0.0.0", rs.protocol, rs.port)
}
}
} catch (_: Exception) {}
if (list.isEmpty()) list.add("lo" to "127.0.0.1")
list
fun saveServersToConfig(servers: List<ServerEntry>) {
val text = buildConfigFromServers(servers)
File(configFilePath()).writeText(text)
LogManager.addLog("INFO", "CFG", "saveServersToConfig: ${servers.size} servers written" +
servers.joinToString(", ") { "${it.ifName}=${it.protocol}" })
}
/** Build full INI config text for NativeLib.start() */
fun buildConfigText(): String {
/** Build complete INI text from server list (without touching config file) */
private fun buildConfigFromServers(servers: List<ServerEntry>): String {
val pubKey = getString("node.public_key") ?: ""
val privKey = getString("node.private_key") ?: ""
val nodeName = getString("node.name") ?: getDeviceName()
val listenPort = getInt("node.listen_port")
val dbDir = getString("db.path") ?: context.filesDir.absolutePath
return buildString {
appendLine("[global]")
@ -287,12 +292,15 @@ class ConfigProvider(private val context: Context) {
appendLine("db_path=$dbDir")
appendLine("db_sync_enabled=1")
appendLine()
appendLine("[server:main]")
appendLine("addr=0.0.0.0:$listenPort")
appendLine()
appendLine("[server:main6]")
appendLine("addr=[::]:$listenPort")
for (srv in servers) {
val family = if (srv.bindIp.contains(":")) "v6" else "v4"
appendLine("[server:${srv.ifName}_$family]")
appendLine("addr=${if (family == "v6") "[::]" else "0.0.0.0"}:${srv.port}")
appendLine("type=public")
appendLine("transport=${srv.protocol}")
if (family == "v6") appendLine("netif=${srv.ifName}")
appendLine()
}
appendLine("[allowed_keys]")
appendLine("allow_all=yes")
appendLine()
@ -329,4 +337,42 @@ class ConfigProvider(private val context: Context) {
}
}
}
/** Return all currently available network interface names (for dropdown) */
fun getAvailableInterfaces(): List<Pair<String, String>> = runBlocking {
val list = mutableListOf<Pair<String, String>>()
try {
val nis = NetworkInterface.getNetworkInterfaces()
while (nis.hasMoreElements()) {
val ni = nis.nextElement()
if (!ni.isUp || ni.isLoopback) continue
for (a in ni.interfaceAddresses) {
val addr = a.address
val ip = when {
addr is Inet4Address -> addr.hostAddress
addr is Inet6Address && isUsableV6(addr) && !isTemporaryV6(ni.name, addr.hostAddress ?: "") -> addr.hostAddress?.let { it.split("%")[0] }
else -> null
} ?: continue
list.add(ni.name to ip)
}
}
} catch (_: Exception) {}
if (list.isEmpty()) list.add("lo" to "127.0.0.1")
list
}
/** Build full INI config text for NativeLib.start() */
fun buildConfigText(): String {
val cfgFile = File(configFilePath())
val existing = if (cfgFile.exists()) loadServersFromConfig() else emptyList()
LogManager.addLog("INFO", "CFG", "buildConfigText: exists=${cfgFile.exists()} srv=${existing.size}" +
existing.joinToString(", ") { "${it.ifName}=${it.protocol}" })
if (!cfgFile.exists() || existing.isEmpty()) {
val detected = runBlocking { detectInterfaces() }
LogManager.addLog("WARN", "CFG", "buildConfigText: auto-detecting ${detected.size} interfaces")
saveServersToConfig(detected)
return buildConfigFromServers(detected)
}
return buildConfigFromServers(existing)
}
}

4
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt

@ -123,11 +123,11 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
if (logUdpIp.isNotEmpty() && port > 0) NativeLib.setUdpLogTarget(logUdpIp, port)
val stored = provider.getString("debug.categories") ?: ""
catLevels = if (stored.isNotEmpty()) stored.split(",").associate { it.split("=", limit = 2).let { (k, v) -> k to v } } else emptyMap()
servers = provider.loadServers()
servers = provider.loadServersFromConfig()
availableIfaces = provider.getAvailableInterfaces()
}
val save = { s: List<ServerEntry> -> onDirty(true); servers = s; scope.launch { provider.saveServers(s) } }
val save = { s: List<ServerEntry> -> onDirty(true); servers = s; provider.saveServersToConfig(s) }
val setDebug = { level: String ->
onDirty(true); NativeLib.setDebugLevel("", level)
scope.launch { provider.setValue("debug.console_level", level) }; debugLevel = level

15
tools/chatgui-android/libutun_lite/instance_lite.c

@ -21,6 +21,8 @@
#include "sqlite3.h"
#include "mem.h"
#include "debug_config.h"
#include "transport_layer/etcp_connections.h"
#include "transport_layer/stcp_link.h"
#include "../jni_bridge/android_udp_log.h"
#include <pthread.h>
#include <stdio.h>
@ -207,6 +209,9 @@ static void* instance_thread(void* arg) {
return NULL;
}
/* sockets are now created — sync my addresses to DB */
chat_core_sync_my_addresses();
/* Bind chat sync via etcp_router */
etcp_router_bind(g_inst, ETCP_RT_ID_CHAT_SYNC, NULL);
@ -347,6 +352,14 @@ static void restart_trampoline(void* arg) {
return;
}
/* Clean up old sockets so init_connections reads new config */
while (g_inst->etcp_sockets) etcp_socket_remove(g_inst->etcp_sockets);
g_inst->etcp_sockets = NULL;
while (g_inst->tcp_sockets) tcp_socket_remove(g_inst->tcp_sockets);
g_inst->tcp_sockets = NULL;
if (g_inst->stcp_server) { stcp_link_server_destroy(g_inst->stcp_server); g_inst->stcp_server = NULL; }
g_inst->socket_init_status = 0;
if (utun_instance_init(g_inst) != 0) {
IL_LOGE("restart: utun_instance_init failed");
g_running = 0;
@ -354,6 +367,8 @@ static void restart_trampoline(void* arg) {
return;
}
chat_core_sync_my_addresses();
if (g_inst->config->global.name[0]) {
chat_core_update_my_name(g_inst->config->global.name);
IL_LOGI("restart: my_name set to '%s'", g_inst->config->global.name);

Loading…
Cancel
Save