diff --git a/api/api.h b/api/api.h index cda22b3..6b05de2 100644 --- a/api/api.h +++ b/api/api.h @@ -36,3 +36,21 @@ NciInit(); void NciCleanup(); + +#define MAX_POOL 256 +#define MAX_INSTANCE_ID MAX_PATH /* TODO: Is MAX_PATH always enough? */ + +typedef struct _WINTUN_ADAPTER +{ + GUID CfgInstanceID; + WCHAR DevInstanceID[MAX_INSTANCE_ID]; + DWORD LuidIndex; + DWORD IfType; + WCHAR Pool[MAX_POOL]; +} WINTUN_ADAPTER; + +VOID WINAPI +WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter); + +_Return_type_success_(return == 0) DWORD WINAPI + WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR IfName, _Out_ WINTUN_ADAPTER **Adapter); diff --git a/api/api.vcxproj b/api/api.vcxproj index a5bcd11..a940f35 100644 --- a/api/api.vcxproj +++ b/api/api.vcxproj @@ -120,7 +120,7 @@ ..\$(WintunPlatform)\$(Configuration);%(AdditionalIncludeDirectories) - Bcrypt.lib;%(AdditionalDependencies) + Bcrypt.lib;Setupapi.lib;%(AdditionalDependencies) exports.def Windows @@ -159,6 +159,7 @@ + diff --git a/api/api.vcxproj.filters b/api/api.vcxproj.filters index e7cba38..606c001 100644 --- a/api/api.vcxproj.filters +++ b/api/api.vcxproj.filters @@ -36,6 +36,9 @@ Source Files + + Source Files + Source Files diff --git a/api/devmgmt.c b/api/devmgmt.c new file mode 100644 index 0000000..eefdb97 --- /dev/null +++ b/api/devmgmt.c @@ -0,0 +1,750 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved. + */ + +#include "api.h" +#include +#include +#include + +#define WINTUN_HWID L"Wintun" +#define WAIT_FOR_REGISTRY_TIMEOUT 10000 /* ms */ + +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 } }; + +/** + * Validate and/or sanitize string value read from registry. + * + * @param Buf On input, it contains pointer to pointer where the data is stored. The data must be + * allocated using HeapAlloc(GetProcessHeap(), 0). + * On output, it contains pointer to pointer where the sanitized data is stored. It must be + * released with HeapFree(GetProcessHeap(), 0, *Buf) after use. + * + * @param Len Length of data string in wide characters + * + * @param ValueType Type of data. Must be either REG_SZ or REG_EXPAND_SZ. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +static _Return_type_success_(return == 0) DWORD GetRegString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType) +{ + HANDLE Heap = GetProcessHeap(); + + if (wcsnlen(*Buf, Len) >= Len) + { + /* String is missing zero-terminator. */ + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + } + + if (ValueType != REG_EXPAND_SZ) + return ERROR_SUCCESS; + + /* ExpandEnvironmentStringsW() returns strlen on success or 0 on error. Bail out on empty input strings to + * disambiguate. */ + if (!(*Buf)[0]) + return ERROR_SUCCESS; + + Len = Len * 2 + 64; + for (;;) + { + LPWSTR Expanded = HeapAlloc(Heap, 0, Len * sizeof(WCHAR)); + if (!Expanded) + return ERROR_OUTOFMEMORY; + DWORD Result = ExpandEnvironmentStringsW(*Buf, Expanded, Len); + if (!Result) + { + Result = GetLastError(); + HeapFree(Heap, 0, Expanded); + return Result; + } + if (Result > Len) + { + HeapFree(Heap, 0, Expanded); + Len = Result; + continue; + } + HeapFree(Heap, 0, *Buf); + *Buf = Expanded; + return ERROR_SUCCESS; + } +} + +/** + * Validate and/or sanitize multi-string value read from registry. + * + * @param Buf On input, it contains pointer to pointer where the data is stored. The data must be + * allocated using HeapAlloc(GetProcessHeap(), 0). + * On output, it contains pointer to pointer where the sanitized data is stored. It must be + * released with HeapFree(GetProcessHeap(), 0, *Buf) after use. + * + * @param Len Length of data string in wide characters + * + * @param ValueType Type of data. Must be one of REG_MULTI_SZ, REG_SZ or REG_EXPAND_SZ. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +static _Return_type_success_(return == 0) DWORD + GetRegMultiString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType) +{ + HANDLE Heap = GetProcessHeap(); + + if (ValueType == REG_MULTI_SZ) + { + for (size_t i = 0;; i += wcsnlen(*Buf + i, Len - i) + 1) + { + if (i > Len) + { + /* Missing string and list terminators. */ + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 2) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + BufZ[Len + 1] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + return ERROR_SUCCESS; + } + if (i == Len) + { + /* Missing list terminator. */ + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + return ERROR_SUCCESS; + } + if (!(*Buf)[i]) + return ERROR_SUCCESS; + } + } + + /* Sanitize REG_SZ/REG_EXPAND_SZ and append a list terminator to make a multi-string. */ + DWORD Result = GetRegString(Buf, Len, ValueType); + if (Result != ERROR_SUCCESS) + return Result; + Len = (DWORD)wcslen(*Buf) + 1; + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + return ERROR_SUCCESS; +} + +/** + * Reads string value from registry key. + * + * @param Key Handle of the registry key to read from. Must be opened with read + * access. + * + * @param Name Name of the value to read + * + * @param Value Pointer to string to retrieve registry value. If the value type is + * REG_EXPAND_SZ the value is expanded using ExpandEnvironmentStrings(). + * The string must be released with HeapFree(GetProcessHeap(), 0, Value) + * after use. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +static _Return_type_success_(return == 0) DWORD + RegQueryString(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ LPWSTR *Value) +{ + HANDLE Heap = GetProcessHeap(); + DWORD Size = 256; + for (;;) + { + *Value = HeapAlloc(Heap, 0, Size); + if (!*Value) + return ERROR_OUTOFMEMORY; + DWORD ValueType; + DWORD Result = RegQueryValueExW(Key, Name, NULL, &ValueType, (BYTE *)*Value, &Size); + if (Result == ERROR_MORE_DATA) + { + HeapFree(Heap, 0, *Value); + continue; + } + if (Result != ERROR_SUCCESS) + { + HeapFree(Heap, 0, *Value); + return Result; + } + + switch (ValueType) + { + case REG_SZ: + case REG_EXPAND_SZ: + Result = GetRegString(Value, Size / sizeof(WCHAR), ValueType); + if (Result != ERROR_SUCCESS) + HeapFree(Heap, 0, *Value); + return Result; + default: + HeapFree(Heap, 0, *Value); + return ERROR_INVALID_DATATYPE; + } + } +} + +/** + * Reads a 32-bit DWORD value from registry key. + * + * @param Key Handle of the registry key to read from. Must be opened with read + * access. + * + * @param Name Name of the value to read + * + * @param Value Pointer to DWORD to retrieve registry value + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +static _Return_type_success_(return == 0) DWORD + RegQueryDWORD(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ DWORD *Value) +{ + DWORD ValueType, Size = sizeof(DWORD); + DWORD Result = RegQueryValueExW(Key, Name, NULL, &ValueType, (BYTE *)Value, &Size); + if (Result != ERROR_SUCCESS) + return Result; + if (ValueType != REG_DWORD) + return ERROR_INVALID_DATATYPE; + if (Size != sizeof(DWORD)) + return ERROR_INVALID_DATA; + return ERROR_SUCCESS; +} + +/** + * 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 _Return_type_success_(return == 0) DWORD 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 _Return_type_success_(return == 0) DWORD 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: + Result = GetRegString(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 _Return_type_success_(return == 0) DWORD 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 = GetRegMultiString(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 IfName, _Out_ LPWSTR Removed) +{ + size_t Len = wcslen(IfName); + if (Len && IfName[Len - 1] < L'0' || IfName[Len - 1] > L'9') + { + wmemcpy(Removed, IfName, Len + 1); + return; + } + for (size_t i = Len; i--;) + { + if (IfName[i] >= L'0' && IfName[i] <= L'9') + continue; + if (IfName[i] == L' ') + { + wmemcpy(Removed, IfName, i); + Removed[i] = 0; + return; + } + break; + } + wmemcpy(Removed, IfName, 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 _Return_type_success_(return == 0) DWORD + GetPoolDeviceTypeName(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _Out_ LPWSTR *Name) +{ + HANDLE Heap = GetProcessHeap(); + int Len = 256; + for (;;) + { + *Name = HeapAlloc(Heap, 0, Len * sizeof(WCHAR)); + if (!*Name) + return ERROR_OUTOFMEMORY; + if (_snwprintf_s(*Name, Len, _TRUNCATE, L"%s Tunnel", Pool) < 0) + { + HeapFree(Heap, 0, *Name); + Len *= 2; + continue; + } + return ERROR_SUCCESS; + } +} + +/** + * Checks if SPDRP_DEVICEDESC or SPDRP_FRIENDLYNAME match device type name. + */ +static _Return_type_success_(return == 0) DWORD 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, PoolDeviceTypeName; + 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; + Result = GetPoolDeviceTypeName(Pool, &PoolDeviceTypeName); + if (Result != ERROR_SUCCESS) + goto cleanupFriendlyName; + if (!_wcsicmp(FriendlyName, PoolDeviceTypeName) || !_wcsicmp(DeviceDesc, PoolDeviceTypeName)) + { + *IsMember = TRUE; + goto cleanupPoolDeviceTypeName; + } + RemoveNumberedSuffix(FriendlyName, FriendlyName); + RemoveNumberedSuffix(DeviceDesc, DeviceDesc); + if (!_wcsicmp(FriendlyName, PoolDeviceTypeName) || !_wcsicmp(DeviceDesc, PoolDeviceTypeName)) + { + *IsMember = TRUE; + goto cleanupPoolDeviceTypeName; + } + *IsMember = FALSE; +cleanupPoolDeviceTypeName: + HeapFree(Heap, 0, PoolDeviceTypeName); +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 _Return_type_success_(return == 0) DWORD 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 = 2048; + 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 _Return_type_success_(return == 0) DWORD + 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 A pointer to a Wintun adapter descriptor + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +static _Return_type_success_(return == 0) DWORD InitAdapterData( + _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\\ registry key. */ + HKEY Key = SetupDiOpenDevRegKey(DevInfo, DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE); + if (Key == INVALID_HANDLE_VALUE) + return GetLastError(); + + /* Read the NetCfgInstanceId value and convert to GUID. */ + LPWSTR ValueStr; + Result = RegQueryString(Key, L"NetCfgInstanceId", &ValueStr); + if (Result != ERROR_SUCCESS) + goto cleanupKey; + if (FAILED(CLSIDFromString(ValueStr, &Adapter->CfgInstanceID))) + { + HeapFree(GetProcessHeap(), 0, ValueStr); + Result = ERROR_INVALID_DATA; + goto cleanupKey; + } + HeapFree(GetProcessHeap(), 0, ValueStr); + + /* Read the NetLuidIndex value. */ + Result = RegQueryDWORD(Key, L"NetLuidIndex", &Adapter->LuidIndex); + if (Result != ERROR_SUCCESS) + goto cleanupKey; + + /* Read the NetLuidIndex value. */ + Result = RegQueryDWORD(Key, L"*IfType", &Adapter->IfType); + if (Result != ERROR_SUCCESS) + goto cleanupKey; + + DWORD Size; + if (!SetupDiGetDeviceInstanceIdW( + DevInfo, DevInfoData, Adapter->DevInstanceID, _countof(Adapter->DevInstanceID), &Size)) + { + Result = GetLastError(); + goto cleanupKey; + } + + wcscpy_s(Adapter->Pool, _countof(Adapter->Pool), Pool); + Result = ERROR_SUCCESS; + +cleanupKey: + RegCloseKey(Key); + 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 IfName 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 + */ +_Return_type_success_(return == 0) DWORD WINAPI + WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR IfName, _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; + HKEY Key = SetupDiOpenDevRegKey(DevInfo, &DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE); + if (Key != INVALID_HANDLE_VALUE) + { + LPWSTR CfgInstanceIDStr; + Result = RegQueryString(Key, L"NetCfgInstanceId", &CfgInstanceIDStr); + if (Result == ERROR_SUCCESS) + { + Result = + SUCCEEDED(CLSIDFromString(CfgInstanceIDStr, &CfgInstanceID)) ? ERROR_SUCCESS : ERROR_INVALID_DATA; + HeapFree(Heap, 0, CfgInstanceIDStr); + } + RegCloseKey(Key); + } + else + Result = GetLastError(); + if (Result != ERROR_SUCCESS) + continue; + + /* TODO: is there a better way than comparing ifnames? */ + WCHAR IfName2[0x400], IfName3[0x400]; /* TODO: Make dynamic. */ + if (NciGetConnectionName(&CfgInstanceID, IfName2, sizeof(IfName2), NULL) != ERROR_SUCCESS) + continue; + IfName2[_countof(IfName2) - 1] = 0; + RemoveNumberedSuffix(IfName2, IfName3); + if (_wcsicmp(IfName, IfName2) && _wcsicmp(IfName, IfName3)) + 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; + } + + *Adapter = HeapAlloc(Heap, 0, sizeof(WINTUN_ADAPTER)); + if (!*Adapter) + { + Result = ERROR_OUTOFMEMORY; + goto cleanupDevInfo; + } + Result = InitAdapterData(DevInfo, &DevInfoData, Pool, *Adapter); + if (Result != ERROR_SUCCESS) + HeapFree(Heap, 0, *Adapter); + goto cleanupDevInfo; + } + Result = ERROR_FILE_NOT_FOUND; +cleanupDevInfo: + SetupDiDestroyDeviceInfoList(DevInfo); +cleanupMutex: + ReleaseNameMutex(Mutex); + return Result; +} diff --git a/api/exports.def b/api/exports.def index e69de29..83903fc 100644 --- a/api/exports.def +++ b/api/exports.def @@ -0,0 +1,3 @@ +EXPORTS + WintunFreeAdapter + WintunGetAdapter