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