274 lines
16 KiB
C
274 lines
16 KiB
C
/* Пропуск недоступных автосокетов, восстановление, ошибки ОС и владение ресурсами на настоящем ядре. */
|
|
#include "nat_fixture.h"
|
|
#include "../src/transport_layer/auto_socket.h"
|
|
#include "../src/transport_layer/stcp_link.h"
|
|
#include "../lib/sqlite3.h"
|
|
#include <ifaddrs.h>
|
|
#include <net/if.h>
|
|
|
|
static int fake_network, present = 1, fail_udp, fail_tcp, fail_random, deny_save, creates;
|
|
static int fail_scan, fail_bind_type, fail_bind_family, bind_error, bind_failures, bind_remaining, clobber_close, fake_ipv6;
|
|
static uint32_t address = 0x7f000001;
|
|
static struct sockaddr_in fake_address;
|
|
static struct sockaddr_in6 fake_address_v6;
|
|
static struct ifaddrs fake_iface, fake_iface_v6;
|
|
|
|
extern int __real_getifaddrs(struct ifaddrs**);
|
|
extern void __real_freeifaddrs(struct ifaddrs*);
|
|
int __wrap_getifaddrs(struct ifaddrs** out) {
|
|
if (!fake_network) return __real_getifaddrs(out);
|
|
if (fail_scan) { errno = EIO; return -1; }
|
|
memset(&fake_address, 0, sizeof(fake_address));
|
|
fake_address.sin_family = AF_INET; fake_address.sin_addr.s_addr = htonl(address);
|
|
fake_iface = (struct ifaddrs){.ifa_name = "lo", .ifa_flags = present ? IFF_UP : 0,
|
|
.ifa_addr = (struct sockaddr*)&fake_address};
|
|
if (fake_ipv6) {
|
|
memset(&fake_address_v6, 0, sizeof(fake_address_v6)); fake_address_v6.sin6_family = AF_INET6;
|
|
assert(inet_pton(AF_INET6, "fd42::1", &fake_address_v6.sin6_addr) == 1);
|
|
fake_iface_v6 = (struct ifaddrs){.ifa_name = "lo", .ifa_flags = fake_iface.ifa_flags,
|
|
.ifa_addr = (struct sockaddr*)&fake_address_v6};
|
|
fake_iface.ifa_next = &fake_iface_v6;
|
|
}
|
|
*out = &fake_iface; return 0;
|
|
}
|
|
void __wrap_freeifaddrs(struct ifaddrs* list) { if (list != &fake_iface) __real_freeifaddrs(list); }
|
|
|
|
/* Отказ настоящего bind; cleanup намеренно стирает errno, как успешный closesocket в Winsock. */
|
|
extern int __real_bind(int, const struct sockaddr*, socklen_t);
|
|
int __wrap_bind(int fd, const struct sockaddr* addr, socklen_t len) {
|
|
if (fail_bind_type && bind_remaining) {
|
|
int type; socklen_t size = sizeof(type);
|
|
assert(getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &size) == 0);
|
|
if (type == fail_bind_type && (!fail_bind_family || addr->sa_family == fail_bind_family)) {
|
|
bind_failures++;
|
|
if (bind_remaining > 0) bind_remaining--;
|
|
errno = bind_error; return -1;
|
|
}
|
|
}
|
|
return __real_bind(fd, addr, len);
|
|
}
|
|
extern int __real_socket_close_wrapper(socket_t);
|
|
int __wrap_socket_close_wrapper(socket_t fd) {
|
|
int result = __real_socket_close_wrapper(fd);
|
|
if (clobber_close) errno = 0;
|
|
return result;
|
|
}
|
|
|
|
extern struct ETCP_SOCKET* __real_etcp_socket_add(struct UTUN_INSTANCE*, struct CFG_SERVER*);
|
|
struct ETCP_SOCKET* __wrap_etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SERVER* server) {
|
|
if (!strncmp(server->name, "as_", 3)) {
|
|
creates++;
|
|
if (fail_udp) { errno = ENOMEM; return NULL; }
|
|
}
|
|
struct ETCP_SOCKET* result = __real_etcp_socket_add(instance, server);
|
|
if (!result && fail_bind_type == SOCK_DGRAM) assert(socket_get_error() == bind_error);
|
|
return result;
|
|
}
|
|
extern struct stcp_server* __real_stcp_server_listen(struct stcp_link_config*, uint16_t,
|
|
stcp_server_on_link_cb, struct ETCP_SOCKET*);
|
|
struct stcp_server* __wrap_stcp_server_listen(struct stcp_link_config* config, uint16_t port,
|
|
stcp_server_on_link_cb callback, struct ETCP_SOCKET* socket) {
|
|
if (fail_tcp) { errno = ENOMEM; return NULL; }
|
|
struct stcp_server* result = __real_stcp_server_listen(config, port, callback, socket);
|
|
if (!result && fail_bind_type == SOCK_STREAM) assert(socket_get_error() == bind_error);
|
|
return result;
|
|
}
|
|
extern int __real_random_bytes(uint8_t*, size_t);
|
|
int __wrap_random_bytes(uint8_t* out, size_t size) {
|
|
if (fail_random) { errno = EIO; return -1; }
|
|
return __real_random_bytes(out, size);
|
|
}
|
|
|
|
static struct ETCP_SOCKET* owned_socket(struct UTUN_INSTANCE* instance, int tcp) {
|
|
struct ETCP_SOCKET* result = NULL;
|
|
for (struct ETCP_SOCKET* es = instance->etcp_sockets; es; es = es->next) {
|
|
if (strncmp(es->name, "as_lo_v4_", 9) || es->is_tcp != tcp) continue;
|
|
assert(!result); result = es;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int port_count(struct UTUN_INSTANCE* instance, int* port) {
|
|
sqlite3_stmt* statement = NULL;
|
|
assert(sqlite3_prepare_v2(instance->topo_sqlite_db,
|
|
"SELECT count(*), max(port) FROM auto_socket_ports WHERE if_name='lo'", -1, &statement, NULL) == SQLITE_OK);
|
|
assert(sqlite3_step(statement) == SQLITE_ROW);
|
|
int count = sqlite3_column_int(statement, 0);
|
|
if (port) *port = sqlite3_column_int(statement, 1);
|
|
assert(sqlite3_finalize(statement) == SQLITE_OK);
|
|
return count;
|
|
}
|
|
|
|
static int authorizer(void* arg, int operation, const char* table, const char* column,
|
|
const char* database, const char* trigger) {
|
|
(void)arg; (void)column; (void)database; (void)trigger;
|
|
return deny_save && operation == SQLITE_INSERT && table && !strcmp(table, "auto_socket_ports") ? SQLITE_DENY : SQLITE_OK;
|
|
}
|
|
|
|
static void rescan(struct nat_fixture* fixture, struct UTUN_INSTANCE* instance) {
|
|
auto_socket_on_network_change(instance); uasync_poll(fixture->ua, 0);
|
|
}
|
|
|
|
int main(void) {
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
|
|
debug_set_category_level(DEBUG_CATEGORY_SOCKET, DEBUG_LEVEL_INFO);
|
|
struct nat_fixture fixture; nat_fixture_init(&fixture);
|
|
struct UTUN_INSTANCE* instance = nat_fixture_node(&fixture, "127.0.0.9", NULL, NULL);
|
|
struct ETCP_SOCKET* fixed = test_etcp_bound_socket(instance, "udp");
|
|
instance->config->global.auto_sockets = CFG_AUTO_SOCKETS_SYSTEM;
|
|
instance->config->global.auto_socket_protocols = CFG_AUTO_SOCKET_UDP;
|
|
instance->config->global.auto_socket_skip_no_default_route = 0;
|
|
fake_network = 1;
|
|
|
|
assert(auto_socket_init(instance) == 0);
|
|
struct ETCP_SOCKET* first = owned_socket(instance, 0); assert(first);
|
|
int saved_port; assert(port_count(instance, &saved_port) == 1 && saved_port == ss_get_port(&first->local_addr));
|
|
auto_socket_destroy(instance);
|
|
assert(!owned_socket(instance, 0) && port_count(instance, NULL) == 1 && instance->etcp_sockets == fixed);
|
|
assert(auto_socket_init(instance) == 0);
|
|
assert(ss_get_port(&owned_socket(instance, 0)->local_addr) == saved_port);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS auto port persistence and owned endpoint destruction");
|
|
|
|
address = 0x7f000002; fail_udp = 1;
|
|
rescan(&fixture, instance);
|
|
assert(!owned_socket(instance, 0));
|
|
fail_udp = 0; rescan(&fixture, instance);
|
|
assert(owned_socket(instance, 0) && ntohl(((struct sockaddr_in*)&owned_socket(instance, 0)->local_addr)->sin_addr.s_addr) == address);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS failed UDP replacement leaves no stale pointer; next scan recovers");
|
|
|
|
auto_socket_on_network_change(instance); auto_socket_on_network_change(instance);
|
|
int before = creates;
|
|
auto_socket_destroy(instance); uasync_poll(fixture.ua, 0);
|
|
assert(creates == before && !instance->auto_socket_state && !owned_socket(instance, 0));
|
|
assert(auto_socket_init(instance) == 0);
|
|
present = 0; rescan(&fixture, instance);
|
|
assert(!owned_socket(instance, 0) && port_count(instance, NULL) == 0);
|
|
auto_socket_destroy(instance); present = 1;
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS pending scan cancellation and pruning absent interfaces");
|
|
|
|
assert(sqlite3_set_authorizer(instance->topo_sqlite_db, authorizer, NULL) == SQLITE_OK);
|
|
deny_save = 1;
|
|
assert(auto_socket_init(instance) == 0 && owned_socket(instance, 0));
|
|
assert(sqlite3_get_autocommit(instance->topo_sqlite_db) && port_count(instance, NULL) == 0);
|
|
auto_socket_destroy(instance); deny_save = 0;
|
|
assert(sqlite3_set_authorizer(instance->topo_sqlite_db, NULL, NULL) == SQLITE_OK);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS failed port save does not leave a SQLite transaction open");
|
|
|
|
fail_random = 1;
|
|
assert(auto_socket_init(instance) == 0 && instance->auto_socket_state && !owned_socket(instance, 0));
|
|
fail_random = 0;
|
|
rescan(&fixture, instance); assert(owned_socket(instance, 0));
|
|
auto_socket_destroy(instance);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS port generation failure skips socket; next scan recovers");
|
|
|
|
instance->config->global.auto_socket_protocols = CFG_AUTO_SOCKET_BOTH;
|
|
fail_tcp = 1;
|
|
assert(auto_socket_init(instance) == 0 && instance->auto_socket_state && owned_socket(instance, 0) && !owned_socket(instance, 1));
|
|
uint64_t udp_generation = owned_socket(instance, 0)->generation;
|
|
fail_tcp = 0;
|
|
rescan(&fixture, instance);
|
|
assert(owned_socket(instance, 1) && owned_socket(instance, 0)->generation == udp_generation);
|
|
uint64_t tcp_generation = owned_socket(instance, 1)->generation;
|
|
rescan(&fixture, instance);
|
|
assert(owned_socket(instance, 1)->generation == tcp_generation && owned_socket(instance, 0)->generation == udp_generation);
|
|
address = 0x7f000003; rescan(&fixture, instance);
|
|
assert(owned_socket(instance, 1)->generation != tcp_generation && owned_socket(instance, 0)->generation != udp_generation);
|
|
auto_socket_on_network_change(instance); auto_socket_destroy(instance); uasync_poll(fixture.ua, 0);
|
|
assert(instance->etcp_sockets == fixed && !stcp_server_list_count(instance));
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS TCP failure preserves UDP; recovery and unchanged scans preserve working endpoints");
|
|
|
|
fail_udp = 1;
|
|
assert(auto_socket_init(instance) == 0 && !owned_socket(instance, 0) && owned_socket(instance, 1));
|
|
tcp_generation = owned_socket(instance, 1)->generation;
|
|
fail_udp = 0; rescan(&fixture, instance);
|
|
assert(owned_socket(instance, 0) && owned_socket(instance, 1)->generation == tcp_generation);
|
|
auto_socket_destroy(instance);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS UDP failure preserves TCP; next scan adds only missing UDP");
|
|
|
|
fail_udp = fail_tcp = 1;
|
|
struct UTUN_INSTANCE* offline = nat_fixture_node(&fixture, "127.0.0.10", NULL,
|
|
"[global]\nauto_sockets=yes\nauto_socket_protocols=tcp+udp\nauto_socket_skip_no_default_route=no\n");
|
|
assert(offline->auto_socket_state && !offline->etcp_sockets && !stcp_server_list_count(offline));
|
|
fail_udp = fail_tcp = 0;
|
|
rescan(&fixture, offline); assert(owned_socket(offline, 0) && owned_socket(offline, 1));
|
|
auto_socket_destroy(offline);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS core starts without any usable automatic sockets and recovers on network change");
|
|
|
|
fail_scan = 1;
|
|
assert(auto_socket_init(instance) == -1 && !instance->auto_socket_state && instance->etcp_sockets == fixed);
|
|
fail_scan = 0;
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS interface scan failure remains fatal and preserves unrelated sockets");
|
|
|
|
clobber_close = 1;
|
|
for (int tcp = 0; tcp < 2; tcp++) {
|
|
fail_bind_type = tcp ? SOCK_STREAM : SOCK_DGRAM;
|
|
bind_error = EADDRNOTAVAIL; bind_remaining = -1; bind_failures = 0;
|
|
assert(auto_socket_init(instance) == 0 && bind_failures == 1);
|
|
assert(!owned_socket(instance, tcp) && owned_socket(instance, !tcp));
|
|
fail_bind_type = 0; rescan(&fixture, instance);
|
|
assert(owned_socket(instance, 0) && owned_socket(instance, 1));
|
|
auto_socket_destroy(instance);
|
|
|
|
fail_bind_type = tcp ? SOCK_STREAM : SOCK_DGRAM;
|
|
bind_error = EADDRINUSE; bind_remaining = 1; bind_failures = 0;
|
|
assert(auto_socket_init(instance) == 0 && bind_failures == 1);
|
|
assert(owned_socket(instance, 0) && owned_socket(instance, 1));
|
|
auto_socket_destroy(instance); fail_bind_type = 0;
|
|
}
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS UDP/TCP bind errors survive cleanup; unavailable address skips, busy port retries");
|
|
|
|
fake_ipv6 = 1; fail_bind_family = AF_INET6; fail_bind_type = SOCK_DGRAM;
|
|
bind_error = EADDRNOTAVAIL; bind_remaining = -1; bind_failures = 0;
|
|
assert(auto_socket_init(instance) == 0 && bind_failures == 1);
|
|
udp_generation = owned_socket(instance, 0)->generation; tcp_generation = owned_socket(instance, 1)->generation;
|
|
uint64_t tcp6_generation = test_etcp_bound_socket(instance, "as_lo_v6_tcp")->generation;
|
|
for (struct ETCP_SOCKET* es = instance->etcp_sockets; es; es = es->next) assert(strcmp(es->name, "as_lo_v6_udp"));
|
|
fail_bind_type = 0; rescan(&fixture, instance);
|
|
assert(test_etcp_bound_socket(instance, "as_lo_v6_udp"));
|
|
assert(owned_socket(instance, 0)->generation == udp_generation && owned_socket(instance, 1)->generation == tcp_generation);
|
|
assert(test_etcp_bound_socket(instance, "as_lo_v6_tcp")->generation == tcp6_generation);
|
|
auto_socket_destroy(instance); fake_ipv6 = fail_bind_family = clobber_close = 0;
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS IPv6 UDP bind failure preserves IPv4 UDP/TCP and IPv6 TCP; rescan adds only missing socket");
|
|
|
|
fake_network = 0; instance->config->global.auto_sockets = CFG_AUTO_SOCKETS_ANDROID;
|
|
assert(auto_socket_init(instance) == 0);
|
|
struct ETCP_SOCKET* android = test_etcp_bound_socket(instance, "udp");
|
|
uint64_t original = android->generation;
|
|
struct CFG_CLIENT* client = u_calloc(1, sizeof(*client)); assert(client);
|
|
struct CFG_CLIENT_LINK* link = u_calloc(1, sizeof(*link)); assert(link);
|
|
strcpy(client->name, "reference-test"); client->links = link; link->local_srv = instance->config->servers;
|
|
instance->config->clients = client;
|
|
const char* same = "[server:udp]\naddr=127.0.0.9:0\ntype=public\n";
|
|
struct utun_config* updated = parse_config_from_buf(same, strlen(same), "auto-reload-test"); assert(updated);
|
|
auto_socket_reload_config(instance, updated);
|
|
assert(test_etcp_bound_socket(instance, "udp")->generation == original);
|
|
assert(link->local_srv == instance->config->servers && !strcmp(link->server_name, "udp"));
|
|
const char* changed = "[server:udp]\naddr=127.0.0.9:0\ntype=public\nmtu=1150\n";
|
|
updated = parse_config_from_buf(changed, strlen(changed), "auto-reload-test"); assert(updated);
|
|
auto_socket_reload_config(instance, updated);
|
|
assert(test_etcp_bound_socket(instance, "udp")->generation != original && test_etcp_bound_socket(instance, "udp")->mtu == 1150);
|
|
assert(link->local_srv == instance->config->servers);
|
|
auto_socket_reload_config(instance, instance->config);
|
|
auto_socket_destroy(instance);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS unchanged ephemeral bind, changed options and config client reference rebinding");
|
|
|
|
fail_bind_type = SOCK_DGRAM; bind_error = EADDRNOTAVAIL; bind_remaining = -1;
|
|
struct UTUN_INSTANCE* android_offline = nat_fixture_node(&fixture, "127.0.0.11", NULL,
|
|
"[global]\nauto_sockets=android\n");
|
|
assert(android_offline->auto_socket_state && !android_offline->etcp_sockets);
|
|
const char* partial = "[server:udp]\naddr=127.0.0.11:0\n"
|
|
"[server:tcp]\naddr=127.0.0.11:0\ntransport=tcp\n";
|
|
updated = parse_config_from_buf(partial, strlen(partial), "android-partial"); assert(updated);
|
|
auto_socket_reload_config(android_offline, updated);
|
|
struct ETCP_SOCKET* retained_tcp = test_etcp_bound_socket(android_offline, "tcp");
|
|
tcp_generation = retained_tcp->generation;
|
|
assert(android_offline->etcp_sockets == retained_tcp && !retained_tcp->next);
|
|
fail_bind_type = 0;
|
|
updated = parse_config_from_buf(partial, strlen(partial), "android-recovered"); assert(updated);
|
|
auto_socket_reload_config(android_offline, updated);
|
|
assert(test_etcp_bound_socket(android_offline, "udp") && test_etcp_bound_socket(android_offline, "tcp")->generation == tcp_generation);
|
|
nat_fixture_cleanup(&fixture);
|
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PASS Android offline startup, partial reload and recovery preserve working TCP; no resource leaks");
|
|
return 0;
|
|
}
|