You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
12 KiB
326 lines
12 KiB
// 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 (wintun.h defines types as functions, need pointer syntax) |
|
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 (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 |
|
if (!WintunCreateAdapter || !WintunCloseAdapter || !WintunOpenAdapter || |
|
!WintunGetAdapterLUID || !WintunStartSession || !WintunEndSession || |
|
!WintunGetReadWaitEvent || !WintunReceivePacket || !WintunReleaseReceivePacket || |
|
!WintunAllocateSendPacket || !WintunSendPacket) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to resolve some 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'; |
|
|
|
// 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 (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); |
|
} |
|
|
|
// 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); |
|
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; |
|
}
|
|
|