Browse Source

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
nodeinfo-routing-update
jeka 1 month ago
parent
commit
d10bddd1f7
  1. 70
      build.sh
  2. 81
      build_direct.sh
  3. 31
      build_full.bat
  4. 96
      configure.ac
  5. 251
      lib/u_async.c
  6. 16
      lib/u_async.h
  7. 25
      net_emulator/Makefile
  8. 2
      src/control_server.c
  9. 2
      src/routing.c
  10. 429
      src/tun_if.c
  11. 159
      src/tun_if.h
  12. 420
      src/tun_windows.c
  13. 7
      src/utun.c
  14. 11
      tests/Makefile.am
  15. 21
      tinycrypt/Makefile

70
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
echo "======================================"
echo "FULL REBUILD: autoreconf + configure"
echo "======================================"
echo ""
# Check for autotools
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
# Linux: use standard autotools build
# Check if configure script exists
if [ ! -f ./configure ]; then
# 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
# 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
fi
# Run configure if needed
if [ ! -f config.status ] || [ config.status -ot configure ]; then
# 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
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

81
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"
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
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"

31
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"

96
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"
AC_MSG_NOTICE([Using MSYS2 MINGW64 OpenSSL])
fi
CFLAGS="$CFLAGS $OPENSSL_CFLAGS"
LDFLAGS="$LDFLAGS $OPENSSL_LIBS"
AC_MSG_NOTICE([Using MSYS2 MINGW64 OpenSSL: /mingw64])
fi
;;
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_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 <linux/if_tun.h>
]], [[
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 <linux/if_tun.h>]], [[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}
"

251
lib/u_async.c

@ -8,6 +8,13 @@
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#ifndef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#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,54 +1049,113 @@ 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;
struct UASYNC* ua = malloc(sizeof(struct UASYNC));
if (!ua) {
socket_platform_cleanup();
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;
memset(ua, 0, sizeof(struct UASYNC));
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
ua->wakeup_initialized = 0;
ua->posted_tasks_head = NULL;
// 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);
#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;
@ -1035,8 +1165,13 @@ struct UASYNC* uasync_create(void) {
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;
@ -1119,6 +1254,9 @@ 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;
@ -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++;
@ -1174,10 +1320,15 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
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
@ -1204,6 +1355,18 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
}
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)
@ -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;

16
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

25
net_emulator/Makefile

@ -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

2
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;

2
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 <string.h>
#include <stdlib.h>
#include "../lib/platform_compat.h"
#define IPv4_VERSION 4
#define IPv6_VERSION 6

429
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 <stdlib.h>
#include <string.h>
// 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 <windows.h>
#endif
// ===================================================================
// Linux-only: TUN read callback (uasync) — определена ПЕРЕД tun_init
// ===================================================================
#ifndef _WIN32
// Internal read callback - called by uasync when data available on TUN
static void tun_read_callback(int fd, void* user_arg) {
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';
}
// Build IP address string from config
char ip_buffer[INET_ADDRSTRLEN];
char tun_ip_str[64];
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) {
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;
}
if (!tun->pool) goto fail;
// 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;
}
tun->output_queue = queue_new(ua, 0);
tun->input_queue = queue_new(ua, 0);
if (!tun->output_queue || !tun->input_queue) 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;
}
// 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;
}
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) {
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 (!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
}
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);
}
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);
}
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);
}
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;
}
// Platform-specific cleanup (skip in test mode)
if (!tun->test_mode) {
tun_platform_cleanup(tun);
}
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);
tun->write_errors++;
return -1;
}
tun->bytes_written += nwritten;
ssize_t n = tun_platform_write(tun, buf, len);
if (n > 0) {
tun->bytes_written += n;
tun->packets_written++;
DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "Wrote %zd bytes to TUN %s", nwritten, tun->ifname);
return nwritten;
}
struct ll_queue* tun_get_input_queue(struct tun_if* tun) {
if (!tun) {
return NULL;
}
return tun->input_queue;
}
struct ll_queue* tun_get_output_queue(struct tun_if* tun) {
if (!tun) {
return NULL;
}
return tun->output_queue;
}
int tun_is_test_mode(struct tun_if* tun) {
if (!tun) {
return -1;
} else {
tun->write_errors++;
}
return tun->test_mode;
return n;
}
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;
}
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; }
if (len > TUN_MAX_PACKET_SIZE) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Packet too large: %zu bytes (max %d)",
len, TUN_MAX_PACKET_SIZE);
}
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;
// 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);
uint8_t* data = malloc(len);
if (!data) { tun->read_errors++; return -1; }
memcpy(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;
}

159
src/tun_if.h

@ -6,11 +6,16 @@
#include <stddef.h>
#include <sys/types.h>
#ifdef _WIN32
#include <windows.h> // для 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" {
@ -19,154 +24,98 @@ extern "C" {
#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)
// ===================================================================
// 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);
#ifdef _WIN32
DWORD WINAPI tun_read_thread_proc(LPVOID arg);
#endif
#ifdef __cplusplus
}
#endif

420
src/tun_windows.c

@ -1,17 +1,16 @@
// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../lib/u_async.h"
#include "tun_if.h"
#include "wintun.h"
#include "ll_queue.h" // ← добавлено
#include "etcp.h" // ← добавлено (struct ETCP_FRAGMENT)
#include <windows.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
#include <stdio.h>
// 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;
@ -25,230 +24,159 @@ 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 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';
break;
}
}
path[i+1] = L'\0';
wcscat(path, L"wintun.dll");
wintun_dll = LoadLibraryW(path);
break;
}
}
// 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;
}
}
// 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';
// ===================================================================
// 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);
// Convert IP string to sockaddr
SOCKADDR_INET addr;
memset(&addr, 0, sizeof(addr));
addr.si_family = AF_INET;
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;
}
}
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;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "IP address %s and MTU %d configured successfully", ip_copy, mtu);
// 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);
}
}
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;
}
// Generate adapter name
const WCHAR* adapter_name = L"utun";
const WCHAR* tunnel_type = L"utun";
// ===================================================================
// 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;
// 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);
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);
WINTUN_ADAPTER_HANDLE adapter = WintunCreateAdapter(wname, L"utun", 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);
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "WintunCreateAdapter failed: %lu", GetLastError());
return -1;
}
} else {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Opened existing Wintun adapter: %s", tun->ifname);
}
// 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);
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;
}
int tun_platform_get_poll_fd(struct tun_if* tun)
{
(void)tun;
return -1;
}
}
// Copy packet data
if (packet_size > len) {
packet_size = (DWORD)len; // Truncate if buffer too small
// ===================================================================
// 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;
}

7
src/utun.c

@ -20,6 +20,7 @@
#include <getopt.h>
#include "../lib/platform_compat.h"
#include "../lib/socket_compat.h"
#ifdef _WIN32
#include <windows.h>
@ -266,11 +267,14 @@ int main(int argc, char *argv[]) {
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;
}

11
tests/Makefile.am

@ -23,7 +23,6 @@ check_PROGRAMS = \
test_route_lib \
test_bgp_route_exchange \
test_routing_mesh \
test_etcp_dummynet \
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)

21
tinycrypt/Makefile

@ -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) *~
Loading…
Cancel
Save