diff options
author | Grant Limberg <grant.limberg@zerotier.com> | 2016-11-16 16:23:56 -0800 |
---|---|---|
committer | Grant Limberg <grant.limberg@zerotier.com> | 2016-11-16 16:23:56 -0800 |
commit | b4bacd50a1ae70d53d16aef6880aa1fc6870bd8c (patch) | |
tree | 21fd07022eff4a5debd4cc37da02f22660348237 | |
parent | 6445337a32f5470e84bb9b139c25697e22d492f6 (diff) | |
parent | 3c248ec61a732f539dcf0c9ea3d92ae8f42b62fe (diff) | |
download | infinitytier-b4bacd50a1ae70d53d16aef6880aa1fc6870bd8c.tar.gz infinitytier-b4bacd50a1ae70d53d16aef6880aa1fc6870bd8c.zip |
Merge branch 'dev' into systemtray
36 files changed, 1465 insertions, 823 deletions
diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 9c7611a4..b78f847e 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,461 +469,47 @@ EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPa EmbeddedNetworkController::~EmbeddedNetworkController() { + Mutex::Lock _l(_threads_m); + if (_threadsStarted) { + for(int i=0;i<(ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT*2);++i) + _queue.post((_RQEntry *)0); + for(int i=0;i<ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT;++i) + Thread::join(_threads[i]); + } } -NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &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; - } - - const uint64_t now = OSUtils::now(); - - // Check rate limit circuit breaker to prevent flooding - { - Mutex::Lock _l(_lastRequestTime_m); - uint64_t &lrt = _lastRequestTime[std::pair<uint64_t,uint64_t>(identity.address().toInt(),nwid)]; - if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD) - return NetworkController::NETCONF_QUERY_IGNORE; - lrt = now; - } - - char nwids[24]; - Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); - json network; - json member; - { - Mutex::Lock _l(_db_m); - network = _db.get("network",nwids,0); - member = _db.get("network",nwids,"member",identity.address().toString(),0); - } - if (!network.size()) - return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND; - _initMember(member); - - { - std::string haveIdStr(_jS(member["identity"],"")); - if (haveIdStr.length() > 0) { - // If we already know this member's identity perform a full compare. This prevents - // 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; - } catch ( ... ) { - return NetworkController::NETCONF_QUERY_ACCESS_DENIED; - } - } else { - // If we do not yet know this member's identity, learn it. - member["identity"] = identity.toString(false); - } - } - - // These are always the same, but make sure they are set - member["id"] = identity.address().toString(); - member["address"] = member["id"]; - member["nwid"] = network["id"]; - - // Determine whether and how member is authorized - const char *authorizedBy = (const char *)0; - 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); - } - } else { - char presentedAuth[512]; - if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) { - presentedAuth[511] = (char)0; // sanity check - - // Check for bearer token presented by member - if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) { - const char *const presentedToken = presentedAuth + 6; - - json &authTokens = network["authTokens"]; - if (authTokens.is_array()) { - for(unsigned long i=0;i<authTokens.size();++i) { - json &token = authTokens[i]; - if (token.is_object()) { - const uint64_t expires = _jI(token["expires"],0ULL); - const uint64_t maxUses = _jI(token["maxUsesPerMember"],0ULL); - std::string tstr = _jS(token["token"],""); + this->_sender = sender; + this->_signingId = signingId; +} - if (((expires == 0ULL)||(expires > now))&&(tstr == presentedToken)) { - bool usable = (maxUses == 0); - if (!usable) { - uint64_t useCount = 0; - json &ahist = member["authHistory"]; - if (ahist.is_array()) { - for(unsigned long j=0;j<ahist.size();++j) { - json &ah = ahist[j]; - if ((_jS(ah["ct"],"") == "token")&&(_jS(ah["c"],"") == tstr)&&(_jB(ah["a"],false))) - ++useCount; - } - } - usable = (useCount < maxUses); - } - 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); - } - } - } - } - } - } - } - } +void EmbeddedNetworkController::request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; - // Log this request { - json rlEntry = json::object(); - rlEntry["ts"] = now; - rlEntry["authorized"] = (authorizedBy) ? true : false; - rlEntry["authorizedBy"] = (authorizedBy) ? authorizedBy : ""; - rlEntry["clientMajorVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0); - rlEntry["clientMinorVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0); - rlEntry["clientRevision"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0); - rlEntry["clientProtocolVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,0); - if (fromAddr) - rlEntry["fromAddr"] = fromAddr.toString(); - - json recentLog = json::array(); - recentLog.push_back(rlEntry); - json &oldLog = member["recentLog"]; - if (oldLog.is_array()) { - for(unsigned long i=0;i<oldLog.size();++i) { - recentLog.push_back(oldLog[i]); - if (recentLog.size() >= ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH) - break; - } - } - member["recentLog"] = recentLog; - } - - // If 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 we made it this far, they are authorized. - // ------------------------------------------------------------------------- - - _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; - - nc.networkId = nwid; - nc.type = _jB(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; - nc.timestamp = now; - nc.credentialTimeMaxDelta = credentialtmd; - nc.revision = _jI(network["revision"],0ULL); - nc.issuedTo = identity.address(); - if (_jB(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; - if (_jB(network["allowPassiveBridging"],false)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; - Utils::scopy(nc.name,sizeof(nc.name),_jS(network["name"],"").c_str()); - nc.multicastLimit = (unsigned int)_jI(network["multicastLimit"],32ULL); - - for(std::set<Address>::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) - nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); - - json &v4AssignMode = network["v4AssignMode"]; - json &v6AssignMode = network["v6AssignMode"]; - json &ipAssignmentPools = network["ipAssignmentPools"]; - json &routes = network["routes"]; - json &rules = network["rules"]; - json &capabilities = network["capabilities"]; - json &memberCapabilities = member["capabilities"]; - json &memberTags = member["tags"]; - - if (rules.is_array()) { - for(unsigned long i=0;i<rules.size();++i) { - if (nc.ruleCount >= 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;i<capabilities.size();++i) { - json &cap = capabilities[i]; - if (cap.is_object()) - capsById[_jI(cap["id"],0ULL) & 0xffffffffULL] = ∩ - } - - for(unsigned long i=0;i<memberCapabilities.size();++i) { - const uint64_t capId = _jI(memberCapabilities[i],0ULL) & 0xffffffffULL; - json *cap = capsById[capId]; - if ((cap->is_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<caprj.size();++j) { - if (caprc >= 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; - } - } - } - - if (memberTags.is_array()) { - std::map< uint32_t,uint32_t > tagsById; - for(unsigned long i=0;i<memberTags.size();++i) { - json &t = memberTags[i]; - if ((t.is_array())&&(t.size() == 2)) - tagsById[(uint32_t)(_jI(t[0],0ULL) & 0xffffffffULL)] = (uint32_t)(_jI(t[1],0ULL) & 0xffffffffULL); - } - 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 (routes.is_array()) { - for(unsigned long i=0;i<routes.size();++i) { - if (nc.routeCount >= ZT_MAX_NETWORK_ROUTES) - break; - json &route = routes[i]; - json &target = route["target"]; - json &via = route["via"]; - if (target.is_string()) { - const InetAddress t(target.get<std::string>()); - InetAddress v; - if (via.is_string()) v.fromString(via.get<std::string>()); - if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) { - ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]); - *(reinterpret_cast<InetAddress *>(&(r->target))) = t; - if (v.ss_family == t.ss_family) - *(reinterpret_cast<InetAddress *>(&(r->via))) = v; - ++nc.routeCount; - } - } - } - } - - const bool noAutoAssignIps = _jB(member["noAutoAssignIps"],false); - - if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) { - if ((_jB(v6AssignMode["rfc4193"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { - nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()); - nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; - } - if ((_jB(v6AssignMode["6plane"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { - nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt()); - nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + Mutex::Lock _l(_threads_m); + if (!_threadsStarted) { + for(int i=0;i<ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT;++i) + _threads[i] = Thread::start(this); } + _threadsStarted = true; } - bool haveManagedIpv4AutoAssignment = false; - bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count - json ipAssignments = member["ipAssignments"]; // we want to make a copy - if (ipAssignments.is_array()) { - for(unsigned long i=0;i<ipAssignments.size();++i) { - if (!ipAssignments[i].is_string()) - continue; - std::string ips = ipAssignments[i]; - InetAddress ip(ips); - - // IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from - // this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source - // of user error / poor UX. - int routedNetmaskBits = 0; - for(unsigned int rk=0;rk<nc.routeCount;++rk) { - if ( (!nc.routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip)) ) - routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits(); - } - - if (routedNetmaskBits > 0) { - if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { - ip.setPort(routedNetmaskBits); - nc.staticIps[nc.staticIpCount++] = ip; - } - if (ip.ss_family == AF_INET) - haveManagedIpv4AutoAssignment = true; - else if (ip.ss_family == AF_INET6) - haveManagedIpv6AutoAssignment = true; - } - } - } else { - ipAssignments = json::array(); - } - - if ( (ipAssignmentPools.is_array()) && ((v6AssignMode.is_object())&&(_jB(v6AssignMode["zt"],false))) && (!haveManagedIpv6AutoAssignment) && (!noAutoAssignIps) ) { - for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv6AutoAssignment));++p) { - json &pool = ipAssignmentPools[p]; - if (pool.is_object()) { - InetAddress ipRangeStart(_jS(pool["ipRangeStart"],"")); - InetAddress ipRangeEnd(_jS(pool["ipRangeEnd"],"")); - if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) { - uint64_t s[2],e[2],x[2],xx[2]; - memcpy(s,ipRangeStart.rawIpData(),16); - memcpy(e,ipRangeEnd.rawIpData(),16); - s[0] = Utils::ntoh(s[0]); - s[1] = Utils::ntoh(s[1]); - e[0] = Utils::ntoh(e[0]); - e[1] = Utils::ntoh(e[1]); - x[0] = s[0]; - x[1] = s[1]; - - for(unsigned int trialCount=0;trialCount<1000;++trialCount) { - if ((trialCount == 0)&&(e[1] > s[1])&&((e[1] - s[1]) >= 0xffffffffffULL)) { - // First see if we can just cram a ZeroTier ID into the higher 64 bits. If so do that. - xx[0] = Utils::hton(x[0]); - xx[1] = Utils::hton(x[1] + identity.address().toInt()); - } else { - // Otherwise pick random addresses -- this technically doesn't explore the whole range if the lower 64 bit range is >= 1 but that won't matter since that would be huge anyway - Utils::getSecureRandom((void *)xx,16); - if ((e[0] > s[0])) - xx[0] %= (e[0] - s[0]); - else xx[0] = 0; - if ((e[1] > s[1])) - xx[1] %= (e[1] - s[1]); - else xx[1] = 0; - xx[0] = Utils::hton(x[0] + xx[0]); - xx[1] = Utils::hton(x[1] + xx[1]); - } - - InetAddress ip6((const void *)xx,16,0); - - // Check if this IP is within a local-to-Ethernet routed network - int routedNetmaskBits = 0; - for(unsigned int rk=0;rk<nc.routeCount;++rk) { - if ( (!nc.routes[rk].via.ss_family) && (nc.routes[rk].target.ss_family == AF_INET6) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip6)) ) - routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits(); - } - - // If it's routed, then try to claim and assign it and if successful end loop - if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip6))) { - ipAssignments.push_back(ip6.toIpString()); - member["ipAssignments"] = ipAssignments; - ip6.setPort((unsigned int)routedNetmaskBits); - if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) - nc.staticIps[nc.staticIpCount++] = ip6; - haveManagedIpv6AutoAssignment = true; - break; - } - } - } - } - } - } - - if ( (ipAssignmentPools.is_array()) && ((v4AssignMode.is_object())&&(_jB(v4AssignMode["zt"],false))) && (!haveManagedIpv4AutoAssignment) && (!noAutoAssignIps) ) { - for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv4AutoAssignment));++p) { - json &pool = ipAssignmentPools[p]; - if (pool.is_object()) { - InetAddress ipRangeStartIA(_jS(pool["ipRangeStart"],"")); - InetAddress ipRangeEndIA(_jS(pool["ipRangeEnd"],"")); - if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) { - uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr)); - uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeEndIA)->sin_addr.s_addr)); - if ((ipRangeEnd < ipRangeStart)||(ipRangeStart == 0)) - continue; - uint32_t ipRangeLen = ipRangeEnd - ipRangeStart; - - // Start with the LSB of the member's address - uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff); - - for(uint32_t k=ipRangeStart,trialCount=0;((k<=ipRangeEnd)&&(trialCount < 1000));++k,++trialCount) { - uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart; - ++ipTrialCounter; - if ((ip & 0x000000ff) == 0x000000ff) - continue; // don't allow addresses that end in .255 - - // Check if this IP is within a local-to-Ethernet routed network - int routedNetmaskBits = -1; - for(unsigned int rk=0;rk<nc.routeCount;++rk) { - if (nc.routes[rk].target.ss_family == AF_INET) { - uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_addr.s_addr)); - int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_port)); - if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) { - routedNetmaskBits = targetBits; - break; - } - } - } - - // If it's routed, then try to claim and assign it and if successful end loop - const InetAddress ip4(Utils::hton(ip),0); - if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip4))) { - ipAssignments.push_back(ip4.toIpString()); - member["ipAssignments"] = ipAssignments; - if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { - struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++])); - v4ip->sin_family = AF_INET; - v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits); - v4ip->sin_addr.s_addr = Utils::hton(ip); - } - haveManagedIpv4AutoAssignment = true; - break; - } - } - } - } - } - } - - CertificateOfMembership com(now,credentialtmd,nwid,identity.address()); - if (com.sign(signingId)) { - nc.com = com; - } else { - return NETCONF_QUERY_INTERNAL_SERVER_ERROR; - } - - { - Mutex::Lock _l(_db_m); - _db.put("network",nwids,"member",identity.address().toString(),member); - } - return NetworkController::NETCONF_QUERY_OK; + _RQEntry *qe = new _RQEntry; + qe->nwid = nwid; + qe->requestPacketId = requestPacketId; + qe->fromAddr = fromAddr; + qe->identity = identity; + qe->metaData = metaData; + _queue.post(qe); } unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( @@ -1073,10 +660,6 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( 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"; @@ -1092,13 +675,6 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( 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()); @@ -1110,19 +686,19 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( Mutex::Lock _l(_db_m); member = _db.get("network",nwids,"member",Address(address).toString(),0); } - if (!member.size()) - return 404; + 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("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; + member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now; + json ah; ah["a"] = newAuth; ah["by"] = "api"; @@ -1189,13 +765,16 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( member["id"] = addrs; member["address"] = addrs; // legacy member["nwid"] = nwids; - member["lastModified"] = now; - json &revj = member["revision"]; - member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); - { - Mutex::Lock _l(_db_m); - _db.put("network",nwids,"member",Address(address).toString(),member); + if (member != origMember) { + member["lastModified"] = now; + json &revj = member["revision"]; + member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); + { + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",Address(address).toString(),member); + } + _pushMemberUpdate(now,nwid,member); } // Add non-persisted fields @@ -1283,9 +862,8 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( } network = _db.get("network",nwids,0); - if (!network.size()) - return 404; } + json origNetwork(network); // for detecting changes _initNetwork(network); try { @@ -1466,13 +1044,20 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( 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); + 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; @@ -1523,20 +1108,25 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( 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()); + 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; @@ -1548,6 +1138,19 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( 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]; @@ -1611,48 +1214,575 @@ void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTes cte->second.jsonResults.append(tmp); } +void EmbeddedNetworkController::_request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; + + const uint64_t now = OSUtils::now(); + + if (requestPacketId) { + Mutex::Lock _l(_lastRequestTime_m); + uint64_t &lrt = _lastRequestTime[std::pair<uint64_t,uint64_t>(identity.address().toInt(),nwid)]; + if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD) + return; + lrt = now; + } + + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + json network; + json member; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids,0); + member = _db.get("network",nwids,"member",identity.address().toString(),0); + } + + 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); + + { + std::string haveIdStr(_jS(member["identity"],"")); + if (haveIdStr.length() > 0) { + // If we already know this member's identity perform a full compare. This prevents + // a "collision" from being able to auth onto our network in place of an already + // known member. + try { + if (Identity(haveIdStr.c_str()) != identity) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } + } catch ( ... ) { + _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. + member["identity"] = identity.toString(false); + } + } + + // These are always the same, but make sure they are set + member["id"] = identity.address().toString(); + member["address"] = member["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")) + autoAuthorized = true; + } else { + char presentedAuth[512]; + if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) { + presentedAuth[511] = (char)0; // sanity check + + // Check for bearer token presented by member + if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) { + const char *const presentedToken = presentedAuth + 6; + + json &authTokens = network["authTokens"]; + if (authTokens.is_array()) { + for(unsigned long i=0;i<authTokens.size();++i) { + json &token = authTokens[i]; + if (token.is_object()) { + const uint64_t expires = _jI(token["expires"],0ULL); + const uint64_t maxUses = _jI(token["maxUsesPerMember"],0ULL); + std::string tstr = _jS(token["token"],""); + + if (((expires == 0ULL)||(expires > now))&&(tstr == presentedToken)) { + bool usable = (maxUses == 0); + if (!usable) { + uint64_t useCount = 0; + json &ahist = member["authHistory"]; + if (ahist.is_array()) { + for(unsigned long j=0;j<ahist.size();++j) { + json &ah = ahist[j]; + if ((_jS(ah["ct"],"") == "token")&&(_jS(ah["c"],"") == tstr)&&(_jB(ah["a"],false))) + ++useCount; + } + } + usable = (useCount < maxUses); + } + if (usable) { + authorizedBy = "token"; + autoAuthorized = true; + autoAuthCredentialType = "token"; + autoAuthCredential = tstr; + } + } + } + } + } + } + } + } + + // 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; + rlEntry["authorizedBy"] = (authorizedBy) ? authorizedBy : ""; + rlEntry["clientMajorVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0); + rlEntry["clientMinorVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0); + rlEntry["clientRevision"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0); + rlEntry["clientProtocolVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,0); + if (fromAddr) + rlEntry["fromAddr"] = fromAddr.toString(); + + json recentLog = json::array(); + recentLog.push_back(rlEntry); + json &oldLog = member["recentLog"]; + if (oldLog.is_array()) { + for(unsigned long i=0;i<oldLog.size();++i) { + recentLog.push_back(oldLog[i]); + if (recentLog.size() >= ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH) + break; + } + } + member["recentLog"] = recentLog; + + // Also only do this on real requests + member["lastRequestMetaData"] = metaData.data(); + } + + // If they are not authorized, STOP! + if (!authorizedBy) { + if (origMember != member) { + member["lastModified"] = now; + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",identity.address().toString(),member); + } + _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); + + 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; + nc.timestamp = now; + nc.credentialTimeMaxDelta = credentialtmd; + nc.revision = _jI(network["revision"],0ULL); + nc.issuedTo = identity.address(); + if (_jB(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + if (_jB(network["allowPassiveBridging"],false)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; + Utils::scopy(nc.name,sizeof(nc.name),_jS(network["name"],"").c_str()); + nc.multicastLimit = (unsigned int)_jI(network["multicastLimit"],32ULL); + + for(std::set<Address>::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) { + nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); + } + + json &v4AssignMode = network["v4AssignMode"]; + json &v6AssignMode = network["v6AssignMode"]; + json &ipAssignmentPools = network["ipAssignmentPools"]; + json &routes = network["routes"]; + json &rules = network["rules"]; + json &capabilities = network["capabilities"]; + json &memberCapabilities = member["capabilities"]; + json &memberTags = member["tags"]; + + 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<rules.size();++i) { + if (nc.ruleCount >= 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;i<capabilities.size();++i) { + json &cap = capabilities[i]; + if (cap.is_object()) + capsById[_jI(cap["id"],0ULL) & 0xffffffffULL] = ∩ + } + + for(unsigned long i=0;i<memberCapabilities.size();++i) { + const uint64_t capId = _jI(memberCapabilities[i],0ULL) & 0xffffffffULL; + json *cap = capsById[capId]; + if ((cap->is_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<caprj.size();++j) { + if (caprc >= 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; + } + } + } + + if (memberTags.is_array()) { + std::map< uint32_t,uint32_t > tagsById; + for(unsigned long i=0;i<memberTags.size();++i) { + json &t = memberTags[i]; + if ((t.is_array())&&(t.size() == 2)) + tagsById[(uint32_t)(_jI(t[0],0ULL) & 0xffffffffULL)] = (uint32_t)(_jI(t[1],0ULL) & 0xffffffffULL); + } + 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 (routes.is_array()) { + for(unsigned long i=0;i<routes.size();++i) { + if (nc.routeCount >= ZT_MAX_NETWORK_ROUTES) + break; + json &route = routes[i]; + json &target = route["target"]; + json &via = route["via"]; + if (target.is_string()) { + const InetAddress t(target.get<std::string>()); + InetAddress v; + if (via.is_string()) v.fromString(via.get<std::string>()); + if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) { + ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]); + *(reinterpret_cast<InetAddress *>(&(r->target))) = t; + if (v.ss_family == t.ss_family) + *(reinterpret_cast<InetAddress *>(&(r->via))) = v; + ++nc.routeCount; + } + } + } + } + + const bool noAutoAssignIps = _jB(member["noAutoAssignIps"],false); + + if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) { + if ((_jB(v6AssignMode["rfc4193"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()); + nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + } + if ((_jB(v6AssignMode["6plane"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt()); + nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + } + } + + bool haveManagedIpv4AutoAssignment = false; + bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count + json ipAssignments = member["ipAssignments"]; // we want to make a copy + if (ipAssignments.is_array()) { + for(unsigned long i=0;i<ipAssignments.size();++i) { + if (!ipAssignments[i].is_string()) + continue; + std::string ips = ipAssignments[i]; + InetAddress ip(ips); + + // IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from + // this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source + // of user error / poor UX. + int routedNetmaskBits = 0; + for(unsigned int rk=0;rk<nc.routeCount;++rk) { + if ( (!nc.routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip)) ) + routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits(); + } + + if (routedNetmaskBits > 0) { + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + ip.setPort(routedNetmaskBits); + nc.staticIps[nc.staticIpCount++] = ip; + } + if (ip.ss_family == AF_INET) + haveManagedIpv4AutoAssignment = true; + else if (ip.ss_family == AF_INET6) + haveManagedIpv6AutoAssignment = true; + } + } + } else { + ipAssignments = json::array(); + } + + if ( (ipAssignmentPools.is_array()) && ((v6AssignMode.is_object())&&(_jB(v6AssignMode["zt"],false))) && (!haveManagedIpv6AutoAssignment) && (!noAutoAssignIps) ) { + for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv6AutoAssignment));++p) { + json &pool = ipAssignmentPools[p]; + if (pool.is_object()) { + InetAddress ipRangeStart(_jS(pool["ipRangeStart"],"")); + InetAddress ipRangeEnd(_jS(pool["ipRangeEnd"],"")); + if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) { + uint64_t s[2],e[2],x[2],xx[2]; + memcpy(s,ipRangeStart.rawIpData(),16); + memcpy(e,ipRangeEnd.rawIpData(),16); + s[0] = Utils::ntoh(s[0]); + s[1] = Utils::ntoh(s[1]); + e[0] = Utils::ntoh(e[0]); + e[1] = Utils::ntoh(e[1]); + x[0] = s[0]; + x[1] = s[1]; + + for(unsigned int trialCount=0;trialCount<1000;++trialCount) { + if ((trialCount == 0)&&(e[1] > s[1])&&((e[1] - s[1]) >= 0xffffffffffULL)) { + // First see if we can just cram a ZeroTier ID into the higher 64 bits. If so do that. + xx[0] = Utils::hton(x[0]); + xx[1] = Utils::hton(x[1] + identity.address().toInt()); + } else { + // Otherwise pick random addresses -- this technically doesn't explore the whole range if the lower 64 bit range is >= 1 but that won't matter since that would be huge anyway + Utils::getSecureRandom((void *)xx,16); + if ((e[0] > s[0])) + xx[0] %= (e[0] - s[0]); + else xx[0] = 0; + if ((e[1] > s[1])) + xx[1] %= (e[1] - s[1]); + else xx[1] = 0; + xx[0] = Utils::hton(x[0] + xx[0]); + xx[1] = Utils::hton(x[1] + xx[1]); + } + + InetAddress ip6((const void *)xx,16,0); + + // Check if this IP is within a local-to-Ethernet routed network + int routedNetmaskBits = 0; + for(unsigned int rk=0;rk<nc.routeCount;++rk) { + if ( (!nc.routes[rk].via.ss_family) && (nc.routes[rk].target.ss_family == AF_INET6) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip6)) ) + routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits(); + } + + // If it's routed, then try to claim and assign it and if successful end loop + if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip6))) { + ipAssignments.push_back(ip6.toIpString()); + member["ipAssignments"] = ipAssignments; + ip6.setPort((unsigned int)routedNetmaskBits); + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) + nc.staticIps[nc.staticIpCount++] = ip6; + haveManagedIpv6AutoAssignment = true; + break; + } + } + } + } + } + } + + if ( (ipAssignmentPools.is_array()) && ((v4AssignMode.is_object())&&(_jB(v4AssignMode["zt"],false))) && (!haveManagedIpv4AutoAssignment) && (!noAutoAssignIps) ) { + for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv4AutoAssignment));++p) { + json &pool = ipAssignmentPools[p]; + if (pool.is_object()) { + InetAddress ipRangeStartIA(_jS(pool["ipRangeStart"],"")); + InetAddress ipRangeEndIA(_jS(pool["ipRangeEnd"],"")); + if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) { + uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr)); + uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeEndIA)->sin_addr.s_addr)); + if ((ipRangeEnd < ipRangeStart)||(ipRangeStart == 0)) + continue; + uint32_t ipRangeLen = ipRangeEnd - ipRangeStart; + + // Start with the LSB of the member's address + uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff); + + for(uint32_t k=ipRangeStart,trialCount=0;((k<=ipRangeEnd)&&(trialCount < 1000));++k,++trialCount) { + uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart; + ++ipTrialCounter; + if ((ip & 0x000000ff) == 0x000000ff) + continue; // don't allow addresses that end in .255 + + // Check if this IP is within a local-to-Ethernet routed network + int routedNetmaskBits = -1; + for(unsigned int rk=0;rk<nc.routeCount;++rk) { + if (nc.routes[rk].target.ss_family == AF_INET) { + uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_addr.s_addr)); + int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_port)); + if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) { + routedNetmaskBits = targetBits; + break; + } + } + } + + // If it's routed, then try to claim and assign it and if successful end loop + const InetAddress ip4(Utils::hton(ip),0); + if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip4))) { + ipAssignments.push_back(ip4.toIpString()); + member["ipAssignments"] = ipAssignments; + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++])); + v4ip->sin_family = AF_INET; + v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits); + v4ip->sin_addr.s_addr = Utils::hton(ip); + } + haveManagedIpv4AutoAssignment = true; + break; + } + } + } + } + } + } + + CertificateOfMembership com(now,credentialtmd,nwid,identity.address()); + if (com.sign(_signingId)) { + nc.com = com; + } else { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR); + return; + } + + if (member != origMember) { + member["lastModified"] = now; + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",identity.address().toString(),member); + } + + _sender->ncSendConfig(nwid,requestPacketId,identity.address(),nc,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6); +} + void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi) { char pfx[256]; Utils::snprintf(pfx,sizeof(pfx),"network/%.16llx/member",nwid); - Mutex::Lock _l(_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; + { + Mutex::Lock _l(_nmiCache_m); + std::map<uint64_t,_NetworkMemberInfo>::iterator c(_nmiCache.find(nwid)); + if ((c != _nmiCache.end())&&((now - c->second.nmiTimestamp) < 1000)) { // a short duration cache but limits CPU use on big networks + nmi = c->second; + return; + } + } + + { + Mutex::Lock _l(_db_m); + _db.filter(pfx,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<mips.size();++i) { - InetAddress mip(_jS(mips[i],"")); - if ((mip.ss_family == AF_INET)||(mip.ss_family == AF_INET6)) - nmi.allocatedIps.insert(mip); + if (member.count("ipAssignments")) { + const json &mips = member["ipAssignments"]; + if (mips.is_array()) { + for(unsigned long i=0;i<mips.size();++i) { + InetAddress mip(_jS(mips[i],"")); + if ((mip.ss_family == AF_INET)||(mip.ss_family == AF_INET6)) + nmi.allocatedIps.insert(mip); + } } } + } else { + nmi.mostRecentDeauthTime = std::max(nmi.mostRecentDeauthTime,_jI(member["lastDeauthorizedTime"],0ULL)); } - } else { - nmi.mostRecentDeauthTime = std::max(nmi.mostRecentDeauthTime,_jI(member["lastDeauthorizedTime"],0ULL)); + } catch ( ... ) {} + return true; + }); + } + nmi.nmiTimestamp = now; + + { + Mutex::Lock _l(_nmiCache_m); + _nmiCache[nwid] = nmi; + } +} + +void EmbeddedNetworkController::_pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member) +{ + try { + const std::string idstr = member["identity"]; + const std::string mdstr = member["lastRequestMetaData"]; + if ((idstr.length() > 0)&&(mdstr.length() > 0)) { + const Identity id(idstr); + bool online; + { + Mutex::Lock _l(_lastRequestTime_m); + std::map<std::pair<uint64_t,uint64_t>,uint64_t>::iterator lrt(_lastRequestTime.find(std::pair<uint64_t,uint64_t>(id.address().toInt(),nwid))); + online = ( (lrt != _lastRequestTime.end()) && ((now - lrt->second) < ZT_NETWORK_AUTOCONF_DELAY) ); } - } catch ( ... ) {} - return true; - }); + Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> *metaData = new Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY>(mdstr.c_str()); + try { + this->request(nwid,InetAddress(),0,id,*metaData); + } catch ( ... ) {} + delete metaData; + } + } catch ( ... ) {} } } // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index 59404f06..cde6522d 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( + virtual void init(const Identity &signingId,Sender *sender); + + virtual void request( + uint64_t nwid, const InetAddress &fromAddr, - const Identity &signingId, + uint64_t requestPacketId, const Identity &identity, - uint64_t nwid, - const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData, - NetworkConfig &nc); + const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData); unsigned int handleControlPlaneHttpGET( const std::vector<std::string> &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<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData); + + struct _RQEntry + { + uint64_t nwid; + uint64_t requestPacketId; + InetAddress fromAddr; + Identity identity; + Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> 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<uint64_t,_NetworkMemberInfo> _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 a9a5e6ac..bd1ae5a5 100644 --- a/controller/JSONDB.hpp +++ b/controller/JSONDB.hpp @@ -79,7 +79,7 @@ public: { for(std::map<std::string,_E>::iterator i(_db.lower_bound(prefix));i!=_db.end();) { if ((i->first.length() >= prefix.length())&&(!memcmp(i->first.data(),prefix.data(),prefix.length()))) { - if (!func(i->first,get(i->second.obj,maxSinceCheck))) { + if (!func(i->first,get(i->first,maxSinceCheck))) { std::map<std::string,_E>::iterator i2(i); ++i2; this->erase(i->first); i = i2; diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 17112e90..d0fef1f1 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -1019,16 +1019,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 */ int versionMajor; diff --git a/java/jni/ZT_jniutils.cpp b/java/jni/ZT_jniutils.cpp index 27ca7374..6b882c6d 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 fb2d1065..eb3d7130 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; @@ -53,20 +51,6 @@ public final class Peer { } /** - * 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 */ public final int versionMajor() { diff --git a/macui/ZeroTier One/Network.h b/macui/ZeroTier One/Network.h index 0f3c4964..957ff8d5 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 16efc6e3..8474acaa 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 5c251674..6138958d 100644 --- a/macui/ZeroTier One/ShowNetworksViewController.h +++ b/macui/ZeroTier One/ShowNetworksViewController.h @@ -23,7 +23,7 @@ @interface ShowNetworksViewController : NSViewController<NSTableViewDelegate, NSTableViewDataSource> -@property (nonatomic) NSArray<Network*> *networkList; +@property (nonatomic) NSMutableArray<Network*> *networkList; @property (nonatomic) NetworkMonitor *netMonitor; @property (nonatomic) BOOL visible; diff --git a/macui/ZeroTier One/ShowNetworksViewController.m b/macui/ZeroTier One/ShowNetworksViewController.m index e3a1e52c..8ca32ed0 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<Network*> *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<Network *> *)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 f26cb446..f210d721 100644 --- a/macui/ZeroTier One/ShowNetworksViewController.xib +++ b/macui/ZeroTier One/ShowNetworksViewController.xib @@ -1,8 +1,9 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2555" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <dependencies> <deployment identifier="macosx"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11542"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="ShowNetworksViewController" customModule="ZeroTier_One" customModuleProvider="target"> @@ -24,7 +25,7 @@ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="none" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="386" rowSizeStyle="automatic" viewBased="YES" id="w5O-vn-cYB"> - <rect key="frame" x="0.0" y="0.0" width="530" height="0.0"/> + <rect key="frame" x="0.0" y="0.0" width="530" height="481"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="0.59999999999999998" colorSpace="calibratedRGB"/> @@ -50,7 +51,7 @@ <subviews> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EUT-1A-lgY"> <rect key="frame" x="480" y="364" width="44" height="19"/> - <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="bf8-gi-tpp"> + <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Label" id="bf8-gi-tpp"> <font key="font" size="13" name="AndaleMono"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> @@ -242,7 +243,7 @@ </textField> <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="GEJ-6D-gWU"> <rect key="frame" x="102" y="86" width="424" height="19"/> - <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Multiline Label" id="A3M-ZZ-6h7"> + <textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="right" title="Multiline Label" id="A3M-ZZ-6h7"> <font key="font" size="13" name="AndaleMono"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> @@ -250,7 +251,7 @@ </textField> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lO9-Jg-9f8"> <rect key="frame" x="1" y="364" width="44" height="19"/> - <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="p7O-rs-RvR"> + <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Label" id="p7O-rs-RvR"> <font key="font" size="13" name="AndaleMono"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> diff --git a/make-linux.mk b/make-linux.mk index 9dfd39bf..ceb97a8a 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 b7042d5d..6400e289 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 5afacd0e..bde5df71 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<uint64_t>(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<uint16_t>(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<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> 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<ZT_NETWORKCONFIG_DICT_CAPACITY> *dconf = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>(); - 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<const uint8_t *>(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 c0e4b105..1f8e7ebf 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<ZT_NETWORKCONFIG_DICT_CAPACITY> *d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>(); + 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<ZT_NETWORKCONFIG_DICT_CAPACITY> *d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>(); - 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 527d3048..1627be58 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -188,6 +188,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 */ inline void setAccessDenied() @@ -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> &peer); diff --git a/node/NetworkController.hpp b/node/NetworkController.hpp index db95dd14..fc5db4af 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: + enum ErrorCode + { + NC_ERROR_NONE = 0, + NC_ERROR_OBJECT_NOT_FOUND = 1, + NC_ERROR_ACCESS_DENIED = 2, + NC_ERROR_INTERNAL_SERVER_ERROR = 3 + }; + /** - * Return value of doNetworkConfigRequest + * Interface for sender used to send pushes and replies */ - enum ResultCode + class Sender { - 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 + 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 - * - * This call is permitted to block, and may be called concurrently from more - * than one thread. Implementations must use locks if needed. + * Called when this is added to a Node to initialize and supply info * - * On internal server errors, the 'error' field in result can be filled in - * to indicate the error. + * @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 * - * @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( + virtual void request( + uint64_t nwid, const InetAddress &fromAddr, - const Identity &signingId, + uint64_t requestPacketId, const Identity &identity, - uint64_t nwid, - const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData, - NetworkConfig &nc) = 0; + const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData) = 0; }; } // namespace ZeroTier diff --git a/node/Node.cpp b/node/Node.cpp index db9b8ea0..c05a1850 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<Peer> > >::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<NetworkController *>(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<const InetAddress *>(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<Network> n(network(nwid)); + if (!n) return; + n->setConfiguration(nc,true); + } else { + Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *dconf = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>(); + 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<const uint8_t *>(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<Network> 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 11462531..e616da3d 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> _network(uint64_t nwid) const { diff --git a/node/Peer.cpp b/node/Peer.cpp index 87882dad..94fb5298 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 d0589ccf..be05aa3a 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -227,24 +227,9 @@ 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; @@ -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;i<argc;++i) { if (argv[i][0] == '-') { @@ -986,6 +987,14 @@ int main(int argc,char **argv) } break; + case 'M': // allow management from this IP/bits network + allowManagementFrom = argv[i] + 2; + if (!strlen(allowManagementFrom)) { + printHelp(argv[0],stdout); + return 1; + } + break; + #ifdef __UNIX_LIKE__ case 'd': // Run in background as daemon runAsDaemon = true; @@ -1167,7 +1176,7 @@ int main(int argc,char **argv) unsigned int returnValue = 0; for(;;) { - zt1Service = OneService::newInstance(homeDir.c_str(),port); + zt1Service = OneService::newInstance(homeDir.c_str(),port,allowManagementFrom); switch(zt1Service->run()) { 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 00000000..6172f4da --- /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 <http://www.gnu.org/licenses/>. + */ + +#ifndef ZT_BLOCKINGQUEUE_HPP +#define ZT_BLOCKINGQUEUE_HPP + +#include <queue> +#include <mutex> +#include <condition_variable> + +namespace ZeroTier { + +/** + * Simple C++11 thread-safe queue + * + * Do not use in node/ since we have not gone C++11 there yet. + */ +template <class T> +class BlockingQueue +{ +public: + BlockingQueue(void) {} + + inline void post(T t) + { + std::lock_guard<std::mutex> lock(m); + q.push(t); + c.notify_one(); + } + + inline T get(void) + { + std::unique_lock<std::mutex> lock(m); + while(q.empty()) + c.wait(lock); + T val = q.front(); + q.pop(); + return val; + } + +private: + std::queue<T> q; + mutable std::mutex m; + std::condition_variable c; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/LinuxDropPrivileges.cpp b/osdep/LinuxDropPrivileges.cpp index dab85bd8..e2688e65 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 5c135636..150bba1b 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<std::string,std::string> 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 30e6c938..91063bad 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<OneServiceImpl *> _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<const InetAddress *>(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 72ff7d84..553bfd5e 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 75c437dd..f487f2bc 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 <tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> <tr><td>address</td><td>string</td><td>10-digit hexadecimal ZeroTier address of this node</td><td>no</td></tr> <tr><td>publicIdentity</td><td>string</td><td>Full public ZeroTier identity of this node</td><td>no</td></tr> +<tr><td>worldId</td><td>integer</td><td>Fixed value representing the virtual data center of Earth.</td><td>no</td></tr> +<tr><td>worldTimestamp</td><td>integer</td><td>Timestamp of the last root server topology change.</td><td>no</td></tr> <tr><td>online</td><td>boolean</td><td>Does this node appear to have upstream network access?</td><td>no</td></tr> <tr><td>tcpFallbackActive</td><td>boolean</td><td>Is TCP fallback mode active?</td><td>no</td></tr> <tr><td>versionMajor</td><td>integer</td><td>ZeroTier major version</td><td>no</td></tr> @@ -77,9 +79,22 @@ Most network settings are not writable, as they are defined by the network contr <tr><td>broadcastEnabled</td><td>boolean</td><td>Is Ethernet broadcast (ff:ff:ff:ff:ff:ff) allowed?</td><td>no</td></tr> <tr><td>portError</td><td>integer</td><td>Error code (if any) returned by underlying OS "tap" driver</td><td>no</td></tr> <tr><td>netconfRevision</td><td>integer</td><td>Network configuration revision ID</td><td>no</td></tr> -<tr><td>multicastSubscriptions</td><td>[string]</td><td>Multicast memberships as array of MAC/ADI tuples</td><td>no</td></tr> <tr><td>assignedAddresses</td><td>[string]</td><td>ZeroTier-managed IP address assignments as array of IP/netmask bits tuples</td><td>no</td></tr> +<tr><td>routes</td><td>[route]</td><td>ZeroTier-managed route assignments for a network. See below for a description of the route object.</td><td>no</td></tr> <tr><td>portDeviceName</td><td>string</td><td>OS-specific network device name (if available)</td><td>no</td></tr> +<tr><td>allowManaged</td><td>boolean</td><td>Whether ZeroTier-managed IP addresses are allowed.</td><td>yes</td></tr> +<tr><td>allowGlobal</td><td>boolean</td><td>Whether globally-reachable IP addresses are allowed to be assigned.</td><td>yes</td></tr> +<tr><td>allowDefault</td><td>boolean</td><td>Whether a default route is allowed to be assigned for the network (route all traffic via ZeroTier)</td><td>yes</td></tr> +</table> + +`route` objects + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>target</td><td>string</td><td>Target network / netmask bits, NULL, or 0.0.0.0/0 for default route</td><td>no</td></tr> +<tr><td>via</td><td>string</td><td>Gateway IP address</td><td>no</td></tr> +<tr><td>flags</td><td>integer</td><td>Route flags</td><td>no</td></tr> +<tr><td>metric</td><td>integer</td><td>Route metric (not currently used)</td><td>no</td></tr> </table> #### /peer @@ -99,8 +114,6 @@ Getting /peer returns an array of peer objects for all current peers. See below <table> <tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> <tr><td>address</td><td>string</td><td>10-digit hex ZeroTier address</td><td>no</td></tr> -<tr><td>lastUnicastFrame</td><td>integer</td><td>Time of last unicast frame in ms since epoch</td><td>no</td></tr> -<tr><td>lastMulticastFrame</td><td>integer</td><td>Time of last multicast frame in ms since epoch</td><td>no</td></tr> <tr><td>versionMajor</td><td>integer</td><td>Major version of remote if known</td><td>no</td></tr> <tr><td>versionMinor</td><td>integer</td><td>Minor version of remote if known</td><td>no</td></tr> <tr><td>versionRev</td><td>integer</td><td>Revision of remote if known</td><td>no</td></tr> diff --git a/windows/WinUI/MainWindow.xaml.cs b/windows/WinUI/MainWindow.xaml.cs new file mode 100644 index 00000000..e4a82f96 --- /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 +{ + /// <summary> + /// Interaction logic for MainWindow.xaml + /// </summary> + 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 aea85490..b3e8cae0 100644 --- a/windows/WinUI/NetworkInfoView.xaml +++ b/windows/WinUI/NetworkInfoView.xaml @@ -64,7 +64,7 @@ <TextBlock x:Name="broadcastEnabled" FontFamily="Lucida Console" TextWrapping="Wrap" Text="ENABLED" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="6" Foreground="#FF000000"/> <TextBlock x:Name="bridgingEnabled" FontFamily="Lucida Console" TextWrapping="Wrap" Text="DISABLED" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="7" Background="#FFEEEEEE" Foreground="#FF000000"/> <TextBlock x:Name="deviceName" FontFamily="Lucida Console" TextWrapping="Wrap" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="8" Foreground="#FF000000"><Span><Run Text="ethernet_32771"/></Span></TextBlock> - <TextBlock x:Name="managedIps" TextWrapping="Wrap" FontFamily="Lucida Console" HorizontalAlignment="Right" TextAlignment="Right" Grid.Column="2" Grid.Row="9" Foreground="#FF000000"><Span><Run Text="28.2.169.248/7 "/></Span><LineBreak/><Span><Run Text="fd80:56c2:e21c:0000:0199:9383:4a02:a9f8/88"/></Span></TextBlock> + <TextBox x:Name="managedIps" TextWrapping="Wrap" FontFamily="Lucida Console" HorizontalAlignment="Right" TextAlignment="Right" Grid.Column="2" Grid.Row="9" Foreground="#FF000000" IsReadOnly="True" BorderThickness="0" Background="#FFEEEEEE" Text="28.2.169.248/7
fd80:56c2:e21c:0000:0199:9383:4a02:a9f8/88"/> <CheckBox x:Name="allowGlobal" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="10" /> <CheckBox x:Name="allowManaged" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="11" /> <CheckBox x:Name="allowDefault" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="12" /> diff --git a/windows/WinUI/NetworkInfoView.xaml.cs b/windows/WinUI/NetworkInfoView.xaml.cs index c6c8726e..ed4cc628 100644 --- a/windows/WinUI/NetworkInfoView.xaml.cs +++ b/windows/WinUI/NetworkInfoView.xaml.cs @@ -20,7 +20,7 @@ namespace WinUI /// </summary> 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 f75b1dbe..a1e74531 100644 --- a/windows/WinUI/NetworkListView.xaml +++ b/windows/WinUI/NetworkListView.xaml @@ -93,7 +93,9 @@ </Grid> </ItemsPanelTemplate> </StatusBar.ItemsPanel> - <StatusBarItem Grid.Column="0" x:Name="networkId" Content="deadbeef00" Foreground="White" FontFamily="Lucida Console"/> + <StatusBarItem Grid.Column="0" x:Name="networkId_placeholder"> + <TextBox x:Name="networkId" Text="deadbeef00" HorizontalAlignment="Left" Grid.Column="0" Foreground="White" FontFamily="Lucida Console" BorderThickness="0" IsReadOnly="true" Background="Transparent"/> + </StatusBarItem> <StatusBarItem Grid.Column="1" x:Name="onlineStatus" Content="ONLINE" Foreground="White" FontFamily="Lucida Console"/> <StatusBarItem Grid.Column="2" x:Name="versionString" Content="1.0.5" Foreground="White" FontFamily="Lucida Console"/> <StatusBarItem Grid.Column="3" x:Name="blank" Content="" Height="43" Foreground="White"/> diff --git a/windows/WinUI/NetworkListView.xaml.cs b/windows/WinUI/NetworkListView.xaml.cs index b1ad3df6..c89d4bcc 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 e6961350..39a2fefc 100644 --- a/windows/WinUI/NetworksPage.xaml.cs +++ b/windows/WinUI/NetworksPage.xaml.cs @@ -27,18 +27,73 @@ namespace WinUI public void setNetworks(List<ZeroTierNetwork> 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<ZeroTierNetwork>(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<ZeroTierNetwork> 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<NetworkInfoView> list = wrapPanel.Children.OfType<NetworkInfoView>().ToList(); + + foreach (NetworkInfoView view in list) + { + if (view.HasNetwork(network)) + { + return view; + } + } + + return null; + } + + private List<ZeroTierNetwork> GetNetworksFromChildren() + { + List<ZeroTierNetwork> networks = new List<ZeroTierNetwork>(wrapPanel.Children.Count); + + List<NetworkInfoView> list = wrapPanel.Children.OfType<NetworkInfoView>().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 e5ea25bf..d19eee6c 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj @@ -20,6 +20,7 @@ </ItemGroup> <ItemGroup> <ClCompile Include="..\..\controller\EmbeddedNetworkController.cpp" /> + <ClCompile Include="..\..\controller\JSONDB.cpp" /> <ClCompile Include="..\..\ext\http-parser\http_parser.c" /> <ClCompile Include="..\..\ext\libnatpmp\getgateway.c" /> <ClCompile Include="..\..\ext\libnatpmp\natpmp.c" /> @@ -269,7 +270,7 @@ <SDLCheck>true</SDLCheck> <AdditionalIncludeDirectories> </AdditionalIncludeDirectories> - <PreprocessorDefinitions>NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>NOMINMAX;STATICLIB;WIN32;ZT_TRACE;ZT_RULES_ENGINE_DEBUGGING;ZT_USE_MINIUPNPC;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <MultiProcessorCompilation>false</MultiProcessorCompilation> <DisableSpecificWarnings>4996</DisableSpecificWarnings> </ClCompile> diff --git a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters index 337b8cbb..1fa39abd 100644 --- a/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters +++ b/windows/ZeroTierOne/ZeroTierOne.vcxproj.filters @@ -261,6 +261,9 @@ <ClCompile Include="..\..\controller\EmbeddedNetworkController.cpp"> <Filter>Source Files\controller</Filter> </ClCompile> + <ClCompile Include="..\..\controller\JSONDB.cpp"> + <Filter>Source Files\controller</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="resource.h"> |