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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⪼TP!m3i%$bUwbDX41P!2h^i$OE_I-l$U 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 diff --git a/ext/bin/devcon/devcon_x86.exe b/ext/bin/devcon/devcon_x86.exe deleted file mode 100644 index ce752b6e905f4355fa0a489d5a2adc98affd09b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86360 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 Mm+UbyFl#3KFM#Pj9smFU 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