diff options
author | Bernhard Schmidt <berni@debian.org> | 2020-08-15 21:29:50 +0200 |
---|---|---|
committer | Bernhard Schmidt <berni@debian.org> | 2020-08-15 21:29:50 +0200 |
commit | 1079962e4c06f88a54e50d997c1b7e84303d30b4 (patch) | |
tree | 4d019426928435425214ccedd6f89b70dbdf035d /src/tapctl/tap.c | |
parent | 620785fe268a1221c1ba7a9cb5a70f3140a4f1ca (diff) |
New upstream version 2.5~beta1upstream/2.5_beta1
Diffstat (limited to 'src/tapctl/tap.c')
-rw-r--r-- | src/tapctl/tap.c | 1441 |
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, + ¶ms.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, + ¶ms.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); + } +} |