Files
utun2/tests/test_ncd_config.c
T

203 lines
12 KiB
C

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "utun_instance.h"
#include "config_parser.h"
#include "chat/chat_core.h"
#include "routing_layer/topo_group.h"
#include "routing_layer/nat_detection.h"
#include "transport_layer/etcp.h"
#include "transport_layer/etcp_connections.h"
#include "transport_layer/node_conn_direct.h"
#include "transport_layer/stcp_link.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "test_utils.h"
/* Настроенный транспорт к UTUN-серверу не даёт общего членства: JOIN отклоняется, NAT не выбирает сервер. */
static void check_chat_bootstrap(struct UASYNC* ua) {
const char* listener = "[global]\nauto_sockets=no\n[server:udp]\naddr=127.0.0.1:0\ntype=public\n"
"[allowed_keys]\nallow_all=yes\n[ntp]\nenabled=no\n";
struct UTUN_INSTANCE* server = utun_instance_create_from_str(ua, listener); assert(server);
assert(utun_instance_init(server) == 0);
char public_key[65], config[1024];
for (unsigned i = 0; i < 32; i++) snprintf(public_key + i * 2, 3, "%02x", server->my_keys.public_key[i]);
uint16_t port = ss_get_port(&server->etcp_sockets->local_addr);
snprintf(config, sizeof(config), "%s[client:bootstrap]\npeer_public_key=%s\nlink=127.0.0.1:%u\n"
"[chatserver]\ngroup_autoconnect=0\n", listener, public_key, port);
struct UTUN_INSTANCE* client = utun_instance_create_from_str(ua, config); assert(client);
assert(utun_core_start(client) == 0 && chat_service_start(client) == 0);
struct ETCP_CONN* conn = node_conn_direct_get_conn(client->config_conn_handles->transport); assert(conn);
uint64_t until = get_time_tb() + 50000;
while (!conn->links_up && get_time_tb() < until) uasync_poll(ua, 10);
assert(conn->links_up && !client->utun_started && !topo_groups_get_default(client->topo_groups));
struct TOPO_PEER_REQUEST* request = NULL;
struct TOPO_GROUP* group = topo_groups_get_default(server->topo_groups); assert(group);
assert(topo_group_peer_open(group, client->node_id, &request) == 0);
until = get_time_tb() + 5000;
while (get_time_tb() < until) uasync_poll(ua, 10);
assert(!topo_groups_get_default(client->topo_groups) && !topo_group_peer_ready(group, client->node_id));
assert(conn->links_up && !conn->close_requested && !client->config_conn_handles->request);
struct nat_check_status status;
assert(nat_detection_get_status(client->nat_det, client->etcp_sockets, &status) == 0);
assert(!status.observer && !status.helper);
/* После замены сокета bootstrap восстанавливается без записи сервера в общем реестре узлов. */
assert(!topo_node_registry_find(client->topo_groups, server->node_id));
etcp_socket_remove(client->etcp_sockets);
struct ETCP_SOCKET* replacement = NULL;
assert(etcp_create_server_socket(client, client->config->servers, &replacement) == 0 && replacement);
client_connections_network_changed(client);
until = get_time_tb() + 50000;
while ((client->config_conn_handles->retry_timer || !conn->links_up) && get_time_tb() < until) uasync_poll(ua, 10);
assert(!client->config_conn_handles->retry_timer && conn->links_up && !conn->close_requested);
assert(node_conn_direct_get_conn(client->config_conn_handles->transport) == conn);
assert(!topo_groups_get_default(client->topo_groups));
topo_group_peer_close(request);
utun_instance_destroy(client); utun_instance_destroy(server); uasync_poll(ua, 0);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[PASS] configured bootstrap UP without UTUN; incoming UTUN JOIN rejected; no NAT candidate");
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[PASS] bootstrap without signed NODEINFO restores UDP after replacing local socket");
}
/* Полный reload сохраняет выбор сервисов и не включает UTUN у работающего чата. */
static void check_chat_reload(struct UASYNC* ua) {
char directory[] = "/tmp/utun-config-chat-XXXXXX", path[512];
assert(test_mkdtemp(directory) == 0); snprintf(path, sizeof(path), "%s/node.cfg", directory);
struct UTUN_INSTANCE* inst = NULL;
for (unsigned pass = 0; pass < 2; pass++) {
FILE* file = fopen(path, "w"); assert(file);
assert(fprintf(file, "[global]\nauto_sockets=no\ntun_ifname=chat%u\n"
"[server:udp]\naddr=127.0.0.1:0\ntype=public\n[ntp]\nenabled=no\n"
"[chatserver]\ngroup_autoconnect=0\n", pass) > 0);
assert(fclose(file) == 0);
if (!pass) {
inst = utun_instance_create(ua, path); assert(inst);
assert(utun_core_start(inst) == 0 && chat_service_start(inst) == 0);
} else inst = utun_instance_reload(inst, ua, path);
assert(inst && inst->core_started && inst->chat_started && !inst->utun_started);
assert(!topo_groups_get_default(inst->topo_groups));
}
utun_instance_destroy(inst); uasync_poll(ua, 0);
test_unlink(path); test_rmdir(directory);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[PASS] full reload preserves core/chat without starting UTUN");
}
static int links(struct ETCP_CONN* conn) {
int count = 0;
for (struct ETCP_LINK* link = conn->links; link; link = link->next) count++;
return count;
}
static void endpoint(struct CFG_CLIENT_LINK* link, struct CFG_SERVER* local, int port) {
memset(link, 0, sizeof(*link)); link->local_srv = local;
struct sockaddr_in* addr = (struct sockaddr_in*)&link->remote_addr;
addr->sin_family = AF_INET; addr->sin_addr.s_addr = htonl(0x7f000001); addr->sin_port = htons((uint16_t)port);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
utun_instance_set_tun_init_enabled(0);
struct UASYNC* ua = uasync_create(); assert(ua);
check_chat_bootstrap(ua);
check_chat_reload(ua);
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua,
"[global]\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"[server: udp]\naddr=127.0.0.1:0\ntype=public\n"
"[server: tcp]\naddr=127.0.0.1:0\ntype=public\ntransport=tcp\n");
assert(inst && utun_core_start(inst) == 0);
assert(!inst->config->global.gui_utun_enabled && !inst->utun_started && !topo_groups_get_default(inst->topo_groups));
for (struct ETCP_SOCKET* sock = inst->etcp_sockets; sock; sock = sock->next) {
uint16_t port;
if (sock->is_tcp) {
struct stcp_server* listener = stcp_server_list_find_by_sock(inst, sock); assert(listener);
port = stcp_link_server_get_port(listener);
} else {
struct sockaddr_storage bound; socklen_t size = sizeof(bound);
assert(getsockname(sock->fd, (struct sockaddr*)&bound, &size) == 0);
port = ss_get_port(&bound);
}
assert(port && ss_get_port(&sock->local_addr) == port && ss_get_port(&sock->interface_addr) == port);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "ephemeral %s socket=%s actual_port=%u verified", sock->is_tcp ? "TCP" : "UDP", sock->name, port);
}
struct CFG_SERVER *udp = NULL, *tcp = NULL;
for (struct CFG_SERVER* s = inst->config->servers; s; s = s->next) {
if (!strcmp(s->name, "udp")) udp = s;
if (!strcmp(s->name, "tcp")) tcp = s;
}
assert(udp && tcp);
struct CFG_CLIENT client = { .name = "peer" };
struct SC_MYKEYS peer; assert(sc_generate_keypair(&peer) == SC_OK);
for (int i = 0; i < 32; i++) snprintf(client.peer_public_key_hex + i * 2, 3, "%02x", peer.public_key[i]);
struct CFG_CLIENT_LINK first, second, third;
endpoint(&first, udp, 9); endpoint(&second, udp, 10); endpoint(&third, tcp, 11);
first.next = &second; second.next = &third; client.links = &first;
assert(init_client_connections(inst, &client) == 0);
assert(inst->config_conn_handles && !inst->config_conn_handles->next);
assert(!inst->config_conn_handles->request);
struct ETCP_CONN* conn = node_conn_direct_get_conn(inst->config_conn_handles->transport);
assert(conn && links(conn) == 3 && !conn->fin_wait);
assert(utun_service_start(inst) == 0);
assert(topo_group_peer_conn(inst->config_conn_handles->request) == conn);
utun_service_stop(inst);
assert(!inst->config_conn_handles->request && !topo_groups_get_default(inst->topo_groups));
assert(node_conn_direct_get_conn(inst->config_conn_handles->transport) == conn && !conn->fin_wait);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[PASS] configured transport works without UTUN and survives UTUN start/stop");
assert(utun_service_start(inst) == 0);
struct NODE_CONN_DIRECT* chat = NULL;
assert(node_conn_direct_open(inst, conn->peer_node_id, NULL, NULL, &chat, NULL) == NCD_REUSED);
assert(init_client_connections(inst, &client) == 0);
assert(topo_group_peer_conn(inst->config_conn_handles->request) == conn && links(conn) == 3 && !conn->fin_wait);
/* Ошибка нового набора не освобождает предыдущие handles. */
struct CFG_CLIENT invalid = { .name = "invalid", .peer_public_key_hex = "not-a-key" };
client.next = &invalid;
struct CONFIG_CONN_HANDLE* previous = inst->config_conn_handles;
assert(init_client_connections(inst, &client) < 0 && inst->config_conn_handles == previous);
assert(node_conn_direct_get_conn(chat) == conn && !conn->fin_wait);
client.next = NULL;
/* REALITY идёт по тому же NCD пути; полное SNI не обрезается. */
client.links = &third; client.reality_enabled = 1;
reality_client_config_set_defaults(&client.reality);
memcpy(client.reality.server_static_pubkey, peer.public_key, 32);
memset(client.reality.server_name, 'a', 180); client.reality.server_name[180] = 0;
assert(init_client_connections(inst, &client) == 0);
assert(links(conn) == 3);
struct ETCP_LINK* real = NULL;
for (struct ETCP_LINK* link = conn->links; link; link = link->next) if (link->is_tcp) real = link;
assert(real && real->reality_set && !strcmp(real->reality.server_name, client.reality.server_name));
assert(init_client_connections(inst, &client) == 0 && links(conn) == 3);
assert(init_client_connections(inst, NULL) == 0 && !inst->config_conn_handles);
assert(node_conn_direct_get_conn(chat) == conn && !conn->fin_wait);
node_conn_direct_force_close(chat);
uasync_poll(ua, 0);
/* Клиентский TCP/REALITY не требует локального TCP listen-сокета. */
struct ETCP_SOCKET* sock = inst->etcp_sockets;
while (sock) {
struct ETCP_SOCKET* next = sock->next;
if (sock->is_tcp) etcp_socket_remove(sock);
sock = next;
}
third.local_srv = NULL;
assert(init_client_connections(inst, &client) == 0);
conn = topo_group_peer_conn(inst->config_conn_handles->request);
assert(conn && links(conn) == 1 && conn->links->is_tcp && !conn->links->conn);
assert(conn->links->reality_set && !strcmp(conn->links->reality.server_name, client.reality.server_name));
/* CLOSED восстанавливает транспорт из собственной копии конфига, включая REALITY и привязку сокета. */
etcp_connection_close(conn);
while (inst->config_conn_handles->transport) uasync_poll(ua, 0);
assert(!inst->config_conn_handles->transport && inst->config_conn_handles->retry_timer);
uint64_t until = get_time_tb() + 30000;
while (!inst->config_conn_handles->transport && get_time_tb() < until) uasync_poll(ua, 10);
assert(inst->config_conn_handles->transport && !inst->config_conn_handles->retry_timer);
conn = node_conn_direct_get_conn(inst->config_conn_handles->transport);
assert(conn && conn->links->reality_set && !strcmp(conn->links->reality.server_name, client.reality.server_name));
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[PASS] configured transport reopens after CLOSED with retained REALITY parameters");
assert(init_client_connections(inst, NULL) == 0);
inst->running = 0; utun_instance_destroy(inst);
uasync_poll(ua, 0);
uasync_destroy(ua, 0);
return 0;
}