Browse Source

Add cross-platform TUN support with Wintun for Windows

- Split TUN implementation into platform-specific files:
  - tun_if.c: Common code (queues, callbacks, statistics)
  - tun_linux.c: Linux TUN/TAP implementation (/dev/net/tun + ioctl)
  - tun_windows.c: Windows Wintun implementation (wintun.dll + IP Helper)

- Update tun_if.h with platform abstraction layer:
  - tun_platform_init/cleanup/read/write/get_poll_fd
  - Platform handles: fd (Linux), WINTUN handles (Windows)

- Windows implementation features:
  - Dynamic loading of wintun.dll with graceful error handling
  - IP Helper API for IP address and MTU configuration
  - HANDLE-based uasync integration
  - Clear error message if wintun.dll is not found

- Update build system:
  - configure.ac: Detect Windows (mingw/msys/cygwin)
  - src/Makefile.am: Conditional compilation of tun_linux/tun_windows
  - tests/Makefile.am: Link platform-specific TUN objects

- Add wintun.dll and wintun.h to lib/ directory
- All 22 tests pass on Linux
- Ready for MSYS2 UCRT64 Windows build
nodeinfo-routing-update
Evgeny 2 months ago
parent
commit
15b670e563
  1. 13
      configure.ac
  2. BIN
      lib/wintun.dll
  3. 270
      lib/wintun.h
  4. 17
      src/Makefile.am
  5. 259
      src/tun_if.c
  6. 15
      src/tun_if.h
  7. 233
      src/tun_linux.c
  8. 335
      src/tun_windows.c
  9. 8
      tests/Makefile.am

13
configure.ac

@ -27,6 +27,19 @@ fi
AM_CONDITIONAL([USE_OPENSSL], [test "x$with_openssl" = "xyes"])
# Detect Windows for TUN implementation
AC_MSG_CHECKING([for Windows])
case $host_os in
*mingw* | *msys* | *cygwin* | *win*)
os_windows=yes
;;
*)
os_windows=no
;;
esac
AC_MSG_RESULT([$os_windows])
AM_CONDITIONAL([OS_WINDOWS], [test "x$os_windows" = "xyes"])
# Checks for header files
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])

BIN
lib/wintun.dll

Binary file not shown.

270
lib/wintun.h

@ -0,0 +1,270 @@
/* SPDX-License-Identifier: GPL-2.0 OR MIT
*
* Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
*/
#pragma once
#include <winsock2.h>
#include <windows.h>
#include <ipexport.h>
#include <ifdef.h>
#include <ws2ipdef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ALIGNED
# if defined(_MSC_VER)
# define ALIGNED(n) __declspec(align(n))
# elif defined(__GNUC__)
# define ALIGNED(n) __attribute__((aligned(n)))
# else
# error "Unable to define ALIGNED"
# endif
#endif
/* MinGW is missing this one, unfortunately. */
#ifndef _Post_maybenull_
# define _Post_maybenull_
#endif
#pragma warning(push)
#pragma warning(disable : 4324) /* structure was padded due to alignment specifier */
/**
* A handle representing Wintun adapter
*/
typedef struct _WINTUN_ADAPTER *WINTUN_ADAPTER_HANDLE;
/**
* Creates a new Wintun adapter.
*
* @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1
* characters.
*
* @param TunnelType Name of the adapter tunnel type. Zero-terminated string of up to MAX_ADAPTER_NAME-1
* characters.
*
* @param RequestedGUID The GUID of the created network adapter, which then influences NLA generation deterministically.
* If it is set to NULL, the GUID is chosen by the system at random, and hence a new NLA entry is
* created for each new adapter. It is called "requested" GUID because the API it uses is
* completely undocumented, and so there could be minor interesting complications with its usage.
*
* @return If the function succeeds, the return value is the adapter handle. Must be released with
* WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call
* GetLastError.
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_CREATE_ADAPTER_FUNC)
(_In_z_ LPCWSTR Name, _In_z_ LPCWSTR TunnelType, _In_opt_ const GUID *RequestedGUID);
/**
* Opens an existing Wintun adapter.
*
* @param Name The requested name of the adapter. Zero-terminated string of up to MAX_ADAPTER_NAME-1
* characters.
*
* @return If the function succeeds, the return value is the adapter handle. Must be released with
* WintunCloseAdapter. If the function fails, the return value is NULL. To get extended error information, call
* GetLastError.
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
WINTUN_ADAPTER_HANDLE(WINAPI WINTUN_OPEN_ADAPTER_FUNC)(_In_z_ LPCWSTR Name);
/**
* Releases Wintun adapter resources and, if adapter was created with WintunCreateAdapter, removes adapter.
*
* @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter.
*/
typedef VOID(WINAPI WINTUN_CLOSE_ADAPTER_FUNC)(_In_opt_ WINTUN_ADAPTER_HANDLE Adapter);
/**
* Deletes the Wintun driver if there are no more adapters in use.
*
* @return If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To
* get extended error information, call GetLastError.
*/
typedef _Return_type_success_(return != FALSE)
BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID);
/**
* Returns the LUID of the adapter.
*
* @param Adapter Adapter handle obtained with WintunCreateAdapter or WintunOpenAdapter
*
* @param Luid Pointer to LUID to receive adapter LUID.
*/
typedef VOID(WINAPI WINTUN_GET_ADAPTER_LUID_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _Out_ NET_LUID *Luid);
/**
* Determines the version of the Wintun driver currently loaded.
*
* @return If the function succeeds, the return value is the version number. If the function fails, the return value is
* zero. To get extended error information, call GetLastError. Possible errors include the following:
* ERROR_FILE_NOT_FOUND Wintun not loaded
*/
typedef _Return_type_success_(return != 0)
DWORD(WINAPI WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC)(VOID);
/**
* Determines the level of logging, passed to WINTUN_LOGGER_CALLBACK.
*/
typedef enum
{
WINTUN_LOG_INFO, /**< Informational */
WINTUN_LOG_WARN, /**< Warning */
WINTUN_LOG_ERR /**< Error */
} WINTUN_LOGGER_LEVEL;
/**
* Called by internal logger to report diagnostic messages
*
* @param Level Message level.
*
* @param Timestamp Message timestamp in in 100ns intervals since 1601-01-01 UTC.
*
* @param Message Message text.
*/
typedef VOID(CALLBACK *WINTUN_LOGGER_CALLBACK)(
_In_ WINTUN_LOGGER_LEVEL Level,
_In_ DWORD64 Timestamp,
_In_z_ LPCWSTR Message);
/**
* Sets logger callback function.
*
* @param NewLogger Pointer to callback function to use as a new global logger. NewLogger may be called from various
* threads concurrently. Should the logging require serialization, you must handle serialization in
* NewLogger. Set to NULL to disable.
*/
typedef VOID(WINAPI WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_CALLBACK NewLogger);
/**
* Minimum ring capacity.
*/
#define WINTUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */
/**
* Maximum ring capacity.
*/
#define WINTUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */
/**
* A handle representing Wintun session
*/
typedef struct _TUN_SESSION *WINTUN_SESSION_HANDLE;
/**
* Starts Wintun session.
*
* @param Adapter Adapter handle obtained with WintunOpenAdapter or WintunCreateAdapter
*
* @param Capacity Rings capacity. Must be between WINTUN_MIN_RING_CAPACITY and WINTUN_MAX_RING_CAPACITY (incl.)
* Must be a power of two.
*
* @return Wintun session handle. Must be released with WintunEndSession. If the function fails, the return value is
* NULL. To get extended error information, call GetLastError.
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
WINTUN_SESSION_HANDLE(WINAPI WINTUN_START_SESSION_FUNC)(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ DWORD Capacity);
/**
* Ends Wintun session.
*
* @param Session Wintun session handle obtained with WintunStartSession
*/
typedef VOID(WINAPI WINTUN_END_SESSION_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);
/**
* Gets Wintun session's read-wait event handle.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @return Pointer to receive event handle to wait for available data when reading. Should
* WintunReceivePackets return ERROR_NO_MORE_ITEMS (after spinning on it for a while under heavy
* load), wait for this event to become signaled before retrying WintunReceivePackets. Do not call
* CloseHandle on this event - it is managed by the session.
*/
typedef HANDLE(WINAPI WINTUN_GET_READ_WAIT_EVENT_FUNC)(_In_ WINTUN_SESSION_HANDLE Session);
/**
* Maximum IP packet size
*/
#define WINTUN_MAX_IP_PACKET_SIZE 0xFFFF
/**
* Retrieves one or packet. After the packet content is consumed, call WintunReleaseReceivePacket with Packet returned
* from this function to release internal buffer. This function is thread-safe.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param PacketSize Pointer to receive packet size.
*
* @return Pointer to layer 3 IPv4 or IPv6 packet. Client may modify its content at will. If the function fails, the
* return value is NULL. To get extended error information, call GetLastError. Possible errors include the
* following:
* ERROR_HANDLE_EOF Wintun adapter is terminating;
* ERROR_NO_MORE_ITEMS Wintun buffer is exhausted;
* ERROR_INVALID_DATA Wintun buffer is corrupt
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
_Post_writable_byte_size_(*PacketSize)
BYTE *(WINAPI WINTUN_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _Out_ DWORD *PacketSize);
/**
* Releases internal buffer after the received packet has been processed by the client. This function is thread-safe.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param Packet Packet obtained with WintunReceivePacket
*/
typedef VOID(
WINAPI WINTUN_RELEASE_RECEIVE_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);
/**
* Allocates memory for a packet to send. After the memory is filled with packet data, call WintunSendPacket to send
* and release internal buffer. WintunAllocateSendPacket is thread-safe and the WintunAllocateSendPacket order of
* calls define the packet sending order.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param PacketSize Exact packet size. Must be less or equal to WINTUN_MAX_IP_PACKET_SIZE.
*
* @return Returns pointer to memory where to prepare layer 3 IPv4 or IPv6 packet for sending. If the function fails,
* the return value is NULL. To get extended error information, call GetLastError. Possible errors include the
* following:
* ERROR_HANDLE_EOF Wintun adapter is terminating;
* ERROR_BUFFER_OVERFLOW Wintun buffer is full;
*/
typedef _Must_inspect_result_
_Return_type_success_(return != NULL)
_Post_maybenull_
_Post_writable_byte_size_(PacketSize)
BYTE *(WINAPI WINTUN_ALLOCATE_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ DWORD PacketSize);
/**
* Sends the packet and releases internal buffer. WintunSendPacket is thread-safe, but the WintunAllocateSendPacket
* order of calls define the packet sending order. This means the packet is not guaranteed to be sent in the
* WintunSendPacket yet.
*
* @param Session Wintun session handle obtained with WintunStartSession
*
* @param Packet Packet obtained with WintunAllocateSendPacket
*/
typedef VOID(WINAPI WINTUN_SEND_PACKET_FUNC)(_In_ WINTUN_SESSION_HANDLE Session, _In_ const BYTE *Packet);
#pragma warning(pop)
#ifdef __cplusplus
}
#endif

17
src/Makefile.am

@ -18,6 +18,15 @@ utun_CORE_SOURCES = \
pkt_normalizer.c \
etcp_api.c
# Platform-specific TUN sources
if OS_WINDOWS
utun_TUN_SOURCES = tun_windows.c
utun_TUN_LIBS = -liphlpapi -lws2_32
else
utun_TUN_SOURCES = tun_linux.c
utun_TUN_LIBS =
endif
# TinyCrypt sources (only used without OpenSSL)
utun_TINYCRYPT_SOURCES = \
$(top_srcdir)/tinycrypt/lib/source/aes_encrypt.c \
@ -36,9 +45,9 @@ utun_TINYCRYPT_SOURCES = \
# Combine sources based on OpenSSL usage
if USE_OPENSSL
utun_SOURCES = $(utun_CORE_SOURCES)
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TUN_SOURCES)
else
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TINYCRYPT_SOURCES)
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TINYCRYPT_SOURCES) $(utun_TUN_SOURCES)
endif
# Include paths
@ -57,9 +66,9 @@ utun_CORE_LDADD = \
-lm
if USE_OPENSSL
utun_LDADD = $(utun_CORE_LDADD) -lcrypto
utun_LDADD = $(utun_CORE_LDADD) -lcrypto $(utun_TUN_LIBS)
else
utun_LDADD = $(utun_CORE_LDADD)
utun_LDADD = $(utun_CORE_LDADD) $(utun_TUN_LIBS)
endif
# TinyCrypt object files (for tests that need them)

259
src/tun_if.c

@ -1,5 +1,6 @@
// tun_if.c - Simplified TUN interface management implementation
#define _POSIX_C_SOURCE 200809L
// 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"
#include "config_parser.h"
#include "etcp.h"
@ -10,163 +11,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <errno.h>
// Helper for interface ioctl operations
static int if_ioctl(const char *ifname, unsigned long request, struct ifreq *ifr) {
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create ioctl socket: %s", strerror(errno));
return -1;
}
strncpy(ifr->ifr_name, ifname, IFNAMSIZ - 1);
ifr->ifr_name[IFNAMSIZ - 1] = '\0';
int ret = ioctl(sock, request, ifr);
if (ret < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "ioctl failed for %s (request=0x%lx): %s", ifname, request, strerror(errno));
}
close(sock);
return ret;
}
// Parse IP address and CIDR mask
static int parse_ip_mask(const char *ip_addr, struct in_addr *ip, struct in_addr *netmask) {
if (!ip_addr || !ip || !netmask) return -1;
char ip_str[INET_ADDRSTRLEN];
char *slash = strchr(ip_addr, '/');
if (!slash) return -1;
size_t ip_len = slash - ip_addr;
if (ip_len >= sizeof(ip_str)) return -1;
strncpy(ip_str, ip_addr, ip_len);
ip_str[ip_len] = '\0';
if (inet_pton(AF_INET, ip_str, ip) <= 0) return -1;
char *endptr;
long cidr = strtol(slash + 1, &endptr, 10);
if (*endptr != '\0' || cidr < 0 || cidr > 32) return -1;
uint32_t mask = (cidr == 0) ? 0 : ~((1U << (32 - cidr)) - 1);
netmask->s_addr = htonl(mask);
return 0;
}
// Create TUN device
static int create_tun_device(char *ifname, size_t ifname_len) {
struct ifreq ifr;
int fd;
fd = open("/dev/net/tun", O_RDWR);
if (fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to open /dev/net/tun: %s", strerror(errno));
return -1;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
if (ifname && ifname_len > 0 && ifname[0] != '\0') {
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
}
if (ioctl(fd, TUNSETIFF, &ifr) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to configure TUN device: %s", strerror(errno));
close(fd);
return -1;
}
if (ifname && ifname_len > 0) {
strncpy(ifname, ifr.ifr_name, ifname_len - 1);
ifname[ifname_len - 1] = '\0';
}
return fd;
}
// Set IP address on TUN interface
static int tun_set_ip(const char *ifname, const char *ip_addr) {
if (!ifname || !ip_addr) {
errno = EINVAL;
return -1;
}
struct in_addr ip, netmask;
if (parse_ip_mask(ip_addr, &ip, &netmask) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid IP address format: %s", ip_addr);
errno = EINVAL;
return -1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
// Set IP address
struct sockaddr_in *addr = (struct sockaddr_in *)&ifr.ifr_addr;
addr->sin_family = AF_INET;
addr->sin_addr = ip;
if (if_ioctl(ifname, SIOCSIFADDR, &ifr) < 0) return -1;
// Set netmask
addr = (struct sockaddr_in *)&ifr.ifr_netmask;
addr->sin_family = AF_INET;
addr->sin_addr = netmask;
if (if_ioctl(ifname, SIOCSIFNETMASK, &ifr) < 0) return -1;
return 0;
}
// Bring TUN interface up
static int tun_set_up(const char *ifname) {
if (!ifname) {
errno = EINVAL;
return -1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
// Get current flags
if (if_ioctl(ifname, SIOCGIFFLAGS, &ifr) < 0) return -1;
// Set UP and RUNNING
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
if (if_ioctl(ifname, SIOCSIFFLAGS, &ifr) < 0) return -1;
return 0;
}
// Set MTU on TUN interface
static int tun_set_mtu(const char *ifname, int mtu) {
if (!ifname || mtu <= 0) {
errno = EINVAL;
return -1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_mtu = mtu;
if (if_ioctl(ifname, SIOCSIFMTU, &ifr) < 0) return -1;
return 0;
}
// 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) {
@ -181,10 +25,9 @@ static void tun_input_queue_callback(struct ll_queue* q, void* arg) {
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 = write(tun->fd, pkt->ll.dgram, pkt->ll.len);
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: %s",
tun->ifname, strerror(errno));
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to write to TUN %s", tun->ifname);
tun->write_errors++;
} else {
tun->bytes_written += nwritten;
@ -208,14 +51,14 @@ static void tun_input_queue_callback(struct ll_queue* q, void* arg) {
// Internal read callback - called by uasync when data available on TUN
static void tun_read_callback(int fd, void* user_arg) {
(void)fd;
struct tun_if* tun = (struct tun_if*)user_arg;
uint8_t buffer[TUN_MAX_PACKET_SIZE];
// Read from TUN device
ssize_t nread = read(fd, buffer, sizeof(buffer));
ssize_t nread = tun_platform_read(tun, buffer, sizeof(buffer));
if (nread < 0) {
if (errno == EINTR) return;
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to read from TUN device %s: %s", tun->ifname, strerror(errno));
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to read from TUN device %s", tun->ifname);
tun->read_errors++;
return;
}
@ -246,9 +89,9 @@ static void tun_read_callback(int fd, void* user_arg) {
pkt->ll.dgram = packet_data;
pkt->ll.len = nread;
// Add to input queue
// 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, "Failed to add packet to input queue");
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add packet to output queue");
free(packet_data);
memory_pool_free(tun->pool, pkt);
tun->read_errors++;
@ -277,19 +120,15 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
tun->ua = ua;
tun->fd = -1;
tun->platform_handle = NULL;
tun->adapter_handle = NULL;
// 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);
}
// Create TUN device
tun->fd = create_tun_device(tun->ifname, sizeof(tun->ifname));
if (tun->fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TUN device");
free(tun);
return NULL;
} else {
tun->ifname[0] = '\0';
}
// Build IP address string from config
@ -298,39 +137,19 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
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);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Only IPv4 is supported for TUN");
close(tun->fd);
free(tun);
return NULL;
}
#ifdef __linux__
snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
snprintf(tun_ip_str, sizeof(tun_ip_str), "%s", ip_buffer);
#else
snprintf(tun_ip_str, sizeof(tun_ip_str), "%s/32", ip_buffer);
#endif
// Configure IP address
if (tun_set_ip(tun->ifname, tun_ip_str) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP: %s", tun_ip_str);
close(tun->fd);
free(tun);
return NULL;
}
// Set MTU to 1500 (default)
// Get MTU
int mtu = config->global.mtu > 0 ? config->global.mtu : TUN_MTU_DEFAULT;
if (tun_set_mtu(tun->ifname, mtu) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set MTU %d on %s: %s", mtu, tun->ifname, strerror(errno));
}
// Bring interface up
if (tun_set_up(tun->ifname) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to bring up TUN interface %s", tun->ifname);
close(tun->fd);
// Platform-specific initialization
if (tun_platform_init(tun, tun->ifname, tun_ip_str, mtu) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Platform-specific TUN initialization failed");
free(tun);
return NULL;
}
@ -339,7 +158,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
tun->pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
if (!tun->pool) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create memory pool");
close(tun->fd);
tun_platform_cleanup(tun);
free(tun);
return NULL;
}
@ -349,7 +168,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
if (!tun->output_queue) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create output queue");
memory_pool_destroy(tun->pool);
close(tun->fd);
tun_platform_cleanup(tun);
free(tun);
return NULL;
}
@ -360,7 +179,7 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create input queue");
queue_free(tun->output_queue);
memory_pool_destroy(tun->pool);
close(tun->fd);
tun_platform_cleanup(tun);
free(tun);
return NULL;
}
@ -369,18 +188,30 @@ struct tun_if* tun_init(struct UASYNC* ua, struct utun_config* config) {
queue_set_callback(tun->input_queue, tun_input_queue_callback, tun);
// Register TUN socket with uasync
tun->socket_id = uasync_add_socket(ua, tun->fd, tun_read_callback, NULL, NULL, tun);
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);
close(tun->fd);
tun_platform_cleanup(tun);
free(tun);
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s (fd=%d, IP=%s, MTU=%d)",
tun->ifname, tun->fd, tun_ip_str, mtu);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s (IP=%s, MTU=%d)",
tun->ifname, tun_ip_str, mtu);
return tun;
}
@ -428,25 +259,21 @@ void tun_close(struct tun_if* tun) {
tun->pool = NULL;
}
// Close file descriptor
if (tun->fd >= 0) {
close(tun->fd);
tun->fd = -1;
}
// Platform-specific cleanup
tun_platform_cleanup(tun);
free(tun);
}
ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
if (!tun || tun->fd < 0 || !buf || len == 0) {
if (!tun || !buf || len == 0) {
if (tun) tun->write_errors++;
errno = EINVAL;
return -1;
}
ssize_t nwritten = write(tun->fd, buf, len);
ssize_t nwritten = tun_platform_write(tun, buf, len);
if (nwritten < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to write to TUN device %s: %s", tun->ifname, strerror(errno));
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to write to TUN device %s", tun->ifname);
tun->write_errors++;
return -1;
}

15
src/tun_if.h

@ -1,4 +1,4 @@
// tun_if.h - Simplified TUN interface management for utun
// tun_if.h - Cross-platform TUN interface management for utun
#ifndef TUN_IF_H
#define TUN_IF_H
@ -21,8 +21,10 @@ extern "C" {
// TUN interface handle - opaque structure
struct tun_if {
char ifname[16]; // Interface name (e.g., "tun12")
int fd; // File descriptor
char ifname[16]; // Interface name (e.g., "tun12" or "utun")
int fd; // Linux: file descriptor, Windows: -1 (unused)
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
@ -65,6 +67,13 @@ void tun_close(struct tun_if* tun);
*/
ssize_t tun_write(struct tun_if* tun, const uint8_t* buf, size_t len);
// 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);
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 __cplusplus
}
#endif

233
src/tun_linux.c

@ -0,0 +1,233 @@
// tun_linux.c - Linux TUN/TAP implementation
// Uses /dev/net/tun device with ioctl
#include "tun_if.h"
#include "../lib/debug_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <errno.h>
// Helper for interface ioctl operations
static int if_ioctl(const char *ifname, unsigned long request, struct ifreq *ifr) {
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create ioctl socket: %s", strerror(errno));
return -1;
}
strncpy(ifr->ifr_name, ifname, IFNAMSIZ - 1);
ifr->ifr_name[IFNAMSIZ - 1] = '\0';
int ret = ioctl(sock, request, ifr);
if (ret < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "ioctl failed for %s (request=0x%lx): %s", ifname, request, strerror(errno));
}
close(sock);
return ret;
}
// Parse IP address and CIDR mask
static int parse_ip_mask(const char *ip_addr, struct in_addr *ip, struct in_addr *netmask) {
if (!ip_addr || !ip || !netmask) return -1;
char ip_str[INET_ADDRSTRLEN];
char *slash = strchr(ip_addr, '/');
if (!slash) return -1;
size_t ip_len = slash - ip_addr;
if (ip_len >= sizeof(ip_str)) return -1;
strncpy(ip_str, ip_addr, ip_len);
ip_str[ip_len] = '\0';
if (inet_pton(AF_INET, ip_str, ip) <= 0) return -1;
char *endptr;
long cidr = strtol(slash + 1, &endptr, 10);
if (*endptr != '\0' || cidr < 0 || cidr > 32) return -1;
uint32_t mask = (cidr == 0) ? 0 : ~((1U << (32 - cidr)) - 1);
netmask->s_addr = htonl(mask);
return 0;
}
// Create TUN device
static int create_tun_device(char *ifname, size_t ifname_len) {
struct ifreq ifr;
int fd;
fd = open("/dev/net/tun", O_RDWR);
if (fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to open /dev/net/tun: %s", strerror(errno));
return -1;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
if (ifname && ifname_len > 0 && ifname[0] != '\0') {
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
}
if (ioctl(fd, TUNSETIFF, &ifr) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to configure TUN device: %s", strerror(errno));
close(fd);
return -1;
}
if (ifname && ifname_len > 0) {
strncpy(ifname, ifr.ifr_name, ifname_len - 1);
ifname[ifname_len - 1] = '\0';
}
return fd;
}
// Set IP address on TUN interface
static int tun_set_ip(const char *ifname, const char *ip_addr) {
if (!ifname || !ip_addr) {
errno = EINVAL;
return -1;
}
struct in_addr ip, netmask;
if (parse_ip_mask(ip_addr, &ip, &netmask) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid IP address format: %s", ip_addr);
errno = EINVAL;
return -1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
// Set IP address
struct sockaddr_in *addr = (struct sockaddr_in *)&ifr.ifr_addr;
addr->sin_family = AF_INET;
addr->sin_addr = ip;
if (if_ioctl(ifname, SIOCSIFADDR, &ifr) < 0) return -1;
// Set netmask
addr = (struct sockaddr_in *)&ifr.ifr_netmask;
addr->sin_family = AF_INET;
addr->sin_addr = netmask;
if (if_ioctl(ifname, SIOCSIFNETMASK, &ifr) < 0) return -1;
return 0;
}
// Bring TUN interface up
static int tun_set_up(const char *ifname) {
if (!ifname) {
errno = EINVAL;
return -1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
// Get current flags
if (if_ioctl(ifname, SIOCGIFFLAGS, &ifr) < 0) return -1;
// Set UP and RUNNING
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
if (if_ioctl(ifname, SIOCSIFFLAGS, &ifr) < 0) return -1;
return 0;
}
// Set MTU on TUN interface
static int tun_set_mtu(const char *ifname, int mtu) {
if (!ifname || mtu <= 0) {
errno = EINVAL;
return -1;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_mtu = mtu;
if (if_ioctl(ifname, SIOCSIFMTU, &ifr) < 0) return -1;
return 0;
}
// Platform-specific initialization for Linux
int tun_platform_init(struct tun_if* tun, const char* ifname, const char* ip_str, int mtu) {
(void)ifname; // ifname is stored in tun->ifname by caller
// Create TUN device
tun->fd = create_tun_device(tun->ifname, sizeof(tun->ifname));
if (tun->fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TUN device");
return -1;
}
// Configure IP address
if (tun_set_ip(tun->ifname, ip_str) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to set TUN IP: %s", ip_str);
close(tun->fd);
tun->fd = -1;
return -1;
}
// Set MTU
if (tun_set_mtu(tun->ifname, mtu) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set MTU %d on %s: %s", mtu, tun->ifname, strerror(errno));
}
// Bring interface up
if (tun_set_up(tun->ifname) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to bring up TUN interface %s", tun->ifname);
close(tun->fd);
tun->fd = -1;
return -1;
}
// Set non-blocking mode
int flags = fcntl(tun->fd, F_GETFL, 0);
if (flags >= 0) {
fcntl(tun->fd, F_SETFL, flags | O_NONBLOCK);
}
return 0;
}
// Platform-specific cleanup for Linux
void tun_platform_cleanup(struct tun_if* tun) {
if (tun->fd >= 0) {
close(tun->fd);
tun->fd = -1;
}
}
// Platform-specific read for Linux
ssize_t tun_platform_read(struct tun_if* tun, uint8_t* buf, size_t len) {
ssize_t nread = read(tun->fd, buf, len);
if (nread < 0 && errno == EINTR) {
return 0; // Interrupted, not an error
}
return nread;
}
// Platform-specific write for Linux
ssize_t tun_platform_write(struct tun_if* tun, const uint8_t* buf, size_t len) {
return write(tun->fd, buf, len);
}
// Get poll fd for uasync
int tun_platform_get_poll_fd(struct tun_if* tun) {
return tun->fd;
}

335
src/tun_windows.c

@ -0,0 +1,335 @@
// tun_windows.c - Windows Wintun implementation
// Uses Wintun.dll for TUN device access
#include "tun_if.h"
#include "../lib/debug_config.h"
#include "../lib/wintun.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
// 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
}
// 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 (path[i] == L'\\' || path[i] == L'/') {
path[i + 1] = L'\0';
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) {
// 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");
return -1;
}
// Resolve function pointers
#define WINTUN_GET_FUNC(name) \
name = (WINTUN_##name##_FUNC)GetProcAddress(wintun_dll, "Wintun" #name); \
if (!name) { \
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to resolve Wintun" #name); \
FreeLibrary(wintun_dll); \
wintun_dll = NULL; \
return -1; \
}
WINTUN_GET_FUNC(CreateAdapter);
WINTUN_GET_FUNC(CloseAdapter);
WINTUN_GET_FUNC(OpenAdapter);
WINTUN_GET_FUNC(GetAdapterLUID);
WINTUN_GET_FUNC(StartSession);
WINTUN_GET_FUNC(EndSession);
WINTUN_GET_FUNC(GetReadWaitEvent);
WINTUN_GET_FUNC(ReceivePacket);
WINTUN_GET_FUNC(ReleaseReceivePacket);
WINTUN_GET_FUNC(AllocateSendPacket);
WINTUN_GET_FUNC(SendPacket);
#undef WINTUN_GET_FUNC
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';
// 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);
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");
return -1;
}
// Set MTU first using MIB_IPINTERFACE_ROW
MIB_IPINTERFACE_ROW row;
memset(&row, 0, sizeof(row));
row.Family = AF_INET;
row.InterfaceLuid = *luid;
row.SitePrefixLength = 0;
row.NlMtu = mtu;
row.UseAutomaticMetric = FALSE;
row.Metric = 0;
row.PromiscuousMode = FALSE;
row.DadState = IpDadStatePreferred;
row.ConnectionType = NET_IF_CONNECTION_WAKED_UP;
row.InterfaceIdentifierPadding = 0;
row.SkipAsSource = FALSE;
DWORD ret = SetIpInterfaceEntry(&row);
if (ret != NO_ERROR && ret != ERROR_OBJECT_ALREADY_EXISTS) {
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Failed to set MTU: %lu", ret);
}
// 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.DadState = IpDadStatePreferred;
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);
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";
// Use provided name or default
wchar_t wname[128];
if (ifname && ifname[0] != '\0') {
MultiByteToWideChar(CP_UTF8, 0, ifname, -1, wname, 128);
adapter_name = wname;
}
// Create or open Wintun adapter
GUID guid = {0};
WINTUN_ADAPTER_HANDLE adapter = WintunCreateAdapter(adapter_name, tunnel_type, &guid);
if (!adapter) {
DWORD err = GetLastError();
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create Wintun adapter: %lu", err);
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);
if (!session) {
DWORD err = GetLastError();
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to start Wintun session: %lu", err);
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) {
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);
return 0;
}
// Platform-specific cleanup for Windows
void tun_platform_cleanup(struct tun_if* tun) {
if (tun->platform_handle) {
WintunEndSession((WINTUN_SESSION_HANDLE)tun->platform_handle);
tun->platform_handle = NULL;
}
if (tun->adapter_handle) {
WintunCloseAdapter((WINTUN_ADAPTER_HANDLE)tun->adapter_handle);
tun->adapter_handle = NULL;
}
}
// 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) {
return -1;
}
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;
}
// Copy packet data
if (packet_size > len) {
packet_size = (DWORD)len; // Truncate if buffer too small
}
memcpy(buf, packet, packet_size);
// Release packet back to Wintun
WintunReleaseReceivePacket((WINTUN_SESSION_HANDLE)tun->platform_handle, packet);
return (ssize_t)packet_size;
}
// 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;
}
// 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;
}
// Copy data and send
memcpy(packet, buf, len);
WintunSendPacket((WINTUN_SESSION_HANDLE)tun->platform_handle, packet);
return (ssize_t)len;
}
// Get poll fd for uasync
int tun_platform_get_poll_fd(struct tun_if* tun) {
if (!tun->platform_handle) {
return -1;
}
HANDLE event = WintunGetReadWaitEvent((WINTUN_SESSION_HANDLE)tun->platform_handle);
if (event == NULL || event == INVALID_HANDLE_VALUE) {
return -1;
}
// Cast HANDLE to int for uasync (Windows specific)
return (int)(intptr_t)event;
}

8
tests/Makefile.am

@ -56,6 +56,13 @@ ETCP_CORE_OBJS = \
$(top_builddir)/src/utun-pkt_normalizer.o \
$(top_builddir)/src/utun-etcp_api.o
# Platform-specific TUN objects
if OS_WINDOWS
TUN_PLATFORM_OBJ = $(top_builddir)/src/utun-tun_windows.o
else
TUN_PLATFORM_OBJ = $(top_builddir)/src/utun-tun_linux.o
endif
# Full ETCP objects
ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-config_parser.o \
@ -64,6 +71,7 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-route_bgp.o \
$(top_builddir)/src/utun-routing.o \
$(top_builddir)/src/utun-tun_if.o \
$(TUN_PLATFORM_OBJ) \
$(top_builddir)/src/utun-utun_instance.o \
$(ETCP_CORE_OBJS)

Loading…
Cancel
Save