summaryrefslogtreecommitdiff
path: root/src/tapctl/tap.c
diff options
context:
space:
mode:
authorBernhard Schmidt <berni@debian.org>2020-09-01 16:52:17 +0200
committerBernhard Schmidt <berni@debian.org>2020-09-01 16:52:17 +0200
commit9fc3b98112217f2d92a67977dbde0987cc7a1803 (patch)
tree29fcc8654ee65d9dd89ade797bea2f3d9dfd9cfd /src/tapctl/tap.c
parenta8758c0e03eed188dcb9da0e4fd781a67c25bf1e (diff)
parent69b02b1f7fd609d84ace13ab04697158de2418a9 (diff)
Merge branch 'debian/experimental-2.5'
Diffstat (limited to 'src/tapctl/tap.c')
-rw-r--r--src/tapctl/tap.c1441
1 files changed, 1441 insertions, 0 deletions
diff --git a/src/tapctl/tap.c b/src/tapctl/tap.c
new file mode 100644
index 0000000..7cb3ded
--- /dev/null
+++ b/src/tapctl/tap.c
@@ -0,0 +1,1441 @@
+/*
+ * tapctl -- Utility to manipulate TUN/TAP adapters on Windows
+ * https://community.openvpn.net/openvpn/wiki/Tapctl
+ *
+ * Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#elif defined(_MSC_VER)
+#include <config-msvc.h>
+#endif
+
+#include "tap.h"
+#include "error.h"
+
+#include <windows.h>
+#include <cfgmgr32.h>
+#include <objbase.h>
+#include <setupapi.h>
+#include <stdio.h>
+#include <tchar.h>
+
+#ifdef _MSC_VER
+#pragma comment(lib, "advapi32.lib")
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "setupapi.lib")
+#endif
+
+const static GUID GUID_DEVCLASS_NET = { 0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };
+
+const static TCHAR szAdapterRegKeyPathTemplate[] = TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\%") TEXT(PRIsLPOLESTR) TEXT("\\%") TEXT(PRIsLPOLESTR) TEXT("\\Connection");
+#define ADAPTER_REGKEY_PATH_MAX (_countof(TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\")) - 1 + 38 + _countof(TEXT("\\")) - 1 + 38 + _countof(TEXT("\\Connection")))
+
+
+/**
+ * Returns length of string of strings
+ *
+ * @param szz Pointer to a string of strings (terminated by an empty string)
+ *
+ * @return Number of characters not counting the final zero terminator
+ **/
+static inline size_t
+_tcszlen(_In_z_ LPCTSTR szz)
+{
+ LPCTSTR s;
+ for (s = szz; s[0]; s += _tcslen(s) + 1)
+ {
+ }
+ return s - szz;
+}
+
+
+/**
+ * Checks if string is contained in the string of strings. Comparison is made case-insensitive.
+ *
+ * @param szzHay Pointer to a string of strings (terminated by an empty string) we are
+ * looking in
+ *
+ * @param szNeedle The string we are searching for
+ *
+ * @return Pointer to the string in szzHay that matches szNeedle is found; NULL otherwise
+ */
+static LPCTSTR
+_tcszistr(_In_z_ LPCTSTR szzHay, _In_z_ LPCTSTR szNeedle)
+{
+ for (LPCTSTR s = szzHay; s[0]; s += _tcslen(s) + 1)
+ {
+ if (_tcsicmp(s, szNeedle) == 0)
+ {
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Function that performs a specific task on a device
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired A pointer to a BOOL flag. If the device requires a system restart,
+ * this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ * allows the flag to be globally initialized to FALSE and reused for multiple
+ * adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+typedef DWORD (*devop_func_t)(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _Inout_ LPBOOL pbRebootRequired);
+
+
+/**
+ * Checks device install parameters if a system reboot is required.
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired A pointer to a BOOL flag. If the device requires a system restart,
+ * this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ * allows the flag to be globally initialized to FALSE and reused for multiple
+ * adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+check_reboot(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ if (pbRebootRequired == NULL)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ SP_DEVINSTALL_PARAMS devinstall_params = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS) };
+ if (!SetupDiGetDeviceInstallParams(
+ hDeviceInfoSet,
+ pDeviceInfoData,
+ &devinstall_params))
+ {
+ DWORD dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceInstallParams failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ if ((devinstall_params.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0)
+ {
+ *pbRebootRequired = TRUE;
+ }
+
+ return ERROR_SUCCESS;
+}
+
+
+/**
+ * Deletes the device.
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired A pointer to a BOOL flag. If the device requires a system restart,
+ * this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ * allows the flag to be globally initialized to FALSE and reused for multiple
+ * adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+delete_device(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ SP_REMOVEDEVICE_PARAMS params =
+ {
+ .ClassInstallHeader =
+ {
+ .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
+ .InstallFunction = DIF_REMOVE,
+ },
+ .Scope = DI_REMOVEDEVICE_GLOBAL,
+ .HwProfile = 0,
+ };
+
+ /* Set class installer parameters for DIF_REMOVE. */
+ if (!SetupDiSetClassInstallParams(
+ hDeviceInfoSet,
+ pDeviceInfoData,
+ &params.ClassInstallHeader,
+ sizeof(SP_REMOVEDEVICE_PARAMS)))
+ {
+ DWORD dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ /* Call appropriate class installer. */
+ if (!SetupDiCallClassInstaller(
+ DIF_REMOVE,
+ hDeviceInfoSet,
+ pDeviceInfoData))
+ {
+ DWORD dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ /* Check if a system reboot is required. */
+ check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
+ return ERROR_SUCCESS;
+}
+
+
+/**
+ * Changes the device state.
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param bEnable TRUE to enable the device; FALSE to disable.
+ *
+ * @param pbRebootRequired A pointer to a BOOL flag. If the device requires a system restart,
+ * this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ * allows the flag to be globally initialized to FALSE and reused for multiple
+ * adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+change_device_state(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _In_ BOOL bEnable,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ SP_PROPCHANGE_PARAMS params =
+ {
+ .ClassInstallHeader =
+ {
+ .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
+ .InstallFunction = DIF_PROPERTYCHANGE,
+ },
+ .StateChange = bEnable ? DICS_ENABLE : DICS_DISABLE,
+ .Scope = DICS_FLAG_GLOBAL,
+ .HwProfile = 0,
+ };
+
+ /* Set class installer parameters for DIF_PROPERTYCHANGE. */
+ if (!SetupDiSetClassInstallParams(
+ hDeviceInfoSet,
+ pDeviceInfoData,
+ &params.ClassInstallHeader,
+ sizeof(SP_PROPCHANGE_PARAMS)))
+ {
+ DWORD dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ /* Call appropriate class installer. */
+ if (!SetupDiCallClassInstaller(
+ DIF_PROPERTYCHANGE,
+ hDeviceInfoSet,
+ pDeviceInfoData))
+ {
+ DWORD dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_PROPERTYCHANGE) failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ /* Check if a system reboot is required. */
+ check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
+ return ERROR_SUCCESS;
+}
+
+
+/**
+ * Enables the device.
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired A pointer to a BOOL flag. If the device requires a system restart,
+ * this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ * allows the flag to be globally initialized to FALSE and reused for multiple
+ * adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+enable_device(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ return change_device_state(hDeviceInfoSet, pDeviceInfoData, TRUE, pbRebootRequired);
+}
+
+
+/**
+ * Disables the device.
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired A pointer to a BOOL flag. If the device requires a system restart,
+ * this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ * allows the flag to be globally initialized to FALSE and reused for multiple
+ * adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+disable_device(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ return change_device_state(hDeviceInfoSet, pDeviceInfoData, FALSE, pbRebootRequired);
+}
+
+
+/**
+ * Reads string value from registry key.
+ *
+ * @param hKey Handle of the registry key to read from. Must be opened with read
+ * access.
+ *
+ * @param szName Name of the value to read.
+ *
+ * @param pszValue 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 free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ */
+static DWORD
+get_reg_string(
+ _In_ HKEY hKey,
+ _In_ LPCTSTR szName,
+ _Out_ LPTSTR *pszValue)
+{
+ if (pszValue == NULL)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ DWORD dwValueType = REG_NONE, dwSize = 0;
+ DWORD dwResult = RegQueryValueEx(
+ hKey,
+ szName,
+ NULL,
+ &dwValueType,
+ NULL,
+ &dwSize);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+ msg(M_NONFATAL | M_ERRNO, "%s: enumerating \"%" PRIsLPTSTR "\" registry value failed", __FUNCTION__, szName);
+ return dwResult;
+ }
+
+ switch (dwValueType)
+ {
+ case REG_SZ:
+ case REG_EXPAND_SZ:
+ {
+ /* Read value. */
+ LPTSTR szValue = (LPTSTR)malloc(dwSize);
+ if (szValue == NULL)
+ {
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSize);
+ return ERROR_OUTOFMEMORY;
+ }
+
+ dwResult = RegQueryValueEx(
+ hKey,
+ szName,
+ NULL,
+ NULL,
+ (LPBYTE)szValue,
+ &dwSize);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+ msg(M_NONFATAL | M_ERRNO, "%s: reading \"%" PRIsLPTSTR "\" registry value failed", __FUNCTION__, szName);
+ free(szValue);
+ return dwResult;
+ }
+
+ if (dwValueType == REG_EXPAND_SZ)
+ {
+ /* Expand the environment strings. */
+ DWORD
+ dwSizeExp = dwSize * 2,
+ dwCountExp =
+#ifdef UNICODE
+ dwSizeExp / sizeof(TCHAR);
+#else
+ dwSizeExp / sizeof(TCHAR) - 1; /* Note: ANSI version requires one extra char. */
+#endif
+ LPTSTR szValueExp = (LPTSTR)malloc(dwSizeExp);
+ if (szValueExp == NULL)
+ {
+ free(szValue);
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
+ return ERROR_OUTOFMEMORY;
+ }
+
+ DWORD dwCountExpResult = ExpandEnvironmentStrings(
+ szValue,
+ szValueExp, dwCountExp
+ );
+ if (dwCountExpResult == 0)
+ {
+ msg(M_NONFATAL | M_ERRNO, "%s: expanding \"%" PRIsLPTSTR "\" registry value failed", __FUNCTION__, szName);
+ free(szValueExp);
+ free(szValue);
+ return dwResult;
+ }
+ else if (dwCountExpResult <= dwCountExp)
+ {
+ /* The buffer was big enough. */
+ free(szValue);
+ *pszValue = szValueExp;
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ /* Retry with a bigger buffer. */
+ free(szValueExp);
+#ifdef UNICODE
+ dwSizeExp = dwCountExpResult * sizeof(TCHAR);
+#else
+ /* Note: ANSI version requires one extra char. */
+ dwSizeExp = (dwCountExpResult + 1) * sizeof(TCHAR);
+#endif
+ dwCountExp = dwCountExpResult;
+ szValueExp = (LPTSTR)malloc(dwSizeExp);
+ if (szValueExp == NULL)
+ {
+ free(szValue);
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
+ return ERROR_OUTOFMEMORY;
+ }
+
+ dwCountExpResult = ExpandEnvironmentStrings(
+ szValue,
+ szValueExp, dwCountExp);
+ free(szValue);
+ *pszValue = szValueExp;
+ return ERROR_SUCCESS;
+ }
+ }
+ else
+ {
+ *pszValue = szValue;
+ return ERROR_SUCCESS;
+ }
+ }
+
+ default:
+ msg(M_NONFATAL, "%s: \"%" PRIsLPTSTR "\" registry value is not string (type %u)", __FUNCTION__, dwValueType);
+ return ERROR_UNSUPPORTED_TYPE;
+ }
+}
+
+
+/**
+ * Returns network adapter ID.
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param iNumAttempts After the device is created, it might take some time before the
+ * registry key is populated. This parameter specifies the number of
+ * attempts to read NetCfgInstanceId value from registry. A 1sec sleep
+ * is inserted between retry attempts.
+ *
+ * @param pguidAdapter A pointer to GUID that receives network adapter ID.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+get_net_adapter_guid(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _In_ int iNumAttempts,
+ _Out_ LPGUID pguidAdapter)
+{
+ DWORD dwResult = ERROR_BAD_ARGUMENTS;
+
+ if (pguidAdapter == NULL || iNumAttempts < 1)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ /* Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\<class>\<id> registry key. */
+ HKEY hKey = SetupDiOpenDevRegKey(
+ hDeviceInfoSet,
+ pDeviceInfoData,
+ DICS_FLAG_GLOBAL,
+ 0,
+ DIREG_DRV,
+ KEY_READ);
+ if (hKey == INVALID_HANDLE_VALUE)
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiOpenDevRegKey failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ while (iNumAttempts > 0)
+ {
+ /* Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the output with error messages while the registry is still being populated. */
+ LPTSTR szCfgGuidString = NULL;
+ dwResult = RegQueryValueEx(hKey, TEXT("NetCfgInstanceId"), NULL, NULL, NULL, NULL);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ if (dwResult == ERROR_FILE_NOT_FOUND && --iNumAttempts > 0)
+ {
+ /* Wait and retry. */
+ Sleep(1000);
+ continue;
+ }
+
+ SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+ msg(M_NONFATAL | M_ERRNO, "%s: querying \"NetCfgInstanceId\" registry value failed", __FUNCTION__);
+ break;
+ }
+
+ /* Read the NetCfgInstanceId value now. */
+ dwResult = get_reg_string(
+ hKey,
+ TEXT("NetCfgInstanceId"),
+ &szCfgGuidString);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ break;
+ }
+
+ dwResult = SUCCEEDED(CLSIDFromString(szCfgGuidString, (LPCLSID)pguidAdapter)) ? ERROR_SUCCESS : ERROR_INVALID_DATA;
+ free(szCfgGuidString);
+ break;
+ }
+
+ RegCloseKey(hKey);
+ return dwResult;
+}
+
+
+/**
+ * Returns a specified Plug and Play device property.
+ *
+ * @param hDeviceInfoSet A handle to a device information set that contains a device
+ * information element that represents the device.
+ *
+ * @param pDeviceInfoData A pointer to an SP_DEVINFO_DATA structure that specifies the
+ * device information element in hDeviceInfoSet.
+ *
+ * @param dwProperty Specifies the property to be retrieved. See
+ * https://msdn.microsoft.com/en-us/library/windows/hardware/ff551967.aspx
+ *
+ * @pdwPropertyRegDataType 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 ppData A pointer to pointer to data that receives the device property. The
+ * data must be released with free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+get_device_reg_property(
+ _In_ HDEVINFO hDeviceInfoSet,
+ _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+ _In_ DWORD dwProperty,
+ _Out_opt_ LPDWORD pdwPropertyRegDataType,
+ _Out_ LPVOID *ppData)
+{
+ DWORD dwResult = ERROR_BAD_ARGUMENTS;
+
+ if (ppData == NULL)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ /* Try with stack buffer first. */
+ BYTE bBufStack[128];
+ DWORD dwRequiredSize = 0;
+ if (SetupDiGetDeviceRegistryProperty(
+ hDeviceInfoSet,
+ pDeviceInfoData,
+ dwProperty,
+ pdwPropertyRegDataType,
+ bBufStack,
+ sizeof(bBufStack),
+ &dwRequiredSize))
+ {
+ /* Copy from stack. */
+ *ppData = malloc(dwRequiredSize);
+ if (*ppData == NULL)
+ {
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwRequiredSize);
+ return ERROR_OUTOFMEMORY;
+ }
+
+ memcpy(*ppData, bBufStack, dwRequiredSize);
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ dwResult = GetLastError();
+ if (dwResult == ERROR_INSUFFICIENT_BUFFER)
+ {
+ /* Allocate on heap and retry. */
+ *ppData = malloc(dwRequiredSize);
+ if (*ppData == NULL)
+ {
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwRequiredSize);
+ return ERROR_OUTOFMEMORY;
+ }
+
+ if (SetupDiGetDeviceRegistryProperty(
+ hDeviceInfoSet,
+ pDeviceInfoData,
+ dwProperty,
+ pdwPropertyRegDataType,
+ *ppData,
+ dwRequiredSize,
+ &dwRequiredSize))
+ {
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed", __FUNCTION__, dwProperty);
+ return dwResult;
+ }
+ }
+ else
+ {
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed", __FUNCTION__, dwProperty);
+ return dwResult;
+ }
+ }
+}
+
+
+DWORD
+tap_create_adapter(
+ _In_opt_ HWND hwndParent,
+ _In_opt_ LPCTSTR szDeviceDescription,
+ _In_ LPCTSTR szHwId,
+ _Inout_ LPBOOL pbRebootRequired,
+ _Out_ LPGUID pguidAdapter)
+{
+ DWORD dwResult;
+
+ if (szHwId == NULL
+ || pbRebootRequired == NULL
+ || pguidAdapter == NULL)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ /* Create an empty device info set for network adapter device class. */
+ HDEVINFO hDevInfoList = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_NET, hwndParent);
+ if (hDevInfoList == INVALID_HANDLE_VALUE)
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiCreateDeviceInfoList failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ /* Get the device class name from GUID. */
+ TCHAR szClassName[MAX_CLASS_NAME_LEN];
+ if (!SetupDiClassNameFromGuid(
+ &GUID_DEVCLASS_NET,
+ szClassName,
+ _countof(szClassName),
+ NULL))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiClassNameFromGuid failed", __FUNCTION__);
+ goto cleanup_hDevInfoList;
+ }
+
+ /* Create a new device info element and add it to the device info set. */
+ SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+ if (!SetupDiCreateDeviceInfo(
+ hDevInfoList,
+ szClassName,
+ &GUID_DEVCLASS_NET,
+ szDeviceDescription,
+ hwndParent,
+ DICD_GENERATE_ID,
+ &devinfo_data))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiCreateDeviceInfo failed", __FUNCTION__);
+ goto cleanup_hDevInfoList;
+ }
+
+ /* Set a device information element as the selected member of a device information set. */
+ if (!SetupDiSetSelectedDevice(
+ hDevInfoList,
+ &devinfo_data))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiSetSelectedDevice failed", __FUNCTION__);
+ goto cleanup_hDevInfoList;
+ }
+
+ /* Set Plug&Play device hardware ID property. */
+ if (!SetupDiSetDeviceRegistryProperty(
+ hDevInfoList,
+ &devinfo_data,
+ SPDRP_HARDWAREID,
+ (const BYTE *)szHwId, (DWORD)((_tcslen(szHwId) + 1) * sizeof(TCHAR))))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiSetDeviceRegistryProperty failed", __FUNCTION__);
+ goto cleanup_hDevInfoList;
+ }
+
+ /* Search for the driver. */
+ if (!SetupDiBuildDriverInfoList(
+ hDevInfoList,
+ &devinfo_data,
+ SPDIT_CLASSDRIVER))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiBuildDriverInfoList failed", __FUNCTION__);
+ goto cleanup_hDevInfoList;
+ }
+ DWORDLONG dwlDriverVersion = 0;
+ DWORD drvinfo_detail_data_size = sizeof(SP_DRVINFO_DETAIL_DATA) + 0x100;
+ SP_DRVINFO_DETAIL_DATA *drvinfo_detail_data = (SP_DRVINFO_DETAIL_DATA *)malloc(drvinfo_detail_data_size);
+ if (drvinfo_detail_data == NULL)
+ {
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, drvinfo_detail_data_size);
+ dwResult = ERROR_OUTOFMEMORY; goto cleanup_DriverInfoList;
+ }
+
+ for (DWORD dwIndex = 0;; dwIndex++)
+ {
+ /* Get a driver from the list. */
+ SP_DRVINFO_DATA drvinfo_data = { .cbSize = sizeof(SP_DRVINFO_DATA) };
+ if (!SetupDiEnumDriverInfo(
+ hDevInfoList,
+ &devinfo_data,
+ SPDIT_CLASSDRIVER,
+ dwIndex,
+ &drvinfo_data))
+ {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ {
+ break;
+ }
+ else
+ {
+ /* Something is wrong with this driver. Skip it. */
+ msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDriverInfo(%u) failed", __FUNCTION__, dwIndex);
+ continue;
+ }
+ }
+
+ /* Get driver info details. */
+ DWORD dwSize;
+ drvinfo_detail_data->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA);
+ if (!SetupDiGetDriverInfoDetail(
+ hDevInfoList,
+ &devinfo_data,
+ &drvinfo_data,
+ drvinfo_detail_data,
+ drvinfo_detail_data_size,
+ &dwSize))
+ {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ /* (Re)allocate buffer. */
+ if (drvinfo_detail_data)
+ {
+ free(drvinfo_detail_data);
+ }
+
+ drvinfo_detail_data_size = dwSize;
+ drvinfo_detail_data = (SP_DRVINFO_DETAIL_DATA *)malloc(drvinfo_detail_data_size);
+ if (drvinfo_detail_data == NULL)
+ {
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, drvinfo_detail_data_size);
+ dwResult = ERROR_OUTOFMEMORY; goto cleanup_DriverInfoList;
+ }
+
+ /* Re-get driver info details. */
+ drvinfo_detail_data->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA);
+ if (!SetupDiGetDriverInfoDetail(
+ hDevInfoList,
+ &devinfo_data,
+ &drvinfo_data,
+ drvinfo_detail_data,
+ drvinfo_detail_data_size,
+ &dwSize))
+ {
+ /* Something is wrong with this driver. Skip it. */
+ continue;
+ }
+ }
+ else
+ {
+ /* Something is wrong with this driver. Skip it. */
+ msg(M_WARN | M_ERRNO, "%s: SetupDiGetDriverInfoDetail(\"%hs\") failed", __FUNCTION__, drvinfo_data.Description);
+ continue;
+ }
+ }
+
+ /* Check the driver version and hardware ID. */
+ if (dwlDriverVersion < drvinfo_data.DriverVersion
+ && drvinfo_detail_data->HardwareID
+ && _tcszistr(drvinfo_detail_data->HardwareID, szHwId))
+ {
+ /* Newer version and matching hardware ID found. Select the driver. */
+ if (!SetupDiSetSelectedDriver(
+ hDevInfoList,
+ &devinfo_data,
+ &drvinfo_data))
+ {
+ /* Something is wrong with this driver. Skip it. */
+ msg(M_WARN | M_ERRNO, "%s: SetupDiSetSelectedDriver(\"%hs\") failed", __FUNCTION__, drvinfo_data.Description);
+ continue;
+ }
+
+ dwlDriverVersion = drvinfo_data.DriverVersion;
+ }
+ }
+ if (drvinfo_detail_data)
+ {
+ free(drvinfo_detail_data);
+ }
+
+ if (dwlDriverVersion == 0)
+ {
+ dwResult = ERROR_NOT_FOUND;
+ msg(M_NONFATAL, "%s: No driver for device \"%" PRIsLPTSTR "\" installed.", __FUNCTION__, szHwId);
+ goto cleanup_DriverInfoList;
+ }
+
+ /* Call appropriate class installer. */
+ if (!SetupDiCallClassInstaller(
+ DIF_REGISTERDEVICE,
+ hDevInfoList,
+ &devinfo_data))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed", __FUNCTION__);
+ goto cleanup_DriverInfoList;
+ }
+
+ /* Register device co-installers if any. */
+ if (!SetupDiCallClassInstaller(
+ DIF_REGISTER_COINSTALLERS,
+ hDevInfoList,
+ &devinfo_data))
+ {
+ dwResult = GetLastError();
+ msg(M_WARN | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS) failed", __FUNCTION__);
+ }
+
+ /* Install adapters if any. */
+ if (!SetupDiCallClassInstaller(
+ DIF_INSTALLINTERFACES,
+ hDevInfoList,
+ &devinfo_data))
+ {
+ dwResult = GetLastError();
+ msg(M_WARN | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_INSTALLINTERFACES) failed", __FUNCTION__);
+ }
+
+ /* Install the device. */
+ if (!SetupDiCallClassInstaller(
+ DIF_INSTALLDEVICE,
+ hDevInfoList,
+ &devinfo_data))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_INSTALLDEVICE) failed", __FUNCTION__);
+ goto cleanup_remove_device;
+ }
+
+ /* Check if a system reboot is required. (Ignore errors) */
+ check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);
+
+ /* Get network adapter ID from registry. Retry for max 30sec. */
+ dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 30, pguidAdapter);
+
+cleanup_remove_device:
+ if (dwResult != ERROR_SUCCESS)
+ {
+ /* The adapter was installed. But, the adapter ID was unobtainable. Clean-up. */
+ SP_REMOVEDEVICE_PARAMS removedevice_params =
+ {
+ .ClassInstallHeader =
+ {
+ .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
+ .InstallFunction = DIF_REMOVE,
+ },
+ .Scope = DI_REMOVEDEVICE_GLOBAL,
+ .HwProfile = 0,
+ };
+
+ /* Set class installer parameters for DIF_REMOVE. */
+ if (SetupDiSetClassInstallParams(
+ hDevInfoList,
+ &devinfo_data,
+ &removedevice_params.ClassInstallHeader,
+ sizeof(SP_REMOVEDEVICE_PARAMS)))
+ {
+ /* Call appropriate class installer. */
+ if (SetupDiCallClassInstaller(
+ DIF_REMOVE,
+ hDevInfoList,
+ &devinfo_data))
+ {
+ /* Check if a system reboot is required. */
+ check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);
+ }
+ else
+ {
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
+ }
+ }
+ else
+ {
+ msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
+ }
+ }
+
+cleanup_DriverInfoList:
+ SetupDiDestroyDriverInfoList(
+ hDevInfoList,
+ &devinfo_data,
+ SPDIT_CLASSDRIVER);
+
+cleanup_hDevInfoList:
+ SetupDiDestroyDeviceInfoList(hDevInfoList);
+ return dwResult;
+}
+
+
+/**
+ * Performs a given task on an adapter.
+ *
+ * @param hwndParent A handle to the top-level window to use for any user adapter that is
+ * related to non-device-specific actions (such as a select-device dialog
+ * box that uses the global class driver list). This handle is optional
+ * and can be NULL. If a specific top-level window is not required, set
+ * hwndParent to NULL.
+ *
+ * @param pguidAdapter A pointer to GUID that contains network adapter ID.
+ *
+ * @param funcOperation A pointer for the function to perform specific task on the adapter.
+ *
+ * @param pbRebootRequired A pointer to a BOOL flag. If the device requires a system restart,
+ * this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ * allows the flag to be globally initialized to FALSE and reused for multiple
+ * adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+execute_on_first_adapter(
+ _In_opt_ HWND hwndParent,
+ _In_ LPCGUID pguidAdapter,
+ _In_ devop_func_t funcOperation,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ DWORD dwResult;
+
+ if (pguidAdapter == NULL)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ /* Create a list of network devices. */
+ HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(
+ &GUID_DEVCLASS_NET,
+ NULL,
+ hwndParent,
+ DIGCF_PRESENT,
+ NULL,
+ NULL,
+ NULL);
+ if (hDevInfoList == INVALID_HANDLE_VALUE)
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ /* Retrieve information associated with a device information set. */
+ SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA) };
+ if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
+ goto cleanup_hDevInfoList;
+ }
+
+ /* Iterate. */
+ for (DWORD dwIndex = 0;; dwIndex++)
+ {
+ /* Get the device from the list. */
+ SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+ if (!SetupDiEnumDeviceInfo(
+ hDevInfoList,
+ dwIndex,
+ &devinfo_data))
+ {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ {
+ LPOLESTR szAdapterId = NULL;
+ StringFromIID((REFIID)pguidAdapter, &szAdapterId);
+ msg(M_NONFATAL, "%s: Adapter %" PRIsLPOLESTR " not found", __FUNCTION__, szAdapterId);
+ CoTaskMemFree(szAdapterId);
+ dwResult = ERROR_FILE_NOT_FOUND;
+ goto cleanup_hDevInfoList;
+ }
+ else
+ {
+ /* Something is wrong with this device. Skip it. */
+ msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__, dwIndex);
+ continue;
+ }
+ }
+
+ /* Get adapter GUID. */
+ GUID guidAdapter;
+ dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ /* Something is wrong with this device. Skip it. */
+ continue;
+ }
+
+ /* Compare GUIDs. */
+ if (memcmp(pguidAdapter, &guidAdapter, sizeof(GUID)) == 0)
+ {
+ dwResult = funcOperation(hDevInfoList, &devinfo_data, pbRebootRequired);
+ break;
+ }
+ }
+
+cleanup_hDevInfoList:
+ SetupDiDestroyDeviceInfoList(hDevInfoList);
+ return dwResult;
+}
+
+
+DWORD
+tap_delete_adapter(
+ _In_opt_ HWND hwndParent,
+ _In_ LPCGUID pguidAdapter,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ return execute_on_first_adapter(hwndParent, pguidAdapter, delete_device, pbRebootRequired);
+}
+
+
+DWORD
+tap_enable_adapter(
+ _In_opt_ HWND hwndParent,
+ _In_ LPCGUID pguidAdapter,
+ _In_ BOOL bEnable,
+ _Inout_ LPBOOL pbRebootRequired)
+{
+ return execute_on_first_adapter(hwndParent, pguidAdapter, bEnable ? enable_device : disable_device, pbRebootRequired);
+}
+
+/* stripped version of ExecCommand in interactive.c */
+static DWORD
+ExecCommand(const WCHAR* cmdline)
+{
+ DWORD exit_code;
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+ DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
+ WCHAR* cmdline_dup = NULL;
+
+ ZeroMemory(&si, sizeof(si));
+ ZeroMemory(&pi, sizeof(pi));
+
+ si.cb = sizeof(si);
+
+ /* CreateProcess needs a modifiable cmdline: make a copy */
+ cmdline_dup = _wcsdup(cmdline);
+ if (cmdline_dup && CreateProcessW(NULL, cmdline_dup, NULL, NULL, FALSE,
+ proc_flags, NULL, NULL, &si, &pi))
+ {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ if (!GetExitCodeProcess(pi.hProcess, &exit_code))
+ {
+ exit_code = GetLastError();
+ }
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+ else
+ {
+ exit_code = GetLastError();
+ }
+
+ free(cmdline_dup);
+ return exit_code;
+}
+
+DWORD
+tap_set_adapter_name(
+ _In_ LPCGUID pguidAdapter,
+ _In_ LPCTSTR szName)
+{
+ DWORD dwResult;
+
+ if (pguidAdapter == NULL || szName == NULL)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ /* Get the device class GUID as string. */
+ LPOLESTR szDevClassNetId = NULL;
+ StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
+
+ /* Get the adapter GUID as string. */
+ LPOLESTR szAdapterId = NULL;
+ StringFromIID((REFIID)pguidAdapter, &szAdapterId);
+
+ /* Render registry key path. */
+ TCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];
+ _stprintf_s(
+ szRegKey, _countof(szRegKey),
+ szAdapterRegKeyPathTemplate,
+ szDevClassNetId,
+ szAdapterId);
+
+ /* Open network adapter registry key. */
+ HKEY hKey = NULL;
+ dwResult = RegOpenKeyEx(
+ HKEY_LOCAL_MACHINE,
+ szRegKey,
+ 0,
+ KEY_QUERY_VALUE,
+ &hKey);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+ msg(M_NONFATAL | M_ERRNO, "%s: RegOpenKeyEx(HKLM, \"%" PRIsLPTSTR "\") failed", __FUNCTION__, szRegKey);
+ goto cleanup_szAdapterId;
+ }
+
+ LPTSTR szOldName = NULL;
+ dwResult = get_reg_string(hKey, TEXT("Name"), &szOldName);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ SetLastError(dwResult);
+ msg(M_NONFATAL | M_ERRNO, "%s: Error reading adapter name", __FUNCTION__);
+ goto cleanup_hKey;
+ }
+
+ /* rename adapter via netsh call */
+ const TCHAR* szFmt = _T("netsh interface set interface name=\"%s\" newname=\"%s\"");
+ size_t ncmdline = _tcslen(szFmt) + _tcslen(szOldName) + _tcslen(szName) + 1;
+ WCHAR* szCmdLine = malloc(ncmdline * sizeof(TCHAR));
+ _stprintf_s(szCmdLine, ncmdline, szFmt, szOldName, szName);
+
+ free(szOldName);
+
+ dwResult = ExecCommand(szCmdLine);
+ free(szCmdLine);
+
+ if (dwResult != ERROR_SUCCESS)
+ {
+ SetLastError(dwResult);
+ msg(M_NONFATAL | M_ERRNO, "%s: Error renaming adapter", __FUNCTION__);
+ goto cleanup_hKey;
+ }
+
+cleanup_hKey:
+ RegCloseKey(hKey);
+cleanup_szAdapterId:
+ CoTaskMemFree(szAdapterId);
+ CoTaskMemFree(szDevClassNetId);
+ return dwResult;
+}
+
+
+DWORD
+tap_list_adapters(
+ _In_opt_ HWND hwndParent,
+ _In_opt_ LPCTSTR szzHwIDs,
+ _Out_ struct tap_adapter_node **ppAdapter)
+{
+ DWORD dwResult;
+
+ if (ppAdapter == NULL)
+ {
+ return ERROR_BAD_ARGUMENTS;
+ }
+
+ /* Create a list of network devices. */
+ HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(
+ &GUID_DEVCLASS_NET,
+ NULL,
+ hwndParent,
+ DIGCF_PRESENT,
+ NULL,
+ NULL,
+ NULL);
+ if (hDevInfoList == INVALID_HANDLE_VALUE)
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
+ return dwResult;
+ }
+
+ /* Retrieve information associated with a device information set. */
+ SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA) };
+ if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
+ {
+ dwResult = GetLastError();
+ msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
+ goto cleanup_hDevInfoList;
+ }
+
+ /* Get the device class GUID as string. */
+ LPOLESTR szDevClassNetId = NULL;
+ StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
+
+ /* Iterate. */
+ *ppAdapter = NULL;
+ struct tap_adapter_node *pAdapterTail = NULL;
+ for (DWORD dwIndex = 0;; dwIndex++)
+ {
+ /* Get the device from the list. */
+ SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+ if (!SetupDiEnumDeviceInfo(
+ hDevInfoList,
+ dwIndex,
+ &devinfo_data))
+ {
+ if (GetLastError() == ERROR_NO_MORE_ITEMS)
+ {
+ break;
+ }
+ else
+ {
+ /* Something is wrong with this device. Skip it. */
+ msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__, dwIndex);
+ continue;
+ }
+ }
+
+ /* Get device hardware ID(s). */
+ DWORD dwDataType = REG_NONE;
+ LPTSTR szzDeviceHardwareIDs = NULL;
+ dwResult = get_device_reg_property(
+ hDevInfoList,
+ &devinfo_data,
+ SPDRP_HARDWAREID,
+ &dwDataType,
+ (LPVOID)&szzDeviceHardwareIDs);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ /* Something is wrong with this device. Skip it. */
+ continue;
+ }
+
+ /* Check that hardware ID is REG_SZ/REG_MULTI_SZ, and optionally if it matches ours. */
+ if (dwDataType == REG_SZ)
+ {
+ if (szzHwIDs && !_tcszistr(szzHwIDs, szzDeviceHardwareIDs))
+ {
+ /* This is not our device. Skip it. */
+ goto cleanup_szzDeviceHardwareIDs;
+ }
+ }
+ else if (dwDataType == REG_MULTI_SZ)
+ {
+ if (szzHwIDs)
+ {
+ for (LPTSTR s = szzDeviceHardwareIDs;; s += _tcslen(s) + 1)
+ {
+ if (s[0] == 0)
+ {
+ /* This is not our device. Skip it. */
+ goto cleanup_szzDeviceHardwareIDs;
+ }
+ else if (_tcszistr(szzHwIDs, s))
+ {
+ /* This is our device. */
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* Unexpected hardware ID format. Skip device. */
+ goto cleanup_szzDeviceHardwareIDs;
+ }
+
+ /* Get adapter GUID. */
+ GUID guidAdapter;
+ dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ /* Something is wrong with this device. Skip it. */
+ goto cleanup_szzDeviceHardwareIDs;
+ }
+
+ /* Get the adapter GUID as string. */
+ LPOLESTR szAdapterId = NULL;
+ StringFromIID((REFIID)&guidAdapter, &szAdapterId);
+
+ /* Render registry key path. */
+ TCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];
+ _stprintf_s(
+ szRegKey, _countof(szRegKey),
+ szAdapterRegKeyPathTemplate,
+ szDevClassNetId,
+ szAdapterId);
+
+ /* Open network adapter registry key. */
+ HKEY hKey = NULL;
+ dwResult = RegOpenKeyEx(
+ HKEY_LOCAL_MACHINE,
+ szRegKey,
+ 0,
+ KEY_READ,
+ &hKey);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+ msg(M_WARN | M_ERRNO, "%s: RegOpenKeyEx(HKLM, \"%" PRIsLPTSTR "\") failed", __FUNCTION__, szRegKey);
+ goto cleanup_szAdapterId;
+ }
+
+ /* Read adapter name. */
+ LPTSTR szName = NULL;
+ dwResult = get_reg_string(
+ hKey,
+ TEXT("Name"),
+ &szName);
+ if (dwResult != ERROR_SUCCESS)
+ {
+ SetLastError(dwResult);
+ msg(M_WARN | M_ERRNO, "%s: Cannot determine %" PRIsLPOLESTR " adapter name", __FUNCTION__, szAdapterId);
+ goto cleanup_hKey;
+ }
+
+ /* Append to the list. */
+ size_t hwid_size = (_tcszlen(szzDeviceHardwareIDs) + 1) * sizeof(TCHAR);
+ size_t name_size = (_tcslen(szName) + 1) * sizeof(TCHAR);
+ struct tap_adapter_node *node = (struct tap_adapter_node *)malloc(sizeof(struct tap_adapter_node) + hwid_size + name_size);
+ if (node == NULL)
+ {
+ msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct tap_adapter_node) + hwid_size + name_size);
+ dwResult = ERROR_OUTOFMEMORY; goto cleanup_szName;
+ }
+
+ memcpy(&node->guid, &guidAdapter, sizeof(GUID));
+ node->szzHardwareIDs = (LPTSTR)(node + 1);
+ memcpy(node->szzHardwareIDs, szzDeviceHardwareIDs, hwid_size);
+ node->szName = (LPTSTR)((LPBYTE)node->szzHardwareIDs + hwid_size);
+ memcpy(node->szName, szName, name_size);
+ node->pNext = NULL;
+ if (pAdapterTail)
+ {
+ pAdapterTail->pNext = node;
+ pAdapterTail = node;
+ }
+ else
+ {
+ *ppAdapter = pAdapterTail = node;
+ }
+
+cleanup_szName:
+ free(szName);
+cleanup_hKey:
+ RegCloseKey(hKey);
+cleanup_szAdapterId:
+ CoTaskMemFree(szAdapterId);
+cleanup_szzDeviceHardwareIDs:
+ free(szzDeviceHardwareIDs);
+ }
+
+ dwResult = ERROR_SUCCESS;
+
+ CoTaskMemFree(szDevClassNetId);
+cleanup_hDevInfoList:
+ SetupDiDestroyDeviceInfoList(hDevInfoList);
+ return dwResult;
+}
+
+
+void
+tap_free_adapter_list(
+ _In_ struct tap_adapter_node *pAdapterList)
+{
+ /* Iterate over all nodes of the list. */
+ while (pAdapterList)
+ {
+ struct tap_adapter_node *node = pAdapterList;
+ pAdapterList = pAdapterList->pNext;
+
+ /* Free the adapter node. */
+ free(node);
+ }
+}