Procházet zdrojové kódy

Bunch more refactoring and work on revocations, etc.

Adam Ierymenko před 8 roky
rodič
revize
eac3667ec1

+ 1 - 1
include/ZeroTierOne.h

@@ -517,7 +517,7 @@ enum ZT_VirtualNetworkRuleType
 	ZT_NETWORK_RULE_ACTION_TEE = 2,
 
 	/**
-	 * Exactly like TEE but frames are dropped if previous TEEs were not acknowledged by the observer
+	 * Exactly like TEE but mandates ACKs from observer
 	 */
 	ZT_NETWORK_RULE_ACTION_WATCH = 3,
 

+ 35 - 18
node/IncomingPacket.cpp

@@ -67,7 +67,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
 			return _doHELLO(RR,false);
 		}
 
-		SharedPtr<Peer> peer(RR->topology->getPeer(sourceAddress));
+		const SharedPtr<Peer> peer(RR->topology->getPeer(sourceAddress));
 		if (peer) {
 			if (!trusted) {
 				if (!dearmor(peer->key())) {
@@ -100,7 +100,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
 				case Packet::VERB_MULTICAST_LIKE:             return _doMULTICAST_LIKE(RR,peer);
 				case Packet::VERB_NETWORK_CREDENTIALS:        return _doNETWORK_CREDENTIALS(RR,peer);
 				case Packet::VERB_NETWORK_CONFIG_REQUEST:     return _doNETWORK_CONFIG_REQUEST(RR,peer);
-				case Packet::VERB_NETWORK_CONFIG_REFRESH:     return _doNETWORK_CONFIG_REFRESH(RR,peer);
+				case Packet::VERB_NETWORK_CONFIG:             return _doNETWORK_CONFIG(RR,peer);
 				case Packet::VERB_MULTICAST_GATHER:           return _doMULTICAST_GATHER(RR,peer);
 				case Packet::VERB_MULTICAST_FRAME:            return _doMULTICAST_FRAME(RR,peer);
 				case Packet::VERB_PUSH_DIRECT_PATHS:          return _doPUSH_DIRECT_PATHS(RR,peer);
@@ -131,12 +131,18 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 
 		//TRACE("ERROR %s from %s(%s) in-re %s",Packet::errorString(errorCode),peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb));
 
+		/* Security note: we do not gate doERROR() with expectingReplyTo() to
+		 * avoid having to log every outgoing packet ID. Instead we put the
+		 * logic to determine whether we should consider an ERROR in each
+		 * error handler. In most cases these are only trusted in specific
+		 * circumstances. */
+
 		switch(errorCode) {
 
 			case Packet::ERROR_OBJ_NOT_FOUND:
 				// Object not found, currently only meaningful from network controllers.
 				if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
-					SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+					const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
 					if ((network)&&(network->controller() == peer->address()))
 						network->setNotFound();
 				}
@@ -147,7 +153,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 				// consider it meaningful from network controllers. This would indicate
 				// that the queried node does not support acting as a controller.
 				if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) {
-					SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+					const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
 					if ((network)&&(network->controller() == peer->address()))
 						network->setNotFound();
 				}
@@ -161,7 +167,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 
 			case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: {
 				// Peers can send this in response to frames if they do not have a recent enough COM from us
-				SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+				const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
 				const uint64_t now = RR->node->now();
 				if ( (network) && (network->config().com) && (peer->rateGateComRequest(now)) )
 					network->pushCredentialsNow(peer->address(),now);
@@ -169,7 +175,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 
 			case Packet::ERROR_NETWORK_ACCESS_DENIED_: {
 				// Network controller: network access denied.
-				SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+				const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
 				if ((network)&&(network->controller() == peer->address()))
 					network->setAccessDenied();
 			}	break;
@@ -177,9 +183,9 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
 			case Packet::ERROR_UNWANTED_MULTICAST: {
 				// Members of networks can use this error to indicate that they no longer
 				// want to receive multicasts on a given channel.
-				SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+				const SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
 				if ((network)&&(network->gate(peer,verb(),packetId()))) {
-					MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
+					const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at<uint32_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14));
 					TRACE("%.16llx: peer %s unsubscrubed from multicast group %s",network->id(),peer->address().toString().c_str(),mg.toString().c_str());
 					RR->mc->remove(network->id(),mg,peer->address());
 				}
@@ -371,7 +377,6 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
 		const uint64_t inRePacketId = at<uint64_t>(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID);
 		bool trustEstablished = false;
 
-		// Don't parse OK packets that are not in response to a packet ID we sent
 		if (!RR->node->expectingReplyTo(inRePacketId)) {
 			TRACE("%s(%s): OK(%s) DROPPED: not expecting reply to %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb),packetId());
 			return true;
@@ -450,7 +455,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
 
 			case Packet::VERB_MULTICAST_GATHER: {
 				const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID);
-				SharedPtr<Network> network(RR->node->network(nwid));
+				const SharedPtr<Network> network(RR->node->network(nwid));
 				if ((network)&&(network->gateMulticastGatherReply(peer,verb(),packetId()))) {
 					trustEstablished = true;
 					const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC,6),6),at<uint32_t>(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI));
@@ -467,7 +472,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
 
 				//TRACE("%s(%s): OK(MULTICAST_FRAME) %.16llx/%s flags %.2x",peer->address().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),flags);
 
-				SharedPtr<Network> network(RR->node->network(nwid));
+				const SharedPtr<Network> network(RR->node->network(nwid));
 				if (network) {
 					unsigned int offset = 0;
 
@@ -683,6 +688,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
 				Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
 				outp.append((uint8_t)Packet::VERB_EXT_FRAME);
 				outp.append((uint64_t)packetId());
+				outp.append((uint64_t)nwid);
 				outp.armor(peer->key(),true);
 				_path->send(RR,outp.data(),outp.size(),RR->node->now());
 			}
@@ -727,7 +733,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
 	try {
 		const uint64_t now = RR->node->now();
 
-		uint64_t authOnNetwork[256];
+		uint64_t authOnNetwork[256]; // cache for approved network IDs
 		unsigned int authOnNetworkCount = 0;
 		SharedPtr<Network> network;
 		bool trustEstablished = false;
@@ -786,7 +792,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 		while ((p < size())&&((*this)[p])) {
 			p += com.deserialize(*this,p);
 			if (com) {
-				SharedPtr<Network> network(RR->node->network(com.networkId()));
+				const SharedPtr<Network> network(RR->node->network(com.networkId()));
 				if (network) {
 					switch (network->addCredential(com)) {
 						case Membership::ADD_REJECTED:
@@ -803,11 +809,11 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 		}
 		++p; // skip trailing 0 after COMs if present
 
-		if (p < size()) { // check if new capabilities and tags fields are present
+		if (p < size()) { // older ZeroTier versions do not send capabilities, tags, or revocations
 			const unsigned int numCapabilities = at<uint16_t>(p); p += 2;
 			for(unsigned int i=0;i<numCapabilities;++i) {
 				p += cap.deserialize(*this,p);
-				SharedPtr<Network> network(RR->node->network(cap.networkId()));
+				const SharedPtr<Network> network(RR->node->network(cap.networkId()));
 				if (network) {
 					switch (network->addCredential(cap)) {
 						case Membership::ADD_REJECTED:
@@ -825,7 +831,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 			const unsigned int numTags = at<uint16_t>(p); p += 2;
 			for(unsigned int i=0;i<numTags;++i) {
 				p += tag.deserialize(*this,p);
-				SharedPtr<Network> network(RR->node->network(tag.networkId()));
+				const SharedPtr<Network> network(RR->node->network(tag.networkId()));
 				if (network) {
 					switch (network->addCredential(tag)) {
 						case Membership::ADD_REJECTED:
@@ -843,8 +849,18 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 			const unsigned int numRevocations = at<uint16_t>(p); p += 2;
 			for(unsigned int i=0;i<numRevocations;++i) {
 				p += revocation.deserialize(*this,p);
-				SharedPtr<Network> network(RR->node->network(revocation.networkId()));
+				const SharedPtr<Network> network(RR->node->network(revocation.networkId()));
 				if (network) {
+					switch(network->addCredential(peer->address(),revocation)) {
+						case Membership::ADD_REJECTED:
+							break;
+						case Membership::ADD_ACCEPTED_NEW:
+						case Membership::ADD_ACCEPTED_REDUNDANT:
+							trustEstablished = true;
+							break;
+						case Membership::ADD_DEFERRED_FOR_WHOIS:
+							return false;
+					}
 				}
 			}
 		}
@@ -879,6 +895,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 						try {
 							if (netconf->toDictionary(*dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) {
 								dconf->wrapWithSignature(ZT_NETWORKCONFIG_DICT_KEY_SIGNATURE,RR->identity.privateKeyPair());
+
 								const unsigned int totalSize = dconf->sizeBytes();
 								unsigned int chunkIndex = 0;
 								while (chunkIndex < totalSize) {
@@ -957,7 +974,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons
 	return true;
 }
 
-bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
+bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
 {
 	try {
 		const uint64_t nwid = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);

+ 1 - 1
node/IncomingPacket.hpp

@@ -146,7 +146,7 @@ private:
 	bool _doMULTICAST_LIKE(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
 	bool _doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
 	bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
-	bool _doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
+	bool _doNETWORK_CONFIG(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
 	bool _doMULTICAST_GATHER(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
 	bool _doMULTICAST_FRAME(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
 	bool _doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);

+ 107 - 36
node/Membership.cpp

@@ -167,24 +167,9 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 			return ADD_REJECTED;
 		case 0:
 			TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
-			if (have) {
-				have->lastReceived = RR->node->now();
-				have->tag = tag;
-			} else {
-				uint64_t minlr = 0xffffffffffffffffULL;
-				for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) {
-					if (_remoteTags[i]->id == 0xffffffffffffffffULL) {
-						have = _remoteTags[i];
-						break;
-					} else if (_remoteTags[i]->lastReceived <= minlr) {
-						have = _remoteTags[i];
-						minlr = _remoteTags[i]->lastReceived;
-					}
-				}
-				have->lastReceived = RR->node->now();
-				have->tag = tag;
-				std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialSorter<_RemoteTag>());
-			}
+			if (!have) have = _newTag(tag.id());
+			have->lastReceived = RR->node->now();
+			have->tag = tag;
 			return ADD_ACCEPTED_NEW;
 		case 1:
 			return ADD_DEFERRED_FOR_WHOIS;
@@ -212,28 +197,114 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme
 			return ADD_REJECTED;
 		case 0:
 			TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
-			if (have) {
-				have->lastReceived = RR->node->now();
-				have->cap = cap;
-			} else {
-				uint64_t minlr = 0xffffffffffffffffULL;
-				for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) {
-					if (_remoteCaps[i]->id == 0xffffffffffffffffULL) {
-						have = _remoteCaps[i];
-						break;
-					} else if (_remoteCaps[i]->lastReceived <= minlr) {
-						have = _remoteCaps[i];
-						minlr = _remoteCaps[i]->lastReceived;
-					}
-				}
-				have->lastReceived = RR->node->now();
-				have->cap = cap;
-				std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialSorter<_RemoteCapability>());
-			}
+			if (!have) have = _newCapability(cap.id());
+			have->lastReceived = RR->node->now();
+			have->cap = cap;
 			return ADD_ACCEPTED_NEW;
 		case 1:
 			return ADD_DEFERRED_FOR_WHOIS;
 	}
 }
 
+Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Revocation &rev)
+{
+	switch(rev.verify(RR)) {
+		default:
+			return ADD_REJECTED;
+		case 0: {
+			const uint64_t now = RR->node->now();
+			switch(rev.type()) {
+				default:
+				//case Revocation::CREDENTIAL_TYPE_ALL:
+					return ( (_revokeCom(rev)||_revokeCap(rev,now)||_revokeTag(rev,now)) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT );
+				case Revocation::CREDENTIAL_TYPE_COM:
+					return (_revokeCom(rev) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
+				case Revocation::CREDENTIAL_TYPE_CAPABILITY:
+					return (_revokeCap(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
+				case Revocation::CREDENTIAL_TYPE_TAG:
+					return (_revokeTag(rev,now) ? ADD_ACCEPTED_NEW : ADD_ACCEPTED_REDUNDANT);
+			}
+		}
+		case 1:
+			return ADD_DEFERRED_FOR_WHOIS;
+	}
+}
+
+Membership::_RemoteTag *Membership::_newTag(const uint64_t id)
+{
+	_RemoteTag *t;
+	uint64_t minlr = 0xffffffffffffffffULL;
+	for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) {
+		if (_remoteTags[i]->id == ZT_MEMBERSHIP_CRED_ID_UNUSED) {
+			t = _remoteTags[i];
+			break;
+		} else if (_remoteTags[i]->lastReceived <= minlr) {
+			t = _remoteTags[i];
+			minlr = _remoteTags[i]->lastReceived;
+		}
+	}
+	t->id = id;
+	t->lastReceived = 0;
+	t->revocationThreshold = 0;
+	t->tag = Tag();
+	std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialSorter<_RemoteTag>());
+	return t;
+}
+
+Membership::_RemoteCapability *Membership::_newCapability(const uint64_t id)
+{
+	_RemoteCapability *c;
+	uint64_t minlr = 0xffffffffffffffffULL;
+	for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) {
+		if (_remoteCaps[i]->id == ZT_MEMBERSHIP_CRED_ID_UNUSED) {
+			c = _remoteCaps[i];
+			break;
+		} else if (_remoteCaps[i]->lastReceived <= minlr) {
+			c = _remoteCaps[i];
+			minlr = _remoteCaps[i]->lastReceived;
+		}
+	}
+	c->id = id;
+	c->lastReceived = 0;
+	c->revocationThreshold = 0;
+	c->cap = Capability();
+	std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialSorter<_RemoteCapability>());
+	return c;
+}
+
+bool Membership::_revokeCom(const Revocation &rev)
+{
+	if (rev.threshold() > _comRevocationThreshold) {
+		_comRevocationThreshold = rev.threshold();
+		return true;
+	}
+	return false;
+}
+
+bool Membership::_revokeCap(const Revocation &rev,const uint64_t now)
+{
+	_RemoteCapability *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)rev.credentialId(),_RemoteCredentialSorter<_RemoteCapability>());
+	_RemoteCapability *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteCapability *)0;
+	if (!have) have = _newCapability(rev.credentialId());
+	if (rev.threshold() > have->revocationThreshold) {
+		have->lastReceived = now;
+		have->revocationThreshold = rev.threshold();
+		return true;
+	}
+	return false;
+}
+
+bool Membership::_revokeTag(const Revocation &rev,const uint64_t now)
+{
+	_RemoteTag *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)rev.credentialId(),_RemoteCredentialSorter<_RemoteTag>());
+	_RemoteTag *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)rev.credentialId())) ? *htmp : (_RemoteTag *)0;
+	if (!have) have = _newTag(rev.credentialId());
+	if (rev.threshold() > have->revocationThreshold) {
+		have->lastReceived = now;
+		have->revocationThreshold = rev.threshold();
+		return true;
+	}
+	return false;
+}
+
 } // namespace ZeroTier

+ 17 - 4
node/Membership.hpp

@@ -29,6 +29,8 @@
 #include "Revocation.hpp"
 #include "NetworkConfig.hpp"
 
+#define ZT_MEMBERSHIP_CRED_ID_UNUSED 0xffffffffffffffffULL
+
 namespace ZeroTier {
 
 class RuntimeEnvironment;
@@ -48,7 +50,7 @@ private:
 	// Tags and related state
 	struct _RemoteTag
 	{
-		_RemoteTag() : id(0xffffffffffffffffULL),lastReceived(0),revocationThreshold(0) {}
+		_RemoteTag() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {}
 		// Tag ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
 		uint64_t id;
 		// Last time we received THEIR tag (with this ID)
@@ -62,7 +64,7 @@ private:
 	// Credentials and related state
 	struct _RemoteCapability
 	{
-		_RemoteCapability() : id(0xffffffffffffffffULL),lastReceived(0),revocationThreshold(0) {}
+		_RemoteCapability() : id(ZT_MEMBERSHIP_CRED_ID_UNUSED),lastReceived(0),revocationThreshold(0) {}
 		// Capability ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
 		uint64_t id;
 		// Last time we received THEIR capability (with this ID)
@@ -114,7 +116,7 @@ public:
 		inline const Capability *next()
 		{
 			for(;;) {
-				if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != 0xffffffffffffffffULL)) {
+				if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) {
 					const Capability *tmp = &((*_i)->cap);
 					if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
 						++_i;
@@ -147,7 +149,7 @@ public:
 		inline const Tag *next()
 		{
 			for(;;) {
-				if ((_i != &(_m->_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*_i)->id != 0xffffffffffffffffULL)) {
+				if ((_i != &(_m->_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) {
 					const Tag *tmp = &((*_i)->tag);
 					if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
 						++_i;
@@ -242,7 +244,18 @@ public:
 	 */
 	AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Capability &cap);
 
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 */
+	AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Revocation &rev);
+
 private:
+	_RemoteTag *_newTag(const uint64_t id);
+	_RemoteCapability *_newCapability(const uint64_t id);
+	bool _revokeCom(const Revocation &rev);
+	bool _revokeCap(const Revocation &rev,const uint64_t now);
+	bool _revokeTag(const Revocation &rev,const uint64_t now);
+
 	template<typename C,typename CS>
 	inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const C &cred,const CS &state) const
 	{

+ 35 - 2
node/Network.cpp

@@ -36,7 +36,7 @@
 #include "Peer.hpp"
 
 // Uncomment to make the rules engine dump trace info to stdout
-#define ZT_RULES_ENGINE_DEBUGGING 1
+//#define ZT_RULES_ENGINE_DEBUGGING 1
 
 namespace ZeroTier {
 
@@ -1116,8 +1116,41 @@ Membership::AddCredentialResult Network::addCredential(const CertificateOfMember
 	return result;
 }
 
-Membership::AddCredentialResult Network::addCredential(const Revocation &rev)
+Membership::AddCredentialResult Network::addCredential(const Address &sentFrom,const Revocation &rev)
 {
+	if (rev.networkId() != _id)
+		return Membership::ADD_REJECTED;
+
+	Mutex::Lock _l(_lock);
+	Membership &m = _membership(rev.target());
+
+	const Membership::AddCredentialResult result = m.addCredential(RR,_config,rev);
+
+	if ((result == Membership::ADD_ACCEPTED_NEW)&&(rev.fastPropagate())) {
+		/* Fast propagation is done by using a very aggressive rumor mill
+		 * propagation algorithm. When we see a Revocation that we haven't
+		 * seen before we blast it to every known member. This leads to
+		 * a huge number of redundant messages, but eventually everybody
+		 * will get it. This helps revocation speed and also helps in cases
+		 * where the controller is under attack. It need only get one
+		 * revocation out and the rest is history. */
+		Address *a = (Address *)0;
+		Membership *m = (Membership *)0;
+		Hashtable<Address,Membership>::Iterator i(_memberships);
+		while (i.next(a,m)) {
+			if ((*a != sentFrom)&&(*a != rev.signer())) {
+				Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
+				outp.append((uint8_t)0x00); // no COM
+				outp.append((uint16_t)0); // no capabilities
+				outp.append((uint16_t)0); // no tags
+				outp.append((uint16_t)1); // one revocation!
+				rev.serialize(outp);
+				RR->sw->send(outp,true);
+			}
+		}
+	}
+
+	return result;
 }
 
 void Network::destroy()

+ 1 - 1
node/Network.hpp

@@ -299,7 +299,7 @@ public:
 	/**
 	 * Validate a credential and learn it if it passes certificate and other checks
 	 */
-	Membership::AddCredentialResult addCredential(const Revocation &rev);
+	Membership::AddCredentialResult addCredential(const Address &sentFrom,const Revocation &rev);
 
 	/**
 	 * Force push credentials (COM, etc.) to a peer now

+ 1 - 1
node/Packet.cpp

@@ -40,7 +40,7 @@ const char *Packet::verbString(Verb v)
 		case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE";
 		case VERB_NETWORK_CREDENTIALS: return "NETWORK_CREDENTIALS";
 		case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST";
-		case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH";
+		case VERB_NETWORK_CONFIG: return "NETWORK_CONFIG_REFRESH";
 		case VERB_MULTICAST_GATHER: return "MULTICAST_GATHER";
 		case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME";
 		case VERB_PUSH_DIRECT_PATHS: return "PUSH_DIRECT_PATHS";

+ 21 - 7
node/Packet.hpp

@@ -674,8 +674,8 @@ public:
 		 * superset of VERB_FRAME. They're used for bridging or when we
 		 * want to attach a certificate since FRAME does not support that.
 		 *
-		 * If the ACK flag (0x08) is set, an OK(EXT_FRAME) is sent with
-		 * no payload to acknowledge receipt of the frame.
+		 * OK payload (if ACK flag is set):
+		 *   <[8] 64-bit network ID>
 		 */
 		VERB_EXT_FRAME = 0x07,
 
@@ -738,9 +738,14 @@ public:
 		 *   <[8] 64-bit timestamp of netconf we currently have>
 		 *
 		 * This message requests network configuration from a node capable of
-		 * providing it. If the optional revision is included, a response is
-		 * only generated if there is a newer network configuration available.
+		 * providing it.
+		 *
+		 * Respones to this are always whole configs intended for the recipient.
+		 * For patches and other updates a NETWORK_CONFIG is sent instead.
 		 *
+		 * It would be valid and correct as of 1.2.0 to use NETWORK_CONFIG always,
+		 * but OK(NTEWORK_CONFIG_REQUEST) should be sent for compatibility.
+		 * 
 		 * OK response payload:
 		 *   <[8] 64-bit network ID>
 		 *   <[2] 16-bit length of network configuration dictionary chunk>
@@ -754,9 +759,10 @@ public:
 		VERB_NETWORK_CONFIG_REQUEST = 0x0b,
 
 		/**
-		 * Network configuration push:
+		 * Network configuration data push:
 		 *   <[8] 64-bit network ID>
-		 *   <[8] 64-bit value used to group chunks in this push>
+		 *   <[8] 64-bit config update ID (token to identify this update)>
+		 *   <[1] flags>
 		 *   <[2] 16-bit length of network configuration dictionary chunk>
 		 *   <[...] network configuration dictionary (may be incomplete)>
 		 *   <[4] 32-bit total length of assembled dictionary>
@@ -766,8 +772,16 @@ public:
 		 * carries the same payload as OK(NETWORK_CONFIG_REQUEST). There is an
 		 * extra number after network ID in this version that is used in place of
 		 * the in-re packet ID sent with OKs to group chunks together.
+		 *
+		 * Unlike OK(NETWORK_CONFIG_REQUEST) this can be sent by peers other than
+		 * network controllers. In that case the certificate inside the Dictionary
+		 * is used for verification purposes.
+		 *
+		 * Flags:
+		 *   0x01 - Patch, not whole config
+		 *   0x02 - Use fast P2P propagation
 		 */
-		VERB_NETWORK_CONFIG_REFRESH = 0x0c,
+		VERB_NETWORK_CONFIG = 0x0c,
 
 		/**
 		 * Request endpoints for multicast distribution:

+ 1 - 1
node/Revocation.hpp

@@ -49,7 +49,7 @@ class Revocation
 public:
 	enum CredentialType
 	{
-		CREDENTIAL_TYPE_NIL = 0,
+		CREDENTIAL_TYPE_ALL = 0,
 		CREDENTIAL_TYPE_COM = 1,
 		CREDENTIAL_TYPE_CAPABILITY = 2,
 		CREDENTIAL_TYPE_TAG = 3