mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2024-12-21 05:53:09 +00:00
0e5651f353
* add note about forceTcpRelay * Create a sample systemd unit for tcp proxy * set gitattributes for rust & cargo so hashes dont conflict on Windows * Revert "set gitattributes for rust & cargo so hashes dont conflict on Windows" This reverts commit032dc5c108
. * Turn off autocrlf for rust source Doesn't appear to play nice well when it comes to git and vendored cargo package hashes * Fix #1883 (#1886) Still unknown as to why, but the call to `nc->GetProperties()` can fail when setting a friendly name on the Windows virtual ethernet adapter. Ensure that `ncp` is not null before continuing and accessing the device GUID. * Don't vendor packages for zeroidc (#1885) * Added docker environment way to join networks (#1871) * add StringUtils * fix headers use recommended headers and remove unused headers * move extern "C" only JNI functions need to be exported * cleanup * fix ANDROID-50: RESULT_ERROR_BAD_PARAMETER typo * fix typo in log message * fix typos in JNI method signatures * fix typo * fix ANDROID-51: fieldName is uninitialized * fix ANDROID-35: memory leak * fix missing DeleteLocalRef in loops * update to use unique error codes * add GETENV macro * add LOG_TAG defines * ANDROID-48: add ZT_jnicache.cpp * ANDROID-48: use ZT_jnicache.cpp and remove ZT_jnilookup.cpp and ZT_jniarray.cpp * add Event.fromInt * add PeerRole.fromInt * add ResultCode.fromInt * fix ANDROID-36: issues with ResultCode * add VirtualNetworkConfigOperation.fromInt * fix ANDROID-40: VirtualNetworkConfigOperation out-of-sync with ZT_VirtualNetworkConfigOperation enum * add VirtualNetworkStatus.fromInt * fix ANDROID-37: VirtualNetworkStatus out-of-sync with ZT_VirtualNetworkStatus enum * add VirtualNetworkType.fromInt * make NodeStatus a plain data class * fix ANDROID-52: synchronization bug with nodeMap * Node init work: separate Node construction and init * add Node.toString * make PeerPhysicalPath a plain data class * remove unused PeerPhysicalPath.fixed * add array functions * make Peer a plain data class * make Version a plain data class * fix ANDROID-42: copy/paste error * fix ANDROID-49: VirtualNetworkConfig.equals is wrong * reimplement VirtualNetworkConfig.equals * reimplement VirtualNetworkConfig.compareTo * add VirtualNetworkConfig.hashCode * make VirtualNetworkConfig a plain data class * remove unused VirtualNetworkConfig.enabled * reimplement VirtualNetworkDNS.equals * add VirtualNetworkDNS.hashCode * make VirtualNetworkDNS a plain data class * reimplement VirtualNetworkRoute.equals * reimplement VirtualNetworkRoute.compareTo * reimplement VirtualNetworkRoute.toString * add VirtualNetworkRoute.hashCode * make VirtualNetworkRoute a plain data class * add isSocketAddressEmpty * add addressPort * add fromSocketAddressObject * invert logic in a couple of places and return early * newInetAddress and newInetSocketAddress work allow newInetSocketAddress to return NULL if given empty address * fix ANDROID-38: stack corruption in onSendPacketRequested * use GETENV macro * JniRef work JniRef does not use callbacks struct, so remove fix NewGlobalRef / DeleteGlobalRef mismatch * use PRId64 macros * switch statement work * comments and logging * Modifier 'public' is redundant for interface members * NodeException can be made a checked Exception * 'NodeException' does not define a 'serialVersionUID' field * 'finalize()' should not be overridden this is fine to do because ZeroTierOneService calls close() when it is done * error handling, error reporting, asserts, logging * simplify loadLibrary * rename Node.networks -> Node.networkConfigs * Windows file permissions fix (#1887) * Allow macOS interfaces to use multiple IP addresses (#1879) Co-authored-by: Sean OMeara <someara@users.noreply.github.com> Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * Fix condition where full HELLOs might not be sent when necessary (#1877) Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * 1.10.4 version bumps * Add security policy to repo (#1889) * [+] add e2k64 arch (#1890) * temp fix for ANDROID-56: crash inside newNetworkConfig from too many args * 1.10.4 release notes * Windows 1.10.4 Advanced Installer bump * Revert "temp fix for ANDROID-56: crash inside newNetworkConfig from too many args" This reverts commitdd627cd7f4
. * actual fix for ANDROID-56: crash inside newNetworkConfig cast all arguments to varargs functions as good style * Fix addIp being called with applied ips (#1897) This was getting called outside of the check for existing ips Because of the added ifdef and a brace getting moved to the wrong place. ``` if (! n.tap()->addIp(*ip)) { fprintf(stderr, "ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString(ipbuf)); } WinFWHelper::newICMPRule(*ip, n.config().nwid); ``` * 1.10.5 (#1905) * 1.10.5 bump * 1.10.5 for Windows * 1.10.5 * Prevent path-learning loops (#1914) * Prevent path-learning loops * Only allow new overwrite if not bonded * fix binding temporary ipv6 addresses on macos (#1910) The check code wasn't running. I don't know why !defined(TARGET_OS_IOS) would exclude code on desktop macOS. I did a quick search and changed it to defined(TARGET_OS_MAC). Not 100% sure what the most correct solution there is. You can verify the old and new versions with `ifconfig | grep temporary` plus `zerotier-cli info -j` -> listeningOn * 1.10.6 (#1929) * 1.10.5 bump * 1.10.6 * 1.10.6 AIP for Windows. * Release notes for 1.10.6 (#1931) * Minor tweak to Synology Docker image script (#1936) * Change if_def again so ios can build (#1937) All apple's variables are "defined" but sometimes they are defined as "0" * move begin/commit into try/catch block (#1932) Thread was exiting in some cases * Bump openssl from 0.10.45 to 0.10.48 in /zeroidc (#1938) Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.45 to 0.10.48. - [Release notes](https://github.com/sfackler/rust-openssl/releases) - [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.45...openssl-v0.10.48) --- updated-dependencies: - dependency-name: openssl dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * new drone bits * Fix multiple network join from environment entrypoint.sh.release (#1961) * _bond_m guards _bond, not _paths_m (#1965) * Fix: warning: mutex '_aqm_m' is not held on every path through here [-Wthread-safety-analysis] (#1964) * Bump h2 from 0.3.16 to 0.3.17 in /zeroidc (#1963) Bumps [h2](https://github.com/hyperium/h2) from 0.3.16 to 0.3.17. - [Release notes](https://github.com/hyperium/h2/releases) - [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/h2/compare/v0.3.16...v0.3.17) --- updated-dependencies: - dependency-name: h2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * Add note that binutils is required on FreeBSD (#1968) * Add prometheus metrics for Central controllers (#1969) * add header-only prometheus lib to ext * rename folder * Undo rename directory * prometheus simpleapi included on mac & linux * wip * wire up some controller stats * Get windows building with prometheus * bsd build flags for prometheus * Fix multiple network join from environment entrypoint.sh.release (#1961) * _bond_m guards _bond, not _paths_m (#1965) * Fix: warning: mutex '_aqm_m' is not held on every path through here [-Wthread-safety-analysis] (#1964) * Serve prom metrics from /metrics endpoint * Add prom metrics for Central controller specific things * reorganize metric initialization * testing out a labled gauge on Networks * increment error counter on throw * Consolidate metrics definitions Put all metric definitions into node/Metrics.hpp. Accessed as needed from there. * Revert "testing out a labled gauge on Networks" This reverts commit499ed6d95e
. * still blows up but adding to the record for completeness right now * Fix runtime issues with metrics * Add metrics files to visual studio project * Missed an "extern" * add copyright headers to new files * Add metrics for sent/received bytes (total) * put /metrics endpoint behind auth * sendto returns int on Win32 --------- Co-authored-by: Leonardo Amaral <leleobhz@users.noreply.github.com> Co-authored-by: Brenton Bostick <bostick@gmail.com> * Central startup update (#1973) * allow specifying authtoken in central startup * set allowManagedFrom * move redis_mem_notification to the correct place * add node checkins metric * wire up min/max connection pool size metrics * x86_64-unknown-linux-gnu on ubuntu runner (#1975) * adding incoming zt packet type metrics (#1976) * use cpp-httplib for HTTP control plane (#1979) refactored the old control plane code to use [cpp-httplib](https://github.com/yhirose/cpp-httplib) instead of a hand rolled HTTP server. Makes the control plane code much more legible. Also no longer randomly stops responding. * Outgoing Packet Metrics (#1980) add tx/rx labels to packet counters and add metrics for outgoing packets * Add short-term validation test workflow (#1974) Add short-term validation test workflow * Brenton/curly braces (#1971) * fix formatting * properly adjust various lines breakup multiple statements onto multiple lines * insert {} around if, for, etc. * Fix rust dependency caching (#1983) * fun with rust caching * kick * comment out invalid yaml keys for now * Caching should now work * re-add/rename key directives * bump * bump * bump * Don't force rebuild on Windows build GH Action (#1985) Switching `/t:ZeroTierOne:Rebuild` to just `/t:ZeroTierOne` allows the Windows build to use the rust cache. `/t:ZeroTierOne:Rebuild` cleared the cache before building. * More packet metrics (#1982) * found path negotation sends that weren't accounted for * Fix histogram so it will actually compile * Found more places for packet metrics * separate the bind & listen calls on the http backplane (#1988) * fix memory leak (#1992) * fix a couple of metrics (#1989) * More aggressive CLI spamming (#1993) * fix type signatures (#1991) * Network-metrics (#1994) * Add a couple quick functions for converting a uint64_t network ID/node ID into std::string * Network metrics * Peer metrics (#1995) * Adding peer metrics still need to be wired up for use * per peer packet metrics * Fix crash from bad instantiation of histogram * separate alive & dead path counts * Add peer metric update block * add peer latency values in doPingAndKeepalive * prevent deadlock * peer latency histogram actually works now * cleanup * capture counts of packets to specific peers --------- Co-authored-by: Joseph Henry <joseph.henry@zerotier.com> * Metrics consolidation (#1997) * Rename zt_packet_incoming -> zt_packet Also consolidate zt_peer_packets into a single metric with tx and rx labels. Same for ztc_tcp_data and ztc_udp_data * Further collapse tcp & udp into metric labels for zt_data * Fix zt_data metric description * zt_peer_packets description fix * Consolidate incoming/outgoing network packets to a single metric * zt_incoming_packet_error -> zt_packet_error * Disable peer metrics for central controllers Can change in the future if needed, but given the traffic our controllers serve, that's going to be a *lot* of data * Disable peer metrics for controllers pt 2 * Update readme files for metrics (#2000) * Controller Metrics & Network Config Request Fix (#2003) * add new metrics for network config request queue size and sso expirations * move sso expiration to its own thread in the controller * fix potential undefined behavior when modifying a set * Enable RTTI in Windows build The new prometheus histogram stuff needs it. Access violation - no RTTI data!INVALID packet 636ebd9ee8cac6c0 from cafe9efeb9(2605:9880:200:1200:30:571:e34:51/9993) (unexpected exception in tryDecode()) * Don't re-apply routes on BSD See issue #1986 * Capture setContent by-value instead of by-reference (#2006) Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * fix typos (#2010) * central controller metrics & request path updates (#2012) * internal db metrics * use shared mutexes for read/write locks * remove this lock. only used for a metric * more metrics * remove exploratory metrics place controller request benchmarks behind ifdef * Improve validation test (#2013) * fix init order for EmbeddedNetworkController (#2014) * add constant for getifaddrs cache time * cache getifaddrs - mac * cache getifaddrs - linux * cache getifaddrs - bsd * cache getifaddrs - windows * Fix oidc client lookup query join condition referenced the wrong table. Worked fine unless there were multiple identical client IDs * Fix udp sent metric was only incrementing by 1 for each packet sent * Allow sending all surface addresses to peer in low-bandwidth mode * allow enabling of low bandwidth mode on controllers * don't unborrow bad connections pool will clean them up later * Multi-arch controller container (#2037) create arm64 & amd64 images for central controller * Update README.md issue #2009 * docker tags change * fix oidc auth url memory leak (#2031) getAuthURL() was not calling zeroidc::free_cstr(url); the only place authAuthURL is called, the url can be retrieved from the network config instead. You could alternatively copy the string and call free_cstr in getAuthURL. If that's better we can change the PR. Since now there are no callers of getAuthURL I deleted it. Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * Bump openssl from 0.10.48 to 0.10.55 in /zeroidc (#2034) Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.48 to 0.10.55. - [Release notes](https://github.com/sfackler/rust-openssl/releases) - [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.48...openssl-v0.10.55) --- updated-dependencies: - dependency-name: openssl dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * zeroidc cargo warnings (#2029) * fix unused struct member cargo warning * fix unused import cargo warning * fix unused return value cargo warning --------- Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * fix memory leak in macos ipv6/dns helper (#2030) Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> * Consider ZEROTIER_JOIN_NETWORKS in healthcheck (#1978) * Add a 2nd auth token only for access to /metrics (#2043) * Add a 2nd auth token for /metrics Allows administrators to distribute a token that only has access to read metrics and nothing else. Also added support for using bearer auth tokens for both types of tokens Separate endpoint for metrics #2041 * Update readme * fix a couple of cases of writing the wrong token * Add warning to cli for allow default on FreeBSD It doesn't work. Not possible to fix with deficient network stack and APIs. ZeroTierOne-freebsd # zerotier-cli set 9bee8941b5xxxxxx allowDefault=1 400 set Allow Default does not work properly on FreeBSD. See #580 root@freebsd13-a:~/ZeroTierOne-freebsd # zerotier-cli get 9bee8941b5xxxxxx allowDefault 1 * ARM64 Support for TapDriver6 (#1949) * Release memory previously allocated by UPNP_GetValidIGD * Fix ifdef that breaks libzt on iOS (#2050) * less drone (#2060) * Exit if loading an invalid identity from disk (#2058) * Exit if loading an invalid identity from disk Previously, if an invalid identity was loaded from disk, ZeroTier would generate a new identity & chug along and generate a brand new identity as if nothing happened. When running in containers, this introduces the possibility for key matter loss; especially when running in containers where the identity files are mounted in the container read only. In this case, ZT will continue chugging along with a brand new identity with no possibility of recovering the private key. ZeroTier should exit upon loading of invalid identity.public/identity.secret #2056 * add validation test for #2056 * tcp-proxy: fix build * Adjust tcp-proxy makefile to support metrics There's no way to get the metrics yet. Someone will have to add the http service. * remove ZT_NO_METRIC ifdef * Implement recvmmsg() for Linux to reduce syscalls. (#2046) Between 5% and 40% speed improvement on Linux, depending on system configuration and load. * suppress warnings: comparison of integers of different signs: 'int64_t' (aka 'long') and 'uint64_t' (aka 'unsigned long') [-Wsign-compare] (#2063) * fix warning: 'OS_STRING' macro redefined [-Wmacro-redefined] (#2064) Even though this is in ext, these particular chunks of code were added by us, so are ok to modify. * Apply default route a different way - macOS The original way we applied default route, by forking 0.0.0.0/0 into 0/1 and 128/1 works, but if mac os has any networking hiccups -if you change SSIDs or sleep/wake- macos erases the system default route. And then all networking on the computer is broken. to summarize the new way: allowDefault=1 ``` sudo route delete default 192.168.82.1 sudo route add default 10.2.0.2 sudo route add -ifscope en1 default 192.168.82.1 ``` gives us this routing table ``` Destination Gateway RT_IFA Flags Refs Use Mtu Netif Expire rtt(ms) rttvar(ms) default 10.2.0.2 10.2.0.18 UGScg 90 1 2800 feth4823 default 192.168.82.1 192.168.82.217 UGScIg ``` allowDefault=0 ``` sudo route delete default sudo route delete -ifscope en1 default sudo route add default 192.168.82.1 ``` Notice the I flag, for -ifscope, on the physical default route. route change does not seem to work reliably. * fix docker tag for controllers (#2066) * Update build.sh (#2068) fix mkwork compilation errors * Fix network DNS on macOS It stopped working for ipv4 only networks in Monterey. See #1696 We add some config like so to System Configuration ``` scutil show State:/Network/Service/9bee8941b5xxxxxx/IPv4 <dictionary> { Addresses : <array> { 0 : 10.2.1.36 } InterfaceName : feth4823 Router : 10.2.1.36 ServerAddress : 127.0.0.1 } ``` * Add search domain to macos dns configuration Stumbled upon this while debugging something else. If we add search domain to our system configuration for network DNS, then search domains work: ``` ping server1 ~ PING server1.my.domain (10.123.3.1): 56 data bytes 64 bytes from 10.123.3.1 ``` * Fix reporting of secondaryPort and tertiaryPort See: #2039 * Fix typos (#2075) * Disable executable stacks on assembly objects (#2071) Add `--noexecstack` to the assembler flags so the resulting binary will link with a non-executable stack. Fixes zerotier/ZeroTierOne#1179 Co-authored-by: Joseph Henry <joseph.henry@zerotier.com> * Test that starting zerotier before internet works * Don't skip hellos when there are no paths available working on #2082 * Update validate-1m-linux.sh * Save zt node log files on abort * Separate test and summary step in validator script * Don't apply default route until zerotier is "online" I was running into issues with restarting the zerotier service while "full tunnel" mode is enabled. When zerotier first boots, it gets network state from the cache on disk. So it immediately applies all the routes it knew about before it shutdown. The network config may have change in this time. If it has, then your default route is via a route you are blocked from talking on. So you can't get the current network config, so your internet does not work. Other options include - don't use cached network state on boot - find a better criteria than "online" * Fix node time-to-online counter in validator script * Export variables so that they are accessible by exit function * Fix PortMapper issue on ZeroTier startup See issue #2082 We use a call to libnatpmp::ininatpp to make sure the computer has working network sockets before we go into the main nat-pmp/upnp logic. With basic exponenetial delay up to 30 seconds. * testing * Comment out PortMapper debug this got left turned on in a confusing merge previously * fix macos default route again see commitfb6af1971
* Fix network DNS on macOS adding that stuff to System Config causes this extra route to be added which breaks ipv4 default route. We figured out a weird System Coniguration setting that works. --- old couldn't figure out how to fix it in SystemConfiguration so here we are# Please enter the commit message for your changes. Lines starting We also moved the dns setter to before the syncIps stuff to help with a race condition. It didn't always work when you re-joined a network with default route enabled. * Catch all conditions in switch statement, remove trailing whitespaces * Add setmtu command, fix bond lifetime issue * Basic cleanups * Check if null is passed to VirtualNetworkConfig.equals and name fixes * ANDROID-96: Simplify and use return code from node_init directly * Windows arm64 (#2099) * ARM64 changes for 1.12 * 1.12 Windows advanced installer updates and updates for ARM64 * 1.12.0 * Linux build fixes for old distros. * release notes --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: travis laduke <travisladuke@gmail.com> Co-authored-by: Grant Limberg <grant.limberg@zerotier.com> Co-authored-by: Grant Limberg <glimberg@users.noreply.github.com> Co-authored-by: Leonardo Amaral <leleobhz@users.noreply.github.com> Co-authored-by: Brenton Bostick <bostick@gmail.com> Co-authored-by: Sean OMeara <someara@users.noreply.github.com> Co-authored-by: Joseph Henry <joseph-henry@users.noreply.github.com> Co-authored-by: Roman Peshkichev <roman.peshkichev@gmail.com> Co-authored-by: Joseph Henry <joseph.henry@zerotier.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Co-authored-by: Jake Vis <jakevis@outlook.com> Co-authored-by: Jörg Thalheim <joerg@thalheim.io> Co-authored-by: lison <imlison@foxmail.com> Co-authored-by: Kenny MacDermid <kenny@macdermid.ca>
1327 lines
47 KiB
C++
1327 lines
47 KiB
C++
/*
|
|
* 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: 2025-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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
#include <tchar.h>
|
|
#include <malloc.h>
|
|
#include <winreg.h>
|
|
#include <wchar.h>
|
|
#include <ws2ipdef.h>
|
|
#include <ws2tcpip.h>
|
|
#include <IPHlpApi.h>
|
|
#include <nldef.h>
|
|
#include <netioapi.h>
|
|
#include <atlbase.h>
|
|
#include <netlistmgr.h>
|
|
#include <nldef.h>
|
|
#include <SetupAPI.h>
|
|
#include <newdev.h>
|
|
#include <cfgmgr32.h>
|
|
|
|
#include <iostream>
|
|
#include <set>
|
|
|
|
#include "../node/Constants.hpp"
|
|
#include "../node/Utils.hpp"
|
|
#include "../node/Mutex.hpp"
|
|
|
|
#include "WindowsEthernetTap.hpp"
|
|
#include "OSUtils.hpp"
|
|
|
|
#include "..\windows\TapDriver6\tap-windows.h"
|
|
#include "WinDNSHelper.hpp"
|
|
|
|
#include <netcon.h>
|
|
|
|
// 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<std::string> 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<std::string>::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<std::string> 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<std::string>::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<InetAddress>::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<std::string> regIps(_getRegistryIPv4Value("IPAddress"));
|
|
std::vector<std::string> regSubnetMasks(_getRegistryIPv4Value("SubnetMask"));
|
|
char ipbuf[64];
|
|
std::string ipstr(ip.toIpString(ipbuf));
|
|
for (std::vector<std::string>::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<InetAddress> WindowsEthernetTap::ips() const
|
|
{
|
|
static const InetAddress linkLocalLoopback("fe80::1/64"); // what is this and why does Windows assign it?
|
|
std::vector<InetAddress> 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<MulticastGroup> &added,std::vector<MulticastGroup> &removed)
|
|
{
|
|
if (!_initialized)
|
|
return;
|
|
HANDLE t = _tap;
|
|
if (t == INVALID_HANDLE_VALUE)
|
|
return;
|
|
|
|
std::vector<MulticastGroup> 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<InetAddress> allIps(ips());
|
|
for(std::vector<InetAddress>::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<MulticastGroup>::iterator m(newGroups.begin());m!=newGroups.end();++m) {
|
|
if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m))
|
|
added.push_back(*m);
|
|
}
|
|
for(std::vector<MulticastGroup>::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<PIP_ADAPTER_ADDRESSES>(aabuf),&aalen) == NO_ERROR) {
|
|
bool isBorked = false;
|
|
|
|
PIP_ADAPTER_ADDRESSES aa = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(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<std::string> WindowsEthernetTap::_getRegistryIPv4Value(const char *regKey)
|
|
{
|
|
std::vector<std::string> 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<std::string> &value)
|
|
{
|
|
std::string regMulti;
|
|
for(std::vector<std::string>::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<InetAddress> haveIps(ips());
|
|
|
|
for(std::vector<InetAddress>::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<std::string> regIps(_getRegistryIPv4Value("IPAddress"));
|
|
if (std::find(regIps.begin(), regIps.end(), ipStr) == regIps.end()) {
|
|
std::vector<std::string> 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<InetAddress>& servers)
|
|
{
|
|
WinDNSHelper::setDNS(_nwid, domain, servers);
|
|
}
|
|
|
|
} // namespace ZeroTier
|