/* Реальный bind netlink при работающем мониторе; управляемые ACK и откат запуска UTUN. */ #include #include #include #include #include #include #include #include #include #include #include #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; }