diff --git a/.gitignore b/.gitignore index 62a6c6a17..584bdb6c1 100755 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,4 @@ __pycache__ attic/world/*.c25519 attic/world/mkworld workspace/ +workspace2/ diff --git a/controller/DB.cpp b/controller/DB.cpp index dfa4fa094..775daf1a5 100644 --- a/controller/DB.cpp +++ b/controller/DB.cpp @@ -48,6 +48,8 @@ void DB::initNetwork(nlohmann::json &network) { "type","ACTION_ACCEPT" } }}; } + if (!network.count("dns")) network["dns"] = nlohmann::json::array(); + network["objtype"] = "network"; } diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index b2bd7bfb9..ad42fabb1 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -585,7 +585,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( responseBody.reserve((members.size() + 2) * 32); std::string mid; for(auto member=members.begin();member!=members.end();++member) { - mid = (*member)["id"]; + mid = OSUtils::jsonString((*member)["id"], ""); char tmp[128]; OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s\"%s\":%llu",(responseBody.length() > 1) ? "," : "",mid.c_str(),(unsigned long long)OSUtils::jsonInt((*member)["revision"],0)); responseBody.append(tmp); @@ -1029,6 +1029,30 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( } } + if (b.count("dns")) { + json &dns = b["dns"]; + if (dns.is_array()) { + json nda = json::array(); + for(unsigned int i=0;imtu = std::max(std::min((unsigned int)OSUtils::jsonInt(network["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU); nc->multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL); + std::string rtt(OSUtils::jsonString(member["remoteTraceTarget"],"")); if (rtt.length() == 10) { nc->remoteTraceTarget = Address(Utils::hexStrToU64(rtt.c_str())); @@ -1392,6 +1417,7 @@ void EmbeddedNetworkController::_request( json &tags = network["tags"]; json &memberCapabilities = member["capabilities"]; json &memberTags = member["tags"]; + json &dns = network["dns"]; if (metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,0) <= 0) { // Old versions with no rules engine support get an allow everything rule. @@ -1684,6 +1710,20 @@ void EmbeddedNetworkController::_request( } } } + + if(dns.is_object()) { + std::string domain = OSUtils::jsonString(dns["domain"],""); + memcpy(nc->dns.domain, domain.c_str(), domain.size()); + json &addrArray = dns["servers"]; + if (addrArray.is_array()) { + for(unsigned int j = 0; j < addrArray.size() && j < ZT_MAX_DNS_SERVERS; ++j) { + json &addr = addrArray[j]; + nc->dns.server_addr[j] = InetAddress(OSUtils::jsonString(addr,"").c_str()); + } + } + } else { + dns = json::object(); + } // Issue a certificate of ownership for all static IPs if (nc->staticIpCount) { diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 0f5fde77b..44ba2ae5d 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -430,6 +430,44 @@ void PostgreSQL::initializeNetworks(PGconn *conn) config["routes"].push_back(route); } + r2 = PQexecParams(conn, + "SELECT domain, servers FROM ztc_network_dns WHERE network_id = $1", + 1, + NULL, + nwidparam, + NULL, + NULL, + 0); + + if (PQresultStatus(r2) != PGRES_TUPLES_OK) { + fprintf(stderr, "ERROR: Error retrieving DNS settings for network: %s\n", PQresultErrorMessage(r2)); + PQclear(r2); + PQclear(res); + exit(1); + } + + n = PQntuples(r2); + if (n > 1) { + fprintf(stderr, "ERROR: invalid number of DNS configurations for network %s. Must be 0 or 1\n", nwid.c_str()); + } else if (n == 1) { + json obj; + std::string domain = PQgetvalue(r2, 0, 0); + std::string serverList = PQgetvalue(r2, 0, 1); + auto servers = json::array(); + if (serverList.rfind("{",0) != std::string::npos) { + serverList = serverList.substr(1, serverList.size()-2); + std::stringstream ss(serverList); + while(ss.good()) { + std::string server; + std::getline(ss, server, ','); + servers.push_back(server); + } + } + obj["domain"] = domain; + obj["servers"] = servers; + config["dns"] = obj; + } + PQclear(r2); _networkChanged(empty, config, false); @@ -1423,6 +1461,38 @@ void PostgreSQL::commitThread() config = nullptr; continue; } + auto dns = (*config)["dns"]; + std::string domain = dns["domain"]; + std::stringstream servers; + servers << "{"; + for (auto j = dns["servers"].begin(); j < dns["servers"].end(); ++j) { + servers << *j; + if ( (j+1) != dns["servers"].end()) { + servers << ","; + } + } + servers << "}"; + + const char *p[3] = { + id.c_str(), + domain.c_str(), + servers.str().c_str() + }; + + res = PQexecParams(conn, "INSERT INTO ztc_network_dns (network_id, domain, servers) VALUES ($1, $2, $3) ON CONFLICT (network_id) DO UPDATE SET domain = EXCLUDED.domain, servers = EXCLUDED.servers", + 3, + NULL, + p, + NULL, + NULL, + 0); + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "ERROR: Error updating DNS: %s\n", PQresultErrorMessage(res)); + PQclear(res); + err = true; + break; + } + PQclear(res); res = PQexec(conn, "COMMIT"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index e1c8f7df3..f7e6ef6dc 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -195,6 +195,11 @@ extern "C" { */ #define ZT_PATH_LINK_QUALITY_MAX 0xff +/** + * Maximum number of DNS servers per domain + */ +#define ZT_MAX_DNS_SERVERS 4 + /** * Packet characteristics flag: packet direction, 1 if inbound 0 if outbound */ @@ -1106,6 +1111,15 @@ typedef struct uint16_t metric; } ZT_VirtualNetworkRoute; +/** + * DNS configuration to be pushed on a virtual network + */ +typedef struct +{ + char domain[128]; + struct sockaddr_storage server_addr[ZT_MAX_DNS_SERVERS]; +} ZT_VirtualNetworkDNS; + /** * An Ethernet multicast group */ @@ -1320,6 +1334,11 @@ typedef struct uint64_t mac; /* MAC in lower 48 bits */ uint32_t adi; /* Additional distinguishing information, usually zero except for IPv4 ARP groups */ } multicastSubscriptions[ZT_MAX_MULTICAST_SUBSCRIPTIONS]; + + /** + * Network specific DNS configuration + */ + ZT_VirtualNetworkDNS dns; } ZT_VirtualNetworkConfig; /** diff --git a/make-mac.mk b/make-mac.mk index ada65ff77..acf17dd42 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -25,10 +25,10 @@ TIMESTAMP=$(shell date +"%Y%m%d%H%M") DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_BUILD_ARCHITECTURE) include objects.mk -ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http_parser.o +ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o osdep/MacDNSHelper.o ext/http-parser/http_parser.o ifeq ($(ZT_CONTROLLER),1) - LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a + LIBS+=-L/usr/local/opt/libpq/lib -lpq ext/redis-plus-plus-1.1.1/install/macos/lib/libredis++.a ext/hiredis-0.14.1/lib/macos/libhiredis.a -framework SystemConfiguration -framework CoreFoundation DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER_USE_REDIS -DZT_CONTROLLER INCLUDES+=-I/usr/local/opt/libpq/include -Iext/hiredis-0.14.1/include/ -Iext/redis-plus-plus-1.1.1/install/macos/include/sw/ @@ -97,7 +97,11 @@ mac-agent: FORCE $(CC) -Ofast -o MacEthernetTapAgent osdep/MacEthernetTapAgent.c $(CODESIGN) -f -s $(CODESIGN_APP_CERT) MacEthernetTapAgent +osdep/MacDNSHelper.o: osdep/MacDNSHelper.mm + $(CXX) $(CXXFLAGS) -c osdep/MacDNSHelper.mm -o osdep/MacDNSHelper.o + one: $(CORE_OBJS) $(ONE_OBJS) one.o mac-agent + $(CXX) $(CXXFLAGS) -o zerotier-one $(CORE_OBJS) $(ONE_OBJS) one.o $(LIBS) # $(STRIP) zerotier-one ln -sf zerotier-one zerotier-idtool diff --git a/node/DNS.hpp b/node/DNS.hpp new file mode 100644 index 000000000..ceb81e33f --- /dev/null +++ b/node/DNS.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (c)2020 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: 2023-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. + */ +/****/ + +#ifndef ZT_DNS_HPP +#define ZT_DNS_HPP +#include +#include +#include + +#include "Buffer.hpp" +#include "InetAddress.hpp" +#include "../include/ZeroTierOne.h" + +namespace ZeroTier { + +/** + * DNS data serealization methods + */ +class DNS { +public: + template + static inline void serializeDNS(Buffer &b, const ZT_VirtualNetworkDNS *dns) + { + b.append(dns->domain, 128); + for(unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + InetAddress tmp(dns->server_addr[j]); + tmp.serialize(b); + } + } + + template + static inline void deserializeDNS(const Buffer &b, unsigned int &p, ZT_VirtualNetworkDNS *dns) + { + char *d = (char*)b.data()+p; + memset(dns, 0, sizeof(ZT_VirtualNetworkDNS)); + memcpy(dns->domain, d, 128); + p += 128; + for (unsigned int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + p += reinterpret_cast(&(dns->server_addr[j]))->deserialize(b, p); + } + } +}; + +} + +#endif // ZT_DNS_HPP diff --git a/node/Network.cpp b/node/Network.cpp index cfdbb3764..3fe489a29 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -586,6 +586,7 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u if (!_portInitialized) { ZT_VirtualNetworkConfig ctmp; + memset(&ctmp, 0, sizeof(ZT_VirtualNetworkConfig)); _externalConfig(&ctmp); _portError = RR->node->configureVirtualNetworkPort(tPtr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); _portInitialized = true; @@ -1426,6 +1427,8 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const ec->multicastSubscriptions[i].mac = _myMulticastGroups[i].mac().toInt(); ec->multicastSubscriptions[i].adi = _myMulticastGroups[i].adi(); } + + memcpy(&ec->dns, &_config.dns, sizeof(ZT_VirtualNetworkDNS)); } void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup) diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 97985c7af..c793462d8 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -176,6 +176,12 @@ bool NetworkConfig::toDictionary(Dictionary &d,b } } + tmp->clear(); + DNS::serializeDNS(*tmp, &dns); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_DNS,*tmp)) return false; + } + delete tmp; } catch ( ... ) { delete tmp; @@ -354,6 +360,11 @@ bool NetworkConfig::fromDictionary(const Dictionaryrules,this->ruleCount,ZT_MAX_NETWORK_RULES); } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_DNS, *tmp)) { + unsigned int p = 0; + DNS::deserializeDNS(*tmp, p, &dns); + } } //printf("~~~\n%s\n~~~\n",d.data()); diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 8311a0743..6e66720e8 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -26,6 +26,7 @@ #include "Constants.hpp" #include "Buffer.hpp" +#include "DNS.hpp" #include "InetAddress.hpp" #include "MulticastGroup.hpp" #include "Address.hpp" @@ -175,6 +176,8 @@ namespace ZeroTier { #define ZT_NETWORKCONFIG_DICT_KEY_TAGS "TAG" // tags (binary blobs) #define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP "COO" +// dns (binary blobs) +#define ZT_NETWORKCONFIG_DICT_KEY_DNS "DNS" // Legacy fields -- these are obsoleted but are included when older clients query @@ -229,13 +232,15 @@ public: capabilities(), tags(), certificatesOfOwnership(), - type(ZT_NETWORK_TYPE_PRIVATE) + type(ZT_NETWORK_TYPE_PRIVATE), + dnsCount(0) { name[0] = 0; memset(specialists, 0, sizeof(uint64_t)*ZT_MAX_NETWORK_SPECIALISTS); memset(routes, 0, sizeof(ZT_VirtualNetworkRoute)*ZT_MAX_NETWORK_ROUTES); memset(staticIps, 0, sizeof(InetAddress)*ZT_MAX_ZT_ASSIGNED_ADDRESSES); memset(rules, 0, sizeof(ZT_VirtualNetworkRule)*ZT_MAX_NETWORK_RULES); + memset(&dns, 0, sizeof(ZT_VirtualNetworkDNS)); } /** @@ -589,6 +594,16 @@ public: * Certificate of membership (for private networks) */ CertificateOfMembership com; + + /** + * Number of ZT-pushed DNS configurations + */ + unsigned int dnsCount; + + /** + * ZT pushed DNS configuration + */ + ZT_VirtualNetworkDNS dns; }; } // namespace ZeroTier diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp index d99cebef3..134168176 100644 --- a/osdep/BSDEthernetTap.hpp +++ b/osdep/BSDEthernetTap.hpp @@ -54,6 +54,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers) {} void threadMain() throw(); diff --git a/osdep/EthernetTap.hpp b/osdep/EthernetTap.hpp index db52e6b43..d8de16886 100644 --- a/osdep/EthernetTap.hpp +++ b/osdep/EthernetTap.hpp @@ -53,6 +53,7 @@ public: virtual void setFriendlyName(const char *friendlyName) = 0; virtual void scanMulticastGroups(std::vector &added,std::vector &removed) = 0; virtual void setMtu(unsigned int mtu) = 0; + virtual void setDns(const char *domain, const std::vector &servers) = 0; }; } // namespace ZeroTier diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp index 7503c5231..58c75f709 100644 --- a/osdep/LinuxEthernetTap.hpp +++ b/osdep/LinuxEthernetTap.hpp @@ -54,6 +54,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers) {} void threadMain() throw(); diff --git a/osdep/MacDNSHelper.hpp b/osdep/MacDNSHelper.hpp new file mode 100644 index 000000000..7a2902fa5 --- /dev/null +++ b/osdep/MacDNSHelper.hpp @@ -0,0 +1,18 @@ +#ifndef MAC_DNS_HELPER +#define MAC_DNS_HELPER + +#include +#include "../node/InetAddress.hpp" + +namespace ZeroTier { + +class MacDNSHelper +{ +public: + static void setDNS(uint64_t nwid, const char *domain, const std::vector &servers); + static void removeDNS(uint64_t nwid); +}; + +} + +#endif diff --git a/osdep/MacDNSHelper.mm b/osdep/MacDNSHelper.mm new file mode 100644 index 000000000..c50de7915 --- /dev/null +++ b/osdep/MacDNSHelper.mm @@ -0,0 +1,79 @@ +#include "MacDNSHelper.hpp" + +#include + +#include + +namespace ZeroTier { + +void MacDNSHelper::setDNS(uint64_t nwid, const char *domain, const std::vector &servers) +{ + SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL); + + CFStringRef *s = new CFStringRef[4]; + for (unsigned int i = 0; i < servers.size(); ++i) { + char buf[64]; + ZeroTier::InetAddress a = servers[i]; + const char *ipStr = a.toIpString(buf); + s[i] = CFStringCreateWithCString(NULL, ipStr, kCFStringEncodingUTF8); + } + + CFArrayRef serverArray = CFArrayCreate(NULL, (const void**)s, servers.size(), &kCFTypeArrayCallBacks); + + CFStringRef keys[2]; + keys[0] = CFSTR("SupplementalMatchDomains"); + keys[1] = CFSTR("ServerAddresses"); + + CFStringRef cfdomain = CFStringCreateWithCString(NULL, domain, kCFStringEncodingUTF8); + CFArrayRef domainArray = CFArrayCreate(NULL, (const void**)&cfdomain, 1, &kCFTypeArrayCallBacks); + + CFTypeRef values[2]; + values[0] = domainArray; + values[1] = serverArray; + + CFDictionaryRef dict = CFDictionaryCreate(NULL, + (const void**)keys, (const void**)values, 2, &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + char buf[256] = {0}; + sprintf(buf, "State:/Network/Service/%.16llx/DNS", nwid); + CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8); + CFArrayRef list = SCDynamicStoreCopyKeyList(ds, key); + + CFIndex i = 0, j = CFArrayGetCount(list); + bool ret = TRUE; + if (j <= 0) { + ret &= SCDynamicStoreAddValue(ds, key, dict); + } else { + ret &= SCDynamicStoreSetValue(ds, (CFStringRef)CFArrayGetValueAtIndex(list, i), dict); + } + if (!ret) { + fprintf(stderr, "Error writing DNS configuration\n"); + } + + CFRelease(list); + CFRelease(key); + CFRelease(dict); + CFRelease(domainArray); + CFRelease(cfdomain); + CFRelease(serverArray); + for (int i = 0; i < servers.size(); ++i) { + CFRelease(s[i]); + } + delete[] s; + CFRelease(ds); +} + +void MacDNSHelper::removeDNS(uint64_t nwid) +{ + SCDynamicStoreRef ds = SCDynamicStoreCreate(NULL, CFSTR("zerotier"), NULL, NULL); + + char buf[256] = {0}; + sprintf(buf, "State:/Network/Service/%.16llx/DNS", nwid); + CFStringRef key = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8); + SCDynamicStoreRemoveValue(ds, key); + CFRelease(key); + CFRelease(ds); +} + +} \ No newline at end of file diff --git a/osdep/MacEthernetTap.cpp b/osdep/MacEthernetTap.cpp index 028f85568..4095e1331 100644 --- a/osdep/MacEthernetTap.cpp +++ b/osdep/MacEthernetTap.cpp @@ -21,6 +21,7 @@ #include "OSUtils.hpp" #include "MacEthernetTap.hpp" #include "MacEthernetTapAgent.h" +#include "MacDNSHelper.hpp" #include #include @@ -54,6 +55,7 @@ #include #include #include +#include static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); @@ -200,6 +202,8 @@ MacEthernetTap::MacEthernetTap( MacEthernetTap::~MacEthernetTap() { + MacDNSHelper::removeDNS(_nwid); + Mutex::Lock _gl(globalTapCreateLock); ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit Thread::join(_thread); @@ -452,6 +456,11 @@ void MacEthernetTap::threadMain() } } +void MacEthernetTap::setDns(const char *domain, const std::vector &servers) +{ + MacDNSHelper::setDNS(this->_nwid, domain, servers); +} + } // namespace ZeroTier #endif // __APPLE__ diff --git a/osdep/MacEthernetTap.hpp b/osdep/MacEthernetTap.hpp index 6d5ce8941..7945bb408 100644 --- a/osdep/MacEthernetTap.hpp +++ b/osdep/MacEthernetTap.hpp @@ -56,6 +56,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers); void threadMain() throw(); diff --git a/osdep/MacKextEthernetTap.cpp b/osdep/MacKextEthernetTap.cpp index 2325d5943..ea26b53c1 100644 --- a/osdep/MacKextEthernetTap.cpp +++ b/osdep/MacKextEthernetTap.cpp @@ -43,6 +43,8 @@ #include #include +#include "MacDNSHelper.hpp" + // OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!? struct prf_ra { u_char onlink : 1; @@ -441,6 +443,8 @@ MacKextEthernetTap::MacKextEthernetTap( MacKextEthernetTap::~MacKextEthernetTap() { + MacDNSHelper::removeDNS(_nwid); + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit Thread::join(_thread); @@ -687,4 +691,9 @@ void MacKextEthernetTap::threadMain() } } +void MacKextEthernetTap::setDns(const char *domain, const std::vector &servers) +{ + MacDNSHelper::setDNS(_nwid, domain, servers); +} + } // namespace ZeroTier diff --git a/osdep/MacKextEthernetTap.hpp b/osdep/MacKextEthernetTap.hpp index f1a6f36fd..d7ea53028 100644 --- a/osdep/MacKextEthernetTap.hpp +++ b/osdep/MacKextEthernetTap.hpp @@ -56,6 +56,8 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char *domain, const std::vector &servers); + void threadMain() throw(); diff --git a/osdep/NetBSDEthernetTap.hpp b/osdep/NetBSDEthernetTap.hpp index 534712e45..9c00c0e25 100644 --- a/osdep/NetBSDEthernetTap.hpp +++ b/osdep/NetBSDEthernetTap.hpp @@ -53,6 +53,7 @@ public: virtual std::string deviceName() const; virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); + virtual void setDns(const char *domain, const std::vector &servers) {} void threadMain() throw(); diff --git a/osdep/WinDNSHelper.cpp b/osdep/WinDNSHelper.cpp new file mode 100644 index 000000000..6bcd2b476 --- /dev/null +++ b/osdep/WinDNSHelper.cpp @@ -0,0 +1,353 @@ +#include "WinDNSHelper.hpp" + +#include +#include + +#include +#include +#include +#include + +#define MAX_KEY_LENGTH 255 +#define MAX_VALUE_NAME 16383 + +namespace ZeroTier +{ + +BOOL RegDelnodeRecurse(HKEY hKeyRoot, LPTSTR lpSubKey) +{ + LPTSTR lpEnd; + LONG lResult; + DWORD dwSize; + TCHAR szName[MAX_PATH]; + HKEY hKey; + FILETIME ftWrite; + + // First, see if we can delete the key without having + // to recurse. + + lResult = RegDeleteKey(hKeyRoot, lpSubKey); + + if (lResult == ERROR_SUCCESS) + return TRUE; + + lResult = RegOpenKeyEx(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey); + + if (lResult != ERROR_SUCCESS) + { + if (lResult == ERROR_FILE_NOT_FOUND) { + return TRUE; + } + else { + return FALSE; + } + } + + // Check for an ending slash and add one if it is missing. + + lpEnd = lpSubKey + lstrlen(lpSubKey); + + if (*(lpEnd - 1) != TEXT('\\')) + { + *lpEnd = TEXT('\\'); + lpEnd++; + *lpEnd = TEXT('\0'); + } + + // Enumerate the keys + + dwSize = MAX_PATH; + lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL, + NULL, NULL, &ftWrite); + + if (lResult == ERROR_SUCCESS) + { + do { + + *lpEnd = TEXT('\0'); + StringCchCat(lpSubKey, MAX_PATH * 2, szName); + + if (!RegDelnodeRecurse(hKeyRoot, lpSubKey)) { + break; + } + + dwSize = MAX_PATH; + + lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL, + NULL, NULL, &ftWrite); + + } while (lResult == ERROR_SUCCESS); + } + + lpEnd--; + *lpEnd = TEXT('\0'); + + RegCloseKey(hKey); + + // Try again to delete the key. + + lResult = RegDeleteKey(hKeyRoot, lpSubKey); + + if (lResult == ERROR_SUCCESS) + return TRUE; + + return FALSE; +} + +//************************************************************* +// +// RegDelnode() +// +// Purpose: Deletes a registry key and all its subkeys / values. +// +// Parameters: hKeyRoot - Root key +// lpSubKey - SubKey to delete +// +// Return: TRUE if successful. +// FALSE if an error occurs. +// +//************************************************************* + +BOOL RegDelnode(HKEY hKeyRoot, LPCTSTR lpSubKey) +{ + TCHAR szDelKey[MAX_PATH * 2]; + + StringCchCopy(szDelKey, MAX_PATH * 2, lpSubKey); + return RegDelnodeRecurse(hKeyRoot, szDelKey); + +} +std::vector getSubKeys(const char* key) +{ + std::vector subkeys; + HKEY hKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, + key, + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + + TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name + DWORD cbName; // size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name + DWORD cchClassName = MAX_PATH; // size of class string + DWORD cSubKeys = 0; // number of subkeys + DWORD cbMaxSubKey; // longest subkey size + DWORD cchMaxClass; // longest class string + DWORD cValues; // number of values for key + DWORD cchMaxValue; // longest value name + DWORD cbMaxValueData; // longest value data + DWORD cbSecurityDescriptor; // size of security descriptor + FILETIME ftLastWriteTime; // last write time + + DWORD i, retCode; + + TCHAR achValue[MAX_VALUE_NAME]; + DWORD cchValue = MAX_VALUE_NAME; + + retCode = RegQueryInfoKey( + hKey, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + + for (i = 0; i < cSubKeys; ++i) { + cbName = MAX_KEY_LENGTH; + retCode = RegEnumKeyEx( + hKey, + i, + achKey, + &cbName, + NULL, + NULL, + NULL, + &ftLastWriteTime); + if (retCode == ERROR_SUCCESS) { + subkeys.push_back(achKey); + } + } + } + RegCloseKey(hKey); + return subkeys; +} + +std::vector getValueList(const char* key) { + std::vector values; + HKEY hKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, + key, + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + + TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name + DWORD cbName; // size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name + DWORD cchClassName = MAX_PATH; // size of class string + DWORD cSubKeys = 0; // number of subkeys + DWORD cbMaxSubKey; // longest subkey size + DWORD cchMaxClass; // longest class string + DWORD cValues; // number of values for key + DWORD cchMaxValue; // longest value name + DWORD cbMaxValueData; // longest value data + DWORD cbSecurityDescriptor; // size of security descriptor + FILETIME ftLastWriteTime; // last write time + + DWORD i, retCode; + + TCHAR achValue[MAX_VALUE_NAME]; + DWORD cchValue = MAX_VALUE_NAME; + + retCode = RegQueryInfoKey( + hKey, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + + for (i = 0, retCode = ERROR_SUCCESS; i < cValues; ++i) { + cchValue = MAX_VALUE_NAME; + achValue[0] = '\0'; + retCode = RegEnumValue( + hKey, + i, + achValue, + &cchValue, + NULL, + NULL, + NULL, + NULL); + if (retCode == ERROR_SUCCESS) { + values.push_back(achValue); + } + } + } + RegCloseKey(hKey); + return values; +} + +std::pair WinDNSHelper::hasDNSConfig(uint64_t nwid) +{ + char networkStr[20] = { 0 }; + sprintf(networkStr, "%.16llx", nwid); + + const char* baseKey = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig"; + auto subkeys = getSubKeys(baseKey); + for (auto it = subkeys.begin(); it != subkeys.end(); ++it) { + char sub[MAX_KEY_LENGTH] = { 0 }; + sprintf(sub, "%s\\%s", baseKey, it->c_str()); + auto dnsRecords = getValueList(sub); + for (auto it2 = dnsRecords.begin(); it2 != dnsRecords.end(); ++it2) { + if ((*it2) == "Comment") { + HKEY hKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, + sub, + 0, + KEY_READ, + &hKey) == ERROR_SUCCESS) { + + char buf[16384] = { 0 }; + DWORD size = sizeof(buf); + DWORD retCode = RegGetValueA( + HKEY_LOCAL_MACHINE, + sub, + it2->c_str(), + RRF_RT_REG_SZ, + NULL, + &buf, + &size); + if (retCode == ERROR_SUCCESS) { + if (std::string(networkStr) == std::string(buf)) { + RegCloseKey(hKey); + return std::make_pair(true, std::string(sub)); + } + } + else { + + } + } + RegCloseKey(hKey); + } + } + } + + return std::make_pair(false, std::string()); +} + +void WinDNSHelper::setDNS(uint64_t nwid, const char* domain, const std::vector& servers) +{ + auto hasConfig = hasDNSConfig(nwid); + + std::stringstream ss; + for (auto it = servers.begin(); it != servers.end(); ++it) { + char ipaddr[256] = { 0 }; + ss << it->toIpString(ipaddr); + if ((it + 1) != servers.end()) { + ss << ";"; + } + } + std::string serverValue = ss.str(); + + if (hasConfig.first) { + // update existing config + HKEY dnsKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, hasConfig.second.c_str(), 0, KEY_READ | KEY_WRITE, &dnsKey) == ERROR_SUCCESS) { + auto retCode = RegSetKeyValueA(dnsKey, NULL, "GenericDNSServers", REG_SZ, serverValue.data(), (DWORD)serverValue.length()); + if (retCode != ERROR_SUCCESS) { + fprintf(stderr, "Error writing dns servers: %d\n", retCode); + } + } + } else { + // add new config + const char* baseKey = "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsPolicyConfig"; + GUID guid; + CoCreateGuid(&guid); + wchar_t guidTmp[128] = { 0 }; + char guidStr[128] = { 0 }; + StringFromGUID2(guid, guidTmp, 128); + wcstombs(guidStr, guidTmp, 128); + char fullKey[MAX_KEY_LENGTH] = { 0 }; + sprintf(fullKey, "%s\\%s", baseKey, guidStr); + HKEY dnsKey; + RegCreateKeyA(HKEY_LOCAL_MACHINE, fullKey, &dnsKey); + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, fullKey, 0, KEY_READ | KEY_WRITE, &dnsKey) == ERROR_SUCCESS) { + char nwString[32] = { 0 }; + sprintf(nwString, "%.16llx", nwid); + RegSetKeyValueA(dnsKey, NULL, "Comment", REG_SZ, nwString, strlen(nwString)); + + DWORD configOpts = 8; + RegSetKeyValueA(dnsKey, NULL, "ConfigOptions", REG_DWORD, &configOpts, sizeof(DWORD)); + RegSetKeyValueA(dnsKey, NULL, "DisplayName", REG_SZ, "", 0); + RegSetKeyValueA(dnsKey, NULL, "GenericDNSServers", REG_SZ, serverValue.data(), serverValue.length()); + RegSetKeyValueA(dnsKey, NULL, "IPSECCARestriction", REG_SZ, "", 0); + std::string d = "." + std::string(domain); + RegSetKeyValueA(dnsKey, NULL, "Name", REG_MULTI_SZ, d.data(), d.length()); + DWORD version = 2; + RegSetKeyValueA(dnsKey, NULL, "Version", REG_DWORD, &version, sizeof(DWORD)); + } + } +} + +void WinDNSHelper::removeDNS(uint64_t nwid) +{ + auto hasConfig = hasDNSConfig(nwid); + if (hasConfig.first) { + RegDelnode(HKEY_LOCAL_MACHINE, hasConfig.second.c_str()); + } +} + +} \ No newline at end of file diff --git a/osdep/WinDNSHelper.hpp b/osdep/WinDNSHelper.hpp new file mode 100644 index 000000000..c42ba0775 --- /dev/null +++ b/osdep/WinDNSHelper.hpp @@ -0,0 +1,24 @@ +#ifndef WIN_DNS_HELPER_H_ +#define WIN_DNS_HELPER_H_ + +#include +#include +#include "../node/InetAddress.hpp" + + +namespace ZeroTier +{ + +class WinDNSHelper +{ +public: + static void setDNS(uint64_t nwid, const char* domain, const std::vector& servers); + static void removeDNS(uint64_t nwid); + +private: + static std::pair hasDNSConfig(uint64_t nwid); +}; + +} + +#endif \ No newline at end of file diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp index fb4b498d1..17aa99116 100644 --- a/osdep/WindowsEthernetTap.cpp +++ b/osdep/WindowsEthernetTap.cpp @@ -44,6 +44,7 @@ #include "OSUtils.hpp" #include "..\windows\TapDriver6\tap-windows.h" +#include "WinDNSHelper.hpp" #include @@ -473,6 +474,29 @@ WindowsEthernetTap::WindowsEthernetTap( char data[1024]; char tag[24]; + // Initialize COM + HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED); + if (FAILED(hres)) { + throw std::runtime_error("WinEthernetTap: COM initialization failed"); + } + + hres = CoInitializeSecurity( + NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE, + NULL + ); + if (FAILED(hres)) { + CoUninitialize(); + throw std::runtime_error("WinEthernetTap: Failed to initialize security"); + } + + // We "tag" registry entries with the network ID to identify persistent devices OSUtils::ztsnprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); @@ -646,6 +670,8 @@ WindowsEthernetTap::WindowsEthernetTap( WindowsEthernetTap::~WindowsEthernetTap() { + WinDNSHelper::removeDNS(_nwid); + CoUninitialize(); _run = false; ReleaseSemaphore(_injectSemaphore,1,NULL); Thread::join(_thread); @@ -1290,4 +1316,9 @@ void WindowsEthernetTap::_syncIps() } } +void WindowsEthernetTap::setDns(const char* domain, const std::vector& servers) +{ + WinDNSHelper::setDNS(_nwid, domain, servers); +} + } // namespace ZeroTier diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp index ea08b2fc0..c03c1e414 100644 --- a/osdep/WindowsEthernetTap.hpp +++ b/osdep/WindowsEthernetTap.hpp @@ -97,6 +97,7 @@ public: virtual void setFriendlyName(const char *friendlyName); virtual void scanMulticastGroups(std::vector &added,std::vector &removed); virtual void setMtu(unsigned int mtu); + virtual void setDns(const char* domain, const std::vector &servers); inline const NET_LUID &luid() const { return _deviceLuid; } inline const GUID &guid() const { return _deviceGuid; } diff --git a/service/OneService.cpp b/service/OneService.cpp index c5d8076d4..076dbb59d 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -231,6 +231,20 @@ static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc, mca.push_back(m); } nj["multicastSubscriptions"] = mca; + + nlohmann::json m; + m["domain"] = nc->dns.domain; + m["servers"] = nlohmann::json::array(); + for(int j=0;jdns.server_addr[j]); + if (a.isV4() || a.isV6()) { + char buf[256]; + m["servers"].push_back(a.toIpString(buf)); + } + } + nj["dns"] = m; + } static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer) @@ -501,6 +515,7 @@ public: settings.allowManaged = true; settings.allowGlobal = false; settings.allowDefault = false; + memset(&config, 0, sizeof(ZT_VirtualNetworkConfig)); } std::shared_ptr tap; @@ -831,7 +846,7 @@ public: Mutex::Lock _l(_nets_m); for(std::map::iterator n(_nets.begin());n!=_nets.end();++n) { if (n->second.tap) - syncManagedStuff(n->second,false,true); + syncManagedStuff(n->second,false,true,false); } } } @@ -1117,7 +1132,7 @@ public: } if (n->second.tap) - syncManagedStuff(n->second,true,true); + syncManagedStuff(n->second,true,true,true); return true; } @@ -1864,7 +1879,7 @@ public: } // Apply or update managed IPs for a configured network (be sure n.tap exists) - void syncManagedStuff(NetworkState &n,bool syncIps,bool syncRoutes) + void syncManagedStuff(NetworkState &n,bool syncIps,bool syncRoutes, bool syncDns) { char ipbuf[64]; @@ -1984,6 +1999,19 @@ public: #endif } } + + if (syncDns) { + if (strlen(n.config.dns.domain) != 0) { + std::vector servers; + for (int j = 0; j < ZT_MAX_DNS_SERVERS; ++j) { + InetAddress a(n.config.dns.server_addr[j]); + if (a.isV4() || a.isV6()) { + servers.push_back(a); + } + } + n.tap->setDns(n.config.dns.domain, servers); + } + } } // ========================================================================= @@ -2333,7 +2361,7 @@ public: Sleep(10); } #endif - syncManagedStuff(n,true,true); + syncManagedStuff(n,true,true,true); n.tap->setMtu(nwc->mtu); } else { _nets.erase(nwid); diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index a91ed7d1a..69036aa0a 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -33,7 +33,6 @@ - @@ -51,6 +50,8 @@ + + MaxSpeed MaxSpeed @@ -100,6 +101,7 @@ + true @@ -135,7 +137,7 @@ - + @@ -162,6 +164,8 @@ + + @@ -205,6 +209,7 @@ + @@ -240,6 +245,7 @@ true v142 MultiByte + false Application @@ -367,7 +373,7 @@ true - wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false "notelemetry.obj" %(AdditionalOptions) @@ -456,7 +462,7 @@ false true true - wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) + wbemuuid.lib;wsock32.lib;ws2_32.lib;Iphlpapi.lib;Rpcrt4.lib;%(AdditionalDependencies) false diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index 78e088c73..6abe49c0d 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -90,9 +90,6 @@ Source Files\service - - Source Files\osdep - Source Files\osdep @@ -264,9 +261,6 @@ Source Files\controller - - Source Files\controller - Source Files\controller @@ -276,6 +270,18 @@ Source Files\osdep + + Source Files\node + + + Source Files\node + + + Source Files\osdep + + + Source Files\osdep + @@ -515,9 +521,6 @@ Header Files\controller - - Header Files\controller - Header Files\controller @@ -530,6 +533,18 @@ Header Files\osdep + + Header Files\node + + + Header Files\node + + + Header Files\controller + + + Header Files\osdep +