This commit is contained in:
evgeny committed 2026-10-21 21:22:49 +02:00
commit ef673eb3b7
15 files changed
+885 -725

No files matched your search

+14 -2
View File
@@ -127,6 +127,7 @@ struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void
ctx->ua = ua;
ctx->output = output;
ctx->output_arg = output_arg;
ctx->local_mss = TCP_MSS;
ctx->pcb_pool = memory_pool_init(sizeof(struct tcp_pcb), "pcb_pool");
ctx->pcb_listen_pool = memory_pool_init(sizeof(struct tcp_pcb_listen), "pcb_listen_pool");
ctx->seg_pool = memory_pool_init(sizeof(struct tcp_seg), "seg_pool");
@@ -143,6 +144,17 @@ struct lwip_tcp_ctx *lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void
}
/* Локальный предел не заменяется MSS из SYN пира. Настройка выполняется до открытия соединений. */
err_t lwip_tcp_set_mtu(struct lwip_tcp_ctx *ctx, uint16_t mtu) {
if (!ctx || mtu < 68 || mtu > TCP_MSS + 40 || ctx->active_pcbs || ctx->listen_pcbs || ctx->bound_pcbs || ctx->tw_pcbs) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "lwIP MTU setup rejected: ctx=%p mtu=%u", (void*)ctx, mtu);
return LERR_ARG;
}
ctx->local_mss = (uint16_t)(mtu - 40);
DEBUG_INFO(DEBUG_CATEGORY_PROXY, "lwIP IPv4 MTU=%u local MSS=%u", mtu, ctx->local_mss);
return LERR_OK;
}
// ============================================
// 2. lwip_tcp_set_timer
// ============================================
@@ -644,7 +656,7 @@ err_t tcp_connect(struct tcp_pcb *pcb, uint32_t ipaddr, uint16_t port, tcp_conne
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND16(TCP_WND);
pcb->rcv_ann_right_edge = pcb->rcv_nxt;
pcb->snd_wnd = TCP_WND;
pcb->mss = INITIAL_MSS;
pcb->mss = LWIP_MIN(INITIAL_MSS, pcb->ctx->local_mss);
pcb->cwnd = 1;
pcb->connected = connected;
@@ -1158,7 +1170,7 @@ struct tcp_pcb *tcp_alloc(struct lwip_tcp_ctx *ctx, uint8_t prio)
pcb->snd_buf = TCP_SND_BUF;
pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND16(TCP_WND);
pcb->ttl = 64;
pcb->mss = INITIAL_MSS;
pcb->mss = LWIP_MIN(INITIAL_MSS, pcb->ctx->local_mss);
pcb->rto = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
pcb->sv = (int16_t)(TCP_RTO_MIN_MS / TCP_SLOW_INTERVAL);
pcb->rtime = -1;
+6 -2
View File
@@ -32,7 +32,7 @@
/* Адаптированный lwIP TCP для TUN-прокси: TCP-состояния, retransmit, окна и callbacks потоков.
* init создаёт контекст с uasync-таймером; input получает TCP-сегмент после IPv4-заголовка,
* output callback передаёт сегмент внешнему IP/TUN-слою. destroy освобождает PCB и таймер.
* output callback передаёт полный IPv4-пакет внешнему TUN-слою. destroy освобождает PCB и таймер.
* API и callbacks требуют последовательного выполнения; input использует общий рабочий контекст. */
#ifndef LWIP_TCP_H
#define LWIP_TCP_H
@@ -120,7 +120,7 @@ typedef err_t (*tcp_poll_fn) (void *arg, struct tcp_pcb *pcb);
typedef void (*tcp_err_fn) (void *arg, err_t err);
typedef err_t (*tcp_connected_fn)(void *arg, struct tcp_pcb *pcb, err_t err);
// Output callback получает TCP-сегмент и IP адреса отдельно; IPv4-заголовок строит вызывающий слой.
// Output callback получает полный IPv4-пакет; src_ip/dst_ip также передаются отдельно.
typedef err_t (*tcp_output_fn)(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
#define TCP_TRACE_SIZE 384
@@ -250,6 +250,7 @@ struct lwip_tcp_ctx {
uint8_t active_pcbs_changed; // коллбэк изменил список active_pcbs — перезапустить обход
uint16_t iss_seed;
uint16_t ip_id;
uint16_t local_mss; // Максимальный TCP payload локального IPv4 MTU; по умолчанию 1460.
uint16_t port_seed;
char trace_id; // 'A' or 'B' for trace dump identification
@@ -272,6 +273,9 @@ struct lwip_tcp_ctx {
// Lifecycle
struct lwip_tcp_ctx* lwip_tcp_init(struct UASYNC *ua, tcp_output_fn output, void *output_arg);
void lwip_tcp_destroy(struct lwip_tcp_ctx *ctx);
// До создания PCB: задать IPv4 MTU 68..1500 для начального/объявляемого/согласованного MSS.
// ctx принадлежит вызывающему; LERR_OK при успехе, LERR_ARG при неверных аргументах/уже созданных списках PCB.
err_t lwip_tcp_set_mtu(struct lwip_tcp_ctx *ctx, uint16_t mtu);
void lwip_tcp_set_timer(struct lwip_tcp_ctx *ctx,
uint16_t interval_ms, uint16_t rto_min_ms, uint16_t rto_max_ms);
+1 -1
View File
@@ -1077,7 +1077,7 @@ static void tcp_parseopt(struct tcp_pcb *pcb)
}
mss = (uint16_t)(tcp_get_next_optbyte() << 8);
mss |= tcp_get_next_optbyte();
pcb->mss = ((mss > TCP_MSS) || (mss == 0)) ? TCP_MSS : mss;
pcb->mss = (mss == 0) ? pcb->ctx->local_mss : LWIP_MIN(mss, pcb->ctx->local_mss);
break;
default:
data = tcp_get_next_optbyte();
+1 -1
View File
@@ -713,7 +713,7 @@ tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
opts = (uint32_t *)(void *)(seg->tcphdr + 1);
if (seg->flags & TF_SEG_OPTS_MSS) {
*opts = TCP_BUILD_MSS_OPTION(TCP_MSS);
*opts = TCP_BUILD_MSS_OPTION(pcb->ctx->local_mss);
opts += 1;
}
+7 -5
View File
@@ -116,16 +116,18 @@ static void nat_transport_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry*
if (tr->nat_via_node_id != 0) {
if (tr->nat_tun) {
tun_write(tr->nat_tun, entry->dgram + ROUTER_SVC_PAYLOAD_OFF - 1, ip_len + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT client: received %zu bytes from provider", ip_len);
if (tun_write(tr->nat_tun, ip_data, ip_len) == (ssize_t)ip_len)
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT client: wrote %zu bytes from provider to TUN", ip_len);
else DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: TUN delivery failed bytes=%zu", ip_len);
}
} else {
int ret = eim_nat_egress(ctx, ip_data, ip_len, src_node_id, conn);
if (ret < 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "Egress NAT failed");
} else if (ret == 0 && tr->nat_tun) {
tun_write(tr->nat_tun, entry->dgram + ROUTER_SVC_PAYLOAD_OFF - 1, ip_len + 1);
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT provider: sent %zu bytes to internet", ip_len);
if (tun_write(tr->nat_tun, ip_data, ip_len) == (ssize_t)ip_len)
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT provider: wrote %zu bytes to internet TUN", ip_len);
else DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: TUN delivery failed bytes=%zu", ip_len);
}
}
@@ -165,7 +167,7 @@ int nat_transport_init(struct UTUN_INSTANCE* inst) {
eim_nat_destroy_ctx(&inst->nat);
return -1;
}
tr->nat_tun = tun_init_nat(inst->ua, tun_name, ip_str, g->mtu, g->tun_test_mode);
tr->nat_tun = tun_init_nat(inst->ua, tun_name, ip_str, TUN_MTU_DEFAULT, g->tun_test_mode);
if (!tr->nat_tun) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to create NAT TUN %s ip=%s", tun_name, ip_str);
eim_nat_destroy_ctx(&inst->nat);
+13 -7
View File
@@ -125,15 +125,16 @@ static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_
struct tcp_proxy_client *proxy = (struct tcp_proxy_client *)arg;
(void)src_ip; (void)dst_ip;
uint16_t len = p->tot_len;
if (len > 2000) return LERR_BUF;
uint8_t buf[2002];
if (len > TUN_MAX_PACKET_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy output too large: len=%u max=%d", len, TUN_MAX_PACKET_SIZE);
return LERR_BUF;
}
uint8_t buf[TUN_MAX_PACKET_SIZE];
if (proxy->tun) {
pbuf_copy_partial(p, buf, len, 0);
ssize_t wr = tun_platform_write(proxy->tun, buf, len);
if (wr != (ssize_t)len) {
uint16_t ip_total = ((uint16_t)buf[2] << 8) | buf[3];
uint32_t sip, dip; memcpy(&sip, buf + 12, 4); memcpy(&dip, buf + 16, 4);
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TUN_WRITE_ERR ret=%zd len=%u ip_total=%u", wr, len, ip_total);
if (tun_write(proxy->tun, buf, len) != (ssize_t)len) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy TUN output failed: len=%u", len);
return LERR_IF;
}
}
return LERR_OK;
@@ -766,6 +767,11 @@ struct tcp_proxy_client* tcp_proxy_client_create(struct UTUN_INSTANCE* inst, str
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
if (!p->lwip) { DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "lwip_tcp_init failed"); tun_close(p->tun); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
if (lwip_tcp_set_mtu(p->lwip, p->tun->mtu) != LERR_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_PROXY, "TCP proxy MTU setup failed: mtu=%d", p->tun->mtu);
tcp_proxy_client_destroy(p); return NULL;
}
if (mapping_count > 0) {
int j; for (j = 0; j < mapping_count; j++) {
uint16_t port_net = htons(mappings[j].local_port);
+10 -12
View File
@@ -76,8 +76,10 @@ static int create_tun_device(char *ifname, size_t ifname_len) {
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
int created = 1;
if (ioctl(sock, SIOCIFCREATE, &ifr) < 0) {
if (errno == EEXIST) {
created = 0;
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface %s already exists, reusing it", ifname);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "SIOCIFCREATE failed for %s: %s", ifname, strerror(errno));
@@ -85,15 +87,17 @@ static int create_tun_device(char *ifname, size_t ifname_len) {
return -1;
}
}
close(sock);
char dev_path[IFNAMSIZ + 6];
snprintf(dev_path, sizeof(dev_path), "/dev/%s", ifname);
fd = open(dev_path, O_RDWR);
if (fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to open %s: %s", dev_path, strerror(errno));
if (created && ioctl(sock, SIOCIFDESTROY, &ifr) < 0)
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to roll back interface %s: %s", ifname, strerror(errno));
close(sock);
return -1;
}
close(sock);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Opened %s (fd=%d)", dev_path, fd);
return fd;
}
@@ -308,23 +312,17 @@ int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str
// Устанавливаем режим POINTOPOINT (должно быть ДО поднятия интерфейса)
if (tun_set_mode(tun->fd, tun->ifname) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN mode on %s", tun->ifname);
close(tun->fd);
tun->fd = -1;
return -1;
goto fail;
}
// Включаем TUNSIFHEAD для multi-af режима
if (tun_set_head(tun->fd, tun->ifname) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN head mode on %s", tun->ifname);
close(tun->fd);
tun->fd = -1;
return -1;
goto fail;
}
// Configure IP address (это также поднимает интерфейс)
if (tun_set_ip(tun->ifname, ip_str) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP: %s", ip_str);
close(tun->fd);
tun->fd = -1;
return -1;
goto fail;
}
// Set MTU
if (tun_set_mtu(tun->ifname, mtu) < 0) {
@@ -378,7 +376,7 @@ void tun_platform_cleanup(struct tun_if* tun) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Successfully destroyed TUN interface %s", tun->ifname);
}
close(sock);
}
} else DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s destroy socket failed: %s", tun->ifname, strerror(errno));
}
}
// Platform-specific read for FreeBSD
+322 -391
View File
@@ -1,460 +1,391 @@
// 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"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#ifndef _WIN32
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
/* Первый член — весь блок освобождает uasync, включая destroy без callback.
* Пакет освобождает callback либо close; отменённая оболочка не ссылается на TUN. */
struct tun_packet_data {
struct posted_task task;
struct tun_if* tun;
struct ll_entry* entry;
};
#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;
}
static void tun_close_handle(struct tun_if* tun, HANDLE handle, const char* name) {
if (handle && !CloseHandle(handle))
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s close %s failed: error=%lu", tun->ifname, name, GetLastError());
}
#endif
// ===================================================================
// Callback: routing → TUN (вызывается из main thread)
// ===================================================================
static void tun_input_queue_callback(struct ll_queue* q, void* arg)
{
static void tun_read_ready(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);
struct tun_if* tun = arg;
if (tun->closing) return;
#ifdef _WIN32
if (!SetEvent(tun->read_permit)) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s read permit failed: error=%lu", tun->ifname, GetLastError());
tun->read_errors++;
return;
}
queue_entry_free(pkt);
queue_resume_callback(tun->input_queue);
#else
if (tun->socket_id && uasync_set_socket_read(tun->ua, tun->socket_id, 1) != ERR_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s could not resume reader", tun->ifname);
tun->read_errors++;
return;
}
#endif
DEBUG_TRACE(DEBUG_CATEGORY_TUN, "TUN %s reader permitted: output queue empty", tun->ifname);
}
// ===================================================================
// 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; }
static void tun_wait_output(struct tun_if* tun) {
if (!tun->closing && queue_waiter_wait(tun->output_queue, &tun->read_waiter, tun_read_ready, tun) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s could not wait for output queue; reader remains paused", tun->ifname);
tun->read_errors++;
}
}
int test_mode = config->global.tun_test_mode;
#ifndef _WIN32
static void tun_read_callback(int fd, void* arg) {
struct tun_if* tun = arg;
if (uasync_set_socket_read(tun->ua, tun->socket_id, 0) != ERR_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s could not pause reader", tun->ifname);
tun->read_errors++;
return;
}
if (queue_entry_count(tun->output_queue)) { tun_wait_output(tun); return; }
uint8_t buffer[TUN_MAX_PACKET_SIZE + 1];
ssize_t n = tun->test_mode ? recv(fd, buffer, sizeof(buffer), 0) : tun_platform_read(tun, buffer, sizeof(buffer));
if (n < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s read failed: %s", tun->ifname, strerror(errno));
tun->read_errors++;
}
} else if (n > 0) tun_inject_packet(tun, buffer, (size_t)n);
else if (tun->test_mode) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s discarded empty test datagram", tun->ifname);
tun->read_errors++;
}
tun_wait_output(tun);
}
#endif
struct tun_if* tun = u_calloc(1, sizeof(struct tun_if));
if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN allocation failed"); return NULL; }
static void tun_input_queue_callback(struct ll_queue* q, void* arg) {
struct tun_if* tun = arg;
struct ll_entry* pkt = queue_data_get(q);
if (pkt) {
if (!pkt->dgram || pkt->len <= 1) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s malformed input: data=%p len=%u", tun->ifname, (void*)pkt->dgram, pkt->len);
tun->write_errors++;
} else tun_write(tun, pkt->dgram + 1, pkt->len - 1);
queue_dgram_free(pkt);
queue_entry_free(pkt);
}
queue_resume_callback(q);
}
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);
static struct tun_if* tun_create(struct UASYNC* ua, const char* name, const char* ip, int mtu, int test_mode) {
struct in_addr addr;
if (!ua || !name || !ip || inet_pton(AF_INET, ip, &addr) != 1 || strlen(name) >= sizeof(((struct tun_if*)0)->ifname)
|| mtu < TUN_MTU_MIN || mtu > TUN_MAX_PACKET_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Invalid TUN init: ua=%p name=%s ip=%s mtu=%d", (void*)ua,
name ? name : "(null)", ip ? ip : "(null)", mtu);
return NULL;
}
int mtu = TUN_MTU_DEFAULT;
#ifdef _WIN32
if (test_mode) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN test_mode is unavailable on Windows"); return NULL; }
#endif
struct tun_if* tun = u_calloc(1, sizeof(*tun));
if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN allocation failed"); return NULL; }
int err = pthread_mutex_init(&tun->post_lock, NULL);
if (err) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN post mutex init failed: %s", strerror(err)); u_free(tun); return NULL; }
tun->ua = ua;
tun->fd = tun->test_fd = -1;
tun->mtu = mtu;
tun->test_mode = !!test_mode;
strcpy(tun->ifname, name[0] ? name : (test_mode ? "tun_test" : ""));
char cidr[64];
snprintf(cidr, sizeof(cidr), "%s/32", ip);
if (!test_mode) {
if (tun_platform_init(tun, tun->ifname, ip_str, mtu) != 0) {
u_free(tun);
return NULL;
}
if (tun_platform_init(tun, name, cidr, mtu) != 0) goto fail;
tun->platform_initialized = 1;
} 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;
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s socketpair failed: %s", tun->ifname, strerror(errno));
goto fail;
}
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) {
tun->fd = fds[0]; tun->test_fd = fds[1];
for (int i = 0; i < 2; i++) {
int flags = fcntl(fds[i], F_GETFL, 0);
if (flags < 0 || fcntl(fds[i], F_SETFL, flags | O_NONBLOCK) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s socketpair nonblocking failed: %s", tun->ifname, strerror(errno));
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; }
tun->output_queue = queue_new(ua, 0, 0, 0, "TUN output");
tun->input_queue = queue_new(ua, 0, 0, 0, "TUN input");
if (!tun->pool || !tun->output_queue || !tun->input_queue) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s queue/pool allocation failed", tun->ifname);
goto fail;
}
queue_set_threshold(tun->output_queue, 0, 0);
queue_set_callback(tun->input_queue, tun_input_queue_callback, tun);
#ifdef _WIN32
if (tun_windows_start_reader(tun) != 0) goto fail;
#else
int fd = test_mode ? tun->fd : tun_platform_get_poll_fd(tun);
tun->socket_id = uasync_add_socket(ua, fd, tun_read_callback, NULL, NULL, "tun", tun);
if (!tun->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s reader registration failed fd=%d", tun->ifname, fd); goto fail; }
#endif
DEBUG_INFO(DEBUG_CATEGORY_TUN, "NAT TUN %s initialized (%s mode)", tun->ifname,
test_mode ? "TEST" : "REAL");
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN %s initialized: mode=%s mtu=%d reader=%s", tun->ifname,
test_mode ? "test" : "real", mtu, tun->socket_id ? "poll" : "thread");
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);
fail:
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s initialization failed; rolling back", tun->ifname);
tun_close(tun);
return NULL;
}
// ===================================================================
// Остальные функции (без изменений)
// ===================================================================
void tun_close(struct tun_if* tun)
{
if (!tun) return;
struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
if (!config || config->global.tun_ip.family != AF_INET) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN requires config with IPv4 address");
return NULL;
}
char ip[INET_ADDRSTRLEN];
if (!inet_ntop(AF_INET, &config->global.tun_ip.addr.v4, ip, sizeof(ip))) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN IPv4 formatting failed: %s", strerror(errno));
return NULL;
}
return tun_create(ua, config->global.tun_ifname, ip, TUN_MTU_DEFAULT, config->global.tun_test_mode);
}
struct tun_if* tun_init_nat(struct UASYNC* ua, const char* name, const char* ip, int mtu, int test_mode) {
return tun_create(ua, name, ip, mtu, test_mode);
}
static void tun_free_queue(struct ll_queue* q) {
if (!q) return;
queue_set_callback(q, NULL, NULL);
struct ll_entry* pkt;
while ((pkt = queue_data_get(q))) { queue_dgram_free(pkt); queue_entry_free(pkt); }
queue_free(q);
}
void tun_close(struct tun_if* tun) {
if (!tun) return;
pthread_mutex_lock(&tun->post_lock);
tun->closing = 1;
pthread_mutex_unlock(&tun->post_lock);
#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);
if (!SetEvent(tun->stop_event)) DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s stop signal failed: %lu", tun->ifname, GetLastError());
DWORD result = WaitForSingleObject(tun->read_thread, INFINITE);
if (result != WAIT_OBJECT_0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s reader join failed: result=%lu error=%lu", tun->ifname, result, GetLastError());
return; // Живой worker запрещает освобождение его ресурсов.
}
tun_close_handle(tun, tun->read_thread, "reader thread");
}
#endif
if (tun->socket_id && tun->ua) {
uasync_remove_socket(tun->ua, tun->socket_id);
pthread_mutex_lock(&tun->post_lock);
unsigned cancelled = tun->pending_packet != NULL;
if (tun->pending_packet) {
struct tun_packet_data* pd = tun->pending_packet;
queue_dgram_free(pd->entry); queue_entry_free(pd->entry);
pd->entry = NULL; pd->tun = NULL; tun->pending_packet = NULL;
}
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);
tun->read_errors += tun->worker_read_errors;
pthread_mutex_unlock(&tun->post_lock);
if (tun->output_queue) queue_waiter_cancel(tun->output_queue, &tun->read_waiter);
if (tun->socket_id) {
if (uasync_remove_socket(tun->ua, tun->socket_id) != ERR_OK)
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s reader removal failed", tun->ifname);
tun->socket_id = NULL;
}
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);
tun_free_queue(tun->input_queue);
tun_free_queue(tun->output_queue);
memory_pool_destroy(tun->pool);
if (!tun->test_mode) tun_platform_cleanup(tun);
if (tun->platform_initialized) tun_platform_cleanup(tun);
#ifndef _WIN32
if (tun->test_mode) {
if (tun->fd >= 0 && close(tun->fd) != 0) DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s close fd failed: %s", tun->ifname, strerror(errno));
if (tun->test_fd >= 0 && close(tun->test_fd) != 0)
DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s close test fd failed: %s", tun->ifname, strerror(errno));
}
#else
tun_close_handle(tun, tun->stop_event, "stop event");
tun_close_handle(tun, tun->read_permit, "read permit");
#endif
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN %s closed: cancelled_posts=%u rx=%u/%llu tx=%u/%llu errors=%u/%u", tun->ifname,
cancelled, tun->packets_read, (unsigned long long)tun->bytes_read, tun->packets_written,
(unsigned long long)tun->bytes_written, tun->read_errors, tun->write_errors);
pthread_mutex_destroy(&tun->post_lock);
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 tun_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
if (!tun || !buf || !len || len > (size_t)tun->mtu || tun->closing) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Invalid TUN write: tun=%p len=%zu", (void*)tun, len);
if (tun) tun->write_errors++;
errno = EINVAL;
return -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 {
ssize_t n;
do {
#ifndef _WIN32
n = tun->test_mode ? send(tun->fd, buf, len, 0) : tun_platform_write(tun, buf, len);
#else
n = tun_platform_write(tun, buf, len);
#endif
} while (n < 0 && errno == EINTR);
if (n != (ssize_t)len) {
int error = n < 0 ? errno : EIO;
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s write failed: requested=%zu written=%zd error=%s", tun->ifname, len, n, strerror(error));
tun->write_errors++;
errno = error;
return -1;
}
tun->bytes_written += len; tun->packets_written++;
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "TUN %s -> OS: bytes=%zu %s", tun->ifname, len, dump_ip_packet_to_buffer(buf, len));
return n;
}
struct ll_queue* tun_get_input_queue(struct tun_if* tun) { return tun ? tun->input_queue : NULL; }
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; }
int tun_get_test_fd(struct tun_if* tun) { return tun && tun->test_mode ? 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;
/* После put entry уже может быть освобождён синхронным consumer. */
static int tun_deliver(struct tun_if* tun, struct ll_entry* entry) {
size_t len = entry->len - 1;
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "TUN %s OS -> core: bytes=%zu %s", tun->ifname, len,
dump_ip_packet_to_buffer(entry->dgram + 1, len));
if (queue_entry_count(tun->output_queue)) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s output busy; discarding concurrent injection len=%zu", tun->ifname, len);
queue_dgram_free(entry); queue_entry_free(entry);
tun->read_errors++; errno = EAGAIN;
return -1;
}
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);
if (queue_data_put(tun->output_queue, entry) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s output queue rejected %zu IP bytes", tun->ifname, len);
tun->read_errors++;
return -1;
}
tun->bytes_read += len + 1;
tun->packets_read++;
tun->bytes_read += len; 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;
static struct ll_entry* tun_packet_new(struct memory_pool* pool, const uint8_t* buf, size_t len) {
struct ll_entry* entry = pool ? queue_entry_new_from_pool(pool) : queue_entry_new(0);
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN packet entry allocation failed"); return NULL; }
entry->dgram = u_malloc(len + 1);
if (!entry->dgram) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN packet buffer allocation failed: len=%zu", len);
queue_entry_free(entry);
return NULL;
}
entry->dgram[0] = 0;
memcpy(entry->dgram + 1, buf, len);
entry->len = (uint16_t)(len + 1);
return entry;
}
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);
int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len) {
if (!tun || !buf || !len || len > (size_t)tun->mtu || tun->closing) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Invalid TUN injection: tun=%p len=%zu", (void*)tun, len);
if (tun) tun->read_errors++;
errno = EINVAL;
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));
if (queue_entry_count(tun->output_queue)) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s injection requires empty output queue", tun->ifname);
errno = EAGAIN;
return -1;
}
return ret;
struct ll_entry* entry = tun_packet_new(tun->pool, buf, len);
if (!entry) { tun->read_errors++; errno = ENOMEM; return -1; }
return tun_deliver(tun, entry);
}
static void tun_packet_handler(void* arg) {
struct tun_packet_data* pd = arg;
struct tun_if* tun = pd->tun;
if (!tun) return; // close освободил пакет; оболочку освободит uasync.
pthread_mutex_lock(&tun->post_lock);
tun->pending_packet = NULL;
tun->read_errors += tun->worker_read_errors; tun->worker_read_errors = 0;
struct ll_entry* entry = pd->entry;
pd->tun = NULL; pd->entry = NULL;
pthread_mutex_unlock(&tun->post_lock);
tun_deliver(tun, entry);
tun_wait_output(tun);
}
void tun_reader_error(struct tun_if* tun, const char* operation, unsigned long error) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s reader %s failed: error=%lu", tun->ifname, operation, error);
pthread_mutex_lock(&tun->post_lock);
tun->worker_read_errors++;
pthread_mutex_unlock(&tun->post_lock);
}
int tun_post_packet(struct tun_if* tun, const uint8_t* buf, size_t len) {
if (!buf || !len || len > (size_t)tun->mtu) { tun_reader_error(tun, "packet size", (unsigned long)len); return -1; }
struct tun_packet_data* pd = u_calloc(1, sizeof(*pd));
if (!pd) { tun_reader_error(tun, "post allocation", ENOMEM); return -1; }
pd->entry = tun_packet_new(NULL, buf, len);
if (!pd->entry) { u_free(pd); tun_reader_error(tun, "packet allocation", ENOMEM); return -1; }
pd->tun = tun; pd->task.callback = tun_packet_handler; pd->task.arg = pd;
pthread_mutex_lock(&tun->post_lock);
if (tun->closing || tun->pending_packet) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s post rejected: closing=%d pending=%d", tun->ifname, tun->closing, tun->pending_packet != NULL);
pthread_mutex_unlock(&tun->post_lock);
queue_dgram_free(pd->entry); queue_entry_free(pd->entry); u_free(pd);
return -1;
}
tun->pending_packet = pd;
uasync_post_reserved(tun->ua, &pd->task);
pthread_mutex_unlock(&tun->post_lock);
return 0;
}
ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len) {
if (!tun || !tun->test_mode || !buf || !len || tun->closing) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Invalid test TUN read: tun=%p capacity=%zu", (void*)tun, len);
errno = EINVAL;
return -1;
}
#ifdef _WIN32
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Test TUN read is unavailable on Windows");
errno = ENOSYS;
return -1;
#else
uint8_t probe[TUN_MAX_PACKET_SIZE + 1];
ssize_t n;
do { n = recv(tun->test_fd, probe, sizeof(probe), MSG_PEEK); } while (n < 0 && errno == EINTR);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return 0;
if (n < 0) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s test peek failed: %s", tun->ifname, strerror(errno)); return -1; }
if ((size_t)n > len) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "TUN %s test read buffer too small: packet=%zd capacity=%zu", tun->ifname, n, len);
errno = EMSGSIZE;
return -1;
}
do { n = recv(tun->test_fd, buf, len, 0); } while (n < 0 && errno == EINTR);
if (n < 0) DEBUG_ERROR(DEBUG_CATEGORY_TUN, "TUN %s test read failed: %s", tun->ifname, strerror(errno));
return n;
#endif
}
+59 -121
View File
@@ -1,158 +1,96 @@
/* Кроссплатформенный TUN: init создаёт интерфейс и очереди, close освобождает их.
* output_queue несёт TUN → ядро, input_queue — ядро → TUN; формат очереди: префикс(1) + IP-пакет.
* tun_write принимает тот же префикс и пропускает его при записи; tun_inject_packet принимает чистый IP.
* Основной API — в uasync; Windows read-thread передаёт пакеты через tun_packet_handler.
* test_mode использует очереди вместо устройства ОС. */
/* TUN: output_queue — ОС → ядро, input_queue — ядро → ОС; очереди содержат префикс(1) + IP.
* tun_write/inject/read_packet работают с чистым IP. API — в потоке ua, close — вне callbacks TUN,
* до уничтожения ua. POSIX test_mode использует socketpair; Windows требует Wintun. */
#ifndef TUN_IF_H
#define TUN_IF_H
#include <stdint.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <pthread.h>
#include "../lib/ll_queue.h"
#ifdef _WIN32
#include <windows.h> // для HANDLE и CRITICAL_SECTION
#endif
// Forward declarations
struct UASYNC;
struct utun_config;
struct ll_queue;
struct ll_entry;
struct memory_pool;
//struct ETCP_FRAGMENT; // используется внутри, определён в etcp.h
// Структура для передачи данных из TUN потока в main thread
struct tun_packet_data {
struct tun_if* tun;
struct ll_entry* entry;
};
struct tun_packet_data;
#ifdef __cplusplus
extern "C" {
#endif
#define TUN_MTU_DEFAULT 1500
#define TUN_MTU_MIN 68 // Минимальный IPv4 MTU.
#define TUN_MAX_PACKET_SIZE 1500
// ===================================================================
// TUN interface handle
// ===================================================================
struct tun_if {
char ifname[16]; // "utun", "tun0", "tun_test" и т.д.
int fd; // Linux: fd /dev/net/tun, Windows: -1
void* platform_handle; // Windows: WINTUN_SESSION_HANDLE
void* adapter_handle; // Windows: WINTUN_ADAPTER_HANDLE
struct UASYNC* ua;
void* socket_id; // uasync socket_id (NULL на Windows)
struct memory_pool* pool; // пул для ll_entry
struct ll_queue* output_queue; // TUN → routing (пакеты из интерфейса)
struct ll_queue* input_queue; // routing → TUN (пакеты в интерфейс)
int test_mode; // 1 = тестовый режим (без реального TUN)
int test_fd; // в тестовом режиме: вторая сторона socket pair для тестов, -1 иначе
// Статистика
uint64_t bytes_read;
uint64_t bytes_written;
uint32_t packets_read;
uint32_t packets_written;
uint32_t read_errors;
uint32_t write_errors;
uint32_t ifindex; // интерфейсный индекс
char ifname[16]; // Имя устройства ОС либо тестового интерфейса.
int fd; // POSIX: устройство или socketpair; Windows: -1.
void* platform_handle; // Windows: WINTUN_SESSION_HANDLE.
void* adapter_handle; // Windows: WINTUN_ADAPTER_HANDLE.
int platform_initialized; // Успешный init передал cleanup общему коду.
struct UASYNC* ua; // Заимствованный цикл, живёт дольше TUN.
void* socket_id; // Регистрация POSIX-чтения; NULL на Windows.
struct memory_pool* pool; // Пул entry, только поток ua.
struct ll_queue* output_queue; // ОС → ядро, следующий read ждёт пустой очереди.
struct ll_queue* input_queue; // Ядро → ОС, callback снимает префикс перед tun_write.
struct queue_waiter_handle read_waiter; // Ожидание output_queue, отменяется до дренажа.
int test_mode; // POSIX socketpair вместо устройства.
int test_fd; // Заимствованный тестами peer socketpair, иначе -1.
int mtu; // Размер IP-пакета: TUN_MTU_MIN..TUN_MAX_PACKET_SIZE.
int closing; // Защищён post_lock при доступе worker.
pthread_mutex_t post_lock; // Защищает pending_packet и worker_read_errors.
struct tun_packet_data* pending_packet; // Не более одной передачи worker → ua.
uint32_t worker_read_errors; // Ошибки worker, переносимые в read_errors под lock.
uint64_t bytes_read; // IP-байты, принятые output_queue; только поток ua.
uint64_t bytes_written; // Полностью записанные IP-байты; только поток ua.
uint32_t packets_read; // Пакеты, принятые output_queue.
uint32_t packets_written; // Полностью записанные пакеты.
uint32_t read_errors; // Ошибки чтения/доставки, включая отказ очереди.
uint32_t write_errors; // Ошибки аргументов/записи, включая неполную запись.
uint32_t ifindex; // Индекс ОС; 0 в тестовом backend.
#ifdef _WIN32
HANDLE read_thread;
HANDLE stop_event;
volatile int running; // 1 = поток работает
HANDLE read_thread; // Производитель, завершается до освобождения TUN.
HANDLE stop_event; // Прерывает ожидание данных и разрешения чтения.
HANDLE read_permit; // Auto-reset: разрешение принять следующий пакет.
HANDLE read_event; // Заимствованный event Wintun; не закрывать отдельно.
#endif
};
/**
* @brief Инициализация TUN интерфейса
* @param ua экземпляр uasync
* @param config конфигурация (IP, MTU, test_mode и т.д.)
* @return указатель на tun_if или NULL при ошибке
*/
// Основной TUN по config: IPv4, MTU 1500. ua/config обязательны; NULL при ошибке.
struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config);
/**
* @brief Инициализация TUN интерфейса с явными параметрами (для NAT TUN)
* @param ua экземпляр uasync
* @param ifname имя интерфейса
* @param ip_addr IP-адрес (строка, например "10.0.1.1")
* @param mtu MTU интерфейса
* @param test_mode тестовый режим
* @return указатель на tun_if или NULL при ошибке
*/
// Отдельный TUN: ifname, IPv4 без CIDR, явный MTU. test_mode только POSIX; NULL при ошибке.
struct tun_if* tun_init_nat(struct UASYNC* ua, const char* ifname, const char* ip_addr, int mtu, int test_mode);
/**
* @brief Закрытие и освобождение всех ресурсов
*/
// Остановить reader, обезвредить post, отменить ожидания и освободить ресурсы. NULL допустим.
void tun_close(struct tun_if* tun);
/**
* @brief Записать пакет в TUN (из routing)
*/
// Синхронно записать чистый IP без передачи владения buf: len при успехе, -1/errno при ошибке.
ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len);
/**
* @brief Получить очередь input (routing → TUN)
*/
// Заимствованные очереди (префикс + IP), либо NULL при tun == NULL.
struct ll_queue* tun_get_input_queue(struct tun_if* tun);
/**
* @brief Получить очередь output (TUN → routing)
*/
struct ll_queue* tun_get_output_queue(struct tun_if* tun);
/**
* @brief Проверка режима
*/
// Вернуть 0/1, при NULL — -1.
int tun_is_test_mode(struct tun_if* tun);
/**
* @brief Получить test_fd (вторая сторона socket pair) для тестов
* @return fd или -1 если не тестовый режим
*/
// Заимствованный POSIX peer socketpair для чистого IP; иначе -1. Самостоятельно не закрывать.
int tun_get_test_fd(struct tun_if* tun);
/**
* @brief Инъекция пакета в output_queue (для тестов и Windows-уведомлений)
*/
// Скопировать IP в свободную output_queue: 0 / -1; занятая очередь — EAGAIN, buf остаётся вызывающему.
int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len);
/**
* @brief Прочитать пакет из input_queue (тестовый helper)
*/
// POSIX helper: чистый IP из test_fd; >0 — размер, 0 — пока пусто, -1/errno — ошибка.
// При малом buf (EMSGSIZE) пакет сохраняется. Счётчики не изменяются; один consumer test_fd.
ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len);
/**
* @brief Callback для обработки пакетов из TUN в main thread (через uasync_post)
*/
void tun_packet_handler(void* arg);
// ===================================================================
// Platform-specific internal functions
// (реализованы в tun_linux.c / tun_windows.c)
// ===================================================================
int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str, int mtu);
/* Внутренний backend API: init сам откатывает ошибку; cleanup — после успешного init.
* read/write передают чистый IP; poll_fd заимствован и регистрируется общим кодом. */
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);
ssize_t tun_platform_read(struct tun_if* tun, uint8_t* buf, size_t len);
ssize_t tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len);
int tun_platform_get_poll_fd(struct tun_if* tun);
int tun_platform_get_poll_fd(struct tun_if* tun);
// Worker: скопировать пакет в резервированный post, 0 / -1. TUN/ua живы до join.
// После успеха следующий пакет разрешает read_permit; очереди и пул worker не использует.
int tun_post_packet(struct tun_if* tun, const uint8_t* buf, size_t len);
// Worker: логировать системную ошибку и увеличить защищённый счётчик; operation — описание операции.
void tun_reader_error(struct tun_if* tun, const char* operation, unsigned long error);
#ifdef _WIN32
DWORD WINAPI tun_read_thread_proc(LPVOID arg);
// Подготовить events и запустить reader: 0 / -1; частичные handles освобождает tun_close.
int tun_windows_start_reader(struct tun_if* tun);
#endif
#ifdef __cplusplus
}
#endif
#endif /* TUN_IF_H */
#endif
+85 -42
View File
@@ -1,54 +1,97 @@
# TUN Interface (tun_if)
## 1. Назначение
Модуль создания и управления виртуальным сетевым интерфейсом (TUN) — точкой входа/выхода всего VPN-трафика. Через TUN-интерфейс ОС отправляет IP-пакеты в uTun и получает обработанные пакеты обратно. Реализует кроссплатформенную абстракцию над платформенными механизмами: Linux (`/dev/net/tun`), FreeBSD (`/dev/tun` + TUNSIFHEAD), Windows (WinTun API).
TUN связывает IP-трафик ОС с ядром. Все операции с интерфейсом, очередями, пулом и
публичными счётчиками выполняются в потоке его `UASYNC`. `tun_close()` вызывается вне
callbacks TUN, после остановки внешних пользователей и до уничтожения uasync.
Модель данных — две очереди:
- **output_queue** — пакеты, прочитанные из TUN (ОС → uTun → routing)
- **input_queue** — пакеты для записи в TUN (routing → uTun → ОС), с callback `tun_input_queue_callback`
## Форматы и направления
## 2. Как пользоваться
```c
// Инициализация из конфига (основной интерфейс VPN)
tun = tun_init(ua, config);
// или для NAT TUN с явными параметрами
nat_tun = tun_init_nat(ua, "tun_nat", "10.0.1.1", 1500, 0);
| API | Направление | Формат |
|---|---|---|
| `output_queue` | ОС → ядро | префикс(1) + IP |
| `input_queue` | ядро → ОС | префикс(1) + IP |
| `tun_write(tun, buf, len)` | ядро → ОС | чистый IP |
| `tun_inject_packet(tun, buf, len)` | тест → output_queue | чистый IP |
| `tun_read_packet(tun, buf, capacity)` | тест читает доставленное ОС | чистый IP |
// Чтение: очередь output_queue наполняется автоматически (через uasync/epoll),
// обработчик читает через queue_set_callback
struct ll_queue* out = tun_get_output_queue(tun);
`input_queue` снимает префикс и вызывает общий `tun_write()`. NAT и TCP proxy
пользуются тем же API. Платформенный `tun_platform_write()` вызывается только модулем
TUN. `tun_write()` сохраняет владение `buf` у вызывающего и возвращает `len` только
после полной записи, иначе `-1` с errno. Неполная запись считается ошибкой.
Пакет, отклонённый `queue_data_put()`, уже освобождён очередью.
// Запись: пакет кладётся в input_queue, callback пишет в TUN
tun_write(tun, buf, len); // прямой вызов, не через очередь
## Создание и MTU
// Завершение
tun_close(tun);
```
`tun_init(ua, config)` создаёт основной IPv4 TUN с MTU 1500.
`tun_init_nat(ua, name, ip, mtu, test_mode)` создаёт отдельный интерфейс для NAT или
TCP proxy: `ip` — IPv4 без CIDR, MTU — 68..1500. Обе функции используют общий путь
создания, регистрации чтения и отката; возвращают NULL при любой ошибке запуска.
Платформенный init сам освобождает ресурсы при ошибке, успешный init передаёт cleanup
общему коду. Длинные имена, неверный IPv4 и неподдерживаемый MTU отклоняются.
**Root-права:** требуются на всех платформах для создания TUN-устройств и настройки IP.
**Тестовый режим** (`test_mode=1`): вместо реального TUN используется `socketpair(AF_UNIX, SOCK_DGRAM)`. Вторая сторона пары доступна через `tun_get_test_fd()`.
`[global] mtu` управляет ETCP, поэтому основной и NAT TUN используют
`TUN_MTU_DEFAULT`. TCP proxy передаёт собственный настроенный MTU и до создания PCB
вызывает `lwip_tcp_set_mtu()`: локальный предел MSS равен IPv4 MTU минус 40 байт.
Он ограничивает начальный MSS, объявление в SYN и MSS, согласованный с пиром.
**Платформенные различия:**
- **Linux:** `open("/dev/net/tun")` + `ioctl(TUNSETIFF)` с `IFF_TUN | IFF_NO_PI`, неблокирующий режим через `fcntl(O_NONBLOCK)`
- **FreeBSD:** `open("/dev/tun")` (cloning device), имя присваивается ядром, `ioctl(TUNSIFHEAD)` для multi-af (4-байтный заголовок AF), `ioctl(TUNSIFMODE)` для POINTOPOINT
- **Windows:** WinTun API через `wintun.dll`, кольцевой буфер 4 MiB, отдельный read-поток (`tun_read_thread_proc`), пакеты передаются в main thread через `uasync_post → tun_packet_handler`
## Чтение и backpressure
## 3. API
На Linux/FreeBSD fd зарегистрирован в uasync. Перед чтением регистрация
приостанавливается; после передачи пакета reader ждёт пустую `output_queue` через
`queue_waiter_wait()`. Затем чтение разрешается снова. Синхронный consumer допустим.
`tun_inject_packet()` копирует пакет, возвращает 0 при успехе и -1 при ошибке;
если output_queue занята, возвращает EAGAIN без передачи пакета.
### Структуры
- **`struct tun_if`** — хендл интерфейса: `fd` (Linux/BSD файловый дескриптор), `platform_handle`/`adapter_handle` (Windows WinTun), `ifname`, `ifindex`, очереди `input_queue`/`output_queue`, пул `pool` для ll_entry, счётчики статистики (`bytes_read/written`, `packets_read/written`, `read_errors`, `write_errors`)
- **`struct tun_packet_data`** — обёртка для передачи пакета из read-потока (Windows) в main thread: указатели на `tun_if` и `ll_entry`
Windows использует Wintun: session, заимствованный read event, stop event,
auto-reset read permit и read thread. Оба конструктора проверяют read event и
создают reader до сообщения об успешном запуске. Worker ожидает данные и разрешение
отдельно; stop event прерывает оба ожидания. Следующий пакет разрешается после
освобождения output_queue, поэтому поток не накапливает задачи в uasync.
### Функции
- **`tun_init(ua, config)`** / **`tun_init_nat(ua, ifname, ip, mtu, test_mode)`** — создают TUN-интерфейс: платформенный init, настройка IP/MTU, поднятие интерфейса, создание очередей, регистрация в uasync (Linux/BSD) или запуск read-потока (Windows). `tun_init_nat` — вариант для отдельного NAT TUN с явными параметрами вместо конфига
- **`tun_close(tun)`** — остановка read-потока (Windows), удаление из uasync, слив и освобождение обеих очередей, уничтожение пула, платформенная очистка (`close(fd)` на Linux, `SIOCIFDESTROY` на FreeBSD, `WintunEndSession`+`WintunCloseAdapter` на Windows)
- **`tun_write(tun, buf, len)`** — прямая запись в TUN (в тестовом режиме — в output_queue, иначе `tun_platform_write`). Пакет имеет префиксный байт, отбрасываемый при записи
- **`tun_inject_packet(tun, buf, len)`** — инжекция пакета в output_queue (для тестов и Windows-нотификаций)
- **`tun_read_packet(tun, buf, len)`** — тестовый helper: чтение одного пакета из input_queue
- **`tun_packet_handler(arg)`** — callback для main thread (Windows): распаковывает `tun_packet_data`, кладёт `ll_entry` в output_queue
- **Платформенные функции** (`tun_linux.c` / `tun_freebsd.c` / `tun_windows.c`):
- `tun_platform_init()` — создание устройства, настройка IP/MTU, поднятие, получение `ifindex`, `O_NONBLOCK`
- `tun_platform_cleanup()` — закрытие fd/хендлов
- `tun_platform_read()` — чтение из TUN (FreeBSD: `readv` с отделением 4-байтного AF-заголовка)
- `tun_platform_write()` — запись в TUN (FreeBSD: `writev` с AF-заголовком; Windows: `WintunAllocateSendPacket` + `WintunSendPacket`)
- `tun_platform_get_poll_fd()` — fd для uasync (Windows возвращает -1, используется read-поток)
Worker не обращается к LL_QUEUE и пулу. Один пакет передаётся через
`tun_post_packet()` и `uasync_post_reserved()`. Пакет и оболочка принадлежат
ожидающей передаче; `posted_task` — первый член оболочки, которую освобождает uasync.
Указатель ожидающей передачи и ошибки worker защищены mutex. Callback снимает
ожидающую передачу, переносит ошибки в общую статистику и передаёт пакет очереди.
## Тестовый backend
`test_mode=1` поддерживается только на POSIX, прав на устройство ОС не требует.
Оба конца `socketpair(AF_UNIX, SOCK_DGRAM)` неблокирующие. Заимствованный
`tun_get_test_fd()` живёт до `tun_close()`; закрывать его тестам нельзя.
- `send(test_fd, ip, len, 0)` моделирует исходящий IP от ОС и запускает обычный reader.
- Запись через `tun_write()`/input_queue доступна через `recv(test_fd, ...)` либо helper.
- `tun_read_packet()` возвращает размер чистого IP, 0 при пустом сокете, -1 при ошибке.
При недостаточной ёмкости возвращает EMSGSIZE и сохраняет пакет для следующего
чтения. Helper и прямой recv требуют одного последовательного consumer.
Тестовые сокеты обходят платформенный TUN read/write. В частности, FreeBSD
AF-заголовок в socketpair не добавляется и не снимается.
## Закрытие, статистика и диагностика
Закрытие выставляет closing, останавливает и присоединяет Windows worker, освобождает
пакет ожидающей передачи и обнуляет её ссылку на TUN. Её оболочку позже освобождает
uasync: callback становится пустым, а destroy без callback также освобождает блок.
Это безопасно и для задачи из уже снятой uasync порции; исполнение чужих задач
ради очистки не требуется.
Затем отменяется read waiter и регистрация fd, снимаются callbacks и освобождаются
элементы очередей, очереди и пул, закрывается backend. Для test_mode закрываются
оба fd обычным close, без удаления интерфейса ОС. Wintun session закрывается после
join; заимствованный read event отдельно не закрывается. Внешние владельцы waiters
должны отменять их перед закрытием TUN.
Счётчики bytes учитывают чистый IP: read — пакеты, принятые output_queue, write —
полностью записанные пакеты. Helper их не меняет. Ошибки worker переносятся при
доставке следующего пакета либо при закрытии. Категория `tun`: INFO — запуск и
итог закрытия, DEBUG — направление/размер/сводка IP, TRACE — разрешение чтения,
WARN/ERROR — некорректные пакеты, отказы памяти/очереди и системных операций.
`test_tun_if` проверяет направления, границы helper, освобождение повреждённых
элементов, очередь с отказом, паузу reader, fd, откаты init и post lifetime на
реальном uasync. `test_nat_transport` проверяет полный socketpair roundtrip.
`test_lwip_tcp 12345 1200` и `test_lwip_tcp 12345 68` проверяют TCP-обмен с разными
MTU, объявления MSS и размеры каждого выходного IP-пакета. Нативный Wintun требует
отдельной проверки на Windows.
+62 -95
View File
@@ -13,6 +13,7 @@
#include <iphlpapi.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <errno.h>
#include "../lib/mem.h"
// Wintun function pointers
@@ -206,110 +207,76 @@ void tun_platform_cleanup(struct tun_if* tun)
}
}
ssize_t tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len)
{
if (!tun->platform_handle) return -1;
ssize_t tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
if (!tun->platform_handle) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun %s has no session", tun->ifname);
errno = ENODEV;
return -1;
}
BYTE* pkt = WintunAllocateSendPacket((WINTUN_SESSION_HANDLE)tun->platform_handle, (DWORD)len);
if (!pkt) return -1;
if (!pkt) {
DWORD error = GetLastError();
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun %s allocate send failed: len=%zu error=%lu", tun->ifname, len, error);
errno = error == ERROR_BUFFER_OVERFLOW ? EAGAIN : EIO;
return -1;
}
memcpy(pkt, buf, len);
WintunSendPacket((WINTUN_SESSION_HANDLE)tun->platform_handle, pkt);
return (ssize_t)len;
}
int tun_platform_get_poll_fd(struct tun_if* tun)
{
(void)tun;
return -1;
}
int tun_platform_get_poll_fd(struct tun_if* tun) { (void)tun; return -1; }
// ===================================================================
// Windows-only notify
// ===================================================================
static void tun_output_notify(void* arg)
{
struct tun_if* tun = (struct tun_if*)arg;
if (tun) {
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "TUN read notify → output_queue (%s)", tun->ifname);
}
}
// ===================================================================
// Read thread
// ===================================================================
DWORD WINAPI tun_read_thread_proc(LPVOID arg)
{
struct tun_if* tun = (struct tun_if*)arg;
WINTUN_SESSION_HANDLE session = (WINTUN_SESSION_HANDLE)tun->platform_handle;
if (!session) return 1;
HANDLE event = WintunGetReadWaitEvent(session);
if (!event) return 1;
HANDLE handles[2] = {event, tun->stop_event};
while (tun->running) {
if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) != WAIT_OBJECT_0)
break;
/* Дренаж всех готовых пакетов */
for (;;) {
DWORD size;
BYTE* wintun_pkt = WintunReceivePacket(session, &size);
if (!wintun_pkt) {
if (GetLastError() == ERROR_NO_MORE_ITEMS)
break;
tun->read_errors++;
break;
}
if (size == 0) {
WintunReleaseReceivePacket(session, wintun_pkt);
continue;
}
uint8_t* data = u_malloc(size + 1);
if (!data) {
WintunReleaseReceivePacket(session, wintun_pkt);
tun->read_errors++;
continue;
}
data[0] = 0;
memcpy(data + 1, wintun_pkt, size);
WintunReleaseReceivePacket(session, wintun_pkt);
// struct ll_entry* pkt = queue_entry_new_from_pool(tun->pool);// from pool нельзя - нет thred safe (memory consistency)
struct ll_entry* pkt = queue_entry_new(0);
if (!pkt) {
u_free(data);
tun->read_errors++;
continue;
}
pkt->dgram = data;
pkt->len = size + 1;
struct tun_packet_data* pd = u_malloc(sizeof(*pd));
if (!pd) {
u_free(data);
queue_entry_free(pkt);
tun->read_errors++;
continue;
}
pd->tun = tun;
pd->entry = pkt;
tun->bytes_read += size + 1;
tun->packets_read ++;
if (debug_should_output(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_TUN)) {
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[TUN_THREAD->] %s", dump_ip_packet_to_buffer(data + 1, size));
}
uasync_post(tun->ua, tun_packet_handler, pd);
/* Один принятый пакет ждёт main thread; stop имеет приоритет в обоих ожиданиях. */
static DWORD WINAPI tun_read_thread_proc(LPVOID arg) {
struct tun_if* tun = arg;
HANDLE permit[2] = {tun->stop_event, tun->read_permit};
HANDLE ready[2] = {tun->stop_event, tun->read_event};
for (;;) {
DWORD result = WaitForMultipleObjects(2, permit, FALSE, INFINITE);
if (result == WAIT_OBJECT_0) break;
if (result != WAIT_OBJECT_0 + 1) { tun_reader_error(tun, "permit wait", GetLastError()); break; }
result = WaitForMultipleObjects(2, ready, FALSE, INFINITE);
if (result == WAIT_OBJECT_0) break;
if (result != WAIT_OBJECT_0 + 1) { tun_reader_error(tun, "read wait", GetLastError()); break; }
DWORD size = 0;
BYTE* packet = WintunReceivePacket((WINTUN_SESSION_HANDLE)tun->platform_handle, &size);
if (!packet) {
DWORD error = GetLastError();
if (error != ERROR_NO_MORE_ITEMS) { tun_reader_error(tun, "receive", error); break; }
if (!SetEvent(tun->read_permit)) { tun_reader_error(tun, "empty receive permit", GetLastError()); break; }
continue;
}
int posted = tun_post_packet(tun, packet, size);
WintunReleaseReceivePacket((WINTUN_SESSION_HANDLE)tun->platform_handle, packet);
if (posted != 0 && !SetEvent(tun->read_permit)) { tun_reader_error(tun, "retry permit", GetLastError()); break; }
}
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "TUN read thread exit");
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Wintun %s reader stopped", tun->ifname);
return 0;
}
int tun_windows_start_reader(struct tun_if* tun) {
tun->read_event = WintunGetReadWaitEvent((WINTUN_SESSION_HANDLE)tun->platform_handle);
if (!tun->read_event) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun %s read event failed: %lu", tun->ifname, GetLastError());
return -1;
}
tun->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!tun->stop_event) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun %s stop event failed: %lu", tun->ifname, GetLastError());
return -1;
}
tun->read_permit = CreateEvent(NULL, FALSE, TRUE, NULL);
if (!tun->read_permit) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun %s permit event failed: %lu", tun->ifname, GetLastError());
return -1;
}
tun->read_thread = CreateThread(NULL, 0, tun_read_thread_proc, tun, 0, NULL);
if (!tun->read_thread) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun %s read thread failed: %lu", tun->ifname, GetLastError());
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Wintun %s reader started with output backpressure", tun->ifname);
return 0;
}
+7 -1
View File
@@ -108,7 +108,8 @@ check_PROGRAMS += \
test_ll_queue_regressions \
test_standby \
test_standby_transport \
test_tun_route
test_tun_route \
test_tun_if
endif
# Короткие интеграционные проверки (до 1с).
@@ -1018,3 +1019,8 @@ endif
test_reality_tls_SOURCES = test_reality_tls.c
test_reality_tls_LDADD = $(LIBUTUN) -lssl $(COMMON_LIBS)
# TUN: изолированные отказы, проверка fd/памяти и модель Windows post на реальном uasync.
test_tun_if_SOURCES = test_tun_if.c $(async_regression_sources)
test_tun_if_LDADD = $(LIBUTUN) $(COMMON_LIBS)
test_tun_if_LDFLAGS = $(async_regression_ldflags) -Wl,--wrap=tun_platform_init -Wl,--wrap=tun_platform_write -Wl,--wrap=socketpair
+29
View File
@@ -1,6 +1,7 @@
// test_lwip_tcp.c — lwIP TCP stress tests
// ===== 1. Types =====
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include "../src/lwip_tcp/lwip_tcp.h"
@@ -30,6 +31,8 @@ struct tcp_bridge {
struct UASYNC *ua;
struct lwip_tcp_ctx *a, *b;
int drop_pct;
uint16_t mtu_a, mtu_b; // Локальные MTU; 0 сохраняет значение по умолчанию 1500.
unsigned syn_a, syn_b; // Проверенные объявления MSS обоих участников.
struct pkt_node *qa_h, *qa_t;
struct pkt_node *qb_h, *qb_t;
void *pt;
@@ -83,6 +86,17 @@ static err_t bridge_out(void *arg, struct pbuf *p, uint32_t s, uint32_t d, int t
struct tcp_bridge *br = arg;
uint16_t ih = (uint16_t)sizeof(struct ip_hdr);
uint16_t tl = p->tot_len - ih;
uint16_t mtu = to ? br->mtu_a : br->mtu_b;
if (mtu) {
assert(p->tot_len <= mtu);
uint8_t header[44];
assert(pbuf_copy_partial(p, header, sizeof(header), 0) >= 40);
if (header[33] & TCP_SYN) {
assert(p->tot_len >= 44 && header[40] == 2 && header[41] == 4);
assert(((uint16_t)header[42] << 8 | header[43]) == mtu - 40);
if (to) br->syn_a++; else br->syn_b++;
}
}
if (br->drop_pct > 0 && (rand() % 100) < br->drop_pct) {
struct tcp_hdr thdr_buf;
uint32_t seq = 0;
@@ -658,9 +672,19 @@ int main(int argc, char *argv[])
struct tcp_bridge *br = u_calloc(1, sizeof(*br));
br->ua = ua; br->drop_pct = DROP_PCT;
// Асимметричные MTU проверяют локальное объявление и ограничение MSS после SYN пира.
if (argc >= 3) {
br->mtu_a = (uint16_t)strtoul(argv[2], NULL, 10);
br->mtu_b = br->mtu_a < 1500 ? 1500 : 1200;
printf("MTU A=%u B=%u\n", br->mtu_a, br->mtu_b);
}
br->a = lwip_tcp_init(ua, bo_a, br);
br->b = lwip_tcp_init(ua, bo_b, br);
if (!br->a || !br->b) { fprintf(stderr, "[FAIL] init\n"); return 1; }
if (br->mtu_a) {
assert(lwip_tcp_set_mtu(br->a, br->mtu_a) == LERR_OK);
assert(lwip_tcp_set_mtu(br->b, br->mtu_b) == LERR_OK);
}
br->a->trace_id = 'A'; br->b->trace_id = 'B';
lwip_tcp_set_timer(br->a, 10, 50, 200);
lwip_tcp_set_timer(br->b, 10, 50, 200);
@@ -672,6 +696,10 @@ int main(int argc, char *argv[])
br->a = lwip_tcp_init(ua, bo_a, br);
br->b = lwip_tcp_init(ua, bo_b, br);
if (!br->a || !br->b) { fprintf(stderr, "[FAIL] reinit\n"); return 1; }
if (br->mtu_a) {
assert(lwip_tcp_set_mtu(br->a, br->mtu_a) == LERR_OK);
assert(lwip_tcp_set_mtu(br->b, br->mtu_b) == LERR_OK);
}
br->a->trace_id = 'A'; br->b->trace_id = 'B';
lwip_tcp_set_timer(br->a, 10, 50, 200);
lwip_tcp_set_timer(br->b, 10, 50, 200);
@@ -679,6 +707,7 @@ int main(int argc, char *argv[])
int r2 = run_test2(br);
lwip_tcp_destroy(br->a); lwip_tcp_destroy(br->b);
if (br->mtu_a) assert(br->syn_a && br->syn_b);
uasync_destroy(ua, 0); u_free(br);
printf("\n%s\n", (r1 == 0 && r2 == 0) ? "[PASS] test_lwip_tcp" : "[FAIL] test_lwip_tcp");
+17 -45
View File
@@ -6,8 +6,7 @@
* и тестирует полный цикл: client → provider (egress) → provider TUN (internet)
* → provider (ingress) → client (response).
*
* Из-за особенностей test-mode TUN (tun_write → output_queue), на клиентской
* стороне для приёма response используется capture-callback, предотвращая loop.
* Тестовый socketpair сохраняет направления TUN: ответы читаются со стороны ОС.
*/
#include <stdio.h>
@@ -174,21 +173,14 @@ static struct ETCP_LINK* first_initialized_link(struct UTUN_INSTANCE* inst) {
return NULL;
}
// ==================== Capture callback for client TUN ====================
static void capture_tun_out_cb(struct ll_queue* q, void* arg) {
(void)arg;
struct ll_entry* pkt = queue_data_get(q);
if (pkt && pkt->dgram && pkt->len > 1) {
size_t copy = pkt->len - 1;
if (copy > sizeof(test_state.captured) - 1) copy = sizeof(test_state.captured) - 1;
memcpy(test_state.captured, pkt->dgram + 1, copy);
test_state.captured_len = copy;
// Получить ответ со стороны ОС, сохраняя рабочий output callback NAT-клиента.
static void capture_tun_read(struct tun_if* tun) {
ssize_t n = tun_read_packet(tun, test_state.captured, sizeof(test_state.captured));
if (n > 0) {
test_state.captured_len = (size_t)n;
test_state.capture_count++;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "capture_tun: %zu bytes, count=%d", copy, test_state.capture_count);
}
queue_dgram_free(pkt);
queue_entry_free(pkt);
queue_resume_callback(q);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Client OS received %zd IP bytes", n);
} else if (n < 0) DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Client test TUN read failed");
}
// ==================== Tests ====================
@@ -386,13 +378,7 @@ static int test_provider_ingress_response(void) {
struct tun_if* tun = inst_provider->nat_tr.nat_tun;
if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No NAT TUN"); free(resp_ip); return 0; }
// Before injecting, set capture callback on client's TUN to prevent loop
struct tun_if* client_tun = inst_client->nat_tr.nat_tun;
if (client_tun && client_tun->output_queue) {
queue_set_callback(client_tun->output_queue, capture_tun_out_cb, NULL);
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Client TUN callback set to capture");
}
memset(&test_state.captured, 0, sizeof(test_state.captured));
test_state.captured_len = 0;
test_state.capture_count = 0;
@@ -406,6 +392,7 @@ static int test_provider_ingress_response(void) {
int cycles = 0;
while (cycles < 500 && !test_timed_out && test_state.capture_count == 0) {
uasync_poll(ua, 10);
capture_tun_read(client_tun);
cycles++;
}
@@ -417,11 +404,6 @@ static int test_provider_ingress_response(void) {
return 0;
}
// Restore original callback
if (client_tun && client_tun->output_queue) {
queue_set_callback(client_tun->output_queue, NULL, NULL);
}
// Verify the captured response
if (test_state.captured_len < 20) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Captured packet too short: %zu", test_state.captured_len);
@@ -450,9 +432,7 @@ static int test_provider_ingress_response(void) {
}
static int test_full_roundtrip(void) {
/* Combined egress + ingress via manual injection.
Cannot do auto-roundtrip because tun_write in test mode puts to output_queue
instead of real TUN, causing egressed packets to loop back. */
/* Полный путь через client socketpair → ETCP → provider TUN и обратно. */
DEBUG_INFO(DEBUG_CATEGORY_NAT, "=== test_full_roundtrip ===");
struct tun_if* client_tun = inst_client->nat_tr.nat_tun;
@@ -463,20 +443,12 @@ static int test_full_roundtrip(void) {
memset(&inst_provider->nat.table[p], 0, sizeof(struct eim_nat_entry));
inst_provider->nat.next_port = inst_provider->nat.port_start;
// === Step 1: Send ETCP_RT_ID_NAT from client to provider via etcp_router (egress) ===
// === Step 1: ОС клиента отправляет пакет в NAT TUN ===
size_t ip_len;
uint8_t* raw_ip = build_udp_pkt(0x0A0000CD, 44444, 0x08080808, 80, &ip_len);
size_t total = NAT_SVC_HDR_SIZE + ip_len;
uint8_t* dgram = u_malloc(total);
dgram[0] = ETCP_RT_ID_NAT;
memcpy(dgram + 1, raw_ip, ip_len);
ssize_t sent = send(tun_get_test_fd(client_tun), raw_ip, ip_len, 0);
free(raw_ip);
struct ll_entry* entry = queue_entry_new(0);
entry->dgram = dgram;
entry->len = total;
int ret = etcp_route_send(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, entry, 0, 0);
if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed"); queue_dgram_free(entry); queue_entry_free(entry); return 0; }
if (sent != (ssize_t)ip_len) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Client test TUN send failed"); return 0; }
// Poll for provider to process egress
int cycles = 0;
@@ -496,7 +468,6 @@ static int test_full_roundtrip(void) {
e->internal_ip, be16toh(e->internal_port), inst_provider->nat.port_start, e->proto);
// === Step 2: Manually inject internet response into provider's TUN ===
if (client_tun->output_queue) queue_set_callback(client_tun->output_queue, capture_tun_out_cb, NULL);
memset(&test_state.captured, 0, sizeof(test_state.captured));
test_state.captured_len = 0;
test_state.capture_count = 0;
@@ -511,9 +482,10 @@ static int test_full_roundtrip(void) {
free(resp_ip);
cycles = 0;
while (cycles < 500 && !test_timed_out && test_state.capture_count == 0) { uasync_poll(ua, 10); cycles++; }
while (cycles < 500 && !test_timed_out && test_state.capture_count == 0) {
uasync_poll(ua, 10); capture_tun_read(client_tun); cycles++;
}
if (client_tun->output_queue) queue_set_callback(client_tun->output_queue, NULL, NULL);
if (test_state.capture_count == 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No response in roundtrip"); return 0; }
@@ -558,7 +530,7 @@ int main(void) {
g_provider_node_id = inst_provider->node_id; g_client_node_id = inst_client->node_id;
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS, NULL, test_timeout_cb, "test_nat_transport");
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb, "test_nat_transport");
// Wait for ETCP link between client and provider
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Waiting for ETCP link...");
+252
View File
@@ -0,0 +1,252 @@
/* Направления TUN, границы пакетов, backpressure, post lifetime и откат отказов без root. */
#include "async_regression.h"
#include "../src/tun_if.h"
#include "../src/config_parser.h"
#include "../src/proxy/tcp_proxy_client.h"
#include "../src/lwip_tcp/lwip_tcp.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
static int mock_platform, observed_mtu, platform_peer = -1, write_mode, fail_pair, freed;
static const uint8_t ip[20] = {0x45, 0, 0, 20, 0, 0, 0, 0, 64, 17, 0, 0, 10, 0, 0, 1, 10, 0, 0, 2};
int __real_socketpair(int domain, int type, int protocol, int fds[2]);
int __wrap_socketpair(int domain, int type, int protocol, int fds[2]) {
if (fail_pair) { errno = EMFILE; return -1; }
return __real_socketpair(domain, type, protocol, fds);
}
int __real_tun_platform_init(struct tun_if*, const char*, const char*, int);
int __wrap_tun_platform_init(struct tun_if* tun, const char* name, const char* address, int mtu) {
if (!mock_platform) return __real_tun_platform_init(tun, name, address, mtu);
int fds[2];
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) != 0) return -1;
tun->fd = fds[0]; platform_peer = fds[1]; observed_mtu = mtu;
return 0;
}
ssize_t __real_tun_platform_write(struct tun_if*, const uint8_t*, size_t);
ssize_t __wrap_tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
if (write_mode == 1) return (ssize_t)len - 1;
if (write_mode == 2) { errno = EAGAIN; return -1; }
return __real_tun_platform_write(tun, buf, len);
}
static struct tun_if* create(struct UASYNC* ua, int main_tun) {
if (!main_tun) return tun_init_nat(ua, "test_tun", "10.0.0.1", 1500, 1);
struct utun_config config = {0};
config.global.tun_ip.family = AF_INET;
config.global.tun_ip.addr.v4.s_addr = htonl(0x0a000001);
config.global.tun_test_mode = 1;
config.global.mtu = 1280; // MTU ETCP не меняет MTU основного TUN.
return tun_init(ua, &config);
}
static void free_packet(struct ll_entry* e) { queue_dgram_free(e); queue_entry_free(e); }
static void counted_free(uint8_t* data) { freed++; u_free(data); }
static int test_directions(int main_tun) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, main_tun); CHECK(tun);
CHECK_EQ(tun->mtu, 1500);
CHECK(fcntl(tun->fd, F_GETFL) & O_NONBLOCK);
CHECK(fcntl(tun->test_fd, F_GETFL) & O_NONBLOCK);
uint8_t buf[23]; memset(buf, 0xa5, sizeof(buf));
CHECK_EQ(tun_read_packet(tun, buf, 20), 0);
CHECK_EQ(tun_write(tun, ip, sizeof(ip)), 20);
CHECK_EQ(queue_entry_count(tun->output_queue), 0);
CHECK_EQ(tun_read_packet(tun, buf + 1, 19), -1); CHECK_EQ(errno, EMSGSIZE);
CHECK_EQ(tun_read_packet(tun, buf + 1, 21), 20);
CHECK(!memcmp(buf + 1, ip, 20) && buf[0] == 0xa5 && buf[21] == 0xa5 && buf[22] == 0xa5);
CHECK_EQ(tun->bytes_written, 20); CHECK_EQ(tun->packets_written, 1);
struct ll_entry* e = ll_alloc_lldgram(21); CHECK(e);
e->len = 21; e->dgram[0] = 0x77; memcpy(e->dgram + 1, ip, 20);
CHECK_EQ(queue_data_put(tun->input_queue, e), 0);
CHECK_EQ(tun_read_packet(tun, buf, sizeof(buf)), 20); CHECK(!memcmp(buf, ip, 20));
CHECK_EQ(tun->bytes_written, 40); CHECK_EQ(tun->packets_written, 2);
CHECK_EQ(send(tun->test_fd, ip, 20, 0), 20);
uasync_poll(ua, 0);
e = queue_data_get(tun->output_queue); CHECK(e && e->len == 21 && e->dgram[0] == 0);
CHECK(!memcmp(e->dgram + 1, ip, 20)); free_packet(e);
CHECK_EQ(tun->bytes_read, 20); CHECK_EQ(tun->packets_read, 1);
int fd = tun->fd, peer = tun->test_fd;
tun_close(tun);
CHECK_EQ(fcntl(fd, F_GETFD), -1); CHECK_EQ(errno, EBADF);
CHECK_EQ(fcntl(peer, F_GETFD), -1); CHECK_EQ(errno, EBADF);
uasync_poll(ua, 0); uasync_destroy(ua, 0);
return 0;
}
static int test_bad_input(int len) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, 0); CHECK(tun);
struct ll_entry* e = ll_alloc_lldgram(1); CHECK(e);
e->len = len; e->dgram_free_fn = counted_free;
CHECK_EQ(queue_data_put(tun->input_queue, e), 0);
CHECK_EQ(freed, 1); CHECK_EQ(tun->write_errors, 1);
tun_close(tun); uasync_destroy(ua, 0); return 0;
}
static int test_backpressure(int unused) {
(void)unused;
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, 0); CHECK(tun);
for (int i = 0; i < 3; i++) CHECK_EQ(send(tun->test_fd, ip, 20, 0), 20);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) uasync_poll(ua, 0);
CHECK_EQ(queue_entry_count(tun->output_queue), 1);
CHECK_EQ(tun->packets_read, i + 1);
free_packet(queue_data_get(tun->output_queue));
}
CHECK_EQ(tun_inject_packet(tun, ip, 20), 0);
CHECK_EQ(tun_inject_packet(tun, ip, 20), -1); CHECK_EQ(errno, EAGAIN);
tun_close(tun); uasync_poll(ua, 0); uasync_destroy(ua, 0); return 0;
}
static int test_queue_failure(int post) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, 0); CHECK(tun);
queue_set_size_limit(tun->output_queue, 0);
if (post) { CHECK_EQ(tun_post_packet(tun, ip, 20), 0); uasync_poll(ua, 0); }
else CHECK_EQ(tun_inject_packet(tun, ip, 20), -1);
CHECK_EQ(tun->read_errors, 1); CHECK_EQ(tun->packets_read, 0);
tun_close(tun); uasync_destroy(ua, 0); return 0;
}
static void close_post(void* arg) { tun_close(arg); }
static void* publish_thread(void* arg) { return (void*)(intptr_t)tun_post_packet(arg, ip, 20); }
static int test_post_lifetime(int mode) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, 0); CHECK(tun);
if (mode == 2) uasync_post(ua, close_post, tun);
pthread_t thread; void* result;
CHECK_EQ(pthread_create(&thread, NULL, publish_thread, tun), 0);
CHECK_EQ(pthread_join(thread, &result), 0); CHECK_EQ((intptr_t)result, 0);
if (mode == 0) {
CHECK_EQ(tun_post_packet(tun, ip, 20), -1); // Ещё одна задача не накапливается.
uasync_poll(ua, 0); CHECK_EQ(tun->packets_read, 1);
tun_close(tun);
} else if (mode == 2) uasync_poll(ua, 0);
else { tun_close(tun); if (mode == 1) uasync_poll(ua, 0); }
uasync_destroy(ua, 0); return 0;
}
static int test_init_failure(int nth) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
if (nth == -1) fail_pair = 1;
else if (nth == -2) regression_epoll_ctl_fail(EPOLL_CTL_ADD);
else regression_fault_arm(FAULT_ALLOC, NULL, nth);
struct tun_if* tun = create(ua, 0);
regression_fault_reset(); fail_pair = 0;
CHECK(!tun);
uasync_destroy(ua, 0); return 0;
}
static int test_post_oom(int nth) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, 0); CHECK(tun);
regression_fault_arm(FAULT_ALLOC, NULL, nth);
int result = tun_post_packet(tun, ip, 20);
regression_fault_reset(); CHECK_EQ(result, -1); CHECK(!tun->pending_packet);
tun_close(tun); uasync_destroy(ua, 0); return 0;
}
static int test_inject_oom(int nth) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, 0); CHECK(tun);
regression_fault_arm(FAULT_ALLOC, NULL, nth);
int result = tun_inject_packet(tun, ip, 20);
regression_fault_reset(); CHECK_EQ(result, -1); CHECK_EQ(tun->read_errors, 1);
tun_close(tun); uasync_destroy(ua, 0); return 0;
}
static int test_close_waiter(int deferred) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tun_if* tun = create(ua, 0); CHECK(tun);
queue_set_waiter_defer(tun->output_queue, deferred);
CHECK_EQ(send(tun->test_fd, ip, 20, 0), 20);
uasync_poll(ua, 0); CHECK(tun->read_waiter.internal);
if (deferred) {
free_packet(queue_data_get(tun->output_queue));
CHECK(tun->read_waiter.call_soon_id);
}
queue_set_callback_defer(tun->input_queue, 1);
for (int i = 0; i < 2; i++) {
struct ll_entry* e = ll_alloc_lldgram(21); CHECK(e);
e->len = 21; e->dgram[0] = 0; memcpy(e->dgram + 1, ip, 20); e->dgram_free_fn = counted_free;
CHECK_EQ(queue_data_put(tun->input_queue, e), 0);
}
tun_close(tun); CHECK_EQ(freed, 2);
uasync_poll(ua, 0); uasync_destroy(ua, 0); return 0;
}
static int test_platform(int mode) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
mock_platform = 1;
struct tun_if* tun = tun_init_nat(ua, "mock", "10.0.0.1", 1200, 0); CHECK(tun);
CHECK_EQ(observed_mtu, 1200); CHECK_EQ(tun->mtu, 1200);
if (!mode) {
CHECK_EQ(tun_write(tun, ip, 20), 20);
uint8_t buf[20]; CHECK_EQ(recv(platform_peer, buf, sizeof(buf), MSG_DONTWAIT), 20); CHECK(!memcmp(buf, ip, 20));
} else {
write_mode = mode;
CHECK_EQ(tun_write(tun, ip, 20), -1);
CHECK_EQ(tun->write_errors, 1); CHECK_EQ(tun->packets_written, 0); CHECK_EQ(tun->bytes_written, 0);
}
int fd = tun->fd;
tun_close(tun); CHECK_EQ(fcntl(fd, F_GETFD), -1); CHECK_EQ(errno, EBADF);
close(platform_peer); uasync_destroy(ua, 0); return 0;
}
static int test_proxy_output(int mode) {
struct UASYNC* ua = uasync_create(); CHECK(ua);
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "proxy", "10.0.0.1", 1200, 1,
NULL, 0, 0, 0, NULL, 0, NULL); CHECK(p);
CHECK_EQ(p->lwip->local_mss, 1160);
struct pbuf* packet = pbuf_alloc(PBUF_RAW, 20); CHECK(packet); memcpy(packet->payload, ip, 20);
if (mode) {
int size = 4096; CHECK_EQ(setsockopt(p->tun->fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)), 0);
while (send(p->tun->fd, ip, 20, 0) == 20) {}
CHECK_EQ(p->lwip->output(p, packet, 0, 0), LERR_IF);
} else {
CHECK_EQ(p->lwip->output(p, packet, 0, 0), LERR_OK);
uint8_t buf[20]; CHECK_EQ(tun_read_packet(p->tun, buf, sizeof(buf)), 20); CHECK(!memcmp(buf, ip, 20));
CHECK_EQ(p->tun->packets_written, 1);
}
pbuf_free(packet); tcp_proxy_client_destroy(p); uasync_destroy(ua, 0); return 0;
}
static int test_arguments(int unused) {
(void)unused;
struct UASYNC* ua = uasync_create(); CHECK(ua);
CHECK(!tun_init_nat(ua, "test", "invalid", 1500, 1));
CHECK(!tun_init_nat(ua, "test", "10.0.0.1", 1501, 1));
CHECK(!tun_init_nat(ua, "test", "10.0.0.1", 67, 1));
CHECK(!tun_init_nat(ua, "name_that_does_not_fit", "10.0.0.1", 1500, 1));
struct tun_if* tun = tun_init_nat(ua, "small", "10.0.0.1", 68, 1); CHECK(tun);
uint8_t oversized[69] = {0};
CHECK_EQ(tun_write(tun, oversized, sizeof(oversized)), -1);
CHECK_EQ(tun_inject_packet(tun, oversized, sizeof(oversized)), -1);
tun_close(tun); uasync_destroy(ua, 0); return 0;
}
int main(int argc, char** argv) {
const struct regression_case cases[] = {
{"directions/main", test_directions, 1}, {"directions/nat", test_directions, 0},
{"input/zero", test_bad_input, 0}, {"input/prefix-only", test_bad_input, 1},
{"read/backpressure", test_backpressure, 0},
{"queue/rejected-inject", test_queue_failure, 0}, {"queue/rejected-post", test_queue_failure, 1},
{"post/deliver", test_post_lifetime, 0}, {"post/close-before-poll", test_post_lifetime, 1},
{"post/close-in-detached-batch", test_post_lifetime, 2}, {"post/destroy-without-poll", test_post_lifetime, 3},
{"init/socketpair-failure", test_init_failure, -1}, {"init/registration-failure", test_init_failure, -2},
{"init/oom-1", test_init_failure, 1}, {"init/oom-2", test_init_failure, 2},
{"init/oom-3", test_init_failure, 3}, {"init/oom-4", test_init_failure, 4},
{"post/oom-carrier", test_post_oom, 1}, {"post/oom-entry", test_post_oom, 2}, {"post/oom-payload", test_post_oom, 3},
{"inject/oom-entry", test_inject_oom, 1}, {"inject/oom-payload", test_inject_oom, 2},
{"close/queued-waiter", test_close_waiter, 0}, {"close/deferred-waiter", test_close_waiter, 1},
{"platform/mtu-and-write", test_platform, 0}, {"platform/short-write", test_platform, 1},
{"platform/write-error", test_platform, 2}, {"proxy/raw-output", test_proxy_output, 0},
{"proxy/output-error", test_proxy_output, 1}, {"arguments", test_arguments, 0}
};
return regression_run(argc, argv, cases, sizeof(cases) / sizeof(cases[0]));
}