Browse Source

Добавлен тестовый режим для TUN интерфейса и тест роутинга mesh

- Добавлена опция tun_test_mode в конфиг для работы без реального TUN устройства
- Добавлены функции для работы с очередями TUN в тестовом режиме
- Добавлен API для программного создания instance из структуры конфига
- Создан тест test_routing_mesh.c с 3-instance mesh топологией
- Ключи генерируются автоматически и распределяются между instance
nodeinfo-routing-update
Evgeny 2 months ago
parent
commit
bee1e41b07
  1. 5
      src/config_parser.c
  2. 2
      src/config_parser.h
  3. 211
      src/tun_if.c
  4. 93
      src/tun_if.h
  5. 89
      src/utun_instance.c
  6. 1
      src/utun_instance.h
  7. 5
      tests/Makefile.am
  8. 801
      tests/test_routing_mesh.c

5
src/config_parser.c

@ -324,6 +324,10 @@ static int parse_global(const char *key, const char *value, struct global_config
global->enable_colors = atoi(value);
return 0;
}
if (strcmp(key, "tun_test_mode") == 0) {
global->tun_test_mode = atoi(value);
return 0;
}
return 0;
}
@ -610,6 +614,7 @@ void print_config(const struct utun_config *cfg) {
printf(" TUN interface: %s\n", g->tun_ifname[0] ? g->tun_ifname : "auto");
printf(" TUN IP: %s\n", ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)));
printf(" MTU: %d\n", g->mtu);
printf(" TUN test mode: %s\n", g->tun_test_mode ? "enabled" : "disabled");
printf(" Net debug: %d\n", g->net_debug);
printf("\nServers:\n");

2
src/config_parser.h

@ -74,6 +74,8 @@ struct global_config {
int enable_function_names; // enable function names in logs
int enable_file_lines; // enable file:line in logs
int enable_colors; // enable ANSI colors in logs
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
};
struct utun_config {

211
src/tun_if.c

@ -111,6 +111,9 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
return NULL;
}
// Check for test mode
int test_mode = config->global.tun_test_mode;
// Allocate tun_if structure
struct tun_if* tun = calloc(1, sizeof(struct tun_if));
if (!tun) {
@ -122,6 +125,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
tun->fd = -1;
tun->platform_handle = NULL;
tun->adapter_handle = NULL;
tun->test_mode = test_mode;
// Get interface name from config (or empty for auto)
const char* ifname = config->global.tun_ifname;
@ -147,18 +151,28 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
// Get MTU
int mtu = config->global.mtu > 0 ? config->global.mtu : TUN_MTU_DEFAULT;
// Platform-specific initialization
if (tun_platform_init(tun, tun->ifname, tun_ip_str, mtu) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Platform-specific TUN initialization failed");
free(tun);
return NULL;
// Platform-specific initialization (skip in test mode)
if (!test_mode) {
if (tun_platform_init(tun, tun->ifname, tun_ip_str, mtu) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Platform-specific TUN initialization failed");
free(tun);
return NULL;
}
} else {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN test mode: skipping platform initialization");
// In test mode, set a fake interface name if not provided
if (tun->ifname[0] == '\0') {
strncpy(tun->ifname, "tun_test", sizeof(tun->ifname) - 1);
}
}
// Create memory pool for ETCP_FRAGMENT
tun->pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
if (!tun->pool) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create memory pool");
tun_platform_cleanup(tun);
if (!test_mode) {
tun_platform_cleanup(tun);
}
free(tun);
return NULL;
}
@ -168,7 +182,9 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
if (!tun->output_queue) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create output queue");
memory_pool_destroy(tun->pool);
tun_platform_cleanup(tun);
if (!test_mode) {
tun_platform_cleanup(tun);
}
free(tun);
return NULL;
}
@ -179,7 +195,9 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create input queue");
queue_free(tun->output_queue);
memory_pool_destroy(tun->pool);
tun_platform_cleanup(tun);
if (!test_mode) {
tun_platform_cleanup(tun);
}
free(tun);
return NULL;
}
@ -187,31 +205,38 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
// Set callback for input_queue - routing will put packets here
queue_set_callback(tun->input_queue, tun_input_queue_callback, tun);
// Register TUN socket with uasync
int poll_fd = tun_platform_get_poll_fd(tun);
if (poll_fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to get poll fd for TUN");
queue_free(tun->output_queue);
queue_free(tun->input_queue);
memory_pool_destroy(tun->pool);
tun_platform_cleanup(tun);
free(tun);
return NULL;
}
// Register TUN socket with uasync (skip in test mode)
if (!test_mode) {
int poll_fd = tun_platform_get_poll_fd(tun);
if (poll_fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to get poll fd for TUN");
queue_free(tun->output_queue);
queue_free(tun->input_queue);
memory_pool_destroy(tun->pool);
tun_platform_cleanup(tun);
free(tun);
return NULL;
}
tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun);
if (!tun->socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to register TUN socket with uasync");
queue_free(tun->output_queue);
queue_free(tun->input_queue);
memory_pool_destroy(tun->pool);
tun_platform_cleanup(tun);
free(tun);
return NULL;
tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun);
if (!tun->socket_id) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to register TUN socket with uasync");
queue_free(tun->output_queue);
queue_free(tun->input_queue);
memory_pool_destroy(tun->pool);
tun_platform_cleanup(tun);
free(tun);
return NULL;
}
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s (IP=%s, MTU=%d)",
tun->ifname, tun_ip_str, mtu);
if (test_mode) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized in TEST MODE: %s (IP=%s, MTU=%d)",
tun->ifname, tun_ip_str, mtu);
} else {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s (IP=%s, MTU=%d)",
tun->ifname, tun_ip_str, mtu);
}
return tun;
}
@ -259,8 +284,10 @@ void tun_close(struct tun_if* tun) {
tun->pool = NULL;
}
// Platform-specific cleanup
tun_platform_cleanup(tun);
// Platform-specific cleanup (skip in test mode)
if (!tun->test_mode) {
tun_platform_cleanup(tun);
}
free(tun);
}
@ -271,6 +298,16 @@ ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
return -1;
}
// In test mode, inject packet into output_queue instead of writing to interface
if (tun->test_mode) {
if (tun_inject_packet(tun, buf, len) == 0) {
return len;
} else {
tun->write_errors++;
return -1;
}
}
ssize_t nwritten = tun_platform_write(tun, buf, len);
if (nwritten < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to write to TUN device %s", tun->ifname);
@ -285,3 +322,113 @@ ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
return nwritten;
}
struct ll_queue* tun_get_input_queue(struct tun_if* tun) {
if (!tun) {
return NULL;
}
return tun->input_queue;
}
struct ll_queue* tun_get_output_queue(struct tun_if* tun) {
if (!tun) {
return NULL;
}
return tun->output_queue;
}
int tun_is_test_mode(struct tun_if* tun) {
if (!tun) {
return -1;
}
return tun->test_mode;
}
int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len) {
if (!tun || !buf || len == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: invalid arguments");
return -1;
}
if (len > TUN_MAX_PACKET_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Packet too large: %zu bytes (max %d)",
len, TUN_MAX_PACKET_SIZE);
}
// Allocate packet data
uint8_t* packet_data = malloc(len);
if (!packet_data) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: failed to allocate packet data");
tun->read_errors++;
return -1;
}
memcpy(packet_data, buf, len);
// Allocate ETCP_FRAGMENT from pool
struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_entry_new_from_pool(tun->pool);
if (!pkt) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: failed to allocate ETCP_FRAGMENT");
free(packet_data);
tun->read_errors++;
return -1;
}
// Initialize fragment
pkt->seq = 0;
pkt->timestamp = 0;
pkt->ll.dgram = packet_data;
pkt->ll.len = len;
// Add to output queue (packets from TUN to routing)
if (queue_data_put(tun->output_queue, (struct ll_entry*)pkt, 0) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: failed to add packet to output queue");
free(packet_data);
memory_pool_free(tun->pool, pkt);
tun->read_errors++;
return -1;
}
// Update statistics
tun->bytes_read += len;
tun->packets_read++;
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Injected %zu bytes into TUN output queue %s", len, tun->ifname);
return 0;
}
ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len) {
if (!tun || !buf || len == 0) {
return -1;
}
struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->input_queue);
if (!pkt) {
return 0; // No packet available
}
size_t pkt_len = pkt->ll.len;
if (pkt_len > len) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "tun_read_packet: buffer too small (%zu > %zu)",
pkt_len, len);
// Put packet back - cannot read partial
// Note: This is a simplification; in real use, consider copying partial
queue_data_put(tun->input_queue, (struct ll_entry*)pkt, 0);
return -1;
}
// Copy data to user buffer
memcpy(buf, pkt->ll.dgram, pkt_len);
// Free packet
free(pkt->ll.dgram);
memory_pool_free(tun->pool, pkt);
// Update statistics
tun->bytes_written += pkt_len;
tun->packets_written++;
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Read %zu bytes from TUN input queue %s", pkt_len, tun->ifname);
return pkt_len;
}

93
src/tun_if.h

@ -30,6 +30,7 @@ struct tun_if {
struct memory_pool* pool; // Pool for ETCP_FRAGMENT structures
struct ll_queue* output_queue; // Queue for incoming packets from TUN (to routing)
struct ll_queue* input_queue; // Queue for outgoing packets to TUN (from routing)
int test_mode; // Test mode flag: 1 = no real TUN, queues only
// Statistics
uint64_t bytes_read; // Bytes read from TUN
uint64_t bytes_written; // Bytes written to TUN
@ -67,6 +68,98 @@ void tun_close(struct tun_if* tun);
*/
ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len);
/**
* @brief Get TUN input queue (for sending packets to TUN from routing)
* @param tun TUN interface handle
* @return Pointer to input queue, NULL if tun is NULL
*
* In normal mode: routing layer puts packets here, TUN reads and sends to interface
* In test mode: tests can put packets here directly to simulate routing output
*/
struct ll_queue* tun_get_input_queue(struct tun_if* tun);
/**
* @brief Get TUN output queue (for receiving packets from TUN to routing)
* @param tun TUN interface handle
* @return Pointer to output queue, NULL if tun is NULL
*
* In normal mode: TUN receives packets from interface and puts here for routing
* In test mode: tests can read packets from here that were "sent" to TUN
*/
struct ll_queue* tun_get_output_queue(struct tun_if* tun);
/**
* @brief Check if TUN interface is in test mode
* @param tun TUN interface handle
* @return 1 if test mode, 0 if normal mode, -1 if tun is NULL
*/
int tun_is_test_mode(struct tun_if* tun);
/**
* @brief Write packet directly to TUN output queue (test mode helper)
* @param tun TUN interface handle
* @param buf Packet data
* @param len Packet size
* @return 0 on success, -1 on error
*
* This function simulates receiving a packet from the TUN interface.
* The packet is placed in the output_queue where routing layer would normally find it.
* Works in both test and normal modes.
*/
int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len);
/**
* @brief Read packet directly from TUN input queue (test mode helper)
* @param tun TUN interface handle
* @param buf Output buffer for packet data
* @param len Output buffer size
* @return Number of bytes read, 0 if no packet available, -1 on error
*
* This function simulates what the TUN interface would normally do:
* take a packet from input_queue and return it.
* Works in both test and normal modes.
*/
ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len);
/**
* @brief Test mode usage example and documentation
*
* To use TUN in test mode, set tun_test_mode=1 in the [global] section
* of the configuration file:
*
* [global]
* tun_test_mode=1
* tun_ip=10.0.0.1/24
*
* Example test code:
*
* // Initialize TUN in test mode
* struct tun_if* tun = tun_init(ua, config);
* if (!tun || !tun_is_test_mode(tun)) {
* // handle error
* }
*
* // Simulate receiving packet from TUN (as if from network)
* uint8_t packet[] = { ... }; // IP packet
* tun_inject_packet(tun, packet, sizeof(packet));
*
* // Routing layer will find this packet in output_queue
* struct ll_queue* out_q = tun_get_output_queue(tun);
* struct ETCP_FRAGMENT* pkt = queue_data_get(out_q);
*
* // Simulate sending packet to TUN (as if from routing)
* struct ll_queue* in_q = tun_get_input_queue(tun);
* // Put packet in input_queue, TUN will process it
*
* In test mode:
* - No real TUN device is created
* - No system calls to configure network interface
* - No socket is registered with uasync
* - Memory pool and queues are still created normally
* - All statistics are tracked normally
* - tun_write() still works (writes to platform, but no real interface)
*/
// Platform-specific internal functions (implemented in tun_linux.c / tun_windows.c)
int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str, int mtu);
void tun_platform_cleanup(struct tun_if* tun);

89
src/utun_instance.c

@ -146,6 +146,95 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config
return instance;
}
// Create instance from existing config structure (config ownership transfers to instance)
struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct utun_config* config) {
if (!config) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "utun_instance_create_from_config: NULL config");
return NULL;
}
struct UTUN_INSTANCE *instance = calloc(1, sizeof(struct UTUN_INSTANCE));
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate UTUN_INSTANCE");
return NULL;
}
// Initialize basic fields
instance->running = 0;
instance->ua = ua;
instance->config = config; // Transfer ownership
// Set node_id from config
instance->node_id = config->global.my_node_id;
// Set my keys
if (sc_init_local_keys(&instance->my_keys, config->global.my_public_key_hex, config->global.my_private_key_hex) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to initialize local keys");
free(instance);
return NULL;
}
instance->ack_pool = memory_pool_init(sizeof(struct ACK_PACKET));
instance->data_pool = memory_pool_init(PACKET_DATA_SIZE);
instance->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE);
// Create routing module
if (routing_create(instance) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to create routing module");
free(instance);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module created");
// Add local subnets from config as static routes
struct CFG_ROUTE_ENTRY* subnet = config->my_subnets;
while (subnet) {
struct ROUTE_ENTRY entry = {0};
entry.network = subnet->ip.addr.v4.s_addr;
entry.prefix_length = subnet->netmask;
entry.next_hop = NULL;
entry.type = ROUTE_TYPE_LOCAL;
entry.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_VALIDATED;
entry.metrics.bandwidth_kbps = UINT32_MAX;
entry.metrics.latency_ms = 0;
entry.metrics.packet_loss_rate = 0;
entry.metrics.hop_count = 0;
if (route_table_insert(instance->rt, &entry)) {
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &entry.network, ip_str, sizeof(ip_str));
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Added local route: %s/%d",
ip_str, entry.prefix_length);
}
subnet = subnet->next;
}
// Initialize TUN device if enabled
if (g_tun_init_enabled) {
instance->tun = tun_init(ua, config);
if (!instance->tun) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to initialize TUN device");
free(instance);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname);
} else {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup");
instance->tun = NULL;
}
// Initialize BGP module
instance->bgp = route_bgp_init(instance);
if (!instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to initialize BGP module");
} else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized");
}
return instance;
}
// Destroy instance and cleanup resources
void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
if (!instance) return;

1
src/utun_instance.h

@ -57,6 +57,7 @@ struct UTUN_INSTANCE {
// Functions
struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char* config_file);
struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct utun_config* config);
void utun_instance_destroy(struct UTUN_INSTANCE* instance);
int utun_instance_init(struct UTUN_INSTANCE *instance);
void utun_instance_run(struct UTUN_INSTANCE *instance);

5
tests/Makefile.am

@ -22,6 +22,7 @@ check_PROGRAMS = \
test_config_debug \
test_route_lib \
test_bgp_route_exchange \
test_routing_mesh \
bench_timeout_heap \
bench_uasync_timeouts
@ -185,6 +186,10 @@ test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_bgp_route_exchange_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_routing_mesh_SOURCES = test_routing_mesh.c
test_routing_mesh_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_routing_mesh_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
bench_timeout_heap_SOURCES = bench_timeout_heap.c
bench_timeout_heap_CFLAGS = -I$(top_srcdir)/lib
bench_timeout_heap_LDADD = $(COMMON_LIBS)

801
tests/test_routing_mesh.c

@ -0,0 +1,801 @@
// test_routing_mesh.c - Test routing between 3 instances in mesh topology
// Each instance has 2 subnets and sends packets to 2 neighbors
// Topology: A <-> B <-> C <-> A (full mesh)
// Verification: packet size, destination IP, payload checksum
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include "test_utils.h"
#include "../lib/platform_compat.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/memory_pool.h"
#include "../src/utun_instance.h"
#include "../src/tun_if.h"
#include "../src/etcp.h"
#include "../src/routing.h"
#include "../src/config_parser.h"
#include "../src/etcp_connections.h"
#include "../src/secure_channel.h"
#define TEST_TIMEOUT_CONNECT_MS 10000 // 10 seconds for connections
#define TEST_TIMEOUT_DELIVERY_MS 10000 // 10 seconds for packet delivery
#define TEST_PACKET_SIZE 100
// Test packet structure with IP header + payload
struct test_packet {
uint8_t ip_header[20]; // Minimal IP header
uint8_t payload[80]; // Test payload
};
// Test state
static struct UASYNC* ua = NULL;
static struct UTUN_INSTANCE* inst_a = NULL;
static struct UTUN_INSTANCE* inst_b = NULL;
static struct UTUN_INSTANCE* inst_c = NULL;
// Config structures (created programmatically)
static struct utun_config* cfg_a = NULL;
static struct utun_config* cfg_b = NULL;
static struct utun_config* cfg_c = NULL;
// Test statistics
static int packets_sent = 0;
static int packets_received = 0;
static int packets_verified = 0;
static int test_phase = 0; // 0=connecting, 1=sending, 2=verifying, 3=done
// Timeout IDs
static void* connect_timeout_id = NULL;
static void* delivery_timeout_id = NULL;
// ============================================================================
// Helper Functions
// ============================================================================
static void bin2hex(const uint8_t* bin, size_t len, char* hex) {
const char* hex_chars = "0123456789ABCDEF";
for (size_t i = 0; i < len; i++) {
hex[i * 2] = hex_chars[bin[i] >> 4];
hex[i * 2 + 1] = hex_chars[bin[i] & 0x0F];
}
hex[len * 2] = '\0';
}
// ============================================================================
// Programmatic Config Creation
// ============================================================================
static void parse_ip_cidr(const char* cidr, struct IP* ip, uint8_t* netmask) {
char buf[64];
strncpy(buf, cidr, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char* slash = strchr(buf, '/');
if (slash) {
*slash = '\0';
*netmask = atoi(slash + 1);
} else {
*netmask = 32;
}
inet_pton(AF_INET, buf, &ip->addr.v4);
ip->family = AF_INET;
}
static void parse_addr_port(const char* addr_port, struct sockaddr_storage* sa) {
char buf[64];
strncpy(buf, addr_port, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char* colon = strrchr(buf, ':');
if (!colon) return;
*colon = '\0';
int port = atoi(colon + 1);
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
inet_pton(AF_INET, buf, &sin->sin_addr);
}
// Create server config
static struct CFG_SERVER* create_server(const char* name, const char* addr_port, int type) {
struct CFG_SERVER* srv = calloc(1, sizeof(struct CFG_SERVER));
if (!srv) return NULL;
strncpy(srv->name, name, MAX_CONN_NAME_LEN - 1);
parse_addr_port(addr_port, &srv->ip);
srv->type = type;
srv->netif_index = 0;
srv->so_mark = 0;
srv->next = NULL;
return srv;
}
// Create client config
static struct CFG_CLIENT* create_client(const char* name, const char* link, const char* peer_key) {
struct CFG_CLIENT* cli = calloc(1, sizeof(struct CFG_CLIENT));
if (!cli) return NULL;
strncpy(cli->name, name, MAX_CONN_NAME_LEN - 1);
cli->keepalive = 1;
if (peer_key) {
strncpy(cli->peer_public_key_hex, peer_key, MAX_KEY_LEN - 1);
}
// Parse link: "server_name:ip:port"
char buf[256];
strncpy(buf, link, sizeof(buf) - 1);
char* first_colon = strchr(buf, ':');
if (first_colon) {
*first_colon = '\0';
const char* server_name = buf;
const char* remote_addr = first_colon + 1;
// Find server in global list (will be set later)
// For now just create the link structure
struct CFG_CLIENT_LINK* clink = calloc(1, sizeof(struct CFG_CLIENT_LINK));
if (clink) {
parse_addr_port(remote_addr, &clink->remote_addr);
// clink->local_srv will be set later when linking
clink->next = NULL;
cli->links = clink;
}
}
cli->next = NULL;
return cli;
}
// Create route entry
static struct CFG_ROUTE_ENTRY* create_route(const char* cidr) {
struct CFG_ROUTE_ENTRY* route = calloc(1, sizeof(struct CFG_ROUTE_ENTRY));
if (!route) return NULL;
parse_ip_cidr(cidr, &route->ip, &route->netmask);
route->next = NULL;
return route;
}
// Link client to server
static void link_client_to_server(struct CFG_CLIENT* client, struct CFG_SERVER* servers) {
if (!client || !client->links) return;
// Find server by name from link
// The server name was stored in the parsing logic above
// We need to find it in the servers list
struct CFG_CLIENT_LINK* clink = client->links;
// Simple approach: assume link name is in client name (to_b -> server b)
const char* target = NULL;
if (strstr(client->name, "_b")) target = "b";
else if (strstr(client->name, "_a")) target = "a";
else if (strstr(client->name, "_c")) target = "c";
if (target) {
struct CFG_SERVER* srv = servers;
while (srv) {
if (strcmp(srv->name, target) == 0) {
clink->local_srv = srv;
return;
}
srv = srv->next;
}
}
}
// Create complete instance config
static struct utun_config* create_instance_config(
uint64_t node_id,
const char* tun_ip,
const char* tun_ifname,
const char** subnets,
int num_subnets,
const char** servers,
int num_servers,
const char** clients,
int num_clients
) {
struct utun_config* cfg = calloc(1, sizeof(struct utun_config));
if (!cfg) return NULL;
// Global config
cfg->global.my_node_id = node_id;
cfg->global.mtu = 1500;
strncpy(cfg->global.tun_ifname, tun_ifname, 15);
parse_ip_cidr(tun_ip, &cfg->global.tun_ip, &(uint8_t){32});
cfg->global.tun_test_mode = 1;
// Servers
struct CFG_SERVER* last_srv = NULL;
for (int i = 0; i < num_servers; i++) {
// Parse "name:addr:port"
char buf[256];
strncpy(buf, servers[i], sizeof(buf) - 1);
char* colon1 = strchr(buf, ':');
if (!colon1) continue;
*colon1 = '\0';
const char* name = buf;
const char* addr = colon1 + 1;
struct CFG_SERVER* srv = create_server(name, addr, CFG_SERVER_TYPE_PUBLIC);
if (last_srv) {
last_srv->next = srv;
} else {
cfg->servers = srv;
}
last_srv = srv;
}
// Subnets (routing)
struct CFG_ROUTE_ENTRY* last_route = NULL;
for (int i = 0; i < num_subnets; i++) {
struct CFG_ROUTE_ENTRY* route = create_route(subnets[i]);
if (last_route) {
last_route->next = route;
} else {
cfg->my_subnets = route;
}
last_route = route;
}
// Clients
struct CFG_CLIENT* last_cli = NULL;
for (int i = 0; i < num_clients; i++) {
// Parse "name:link" (link format: "server:ip:port")
char buf[256];
strncpy(buf, clients[i], sizeof(buf) - 1);
char* colon = strchr(buf, ':');
if (!colon) continue;
*colon = '\0';
const char* name = buf;
const char* link = colon + 1;
struct CFG_CLIENT* cli = create_client(name, link, NULL); // peer_key will be set later
if (last_cli) {
last_cli->next = cli;
} else {
cfg->clients = cli;
}
last_cli = cli;
}
// Link clients to servers
struct CFG_CLIENT* cli = cfg->clients;
while (cli) {
link_client_to_server(cli, cfg->servers);
cli = cli->next;
}
return cfg;
}
// Free config memory
static void free_instance_config(struct utun_config* cfg) {
if (!cfg) return;
// Free servers
struct CFG_SERVER* srv = cfg->servers;
while (srv) {
struct CFG_SERVER* next = srv->next;
free(srv);
srv = next;
}
// Free clients and their links
struct CFG_CLIENT* cli = cfg->clients;
while (cli) {
struct CFG_CLIENT* next = cli->next;
struct CFG_CLIENT_LINK* link = cli->links;
while (link) {
struct CFG_CLIENT_LINK* lnext = link->next;
free(link);
link = lnext;
}
free(cli);
cli = next;
}
// Free routes
struct CFG_ROUTE_ENTRY* route = cfg->my_subnets;
while (route) {
struct CFG_ROUTE_ENTRY* next = route->next;
free(route);
route = next;
}
free(cfg);
}
// ============================================================================
// Setup peer public keys after auto-generation
// ============================================================================
static void setup_peer_keys(void) {
printf("[SETUP] Configuring peer public keys from auto-generated keys...\n");
// Build mapping: server_name -> public_key
struct { const char* name; const char* pubkey; } key_map[3];
key_map[0].name = "a"; key_map[0].pubkey = inst_a->config->global.my_public_key_hex;
key_map[1].name = "b"; key_map[1].pubkey = inst_b->config->global.my_public_key_hex;
key_map[2].name = "c"; key_map[2].pubkey = inst_c->config->global.my_public_key_hex;
printf(" Generated keys:\n");
printf(" Instance A (a): %.20s...\n", key_map[0].pubkey);
printf(" Instance B (b): %.20s...\n", key_map[1].pubkey);
printf(" Instance C (c): %.20s...\n", key_map[2].pubkey);
struct UTUN_INSTANCE* instances[] = {inst_a, inst_b, inst_c};
for (int i = 0; i < 3; i++) {
struct UTUN_INSTANCE* inst = instances[i];
if (!inst || !inst->config) continue;
struct CFG_CLIENT* client = inst->config->clients;
while (client) {
// Determine peer from client name (to_b -> peer b)
const char* peer_name = NULL;
if (strstr(client->name, "_b") || strstr(client->name, "_B")) {
peer_name = "b";
} else if (strstr(client->name, "_a") || strstr(client->name, "_A")) {
peer_name = "a";
} else if (strstr(client->name, "_c") || strstr(client->name, "_C")) {
peer_name = "c";
}
if (peer_name) {
for (int j = 0; j < 3; j++) {
if (strcmp(key_map[j].name, peer_name) == 0) {
strncpy(client->peer_public_key_hex, key_map[j].pubkey, MAX_KEY_LEN - 1);
client->peer_public_key_hex[MAX_KEY_LEN - 1] = '\0';
printf(" %s client '%s' -> peer '%s': OK\n",
(i == 0 ? "A" : (i == 1 ? "B" : "C")),
client->name, peer_name);
break;
}
}
}
client = client->next;
}
}
printf("[SETUP] Peer keys configured.\n");
}
// ============================================================================
// Helper Functions
// ============================================================================
static uint32_t calculate_crc32(const uint8_t* data, size_t len) {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
}
}
return ~crc;
}
static void build_ip_header(uint8_t* header, uint32_t src_ip, uint32_t dst_ip, uint16_t total_len) {
memset(header, 0, 20);
header[0] = 0x45; // Version 4, IHL 5
header[1] = 0;
header[2] = (total_len >> 8) & 0xFF;
header[3] = total_len & 0xFF;
header[4] = 0;
header[5] = 0;
header[6] = 0x40; // Don't fragment
header[7] = 0;
header[8] = 64; // TTL
header[9] = 1; // Protocol: ICMP
header[10] = 0;
header[11] = 0;
// Source IP
header[12] = (src_ip >> 24) & 0xFF;
header[13] = (src_ip >> 16) & 0xFF;
header[14] = (src_ip >> 8) & 0xFF;
header[15] = src_ip & 0xFF;
// Destination IP
header[16] = (dst_ip >> 24) & 0xFF;
header[17] = (dst_ip >> 16) & 0xFF;
header[18] = (dst_ip >> 8) & 0xFF;
header[19] = dst_ip & 0xFF;
}
// ============================================================================
// Connection Monitoring
// ============================================================================
static int count_initialized_links(struct UTUN_INSTANCE* inst) {
int count = 0;
struct ETCP_CONN* conn = inst->connections;
while (conn) {
struct ETCP_LINK* link = conn->links;
while (link) {
if (link->initialized) count++;
link = link->next;
}
conn = conn->next;
}
return count;
}
static int are_all_connections_ready(void) {
int links_a = count_initialized_links(inst_a);
int links_b = count_initialized_links(inst_b);
int links_c = count_initialized_links(inst_c);
printf("[CONNECT] Links ready: A=%d, B=%d, C=%d (need 2 each)\n",
links_a, links_b, links_c);
return (links_a >= 2 && links_b >= 2 && links_c >= 2);
}
// ============================================================================
// Packet Injection and Verification
// ============================================================================
#define MAX_TEST_PACKETS 6
struct packet_record {
uint64_t sender_node_id;
uint64_t receiver_node_id;
uint32_t src_ip;
uint32_t dst_ip;
uint16_t payload_len;
uint32_t payload_crc;
int sent;
int received;
int verified;
};
static struct packet_record test_packets[MAX_TEST_PACKETS];
static int packet_count = 0;
static void send_test_packet(struct UTUN_INSTANCE* sender, struct UTUN_INSTANCE* receiver,
uint32_t src_ip, uint32_t dst_ip) {
struct test_packet pkt;
uint16_t total_len = 20 + TEST_PACKET_SIZE;
build_ip_header(pkt.ip_header, src_ip, dst_ip, total_len);
// Fill payload with pattern based on sender/receiver
uint64_t pattern = sender->node_id ^ receiver->node_id;
for (int i = 0; i < TEST_PACKET_SIZE; i++) {
pkt.payload[i] = (uint8_t)((pattern >> (i % 8)) ^ i);
}
uint32_t payload_crc = calculate_crc32(pkt.payload, TEST_PACKET_SIZE);
// Inject into sender's TUN output queue (simulates packet from TUN)
if (tun_inject_packet(sender->tun, pkt.ip_header, total_len) == 0) {
if (packet_count < MAX_TEST_PACKETS) {
test_packets[packet_count].sender_node_id = sender->node_id;
test_packets[packet_count].receiver_node_id = receiver->node_id;
test_packets[packet_count].src_ip = src_ip;
test_packets[packet_count].dst_ip = dst_ip;
test_packets[packet_count].payload_len = TEST_PACKET_SIZE;
test_packets[packet_count].payload_crc = payload_crc;
test_packets[packet_count].sent = 1;
test_packets[packet_count].received = 0;
test_packets[packet_count].verified = 0;
packet_count++;
}
packets_sent++;
printf("[SEND] %llX -> %llX (dst=%d.%d.%d.%d)\n",
(unsigned long long)sender->node_id,
(unsigned long long)receiver->node_id,
(dst_ip >> 24) & 0xFF, (dst_ip >> 16) & 0xFF,
(dst_ip >> 8) & 0xFF, dst_ip & 0xFF);
}
}
static int verify_packet(struct UTUN_INSTANCE* receiver, struct ETCP_FRAGMENT* pkt) {
if (!pkt || !pkt->ll.dgram || pkt->ll.len < 20) {
return -1;
}
// Extract destination IP
uint32_t dst_ip = ((uint32_t)pkt->ll.dgram[16] << 24) |
((uint32_t)pkt->ll.dgram[17] << 16) |
((uint32_t)pkt->ll.dgram[18] << 8) |
(uint32_t)pkt->ll.dgram[19];
uint16_t ip_total_len = ((uint16_t)pkt->ll.dgram[2] << 8) | pkt->ll.dgram[3];
uint16_t payload_len = ip_total_len - 20;
if (payload_len > TEST_PACKET_SIZE) {
return -1;
}
// Verify payload CRC
uint32_t crc = calculate_crc32(pkt->ll.dgram + 20, payload_len);
for (int i = 0; i < packet_count; i++) {
if (!test_packets[i].sent || test_packets[i].received) continue;
if (payload_len == test_packets[i].payload_len && crc == test_packets[i].payload_crc) {
test_packets[i].received = 1;
test_packets[i].verified = 1;
packets_received++;
packets_verified++;
printf("[VERIFY] Packet verified: %llX -> %llX (size=%d)\n",
(unsigned long long)test_packets[i].sender_node_id,
(unsigned long long)receiver->node_id,
payload_len);
return 0;
}
}
return -1;
}
static void check_received_packets(void) {
struct UTUN_INSTANCE* instances[] = {inst_a, inst_b, inst_c};
for (int i = 0; i < 3; i++) {
struct UTUN_INSTANCE* inst = instances[i];
if (!inst || !inst->tun) continue;
struct ll_queue* out_q = tun_get_output_queue(inst->tun);
if (!out_q) continue;
struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_data_get(out_q);
while (pkt) {
verify_packet(inst, pkt);
if (pkt->ll.dgram) free(pkt->ll.dgram);
if (inst->tun && inst->tun->pool) {
memory_pool_free(inst->tun->pool, pkt);
}
pkt = (struct ETCP_FRAGMENT*)queue_data_get(out_q);
}
}
}
// ============================================================================
// Test State Machine
// ============================================================================
static void start_packet_transmission(void);
static void delivery_check_callback(void* arg);
static void connection_check_callback(void* arg) {
(void)arg;
if (test_phase != 0) return;
if (are_all_connections_ready()) {
printf("\n[PHASE] All connections established! Starting packet transmission...\n\n");
test_phase = 1;
if (connect_timeout_id) {
uasync_cancel_timeout(ua, connect_timeout_id);
connect_timeout_id = NULL;
}
start_packet_transmission();
delivery_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_DELIVERY_MS, NULL, delivery_check_callback);
} else {
connect_timeout_id = uasync_set_timeout(ua, 100, NULL, connection_check_callback);
}
}
static void start_packet_transmission(void) {
printf("[PHASE] Sending test packets (mesh topology)...\n");
// A sends to B and C
send_test_packet(inst_a, inst_b,
(192U << 24) | (168 << 16) | (10 << 8) | 1,
(192U << 24) | (168 << 16) | (20 << 8) | 1);
send_test_packet(inst_a, inst_c,
(192U << 24) | (168 << 16) | (10 << 8) | 1,
(192U << 24) | (168 << 16) | (30 << 8) | 1);
// B sends to A and C
send_test_packet(inst_b, inst_a,
(192U << 24) | (168 << 16) | (20 << 8) | 1,
(192U << 24) | (168 << 16) | (10 << 8) | 1);
send_test_packet(inst_b, inst_c,
(192U << 24) | (168 << 16) | (20 << 8) | 1,
(192U << 24) | (168 << 16) | (30 << 8) | 1);
// C sends to A and B
send_test_packet(inst_c, inst_a,
(192U << 24) | (168 << 16) | (30 << 8) | 1,
(192U << 24) | (168 << 16) | (10 << 8) | 1);
send_test_packet(inst_c, inst_b,
(192U << 24) | (168 << 16) | (30 << 8) | 1,
(192U << 24) | (168 << 16) | (20 << 8) | 1);
printf("\n[PHASE] Packets sent: %d, starting verification...\n", packets_sent);
test_phase = 2;
}
static void delivery_check_callback(void* arg) {
(void)arg;
check_received_packets();
if (packets_verified >= packets_sent && packets_sent > 0) {
printf("\n[SUCCESS] All packets verified! (%d/%d)\n", packets_verified, packets_sent);
test_phase = 3;
return;
}
delivery_timeout_id = uasync_set_timeout(ua, 50, NULL, delivery_check_callback);
}
static void connect_timeout_handler(void* arg) {
(void)arg;
if (test_phase == 0) {
printf("\n[TIMEOUT] Connection establishment failed\n");
test_phase = -1;
}
}
// ============================================================================
// Main Test
// ============================================================================
int main(void) {
printf("========================================\n");
printf(" Routing Mesh Test (3 instances)\n");
printf("========================================\n\n");
// Initialize debug
debug_config_init();
debug_set_level(DEBUG_LEVEL_WARN);
// Create shared uasync
ua = uasync_create();
if (!ua) {
fprintf(stderr, "Failed to create uasync\n");
return 1;
}
printf("[INIT] Creating configs programmatically...\n");
// Instance A config
const char* subnets_a[] = {"192.168.10.0/24", "192.168.11.0/24"};
const char* servers_a[] = {"b:127.0.0.1:9101", "c:127.0.0.1:9102"};
const char* clients_a[] = {"to_b:b:127.0.0.1:9201", "to_c:c:127.0.0.1:9301"};
cfg_a = create_instance_config(0xAAAAAAAAAAAAAAAA, "10.99.1.1/24", "tun_a",
subnets_a, 2, servers_a, 2, clients_a, 2);
// Instance B config
const char* subnets_b[] = {"192.168.20.0/24", "192.168.21.0/24"};
const char* servers_b[] = {"a:127.0.0.1:9201", "c:127.0.0.1:9202"};
const char* clients_b[] = {"to_a:a:127.0.0.1:9101", "to_c:c:127.0.0.1:9301"};
cfg_b = create_instance_config(0xBBBBBBBBBBBBBBBB, "10.99.2.1/24", "tun_b",
subnets_b, 2, servers_b, 2, clients_b, 2);
// Instance C config
const char* subnets_c[] = {"192.168.30.0/24", "192.168.31.0/24"};
const char* servers_c[] = {"a:127.0.0.1:9302", "b:127.0.0.1:9303"};
const char* clients_c[] = {"to_a:a:127.0.0.1:9102", "to_b:b:127.0.0.1:9202"};
cfg_c = create_instance_config(0xCCCCCCCCCCCCCCCC, "10.99.3.1/24", "tun_c",
subnets_c, 2, servers_c, 2, clients_c, 2);
if (!cfg_a || !cfg_b || !cfg_c) {
fprintf(stderr, "Failed to create configs\n");
return 1;
}
printf("[INIT] Generating keys for configs...\n");
// Generate keys for each config
struct SC_MYKEYS keys_a, keys_b, keys_c;
if (sc_generate_keypair(&keys_a) != SC_OK ||
sc_generate_keypair(&keys_b) != SC_OK ||
sc_generate_keypair(&keys_c) != SC_OK) {
fprintf(stderr, "Failed to generate key pairs\n");
return 1;
}
// Copy keys to configs (convert binary to hex)
bin2hex(keys_a.private_key, SC_PRIVKEY_SIZE, cfg_a->global.my_private_key_hex);
bin2hex(keys_a.public_key, SC_PUBKEY_SIZE, cfg_a->global.my_public_key_hex);
bin2hex(keys_b.private_key, SC_PRIVKEY_SIZE, cfg_b->global.my_private_key_hex);
bin2hex(keys_b.public_key, SC_PUBKEY_SIZE, cfg_b->global.my_public_key_hex);
bin2hex(keys_c.private_key, SC_PRIVKEY_SIZE, cfg_c->global.my_private_key_hex);
bin2hex(keys_c.public_key, SC_PUBKEY_SIZE, cfg_c->global.my_public_key_hex);
printf("[INIT] Creating instances...\n");
// Create instances from configs
inst_a = utun_instance_create_from_config(ua, cfg_a);
inst_b = utun_instance_create_from_config(ua, cfg_b);
inst_c = utun_instance_create_from_config(ua, cfg_c);
if (!inst_a || !inst_b || !inst_c) {
fprintf(stderr, "Failed to create instances\n");
return 1;
}
printf(" Instance A: node_id=%016llX\n", (unsigned long long)inst_a->node_id);
printf(" Instance B: node_id=%016llX\n", (unsigned long long)inst_b->node_id);
printf(" Instance C: node_id=%016llX\n", (unsigned long long)inst_c->node_id);
// Setup peer keys after auto-generation
setup_peer_keys();
// Initialize connections for each instance
printf("\n[INIT] Initializing connections...\n");
if (init_connections(inst_a) < 0) {
fprintf(stderr, "Failed to initialize A connections\n");
return 1;
}
if (init_connections(inst_b) < 0) {
fprintf(stderr, "Failed to initialize B connections\n");
return 1;
}
if (init_connections(inst_c) < 0) {
fprintf(stderr, "Failed to initialize C connections\n");
return 1;
}
printf(" A connections: OK\n");
printf(" B connections: OK\n");
printf(" C connections: OK\n");
// Set up connection monitoring
printf("\n[PHASE] Waiting for connections to establish...\n");
connect_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_CONNECT_MS, NULL, connect_timeout_handler);
connection_check_callback(NULL);
// Main event loop
printf("\n[LOOP] Running event loop...\n");
int elapsed = 0;
while (test_phase >= 0 && test_phase < 3 && elapsed < (TEST_TIMEOUT_CONNECT_MS + TEST_TIMEOUT_DELIVERY_MS + 2000)) {
uasync_poll(ua, 10);
elapsed += 10;
}
// Final verification
check_received_packets();
// Cleanup
printf("\n[CLEANUP] Cleaning up...\n");
if (connect_timeout_id) uasync_cancel_timeout(ua, connect_timeout_id);
if (delivery_timeout_id) uasync_cancel_timeout(ua, delivery_timeout_id);
if (inst_a) utun_instance_destroy(inst_a);
if (inst_b) utun_instance_destroy(inst_b);
if (inst_c) utun_instance_destroy(inst_c);
// Note: cfg_a, cfg_b, cfg_c are freed by utun_instance_destroy
uasync_destroy(ua, 0);
// Results
printf("\n========================================\n");
printf(" RESULTS\n");
printf("========================================\n");
printf("Packets sent: %d/%d\n", packets_sent, MAX_TEST_PACKETS);
printf("Packets received: %d\n", packets_received);
printf("Packets verified: %d\n", packets_verified);
if (test_phase == 3 && packets_verified == MAX_TEST_PACKETS) {
printf("\n✓ TEST PASSED\n");
return 0;
} else {
printf("\n✗ TEST FAILED (phase=%d)\n", test_phase);
return 1;
}
}
Loading…
Cancel
Save