You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

241 lines
14 KiB

/* Реальный 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;
}