Browse Source

Add confirmation step to new netconf, with the caveat that it will be disabled for older netconf servers to avoid race. Also add some comments.

Adam Ierymenko 10 years ago
parent
commit
0b84c10ccc
5 changed files with 75 additions and 9 deletions
  1. 33 6
      node/IncomingPacket.cpp
  2. 1 1
      node/Network.cpp
  3. 14 1
      node/NetworkConfigMaster.cpp
  4. 26 0
      node/Peer.hpp
  5. 1 1
      version.h

+ 33 - 6
node/IncomingPacket.cpp

@@ -123,6 +123,14 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 				}
 				break;
 
+			case Packet::ERROR_UNSUPPORTED_OPERATION:
+				if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
+					SharedPtr<Network> network(RR->nc->network(at<uint64_t>(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<Peer> &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<Peer> &p
 			case Packet::VERB_NETWORK_CONFIG_REQUEST: {
 				SharedPtr<Network> nw(RR->nc->network(at<uint64_t>(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<uint16_t>(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<NetworkConfig> 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());
 					}
 				}

+ 1 - 1
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) {

+ 14 - 1
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);

+ 26 - 0
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)); }
 
 	/**

+ 1 - 1
version.h

@@ -41,6 +41,6 @@
 /**
  * Revision
  */
-#define ZEROTIER_ONE_VERSION_REVISION 2
+#define ZEROTIER_ONE_VERSION_REVISION 3
 
 #endif