/* * tapctl -- Utility to manipulate TUN/TAP interfaces on Windows * https://community.openvpn.net/openvpn/wiki/Tapctl * * Copyright (C) 2018 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 #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 szzHardwareIDs[] = TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0"); const static TCHAR szInterfaceRegKeyPathTemplate[] = TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\%") TEXT(PRIsLPOLESTR) TEXT("\\%") TEXT(PRIsLPOLESTR) TEXT("\\Connection"); #define INTERFACE_REGKEY_PATH_MAX (_countof(TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\")) - 1 + 38 + _countof(TEXT("\\")) - 1 + 38 + _countof(TEXT("\\Connection"))) /** * 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 for which to * * @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 interface installation 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 interface installations. * * @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; } /** * 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 interface ID. * * @param hDeviceInfoSet A handle to a device information set that contains a device * information element that represents the device for which to * * @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 pguidInterface A pointer to GUID that receives network interface ID. * * @return ERROR_SUCCESS on success; Win32 error code otherwise **/ static DWORD get_net_interface_guid( _In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _In_ int iNumAttempts, _Out_ LPGUID pguidInterface) { DWORD dwResult = ERROR_BAD_ARGUMENTS; if (pguidInterface == 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)pguidInterface)) ? 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 for which to * retrieve a Plug and Play property. * * @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; } } } /** * Returns length of list of strings * * @param str Pointer to a list of strings terminated by an empty string. * * @return Number of characters not counting the final zero terminator **/ static inline size_t _tcszlen(_In_ LPCTSTR str) { LPCTSTR s; for (s = str; s[0]; s += _tcslen(s) + 1) { } return s - str; } DWORD tap_create_interface( _In_opt_ HWND hwndParent, _In_opt_ LPCTSTR szDeviceDescription, _Inout_ LPBOOL pbRebootRequired, _Out_ LPGUID pguidInterface) { DWORD dwResult; if (pbRebootRequired == NULL || pguidInterface == 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 *)szzHardwareIDs, sizeof(szzHardwareIDs))) { 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 first, since the check is trivial and will save us iterating over hardware IDs for any driver versioned prior our best match. */ if (dwlDriverVersion < drvinfo_data.DriverVersion) { /* Search the list of hardware IDs. */ for (LPTSTR szHwdID = drvinfo_detail_data->HardwareID; szHwdID && szHwdID[0]; szHwdID += _tcslen(szHwdID) + 1) { if (_tcsicmp(szHwdID, szzHardwareIDs) == 0) { /* 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); break; } dwlDriverVersion = drvinfo_data.DriverVersion; break; } } } } 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__, szzHardwareIDs); 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 interfaces 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 interface ID from registry. Retry for max 30sec. */ dwResult = get_net_interface_guid(hDevInfoList, &devinfo_data, 30, pguidInterface); cleanup_remove_device: if (dwResult != ERROR_SUCCESS) { /* The interface was installed. But, the interface 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; } DWORD tap_delete_interface( _In_opt_ HWND hwndParent, _In_ LPCGUID pguidInterface, _Inout_ LPBOOL pbRebootRequired) { DWORD dwResult; if (pguidInterface == 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 szInterfaceId = NULL; StringFromIID((REFIID)pguidInterface, &szInterfaceId); msg(M_NONFATAL, "%s: Interface %" PRIsLPOLESTR " not found", __FUNCTION__, szInterfaceId); CoTaskMemFree(szInterfaceId); 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 interface GUID. */ GUID guidInterface; dwResult = get_net_interface_guid(hDevInfoList, &devinfo_data, 1, &guidInterface); if (dwResult != ERROR_SUCCESS) { /* Something is wrong with this device. Skip it. */ continue; } /* Compare GUIDs. */ if (memcmp(pguidInterface, &guidInterface, sizeof(GUID)) == 0) { /* Remove the device. */ 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))) { dwResult = GetLastError(); msg(M_NONFATAL, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__); goto cleanup_hDevInfoList; } /* Call appropriate class installer. */ if (!SetupDiCallClassInstaller( DIF_REMOVE, hDevInfoList, &devinfo_data)) { dwResult = GetLastError(); msg(M_NONFATAL, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__); goto cleanup_hDevInfoList; } /* Check if a system reboot is required. */ check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired); dwResult = ERROR_SUCCESS; break; } } cleanup_hDevInfoList: SetupDiDestroyDeviceInfoList(hDevInfoList); return dwResult; } DWORD tap_set_interface_name( _In_ LPCGUID pguidInterface, _In_ LPCTSTR szName) { DWORD dwResult; if (pguidInterface == 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 interface GUID as string. */ LPOLESTR szInterfaceId = NULL; StringFromIID((REFIID)pguidInterface, &szInterfaceId); /* Render registry key path. */ TCHAR szRegKey[INTERFACE_REGKEY_PATH_MAX]; _stprintf_s( szRegKey, _countof(szRegKey), szInterfaceRegKeyPathTemplate, szDevClassNetId, szInterfaceId); /* Open network interface registry key. */ HKEY hKey = NULL; dwResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_SET_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_szInterfaceId; } /* Set the interface name. */ size_t sizeName = ((_tcslen(szName) + 1) * sizeof(TCHAR)); #ifdef _WIN64 if (sizeName > DWORD_MAX) { dwResult = ERROR_BAD_ARGUMENTS; msg(M_NONFATAL, "%s: string too big (size %u).", __FUNCTION__, sizeName); goto cleanup_hKey; } #endif dwResult = RegSetKeyValue( hKey, NULL, TEXT("Name"), REG_SZ, szName, (DWORD)sizeName); if (dwResult != ERROR_SUCCESS) { SetLastError(dwResult); /* MSDN does not mention RegSetKeyValue() to set GetLastError(). But we do have an error code. Set last error manually. */ msg(M_NONFATAL | M_ERRNO, "%s: RegSetKeyValue(\"Name\") failed", __FUNCTION__); goto cleanup_hKey; } cleanup_hKey: RegCloseKey(hKey); cleanup_szInterfaceId: CoTaskMemFree(szInterfaceId); CoTaskMemFree(szDevClassNetId); return dwResult; } DWORD tap_list_interfaces( _In_opt_ HWND hwndParent, _Out_ struct tap_interface_node **ppInterface, _In_ BOOL bAll) { DWORD dwResult; if (ppInterface == 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. */ *ppInterface = NULL; struct tap_interface_node *pInterfaceTail = 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 (!bAll && _tcsicmp(szzDeviceHardwareIDs, szzHardwareIDs) != 0) { /* This is not our device. Skip it. */ goto cleanup_szzDeviceHardwareIDs; } } else if (dwDataType == REG_MULTI_SZ) { if (!bAll) { for (LPTSTR szHwdID = szzDeviceHardwareIDs;; szHwdID += _tcslen(szHwdID) + 1) { if (szHwdID[0] == 0) { /* This is not our device. Skip it. */ goto cleanup_szzDeviceHardwareIDs; } else if (_tcsicmp(szHwdID, szzHardwareIDs) == 0) { /* This is our device. */ break; } } } } else { /* Unexpected hardware ID format. Skip device. */ goto cleanup_szzDeviceHardwareIDs; } /* Get interface GUID. */ GUID guidInterface; dwResult = get_net_interface_guid(hDevInfoList, &devinfo_data, 1, &guidInterface); if (dwResult != ERROR_SUCCESS) { /* Something is wrong with this device. Skip it. */ goto cleanup_szzDeviceHardwareIDs; } /* Get the interface GUID as string. */ LPOLESTR szInterfaceId = NULL; StringFromIID((REFIID)&guidInterface, &szInterfaceId); /* Render registry key path. */ TCHAR szRegKey[INTERFACE_REGKEY_PATH_MAX]; _stprintf_s( szRegKey, _countof(szRegKey), szInterfaceRegKeyPathTemplate, szDevClassNetId, szInterfaceId); /* Open network interface 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_szInterfaceId; } /* Read interface 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 " interface name", __FUNCTION__, szInterfaceId); 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_interface_node *node = (struct tap_interface_node *)malloc(sizeof(struct tap_interface_node) + hwid_size + name_size); if (node == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct tap_interface_node) + hwid_size + name_size); dwResult = ERROR_OUTOFMEMORY; goto cleanup_szName; } memcpy(&node->guid, &guidInterface, 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 (pInterfaceTail) { pInterfaceTail->pNext = node; pInterfaceTail = node; } else { *ppInterface = pInterfaceTail = node; } cleanup_szName: free(szName); cleanup_hKey: RegCloseKey(hKey); cleanup_szInterfaceId: CoTaskMemFree(szInterfaceId); cleanup_szzDeviceHardwareIDs: free(szzDeviceHardwareIDs); } dwResult = ERROR_SUCCESS; CoTaskMemFree(szDevClassNetId); cleanup_hDevInfoList: SetupDiDestroyDeviceInfoList(hDevInfoList); return dwResult; } void tap_free_interface_list( _In_ struct tap_interface_node *pInterfaceList) { /* Iterate over all nodes of the list. */ while (pInterfaceList) { struct tap_interface_node *node = pInterfaceList; pInterfaceList = pInterfaceList->pNext; /* Free the interface node. */ free(node); } }