diff --git a/src/lwip_tcp/lwip_tcp.c b/src/lwip_tcp/lwip_tcp.c index 33ddcf53..8f48e058 100644 --- a/src/lwip_tcp/lwip_tcp.c +++ b/src/lwip_tcp/lwip_tcp.c @@ -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; diff --git a/src/lwip_tcp/lwip_tcp.h b/src/lwip_tcp/lwip_tcp.h index a01f9de7..d7a8adab 100644 --- a/src/lwip_tcp/lwip_tcp.h +++ b/src/lwip_tcp/lwip_tcp.h @@ -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); diff --git a/src/lwip_tcp/lwip_tcp_in.c b/src/lwip_tcp/lwip_tcp_in.c index 1ac942db..83284e5a 100644 --- a/src/lwip_tcp/lwip_tcp_in.c +++ b/src/lwip_tcp/lwip_tcp_in.c @@ -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(); diff --git a/src/lwip_tcp/lwip_tcp_out.c b/src/lwip_tcp/lwip_tcp_out.c index 28e67fbb..ca6d6b4d 100644 --- a/src/lwip_tcp/lwip_tcp_out.c +++ b/src/lwip_tcp/lwip_tcp_out.c @@ -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; } diff --git a/src/nat_transport.c b/src/nat_transport.c index 163373c9..5157b04c 100644 --- a/src/nat_transport.c +++ b/src/nat_transport.c @@ -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); diff --git a/src/proxy/tcp_proxy_client.c b/src/proxy/tcp_proxy_client.c index 5b2eca9c..caef252c 100644 --- a/src/proxy/tcp_proxy_client.c +++ b/src/proxy/tcp_proxy_client.c @@ -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); diff --git a/src/tun_freebsd.c b/src/tun_freebsd.c index 47f2daed..48a8d20c 100644 --- a/src/tun_freebsd.c +++ b/src/tun_freebsd.c @@ -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 diff --git a/src/tun_if.c b/src/tun_if.c index ab49d240..1593346d 100644 --- a/src/tun_if.c +++ b/src/tun_if.c @@ -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 -#include -#include -#include -#ifndef _WIN32 -#include -#endif #include "../lib/mem.h" +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#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 -#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 } diff --git a/src/tun_if.h b/src/tun_if.h index 49d5ea63..d3c65b5c 100644 --- a/src/tun_if.h +++ b/src/tun_if.h @@ -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 #include +#include #include +#include +#include "../lib/ll_queue.h" -#ifdef _WIN32 -#include // для 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 diff --git a/src/tun_if_doc.md b/src/tun_if_doc.md index f87225e1..732cea5b 100644 --- a/src/tun_if_doc.md +++ b/src/tun_if_doc.md @@ -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. diff --git a/src/tun_windows.c b/src/tun_windows.c index f624e7aa..9ac27a18 100644 --- a/src/tun_windows.c +++ b/src/tun_windows.c @@ -13,6 +13,7 @@ #include #include #include +#include #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; } diff --git a/tests/Makefile.am b/tests/Makefile.am index c801c4e3..722c1ba0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 diff --git a/tests/test_lwip_tcp.c b/tests/test_lwip_tcp.c index 7e4d36c2..21bb5469 100644 --- a/tests/test_lwip_tcp.c +++ b/tests/test_lwip_tcp.c @@ -1,6 +1,7 @@ // test_lwip_tcp.c — lwIP TCP stress tests // ===== 1. Types ===== #include +#include #include #include #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"); diff --git a/tests/test_nat_transport.c b/tests/test_nat_transport.c index 057f0046..72b79925 100644 --- a/tests/test_nat_transport.c +++ b/tests/test_nat_transport.c @@ -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 @@ -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..."); diff --git a/tests/test_tun_if.c b/tests/test_tun_if.c new file mode 100644 index 00000000..e3f7d361 --- /dev/null +++ b/tests/test_tun_if.c @@ -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 +#include +#include +#include +#include + +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])); +}