From 8169b3548251b6e1cb78fa1c7561469bad4e1692 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 30 Jul 2015 11:31:38 -0700 Subject: [PATCH] Kill the devcon.exe dependency by dynamically loading cfgmgr32, newdev, and setupapi and using these functions directly. --- ext/bin/devcon/README.txt | 7 - ext/bin/devcon/devcon_x64.exe | Bin 90456 -> 0 bytes ext/bin/devcon/devcon_x86.exe | Bin 86360 -> 0 bytes ext/installfiles/windows/ZeroTier One.aip | 23 +- one.cpp | 35 +- osdep/WindowsEthernetTap.cpp | 569 ++++++++++++++-------- osdep/WindowsEthernetTap.hpp | 42 +- service/OneService.cpp | 2 +- windows/ZeroTierOne/ZeroTierOne.vcxproj | 8 +- 9 files changed, 442 insertions(+), 244 deletions(-) delete mode 100644 ext/bin/devcon/README.txt delete mode 100644 ext/bin/devcon/devcon_x64.exe delete mode 100644 ext/bin/devcon/devcon_x86.exe diff --git a/ext/bin/devcon/README.txt b/ext/bin/devcon/README.txt deleted file mode 100644 index 15cf1478c..000000000 --- a/ext/bin/devcon/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -This is the Microsoft "devcon" utility, which as far as I know is -fair game to redistribute. It's packaged with OpenVPN and several -other things and also distributed in source code form as an example -program by Microsoft. - -It's called by zerotier-one.exe to automagically install and remove -instances of the tap device. diff --git a/ext/bin/devcon/devcon_x64.exe b/ext/bin/devcon/devcon_x64.exe deleted file mode 100644 index 5181aeb676d452591a9c30729fc69f7f88d8278e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90456 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 diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index 8b26171cf..5bdebe40c 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -62,8 +62,6 @@ - - @@ -73,15 +71,13 @@ - + - - @@ -234,10 +230,8 @@ - - - - + + @@ -259,10 +253,8 @@ - - - - + + @@ -306,6 +298,11 @@ + + + + + diff --git a/one.cpp b/one.cpp index d63459193..d384270d3 100644 --- a/one.cpp +++ b/one.cpp @@ -42,6 +42,7 @@ #include #include #include +#include "osdep/WindowsEthernetTap.hpp" #include "windows/ZeroTierOne/ServiceInstaller.h" #include "windows/ZeroTierOne/ServiceBase.h" #include "windows/ZeroTierOne/ZeroTierOneService.h" @@ -914,17 +915,20 @@ static void printHelp(const char *cn,FILE *out) fprintf(out," -U - Run as unprivileged user (skip privilege check)"ZT_EOL_S); fprintf(out," -p - Port for UDP and TCP/HTTP (default: 9993)"ZT_EOL_S); //fprintf(out," -T - Override root topology, do not authenticate or update"ZT_EOL_S); + #ifdef __UNIX_LIKE__ fprintf(out," -d - Fork and run as daemon (Unix-ish OSes)"ZT_EOL_S); #endif // __UNIX_LIKE__ - fprintf(out," -i - Generate and manage identities (zerotier-idtool)"ZT_EOL_S); - fprintf(out," -q - Query API (zerotier-cli)"ZT_EOL_S); + #ifdef __WINDOWS__ fprintf(out," -C - Run from command line instead of as service (Windows)"ZT_EOL_S); fprintf(out," -I - Install Windows service (Windows)"ZT_EOL_S); fprintf(out," -R - Uninstall Windows service (Windows)"ZT_EOL_S); - fprintf(out," -D - Load tap driver into system driver store (Windows)"ZT_EOL_S); + fprintf(out," -D - Remove all instances of Windows tap device (Windows)"ZT_EOL_S); #endif // __WINDOWS__ + + fprintf(out," -i - Generate and manage identities (zerotier-idtool)"ZT_EOL_S); + fprintf(out," -q - Query API (zerotier-cli)"ZT_EOL_S); } #ifdef __WINDOWS__ @@ -1059,26 +1063,15 @@ int main(int argc,char **argv) return 0; } break; -#if 0 - case 'D': { // Install Windows driver (since PNPUTIL.EXE seems to be weirdly unreliable) - std::string pathToInf; -#ifdef _WIN64 - pathToInf = ZT_DEFAULTS.defaultHomePath + "\\tap-windows\\x64\\zttap200.inf"; -#else - pathToInf = ZT_DEFAULTS.defaultHomePath + "\\tap-windows\\x86\\zttap200.inf"; -#endif - printf("Installing ZeroTier One virtual Ethernet port driver."ZT_EOL_S""ZT_EOL_S"NOTE: If you don't see a confirmation window to allow driver installation,"ZT_EOL_S"check to make sure it didn't appear under the installer."ZT_EOL_S); - BOOL needReboot = FALSE; - if (DiInstallDriverA(NULL,pathToInf.c_str(),DIIRFLAG_FORCE_INF,&needReboot)) { - printf("%s: driver successfully installed from %s"ZT_EOL_S,argv[0],pathToInf.c_str()); - return 0; - } else { - printf("%s: failed installing %s: %d"ZT_EOL_S,argv[0],pathToInf.c_str(),(int)GetLastError()); + case 'D': { + std::string err = WindowsEthernetTap::destroyAllPersistentTapDevices(); + if (err.length() > 0) { + fprintf(stderr,"%s: unable to uninstall one or more persistent tap devices: %s"ZT_EOL_S,argv[0],err.c_str()); return 3; } + return 0; } break; #endif // __WINDOWS__ -#endif case 'h': case '?': @@ -1134,6 +1127,10 @@ int main(int argc,char **argv) #endif // __UNIX_LIKE__ #ifdef __WINDOWS__ + // Uninstall legacy tap devices. New devices will automatically be installed and configured + // when tap instances are created. + WindowsEthernetTap::destroyAllLegacyPersistentTapDevices(); + if (winRunFromCommandLine) { // Running in "interactive" mode (mostly for debugging) if (IsCurrentUserLocalAdministrator() != TRUE) { diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index b2d3ed8b8..d8ba1f982 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,9 @@ #include #include #include +#include +#include +#include #include #include @@ -55,15 +59,28 @@ #include "..\windows\TapDriver6\tap-windows.h" -// ff:ff:ff:ff:ff:ff with no ADI -//static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); - +// Create a fake unused default route to force detection of network type on networks without gateways #define ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE +// Function signatures of dynamically loaded functions, from newdev.h, setupapi.h, and cfgmgr32.h +typedef BOOL (WINAPI *UpdateDriverForPlugAndPlayDevicesA_t)(_In_opt_ HWND hwndParent,_In_ LPCSTR HardwareId,_In_ LPCSTR FullInfPath,_In_ DWORD InstallFlags,_Out_opt_ PBOOL bRebootRequired); +typedef BOOL (WINAPI *SetupDiGetINFClassA_t)(_In_ PCSTR InfName,_Out_ LPGUID ClassGuid,_Out_writes_(ClassNameSize) PSTR ClassName,_In_ DWORD ClassNameSize,_Out_opt_ PDWORD RequiredSize); +typedef HDEVINFO (WINAPI *SetupDiCreateDeviceInfoList_t)(_In_opt_ CONST GUID *ClassGuid,_In_opt_ HWND hwndParent); +typedef BOOL (WINAPI *SetupDiCreateDeviceInfoA_t)(_In_ HDEVINFO DeviceInfoSet,_In_ PCSTR DeviceName,_In_ CONST GUID *ClassGuid,_In_opt_ PCSTR DeviceDescription,_In_opt_ HWND hwndParent,_In_ DWORD CreationFlags,_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiSetDeviceRegistryPropertyA_t)(_In_ HDEVINFO DeviceInfoSet,_Inout_ PSP_DEVINFO_DATA DeviceInfoData,_In_ DWORD Property,_In_reads_bytes_opt_(PropertyBufferSize) CONST BYTE *PropertyBuffer,_In_ DWORD PropertyBufferSize); +typedef BOOL (WINAPI *SetupDiCallClassInstaller_t)(_In_ DI_FUNCTION InstallFunction,_In_ HDEVINFO DeviceInfoSet,_In_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiDestroyDeviceInfoList_t)(_In_ HDEVINFO DeviceInfoSet); +typedef HDEVINFO (WINAPI *SetupDiGetClassDevsExA_t)(_In_opt_ CONST GUID *ClassGuid,_In_opt_ PCSTR Enumerator,_In_opt_ HWND hwndParent,_In_ DWORD Flags,_In_opt_ HDEVINFO DeviceInfoSet,_In_opt_ PCSTR MachineName,_Reserved_ PVOID Reserved); +typedef BOOL (WINAPI *SetupDiOpenDeviceInfoA_t)(_In_ HDEVINFO DeviceInfoSet,_In_ PCSTR DeviceInstanceId,_In_opt_ HWND hwndParent,_In_ DWORD OpenFlags,_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiEnumDeviceInfo_t)(_In_ HDEVINFO DeviceInfoSet,_In_ DWORD MemberIndex,_Out_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiSetClassInstallParamsA_t)(_In_ HDEVINFO DeviceInfoSet,_In_opt_ PSP_DEVINFO_DATA DeviceInfoData,_In_reads_bytes_opt_(ClassInstallParamsSize) PSP_CLASSINSTALL_HEADER ClassInstallParams,_In_ DWORD ClassInstallParamsSize); +typedef CONFIGRET (WINAPI *CM_Get_Device_ID_ExA_t)(_In_ DEVINST dnDevInst,_Out_writes_(BufferLen) PSTR Buffer,_In_ ULONG BufferLen,_In_ ULONG ulFlags,_In_opt_ HMACHINE hMachine); + namespace ZeroTier { namespace { +// Static/singleton class that when initialized loads a bunch of environment information and a few dynamically loaded DLLs class WindowsEthernetTapEnv { public: @@ -71,29 +88,349 @@ public: { #ifdef _WIN64 is64Bit = TRUE; - devcon = "\\devcon_x64.exe"; - tapDriverNdis5 = "\\tap-windows\\x64\\zttap200.inf"; - tapDriverNdis6 = "\\tap-windows\\x64\\zttap300.inf"; + tapDriverPath = "\\tap-windows\\x64\\zttap300.inf"; #else is64Bit = FALSE; IsWow64Process(GetCurrentProcess(),&is64Bit); - devcon = ((is64Bit == TRUE) ? "\\devcon_x64.exe" : "\\devcon_x86.exe"); - tapDriverNdis5 = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap200.inf" : "\\tap-windows\\x86\\zttap200.inf"); - tapDriverNdis6 = ((is64Bit == TRUE) ? "\\tap-windows\\x64\\zttap300.inf" : "\\tap-windows\\x86\\zttap300.inf"); + if (is64Bit) { + fprintf(stderr,"FATAL: you must use the 64-bit ZeroTier One service on 64-bit Windows systems\r\n"); + _exit(1); + } + tapDriverPath = "\\tap-windows\\x86\\zttap300.inf"; #endif + tapDriverName = "zttap300"; + + setupApiMod = LoadLibraryA("setupapi.dll"); + if (!setupApiMod) { + fprintf(stderr,"FATAL: unable to dynamically load setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiGetINFClassA = (SetupDiGetINFClassA_t)GetProcAddress(setupApiMod,"SetupDiGetINFClassA"))) { + fprintf(stderr,"FATAL: SetupDiGetINFClassA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCreateDeviceInfoList = (SetupDiCreateDeviceInfoList_t)GetProcAddress(setupApiMod,"SetupDiCreateDeviceInfoList"))) { + fprintf(stderr,"FATAL: SetupDiCreateDeviceInfoList not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCreateDeviceInfoA = (SetupDiCreateDeviceInfoA_t)GetProcAddress(setupApiMod,"SetupDiCreateDeviceInfoA"))) { + fprintf(stderr,"FATAL: SetupDiCreateDeviceInfoA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiSetDeviceRegistryPropertyA = (SetupDiSetDeviceRegistryPropertyA_t)GetProcAddress(setupApiMod,"SetupDiSetDeviceRegistryPropertyA"))) { + fprintf(stderr,"FATAL: SetupDiSetDeviceRegistryPropertyA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCallClassInstaller = (SetupDiCallClassInstaller_t)GetProcAddress(setupApiMod,"SetupDiCallClassInstaller"))) { + fprintf(stderr,"FATAL: SetupDiCallClassInstaller not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiDestroyDeviceInfoList = (SetupDiDestroyDeviceInfoList_t)GetProcAddress(setupApiMod,"SetupDiDestroyDeviceInfoList"))) { + fprintf(stderr,"FATAL: SetupDiDestroyDeviceInfoList not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiGetClassDevsExA = (SetupDiGetClassDevsExA_t)GetProcAddress(setupApiMod,"SetupDiGetClassDevsExA"))) { + fprintf(stderr,"FATAL: SetupDiGetClassDevsExA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiOpenDeviceInfoA = (SetupDiOpenDeviceInfoA_t)GetProcAddress(setupApiMod,"SetupDiOpenDeviceInfoA"))) { + fprintf(stderr,"FATAL: SetupDiOpenDeviceInfoA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiEnumDeviceInfo = (SetupDiEnumDeviceInfo_t)GetProcAddress(setupApiMod,"SetupDiEnumDeviceInfo"))) { + fprintf(stderr,"FATAL: SetupDiEnumDeviceInfo not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiSetClassInstallParamsA = (SetupDiSetClassInstallParamsA_t)GetProcAddress(setupApiMod,"SetupDiSetClassInstallParamsA"))) { + fprintf(stderr,"FATAL: SetupDiSetClassInstallParamsA not found in setupapi.dll\r\n"); + _exit(1); + } + + newDevMod = LoadLibraryA("newdev.dll"); + if (!newDevMod) { + fprintf(stderr,"FATAL: unable to dynamically load newdev.dll\r\n"); + _exit(1); + } + if (!(this->UpdateDriverForPlugAndPlayDevicesA = (UpdateDriverForPlugAndPlayDevicesA_t)GetProcAddress(newDevMod,"UpdateDriverForPlugAndPlayDevicesA"))) { + fprintf(stderr,"FATAL: UpdateDriverForPlugAndPlayDevicesA not found in newdev.dll\r\n"); + _exit(1); + } + + cfgMgrMod = LoadLibraryA("cfgmgr32.dll"); + if (!cfgMgrMod) { + fprintf(stderr,"FATAL: unable to dynamically load cfgmgr32.dll\r\n"); + _exit(1); + } + if (!(this->CM_Get_Device_ID_ExA = (CM_Get_Device_ID_ExA_t)GetProcAddress(cfgMgrMod,"CM_Get_Device_ID_ExA"))) { + fprintf(stderr,"FATAL: CM_Get_Device_ID_ExA not found in cfgmgr32.dll\r\n"); + _exit(1); + } } - BOOL is64Bit; - const char *devcon; - const char *tapDriverNdis5; - const char *tapDriverNdis6; + + BOOL is64Bit; // is the system 64-bit, regardless of whether this binary is or not + std::string tapDriverPath; + std::string tapDriverName; + + UpdateDriverForPlugAndPlayDevicesA_t UpdateDriverForPlugAndPlayDevicesA; + + SetupDiGetINFClassA_t SetupDiGetINFClassA; + SetupDiCreateDeviceInfoList_t SetupDiCreateDeviceInfoList; + SetupDiCreateDeviceInfoA_t SetupDiCreateDeviceInfoA; + SetupDiSetDeviceRegistryPropertyA_t SetupDiSetDeviceRegistryPropertyA; + SetupDiCallClassInstaller_t SetupDiCallClassInstaller; + SetupDiDestroyDeviceInfoList_t SetupDiDestroyDeviceInfoList; + SetupDiGetClassDevsExA_t SetupDiGetClassDevsExA; + SetupDiOpenDeviceInfoA_t SetupDiOpenDeviceInfoA; + SetupDiEnumDeviceInfo_t SetupDiEnumDeviceInfo; + SetupDiSetClassInstallParamsA_t SetupDiSetClassInstallParamsA; + + CM_Get_Device_ID_ExA_t CM_Get_Device_ID_ExA; + +private: + HMODULE setupApiMod; + HMODULE newDevMod; + HMODULE cfgMgrMod; }; static const WindowsEthernetTapEnv WINENV; // Only create or delete devices one at a time static Mutex _systemTapInitLock; +// Only perform installation or uninstallation options one at a time +static Mutex _systemDeviceManagementLock; + } // anonymous namespace +std::string WindowsEthernetTap::addNewPersistentTapDevice(const char *pathToInf) +{ + Mutex::Lock _l(_systemDeviceManagementLock); + + GUID classGuid; + char className[4096]; + if (!WINENV.SetupDiGetINFClassA(pathToInf,&classGuid,className,sizeof(className),(PDWORD)0)) { + return std::string("SetupDiGetINFClassA() failed -- unable to read zttap driver INF file"); + } + + HDEVINFO deviceInfoSet = WINENV.SetupDiCreateDeviceInfoList(&classGuid,(HWND)0); + if (deviceInfoSet == INVALID_HANDLE_VALUE) { + return std::string("SetupDiCreateDeviceInfoList() failed"); + } + + SP_DEVINFO_DATA deviceInfoData; + memset(&deviceInfoData,0,sizeof(deviceInfoData)); + deviceInfoData.cbSize = sizeof(deviceInfoData); + if (!WINENV.SetupDiCreateDeviceInfoA(deviceInfoSet,className,&classGuid,(PCSTR)0,(HWND)0,DICD_GENERATE_ID,&deviceInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiCreateDeviceInfoA() failed"); + } + + if (!WINENV.SetupDiSetDeviceRegistryPropertyA(deviceInfoSet,&deviceInfoData,SPDRP_HARDWAREID,(const BYTE *)WINENV.tapDriverName.c_str(),(DWORD)(WINENV.tapDriverName.length() + 1))) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiSetDeviceRegistryPropertyA() failed"); + } + + if (!WINENV.SetupDiCallClassInstaller(DIF_REGISTERDEVICE,deviceInfoSet,&deviceInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed"); + } + + BOOL rebootRequired = FALSE; + if (!WINENV.UpdateDriverForPlugAndPlayDevicesA((HWND)0,WINENV.tapDriverName.c_str(),pathToInf,INSTALLFLAG_FORCE|INSTALLFLAG_NONINTERACTIVE,&rebootRequired)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("UpdateDriverForPlugAndPlayDevices() failed -- unable to install driver on device"); + } + + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + + return std::string(); +} + +std::string WindowsEthernetTap::destroyAllLegacyPersistentTapDevices() +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + + std::set instanceIdPathsToRemove; + { + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + return std::string("Could not open registry key"); + + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + + if ((!strnicmp(data,"zttap",5))&&(WINENV.tapDriverName != data)) { + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + if (instanceIdPath.length() != 0) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { + std::string err = deletePersistentTapDevice(iidp->c_str()); + if (err.length() > 0) + return err; + } + + return std::string(); +} + +std::string WindowsEthernetTap::destroyAllPersistentTapDevices() +{ + char subkeyName[4096]; + char subkeyClass[4096]; + char data[4096]; + + std::set instanceIdPathsToRemove; + { + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + return std::string("Could not open registry key"); + + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + + if (!strnicmp(data,"zttap",5)) { + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + if (instanceIdPath.length() != 0) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { + std::string err = deletePersistentTapDevice(iidp->c_str()); + if (err.length() > 0) + return err; + } + + return std::string(); +} + +std::string WindowsEthernetTap::deletePersistentTapDevice(const char *instanceId) +{ + char iid[256]; + SP_REMOVEDEVICE_PARAMS rmdParams; + + memset(&rmdParams,0,sizeof(rmdParams)); + rmdParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + rmdParams.ClassInstallHeader.InstallFunction = DIF_REMOVE; + rmdParams.Scope = DI_REMOVEDEVICE_GLOBAL; + rmdParams.HwProfile = 0; + + Mutex::Lock _l(_systemDeviceManagementLock); + + HDEVINFO devInfo = WINENV.SetupDiGetClassDevsExA((const GUID *)0,(PCSTR)0,(HWND)0,DIGCF_ALLCLASSES,(HDEVINFO)0,(PCSTR)0,(PVOID)0); + if (devInfo == INVALID_HANDLE_VALUE) + return std::string("SetupDiGetClassDevsExA() failed"); + WINENV.SetupDiOpenDeviceInfoA(devInfo,instanceId,(HWND)0,0,(PSP_DEVINFO_DATA)0); + + SP_DEVINFO_DATA devInfoData; + memset(&devInfoData,0,sizeof(devInfoData)); + devInfoData.cbSize = sizeof(devInfoData); + for(DWORD devIndex=0;WINENV.SetupDiEnumDeviceInfo(devInfo,devIndex,&devInfoData);devIndex++) { + if ((WINENV.CM_Get_Device_ID_ExA(devInfoData.DevInst,iid,sizeof(iid),0,(HMACHINE)0) == CR_SUCCESS)&&(!strcmp(iid,instanceId))) { + if (!WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,&rmdParams.ClassInstallHeader,sizeof(rmdParams))) { + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("SetupDiSetClassInstallParams() failed"); + } + + if (!WINENV.SetupDiCallClassInstaller(DIF_REMOVE,devInfo,&devInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("SetupDiCallClassInstaller(DIF_REMOVE) failed"); + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string(); + } + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("instance ID not found"); +} + +bool WindowsEthernetTap::setPersistentTapDeviceState(const char *instanceId,bool enabled) +{ + char iid[256]; + SP_PROPCHANGE_PARAMS params; + + Mutex::Lock _l(_systemDeviceManagementLock); + + HDEVINFO devInfo = WINENV.SetupDiGetClassDevsExA((const GUID *)0,(PCSTR)0,(HWND)0,DIGCF_ALLCLASSES,(HDEVINFO)0,(PCSTR)0,(PVOID)0); + if (devInfo == INVALID_HANDLE_VALUE) + return false; + WINENV.SetupDiOpenDeviceInfoA(devInfo,instanceId,(HWND)0,0,(PSP_DEVINFO_DATA)0); + + SP_DEVINFO_DATA devInfoData; + memset(&devInfoData,0,sizeof(devInfoData)); + devInfoData.cbSize = sizeof(devInfoData); + for(DWORD devIndex=0;WINENV.SetupDiEnumDeviceInfo(devInfo,devIndex,&devInfoData);devIndex++) { + if ((WINENV.CM_Get_Device_ID_ExA(devInfoData.DevInst,iid,sizeof(iid),0,(HMACHINE)0) == CR_SUCCESS)&&(!strcmp(iid,instanceId))) { + memset(¶ms,0,sizeof(params)); + params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + params.StateChange = enabled ? DICS_ENABLE : DICS_DISABLE; + params.Scope = DICS_FLAG_GLOBAL; + params.HwProfile = 0; + + WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,¶ms.ClassInstallHeader,sizeof(params)); + WINENV.SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,devInfo,&devInfoData); + + memset(¶ms,0,sizeof(params)); + params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + params.StateChange = enabled ? DICS_ENABLE : DICS_DISABLE; + params.Scope = DICS_FLAG_CONFIGSPECIFIC; + params.HwProfile = 0; + + WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,¶ms.ClassInstallHeader,sizeof(params)); + WINENV.SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,devInfo,&devInfoData); + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return true; + } + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return false; +} + WindowsEthernetTap::WindowsEthernetTap( const char *hp, const MAC &mac, @@ -118,35 +455,21 @@ WindowsEthernetTap::WindowsEthernetTap( char subkeyClass[4096]; char data[4096]; char tag[24]; + std::set existingDeviceInstances; + std::string mySubkeyName; if (mtu > 2800) throw std::runtime_error("MTU too large for Windows tap"); - Mutex::Lock _l(_systemTapInitLock); + // We "tag" registry entries with the network ID to identify persistent devices + Utils::snprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); - // Use NDIS5 if it's installed, since we don't want to switch out the driver on - // pre-existing installs (yet). We won't ship NDIS5 anymore so new installs will - // use NDIS6. - std::string tapDriverPath(_pathToHelpers + WINENV.tapDriverNdis5); - const char *tapDriverName = "zttap200"; - if (::PathFileExistsA(tapDriverPath.c_str()) == FALSE) { - tapDriverPath = _pathToHelpers + WINENV.tapDriverNdis6; - tapDriverName = "zttap300"; - if (::PathFileExistsA(tapDriverPath.c_str()) == FALSE) { - throw std::runtime_error("no tap driver available: cannot find zttap300.inf (NDIS6) or zttap200.inf (NDIS5) under home path"); - } - } + Mutex::Lock _l(_systemTapInitLock); HKEY nwAdapters; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) throw std::runtime_error("unable to open registry key for network adapter enumeration"); - std::set existingDeviceInstances; - std::string mySubkeyName; - - // We "tag" registry entries with the network ID to identify persistent devices - Utils::snprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); - // Look for the tap instance that corresponds with this network for(DWORD subkeyIndex=0;;++subkeyIndex) { DWORD type; @@ -158,8 +481,9 @@ WindowsEthernetTap::WindowsEthernetTap( type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { - data[dataLen] = '\0'; - if (!strnicmp(data,"zttap",5)) { + data[dataLen] = (char)0; + + if (WINENV.tapDriverName == data) { std::string instanceId; type = 0; dataLen = sizeof(data); @@ -196,34 +520,9 @@ WindowsEthernetTap::WindowsEthernetTap( // If there is no device, try to create one bool creatingNewDevice = (_netCfgInstanceId.length() == 0); if (creatingNewDevice) { - // Log devcon output to a file - HANDLE devconLog = CreateFileA((_pathToHelpers + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - // Execute devcon to create a new tap device - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - SetFilePointer(devconLog,0,0,FILE_END); - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _pathToHelpers + WINENV.devcon + "\" install \"" + tapDriverPath + "\" " + tapDriverName).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - RegCloseKey(nwAdapters); - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - throw std::runtime_error(std::string("unable to find or execute devcon at ") + WINENV.devcon); - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); + std::string errm = addNewPersistentTapDevice((std::string(_pathToHelpers) + WINENV.tapDriverPath).c_str()); + if (errm.length() != 0) + throw std::runtime_error(errm); // Scan for the new instance by simply looking for taps that weren't originally there... for(DWORD subkeyIndex=0;;++subkeyIndex) { @@ -237,7 +536,8 @@ WindowsEthernetTap::WindowsEthernetTap( dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { data[dataLen] = '\0'; - if (!strnicmp(data,"zttap",5)) { + + if (WINENV.tapDriverName == data) { type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { @@ -281,6 +581,7 @@ WindowsEthernetTap::WindowsEthernetTap( RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"*IfType",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); if (creatingNewDevice) { + // Set EnableDHCP to 0 by default on new devices tmp = 0; RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"EnableDHCP",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); } @@ -291,7 +592,7 @@ WindowsEthernetTap::WindowsEthernetTap( } { - char nobraces[128]; + char nobraces[128]; // strip braces from GUID before converting it, because Windows const char *nbtmp1 = _netCfgInstanceId.c_str(); char *nbtmp2 = nobraces; while (*nbtmp1) { @@ -304,17 +605,15 @@ WindowsEthernetTap::WindowsEthernetTap( throw std::runtime_error("unable to convert instance ID GUID to native GUID (invalid NetCfgInstanceId in registry?)"); } - // Look up interface LUID... why are there (at least) four fucking ways to refer to a network device in Windows? + // Get the LUID, which is one of like four fucking ways to refer to a network device in Windows if (ConvertInterfaceGuidToLuid(&_deviceGuid,&_deviceLuid) != NO_ERROR) throw std::runtime_error("unable to convert device interface GUID to LUID"); - // Certain functions can now work (e.g. ips()) _initialized = true; if (friendlyName) setFriendlyName(friendlyName); - // Start background thread that actually performs I/O _injectSemaphore = CreateSemaphore(NULL,0,1,NULL); _thread = Thread::start(this); } @@ -325,7 +624,7 @@ WindowsEthernetTap::~WindowsEthernetTap() ReleaseSemaphore(_injectSemaphore,1,NULL); Thread::join(_thread); CloseHandle(_injectSemaphore); - _disableTapDevice(); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); } void WindowsEthernetTap::setEnabled(bool en) @@ -572,14 +871,15 @@ void WindowsEthernetTap::threadMain() try { while (_run) { - _enableTapDevice(); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); Sleep(500); _tap = CreateFileA(tapPath,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED,NULL); if (_tap == INVALID_HANDLE_VALUE) { - _disableTapDevice(); - _enableTapDevice(); - Sleep(1000); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); + Sleep(500); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); + Sleep(500); continue; } @@ -761,131 +1061,6 @@ void WindowsEthernetTap::threadMain() } catch ( ... ) {} // catch unexpected exceptions -- this should not happen but would prevent program crash or other weird issues since threads should not throw } -void WindowsEthernetTap::destroyAllPersistentTapDevices(const char *pathToHelpers) -{ - char subkeyName[4096]; - char subkeyClass[4096]; - char data[4096]; - - std::set instanceIdPathsToRemove; - { - HKEY nwAdapters; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) - return; - - for(DWORD subkeyIndex=0;;++subkeyIndex) { - DWORD type; - DWORD dataLen; - DWORD subkeyNameLen = sizeof(subkeyName); - DWORD subkeyClassLen = sizeof(subkeyClass); - FILETIME lastWriteTime; - if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { - type = 0; - dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { - data[dataLen] = '\0'; - if (!strnicmp(data,"zttap",5)) { - std::string instanceIdPath; - type = 0; - dataLen = sizeof(data); - if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) - instanceIdPath.assign(data,dataLen); - if (instanceIdPath.length() != 0) - instanceIdPathsToRemove.insert(instanceIdPath); - } - } - } else break; // end of list or failure - } - - RegCloseKey(nwAdapters); - } - - for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) - deletePersistentTapDevice(pathToHelpers,iidp->c_str()); -} - -void WindowsEthernetTap::deletePersistentTapDevice(const char *pathToHelpers,const char *instanceId) -{ - HANDLE devconLog = CreateFileA((std::string(pathToHelpers) + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - SetFilePointer(devconLog,0,0,FILE_END); - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (CreateProcessA(NULL,(LPSTR)(std::string("\"") + pathToHelpers + WINENV.devcon + "\" remove @" + instanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); -} - -bool WindowsEthernetTap::_disableTapDevice() -{ - HANDLE devconLog = CreateFileA((_pathToHelpers + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _pathToHelpers + WINENV.devcon + "\" disable @" + _deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - return false; - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - - return true; -} - -bool WindowsEthernetTap::_enableTapDevice() -{ - HANDLE devconLog = CreateFileA((_pathToHelpers + "\\devcon.log").c_str(),GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); - if (devconLog != INVALID_HANDLE_VALUE) - SetFilePointer(devconLog,0,0,FILE_END); - - STARTUPINFOA startupInfo; - startupInfo.cb = sizeof(startupInfo); - if (devconLog != INVALID_HANDLE_VALUE) { - startupInfo.hStdOutput = devconLog; - startupInfo.hStdError = devconLog; - } - PROCESS_INFORMATION processInfo; - memset(&startupInfo,0,sizeof(STARTUPINFOA)); - memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); - if (!CreateProcessA(NULL,(LPSTR)(std::string("\"") + _pathToHelpers + WINENV.devcon + "\" enable @" + _deviceInstanceId).c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - return false; - } - WaitForSingleObject(processInfo.hProcess,INFINITE); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - if (devconLog != INVALID_HANDLE_VALUE) - CloseHandle(devconLog); - - return true; -} - NET_IFINDEX WindowsEthernetTap::_getDeviceIndex() { MIB_IF_TABLE2 *ift = (MIB_IF_TABLE2 *)0; diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index 944b53f33..97113d973 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -48,6 +48,45 @@ namespace ZeroTier { class WindowsEthernetTap { public: + /** + * Installs a new instance of the ZT tap driver + * + * @param pathToInf Path to zttap driver .inf file + * @return Empty string on success, otherwise an error message + */ + static std::string addNewPersistentTapDevice(const char *pathToInf); + + /** + * Uninstalls all persistent tap devices that have legacy drivers + * + * @return Empty string on success, otherwise an error message + */ + static std::string destroyAllLegacyPersistentTapDevices(); + + /** + * Uninstalls all persistent tap devices on the system + * + * @return Empty string on success, otherwise an error message + */ + static std::string destroyAllPersistentTapDevices(); + + /** + * Uninstall a specific persistent tap device by instance ID + * + * @param instanceId Device instance ID + * @return Empty string on success, otherwise an error message + */ + static std::string deletePersistentTapDevice(const char *instanceId); + + /** + * Disable a persistent tap device by instance ID + * + * @param instanceId Device instance ID + * @param enabled Enable device? + * @return True if device was found and disabled + */ + static bool setPersistentTapDeviceState(const char *instanceId,bool enabled); + WindowsEthernetTap( const char *hp, const MAC &mac, @@ -77,9 +116,6 @@ public: void threadMain() throw(); - static void destroyAllPersistentTapDevices(const char *pathToHelpers); - static void deletePersistentTapDevice(const char *pathToHelpers,const char *instanceId); - private: bool _disableTapDevice(); bool _enableTapDevice(); diff --git a/service/OneService.cpp b/service/OneService.cpp index 06e37a45f..4ee473f87 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -982,7 +982,7 @@ public: _tapAssignedIps.erase(nwid); #ifdef __WINDOWS__ if ((op == ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY)&&(winInstanceId.length() > 0)) - WindowsEthernetTap::deletePersistentTapDevice(_homePath.c_str(),winInstanceId.c_str()); + WindowsEthernetTap::deletePersistentTapDevice(winInstanceId.c_str()); #endif } break; diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index 14bf7c3eb..0a43a6f60 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -217,7 +217,7 @@ true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false @@ -232,7 +232,7 @@ true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false @@ -257,7 +257,7 @@ true true true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x86\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false @@ -282,7 +282,7 @@ true true true - $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;newdev.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + $(SolutionDir)..\ext\bin\miniupnpc\windows-x64\miniupnpc.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false