From c69f8d8975171adbd13e3fb8659157bce7d7a3a2 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 17 Oct 2026 22:07:58 +0200 Subject: [PATCH] fix(nat): notify GUI and include profiles in Android socket snapshots --- src/routing_layer/nat_detection.c | 2 + tests/test_android_replies.c | 35 ++++++++++ .../jni_bridge/android_jni_bridge.c | 66 ++++++++++++++----- 3 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/routing_layer/nat_detection.c b/src/routing_layer/nat_detection.c index 46c336e1..e00f4c72 100644 --- a/src/routing_layer/nat_detection.c +++ b/src/routing_layer/nat_detection.c @@ -2,6 +2,7 @@ * через router, независимо от прямого UDP-ответа. Входящие пробы не получают PONG; * незнакомость фактического source IP проверяет только получатель. */ #include "nat_detection.h" +#include "chat/chat_event.h" #include #include @@ -311,6 +312,7 @@ static void publish(struct NAT_DETECTION *detector) { detector->publish_pending = topo_node_update_my_addresses(detector->instance) < 0; if (detector->publish_pending) detector->publish_after = get_time_tb() + 10000; if (detector->publish_pending) DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT profile publication failed; retry scheduled"); + else chat_event_post(detector->instance, CHAT_EVT_LOCAL_SOCKETS, NULL, 0); } static struct nat_socket *socket_state(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, int create) { diff --git a/tests/test_android_replies.c b/tests/test_android_replies.c index 149d5ff2..dc490e7f 100644 --- a/tests/test_android_replies.c +++ b/tests/test_android_replies.c @@ -9,6 +9,7 @@ #include "../src/chat/chat_core.h" #include "../src/chat/chat_event.h" #include "../src/routing_layer/topo_node_sqlite.h" +#include "../src/transport_layer/etcp_connections.h" #include "../src/config_parser.h" #include "../tools/chatgui-android/jni_bridge/android_jni_bridge.h" #include "test_utils.h" @@ -87,6 +88,39 @@ static int check_addresses(const char *channel) { u_free(json); return 0; } +/* Собственные сокеты передают профиль независимо от адреса интерфейса; тестовые порты скрыты. */ +static int check_local_sockets(void) { + struct ETCP_SOCKET* saved = instance->etcp_sockets; + struct ETCP_SOCKET tcp = {.sock_id = 7, .is_tcp = 1, .name = "as_wlan0_v4_tcp", + .address_profile = {ADDRESS_MAP_UNKNOWN, ADDRESS_IN_UNKNOWN, ADDRESS_SOURCE_ETCP}}; + struct ETCP_SOCKET udp = {.sock_id = 8, .name = "as_wlan0_v6_udp", + .address_profile = {ADDRESS_MAP_EIM, ADDRESS_IN_OPEN, ADDRESS_SOURCE_ETCP}}; + struct ETCP_SOCKET probe = {.sock_id = 9, .probe_only = 1}; + struct sockaddr_in* local4 = (struct sockaddr_in*)&tcp.interface_addr; + local4->sin_family = AF_INET; local4->sin_port = htons(9892); inet_pton(AF_INET, "192.168.40.250", &local4->sin_addr); + struct sockaddr_in* external4 = (struct sockaddr_in*)&tcp.nat_addr; + external4->sin_family = AF_INET; inet_pton(AF_INET, "194.63.142.140", &external4->sin_addr); + struct sockaddr_in6* local6 = (struct sockaddr_in6*)&udp.interface_addr; + local6->sin6_family = AF_INET6; local6->sin6_port = htons(9893); inet_pton(AF_INET6, "fd00::1", &local6->sin6_addr); + struct sockaddr_in6* external6 = (struct sockaddr_in6*)&udp.nat_addr; + external6->sin6_family = AF_INET6; external6->sin6_port = htons(4101); inet_pton(AF_INET6, "2a04:5200::f", &external6->sin6_addr); + tcp.next = &udp; udp.next = &probe; instance->etcp_sockets = &tcp; + char* json = utun_bridge_get_local_sockets_json(); + instance->etcp_sockets = saved; + char value[128]; + REQUIRE(json && !extract(json, "$[0].nmapping", value, sizeof(value)) && !strcmp(value, "0")); + REQUIRE(!extract(json, "$[0].nsource", value, sizeof(value)) && !strcmp(value, "1")); + REQUIRE(!extract(json, "$[0].ad", value, sizeof(value)) && !strcmp(value, "192.168.40.250")); + REQUIRE(!extract(json, "$[0].external", value, sizeof(value)) && !strcmp(value, "194.63.142.140")); + REQUIRE(!extract(json, "$[0].externalPort", value, sizeof(value)) && !strcmp(value, "0")); + REQUIRE(!extract(json, "$[1].nmapping", value, sizeof(value)) && !strcmp(value, "2")); + REQUIRE(!extract(json, "$[1].ninbound", value, sizeof(value)) && !strcmp(value, "1")); + REQUIRE(!extract(json, "$[1].external", value, sizeof(value)) && !strcmp(value, "2a04:5200::f")); + REQUIRE(!extract(json, "$[1].externalPort", value, sizeof(value)) && !strcmp(value, "4101")); + REQUIRE(!extract(json, "$[#-1].si", value, sizeof(value)) && !strcmp(value, "8")); + u_free(json); return 0; +} + int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); char directory[512] = "/tmp/utun_android_replies_XXXXXX", config[1024], channel[64], hex[65]; @@ -101,6 +135,7 @@ int main(void) { char* json = utun_bridge_get_channels_json(); REQUIRE(!extract(json, "$[0].id", channel, sizeof(channel))); u_free(json); REQUIRE(!check_addresses(channel)); + REQUIRE(!check_local_sockets()); uint8_t empty[32] = {0}, original[32], missing[32]; memset(missing, 0xff, sizeof(missing)); REQUIRE(!utun_bridge_send_message(channel, "Привет 😀 \"original\"\nnext line", empty, 1) && wait_result(1) == 1); json = utun_bridge_get_messages_json(channel, 1, NULL, 0); diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index aa46d5ff..5c233965 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -27,6 +27,7 @@ #include "../../../lib/mem.h" #include "../../../lib/sqlite3.h" #include "../../../src/chat/chat_core.h" +#include "../../../src/chat/chat_event.h" #include "../../../src/chat/chat_poll.h" #include "../../../lib/strbuf.h" #include "../../../src/chat/chat_member.h" @@ -1163,7 +1164,7 @@ char* utun_bridge_get_local_sockets_json(void) { json[0] = '['; int first = 1; - bridge_log(BLEV_INFO, "getLocalSockets: etcp_sockets=%p", (void*)inst->etcp_sockets); + bridge_log(BLEV_DEBUG, "local sockets snapshot: etcp_sockets=%p", (void*)inst->etcp_sockets); /* Helper: count per-socket links_up / links_down */ #define SOCK_COUNT_LINKS(_s, _lu, _ld) do { \ @@ -1194,7 +1195,7 @@ char* utun_bridge_get_local_sockets_json(void) { /* TCP sockets */ { struct ETCP_SOCKET* s = inst->etcp_sockets; while (s) { - if (!s->is_tcp) { s = s->next; continue; } + if (!s->is_tcp || s->probe_only) { s = s->next; continue; } char ip_str[INET6_ADDRSTRLEN] = ""; int port = 0; const struct sockaddr_storage* addr = s->interface_addr.ss_family ? &s->interface_addr : &s->local_addr; if (addr->ss_family == AF_INET) { @@ -1210,14 +1211,26 @@ char* utun_bridge_get_local_sockets_json(void) { int state = 2; /* bound and listening */ const char* sep = first ? "" : ","; first = 0; const char* pn = s->reality_enabled ? "TCP+REALITY" : "TCP"; + char external[INET6_ADDRSTRLEN] = ""; int external_port = 0; + if (s->nat_addr.ss_family == AF_INET) { + const struct sockaddr_in* nat = (const struct sockaddr_in*)&s->nat_addr; + inet_ntop(AF_INET, &nat->sin_addr, external, sizeof(external)); external_port = ntohs(nat->sin_port); + } else if (s->nat_addr.ss_family == AF_INET6) { + const struct sockaddr_in6* nat = (const struct sockaddr_in6*)&s->nat_addr; + inet_ntop(AF_INET6, &nat->sin6_addr, external, sizeof(external)); external_port = ntohs(nat->sin6_port); + } size_t needed = snprintf(NULL, 0, - "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"%s\",\"st\":%d,\"lu\":%d,\"ld\":%d}", - sep, s->sock_id, s->name, ip_str, port, pn, state, lu, ld); + "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"%s\",\"st\":%d,\"lu\":%d,\"ld\":%d," + "\"nmapping\":%u,\"ninbound\":%u,\"nsource\":%u,\"external\":\"%s\",\"externalPort\":%d}", + sep, s->sock_id, s->name, ip_str, port, pn, state, lu, ld, + s->address_profile.mapping, s->address_profile.inbound, s->address_profile.source, external, external_port); while (pos + needed + 2 > cap) { char* tmp = u_realloc(json, cap * 2); if (!tmp) break; json = tmp; cap *= 2; } if (pos + needed + 2 <= cap) pos += (size_t)snprintf(json + pos, cap - pos, - "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"%s\",\"st\":%d,\"lu\":%d,\"ld\":%d}", - sep, s->sock_id, s->name, ip_str, port, pn, state, lu, ld); + "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"%s\",\"st\":%d,\"lu\":%d,\"ld\":%d," + "\"nmapping\":%u,\"ninbound\":%u,\"nsource\":%u,\"external\":\"%s\",\"externalPort\":%d}", + sep, s->sock_id, s->name, ip_str, port, pn, state, lu, ld, + s->address_profile.mapping, s->address_profile.inbound, s->address_profile.source, external, external_port); s = s->next; } } @@ -1225,7 +1238,7 @@ char* utun_bridge_get_local_sockets_json(void) { /* UDP sockets */ { struct ETCP_SOCKET* es = inst->etcp_sockets; while (es) { - if (es->is_tcp) { es = es->next; continue; } + if (es->is_tcp || es->probe_only) { es = es->next; continue; } char ip_str[INET6_ADDRSTRLEN] = ""; int port = 0; const struct sockaddr_storage* addr = es->interface_addr.ss_family ? &es->interface_addr : &es->local_addr; if (addr->ss_family == AF_INET) { @@ -1240,14 +1253,26 @@ char* utun_bridge_get_local_sockets_json(void) { int lu, ld; SOCK_COUNT_LINKS(es, lu, ld); int state = 2; /* bound and listening */ const char* sep = first ? "" : ","; first = 0; + char external[INET6_ADDRSTRLEN] = ""; int external_port = 0; + if (es->nat_addr.ss_family == AF_INET) { + const struct sockaddr_in* nat = (const struct sockaddr_in*)&es->nat_addr; + inet_ntop(AF_INET, &nat->sin_addr, external, sizeof(external)); external_port = ntohs(nat->sin_port); + } else if (es->nat_addr.ss_family == AF_INET6) { + const struct sockaddr_in6* nat = (const struct sockaddr_in6*)&es->nat_addr; + inet_ntop(AF_INET6, &nat->sin6_addr, external, sizeof(external)); external_port = ntohs(nat->sin6_port); + } size_t needed = snprintf(NULL, 0, - "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"UDP\",\"st\":%d,\"lu\":%d,\"ld\":%d}", - sep, es->sock_id, es->name, ip_str, port, state, lu, ld); + "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"UDP\",\"st\":%d,\"lu\":%d,\"ld\":%d," + "\"nmapping\":%u,\"ninbound\":%u,\"nsource\":%u,\"external\":\"%s\",\"externalPort\":%d}", + sep, es->sock_id, es->name, ip_str, port, state, lu, ld, + es->address_profile.mapping, es->address_profile.inbound, es->address_profile.source, external, external_port); while (pos + needed + 2 > cap) { char* tmp = u_realloc(json, cap * 2); if (!tmp) break; json = tmp; cap *= 2; } if (pos + needed + 2 <= cap) pos += (size_t)snprintf(json + pos, cap - pos, - "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"UDP\",\"st\":%d,\"lu\":%d,\"ld\":%d}", - sep, es->sock_id, es->name, ip_str, port, state, lu, ld); + "%s{\"si\":%u,\"nm\":\"%s\",\"ad\":\"%s\",\"po\":%d,\"pt\":\"UDP\",\"st\":%d,\"lu\":%d,\"ld\":%d," + "\"nmapping\":%u,\"ninbound\":%u,\"nsource\":%u,\"external\":\"%s\",\"externalPort\":%d}", + sep, es->sock_id, es->name, ip_str, port, state, lu, ld, + es->address_profile.mapping, es->address_profile.inbound, es->address_profile.source, external, external_port); es = es->next; } } @@ -2573,13 +2598,22 @@ JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetMemberLinks /* ── Local sockets JSON ── */ -JNIEXPORT jstring JNICALL Java_com_utun_chat_data_NativeLib_nativeGetLocalSockets( - JNIEnv* env, jobject thiz) { - (void)thiz; +static void local_sockets_trampoline(void* arg) { + (void)arg; + struct UTUN_INSTANCE* inst = instance_lite_get_instance(); + if (!inst) { bridge_log(BLEV_WARN, "local sockets snapshot: core stopped"); return; } char* json = utun_bridge_get_local_sockets_json(); - jstring result = (*env)->NewStringUTF(env, json ? json : "[]"); + if (!json) { bridge_log(BLEV_ERROR, "local sockets snapshot allocation failed"); return; } + chat_event_post(inst, CHAT_EVT_LOCAL_SOCKETS, (const uint8_t*)json, (int)strlen(json)); u_free(json); - return result; +} + +JNIEXPORT void JNICALL Java_com_utun_chat_data_NativeLib_nativeRequestLocalSockets( + JNIEnv* env, jobject thiz) { + (void)env; (void)thiz; + struct UASYNC* ua = instance_lite_get_uasync(); + if (!uasync_is_running(ua)) { bridge_log(BLEV_DEBUG, "local sockets request: core stopped"); return; } + uasync_post(ua, local_sockets_trampoline, NULL); } /* ── Network change notification ── */