diff options
Diffstat (limited to 'controller')
| -rw-r--r-- | controller/EmbeddedNetworkController.cpp | 844 | ||||
| -rw-r--r-- | controller/EmbeddedNetworkController.hpp | 121 | ||||
| -rw-r--r-- | controller/JSONDB.cpp | 370 | ||||
| -rw-r--r-- | controller/JSONDB.hpp | 125 | ||||
| -rw-r--r-- | controller/README.md | 22 | ||||
| -rw-r--r-- | controller/migrate-sqlite/migrate.js | 2 |
6 files changed, 790 insertions, 694 deletions
diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 597bc9c9..b57a37e8 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -1,6 +1,6 @@ /* * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2015 ZeroTier, Inc. + * Copyright (C) 2011-2015 ZeroTier, Inc-> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,8 +30,9 @@ #include <algorithm> #include <utility> #include <stdexcept> -#include <set> #include <map> +#include <thread> +#include <memory> #include "../include/ZeroTierOne.h" #include "../node/Constants.hpp" @@ -58,9 +59,6 @@ using json = nlohmann::json; // Min duration between requests for an address/nwid combo to prevent floods #define ZT_NETCONF_MIN_REQUEST_PERIOD 1000 -// Nodes are considered active if they've queried in less than this long -#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD (ZT_NETWORK_AUTOCONF_DELAY * 2) - namespace ZeroTier { static json _renderRule(ZT_VirtualNetworkRule &rule) @@ -78,19 +76,19 @@ static json _renderRule(ZT_VirtualNetworkRule &rule) break; case ZT_NETWORK_RULE_ACTION_TEE: r["type"] = "ACTION_TEE"; - r["address"] = Address(rule.v.fwd.address).toString(); + r["address"] = Address(rule.v.fwd.address).toString(tmp); r["flags"] = (unsigned int)rule.v.fwd.flags; r["length"] = (unsigned int)rule.v.fwd.length; break; case ZT_NETWORK_RULE_ACTION_WATCH: r["type"] = "ACTION_WATCH"; - r["address"] = Address(rule.v.fwd.address).toString(); + r["address"] = Address(rule.v.fwd.address).toString(tmp); r["flags"] = (unsigned int)rule.v.fwd.flags; r["length"] = (unsigned int)rule.v.fwd.length; break; case ZT_NETWORK_RULE_ACTION_REDIRECT: r["type"] = "ACTION_REDIRECT"; - r["address"] = Address(rule.v.fwd.address).toString(); + r["address"] = Address(rule.v.fwd.address).toString(tmp); r["flags"] = (unsigned int)rule.v.fwd.flags; break; case ZT_NETWORK_RULE_ACTION_BREAK: @@ -104,11 +102,11 @@ static json _renderRule(ZT_VirtualNetworkRule &rule) switch(rt) { case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS"; - r["zt"] = Address(rule.v.zt).toString(); + r["zt"] = Address(rule.v.zt).toString(tmp); break; case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS"; - r["zt"] = Address(rule.v.zt).toString(); + r["zt"] = Address(rule.v.zt).toString(tmp); break; case ZT_NETWORK_RULE_MATCH_VLAN_ID: r["type"] = "MATCH_VLAN_ID"; @@ -124,29 +122,29 @@ static json _renderRule(ZT_VirtualNetworkRule &rule) break; case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: r["type"] = "MATCH_MAC_SOURCE"; - Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); r["mac"] = tmp; break; case ZT_NETWORK_RULE_MATCH_MAC_DEST: r["type"] = "MATCH_MAC_DEST"; - Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); r["mac"] = tmp; break; case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: r["type"] = "MATCH_IPV4_SOURCE"; - r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(); + r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(tmp); break; case ZT_NETWORK_RULE_MATCH_IPV4_DEST: r["type"] = "MATCH_IPV4_DEST"; - r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(); + r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(tmp); break; case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: r["type"] = "MATCH_IPV6_SOURCE"; - r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(); + r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(tmp); break; case ZT_NETWORK_RULE_MATCH_IPV6_DEST: r["type"] = "MATCH_IPV6_DEST"; - r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(); + r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(tmp); break; case ZT_NETWORK_RULE_MATCH_IP_TOS: r["type"] = "MATCH_IP_TOS"; @@ -181,7 +179,7 @@ static json _renderRule(ZT_VirtualNetworkRule &rule) break; case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: r["type"] = "MATCH_CHARACTERISTICS"; - Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics); + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics); r["mask"] = tmp; break; case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: @@ -314,28 +312,28 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) return true; } else if (t == "MATCH_IPV4_SOURCE") { rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_SOURCE; - InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0")); + InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0").c_str()); rule.v.ipv4.ip = reinterpret_cast<struct sockaddr_in *>(&ip)->sin_addr.s_addr; rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in *>(&ip)->sin_port) & 0xff; if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32; return true; } else if (t == "MATCH_IPV4_DEST") { rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_DEST; - InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0")); + InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0").c_str()); rule.v.ipv4.ip = reinterpret_cast<struct sockaddr_in *>(&ip)->sin_addr.s_addr; rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in *>(&ip)->sin_port) & 0xff; if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32; return true; } else if (t == "MATCH_IPV6_SOURCE") { rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_SOURCE; - InetAddress ip(OSUtils::jsonString(r["ip"],"::0")); + InetAddress ip(OSUtils::jsonString(r["ip"],"::0").c_str()); memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16); rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff; if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128; return true; } else if (t == "MATCH_IPV6_DEST") { rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_DEST; - InetAddress ip(OSUtils::jsonString(r["ip"],"::0")); + InetAddress ip(OSUtils::jsonString(r["ip"],"::0").c_str()); memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16); rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff; if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128; @@ -430,7 +428,7 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath) : _startTime(OSUtils::now()), - _threadsStarted(false), + _running(true), _db(dbPath), _node(node) { @@ -438,12 +436,16 @@ EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPa EmbeddedNetworkController::~EmbeddedNetworkController() { - Mutex::Lock _l(_threads_m); - if (_threadsStarted) { - for(int i=0;i<(ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT*2);++i) - _queue.post((_RQEntry *)0); - for(int i=0;i<ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT;++i) - Thread::join(_threads[i]); + std::vector<Thread> t; + { + Mutex::Lock _l(_threads_m); + _running = false; + t = _threads; + } + if (t.size() > 0) { + _queue.stop(); + for(std::vector<Thread>::iterator i(t.begin());i!=t.end();++i) + Thread::join(*i); } } @@ -462,22 +464,14 @@ void EmbeddedNetworkController::request( { if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) return; - - { - Mutex::Lock _l(_threads_m); - if (!_threadsStarted) { - for(int i=0;i<ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT;++i) - _threads[i] = Thread::start(this); - } - _threadsStarted = true; - } - + _startThreads(); _RQEntry *qe = new _RQEntry; qe->nwid = nwid; qe->requestPacketId = requestPacketId; qe->fromAddr = fromAddr; qe->identity = identity; qe->metaData = metaData; + qe->type = _RQEntry::RQENTRY_TYPE_REQUEST; _queue.post(qe); } @@ -493,15 +487,8 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( if ((path.size() >= 2)&&(path[1].length() == 16)) { const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - char nwids[24]; - Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); - json network; - { - Mutex::Lock _l(_db_m); - network = _db.get("network",nwids); - } - if (!network.size()) + if (!_db.getNetwork(nwid,network)) return 404; if (path.size() >= 3) { @@ -509,82 +496,73 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( if (path[2] == "member") { if (path.size() >= 4) { - const uint64_t address = Utils::hexStrToU64(path[3].c_str()); + // Get member + const uint64_t address = Utils::hexStrToU64(path[3].c_str()); json member; - { - Mutex::Lock _l(_db_m); - member = _db.get("network",nwids,"member",Address(address).toString()); - } - if (!member.size()) + if (!_db.getNetworkMember(nwid,address,member)) return 404; - - _addMemberNonPersistedFields(member,OSUtils::now()); + _addMemberNonPersistedFields(nwid,address,member,OSUtils::now()); responseBody = OSUtils::jsonDump(member); responseContentType = "application/json"; - return 200; } else { - - Mutex::Lock _l(_db_m); + // List members and their revisions responseBody = "{"; - _db.filter((std::string("network/") + nwids + "/member/"),[&responseBody](const std::string &n,const json &member) { + responseBody.reserve((_db.memberCount(nwid) + 1) * 32); + _db.eachMember(nwid,[&responseBody](uint64_t networkId,uint64_t nodeId,const json &member) { if ((member.is_object())&&(member.size() > 0)) { - responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); - responseBody.append(OSUtils::jsonString(member["id"],"0")); - responseBody.append("\":"); - responseBody.append(OSUtils::jsonString(member["revision"],"0")); + char tmp[128]; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s%.10llx\":%llu",(responseBody.length() > 1) ? ",\"" : "\"",(unsigned long long)nodeId,(unsigned long long)OSUtils::jsonInt(member["revision"],0)); + responseBody.append(tmp); } - return true; // never delete }); responseBody.push_back('}'); responseContentType = "application/json"; - return 200; } + return 200; } // else 404 } else { + // Get network const uint64_t now = OSUtils::now(); - _NetworkMemberInfo nmi; - _getNetworkMemberInfo(now,nwid,nmi); - _addNetworkNonPersistedFields(network,now,nmi); + JSONDB::NetworkSummaryInfo ns; + _db.getNetworkSummaryInfo(nwid,ns); + _addNetworkNonPersistedFields(network,now,ns); responseBody = OSUtils::jsonDump(network); responseContentType = "application/json"; return 200; } } else if (path.size() == 1) { - - std::set<std::string> networkIds; - { - Mutex::Lock _l(_db_m); - _db.filter("network/",[&networkIds](const std::string &n,const json &obj) { - if (n.length() == (16 + 8)) - networkIds.insert(n.substr(8)); - return true; // do not delete - }); - } - - responseBody.push_back('['); - for(std::set<std::string>::iterator i(networkIds.begin());i!=networkIds.end();++i) { - responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); - responseBody.append(*i); - responseBody.append("\""); + // List networks + + std::vector<uint64_t> networkIds(_db.networkIds()); + char tmp[64]; + responseBody = "["; + responseBody.reserve((networkIds.size() + 1) * 24); + for(std::vector<uint64_t>::const_iterator i(networkIds.begin());i!=networkIds.end();++i) { + if (responseBody.length() > 1) + responseBody.push_back(','); + OSUtils::ztsnprintf(tmp,sizeof(tmp),"\"%.16llx\"",(unsigned long long)*i); + responseBody.append(tmp); } responseBody.push_back(']'); responseContentType = "application/json"; + return 200; } // else 404 } else { + // Controller status char tmp[4096]; - Utils::snprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); + OSUtils::ztsnprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); responseBody = tmp; responseContentType = "application/json"; return 200; @@ -625,20 +603,17 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( if ((path.size() >= 2)&&(path[1].length() == 16)) { uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); char nwids[24]; - Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); if (path.size() >= 3) { if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { uint64_t address = Utils::hexStrToU64(path[3].c_str()); char addrs[24]; - Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address); + OSUtils::ztsnprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address); json member; - { - Mutex::Lock _l(_db_m); - member = _db.get("network",nwids,"member",Address(address).toString()); - } + _db.getNetworkMember(nwid,address,member); json origMember(member); // for detecting changes _initMember(member); @@ -662,13 +637,13 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( // Member is being de-authorized, so spray Revocation objects to all online members if (!newAuth) { - _clearNetworkMemberInfoCache(nwid); Revocation rev((uint32_t)_node->prng(),nwid,0,now,ZT_REVOCATION_FLAG_FAST_PROPAGATE,Address(address),Revocation::CREDENTIAL_TYPE_COM); rev.sign(_signingId); - Mutex::Lock _l(_lastRequestTime_m); - for(std::map< std::pair<uint64_t,uint64_t>,uint64_t >::iterator i(_lastRequestTime.begin());i!=_lastRequestTime.end();++i) { - if ((now - i->second) < ZT_NETWORK_AUTOCONF_DELAY) - _node->ncSendRevocation(Address(i->first.first),rev); + + Mutex::Lock _l(_memberStatus_m); + for(auto i=_memberStatus.begin();i!=_memberStatus.end();++i) { + if ((i->first.networkId == nwid)&&(i->second.online(now))) + _node->ncSendRevocation(Address(i->first.nodeId),rev); } } } @@ -680,9 +655,10 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( json mipa(json::array()); for(unsigned long i=0;i<ipa.size();++i) { std::string ips = ipa[i]; - InetAddress ip(ips); + InetAddress ip(ips.c_str()); if ((ip.ss_family == AF_INET)||(ip.ss_family == AF_INET6)) { - mipa.push_back(ip.toIpString()); + char tmpip[64]; + mipa.push_back(ip.toIpString(tmpip)); } } member["ipAssignments"] = mipa; @@ -731,106 +707,52 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( member["address"] = addrs; // legacy member["nwid"] = nwids; + _removeMemberNonPersistedFields(member); if (member != origMember) { - member["lastModified"] = now; json &revj = member["revision"]; member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); - { - Mutex::Lock _l(_db_m); - _db.put("network",nwids,"member",Address(address).toString(),member); - } - _pushMemberUpdate(now,nwid,member); + _db.saveNetworkMember(nwid,address,member); + + // Push update to member if online + try { + Mutex::Lock _l(_memberStatus_m); + _MemberStatus &ms = _memberStatus[_MemberStatusKey(nwid,address)]; + if ((ms.online(now))&&(ms.lastRequestMetaData)) + request(nwid,InetAddress(),0,ms.identity,ms.lastRequestMetaData); + } catch ( ... ) {} } - // Add non-persisted fields - member["clock"] = now; - + _addMemberNonPersistedFields(nwid,address,member,now); responseBody = OSUtils::jsonDump(member); responseContentType = "application/json"; - return 200; - } else if ((path.size() == 3)&&(path[2] == "test")) { - - Mutex::Lock _l(_tests_m); - - _tests.push_back(ZT_CircuitTest()); - ZT_CircuitTest *const test = &(_tests.back()); - memset(test,0,sizeof(ZT_CircuitTest)); - - Utils::getSecureRandom(&(test->testId),sizeof(test->testId)); - test->credentialNetworkId = nwid; - test->ptr = (void *)this; - json hops = b["hops"]; - if (hops.is_array()) { - for(unsigned long i=0;i<hops.size();++i) { - json &hops2 = hops[i]; - if (hops2.is_array()) { - for(unsigned long j=0;j<hops2.size();++j) { - std::string s = hops2[j]; - test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; - } - ++test->hopCount; - } else if (hops2.is_string()) { - std::string s = hops2; - test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; - ++test->hopCount; - } - } - } - test->reportAtEveryHop = (OSUtils::jsonBool(b["reportAtEveryHop"],true) ? 1 : 0); - - if (!test->hopCount) { - _tests.pop_back(); - responseBody = "{ \"message\": \"a test must contain at least one hop\" }"; - responseContentType = "application/json"; - return 400; - } - - test->timestamp = OSUtils::now(); - - if (_node) { - _node->circuitTestBegin((void *)0,test,&(EmbeddedNetworkController::_circuitTestCallback)); - } else { - _tests.pop_back(); - return 500; - } - - char json[512]; - Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\",\"timestamp\":%llu}",test->testId,test->timestamp); - responseBody = json; - responseContentType = "application/json"; return 200; - } // else 404 } else { // POST to network ID - json network; - { - Mutex::Lock _l(_db_m); - - // Magic ID ending with ______ picks a random unused network ID - if (path[1].substr(10) == "______") { - nwid = 0; - uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL; - uint64_t nwidPostfix = 0; - for(unsigned long k=0;k<100000;++k) { // sanity limit on trials - Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); - uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); - if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; - Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid); - if (_db.get("network",nwids).size() <= 0) { - nwid = tryNwid; - break; - } + // Magic ID ending with ______ picks a random unused network ID + if (path[1].substr(10) == "______") { + nwid = 0; + uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL; + uint64_t nwidPostfix = 0; + for(unsigned long k=0;k<100000;++k) { // sanity limit on trials + Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); + uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); + if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; + if (!_db.hasNetwork(tryNwid)) { + nwid = tryNwid; + break; } - if (!nwid) - return 503; } - - network = _db.get("network",nwids); + if (!nwid) + return 503; } + OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + + json network; + _db.getNetwork(nwid,network); json origNetwork(network); // for detecting changes _initNetwork(network); @@ -840,6 +762,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false); if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = OSUtils::jsonBool(b["allowPassiveBridging"],false); if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL); + if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU); if (b.count("v4AssignMode")) { json nv4m; @@ -893,14 +816,15 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( json &target = rt["target"]; json &via = rt["via"]; if (target.is_string()) { - InetAddress t(target.get<std::string>()); + InetAddress t(target.get<std::string>().c_str()); InetAddress v; - if (via.is_string()) v.fromString(via.get<std::string>()); + if (via.is_string()) v.fromString(via.get<std::string>().c_str()); if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { json tmp; - tmp["target"] = t.toString(); + char tmp2[64]; + tmp["target"] = t.toString(tmp2); if (v.ss_family == t.ss_family) - tmp["via"] = v.toIpString(); + tmp["via"] = v.toIpString(tmp2); else tmp["via"] = json(); nrts.push_back(tmp); } @@ -918,12 +842,13 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( for(unsigned long i=0;i<ipp.size();++i) { json &ip = ipp[i]; if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("ipRangeEnd"))) { - InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],"")); - InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],"")); + InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],"").c_str()); + InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],"").c_str()); if ( ((f.ss_family == AF_INET)||(f.ss_family == AF_INET6)) && (f.ss_family == t.ss_family) ) { json tmp = json::object(); - tmp["ipRangeStart"] = f.toIpString(); - tmp["ipRangeEnd"] = t.toIpString(); + char tmp2[64]; + tmp["ipRangeStart"] = f.toIpString(tmp2); + tmp["ipRangeEnd"] = t.toIpString(tmp2); nipp.push_back(tmp); } } @@ -1040,25 +965,23 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( network["id"] = nwids; network["nwid"] = nwids; // legacy + _removeNetworkNonPersistedFields(network); if (network != origNetwork) { json &revj = network["revision"]; network["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); - network["lastModified"] = now; - { - Mutex::Lock _l(_db_m); - _db.put("network",nwids,network); - } + _db.saveNetwork(nwid,network); - // Send an update to all members of the network - _db.filter((std::string("network/") + nwids + "/member/"),[this,&now,&nwid](const std::string &n,const json &obj) { - _pushMemberUpdate(now,nwid,obj); - return true; // do not delete - }); + // Send an update to all members of the network that are online + Mutex::Lock _l(_memberStatus_m); + for(auto i=_memberStatus.begin();i!=_memberStatus.end();++i) { + if ((i->first.networkId == nwid)&&(i->second.online(now))&&(i->second.lastRequestMetaData)) + request(nwid,InetAddress(),0,i->second.identity,i->second.lastRequestMetaData); + } } - _NetworkMemberInfo nmi; - _getNetworkMemberInfo(now,nwid,nmi); - _addNetworkNonPersistedFields(network,now,nmi); + JSONDB::NetworkSummaryInfo ns; + _db.getNetworkSummaryInfo(nwid,ns); + _addNetworkNonPersistedFields(network,now,ns); responseBody = OSUtils::jsonDump(network); responseContentType = "application/json"; @@ -1069,14 +992,16 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( } else if (path[0] == "ping") { - json testRec; - const uint64_t now = OSUtils::now(); - testRec["clock"] = now; - testRec["uptime"] = (now - _startTime); - testRec["content"] = b; - responseBody = OSUtils::jsonDump(testRec); - _db.writeRaw("pong",responseBody); + _startThreads(); + _RQEntry *qe = new _RQEntry; + qe->type = _RQEntry::RQENTRY_TYPE_PING; + _queue.post(qe); + + char tmp[64]; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"{\"clock\":%llu,\"ping\":%s}",(unsigned long long)now,OSUtils::jsonDump(b).c_str()); + responseBody = tmp; responseContentType = "application/json"; + return 200; } @@ -1098,25 +1023,16 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( if (path[0] == "network") { if ((path.size() >= 2)&&(path[1].length() == 16)) { const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); - - char nwids[24]; - Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); - json network; - { - Mutex::Lock _l(_db_m); - network = _db.get("network",nwids); - } - if (!network.size()) - return 404; - if (path.size() >= 3) { if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { const uint64_t address = Utils::hexStrToU64(path[3].c_str()); - Mutex::Lock _l(_db_m); + json member = _db.eraseNetworkMember(nwid,address); - json member = _db.get("network",nwids,"member",Address(address).toString()); - _db.erase("network",nwids,"member",Address(address).toString()); + { + Mutex::Lock _l(_memberStatus_m); + _memberStatus.erase(_MemberStatusKey(nwid,address)); + } if (!member.size()) return 404; @@ -1125,17 +1041,19 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( return 200; } } else { - Mutex::Lock _l(_db_m); + json network = _db.eraseNetwork(nwid); - std::string pfx("network/"); - pfx.append(nwids); - _db.filter(pfx,[](const std::string &n,const json &obj) { - return false; // delete - }); - - Mutex::Lock _l2(_nmiCache_m); - _nmiCache.erase(nwid); + { + Mutex::Lock _l(_memberStatus_m); + for(auto i=_memberStatus.begin();i!=_memberStatus.end();) { + if (i->first.networkId == nwid) + _memberStatus.erase(i++); + else ++i; + } + } + if (!network.size()) + return 404; responseBody = OSUtils::jsonDump(network); responseContentType = "application/json"; return 200; @@ -1150,86 +1068,43 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( void EmbeddedNetworkController::threadMain() throw() { - uint64_t lastCircuitTestCheck = 0; - for(;;) { - _RQEntry *const qe = _queue.get(); // waits on next request - if (!qe) break; // enqueue a NULL to terminate threads + _RQEntry *qe = (_RQEntry *)0; + while ((_running)&&(_queue.get(qe))) { try { - _request(qe->nwid,qe->fromAddr,qe->requestPacketId,qe->identity,qe->metaData); + if (qe->type == _RQEntry::RQENTRY_TYPE_REQUEST) { + _request(qe->nwid,qe->fromAddr,qe->requestPacketId,qe->identity,qe->metaData); + } else if (qe->type == _RQEntry::RQENTRY_TYPE_PING) { + const uint64_t now = OSUtils::now(); + bool first = true; + std::string pong("{\"memberStatus\":{"); + { + Mutex::Lock _l(_memberStatus_m); + pong.reserve(48 * (_memberStatus.size() + 1)); + _db.eachId([this,&pong,&now,&first](uint64_t networkId,uint64_t nodeId) { + char tmp[64]; + uint64_t lrt = 0ULL; + auto ms = this->_memberStatus.find(_MemberStatusKey(networkId,nodeId)); + if (ms != _memberStatus.end()) + lrt = ms->second.lastRequestTime; + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s\"%.16llx-%.10llx\":%llu", + (first) ? "" : ",", + (unsigned long long)networkId, + (unsigned long long)nodeId, + (unsigned long long)lrt); + pong.append(tmp); + first = false; + }); + } + char tmp2[256]; + OSUtils::ztsnprintf(tmp2,sizeof(tmp2),"},\"clock\":%llu,\"startTime\":%llu}",(unsigned long long)now,(unsigned long long)_startTime); + pong.append(tmp2); + _db.writeRaw("pong",pong); + } } catch ( ... ) {} delete qe; - - uint64_t now = OSUtils::now(); - if ((now - lastCircuitTestCheck) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) { - lastCircuitTestCheck = now; - Mutex::Lock _l(_tests_m); - for(std::list< ZT_CircuitTest >::iterator i(_tests.begin());i!=_tests.end();) { - if ((now - i->timestamp) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) { - _node->circuitTestEnd(&(*i)); - _tests.erase(i++); - } else ++i; - } - } } } -void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report) -{ - char tmp[1024],id[128]; - EmbeddedNetworkController *const self = reinterpret_cast<EmbeddedNetworkController *>(test->ptr); - - if ((!test)||(!report)||(!test->credentialNetworkId)) return; // sanity check - - const uint64_t now = OSUtils::now(); - Utils::snprintf(id,sizeof(id),"network/%.16llx/test/%.16llx-%.16llx-%.10llx-%.10llx",test->credentialNetworkId,test->testId,now,report->upstream,report->current); - Utils::snprintf(tmp,sizeof(tmp), - "{\"id\": \"%s\"," - "\"timestamp\": %llu," - "\"networkId\": \"%.16llx\"," - "\"testId\": \"%.16llx\"," - "\"upstream\": \"%.10llx\"," - "\"current\": \"%.10llx\"," - "\"receivedTimestamp\": %llu," - "\"sourcePacketId\": \"%.16llx\"," - "\"flags\": %llu," - "\"sourcePacketHopCount\": %u," - "\"errorCode\": %u," - "\"vendor\": %d," - "\"protocolVersion\": %u," - "\"majorVersion\": %u," - "\"minorVersion\": %u," - "\"revision\": %u," - "\"platform\": %d," - "\"architecture\": %d," - "\"receivedOnLocalAddress\": \"%s\"," - "\"receivedFromRemoteAddress\": \"%s\"," - "\"receivedFromLinkQuality\": %f}", - id + 30, // last bit only, not leading path - (unsigned long long)test->timestamp, - (unsigned long long)test->credentialNetworkId, - (unsigned long long)test->testId, - (unsigned long long)report->upstream, - (unsigned long long)report->current, - (unsigned long long)now, - (unsigned long long)report->sourcePacketId, - (unsigned long long)report->flags, - report->sourcePacketHopCount, - report->errorCode, - (int)report->vendor, - report->protocolVersion, - report->majorVersion, - report->minorVersion, - report->revision, - (int)report->platform, - (int)report->architecture, - reinterpret_cast<const InetAddress *>(&(report->receivedOnLocalAddress))->toString().c_str(), - reinterpret_cast<const InetAddress *>(&(report->receivedFromRemoteAddress))->toString().c_str(), - ((double)report->receivedFromLinkQuality / (double)ZT_PATH_LINK_QUALITY_MAX)); - - Mutex::Lock _l(self->_db_m); - self->_db.writeRaw(id,std::string(tmp)); -} - void EmbeddedNetworkController::_request( uint64_t nwid, const InetAddress &fromAddr, @@ -1237,37 +1112,30 @@ void EmbeddedNetworkController::_request( const Identity &identity, const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData) { + char nwids[24]; + JSONDB::NetworkSummaryInfo ns; + json network,member,origMember; + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) return; const uint64_t now = OSUtils::now(); if (requestPacketId) { - Mutex::Lock _l(_lastRequestTime_m); - uint64_t &lrt = _lastRequestTime[std::pair<uint64_t,uint64_t>(identity.address().toInt(),nwid)]; - if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD) + Mutex::Lock _l(_memberStatus_m); + _MemberStatus &ms = _memberStatus[_MemberStatusKey(nwid,identity.address().toInt())]; + if ((now - ms.lastRequestTime) <= ZT_NETCONF_MIN_REQUEST_PERIOD) return; - lrt = now; + ms.lastRequestTime = now; } - char nwids[24]; - Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); - json network; - json member; - { - Mutex::Lock _l(_db_m); - network = _db.get("network",nwids); - member = _db.get("network",nwids,"member",identity.address().toString()); - } - - if (!network.size()) { + OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",nwid); + if (!_db.getNetworkAndMember(nwid,identity.address().toInt(),network,member,ns)) { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND); return; } - - const bool newMember = (member.size() == 0); - - json origMember(member); // for detecting modification later + origMember = member; + const bool newMember = ((!member.is_object())||(member.size() == 0)); _initMember(member); { @@ -1287,14 +1155,19 @@ void EmbeddedNetworkController::_request( } } else { // If we do not yet know this member's identity, learn it. - member["identity"] = identity.toString(false); + char idtmp[1024]; + member["identity"] = identity.toString(false,idtmp); } } // These are always the same, but make sure they are set - member["id"] = identity.address().toString(); - member["address"] = member["id"]; - member["nwid"] = nwids; + { + char tmpid[128]; + const std::string addrs(identity.address().toString(tmpid)); + member["id"] = addrs; + member["address"] = addrs; + member["nwid"] = nwids; + } // Determine whether and how member is authorized const char *authorizedBy = (const char *)0; @@ -1370,42 +1243,42 @@ void EmbeddedNetworkController::_request( member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); } - // Log this request - if (requestPacketId) { // only log if this is a request, not for generated pushes - json rlEntry = json::object(); - rlEntry["ts"] = now; - rlEntry["auth"] = (authorizedBy) ? true : false; - rlEntry["authBy"] = (authorizedBy) ? authorizedBy : ""; - rlEntry["vMajor"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0); - rlEntry["vMinor"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0); - rlEntry["vRev"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0); - rlEntry["vProto"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,0); - if (fromAddr) - rlEntry["fromAddr"] = fromAddr.toString(); - - json recentLog = json::array(); - recentLog.push_back(rlEntry); - json &oldLog = member["recentLog"]; - if (oldLog.is_array()) { - for(unsigned long i=0;i<oldLog.size();++i) { - recentLog.push_back(oldLog[i]); - if (recentLog.size() >= ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH) - break; - } - } - member["recentLog"] = recentLog; + if (authorizedBy) { + // Update version info and meta-data if authorized and if this is a genuine request + if (requestPacketId) { + const uint64_t vMajor = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0); + const uint64_t vMinor = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0); + const uint64_t vRev = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0); + const uint64_t vProto = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,0); - // Also only do this on real requests - member["lastRequestMetaData"] = metaData.data(); - } + member["vMajor"] = vMajor; + member["vMinor"] = vMinor; + member["vRev"] = vRev; + member["vProto"] = vProto; - // If they are not authorized, STOP! - if (!authorizedBy) { - if (origMember != member) { - member["lastModified"] = now; - Mutex::Lock _l(_db_m); - _db.put("network",nwids,"member",identity.address().toString(),member); + { + Mutex::Lock _l(_memberStatus_m); + _MemberStatus &ms = _memberStatus[_MemberStatusKey(nwid,identity.address().toInt())]; + + ms.vMajor = (int)vMajor; + ms.vMinor = (int)vMinor; + ms.vRev = (int)vRev; + ms.vProto = (int)vProto; + ms.lastRequestMetaData = metaData; + ms.identity = identity; + + if (fromAddr) + ms.physicalAddr = fromAddr; + char tmpip[64]; + if (ms.physicalAddr) + member["physicalAddr"] = ms.physicalAddr.toString(tmpip); + } } + } else { + // If they are not authorized, STOP! + _removeMemberNonPersistedFields(member); + if (origMember != member) + _db.saveNetworkMember(nwid,identity.address().toInt(),member); _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); return; } @@ -1414,16 +1287,12 @@ void EmbeddedNetworkController::_request( // If we made it this far, they are authorized. // ------------------------------------------------------------------------- - NetworkConfig nc; - _NetworkMemberInfo nmi; - _getNetworkMemberInfo(now,nwid,nmi); - uint64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; - if (now > nmi.mostRecentDeauthTime) { + if (now > ns.mostRecentDeauthTime) { // If we recently de-authorized a member, shrink credential TTL/max delta to // be below the threshold required to exclude it. Cap this to a min/max to // prevent jitter or absurdly large values. - const uint64_t deauthWindow = now - nmi.mostRecentDeauthTime; + const uint64_t deauthWindow = now - ns.mostRecentDeauthTime; if (deauthWindow < ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA) { credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA; } else if (deauthWindow < (ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA + 5000ULL)) { @@ -1431,20 +1300,22 @@ void EmbeddedNetworkController::_request( } } - nc.networkId = nwid; - nc.type = OSUtils::jsonBool(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; - nc.timestamp = now; - nc.credentialTimeMaxDelta = credentialtmd; - nc.revision = OSUtils::jsonInt(network["revision"],0ULL); - nc.issuedTo = identity.address(); - if (OSUtils::jsonBool(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; - if (OSUtils::jsonBool(network["allowPassiveBridging"],false)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; - Utils::scopy(nc.name,sizeof(nc.name),OSUtils::jsonString(network["name"],"").c_str()); - nc.multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL); - - for(std::set<Address>::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) { - nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); - } + std::auto_ptr<NetworkConfig> nc(new NetworkConfig()); + + nc->networkId = nwid; + nc->type = OSUtils::jsonBool(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; + nc->timestamp = now; + nc->credentialTimeMaxDelta = credentialtmd; + nc->revision = OSUtils::jsonInt(network["revision"],0ULL); + nc->issuedTo = identity.address(); + if (OSUtils::jsonBool(network["enableBroadcast"],true)) nc->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + if (OSUtils::jsonBool(network["allowPassiveBridging"],false)) nc->flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; + Utils::scopy(nc->name,sizeof(nc->name),OSUtils::jsonString(network["name"],"").c_str()); + nc->mtu = 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); + + for(std::vector<Address>::const_iterator ab(ns.activeBridges.begin());ab!=ns.activeBridges.end();++ab) + nc->addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); json &v4AssignMode = network["v4AssignMode"]; json &v6AssignMode = network["v6AssignMode"]; @@ -1460,15 +1331,15 @@ void EmbeddedNetworkController::_request( // Old versions with no rules engine support get an allow everything rule. // Since rules are enforced bidirectionally, newer versions *will* still // enforce rules on the inbound side. - nc.ruleCount = 1; - nc.rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT; + nc->ruleCount = 1; + nc->rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT; } else { if (rules.is_array()) { for(unsigned long i=0;i<rules.size();++i) { - if (nc.ruleCount >= ZT_MAX_NETWORK_RULES) + if (nc->ruleCount >= ZT_MAX_NETWORK_RULES) break; - if (_parseRule(rules[i],nc.rules[nc.ruleCount])) - ++nc.ruleCount; + if (_parseRule(rules[i],nc->rules[nc->ruleCount])) + ++nc->ruleCount; } } @@ -1512,10 +1383,10 @@ void EmbeddedNetworkController::_request( ++caprc; } } - nc.capabilities[nc.capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc); - if (nc.capabilities[nc.capabilityCount].sign(_signingId,identity.address())) - ++nc.capabilityCount; - if (nc.capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES) + nc->capabilities[nc->capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc); + if (nc->capabilities[nc->capabilityCount].sign(_signingId,identity.address())) + ++nc->capabilityCount; + if (nc->capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES) break; } } @@ -1546,31 +1417,31 @@ void EmbeddedNetworkController::_request( } } for(std::map< uint32_t,uint32_t >::const_iterator t(memberTagsById.begin());t!=memberTagsById.end();++t) { - if (nc.tagCount >= ZT_MAX_NETWORK_TAGS) + if (nc->tagCount >= ZT_MAX_NETWORK_TAGS) break; - nc.tags[nc.tagCount] = Tag(nwid,now,identity.address(),t->first,t->second); - if (nc.tags[nc.tagCount].sign(_signingId)) - ++nc.tagCount; + nc->tags[nc->tagCount] = Tag(nwid,now,identity.address(),t->first,t->second); + if (nc->tags[nc->tagCount].sign(_signingId)) + ++nc->tagCount; } } if (routes.is_array()) { for(unsigned long i=0;i<routes.size();++i) { - if (nc.routeCount >= ZT_MAX_NETWORK_ROUTES) + if (nc->routeCount >= ZT_MAX_NETWORK_ROUTES) break; json &route = routes[i]; json &target = route["target"]; json &via = route["via"]; if (target.is_string()) { - const InetAddress t(target.get<std::string>()); + const InetAddress t(target.get<std::string>().c_str()); InetAddress v; - if (via.is_string()) v.fromString(via.get<std::string>()); + if (via.is_string()) v.fromString(via.get<std::string>().c_str()); if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) { - ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]); + ZT_VirtualNetworkRoute *r = &(nc->routes[nc->routeCount]); *(reinterpret_cast<InetAddress *>(&(r->target))) = t; if (v.ss_family == t.ss_family) *(reinterpret_cast<InetAddress *>(&(r->via))) = v; - ++nc.routeCount; + ++nc->routeCount; } } } @@ -1579,13 +1450,13 @@ void EmbeddedNetworkController::_request( const bool noAutoAssignIps = OSUtils::jsonBool(member["noAutoAssignIps"],false); if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) { - if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { - nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()); - nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc->staticIps[nc->staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()); + nc->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; } - if ((OSUtils::jsonBool(v6AssignMode["6plane"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { - nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt()); - nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + if ((OSUtils::jsonBool(v6AssignMode["6plane"],false))&&(nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc->staticIps[nc->staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt()); + nc->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; } } @@ -1597,21 +1468,21 @@ void EmbeddedNetworkController::_request( if (!ipAssignments[i].is_string()) continue; std::string ips = ipAssignments[i]; - InetAddress ip(ips); + InetAddress ip(ips.c_str()); // IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from // this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source // of user error / poor UX. int routedNetmaskBits = 0; - for(unsigned int rk=0;rk<nc.routeCount;++rk) { - if ( (!nc.routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip)) ) - routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits(); + for(unsigned int rk=0;rk<nc->routeCount;++rk) { + if ( (!nc->routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->containsAddress(ip)) ) + routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->netmaskBits(); } if (routedNetmaskBits > 0) { - if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + if (nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { ip.setPort(routedNetmaskBits); - nc.staticIps[nc.staticIpCount++] = ip; + nc->staticIps[nc->staticIpCount++] = ip; } if (ip.ss_family == AF_INET) haveManagedIpv4AutoAssignment = true; @@ -1627,8 +1498,8 @@ void EmbeddedNetworkController::_request( for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv6AutoAssignment));++p) { json &pool = ipAssignmentPools[p]; if (pool.is_object()) { - InetAddress ipRangeStart(OSUtils::jsonString(pool["ipRangeStart"],"")); - InetAddress ipRangeEnd(OSUtils::jsonString(pool["ipRangeEnd"],"")); + InetAddress ipRangeStart(OSUtils::jsonString(pool["ipRangeStart"],"").c_str()); + InetAddress ipRangeEnd(OSUtils::jsonString(pool["ipRangeEnd"],"").c_str()); if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) { uint64_t s[2],e[2],x[2],xx[2]; memcpy(s,ipRangeStart.rawIpData(),16); @@ -1662,20 +1533,20 @@ void EmbeddedNetworkController::_request( // Check if this IP is within a local-to-Ethernet routed network int routedNetmaskBits = 0; - for(unsigned int rk=0;rk<nc.routeCount;++rk) { - if ( (!nc.routes[rk].via.ss_family) && (nc.routes[rk].target.ss_family == AF_INET6) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip6)) ) - routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits(); + for(unsigned int rk=0;rk<nc->routeCount;++rk) { + if ( (!nc->routes[rk].via.ss_family) && (nc->routes[rk].target.ss_family == AF_INET6) && (reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->containsAddress(ip6)) ) + routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc->routes[rk].target))->netmaskBits(); } // If it's routed, then try to claim and assign it and if successful end loop - if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip6))) { - ipAssignments.push_back(ip6.toIpString()); + if ( (routedNetmaskBits > 0) && (!std::binary_search(ns.allocatedIps.begin(),ns.allocatedIps.end(),ip6)) ) { + char tmpip[64]; + ipAssignments.push_back(ip6.toIpString(tmpip)); member["ipAssignments"] = ipAssignments; ip6.setPort((unsigned int)routedNetmaskBits); - if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) - nc.staticIps[nc.staticIpCount++] = ip6; + if (nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) + nc->staticIps[nc->staticIpCount++] = ip6; haveManagedIpv6AutoAssignment = true; - _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns break; } } @@ -1688,8 +1559,8 @@ void EmbeddedNetworkController::_request( for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv4AutoAssignment));++p) { json &pool = ipAssignmentPools[p]; if (pool.is_object()) { - InetAddress ipRangeStartIA(OSUtils::jsonString(pool["ipRangeStart"],"")); - InetAddress ipRangeEndIA(OSUtils::jsonString(pool["ipRangeEnd"],"")); + InetAddress ipRangeStartIA(OSUtils::jsonString(pool["ipRangeStart"],"").c_str()); + InetAddress ipRangeEndIA(OSUtils::jsonString(pool["ipRangeEnd"],"").c_str()); if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) { uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr)); uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeEndIA)->sin_addr.s_addr)); @@ -1708,10 +1579,10 @@ void EmbeddedNetworkController::_request( // Check if this IP is within a local-to-Ethernet routed network int routedNetmaskBits = -1; - for(unsigned int rk=0;rk<nc.routeCount;++rk) { - if (nc.routes[rk].target.ss_family == AF_INET) { - uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_addr.s_addr)); - int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_port)); + for(unsigned int rk=0;rk<nc->routeCount;++rk) { + if (nc->routes[rk].target.ss_family == AF_INET) { + uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc->routes[rk].target))->sin_addr.s_addr)); + int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc->routes[rk].target))->sin_port)); if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) { routedNetmaskBits = targetBits; break; @@ -1721,17 +1592,17 @@ void EmbeddedNetworkController::_request( // If it's routed, then try to claim and assign it and if successful end loop const InetAddress ip4(Utils::hton(ip),0); - if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip4))) { - ipAssignments.push_back(ip4.toIpString()); + if ( (routedNetmaskBits > 0) && (!std::binary_search(ns.allocatedIps.begin(),ns.allocatedIps.end(),ip4)) ) { + char tmpip[64]; + ipAssignments.push_back(ip4.toIpString(tmpip)); member["ipAssignments"] = ipAssignments; - if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { - struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++])); + if (nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc->staticIps[nc->staticIpCount++])); v4ip->sin_family = AF_INET; v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits); v4ip->sin_addr.s_addr = Utils::hton(ip); } haveManagedIpv4AutoAssignment = true; - _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns break; } } @@ -1741,114 +1612,27 @@ void EmbeddedNetworkController::_request( } // Issue a certificate of ownership for all static IPs - if (nc.staticIpCount) { - nc.certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1); - for(unsigned int i=0;i<nc.staticIpCount;++i) - nc.certificatesOfOwnership[0].addThing(nc.staticIps[i]); - nc.certificatesOfOwnership[0].sign(_signingId); - nc.certificateOfOwnershipCount = 1; + if (nc->staticIpCount) { + nc->certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1); + for(unsigned int i=0;i<nc->staticIpCount;++i) + nc->certificatesOfOwnership[0].addThing(nc->staticIps[i]); + nc->certificatesOfOwnership[0].sign(_signingId); + nc->certificateOfOwnershipCount = 1; } CertificateOfMembership com(now,credentialtmd,nwid,identity.address()); if (com.sign(_signingId)) { - nc.com = com; + nc->com = com; } else { _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR); return; } - if (member != origMember) { - member["lastModified"] = now; - Mutex::Lock _l(_db_m); - _db.put("network",nwids,"member",identity.address().toString(),member); - } - - _sender->ncSendConfig(nwid,requestPacketId,identity.address(),nc,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6); -} - -void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi) -{ - char pfx[256]; - Utils::snprintf(pfx,sizeof(pfx),"network/%.16llx/member",nwid); - - { - Mutex::Lock _l(_nmiCache_m); - std::map<uint64_t,_NetworkMemberInfo>::iterator c(_nmiCache.find(nwid)); - if ((c != _nmiCache.end())&&((now - c->second.nmiTimestamp) < 1000)) { // a short duration cache but limits CPU use on big networks - nmi = c->second; - return; - } - } - - { - Mutex::Lock _l(_db_m); - _db.filter(pfx,[&nmi,&now](const std::string &n,const json &member) { - try { - if (OSUtils::jsonBool(member["authorized"],false)) { - ++nmi.authorizedMemberCount; - - if (member.count("recentLog")) { - const json &mlog = member["recentLog"]; - if ((mlog.is_array())&&(mlog.size() > 0)) { - const json &mlog1 = mlog[0]; - if (mlog1.is_object()) { - if ((now - OSUtils::jsonInt(mlog1["ts"],0ULL)) < ZT_NETCONF_NODE_ACTIVE_THRESHOLD) - ++nmi.activeMemberCount; - } - } - } - - if (OSUtils::jsonBool(member["activeBridge"],false)) { - nmi.activeBridges.insert(Address(Utils::hexStrToU64(OSUtils::jsonString(member["id"],"0000000000").c_str()))); - } - - if (member.count("ipAssignments")) { - const json &mips = member["ipAssignments"]; - if (mips.is_array()) { - for(unsigned long i=0;i<mips.size();++i) { - InetAddress mip(OSUtils::jsonString(mips[i],"")); - if ((mip.ss_family == AF_INET)||(mip.ss_family == AF_INET6)) - nmi.allocatedIps.insert(mip); - } - } - } - } else { - nmi.mostRecentDeauthTime = std::max(nmi.mostRecentDeauthTime,OSUtils::jsonInt(member["lastDeauthorizedTime"],0ULL)); - } - } catch ( ... ) {} - return true; - }); - } - nmi.nmiTimestamp = now; - - { - Mutex::Lock _l(_nmiCache_m); - _nmiCache[nwid] = nmi; - } -} + _removeMemberNonPersistedFields(member); + if (member != origMember) + _db.saveNetworkMember(nwid,identity.address().toInt(),member); -void EmbeddedNetworkController::_pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member) -{ - try { - const std::string idstr = member["identity"]; - const std::string mdstr = member["lastRequestMetaData"]; - if ((idstr.length() > 0)&&(mdstr.length() > 0)) { - const Identity id(idstr); - bool online; - { - Mutex::Lock _l(_lastRequestTime_m); - std::map< std::pair<uint64_t,uint64_t>,uint64_t >::iterator lrt(_lastRequestTime.find(std::pair<uint64_t,uint64_t>(id.address().toInt(),nwid))); - online = ( (lrt != _lastRequestTime.end()) && ((now - lrt->second) < ZT_NETWORK_AUTOCONF_DELAY) ); - } - if (online) { - Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> *metaData = new Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY>(mdstr.c_str()); - try { - this->request(nwid,InetAddress(),0,id,*metaData); - } catch ( ... ) {} - delete metaData; - } - } - } catch ( ... ) {} + _sender->ncSendConfig(nwid,requestPacketId,identity.address(),*(nc.get()),metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6); } } // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index 0ae2f3b5..1589ea71 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -26,6 +26,7 @@ #include <vector> #include <set> #include <list> +#include <thread> #include "../node/Constants.hpp" @@ -34,6 +35,7 @@ #include "../node/Utils.hpp" #include "../node/Address.hpp" #include "../node/InetAddress.hpp" +#include "../node/NonCopyable.hpp" #include "../osdep/OSUtils.hpp" #include "../osdep/Thread.hpp" @@ -43,17 +45,11 @@ #include "JSONDB.hpp" -// Number of background threads to start -- not actually started until needed -#define ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT 4 - -// TTL for circuit tests -#define ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION 120000 - namespace ZeroTier { class Node; -class EmbeddedNetworkController : public NetworkController +class EmbeddedNetworkController : public NetworkController,NonCopyable { public: /** @@ -105,26 +101,27 @@ private: InetAddress fromAddr; Identity identity; Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData; + enum { + RQENTRY_TYPE_REQUEST = 0, + RQENTRY_TYPE_PING = 1 + } type; }; - // Gathers a bunch of statistics about members of a network, IP assignments, etc. that we need in various places - struct _NetworkMemberInfo - { - _NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {} - std::set<Address> activeBridges; - std::set<InetAddress> allocatedIps; - unsigned long authorizedMemberCount; - unsigned long activeMemberCount; - unsigned long totalMemberCount; - uint64_t mostRecentDeauthTime; - uint64_t nmiTimestamp; // time this NMI structure was computed - }; - - static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report); void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData); - void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi); - inline void _clearNetworkMemberInfoCache(const uint64_t nwid) { Mutex::Lock _l(_nmiCache_m); _nmiCache.erase(nwid); } - void _pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member); + + inline void _startThreads() + { + Mutex::Lock _l(_threads_m); + if (_threads.size() == 0) { + long hwc = (long)std::thread::hardware_concurrency(); + if (hwc < 1) + hwc = 1; + else if (hwc > 16) + hwc = 16; + for(long i=0;i<hwc;++i) + _threads.push_back(Thread::start(this)); + } + } // These init objects with default and static/informational fields inline void _initMember(nlohmann::json &member) @@ -132,7 +129,6 @@ private: if (!member.count("authorized")) member["authorized"] = false; if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array(); if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array(); - if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array(); if (!member.count("activeBridge")) member["activeBridge"] = false; if (!member.count("tags")) member["tags"] = nlohmann::json::array(); if (!member.count("capabilities")) member["capabilities"] = nlohmann::json::array(); @@ -141,6 +137,11 @@ private: if (!member.count("revision")) member["revision"] = 0ULL; if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL; if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL; + if (!member.count("vMajor")) member["vMajor"] = -1; + if (!member.count("vMinor")) member["vMinor"] = -1; + if (!member.count("vRev")) member["vRev"] = -1; + if (!member.count("vProto")) member["vProto"] = -1; + if (!member.count("physicalAddr")) member["physicalAddr"] = nlohmann::json(); member["objtype"] = "member"; } inline void _initNetwork(nlohmann::json &network) @@ -157,6 +158,7 @@ private: if (!network.count("tags")) network["tags"] = nlohmann::json::array(); if (!network.count("routes")) network["routes"] = nlohmann::json::array(); if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array(); + if (!network.count("mtu")) network["mtu"] = ZT_DEFAULT_MTU; if (!network.count("rules")) { // If unspecified, rules are set to allow anything and behave like a flat L2 segment network["rules"] = {{ @@ -167,30 +169,45 @@ private: } network["objtype"] = "network"; } - inline void _addNetworkNonPersistedFields(nlohmann::json &network,uint64_t now,const _NetworkMemberInfo &nmi) + inline void _addNetworkNonPersistedFields(nlohmann::json &network,uint64_t now,const JSONDB::NetworkSummaryInfo &ns) { network["clock"] = now; - network["authorizedMemberCount"] = nmi.authorizedMemberCount; - network["activeMemberCount"] = nmi.activeMemberCount; - network["totalMemberCount"] = nmi.totalMemberCount; + network["authorizedMemberCount"] = ns.authorizedMemberCount; + network["activeMemberCount"] = ns.activeMemberCount; + network["totalMemberCount"] = ns.totalMemberCount; } - inline void _addMemberNonPersistedFields(nlohmann::json &member,uint64_t now) + inline void _removeNetworkNonPersistedFields(nlohmann::json &network) + { + network.erase("clock"); + network.erase("authorizedMemberCount"); + network.erase("activeMemberCount"); + network.erase("totalMemberCount"); + // legacy fields + network.erase("lastModified"); + } + inline void _addMemberNonPersistedFields(uint64_t nwid,uint64_t nodeId,nlohmann::json &member,uint64_t now) { member["clock"] = now; + Mutex::Lock _l(_memberStatus_m); + member["online"] = _memberStatus[_MemberStatusKey(nwid,nodeId)].online(now); + } + inline void _removeMemberNonPersistedFields(nlohmann::json &member) + { + member.erase("clock"); + // legacy fields + member.erase("recentLog"); + member.erase("lastModified"); + member.erase("lastRequestMetaData"); } const uint64_t _startTime; + volatile bool _running; BlockingQueue<_RQEntry *> _queue; - Thread _threads[ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT]; - bool _threadsStarted; + std::vector<Thread> _threads; Mutex _threads_m; - std::map<uint64_t,_NetworkMemberInfo> _nmiCache; - Mutex _nmiCache_m; - JSONDB _db; - Mutex _db_m; Node *const _node; std::string _path; @@ -198,11 +215,33 @@ private: NetworkController::Sender *_sender; Identity _signingId; - std::list< ZT_CircuitTest > _tests; - Mutex _tests_m; - - std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime; // last request time by <address,networkId> - Mutex _lastRequestTime_m; + struct _MemberStatusKey + { + _MemberStatusKey() : networkId(0),nodeId(0) {} + _MemberStatusKey(const uint64_t nwid,const uint64_t nid) : networkId(nwid),nodeId(nid) {} + uint64_t networkId; + uint64_t nodeId; + inline bool operator==(const _MemberStatusKey &k) const { return ((k.networkId == networkId)&&(k.nodeId == nodeId)); } + }; + struct _MemberStatus + { + _MemberStatus() : lastRequestTime(0),vMajor(-1),vMinor(-1),vRev(-1),vProto(-1) {} + uint64_t lastRequestTime; + int vMajor,vMinor,vRev,vProto; + Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> lastRequestMetaData; + Identity identity; + InetAddress physicalAddr; // last known physical address + inline bool online(const uint64_t now) const { return ((now - lastRequestTime) < (ZT_NETWORK_AUTOCONF_DELAY * 2)); } + }; + struct _MemberStatusHash + { + inline std::size_t operator()(const _MemberStatusKey &networkIdNodeId) const + { + return (std::size_t)(networkIdNodeId.networkId + networkIdNodeId.nodeId); + } + }; + std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus; + Mutex _memberStatus_m; }; } // namespace ZeroTier diff --git a/controller/JSONDB.cpp b/controller/JSONDB.cpp index d3e76fc1..97a217a1 100644 --- a/controller/JSONDB.cpp +++ b/controller/JSONDB.cpp @@ -27,11 +27,11 @@ static const std::map<std::string,std::string> _ZT_JSONDB_GET_HEADERS; JSONDB::JSONDB(const std::string &basePath) : _basePath(basePath), - _ready(false) + _summaryThreadRun(true) { if ((_basePath.length() > 7)&&(_basePath.substr(0,7) == "http://")) { // TODO: this doesn't yet support IPv6 since bracketed address notiation isn't supported. - // Typically it's used with 127.0.0.1 anyway. + // Typically it's just used with 127.0.0.1 anyway. std::string hn = _basePath.substr(7); std::size_t hnend = hn.find_first_of('/'); if (hnend != std::string::npos) @@ -39,7 +39,7 @@ JSONDB::JSONDB(const std::string &basePath) : std::size_t hnsep = hn.find_last_of(':'); if (hnsep != std::string::npos) hn[hnsep] = '/'; - _httpAddr.fromString(hn); + _httpAddr.fromString(hn.c_str()); if (hnend != std::string::npos) _basePath = _basePath.substr(7 + hnend); if (_basePath.length() == 0) @@ -50,22 +50,54 @@ JSONDB::JSONDB(const std::string &basePath) : OSUtils::mkdir(_basePath.c_str()); OSUtils::lockDownFile(_basePath.c_str(),true); // networks might contain auth tokens, etc., so restrict directory permissions } - _ready = _reload(_basePath,std::string()); + + unsigned int cnt = 0; + while (!_load(_basePath)) { + if ((++cnt & 7) == 0) + fprintf(stderr,"WARNING: controller still waiting to read '%s'..." ZT_EOL_S,_basePath.c_str()); + Thread::sleep(250); + } + + for(std::unordered_map<uint64_t,_NW>::iterator n(_networks.begin());n!=_networks.end();++n) + _recomputeSummaryInfo(n->first); + for(;;) { + _summaryThread_m.lock(); + if (_summaryThreadToDo.empty()) { + _summaryThread_m.unlock(); + break; + } + _summaryThread_m.unlock(); + Thread::sleep(50); + } +} + +JSONDB::~JSONDB() +{ + { + Mutex::Lock _l(_networks_m); + _networks.clear(); + } + Thread t; + { + Mutex::Lock _l(_summaryThread_m); + _summaryThreadRun = false; + t = _summaryThread; + } + if (t) + Thread::join(t); } bool JSONDB::writeRaw(const std::string &n,const std::string &obj) { - if (!_isValidObjectName(n)) - return false; if (_httpAddr) { std::map<std::string,std::string> headers; std::string body; std::map<std::string,std::string> reqHeaders; char tmp[64]; - Utils::snprintf(tmp,sizeof(tmp),"%lu",(unsigned long)obj.length()); + OSUtils::ztsnprintf(tmp,sizeof(tmp),"%lu",(unsigned long)obj.length()); reqHeaders["Content-Length"] = tmp; reqHeaders["Content-Type"] = "application/json"; - const unsigned int sc = Http::PUT(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),reqHeaders,obj.data(),(unsigned long)obj.length(),headers,body); + const unsigned int sc = Http::PUT(0,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),reqHeaders,obj.data(),(unsigned long)obj.length(),headers,body); return (sc == 200); } else { const std::string path(_genPath(n,true)); @@ -75,84 +107,269 @@ bool JSONDB::writeRaw(const std::string &n,const std::string &obj) } } -bool JSONDB::put(const std::string &n,const nlohmann::json &obj) +bool JSONDB::hasNetwork(const uint64_t networkId) const { - const bool r = writeRaw(n,OSUtils::jsonDump(obj)); - _db[n].obj = obj; - return r; + Mutex::Lock _l(_networks_m); + return (_networks.find(networkId) != _networks.end()); } -const nlohmann::json &JSONDB::get(const std::string &n) +bool JSONDB::getNetwork(const uint64_t networkId,nlohmann::json &config) const { - while (!_ready) { - Thread::sleep(250); - _ready = _reload(_basePath,std::string()); + Mutex::Lock _l(_networks_m); + const std::unordered_map<uint64_t,_NW>::const_iterator i(_networks.find(networkId)); + if (i == _networks.end()) + return false; + config = nlohmann::json::from_msgpack(i->second.config); + return true; +} + +bool JSONDB::getNetworkSummaryInfo(const uint64_t networkId,NetworkSummaryInfo &ns) const +{ + Mutex::Lock _l(_networks_m); + const std::unordered_map<uint64_t,_NW>::const_iterator i(_networks.find(networkId)); + if (i == _networks.end()) + return false; + ns = i->second.summaryInfo; + return true; +} + +int JSONDB::getNetworkAndMember(const uint64_t networkId,const uint64_t nodeId,nlohmann::json &networkConfig,nlohmann::json &memberConfig,NetworkSummaryInfo &ns) const +{ + Mutex::Lock _l(_networks_m); + const std::unordered_map<uint64_t,_NW>::const_iterator i(_networks.find(networkId)); + if (i == _networks.end()) + return 0; + const std::unordered_map< uint64_t,std::vector<uint8_t> >::const_iterator j(i->second.members.find(nodeId)); + if (j == i->second.members.end()) + return 1; + networkConfig = nlohmann::json::from_msgpack(i->second.config); + memberConfig = nlohmann::json::from_msgpack(j->second); + ns = i->second.summaryInfo; + return 3; +} + +bool JSONDB::getNetworkMember(const uint64_t networkId,const uint64_t nodeId,nlohmann::json &memberConfig) const +{ + Mutex::Lock _l(_networks_m); + const std::unordered_map<uint64_t,_NW>::const_iterator i(_networks.find(networkId)); + if (i == _networks.end()) + return false; + const std::unordered_map< uint64_t,std::vector<uint8_t> >::const_iterator j(i->second.members.find(nodeId)); + if (j == i->second.members.end()) + return false; + memberConfig = nlohmann::json::from_msgpack(j->second); + return true; +} + +void JSONDB::saveNetwork(const uint64_t networkId,const nlohmann::json &networkConfig) +{ + char n[64]; + OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx",(unsigned long long)networkId); + writeRaw(n,OSUtils::jsonDump(networkConfig)); + { + Mutex::Lock _l(_networks_m); + _networks[networkId].config = nlohmann::json::to_msgpack(networkConfig); } + _recomputeSummaryInfo(networkId); +} - if (!_isValidObjectName(n)) - return _EMPTY_JSON; - std::map<std::string,_E>::iterator e(_db.find(n)); - if (e != _db.end()) - return e->second.obj; +void JSONDB::saveNetworkMember(const uint64_t networkId,const uint64_t nodeId,const nlohmann::json &memberConfig) +{ + char n[256]; + OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx/member/%.10llx",(unsigned long long)networkId,(unsigned long long)nodeId); + writeRaw(n,OSUtils::jsonDump(memberConfig)); + { + Mutex::Lock _l(_networks_m); + _networks[networkId].members[nodeId] = nlohmann::json::to_msgpack(memberConfig); + } + _recomputeSummaryInfo(networkId); +} + +nlohmann::json JSONDB::eraseNetwork(const uint64_t networkId) +{ + if (!_httpAddr) { // Member deletion is done by Central in harnessed mode, and deleting the cache network entry also deletes all members + std::vector<uint64_t> memberIds; + { + Mutex::Lock _l(_networks_m); + const std::unordered_map<uint64_t,_NW>::iterator i(_networks.find(networkId)); + if (i == _networks.end()) + return _EMPTY_JSON; + for(std::unordered_map< uint64_t,std::vector<uint8_t> >::iterator m(i->second.members.begin());m!=i->second.members.end();++m) + memberIds.push_back(m->first); + } + for(std::vector<uint64_t>::iterator m(memberIds.begin());m!=memberIds.end();++m) + eraseNetworkMember(networkId,*m,false); + } + + char n[256]; + OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx",(unsigned long long)networkId); - std::string buf; if (_httpAddr) { - std::map<std::string,std::string> headers; - const unsigned int sc = Http::GET(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,buf); - if (sc != 200) - return _EMPTY_JSON; + // Deletion is currently done by Central in harnessed mode + //std::map<std::string,std::string> headers; + //std::string body; + //Http::DEL(0,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); } else { const std::string path(_genPath(n,false)); - if (!path.length()) - return _EMPTY_JSON; - if (!OSUtils::readFile(path.c_str(),buf)) - return _EMPTY_JSON; + if (path.length()) + OSUtils::rm(path.c_str()); } - try { - _E &e2 = _db[n]; - e2.obj = OSUtils::jsonParse(buf); - return e2.obj; - } catch ( ... ) { - _db.erase(n); - return _EMPTY_JSON; + { + Mutex::Lock _l(_networks_m); + std::unordered_map<uint64_t,_NW>::iterator i(_networks.find(networkId)); + if (i == _networks.end()) + return _EMPTY_JSON; // sanity check, shouldn't happen + nlohmann::json tmp(nlohmann::json::from_msgpack(i->second.config)); + _networks.erase(i); + return tmp; } } -void JSONDB::erase(const std::string &n) +nlohmann::json JSONDB::eraseNetworkMember(const uint64_t networkId,const uint64_t nodeId,bool recomputeSummaryInfo) { - if (!_isValidObjectName(n)) - return; + char n[256]; + OSUtils::ztsnprintf(n,sizeof(n),"network/%.16llx/member/%.10llx",(unsigned long long)networkId,(unsigned long long)nodeId); if (_httpAddr) { - std::string body; - std::map<std::string,std::string> headers; - Http::DEL(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + // Deletion is currently done by the caller in Central harnessed mode + //std::map<std::string,std::string> headers; + //std::string body; + //Http::DEL(0,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); } else { - std::string path(_genPath(n,true)); - if (!path.length()) - return; - OSUtils::rm(path.c_str()); + const std::string path(_genPath(n,false)); + if (path.length()) + OSUtils::rm(path.c_str()); + } + + { + Mutex::Lock _l(_networks_m); + std::unordered_map<uint64_t,_NW>::iterator i(_networks.find(networkId)); + if (i == _networks.end()) + return _EMPTY_JSON; + std::unordered_map< uint64_t,std::vector<uint8_t> >::iterator j(i->second.members.find(nodeId)); + if (j == i->second.members.end()) + return _EMPTY_JSON; + nlohmann::json tmp(j->second); + i->second.members.erase(j); + if (recomputeSummaryInfo) + _recomputeSummaryInfo(networkId); + return tmp; } +} + +void JSONDB::threadMain() + throw() +{ + std::vector<uint64_t> todo; + while (_summaryThreadRun) { + Thread::sleep(10); + + { + Mutex::Lock _l(_summaryThread_m); + if (_summaryThreadToDo.empty()) + continue; + else _summaryThreadToDo.swap(todo); + } + + const uint64_t now = OSUtils::now(); + for(std::vector<uint64_t>::iterator ii(todo.begin());ii!=todo.end();++ii) { + const uint64_t networkId = *ii; + + Mutex::Lock _l(_networks_m); + std::unordered_map<uint64_t,_NW>::iterator n(_networks.find(networkId)); + if (n != _networks.end()) { + NetworkSummaryInfo &ns = n->second.summaryInfo; + ns.activeBridges.clear(); + ns.allocatedIps.clear(); + ns.authorizedMemberCount = 0; + ns.activeMemberCount = 0; + ns.totalMemberCount = 0; + ns.mostRecentDeauthTime = 0; + + for(std::unordered_map< uint64_t,std::vector<uint8_t> >::const_iterator m(n->second.members.begin());m!=n->second.members.end();++m) { + try { + nlohmann::json member(nlohmann::json::from_msgpack(m->second)); + + if (OSUtils::jsonBool(member["authorized"],false)) { + ++ns.authorizedMemberCount; + + try { + const nlohmann::json &mlog = member["recentLog"]; + if ((mlog.is_array())&&(mlog.size() > 0)) { + const nlohmann::json &mlog1 = mlog[0]; + if (mlog1.is_object()) { + if ((now - OSUtils::jsonInt(mlog1["ts"],0ULL)) < (ZT_NETWORK_AUTOCONF_DELAY * 2)) + ++ns.activeMemberCount; + } + } + } catch ( ... ) {} - _db.erase(n); + try { + if (OSUtils::jsonBool(member["activeBridge"],false)) + ns.activeBridges.push_back(Address(m->first)); + } catch ( ... ) {} + + try { + const nlohmann::json &mips = member["ipAssignments"]; + if (mips.is_array()) { + for(unsigned long i=0;i<mips.size();++i) { + InetAddress mip(OSUtils::jsonString(mips[i],"").c_str()); + if ((mip.ss_family == AF_INET)||(mip.ss_family == AF_INET6)) + ns.allocatedIps.push_back(mip); + } + } + } catch ( ... ) {} + } else { + try { + ns.mostRecentDeauthTime = std::max(ns.mostRecentDeauthTime,OSUtils::jsonInt(member["lastDeauthorizedTime"],0ULL)); + } catch ( ... ) {} + } + ++ns.totalMemberCount; + } catch ( ... ) {} + } + + std::sort(ns.activeBridges.begin(),ns.activeBridges.end()); + std::sort(ns.allocatedIps.begin(),ns.allocatedIps.end()); + + n->second.summaryInfoLastComputed = now; + } + } + + todo.clear(); + } } -bool JSONDB::_reload(const std::string &p,const std::string &b) +bool JSONDB::_load(const std::string &p) { if (_httpAddr) { + // In HTTP harnessed mode we download our entire working data set on startup. + std::string body; std::map<std::string,std::string> headers; - const unsigned int sc = Http::GET(2147483647,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),_basePath.c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + const unsigned int sc = Http::GET(0,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_httpAddr),_basePath.c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); if (sc == 200) { try { nlohmann::json dbImg(OSUtils::jsonParse(body)); std::string tmp; if (dbImg.is_object()) { + Mutex::Lock _l(_networks_m); for(nlohmann::json::iterator i(dbImg.begin());i!=dbImg.end();++i) { - if (i.value().is_object()) { - tmp = i.key(); - _db[tmp].obj = i.value(); + nlohmann::json &j = i.value(); + if (j.is_object()) { + std::string id(OSUtils::jsonString(j["id"],"0")); + std::string objtype(OSUtils::jsonString(j["objtype"],"")); + + if ((id.length() == 16)&&(objtype == "network")) { + const uint64_t nwid = Utils::hexStrToU64(id.c_str()); + if (nwid) + _networks[nwid].config = nlohmann::json::to_msgpack(j); + } else if ((id.length() == 10)&&(objtype == "member")) { + const uint64_t mid = Utils::hexStrToU64(id.c_str()); + const uint64_t nwid = Utils::hexStrToU64(OSUtils::jsonString(j["nwid"],"0").c_str()); + if ((mid)&&(nwid)) + _networks[nwid].members[mid] = nlohmann::json::to_msgpack(j); + } } } return true; @@ -160,31 +377,52 @@ bool JSONDB::_reload(const std::string &p,const std::string &b) } catch ( ... ) {} // invalid JSON, so maybe incomplete request } return false; + } else { + // In regular mode we recursively read it from controller.d/ on disk + std::vector<std::string> dl(OSUtils::listDirectory(p.c_str(),true)); for(std::vector<std::string>::const_iterator di(dl.begin());di!=dl.end();++di) { if ((di->length() > 5)&&(di->substr(di->length() - 5) == ".json")) { - this->get(b + di->substr(0,di->length() - 5)); + std::string buf; + if (OSUtils::readFile((p + ZT_PATH_SEPARATOR_S + *di).c_str(),buf)) { + try { + nlohmann::json j(OSUtils::jsonParse(buf)); + std::string id(OSUtils::jsonString(j["id"],"0")); + std::string objtype(OSUtils::jsonString(j["objtype"],"")); + + if ((id.length() == 16)&&(objtype == "network")) { + const uint64_t nwid = Utils::hexStrToU64(id.c_str()); + if (nwid) { + Mutex::Lock _l(_networks_m); + _networks[nwid].config = nlohmann::json::to_msgpack(j); + } + } else if ((id.length() == 10)&&(objtype == "member")) { + const uint64_t mid = Utils::hexStrToU64(id.c_str()); + const uint64_t nwid = Utils::hexStrToU64(OSUtils::jsonString(j["nwid"],"0").c_str()); + if ((mid)&&(nwid)) { + Mutex::Lock _l(_networks_m); + _networks[nwid].members[mid] = nlohmann::json::to_msgpack(j); + } + } + } catch ( ... ) {} + } } else { - this->_reload((p + ZT_PATH_SEPARATOR + *di),(b + *di + ZT_PATH_SEPARATOR)); + this->_load((p + ZT_PATH_SEPARATOR_S + *di)); } } return true; + } } -bool JSONDB::_isValidObjectName(const std::string &n) +void JSONDB::_recomputeSummaryInfo(const uint64_t networkId) { - if (n.length() == 0) - return false; - const char *p = n.c_str(); - char c; - // For security reasons we should not allow dots, backslashes, or other path characters or potential path characters. - while ((c = *(p++))) { - if (!( ((c >= 'a')&&(c <= 'z')) || ((c >= 'A')&&(c <= 'Z')) || ((c >= '0')&&(c <= '9')) || (c == '/') || (c == '_') || (c == '~') || (c == '-') )) - return false; - } - return true; + Mutex::Lock _l(_summaryThread_m); + if (std::find(_summaryThreadToDo.begin(),_summaryThreadToDo.end(),networkId) == _summaryThreadToDo.end()) + _summaryThreadToDo.push_back(networkId); + if (!_summaryThread) + _summaryThread = Thread::start(this); } std::string JSONDB::_genPath(const std::string &n,bool create) diff --git a/controller/JSONDB.hpp b/controller/JSONDB.hpp index beafbaf5..e164a14e 100644 --- a/controller/JSONDB.hpp +++ b/controller/JSONDB.hpp @@ -28,6 +28,7 @@ #include <stdexcept> #include <vector> #include <algorithm> +#include <unordered_map> #include "../node/Constants.hpp" #include "../node/Utils.hpp" @@ -46,69 +47,115 @@ namespace ZeroTier { class JSONDB { public: + struct NetworkSummaryInfo + { + NetworkSummaryInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {} + std::vector<Address> activeBridges; + std::vector<InetAddress> allocatedIps; + unsigned long authorizedMemberCount; + unsigned long activeMemberCount; + unsigned long totalMemberCount; + uint64_t mostRecentDeauthTime; + }; + JSONDB(const std::string &basePath); + ~JSONDB(); bool writeRaw(const std::string &n,const std::string &obj); - bool put(const std::string &n,const nlohmann::json &obj); + bool hasNetwork(const uint64_t networkId) const; + + bool getNetwork(const uint64_t networkId,nlohmann::json &config) const; + + bool getNetworkSummaryInfo(const uint64_t networkId,NetworkSummaryInfo &ns) const; + + /** + * @return Bit mask: 0 == none, 1 == network only, 3 == network and member + */ + int getNetworkAndMember(const uint64_t networkId,const uint64_t nodeId,nlohmann::json &networkConfig,nlohmann::json &memberConfig,NetworkSummaryInfo &ns) const; - inline bool put(const std::string &n1,const std::string &n2,const nlohmann::json &obj) { return this->put((n1 + "/" + n2),obj); } - inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3),obj); } - inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4),obj); } - inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5),obj); } + bool getNetworkMember(const uint64_t networkId,const uint64_t nodeId,nlohmann::json &memberConfig) const; - const nlohmann::json &get(const std::string &n); + void saveNetwork(const uint64_t networkId,const nlohmann::json &networkConfig); - inline const nlohmann::json &get(const std::string &n1,const std::string &n2) { return this->get((n1 + "/" + n2)); } - inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3) { return this->get((n1 + "/" + n2 + "/" + n3)); } - inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4)); } - inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5)); } + void saveNetworkMember(const uint64_t networkId,const uint64_t nodeId,const nlohmann::json &memberConfig); - void erase(const std::string &n); + nlohmann::json eraseNetwork(const uint64_t networkId); - inline void erase(const std::string &n1,const std::string &n2) { this->erase(n1 + "/" + n2); } - inline void erase(const std::string &n1,const std::string &n2,const std::string &n3) { this->erase(n1 + "/" + n2 + "/" + n3); } - inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4); } - inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5); } + nlohmann::json eraseNetworkMember(const uint64_t networkId,const uint64_t nodeId,bool recomputeSummaryInfo = true); + + std::vector<uint64_t> networkIds() const + { + std::vector<uint64_t> r; + Mutex::Lock _l(_networks_m); + for(std::unordered_map<uint64_t,_NW>::const_iterator n(_networks.begin());n!=_networks.end();++n) + r.push_back(n->first); + return r; + } + + inline unsigned long memberCount(const uint64_t networkId) + { + Mutex::Lock _l(_networks_m); + std::unordered_map<uint64_t,_NW>::const_iterator i(_networks.find(networkId)); + if (i != _networks.end()) + return (unsigned long)i->second.members.size(); + return 0; + } template<typename F> - inline void filter(const std::string &prefix,F func) + inline void eachMember(const uint64_t networkId,F func) { - while (!_ready) { - Thread::sleep(250); - _ready = _reload(_basePath,std::string()); + Mutex::Lock _l(_networks_m); + std::unordered_map<uint64_t,_NW>::const_iterator i(_networks.find(networkId)); + if (i != _networks.end()) { + for(std::unordered_map< uint64_t,std::vector<uint8_t> >::const_iterator m(i->second.members.begin());m!=i->second.members.end();++m) { + try { + func(networkId,m->first,nlohmann::json::from_msgpack(m->second)); + } catch ( ... ) {} + } } + } - for(std::map<std::string,_E>::iterator i(_db.lower_bound(prefix));i!=_db.end();) { - if ((i->first.length() >= prefix.length())&&(!memcmp(i->first.data(),prefix.data(),prefix.length()))) { - if (!func(i->first,get(i->first))) { - std::map<std::string,_E>::iterator i2(i); ++i2; - this->erase(i->first); - i = i2; - } else ++i; - } else break; + template<typename F> + inline void eachId(F func) + { + Mutex::Lock _l(_networks_m); + for(std::unordered_map<uint64_t,_NW>::const_iterator i(_networks.begin());i!=_networks.end();++i) { + for(std::unordered_map< uint64_t,std::vector<uint8_t> >::const_iterator m(i->second.members.begin());m!=i->second.members.end();++m) { + try { + func(i->first,m->first); + } catch ( ... ) {} + } } } - inline bool operator==(const JSONDB &db) const { return ((_basePath == db._basePath)&&(_db == db._db)); } - inline bool operator!=(const JSONDB &db) const { return (!(*this == db)); } + void threadMain() + throw(); private: - bool _reload(const std::string &p,const std::string &b); - bool _isValidObjectName(const std::string &n); + bool _load(const std::string &p); + void _recomputeSummaryInfo(const uint64_t networkId); std::string _genPath(const std::string &n,bool create); - struct _E + std::string _basePath; + InetAddress _httpAddr; + + Thread _summaryThread; + std::vector<uint64_t> _summaryThreadToDo; + volatile bool _summaryThreadRun; + Mutex _summaryThread_m; + + struct _NW { - nlohmann::json obj; - inline bool operator==(const _E &e) const { return (obj == e.obj); } - inline bool operator!=(const _E &e) const { return (obj != e.obj); } + _NW() : summaryInfoLastComputed(0) {} + std::vector<uint8_t> config; + NetworkSummaryInfo summaryInfo; + uint64_t summaryInfoLastComputed; + std::unordered_map< uint64_t,std::vector<uint8_t> > members; }; - InetAddress _httpAddr; - std::string _basePath; - std::map<std::string,_E> _db; - volatile bool _ready; + std::unordered_map<uint64_t,_NW> _networks; + Mutex _networks_m; }; } // namespace ZeroTier diff --git a/controller/README.md b/controller/README.md index 8a1a3dff..5a9dadc2 100644 --- a/controller/README.md +++ b/controller/README.md @@ -233,22 +233,12 @@ This returns an object containing all currently online members and the most rece | activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES | | identity | string | Member's public ZeroTier identity (if known) | no | | ipAssignments | array[string] | Managed IP address assignments | YES | -| memberRevision | integer | Member revision counter | no | -| recentLog | array[object] | Recent member activity log; see below | no | +| revision | integer | Member revision counter | no | +| vMajor | integer | Most recently known major version | no | +| vMinor | integer | Most recently known minor version | no | +| vRev | integer | Most recently known revision | no | +| vProto | integer | Most recently known protocl version | no | +| physicalAddr | string | Last known physical IP/port or null if none | no | Note that managed IP assignments are only used if they fall within a managed route. Otherwise they are ignored. -**Recent log object format:** - -| Field | Type | Description | -| --------------------- | ------------- | ------------------------------------------------- | -| ts | integer | Time of request, ms since epoch | -| auth | boolean | Was member authorized? | -| authBy | string | How was member authorized? | -| vMajor | integer | Client major version or -1 if unknown | -| vMinor | integer | Client minor version or -1 if unknown | -| vRev | integer | Client revision or -1 if unknown | -| vProto | integer | ZeroTier protocol version reported by client | -| fromAddr | string | Physical address if known | - -The controller can only know a member's `fromAddr` if it's able to establish a direct path to it. Members behind very restrictive firewalls may not have this information since the controller will be receiving the member's requests by way of a relay. ZeroTier does not back-trace IP paths as packets are relayed since this would add a lot of protocol overhead. diff --git a/controller/migrate-sqlite/migrate.js b/controller/migrate-sqlite/migrate.js index ac9678a7..2f2462c9 100644 --- a/controller/migrate-sqlite/migrate.js +++ b/controller/migrate-sqlite/migrate.js @@ -95,7 +95,6 @@ async.series([function(nextStep) { creationTime: parseInt(row.creationTime)||0, enableBroadcast: !!row.enableBroadcast, ipAssignmentPools: [], - lastModified: Date.now(), multicastLimit: row.multicastLimit||32, name: row.name||'', private: !!row.private, @@ -177,7 +176,6 @@ async.series([function(nextStep) { ipAssignments: [], lastAuthorizedTime: (row.authorized) ? Date.now() : 0, lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(), - lastModified: Date.now(), lastRequestMetaData: '', noAutoAssignIps: false, nwid: row.networkId, |
