Browse Source
- 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 buildnodeinfo-routing-update
9 changed files with 927 additions and 223 deletions
Binary file not shown.
@ -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 |
||||
@ -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; |
||||
} |
||||
@ -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; |
||||
} |
||||
Loading…
Reference in new issue