// 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 #include #include #include #include #include // 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; }