7e3740018d
Signed-off-by: Simon Rozman <simon@rozman.si>
1274 lines
44 KiB
C
1274 lines
44 KiB
C
/* SPDX-License-Identifier: GPL-2.0
|
|
*
|
|
* Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved.
|
|
*/
|
|
|
|
#include "api.h"
|
|
#include <cfgmgr32.h>
|
|
#include <iphlpapi.h>
|
|
#include <objbase.h>
|
|
#include <SetupAPI.h>
|
|
#include <wchar.h>
|
|
|
|
#define WINTUN_HWID L"Wintun"
|
|
#define WAIT_FOR_REGISTRY_TIMEOUT 10000 /* ms */
|
|
#define MAX_POOL_DEVICE_TYPE (MAX_POOL + 0x100)
|
|
|
|
const static GUID CLASS_NET_GUID = { 0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };
|
|
const static GUID ADAPTER_NET_GUID = { 0xcac88484L,
|
|
0x7515,
|
|
0x4c03,
|
|
{ 0x82, 0xe6, 0x71, 0xa8, 0x7a, 0xba, 0xc3, 0x61 } };
|
|
|
|
/**
|
|
* Returns the version of the Wintun driver and NDIS system currently loaded.
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunGetVersion(
|
|
_Out_ DWORD *DriverVersionMaj,
|
|
_Out_ DWORD *DriverVersionMin,
|
|
_Out_ DWORD *NdisVersionMaj,
|
|
_Out_ DWORD *NdisVersionMin)
|
|
{
|
|
HKEY Key;
|
|
DWORD Result =
|
|
RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\Wintun", 0, KEY_QUERY_VALUE, &Key);
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
Result = RegistryQueryDWORD(Key, L"DriverMajorVersion", DriverVersionMaj);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupKey;
|
|
Result = RegistryQueryDWORD(Key, L"DriverMinorVersion", DriverVersionMin);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupKey;
|
|
Result = RegistryQueryDWORD(Key, L"NdisMajorVersion", NdisVersionMaj);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupKey;
|
|
Result = RegistryQueryDWORD(Key, L"NdisMinorVersion", NdisVersionMin);
|
|
cleanupKey:
|
|
RegCloseKey(Key);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a specified Plug and Play device property.
|
|
*
|
|
* @param DevInfo A handle to the device information set that contains a device information element that
|
|
* represents the device for which to open a registry key.
|
|
*
|
|
* @param DevInfoData A pointer to a structure that specifies the device information element in DeviceInfoSet.
|
|
*
|
|
* @param Property The property to be retrieved. One of the SPDRP_* constants.
|
|
*
|
|
* @param PropertyRegDataType A pointer to a variable that receives the data type of the property that is being
|
|
* retrieved. This is one of the standard registry data types. This parameter is optional
|
|
* and can be NULL.
|
|
*
|
|
* @param PropertyBuffer A pointer to a buffer that receives the property that is being retrieved. Must be
|
|
* released with HeapFree(GetProcessHeap(), 0, Value) after use.
|
|
*
|
|
* @param PropertySize A pointer to a variable of type DWORD that receives the property size, in bytes, of the
|
|
* PropertyBuffer buffer. This parameter is optional and can be NULL.
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
static WINSTATUS
|
|
GetDeviceRegistryProperty(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ DWORD Property,
|
|
_Out_opt_ DWORD *PropertyRegDataType,
|
|
_Out_ void **PropertyBuffer,
|
|
_Out_opt_ DWORD *PropertySize)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
DWORD Result, Size = 256;
|
|
for (;;)
|
|
{
|
|
void *Buf = HeapAlloc(Heap, 0, Size);
|
|
if (!Buf)
|
|
return ERROR_OUTOFMEMORY;
|
|
DWORD ValueType;
|
|
if (!SetupDiGetDeviceRegistryPropertyW(DevInfo, DevInfoData, Property, &ValueType, Buf, Size, &Size))
|
|
{
|
|
Result = GetLastError();
|
|
HeapFree(Heap, 0, Buf);
|
|
if (Result == ERROR_INSUFFICIENT_BUFFER)
|
|
continue;
|
|
return Result;
|
|
}
|
|
|
|
if (PropertyRegDataType)
|
|
*PropertyRegDataType = ValueType;
|
|
*PropertyBuffer = Buf;
|
|
if (PropertySize)
|
|
*PropertySize = Size;
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves a specified Plug and Play device property string.
|
|
*
|
|
* @param DevInfo A handle to the device information set that contains a device information element that
|
|
* represents the device for which to open a registry key.
|
|
*
|
|
* @param DevInfoData A pointer to a structure that specifies the device information element in DeviceInfoSet.
|
|
*
|
|
* @param Property The property to be retrieved. One of the SPDRP_* constants.
|
|
*
|
|
* @param PropertyBuffer A pointer to a string that receives the string that is being retrieved. Must be
|
|
* released with HeapFree(GetProcessHeap(), 0, Value) after use.
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
static WINSTATUS
|
|
GetDeviceRegistryString(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ DWORD Property,
|
|
_Out_ LPWSTR *PropertyBuffer)
|
|
{
|
|
DWORD Result, ValueType, Size;
|
|
Result = GetDeviceRegistryProperty(DevInfo, DevInfoData, Property, &ValueType, PropertyBuffer, &Size);
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
|
|
switch (ValueType)
|
|
{
|
|
case REG_SZ:
|
|
case REG_EXPAND_SZ:
|
|
case REG_MULTI_SZ:
|
|
Result = RegistryGetString(PropertyBuffer, Size / sizeof(WCHAR), ValueType);
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(GetProcessHeap(), 0, *PropertyBuffer);
|
|
return Result;
|
|
default:
|
|
HeapFree(GetProcessHeap(), 0, *PropertyBuffer);
|
|
return ERROR_INVALID_DATATYPE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves a specified Plug and Play device property multi-string.
|
|
*
|
|
* @param DevInfo A handle to the device information set that contains a device information element that
|
|
* represents the device for which to open a registry key.
|
|
*
|
|
* @param DevInfoData A pointer to a structure that specifies the device information element in DeviceInfoSet.
|
|
*
|
|
* @param Property The property to be retrieved. One of the SPDRP_* constants.
|
|
*
|
|
* @param PropertyBuffer A pointer to a multi-string that receives the string that is being retrieved. Must be
|
|
* released with HeapFree(GetProcessHeap(), 0, Value) after use.
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
static WINSTATUS
|
|
GetDeviceRegistryMultiString(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ DWORD Property,
|
|
_Out_ LPWSTR *PropertyBuffer)
|
|
{
|
|
DWORD Result, ValueType, Size;
|
|
Result = GetDeviceRegistryProperty(DevInfo, DevInfoData, Property, &ValueType, PropertyBuffer, &Size);
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
|
|
switch (ValueType)
|
|
{
|
|
case REG_SZ:
|
|
case REG_EXPAND_SZ:
|
|
case REG_MULTI_SZ:
|
|
Result = RegistryGetMultiString(PropertyBuffer, Size / sizeof(WCHAR), ValueType);
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(GetProcessHeap(), 0, *PropertyBuffer);
|
|
return Result;
|
|
default:
|
|
HeapFree(GetProcessHeap(), 0, *PropertyBuffer);
|
|
return ERROR_INVALID_DATATYPE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes numbered suffix from adapter name.
|
|
*/
|
|
static void
|
|
RemoveNumberedSuffix(_In_z_ LPCWSTR Name, _Out_ LPWSTR 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);
|
|
}
|
|
|
|
/**
|
|
* Tests if any of the hardware IDs match ours.
|
|
*
|
|
* @param Hwids Multi-string containing a list of hardware IDs
|
|
*
|
|
* @return TRUE on match; FALSE otherwise.
|
|
*/
|
|
static BOOL
|
|
IsOurHardwareID(_In_z_ LPWSTR Hwids)
|
|
{
|
|
for (; Hwids[0]; Hwids += wcslen(Hwids) + 1)
|
|
if (!_wcsicmp(Hwids, WINTUN_HWID))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Returns pool-specific device type name.
|
|
*/
|
|
static void
|
|
GetPoolDeviceTypeName(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _Out_cap_c_(MAX_POOL_DEVICE_TYPE) LPWSTR Name)
|
|
{
|
|
_snwprintf_s(Name, MAX_POOL_DEVICE_TYPE, _TRUNCATE, L"%.*s Tunnel", MAX_POOL, Pool);
|
|
}
|
|
|
|
/**
|
|
* Checks if SPDRP_DEVICEDESC or SPDRP_FRIENDLYNAME match device type name.
|
|
*/
|
|
static WINSTATUS
|
|
IsPoolMember(
|
|
_In_z_count_c_(MAX_POOL) LPCWSTR Pool,
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_Out_ BOOL *IsMember)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
LPWSTR DeviceDesc, FriendlyName;
|
|
DWORD Result = GetDeviceRegistryString(DevInfo, DevInfoData, SPDRP_DEVICEDESC, &DeviceDesc);
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
Result = GetDeviceRegistryString(DevInfo, DevInfoData, SPDRP_FRIENDLYNAME, &FriendlyName);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupDeviceDesc;
|
|
WCHAR PoolDeviceTypeName[MAX_POOL_DEVICE_TYPE];
|
|
GetPoolDeviceTypeName(Pool, PoolDeviceTypeName);
|
|
if (!_wcsicmp(FriendlyName, PoolDeviceTypeName) || !_wcsicmp(DeviceDesc, PoolDeviceTypeName))
|
|
{
|
|
*IsMember = TRUE;
|
|
goto cleanupFriendlyName;
|
|
}
|
|
RemoveNumberedSuffix(FriendlyName, FriendlyName);
|
|
RemoveNumberedSuffix(DeviceDesc, DeviceDesc);
|
|
if (!_wcsicmp(FriendlyName, PoolDeviceTypeName) || !_wcsicmp(DeviceDesc, PoolDeviceTypeName))
|
|
{
|
|
*IsMember = TRUE;
|
|
goto cleanupFriendlyName;
|
|
}
|
|
*IsMember = FALSE;
|
|
cleanupFriendlyName:
|
|
HeapFree(Heap, 0, FriendlyName);
|
|
cleanupDeviceDesc:
|
|
HeapFree(Heap, 0, DeviceDesc);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Retrieves driver information detail for a device information set or a particular device information element in the
|
|
* device information set.
|
|
*
|
|
* @param DevInfo A handle to the device information set that contains a device information element that
|
|
* represents the device for which to open a registry key.
|
|
*
|
|
* @param DevInfoData A pointer to a structure that specifies the device information element in DeviceInfoSet.
|
|
*
|
|
* @param DriverData A pointer to a structure that specifies the driver information element that represents the
|
|
* driver for which to retrieve details.
|
|
*
|
|
* @param DriverDetailData A pointer to a structure that receives detailed information about the specified driver.
|
|
* Must be released with HeapFree(GetProcessHeap(), 0, Value) after use.
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
static WINSTATUS
|
|
GetDriverInfoDetail(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_ SP_DRVINFO_DATA_W *DriverData,
|
|
_Out_ SP_DRVINFO_DETAIL_DATA_W **DriverDetailData)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
DWORD Size = sizeof(SP_DRVINFO_DETAIL_DATA_W) + 0x100;
|
|
for (;;)
|
|
{
|
|
*DriverDetailData = HeapAlloc(Heap, 0, Size);
|
|
if (!*DriverDetailData)
|
|
return ERROR_OUTOFMEMORY;
|
|
(*DriverDetailData)->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA_W);
|
|
if (!SetupDiGetDriverInfoDetailW(DevInfo, DevInfoData, DriverData, *DriverDetailData, Size, &Size))
|
|
{
|
|
DWORD Result = GetLastError();
|
|
HeapFree(Heap, 0, *DriverDetailData);
|
|
if (Result == ERROR_INSUFFICIENT_BUFFER)
|
|
continue;
|
|
return Result;
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the device is using Wintun driver.
|
|
*/
|
|
static WINSTATUS
|
|
IsUsingOurDriver(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData, _Out_ BOOL *IsOurDriver)
|
|
{
|
|
if (!SetupDiBuildDriverInfoList(DevInfo, DevInfoData, SPDIT_COMPATDRIVER))
|
|
return GetLastError();
|
|
*IsOurDriver = FALSE;
|
|
HANDLE Heap = GetProcessHeap();
|
|
for (DWORD DriverIndex = 0;; ++DriverIndex)
|
|
{
|
|
SP_DRVINFO_DATA_W DriverData = { .cbSize = sizeof(SP_DRVINFO_DATA_W) };
|
|
if (!SetupDiEnumDriverInfoW(DevInfo, DevInfoData, SPDIT_COMPATDRIVER, DriverIndex, &DriverData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
SP_DRVINFO_DETAIL_DATA_W *DriverDetailData;
|
|
if (GetDriverInfoDetail(DevInfo, DevInfoData, &DriverData, &DriverDetailData) != ERROR_SUCCESS)
|
|
continue;
|
|
if (DriverDetailData->CompatIDsOffset > 1 && !_wcsicmp(DriverDetailData->HardwareID, WINTUN_HWID) ||
|
|
DriverDetailData->CompatIDsLength &&
|
|
IsOurHardwareID(DriverDetailData->HardwareID + DriverDetailData->CompatIDsOffset))
|
|
{
|
|
HeapFree(Heap, 0, DriverDetailData);
|
|
*IsOurDriver = TRUE;
|
|
break;
|
|
}
|
|
HeapFree(Heap, 0, DriverDetailData);
|
|
}
|
|
SetupDiDestroyDriverInfoList(DevInfo, DevInfoData, SPDIT_COMPATDRIVER);
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Creates a Wintun interface descriptor and populates it from the device's registry key.
|
|
*
|
|
* @param DevInfo A handle to the device information set that contains a device information element that
|
|
* represents the device for which to open a registry key.
|
|
*
|
|
* @param DevInfoData A pointer to a structure that specifies the device information element in DeviceInfoSet.
|
|
*
|
|
* @param Pool Name of the adapter pool
|
|
*
|
|
* @param Adapter Pointer to a handle to receive the adapter descriptor. Must be released with
|
|
* HeapFree(GetProcessHeap(), 0, *Adapter).
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
static WINSTATUS
|
|
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 GetLastError();
|
|
LPWSTR ValueStr;
|
|
DWORD Result = RegistryQueryString(Key, L"NetCfgInstanceId", &ValueStr);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupKey;
|
|
Result = SUCCEEDED(CLSIDFromString(ValueStr, CfgInstanceID)) ? ERROR_SUCCESS : ERROR_INVALID_DATA;
|
|
HeapFree(GetProcessHeap(), 0, ValueStr);
|
|
cleanupKey:
|
|
RegCloseKey(Key);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Creates a Wintun interface descriptor and populates it from the device's registry key.
|
|
*
|
|
* @param DevInfo A handle to the device information set that contains a device information element that
|
|
* represents the device for which to open a registry key.
|
|
*
|
|
* @param DevInfoData A pointer to a structure that specifies the device information element in DeviceInfoSet.
|
|
*
|
|
* @param Pool Name of the adapter pool
|
|
*
|
|
* @param Adapter Pointer to a handle to receive the adapter descriptor. Must be released with
|
|
* HeapFree(GetProcessHeap(), 0, *Adapter).
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
static WINSTATUS
|
|
CreateAdapterData(
|
|
_In_ HDEVINFO DevInfo,
|
|
_In_ SP_DEVINFO_DATA *DevInfoData,
|
|
_In_z_count_c_(MAX_POOL) LPCWSTR Pool,
|
|
_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 GetLastError();
|
|
|
|
HANDLE Heap = GetProcessHeap();
|
|
*Adapter = HeapAlloc(Heap, 0, sizeof(WINTUN_ADAPTER));
|
|
if (!*Adapter)
|
|
{
|
|
Result = ERROR_OUTOFMEMORY;
|
|
goto cleanupKey;
|
|
}
|
|
|
|
/* Read the NetCfgInstanceId value and convert to GUID. */
|
|
LPWSTR ValueStr;
|
|
Result = RegistryQueryString(Key, L"NetCfgInstanceId", &ValueStr);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupAdapter;
|
|
if (FAILED(CLSIDFromString(ValueStr, &(*Adapter)->CfgInstanceID)))
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, ValueStr);
|
|
Result = ERROR_INVALID_DATA;
|
|
goto cleanupAdapter;
|
|
}
|
|
HeapFree(GetProcessHeap(), 0, ValueStr);
|
|
|
|
/* Read the NetLuidIndex value. */
|
|
Result = RegistryQueryDWORD(Key, L"NetLuidIndex", &(*Adapter)->LuidIndex);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupAdapter;
|
|
|
|
/* Read the NetLuidIndex value. */
|
|
Result = RegistryQueryDWORD(Key, L"*IfType", &(*Adapter)->IfType);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupAdapter;
|
|
|
|
DWORD Size;
|
|
if (!SetupDiGetDeviceInstanceIdW(
|
|
DevInfo, DevInfoData, (*Adapter)->DevInstanceID, _countof((*Adapter)->DevInstanceID), &Size))
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupAdapter;
|
|
}
|
|
|
|
wcscpy_s((*Adapter)->Pool, _countof((*Adapter)->Pool), Pool);
|
|
Result = ERROR_SUCCESS;
|
|
|
|
cleanupAdapter:
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(Heap, 0, *Adapter);
|
|
cleanupKey:
|
|
RegCloseKey(Key);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Returns the device-level registry key path.
|
|
*/
|
|
static void
|
|
GetDeviceRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_PATH + MAX_INSTANCE_ID) LPWSTR Path)
|
|
{
|
|
_snwprintf_s(
|
|
Path,
|
|
MAX_PATH + MAX_INSTANCE_ID,
|
|
_TRUNCATE,
|
|
L"SYSTEM\\CurrentControlSet\\Enum\\%.*s",
|
|
MAX_INSTANCE_ID,
|
|
Adapter->DevInstanceID);
|
|
}
|
|
|
|
/**
|
|
* Returns the adapter-specific TCP/IP network registry key path.
|
|
*/
|
|
static void
|
|
GetTcpipAdapterRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_PATH) LPWSTR Path)
|
|
{
|
|
WCHAR Guid[MAX_GUID_STRING_LEN];
|
|
_snwprintf_s(
|
|
Path,
|
|
MAX_PATH,
|
|
_TRUNCATE,
|
|
L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%.*s",
|
|
StringFromGUID2(&Adapter->CfgInstanceID, Guid, _countof(Guid)),
|
|
Guid);
|
|
}
|
|
|
|
/**
|
|
* Returns the interface-specific TCP/IP network registry key path.
|
|
*/
|
|
static WINSTATUS
|
|
GetTcpipInterfaceRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_PATH) LPWSTR Path)
|
|
{
|
|
DWORD Result;
|
|
HKEY TcpipAdapterRegKey;
|
|
WCHAR TcpipAdapterRegPath[MAX_PATH];
|
|
GetTcpipAdapterRegPath(Adapter, TcpipAdapterRegPath);
|
|
Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TcpipAdapterRegPath, 0, KEY_QUERY_VALUE, &TcpipAdapterRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
LPWSTR Paths;
|
|
Result = RegistryQueryString(TcpipAdapterRegKey, L"IpConfig", &Paths);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupTcpipAdapterRegKey;
|
|
if (!Paths[0])
|
|
{
|
|
Result = ERROR_NETWORK_NOT_AVAILABLE;
|
|
goto cleanupPaths;
|
|
}
|
|
_snwprintf_s(Path, MAX_PATH, _TRUNCATE, L"SYSTEM\\CurrentControlSet\\Services\\%s", Paths);
|
|
cleanupPaths:
|
|
HeapFree(GetProcessHeap(), 0, Paths);
|
|
cleanupTcpipAdapterRegKey:
|
|
RegCloseKey(TcpipAdapterRegKey);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Releases Wintun adapter resources.
|
|
*
|
|
* @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter
|
|
*/
|
|
VOID WINAPI
|
|
WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, Adapter);
|
|
}
|
|
|
|
/**
|
|
* Finds a Wintun adapter by its name.
|
|
*
|
|
* @param Pool Name of the adapter pool
|
|
*
|
|
* @param Name Adapter name
|
|
*
|
|
* @param Adapter Pointer to a handle to receive the adapter handle. Must be released with
|
|
* WintunFreeAdapter.
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise;
|
|
* ERROR_FILE_NOT_FOUND if adapter with given name is not found;
|
|
* ERROR_ALREADY_EXISTS if adapter is found but not a Wintun-class or not a member of the pool
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR Name, _Out_ WINTUN_ADAPTER **Adapter)
|
|
{
|
|
DWORD Result;
|
|
HANDLE Mutex = TakeNameMutex(Pool);
|
|
if (!Mutex)
|
|
return ERROR_GEN_FAILURE;
|
|
|
|
HDEVINFO DevInfo = SetupDiGetClassDevsExW(&CLASS_NET_GUID, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
|
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupMutex;
|
|
}
|
|
|
|
HANDLE Heap = GetProcessHeap();
|
|
for (DWORD MemberIndex = 0;; ++MemberIndex)
|
|
{
|
|
SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) };
|
|
if (!SetupDiEnumDeviceInfo(DevInfo, MemberIndex, &DevInfoData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
GUID CfgInstanceID;
|
|
Result = GetNetCfgInstanceId(DevInfo, &DevInfoData, &CfgInstanceID);
|
|
if (Result != ERROR_SUCCESS)
|
|
continue;
|
|
|
|
/* TODO: is there a better way than comparing ifnames? */
|
|
WCHAR Name2[MAX_ADAPTER_NAME], Name3[MAX_ADAPTER_NAME];
|
|
if (NciGetConnectionName(&CfgInstanceID, Name2, sizeof(Name2), NULL) != ERROR_SUCCESS)
|
|
continue;
|
|
Name2[_countof(Name2) - 1] = 0;
|
|
RemoveNumberedSuffix(Name2, Name3);
|
|
if (_wcsicmp(Name, Name2) && _wcsicmp(Name, Name3))
|
|
continue;
|
|
|
|
/* Check the Hardware ID to make sure it's a real Wintun device. This avoids doing slow operations on non-Wintun
|
|
* devices. */
|
|
LPWSTR Hwids;
|
|
Result = GetDeviceRegistryMultiString(DevInfo, &DevInfoData, SPDRP_HARDWAREID, &Hwids);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupDevInfo;
|
|
if (!IsOurHardwareID(Hwids))
|
|
{
|
|
HeapFree(Heap, 0, Hwids);
|
|
Result = ERROR_ALREADY_EXISTS;
|
|
goto cleanupDevInfo;
|
|
}
|
|
HeapFree(Heap, 0, Hwids);
|
|
|
|
BOOL IsOurDriver;
|
|
Result = IsUsingOurDriver(DevInfo, &DevInfoData, &IsOurDriver);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupDevInfo;
|
|
if (!IsOurDriver)
|
|
{
|
|
Result = ERROR_ALREADY_EXISTS;
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
BOOL IsMember;
|
|
Result = IsPoolMember(Pool, DevInfo, &DevInfoData, &IsMember);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupDevInfo;
|
|
if (!IsMember)
|
|
{
|
|
Result = ERROR_ALREADY_EXISTS;
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
Result = CreateAdapterData(DevInfo, &DevInfoData, Pool, Adapter);
|
|
goto cleanupDevInfo;
|
|
}
|
|
Result = ERROR_FILE_NOT_FOUND;
|
|
cleanupDevInfo:
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
cleanupMutex:
|
|
ReleaseNameMutex(Mutex);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the Wintun interface.
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunGetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_ADAPTER_NAME) LPWSTR Name)
|
|
{
|
|
return NciGetConnectionName(&Adapter->CfgInstanceID, Name, MAX_ADAPTER_NAME * sizeof(WCHAR), NULL);
|
|
}
|
|
|
|
static WINSTATUS
|
|
InterfaceGuidFromAlias(_In_z_ LPCWSTR Alias, _Out_ GUID *Guid)
|
|
{
|
|
NET_LUID Luid;
|
|
DWORD Result = ConvertInterfaceAliasToLuid(Alias, &Luid);
|
|
if (Result != NO_ERROR)
|
|
return Result;
|
|
return ConvertInterfaceLuidToGuid(&Luid, Guid);
|
|
}
|
|
|
|
/**
|
|
* Sets name of the Wintun interface.
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunSetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _In_z_count_c_(MAX_ADAPTER_NAME) LPCWSTR Name)
|
|
{
|
|
DWORD Result;
|
|
const int MaxSuffix = 1000;
|
|
WCHAR AvailableName[MAX_ADAPTER_NAME];
|
|
wcscpy_s(AvailableName, _countof(AvailableName), Name);
|
|
for (int i = 0;; ++i)
|
|
{
|
|
Result = NciSetConnectionName(&Adapter->CfgInstanceID, AvailableName);
|
|
if (Result == ERROR_DUP_NAME)
|
|
{
|
|
GUID Guid2;
|
|
DWORD Result2 = InterfaceGuidFromAlias(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 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_PATH + MAX_INSTANCE_ID];
|
|
GetDeviceRegPath(Adapter, DeviceRegPath);
|
|
Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, DeviceRegPath, 0, KEY_SET_VALUE, &DeviceRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
return 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;
|
|
}
|
|
|
|
/**
|
|
* Returns the GUID of the interface.
|
|
*
|
|
* @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter
|
|
*
|
|
* @param Guid Pointer to GUID to receive adapter ID.
|
|
*/
|
|
void WINAPI
|
|
WintunGetAdapterGUID(_In_ const WINTUN_ADAPTER *Adapter, _Out_ GUID *Guid)
|
|
{
|
|
memcpy_s(Guid, sizeof(*Guid), &Adapter->CfgInstanceID, sizeof(Adapter->CfgInstanceID));
|
|
}
|
|
|
|
/**
|
|
* Returns the LUID of the interface.
|
|
*
|
|
* @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter
|
|
*
|
|
* @param Luid Pointer to LUID to receive adapter LUID.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Returns a handle to the adapter device object.
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunGetAdapterDeviceObject(_In_ const WINTUN_ADAPTER *Adapter, _Out_ HANDLE *Handle)
|
|
{
|
|
HANDLE Heap = GetProcessHeap();
|
|
ULONG InterfacesLen;
|
|
DWORD Result = CM_Get_Device_Interface_List_SizeW(
|
|
&InterfacesLen,
|
|
(GUID *)&ADAPTER_NET_GUID,
|
|
(DEVINSTID_W)Adapter->DevInstanceID,
|
|
CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
|
|
if (Result != CR_SUCCESS)
|
|
return Result;
|
|
LPWSTR Interfaces = HeapAlloc(Heap, 0, InterfacesLen * sizeof(WCHAR));
|
|
if (!Interfaces)
|
|
return ERROR_OUTOFMEMORY;
|
|
Result = CM_Get_Device_Interface_ListW(
|
|
(GUID *)&ADAPTER_NET_GUID,
|
|
(DEVINSTID_W)Adapter->DevInstanceID,
|
|
Interfaces,
|
|
InterfacesLen,
|
|
CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
|
|
if (Result != CR_SUCCESS)
|
|
goto cleanupBuf;
|
|
*Handle = CreateFileW(
|
|
Interfaces,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
if (*Handle == INVALID_HANDLE_VALUE)
|
|
Result = GetLastError();
|
|
cleanupBuf:
|
|
HeapFree(Heap, 0, Interfaces);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Sets device install parameters for a quiet installation.
|
|
*/
|
|
static WINSTATUS
|
|
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 GetLastError();
|
|
DevInstallParams.Flags |= DI_QUIETINSTALL;
|
|
if (!SetupDiSetDeviceInstallParams(DevInfo, DevInfoData, &DevInstallParams))
|
|
return GetLastError();
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @return TRUE if DriverData date and version is newer than supplied parameters.
|
|
*/
|
|
static BOOL
|
|
IsNewer(_In_ const SP_DRVINFO_DATA_W *DriverData, _In_ const FILETIME *DriverDate, _In_ DWORDLONG DriverVersion)
|
|
{
|
|
if (DriverData->DriverDate.dwHighDateTime > DriverDate->dwHighDateTime)
|
|
return TRUE;
|
|
if (DriverData->DriverDate.dwHighDateTime < DriverDate->dwHighDateTime)
|
|
return FALSE;
|
|
|
|
if (DriverData->DriverDate.dwLowDateTime > DriverDate->dwLowDateTime)
|
|
return TRUE;
|
|
if (DriverData->DriverDate.dwLowDateTime < DriverDate->dwLowDateTime)
|
|
return FALSE;
|
|
|
|
if (DriverData->DriverVersion > DriverVersion)
|
|
return TRUE;
|
|
if (DriverData->DriverVersion < DriverVersion)
|
|
return FALSE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Checks device install parameters if a system reboot is required.
|
|
*/
|
|
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))
|
|
return FALSE;
|
|
|
|
return (DevInstallParams.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0;
|
|
}
|
|
|
|
/**
|
|
* Creates a Wintun interface.
|
|
*
|
|
* @param Pool Name of the adapter pool
|
|
*
|
|
* @param Name The requested name of the interface
|
|
*
|
|
* @param RequestedGUID The GUID of the created network interface, which then influences NLA generation
|
|
* deterministically. If it is set to NULL, the GUID is chosen by the system at random, and hence
|
|
* a new NLA entry is created for each new interface. It is called "requested" GUID because the API
|
|
* it uses is completely undocumented, and so there could be minor interesting complications with
|
|
* its usage.
|
|
*
|
|
* @param Adapter Pointer to a handle to receive the adapter handle. Must be released with
|
|
* WintunFreeAdapter.
|
|
*
|
|
* @param RebootRequired Pointer to a boolean flag to be set to TRUE in case SetupAPI suggests a reboot. Must be
|
|
* initialised to FALSE manually before this function is called.
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunCreateAdapter(
|
|
_In_z_count_c_(MAX_POOL) LPCWSTR Pool,
|
|
_In_z_ LPCWSTR Name,
|
|
_In_opt_ const GUID *RequestedGUID,
|
|
_Out_ WINTUN_ADAPTER **Adapter,
|
|
_Inout_ BOOL *RebootRequired)
|
|
{
|
|
DWORD Result;
|
|
HANDLE Mutex = TakeNameMutex(Pool);
|
|
if (!Mutex)
|
|
return ERROR_GEN_FAILURE;
|
|
|
|
HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&CLASS_NET_GUID, NULL, NULL, NULL);
|
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupMutex;
|
|
}
|
|
|
|
WCHAR ClassName[MAX_CLASS_NAME_LEN];
|
|
if (!SetupDiClassNameFromGuidExW(&CLASS_NET_GUID, ClassName, _countof(ClassName), NULL, NULL, NULL))
|
|
{
|
|
Result = GetLastError();
|
|
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, &CLASS_NET_GUID, PoolDeviceTypeName, NULL, DICD_GENERATE_ID, &DevInfoData))
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupDevInfo;
|
|
}
|
|
Result = SetQuietInstall(DevInfo, &DevInfoData);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupDevInfo;
|
|
|
|
if (!SetupDiSetSelectedDevice(DevInfo, &DevInfoData))
|
|
{
|
|
Result = GetLastError();
|
|
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 = GetLastError();
|
|
goto cleanupDevInfo;
|
|
}
|
|
if (!SetupDiBuildDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER)) /* TODO: This takes ~510ms */
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupDevInfo;
|
|
}
|
|
|
|
FILETIME DriverDate = { 0, 0 };
|
|
DWORDLONG DriverVersion = 0;
|
|
for (DWORD DriverIndex = 0;; ++DriverIndex) /* TODO: This loop takes ~600ms */
|
|
{
|
|
SP_DRVINFO_DATA_W DriverData = { .cbSize = sizeof(SP_DRVINFO_DATA_W) };
|
|
if (!SetupDiEnumDriverInfoW(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER, DriverIndex, &DriverData))
|
|
{
|
|
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(&DriverData, &DriverDate, DriverVersion))
|
|
continue;
|
|
|
|
SP_DRVINFO_DETAIL_DATA_W *DriverDetailData;
|
|
if (GetDriverInfoDetail(DevInfo, &DevInfoData, &DriverData, &DriverDetailData) != ERROR_SUCCESS)
|
|
continue;
|
|
if ((DriverDetailData->CompatIDsOffset <= 1 || _wcsicmp(DriverDetailData->HardwareID, WINTUN_HWID)) &&
|
|
(!DriverDetailData->CompatIDsLength ||
|
|
!IsOurHardwareID(DriverDetailData->HardwareID + DriverDetailData->CompatIDsOffset)))
|
|
{
|
|
HeapFree(Heap, 0, DriverDetailData);
|
|
continue;
|
|
}
|
|
HeapFree(Heap, 0, DriverDetailData);
|
|
|
|
if (!SetupDiSetSelectedDriverW(DevInfo, &DevInfoData, &DriverData))
|
|
continue;
|
|
|
|
DriverDate = DriverData.DriverDate;
|
|
DriverVersion = DriverData.DriverVersion;
|
|
}
|
|
|
|
if (!DriverVersion)
|
|
{
|
|
Result = ERROR_FILE_NOT_FOUND;
|
|
goto cleanupDriverInfoList;
|
|
}
|
|
|
|
if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, DevInfo, &DevInfoData))
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupDevice;
|
|
}
|
|
SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS, DevInfo, &DevInfoData); /* Ignore errors */
|
|
|
|
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 = GetLastError();
|
|
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)
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
|
|
SetupDiCallClassInstaller(DIF_INSTALLINTERFACES, DevInfo, &DevInfoData); /* Ignore errors */
|
|
|
|
if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICE, DevInfo, &DevInfoData))
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupNetDevRegKey;
|
|
}
|
|
*RebootRequired = *RebootRequired || CheckReboot(DevInfo, &DevInfoData);
|
|
|
|
if (!SetupDiSetDeviceRegistryPropertyW(
|
|
DevInfo,
|
|
&DevInfoData,
|
|
SPDRP_DEVICEDESC,
|
|
(const BYTE *)PoolDeviceTypeName,
|
|
(DWORD)(wcslen(PoolDeviceTypeName) * sizeof(WCHAR))))
|
|
{
|
|
Result = GetLastError();
|
|
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. */
|
|
LPWSTR DummyStr;
|
|
Result = RegistryQueryStringWait(NetDevRegKey, L"NetCfgInstanceId", WAIT_FOR_REGISTRY_TIMEOUT, &DummyStr);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupNetDevRegKey;
|
|
HeapFree(Heap, 0, DummyStr);
|
|
DWORD DummyDWORD;
|
|
Result = RegistryQueryDWORDWait(NetDevRegKey, L"NetLuidIndex", WAIT_FOR_REGISTRY_TIMEOUT, &DummyDWORD);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupNetDevRegKey;
|
|
Result = RegistryQueryDWORDWait(NetDevRegKey, L"*IfType", WAIT_FOR_REGISTRY_TIMEOUT, &DummyDWORD);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupNetDevRegKey;
|
|
|
|
Result = CreateAdapterData(DevInfo, &DevInfoData, Pool, Adapter);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupNetDevRegKey;
|
|
|
|
HKEY TcpipAdapterRegKey;
|
|
WCHAR TcpipAdapterRegPath[MAX_PATH];
|
|
GetTcpipAdapterRegPath(*Adapter, TcpipAdapterRegPath);
|
|
Result = RegistryOpenKeyWait(
|
|
HKEY_LOCAL_MACHINE,
|
|
TcpipAdapterRegPath,
|
|
KEY_QUERY_VALUE | KEY_NOTIFY,
|
|
WAIT_FOR_REGISTRY_TIMEOUT,
|
|
&TcpipAdapterRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupAdapter;
|
|
Result = RegistryQueryStringWait(TcpipAdapterRegKey, L"IpConfig", WAIT_FOR_REGISTRY_TIMEOUT, &DummyStr);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupTcpipAdapterRegKey;
|
|
HeapFree(Heap, 0, DummyStr);
|
|
|
|
HKEY TcpipInterfaceRegKey;
|
|
WCHAR TcpipInterfaceRegPath[MAX_PATH];
|
|
Result = GetTcpipInterfaceRegPath(*Adapter, TcpipInterfaceRegPath);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupTcpipAdapterRegKey;
|
|
Result = RegistryOpenKeyWait(
|
|
HKEY_LOCAL_MACHINE,
|
|
TcpipInterfaceRegPath,
|
|
KEY_QUERY_VALUE | KEY_SET_VALUE,
|
|
WAIT_FOR_REGISTRY_TIMEOUT,
|
|
&TcpipInterfaceRegKey);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto cleanupTcpipAdapterRegKey;
|
|
|
|
const static DWORD EnableDeadGWDetect = 0;
|
|
RegSetKeyValueW(
|
|
TcpipInterfaceRegKey,
|
|
NULL,
|
|
L"EnableDeadGWDetect",
|
|
REG_DWORD,
|
|
&EnableDeadGWDetect,
|
|
sizeof(EnableDeadGWDetect)); /* Ignore errors */
|
|
|
|
Result = WintunSetAdapterName(*Adapter, Name);
|
|
RegCloseKey(TcpipInterfaceRegKey);
|
|
cleanupTcpipAdapterRegKey:
|
|
RegCloseKey(TcpipAdapterRegKey);
|
|
cleanupAdapter:
|
|
if (Result != ERROR_SUCCESS)
|
|
HeapFree(Heap, 0, *Adapter);
|
|
cleanupNetDevRegKey:
|
|
RegCloseKey(NetDevRegKey);
|
|
cleanupDevice:
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
/* The interface failed to install, or the interface 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:
|
|
ReleaseNameMutex(Mutex);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Returns TUN device info list handle and interface device info data. The device info list handle must be closed after
|
|
* use. In case the device is not found, ERROR_OBJECT_NOT_FOUND is returned.
|
|
*/
|
|
static WINSTATUS
|
|
GetDevInfoData(_In_ const GUID *CfgInstanceID, _Out_ HDEVINFO *DevInfo, _Out_ SP_DEVINFO_DATA *DevInfoData)
|
|
{
|
|
DWORD Result;
|
|
*DevInfo = SetupDiGetClassDevsExW(&CLASS_NET_GUID, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
|
|
if (!*DevInfo)
|
|
return GetLastError();
|
|
for (DWORD MemberIndex = 0;; ++MemberIndex)
|
|
{
|
|
DevInfoData->cbSize = sizeof(SP_DEVINFO_DATA);
|
|
if (!SetupDiEnumDeviceInfo(*DevInfo, MemberIndex, DevInfoData))
|
|
{
|
|
Result = GetLastError();
|
|
if (Result == ERROR_NO_MORE_ITEMS)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
GUID CfgInstanceID2;
|
|
Result = GetNetCfgInstanceId(*DevInfo, DevInfoData, &CfgInstanceID2);
|
|
if (Result != ERROR_SUCCESS || memcmp(CfgInstanceID, &CfgInstanceID2, sizeof(GUID)) != 0)
|
|
continue;
|
|
|
|
Result = SetQuietInstall(*DevInfo, DevInfoData);
|
|
if (Result != ERROR_SUCCESS)
|
|
{
|
|
SetupDiDestroyDeviceInfoList(*DevInfo);
|
|
return Result;
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
SetupDiDestroyDeviceInfoList(*DevInfo);
|
|
return ERROR_OBJECT_NOT_FOUND;
|
|
}
|
|
|
|
/**
|
|
* Deletes a Wintun interface.
|
|
*
|
|
* @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter
|
|
*
|
|
* @param RebootRequired Pointer to a boolean flag to be set to TRUE in case SetupAPI suggests a reboot. Must be
|
|
* initialised to FALSE manually before this function is called.
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise. This function succeeds if the interface was not found.
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunDeleteAdapter(_In_ const WINTUN_ADAPTER *Adapter, _Inout_ BOOL *RebootRequired)
|
|
{
|
|
HDEVINFO DevInfo;
|
|
SP_DEVINFO_DATA DevInfoData;
|
|
DWORD Result = GetDevInfoData(&Adapter->CfgInstanceID, &DevInfo, &DevInfoData);
|
|
if (Result == ERROR_OBJECT_NOT_FOUND)
|
|
return ERROR_SUCCESS;
|
|
if (Result != ERROR_SUCCESS)
|
|
return Result;
|
|
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 = GetLastError();
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Enumerates all Wintun adapters.
|
|
*
|
|
* @param Pool Name of the adapter pool
|
|
*
|
|
* @param Func Callback function. To continue enumeration, the callback function must return TRUE; to stop
|
|
* enumeration, it must return FALSE.
|
|
*
|
|
* @param Param An application-defined value to be passed to the callback function
|
|
*
|
|
* @return ERROR_SUCCESS on success; Win32 error code otherwise
|
|
*/
|
|
WINSTATUS WINAPI
|
|
WintunEnumAdapters(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_ WINTUN_ENUMPROC Func, _In_ LPARAM Param)
|
|
{
|
|
DWORD Result;
|
|
HANDLE Mutex = TakeNameMutex(Pool);
|
|
if (!Mutex)
|
|
return ERROR_GEN_FAILURE;
|
|
HDEVINFO DevInfo = SetupDiGetClassDevsExW(&CLASS_NET_GUID, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
|
|
if (DevInfo == INVALID_HANDLE_VALUE)
|
|
{
|
|
Result = GetLastError();
|
|
goto cleanupMutex;
|
|
}
|
|
HANDLE Heap = GetProcessHeap();
|
|
for (DWORD MemberIndex = 0;; ++MemberIndex)
|
|
{
|
|
SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) };
|
|
if (!SetupDiEnumDeviceInfo(DevInfo, MemberIndex, &DevInfoData))
|
|
{
|
|
if (GetLastError() == ERROR_NO_MORE_ITEMS)
|
|
{
|
|
Result = ERROR_SUCCESS;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Check the Hardware ID to make sure it's a real Wintun device. This avoids doing slow operations on non-Wintun
|
|
* devices. */
|
|
LPWSTR Hwids;
|
|
Result = GetDeviceRegistryMultiString(DevInfo, &DevInfoData, SPDRP_HARDWAREID, &Hwids);
|
|
if (Result != ERROR_SUCCESS)
|
|
break;
|
|
if (!IsOurHardwareID(Hwids))
|
|
{
|
|
HeapFree(Heap, 0, Hwids);
|
|
continue;
|
|
}
|
|
HeapFree(Heap, 0, Hwids);
|
|
|
|
BOOL IsOurDriver;
|
|
Result = IsUsingOurDriver(DevInfo, &DevInfoData, &IsOurDriver);
|
|
if (Result != ERROR_SUCCESS)
|
|
break;
|
|
if (!IsOurDriver)
|
|
continue;
|
|
|
|
BOOL IsMember;
|
|
Result = IsPoolMember(Pool, DevInfo, &DevInfoData, &IsMember);
|
|
if (Result != ERROR_SUCCESS)
|
|
break;
|
|
if (!IsMember)
|
|
continue;
|
|
|
|
SetQuietInstall(DevInfo, &DevInfoData); /* Ignore errors */
|
|
|
|
WINTUN_ADAPTER *Adapter;
|
|
Result = CreateAdapterData(DevInfo, &DevInfoData, Pool, &Adapter);
|
|
if (Result != ERROR_SUCCESS)
|
|
break;
|
|
if (Func(Adapter, Param))
|
|
HeapFree(Heap, 0, Adapter);
|
|
else
|
|
{
|
|
HeapFree(Heap, 0, Adapter);
|
|
break;
|
|
}
|
|
}
|
|
SetupDiDestroyDeviceInfoList(DevInfo);
|
|
cleanupMutex:
|
|
ReleaseNameMutex(Mutex);
|
|
return Result;
|
|
}
|