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
This commit is contained in:
jeka committed 2026-02-20 21:20:50 +03:00
1 parent 734851329f
commit d10bddd1f7
15 files changed
+950 -915

No files matched your search

+242 -51
View File
@@ -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,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");
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;
ua->wakeup_pipe[0] = -1;
ua->wakeup_pipe[1] = -1;
ua->wakeup_initialized = 0;
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 UASYNC* ua = malloc(sizeof(struct UASYNC));
if (!ua) {
socket_platform_cleanup();
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;
}
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->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;
+16
View File
@@ -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