summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrant Limberg <grant.limberg@zerotier.com>2016-11-16 16:23:56 -0800
committerGrant Limberg <grant.limberg@zerotier.com>2016-11-16 16:23:56 -0800
commitb4bacd50a1ae70d53d16aef6880aa1fc6870bd8c (patch)
tree21fd07022eff4a5debd4cc37da02f22660348237
parent6445337a32f5470e84bb9b139c25697e22d492f6 (diff)
parent3c248ec61a732f539dcf0c9ea3d92ae8f42b62fe (diff)
downloadinfinitytier-b4bacd50a1ae70d53d16aef6880aa1fc6870bd8c.tar.gz
infinitytier-b4bacd50a1ae70d53d16aef6880aa1fc6870bd8c.zip
Merge branch 'dev' into systemtray
-rw-r--r--controller/EmbeddedNetworkController.cpp1140
-rw-r--r--controller/EmbeddedNetworkController.hpp50
-rw-r--r--controller/JSONDB.hpp2
-rw-r--r--include/ZeroTierOne.h10
-rw-r--r--java/jni/ZT_jniutils.cpp18
-rw-r--r--java/src/com/zerotier/sdk/Peer.java16
-rw-r--r--macui/ZeroTier One/Network.h6
-rw-r--r--macui/ZeroTier One/Network.m59
-rw-r--r--macui/ZeroTier One/ShowNetworksViewController.h2
-rw-r--r--macui/ZeroTier One/ShowNetworksViewController.m40
-rw-r--r--macui/ZeroTier One/ShowNetworksViewController.xib15
-rw-r--r--make-linux.mk4
-rw-r--r--node/Constants.hpp4
-rw-r--r--node/IncomingPacket.cpp84
-rw-r--r--node/Network.cpp105
-rw-r--r--node/Network.hpp10
-rw-r--r--node/NetworkController.hpp69
-rw-r--r--node/Node.cpp89
-rw-r--r--node/Node.hpp8
-rw-r--r--node/Peer.cpp17
-rw-r--r--node/Peer.hpp20
-rw-r--r--one.cpp11
-rw-r--r--osdep/BlockingQueue.hpp64
-rw-r--r--osdep/LinuxDropPrivileges.cpp4
-rw-r--r--service/ControlPlane.cpp7
-rw-r--r--service/OneService.cpp41
-rw-r--r--service/OneService.hpp4
-rw-r--r--service/README.md19
-rw-r--r--windows/WinUI/MainWindow.xaml.cs233
-rw-r--r--windows/WinUI/NetworkInfoView.xaml2
-rw-r--r--windows/WinUI/NetworkInfoView.xaml.cs56
-rw-r--r--windows/WinUI/NetworkListView.xaml4
-rw-r--r--windows/WinUI/NetworkListView.xaml.cs4
-rw-r--r--windows/WinUI/NetworksPage.xaml.cs65
-rw-r--r--windows/ZeroTierOne/ZeroTierOne.vcxproj3
-rw-r--r--windows/ZeroTierOne/ZeroTierOne.vcxproj.filters3
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] = &cap;
- }
-
- 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] = &cap;
+ }
+
+ 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;
diff --git a/one.cpp b/one.cpp
index 55cc2e19..51cda0c7 100644
--- a/one.cpp
+++ b/one.cpp
@@ -973,6 +973,7 @@ int main(int argc,char **argv)
std::string homeDir;
unsigned int port = ZT_DEFAULT_PORT;
bool skipRootCheck = false;
+ const char *allowManagementFrom = (const char *)0;
for(int i=1;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&#x0a;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">