/* * tapctl -- Utility to manipulate TUN/TAP adapters on Windows * https://community.openvpn.net/openvpn/wiki/Tapctl * * Copyright (C) 2018-2020 Simon Rozman * * 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 #elif defined(_MSC_VER) #include #endif #include "tap.h" #include "error.h" #include #include #include #include #include #include #include #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; } size_t len = _countof(libpath) - wcslen(libpath) - 1; if (len < wcslen(libname) + 1) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return NULL; } wcsncat(libpath, L"\\", len); wcsncat(libpath, libname, len-1); *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, ¶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\\ 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); } }