From d10bddd1f7e56826f41e57b2810a4254b5697aaa Mon Sep 17 00:00:00 2001 From: jeka Date: Fri, 20 Feb 2026 21:20:50 +0300 Subject: [PATCH] Build: incremental build support, Windows uasync fixes, TUN cross-platform refactoring - build.sh: added --full flag for full rebuild (autoreconf + configure + make) - build_direct.sh: incremental compilation (skips unchanged files) - build_full.bat: new Windows batch for full rebuild - configure.ac: fixed Windows detection using AC_COMPILE_IFELSE - lib/u_async.c/h: Windows wakeup socket support, uasync_post for thread-safe callbacks - src/tun_if.c/h: cross-platform TUN refactoring, Windows read thread support - src/tun_windows.c: Windows TUN implementation improvements - src/control_server.c: removed premature return statement - src/routing.c: include ordering fix - src/utun.c: windows compat includes - Deleted obsolete net_emulator/Makefile and tinycrypt/Makefile --- build.sh | 82 ++++++-- build_direct.sh | 91 +++++++-- build_full.bat | 31 +++ configure.ac | 104 ++++------ lib/u_async.c | 293 ++++++++++++++++++++++----- lib/u_async.h | 16 ++ net_emulator/Makefile | 25 --- src/control_server.c | 2 +- src/routing.c | 2 +- src/tun_if.c | 451 ++++++++++++++++------------------------- src/tun_if.h | 165 ++++++--------- src/tun_windows.c | 452 +++++++++++++++++++----------------------- src/utun.c | 7 +- tests/Makefile.am | 15 +- tinycrypt/Makefile | 21 -- 15 files changed, 896 insertions(+), 861 deletions(-) create mode 100644 build_full.bat delete mode 100644 net_emulator/Makefile delete mode 100644 tinycrypt/Makefile diff --git a/build.sh b/build.sh index d7feb6c..c838def 100644 --- a/build.sh +++ b/build.sh @@ -4,6 +4,7 @@ # Options: # -h, --help Show this help # --clean Clean before build +# --full Full rebuild (autoreconf + configure + make) # -j[N] Build with N parallel jobs (default: 4) set -e # Exit on error @@ -19,6 +20,7 @@ fi # Parse arguments CLEAN=0 +FULL_REBUILD=0 JOBS="-j4" MAKE_ARGS="" @@ -29,6 +31,7 @@ while [[ $# -gt 0 ]]; do echo "Options:" echo " -h, --help Show this help" echo " --clean Clean before build" + echo " --full Full rebuild (autoreconf + configure + make)" echo " -j[N] Build with N parallel jobs (default: 4)" exit 0 ;; @@ -36,6 +39,10 @@ while [[ $# -gt 0 ]]; do CLEAN=1 shift ;; + --full) + FULL_REBUILD=1 + shift + ;; -j*) JOBS="$1" shift @@ -65,35 +72,72 @@ if [ "$CLEAN" -eq 1 ]; then rm -f config.status config.h fi -# On Windows, use direct build to avoid make/autotools issues -if [ "$IS_WINDOWS" -eq 1 ]; then +# Full rebuild: clean everything and regenerate +if [ "$FULL_REBUILD" -eq 1 ]; then echo "" - echo "Using direct build for Windows (bypassing make)..." - bash build_direct.sh - exit 0 -fi - -# Linux: use standard autotools build -# Check if configure script exists -if [ ! -f ./configure ]; then - echo "Configure script not found. Generating..." + echo "======================================" + echo "FULL REBUILD: autoreconf + configure" + echo "======================================" + echo "" + + # Check for autotools if ! command -v autoreconf &> /dev/null; then echo "Error: autoreconf not found." - echo "Install autotools: apt-get install autoconf automake libtool" + if [ "$IS_WINDOWS" -eq 1 ]; then + echo "Install autotools: pacman -S mingw-w64-ucrt-x86_64-autotools" + else + echo "Install autotools: apt-get install autoconf automake libtool" + fi exit 1 fi - autoreconf -fiv -fi - -# Run configure if needed -if [ ! -f config.status ] || [ config.status -ot configure ]; then - echo "Configuring build..." + + # Clean generated files + echo "Cleaning generated files..." + make distclean 2>/dev/null || true + rm -f config.status config.h Makefile */Makefile + + # Regenerate build system + echo "" + echo "Running autoreconf -fi..." + autoreconf -fi 2>&1 | tee autoreconf.log + + # Configure + echo "" + echo "Running configure..." ./configure --prefix=/usr/local 2>&1 | tee configure.log + + echo "" + echo "======================================" + echo "Build system regenerated successfully" + echo "======================================" + echo "" else - echo "Using existing configuration." + # Quick/incremental build: only configure if needed + if [ ! -f ./configure ]; then + echo "Configure script not found. Generating..." + if ! command -v autoreconf &> /dev/null; then + echo "Error: autoreconf not found." + if [ "$IS_WINDOWS" -eq 1 ]; then + echo "Install autotools: pacman -S mingw-w64-ucrt-x86_64-autotools" + else + echo "Install autotools: apt-get install autoconf automake libtool" + fi + exit 1 + fi + autoreconf -fiv + fi + + # Run configure if needed + if [ ! -f config.status ] || [ config.status -ot configure ]; then + echo "Configuring build..." + ./configure --prefix=/usr/local 2>&1 | tee configure.log + else + echo "Using existing configuration." + fi fi # Build +echo "" echo "Compiling with $JOBS..." BUILD_SUCCESS=0 make $JOBS $MAKE_ARGS 2>&1 | tee build_linux.log diff --git a/build_direct.sh b/build_direct.sh index eaf1dd8..4ec5570 100644 --- a/build_direct.sh +++ b/build_direct.sh @@ -1,14 +1,14 @@ #!/bin/bash # Direct build script for uTun on Windows with OpenSSL -# This script bypasses the complex autotools make dependencies +# Инкрементальная сборка (не пересобирает неизменённые файлы) set -e -echo "uTun Direct Build Script for Windows with OpenSSL" -echo "==================================================" +echo "uTun Direct Build Script for Windows with OpenSSL (Incremental)" +echo "==============================================================" echo "" -# Detect MSYS2 environment and set OpenSSL paths +# ====================== OpenSSL paths ====================== if [ -d "/c/msys64/ucrt64/include/openssl" ]; then export CFLAGS="$CFLAGS -I/c/msys64/ucrt64/include" export LDFLAGS="$LDFLAGS -L/c/msys64/ucrt64/lib" @@ -23,57 +23,104 @@ elif [ -d "/mingw64/include/openssl" ]; then echo "Using MSYS2 MINGW64 OpenSSL" fi -# Run configure if needed +# ====================== Configure (только если нужно) ====================== if [ ! -f config.status ] || [ config.status -ot configure ]; then echo "" echo "Running configure..." ./configure --prefix=/usr/local --host=x86_64-w64-mingw32 --disable-dependency-tracking 2>&1 | tee configure.log fi -# Create necessary directories +# ====================== Directories ====================== mkdir -p src/.deps lib/.deps tests/.deps +# ====================== Build libuasync.a (incremental) ====================== echo "" echo "Building libuasync.a..." + cd lib +LIB_NEEDS_REBUILD=0 -# Build library objects for src in u_async.c ll_queue.c memory_pool.c debug_config.c timeout_heap.c sha256.c platform_compat.c socket_compat.c; do if [ -f "$src" ]; then obj="${src%.c}.o" - echo " CC $src" - x86_64-w64-mingw32-gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -c "$src" -o "$obj" 2>&1 | grep -v "warning:" || true + if [ ! -f "$obj" ] || [ "$src" -nt "$obj" ]; then + echo " CC $src" + x86_64-w64-mingw32-gcc -DHAVE_CONFIG_H -I. -I.. -g -O2 -c "$src" -o "$obj" 2>&1 | grep -v "warning:" || true + LIB_NEEDS_REBUILD=1 + else + echo " SKIPPED $src (up to date)" + fi fi done -# Create static library -ar cru libuasync.a *.o -ranlib libuasync.a +# Пересобираем библиотеку только если что-то изменилось +if [ ! -f "libuasync.a" ] || [ $LIB_NEEDS_REBUILD -eq 1 ] || [ -n "$(find . -name '*.o' -newer libuasync.a 2>/dev/null)" ]; then + echo " AR libuasync.a" + ar cru libuasync.a *.o + ranlib libuasync.a +else + echo " SKIPPED libuasync.a (up to date)" +fi cd .. +# ====================== Build utun.exe (incremental) ====================== echo "" echo "Building utun.exe..." + cd src +EXE_NEEDS_REBUILD=0 -# Build source objects CORE_SOURCES="utun.c utun_instance.c config_parser.c config_updater.c route_lib.c route_bgp.c routing.c tun_if.c tun_route.c etcp.c etcp_connections.c etcp_loadbalancer.c secure_channel.c crc32.c pkt_normalizer.c etcp_api.c tun_windows.c" for src in $CORE_SOURCES; do - obj="utun-${src%.c}.o" - echo " CC $src" - x86_64-w64-mingw32-gcc -DHAVE_CONFIG_H -I. -I.. -I../lib -I../tinycrypt/lib/include -I../tinycrypt/lib/source $CFLAGS -g -O2 -c "$src" -o "$obj" 2>&1 | grep -E "error:|warning:.*deprecated" || true + if [ -f "$src" ]; then + obj="utun-${src%.c}.o" + if [ ! -f "$obj" ] || [ "$src" -nt "$obj" ]; then + echo " CC $src" + x86_64-w64-mingw32-gcc -DHAVE_CONFIG_H -I. -I.. -I../lib -I../tinycrypt/lib/include -I../tinycrypt/lib/source $CFLAGS -g -O2 -c "$src" -o "$obj" 2>&1 | grep -E "error:|warning:.*deprecated" || true + EXE_NEEDS_REBUILD=1 + else + echo " SKIPPED $src (up to date)" + fi + else + echo " WARNING: $src not found!" + fi done -# Link executable -echo " LINK utun.exe" -x86_64-w64-mingw32-gcc -o utun.exe utun-*.o ../lib/libuasync.a -lpthread -lm -lws2_32 -liphlpapi -lbcrypt $LDFLAGS -lcrypto 2>&1 | grep -E "error:|undefined reference" || true +# Проверяем, нужно ли линковать +LINK_NEEDED=0 +if [ ! -f "utun.exe" ] || [ $EXE_NEEDS_REBUILD -eq 1 ]; then + LINK_NEEDED=1 +else + # Проверяем все объектники и библиотеку + for obj in utun-*.o; do + if [ -f "$obj" ] && [ "$obj" -nt "utun.exe" ]; then + LINK_NEEDED=1 + break + fi + done + if [ -f "../lib/libuasync.a" ] && [ "../lib/libuasync.a" -nt "utun.exe" ]; then + LINK_NEEDED=1 + fi +fi + +if [ $LINK_NEEDED -eq 1 ]; then + echo " LINK utun.exe" + x86_64-w64-mingw32-gcc -o utun.exe utun-*.o ../lib/libuasync.a -lpthread -lm -lws2_32 -liphlpapi -lbcrypt $LDFLAGS -lcrypto 2>&1 | grep -E "error:|undefined reference" || true +else + echo " SKIPPED LINK utun.exe (up to date)" +fi + +# Копируем в корень проекта +if [ -f "utun.exe" ]; then + cp utun.exe ../utun.exe + echo "Binary updated: ../utun.exe" +fi -# Copy to root -cp utun.exe ../utun.exe cd .. echo "" -echo "Build completed successfully!" +echo "Build completed successfully! ✅" echo "Binary: utun.exe" echo "" echo "Make sure wintun.dll is in the same directory as utun.exe" diff --git a/build_full.bat b/build_full.bat new file mode 100644 index 0000000..5511329 --- /dev/null +++ b/build_full.bat @@ -0,0 +1,31 @@ +@echo off +echo uTun Full Build Script for MSYS2 UCRT64 +echo ========================================= +echo This will regenerate build system with autoreconf +echo. + +rem export http_proxy="http://192.168.29.1:9999" +rem export https_proxy="http://192.168.29.1:9999" + + +REM Default MSYS2 path +set MSYS2_PATH=C:\msys64 + +REM Use environment variable if set +if not "%MSYS2_ROOT%"=="" set MSYS2_PATH=%MSYS2_ROOT% +if not "%MSYS2%"=="" set MSYS2_PATH=%MSYS2% + +REM Check if msys2_shell.cmd exists +if not exist "%MSYS2_PATH%\msys2_shell.cmd" ( + echo MSYS2 not found at %MSYS2_PATH% + echo Please install MSYS2 from https://www.msys2.org/ + echo or set MSYS2_ROOT environment variable. + pause + exit /b 1 +) + +REM Launch MSYS2 UCRT64 shell and run build script with --full flag +echo Starting MSYS2 UCRT64 environment for FULL REBUILD... +echo Running: bash build.sh --full +echo. +call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "bash build.sh --full 2>&1 | tee build_full.log" diff --git a/configure.ac b/configure.ac index f0d6ea5..a1d054d 100644 --- a/configure.ac +++ b/configure.ac @@ -4,93 +4,72 @@ AC_CONFIG_HEADERS([config.h]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) +# === ОБЯЗАТЕЛЬНО для правильного определения host === +AC_CANONICAL_HOST +AC_CANONICAL_BUILD + # Required programs AC_PROG_CC AC_PROG_RANLIB AM_PROG_AR AM_INIT_AUTOMAKE([foreign subdir-objects -Wall]) -# Checks for programs AC_PROG_INSTALL AC_PROG_MAKE_SET -# Option to use OpenSSL instead of TinyCrypt for secure_channel +# Option to use OpenSSL AC_ARG_WITH([openssl], AS_HELP_STRING([--with-openssl], [Use OpenSSL for cryptography (default: yes)]), [with_openssl=$withval], [with_openssl=yes]) if test "x$with_openssl" = "xyes"; then - # For Windows cross-compilation, check MSYS2 paths + # MSYS2 OpenSSL paths (для Windows) case "$host" in *mingw* | *msys* | *cygwin* | *win*) - # Try MSYS2 UCRT64 paths if test -d "/c/msys64/ucrt64/include/openssl"; then OPENSSL_CFLAGS="-I/c/msys64/ucrt64/include" OPENSSL_LIBS="-L/c/msys64/ucrt64/lib -lcrypto" - CFLAGS="$CFLAGS $OPENSSL_CFLAGS" - LDFLAGS="$LDFLAGS $OPENSSL_LIBS" - AC_MSG_NOTICE([Using MSYS2 UCRT64 OpenSSL: /c/msys64/ucrt64]) + AC_MSG_NOTICE([Using MSYS2 UCRT64 OpenSSL]) elif test -d "/ucrt64/include/openssl"; then OPENSSL_CFLAGS="-I/ucrt64/include" OPENSSL_LIBS="-L/ucrt64/lib -lcrypto" - CFLAGS="$CFLAGS $OPENSSL_CFLAGS" - LDFLAGS="$LDFLAGS $OPENSSL_LIBS" - AC_MSG_NOTICE([Using MSYS2 UCRT64 OpenSSL: /ucrt64]) + AC_MSG_NOTICE([Using MSYS2 UCRT64 OpenSSL]) elif test -d "/mingw64/include/openssl"; then OPENSSL_CFLAGS="-I/mingw64/include" OPENSSL_LIBS="-L/mingw64/lib -lcrypto" - CFLAGS="$CFLAGS $OPENSSL_CFLAGS" - LDFLAGS="$LDFLAGS $OPENSSL_LIBS" - AC_MSG_NOTICE([Using MSYS2 MINGW64 OpenSSL: /mingw64]) + AC_MSG_NOTICE([Using MSYS2 MINGW64 OpenSSL]) fi + CFLAGS="$CFLAGS $OPENSSL_CFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LIBS" ;; esac - - # Save original LIBS + save_LIBS="$LIBS" - # Add crypto library to LIBS for the check LIBS="-lcrypto $LIBS" - - AC_CHECK_LIB([crypto], [SHA256_Init], - [ - AC_DEFINE([USE_OPENSSL], [1], [Define to use OpenSSL for cryptography]) - ], - [ - AC_MSG_ERROR([OpenSSL crypto library required. Use --without-openssl to use TinyCrypt instead.]) - ]) - - # Restore LIBS (the actual linking will use the flags we set) + AC_CHECK_LIB([crypto], [SHA256_Init], + [AC_DEFINE([USE_OPENSSL], [1], [Define to use OpenSSL for cryptography])], + [AC_MSG_ERROR([OpenSSL crypto library required. Use --without-openssl to use TinyCrypt.])]) LIBS="$save_LIBS" fi AM_CONDITIONAL([USE_OPENSSL], [test "x$with_openssl" = "xyes"]) -# Detect Windows for TUN implementation +# ==================== НАДЁЖНЫЙ ДЕТЕКТ WINDOWS ==================== AC_MSG_CHECKING([for Windows]) -case $host_os in - *mingw* | *msys* | *cygwin* | *win*) - os_windows=yes - ;; - *) - # Also check $host for cross-compilation case - case $host in - *mingw* | *msys* | *cygwin* | *win*) - os_windows=yes - ;; - *) - os_windows=no - ;; - esac - ;; -esac +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#ifndef _WIN32 +#error "Not Windows" +#endif +]], [[ return 0; ]])], + [os_windows=yes], + [os_windows=no]) AC_MSG_RESULT([$os_windows]) + AM_CONDITIONAL([OS_WINDOWS], [test "x$os_windows" = "xyes"]) -# Checks for header files +# Checks for header files, typedefs, functions... AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/socket.h unistd.h]) - -# Checks for typedefs, structures, and compiler characteristics AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT16_T @@ -99,41 +78,26 @@ AC_TYPE_UINT64_T AC_TYPE_UINT8_T AC_C_INLINE -# Checks for library functions AC_FUNC_MALLOC AC_CHECK_FUNCS([gettimeofday memset socket strchr strdup strerror strstr]) -# Check for TUN/TAP support +# TUN/TAP (только для Linux) AC_MSG_CHECKING([for TUN/TAP support]) -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#include -]], [[ -int fd = open("/dev/net/tun", O_RDWR); -]])], [ - AC_MSG_RESULT([yes]) - AC_DEFINE([HAVE_TUN_TAP], [1], [Define if TUN/TAP is available]) -], [ - AC_MSG_RESULT([no]) -]) - -# Output files -AC_CONFIG_FILES([ - Makefile - src/Makefile - tests/Makefile - lib/Makefile -]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int fd = open("/dev/net/tun", O_RDWR);]])], + [AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_TUN_TAP], [1], [Define if TUN/TAP is available])], + [AC_MSG_RESULT([no])]) +# Output +AC_CONFIG_FILES([Makefile src/Makefile tests/Makefile lib/Makefile]) AC_OUTPUT # Summary echo " Configuration summary: ---------------------- + Host: ${host} + Windows: ${os_windows} Prefix: ${prefix} Compiler: ${CC} CFLAGS: ${CFLAGS} - - Features: - TUN/TAP: ${have_tun_tap} -" +" \ No newline at end of file diff --git a/lib/u_async.c b/lib/u_async.c index bce7ad6..5798200 100644 --- a/lib/u_async.c +++ b/lib/u_async.c @@ -8,6 +8,13 @@ #include #include #include +#include + +#ifndef _WIN32 +#include +#else +#include +#endif // Platform-specific includes #ifdef __linux__ @@ -276,7 +283,22 @@ static void timeout_node_free_callback(void* user_data, void* data) { // Helper to get current time static void get_current_time(struct timeval* tv) { +#ifdef _WIN32 + // Для Windows используем GetTickCount или другие механизмы + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + ULARGE_INTEGER ul; + ul.LowPart = ft.dwLowDateTime; + ul.HighPart = ft.dwHighDateTime; + + // Конвертируем 100-наносекундные интервалы в секунды и микросекунды + tv->tv_sec = (long)((ul.QuadPart - 116444736000000000LL) / 10000000); + tv->tv_usec = (long)((ul.QuadPart % 10000000) / 10); +#else + // Для Linux и других Unix-подобных систем используем gettimeofday gettimeofday(tv, NULL); +#endif } @@ -292,6 +314,55 @@ static void drain_wakeup_pipe(struct UASYNC* ua) { } } +// Process posted tasks (lock-free during execution) +static void process_posted_tasks(struct UASYNC* ua) { + if (!ua) return; + + struct posted_task* list = NULL; + +#ifdef _WIN32 + EnterCriticalSection(&ua->posted_lock); +#else + pthread_mutex_lock(&ua->posted_lock); +#endif + list = ua->posted_tasks_head; + ua->posted_tasks_head = NULL; +#ifdef _WIN32 + LeaveCriticalSection(&ua->posted_lock); +#else + pthread_mutex_unlock(&ua->posted_lock); +#endif + + while (list) { + struct posted_task* t = list; + list = list->next; + + if (t->callback) { + t->callback(t->arg); + } + free(t); + } +} + +// Unified wakeup handler (drain + execute posted callbacks) +static void handle_wakeup(struct UASYNC* ua) { + if (!ua || !ua->wakeup_initialized) return; + + // Drain the wakeup pipe/socket +#ifdef _WIN32 + char buf[64]; + SOCKET s = (SOCKET)(intptr_t)ua->wakeup_pipe[0]; + while (recv(s, buf, sizeof(buf), 0) > 0) {} +#else + char buf[64]; + while (read(ua->wakeup_pipe[0], buf, sizeof(buf)) > 0) {} +#endif + + // Execute all posted callbacks (in main thread) + process_posted_tasks(ua); +} + + // Helper to add timeval: tv += dt (timebase units) static void timeval_add_tb(struct timeval* tv, int dt) { tv->tv_usec += (dt % 10000) * 100; @@ -978,73 +1049,137 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) { } +// Put this near the top of u_async.c, after includes and before uasync_create + + +#ifdef _WIN32 +static void wakeup_read_callback_win(socket_t sock, void* arg) { + (void)sock; // не нужен + handle_wakeup((struct UASYNC*)arg); +} +#else +static void wakeup_read_callback_posix(int fd, void* arg) { + (void)fd; + handle_wakeup((struct UASYNC*)arg); +} +#endif + // ========== Instance management functions ========== +// Modified function in u_async.c: uasync_create +// Changes: Use self-connected UDP socket for wakeup on Windows instead of pipe. +// This ensures the wakeup is a selectable SOCKET. + struct UASYNC* uasync_create(void) { - // Initialize socket platform first (WSAStartup on Windows) - if (socket_platform_init() != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "Failed to initialize socket platform"); - return NULL; - } + struct UASYNC* ua = calloc(1, sizeof(struct UASYNC)); + if (!ua) return NULL; + + ua->timer_alloc_count = 0; + ua->timer_free_count = 0; + ua->socket_alloc_count = 0; + ua->socket_free_count = 0; + + ua->poll_fds = NULL; + ua->poll_fds_capacity = 0; + ua->poll_fds_count = 0; + ua->poll_fds_dirty = 1; - struct UASYNC* ua = malloc(sizeof(struct UASYNC)); - if (!ua) { - socket_platform_cleanup(); - return NULL; - } - - memset(ua, 0, sizeof(struct UASYNC)); ua->wakeup_pipe[0] = -1; ua->wakeup_pipe[1] = -1; ua->wakeup_initialized = 0; - - // Create wakeup pipe (skip on Windows - pipes don't work with poll/WSAPoll) -#ifndef _WIN32 - if (pipe(ua->wakeup_pipe) < 0) { - DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup pipe: %s", strerror(errno)); - // Continue without wakeup mechanism - ua->wakeup_pipe[0] = -1; - ua->wakeup_pipe[1] = -1; - } else { - ua->wakeup_initialized = 1; - // Set non-blocking on read end to avoid blocking if pipe is full - int flags = fcntl(ua->wakeup_pipe[0], F_GETFL, 0); - if (flags >= 0) { - fcntl(ua->wakeup_pipe[0], F_SETFL, flags | O_NONBLOCK); - } + ua->posted_tasks_head = NULL; + +#ifdef _WIN32 + // Windows: self-connected UDP socket for wakeup + SOCKET r = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SOCKET w = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (r == INVALID_SOCKET || w == INVALID_SOCKET) { + DEBUG_ERROR(DEBUG_CATEGORY_UASYNC, "Failed to create wakeup sockets"); + free(ua); + return NULL; } + + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind(r, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR || + getsockname(r, (struct sockaddr*)&addr, &(int){sizeof(addr)}) == SOCKET_ERROR || + connect(w, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(r); + closesocket(w); + DEBUG_ERROR(DEBUG_CATEGORY_UASYNC, "Wakeup socket setup failed: %d", WSAGetLastError()); + free(ua); + return NULL; + } + + ua->wakeup_pipe[0] = (int)(intptr_t)r; + ua->wakeup_pipe[1] = (int)(intptr_t)w; + ua->wakeup_initialized = 1; + + u_long mode = 1; + ioctlsocket(r, FIONBIO, &mode); + + // Register the read socket with uasync + uasync_add_socket_t(ua, r, wakeup_read_callback_win, NULL, NULL, ua); // ← ua как user_data +// uasync_add_socket_t(ua, r, wakeup_read_callback_win, NULL, NULL, NULL); + InitializeCriticalSection(&ua->posted_lock); + #else - // On Windows, pipes don't work with WSAPoll - skip wakeup mechanism - ua->wakeup_pipe[0] = -1; - ua->wakeup_pipe[1] = -1; - ua->wakeup_initialized = 0; + // POSIX pipe + if (pipe(ua->wakeup_pipe) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_UASYNC, "pipe() failed: %s", strerror(errno)); + free(ua); + return NULL; + } + ua->wakeup_initialized = 1; + + fcntl(ua->wakeup_pipe[0], F_SETFL, fcntl(ua->wakeup_pipe[0], F_GETFL, 0) | O_NONBLOCK); + fcntl(ua->wakeup_pipe[1], F_SETFL, fcntl(ua->wakeup_pipe[1], F_GETFL, 0) | O_NONBLOCK); + + if (!ua->use_epoll) { + uasync_add_socket(ua, ua->wakeup_pipe[0], wakeup_read_callback_posix, NULL, NULL, ua); // ← ua + } + pthread_mutex_init(&ua->posted_lock, NULL); + #endif - + ua->sockets = socket_array_create(16); if (!ua->sockets) { if (ua->wakeup_initialized) { +#ifdef _WIN32 + closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[0]); + closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[1]); +#else close(ua->wakeup_pipe[0]); close(ua->wakeup_pipe[1]); +#endif } free(ua); return NULL; } - + ua->timeout_heap = timeout_heap_create(16); if (!ua->timeout_heap) { socket_array_destroy(ua->sockets); if (ua->wakeup_initialized) { +#ifdef _WIN32 + closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[0]); + closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[1]); +#else close(ua->wakeup_pipe[0]); close(ua->wakeup_pipe[1]); +#endif } free(ua); return NULL; } - + // Set callback to free timeout nodes and update counters timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback); - + // Initialize epoll on Linux ua->epoll_fd = -1; ua->use_epoll = 0; @@ -1066,7 +1201,7 @@ struct UASYNC* uasync_create(void) { DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to create epoll fd, falling back to poll: %s", strerror(errno)); } #endif - + return ua; } @@ -1119,14 +1254,17 @@ void uasync_print_resources(struct UASYNC* ua, const char* prefix) { printf("🔚 %s: End of resource report\n\n", prefix); } +// Modified function in u_async.c: uasync_destroy +// Changes: Close wakeup sockets properly on Windows. + void uasync_destroy(struct UASYNC* ua, int close_fds) { if (!ua) return; - + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua); - + // Диагностика ресурсов перед очисткой uasync_print_resources(ua, "BEFORE_DESTROY"); - + // Check for potential memory leaks if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu", @@ -1139,7 +1277,7 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) { (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); // Continue cleanup, will abort after if leaks remain } - + // Free all remaining timeouts if (ua->timeout_heap) { size_t freed_count = 0; @@ -1147,7 +1285,7 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) { TimeoutEntry entry; if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break; struct timeout_node* node = (struct timeout_node*)entry.data; - + // Free all timer nodes (avoid double-free bug) if (node) { ua->timer_free_count++; @@ -1156,7 +1294,7 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) { } timeout_heap_destroy(ua->timeout_heap); } - + // Free all socket nodes using array approach if (ua->sockets) { // Count and free all active sockets @@ -1164,7 +1302,15 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) { for (int i = 0; i < ua->sockets->capacity; i++) { if (ua->sockets->sockets[i].active) { if (close_fds && ua->sockets->sockets[i].fd >= 0) { +#ifdef _WIN32 + if (ua->sockets->sockets[i].type == SOCKET_NODE_TYPE_SOCK) { + closesocket(ua->sockets->sockets[i].sock); + } else { + close(ua->sockets->sockets[i].fd); // For pipes/FDs + } +#else close(ua->sockets->sockets[i].fd); +#endif } ua->socket_free_count++; freed_count++; @@ -1173,23 +1319,28 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) { DEBUG_DEBUG(DEBUG_CATEGORY_MEMORY, "Freed %d socket nodes in destroy", freed_count); socket_array_destroy(ua->sockets); } - - // Close wakeup pipe + + // Close wakeup pipe/sockets if (ua->wakeup_initialized) { +#ifdef _WIN32 + closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[0]); + closesocket((SOCKET)(intptr_t)ua->wakeup_pipe[1]); +#else close(ua->wakeup_pipe[0]); close(ua->wakeup_pipe[1]); +#endif } - + // Free cached poll_fds free(ua->poll_fds); - + // Close epoll fd on Linux #if HAS_EPOLL if (ua->epoll_fd >= 0) { close(ua->epoll_fd); } #endif - + // Final leak check if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) { DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected after cleanup: timers %zu/%zu, sockets %zu/%zu", @@ -1202,10 +1353,22 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) { (ssize_t)(ua->socket_alloc_count - ua->socket_free_count)); abort(); } - + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: completed successfully for ua=%p", ua); + + while (ua->posted_tasks_head) { + struct posted_task* t = ua->posted_tasks_head; + ua->posted_tasks_head = t->next; + free(t); + } +#ifdef _WIN32 + DeleteCriticalSection(&ua->posted_lock); +#else + pthread_mutex_destroy(&ua->posted_lock); +#endif + free(ua); - + // Cleanup socket platform (WSACleanup on Windows) socket_platform_cleanup(); } @@ -1235,14 +1398,42 @@ void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free if (socket_free) *socket_free = ua->socket_free_count; } -// Get global instance for backward compatibility +void uasync_post(struct UASYNC* ua, uasync_post_callback_t callback, void* arg) { + if (!ua || !callback) return; + + struct posted_task* task = malloc(sizeof(struct posted_task)); + if (!task) return; + + task->callback = callback; + task->arg = arg; + task->next = NULL; + +#ifdef _WIN32 + EnterCriticalSection(&ua->posted_lock); +#else + pthread_mutex_lock(&ua->posted_lock); +#endif + task->next = ua->posted_tasks_head; + ua->posted_tasks_head = task; +#ifdef _WIN32 + LeaveCriticalSection(&ua->posted_lock); +#else + pthread_mutex_unlock(&ua->posted_lock); +#endif + + uasync_wakeup(ua); // будим mainloop +} // Wakeup mechanism int uasync_wakeup(struct UASYNC* ua) { if (!ua || !ua->wakeup_initialized) return -1; - + char byte = 0; +#ifdef _WIN32 + int ret = send((SOCKET)(intptr_t)ua->wakeup_pipe[1], &byte, 1, 0); +#else ssize_t ret = write(ua->wakeup_pipe[1], &byte, 1); +#endif if (ret != 1) { // Don't print error from signal handler return -1; diff --git a/lib/u_async.h b/lib/u_async.h index 9025558..b49cea7 100644 --- a/lib/u_async.h +++ b/lib/u_async.h @@ -25,6 +25,8 @@ typedef int err_t; #define SOCKET_NODE_TYPE_FD 0 // Regular file descriptor (pipe, file) #define SOCKET_NODE_TYPE_SOCK 1 // Socket (socket_t) +typedef void (*uasync_post_callback_t)(void* user_arg); + // Uasync instance structure struct UASYNC { TimeoutHeap* timeout_heap; // Heap for timeout management @@ -45,6 +47,17 @@ struct UASYNC { // Epoll support (Linux only) int epoll_fd; // epoll file descriptor (-1 if not using epoll) int use_epoll; // 1 if using epoll, 0 if using poll + struct posted_task { + uasync_post_callback_t callback; + void* arg; + struct posted_task* next; + } *posted_tasks_head; + +#ifdef _WIN32 + CRITICAL_SECTION posted_lock; +#else + pthread_mutex_t posted_lock; +#endif }; // Type definitions @@ -86,4 +99,7 @@ void uasync_print_resources(struct UASYNC* ua, const char* prefix); int uasync_wakeup(struct UASYNC* ua); int uasync_get_wakeup_fd(struct UASYNC* ua); // returns write fd for wakeup pipe (for signal handlers) +// сообщить async (из другого thread) чтобы он вызвал callback с аргументом +void uasync_post(struct UASYNC* ua, uasync_post_callback_t callback, void* user_arg); + #endif // UASYNC_H diff --git a/net_emulator/Makefile b/net_emulator/Makefile deleted file mode 100644 index e6ee674..0000000 --- a/net_emulator/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -# Makefile for network emulator -CC = gcc -CFLAGS = -Os -std=c99 -Wall -Wextra -D_ISOC99_SOURCE -INCLUDES = -I. -I../u_async - -SRCS = net_emulator.c -OBJS = $(SRCS:.c=.o) -UASYNC_OBJS = ../obj/src/u_async.o ../obj/src/timeout_heap.o - -TARGET = net_emulator - -all: $(TARGET) - -$(TARGET): $(OBJS) $(UASYNC_OBJS) - $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ - - - -%.o: %.c - $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ - -clean: - rm -f $(OBJS) $(TARGET) - -.PHONY: all clean \ No newline at end of file diff --git a/src/control_server.c b/src/control_server.c index efcc976..b786dcf 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -101,7 +101,7 @@ int control_server_init(struct control_server* server, DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid parameters for control_server_init"); return -1; } -return 0; + memset(server, 0, sizeof(*server)); server->instance = instance; server->ua = ua; diff --git a/src/routing.c b/src/routing.c index a92d492..bdaaa09 100644 --- a/src/routing.c +++ b/src/routing.c @@ -1,4 +1,5 @@ // routing.c - Centralized routing module for utun +#include "../lib/platform_compat.h" #include "routing.h" #include "route_lib.h" #include "tun_if.h" @@ -10,7 +11,6 @@ #include "../lib/debug_config.h" #include #include -#include "../lib/platform_compat.h" #define IPv4_VERSION 4 #define IPv6_VERSION 6 diff --git a/src/tun_if.c b/src/tun_if.c index 0d75f99..28e5a52 100644 --- a/src/tun_if.c +++ b/src/tun_if.c @@ -1,8 +1,6 @@ -// tun_if.c - Cross-platform TUN interface management (common code) -// Platform-specific code is in tun_linux.c and tun_windows.c - -#include "tun_if.h" +// tun_if.c - Cross-platform TUN interface #include "config_parser.h" +#include "tun_if.h" #include "etcp.h" #include "../lib/debug_config.h" #include "../lib/u_async.h" @@ -12,45 +10,17 @@ #include #include -// Callback for input_queue - called when routing puts packet to send to TUN -static void tun_input_queue_callback(struct ll_queue* q, void* arg) { - (void)q; - struct tun_if* tun = (struct tun_if*)arg; - struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->input_queue); - - while (pkt) { - if (pkt->ll.dgram && pkt->ll.len > 0) { - // Check MTU - if (pkt->ll.len > TUN_MAX_PACKET_SIZE) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Packet too large for TUN: %zu bytes (max %d)", - pkt->ll.len, TUN_MAX_PACKET_SIZE); - } else { - ssize_t nwritten = tun_platform_write(tun, pkt->ll.dgram, pkt->ll.len); - if (nwritten < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to write to TUN %s", tun->ifname); - tun->write_errors++; - } else { - tun->bytes_written += nwritten; - tun->packets_written++; - DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Wrote %zd bytes to TUN %s from input_queue", - nwritten, tun->ifname); - } - } - - // Free packet data - free(pkt->ll.dgram); - } - - // Free fragment structure - memory_pool_free(tun->pool, pkt); - - // Get next packet - pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->input_queue); - } -} +#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) { +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]; @@ -62,7 +32,6 @@ static void tun_read_callback(int fd, void* user_arg) { tun->read_errors++; return; } - if (nread == 0) return; // Allocate packet data @@ -89,7 +58,7 @@ static void tun_read_callback(int fd, void* user_arg) { pkt->ll.dgram = packet_data; pkt->ll.len = nread; - // Add to output queue (packets from TUN to routing) + // Add to output queue (TUN → routing) if (queue_data_put(tun->output_queue, (struct ll_entry*)pkt, 0) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue"); free(packet_data); @@ -101,334 +70,256 @@ static void tun_read_callback(int fd, void* user_arg) { // Update statistics tun->bytes_read += nread; tun->packets_read++; - DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Read %zd bytes from TUN %s", nread, tun->ifname); } +#endif -struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) { - if (!ua || !config) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Invalid arguments: ua=%p, config=%p", ua, config); - return NULL; +// =================================================================== +// Callback: routing → TUN (вызывается из main thread) +// =================================================================== +static void tun_input_queue_callback(struct ll_queue* q, void* arg) +{ + (void)q; + struct tun_if* tun = (struct tun_if*)arg; + struct ETCP_FRAGMENT* pkt; + + while ((pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->input_queue)) != NULL) { + if (pkt->ll.dgram && pkt->ll.len > 0) { + if (pkt->ll.len > TUN_MAX_PACKET_SIZE) { + DEBUG_WARN(DEBUG_CATEGORY_TUN, "Packet too large: %zu bytes", pkt->ll.len); + } else { + ssize_t n = tun_platform_write(tun, pkt->ll.dgram, pkt->ll.len); + if (n < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_platform_write failed"); + tun->write_errors++; + } else { + tun->bytes_written += n; + tun->packets_written++; + } + } + free(pkt->ll.dgram); + } + memory_pool_free(tun->pool, pkt); } +} + +// =================================================================== +// Init +// =================================================================== +struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) +{ + if (!ua || !config) return NULL; - // Check for test mode int test_mode = config->global.tun_test_mode; - // Allocate tun_if structure struct tun_if* tun = calloc(1, sizeof(struct tun_if)); - if (!tun) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to allocate tun_if"); - return NULL; - } + if (!tun) return NULL; tun->ua = ua; - tun->fd = -1; - tun->platform_handle = NULL; - tun->adapter_handle = NULL; tun->test_mode = test_mode; + tun->fd = -1; - // Get interface name from config (or empty for auto) - const char* ifname = config->global.tun_ifname; - if (ifname && ifname[0] != '\0') { - strncpy(tun->ifname, ifname, sizeof(tun->ifname) - 1); - } else { - tun->ifname[0] = '\0'; - } + const char* name = config->global.tun_ifname; + if (name && name[0]) strncpy(tun->ifname, name, sizeof(tun->ifname)-1); - // Build IP address string from config - char ip_buffer[INET_ADDRSTRLEN]; - char tun_ip_str[64]; - + char ip_str[64]; if (config->global.tun_ip.family == AF_INET) { - inet_ntop(AF_INET, &config->global.tun_ip.addr.v4, ip_buffer, sizeof(ip_buffer)); - snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer); + char buf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &config->global.tun_ip.addr.v4, buf, sizeof(buf)); + snprintf(ip_str, sizeof(ip_str), "%s/32", buf); } else { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Only IPv4 is supported for TUN"); free(tun); return NULL; } - // Get MTU int mtu = config->global.mtu > 0 ? config->global.mtu : TUN_MTU_DEFAULT; - // Platform-specific initialization (skip in test mode) if (!test_mode) { - if (tun_platform_init(tun, tun->ifname, tun_ip_str, mtu) != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Platform-specific TUN initialization failed"); + if (tun_platform_init(tun, tun->ifname, ip_str, mtu) != 0) { free(tun); return NULL; } - } else { - DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN test mode: skipping platform initialization"); - // In test mode, set a fake interface name if not provided - if (tun->ifname[0] == '\0') { - strncpy(tun->ifname, "tun_test", sizeof(tun->ifname) - 1); - } + } else if (tun->ifname[0] == '\0') { + strncpy(tun->ifname, "tun_test", sizeof(tun->ifname)-1); } - // Create memory pool for ETCP_FRAGMENT tun->pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT)); - if (!tun->pool) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create memory pool"); - if (!test_mode) { - tun_platform_cleanup(tun); - } - free(tun); - return NULL; - } - - // Create output queue (packets from TUN to routing) - tun->output_queue = queue_new(ua, 0); // hash_size=0 - no ID lookup needed - if (!tun->output_queue) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create output queue"); - memory_pool_destroy(tun->pool); - if (!test_mode) { - tun_platform_cleanup(tun); - } - free(tun); - return NULL; - } + if (!tun->pool) goto fail; - // Create input queue (packets from routing to TUN) - tun->input_queue = queue_new(ua, 0); // hash_size=0 - no ID lookup needed - if (!tun->input_queue) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create input queue"); - queue_free(tun->output_queue); - memory_pool_destroy(tun->pool); - if (!test_mode) { - tun_platform_cleanup(tun); - } - free(tun); - return NULL; - } + tun->output_queue = queue_new(ua, 0); + tun->input_queue = queue_new(ua, 0); + if (!tun->output_queue || !tun->input_queue) goto fail; - // Set callback for input_queue - routing will put packets here queue_set_callback(tun->input_queue, tun_input_queue_callback, tun); - // Register TUN socket with uasync (skip in test mode) +#ifdef _WIN32 + InitializeCriticalSection(&tun->output_queue_lock); +#endif + if (!test_mode) { int poll_fd = tun_platform_get_poll_fd(tun); - if (poll_fd < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to get poll fd for TUN"); - queue_free(tun->output_queue); - queue_free(tun->input_queue); - memory_pool_destroy(tun->pool); - tun_platform_cleanup(tun); - free(tun); - return NULL; - } - - tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun); - if (!tun->socket_id) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to register TUN socket with uasync"); - queue_free(tun->output_queue); - queue_free(tun->input_queue); - memory_pool_destroy(tun->pool); - tun_platform_cleanup(tun); - free(tun); - return NULL; - } - } - if (test_mode) { - DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized in TEST MODE: %s (IP=%s, MTU=%d)", - tun->ifname, tun_ip_str, mtu); - } else { - DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s (IP=%s, MTU=%d)", - tun->ifname, tun_ip_str, mtu); + if (poll_fd >= 0) { +#ifndef _WIN32 + // ==================== LINUX ==================== + tun->socket_id = uasync_add_socket(ua, poll_fd, tun_read_callback, NULL, NULL, tun); + if (!tun->socket_id) goto fail; +#endif + } else { +#ifdef _WIN32 + // ==================== WINDOWS ==================== + tun->running = 1; + tun->stop_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!tun->stop_event) goto fail; + + tun->read_thread = CreateThread(NULL, 0, tun_read_thread_proc, tun, 0, NULL); + if (!tun->read_thread) { + CloseHandle(tun->stop_event); + goto fail; + } +#endif + } } + DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN %s initialized (%s mode)", tun->ifname, + test_mode ? "TEST" : "REAL"); return tun; + +fail: +#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); + } + DeleteCriticalSection(&tun->output_queue_lock); +#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); + free(tun); + return NULL; } -void tun_close(struct tun_if* tun) { +// =================================================================== +// Остальные функции (без изменений) +// =================================================================== +void tun_close(struct tun_if* tun) +{ if (!tun) return; - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Closing TUN interface: %s", tun->ifname); +#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); + DeleteCriticalSection(&tun->output_queue_lock); + } +#endif - // Unregister socket from uasync if (tun->socket_id && tun->ua) { uasync_remove_socket(tun->ua, tun->socket_id); - tun->socket_id = NULL; } - // Drain and free all packets from input_queue (packets from routing to TUN) - if (tun->input_queue) { - struct ETCP_FRAGMENT* pkt; - while ((pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->input_queue)) != NULL) { - if (pkt->ll.dgram) { - free(pkt->ll.dgram); - } - memory_pool_free(tun->pool, pkt); - } - queue_free(tun->input_queue); - tun->input_queue = NULL; - } - - // Drain and free all packets from output_queue (packets from TUN to routing) - if (tun->output_queue) { - struct ETCP_FRAGMENT* pkt; - while ((pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->output_queue)) != NULL) { - if (pkt->ll.dgram) { - free(pkt->ll.dgram); - } - memory_pool_free(tun->pool, pkt); - } - queue_free(tun->output_queue); - tun->output_queue = NULL; - } - - // Destroy memory pool - if (tun->pool) { - memory_pool_destroy(tun->pool); - tun->pool = NULL; + struct ETCP_FRAGMENT* pkt; + while ((pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->input_queue)) != NULL) { + if (pkt->ll.dgram) free(pkt->ll.dgram); + memory_pool_free(tun->pool, pkt); } + queue_free(tun->input_queue); - // Platform-specific cleanup (skip in test mode) - if (!tun->test_mode) { - tun_platform_cleanup(tun); + while ((pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->output_queue)) != NULL) { + if (pkt->ll.dgram) free(pkt->ll.dgram); + memory_pool_free(tun->pool, pkt); } + queue_free(tun->output_queue); + memory_pool_destroy(tun->pool); + if (!tun->test_mode) tun_platform_cleanup(tun); free(tun); } -ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len) { - if (!tun || !buf || len == 0) { - if (tun) tun->write_errors++; - return -1; - } +ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len) +{ + if (!tun || !buf || len == 0) return -1; - // In test mode, inject packet into output_queue instead of writing to interface if (tun->test_mode) { - if (tun_inject_packet(tun, buf, len) == 0) { - return len; - } else { - tun->write_errors++; - return -1; - } + return tun_inject_packet(tun, buf, len) == 0 ? (ssize_t)len : -1; } - ssize_t nwritten = tun_platform_write(tun, buf, len); - if (nwritten < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to write to TUN device %s", tun->ifname); + ssize_t n = tun_platform_write(tun, buf, len); + if (n > 0) { + tun->bytes_written += n; + tun->packets_written++; + } else { tun->write_errors++; - return -1; } - - tun->bytes_written += nwritten; - tun->packets_written++; - - DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Wrote %zd bytes to TUN %s", nwritten, tun->ifname); - - return nwritten; + return n; } -struct ll_queue* tun_get_input_queue(struct tun_if* tun) { - if (!tun) { - return NULL; - } - return tun->input_queue; -} +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; } -struct ll_queue* tun_get_output_queue(struct tun_if* tun) { - if (!tun) { - return NULL; - } - return tun->output_queue; -} +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; -int tun_is_test_mode(struct tun_if* tun) { - if (!tun) { - return -1; - } - return tun->test_mode; -} + uint8_t* data = malloc(len); + if (!data) { tun->read_errors++; return -1; } + memcpy(data, buf, len); -int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len) { - if (!tun || !buf || len == 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: invalid arguments"); - return -1; - } - - if (len > TUN_MAX_PACKET_SIZE) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Packet too large: %zu bytes (max %d)", - len, TUN_MAX_PACKET_SIZE); - } - - // Allocate packet data - uint8_t* packet_data = malloc(len); - if (!packet_data) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: failed to allocate packet data"); - tun->read_errors++; - return -1; - } - memcpy(packet_data, buf, len); - - // Allocate ETCP_FRAGMENT from pool struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_entry_new_from_pool(tun->pool); - if (!pkt) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: failed to allocate ETCP_FRAGMENT"); - free(packet_data); - tun->read_errors++; - return -1; - } + if (!pkt) { free(data); tun->read_errors++; return -1; } - // Initialize fragment pkt->seq = 0; pkt->timestamp = 0; - pkt->ll.dgram = packet_data; + pkt->ll.dgram = data; pkt->ll.len = len; - // Add to output queue (packets from TUN to routing) - if (queue_data_put(tun->output_queue, (struct ll_entry*)pkt, 0) != 0) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tun_inject_packet: failed to add packet to output queue"); - free(packet_data); +#ifdef _WIN32 + EnterCriticalSection(&tun->output_queue_lock); +#endif + int ret = queue_data_put(tun->output_queue, (struct ll_entry*)pkt, 0); +#ifdef _WIN32 + LeaveCriticalSection(&tun->output_queue_lock); +#endif + + if (ret != 0) { + free(data); memory_pool_free(tun->pool, pkt); tun->read_errors++; return -1; } - // Update statistics tun->bytes_read += len; tun->packets_read++; - - DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Injected %zu bytes into TUN output queue %s", len, tun->ifname); - return 0; } -ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len) { - if (!tun || !buf || len == 0) { - return -1; - } +ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len) +{ + if (!tun || !buf || len == 0) return -1; struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_data_get(tun->input_queue); - if (!pkt) { - return 0; // No packet available - } + if (!pkt) return 0; - size_t pkt_len = pkt->ll.len; - if (pkt_len > len) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "tun_read_packet: buffer too small (%zu > %zu)", - pkt_len, len); - // Put packet back - cannot read partial - // Note: This is a simplification; in real use, consider copying partial + if (pkt->ll.len > len) { queue_data_put(tun->input_queue, (struct ll_entry*)pkt, 0); return -1; } - // Copy data to user buffer - memcpy(buf, pkt->ll.dgram, pkt_len); + memcpy(buf, pkt->ll.dgram, pkt->ll.len); + ssize_t ret = pkt->ll.len; - // Free packet free(pkt->ll.dgram); memory_pool_free(tun->pool, pkt); - // Update statistics - tun->bytes_written += pkt_len; + tun->bytes_written += ret; tun->packets_written++; - - DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Read %zu bytes from TUN input queue %s", pkt_len, tun->ifname); - - return pkt_len; + return ret; } diff --git a/src/tun_if.h b/src/tun_if.h index 4bbb4c3..b355e27 100644 --- a/src/tun_if.h +++ b/src/tun_if.h @@ -6,166 +6,115 @@ #include #include +#ifdef _WIN32 +#include // для HANDLE и CRITICAL_SECTION +#endif + // Forward declarations struct UASYNC; struct utun_config; struct ll_queue; struct memory_pool; +struct ETCP_FRAGMENT; // используется внутри, определён в etcp.h #ifdef __cplusplus extern "C" { #endif -#define TUN_MTU_DEFAULT 1500 +#define TUN_MTU_DEFAULT 1500 #define TUN_MAX_PACKET_SIZE 1500 -// TUN interface handle - opaque structure +// =================================================================== +// TUN interface handle +// =================================================================== struct tun_if { - char ifname[16]; // Interface name (e.g., "tun12" or "utun") - int fd; // Linux: file descriptor, Windows: -1 (unused) + 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; // uasync instance - void* socket_id; // Socket ID from uasync_add_socket - struct memory_pool* pool; // Pool for ETCP_FRAGMENT structures - struct ll_queue* output_queue; // Queue for incoming packets from TUN (to routing) - struct ll_queue* input_queue; // Queue for outgoing packets to TUN (from routing) - int test_mode; // Test mode flag: 1 = no real TUN, queues only - // Statistics - uint64_t bytes_read; // Bytes read from TUN - uint64_t bytes_written; // Bytes written to TUN - uint32_t packets_read; // Packets read from TUN - uint32_t packets_written; // Packets written to TUN - uint32_t read_errors; // Read errors - uint32_t write_errors; // Write errors + + struct UASYNC* ua; + void* socket_id; // uasync socket_id (NULL на Windows) + + struct memory_pool* pool; // пул для ETCP_FRAGMENT + struct ll_queue* output_queue; // TUN → routing (пакеты из интерфейса) + struct ll_queue* input_queue; // routing → TUN (пакеты в интерфейс) + + int test_mode; // 1 = тестовый режим (без реального TUN) + + // Статистика + uint64_t bytes_read; + uint64_t bytes_written; + uint32_t packets_read; + uint32_t packets_written; + uint32_t read_errors; + uint32_t write_errors; + +#ifdef _WIN32 + CRITICAL_SECTION output_queue_lock; // защита output_queue (TUN → routing) + HANDLE read_thread; + HANDLE stop_event; + volatile int running; // 1 = поток работает +#endif }; /** - * @brief Initialize TUN interface - * Creates TUN device, sets MTU=1500, brings interface up, - * configures IP from config, registers read callback in uasync, - * creates memory pool and input queue for incoming packets. - * @param ua uasync instance for event handling - * @param config Configuration containing tun_ip and other settings - * @return Pointer to tun_if on success, NULL on error + * @brief Инициализация TUN интерфейса + * @param ua экземпляр uasync + * @param config конфигурация (IP, MTU, test_mode и т.д.) + * @return указатель на tun_if или NULL при ошибке */ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config); /** - * @brief Close TUN interface and free resources - * Unregisters socket from uasync, closes fd, drains and frees - * all packets from output_queue, destroys pool and queue. - * @param tun TUN interface handle + * @brief Закрытие и освобождение всех ресурсов */ void tun_close(struct tun_if* tun); /** - * @brief Write packet to TUN interface - * @param tun TUN interface handle - * @param buf Packet data - * @param len Packet size - * @return Number of bytes written, -1 on error + * @brief Записать пакет в TUN (из routing) */ ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len); /** - * @brief Get TUN input queue (for sending packets to TUN from routing) - * @param tun TUN interface handle - * @return Pointer to input queue, NULL if tun is NULL - * - * In normal mode: routing layer puts packets here, TUN reads and sends to interface - * In test mode: tests can put packets here directly to simulate routing output + * @brief Получить очередь input (routing → TUN) */ struct ll_queue* tun_get_input_queue(struct tun_if* tun); /** - * @brief Get TUN output queue (for receiving packets from TUN to routing) - * @param tun TUN interface handle - * @return Pointer to output queue, NULL if tun is NULL - * - * In normal mode: TUN receives packets from interface and puts here for routing - * In test mode: tests can read packets from here that were "sent" to TUN + * @brief Получить очередь output (TUN → routing) */ struct ll_queue* tun_get_output_queue(struct tun_if* tun); /** - * @brief Check if TUN interface is in test mode - * @param tun TUN interface handle - * @return 1 if test mode, 0 if normal mode, -1 if tun is NULL + * @brief Проверка режима */ int tun_is_test_mode(struct tun_if* tun); /** - * @brief Write packet directly to TUN output queue (test mode helper) - * @param tun TUN interface handle - * @param buf Packet data - * @param len Packet size - * @return 0 on success, -1 on error - * - * This function simulates receiving a packet from the TUN interface. - * The packet is placed in the output_queue where routing layer would normally find it. - * Works in both test and normal modes. + * @brief Инъекция пакета в output_queue (для тестов и Windows-уведомлений) */ int tun_inject_packet(struct tun_if* tun, const uint8_t* buf, size_t len); /** - * @brief Read packet directly from TUN input queue (test mode helper) - * @param tun TUN interface handle - * @param buf Output buffer for packet data - * @param len Output buffer size - * @return Number of bytes read, 0 if no packet available, -1 on error - * - * This function simulates what the TUN interface would normally do: - * take a packet from input_queue and return it. - * Works in both test and normal modes. + * @brief Прочитать пакет из input_queue (тестовый helper) */ ssize_t tun_read_packet(struct tun_if* tun, uint8_t* buf, size_t len); -/** - * @brief Test mode usage example and documentation - * - * To use TUN in test mode, set tun_test_mode=1 in the [global] section - * of the configuration file: - * - * [global] - * tun_test_mode=1 - * tun_ip=10.0.0.1/24 - * - * Example test code: - * - * // Initialize TUN in test mode - * struct tun_if* tun = tun_init(ua, config); - * if (!tun || !tun_is_test_mode(tun)) { - * // handle error - * } - * - * // Simulate receiving packet from TUN (as if from network) - * uint8_t packet[] = { ... }; // IP packet - * tun_inject_packet(tun, packet, sizeof(packet)); - * - * // Routing layer will find this packet in output_queue - * struct ll_queue* out_q = tun_get_output_queue(tun); - * struct ETCP_FRAGMENT* pkt = queue_data_get(out_q); - * - * // Simulate sending packet to TUN (as if from routing) - * struct ll_queue* in_q = tun_get_input_queue(tun); - * // Put packet in input_queue, TUN will process it - * - * In test mode: - * - No real TUN device is created - * - No system calls to configure network interface - * - No socket is registered with uasync - * - Memory pool and queues are still created normally - * - All statistics are tracked normally - * - tun_write() still works (writes to platform, but no real interface) - */ - -// Platform-specific internal functions (implemented in tun_linux.c / tun_windows.c) -int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str, int mtu); +// =================================================================== +// 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); 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); + +#ifdef _WIN32 +DWORD WINAPI tun_read_thread_proc(LPVOID arg); +#endif #ifdef __cplusplus } diff --git a/src/tun_windows.c b/src/tun_windows.c index 35114d6..0b1df67 100644 --- a/src/tun_windows.c +++ b/src/tun_windows.c @@ -1,254 +1,182 @@ -// tun_windows.c - Windows Wintun implementation -// Uses Wintun.dll for TUN device access - -#include "tun_if.h" +// tun_windows.c - Wintun implementation #include "../lib/debug_config.h" -#include "../lib/wintun.h" -#include -#include -#include +#include "../lib/u_async.h" +#include "tun_if.h" +#include "wintun.h" +#include "ll_queue.h" // ← добавлено +#include "etcp.h" // ← добавлено (struct ETCP_FRAGMENT) #include #include #include +#include -// Wintun function pointers (wintun.h defines types as functions, need pointer syntax) +// Wintun function pointers static HMODULE wintun_dll = NULL; -static WINTUN_CREATE_ADAPTER_FUNC *WintunCreateAdapter = NULL; -static WINTUN_CLOSE_ADAPTER_FUNC *WintunCloseAdapter = NULL; -static WINTUN_OPEN_ADAPTER_FUNC *WintunOpenAdapter = NULL; -static WINTUN_GET_ADAPTER_LUID_FUNC *WintunGetAdapterLUID = NULL; -static WINTUN_START_SESSION_FUNC *WintunStartSession = NULL; -static WINTUN_END_SESSION_FUNC *WintunEndSession = NULL; -static WINTUN_GET_READ_WAIT_EVENT_FUNC *WintunGetReadWaitEvent = NULL; -static WINTUN_RECEIVE_PACKET_FUNC *WintunReceivePacket = NULL; -static WINTUN_RELEASE_RECEIVE_PACKET_FUNC *WintunReleaseReceivePacket = NULL; -static WINTUN_ALLOCATE_SEND_PACKET_FUNC *WintunAllocateSendPacket = NULL; -static WINTUN_SEND_PACKET_FUNC *WintunSendPacket = NULL; - -// Load Wintun.dll and resolve function pointers -static int wintun_load_dll(void) { - if (wintun_dll) { - return 0; // Already loaded - } +static WINTUN_CREATE_ADAPTER_FUNC *WintunCreateAdapter = NULL; +static WINTUN_CLOSE_ADAPTER_FUNC *WintunCloseAdapter = NULL; +static WINTUN_OPEN_ADAPTER_FUNC *WintunOpenAdapter = NULL; +static WINTUN_GET_ADAPTER_LUID_FUNC *WintunGetAdapterLUID = NULL; +static WINTUN_START_SESSION_FUNC *WintunStartSession = NULL; +static WINTUN_END_SESSION_FUNC *WintunEndSession = NULL; +static WINTUN_GET_READ_WAIT_EVENT_FUNC *WintunGetReadWaitEvent = NULL; +static WINTUN_RECEIVE_PACKET_FUNC *WintunReceivePacket = NULL; +static WINTUN_RELEASE_RECEIVE_PACKET_FUNC *WintunReleaseReceivePacket = NULL; +static WINTUN_ALLOCATE_SEND_PACKET_FUNC *WintunAllocateSendPacket = NULL; +static WINTUN_SEND_PACKET_FUNC *WintunSendPacket = NULL; + +static int wintun_load_dll(void) +{ + if (wintun_dll) return 0; - // Try to load from the same directory as executable wchar_t path[MAX_PATH]; - DWORD path_len = GetModuleFileNameW(NULL, path, MAX_PATH); - if (path_len > 0 && path_len < MAX_PATH) { - // Find last backslash - for (int i = path_len - 1; i >= 0; i--) { + if (GetModuleFileNameW(NULL, path, MAX_PATH)) { + for (int i = (int)wcslen(path)-1; i >= 0; i--) { if (path[i] == L'\\' || path[i] == L'/') { - path[i + 1] = L'\0'; + path[i+1] = L'\0'; + wcscat(path, L"wintun.dll"); + wintun_dll = LoadLibraryW(path); break; } } - wcscat(path, L"wintun.dll"); - wintun_dll = LoadLibraryW(path); - } - - // If not found in executable directory, try system directories - if (!wintun_dll) { - wintun_dll = LoadLibraryA("wintun.dll"); } + if (!wintun_dll) wintun_dll = LoadLibraryA("wintun.dll"); if (!wintun_dll) { - // Try to construct full path for error message - wchar_t full_path[MAX_PATH]; - GetCurrentDirectoryW(MAX_PATH, full_path); - wcscat(full_path, L"\\wintun.dll"); - - char path_utf8[MAX_PATH]; - WideCharToMultiByte(CP_UTF8, 0, full_path, -1, path_utf8, MAX_PATH, NULL, NULL); - - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to load wintun.dll from: %s", path_utf8); - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Please ensure wintun.dll is in the same directory as the executable"); - fprintf(stderr, "FATAL ERROR: wintun.dll not found at: %s\n", path_utf8); - fprintf(stderr, "Please download Wintun from https://www.wintun.net/\n"); - fprintf(stderr, "and place wintun.dll in the same directory as utun.exe\n"); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "wintun.dll not found"); return -1; } - // Resolve function pointers (cast to void* first to avoid errors) - WintunCreateAdapter = (WINTUN_CREATE_ADAPTER_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunCreateAdapter"); - WintunCloseAdapter = (WINTUN_CLOSE_ADAPTER_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunCloseAdapter"); - WintunOpenAdapter = (WINTUN_OPEN_ADAPTER_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunOpenAdapter"); - WintunGetAdapterLUID = (WINTUN_GET_ADAPTER_LUID_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunGetAdapterLUID"); - WintunStartSession = (WINTUN_START_SESSION_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunStartSession"); - WintunEndSession = (WINTUN_END_SESSION_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunEndSession"); - WintunGetReadWaitEvent = (WINTUN_GET_READ_WAIT_EVENT_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunGetReadWaitEvent"); - WintunReceivePacket = (WINTUN_RECEIVE_PACKET_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunReceivePacket"); - WintunReleaseReceivePacket = (WINTUN_RELEASE_RECEIVE_PACKET_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunReleaseReceivePacket"); - WintunAllocateSendPacket = (WINTUN_ALLOCATE_SEND_PACKET_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunAllocateSendPacket"); - WintunSendPacket = (WINTUN_SEND_PACKET_FUNC *)(void *)GetProcAddress(wintun_dll, "WintunSendPacket"); - - // Check that all functions were resolved + WintunCreateAdapter = (WINTUN_CREATE_ADAPTER_FUNC*)GetProcAddress(wintun_dll, "WintunCreateAdapter"); + WintunCloseAdapter = (WINTUN_CLOSE_ADAPTER_FUNC*)GetProcAddress(wintun_dll, "WintunCloseAdapter"); + WintunOpenAdapter = (WINTUN_OPEN_ADAPTER_FUNC*)GetProcAddress(wintun_dll, "WintunOpenAdapter"); + WintunGetAdapterLUID = (WINTUN_GET_ADAPTER_LUID_FUNC*)GetProcAddress(wintun_dll, "WintunGetAdapterLUID"); + WintunStartSession = (WINTUN_START_SESSION_FUNC*)GetProcAddress(wintun_dll, "WintunStartSession"); + WintunEndSession = (WINTUN_END_SESSION_FUNC*)GetProcAddress(wintun_dll, "WintunEndSession"); + WintunGetReadWaitEvent = (WINTUN_GET_READ_WAIT_EVENT_FUNC*)GetProcAddress(wintun_dll, "WintunGetReadWaitEvent"); + WintunReceivePacket = (WINTUN_RECEIVE_PACKET_FUNC*)GetProcAddress(wintun_dll, "WintunReceivePacket"); + WintunReleaseReceivePacket = (WINTUN_RELEASE_RECEIVE_PACKET_FUNC*)GetProcAddress(wintun_dll, "WintunReleaseReceivePacket"); + WintunAllocateSendPacket = (WINTUN_ALLOCATE_SEND_PACKET_FUNC*)GetProcAddress(wintun_dll, "WintunAllocateSendPacket"); + WintunSendPacket = (WINTUN_SEND_PACKET_FUNC*)GetProcAddress(wintun_dll, "WintunSendPacket"); + if (!WintunCreateAdapter || !WintunCloseAdapter || !WintunOpenAdapter || !WintunGetAdapterLUID || !WintunStartSession || !WintunEndSession || !WintunGetReadWaitEvent || !WintunReceivePacket || !WintunReleaseReceivePacket || !WintunAllocateSendPacket || !WintunSendPacket) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to resolve some Wintun functions"); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to resolve Wintun functions"); FreeLibrary(wintun_dll); wintun_dll = NULL; return -1; } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Wintun.dll loaded successfully"); return 0; } -// Unload Wintun.dll -static void wintun_unload_dll(void) { - if (wintun_dll) { - FreeLibrary(wintun_dll); - wintun_dll = NULL; - WintunCreateAdapter = NULL; - WintunCloseAdapter = NULL; - WintunOpenAdapter = NULL; - WintunGetAdapterLUID = NULL; - WintunStartSession = NULL; - WintunEndSession = NULL; - WintunGetReadWaitEvent = NULL; - WintunReceivePacket = NULL; - WintunReleaseReceivePacket = NULL; - WintunAllocateSendPacket = NULL; - WintunSendPacket = NULL; +// =================================================================== +// Helper: set IP + MTU + bring interface up +// =================================================================== +static int wintun_configure_interface(WINTUN_ADAPTER_HANDLE adapter, const char* ip_str, int mtu) +{ + NET_LUID luid; + WintunGetAdapterLUID(adapter, &luid); + + char ip_only[64] = {0}; + int prefix = 32; + if (ip_str) { + strncpy(ip_only, ip_str, sizeof(ip_only)-1); + char* slash = strchr(ip_only, '/'); + if (slash) { + *slash = '\0'; + prefix = atoi(slash + 1); + if (prefix < 0 || prefix > 32) prefix = 32; + } } -} -// Set IP address and MTU using IP Helper API -static int wintun_set_ip_and_mtu(const NET_LUID* luid, const char* ip_str, int mtu) { - // Parse IP address (remove /32 suffix if present) - char ip_copy[64]; - strncpy(ip_copy, ip_str, sizeof(ip_copy) - 1); - ip_copy[sizeof(ip_copy) - 1] = '\0'; - - char* slash = strchr(ip_copy, '/'); - if (slash) *slash = '\0'; - - // Convert IP string to sockaddr - SOCKADDR_INET addr; - memset(&addr, 0, sizeof(addr)); - addr.si_family = AF_INET; - - if (inet_pton(AF_INET, ip_copy, &addr.Ipv4.sin_addr) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to parse IP address: %s", ip_copy); + struct in_addr addr; + if (inet_pton(AF_INET, ip_only, &addr) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Invalid IP: %s", ip_str); return -1; } - // Get interface index from LUID - NET_IFINDEX ifindex; - if (ConvertInterfaceLuidToIndex(luid, &ifindex) != NO_ERROR) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to convert LUID to interface index"); + // IP address + MIB_UNICASTIPADDRESS_ROW row = {0}; + InitializeUnicastIpAddressEntry(&row); + row.Address.si_family = AF_INET; + row.Address.Ipv4.sin_family = AF_INET; + row.Address.Ipv4.sin_addr.s_addr = addr.s_addr; + row.InterfaceLuid = luid; + row.OnLinkPrefixLength = (UINT8)prefix; + row.DadState = IpDadStatePreferred; + row.SuffixOrigin = IpSuffixOriginManual; + row.PrefixOrigin = IpPrefixOriginManual; + + DWORD err = CreateUnicastIpAddressEntry(&row); + if (err != NO_ERROR && err != ERROR_OBJECT_ALREADY_EXISTS) { + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "CreateUnicastIpAddressEntry failed: %lu", err); return -1; } - // Set MTU first using MIB_IPINTERFACE_ROW (minimal required fields) - MIB_IPINTERFACE_ROW row; - memset(&row, 0, sizeof(row)); - row.Family = AF_INET; - row.InterfaceLuid = *luid; - row.NlMtu = mtu; - - DWORD ret = SetIpInterfaceEntry(&row); - if (ret != NO_ERROR && ret != ERROR_OBJECT_ALREADY_EXISTS) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set MTU: %lu", ret); + // MTU + if (mtu > 0) { + MIB_IPINTERFACE_ROW ifRow = {0}; + InitializeIpInterfaceEntry(&ifRow); + ifRow.Family = AF_INET; + ifRow.InterfaceLuid = luid; + if (GetIpInterfaceEntry(&ifRow) == NO_ERROR) { + ifRow.NlMtu = mtu; + SetIpInterfaceEntry(&ifRow); + } } - // Add unicast IP address - MIB_UNICASTIPADDRESS_ROW addr_row; - InitializeUnicastIpAddressEntry(&addr_row); - addr_row.InterfaceLuid = *luid; - addr_row.Address = addr; - addr_row.OnLinkPrefixLength = 32; // /32 for point-to-point - addr_row.ValidLifetime = 0xffffffff; - addr_row.PreferredLifetime = 0xffffffff; - - ret = CreateUnicastIpAddressEntry(&addr_row); - if (ret != NO_ERROR) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set IP address: %lu", ret); - return -1; + // Bring UP + NET_IFINDEX idx = 0; + if (ConvertInterfaceLuidToIndex(&luid, &idx) == NO_ERROR) { + MIB_IFROW ifr = {0}; + ifr.dwIndex = idx; + if (GetIfEntry(&ifr) == NO_ERROR) { + ifr.dwAdminStatus = MIB_IF_ADMIN_STATUS_UP; + SetIfEntry(&ifr); + } } - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "IP address %s and MTU %d configured successfully", ip_copy, mtu); return 0; } -// Platform-specific initialization for Windows -int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str, int mtu) { - // Load Wintun.dll - if (wintun_load_dll() != 0) { - return -1; - } +// =================================================================== +// Platform functions +// =================================================================== +int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str, int mtu) +{ + if (wintun_load_dll() != 0) return -1; - // Generate adapter name - const WCHAR* adapter_name = L"utun"; - const WCHAR* tunnel_type = L"utun"; - - // Use provided name or default - wchar_t wname[128]; - if (ifname && ifname[0] != '\0') { + wchar_t wname[128] = L"utun"; + if (ifname && ifname[0]) MultiByteToWideChar(CP_UTF8, 0, ifname, -1, wname, 128); - adapter_name = wname; - } - // Try to open existing adapter first - WINTUN_ADAPTER_HANDLE adapter = WintunOpenAdapter(adapter_name); + WINTUN_ADAPTER_HANDLE adapter = WintunCreateAdapter(wname, L"utun", NULL); if (!adapter) { - DWORD err = GetLastError(); - if (err == ERROR_NOT_FOUND || err == ERROR_FILE_NOT_FOUND) { // 1168 or 2 - // Adapter does not exist → safe to create a new one - adapter = WintunCreateAdapter(adapter_name, tunnel_type, NULL); - if (!adapter) { - err = GetLastError(); - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create new Wintun adapter: %lu", err); - return -1; - } - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Created new Wintun adapter: %s", tun->ifname); - } else { - // Some other real failure (e.g. access denied, driver not loaded, etc.) - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to open existing Wintun adapter: %lu", err); - return -1; - } - } else { - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Opened existing Wintun adapter: %s", tun->ifname); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "WintunCreateAdapter failed: %lu", GetLastError()); + return -1; } - // Get adapter LUID for IP configuration - NET_LUID luid; - WintunGetAdapterLUID(adapter, &luid); - - // Start Wintun session - WINTUN_SESSION_HANDLE session = WintunStartSession(adapter, WINTUN_MIN_RING_CAPACITY); + WINTUN_SESSION_HANDLE session = WintunStartSession(adapter, 0x400000); // 4 MiB ring if (!session) { - DWORD err = GetLastError(); - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to start Wintun session: %lu", err); + DEBUG_ERROR(DEBUG_CATEGORY_TUN, "WintunStartSession failed: %lu", GetLastError()); WintunCloseAdapter(adapter); return -1; } - // Store handles - tun->adapter_handle = adapter; - tun->platform_handle = session; - - // Set IP and MTU - if (wintun_set_ip_and_mtu(&luid, ip_str, mtu) != 0) { + if (wintun_configure_interface(adapter, ip_str, mtu) != 0) { WintunEndSession(session); WintunCloseAdapter(adapter); - tun->adapter_handle = NULL; - tun->platform_handle = NULL; return -1; } - // Store interface name - strncpy(tun->ifname, ifname && ifname[0] ? ifname : "utun", sizeof(tun->ifname) - 1); - tun->ifname[sizeof(tun->ifname) - 1] = '\0'; - - DEBUG_INFO(DEBUG_CATEGORY_TUN, "Wintun adapter created: %s", tun->ifname); + tun->platform_handle = session; + tun->adapter_handle = adapter; + strncpy(tun->ifname, ifname && ifname[0] ? ifname : "utun", sizeof(tun->ifname)-1); return 0; } -// Platform-specific cleanup for Windows -void tun_platform_cleanup(struct tun_if* tun) { +void tun_platform_cleanup(struct tun_if* tun) +{ if (tun->platform_handle) { WintunEndSession((WINTUN_SESSION_HANDLE)tun->platform_handle); tun->platform_handle = NULL; @@ -259,81 +187,101 @@ void tun_platform_cleanup(struct tun_if* tun) { } } -// Platform-specific read for Windows -ssize_t tun_platform_read(struct tun_if* tun, uint8_t* buf, size_t len) { - (void)len; // Wintun handles packet size - - if (!tun->platform_handle) { +ssize_t tun_platform_read(struct tun_if* tun, uint8_t* buf, size_t len) +{ + if (!tun->platform_handle) return -1; + DWORD size; + BYTE* pkt = WintunReceivePacket((WINTUN_SESSION_HANDLE)tun->platform_handle, &size); + if (!pkt) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) return 0; return -1; } + if (size > len) size = (DWORD)len; + memcpy(buf, pkt, size); + WintunReleaseReceivePacket((WINTUN_SESSION_HANDLE)tun->platform_handle, pkt); + return (ssize_t)size; +} - DWORD packet_size; - BYTE* packet = WintunReceivePacket((WINTUN_SESSION_HANDLE)tun->platform_handle, &packet_size); - if (!packet) { - DWORD err = GetLastError(); - if (err == ERROR_NO_MORE_ITEMS) { - return 0; // No data available (non-blocking) - } - if (err == ERROR_HANDLE_EOF) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun adapter closed"); - return -1; - } - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun receive error: %lu", err); - return -1; - } +ssize_t tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len) +{ + if (!tun->platform_handle) return -1; + BYTE* pkt = WintunAllocateSendPacket((WINTUN_SESSION_HANDLE)tun->platform_handle, (DWORD)len); + if (!pkt) return -1; + memcpy(pkt, buf, len); + WintunSendPacket((WINTUN_SESSION_HANDLE)tun->platform_handle, pkt); + return (ssize_t)len; +} - // Copy packet data - if (packet_size > len) { - packet_size = (DWORD)len; // Truncate if buffer too small +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); } - memcpy(buf, packet, packet_size); +} - // Release packet back to Wintun - WintunReleaseReceivePacket((WINTUN_SESSION_HANDLE)tun->platform_handle, packet); +// =================================================================== +// Read thread +// =================================================================== +DWORD WINAPI tun_read_thread_proc(LPVOID arg) +{ + struct tun_if* tun = (struct tun_if*)arg; + HANDLE event = WintunGetReadWaitEvent((WINTUN_SESSION_HANDLE)tun->platform_handle); + if (!event) return 1; - return (ssize_t)packet_size; -} + HANDLE handles[2] = {event, tun->stop_event}; -// Platform-specific write for Windows -ssize_t tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len) { - if (!tun->platform_handle) { - return -1; - } + while (tun->running) { + if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) != WAIT_OBJECT_0) + break; - // Allocate packet from Wintun ring - BYTE* packet = WintunAllocateSendPacket((WINTUN_SESSION_HANDLE)tun->platform_handle, (DWORD)len); - if (!packet) { - DWORD err = GetLastError(); - if (err == ERROR_HANDLE_EOF) { - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun adapter closed"); - return -1; - } - if (err == ERROR_BUFFER_OVERFLOW) { - DEBUG_WARN(DEBUG_CATEGORY_TUN, "Wintun send buffer overflow"); - return -1; - } - DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Wintun allocate packet failed: %lu", err); - return -1; - } + uint8_t buf[TUN_MAX_PACKET_SIZE]; + int got_packet = 0; - // Copy data and send - memcpy(packet, buf, len); - WintunSendPacket((WINTUN_SESSION_HANDLE)tun->platform_handle, packet); + ssize_t n; + while ((n = tun_platform_read(tun, buf, sizeof(buf))) > 0) { + got_packet = 1; - return (ssize_t)len; -} + uint8_t* data = malloc(n); + if (!data) { tun->read_errors++; continue; } + memcpy(data, buf, n); -// Get poll fd for uasync -int tun_platform_get_poll_fd(struct tun_if* tun) { - if (!tun->platform_handle) { - return -1; - } + struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_entry_new_from_pool(tun->pool); + if (!pkt) { + free(data); + tun->read_errors++; + continue; + } - HANDLE event = WintunGetReadWaitEvent((WINTUN_SESSION_HANDLE)tun->platform_handle); - if (event == NULL || event == INVALID_HANDLE_VALUE) { - return -1; - } + pkt->ll.dgram = data; + pkt->ll.len = n; + + EnterCriticalSection(&tun->output_queue_lock); + int ok = queue_data_put(tun->output_queue, (struct ll_entry*)pkt, 0); + LeaveCriticalSection(&tun->output_queue_lock); + + if (ok != 0) { + free(data); + memory_pool_free(tun->pool, pkt); + tun->read_errors++; + } else { + tun->bytes_read += n; + tun->packets_read++; + } + } - // Cast HANDLE to int for uasync (Windows specific) - return (int)(intptr_t)event; + if (got_packet) { + uasync_post(tun->ua, tun_output_notify, tun); + } + } + return 0; } diff --git a/src/utun.c b/src/utun.c index 5b9f8b7..f62f202 100644 --- a/src/utun.c +++ b/src/utun.c @@ -20,6 +20,7 @@ #include #include "../lib/platform_compat.h" +#include "../lib/socket_compat.h" #ifdef _WIN32 #include @@ -265,12 +266,15 @@ int main(int argc, char *argv[]) { print_usage(argv[0]); return 0; } + + socket_platform_init(); // Initialize global debug system early if configured from command line debug_config_init(); debug_set_level(DEBUG_LEVEL_TRACE); - debug_set_categories(DEBUG_CATEGORY_ALL & ~DEBUG_CATEGORY_UASYNC & ~DEBUG_CATEGORY_TIMERS); // Enable all except uasync + debug_set_categories(DEBUG_CATEGORY_ALL & ~DEBUG_CATEGORY_UASYNC & ~DEBUG_CATEGORY_TIMERS & ~DEBUG_CATEGORY_CRYPTO & ~DEBUG_CATEGORY_ETCP & ~DEBUG_CATEGORY_CONNECTION); // Enable all except uasync +// debug_set_categories(DEBUG_CATEGORY_ALL); debug_enable_function_name(1); if (args.debug_config) { @@ -385,6 +389,7 @@ int main(int argc, char *argv[]) { } remove_pidfile(args.pid_file); + socket_platform_cleanup(); return 0; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 76165e4..8df0472 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -22,9 +22,8 @@ check_PROGRAMS = \ test_config_debug \ test_route_lib \ test_bgp_route_exchange \ - test_routing_mesh \ - test_etcp_dummynet \ - test_control_server \ + test_routing_mesh \ + test_control_server \ bench_timeout_heap \ bench_uasync_timeouts @@ -81,13 +80,13 @@ ETCP_FULL_OBJS = \ # Windows-specific libraries (advapi32 for CryptGenRandom, ws2_32 for sockets) if OS_WINDOWS -WIN_LIBS = -lws2_32 -liphlpapi -ladvapi32 +WIN_LIBS = -lws2_32 -liphlpapi -ladvapi32 -lbcrypt else WIN_LIBS = endif # Common libraries (libuasync.a from lib directory) -COMMON_LIBS = $(top_builddir)/lib/libuasync.a -lpthread $(WIN_LIBS) +COMMON_LIBS = $(top_builddir)/src/utun-control_server.o $(top_builddir)/lib/libuasync.a -lpthread $(WIN_LIBS) # Crypto libraries (conditional) if USE_OPENSSL @@ -198,10 +197,6 @@ test_routing_mesh_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS # test_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib # test_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(COMMON_LIBS) -test_etcp_dummynet_SOURCES = test_etcp_dummynet.c -test_etcp_dummynet_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -test_etcp_dummynet_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) - bench_timeout_heap_SOURCES = bench_timeout_heap.c bench_timeout_heap_CFLAGS = -I$(top_srcdir)/lib bench_timeout_heap_LDADD = $(COMMON_LIBS) @@ -213,7 +208,7 @@ bench_uasync_timeouts_LDADD = $(COMMON_LIBS) # Control Server Test - Tests the ETCP monitoring protocol test_control_server_SOURCES = test_control_server.c test_control_server_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tools/etcpmon -test_control_server_LDADD = $(top_builddir)/src/utun-control_server.o $(COMMON_LIBS) +test_control_server_LDADD = $(COMMON_LIBS) # Build tinycrypt objects before tests that need them BUILT_SOURCES = $(TINYCRYPT_BUILT) diff --git a/tinycrypt/Makefile b/tinycrypt/Makefile deleted file mode 100644 index 3c0e42b..0000000 --- a/tinycrypt/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -################################################################################ -# -# Copyright (C) 2017 by Intel Corporation, All Rights Reserved. -# -# Global Makefile. -# See lib/Makefile and tests/Makefile for further configuration. -# -################################################################################ -include config.mk - -all: - $(MAKE) -C lib -ifeq ($(ENABLE_TESTS),true) - $(MAKE) -C tests -endif - -clean: - $(MAKE) -C lib clean - $(MAKE) -C tests clean - $(RM) *~ -