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.
586 lines
20 KiB
586 lines
20 KiB
#include "utun_test_framework.h" |
|
#include "utun_instance.h" |
|
#include "../lib/u_async.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/ll_queue.h" |
|
#include "test_udp_socket.h" |
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <errno.h> |
|
#include <unistd.h> |
|
#include <pthread.h> |
|
#include <sys/time.h> |
|
|
|
// External mutex from test_udp_socket.c |
|
extern pthread_mutex_t g_registry_mutex; |
|
|
|
#ifndef DEBUG_CATEGORY_TEST |
|
#define DEBUG_CATEGORY_TEST (1 << 30) |
|
#endif |
|
|
|
// Default test configurations |
|
const struct test_instance_config TEST_SERVER_CONFIG = { |
|
.config_file = "test_server.conf", |
|
.node_id = 0x1111222233334444, |
|
.port_base = 40000, |
|
.enable_tun = false, |
|
.enable_socket_hooks = true, |
|
.enable_packet_capture = true, |
|
.log_file = NULL |
|
}; |
|
|
|
const struct test_instance_config TEST_CLIENT_CONFIG = { |
|
.config_file = "test_client.conf", |
|
.node_id = 0x8888777766665555, |
|
.port_base = 40100, |
|
.enable_tun = false, |
|
.enable_socket_hooks = true, |
|
.enable_packet_capture = true, |
|
.log_file = NULL |
|
}; |
|
|
|
// Forward declarations for internal functions |
|
static int test_tun_create_hook(void* priv_data); |
|
static ssize_t test_tun_read_hook(int fd, void *buf, size_t count, void* priv_data); |
|
static ssize_t test_tun_write_hook(int fd, const void *buf, size_t count, void* priv_data); |
|
static int test_socket_create_hook(int domain, int type, int protocol, void* context); |
|
static void test_packet_capture_hook(struct UTUN_INSTANCE* instance, const uint8_t* packet, size_t len, |
|
const char* direction, void* context); |
|
static void test_cleanup_captured_packets(struct test_instance* test); |
|
|
|
// Create test instance with virtual components |
|
struct test_instance* test_create_instance(const struct test_instance_config* config) { |
|
if (!config) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "NULL configuration provided"); |
|
return NULL; |
|
} |
|
|
|
struct test_instance* test = calloc(1, sizeof(struct test_instance)); |
|
if (!test) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate test instance"); |
|
return NULL; |
|
} |
|
|
|
// Copy configuration |
|
test->config = *config; |
|
|
|
// Create uasync instance |
|
test->ua = uasync_create(); |
|
if (!test->ua) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create uasync instance"); |
|
free(test); |
|
return NULL; |
|
} |
|
|
|
// Setup test hooks based on configuration |
|
if (config->enable_tun || config->enable_socket_hooks || config->enable_packet_capture) { |
|
test->hooks.test_context = test; |
|
|
|
if (config->enable_tun) { |
|
// Create virtual TUN |
|
char vtun_name[32]; |
|
snprintf(vtun_name, sizeof(vtun_name), "vtun%ld", config->node_id & 0xFFFF); |
|
test->vtun = virtual_tun_create(vtun_name); |
|
if (!test->vtun) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create virtual TUN"); |
|
uasync_destroy(test->ua); |
|
free(test); |
|
return NULL; |
|
} |
|
|
|
test->hooks.tun_create_override = test_tun_create_hook; |
|
test->hooks.tun_read_override = test_tun_read_hook; |
|
test->hooks.tun_write_override = test_tun_write_hook; |
|
} |
|
|
|
if (config->enable_socket_hooks) { |
|
test->hooks.socket_create_override = test_socket_create_hook; |
|
test->hooks.sendto_override = NULL; // Will be handled by virtual UDP sockets |
|
test->hooks.recvfrom_override = NULL; // Will be handled by virtual UDP sockets |
|
} |
|
|
|
if (config->enable_packet_capture) { |
|
test->hooks.packet_captured = test_packet_capture_hook; |
|
} |
|
|
|
// Install hooks |
|
#ifdef TEST_BUILD |
|
utun_test_hooks_set(&test->hooks); |
|
test->hooks_installed = true; |
|
#endif |
|
} |
|
|
|
// Allocate packet capture buffer |
|
if (config->enable_packet_capture) { |
|
test->captured_packets.capacity = 1000; |
|
test->captured_packets.packets = calloc(test->captured_packets.capacity, sizeof(uint8_t*)); |
|
test->captured_packets.lengths = calloc(test->captured_packets.capacity, sizeof(size_t)); |
|
test->captured_packets.directions = calloc(test->captured_packets.capacity, sizeof(char*)); |
|
|
|
if (!test->captured_packets.packets || !test->captured_packets.lengths || !test->captured_packets.directions) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate packet capture buffers"); |
|
if (test->vtun) virtual_tun_destroy(test->vtun); |
|
uasync_destroy(test->ua); |
|
free(test); |
|
return NULL; |
|
} |
|
} |
|
|
|
// Allocate UDP socket tracking |
|
test->udp_socket_capacity = 16; |
|
test->udp_sockets = calloc(test->udp_socket_capacity, sizeof(struct test_udp_socket*)); |
|
if (!test->udp_sockets) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to allocate UDP socket tracking"); |
|
test_cleanup_captured_packets(test); |
|
if (test->vtun) virtual_tun_destroy(test->vtun); |
|
uasync_destroy(test->ua); |
|
free(test); |
|
return NULL; |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Created test instance: node_id=0x%016lX, config=%s", |
|
config->node_id, config->config_file); |
|
return test; |
|
} |
|
|
|
// Destroy test instance |
|
void test_destroy_instance(struct test_instance* test) { |
|
if (!test) return; |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Destroying test instance: node_id=0x%016lX", |
|
test->config.node_id); |
|
|
|
// Stop instance if running |
|
test_stop_instance(test); |
|
|
|
// Clear hooks if installed |
|
if (test->hooks_installed) { |
|
#ifdef TEST_BUILD |
|
utun_test_hooks_clear(); |
|
#endif |
|
test->hooks_installed = false; |
|
} |
|
|
|
// Destroy virtual components |
|
if (test->vtun) { |
|
virtual_tun_destroy(test->vtun); |
|
} |
|
|
|
// Destroy UDP sockets |
|
for (int i = 0; i < test->udp_socket_count; i++) { |
|
if (test->udp_sockets[i]) { |
|
test_udp_socket_destroy(test->udp_sockets[i]); |
|
} |
|
} |
|
free(test->udp_sockets); |
|
|
|
// Cleanup captured packets |
|
test_cleanup_captured_packets(test); |
|
|
|
// Destroy uasync |
|
if (test->ua) { |
|
uasync_destroy(test->ua); |
|
} |
|
|
|
// Destroy UTUN instance |
|
if (test->utun) { |
|
utun_instance_destroy(test->utun); |
|
} |
|
|
|
free(test); |
|
} |
|
|
|
// Start test instance |
|
int test_start_instance(struct test_instance* test) { |
|
if (!test) return -1; |
|
if (test->utun) return 0; // Already started |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Starting test instance: node_id=0x%016lX", test->config.node_id); |
|
|
|
// Determine flags for instance creation |
|
uint32_t flags = 0; |
|
if (!test->config.enable_tun) { |
|
flags |= UTUN_CREATE_NO_TUN; |
|
} |
|
if (test->config.enable_socket_hooks || test->config.enable_packet_capture) { |
|
flags |= UTUN_CREATE_TEST_MODE; |
|
} |
|
if (test->config.enable_socket_hooks) { |
|
flags |= UTUN_CREATE_NO_SOCKET_BIND; |
|
} |
|
if (test->config.enable_tun) { |
|
flags |= UTUN_CREATE_ALLOW_TUN_FAILURE; // Allow virtual TUN to fail gracefully |
|
} |
|
|
|
// Create UTUN instance |
|
test->utun = utun_instance_create_ex(test->ua, test->config.config_file, test->config.log_file, flags); |
|
if (!test->utun) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create UTUN instance"); |
|
return -1; |
|
} |
|
|
|
// Initialize instance |
|
if (utun_instance_init(test->utun) < 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to initialize UTUN instance"); |
|
utun_instance_destroy(test->utun); |
|
test->utun = NULL; |
|
return -1; |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Test instance started successfully"); |
|
return 0; |
|
} |
|
|
|
// Stop test instance |
|
void test_stop_instance(struct test_instance* test) { |
|
if (!test || !test->utun) return; |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Stopping test instance: node_id=0x%016lX", test->config.node_id); |
|
|
|
utun_instance_stop(test->utun); |
|
} |
|
|
|
// Inject packet into TUN interface |
|
int test_inject_tun_packet(struct test_instance* test, const uint8_t* packet, size_t len) { |
|
if (!test || !test->vtun || !packet || len == 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for TUN packet injection"); |
|
return -1; |
|
} |
|
|
|
return virtual_tun_inject_packet(test->vtun, packet, len); |
|
} |
|
|
|
// Inject UDP packet |
|
int test_inject_udp_packet(struct test_instance* test, int socket_fd, |
|
const uint8_t* packet, size_t len, |
|
const struct sockaddr* src_addr, socklen_t addr_len) { |
|
if (!test || !packet || len == 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for UDP packet injection"); |
|
return -1; |
|
} |
|
|
|
struct test_udp_socket* sock = test_udp_socket_find_by_fd(socket_fd); |
|
if (!sock) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Socket fd=%d not found", socket_fd); |
|
return -1; |
|
} |
|
|
|
return test_udp_socket_inject(sock, packet, len, src_addr, addr_len); |
|
} |
|
|
|
// Get captured packets |
|
size_t test_get_captured_packets(struct test_instance* test, |
|
const char* direction, |
|
uint8_t*** packets, size_t** lengths) { |
|
if (!test || !direction || !packets || !lengths) return 0; |
|
|
|
// Count packets matching direction |
|
size_t count = 0; |
|
for (size_t i = 0; i < test->captured_packets.count; i++) { |
|
if (strcmp(test->captured_packets.directions[i], direction) == 0) { |
|
count++; |
|
} |
|
} |
|
|
|
if (count == 0) return 0; |
|
|
|
// Allocate result arrays |
|
*packets = calloc(count, sizeof(uint8_t*)); |
|
*lengths = calloc(count, sizeof(size_t)); |
|
if (!*packets || !*lengths) { |
|
free(*packets); |
|
free(*lengths); |
|
return 0; |
|
} |
|
|
|
// Fill result arrays |
|
size_t result_idx = 0; |
|
for (size_t i = 0; i < test->captured_packets.count; i++) { |
|
if (strcmp(test->captured_packets.directions[i], direction) == 0) { |
|
(*packets)[result_idx] = test->captured_packets.packets[i]; |
|
(*lengths)[result_idx] = test->captured_packets.lengths[i]; |
|
result_idx++; |
|
} |
|
} |
|
|
|
return count; |
|
} |
|
|
|
// Get captured packet count |
|
size_t test_get_captured_packet_count(struct test_instance* test, const char* direction) { |
|
if (!test || !direction) return 0; |
|
|
|
size_t count = 0; |
|
for (size_t i = 0; i < test->captured_packets.count; i++) { |
|
if (strcmp(test->captured_packets.directions[i], direction) == 0) { |
|
count++; |
|
} |
|
} |
|
return count; |
|
} |
|
|
|
// Clear captured packets |
|
void test_clear_captured_packets(struct test_instance* test) { |
|
if (!test) return; |
|
|
|
for (size_t i = 0; i < test->captured_packets.count; i++) { |
|
if (test->captured_packets.packets[i]) { |
|
free(test->captured_packets.packets[i]); |
|
} |
|
if (test->captured_packets.directions[i]) { |
|
free(test->captured_packets.directions[i]); |
|
} |
|
} |
|
test->captured_packets.count = 0; |
|
} |
|
|
|
// Wait for packet with timeout |
|
int test_wait_for_packet(struct test_instance* test, const char* direction, int timeout_ms) { |
|
if (!test || !direction || timeout_ms <= 0) return -1; |
|
|
|
size_t initial_count = test_get_captured_packet_count(test, direction); |
|
size_t current_count = initial_count; |
|
|
|
struct timeval start, current; |
|
gettimeofday(&start, NULL); |
|
|
|
while (current_count == initial_count) { |
|
gettimeofday(¤t, NULL); |
|
int elapsed_ms = (current.tv_sec - start.tv_sec) * 1000 + (current.tv_usec - start.tv_usec) / 1000; |
|
|
|
if (elapsed_ms >= timeout_ms) { |
|
DEBUG_WARN(DEBUG_CATEGORY_TEST, "Timeout waiting for %s packet after %dms", direction, timeout_ms); |
|
return -1; |
|
} |
|
|
|
usleep(10000); // Sleep 10ms |
|
current_count = test_get_captured_packet_count(test, direction); |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Detected %s packet after %dms", direction, |
|
(current.tv_sec - start.tv_sec) * 1000 + (current.tv_usec - start.tv_usec) / 1000); |
|
return 0; |
|
} |
|
|
|
// Two-instance test runner |
|
int test_run_two_instances(const struct test_instance_config* server_config, |
|
const struct test_instance_config* client_config, |
|
void (*test_scenario)(struct test_instance* server, |
|
struct test_instance* client), |
|
int timeout_ms) { |
|
if (!server_config || !client_config || !test_scenario) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Invalid parameters for two-instance test"); |
|
return -1; |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Starting two-instance test"); |
|
|
|
// Create instances |
|
struct test_instance* server = test_create_instance(server_config); |
|
struct test_instance* client = test_create_instance(client_config); |
|
|
|
if (!server || !client) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to create test instances"); |
|
test_destroy_instance(server); |
|
test_destroy_instance(client); |
|
return -1; |
|
} |
|
|
|
// Start instances |
|
if (test_start_instance(server) < 0 || test_start_instance(client) < 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to start test instances"); |
|
test_destroy_instance(server); |
|
test_destroy_instance(client); |
|
return -1; |
|
} |
|
|
|
// Give instances time to initialize |
|
sleep(1); |
|
|
|
// Run test scenario |
|
int result = 0; |
|
if (timeout_ms > 0) { |
|
// TODO: Implement timeout mechanism for test scenario |
|
test_scenario(server, client); |
|
} else { |
|
test_scenario(server, client); |
|
} |
|
|
|
// Cleanup |
|
test_destroy_instance(server); |
|
test_destroy_instance(client); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TEST, "Two-instance test completed: %s", result == 0 ? "SUCCESS" : "FAILED"); |
|
return result; |
|
} |
|
|
|
// Get test instance statistics |
|
void test_get_instance_stats(struct test_instance* test, size_t* tun_packets_sent, |
|
size_t* tun_packets_received, size_t* udp_packets_sent, |
|
size_t* udp_packets_received) { |
|
if (!test) return; |
|
|
|
if (test->vtun) { |
|
virtual_tun_get_stats(test->vtun, tun_packets_sent, tun_packets_received, NULL, NULL); |
|
} |
|
|
|
// Aggregate UDP socket statistics |
|
size_t udp_sent = 0, udp_received = 0; |
|
for (int i = 0; i < test->udp_socket_count; i++) { |
|
if (test->udp_sockets[i]) { |
|
size_t sent, recv; |
|
test_udp_socket_get_stats(test->udp_sockets[i], &sent, &recv, NULL, NULL, NULL, NULL); |
|
udp_sent += sent; |
|
udp_received += recv; |
|
} |
|
} |
|
|
|
if (udp_packets_sent) *udp_packets_sent = udp_sent; |
|
if (udp_packets_received) *udp_packets_received = udp_received; |
|
} |
|
|
|
// Utility functions |
|
const char* test_get_direction_name(const char* direction) { |
|
if (!direction) return "unknown"; |
|
|
|
if (strcmp(direction, "tun_in") == 0) return "TUN incoming"; |
|
if (strcmp(direction, "tun_out") == 0) return "TUN outgoing"; |
|
if (strcmp(direction, "udp_in") == 0) return "UDP incoming"; |
|
if (strcmp(direction, "udp_out") == 0) return "UDP outgoing"; |
|
return direction; |
|
} |
|
|
|
int test_compare_packets(const uint8_t* pkt1, size_t len1, const uint8_t* pkt2, size_t len2) { |
|
if (!pkt1 || !pkt2) return -1; |
|
if (len1 != len2) return -1; |
|
return memcmp(pkt1, pkt2, len1); |
|
} |
|
|
|
void test_dump_packet(const char* prefix, const uint8_t* packet, size_t len) { |
|
if (!prefix || !packet || len == 0) return; |
|
|
|
printf("%s: %zu bytes\n", prefix, len); |
|
for (size_t i = 0; i < len; i++) { |
|
printf("%02X ", packet[i]); |
|
if ((i + 1) % 16 == 0) printf("\n"); |
|
} |
|
if (len % 16 != 0) printf("\n"); |
|
} |
|
|
|
// Internal hook implementations |
|
static int test_tun_create_hook(void* priv_data) { |
|
struct test_instance* test = (struct test_instance*)priv_data; |
|
if (!test || !test->vtun) return -1; |
|
|
|
// Return the read fd as the TUN device fd |
|
return virtual_tun_get_read_fd(test->vtun); |
|
} |
|
|
|
static ssize_t test_tun_read_hook(int fd, void *buf, size_t count, void* priv_data) { |
|
struct test_instance* test = (struct test_instance*)priv_data; |
|
if (!test || !test->vtun) return -1; |
|
|
|
return virtual_tun_read_packet(test->vtun, buf, count); |
|
} |
|
|
|
static ssize_t test_tun_write_hook(int fd, const void *buf, size_t count, void* priv_data) { |
|
struct test_instance* test = (struct test_instance*)priv_data; |
|
if (!test || !test->vtun) return -1; |
|
|
|
ssize_t result = virtual_tun_write_packet(test->vtun, buf, count); |
|
|
|
// Capture packet if enabled |
|
if (test->config.enable_packet_capture && result > 0) { |
|
test_packet_capture_hook(test->utun, buf, result, "tun_out", test); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static int test_socket_create_hook(int domain, int type, int protocol, void* context) { |
|
struct test_instance* test = (struct test_instance*)context; |
|
if (!test || !test->config.enable_socket_hooks) return -1; |
|
|
|
// Create virtual UDP socket |
|
struct test_udp_socket* sock = test_udp_socket_create(domain); |
|
if (!sock) return -1; |
|
|
|
// Add to tracking |
|
pthread_mutex_lock(&g_registry_mutex); |
|
if (test->udp_socket_count >= test->udp_socket_capacity) { |
|
// Expand array |
|
int new_capacity = test->udp_socket_capacity * 2; |
|
struct test_udp_socket** new_sockets = realloc(test->udp_sockets, |
|
new_capacity * sizeof(struct test_udp_socket*)); |
|
if (!new_sockets) { |
|
pthread_mutex_unlock(&g_registry_mutex); |
|
test_udp_socket_destroy(sock); |
|
return -1; |
|
} |
|
test->udp_sockets = new_sockets; |
|
test->udp_socket_capacity = new_capacity; |
|
} |
|
test->udp_sockets[test->udp_socket_count++] = sock; |
|
pthread_mutex_unlock(&g_registry_mutex); |
|
|
|
return test_udp_socket_get_fd(sock); |
|
} |
|
|
|
static void test_packet_capture_hook(struct UTUN_INSTANCE* instance, const uint8_t* packet, size_t len, |
|
const char* direction, void* context) { |
|
struct test_instance* test = (struct test_instance*)context; |
|
if (!test || !test->config.enable_packet_capture || !packet || len == 0) return; |
|
|
|
// Expand capture buffer if needed |
|
if (test->captured_packets.count >= test->captured_packets.capacity) { |
|
size_t new_capacity = test->captured_packets.capacity * 2; |
|
uint8_t** new_packets = realloc(test->captured_packets.packets, |
|
new_capacity * sizeof(uint8_t*)); |
|
size_t* new_lengths = realloc(test->captured_packets.lengths, |
|
new_capacity * sizeof(size_t)); |
|
char** new_directions = realloc(test->captured_packets.directions, |
|
new_capacity * sizeof(char*)); |
|
|
|
if (!new_packets || !new_lengths || !new_directions) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TEST, "Failed to expand packet capture buffer"); |
|
return; |
|
} |
|
|
|
test->captured_packets.packets = new_packets; |
|
test->captured_packets.lengths = new_lengths; |
|
test->captured_packets.directions = new_directions; |
|
test->captured_packets.capacity = new_capacity; |
|
} |
|
|
|
// Store packet |
|
uint8_t* packet_copy = malloc(len); |
|
if (!packet_copy) return; |
|
|
|
memcpy(packet_copy, packet, len); |
|
test->captured_packets.packets[test->captured_packets.count] = packet_copy; |
|
test->captured_packets.lengths[test->captured_packets.count] = len; |
|
test->captured_packets.directions[test->captured_packets.count] = strdup(direction); |
|
test->captured_packets.count++; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_TEST, "Captured packet: %s, %zu bytes", direction, len); |
|
} |
|
|
|
static void test_cleanup_captured_packets(struct test_instance* test) { |
|
if (!test) return; |
|
|
|
for (size_t i = 0; i < test->captured_packets.count; i++) { |
|
if (test->captured_packets.packets[i]) { |
|
free(test->captured_packets.packets[i]); |
|
} |
|
if (test->captured_packets.directions[i]) { |
|
free(test->captured_packets.directions[i]); |
|
} |
|
} |
|
|
|
free(test->captured_packets.packets); |
|
free(test->captured_packets.lengths); |
|
free(test->captured_packets.directions); |
|
|
|
memset(&test->captured_packets, 0, sizeof(test->captured_packets)); |
|
} |