/* * Copyright (c)2019 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * * Change Date: 2026-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. */ /****/ #include "WindowsEthernetTap.hpp" #include "../node/Constants.hpp" #include "../node/Mutex.hpp" #include "../node/Utils.hpp" #include "..\windows\TapDriver6\tap-windows.h" #include "OSUtils.hpp" #include "WinDNSHelper.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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); typedef BOOL( WINAPI* SetupDiGetDeviceInstanceIdA_t)(_In_ HDEVINFO DeviceInfoSet, _In_ PSP_DEVINFO_DATA DeviceInfoData, _Out_writes_opt_(DeviceInstanceIdSize) PSTR DeviceInstanceId, _In_ DWORD DeviceInstanceIdSize, _Out_opt_ PDWORD RequiredSize); namespace ZeroTier { namespace { // Static/singleton class that when initialized loads a bunch of environment information and a few dynamically loaded DLLs class WindowsEthernetTapEnv { public: WindowsEthernetTapEnv() { #ifdef _WIN64 is64Bit = TRUE; // tapDriverPath = "\\tap-windows\\x64\\zttap300.inf"; #else is64Bit = FALSE; IsWow64Process(GetCurrentProcess(), &is64Bit); 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"; tapDriverPath = "\\zttap300.inf"; 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); } if (! (this->SetupDiGetDeviceInstanceIdA = (SetupDiGetDeviceInstanceIdA_t)GetProcAddress(setupApiMod, "SetupDiGetDeviceInstanceIdA"))) { fprintf(stderr, "FATAL: SetupDiGetDeviceInstanceIdA 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; // 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; SetupDiGetDeviceInstanceIdA_t SetupDiGetDeviceInstanceIdA; 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, std::string& deviceInstanceId) { Mutex::Lock _l(_systemDeviceManagementLock); GUID classGuid; char className[1024]; 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"); } // HACK: During upgrades, this can fail while the installer is still running. So make 60 attempts // with a 1s delay between each attempt. bool driverInstalled = false; for (int retryCounter = 0; retryCounter < 60; ++retryCounter) { BOOL rebootRequired = FALSE; if (WINENV.UpdateDriverForPlugAndPlayDevicesA((HWND)0, WINENV.tapDriverName.c_str(), pathToInf, INSTALLFLAG_FORCE | INSTALLFLAG_NONINTERACTIVE, &rebootRequired)) { driverInstalled = true; break; } else Sleep(1000); } if (! driverInstalled) { WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); return std::string("UpdateDriverForPlugAndPlayDevices() failed (made 60 attempts)"); } char iidbuf[1024]; DWORD iidReqSize = sizeof(iidbuf); if (WINENV.SetupDiGetDeviceInstanceIdA(deviceInfoSet, &deviceInfoData, iidbuf, sizeof(iidbuf), &iidReqSize)) { deviceInstanceId = iidbuf; } // failure here is not fatal since we only need this on Vista and 2008 -- other versions fill it into the registry automatically WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); return std::string(); } std::string WindowsEthernetTap::destroyAllLegacyPersistentTapDevices() { char subkeyName[1024]; char subkeyClass[1024]; char data[1024]; 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); } std::string errlist; for (std::set::iterator iidp(instanceIdPathsToRemove.begin()); iidp != instanceIdPathsToRemove.end(); ++iidp) { std::string err = deletePersistentTapDevice(iidp->c_str()); if (err.length() > 0) { if (errlist.length() > 0) errlist.push_back(','); errlist.append(err); } } return errlist; } std::string WindowsEthernetTap::destroyAllPersistentTapDevices() { char subkeyName[1024]; char subkeyClass[1024]; char data[1024]; 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); } std::string errlist; for (std::set::iterator iidp(instanceIdPathsToRemove.begin()); iidp != instanceIdPathsToRemove.end(); ++iidp) { std::string err = deletePersistentTapDevice(iidp->c_str()); if (err.length() > 0) { if (errlist.length() > 0) errlist.push_back(','); errlist.append(err); } } return errlist; } 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, unsigned int mtu, unsigned int metric, uint64_t nwid, const char* friendlyName, void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int), void* arg) : _handler(handler) , _arg(arg) , _mac(mac) , _nwid(nwid) , _mtu(mtu) , _tap(INVALID_HANDLE_VALUE) , _friendlyName(friendlyName) , _injectSemaphore(INVALID_HANDLE_VALUE) , _pathToHelpers(hp) , _run(true) , _initialized(false) , _enabled(true) , _lastIfAddrsUpdate(0) { char subkeyName[1024]; char subkeyClass[1024]; char data[1024]; char tag[24]; // We "tag" registry entries with the network ID to identify persistent devices OSUtils::ztsnprintf(tag, sizeof(tag), "%.16llx", (unsigned long long)nwid); 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"); // Look for the tap instance that corresponds with this network 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] = (char)0; if (WINENV.tapDriverName == data) { std::string instanceId; type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters, subkeyName, "NetCfgInstanceId", RRF_RT_ANY, &type, (PVOID)data, &dataLen) == ERROR_SUCCESS) instanceId.assign(data, dataLen); 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 ((_netCfgInstanceId.length() == 0) && (instanceId.length() != 0) && (instanceIdPath.length() != 0)) { type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters, subkeyName, "_ZeroTierTapIdentifier", RRF_RT_ANY, &type, (PVOID)data, &dataLen) == ERROR_SUCCESS) { data[dataLen] = '\0'; if (! strcmp(data, tag)) { _netCfgInstanceId = instanceId; _deviceInstanceId = instanceIdPath; _mySubkeyName = subkeyName; break; // found it! } } } } } } else break; // no more subkeys or error occurred enumerating them } // If there is no device, try to create one bool creatingNewDevice = (_netCfgInstanceId.length() == 0); std::string newDeviceInstanceId; if (creatingNewDevice) { for (int getNewAttemptCounter = 0; getNewAttemptCounter < 2; ++getNewAttemptCounter) { 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 (WINENV.tapDriverName == data) { type = 0; dataLen = sizeof(data); if ((RegGetValueA(nwAdapters, subkeyName, "_ZeroTierTapIdentifier", RRF_RT_ANY, &type, (PVOID)data, &dataLen) != ERROR_SUCCESS) || (dataLen <= 0)) { type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters, subkeyName, "NetCfgInstanceId", RRF_RT_ANY, &type, (PVOID)data, &dataLen) == ERROR_SUCCESS) { RegSetKeyValueA(nwAdapters, subkeyName, "_ZeroTierTapIdentifier", REG_SZ, tag, (DWORD)(strlen(tag) + 1)); _netCfgInstanceId.assign(data, dataLen); type = 0; dataLen = sizeof(data); if (RegGetValueA(nwAdapters, subkeyName, "DeviceInstanceID", RRF_RT_ANY, &type, (PVOID)data, &dataLen) == ERROR_SUCCESS) _deviceInstanceId.assign(data, dataLen); _mySubkeyName = subkeyName; // Disable DHCP by default on new devices HKEY tcpIpInterfaces; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces", 0, KEY_READ | KEY_WRITE, &tcpIpInterfaces) == ERROR_SUCCESS) { DWORD enable = 0; RegSetKeyValueA(tcpIpInterfaces, _netCfgInstanceId.c_str(), "EnableDHCP", REG_DWORD, &enable, sizeof(enable)); RegCloseKey(tcpIpInterfaces); } break; // found an unused zttap device } } } } } else break; // no more keys or error occurred } if (_netCfgInstanceId.length() > 0) { break; // found an unused zttap device } else { // no unused zttap devices, so create one std::string errm = addNewPersistentTapDevice((std::string(_pathToHelpers) + WINENV.tapDriverPath).c_str(), newDeviceInstanceId); if (errm.length() > 0) throw std::runtime_error(std::string("unable to create new device instance: ") + errm); } } } if (_netCfgInstanceId.length() > 0) { char tmps[64]; unsigned int tmpsl = OSUtils::ztsnprintf(tmps, sizeof(tmps), "%.2X-%.2X-%.2X-%.2X-%.2X-%.2X", (unsigned int)mac[0], (unsigned int)mac[1], (unsigned int)mac[2], (unsigned int)mac[3], (unsigned int)mac[4], (unsigned int)mac[5]) + 1; RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "NetworkAddress", REG_SZ, tmps, tmpsl); RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "MAC", REG_SZ, tmps, tmpsl); tmpsl = OSUtils::ztsnprintf(tmps, sizeof(tmps), "%d", mtu); RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "MTU", REG_SZ, tmps, tmpsl); DWORD tmp = 0; RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "*NdisDeviceType", REG_DWORD, (LPCVOID)&tmp, sizeof(tmp)); tmp = IF_TYPE_ETHERNET_CSMACD; RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "*IfType", REG_DWORD, (LPCVOID)&tmp, sizeof(tmp)); if (creatingNewDevice) { // Vista/2008 does not set this if (newDeviceInstanceId.length() > 0) RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "DeviceInstanceID", REG_SZ, newDeviceInstanceId.c_str(), (DWORD)newDeviceInstanceId.length()); // Set EnableDHCP to 0 by default on new devices tmp = 0; RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "EnableDHCP", REG_DWORD, (LPCVOID)&tmp, sizeof(tmp)); } RegCloseKey(nwAdapters); } else { RegCloseKey(nwAdapters); throw std::runtime_error("unable to find or create tap adapter"); } { char nobraces[128]; // strip braces from GUID before converting it, because Windows const char* nbtmp1 = _netCfgInstanceId.c_str(); char* nbtmp2 = nobraces; while (*nbtmp1) { if ((*nbtmp1 != '{') && (*nbtmp1 != '}')) *nbtmp2++ = *nbtmp1; ++nbtmp1; } *nbtmp2 = (char)0; if (UuidFromStringA((RPC_CSTR)nobraces, &_deviceGuid) != RPC_S_OK) throw std::runtime_error("unable to convert instance ID GUID to native GUID (invalid NetCfgInstanceId in registry?)"); } // 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"); //_initialized = true; if (friendlyName) setFriendlyName(friendlyName); _injectSemaphore = CreateSemaphore(NULL, 0, 1, NULL); _thread = Thread::start(this); } WindowsEthernetTap::~WindowsEthernetTap() { WinDNSHelper::removeDNS(_nwid); _run = false; ReleaseSemaphore(_injectSemaphore, 1, NULL); Thread::join(_thread); CloseHandle(_injectSemaphore); setPersistentTapDeviceState(_deviceInstanceId.c_str(), false); } void WindowsEthernetTap::setEnabled(bool en) { _enabled = en; } bool WindowsEthernetTap::enabled() const { return _enabled; } bool WindowsEthernetTap::addIp(const InetAddress& ip) { if (! ip.netmaskBits()) // sanity check... netmask of 0.0.0.0 is WUT? return false; Mutex::Lock _l(_assignedIps_m); if (std::find(_assignedIps.begin(), _assignedIps.end(), ip) != _assignedIps.end()) return true; _assignedIps.push_back(ip); _syncIps(); return true; } bool WindowsEthernetTap::removeIp(const InetAddress& ip) { if (ip.isV6()) return true; { Mutex::Lock _l(_assignedIps_m); std::vector::iterator aip(std::find(_assignedIps.begin(), _assignedIps.end(), ip)); if (aip != _assignedIps.end()) _assignedIps.erase(aip); } if (! _initialized) return false; try { MIB_UNICASTIPADDRESS_TABLE* ipt = (MIB_UNICASTIPADDRESS_TABLE*)0; if (GetUnicastIpAddressTable(AF_UNSPEC, &ipt) == NO_ERROR) { if ((ipt) && (ipt->NumEntries > 0)) { for (DWORD i = 0; i < (DWORD)ipt->NumEntries; ++i) { if (ipt->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { InetAddress addr; switch (ipt->Table[i].Address.si_family) { case AF_INET: addr.set(&(ipt->Table[i].Address.Ipv4.sin_addr.S_un.S_addr), 4, ipt->Table[i].OnLinkPrefixLength); break; case AF_INET6: addr.set(ipt->Table[i].Address.Ipv6.sin6_addr.u.Byte, 16, ipt->Table[i].OnLinkPrefixLength); if (addr.ipScope() == InetAddress::IP_SCOPE_LINK_LOCAL) continue; // can't remove link-local IPv6 addresses break; } if (addr == ip) { DeleteUnicastIpAddressEntry(&(ipt->Table[i])); FreeMibTable(ipt); if (ip.isV4()) { std::vector regIps(_getRegistryIPv4Value("IPAddress")); std::vector regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); char ipbuf[64]; std::string ipstr(ip.toIpString(ipbuf)); for (std::vector::iterator rip(regIps.begin()), rm(regSubnetMasks.begin()); ((rip != regIps.end()) && (rm != regSubnetMasks.end())); ++rip, ++rm) { if (*rip == ipstr) { regIps.erase(rip); regSubnetMasks.erase(rm); _setRegistryIPv4Value("IPAddress", regIps); _setRegistryIPv4Value("SubnetMask", regSubnetMasks); break; } } } return true; } } } } FreeMibTable((PVOID)ipt); } } catch (...) { } return false; } std::vector WindowsEthernetTap::ips() const { static const InetAddress linkLocalLoopback("fe80::1/64"); // what is this and why does Windows assign it? std::vector addrs; if (! _initialized) return addrs; uint64_t now = OSUtils::now(); if ((now - _lastIfAddrsUpdate) <= GETIFADDRS_CACHE_TIME) { return _ifaddrs; } _lastIfAddrsUpdate = now; try { MIB_UNICASTIPADDRESS_TABLE* ipt = (MIB_UNICASTIPADDRESS_TABLE*)0; if (GetUnicastIpAddressTable(AF_UNSPEC, &ipt) == NO_ERROR) { if ((ipt) && (ipt->NumEntries > 0)) { for (DWORD i = 0; i < (DWORD)ipt->NumEntries; ++i) { if (ipt->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { switch (ipt->Table[i].Address.si_family) { case AF_INET: { InetAddress ip(&(ipt->Table[i].Address.Ipv4.sin_addr.S_un.S_addr), 4, ipt->Table[i].OnLinkPrefixLength); if (ip != InetAddress::LO4) addrs.push_back(ip); } break; case AF_INET6: { InetAddress ip(ipt->Table[i].Address.Ipv6.sin6_addr.u.Byte, 16, ipt->Table[i].OnLinkPrefixLength); if ((ip != linkLocalLoopback) && (ip != InetAddress::LO6)) addrs.push_back(ip); } break; } } } } FreeMibTable(ipt); } } catch (...) { } // sanity check, shouldn't happen unless out of memory std::sort(addrs.begin(), addrs.end()); addrs.erase(std::unique(addrs.begin(), addrs.end()), addrs.end()); _ifaddrs = addrs; return addrs; } void WindowsEthernetTap::put(const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len) { if ((! _initialized) || (! _enabled) || (_tap == INVALID_HANDLE_VALUE) || (len > _mtu)) return; Mutex::Lock _l(_injectPending_m); _injectPending.emplace(); _injectPending.back().len = len + 14; char* const d = _injectPending.back().data; to.copyTo(d, 6); from.copyTo(d + 6, 6); d[12] = (char)((etherType >> 8) & 0xff); d[13] = (char)(etherType & 0xff); memcpy(d + 14, data, len); ReleaseSemaphore(_injectSemaphore, 1, NULL); } std::string WindowsEthernetTap::deviceName() const { char tmp[1024]; if (ConvertInterfaceLuidToNameA(&_deviceLuid, tmp, sizeof(tmp)) != NO_ERROR) return std::string("[ConvertInterfaceLuidToName() failed]"); return std::string(tmp); } void WindowsEthernetTap::setFriendlyName(const char* dn) { if (! _initialized) return; HKEY ifp; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, (std::string("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\") + _netCfgInstanceId).c_str(), 0, KEY_READ | KEY_WRITE, &ifp) == ERROR_SUCCESS) { RegSetKeyValueA(ifp, "Connection", "Name", REG_SZ, (LPCVOID)dn, (DWORD)(strlen(dn) + 1)); RegCloseKey(ifp); } HRESULT hr = S_OK; INetSharingManager* nsm; hr = CoCreateInstance(__uuidof(NetSharingManager), NULL, CLSCTX_ALL, __uuidof(INetSharingManager), (void**)&nsm); if (hr != S_OK) return; bool found = false; INetSharingEveryConnectionCollection* nsecc = nullptr; hr = nsm->get_EnumEveryConnection(&nsecc); if (! nsecc) { fprintf(stderr, "Failed to get NSM connections"); return; } IEnumVARIANT* ev = nullptr; IUnknown* unk = nullptr; hr = nsecc->get__NewEnum(&unk); if (unk) { hr = unk->QueryInterface(__uuidof(IEnumVARIANT), (void**)&ev); unk->Release(); } if (ev) { VARIANT v; VariantInit(&v); while ((S_OK == ev->Next(1, &v, NULL)) && found == FALSE) { if (V_VT(&v) == VT_UNKNOWN) { INetConnection* nc = nullptr; V_UNKNOWN(&v)->QueryInterface(__uuidof(INetConnection), (void**)&nc); if (nc) { NETCON_PROPERTIES* ncp = nullptr; nc->GetProperties(&ncp); if (ncp != nullptr) { GUID curId = ncp->guidId; if (curId == _deviceGuid) { wchar_t wtext[255]; mbstowcs(wtext, dn, strlen(dn) + 1); nc->Rename(wtext); found = true; } } nc->Release(); } } VariantClear(&v); } ev->Release(); } nsecc->Release(); _friendlyName_m.lock(); _friendlyName = dn; _friendlyName_m.unlock(); } std::string WindowsEthernetTap::friendlyName() const { Mutex::Lock l(_friendlyName_m); return _friendlyName; } void WindowsEthernetTap::scanMulticastGroups(std::vector& added, std::vector& removed) { if (! _initialized) return; HANDLE t = _tap; if (t == INVALID_HANDLE_VALUE) return; std::vector newGroups; // The ZT1 tap driver supports an IOCTL to get multicast memberships at the L2 // level... something Windows does not seem to expose ordinarily. This lets // pretty much anything work... IPv4, IPv6, IPX, oldskool Netbios, who knows... unsigned char mcastbuf[TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE]; DWORD bytesReturned = 0; if (DeviceIoControl(t, TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS, (LPVOID)mcastbuf, sizeof(mcastbuf), (LPVOID)mcastbuf, sizeof(mcastbuf), &bytesReturned, NULL)) { if ((bytesReturned > 0) && (bytesReturned <= TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE)) { // sanity check MAC mac; DWORD i = 0; while ((i + 6) <= bytesReturned) { mac.setTo(mcastbuf + i, 6); i += 6; if ((mac.isMulticast()) && (! mac.isBroadcast())) { // exclude the nulls that may be returned or any other junk Windows puts in there newGroups.push_back(MulticastGroup(mac, 0)); } } } } std::vector allIps(ips()); for (std::vector::iterator ip(allIps.begin()); ip != allIps.end(); ++ip) newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); std::sort(newGroups.begin(), newGroups.end()); newGroups.erase(std::unique(newGroups.begin(), newGroups.end()), newGroups.end()); for (std::vector::iterator m(newGroups.begin()); m != newGroups.end(); ++m) { if (! std::binary_search(_multicastGroups.begin(), _multicastGroups.end(), *m)) added.push_back(*m); } for (std::vector::iterator m(_multicastGroups.begin()); m != _multicastGroups.end(); ++m) { if (! std::binary_search(newGroups.begin(), newGroups.end(), *m)) removed.push_back(*m); } _multicastGroups.swap(newGroups); } void WindowsEthernetTap::setMtu(unsigned int mtu) { if (mtu != _mtu) { _mtu = mtu; HKEY nwAdapters; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}", 0, KEY_READ | KEY_WRITE, &nwAdapters) == ERROR_SUCCESS) { char tmps[64]; unsigned int tmpsl = OSUtils::ztsnprintf(tmps, sizeof(tmps), "%d", mtu); RegSetKeyValueA(nwAdapters, _mySubkeyName.c_str(), "MTU", REG_SZ, tmps, tmpsl); RegCloseKey(nwAdapters); } } } NET_IFINDEX WindowsEthernetTap::interfaceIndex() const { NET_IFINDEX idx = -1; if (ConvertInterfaceLuidToIndex(&_deviceLuid, &idx) == NO_ERROR) return idx; return -1; } void WindowsEthernetTap::threadMain() throw() { HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED); if (FAILED(hres)) { fprintf(stderr, "WinEthernetTap: COM initialization failed"); return; } char tapReadBuf[ZT_MAX_MTU + 32]; char tapPath[128]; HANDLE wait4[3]; OVERLAPPED tapOvlRead, tapOvlWrite; OSUtils::ztsnprintf(tapPath, sizeof(tapPath), "\\\\.\\Global\\%s.tap", _netCfgInstanceId.c_str()); try { while (_run) { // Because Windows Sleep(250); setPersistentTapDeviceState(_deviceInstanceId.c_str(), false); Sleep(250); setPersistentTapDeviceState(_deviceInstanceId.c_str(), true); Sleep(250); setPersistentTapDeviceState(_deviceInstanceId.c_str(), false); Sleep(250); setPersistentTapDeviceState(_deviceInstanceId.c_str(), true); Sleep(250); _tap = CreateFileA(tapPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, NULL); if (_tap == INVALID_HANDLE_VALUE) { Sleep(250); continue; } { uint32_t tmpi = 1; DWORD bytesReturned = 0; DeviceIoControl(_tap, TAP_WIN_IOCTL_SET_MEDIA_STATUS, &tmpi, sizeof(tmpi), &tmpi, sizeof(tmpi), &bytesReturned, NULL); } #ifdef ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE { /* This inserts a fake default route and a fake ARP entry, forcing * Windows to detect this as a "real" network and apply proper * firewall rules. * * This hack is completely stupid, but Windows made me do it * by being broken and insane. * * Background: Windows tries to detect its network location by * matching it to the ARP address of the default route. Networks * without default routes are "unidentified networks" and cannot * have their firewall classification changed by the user (easily). * * Yes, you read that right. * * The common workaround is to set *NdisDeviceType to 1, which * totally disables all Windows firewall functionality. This is * the answer you'll find on most forums for things like OpenVPN. * * Yes, you read that right. * * The default route workaround is also known, but for this to * work there must be a known default IP that resolves to a known * ARP address. This works for an OpenVPN tunnel, but not here * because this isn't a tunnel. It's a mesh. There is no "other * end," or any other known always on IP. * * So let's make a fake one and shove it in there along with its * fake static ARP entry. Also makes it instant-on and static. * * We'll have to see what DHCP does with this. In the future we * probably will not want to do this on DHCP-enabled networks, so * when we enable DHCP we will go in and yank this wacko hacko from * the routing table before doing so. * * Like Jesse Pinkman would say: "YEEEEAAH BITCH!" */ const uint32_t fakeIp = htonl(0x19fffffe); // 25.255.255.254 -- unrouted IPv4 block for (int i = 0; i < 8; ++i) { MIB_IPNET_ROW2 ipnr; memset(&ipnr, 0, sizeof(ipnr)); ipnr.Address.si_family = AF_INET; ipnr.Address.Ipv4.sin_addr.s_addr = fakeIp; ipnr.InterfaceLuid.Value = _deviceLuid.Value; ipnr.PhysicalAddress[0] = _mac[0] ^ 0x10; // just make something up that's consistent and not part of this net ipnr.PhysicalAddress[1] = 0x00; ipnr.PhysicalAddress[2] = (UCHAR)((_deviceGuid.Data1 >> 24) & 0xff); ipnr.PhysicalAddress[3] = (UCHAR)((_deviceGuid.Data1 >> 16) & 0xff); ipnr.PhysicalAddress[4] = (UCHAR)((_deviceGuid.Data1 >> 8) & 0xff); ipnr.PhysicalAddress[5] = (UCHAR)(_deviceGuid.Data1 & 0xff); ipnr.PhysicalAddressLength = 6; ipnr.State = NlnsPermanent; ipnr.IsRouter = 1; ipnr.IsUnreachable = 0; ipnr.ReachabilityTime.LastReachable = 0x0fffffff; ipnr.ReachabilityTime.LastUnreachable = 1; DWORD result = CreateIpNetEntry2(&ipnr); if (result != NO_ERROR) Sleep(250); else break; } for (int i = 0; i < 8; ++i) { MIB_IPFORWARD_ROW2 nr; memset(&nr, 0, sizeof(nr)); InitializeIpForwardEntry(&nr); nr.InterfaceLuid.Value = _deviceLuid.Value; nr.DestinationPrefix.Prefix.si_family = AF_INET; // rest is left as 0.0.0.0/0 nr.NextHop.si_family = AF_INET; nr.NextHop.Ipv4.sin_addr.s_addr = fakeIp; nr.Metric = 9999; // do not use as real default route nr.Protocol = MIB_IPPROTO_NETMGMT; DWORD result = CreateIpForwardEntry2(&nr); if (result != NO_ERROR) Sleep(250); else break; } } #endif // Assign or re-assign any should-be-assigned IPs in case we have restarted { Mutex::Lock _l(_assignedIps_m); _syncIps(); } memset(&tapOvlRead, 0, sizeof(tapOvlRead)); tapOvlRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); memset(&tapOvlWrite, 0, sizeof(tapOvlWrite)); tapOvlWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); wait4[0] = _injectSemaphore; wait4[1] = tapOvlRead.hEvent; wait4[2] = tapOvlWrite.hEvent; // only included if writeInProgress is true ReadFile(_tap, tapReadBuf, sizeof(tapReadBuf), NULL, &tapOvlRead); bool writeInProgress = false; ULONGLONG timeOfLastBorkCheck = GetTickCount64(); _initialized = true; unsigned int oldmtu = _mtu; setFriendlyName(_friendlyName.c_str()); while (_run) { DWORD waitResult = WaitForMultipleObjectsEx(writeInProgress ? 3 : 2, wait4, FALSE, 2500, TRUE); if (! _run) break; // will also break outer while(_run) since _run is false // Check for changes in MTU and break to restart tap device to reconfigure in this case if (_mtu != oldmtu) break; // Check for issues with adapter and close/reopen if any are detected. This // check fixes a while boatload of Windows adapter 'coma' issues after // sleep/wake and when adapters are added/removed. Basically if the tap // device is borked, whack it. { ULONGLONG tc = GetTickCount64(); if ((tc - timeOfLastBorkCheck) >= 2500) { timeOfLastBorkCheck = tc; char aabuf[16384]; ULONG aalen = sizeof(aabuf); if (GetAdaptersAddresses( AF_UNSPEC, GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, (void*)0, reinterpret_cast(aabuf), &aalen) == NO_ERROR) { bool isBorked = false; PIP_ADAPTER_ADDRESSES aa = reinterpret_cast(aabuf); while (aa) { if (_deviceLuid.Value == aa->Luid.Value) { isBorked = (aa->OperStatus != IfOperStatusUp); break; } aa = aa->Next; } if (isBorked) { // Close and reopen tap device if there's an issue (outer loop) break; } } } } if ((waitResult == WAIT_TIMEOUT) || (waitResult == WAIT_FAILED)) { Sleep(250); // guard against spinning under some conditions continue; } if (HasOverlappedIoCompleted(&tapOvlRead)) { DWORD bytesRead = 0; if (GetOverlappedResult(_tap, &tapOvlRead, &bytesRead, FALSE)) { if ((bytesRead > 14) && (_enabled)) { MAC to(tapReadBuf, 6); MAC from(tapReadBuf + 6, 6); unsigned int etherType = ((((unsigned int)tapReadBuf[12]) & 0xff) << 8) | (((unsigned int)tapReadBuf[13]) & 0xff); try { _handler(_arg, (void*)0, _nwid, from, to, etherType, 0, tapReadBuf + 14, bytesRead - 14); } catch (...) { } // handlers should not throw } } ReadFile(_tap, tapReadBuf, ZT_MAX_MTU + 32, NULL, &tapOvlRead); } if (writeInProgress) { if (HasOverlappedIoCompleted(&tapOvlWrite)) { writeInProgress = false; _injectPending_m.lock(); _injectPending.pop(); } else continue; // still writing, so skip code below and wait } else _injectPending_m.lock(); if (! _injectPending.empty()) { WriteFile(_tap, _injectPending.front().data, _injectPending.front().len, NULL, &tapOvlWrite); writeInProgress = true; } _injectPending_m.unlock(); } CancelIo(_tap); CloseHandle(tapOvlRead.hEvent); CloseHandle(tapOvlWrite.hEvent); CloseHandle(_tap); _tap = INVALID_HANDLE_VALUE; // We will restart and re-open the tap unless _run == false } } catch (...) { } // catch unexpected exceptions -- this should not happen but would prevent program crash or other weird issues since threads should not throw CoUninitialize(); } NET_IFINDEX WindowsEthernetTap::_getDeviceIndex() { MIB_IF_TABLE2* ift = (MIB_IF_TABLE2*)0; if (GetIfTable2Ex(MibIfTableRaw, &ift) != NO_ERROR) throw std::runtime_error("GetIfTable2Ex() failed"); if (ift->NumEntries > 0) { for (ULONG i = 0; i < ift->NumEntries; ++i) { if (ift->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { NET_IFINDEX idx = ift->Table[i].InterfaceIndex; FreeMibTable(ift); return idx; } } } FreeMibTable(&ift); throw std::runtime_error("interface not found"); } std::vector WindowsEthernetTap::_getRegistryIPv4Value(const char* regKey) { std::vector value; HKEY tcpIpInterfaces; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces", 0, KEY_READ | KEY_WRITE, &tcpIpInterfaces) == ERROR_SUCCESS) { char buf[16384]; DWORD len = sizeof(buf); DWORD kt = REG_MULTI_SZ; if (RegGetValueA(tcpIpInterfaces, _netCfgInstanceId.c_str(), regKey, 0, &kt, &buf, &len) == ERROR_SUCCESS) { switch (kt) { case REG_SZ: if (len > 0) value.push_back(std::string(buf)); break; case REG_MULTI_SZ: { for (DWORD k = 0, s = 0; k < len; ++k) { if (! buf[k]) { if (s < k) { value.push_back(std::string(buf + s)); s = k + 1; } else break; } } } break; } } RegCloseKey(tcpIpInterfaces); } return value; } void WindowsEthernetTap::_setRegistryIPv4Value(const char* regKey, const std::vector& value) { std::string regMulti; for (std::vector::const_iterator s(value.begin()); s != value.end(); ++s) { regMulti.append(*s); regMulti.push_back((char)0); } HKEY tcpIpInterfaces; if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces", 0, KEY_READ | KEY_WRITE, &tcpIpInterfaces) == ERROR_SUCCESS) { if (regMulti.length() > 0) { regMulti.push_back((char)0); RegSetKeyValueA(tcpIpInterfaces, _netCfgInstanceId.c_str(), regKey, REG_MULTI_SZ, regMulti.data(), (DWORD)regMulti.length()); } else { RegDeleteKeyValueA(tcpIpInterfaces, _netCfgInstanceId.c_str(), regKey); } RegCloseKey(tcpIpInterfaces); } } void WindowsEthernetTap::_syncIps() { // assumes _assignedIps_m is locked if (! _initialized) return; std::vector haveIps(ips()); for (std::vector::const_iterator aip(_assignedIps.begin()); aip != _assignedIps.end(); ++aip) { if (std::find(haveIps.begin(), haveIps.end(), *aip) == haveIps.end()) { MIB_UNICASTIPADDRESS_ROW ipr; InitializeUnicastIpAddressEntry(&ipr); if (aip->isV4()) { ipr.Address.Ipv4.sin_family = AF_INET; ipr.Address.Ipv4.sin_addr.S_un.S_addr = *((const uint32_t*)aip->rawIpData()); ipr.OnLinkPrefixLength = aip->netmaskBits(); if (ipr.OnLinkPrefixLength >= 32) continue; } else if (aip->isV6()) { ipr.Address.Ipv6.sin6_family = AF_INET6; memcpy(ipr.Address.Ipv6.sin6_addr.u.Byte, aip->rawIpData(), 16); ipr.OnLinkPrefixLength = aip->netmaskBits(); if (ipr.OnLinkPrefixLength >= 128) continue; } else continue; ipr.PrefixOrigin = IpPrefixOriginManual; ipr.SuffixOrigin = IpSuffixOriginManual; ipr.ValidLifetime = 0xffffffff; ipr.PreferredLifetime = 0xffffffff; ipr.InterfaceLuid = _deviceLuid; ipr.InterfaceIndex = _getDeviceIndex(); CreateUnicastIpAddressEntry(&ipr); } if (aip->isV4()) { char ipbuf[64]; std::string ipStr(aip->toIpString(ipbuf)); std::vector regIps(_getRegistryIPv4Value("IPAddress")); if (std::find(regIps.begin(), regIps.end(), ipStr) == regIps.end()) { std::vector regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); regIps.push_back(ipStr); regSubnetMasks.push_back(aip->netmask().toIpString(ipbuf)); _setRegistryIPv4Value("IPAddress", regIps); _setRegistryIPv4Value("SubnetMask", regSubnetMasks); } } } } void WindowsEthernetTap::setDns(const char* domain, const std::vector& servers) { WinDNSHelper::setDNS(_nwid, domain, servers); } } // namespace ZeroTier