84c066fa79
RegistryQueryString() may produce one or more "File not found" errors when called from RegistryQueryStringWait() - which is expected while waiting. Those errors were annoying and awkward to read in the log. Furthermore, should RegistryQueryString() fail, it already displays detailed Windows error message and the parent's logging was simplified to prevent repetitions in the log. Signed-off-by: Simon Rozman <simon@rozman.si>
1695 lines
60 KiB
C
1695 lines
60 KiB
C
/* SPDX-License-Identifier: GPL-2.0
|
|
*
|
|
* Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved.
|
|
*/
|
|
|
|
#include "pch.h"
|
|
|
|
#pragma warning(disable : 4221) /* nonstandard: address of automatic in initializer */
|
|
|
|
#define WAIT_FOR_REGISTRY_TIMEOUT 10000 /* ms */
|
|
#define MAX_POOL_DEVICE_TYPE (MAX_POOL + 8) /* Should accommodate a pool name with " Tunnel" appended */
|
|
#if defined(_M_IX86)
|
|
# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_I386
|
|
#elif defined(_M_AMD64)
|
|
# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_AMD64
|
|
#elif defined(_M_ARM)
|
|
# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_ARMNT
|
|
#elif defined(_M_ARM64)
|
|
# define IMAGE_FILE_PROCESS IMAGE_FILE_MACHINE_ARM64
|
|
#else
|
|
# error Unsupported architecture
|
|
#endif
|
|
|
|
static _locale_t Locale;
|
|
static USHORT NativeMachine = IMAGE_FILE_PROCESS;
|
|
|
|
WINTUN_STATUS
|
|
AdapterGetDrvInfoDetail(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_opt_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ SP_DRVINFO_DATA_W *DrvInfoData,
|
|
_Out_ SP_DRVINFO_DETAIL_DATA_W **DrvInfoDetailData)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
DWORD Size = sizeof(SP_DRVINFO_DETAIL_DATA_W) + 0x100;
|
|
for (;;)
|
|
{
|
|
*DrvInfoDetailData = HeapAlloc(Heap, 0, Size);
|
|
if (!*DrvInfoDetailData)
|
|
return LOG(WINTUN_LOG_ERR, L"Out of memory"), ERROR_OUTOFMEMORY;
|
|
(*DrvInfoDetailData)->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA_W);
|
|
if (SetupDiGetDriverInfoDetailW(DevInfo, DevInfoData, DrvInfoData, *DrvInfoDetailData, Size, &Size))
|
|
return ERROR_SUCCESS;
|
|
DWORD Result = GetLastError();
|
|
HeapFree(Heap, 0, *DrvInfoDetailData);
|
|
if (Result != ERROR_INSUFFICIENT_BUFFER)
|
|
return LOG_ERROR(L"Failed", Result);
|
|
}
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetDeviceRegistryProperty(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ DWORD Property,
|
|
_Out_opt_ DWORD *ValueType,
|
|
_Out_ void **Buf,
|
|
_Inout_ DWORD *BufLen)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
for (;;)
|
|
{
|
|
*Buf = HeapAlloc(Heap, 0, *BufLen);
|
|
if (!*Buf)
|
|
return LOG(WINTUN_LOG_ERR, L"Out of memory"), ERROR_OUTOFMEMORY;
|
|
if (SetupDiGetDeviceRegistryPropertyW(DevInfo, DevInfoData, Property, ValueType, *Buf, *BufLen, BufLen))
|
|
return ERROR_SUCCESS;
|
|
DWORD Result = GetLastError();
|
|
HeapFree(Heap, 0, *Buf);
|
|
if (Result != ERROR_INSUFFICIENT_BUFFER)
|
|
return LOG_ERROR(L"Querying property failed", Result);
|
|
}
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetDeviceRegistryString(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ DWORD Property,
|
|
_Out_ WCHAR **Buf)
|
|
{
|
|
DWORD Result, ValueType, Size = 256 * sizeof(WCHAR);
|
|
Result = GetDeviceRegistryProperty(DevInfo, DevInfoData, Property, &ValueType, Buf, &Size);
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
switch (ValueType)
|
|
{
|
|
case REG_SZ:
|
|
case REG_EXPAND_SZ:
|
|
case REG_MULTI_SZ:
|
|
Result = RegistryGetString(Buf, Size / sizeof(WCHAR), ValueType);
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(GetProcessHeap(), 0, *Buf);
|
|
return Result;
|
|
default:
|
|
LOG(WINTUN_LOG_ERR, L"Property is not a string");
|
|
HeapFree(GetProcessHeap(), 0, *Buf);
|
|
return ERROR_INVALID_DATATYPE;
|
|
}
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetDeviceRegistryMultiString(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ DWORD Property,
|
|
_Out_ WCHAR **Buf)
|
|
{
|
|
DWORD Result, ValueType, Size = 256 * sizeof(WCHAR);
|
|
Result = GetDeviceRegistryProperty(DevInfo, DevInfoData, Property, &ValueType, Buf, &Size);
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
switch (ValueType)
|
|
{
|
|
case REG_SZ:
|
|
case REG_EXPAND_SZ:
|
|
case REG_MULTI_SZ:
|
|
Result = RegistryGetMultiString(Buf, Size / sizeof(WCHAR), ValueType);
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(GetProcessHeap(), 0, *Buf);
|
|
return Result;
|
|
default:
|
|
LOG(WINTUN_LOG_ERR, L"Property is not a string");
|
|
HeapFree(GetProcessHeap(), 0, *Buf);
|
|
return ERROR_INVALID_DATATYPE;
|
|
}
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
IsOurAdapter(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData, _Out_ BOOL *IsOurs)
|
|
{
|
|
WCHAR *Hwids;
|
|
DWORD Result = GetDeviceRegistryMultiString(DevInfo, DevInfoData, SPDRP_HARDWAREID, &Hwids);
|
|
if (Result != ERROR_SUCCESS)
|
|
return LOG(WINTUN_LOG_ERR, L"Failed to query hardware ID"), Result;
|
|
*IsOurs = DriverIsOurHardwareID(Hwids);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetDeviceObject(_In_opt_z_ const WCHAR *InstanceId, _Out_ HANDLE *Handle)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
ULONG InterfacesLen;
|
|
DWORD Result = CM_Get_Device_Interface_List_SizeW(
|
|
&InterfacesLen, (GUID *)&GUID_DEVINTERFACE_NET, (DEVINSTID_W)InstanceId, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
|
|
if (Result != CR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to get device associated device instances size");
|
|
return ERROR_GEN_FAILURE;
|
|
}
|
|
WCHAR *Interfaces = HeapAlloc(Heap, 0, InterfacesLen * sizeof(WCHAR));
|
|
if (!Interfaces)
|
|
return LOG(WINTUN_LOG_ERR, L"Out of memory"), ERROR_OUTOFMEMORY;
|
|
Result = CM_Get_Device_Interface_ListW(
|
|
(GUID *)&GUID_DEVINTERFACE_NET,
|
|
(DEVINSTID_W)InstanceId,
|
|
Interfaces,
|
|
InterfacesLen,
|
|
CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
|
|
if (Result != CR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to get device associated device instances");
|
|
Result = ERROR_GEN_FAILURE;
|
|
goto cleanupBuf;
|
|
}
|
|
*Handle = CreateFileW(
|
|
Interfaces,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
Result = *Handle != INVALID_HANDLE_VALUE ? ERROR_SUCCESS : LOG_LAST_ERROR(L"Failed to connect to device");
|
|
cleanupBuf:
|
|
HeapFree(Heap, 0, Interfaces);
|
|
return Result;
|
|
}
|
|
|
|
#define TUN_IOCTL_FORCE_CLOSE_HANDLES CTL_CODE(51820U, 0x971U, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA)
|
|
|
|
static WINTUN_STATUS
|
|
ForceCloseWintunAdapterHandle(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData)
|
|
{
|
|
DWORD Result = ERROR_SUCCESS;
|
|
DWORD RequiredBytes;
|
|
if (SetupDiGetDeviceInstanceIdW(DevInfo, DevInfoData, NULL, 0, &RequiredBytes) ||
|
|
(Result = GetLastError()) != ERROR_INSUFFICIENT_BUFFER)
|
|
return LOG_ERROR(L"Failed to query device instance ID size", Result);
|
|
HANDLE Heap = GetProcessHeap();
|
|
WCHAR *InstanceId = HeapAlloc(Heap, HEAP_ZERO_MEMORY, sizeof(*InstanceId) * RequiredBytes);
|
|
if (!InstanceId)
|
|
return LOG(WINTUN_LOG_ERR, L"Out of memory"), ERROR_OUTOFMEMORY;
|
|
if (!SetupDiGetDeviceInstanceIdW(DevInfo, DevInfoData, InstanceId, RequiredBytes, &RequiredBytes))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to get device instance ID");
|
|
goto out;
|
|
}
|
|
HANDLE NdisHandle;
|
|
Result = GetDeviceObject(InstanceId, &NdisHandle);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to get adapter device object");
|
|
goto out;
|
|
}
|
|
Result = DeviceIoControl(NdisHandle, TUN_IOCTL_FORCE_CLOSE_HANDLES, NULL, 0, NULL, 0, &RequiredBytes, NULL)
|
|
? ERROR_SUCCESS
|
|
: LOG_LAST_ERROR(L"Failed to perform ioctl");
|
|
CloseHandle(NdisHandle);
|
|
out:
|
|
HeapFree(Heap, 0, InstanceId);
|
|
return Result;
|
|
}
|
|
|
|
WINTUN_STATUS
|
|
AdapterDisableAllOurs(_In_ HDEVINFO DevInfo, _Inout_ SP_DEVINFO_DATA_LIST **DisabledAdapters)
|
|
{
|
|
SP_PROPCHANGE_PARAMS Params = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
|
|
.InstallFunction = DIF_PROPERTYCHANGE },
|
|
.StateChange = DICS_DISABLE,
|
|
.Scope = DICS_FLAG_GLOBAL };
|
|
DWORD Result = ERROR_SUCCESS;
|
|
HANDLE Heap = GetProcessHeap();
|
|
for (DWORD EnumIndex = 0;; ++EnumIndex)
|
|
{
|
|
SP_DEVINFO_DATA_LIST *DeviceNode = HeapAlloc(Heap, 0, sizeof(SP_DEVINFO_DATA_LIST));
|
|
if (!DeviceNode)
|
|
return LOG(WINTUN_LOG_ERR, L"Out of memory"), ERROR_OUTOFMEMORY;
|
|
DeviceNode->Data.cbSize = sizeof(SP_DEVINFO_DATA);
|
|
if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DeviceNode->Data))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
{
|
|
HeapFree(Heap, 0, DeviceNode);
|
|
break;
|
|
}
|
|
goto cleanupDeviceInfoData;
|
|
}
|
|
BOOL IsOurs;
|
|
if (IsOurAdapter(DevInfo, &DeviceNode->Data, &IsOurs) != ERROR_SUCCESS || !IsOurs)
|
|
goto cleanupDeviceInfoData;
|
|
|
|
ULONG Status, ProblemCode;
|
|
if (CM_Get_DevNode_Status(&Status, &ProblemCode, DeviceNode->Data.DevInst, 0) != CR_SUCCESS ||
|
|
((Status & DN_HAS_PROBLEM) && ProblemCode == CM_PROB_DISABLED))
|
|
goto cleanupDeviceInfoData;
|
|
|
|
LOG(WINTUN_LOG_INFO, L"Force closing all open handles for existing adapter");
|
|
if (ForceCloseWintunAdapterHandle(DevInfo, &DeviceNode->Data) != ERROR_SUCCESS)
|
|
LOG(WINTUN_LOG_WARN, L"Failed to force close adapter handles");
|
|
Sleep(200);
|
|
|
|
LOG(WINTUN_LOG_INFO, L"Disabling existing adapter");
|
|
if (!SetupDiSetClassInstallParamsW(DevInfo, &DeviceNode->Data, &Params.ClassInstallHeader, sizeof(Params)) ||
|
|
!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, DevInfo, &DeviceNode->Data))
|
|
{
|
|
LOG_LAST_ERROR(L"Unable to disable existing adapter");
|
|
Result = Result != ERROR_SUCCESS ? Result : GetLastError();
|
|
goto cleanupDeviceInfoData;
|
|
}
|
|
|
|
DeviceNode->Next = *DisabledAdapters;
|
|
*DisabledAdapters = DeviceNode;
|
|
continue;
|
|
|
|
cleanupDeviceInfoData:
|
|
HeapFree(Heap, 0, &DeviceNode->Data);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
WINTUN_STATUS
|
|
AdapterEnableAll(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA_LIST *AdaptersToEnable)
|
|
{
|
|
SP_PROPCHANGE_PARAMS Params = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
|
|
.InstallFunction = DIF_PROPERTYCHANGE },
|
|
.StateChange = DICS_ENABLE,
|
|
.Scope = DICS_FLAG_GLOBAL };
|
|
DWORD Result = ERROR_SUCCESS;
|
|
for (SP_DEVINFO_DATA_LIST *DeviceNode = AdaptersToEnable; DeviceNode; DeviceNode = DeviceNode->Next)
|
|
{
|
|
LOG(WINTUN_LOG_INFO, L"Enabling existing adapter");
|
|
if (!SetupDiSetClassInstallParamsW(DevInfo, &DeviceNode->Data, &Params.ClassInstallHeader, sizeof(Params)) ||
|
|
!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, DevInfo, &DeviceNode->Data))
|
|
{
|
|
LOG_LAST_ERROR(L"Unable to enable existing adapter");
|
|
Result = Result != ERROR_SUCCESS ? Result : GetLastError();
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
WINTUN_STATUS
|
|
AdapterDeleteAllOurs()
|
|
{
|
|
DWORD Result = ERROR_SUCCESS;
|
|
HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
|
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
|
return LOG_LAST_ERROR(L"Failed to get present class devices");
|
|
SP_REMOVEDEVICE_PARAMS Params = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
|
|
.InstallFunction = DIF_REMOVE },
|
|
.Scope = DI_REMOVEDEVICE_GLOBAL };
|
|
for (DWORD EnumIndex = 0;; ++EnumIndex)
|
|
{
|
|
SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) };
|
|
if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
BOOL IsOurs;
|
|
if (IsOurAdapter(DevInfo, &DevInfoData, &IsOurs) != ERROR_SUCCESS || !IsOurs)
|
|
continue;
|
|
|
|
LOG(WINTUN_LOG_INFO, L"Force closing all open handles for existing adapter");
|
|
if (ForceCloseWintunAdapterHandle(DevInfo, &DevInfoData) != ERROR_SUCCESS)
|
|
LOG(WINTUN_LOG_WARN, L"Failed to force close adapter handles");
|
|
Sleep(200);
|
|
|
|
LOG(WINTUN_LOG_INFO, L"Removing existing adapter");
|
|
if (!SetupDiSetClassInstallParamsW(DevInfo, &DevInfoData, &Params.ClassInstallHeader, sizeof(Params)) ||
|
|
!SetupDiCallClassInstaller(DIF_REMOVE, DevInfo, &DevInfoData))
|
|
{
|
|
LOG_LAST_ERROR(L"Unable to remove existing adapter");
|
|
Result = Result != ERROR_SUCCESS ? Result : GetLastError();
|
|
}
|
|
}
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
return Result;
|
|
}
|
|
|
|
void
|
|
AdapterInit()
|
|
{
|
|
Locale = _wcreate_locale(LC_ALL, L"");
|
|
|
|
#if defined(_M_IX86) || defined(_M_ARM)
|
|
typedef BOOL(WINAPI * IsWow64Process2_t)(
|
|
_In_ HANDLE hProcess, _Out_ USHORT * pProcessMachine, _Out_opt_ USHORT * pNativeMachine);
|
|
HANDLE Kernel32;
|
|
IsWow64Process2_t IsWow64Process2;
|
|
USHORT ProcessMachine;
|
|
if ((Kernel32 = GetModuleHandleW(L"kernel32.dll")) == NULL ||
|
|
(IsWow64Process2 = (IsWow64Process2_t)GetProcAddress(Kernel32, "IsWow64Process2")) == NULL ||
|
|
!IsWow64Process2(GetCurrentProcess(), &ProcessMachine, &NativeMachine))
|
|
{
|
|
BOOL IsWoW64;
|
|
NativeMachine =
|
|
IsWow64Process(GetCurrentProcess(), &IsWoW64) && IsWoW64 ? IMAGE_FILE_MACHINE_AMD64 : IMAGE_FILE_PROCESS;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
AdapterCleanup()
|
|
{
|
|
_free_locale(Locale);
|
|
}
|
|
|
|
static BOOL
|
|
CheckReboot(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData)
|
|
{
|
|
SP_DEVINSTALL_PARAMS_W DevInstallParams = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS_W) };
|
|
if (!SetupDiGetDeviceInstallParamsW(DevInfo, DevInfoData, &DevInstallParams))
|
|
{
|
|
LOG_LAST_ERROR(L"Retrieving device installation parameters failed");
|
|
return FALSE;
|
|
}
|
|
return (DevInstallParams.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0;
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
SetQuietInstall(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData)
|
|
{
|
|
SP_DEVINSTALL_PARAMS_W DevInstallParams = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS_W) };
|
|
if (!SetupDiGetDeviceInstallParamsW(DevInfo, DevInfoData, &DevInstallParams))
|
|
return LOG_LAST_ERROR(L"Retrieving device installation parameters failed");
|
|
DevInstallParams.Flags |= DI_QUIETINSTALL;
|
|
if (!SetupDiSetDeviceInstallParamsW(DevInfo, DevInfoData, &DevInstallParams))
|
|
return LOG_LAST_ERROR(L"Setting device installation parameters failed");
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetNetCfgInstanceId(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData, _Out_ GUID *CfgInstanceID)
|
|
{
|
|
HKEY Key = SetupDiOpenDevRegKey(DevInfo, DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE);
|
|
if (Key == INVALID_HANDLE_VALUE)
|
|
return LOG_LAST_ERROR(L"Opening device registry key failed");
|
|
WCHAR *ValueStr;
|
|
DWORD Result = RegistryQueryString(Key, L"NetCfgInstanceId", &ValueStr, TRUE);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query NetCfgInstanceId value");
|
|
goto cleanupKey;
|
|
}
|
|
if (FAILED(CLSIDFromString(ValueStr, CfgInstanceID)))
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"NetCfgInstanceId is not a GUID");
|
|
Result = ERROR_INVALID_DATA;
|
|
}
|
|
else
|
|
Result = ERROR_SUCCESS;
|
|
HeapFree(GetProcessHeap(), 0, ValueStr);
|
|
cleanupKey:
|
|
RegCloseKey(Key);
|
|
return Result;
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetDevInfoData(_In_ const GUID *CfgInstanceID, _Out_ HDEVINFO *DevInfo, _Out_ SP_DEVINFO_DATA *DevInfoData)
|
|
{
|
|
*DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
|
|
if (!*DevInfo)
|
|
return LOG_LAST_ERROR(L"Failed to get present class devices");
|
|
for (DWORD EnumIndex = 0;; ++EnumIndex)
|
|
{
|
|
DevInfoData->cbSize = sizeof(SP_DEVINFO_DATA);
|
|
if (!SetupDiEnumDeviceInfo(*DevInfo, EnumIndex, DevInfoData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
GUID CfgInstanceID2;
|
|
if (GetNetCfgInstanceId(*DevInfo, DevInfoData, &CfgInstanceID2) == ERROR_SUCCESS &&
|
|
!memcmp(CfgInstanceID, &CfgInstanceID2, sizeof(GUID)))
|
|
return ERROR_SUCCESS;
|
|
}
|
|
SetupDiDestroyDeviceInfoList(*DevInfo);
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
static void
|
|
RemoveNumberedSuffix(_In_z_ const WCHAR *Name, _Out_ WCHAR *Removed)
|
|
{
|
|
size_t Len = wcslen(Name);
|
|
if (Len && (Name[Len - 1] < L'0' || Name[Len - 1] > L'9'))
|
|
{
|
|
wmemcpy(Removed, Name, Len + 1);
|
|
return;
|
|
}
|
|
for (size_t i = Len; i--;)
|
|
{
|
|
if (Name[i] >= L'0' && Name[i] <= L'9')
|
|
continue;
|
|
if (Name[i] == L' ')
|
|
{
|
|
wmemcpy(Removed, Name, i);
|
|
Removed[i] = 0;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
wmemcpy(Removed, Name, Len + 1);
|
|
}
|
|
|
|
static void
|
|
GetPoolDeviceTypeName(_In_z_count_c_(MAX_POOL) const WCHAR *Pool, _Out_cap_c_(MAX_POOL_DEVICE_TYPE) WCHAR *Name)
|
|
{
|
|
_snwprintf_s(Name, MAX_POOL_DEVICE_TYPE, _TRUNCATE, L"%.*s Tunnel", MAX_POOL, Pool);
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
IsPoolMember(
|
|
_In_z_count_c_(MAX_POOL) const WCHAR *Pool,
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_Out_ BOOL *IsMember)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
WCHAR *DeviceDesc, *FriendlyName;
|
|
DWORD Result = GetDeviceRegistryString(DevInfo, DevInfoData, SPDRP_DEVICEDESC, &DeviceDesc);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query device description property");
|
|
return Result;
|
|
}
|
|
Result = GetDeviceRegistryString(DevInfo, DevInfoData, SPDRP_FRIENDLYNAME, &FriendlyName);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query friendly name property");
|
|
goto cleanupDeviceDesc;
|
|
}
|
|
WCHAR PoolDeviceTypeName[MAX_POOL_DEVICE_TYPE];
|
|
GetPoolDeviceTypeName(Pool, PoolDeviceTypeName);
|
|
if (!_wcsicmp_l(FriendlyName, PoolDeviceTypeName, Locale) || !_wcsicmp_l(DeviceDesc, PoolDeviceTypeName, Locale))
|
|
{
|
|
*IsMember = TRUE;
|
|
goto cleanupFriendlyName;
|
|
}
|
|
RemoveNumberedSuffix(FriendlyName, FriendlyName);
|
|
RemoveNumberedSuffix(DeviceDesc, DeviceDesc);
|
|
if (!_wcsicmp_l(FriendlyName, PoolDeviceTypeName, Locale) || !_wcsicmp_l(DeviceDesc, PoolDeviceTypeName, Locale))
|
|
{
|
|
*IsMember = TRUE;
|
|
goto cleanupFriendlyName;
|
|
}
|
|
*IsMember = FALSE;
|
|
cleanupFriendlyName:
|
|
HeapFree(Heap, 0, FriendlyName);
|
|
cleanupDeviceDesc:
|
|
HeapFree(Heap, 0, DeviceDesc);
|
|
return Result;
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
CreateAdapterData(
|
|
_In_z_count_c_(MAX_POOL) const WCHAR *Pool,
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_Out_ WINTUN_ADAPTER **Adapter)
|
|
{
|
|
DWORD Result;
|
|
|
|
/* Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\<class>\<id> registry key. */
|
|
HKEY Key = SetupDiOpenDevRegKey(DevInfo, DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE);
|
|
if (Key == INVALID_HANDLE_VALUE)
|
|
return LOG_LAST_ERROR(L"Opening device registry key failed");
|
|
|
|
HANDLE Heap = GetProcessHeap();
|
|
*Adapter = HeapAlloc(Heap, 0, sizeof(WINTUN_ADAPTER));
|
|
if (!*Adapter)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Out of memory");
|
|
Result = ERROR_OUTOFMEMORY;
|
|
goto cleanupKey;
|
|
}
|
|
|
|
/* Read the NetCfgInstanceId value and convert to GUID. */
|
|
WCHAR *ValueStr;
|
|
Result = RegistryQueryString(Key, L"NetCfgInstanceId", &ValueStr, TRUE);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query NetCfgInstanceId value");
|
|
goto cleanupAdapter;
|
|
}
|
|
if (FAILED(CLSIDFromString(ValueStr, &(*Adapter)->CfgInstanceID)))
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"NetCfgInstanceId is not a GUID");
|
|
HeapFree(Heap, 0, ValueStr);
|
|
Result = ERROR_INVALID_DATA;
|
|
goto cleanupAdapter;
|
|
}
|
|
HeapFree(Heap, 0, ValueStr);
|
|
|
|
/* Read the NetLuidIndex value. */
|
|
Result = RegistryQueryDWORD(Key, L"NetLuidIndex", &(*Adapter)->LuidIndex);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query NetLuidIndex value");
|
|
goto cleanupAdapter;
|
|
}
|
|
|
|
/* Read the NetLuidIndex value. */
|
|
Result = RegistryQueryDWORD(Key, L"*IfType", &(*Adapter)->IfType);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query *IfType value");
|
|
goto cleanupAdapter;
|
|
}
|
|
|
|
DWORD Size;
|
|
if (!SetupDiGetDeviceInstanceIdW(
|
|
DevInfo, DevInfoData, (*Adapter)->DevInstanceID, _countof((*Adapter)->DevInstanceID), &Size))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to get device instance ID");
|
|
goto cleanupAdapter;
|
|
}
|
|
|
|
wcsncpy_s((*Adapter)->Pool, _countof((*Adapter)->Pool), Pool, _TRUNCATE);
|
|
Result = ERROR_SUCCESS;
|
|
|
|
cleanupAdapter:
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(Heap, 0, *Adapter);
|
|
cleanupKey:
|
|
RegCloseKey(Key);
|
|
return Result;
|
|
}
|
|
|
|
static void
|
|
GetDeviceRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_REG_PATH) WCHAR *Path)
|
|
{
|
|
_snwprintf_s(
|
|
Path,
|
|
MAX_REG_PATH,
|
|
_TRUNCATE,
|
|
L"SYSTEM\\CurrentControlSet\\Enum\\%.*s",
|
|
MAX_INSTANCE_ID,
|
|
Adapter->DevInstanceID);
|
|
}
|
|
|
|
void WINAPI
|
|
WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, Adapter);
|
|
}
|
|
|
|
WINTUN_STATUS WINAPI
|
|
WintunGetAdapter(
|
|
_In_z_count_c_(MAX_POOL) const WCHAR *Pool,
|
|
_In_z_count_c_(MAX_ADAPTER_NAME) const WCHAR *Name,
|
|
_Out_ WINTUN_ADAPTER **Adapter)
|
|
{
|
|
DWORD Result;
|
|
HANDLE Mutex = NamespaceTakeMutex(Pool);
|
|
if (!Mutex)
|
|
return ERROR_INVALID_HANDLE;
|
|
|
|
HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
|
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to get present class devices");
|
|
goto cleanupMutex;
|
|
}
|
|
|
|
for (DWORD EnumIndex = 0;; ++EnumIndex)
|
|
{
|
|
SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) };
|
|
if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
GUID CfgInstanceID;
|
|
if (GetNetCfgInstanceId(DevInfo, &DevInfoData, &CfgInstanceID) != ERROR_SUCCESS)
|
|
continue;
|
|
|
|
/* TODO: is there a better way than comparing ifnames? */
|
|
WCHAR Name2[MAX_ADAPTER_NAME];
|
|
if (NciGetConnectionName(&CfgInstanceID, Name2, sizeof(Name2), NULL) != ERROR_SUCCESS)
|
|
continue;
|
|
Name2[_countof(Name2) - 1] = 0;
|
|
if (_wcsicmp_l(Name, Name2, Locale))
|
|
{
|
|
RemoveNumberedSuffix(Name2, Name2);
|
|
if (_wcsicmp_l(Name, Name2, Locale))
|
|
continue;
|
|
}
|
|
|
|
/* Check the Hardware ID to make sure it's a real Wintun device. */
|
|
BOOL IsOurs;
|
|
Result = IsOurAdapter(DevInfo, &DevInfoData, &IsOurs);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to determine hardware ID");
|
|
goto cleanupDevInfo;
|
|
}
|
|
if (!IsOurs)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Foreign adapter with the same name exists");
|
|
Result = ERROR_ALREADY_EXISTS;
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
BOOL IsMember;
|
|
Result = IsPoolMember(Pool, DevInfo, &DevInfoData, &IsMember);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to determine pool membership");
|
|
goto cleanupDevInfo;
|
|
}
|
|
if (!IsMember)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Wintun adapter with the same name exists in another pool");
|
|
Result = ERROR_ALREADY_EXISTS;
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
Result = CreateAdapterData(Pool, DevInfo, &DevInfoData, Adapter);
|
|
if (Result != ERROR_SUCCESS)
|
|
LOG(WINTUN_LOG_ERR, L"Failed to create adapter data");
|
|
|
|
goto cleanupDevInfo;
|
|
}
|
|
Result = ERROR_FILE_NOT_FOUND;
|
|
cleanupDevInfo:
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
cleanupMutex:
|
|
NamespaceReleaseMutex(Mutex);
|
|
return Result;
|
|
}
|
|
|
|
WINTUN_STATUS WINAPI
|
|
WintunGetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_ADAPTER_NAME) WCHAR *Name)
|
|
{
|
|
return NciGetConnectionName(&Adapter->CfgInstanceID, Name, MAX_ADAPTER_NAME * sizeof(WCHAR), NULL);
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
ConvertInterfaceAliasToGuid(_In_z_ const WCHAR *Name, _Out_ GUID *Guid)
|
|
{
|
|
NET_LUID Luid;
|
|
DWORD Result = ConvertInterfaceAliasToLuid(Name, &Luid);
|
|
if (Result != NO_ERROR)
|
|
return LOG_ERROR(L"Failed convert interface alias name to the locally unique identifier", Result);
|
|
return ConvertInterfaceLuidToGuid(&Luid, Guid);
|
|
}
|
|
|
|
WINTUN_STATUS WINAPI
|
|
WintunSetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _In_z_count_c_(MAX_ADAPTER_NAME) const WCHAR *Name)
|
|
{
|
|
DWORD Result;
|
|
const int MaxSuffix = 1000;
|
|
WCHAR AvailableName[MAX_ADAPTER_NAME];
|
|
wcsncpy_s(AvailableName, _countof(AvailableName), Name, _TRUNCATE);
|
|
for (int i = 0;; ++i)
|
|
{
|
|
Result = NciSetConnectionName(&Adapter->CfgInstanceID, AvailableName);
|
|
if (Result == ERROR_DUP_NAME)
|
|
{
|
|
GUID Guid2;
|
|
DWORD Result2 = ConvertInterfaceAliasToGuid(AvailableName, &Guid2);
|
|
if (Result2 == ERROR_SUCCESS)
|
|
{
|
|
for (int j = 0; j < MaxSuffix; ++j)
|
|
{
|
|
WCHAR Proposal[MAX_ADAPTER_NAME];
|
|
_snwprintf_s(Proposal, _countof(Proposal), _TRUNCATE, L"%.*s %d", MAX_ADAPTER_NAME, Name, j + 1);
|
|
if (_wcsnicmp(Proposal, AvailableName, MAX_ADAPTER_NAME) == 0)
|
|
continue;
|
|
Result2 = NciSetConnectionName(&Guid2, Proposal);
|
|
if (Result2 == ERROR_DUP_NAME)
|
|
continue;
|
|
if (Result2 == ERROR_SUCCESS)
|
|
{
|
|
Result = NciSetConnectionName(&Adapter->CfgInstanceID, AvailableName);
|
|
if (Result == ERROR_SUCCESS)
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (Result == ERROR_SUCCESS)
|
|
break;
|
|
if (i > MaxSuffix || Result != ERROR_DUP_NAME)
|
|
return LOG_ERROR(L"Setting adapter name failed", Result);
|
|
_snwprintf_s(AvailableName, _countof(AvailableName), _TRUNCATE, L"%.*s %d", MAX_ADAPTER_NAME, Name, i + 1);
|
|
}
|
|
|
|
/* TODO: This should use NetSetup2 so that it doesn't get unset. */
|
|
HKEY DeviceRegKey;
|
|
WCHAR DeviceRegPath[MAX_REG_PATH];
|
|
GetDeviceRegPath(Adapter, DeviceRegPath);
|
|
Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, DeviceRegPath, 0, KEY_SET_VALUE, &DeviceRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
return LOG_ERROR(L"Failed to open registry key", Result);
|
|
WCHAR PoolDeviceTypeName[MAX_POOL_DEVICE_TYPE];
|
|
GetPoolDeviceTypeName(Adapter->Pool, PoolDeviceTypeName);
|
|
Result = RegSetKeyValueW(
|
|
DeviceRegKey,
|
|
NULL,
|
|
L"FriendlyName",
|
|
REG_SZ,
|
|
PoolDeviceTypeName,
|
|
(DWORD)((wcslen(PoolDeviceTypeName) + 1) * sizeof(WCHAR)));
|
|
RegCloseKey(DeviceRegKey);
|
|
return Result;
|
|
}
|
|
|
|
void WINAPI
|
|
WintunGetAdapterGUID(_In_ const WINTUN_ADAPTER *Adapter, _Out_ GUID *Guid)
|
|
{
|
|
memcpy(Guid, &Adapter->CfgInstanceID, sizeof(GUID));
|
|
}
|
|
|
|
void WINAPI
|
|
WintunGetAdapterLUID(_In_ const WINTUN_ADAPTER *Adapter, _Out_ LUID *Luid)
|
|
{
|
|
*(LONGLONG *)Luid = (((LONGLONG)Adapter->LuidIndex & ((1 << 24) - 1)) << 24) |
|
|
(((LONGLONG)Adapter->IfType & ((1 << 16) - 1)) << 48);
|
|
}
|
|
|
|
WINTUN_STATUS WINAPI
|
|
WintunGetAdapterDeviceObject(_In_ const WINTUN_ADAPTER *Adapter, _Out_ HANDLE *Handle)
|
|
{
|
|
return GetDeviceObject(Adapter->DevInstanceID, Handle);
|
|
}
|
|
|
|
#if defined(HAVE_EV) || defined(HAVE_WHQL)
|
|
|
|
/* We can't use RtlGetVersion, because appcompat's aclayers.dll shims it to report Vista
|
|
* when run from legacy contexts. So, we instead use the undocumented RtlGetNtVersionNumbers.
|
|
*
|
|
* Another way would be reading from the PEB directly:
|
|
* ((DWORD *)NtCurrentTeb()->ProcessEnvironmentBlock)[sizeof(void *) == 8 ? 70 : 41]
|
|
* Or just read from KUSER_SHARED_DATA the same way on 32-bit and 64-bit:
|
|
* *(DWORD *)0x7FFE026C
|
|
*/
|
|
extern VOID NTAPI
|
|
RtlGetNtVersionNumbers(_Out_opt_ DWORD *MajorVersion, _Out_opt_ DWORD *MinorVersion, _Out_opt_ DWORD *BuildNumber);
|
|
|
|
static BOOL
|
|
HaveWHQL()
|
|
{
|
|
# if defined(HAVE_EV) && defined(HAVE_WHQL)
|
|
DWORD MajorVersion;
|
|
RtlGetNtVersionNumbers(&MajorVersion, NULL, NULL);
|
|
return MajorVersion >= 10;
|
|
# elif defined(HAVE_EV)
|
|
return FALSE;
|
|
# elif defined(HAVE_WHQL)
|
|
return TRUE;
|
|
# endif
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
InstallCertificate(_In_z_ const WCHAR *SignedResource)
|
|
{
|
|
LOG(WINTUN_LOG_INFO, L"Trusting code signing certificate");
|
|
const void *LockedResource;
|
|
DWORD SizeResource;
|
|
DWORD Result = ResourceGetAddress(SignedResource, &LockedResource, &SizeResource);
|
|
if (Result != ERROR_SUCCESS)
|
|
return LOG(WINTUN_LOG_ERR, L"Failed to locate resource"), Result;
|
|
const CERT_BLOB CertBlob = { .cbData = SizeResource, .pbData = (BYTE *)LockedResource };
|
|
HCERTSTORE QueriedStore;
|
|
if (!CryptQueryObject(
|
|
CERT_QUERY_OBJECT_BLOB,
|
|
&CertBlob,
|
|
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
|
|
CERT_QUERY_FORMAT_FLAG_ALL,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
&QueriedStore,
|
|
0,
|
|
NULL))
|
|
return LOG_LAST_ERROR(L"Failed to find certificate");
|
|
HCERTSTORE TrustedStore =
|
|
CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"TrustedPublisher");
|
|
if (!TrustedStore)
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to open store");
|
|
goto cleanupQueriedStore;
|
|
}
|
|
LPSTR CodeSigningOid[] = { szOID_PKIX_KP_CODE_SIGNING };
|
|
CERT_ENHKEY_USAGE EnhancedUsage = { .cUsageIdentifier = 1, .rgpszUsageIdentifier = CodeSigningOid };
|
|
for (const CERT_CONTEXT *CertContext = NULL; (CertContext = CertFindCertificateInStore(
|
|
QueriedStore,
|
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
|
|
CERT_FIND_ENHKEY_USAGE,
|
|
&EnhancedUsage,
|
|
CertContext)) != NULL;)
|
|
{
|
|
CERT_EXTENSION *Ext = CertFindExtension(
|
|
szOID_BASIC_CONSTRAINTS2, CertContext->pCertInfo->cExtension, CertContext->pCertInfo->rgExtension);
|
|
CERT_BASIC_CONSTRAINTS2_INFO Constraints;
|
|
DWORD Size = sizeof(Constraints);
|
|
if (Ext &&
|
|
CryptDecodeObjectEx(
|
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
szOID_BASIC_CONSTRAINTS2,
|
|
Ext->Value.pbData,
|
|
Ext->Value.cbData,
|
|
0,
|
|
NULL,
|
|
&Constraints,
|
|
&Size) &&
|
|
!Constraints.fCA)
|
|
if (!CertAddCertificateContextToStore(TrustedStore, CertContext, CERT_STORE_ADD_REPLACE_EXISTING, NULL))
|
|
{
|
|
LOG_LAST_ERROR(L"Failed to add certificate to store");
|
|
Result = Result != ERROR_SUCCESS ? Result : GetLastError();
|
|
}
|
|
}
|
|
CertCloseStore(TrustedStore, 0);
|
|
cleanupQueriedStore:
|
|
CertCloseStore(QueriedStore, 0);
|
|
return Result;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if defined(HAVE_EV) || defined(HAVE_WHQL)
|
|
|
|
static BOOL
|
|
IsNewer(_In_ const SP_DRVINFO_DATA_W *DrvInfoData, _In_ const FILETIME *DriverDate, _In_ DWORDLONG DriverVersion)
|
|
{
|
|
if (DrvInfoData->DriverDate.dwHighDateTime > DriverDate->dwHighDateTime)
|
|
return TRUE;
|
|
if (DrvInfoData->DriverDate.dwHighDateTime < DriverDate->dwHighDateTime)
|
|
return FALSE;
|
|
|
|
if (DrvInfoData->DriverDate.dwLowDateTime > DriverDate->dwLowDateTime)
|
|
return TRUE;
|
|
if (DrvInfoData->DriverDate.dwLowDateTime < DriverDate->dwLowDateTime)
|
|
return FALSE;
|
|
|
|
if (DrvInfoData->DriverVersion > DriverVersion)
|
|
return TRUE;
|
|
if (DrvInfoData->DriverVersion < DriverVersion)
|
|
return FALSE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
GetTcpipAdapterRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_REG_PATH) WCHAR *Path)
|
|
{
|
|
WCHAR Guid[MAX_GUID_STRING_LEN];
|
|
_snwprintf_s(
|
|
Path,
|
|
MAX_REG_PATH,
|
|
_TRUNCATE,
|
|
L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%.*s",
|
|
StringFromGUID2(&Adapter->CfgInstanceID, Guid, _countof(Guid)),
|
|
Guid);
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetTcpipInterfaceRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_REG_PATH) WCHAR *Path)
|
|
{
|
|
DWORD Result;
|
|
HKEY TcpipAdapterRegKey;
|
|
WCHAR TcpipAdapterRegPath[MAX_REG_PATH];
|
|
GetTcpipAdapterRegPath(Adapter, TcpipAdapterRegPath);
|
|
Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TcpipAdapterRegPath, 0, KEY_QUERY_VALUE, &TcpipAdapterRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
return LOG_ERROR(L"Failed to open registry key", Result);
|
|
WCHAR *Paths;
|
|
Result = RegistryQueryString(TcpipAdapterRegKey, L"IpConfig", &Paths, TRUE);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query IpConfig value");
|
|
goto cleanupTcpipAdapterRegKey;
|
|
}
|
|
if (!Paths[0])
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"IpConfig is empty");
|
|
Result = ERROR_INVALID_DATA;
|
|
goto cleanupPaths;
|
|
}
|
|
_snwprintf_s(Path, MAX_REG_PATH, _TRUNCATE, L"SYSTEM\\CurrentControlSet\\Services\\%s", Paths);
|
|
cleanupPaths:
|
|
HeapFree(GetProcessHeap(), 0, Paths);
|
|
cleanupTcpipAdapterRegKey:
|
|
RegCloseKey(TcpipAdapterRegKey);
|
|
return Result;
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
CreateAdapter(
|
|
_In_z_count_c_(MAX_PATH) const WCHAR *InfPath,
|
|
_In_z_count_c_(MAX_POOL) const WCHAR *Pool,
|
|
_In_z_count_c_(MAX_ADAPTER_NAME) const WCHAR *Name,
|
|
_In_opt_ const GUID *RequestedGUID,
|
|
_Out_ WINTUN_ADAPTER **Adapter,
|
|
_Inout_ BOOL *RebootRequired)
|
|
{
|
|
DWORD Result;
|
|
HANDLE Mutex = NamespaceTakeMutex(Pool);
|
|
if (!Mutex)
|
|
return ERROR_INVALID_HANDLE;
|
|
|
|
HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&GUID_DEVCLASS_NET, NULL, NULL, NULL);
|
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Creating empty device information set failed");
|
|
goto cleanupMutex;
|
|
}
|
|
|
|
WCHAR ClassName[MAX_CLASS_NAME_LEN];
|
|
if (!SetupDiClassNameFromGuidExW(&GUID_DEVCLASS_NET, ClassName, _countof(ClassName), NULL, NULL, NULL))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Retrieving class name associated with class GUID failed");
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
HANDLE Heap = GetProcessHeap();
|
|
WCHAR PoolDeviceTypeName[MAX_POOL_DEVICE_TYPE];
|
|
GetPoolDeviceTypeName(Pool, PoolDeviceTypeName);
|
|
SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) };
|
|
if (!SetupDiCreateDeviceInfoW(
|
|
DevInfo, ClassName, &GUID_DEVCLASS_NET, PoolDeviceTypeName, NULL, DICD_GENERATE_ID, &DevInfoData))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Creating new device information element failed");
|
|
goto cleanupDevInfo;
|
|
}
|
|
SP_DEVINSTALL_PARAMS_W DevInstallParams = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS_W) };
|
|
if (!SetupDiGetDeviceInstallParamsW(DevInfo, &DevInfoData, &DevInstallParams))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Retrieving device installation parameters failed");
|
|
goto cleanupDevInfo;
|
|
}
|
|
DevInstallParams.Flags |= DI_QUIETINSTALL | DI_ENUMSINGLEINF;
|
|
wcscpy_s(DevInstallParams.DriverPath, _countof(DevInstallParams.DriverPath), InfPath);
|
|
if (!SetupDiSetDeviceInstallParamsW(DevInfo, &DevInfoData, &DevInstallParams))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Setting device installation parameters failed");
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
if (!SetupDiSetSelectedDevice(DevInfo, &DevInfoData))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed selecting device");
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
static const WCHAR Hwids[_countof(WINTUN_HWID) + 1 /*Multi-string terminator*/] = WINTUN_HWID;
|
|
if (!SetupDiSetDeviceRegistryPropertyW(DevInfo, &DevInfoData, SPDRP_HARDWAREID, (const BYTE *)Hwids, sizeof(Hwids)))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed setting hardware ID");
|
|
goto cleanupDevInfo;
|
|
}
|
|
if (!SetupDiBuildDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER)) /* TODO: This takes ~510ms */
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed building driver info list");
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
FILETIME DriverDate = { 0, 0 };
|
|
DWORDLONG DriverVersion = 0;
|
|
for (DWORD EnumIndex = 0;; ++EnumIndex) /* TODO: This loop takes ~600ms */
|
|
{
|
|
SP_DRVINFO_DATA_W DrvInfoData = { .cbSize = sizeof(SP_DRVINFO_DATA_W) };
|
|
if (!SetupDiEnumDriverInfoW(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER, EnumIndex, &DrvInfoData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
/* Check the driver version first, since the check is trivial and will save us iterating over hardware IDs for
|
|
* any driver versioned prior our best match. */
|
|
if (!IsNewer(&DrvInfoData, &DriverDate, DriverVersion))
|
|
continue;
|
|
|
|
SP_DRVINFO_DETAIL_DATA_W *DrvInfoDetailData;
|
|
if (AdapterGetDrvInfoDetail(DevInfo, &DevInfoData, &DrvInfoData, &DrvInfoDetailData) != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_WARN, L"Failed getting driver info detail");
|
|
continue;
|
|
}
|
|
if (!DriverIsOurDrvInfoDetail(DrvInfoDetailData))
|
|
{
|
|
HeapFree(Heap, 0, DrvInfoDetailData);
|
|
continue;
|
|
}
|
|
HeapFree(Heap, 0, DrvInfoDetailData);
|
|
|
|
if (!SetupDiSetSelectedDriverW(DevInfo, &DevInfoData, &DrvInfoData))
|
|
continue;
|
|
|
|
DriverDate = DrvInfoData.DriverDate;
|
|
DriverVersion = DrvInfoData.DriverVersion;
|
|
}
|
|
|
|
if (!DriverVersion)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"No appropriate drivers found");
|
|
Result = ERROR_FILE_NOT_FOUND;
|
|
goto cleanupDriverInfoList;
|
|
}
|
|
|
|
if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, DevInfo, &DevInfoData))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Registering device failed");
|
|
goto cleanupDevice;
|
|
}
|
|
if (!SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS, DevInfo, &DevInfoData))
|
|
LOG_LAST_ERROR(L"Registering coinstallers failed");
|
|
|
|
HKEY NetDevRegKey = INVALID_HANDLE_VALUE;
|
|
const int PollTimeout = 50 /* ms */;
|
|
for (int i = 0; NetDevRegKey == INVALID_HANDLE_VALUE && i < WAIT_FOR_REGISTRY_TIMEOUT / PollTimeout; ++i)
|
|
{
|
|
if (i)
|
|
Sleep(PollTimeout);
|
|
NetDevRegKey = SetupDiOpenDevRegKey(
|
|
DevInfo, &DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_NOTIFY);
|
|
}
|
|
if (NetDevRegKey == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to open device-specific registry key");
|
|
goto cleanupDevice;
|
|
}
|
|
if (RequestedGUID)
|
|
{
|
|
WCHAR RequestedGUIDStr[MAX_GUID_STRING_LEN];
|
|
Result = RegSetValueExW(
|
|
NetDevRegKey,
|
|
L"NetSetupAnticipatedInstanceId",
|
|
0,
|
|
REG_SZ,
|
|
(const BYTE *)RequestedGUIDStr,
|
|
StringFromGUID2(RequestedGUID, RequestedGUIDStr, _countof(RequestedGUIDStr)) * sizeof(WCHAR));
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG_LAST_ERROR(L"Failed to set NetSetupAnticipatedInstanceId");
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
}
|
|
|
|
if (!SetupDiCallClassInstaller(DIF_INSTALLINTERFACES, DevInfo, &DevInfoData))
|
|
LOG_LAST_ERROR(L"Installing interfaces failed");
|
|
|
|
if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICE, DevInfo, &DevInfoData))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Installing device failed");
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
*RebootRequired = *RebootRequired || CheckReboot(DevInfo, &DevInfoData);
|
|
|
|
if (!SetupDiSetDeviceRegistryPropertyW(
|
|
DevInfo,
|
|
&DevInfoData,
|
|
SPDRP_DEVICEDESC,
|
|
(const BYTE *)PoolDeviceTypeName,
|
|
(DWORD)((wcslen(PoolDeviceTypeName) + 1) * sizeof(WCHAR))))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to set device description");
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
|
|
/* DIF_INSTALLDEVICE returns almost immediately, while the device installation continues in the background. It might
|
|
* take a while, before all registry keys and values are populated. */
|
|
WCHAR *DummyStr;
|
|
Result = RegistryQueryStringWait(NetDevRegKey, L"NetCfgInstanceId", WAIT_FOR_REGISTRY_TIMEOUT, &DummyStr);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query NetCfgInstanceId value");
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
HeapFree(Heap, 0, DummyStr);
|
|
DWORD DummyDWORD;
|
|
Result = RegistryQueryDWORDWait(NetDevRegKey, L"NetLuidIndex", WAIT_FOR_REGISTRY_TIMEOUT, &DummyDWORD);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query NetLuidIndex value");
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
Result = RegistryQueryDWORDWait(NetDevRegKey, L"*IfType", WAIT_FOR_REGISTRY_TIMEOUT, &DummyDWORD);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query *IfType value");
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
|
|
Result = CreateAdapterData(Pool, DevInfo, &DevInfoData, Adapter);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to create adapter data");
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
|
|
HKEY TcpipAdapterRegKey;
|
|
WCHAR TcpipAdapterRegPath[MAX_REG_PATH];
|
|
GetTcpipAdapterRegPath(*Adapter, TcpipAdapterRegPath);
|
|
Result = RegistryOpenKeyWait(
|
|
HKEY_LOCAL_MACHINE,
|
|
TcpipAdapterRegPath,
|
|
KEY_QUERY_VALUE | KEY_NOTIFY,
|
|
WAIT_FOR_REGISTRY_TIMEOUT,
|
|
&TcpipAdapterRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to open adapter-specific TCP/IP adapter registry key");
|
|
goto cleanupAdapter;
|
|
}
|
|
Result = RegistryQueryStringWait(TcpipAdapterRegKey, L"IpConfig", WAIT_FOR_REGISTRY_TIMEOUT, &DummyStr);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to query IpConfig value");
|
|
goto cleanupTcpipAdapterRegKey;
|
|
}
|
|
HeapFree(Heap, 0, DummyStr);
|
|
|
|
HKEY TcpipInterfaceRegKey;
|
|
WCHAR TcpipInterfaceRegPath[MAX_REG_PATH];
|
|
Result = GetTcpipInterfaceRegPath(*Adapter, TcpipInterfaceRegPath);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to determine interface-specific TCP/IP network registry key path");
|
|
goto cleanupTcpipAdapterRegKey;
|
|
}
|
|
Result = RegistryOpenKeyWait(
|
|
HKEY_LOCAL_MACHINE,
|
|
TcpipInterfaceRegPath,
|
|
KEY_QUERY_VALUE | KEY_SET_VALUE,
|
|
WAIT_FOR_REGISTRY_TIMEOUT,
|
|
&TcpipInterfaceRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to open interface-specific TCP/IP network registry key");
|
|
goto cleanupTcpipAdapterRegKey;
|
|
}
|
|
|
|
static const DWORD EnableDeadGWDetect = 0;
|
|
Result = RegSetKeyValueW(
|
|
TcpipInterfaceRegKey, NULL, L"EnableDeadGWDetect", REG_DWORD, &EnableDeadGWDetect, sizeof(EnableDeadGWDetect));
|
|
if (Result != ERROR_SUCCESS)
|
|
LOG_ERROR(L"Failed to set EnableDeadGWDetect", Result);
|
|
|
|
Result = WintunSetAdapterName(*Adapter, Name);
|
|
if (Result != ERROR_SUCCESS)
|
|
LOG_ERROR(L"Failed to set adapter name", Result);
|
|
RegCloseKey(TcpipInterfaceRegKey);
|
|
cleanupTcpipAdapterRegKey:
|
|
RegCloseKey(TcpipAdapterRegKey);
|
|
cleanupAdapter:
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(Heap, 0, *Adapter);
|
|
cleanupNetDevRegKey:
|
|
RegCloseKey(NetDevRegKey);
|
|
cleanupDevice:
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
/* The adapter failed to install, or the adapter ID was unobtainable. Clean-up. */
|
|
SP_REMOVEDEVICE_PARAMS RemoveDeviceParams = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
|
|
.InstallFunction = DIF_REMOVE },
|
|
.Scope = DI_REMOVEDEVICE_GLOBAL };
|
|
if (SetupDiSetClassInstallParamsW(
|
|
DevInfo, &DevInfoData, &RemoveDeviceParams.ClassInstallHeader, sizeof(RemoveDeviceParams)) &&
|
|
SetupDiCallClassInstaller(DIF_REMOVE, DevInfo, &DevInfoData))
|
|
*RebootRequired = *RebootRequired || CheckReboot(DevInfo, &DevInfoData);
|
|
}
|
|
cleanupDriverInfoList:
|
|
SetupDiDestroyDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER);
|
|
cleanupDevInfo:
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
cleanupMutex:
|
|
NamespaceReleaseMutex(Mutex);
|
|
return Result;
|
|
}
|
|
|
|
#endif
|
|
|
|
static WINTUN_STATUS
|
|
CreateTemporaryDirectory(_Out_cap_c_(MAX_PATH) WCHAR *RandomTempSubDirectory)
|
|
{
|
|
WCHAR WindowsDirectory[MAX_PATH];
|
|
if (!GetWindowsDirectoryW(WindowsDirectory, _countof(WindowsDirectory)))
|
|
return LOG_LAST_ERROR(L"Failed to get Windows folder");
|
|
WCHAR WindowsTempDirectory[MAX_PATH];
|
|
if (!PathCombineW(WindowsTempDirectory, WindowsDirectory, L"Temp"))
|
|
return ERROR_BUFFER_OVERFLOW;
|
|
UCHAR RandomBytes[32] = { 0 };
|
|
#pragma warning(suppress : 6387)
|
|
if (!RtlGenRandom(RandomBytes, sizeof(RandomBytes)))
|
|
return LOG_LAST_ERROR(L"Failed to generate random");
|
|
WCHAR RandomSubDirectory[sizeof(RandomBytes) * 2 + 1];
|
|
for (int i = 0; i < sizeof(RandomBytes); ++i)
|
|
swprintf_s(&RandomSubDirectory[i * 2], 3, L"%02x", RandomBytes[i]);
|
|
if (!PathCombineW(RandomTempSubDirectory, WindowsTempDirectory, RandomSubDirectory))
|
|
return ERROR_BUFFER_OVERFLOW;
|
|
if (!CreateDirectoryW(RandomTempSubDirectory, SecurityAttributes))
|
|
return LOG_LAST_ERROR(L"Failed to create temporary folder");
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
#if defined(_M_IX86) || defined(_M_ARM)
|
|
|
|
typedef struct _PROCESS_STDOUT_STATE
|
|
{
|
|
HANDLE Stdout;
|
|
WCHAR *Response;
|
|
DWORD ResponseCapacity;
|
|
} PROCESS_STDOUT_STATE;
|
|
|
|
static DWORD WINAPI
|
|
ProcessStdout(_Inout_ PROCESS_STDOUT_STATE *State)
|
|
{
|
|
for (DWORD Offset = 0, MaxLen = State->ResponseCapacity - 1; Offset < MaxLen;)
|
|
{
|
|
DWORD SizeRead;
|
|
if (!ReadFile(State->Stdout, State->Response + Offset, sizeof(WCHAR) * (MaxLen - Offset), &SizeRead, NULL))
|
|
return 0;
|
|
if (SizeRead % sizeof(WCHAR))
|
|
return 1;
|
|
Offset += SizeRead / sizeof(WCHAR);
|
|
State->Response[Offset] = 0;
|
|
}
|
|
return 2;
|
|
}
|
|
|
|
static DWORD WINAPI
|
|
ProcessStderr(_In_ HANDLE Stderr)
|
|
{
|
|
enum
|
|
{
|
|
OnNone,
|
|
OnLevelStart,
|
|
OnLevel,
|
|
OnLevelEnd,
|
|
OnSpace,
|
|
OnMsg
|
|
} State = OnNone;
|
|
WCHAR Msg[0x200];
|
|
DWORD Count = 0;
|
|
WINTUN_LOGGER_LEVEL Level = WINTUN_LOG_INFO;
|
|
for (;;)
|
|
{
|
|
WCHAR Buf[0x200];
|
|
DWORD SizeRead;
|
|
if (!ReadFile(Stderr, Buf, sizeof(Buf), &SizeRead, NULL))
|
|
return 0;
|
|
if (SizeRead % sizeof(WCHAR))
|
|
return 1;
|
|
SizeRead /= sizeof(WCHAR);
|
|
for (DWORD i = 0; i < SizeRead; ++i)
|
|
{
|
|
WCHAR c = Buf[i];
|
|
if (State == OnNone && c == L'[')
|
|
State = OnLevelStart;
|
|
else if (
|
|
State == OnLevelStart && ((Level = WINTUN_LOG_INFO, c == L'+') ||
|
|
(Level = WINTUN_LOG_WARN, c == L'-') || (Level = WINTUN_LOG_ERR, c == L'!')))
|
|
State = OnLevelEnd;
|
|
else if (State == OnLevelEnd && c == L']')
|
|
State = OnSpace;
|
|
else if (State == OnSpace && !iswspace(c) || State == OnMsg && c != L'\r' && c != L'\n')
|
|
{
|
|
if (Count < _countof(Msg) - 1)
|
|
Msg[Count++] = c;
|
|
State = OnMsg;
|
|
}
|
|
else if (State == OnMsg && c == L'\n')
|
|
{
|
|
Msg[Count] = 0;
|
|
Logger(Level, Msg);
|
|
State = OnNone;
|
|
Count = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
ExecuteRunDll32(
|
|
_In_z_ const WCHAR *Arguments,
|
|
_Out_z_cap_c_(ResponseCapacity) WCHAR *Response,
|
|
_In_ DWORD ResponseCapacity)
|
|
{
|
|
WCHAR WindowsDirectory[MAX_PATH];
|
|
if (!GetWindowsDirectoryW(WindowsDirectory, _countof(WindowsDirectory)))
|
|
return LOG_LAST_ERROR(L"Failed to get Windows folder");
|
|
WCHAR RunDll32Path[MAX_PATH];
|
|
if (!PathCombineW(RunDll32Path, WindowsDirectory, L"Sysnative\\rundll32.exe"))
|
|
return ERROR_BUFFER_OVERFLOW;
|
|
|
|
DWORD Result;
|
|
WCHAR RandomTempSubDirectory[MAX_PATH];
|
|
if ((Result = CreateTemporaryDirectory(RandomTempSubDirectory)) != ERROR_SUCCESS)
|
|
return LOG(WINTUN_LOG_ERR, L"Failed to create temporary folder"), Result;
|
|
WCHAR DllPath[MAX_PATH] = { 0 };
|
|
if (!PathCombineW(DllPath, RandomTempSubDirectory, L"wintun.dll"))
|
|
{
|
|
Result = ERROR_BUFFER_OVERFLOW;
|
|
goto cleanupDirectory;
|
|
}
|
|
if ((Result = ResourceCopyToFile(
|
|
DllPath, NativeMachine == IMAGE_FILE_MACHINE_ARM64 ? L"wintun-arm64.dll" : L"wintun-amd64.dll")) !=
|
|
ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to copy resource");
|
|
goto cleanupDelete;
|
|
}
|
|
HANDLE Heap = GetProcessHeap();
|
|
size_t CommandLineLen = 10 + MAX_PATH + 2 + wcslen(Arguments) + 1;
|
|
WCHAR *CommandLine = HeapAlloc(Heap, 0, CommandLineLen * sizeof(WCHAR));
|
|
if (!CommandLine)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Out of memory");
|
|
Result = ERROR_OUTOFMEMORY;
|
|
goto cleanupDelete;
|
|
}
|
|
_snwprintf_s(CommandLine, CommandLineLen, _TRUNCATE, L"rundll32 \"%.*s\",%s", MAX_PATH, DllPath, Arguments);
|
|
SECURITY_ATTRIBUTES sa = { .nLength = sizeof(SECURITY_ATTRIBUTES),
|
|
.bInheritHandle = TRUE,
|
|
.lpSecurityDescriptor =
|
|
SecurityAttributes ? SecurityAttributes->lpSecurityDescriptor : NULL };
|
|
typedef enum
|
|
{
|
|
Stdout = 0,
|
|
Stderr
|
|
} stdid_t;
|
|
HANDLE StreamR[] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE },
|
|
StreamW[] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
|
|
if (!CreatePipe(&StreamR[Stdout], &StreamW[Stdout], &sa, 0) ||
|
|
!CreatePipe(&StreamR[Stderr], &StreamW[Stderr], &sa, 0))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to create pipes");
|
|
goto cleanupPipes;
|
|
}
|
|
if (!SetHandleInformation(StreamR[Stdout], HANDLE_FLAG_INHERIT, 0) ||
|
|
!SetHandleInformation(StreamR[Stderr], HANDLE_FLAG_INHERIT, 0))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to set handle info");
|
|
goto cleanupPipes;
|
|
}
|
|
if (ResponseCapacity)
|
|
Response[0] = 0;
|
|
PROCESS_STDOUT_STATE ProcessStdoutState = { .Stdout = StreamR[Stdout],
|
|
.Response = Response,
|
|
.ResponseCapacity = ResponseCapacity };
|
|
HANDLE Thread[] = { NULL, NULL };
|
|
if ((Thread[Stdout] = CreateThread(SecurityAttributes, 0, ProcessStdout, &ProcessStdoutState, 0, NULL)) == NULL ||
|
|
(Thread[Stderr] = CreateThread(SecurityAttributes, 0, ProcessStderr, StreamR[Stderr], 0, NULL)) == NULL)
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to spawn reader threads");
|
|
goto cleanupThreads;
|
|
}
|
|
STARTUPINFOW si = { .cb = sizeof(STARTUPINFO),
|
|
.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES,
|
|
.wShowWindow = SW_HIDE,
|
|
.hStdOutput = StreamW[Stdout],
|
|
.hStdError = StreamW[Stderr] };
|
|
PROCESS_INFORMATION pi;
|
|
if (!CreateProcessW(RunDll32Path, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Creating process failed");
|
|
goto cleanupThreads;
|
|
}
|
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
cleanupThreads:
|
|
for (size_t i = _countof(Thread); i--;)
|
|
if (Thread[i])
|
|
{
|
|
CloseHandle(StreamW[i]);
|
|
StreamW[i] = INVALID_HANDLE_VALUE;
|
|
WaitForSingleObject(Thread[i], INFINITE);
|
|
CloseHandle(Thread[i]);
|
|
}
|
|
cleanupPipes:
|
|
CloseHandle(StreamR[Stderr]);
|
|
CloseHandle(StreamW[Stderr]);
|
|
CloseHandle(StreamR[Stdout]);
|
|
CloseHandle(StreamW[Stdout]);
|
|
HeapFree(Heap, 0, CommandLine);
|
|
cleanupDelete:
|
|
DeleteFileW(DllPath);
|
|
cleanupDirectory:
|
|
RemoveDirectoryW(RandomTempSubDirectory);
|
|
return Result;
|
|
}
|
|
|
|
static WINTUN_STATUS
|
|
GetAdapter(_In_z_count_c_(MAX_POOL) const WCHAR *Pool, _In_ const GUID *CfgInstanceID, _Out_ WINTUN_ADAPTER **Adapter)
|
|
{
|
|
HANDLE Mutex = NamespaceTakeMutex(Pool);
|
|
if (!Mutex)
|
|
return ERROR_INVALID_HANDLE;
|
|
HDEVINFO DevInfo;
|
|
SP_DEVINFO_DATA DevInfoData;
|
|
DWORD Result = GetDevInfoData(CfgInstanceID, &DevInfo, &DevInfoData);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to locate adapter");
|
|
goto cleanupMutex;
|
|
}
|
|
Result = CreateAdapterData(Pool, DevInfo, &DevInfoData, Adapter);
|
|
if (Result != ERROR_SUCCESS)
|
|
LOG(WINTUN_LOG_ERR, L"Failed to create adapter data");
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
cleanupMutex:
|
|
NamespaceReleaseMutex(Mutex);
|
|
return Result;
|
|
}
|
|
|
|
#endif
|
|
|
|
WINTUN_STATUS WINAPI
|
|
WintunCreateAdapter(
|
|
_In_z_count_c_(MAX_POOL) const WCHAR *Pool,
|
|
_In_z_count_c_(MAX_ADAPTER_NAME) const WCHAR *Name,
|
|
_In_opt_ const GUID *RequestedGUID,
|
|
_Out_ WINTUN_ADAPTER **Adapter,
|
|
_Inout_ BOOL *RebootRequired)
|
|
{
|
|
#if defined(_M_IX86) || defined(_M_ARM)
|
|
if (NativeMachine != IMAGE_FILE_PROCESS)
|
|
{
|
|
LOG(WINTUN_LOG_INFO, L"Spawning native process for the job");
|
|
WCHAR RequestedGUIDStr[MAX_GUID_STRING_LEN];
|
|
WCHAR Arguments[15 + MAX_POOL + 3 + MAX_ADAPTER_NAME + 2 + MAX_GUID_STRING_LEN + 1];
|
|
_snwprintf_s(
|
|
Arguments,
|
|
_countof(Arguments),
|
|
_TRUNCATE,
|
|
RequestedGUID ? L"CreateAdapter \"%.*s\" \"%.*s\" %.*s" : L"CreateAdapter \"%.*s\" \"%.*s\"",
|
|
MAX_POOL,
|
|
Pool,
|
|
MAX_ADAPTER_NAME,
|
|
Name,
|
|
RequestedGUID ? StringFromGUID2(RequestedGUID, RequestedGUIDStr, _countof(RequestedGUIDStr)) : 0,
|
|
RequestedGUIDStr);
|
|
WCHAR Response[8 + 1 + MAX_GUID_STRING_LEN + 1 + 8 + 1];
|
|
DWORD Result = ExecuteRunDll32(Arguments, Response, _countof(Response));
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Error executing worker process");
|
|
return Result;
|
|
}
|
|
int Argc;
|
|
WCHAR **Argv = CommandLineToArgvW(Response, &Argc);
|
|
GUID CfgInstanceID;
|
|
if (Argc < 3 || FAILED(CLSIDFromString(Argv[1], &CfgInstanceID)))
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Incomplete or invalid response");
|
|
Result = ERROR_INVALID_PARAMETER;
|
|
goto cleanupArgv;
|
|
}
|
|
Result = wcstoul(Argv[0], NULL, 16);
|
|
if (Result == ERROR_SUCCESS && GetAdapter(Pool, &CfgInstanceID, Adapter) != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to get adapter");
|
|
Result = ERROR_FILE_NOT_FOUND;
|
|
}
|
|
if (wcstoul(Argv[2], NULL, 16))
|
|
*RebootRequired = TRUE;
|
|
cleanupArgv:
|
|
LocalFree(Argv);
|
|
return Result;
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_EV) || defined(HAVE_WHQL)
|
|
DWORD Result = ERROR_SUCCESS;
|
|
WCHAR RandomTempSubDirectory[MAX_PATH];
|
|
if ((Result = CreateTemporaryDirectory(RandomTempSubDirectory)) != ERROR_SUCCESS)
|
|
return LOG(WINTUN_LOG_ERR, L"Failed to create temporary folder"), Result;
|
|
|
|
WCHAR CatPath[MAX_PATH] = { 0 };
|
|
WCHAR SysPath[MAX_PATH] = { 0 };
|
|
WCHAR InfPath[MAX_PATH] = { 0 };
|
|
if (!PathCombineW(CatPath, RandomTempSubDirectory, L"wintun.cat") ||
|
|
!PathCombineW(SysPath, RandomTempSubDirectory, L"wintun.sys") ||
|
|
!PathCombineW(InfPath, RandomTempSubDirectory, L"wintun.inf"))
|
|
{
|
|
Result = ERROR_BUFFER_OVERFLOW;
|
|
goto cleanupDirectory;
|
|
}
|
|
|
|
BOOL UseWHQL = HaveWHQL();
|
|
if (!UseWHQL && (Result = InstallCertificate(L"wintun.sys")) != ERROR_SUCCESS)
|
|
LOG(WINTUN_LOG_WARN, L"Unable to install code signing certificate");
|
|
|
|
LOG(WINTUN_LOG_INFO, L"Copying resources to temporary path");
|
|
if ((Result = ResourceCopyToFile(CatPath, UseWHQL ? L"wintun-whql.cat" : L"wintun.cat")) != ERROR_SUCCESS ||
|
|
(Result = ResourceCopyToFile(SysPath, UseWHQL ? L"wintun-whql.sys" : L"wintun.sys")) != ERROR_SUCCESS ||
|
|
(Result = ResourceCopyToFile(InfPath, UseWHQL ? L"wintun-whql.inf" : L"wintun.inf")) != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to copy resources");
|
|
goto cleanupDelete;
|
|
}
|
|
|
|
LOG(WINTUN_LOG_INFO, L"Installing driver");
|
|
if (!SetupCopyOEMInfW(InfPath, NULL, SPOST_PATH, 0, NULL, 0, NULL, NULL))
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Could not install driver to store");
|
|
goto cleanupDelete;
|
|
}
|
|
|
|
Result = CreateAdapter(InfPath, Pool, Name, RequestedGUID, Adapter, RebootRequired);
|
|
DriverRemoveAllOurs();
|
|
cleanupDelete:
|
|
DeleteFileW(CatPath);
|
|
DeleteFileW(SysPath);
|
|
DeleteFileW(InfPath);
|
|
cleanupDirectory:
|
|
RemoveDirectoryW(RandomTempSubDirectory);
|
|
return Result;
|
|
#else
|
|
return ERROR_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
WINTUN_STATUS WINAPI
|
|
WintunDeleteAdapter(_In_ const WINTUN_ADAPTER *Adapter, _Inout_ BOOL *RebootRequired)
|
|
{
|
|
#if defined(_M_IX86) || defined(_M_ARM)
|
|
if (NativeMachine != IMAGE_FILE_PROCESS)
|
|
{
|
|
LOG(WINTUN_LOG_INFO, L"Spawning native process for the job");
|
|
WCHAR GuidStr[MAX_GUID_STRING_LEN];
|
|
WCHAR Arguments[14 + MAX_GUID_STRING_LEN + 1];
|
|
_snwprintf_s(
|
|
Arguments,
|
|
_countof(Arguments),
|
|
_TRUNCATE,
|
|
L"DeleteAdapter %.*s",
|
|
StringFromGUID2(&Adapter->CfgInstanceID, GuidStr, _countof(GuidStr)),
|
|
GuidStr);
|
|
WCHAR Response[8 + 1 + 8 + 1];
|
|
DWORD Result = ExecuteRunDll32(Arguments, Response, _countof(Response));
|
|
if (Result != ERROR_SUCCESS)
|
|
LOG(WINTUN_LOG_ERR, L"Error executing worker process");
|
|
int Argc;
|
|
WCHAR **Argv = CommandLineToArgvW(Response, &Argc);
|
|
if (Argc < 2)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Incomplete or invalid response");
|
|
Result = ERROR_INVALID_PARAMETER;
|
|
goto cleanupArgv;
|
|
}
|
|
Result = wcstoul(Argv[0], NULL, 16);
|
|
if (wcstoul(Argv[1], NULL, 16))
|
|
*RebootRequired = TRUE;
|
|
cleanupArgv:
|
|
LocalFree(Argv);
|
|
return Result;
|
|
}
|
|
#endif
|
|
|
|
HDEVINFO DevInfo;
|
|
SP_DEVINFO_DATA DevInfoData;
|
|
DWORD Result = GetDevInfoData(&Adapter->CfgInstanceID, &DevInfo, &DevInfoData);
|
|
if (Result == ERROR_FILE_NOT_FOUND)
|
|
return ERROR_SUCCESS;
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to get device info data");
|
|
return Result;
|
|
}
|
|
SetQuietInstall(DevInfo, &DevInfoData);
|
|
SP_REMOVEDEVICE_PARAMS RemoveDeviceParams = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
|
|
.InstallFunction = DIF_REMOVE },
|
|
.Scope = DI_REMOVEDEVICE_GLOBAL };
|
|
if (SetupDiSetClassInstallParamsW(
|
|
DevInfo, &DevInfoData, &RemoveDeviceParams.ClassInstallHeader, sizeof(RemoveDeviceParams)) &&
|
|
SetupDiCallClassInstaller(DIF_REMOVE, DevInfo, &DevInfoData))
|
|
*RebootRequired = *RebootRequired || CheckReboot(DevInfo, &DevInfoData);
|
|
else
|
|
Result = LOG_LAST_ERROR(L"Unable to remove existing adapter");
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
return Result;
|
|
}
|
|
|
|
WINTUN_STATUS WINAPI
|
|
WintunEnumAdapters(_In_z_count_c_(MAX_POOL) const WCHAR *Pool, _In_ WINTUN_ENUM_FUNC Func, _In_ LPARAM Param)
|
|
{
|
|
HANDLE Mutex = NamespaceTakeMutex(Pool);
|
|
if (!Mutex)
|
|
return ERROR_INVALID_HANDLE;
|
|
DWORD Result = ERROR_SUCCESS;
|
|
HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
|
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = LOG_LAST_ERROR(L"Failed to get present class devices");
|
|
goto cleanupMutex;
|
|
}
|
|
HANDLE Heap = GetProcessHeap();
|
|
BOOL Continue = TRUE;
|
|
for (DWORD EnumIndex = 0; Continue; ++EnumIndex)
|
|
{
|
|
SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) };
|
|
if (!SetupDiEnumDeviceInfo(DevInfo, EnumIndex, &DevInfoData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
BOOL IsOurs;
|
|
if (IsOurAdapter(DevInfo, &DevInfoData, &IsOurs) != ERROR_SUCCESS || !IsOurs)
|
|
continue;
|
|
|
|
BOOL IsMember;
|
|
Result = IsPoolMember(Pool, DevInfo, &DevInfoData, &IsMember);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to determine pool membership");
|
|
break;
|
|
}
|
|
if (!IsMember)
|
|
continue;
|
|
|
|
WINTUN_ADAPTER *Adapter;
|
|
Result = CreateAdapterData(Pool, DevInfo, &DevInfoData, &Adapter);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
LOG(WINTUN_LOG_ERR, L"Failed to create adapter data");
|
|
break;
|
|
}
|
|
Continue = Func(Adapter, Param);
|
|
HeapFree(Heap, 0, Adapter);
|
|
}
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
cleanupMutex:
|
|
NamespaceReleaseMutex(Mutex);
|
|
return Result;
|
|
}
|