Files
utun2/src/tun_if.c
T
evgeny 72e2c89e3b Fix sockless TCP keepalive and require complete UTUN initialization
Receive keepalive through the owning ETCP link, preserve RX deadlines, and report routing metrics without TUN. Use independent netlink requests and track installed routes for rollback. Reject incomplete resource initialization and release partially constructed cores while preserving caller config ownership.

Validated startup failure and rollback, padded keepalive, standby, STCP traffic, transport lifecycle, dynamic sockets, and daemon startup. Real TUN/netlink checks passed in an isolated Linux network namespace; modified modules passed FreeBSD compilation checks.
2026-10-12 14:24:32 +02:00

461 lines
15 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)
// При провале queue_data_put сам освобождает entry+dgram, повторный free дал бы double-free.
if (queue_data_put(tun->output_queue, pkt) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue");
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) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN initialization without event loop/config"); return NULL; }
int test_mode = config->global.tun_test_mode;
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN allocation failed"); 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 {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Unsupported TUN IP family=%d", config->global.tun_ip.family);
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", tun);
if (!tun->socket_id) goto fail;
} else { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s has no poll descriptor", tun->ifname); 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) {
goto fail;
}
}
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN %s initialized (%s mode)", tun->ifname,
test_mode ? "TEST" : "REAL");
return tun;
fail:
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s initialization failed; releasing device and queues", tun->ifname);
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);
else if (tun->fd >= 0) close(tun->fd);
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) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Invalid NAT TUN initialization arguments"); return NULL; }
(void)mtu;
int tun_mtu = TUN_MTU_DEFAULT;
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN allocation failed"); 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", tun);
if (!tun->socket_id) goto fail2;
} else { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN %s has no poll descriptor", tun->ifname); goto fail2; }
}
#endif
DEBUG_INFO(DEBUG_CATEGORY_TUN, "NAT TUN %s initialized (%s mode)", tun->ifname,
test_mode ? "TEST" : "REAL");
return tun;
fail2:
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "NAT TUN %s initialization failed; releasing device and queues", tun->ifname);
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);
else if (tun->fd >= 0) close(tun->fd);
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, ret - 1));
}
return ret;
}