- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
458 lines
14 KiB
C
458 lines
14 KiB
C
// tun_if.c - Cross-platform TUN interface
|
|
#include "config_parser.h"
|
|
#include "tun_if.h"
|
|
#include "etcp.h"
|
|
#include "packet_dump.h"
|
|
#include "../lib/debug_config.h"
|
|
#include "../lib/u_async.h"
|
|
#include "../lib/ll_queue.h"
|
|
#include "../lib/memory_pool.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#ifndef _WIN32
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#include "../lib/mem.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
// ===================================================================
|
|
// Linux-only: TUN read callback (uasync) — определена ПЕРЕД tun_init
|
|
// ===================================================================
|
|
#ifndef _WIN32
|
|
// Internal read callback - called by uasync when data available on TUN
|
|
static void tun_read_callback(int fd, void* user_arg)
|
|
{
|
|
(void)fd;
|
|
struct tun_if* tun = (struct tun_if*)user_arg;
|
|
uint8_t buffer[TUN_MAX_PACKET_SIZE];
|
|
|
|
// Read from TUN device
|
|
ssize_t nread = tun_platform_read(tun, buffer, sizeof(buffer));
|
|
if (nread < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to read from TUN device %s", tun->ifname);
|
|
tun->read_errors++;
|
|
return;
|
|
}
|
|
if (nread == 0) return;
|
|
|
|
// Allocate packet data (+1 for prefix byte)
|
|
uint8_t* packet_data = u_malloc(nread + 1);
|
|
if (!packet_data) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to allocate packet data (size=%zd)", nread + 1);
|
|
tun->read_errors++;
|
|
return;
|
|
}
|
|
packet_data[0] = 0; // Prefix byte
|
|
memcpy(packet_data + 1, buffer, nread);
|
|
|
|
struct ll_entry* pkt = queue_entry_new_from_pool(tun->pool);
|
|
if (!pkt) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to allocate struct ll_entry from pool");
|
|
u_free(packet_data);
|
|
tun->read_errors++;
|
|
return;
|
|
}
|
|
|
|
// Initialize entry
|
|
pkt->dgram = packet_data;
|
|
pkt->len = nread + 1;
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Read %zd bytes from TUN %s", nread + 1, tun->ifname);
|
|
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[TUN->] %s", dump_ip_packet_to_buffer(packet_data + 1, nread));
|
|
}
|
|
|
|
// Update statistics
|
|
tun->bytes_read += nread + 1;
|
|
tun->packets_read++;
|
|
|
|
// Add to output queue (TUN → routing)
|
|
if (queue_data_put(tun->output_queue, pkt) != 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue");
|
|
u_free(packet_data);
|
|
queue_entry_free(pkt);
|
|
tun->read_errors++;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// ===================================================================
|
|
// Callback: routing → TUN (вызывается из main thread)
|
|
// ===================================================================
|
|
static void tun_input_queue_callback(struct ll_queue* q, void* arg)
|
|
{
|
|
(void)q;
|
|
struct tun_if* tun = (struct tun_if*)arg;
|
|
struct ll_entry* pkt = queue_data_get(tun->input_queue);
|
|
if (!pkt) return;
|
|
|
|
if (pkt->dgram && pkt->len > 1) {
|
|
if (pkt->len > TUN_MAX_PACKET_SIZE + 1) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Packet too large: %zu bytes", pkt->len);
|
|
} else {
|
|
// Skip prefix byte, write from second byte
|
|
ssize_t n = tun_platform_write(tun, pkt->dgram + 1, pkt->len - 1);
|
|
if (n < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_platform_write failed");
|
|
tun->write_errors++;
|
|
} else {
|
|
tun->bytes_written += n;
|
|
tun->packets_written++;
|
|
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[->TUN] %s", dump_ip_packet_to_buffer(pkt->dgram + 1, pkt->len - 1));
|
|
}
|
|
}
|
|
}
|
|
queue_dgram_free(pkt);
|
|
}
|
|
queue_entry_free(pkt);
|
|
|
|
queue_resume_callback(tun->input_queue);
|
|
}
|
|
|
|
// ===================================================================
|
|
// Init
|
|
// ===================================================================
|
|
struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config)
|
|
{
|
|
if (!ua || !config) return NULL;
|
|
|
|
int test_mode = config->global.tun_test_mode;
|
|
|
|
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
|
|
if (!tun) return NULL;
|
|
|
|
tun->ua = ua;
|
|
tun->test_mode = test_mode;
|
|
tun->fd = -1;
|
|
tun->test_fd = -1;
|
|
|
|
const char* name = config->global.tun_ifname;
|
|
if (name && name[0]) strncpy(tun->ifname, name, sizeof(tun->ifname)-1);
|
|
|
|
char ip_str[64];
|
|
if (config->global.tun_ip.family == AF_INET) {
|
|
snprintf(ip_str, sizeof(ip_str), "%s/32", ip_to_str(&config->global.tun_ip.addr.v4, AF_INET).str);
|
|
} else {
|
|
u_free(tun);
|
|
return NULL;
|
|
}
|
|
|
|
int mtu = TUN_MTU_DEFAULT;
|
|
|
|
if (!test_mode) {
|
|
if (tun_platform_init(tun, tun->ifname, ip_str, mtu) != 0) {
|
|
u_free(tun);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
if (tun->ifname[0] == '\0') strncpy(tun->ifname, "tun_test", sizeof(tun->ifname)-1);
|
|
#ifndef _WIN32
|
|
int fds[2];
|
|
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s test_mode socketpair failed: %s", tun->ifname, strerror(errno));
|
|
u_free(tun);
|
|
return NULL;
|
|
}
|
|
tun->fd = fds[0];
|
|
tun->test_fd = fds[1];
|
|
#else
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN test_mode not supported on Windows");
|
|
u_free(tun);
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
tun->pool = memory_pool_init(sizeof(struct ll_entry), "tun_pool");
|
|
if (!tun->pool) goto fail;
|
|
|
|
tun->output_queue = queue_new(ua, 0, 0, 0, "TUN output");
|
|
tun->input_queue = queue_new(ua, 0, 0, 0, "TUN input");
|
|
if (!tun->output_queue || !tun->input_queue) goto fail;
|
|
|
|
queue_set_callback(tun->input_queue, tun_input_queue_callback, tun);
|
|
|
|
#ifndef _WIN32
|
|
{
|
|
int poll_fd = test_mode ? tun->fd : tun_platform_get_poll_fd(tun);
|
|
if (poll_fd >= 0) {
|
|
tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun);
|
|
if (!tun->socket_id) goto fail;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef _WIN32
|
|
if (!test_mode) {
|
|
int poll_fd = tun_platform_get_poll_fd(tun);
|
|
if (poll_fd < 0) {
|
|
tun->running = 1;
|
|
tun->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (!tun->stop_event) goto fail;
|
|
tun->read_thread = CreateThread(NULL, 0, tun_read_thread_proc, tun, 0, NULL);
|
|
if (!tun->read_thread) {
|
|
CloseHandle(tun->stop_event);
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN %s initialized (%s mode)", tun->ifname,
|
|
test_mode ? "TEST" : "REAL");
|
|
return tun;
|
|
|
|
fail:
|
|
if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; }
|
|
#ifdef _WIN32
|
|
if (tun->stop_event) CloseHandle(tun->stop_event);
|
|
if (tun->read_thread) {
|
|
tun->running = 0;
|
|
WaitForSingleObject(tun->read_thread, INFINITE);
|
|
CloseHandle(tun->read_thread);
|
|
}
|
|
#endif
|
|
if (tun->output_queue) queue_free(tun->output_queue);
|
|
if (tun->input_queue) queue_free(tun->input_queue);
|
|
if (tun->pool) memory_pool_destroy(tun->pool);
|
|
if (!test_mode) tun_platform_cleanup(tun);
|
|
u_free(tun);
|
|
return NULL;
|
|
}
|
|
|
|
// ===================================================================
|
|
// Init NAT TUN (separate TUN interface for NAT traffic)
|
|
// ===================================================================
|
|
struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* ip_addr_str, int mtu, int test_mode)
|
|
{
|
|
if (!ua || !ifname || !ip_addr_str) return NULL;
|
|
(void)mtu;
|
|
int tun_mtu = TUN_MTU_DEFAULT;
|
|
|
|
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
|
|
if (!tun) return NULL;
|
|
|
|
tun->ua = ua;
|
|
tun->test_mode = test_mode;
|
|
tun->fd = -1;
|
|
tun->test_fd = -1;
|
|
strncpy(tun->ifname, ifname, sizeof(tun->ifname) - 1);
|
|
|
|
char ip_str[64];
|
|
snprintf(ip_str, sizeof(ip_str), "%s/32", ip_addr_str);
|
|
|
|
if (!test_mode) {
|
|
if (tun_platform_init(tun, tun->ifname, ip_str, tun_mtu) != 0) {
|
|
u_free(tun);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
#ifndef _WIN32
|
|
int fds[2];
|
|
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) < 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN %s test_mode socketpair failed: %s", tun->ifname, strerror(errno));
|
|
u_free(tun);
|
|
return NULL;
|
|
}
|
|
tun->fd = fds[0];
|
|
tun->test_fd = fds[1];
|
|
#else
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN test_mode not supported on Windows");
|
|
u_free(tun);
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
tun->pool = memory_pool_init(sizeof(struct ll_entry), "tun_pool");
|
|
if (!tun->pool) goto fail2;
|
|
|
|
tun->output_queue = queue_new(ua, 0, 0, 0, "NAT TUN output");
|
|
tun->input_queue = queue_new(ua, 0, 0, 0, "NAT TUN input");
|
|
if (!tun->output_queue || !tun->input_queue) goto fail2;
|
|
|
|
queue_set_callback(tun->input_queue, tun_input_queue_callback, tun);
|
|
|
|
#ifndef _WIN32
|
|
{
|
|
int poll_fd = test_mode ? tun->fd : tun_platform_get_poll_fd(tun);
|
|
if (poll_fd >= 0) {
|
|
tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun);
|
|
if (!tun->socket_id) goto fail2;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_TUN, "NAT TUN %s initialized (%s mode)", tun->ifname,
|
|
test_mode ? "TEST" : "REAL");
|
|
return tun;
|
|
|
|
fail2:
|
|
if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; }
|
|
if (tun->output_queue) queue_free(tun->output_queue);
|
|
if (tun->input_queue) queue_free(tun->input_queue);
|
|
if (tun->pool) memory_pool_destroy(tun->pool);
|
|
if (!test_mode) tun_platform_cleanup(tun);
|
|
u_free(tun);
|
|
return NULL;
|
|
}
|
|
|
|
// ===================================================================
|
|
// Остальные функции (без изменений)
|
|
// ===================================================================
|
|
void tun_close(struct tun_if* tun)
|
|
{
|
|
if (!tun) return;
|
|
|
|
#ifdef _WIN32
|
|
if (tun->read_thread) {
|
|
tun->running = 0;
|
|
SetEvent(tun->stop_event);
|
|
WaitForSingleObject(tun->read_thread, INFINITE);
|
|
CloseHandle(tun->read_thread);
|
|
CloseHandle(tun->stop_event);
|
|
}
|
|
#endif
|
|
|
|
if (tun->socket_id && tun->ua) {
|
|
uasync_remove_socket(tun->ua, tun->socket_id);
|
|
}
|
|
|
|
if (tun->test_fd >= 0) { close(tun->test_fd); tun->test_fd = -1; }
|
|
|
|
struct ll_entry* pkt;
|
|
while ((pkt = queue_data_get(tun->input_queue)) != NULL) {
|
|
queue_dgram_free(pkt);
|
|
queue_entry_free(pkt);
|
|
}
|
|
queue_free(tun->input_queue);
|
|
|
|
while ((pkt = queue_data_get(tun->output_queue)) != NULL) {
|
|
queue_dgram_free(pkt);
|
|
queue_entry_free(pkt);
|
|
}
|
|
queue_free(tun->output_queue);
|
|
|
|
memory_pool_destroy(tun->pool);
|
|
if (!tun->test_mode) tun_platform_cleanup(tun);
|
|
u_free(tun);
|
|
}
|
|
|
|
ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len)
|
|
{
|
|
if (!tun || !buf || len <= 1) return -1;
|
|
|
|
if (tun->test_mode) {
|
|
return tun_inject_packet(tun, buf + 1, len - 1) == 0 ? (ssize_t)(len - 1) : -1;
|
|
}
|
|
|
|
ssize_t n = tun_platform_write(tun, buf + 1, len - 1);
|
|
if (n > 0) {
|
|
tun->bytes_written += n;
|
|
tun->packets_written++;
|
|
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[->TUN] %s", dump_ip_packet_to_buffer(buf + 1, len - 1));
|
|
}
|
|
} else {
|
|
tun->write_errors++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
struct ll_queue* tun_get_input_queue(struct tun_if* tun) { return tun ? tun->input_queue : NULL; }
|
|
struct ll_queue* tun_get_output_queue(struct tun_if* tun) { return tun ? tun->output_queue : NULL; }
|
|
int tun_is_test_mode(struct tun_if* tun) { return tun ? tun->test_mode : -1; }
|
|
int tun_get_test_fd(struct tun_if* tun) { return tun ? tun->test_fd : -1; }
|
|
|
|
// ===================================================================
|
|
// Callback для обработки пакетов из TUN в main thread
|
|
// Вызывается через uasync_post из read thread (Windows)
|
|
// ===================================================================
|
|
void tun_packet_handler(void* arg) {
|
|
struct tun_packet_data* pd = (struct tun_packet_data*)arg;
|
|
if (!pd || !pd->tun || !pd->entry) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "ARG ZERO");
|
|
if (pd) u_free(pd);
|
|
return;
|
|
}
|
|
|
|
struct tun_if* tun = pd->tun;
|
|
struct ll_entry* entry = pd->entry;
|
|
|
|
// Освободить временную структуру
|
|
u_free(pd);
|
|
|
|
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[TUNh->] %s", dump_ip_packet_to_buffer(entry->dgram, entry->len));
|
|
}
|
|
|
|
// Положить в очередь (уже в main thread)
|
|
int ok = queue_data_put(tun->output_queue, entry);
|
|
if (ok != 0) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Put error");
|
|
queue_entry_free(entry);
|
|
tun->read_errors++;
|
|
}
|
|
// Статистика уже обновлена в read thread
|
|
}
|
|
|
|
int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len)
|
|
{
|
|
if (!tun || !buf || len == 0 || len > TUN_MAX_PACKET_SIZE) return -1;
|
|
|
|
uint8_t* data = u_malloc(len + 1);
|
|
if (!data) { tun->read_errors++; return -1; }
|
|
data[0] = 0;
|
|
memcpy(data + 1, buf, len);
|
|
|
|
struct ll_entry* pkt = queue_entry_new_from_pool(tun->pool);
|
|
if (!pkt) { u_free(data); tun->read_errors++; return -1; }
|
|
|
|
pkt->dgram = data;
|
|
pkt->len = len + 1;
|
|
|
|
int ret = queue_data_put(tun->output_queue, pkt);
|
|
|
|
if (ret != 0) {
|
|
u_free(data);
|
|
memory_pool_free(tun->pool, pkt);
|
|
tun->read_errors++;
|
|
return -1;
|
|
}
|
|
|
|
tun->bytes_read += len + 1;
|
|
tun->packets_read++;
|
|
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 ll_entry* pkt = queue_data_get(tun->input_queue);
|
|
if (!pkt) return 0;
|
|
|
|
if (pkt->len > len) {
|
|
queue_data_put(tun->input_queue, pkt);
|
|
return -1;
|
|
}
|
|
|
|
buf[0] = 0;
|
|
memcpy(buf + 1, pkt->dgram, pkt->len);
|
|
ssize_t ret = pkt->len + 1;
|
|
|
|
queue_dgram_free(pkt);
|
|
queue_entry_free(pkt);
|
|
|
|
tun->bytes_written += ret;
|
|
tun->packets_written++;
|
|
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[TUN->] %s", dump_ip_packet_to_buffer(buf + 1, pkt->len));
|
|
}
|
|
return ret;
|
|
}
|