blob: c9795bce1d6d04b69c97c532a437d8fdbe201d3b [file] [log] [blame]
/*
* tapctl -- Utility to manipulate TUN/TAP adapters on Windows
* https://community.openvpn.net/openvpn/wiki/Tapctl
*
* Copyright (C) 2018-2022 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>
#include <newdev.h>
#ifdef _MSC_VER
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "newdev.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")))
/**
* Dynamically load a library and find a function in it
*
* @param libname Name of the library to load
* @param funcname Name of the function to find
* @param m Pointer to a module. On return this is set to the
* the handle to the loaded library. The caller must
* free it by calling FreeLibrary() if not NULL.
*
* @return Pointer to the function
* NULL on error -- use GetLastError() to find the error code.
*
**/
static void *
find_function(const WCHAR *libname, const char *funcname, HMODULE *m)
{
WCHAR libpath[MAX_PATH];
void *fptr = NULL;
/* Make sure the dll is loaded from the system32 folder */
if (!GetSystemDirectoryW(libpath, _countof(libpath)))
{
return NULL;
}
/* +1 for the path seperator '\' */
const size_t path_length = wcslen(libpath) + 1 + wcslen(libname);
if (path_length >= _countof(libpath))
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return NULL;
}
wcscat_s(libpath, _countof(libpath), L"\\");
wcscat_s(libpath, _countof(libpath), libname);
*m = LoadLibraryW(libpath);
if (*m == NULL)
{
return NULL;
}
fptr = GetProcAddress(*m, funcname);
if (!fptr)
{
FreeLibrary(*m);
*m = NULL;
return NULL;
}
return fptr;
}
/**
* 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;
HMODULE libnewdev = NULL;
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;
}
/* Register the device instance with the PnP Manager */
if (!SetupDiCallClassInstaller(
DIF_REGISTERDEVICE,
hDevInfoList,
&devinfo_data))
{
dwResult = GetLastError();
msg(M_NONFATAL, "%s: SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed", __FUNCTION__);
goto cleanup_hDevInfoList;
}
/* Install the device using DiInstallDevice()
* We instruct the system to use the best driver in the driver store
* by setting the drvinfo argument of DiInstallDevice as NULL. This
* assumes a driver is already installed in the driver store.
*/
#ifdef HAVE_DIINSTALLDEVICE
if (!DiInstallDevice(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))
#else
/* mingw does not resolve DiInstallDevice, so load it at run time. */
typedef BOOL (WINAPI *DiInstallDeviceFn) (HWND, HDEVINFO, SP_DEVINFO_DATA *,
SP_DRVINFO_DATA *, DWORD, BOOL *);
DiInstallDeviceFn installfn
= find_function (L"newdev.dll", "DiInstallDevice", &libnewdev);
if (!installfn)
{
dwResult = GetLastError();
msg(M_NONFATAL | M_ERRNO, "%s: Failed to locate DiInstallDevice()", __FUNCTION__);
goto cleanup_hDevInfoList;
}
if (!installfn(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))
#endif
{
dwResult = GetLastError();
msg(M_NONFATAL | M_ERRNO, "%s: DiInstallDevice failed", __FUNCTION__);
goto cleanup_remove_device;
}
/* 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_hDevInfoList:
if (libnewdev)
{
FreeLibrary(libnewdev);
}
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,
_In_ BOOL bSilent)
{
DWORD dwResult;
int msg_flag = bSilent ? M_WARN : M_NONFATAL;
msg_flag |= M_ERRNO;
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(msg_flag, "%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(msg_flag, "%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(msg_flag, "%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);
}
}