wintun/example/example.c
Simon Rozman cef7922556 api: elevate to SYSTEM in WintunEnumAdapters()
The WintunEnumAdapters() requires namespace mutex. However,
NamespaceTakePoolMutex() works as SYSTEM user only.

WireGuard is using the WintunEnumAdapters() in its manager service to
cleanup stale adapters. As the WireGuard manager service is running as
SYSTEM, this requirement was not apparent before.

This commit also extends the example project to list its existing
adapters at start.

Signed-off-by: Simon Rozman <simon@rozman.si>
2021-03-08 13:48:29 +01:00

400 lines
13 KiB
C

/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
*/
#include <winsock2.h>
#include <Windows.h>
#include <ws2ipdef.h>
#include <iphlpapi.h>
#include <ip2string.h>
#include <stdarg.h>
#include <stdio.h>
#include "wintun.h"
static WINTUN_CREATE_ADAPTER_FUNC WintunCreateAdapter;
static WINTUN_DELETE_ADAPTER_FUNC WintunDeleteAdapter;
static WINTUN_DELETE_POOL_DRIVER_FUNC WintunDeletePoolDriver;
static WINTUN_ENUM_ADAPTERS_FUNC WintunEnumAdapters;
static WINTUN_FREE_ADAPTER_FUNC WintunFreeAdapter;
static WINTUN_OPEN_ADAPTER_FUNC WintunOpenAdapter;
static WINTUN_GET_ADAPTER_LUID_FUNC WintunGetAdapterLUID;
static WINTUN_GET_ADAPTER_NAME_FUNC WintunGetAdapterName;
static WINTUN_SET_ADAPTER_NAME_FUNC WintunSetAdapterName;
static WINTUN_GET_RUNNING_DRIVER_VERSION_FUNC WintunGetRunningDriverVersion;
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_RELEASE_RECEIVE_PACKET_FUNC WintunReleaseReceivePacket;
static WINTUN_ALLOCATE_SEND_PACKET_FUNC WintunAllocateSendPacket;
static WINTUN_SEND_PACKET_FUNC WintunSendPacket;
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(WintunDeletePoolDriver, WINTUN_DELETE_POOL_DRIVER_FUNC) || X(WintunEnumAdapters, WINTUN_ENUM_ADAPTERS_FUNC) ||
X(WintunFreeAdapter, WINTUN_FREE_ADAPTER_FUNC) || X(WintunOpenAdapter, WINTUN_OPEN_ADAPTER_FUNC) ||
X(WintunGetAdapterLUID, WINTUN_GET_ADAPTER_LUID_FUNC) ||
X(WintunGetAdapterName, WINTUN_GET_ADAPTER_NAME_FUNC) ||
X(WintunSetAdapterName, WINTUN_SET_ADAPTER_NAME_FUNC) ||
X(WintunGetRunningDriverVersion, WINTUN_GET_RUNNING_DRIVER_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(WintunReleaseReceivePacket, WINTUN_RELEASE_RECEIVE_PACKET_FUNC) ||
X(WintunAllocateSendPacket, WINTUN_ALLOCATE_SEND_PACKET_FUNC) || X(WintunSendPacket, WINTUN_SEND_PACKET_FUNC))
#undef X
{
DWORD LastError = GetLastError();
FreeLibrary(Wintun);
SetLastError(LastError);
return NULL;
}
return Wintun;
}
static void 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;
}
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);
}
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 DWORD
LogLastError(_In_z_ const WCHAR *Prefix)
{
DWORD LastError = GetLastError();
LogError(Prefix, LastError);
SetLastError(LastError);
return LastError;
}
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 HANDLE QuitEvent;
static volatile BOOL HaveQuit;
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(_In_reads_bytes_(Len) BYTE *Buffer, _In_ 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(_Out_writes_bytes_all_(28) 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)
{
DWORD PacketSize;
BYTE *Packet = WintunReceivePacket(Session, &PacketSize);
if (Packet)
{
PrintPacket(Packet, PacketSize);
WintunReleaseReceivePacket(Session, Packet);
}
else
{
DWORD LastError = GetLastError();
switch (LastError)
{
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", LastError);
return LastError;
}
}
}
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);
if (Packet)
{
MakeICMP(Packet);
WintunSendPacket(Session, Packet);
}
else if (GetLastError() != ERROR_BUFFER_OVERFLOW)
return LogLastError(L"Packet write failed");
switch (WaitForSingleObject(QuitEvent, 1000 /* 1 second */))
{
case WAIT_ABANDONED:
case WAIT_OBJECT_0:
return ERROR_SUCCESS;
}
}
return ERROR_SUCCESS;
}
static BOOL CALLBACK
PrintAdapter(_In_ WINTUN_ADAPTER_HANDLE Adapter, _In_ LPARAM Param)
{
UNREFERENCED_PARAMETER(Param);
WCHAR szAdapterName[MAX_ADAPTER_NAME];
if (WintunGetAdapterName(Adapter, szAdapterName))
Log(WINTUN_LOG_INFO, L"Existing Wintun adapter: %s", szAdapterName);
return TRUE;
}
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");
WintunEnumAdapters(L"Example", PrintAdapter, 0);
DWORD LastError;
HaveQuit = FALSE;
QuitEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
if (!QuitEvent)
{
LastError = LogError(L"Failed to create event", GetLastError());
goto cleanupWintun;
}
if (!SetConsoleCtrlHandler(CtrlHandler, TRUE))
{
LastError = 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 = WintunOpenAdapter(L"Example", L"Demo");
if (!Adapter)
{
Adapter = WintunCreateAdapter(L"Example", L"Demo", &ExampleGuid, NULL);
if (!Adapter)
{
LastError = GetLastError();
LogError(L"Failed to create adapter", LastError);
goto cleanupQuit;
}
}
DWORD Version = WintunGetRunningDriverVersion();
Log(WINTUN_LOG_INFO, L"Wintun v%u.%u loaded", (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 */
LastError = CreateUnicastIpAddressEntry(&AddressRow);
if (LastError != ERROR_SUCCESS && LastError != ERROR_OBJECT_ALREADY_EXISTS)
{
LogError(L"Failed to set IP address", LastError);
goto cleanupAdapter;
}
WINTUN_SESSION_HANDLE Session = WintunStartSession(Adapter, 0x400000);
if (!Session)
{
LastError = LogLastError(L"Failed to create adapter");
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])
{
LastError = LogError(L"Failed to create threads", GetLastError());
goto cleanupWorkers;
}
WaitForMultipleObjectsEx(_countof(Workers), Workers, TRUE, INFINITE, TRUE);
LastError = 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 LastError;
}