Browse Source
Receive keepalive through the owning ETCP link, preserve RX deadlines, and report routing metrics without TUN. Use independent netlink requests and track installed routes for rollback. Reject incomplete resource initialization and release partially constructed cores while preserving caller config ownership. Validated startup failure and rollback, padded keepalive, standby, STCP traffic, transport lifecycle, dynamic sockets, and daemon startup. Real TUN/netlink checks passed in an isolated Linux network namespace; modified modules passed FreeBSD compilation checks.stream-multiview
19 changed files with 539 additions and 373 deletions
@ -0,0 +1,194 @@
|
||||
/* Реальный bind netlink при работающем мониторе; управляемые ACK и откат запуска UTUN. */ |
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
#include <stdlib.h> |
||||
#include <string.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 "../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; |
||||
|
||||
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 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); |
||||
DEBUG_INFO(DEBUG_CATEGORY_TUN, "PASS second socket failure rejects startup and cleans opened sockets automatic=%d", automatic); |
||||
} |
||||
|
||||
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(); } |
||||
assert(u_get_allocated_count() == baseline); return 0; |
||||
} |
||||
Loading…
Reference in new issue