diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 9c7611a4a..b78f847e5 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -459,6 +459,7 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) } EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath) : + _threadsStarted(false), _db(dbPath), _node(node) { @@ -468,22 +469,768 @@ 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 &metaData,NetworkConfig &nc) +void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender) { - if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) { - return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR; + this->_sender = sender; + this->_signingId = signingId; +} + +void EmbeddedNetworkController::request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; + + { + Mutex::Lock _l(_threads_m); + if (!_threadsStarted) { + for(int i=0;inwid = nwid; + qe->requestPacketId = requestPacketId; + qe->fromAddr = fromAddr; + qe->identity = identity; + qe->metaData = metaData; + _queue.post(qe); +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if ((path.size() > 0)&&(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",(unsigned long long)nwid); + + json network; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids,0); + } + if (!network.size()) + return 404; + + if (path.size() >= 3) { + + if (path[2] == "member") { + + if (path.size() >= 4) { + 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(),0); + } + if (!member.size()) + return 404; + + _addMemberNonPersistedFields(member,OSUtils::now()); + responseBody = member.dump(2); + responseContentType = "application/json"; + + return 200; + } else { + + Mutex::Lock _l(_db_m); + + responseBody = "{"; + std::string pfx(std::string("network/") + nwids + "member/"); + _db.filter(pfx,120000,[&responseBody](const std::string &n,const json &member) { + if (member.size() > 0) { + responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); + responseBody.append(_jS(member["id"],"")); + responseBody.append("\":"); + responseBody.append(_jS(member["revision"],"0")); + } + return true; // never delete + }); + responseBody.push_back('}'); + responseContentType = "application/json"; + + return 200; + } + + } else if ((path[2] == "test")&&(path.size() >= 4)) { + + Mutex::Lock _l(_circuitTests_m); + std::map< uint64_t,_CircuitTestEntry >::iterator cte(_circuitTests.find(Utils::hexStrToU64(path[3].c_str()))); + if ((cte != _circuitTests.end())&&(cte->second.test)) { + + responseBody = "["; + responseBody.append(cte->second.jsonResults); + responseBody.push_back(']'); + responseContentType = "application/json"; + + return 200; + + } // else 404 + + } // else 404 + + } else { + + const uint64_t now = OSUtils::now(); + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + _addNetworkNonPersistedFields(network,now,nmi); + responseBody = network.dump(2); + responseContentType = "application/json"; + return 200; + + } + } else if (path.size() == 1) { + + std::set networkIds; + { + Mutex::Lock _l(_db_m); + _db.filter("network/",120000,[&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::iterator i(networkIds.begin());i!=networkIds.end();++i) { + responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); + responseBody.append(*i); + responseBody.append("\""); + } + responseBody.push_back(']'); + responseContentType = "application/json"; + return 200; + + } // else 404 + + } else { + + 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()); + responseBody = tmp; + responseContentType = "application/json"; + return 200; + + } + + return 404; +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if (path.empty()) + return 404; + + json b; + try { + b = json::parse(body); + if (!b.is_object()) { + responseBody = "{ \"message\": \"body is not a JSON object\" }"; + responseContentType = "application/json"; + return 400; + } + } catch ( ... ) { + responseBody = "{ \"message\": \"body JSON is invalid\" }"; + responseContentType = "application/json"; + return 400; + } + const uint64_t now = OSUtils::now(); + + if (path[0] == "network") { + + 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); + + 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); + + json member; + { + Mutex::Lock _l(_db_m); + member = _db.get("network",nwids,"member",Address(address).toString(),0); + } + json origMember(member); // for detecting changes + _initMember(member); + + try { + if (b.count("activeBridge")) member["activeBridge"] = _jB(b["activeBridge"],false); + if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = _jB(b["noAutoAssignIps"],false); + + if (b.count("authorized")) { + const bool newAuth = _jB(b["authorized"],false); + if (newAuth != _jB(member["authorized"],false)) { + member["authorized"] = newAuth; + member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now; + + json ah; + ah["a"] = newAuth; + ah["by"] = "api"; + ah["ts"] = now; + ah["ct"] = json(); + ah["c"] = json(); + member["authHistory"].push_back(ah); + } + } + + if (b.count("ipAssignments")) { + json &ipa = b["ipAssignments"]; + if (ipa.is_array()) { + json mipa(json::array()); + for(unsigned long i=0;i mtags; + for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { + json ta = json::array(); + ta.push_back(t->first); + ta.push_back(t->second); + mtagsa.push_back(ta); + } + member["tags"] = mtagsa; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + json mcaps = json::array(); + for(unsigned long i=0;itestId),sizeof(test->testId)); + test->credentialNetworkId = nwid; + test->ptr = (void *)this; + json hops = b["hops"]; + if (hops.is_array()) { + for(unsigned long i=0;ihops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; + } + } 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->reportAtEveryHop = (_jB(b["reportAtEveryHop"],true) ? 1 : 0); + + if (!test->hopCount) { + ::free((void *)test); + responseBody = "{ \"message\": \"a test must contain at least one hop\" }"; + responseContentType = "application/json"; + return 400; + } + + test->timestamp = OSUtils::now(); + + _CircuitTestEntry &te = _circuitTests[test->testId]; + te.test = test; + te.jsonResults = ""; + + if (_node) + _node->circuitTestBegin(test,&(EmbeddedNetworkController::_circuitTestCallback)); + else return 500; + + char json[1024]; + Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\"}",test->testId); + 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,120000).size() <= 0) { + nwid = tryNwid; + break; + } + } + if (!nwid) + return 503; + } + + network = _db.get("network",nwids,0); + } + json origNetwork(network); // for detecting changes + _initNetwork(network); + + try { + if (b.count("name")) network["name"] = _jS(b["name"],""); + if (b.count("private")) network["private"] = _jB(b["private"],true); + if (b.count("enableBroadcast")) network["enableBroadcast"] = _jB(b["enableBroadcast"],false); + if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = _jB(b["allowPassiveBridging"],false); + if (b.count("multicastLimit")) network["multicastLimit"] = _jI(b["multicastLimit"],32ULL); + + if (b.count("v4AssignMode")) { + json nv4m; + json &v4m = b["v4AssignMode"]; + if (v4m.is_string()) { // backward compatibility + nv4m["zt"] = (_jS(v4m,"") == "zt"); + } else if (v4m.is_object()) { + nv4m["zt"] = _jB(v4m["zt"],false); + } else nv4m["zt"] = false; + network["v4AssignMode"] = nv4m; + } + + if (b.count("v6AssignMode")) { + json nv6m; + json &v6m = b["v6AssignMode"]; + if (!nv6m.is_object()) nv6m = json::object(); + if (v6m.is_string()) { // backward compatibility + std::vector v6ms(Utils::split(_jS(v6m,"").c_str(),",","","")); + std::sort(v6ms.begin(),v6ms.end()); + v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { + if (*i == "rfc4193") + nv6m["rfc4193"] = true; + else if (*i == "zt") + nv6m["zt"] = true; + else if (*i == "6plane") + nv6m["6plane"] = true; + } + } else if (v6m.is_object()) { + if (v6m.count("rfc4193")) nv6m["rfc4193"] = _jB(v6m["rfc4193"],false); + if (v6m.count("zt")) nv6m["zt"] = _jB(v6m["zt"],false); + if (v6m.count("6plane")) nv6m["6plane"] = _jB(v6m["6plane"],false); + } else { + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + } + network["v6AssignMode"] = nv6m; + } + + if (b.count("routes")) { + json &rts = b["routes"]; + if (rts.is_array()) { + json nrts = json::array(); + for(unsigned long i=0;i()); + InetAddress v; + if (via.is_string()) v.fromString(via.get()); + if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { + json tmp; + tmp["target"] = t.toString(); + if (v.ss_family == t.ss_family) + tmp["via"] = v.toIpString(); + else tmp["via"] = json(); + nrts.push_back(tmp); + } + } + } + } + network["routes"] = nrts; + } + } + + if (b.count("ipAssignmentPools")) { + json &ipp = b["ipAssignmentPools"]; + if (ipp.is_array()) { + json nipp = json::array(); + for(unsigned long i=0;i 0) { + json t = json::object(); + t["token"] = tstr; + t["expires"] = _jI(token["expires"],0ULL); + t["maxUsesPerMember"] = _jI(token["maxUsesPerMember"],0ULL); + nat.push_back(t); + } + } + } + network["authTokens"] = nat; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + std::map< uint64_t,json > ncaps; + for(unsigned long i=0;i::iterator c(ncaps.begin());c!=ncaps.end();++c) + ncapsa.push_back(c->second); + network["capabilities"] = ncapsa; + } + } + } catch ( ... ) { + responseBody = "{ \"message\": \"exception occurred while parsing body variables\" }"; + responseContentType = "application/json"; + return 400; + } + + network["id"] = nwids; + network["nwid"] = nwids; // legacy + + 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); + } + std::string pfx("network/"); pfx.append(nwids); pfx.append("/member/"); + _db.filter(pfx,120000,[this,&now,&nwid](const std::string &n,const json &obj) { + _pushMemberUpdate(now,nwid,obj); + return true; // do not delete + }); + } + + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + _addNetworkNonPersistedFields(network,now,nmi); + + responseBody = network.dump(2); + responseContentType = "application/json"; + return 200; + } // else 404 + + } // else 404 + + } // else 404 + + return 404; +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if (path.empty()) + return 404; + + 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,0); + } + 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.get("network",nwids,"member",Address(address).toString(),0); + _db.erase("network",nwids,"member",Address(address).toString()); + + if (!member.size()) + return 404; + responseBody = member.dump(2); + responseContentType = "application/json"; + return 200; + } + } else { + Mutex::Lock _l(_db_m); + + std::string pfx("network/"); pfx.append(nwids); + _db.filter(pfx,120000,[](const std::string &n,const json &obj) { + return false; // delete + }); + + Mutex::Lock _l2(_nmiCache_m); + _nmiCache.erase(nwid); + + responseBody = network.dump(2); + responseContentType = "application/json"; + return 200; + } + } // else 404 + + } // else 404 + + return 404; +} + +void EmbeddedNetworkController::threadMain() + throw() +{ + for(;;) { + _RQEntry *const qe = _queue.get(); + if (!qe) break; // enqueue a NULL to terminate threads + try { + _request(qe->nwid,qe->fromAddr,qe->requestPacketId,qe->identity,qe->metaData); + } catch ( ... ) {} + delete qe; + } +} + +void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report) +{ + char tmp[65535]; + EmbeddedNetworkController *const self = reinterpret_cast(test->ptr); + + if (!test) + return; + if (!report) + return; + + Mutex::Lock _l(self->_circuitTests_m); + std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId)); + + if (cte == self->_circuitTests.end()) { // sanity check: a circuit test we didn't launch? + self->_node->circuitTestEnd(test); + ::free((void *)test); + return; + } + + Utils::snprintf(tmp,sizeof(tmp), + "%s{\n" + "\t\"timestamp\": %llu," ZT_EOL_S + "\t\"testId\": \"%.16llx\"," ZT_EOL_S + "\t\"upstream\": \"%.10llx\"," ZT_EOL_S + "\t\"current\": \"%.10llx\"," ZT_EOL_S + "\t\"receivedTimestamp\": %llu," ZT_EOL_S + "\t\"sourcePacketId\": \"%.16llx\"," ZT_EOL_S + "\t\"flags\": %llu," ZT_EOL_S + "\t\"sourcePacketHopCount\": %u," ZT_EOL_S + "\t\"errorCode\": %u," ZT_EOL_S + "\t\"vendor\": %d," ZT_EOL_S + "\t\"protocolVersion\": %u," ZT_EOL_S + "\t\"majorVersion\": %u," ZT_EOL_S + "\t\"minorVersion\": %u," ZT_EOL_S + "\t\"revision\": %u," ZT_EOL_S + "\t\"platform\": %d," ZT_EOL_S + "\t\"architecture\": %d," ZT_EOL_S + "\t\"receivedOnLocalAddress\": \"%s\"," ZT_EOL_S + "\t\"receivedFromRemoteAddress\": \"%s\"" ZT_EOL_S + "}", + ((cte->second.jsonResults.length() > 0) ? ",\n" : ""), + (unsigned long long)report->timestamp, + (unsigned long long)test->testId, + (unsigned long long)report->upstream, + (unsigned long long)report->current, + (unsigned long long)OSUtils::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(&(report->receivedOnLocalAddress))->toString().c_str(), + reinterpret_cast(&(report->receivedFromRemoteAddress))->toString().c_str()); + + cte->second.jsonResults.append(tmp); +} + +void EmbeddedNetworkController::_request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; + const uint64_t now = OSUtils::now(); - // Check rate limit circuit breaker to prevent flooding - { + if (requestPacketId) { Mutex::Lock _l(_lastRequestTime_m); uint64_t &lrt = _lastRequestTime[std::pair(identity.address().toInt(),nwid)]; if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD) - return NetworkController::NETCONF_QUERY_IGNORE; + return; lrt = now; } @@ -496,8 +1243,13 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( network = _db.get("network",nwids,0); member = _db.get("network",nwids,"member",identity.address().toString(),0); } - if (!network.size()) - return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND; + + if (!network.size()) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND); + return; + } + + json origMember(member); // for detecting modification later _initMember(member); { @@ -507,10 +1259,13 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( // a "collision" from being able to auth onto our network in place of an already // known member. try { - if (Identity(haveIdStr.c_str()) != identity) - return NetworkController::NETCONF_QUERY_ACCESS_DENIED; + if (Identity(haveIdStr.c_str()) != identity) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } } catch ( ... ) { - return NetworkController::NETCONF_QUERY_ACCESS_DENIED; + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; } } else { // If we do not yet know this member's identity, learn it. @@ -521,27 +1276,18 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( // These are always the same, but make sure they are set member["id"] = identity.address().toString(); member["address"] = member["id"]; - member["nwid"] = network["id"]; + member["nwid"] = nwids; // Determine whether and how member is authorized const char *authorizedBy = (const char *)0; + bool autoAuthorized = false; + json autoAuthCredentialType,autoAuthCredential; if (_jB(member["authorized"],false)) { authorizedBy = "memberIsAuthorized"; } else if (!_jB(network["private"],true)) { authorizedBy = "networkIsPublic"; - if (!member.count("authorized")) { - member["authorized"] = true; - json ah; - ah["a"] = true; - ah["by"] = authorizedBy; - ah["ts"] = now; - ah["ct"] = json(); - ah["c"] = json(); - member["authHistory"].push_back(ah); - member["lastModified"] = now; - json &revj = member["revision"]; - member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); - } + if (!member.count("authorized")) + autoAuthorized = true; } else { char presentedAuth[512]; if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) { @@ -576,17 +1322,9 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( } if (usable) { authorizedBy = "token"; - member["authorized"] = true; - json ah; - ah["a"] = true; - ah["by"] = authorizedBy; - ah["ts"] = now; - ah["ct"] = "token"; - ah["c"] = tstr; - member["authHistory"].push_back(ah); - member["lastModified"] = now; - json &revj = member["revision"]; - member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); + autoAuthorized = true; + autoAuthCredentialType = "token"; + autoAuthCredential = tstr; } } } @@ -596,8 +1334,25 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( } } + // If we auto-authorized, update member record + if ((autoAuthorized)&&(authorizedBy)) { + member["authorized"] = true; + member["lastAuthorizedTime"] = now; + + json ah; + ah["a"] = true; + ah["by"] = authorizedBy; + ah["ts"] = now; + ah["ct"] = autoAuthCredentialType; + ah["c"] = autoAuthCredential; + member["authHistory"].push_back(ah); + + json &revj = member["revision"]; + 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["authorized"] = (authorizedBy) ? true : false; @@ -620,32 +1375,42 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( } } member["recentLog"] = recentLog; + + // Also only do this on real requests + member["lastRequestMetaData"] = metaData.data(); } // If they are not authorized, STOP! if (!authorizedBy) { - Mutex::Lock _l(_db_m); - _db.put("network",nwids,"member",identity.address().toString(),member); - return NetworkController::NETCONF_QUERY_ACCESS_DENIED; + if (origMember != member) { + member["lastModified"] = now; + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",identity.address().toString(),member); + } + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; } // ------------------------------------------------------------------------- // If we made it this far, they are authorized. // ------------------------------------------------------------------------- + NetworkConfig nc; _NetworkMemberInfo nmi; _getNetworkMemberInfo(now,nwid,nmi); - // Compute credential TTL. This is the "moving window" for COM agreement and - // the global TTL for Capability and Tag objects. (The same value is used - // for both.) This is computed by reference to the last time we deauthorized - // a member, since within the time period since this event any temporal - // differences are not particularly relevant. - uint64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA; - if (now > nmi.mostRecentDeauthTime) - credentialtmd += (now - nmi.mostRecentDeauthTime); - if (credentialtmd > ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA) - credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; + uint64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; + if (now > nmi.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; + 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)) { + credentialtmd = deauthWindow - 5000ULL; + } + } nc.networkId = nwid; nc.type = _jB(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; @@ -658,8 +1423,9 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( Utils::scopy(nc.name,sizeof(nc.name),_jS(network["name"],"").c_str()); nc.multicastLimit = (unsigned int)_jI(network["multicastLimit"],32ULL); - for(std::set
::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) + for(std::set
::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) { nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); + } json &v4AssignMode = network["v4AssignMode"]; json &v6AssignMode = network["v6AssignMode"]; @@ -670,60 +1436,68 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( json &memberCapabilities = member["capabilities"]; json &memberTags = member["tags"]; - if (rules.is_array()) { - for(unsigned long i=0;i= ZT_MAX_NETWORK_RULES) - break; - if (_parseRule(rules[i],nc.rules[nc.ruleCount])) - ++nc.ruleCount; - } - } - - if ((memberCapabilities.is_array())&&(memberCapabilities.size() > 0)&&(capabilities.is_array())) { - std::map< uint64_t,json * > capsById; - for(unsigned long i=0;iis_object())&&(cap->size() > 0)) { - ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES]; - unsigned int caprc = 0; - json &caprj = (*cap)["rules"]; - if ((caprj.is_array())&&(caprj.size() > 0)) { - for(unsigned long j=0;j= ZT_MAX_CAPABILITY_RULES) - break; - if (_parseRule(caprj[j],capr[caprc])) - ++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) + 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. + // 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; + } else { + if (rules.is_array()) { + for(unsigned long i=0;i= ZT_MAX_NETWORK_RULES) break; + if (_parseRule(rules[i],nc.rules[nc.ruleCount])) + ++nc.ruleCount; } } - } - if (memberTags.is_array()) { - std::map< uint32_t,uint32_t > tagsById; - for(unsigned long i=0;i 0)&&(capabilities.is_array())) { + std::map< uint64_t,json * > capsById; + for(unsigned long i=0;iis_object())&&(cap->size() > 0)) { + ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES]; + unsigned int caprc = 0; + json &caprj = (*cap)["rules"]; + if ((caprj.is_array())&&(caprj.size() > 0)) { + for(unsigned long j=0;j= ZT_MAX_CAPABILITY_RULES) + break; + if (_parseRule(caprj[j],capr[caprc])) + ++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) + break; + } + } } - for(std::map< uint32_t,uint32_t >::const_iterator t(tagsById.begin());t!=tagsById.end();++t) { - 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; + + if (memberTags.is_array()) { + std::map< uint32_t,uint32_t > tagsById; + for(unsigned long i=0;i::const_iterator t(tagsById.begin());t!=tagsById.end();++t) { + 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; + } } } @@ -912,703 +1686,20 @@ NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest( } CertificateOfMembership com(now,credentialtmd,nwid,identity.address()); - if (com.sign(signingId)) { + if (com.sign(_signingId)) { nc.com = com; } else { - return NETCONF_QUERY_INTERNAL_SERVER_ERROR; + _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); } - return NetworkController::NETCONF_QUERY_OK; -} -unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) -{ - if ((path.size() > 0)&&(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",(unsigned long long)nwid); - - json network; - { - Mutex::Lock _l(_db_m); - network = _db.get("network",nwids,0); - } - if (!network.size()) - return 404; - - if (path.size() >= 3) { - - if (path[2] == "member") { - - if (path.size() >= 4) { - 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(),0); - } - if (!member.size()) - return 404; - - _addMemberNonPersistedFields(member,OSUtils::now()); - responseBody = member.dump(2); - responseContentType = "application/json"; - - return 200; - } else { - - Mutex::Lock _l(_db_m); - - responseBody = "{"; - std::string pfx(std::string("network/") + nwids + "member/"); - _db.filter(pfx,120000,[&responseBody](const std::string &n,const json &member) { - if (member.size() > 0) { - responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); - responseBody.append(_jS(member["id"],"")); - responseBody.append("\":"); - responseBody.append(_jS(member["revision"],"0")); - } - return true; // never delete - }); - responseBody.push_back('}'); - responseContentType = "application/json"; - - return 200; - } - - } else if ((path[2] == "test")&&(path.size() >= 4)) { - - Mutex::Lock _l(_circuitTests_m); - std::map< uint64_t,_CircuitTestEntry >::iterator cte(_circuitTests.find(Utils::hexStrToU64(path[3].c_str()))); - if ((cte != _circuitTests.end())&&(cte->second.test)) { - - responseBody = "["; - responseBody.append(cte->second.jsonResults); - responseBody.push_back(']'); - responseContentType = "application/json"; - - return 200; - - } // else 404 - - } // else 404 - - } else { - - const uint64_t now = OSUtils::now(); - _NetworkMemberInfo nmi; - _getNetworkMemberInfo(now,nwid,nmi); - _addNetworkNonPersistedFields(network,now,nmi); - responseBody = network.dump(2); - responseContentType = "application/json"; - return 200; - - } - } else if (path.size() == 1) { - - std::set networkIds; - { - Mutex::Lock _l(_db_m); - _db.filter("network/",120000,[&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::iterator i(networkIds.begin());i!=networkIds.end();++i) { - responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); - responseBody.append(*i); - responseBody.append("\""); - } - responseBody.push_back(']'); - responseContentType = "application/json"; - return 200; - - } // else 404 - - } else { - - 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()); - responseBody = tmp; - responseContentType = "application/json"; - return 200; - - } - - return 404; -} - -unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) -{ - if (path.empty()) - return 404; - - json b; - try { - b = json::parse(body); - if (!b.is_object()) { - responseBody = "{ \"message\": \"body is not a JSON object\" }"; - responseContentType = "application/json"; - return 400; - } - } catch (std::exception &exc) { - responseBody = std::string("{ \"message\": \"body JSON is invalid: ") + exc.what() + "\" }"; - responseContentType = "application/json"; - return 400; - } catch ( ... ) { - responseBody = "{ \"message\": \"body JSON is invalid\" }"; - responseContentType = "application/json"; - return 400; - } - const uint64_t now = OSUtils::now(); - - if (path[0] == "network") { - - 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); - - if (path.size() >= 3) { - json network; - { - Mutex::Lock _l(_db_m); - network = _db.get("network",nwids,0); - } - if (!network.size()) - return 404; - - 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); - - json member; - { - Mutex::Lock _l(_db_m); - member = _db.get("network",nwids,"member",Address(address).toString(),0); - } - if (!member.size()) - return 404; - _initMember(member); - - try { - if (b.count("activeBridge")) member["activeBridge"] = _jB(b["activeBridge"],false); - if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = _jB(b["noAutoAssignIps"],false); - if ((b.count("identity"))&&(!member.count("identity"))) member["identity"] = _jS(b["identity"],""); // allow identity to be populated only if not already known - - if (b.count("authorized")) { - const bool newAuth = _jB(b["authorized"],false); - if (newAuth != _jB(member["authorized"],false)) { - member["authorized"] = newAuth; - json ah; - ah["a"] = newAuth; - ah["by"] = "api"; - ah["ts"] = now; - ah["ct"] = json(); - ah["c"] = json(); - member["authHistory"].push_back(ah); - } - } - - if (b.count("ipAssignments")) { - json &ipa = b["ipAssignments"]; - if (ipa.is_array()) { - json mipa(json::array()); - for(unsigned long i=0;i mtags; - for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { - json ta = json::array(); - ta.push_back(t->first); - ta.push_back(t->second); - mtagsa.push_back(ta); - } - member["tags"] = mtagsa; - } - } - - if (b.count("capabilities")) { - json &capabilities = b["capabilities"]; - if (capabilities.is_array()) { - json mcaps = json::array(); - for(unsigned long i=0;itestId),sizeof(test->testId)); - test->credentialNetworkId = nwid; - test->ptr = (void *)this; - json hops = b["hops"]; - if (hops.is_array()) { - for(unsigned long i=0;ihops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; - } - } 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->reportAtEveryHop = (_jB(b["reportAtEveryHop"],true) ? 1 : 0); - - if (!test->hopCount) { - ::free((void *)test); - responseBody = "{ \"message\": \"a test must contain at least one hop\" }"; - responseContentType = "application/json"; - return 400; - } - - test->timestamp = OSUtils::now(); - - _CircuitTestEntry &te = _circuitTests[test->testId]; - te.test = test; - te.jsonResults = ""; - - if (_node) - _node->circuitTestBegin(test,&(EmbeddedNetworkController::_circuitTestCallback)); - else return 500; - - char json[1024]; - Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\"}",test->testId); - 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,120000).size() <= 0) { - nwid = tryNwid; - break; - } - } - if (!nwid) - return 503; - } - - network = _db.get("network",nwids,0); - if (!network.size()) - return 404; - } - _initNetwork(network); - - try { - if (b.count("name")) network["name"] = _jS(b["name"],""); - if (b.count("private")) network["private"] = _jB(b["private"],true); - if (b.count("enableBroadcast")) network["enableBroadcast"] = _jB(b["enableBroadcast"],false); - if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = _jB(b["allowPassiveBridging"],false); - if (b.count("multicastLimit")) network["multicastLimit"] = _jI(b["multicastLimit"],32ULL); - - if (b.count("v4AssignMode")) { - json nv4m; - json &v4m = b["v4AssignMode"]; - if (v4m.is_string()) { // backward compatibility - nv4m["zt"] = (_jS(v4m,"") == "zt"); - } else if (v4m.is_object()) { - nv4m["zt"] = _jB(v4m["zt"],false); - } else nv4m["zt"] = false; - network["v4AssignMode"] = nv4m; - } - - if (b.count("v6AssignMode")) { - json nv6m; - json &v6m = b["v6AssignMode"]; - if (!nv6m.is_object()) nv6m = json::object(); - if (v6m.is_string()) { // backward compatibility - std::vector v6ms(Utils::split(_jS(v6m,"").c_str(),",","","")); - std::sort(v6ms.begin(),v6ms.end()); - v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); - nv6m["rfc4193"] = false; - nv6m["zt"] = false; - nv6m["6plane"] = false; - for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { - if (*i == "rfc4193") - nv6m["rfc4193"] = true; - else if (*i == "zt") - nv6m["zt"] = true; - else if (*i == "6plane") - nv6m["6plane"] = true; - } - } else if (v6m.is_object()) { - if (v6m.count("rfc4193")) nv6m["rfc4193"] = _jB(v6m["rfc4193"],false); - if (v6m.count("zt")) nv6m["zt"] = _jB(v6m["zt"],false); - if (v6m.count("6plane")) nv6m["6plane"] = _jB(v6m["6plane"],false); - } else { - nv6m["rfc4193"] = false; - nv6m["zt"] = false; - nv6m["6plane"] = false; - } - network["v6AssignMode"] = nv6m; - } - - if (b.count("routes")) { - json &rts = b["routes"]; - if (rts.is_array()) { - json nrts = json::array(); - for(unsigned long i=0;i()); - InetAddress v; - if (via.is_string()) v.fromString(via.get()); - if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { - json tmp; - tmp["target"] = t.toString(); - if (v.ss_family == t.ss_family) - tmp["via"] = v.toIpString(); - else tmp["via"] = json(); - nrts.push_back(tmp); - } - } - } - } - network["routes"] = nrts; - } - } - - if (b.count("ipAssignmentPools")) { - json &ipp = b["ipAssignmentPools"]; - if (ipp.is_array()) { - json nipp = json::array(); - for(unsigned long i=0;i 0) { - json t = json::object(); - t["token"] = tstr; - t["expires"] = _jI(token["expires"],0ULL); - t["maxUsesPerMember"] = _jI(token["maxUsesPerMember"],0ULL); - nat.push_back(t); - } - } - } - network["authTokens"] = nat; - } - } - - if (b.count("capabilities")) { - json &capabilities = b["capabilities"]; - if (capabilities.is_array()) { - std::map< uint64_t,json > ncaps; - for(unsigned long i=0;i::iterator c(ncaps.begin());c!=ncaps.end();++c) - ncapsa.push_back(c->second); - network["capabilities"] = ncapsa; - } - } - } catch ( ... ) { - responseBody = "{ \"message\": \"exception occurred while parsing body variables\" }"; - responseContentType = "application/json"; - return 400; - } - - network["id"] = nwids; - network["nwid"] = nwids; // legacy - json &rev = network["revision"]; - network["revision"] = (rev.is_number() ? ((uint64_t)rev + 1ULL) : 1ULL); - network["lastModified"] = now; - - { - Mutex::Lock _l(_db_m); - _db.put("network",nwids,network); - } - - _NetworkMemberInfo nmi; - _getNetworkMemberInfo(now,nwid,nmi); - _addNetworkNonPersistedFields(network,now,nmi); - - responseBody = network.dump(2); - responseContentType = "application/json"; - return 200; - } // else 404 - - } // else 404 - - } // else 404 - - return 404; -} - -unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) -{ - if (path.empty()) - return 404; - - 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,0); - } - 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.get("network",nwids,"member",Address(address).toString(),0); - if (!member.size()) - return 404; - _db.erase("network",nwids,"member",Address(address).toString()); - - responseBody = member.dump(2); - responseContentType = "application/json"; - return 200; - } - } else { - Mutex::Lock _l(_db_m); - std::string pfx("network/"); pfx.append(nwids); - _db.filter(pfx,120000,[](const std::string &n,const json &obj) { - return false; // delete - }); - responseBody = network.dump(2); - responseContentType = "application/json"; - return 200; - } - } // else 404 - - } // else 404 - - return 404; -} - -void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report) -{ - char tmp[65535]; - EmbeddedNetworkController *const self = reinterpret_cast(test->ptr); - - if (!test) - return; - if (!report) - return; - - Mutex::Lock _l(self->_circuitTests_m); - std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId)); - - if (cte == self->_circuitTests.end()) { // sanity check: a circuit test we didn't launch? - self->_node->circuitTestEnd(test); - ::free((void *)test); - return; - } - - Utils::snprintf(tmp,sizeof(tmp), - "%s{\n" - "\t\"timestamp\": %llu," ZT_EOL_S - "\t\"testId\": \"%.16llx\"," ZT_EOL_S - "\t\"upstream\": \"%.10llx\"," ZT_EOL_S - "\t\"current\": \"%.10llx\"," ZT_EOL_S - "\t\"receivedTimestamp\": %llu," ZT_EOL_S - "\t\"sourcePacketId\": \"%.16llx\"," ZT_EOL_S - "\t\"flags\": %llu," ZT_EOL_S - "\t\"sourcePacketHopCount\": %u," ZT_EOL_S - "\t\"errorCode\": %u," ZT_EOL_S - "\t\"vendor\": %d," ZT_EOL_S - "\t\"protocolVersion\": %u," ZT_EOL_S - "\t\"majorVersion\": %u," ZT_EOL_S - "\t\"minorVersion\": %u," ZT_EOL_S - "\t\"revision\": %u," ZT_EOL_S - "\t\"platform\": %d," ZT_EOL_S - "\t\"architecture\": %d," ZT_EOL_S - "\t\"receivedOnLocalAddress\": \"%s\"," ZT_EOL_S - "\t\"receivedFromRemoteAddress\": \"%s\"" ZT_EOL_S - "}", - ((cte->second.jsonResults.length() > 0) ? ",\n" : ""), - (unsigned long long)report->timestamp, - (unsigned long long)test->testId, - (unsigned long long)report->upstream, - (unsigned long long)report->current, - (unsigned long long)OSUtils::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(&(report->receivedOnLocalAddress))->toString().c_str(), - reinterpret_cast(&(report->receivedFromRemoteAddress))->toString().c_str()); - - cte->second.jsonResults.append(tmp); + _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) @@ -1616,43 +1707,82 @@ void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid char pfx[256]; Utils::snprintf(pfx,sizeof(pfx),"network/%.16llx/member",nwid); - Mutex::Lock _l(_db_m); - _db.filter(pfx,120000,[&nmi,&now](const std::string &n,const json &member) { - try { - if (_jB(member["authorized"],false)) { - ++nmi.authorizedMemberCount; + { + Mutex::Lock _l(_nmiCache_m); + std::map::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; + } + } - 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 - _jI(mlog1["ts"],0ULL)) < ZT_NETCONF_NODE_ACTIVE_THRESHOLD) - ++nmi.activeMemberCount; + { + Mutex::Lock _l(_db_m); + _db.filter(pfx,120000,[&nmi,&now](const std::string &n,const json &member) { + try { + if (_jB(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 - _jI(mlog1["ts"],0ULL)) < ZT_NETCONF_NODE_ACTIVE_THRESHOLD) + ++nmi.activeMemberCount; + } } } - } - if (_jB(member["activeBridge"],false)) { - nmi.activeBridges.insert(_jS(member["id"],"0000000000")); - } + if (_jB(member["activeBridge"],false)) { + nmi.activeBridges.insert(_jS(member["id"],"0000000000")); + } - if (member.count("ipAssignments")) { - const json &mips = member["ipAssignments"]; - if (mips.is_array()) { - for(unsigned long i=0;i 0)&&(mdstr.length() > 0)) { + const Identity id(idstr); + bool online; + { + Mutex::Lock _l(_lastRequestTime_m); + std::map,uint64_t>::iterator lrt(_lastRequestTime.find(std::pair(id.address().toInt(),nwid))); + online = ( (lrt != _lastRequestTime.end()) && ((now - lrt->second) < ZT_NETWORK_AUTOCONF_DELAY) ); } - } catch ( ... ) {} - return true; - }); + Dictionary *metaData = new Dictionary(mdstr.c_str()); + try { + this->request(nwid,InetAddress(),0,id,*metaData); + } catch ( ... ) {} + delete metaData; + } + } catch ( ... ) {} } } // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index 59404f06a..cde6522da 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -37,11 +37,15 @@ #include "../osdep/OSUtils.hpp" #include "../osdep/Thread.hpp" +#include "../osdep/BlockingQueue.hpp" #include "../ext/json/json.hpp" #include "JSONDB.hpp" +// Number of background threads to start -- not actually started until needed +#define ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT 2 + namespace ZeroTier { class Node; @@ -52,13 +56,14 @@ public: EmbeddedNetworkController(Node *node,const char *dbPath); virtual ~EmbeddedNetworkController(); - virtual NetworkController::ResultCode doNetworkConfigRequest( - const InetAddress &fromAddr, - const Identity &signingId, - const Identity &identity, + virtual void init(const Identity &signingId,Sender *sender); + + virtual void request( uint64_t nwid, - const Dictionary &metaData, - NetworkConfig &nc); + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData); unsigned int handleControlPlaneHttpGET( const std::vector &path, @@ -82,8 +87,31 @@ public: std::string &responseBody, std::string &responseContentType); + void threadMain() + throw(); + private: 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 &metaData); + + struct _RQEntry + { + uint64_t nwid; + uint64_t requestPacketId; + InetAddress fromAddr; + Identity identity; + Dictionary metaData; + }; + BlockingQueue<_RQEntry *> _queue; + + Thread _threads[ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT]; + bool _threadsStarted; + Mutex _threads_m; // Gathers a bunch of statistics about members of a network, IP assignments, etc. that we need in various places // This does lock _networkMemberCache_m @@ -96,9 +124,14 @@ private: unsigned long activeMemberCount; unsigned long totalMemberCount; uint64_t mostRecentDeauthTime; + uint64_t nmiTimestamp; // time this NMI structure was computed }; + std::map _nmiCache; + Mutex _nmiCache_m; void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi); + void _pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member); + // These init objects with default and static/informational fields inline void _initMember(nlohmann::json &member) { @@ -112,7 +145,8 @@ private: if (!member.count("creationTime")) member["creationTime"] = OSUtils::now(); if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false; if (!member.count("revision")) member["revision"] = 0ULL; - if (!member.count("enableBroadcast")) member["enableBroadcast"] = true; + if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL; + if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL; member["objtype"] = "member"; } inline void _initNetwork(nlohmann::json &network) @@ -121,6 +155,7 @@ private: if (!network.count("creationTime")) network["creationTime"] = OSUtils::now(); if (!network.count("name")) network["name"] = ""; if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32; + if (!network.count("enableBroadcast")) network["enableBroadcast"] = true; if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}}; if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}}; if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array(); @@ -154,6 +189,9 @@ private: Node *const _node; std::string _path; + NetworkController::Sender *_sender; + Identity _signingId; + struct _CircuitTestEntry { ZT_CircuitTest *test; diff --git a/controller/JSONDB.hpp b/controller/JSONDB.hpp index a9a5e6acf..bd1ae5a56 100644 --- a/controller/JSONDB.hpp +++ b/controller/JSONDB.hpp @@ -79,7 +79,7 @@ public: { for(std::map::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->second.obj,maxSinceCheck))) { + if (!func(i->first,get(i->first,maxSinceCheck))) { std::map::iterator i2(i); ++i2; this->erase(i->first); i = i2; diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 17112e905..d0fef1f1b 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -1018,16 +1018,6 @@ typedef struct */ uint64_t address; - /** - * Time we last received a unicast frame from this peer - */ - uint64_t lastUnicastFrame; - - /** - * Time we last received a multicast rame from this peer - */ - uint64_t lastMulticastFrame; - /** * Remote major version or -1 if not known */ diff --git a/java/jni/ZT_jniutils.cpp b/java/jni/ZT_jniutils.cpp index 27ca73740..6b882c6de 100644 --- a/java/jni/ZT_jniutils.cpp +++ b/java/jni/ZT_jniutils.cpp @@ -475,8 +475,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer) jclass peerClass = NULL; jfieldID addressField = NULL; - jfieldID lastUnicastFrameField = NULL; - jfieldID lastMulticastFrameField = NULL; jfieldID versionMajorField = NULL; jfieldID versionMinorField = NULL; jfieldID versionRevField = NULL; @@ -500,20 +498,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer) return NULL; } - lastUnicastFrameField = lookup.findField(peerClass, "lastUnicastFrame", "J"); - if(env->ExceptionCheck() || lastUnicastFrameField == NULL) - { - LOGE("Error finding lastUnicastFrame field of Peer object"); - return NULL; - } - - lastMulticastFrameField = lookup.findField(peerClass, "lastMulticastFrame", "J"); - if(env->ExceptionCheck() || lastMulticastFrameField == NULL) - { - LOGE("Error finding lastMulticastFrame field of Peer object"); - return NULL; - } - versionMajorField = lookup.findField(peerClass, "versionMajor", "I"); if(env->ExceptionCheck() || versionMajorField == NULL) { @@ -571,8 +555,6 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer) } env->SetLongField(peerObject, addressField, (jlong)peer.address); - env->SetLongField(peerObject, lastUnicastFrameField, (jlong)peer.lastUnicastFrame); - env->SetLongField(peerObject, lastMulticastFrameField, (jlong)peer.lastMulticastFrame); env->SetIntField(peerObject, versionMajorField, peer.versionMajor); env->SetIntField(peerObject, versionMinorField, peer.versionMinor); env->SetIntField(peerObject, versionRevField, peer.versionRev); diff --git a/java/src/com/zerotier/sdk/Peer.java b/java/src/com/zerotier/sdk/Peer.java index fb2d1065c..eb3d71300 100644 --- a/java/src/com/zerotier/sdk/Peer.java +++ b/java/src/com/zerotier/sdk/Peer.java @@ -34,8 +34,6 @@ import java.util.ArrayList; */ public final class Peer { private long address; - private long lastUnicastFrame; - private long lastMulticastFrame; private int versionMajor; private int versionMinor; private int versionRev; @@ -52,20 +50,6 @@ public final class Peer { return address; } - /** - * Time we last received a unicast frame from this peer - */ - public final long lastUnicastFrame() { - return lastUnicastFrame; - } - - /** - * Time we last received a multicast rame from this peer - */ - public final long lastMulticastFrame() { - return lastMulticastFrame; - } - /** * Remote major version or -1 if not known */ diff --git a/macui/ZeroTier One/Network.h b/macui/ZeroTier One/Network.h index 0f3c49643..957ff8d56 100644 --- a/macui/ZeroTier One/Network.h +++ b/macui/ZeroTier One/Network.h @@ -59,4 +59,10 @@ enum NetworkType { - (NSString*)statusString; - (NSString*)typeString; +- (BOOL)hasSameNetworkId:(UInt64)networkId; + +- (BOOL)isEqualToNetwork:(Network*)network; +- (BOOL)isEqual:(id)object; +- (NSUInteger)hash; + @end diff --git a/macui/ZeroTier One/Network.m b/macui/ZeroTier One/Network.m index 16efc6e32..8474acaaf 100644 --- a/macui/ZeroTier One/Network.m +++ b/macui/ZeroTier One/Network.m @@ -275,4 +275,63 @@ NSString *NetworkAllowDefaultKey = @"allowDefault"; } } +- (BOOL)hasSameNetworkId:(UInt64)networkId +{ + return self.nwid == networkId; +} + +- (BOOL)isEqualToNetwork:(Network*)network +{ + return [self.assignedAddresses isEqualToArray:network.assignedAddresses] && + self.bridge == network.bridge && + self.broadcastEnabled == network.broadcastEnabled && + self.dhcp == network.dhcp && + [self.mac isEqualToString:network.mac] && + self.mtu == network.mtu && + self.netconfRevision == network.netconfRevision && + [self.name isEqualToString:network.name] && + self.nwid == network.nwid && + [self.portDeviceName isEqualToString:network.portDeviceName] && + self.status == network.status && + self.type == network.type && + self.allowManaged == network.allowManaged && + self.allowGlobal == network.allowGlobal && + self.allowDefault == network.allowDefault && + self.connected == network.connected; +} + +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[Network class]]) { + return NO; + } + + return [self isEqualToNetwork:object]; +} + +- (NSUInteger)hash +{ + return [self.assignedAddresses hash] ^ + self.bridge ^ + self.broadcastEnabled ^ + self.dhcp ^ + [self.mac hash] ^ + self.mtu ^ + self.netconfRevision ^ + [self.name hash] ^ + self.nwid ^ + [self.portDeviceName hash] ^ + self.portError ^ + self.status ^ + self.type ^ + self.allowManaged ^ + self.allowGlobal ^ + self.allowDefault ^ + self.connected; +} + @end diff --git a/macui/ZeroTier One/ShowNetworksViewController.h b/macui/ZeroTier One/ShowNetworksViewController.h index 5c251674d..6138958d3 100644 --- a/macui/ZeroTier One/ShowNetworksViewController.h +++ b/macui/ZeroTier One/ShowNetworksViewController.h @@ -23,7 +23,7 @@ @interface ShowNetworksViewController : NSViewController -@property (nonatomic) NSArray *networkList; +@property (nonatomic) NSMutableArray *networkList; @property (nonatomic) NetworkMonitor *netMonitor; @property (nonatomic) BOOL visible; diff --git a/macui/ZeroTier One/ShowNetworksViewController.m b/macui/ZeroTier One/ShowNetworksViewController.m index e3a1e52c1..8ca32ed09 100644 --- a/macui/ZeroTier One/ShowNetworksViewController.m +++ b/macui/ZeroTier One/ShowNetworksViewController.m @@ -21,6 +21,17 @@ #import "NetworkInfoCell.h" #import "Network.h" +BOOL hasNetworkWithID(NSArray *list, UInt64 nwid) +{ + for(Network *n in list) { + if(n.nwid == nwid) { + return YES; + } + } + + return NO; +} + @interface ShowNetworksViewController () @end @@ -30,6 +41,8 @@ - (void)viewDidLoad { [super viewDidLoad]; + self.networkList = [NSMutableArray array]; + [self.tableView setDelegate:self]; [self.tableView setDataSource:self]; [self.tableView setBackgroundColor:[NSColor clearColor]]; @@ -50,9 +63,30 @@ } - (void)setNetworks:(NSArray *)list { - _networkList = list; - if(_visible) { - [_tableView reloadData]; + for (Network *n in list) { + if ([_networkList containsObject:n]) { + // don't need to do anything here. Already an identical object in the list + continue; + } + else { + // network not in the list based on equality. Did an object change? or is it a new item? + if (hasNetworkWithID(_networkList, n.nwid)) { + + for (int i = 0; i < [_networkList count]; ++i) { + Network *n2 = [_networkList objectAtIndex:i]; + if (n.nwid == n2.nwid) + { + [_networkList replaceObjectAtIndex:i withObject:n]; + [_tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:i] + columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + } + } + } + else { + [_networkList addObject:n]; + [_tableView reloadData]; + } + } } } diff --git a/macui/ZeroTier One/ShowNetworksViewController.xib b/macui/ZeroTier One/ShowNetworksViewController.xib index f26cb446c..f210d7210 100644 --- a/macui/ZeroTier One/ShowNetworksViewController.xib +++ b/macui/ZeroTier One/ShowNetworksViewController.xib @@ -1,8 +1,9 @@ - - + + - + + @@ -24,7 +25,7 @@ - + @@ -50,7 +51,7 @@ - + @@ -242,7 +243,7 @@ - + @@ -250,7 +251,7 @@ - + diff --git a/make-linux.mk b/make-linux.mk index 9dfd39bf6..ceb97a8a5 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -84,6 +84,10 @@ ifeq ($(ZT_TRACE),1) DEFS+=-DZT_TRACE endif +ifeq ($(ZT_RULES_ENGINE_DEBUGGING),1) + DEFS+=-DZT_RULES_ENGINE_DEBUGGING +endif + ifeq ($(ZT_DEBUG),1) DEFS+=-DZT_TRACE override CFLAGS+=-Wall -g -O -pthread $(INCLUDES) $(DEFS) diff --git a/node/Constants.hpp b/node/Constants.hpp index b7042d5dc..6400e2895 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -256,12 +256,12 @@ /** * How frequently to send heartbeats over in-use paths */ -#define ZT_PATH_HEARTBEAT_PERIOD 10000 +#define ZT_PATH_HEARTBEAT_PERIOD 14000 /** * Paths are considered inactive if they have not received traffic in this long */ -#define ZT_PATH_ALIVE_TIMEOUT 25000 +#define ZT_PATH_ALIVE_TIMEOUT 45000 /** * Minimum time between attempts to check dead paths to see if they can be re-awakened diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 5afacd0ed..bde5df713 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -865,92 +865,12 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons const uint64_t nwid = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); const unsigned int hopCount = hops(); const uint64_t requestPacketId = packetId(); - bool trustEstablished = false; if (RR->localNetworkController) { const unsigned int metaDataLength = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN); const char *metaDataBytes = (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength); const Dictionary metaData(metaDataBytes,metaDataLength); - - NetworkConfig *netconf = new NetworkConfig(); - try { - switch(RR->localNetworkController->doNetworkConfigRequest((hopCount > 0) ? InetAddress() : _path->address(),RR->identity,peer->identity(),nwid,metaData,*netconf)) { - - case NetworkController::NETCONF_QUERY_OK: { - trustEstablished = true; - Dictionary *dconf = new Dictionary(); - try { - if (netconf->toDictionary(*dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) { - uint64_t configUpdateId = RR->node->prng(); - if (!configUpdateId) ++configUpdateId; - const unsigned int totalSize = dconf->sizeBytes(); - unsigned int chunkIndex = 0; - while (chunkIndex < totalSize) { - const unsigned int chunkLen = std::min(totalSize - chunkIndex,(unsigned int)(ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 256))); - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(requestPacketId); - - const unsigned int sigStart = outp.size(); - outp.append(nwid); - outp.append((uint16_t)chunkLen); - outp.append((const void *)(dconf->data() + chunkIndex),chunkLen); - - outp.append((uint8_t)0); // no flags - outp.append((uint64_t)configUpdateId); - outp.append((uint32_t)totalSize); - outp.append((uint32_t)chunkIndex); - - C25519::Signature sig(RR->identity.sign(reinterpret_cast(outp.data()) + sigStart,outp.size() - sigStart)); - outp.append((uint8_t)1); - outp.append((uint16_t)ZT_C25519_SIGNATURE_LEN); - outp.append(sig.data,ZT_C25519_SIGNATURE_LEN); - - outp.compress(); - RR->sw->send(outp,true); - chunkIndex += chunkLen; - } - } - delete dconf; - } catch ( ... ) { - delete dconf; - throw; - } - } break; - - case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(requestPacketId); - outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND); - outp.append(nwid); - outp.armor(peer->key(),true); - _path->send(RR,outp.data(),outp.size(),RR->node->now()); - } break; - - case NetworkController::NETCONF_QUERY_ACCESS_DENIED: { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(requestPacketId); - outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); - outp.append(nwid); - outp.armor(peer->key(),true); - _path->send(RR,outp.data(),outp.size(),RR->node->now()); - } break; - - case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: - break; - case NetworkController::NETCONF_QUERY_IGNORE: - break; - default: - TRACE("NETWORK_CONFIG_REQUEST failed: invalid return value from NetworkController::doNetworkConfigRequest()"); - break; - } - delete netconf; - } catch ( ... ) { - delete netconf; - throw; - } + RR->localNetworkController->request(nwid,(hopCount > 0) ? InetAddress() : _path->address(),requestPacketId,peer->identity(),metaData); } else { Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); @@ -961,7 +881,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons _path->send(RR,outp.data(),outp.size(),RR->node->now()); } - peer->received(_path,hopCount,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,trustEstablished); + peer->received(_path,hopCount,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,false); } catch (std::exception &exc) { fprintf(stderr,"WARNING: network config request failed with exception: %s" ZT_EOL_S,exc.what()); TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): %s",source().toString().c_str(),_path->address().toString().c_str(),exc.what()); diff --git a/node/Network.cpp b/node/Network.cpp index c0e4b105b..1f8e7ebfe 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -599,7 +599,7 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) : if (conf.length()) { dconf->load(conf.c_str()); if (nconf->fromDictionary(*dconf)) { - this->_setConfiguration(*nconf,false); + this->setConfiguration(*nconf,false); _lastConfigUpdate = 0; // we still want to re-request a new config from the network gotConf = true; } @@ -1015,7 +1015,7 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr) } if (nc) { - this->_setConfiguration(*nc,true); + this->setConfiguration(*nc,true); delete nc; return configUpdateId; } else { @@ -1025,6 +1025,46 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr) return 0; } +int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk) +{ + // _lock is NOT locked when this is called + try { + if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) + return 0; + if (_config == nconf) + return 1; // OK config, but duplicate of what we already have + + ZT_VirtualNetworkConfig ctmp; + bool oldPortInitialized; + { + Mutex::Lock _l(_lock); + _config = nconf; + _lastConfigUpdate = RR->node->now(); + _netconfFailure = NETCONF_FAILURE_NONE; + oldPortInitialized = _portInitialized; + _portInitialized = true; + _externalConfig(&ctmp); + } + _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); + + if (saveToDisk) { + Dictionary *d = new Dictionary(); + try { + char n[64]; + Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id); + if (nconf.toDictionary(*d,false)) + RR->node->dataStorePut(n,(const void *)d->data(),d->sizeBytes(),true); + } catch ( ... ) {} + delete d; + } + + return 2; // OK and configuration has changed + } catch ( ... ) { + TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id); + } + return 0; +} + void Network::requestConfiguration() { const Address ctrl(controller()); @@ -1046,26 +1086,7 @@ void Network::requestConfiguration() if (ctrl == RR->identity.address()) { if (RR->localNetworkController) { - NetworkConfig *nconf = new NetworkConfig(); - try { - switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,*nconf)) { - case NetworkController::NETCONF_QUERY_OK: - this->_setConfiguration(*nconf,true); - break; - case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: - this->setNotFound(); - break; - case NetworkController::NETCONF_QUERY_ACCESS_DENIED: - this->setAccessDenied(); - break; - default: - this->setNotFound(); - break; - } - } catch ( ... ) { - this->setNotFound(); - } - delete nconf; + RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,rmd); } else { this->setNotFound(); } @@ -1257,46 +1278,6 @@ ZT_VirtualNetworkStatus Network::_status() const } } -int Network::_setConfiguration(const NetworkConfig &nconf,bool saveToDisk) -{ - // _lock is NOT locked when this is called - try { - if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) - return 0; - if (_config == nconf) - return 1; // OK config, but duplicate of what we already have - - ZT_VirtualNetworkConfig ctmp; - bool oldPortInitialized; - { - Mutex::Lock _l(_lock); - _config = nconf; - _lastConfigUpdate = RR->node->now(); - _netconfFailure = NETCONF_FAILURE_NONE; - oldPortInitialized = _portInitialized; - _portInitialized = true; - _externalConfig(&ctmp); - } - _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); - - if (saveToDisk) { - Dictionary *d = new Dictionary(); - try { - char n[64]; - Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id); - if (nconf.toDictionary(*d,false)) - RR->node->dataStorePut(n,(const void *)d->data(),d->sizeBytes(),true); - } catch ( ... ) {} - delete d; - } - - return 2; // OK and configuration has changed - } catch ( ... ) { - TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id); - } - return 0; -} - void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const { // assumes _lock is locked diff --git a/node/Network.hpp b/node/Network.hpp index 527d30481..1627be583 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -187,6 +187,15 @@ public: */ uint64_t handleConfigChunk(const Packet &chunk,unsigned int ptr); + /** + * Set network configuration + * + * @param nconf Network configuration + * @param saveToDisk Save to disk? Used during loading, should usually be true otherwise. + * @return 0 == bad, 1 == accepted but duplicate/unchanged, 2 == accepted and new + */ + int setConfiguration(const NetworkConfig &nconf,bool saveToDisk); + /** * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this */ @@ -328,7 +337,6 @@ public: inline void **userPtr() throw() { return &_uPtr; } private: - int _setConfiguration(const NetworkConfig &nconf,bool saveToDisk); ZT_VirtualNetworkStatus _status() const; void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked bool _gate(const SharedPtr &peer); diff --git a/node/NetworkController.hpp b/node/NetworkController.hpp index db95dd14a..fc5db4af0 100644 --- a/node/NetworkController.hpp +++ b/node/NetworkController.hpp @@ -27,7 +27,6 @@ namespace ZeroTier { -class RuntimeEnvironment; class Identity; class Address; struct InetAddress; @@ -38,45 +37,69 @@ struct InetAddress; class NetworkController { public: - /** - * Return value of doNetworkConfigRequest - */ - enum ResultCode + enum ErrorCode { - NETCONF_QUERY_OK = 0, - NETCONF_QUERY_OBJECT_NOT_FOUND = 1, - NETCONF_QUERY_ACCESS_DENIED = 2, - NETCONF_QUERY_INTERNAL_SERVER_ERROR = 3, - NETCONF_QUERY_IGNORE = 4 + NC_ERROR_NONE = 0, + NC_ERROR_OBJECT_NOT_FOUND = 1, + NC_ERROR_ACCESS_DENIED = 2, + NC_ERROR_INTERNAL_SERVER_ERROR = 3 + }; + + /** + * Interface for sender used to send pushes and replies + */ + class Sender + { + public: + /** + * Send a configuration to a remote peer + * + * @param nwid Network ID + * @param requestPacketId Request packet ID to send OK(NETWORK_CONFIG_REQUEST) or 0 to send NETWORK_CONFIG (push) + * @param destination Destination peer Address + * @param nc Network configuration to send + * @param sendLegacyFormatConfig If true, send an old-format network config + */ + virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig) = 0; + + /** + * Send a network configuration request error + * + * @param nwid Network ID + * @param requestPacketId Request packet ID or 0 if none + * @param destination Destination peer Address + * @param errorCode Error code + */ + virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode) = 0; }; NetworkController() {} virtual ~NetworkController() {} /** - * Handle a network config request, sending replies if necessary + * Called when this is added to a Node to initialize and supply info * - * This call is permitted to block, and may be called concurrently from more - * than one thread. Implementations must use locks if needed. + * @param signingId Identity for signing of network configurations, certs, etc. + * @param sender Sender implementation for sending replies or config pushes + */ + virtual void init(const Identity &signingId,Sender *sender) = 0; + + /** + * Handle a network configuration request * - * On internal server errors, the 'error' field in result can be filled in - * to indicate the error. - * - * @param fromAddr Originating wire address or null address if packet is not direct (or from self) - * @param signingId Identity that should be used to sign results -- must include private key - * @param identity Originating peer ZeroTier identity * @param nwid 64-bit network ID + * @param fromAddr Originating wire address or null address if packet is not direct (or from self) + * @param requestPacketId Packet ID of request packet or 0 if not initiated by remote request + * @param identity ZeroTier identity of originating peer * @param metaData Meta-data bundled with request (if any) - * @param nc NetworkConfig to fill with results * @return Returns NETCONF_QUERY_OK if result 'nc' is valid, or an error code on error */ - virtual NetworkController::ResultCode doNetworkConfigRequest( - const InetAddress &fromAddr, - const Identity &signingId, - const Identity &identity, + virtual void request( uint64_t nwid, - const Dictionary &metaData, - NetworkConfig &nc) = 0; + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) = 0; }; } // namespace ZeroTier diff --git a/node/Node.cpp b/node/Node.cpp index db9b8ea07..c05a1850a 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -405,8 +405,6 @@ ZT_PeerList *Node::peers() const for(std::vector< std::pair< Address,SharedPtr > >::iterator pi(peers.begin());pi!=peers.end();++pi) { ZT_Peer *p = &(pl->peers[pl->peerCount++]); p->address = pi->second->address().toInt(); - p->lastUnicastFrame = pi->second->lastUnicastFrame(); - p->lastMulticastFrame = pi->second->lastMulticastFrame(); if (pi->second->remoteVersionKnown()) { p->versionMajor = pi->second->remoteVersionMajor(); p->versionMinor = pi->second->remoteVersionMinor(); @@ -492,6 +490,7 @@ void Node::clearLocalInterfaceAddresses() void Node::setNetconfMaster(void *networkControllerInstance) { RR->localNetworkController = reinterpret_cast(networkControllerInstance); + RR->localNetworkController->init(RR->identity,this); } ZT_ResultCode Node::circuitTestBegin(ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)) @@ -720,6 +719,92 @@ void Node::setTrustedPaths(const struct sockaddr_storage *networks,const uint64_ RR->topology->setTrustedPaths(reinterpret_cast(networks),ids,count); } +void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig) +{ + if (destination == RR->identity.address()) { + SharedPtr n(network(nwid)); + if (!n) return; + n->setConfiguration(nc,true); + } else { + Dictionary *dconf = new Dictionary(); + try { + if (nc.toDictionary(*dconf,sendLegacyFormatConfig)) { + uint64_t configUpdateId = prng(); + if (!configUpdateId) ++configUpdateId; + + const unsigned int totalSize = dconf->sizeBytes(); + unsigned int chunkIndex = 0; + while (chunkIndex < totalSize) { + const unsigned int chunkLen = std::min(totalSize - chunkIndex,(unsigned int)(ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 256))); + Packet outp(destination,RR->identity.address(),(requestPacketId) ? Packet::VERB_OK : Packet::VERB_NETWORK_CONFIG); + if (requestPacketId) { + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(requestPacketId); + } + + const unsigned int sigStart = outp.size(); + outp.append(nwid); + outp.append((uint16_t)chunkLen); + outp.append((const void *)(dconf->data() + chunkIndex),chunkLen); + + outp.append((uint8_t)0); // no flags + outp.append((uint64_t)configUpdateId); + outp.append((uint32_t)totalSize); + outp.append((uint32_t)chunkIndex); + + C25519::Signature sig(RR->identity.sign(reinterpret_cast(outp.data()) + sigStart,outp.size() - sigStart)); + outp.append((uint8_t)1); + outp.append((uint16_t)ZT_C25519_SIGNATURE_LEN); + outp.append(sig.data,ZT_C25519_SIGNATURE_LEN); + + outp.compress(); + RR->sw->send(outp,true); + chunkIndex += chunkLen; + } + } + delete dconf; + } catch ( ... ) { + delete dconf; + throw; + } + } +} + +void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode) +{ + if (destination == RR->identity.address()) { + SharedPtr n(network(nwid)); + if (!n) return; + switch(errorCode) { + case NetworkController::NC_ERROR_OBJECT_NOT_FOUND: + case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: + n->setNotFound(); + break; + case NetworkController::NC_ERROR_ACCESS_DENIED: + n->setAccessDenied(); + break; + + default: break; + } + } else if (requestPacketId) { + Packet outp(destination,RR->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(requestPacketId); + switch(errorCode) { + //case NetworkController::NC_ERROR_OBJECT_NOT_FOUND: + //case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: + default: + outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND); + break; + case NetworkController::NC_ERROR_ACCESS_DENIED: + outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); + break; + } + outp.append(nwid); + RR->sw->send(outp,true); + } // else we can't send an ERROR() in response to nothing, so discard +} + } // namespace ZeroTier /****************************************************************************/ diff --git a/node/Node.hpp b/node/Node.hpp index 114625314..e616da3d7 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -36,6 +36,7 @@ #include "Network.hpp" #include "Path.hpp" #include "Salsa20.hpp" +#include "NetworkController.hpp" #undef TRACE #ifdef ZT_TRACE @@ -55,7 +56,7 @@ namespace ZeroTier { * * The pointer returned by ZT_Node_new() is an instance of this class. */ -class Node +class Node : public NetworkController::Sender { public: Node( @@ -69,7 +70,7 @@ public: ZT_PathCheckFunction pathCheckFunction, ZT_EventCallback eventCallback); - ~Node(); + virtual ~Node(); // Public API Functions ---------------------------------------------------- @@ -282,6 +283,9 @@ public: return false; } + virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig); + virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode); + private: inline SharedPtr _network(uint64_t nwid) const { diff --git a/node/Peer.cpp b/node/Peer.cpp index 87882dadc..94fb5298f 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -42,8 +42,7 @@ static uint32_t _natKeepaliveBuf = 0; Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : _lastReceive(0), - _lastUnicastFrame(0), - _lastMulticastFrame(0), + _lastNontrivialReceive(0), _lastDirectPathPushSent(0), _lastDirectPathPushReceive(0), _lastCredentialRequestSent(0), @@ -128,10 +127,16 @@ void Peer::received( #endif _lastReceive = now; - if ((verb == Packet::VERB_FRAME)||(verb == Packet::VERB_EXT_FRAME)) - _lastUnicastFrame = now; - else if (verb == Packet::VERB_MULTICAST_FRAME) - _lastMulticastFrame = now; + switch (verb) { + case Packet::VERB_FRAME: + case Packet::VERB_EXT_FRAME: + case Packet::VERB_NETWORK_CONFIG_REQUEST: + case Packet::VERB_NETWORK_CONFIG: + case Packet::VERB_MULTICAST_FRAME: + _lastNontrivialReceive = now; + break; + default: break; + } if (trustEstablished) { _lastTrustEstablishedPacketReceived = now; diff --git a/node/Peer.hpp b/node/Peer.hpp index d0589ccfd..be05aa3a8 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -226,25 +226,10 @@ public: */ inline bool isAlive(const uint64_t now) const { return ((now - _lastReceive) < ZT_PEER_ACTIVITY_TIMEOUT); } - /** - * @return Time of most recent unicast frame received - */ - inline uint64_t lastUnicastFrame() const { return _lastUnicastFrame; } - - /** - * @return Time of most recent multicast frame received - */ - inline uint64_t lastMulticastFrame() const { return _lastMulticastFrame; } - - /** - * @return Time of most recent frame of any kind (unicast or multicast) - */ - inline uint64_t lastFrame() const { return std::max(_lastUnicastFrame,_lastMulticastFrame); } - /** * @return True if this peer has sent us real network traffic recently */ - inline uint64_t isActive(uint64_t now) const { return ((now - lastFrame()) < ZT_PEER_ACTIVITY_TIMEOUT); } + inline uint64_t isActive(uint64_t now) const { return ((now - _lastNontrivialReceive) < ZT_PEER_ACTIVITY_TIMEOUT); } /** * @return Latency in milliseconds or 0 if unknown @@ -469,8 +454,7 @@ private: uint8_t _key[ZT_PEER_SECRET_KEY_LENGTH]; uint8_t _remoteClusterOptimal6[16]; uint64_t _lastReceive; // direct or indirect - uint64_t _lastUnicastFrame; - uint64_t _lastMulticastFrame; + uint64_t _lastNontrivialReceive; // frames, things like netconf, etc. uint64_t _lastDirectPathPushSent; uint64_t _lastDirectPathPushReceive; uint64_t _lastCredentialRequestSent; diff --git a/one.cpp b/one.cpp index 55cc2e193..51cda0c70 100644 --- a/one.cpp +++ b/one.cpp @@ -973,6 +973,7 @@ int main(int argc,char **argv) std::string homeDir; unsigned int port = ZT_DEFAULT_PORT; bool skipRootCheck = false; + const char *allowManagementFrom = (const char *)0; for(int i=1;irun()) { case OneService::ONE_STILL_RUNNING: // shouldn't happen, run() won't return until done case OneService::ONE_NORMAL_TERMINATION: diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp new file mode 100644 index 000000000..6172f4dab --- /dev/null +++ b/osdep/BlockingQueue.hpp @@ -0,0 +1,64 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZT_BLOCKINGQUEUE_HPP +#define ZT_BLOCKINGQUEUE_HPP + +#include +#include +#include + +namespace ZeroTier { + +/** + * Simple C++11 thread-safe queue + * + * Do not use in node/ since we have not gone C++11 there yet. + */ +template +class BlockingQueue +{ +public: + BlockingQueue(void) {} + + inline void post(T t) + { + std::lock_guard lock(m); + q.push(t); + c.notify_one(); + } + + inline T get(void) + { + std::unique_lock lock(m); + while(q.empty()) + c.wait(lock); + T val = q.front(); + q.pop(); + return val; + } + +private: + std::queue q; + mutable std::mutex m; + std::condition_variable c; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/LinuxDropPrivileges.cpp b/osdep/LinuxDropPrivileges.cpp index dab85bd8f..e2688e65a 100644 --- a/osdep/LinuxDropPrivileges.cpp +++ b/osdep/LinuxDropPrivileges.cpp @@ -102,6 +102,8 @@ void dropPrivileges(std::string homeDir) { return; } + createOwnedHomedir(homeDir, targetUser); + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) { // Kernel has no support for ambient capabilities. notDropping(homeDir); @@ -113,8 +115,6 @@ void dropPrivileges(std::string homeDir) { return; } - createOwnedHomedir(homeDir, targetUser); - if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID)) < 0) { fprintf(stderr, "ERROR: failed to set capabilities (not running as real root?)\n"); exit(1); diff --git a/service/ControlPlane.cpp b/service/ControlPlane.cpp index 5c1356368..150bba1b5 100644 --- a/service/ControlPlane.cpp +++ b/service/ControlPlane.cpp @@ -222,8 +222,6 @@ static void _jsonAppend(unsigned int depth,std::string &buf,const ZT_Peer *peer) Utils::snprintf(json,sizeof(json), "%s{\n" "%s\t\"address\": \"%.10llx\",\n" - "%s\t\"lastUnicastFrame\": %llu,\n" - "%s\t\"lastMulticastFrame\": %llu,\n" "%s\t\"versionMajor\": %d,\n" "%s\t\"versionMinor\": %d,\n" "%s\t\"versionRev\": %d,\n" @@ -234,8 +232,6 @@ static void _jsonAppend(unsigned int depth,std::string &buf,const ZT_Peer *peer) "%s}", prefix, prefix,peer->address, - prefix,peer->lastUnicastFrame, - prefix,peer->lastMulticastFrame, prefix,peer->versionMajor, prefix,peer->versionMinor, prefix,peer->versionRev, @@ -274,9 +270,6 @@ unsigned int ControlPlane::handleRequest( std::map urlArgs; Mutex::Lock _l(_lock); - if (!((fromAddress.ipsEqual(InetAddress::LO4))||(fromAddress.ipsEqual(InetAddress::LO6)))) - return 403; // Forbidden: we only allow access from localhost right now - /* Note: this is kind of restricted in what it'll take. It does not support * URL encoding, and /'s in URL args will screw it up. But the only URL args * it really uses in ?jsonp=funcionName, and otherwise it just takes simple diff --git a/service/OneService.cpp b/service/OneService.cpp index 30e6c9387..91063bada 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -483,6 +483,7 @@ public: const std::string _homePath; BackgroundResolver _tcpFallbackResolver; + InetAddress _allowManagementFrom; EmbeddedNetworkController *_controller; Phy _phy; Node *_node; @@ -570,7 +571,7 @@ public: // end member variables ---------------------------------------------------- - OneServiceImpl(const char *hp,unsigned int port) : + OneServiceImpl(const char *hp,unsigned int port,const char *allowManagementFrom) : _homePath((hp) ? hp : ".") ,_tcpFallbackResolver(ZT_TCP_FALLBACK_RELAY) ,_controller((EmbeddedNetworkController *)0) @@ -595,6 +596,9 @@ public: #endif ,_run(true) { + if (allowManagementFrom) + _allowManagementFrom.fromString(allowManagementFrom); + _ports[0] = 0; _ports[1] = 0; _ports[2] = 0; @@ -614,7 +618,7 @@ public: struct sockaddr_in in4; memset(&in4,0,sizeof(in4)); in4.sin_family = AF_INET; - in4.sin_addr.s_addr = Utils::hton((uint32_t)0x7f000001); // right now we just listen for TCP @127.0.0.1 + in4.sin_addr.s_addr = Utils::hton((uint32_t)((allowManagementFrom) ? 0 : 0x7f000001)); // right now we just listen for TCP @127.0.0.1 in4.sin_port = Utils::hton((uint16_t)port); _v4TcpControlSocket = _phy.tcpListen((const struct sockaddr *)&in4,this); @@ -622,7 +626,8 @@ public: memset((void *)&in6,0,sizeof(in6)); in6.sin6_family = AF_INET6; in6.sin6_port = in4.sin_port; - in6.sin6_addr.s6_addr[15] = 1; // IPv6 localhost == ::1 + if (!allowManagementFrom) + in6.sin6_addr.s6_addr[15] = 1; // IPv6 localhost == ::1 _v6TcpControlSocket = _phy.tcpListen((const struct sockaddr *)&in6,this); // We must bind one of IPv4 or IPv6 -- support either failing to support hosts that @@ -1259,12 +1264,10 @@ public: inline void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) { - if ((!from)||(reinterpret_cast(from)->ipScope() != InetAddress::IP_SCOPE_LOOPBACK)) { - // Non-Loopback: deny (for now) + if (!from) { _phy.close(sockN,false); return; } else { - // Loopback == HTTP JSON API request TcpConnection *tc = new TcpConnection(); _tcpConnections.insert(tc); tc->type = TcpConnection::TCP_HTTP_INCOMING; @@ -1701,16 +1704,20 @@ public: std::string contentType("text/plain"); // default if not changed in handleRequest() unsigned int scode = 404; - try { - if (_controlPlane) - scode = _controlPlane->handleRequest(tc->from,tc->parser.method,tc->url,tc->headers,tc->body,data,contentType); - else scode = 500; - } catch (std::exception &exc) { - fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: %s" ZT_EOL_S,exc.what()); - scode = 500; - } catch ( ... ) { - fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: unknown exceptino" ZT_EOL_S); - scode = 500; + if ( ((!_allowManagementFrom)&&(tc->from.ipScope() == InetAddress::IP_SCOPE_LOOPBACK)) || (_allowManagementFrom.containsAddress(tc->from)) ) { + try { + if (_controlPlane) + scode = _controlPlane->handleRequest(tc->from,tc->parser.method,tc->url,tc->headers,tc->body,data,contentType); + else scode = 500; + } catch (std::exception &exc) { + fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: %s" ZT_EOL_S,exc.what()); + scode = 500; + } catch ( ... ) { + fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: unknown exceptino" ZT_EOL_S); + scode = 500; + } + } else { + scode = 401; } const char *scodestr; @@ -1975,7 +1982,7 @@ std::string OneService::autoUpdateUrl() return std::string(); } -OneService *OneService::newInstance(const char *hp,unsigned int port) { return new OneServiceImpl(hp,port); } +OneService *OneService::newInstance(const char *hp,unsigned int port,const char *allowManagementFrom) { return new OneServiceImpl(hp,port,allowManagementFrom); } OneService::~OneService() {} } // namespace ZeroTier diff --git a/service/OneService.hpp b/service/OneService.hpp index 72ff7d842..553bfd5e1 100644 --- a/service/OneService.hpp +++ b/service/OneService.hpp @@ -98,10 +98,12 @@ public: * * @param hp Home path * @param port TCP and UDP port for packets and HTTP control (if 0, pick random port) + * @param allowManagementFrom If non-NULL, allow control from supplied IP/netmask */ static OneService *newInstance( const char *hp, - unsigned int port); + unsigned int port, + const char *allowManagementFrom = (const char *)0); virtual ~OneService(); diff --git a/service/README.md b/service/README.md index 75c437dd9..f487f2bc0 100644 --- a/service/README.md +++ b/service/README.md @@ -25,6 +25,8 @@ A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP r FieldTypeDescriptionWritable addressstring10-digit hexadecimal ZeroTier address of this nodeno publicIdentitystringFull public ZeroTier identity of this nodeno +worldIdintegerFixed value representing the virtual data center of Earth.no +worldTimestampintegerTimestamp of the last root server topology change.no onlinebooleanDoes this node appear to have upstream network access?no tcpFallbackActivebooleanIs TCP fallback mode active?no versionMajorintegerZeroTier major versionno @@ -77,9 +79,22 @@ Most network settings are not writable, as they are defined by the network contr broadcastEnabledbooleanIs Ethernet broadcast (ff:ff:ff:ff:ff:ff) allowed?no portErrorintegerError code (if any) returned by underlying OS "tap" driverno netconfRevisionintegerNetwork configuration revision IDno -multicastSubscriptions[string]Multicast memberships as array of MAC/ADI tuplesno assignedAddresses[string]ZeroTier-managed IP address assignments as array of IP/netmask bits tuplesno +routes[route]ZeroTier-managed route assignments for a network. See below for a description of the route object.no portDeviceNamestringOS-specific network device name (if available)no +allowManagedbooleanWhether ZeroTier-managed IP addresses are allowed.yes +allowGlobalbooleanWhether globally-reachable IP addresses are allowed to be assigned.yes +allowDefaultbooleanWhether a default route is allowed to be assigned for the network (route all traffic via ZeroTier)yes + + +`route` objects + + + + + + +
FieldTypeDescriptionWritable
targetstringTarget network / netmask bits, NULL, or 0.0.0.0/0 for default routeno
viastringGateway IP addressno
flagsintegerRoute flagsno
metricintegerRoute metric (not currently used)no
#### /peer @@ -99,8 +114,6 @@ Getting /peer returns an array of peer objects for all current peers. See below - - diff --git a/windows/WinUI/MainWindow.xaml.cs b/windows/WinUI/MainWindow.xaml.cs new file mode 100644 index 000000000..e4a82f966 --- /dev/null +++ b/windows/WinUI/MainWindow.xaml.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Timers; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Threading; + +namespace WinUI +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + APIHandler handler; + Regex charRegex = new Regex("[0-9a-fxA-FX]"); + Regex wholeStringRegex = new Regex("^[0-9a-fxA-FX]+$"); + + Timer timer = new Timer(); + + bool connected = false; + + public MainWindow() + { + InitializeComponent(); + + if (InitAPIHandler()) + { + networksPage.SetAPIHandler(handler); + + updateStatus(); + if (!connected) + { + MessageBox.Show("Unable to connect to ZeroTier Service."); + } + + updateNetworks(); + //updatePeers(); + + DataObject.AddPastingHandler(joinNetworkID, OnPaste); + + timer.Elapsed += new ElapsedEventHandler(OnUpdateTimer); + timer.Interval = 2000; + timer.Enabled = true; + } + } + + + private String readAuthToken(String path) + { + String authToken = ""; + + if (File.Exists(path)) + { + try + { + byte[] tmp = File.ReadAllBytes(path); + authToken = System.Text.Encoding.UTF8.GetString(tmp).Trim(); + } + catch + { + MessageBox.Show("Unable to read ZeroTier One Auth Token from:\r\n" + path, "ZeroTier One"); + } + } + + return authToken; + } + + private Int32 readPort(String path) + { + Int32 port = 9993; + + try + { + byte[] tmp = File.ReadAllBytes(path); + port = Int32.Parse(System.Text.Encoding.ASCII.GetString(tmp).Trim()); + if ((port <= 0) || (port > 65535)) + port = 9993; + } + catch + { + } + + return port; + } + + private bool InitAPIHandler() + { + String localZtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\ZeroTier\\One"; + String globalZtDir = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\ZeroTier\\One"; + + String authToken = ""; + Int32 port = 9993; + + if (!File.Exists(localZtDir + "\\authtoken.secret") || !File.Exists(localZtDir + "\\zerotier-one.port")) + { + // launch external process to copy file into place + String curPath = System.Reflection.Assembly.GetEntryAssembly().Location; + int index = curPath.LastIndexOf("\\"); + curPath = curPath.Substring(0, index); + ProcessStartInfo startInfo = new ProcessStartInfo(curPath + "\\copyutil.exe", globalZtDir + " " + localZtDir); + startInfo.Verb = "runas"; + + + var process = Process.Start(startInfo); + process.WaitForExit(); + } + + authToken = readAuthToken(localZtDir + "\\authtoken.secret"); + + if ((authToken == null) || (authToken.Length <= 0)) + { + MessageBox.Show("Unable to read ZeroTier One authtoken", "ZeroTier One"); + this.Close(); + return false; + } + + port = readPort(localZtDir + "\\zerotier-one.port"); + handler = new APIHandler(port, authToken); + return true; + } + + private void updateStatus() + { + var status = handler.GetStatus(); + + if (status != null) + { + connected = true; + + networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + { + this.networkId.Text = status.Address; + })); + versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + { + this.versionString.Content = status.Version; + })); + onlineStatus.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + { + this.onlineStatus.Content = (status.Online ? "ONLINE" : "OFFLINE"); + })); + } + else + { + connected = false; + + networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + { + this.networkId.Text = ""; + })); + versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + { + this.versionString.Content = "0"; + })); + onlineStatus.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + { + this.onlineStatus.Content = "OFFLINE"; + })); + } + } + + private void updateNetworks() + { + var networks = handler.GetNetworks(); + + networksPage.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + { + networksPage.setNetworks(networks); + })); + } + + private void updatePeers() + { + //var peers = handler.GetPeers(); + + //peersPage.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => + //{ + // peersPage.SetPeers(peers); + //})); + } + + private void OnUpdateTimer(object source, ElapsedEventArgs e) + { + updateStatus(); + updateNetworks(); + //updatePeers(); + } + + private void joinButton_Click(object sender, RoutedEventArgs e) + { + if (joinNetworkID.Text.Length < 16) + { + MessageBox.Show("Invalid Network ID"); + } + else + { + handler.JoinNetwork(joinNetworkID.Text); + } + } + + private void OnNetworkEntered(object sender, TextCompositionEventArgs e) + { + e.Handled = !charRegex.IsMatch(e.Text); + } + + private void OnPaste(object sender, DataObjectPastingEventArgs e) + { + var isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true); + if (!isText) return; + + var text = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string; + + if (!wholeStringRegex.IsMatch(text)) + { + e.CancelCommand(); + } + } + } +} diff --git a/windows/WinUI/NetworkInfoView.xaml b/windows/WinUI/NetworkInfoView.xaml index aea854904..b3e8cae0d 100644 --- a/windows/WinUI/NetworkInfoView.xaml +++ b/windows/WinUI/NetworkInfoView.xaml @@ -64,7 +64,7 @@ - + diff --git a/windows/WinUI/NetworkInfoView.xaml.cs b/windows/WinUI/NetworkInfoView.xaml.cs index c6c8726e5..ed4cc628d 100644 --- a/windows/WinUI/NetworkInfoView.xaml.cs +++ b/windows/WinUI/NetworkInfoView.xaml.cs @@ -20,7 +20,7 @@ namespace WinUI /// public partial class NetworkInfoView : UserControl { - private ZeroTierNetwork network; + public ZeroTierNetwork network; public NetworkInfoView(ZeroTierNetwork network) { @@ -29,19 +29,41 @@ namespace WinUI this.network = network; UpdateNetworkData(); + + allowDefault.Checked += AllowDefault_CheckStateChanged; + allowDefault.Unchecked += AllowDefault_CheckStateChanged; + allowGlobal.Checked += AllowGlobal_CheckStateChanged; + allowGlobal.Unchecked += AllowGlobal_CheckStateChanged; + allowManaged.Checked += AllowManaged_CheckStateChanged; + allowManaged.Unchecked += AllowManaged_CheckStateChanged; } private void UpdateNetworkData() { - this.networkId.Text = network.NetworkId; - this.networkName.Text = network.NetworkName; - this.networkStatus.Text = network.NetworkStatus; - this.networkType.Text = network.NetworkType; - this.macAddress.Text = network.MacAddress; - this.mtu.Text = network.MTU.ToString(); + + if (this.networkId.Text != network.NetworkId) + this.networkId.Text = network.NetworkId; + + if (this.networkName.Text != network.NetworkName) + this.networkName.Text = network.NetworkName; + + if (this.networkStatus.Text != network.NetworkStatus) + this.networkStatus.Text = network.NetworkStatus; + + if (this.networkType.Text != network.NetworkType) + this.networkType.Text = network.NetworkType; + + if (this.macAddress.Text != network.MacAddress) + this.macAddress.Text = network.MacAddress; + + if (this.mtu.Text != network.MTU.ToString()) + this.mtu.Text = network.MTU.ToString(); + this.broadcastEnabled.Text = (network.BroadcastEnabled ? "ENABLED" : "DISABLED"); this.bridgingEnabled.Text = (network.Bridge ? "ENABLED" : "DISABLED"); - this.deviceName.Text = network.DeviceName; + + if (this.deviceName.Text != network.DeviceName) + this.deviceName.Text = network.DeviceName; string iplist = ""; for (int i = 0; i < network.AssignedAddresses.Length; ++i) @@ -51,19 +73,12 @@ namespace WinUI iplist += "\n"; } - this.managedIps.Text = iplist; + if (this.managedIps.Text != iplist) + this.managedIps.Text = iplist; this.allowDefault.IsChecked = network.AllowDefault; this.allowGlobal.IsChecked = network.AllowGlobal; this.allowManaged.IsChecked = network.AllowManaged; - - allowDefault.Checked += AllowDefault_CheckStateChanged; - allowDefault.Unchecked += AllowDefault_CheckStateChanged; - allowGlobal.Checked += AllowGlobal_CheckStateChanged; - allowGlobal.Unchecked += AllowGlobal_CheckStateChanged; - allowManaged.Checked += AllowManaged_CheckStateChanged; - allowManaged.Unchecked += AllowManaged_CheckStateChanged; - } public bool HasNetwork(ZeroTierNetwork network) @@ -74,6 +89,13 @@ namespace WinUI return false; } + public void SetNetworkInfo(ZeroTierNetwork network) + { + this.network = network; + + UpdateNetworkData(); + } + private void leaveButton_Click(object sender, RoutedEventArgs e) { APIHandler.Instance.LeaveNetwork(network.NetworkId); diff --git a/windows/WinUI/NetworkListView.xaml b/windows/WinUI/NetworkListView.xaml index f75b1dbeb..a1e745318 100644 --- a/windows/WinUI/NetworkListView.xaml +++ b/windows/WinUI/NetworkListView.xaml @@ -93,7 +93,9 @@ - + + + diff --git a/windows/WinUI/NetworkListView.xaml.cs b/windows/WinUI/NetworkListView.xaml.cs index b1ad3df66..c89d4bccc 100644 --- a/windows/WinUI/NetworkListView.xaml.cs +++ b/windows/WinUI/NetworkListView.xaml.cs @@ -63,7 +63,7 @@ namespace WinUI networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { - this.networkId.Content = status.Address; + this.networkId.Text = status.Address; })); versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { @@ -80,7 +80,7 @@ namespace WinUI networkId.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { - this.networkId.Content = ""; + this.networkId.Text = ""; })); versionString.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { diff --git a/windows/WinUI/NetworksPage.xaml.cs b/windows/WinUI/NetworksPage.xaml.cs index e69613501..39a2fefc9 100644 --- a/windows/WinUI/NetworksPage.xaml.cs +++ b/windows/WinUI/NetworksPage.xaml.cs @@ -27,18 +27,73 @@ namespace WinUI public void setNetworks(List networks) { - this.wrapPanel.Children.Clear(); if (networks == null) { + this.wrapPanel.Children.Clear(); return; } - for (int i = 0; i < networks.Count; ++i) + foreach (ZeroTierNetwork network in networks) { - this.wrapPanel.Children.Add( - new NetworkInfoView( - networks.ElementAt(i))); + NetworkInfoView view = ChildWithNetwork(network); + if (view != null) + { + view.SetNetworkInfo(network); + } + else + { + wrapPanel.Children.Add( + new NetworkInfoView( + network)); + } } + + // remove networks we're no longer joined to. + List tmpList = GetNetworksFromChildren(); + foreach (ZeroTierNetwork n in networks) + { + if (tmpList.Contains(n)) + { + tmpList.Remove(n); + } + } + + foreach (ZeroTierNetwork n in tmpList) + { + NetworkInfoView view = ChildWithNetwork(n); + if (view != null) + { + wrapPanel.Children.Remove(view); + } + } + } + + private NetworkInfoView ChildWithNetwork(ZeroTierNetwork network) + { + List list = wrapPanel.Children.OfType().ToList(); + + foreach (NetworkInfoView view in list) + { + if (view.HasNetwork(network)) + { + return view; + } + } + + return null; + } + + private List GetNetworksFromChildren() + { + List networks = new List(wrapPanel.Children.Count); + + List list = wrapPanel.Children.OfType().ToList(); + foreach (NetworkInfoView n in list) + { + networks.Add(n.network); + } + + return networks; } } } diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj b/windows/ZeroTierOne/ZeroTierOne.vcxproj index e5ea25bf9..d19eee6cf 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -20,6 +20,7 @@ + @@ -269,7 +270,7 @@ true - NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) + NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_RULES_ENGINE_DEBUGGING;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;%(PreprocessorDefinitions) false 4996 diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index 337b8cbbf..1fa39abda 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -261,6 +261,9 @@ Source Files\controller + + Source Files\controller +
FieldTypeDescriptionWritable
addressstring10-digit hex ZeroTier addressno
lastUnicastFrameintegerTime of last unicast frame in ms since epochno
lastMulticastFrameintegerTime of last multicast frame in ms since epochno
versionMajorintegerMajor version of remote if knownno
versionMinorintegerMinor version of remote if knownno
versionRevintegerRevision of remote if knownno