/* SPDX-License-Identifier: GPL-2.0 * * Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved. */ #include #include #include #include #include #include #include #include "wintun.h" static WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter; static WINTUN_DELETE_ADAPTER_FUNC WintunDeleteAdapter; static WINTUN_DELETE_DRIVER_FUNC WintunDeleteDriver; static WINTUN_ENUM_ADAPTERS_FUNC WintunEnumAdapters; static WINTUN_FREE_ADAPTER_FUNC WintunFreeAdapter; static WINTUN_GET_ADAPTER_FUNC WintunGetAdapter; static WINTUN_GET_ADAPTER_DEVICE_OBJECT_FUNC WintunGetAdapterDeviceObject; static WINTUN_GET_ADAPTER_GUID_FUNC WintunGetAdapterGUID; static WINTUN_GET_ADAPTER_LUID_FUNC WintunGetAdapterLUID; static WINTUN_GET_ADAPTER_NAME_FUNC WintunGetAdapterName; static WINTUN_SET_ADAPTER_NAME_FUNC WintunSetAdapterName; static WINTUN_GET_VERSION_FUNC WintunGetVersion; static WINTUN_SET_LOGGER_FUNC WintunSetLogger; static WINTUN_START_SESSION_FUNC WintunStartSession; static WINTUN_END_SESSION_FUNC WintunEndSession; static WINTUN_GET_READ_WAIT_EVENT_FUNC WintunGetReadWaitEvent; static WINTUN_RECEIVE_PACKET_FUNC WintunReceivePacket; static WINTUN_RECEIVE_RELEASE_FUNC WintunReceiveRelease; static WINTUN_ALLOCATE_SEND_PACKET_FUNC WintunAllocateSendPacket; static WINTUN_SEND_PACKET_FUNC WintunSendPacket; static HANDLE QuitEvent; static volatile BOOL HaveQuit; static BOOL CALLBACK ConsoleLogger(_In_ WINTUN_LOGGER_LEVEL Level, _In_z_ const WCHAR *LogLine) { FILETIME Timestamp; GetSystemTimePreciseAsFileTime(&Timestamp); SYSTEMTIME SystemTime; FileTimeToSystemTime(&Timestamp, &SystemTime); WCHAR LevelMarker; switch (Level) { case WINTUN_LOG_INFO: LevelMarker = L'+'; break; case WINTUN_LOG_WARN: LevelMarker = L'-'; break; case WINTUN_LOG_ERR: LevelMarker = L'!'; break; default: return FALSE; } fwprintf( stderr, L"%04d-%02d-%02d %02d:%02d:%02d.%04d [%c] %s\n", SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds, LevelMarker, LogLine); return TRUE; } static DWORD LogError(_In_z_ const WCHAR *Prefix, _In_ DWORD Error) { WCHAR *SystemMessage = NULL, *FormattedMessage = NULL; FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, HRESULT_FROM_SETUPAPI(Error), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (void *)&SystemMessage, 0, NULL); FormatMessageW( FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_MAX_WIDTH_MASK, SystemMessage ? L"%1: %3(Code 0x%2!08X!)" : L"%1: Code 0x%2!08X!", 0, 0, (void *)&FormattedMessage, 0, (va_list *)(DWORD_PTR[]){ (DWORD_PTR)Prefix, (DWORD_PTR)Error, (DWORD_PTR)SystemMessage }); if (FormattedMessage) ConsoleLogger(WINTUN_LOG_ERR, FormattedMessage); LocalFree(FormattedMessage); LocalFree(SystemMessage); return Error; } static void Log(_In_ WINTUN_LOGGER_LEVEL Level, _In_z_ const WCHAR *Format, ...) { WCHAR LogLine[0x200]; va_list args; va_start(args, Format); _vsnwprintf_s(LogLine, _countof(LogLine), _TRUNCATE, Format, args); va_end(args); ConsoleLogger(Level, LogLine); } static BOOL WINAPI CtrlHandler(_In_ DWORD CtrlType) { switch (CtrlType) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: Log(WINTUN_LOG_INFO, L"Cleaning up and shutting down..."); HaveQuit = TRUE; SetEvent(QuitEvent); return TRUE; } return FALSE; } static void PrintPacket(_In_ const BYTE *Packet, _In_ DWORD PacketSize) { if (PacketSize < 20) { Log(WINTUN_LOG_INFO, L"Received packet without room for an IP header"); return; } BYTE IpVersion = Packet[0] >> 4, Proto; WCHAR Src[46], Dst[46]; if (IpVersion == 4) { RtlIpv4AddressToStringW((struct in_addr *)&Packet[12], Src); RtlIpv4AddressToStringW((struct in_addr *)&Packet[16], Dst); Proto = Packet[9]; Packet += 20, PacketSize -= 20; } else if (IpVersion == 6 && PacketSize < 40) { Log(WINTUN_LOG_INFO, L"Received packet without room for an IP header"); return; } else if (IpVersion == 6) { RtlIpv6AddressToStringW((struct in6_addr *)&Packet[8], Src); RtlIpv6AddressToStringW((struct in6_addr *)&Packet[24], Dst); Proto = Packet[6]; Packet += 40, PacketSize -= 40; } else { Log(WINTUN_LOG_INFO, L"Received packet that was not IP"); return; } if (Proto == 1 && PacketSize >= 8 && Packet[0] == 0) Log(WINTUN_LOG_INFO, L"Received IPv%d ICMP echo reply from %s to %s", IpVersion, Src, Dst); else Log(WINTUN_LOG_INFO, L"Received IPv%d proto 0x%x packet from %s to %s", IpVersion, Proto, Src, Dst); } static USHORT IPChecksum(BYTE *Buffer, DWORD Len) { ULONG Sum = 0; for (; Len > 1; Len -= 2, Buffer += 2) Sum += *(USHORT *)Buffer; if (Len) Sum += *Buffer; Sum = (Sum >> 16) + (Sum & 0xffff); Sum += (Sum >> 16); return (USHORT)(~Sum); } static void MakeICMP(_Inout_ BYTE Packet[28]) { memset(Packet, 0, 28); Packet[0] = 0x45; *(USHORT *)&Packet[2] = htons(28); Packet[8] = 255; Packet[9] = 1; *(ULONG *)&Packet[12] = htonl((10 << 24) | (6 << 16) | (7 << 8) | (8 << 0)); /* 10.6.7.8 */ *(ULONG *)&Packet[16] = htonl((10 << 24) | (6 << 16) | (7 << 8) | (7 << 0)); /* 10.6.7.7 */ *(USHORT *)&Packet[10] = IPChecksum(Packet, 20); Packet[20] = 8; *(USHORT *)&Packet[22] = IPChecksum(&Packet[20], 8); Log(WINTUN_LOG_INFO, L"Sending IPv4 ICMP echo request to 10.6.7.8 from 10.6.7.7"); } static DWORD WINAPI ReceivePackets(_Inout_ DWORD_PTR SessionPtr) { WINTUN_SESSION_HANDLE Session = (WINTUN_SESSION_HANDLE)SessionPtr; HANDLE WaitHandles[] = { WintunGetReadWaitEvent(Session), QuitEvent }; while (!HaveQuit) { BYTE *Packet; DWORD PacketSize; DWORD Result = WintunReceivePacket(Session, &Packet, &PacketSize); switch (Result) { case ERROR_SUCCESS: PrintPacket(Packet, PacketSize); WintunReceiveRelease(Session, Packet); continue; case ERROR_NO_MORE_ITEMS: if (WaitForMultipleObjects(_countof(WaitHandles), WaitHandles, FALSE, INFINITE) == WAIT_OBJECT_0) continue; return ERROR_SUCCESS; default: LogError(L"Packet read failed", Result); return Result; } } return ERROR_SUCCESS; } static DWORD WINAPI SendPackets(_Inout_ DWORD_PTR SessionPtr) { WINTUN_SESSION_HANDLE Session = (WINTUN_SESSION_HANDLE)SessionPtr; while (!HaveQuit) { BYTE *Packet; WintunAllocateSendPacket(Session, 28, &Packet); MakeICMP(Packet); WintunSendPacket(Session, Packet); switch (WaitForSingleObject(QuitEvent, 1000 /* 1 second */)) { case WAIT_ABANDONED: case WAIT_OBJECT_0: return ERROR_SUCCESS; } } return ERROR_SUCCESS; } static HMODULE InitializeWintun(void) { HMODULE Wintun = LoadLibraryExW(L"wintun.dll", NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32); if (!Wintun) return NULL; #define X(Name, Type) ((Name = (Type)GetProcAddress(Wintun, #Name)) == NULL) if (X(WintunCreateAdapter, WINTUN_CREATE_ADAPTER_FUNC) || X(WintunDeleteAdapter, WINTUN_DELETE_ADAPTER_FUNC) || X(WintunDeleteDriver, WINTUN_DELETE_DRIVER_FUNC) || X(WintunEnumAdapters, WINTUN_ENUM_ADAPTERS_FUNC) || X(WintunFreeAdapter, WINTUN_FREE_ADAPTER_FUNC) || X(WintunGetAdapter, WINTUN_GET_ADAPTER_FUNC) || X(WintunGetAdapterDeviceObject, WINTUN_GET_ADAPTER_DEVICE_OBJECT_FUNC) || X(WintunGetAdapterGUID, WINTUN_GET_ADAPTER_GUID_FUNC) || X(WintunGetAdapterLUID, WINTUN_GET_ADAPTER_LUID_FUNC) || X(WintunGetAdapterName, WINTUN_GET_ADAPTER_NAME_FUNC) || X(WintunSetAdapterName, WINTUN_SET_ADAPTER_NAME_FUNC) || X(WintunGetVersion, WINTUN_GET_VERSION_FUNC) || X(WintunSetLogger, WINTUN_SET_LOGGER_FUNC) || X(WintunStartSession, WINTUN_START_SESSION_FUNC) || X(WintunEndSession, WINTUN_END_SESSION_FUNC) || X(WintunGetReadWaitEvent, WINTUN_GET_READ_WAIT_EVENT_FUNC) || X(WintunReceivePacket, WINTUN_RECEIVE_PACKET_FUNC) || X(WintunReceiveRelease, WINTUN_RECEIVE_RELEASE_FUNC) || X(WintunAllocateSendPacket, WINTUN_ALLOCATE_SEND_PACKET_FUNC) || X(WintunSendPacket, WINTUN_SEND_PACKET_FUNC)) #undef X { DWORD Result = GetLastError(); FreeLibrary(Wintun); SetLastError(Result); return NULL; } SetLastError(ERROR_SUCCESS); return Wintun; } int main(void) { HMODULE Wintun = InitializeWintun(); if (!Wintun) return LogError(L"Failed to initialize Wintun", GetLastError()); WintunSetLogger(ConsoleLogger); Log(WINTUN_LOG_INFO, L"Wintun library loaded"); DWORD Result; HaveQuit = FALSE; QuitEvent = CreateEventW(NULL, TRUE, FALSE, NULL); if (!QuitEvent) { Result = LogError(L"Failed to create event", GetLastError()); goto cleanupWintun; } if (!SetConsoleCtrlHandler(CtrlHandler, TRUE)) { Result = LogError(L"Failed to set console handler", GetLastError()); goto cleanupQuit; } GUID ExampleGuid = { 0xdeadbabe, 0xcafe, 0xbeef, { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef } }; WINTUN_ADAPTER_HANDLE Adapter; Result = WintunGetAdapter(L"Example", L"Demo", &Adapter); if (Result != ERROR_SUCCESS) Result = WintunCreateAdapter(L"Example", L"Demo", &ExampleGuid, &Adapter, NULL); if (Result != ERROR_SUCCESS) { LogError(L"Failed to create adapter", Result); goto cleanupQuit; } DWORDLONG Version = WintunGetVersion(); Log(WINTUN_LOG_INFO, L"Wintun v%d.%d.%d.%d loaded", (Version >> 48) & 0xff, (Version >> 32) & 0xff, (Version >> 16) & 0xff, (Version >> 0) & 0xff); MIB_UNICASTIPADDRESS_ROW AddressRow; InitializeUnicastIpAddressEntry(&AddressRow); WintunGetAdapterLUID(Adapter, &AddressRow.InterfaceLuid); AddressRow.Address.Ipv4.sin_family = AF_INET; AddressRow.Address.Ipv4.sin_addr.S_un.S_addr = htonl((10 << 24) | (6 << 16) | (7 << 8) | (7 << 0)); /* 10.6.7.7 */ AddressRow.OnLinkPrefixLength = 24; /* This is a /24 network */ Result = CreateUnicastIpAddressEntry(&AddressRow); if (Result != ERROR_SUCCESS) { LogError(L"Failed to set IP address", Result); goto cleanupAdapter; } WINTUN_SESSION_HANDLE Session; Result = WintunStartSession(Adapter, 0x40000, &Session); if (Result != ERROR_SUCCESS) { LogError(L"Failed to create adapter", Result); goto cleanupAdapter; } Log(WINTUN_LOG_INFO, L"Launching threads and mangling packets..."); HANDLE Workers[] = { CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReceivePackets, (LPVOID)Session, 0, NULL), CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendPackets, (LPVOID)Session, 0, NULL) }; if (!Workers[0] || !Workers[1]) { Result = LogError(L"Failed to create threads", GetLastError()); goto cleanupWorkers; } WaitForMultipleObjectsEx(_countof(Workers), Workers, TRUE, INFINITE, TRUE); Result = ERROR_SUCCESS; cleanupWorkers: HaveQuit = TRUE; SetEvent(QuitEvent); for (size_t i = 0; i < _countof(Workers); ++i) { if (Workers[i]) { WaitForSingleObject(Workers[i], INFINITE); CloseHandle(Workers[i]); } } WintunEndSession(Session); cleanupAdapter: WintunDeleteAdapter(Adapter, FALSE, NULL); WintunFreeAdapter(Adapter); cleanupQuit: SetConsoleCtrlHandler(CtrlHandler, FALSE); CloseHandle(QuitEvent); cleanupWintun: FreeLibrary(Wintun); return Result; }