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
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; |
|
}
|
|
|