diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index f5379458c..8e6de0e9d 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -123,6 +123,14 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr } break; + case Packet::ERROR_UNSUPPORTED_OPERATION: + if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { + SharedPtr network(RR->nc->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + if ((network)&&(network->controller() == source())) + network->setNotFound(); + } + break; + case Packet::ERROR_IDENTITY_COLLISION: // TODO: if it comes from a supernode, regenerate a new identity // if (RR->topology->isSupernode(source())) {} @@ -286,9 +294,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr &p peer->setRemoteVersion(vProto,vMajor,vMinor,vRevision); // If a supernode has a version higher than ours, this causes a software - // update check to run now. This might bum-rush download.zerotier.com, but - // it's hosted on S3 so hopefully it can take it. This should cause updates - // to propagate out very quickly. + // update check to run now. if ((RR->updater)&&(RR->topology->isSupernode(peer->address()))) RR->updater->sawRemoteVersion(vMajor,vMinor,vRevision); } break; @@ -307,12 +313,33 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr &p case Packet::VERB_NETWORK_CONFIG_REQUEST: { SharedPtr nw(RR->nc->network(at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID))); if ((nw)&&(nw->controller() == source())) { - // OK(NETWORK_CONFIG_REQUEST) is only accepted from a network's - // controller. unsigned int dictlen = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN); std::string dict((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,dictlen),dictlen); if (dict.length()) { - nw->setConfiguration(Dictionary(dict)); + if (nw->setConfiguration(Dictionary(dict)) == 2) { // 2 == accepted and actually new + /* If this configuration was indeed new, we do another + * netconf request with its timestamp. We do this in + * order to (a) tell the netconf server we got it (it + * won't send a duplicate if ts == current), and (b) + * get another one if the netconf is changing rapidly + * until we finally have the final version. + * + * Note that we don't do this for netconf masters with + * versions <= 1.0.3, since those regenerate a new netconf + * with a new timestamp every time. In that case this double + * confirmation would create a race condition. */ + if (peer->atLeastVersion(1,0,3)) { + SharedPtr nc(nw->config2()); + if ((nc)&&(nc->timestamp() > 0)) { // sanity check + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append((uint64_t)nw->id()); + outp.append((uint16_t)0); // no meta-data + outp.append((uint64_t)nc->timestamp()); + outp.armor(peer->key(),true); + _fromSock->send(_remoteAddress,outp.data(),outp.size()); + } + } + } TRACE("got network configuration for network %.16llx from %s",(unsigned long long)nw->id(),source().toString().c_str()); } } diff --git a/node/Network.cpp b/node/Network.cpp index df741026c..aac8d5d61 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -274,7 +274,7 @@ int Network::setConfiguration(const Dictionary &conf,bool saveToDisk) { Mutex::Lock _l(_lock); if ((_config)&&(*_config == *newConfig)) - return 1; // OK but duplicate + return 1; // OK config, but duplicate of what we already have } if (applyConfiguration(newConfig)) { if (saveToDisk) { diff --git a/node/NetworkConfigMaster.cpp b/node/NetworkConfigMaster.cpp index cbf7252cd..3be2ad8cc 100644 --- a/node/NetworkConfigMaster.cpp +++ b/node/NetworkConfigMaster.cpp @@ -94,8 +94,9 @@ void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uin Utils::snprintf(nwKey,sizeof(nwKey),"zt1:network:%s:~",nwids); Utils::snprintf(revKey,sizeof(revKey),"zt1:network:%s:revision",nwids); - TRACE("netconf: request from %s for %s (if newer than %llu)",addrs,nwids,(unsigned long long)haveTimestamp); + TRACE("netconf: %s : %s if > %llu",nwids,addrs,(unsigned long long)haveTimestamp); + // Check to make sure network itself exists and is valid if (!_hget(nwKey,"id",tmps2)) { LOG("netconf: Redis error retrieving %s/id",nwKey); return; @@ -111,6 +112,7 @@ void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uin return; } + // Get network revision if (!_get(revKey,revision)) { LOG("netconf: Redis error retrieving %s",revKey); return; @@ -118,20 +120,26 @@ void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uin if (!revision.length()) revision = "0"; + // Get network member record for this peer if (!_hgetall(memberKey,memberRecord)) { LOG("netconf: Redis error retrieving %s",memberKey); return; } + // If there is no member record, init a new one -- for public networks this + // auto-authorizes, and for private nets it makes the peer show up in the UI + // so the admin can authorize or delete/hide it. if ((memberRecord.size() == 0)||(memberRecord.get("id","") != addrs)||(memberRecord.get("nwid","") != nwids)) { if (!_initNewMember(nwid,member,metaData,memberRecord)) return; } if (memberRecord.getBoolean("authorized")) { + // Get current netconf and netconf timestamp uint64_t ts = memberRecord.getHexUInt("netconfTimestamp",0); std::string netconf(memberRecord.get("netconf","")); + // Update statistics for this node Dictionary upd; upd.setHex("netconfClientTimestamp",haveTimestamp); if (fromAddr) @@ -139,11 +147,16 @@ void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uin upd.setHex("lastSeen",Utils::now()); _hmset(memberKey,upd); + // Attempt to generate netconf for this node if there isn't + // one or it's not in step with the network's revision. if (((ts == 0)||(netconf.length() == 0))||(memberRecord.get("netconfRevision","") != revision)) { if (!_generateNetconf(nwid,member,metaData,netconf,ts)) return; } + // If the netconf we have (or just generated) is newer than what + // the client reports that it has, send it. Otherwise we just + // ignore the message since the client is up to date. if (ts > haveTimestamp) { TRACE("netconf: sending %u bytes of netconf data to %s",netconf.length(),addrs); Packet outp(member,RR->identity.address(),Packet::VERB_OK); diff --git a/node/Peer.hpp b/node/Peer.hpp index 142002a8f..09bf0e043 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -341,6 +341,32 @@ public: inline unsigned int remoteVersionMinor() const throw() { return _vMinor; } inline unsigned int remoteVersionRevision() const throw() { return _vRevision; } + /** + * Check whether this peer's version is both known and is at least what is specified + * + * @param major Major version to check against + * @param minor Minor version + * @param rev Revision + * @return True if peer's version is at least supplied tuple + */ + inline bool atLeastVersion(unsigned int major,unsigned int minor,unsigned int rev) + throw() + { + if ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)) { + if (_vMajor > major) + return true; + else if (_vMajor == major) { + if (_vMinor > minor) + return true; + else if (_vMinor == minor) { + if (_vRevision >= rev) + return true; + } + } + } + return false; + } + inline bool remoteVersionKnown() const throw() { return ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)); } /** diff --git a/version.h b/version.h index 60e058dfd..fabfa3843 100644 --- a/version.h +++ b/version.h @@ -41,6 +41,6 @@ /** * Revision */ -#define ZEROTIER_ONE_VERSION_REVISION 2 +#define ZEROTIER_ONE_VERSION_REVISION 3 #endif