Files
utun2/tests/test_tun_route.c
T
evgeny a96d5926c7 Fix Android external network monitoring and startup readiness
Select frontend network callbacks for auto_sockets=android instead of opening a denied netlink monitor. Preserve monitor descriptor ownership, keep native monitoring failures fatal, and release caller config on failed Android core creation and restart.

Register ConnectivityManager before requesting startup, report readiness after SERVICE_STARTED, and refresh addresses after initialization with serialized updates and generation checks. Add regression coverage for denied netlink, socket failures, address updates, and fd=0 preservation. Record the separate cancellation issue for startup before readiness.

Validation: full check.sh passed, including proxy/burst/load integrations. Clean Android build installed successfully on SM-A525F and Q8; logcat confirms both cores initialized and entered the poll loop with external monitoring and post-start address refresh.
2026-10-12 16:01:26 +02:00

242 lines
14 KiB
C

/* Реальный bind netlink при работающем мониторе; управляемые ACK и откат запуска UTUN. */
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <poll.h>
#include <net/if.h>
#include "utun_instance.h"
#include "tun_route.h"
#include "tun_if.h"
#include "chat/chat_core.h"
#include "transport_layer/auto_socket.h"
#include "transport_layer/etcp_connections.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
static int mock = 1, route_fd = -1, pending, additions, deletions, fail_add, fail_delete, bad_ack;
static uint32_t added[8], removed[8], request_seq;
static uint16_t request_cmd;
static int ack_error;
static int fail_tun;
static int deny_netlink, netlink_calls;
int __real_socket(int, int, int);
int __wrap_socket(int domain, int type, int protocol) {
if (domain == AF_NETLINK) netlink_calls++;
return __real_socket(domain, type, protocol);
}
int __real_bind(int, const struct sockaddr*, socklen_t);
int __wrap_bind(int fd, const struct sockaddr* addr, socklen_t len) {
if (deny_netlink && addr->sa_family == AF_NETLINK) { errno = EACCES; return -1; }
return __real_bind(fd, addr, len);
}
int __real_tun_platform_init(struct tun_if*, const char*, const char*, int);
int __wrap_tun_platform_init(struct tun_if* tun, const char* name, const char* ip, int mtu) {
if (!fail_tun) return __real_tun_platform_init(tun, name, ip, mtu);
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Injected TUN initialization failure"); return -1;
}
ssize_t __real_sendto(int, const void*, size_t, int, const struct sockaddr*, socklen_t);
ssize_t __wrap_sendto(int fd, const void* buf, size_t len, int flags, const struct sockaddr* dest, socklen_t alen) {
if (!mock || dest->sa_family != AF_NETLINK) return __real_sendto(fd, buf, len, flags, dest, alen);
const struct nlmsghdr* hdr = buf;
assert(len >= NLMSG_LENGTH(sizeof(struct rtmsg)) && hdr->nlmsg_len == len);
struct sockaddr_nl bound = {0}; socklen_t size = sizeof(bound);
assert(getsockname(fd, (struct sockaddr*)&bound, &size) == 0 && hdr->nlmsg_pid == bound.nl_pid);
const struct rtmsg* rt = NLMSG_DATA(hdr); assert(rt->rtm_family == AF_INET);
int remaining = RTM_PAYLOAD(hdr); uint32_t network = 0;
for (struct rtattr* attr = RTM_RTA(rt); RTA_OK(attr, remaining); attr = RTA_NEXT(attr, remaining)) {
if (attr->rta_type == RTA_DST) { assert(RTA_PAYLOAD(attr) == 4); memcpy(&network, RTA_DATA(attr), 4); }
}
route_fd = fd; request_seq = hdr->nlmsg_seq; request_cmd = hdr->nlmsg_type; pending = 1; ack_error = 0;
if (request_cmd == RTM_NEWROUTE) {
assert(additions < 8); added[additions++] = ntohl(network);
if (additions == fail_add) ack_error = -EEXIST;
} else {
assert(request_cmd == RTM_DELROUTE && deletions < 8); removed[deletions++] = ntohl(network);
if (deletions == fail_delete) ack_error = -EPERM;
}
return len;
}
int __real_poll(struct pollfd*, nfds_t, int);
int __wrap_poll(struct pollfd* fds, nfds_t n, int timeout) {
if (!mock || n != 1 || fds[0].fd != route_fd) return __real_poll(fds, n, timeout);
if (!pending) return 0;
fds[0].revents = POLLIN; return 1;
}
ssize_t __real_recvfrom(int, void*, size_t, int, struct sockaddr*, socklen_t*);
ssize_t __wrap_recvfrom(int fd, void* buf, size_t len, int flags, struct sockaddr* source, socklen_t* alen) {
if (!mock || fd != route_fd || !pending) return __real_recvfrom(fd, buf, len, flags, source, alen);
struct { struct nlmsghdr hdr; struct nlmsgerr error; } reply = {0};
reply.hdr.nlmsg_len = sizeof(reply); reply.hdr.nlmsg_type = bad_ack == 1 ? RTM_NEWROUTE : NLMSG_ERROR;
reply.hdr.nlmsg_seq = request_seq + (bad_ack == 3); reply.error.error = ack_error;
struct sockaddr_nl kernel = { .nl_family = AF_NETLINK, .nl_pid = bad_ack == 4 ? 99 : 0 };
assert(len >= sizeof(reply) && *alen >= sizeof(kernel));
memcpy(source, &kernel, sizeof(kernel)); *alen = sizeof(kernel);
memcpy(buf, &reply, sizeof(reply)); pending = 0;
if (bad_ack == 2) { ((struct nlmsghdr*)buf)->nlmsg_len = sizeof(struct nlmsghdr); return sizeof(struct nlmsghdr); }
return sizeof(reply);
}
static void reset(void) {
additions = deletions = fail_add = fail_delete = bad_ack = pending = 0; route_fd = -1;
}
static struct UTUN_INSTANCE* create(struct UASYNC* ua, int test) {
const char* config = test ?
"[global]\ntun_enabled=yes\ntun_test_mode=1\ntun_ifname=lo\ntun_ip=198.51.100.1\n"
"[routing]\nmy_subnet=198.51.100.1/32\n"
"[server:local]\naddr=127.0.0.1:0\ntype=private\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n" :
"[global]\ntun_enabled=yes\ntun_ifname=utest\ntun_ip=198.51.100.1\n"
"[routing]\nmy_subnet=198.51.100.1/32\n"
"[server:local]\naddr=127.0.0.1:0\ntype=private\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n";
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); assert(inst);
inst->config->global.db_sync_enabled = 0;
assert(utun_core_start(inst) == 0 && inst->socket_monitor);
return inst;
}
static struct CFG_ROUTE_ENTRY* config_route(struct UTUN_INSTANCE* inst, uint32_t network) {
struct CFG_ROUTE_ENTRY* entry = u_calloc(1, sizeof(*entry)); assert(entry);
entry->ip.family = AF_INET; entry->ip.addr.v4.s_addr = htonl(network); entry->netmask = 24;
struct CFG_ROUTE_ENTRY** tail = &inst->config->route_subnets;
while (*tail) tail = &(*tail)->next;
*tail = entry; return entry;
}
static void destroy(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
utun_instance_destroy(inst); uasync_drain_immediate(ua); uasync_destroy(ua, 0);
}
static void check_mock(void) {
struct UASYNC* ua = uasync_create(); assert(ua);
struct UTUN_INSTANCE* inst = create(ua, 1);
assert(chat_service_start(inst) == 0); void* chat = inst->chat_core; sqlite3* db = inst->topo_sqlite_db;
config_route(inst, 0xc0000200); config_route(inst, 0xcb007100);
reset(); fail_tun = 1; inst->config->global.tun_test_mode = 0;
assert(utun_service_start(inst) < 0 && !inst->utun_started && !inst->tun && !additions && !deletions);
assert(inst->chat_core == chat && inst->chat_started && inst->core_started);
fail_tun = 0; inst->config->global.tun_test_mode = 1;
reset(); fail_add = 2;
assert(utun_service_start(inst) < 0 && !inst->utun_started && !inst->tun && !inst->tun_routes);
assert(additions == 2 && deletions == 1 && added[0] == 0xc0000200 && removed[0] == added[0]);
assert(!topo_groups_get_default(inst->topo_groups) && inst->chat_core == chat && inst->chat_started && inst->topo_sqlite_db == db);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS second route failure rolls back only first owned route and preserves chat/core");
reset(); fail_add = 1;
assert(utun_service_start(inst) < 0 && additions == 1 && !deletions && !inst->utun_started);
reset(); assert(utun_service_start(inst) == 0 && inst->utun_started && additions == 2 && inst->tun_routes);
struct tun_route* owned = inst->tun_routes;
assert(utun_service_start(inst) == 0 && inst->tun_routes == owned && additions == 2);
inst->config->route_subnets->ip.addr.v4.s_addr = htonl(0xc6336400);
fail_delete = 1; utun_service_stop(inst);
assert(!inst->tun_routes && !inst->tun && !inst->utun_started && deletions == 2);
assert(removed[0] == 0xcb007100 && removed[1] == 0xc0000200);
utun_service_stop(inst); assert(deletions == 2);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS stop uses owned snapshot, continues after deletion error and is idempotent");
for (int mode = 1; mode <= 4; mode++) {
reset(); bad_ack = mode;
assert(tun_route_add(0, "lo", 0xc0000200, 24) < 0);
}
destroy(inst, ua);
}
/* Ошибка второго listen-сокета отклоняет весь запуск, включая начальный auto_sockets=android. */
static void check_socket_failure(int automatic) {
int stdin_flags = fcntl(STDIN_FILENO, F_GETFD); assert(stdin_flags >= 0);
int blocker = socket(AF_INET, SOCK_STREAM, 0); assert(blocker >= 0);
struct sockaddr_in address = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK) };
assert(bind(blocker, (struct sockaddr*)&address, sizeof(address)) == 0 && listen(blocker, 1) == 0);
socklen_t len = sizeof(address); assert(getsockname(blocker, (struct sockaddr*)&address, &len) == 0);
char config[512];
snprintf(config, sizeof(config), "[global]\ntun_enabled=no\nauto_sockets=%s\n"
"[server:first]\naddr=127.0.0.1:0\ntype=private\n"
"[server:blocked]\naddr=127.0.0.1:%u\ntransport=tcp\ntype=private\n", automatic ? "android" : "no",
ntohs(address.sin_port));
struct UASYNC* ua = uasync_create(); assert(ua);
assert(!utun_instance_create_from_str(ua, config));
uasync_drain_immediate(ua); uasync_destroy(ua, 0); close(blocker);
assert(fcntl(STDIN_FILENO, F_GETFD) == stdin_flags);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS second socket failure rejects startup and cleans opened sockets automatic=%d", automatic);
}
/* Android/JNI backend запускается без разрешений netlink и принимает свежие адреса без рестарта. */
static void check_external_monitor(void) {
const char* config = "[global]\ntun_enabled=no\nauto_sockets=android\n"
"[server:external]\naddr=127.0.0.1:0\ntransport=udp\ntype=private\n"
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n";
int stdin_flags = fcntl(STDIN_FILENO, F_GETFD); assert(stdin_flags >= 0);
struct UASYNC* ua = uasync_create(); assert(ua);
deny_netlink = 1; netlink_calls = 0;
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config);
assert(inst && !inst->socket_monitor && inst->auto_socket_state && !netlink_calls);
inst->config->global.db_sync_enabled = 0;
assert(utun_core_start(inst) == 0 && chat_service_start(inst) == 0);
sqlite3* db = inst->topo_sqlite_db; void* chat = inst->chat_core;
struct utun_config* updated = parse_config_from_buf(config, strlen(config), "external-update"); assert(updated);
((struct sockaddr_in*)&updated->servers->ip)->sin_addr.s_addr = htonl(0x7f000002);
auto_socket_reload_config(inst, updated);
assert(inst->core_started && inst->chat_core == chat && inst->topo_sqlite_db == db);
assert(((struct sockaddr_in*)&inst->etcp_sockets->local_addr)->sin_addr.s_addr == htonl(0x7f000002));
assert(!netlink_calls);
destroy(inst, ua); assert(fcntl(STDIN_FILENO, F_GETFD) == stdin_flags);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS external monitoring: no netlink, core/chat ready, address update preserves DB and fd=0");
const char* native = "[global]\ntun_enabled=no\n[server:local]\naddr=127.0.0.1:0\ntype=private\n";
ua = uasync_create(); assert(ua);
assert(!utun_instance_create_from_str(ua, native) && netlink_calls > 0);
uasync_drain_immediate(ua); uasync_destroy(ua, 0); deny_netlink = 0;
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS native monitoring failure remains fatal");
}
static void check_control_failure(void) {
int blocker = socket(AF_INET, SOCK_STREAM, 0); assert(blocker >= 0);
struct sockaddr_in address = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK) };
assert(bind(blocker, (struct sockaddr*)&address, sizeof(address)) == 0 && listen(blocker, 1) == 0);
socklen_t len = sizeof(address); assert(getsockname(blocker, (struct sockaddr*)&address, &len) == 0);
char config[256];
snprintf(config, sizeof(config), "[global]\ntun_enabled=no\n[server:local]\naddr=127.0.0.1:0\ntype=private\n"
"[control]\nip=127.0.0.1\nport=%u\ncontrol_allow=127.0.0.1/32\n", ntohs(address.sin_port));
struct UASYNC* ua = uasync_create(); assert(ua);
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); assert(inst);
assert(utun_core_start(inst) < 0 && !inst->core_started && !inst->running && !inst->control_srv);
destroy(inst, ua); close(blocker);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS configured control socket failure rejects core startup and releases resources");
}
static void check_real(void) {
mock = 0;
struct UASYNC* ua = uasync_create(); assert(ua);
struct UTUN_INSTANCE* inst = create(ua, 0);
config_route(inst, 0xc0000200); config_route(inst, 0xcb007100);
assert(utun_service_start(inst) == 0 && inst->tun_routes && inst->utun_started);
assert(system("ip -4 route get 192.0.2.1 | grep -q 'dev utest'") == 0);
utun_service_stop(inst); assert(!inst->tun_routes && !inst->utun_started);
assert(system("test -z \"$(ip -4 route show 192.0.2.0/24)\"") == 0);
unsigned loopback = if_nametoindex("lo"); assert(loopback);
assert(tun_route_add(loopback, "lo", 0xcb007100, 24) == 0);
assert(utun_service_start(inst) < 0 && !inst->tun && !inst->tun_routes && !inst->utun_started);
assert(system("test -z \"$(ip -4 route show 192.0.2.0/24)\"") == 0);
assert(system("ip -4 route get 203.0.113.1 | grep -q 'dev lo'") == 0);
assert(tun_route_del(loopback, "lo", 0xcb007100, 24) == 0);
destroy(inst, ua);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS real netlink/TUN with running monitor: add, lookup, stop, conflict and partial rollback");
}
int main(int argc, char** argv) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_TUN, DEBUG_LEVEL_DEBUG);
utun_instance_set_tun_init_enabled(1);
size_t baseline = u_get_allocated_count();
if (argc == 2 && !strcmp(argv[1], "--real")) check_real();
else { check_mock(); check_socket_failure(0); check_socket_failure(1); check_control_failure(); check_external_monitor(); }
assert(u_get_allocated_count() == baseline); return 0;
}