Merge branch 'master' of http://git.syqiq.tech/jeka/utun2
This commit is contained in:
commit
ef673eb3b7
15 files changed
+885
-725
No files matched your search
+14
-2
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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...");
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
Reference in new issue
Block a user