Browse Source

More work on tags and capabilities.

Adam Ierymenko 9 years ago
parent
commit
f057bb63cd

+ 52 - 0
node/Capability.cpp

@@ -0,0 +1,52 @@
+/*
+ * 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/>.
+ */
+
+#include "Capability.hpp"
+#include "RuntimeEnvironment.hpp"
+#include "Identity.hpp"
+#include "Topology.hpp"
+#include "Switch.hpp"
+
+namespace ZeroTier {
+
+int Capability::verify(const RuntimeEnvironment *RR) const
+{
+	try {
+		Buffer<(sizeof(Capability) * 2)> tmp;
+		this->serialize(tmp,true);
+		for(unsigned int c=0;c<ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH;++c) {
+			if (!_custody[c].to)
+				return ((c == 0) ? -1 : 0);
+			if (!_custody[c].from)
+				return -1;
+			const Identity id(RR->topology->getIdentity(_custody[c].from));
+			if (id) {
+				if (!id.verify(tmp.data(),tmp.size(),_custody[c].signature))
+					return -1;
+			} else {
+				RR->sw->requestWhois(_custody[c].from);
+				return 1;
+			}
+		}
+		return 0;
+	} catch ( ... ) {
+		return -1;
+	}
+}
+
+} // namespace ZeroTier

+ 25 - 13
node/Capability.hpp

@@ -130,11 +130,11 @@ public:
 	inline bool sign(const Identity &from,const Address &to)
 	inline bool sign(const Identity &from,const Address &to)
 	{
 	{
 		try {
 		try {
-			Buffer<(sizeof(Capability) * 2)> tmp;
 			for(unsigned int i=0;((i<_maxCustodyChainLength)&&(i<ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH));++i) {
 			for(unsigned int i=0;((i<_maxCustodyChainLength)&&(i<ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH));++i) {
 				if (!(_custody[i].to)) {
 				if (!(_custody[i].to)) {
 					_custody[i].to = to;
 					_custody[i].to = to;
 					_custody[i].from = from.address();
 					_custody[i].from = from.address();
+					Buffer<(sizeof(Capability) * 2)> tmp;
 					this->serialize(tmp,true);
 					this->serialize(tmp,true);
 					_custody[i].signature = from.sign(tmp.data(),tmp.size());
 					_custody[i].signature = from.sign(tmp.data(),tmp.size());
 					return true;
 					return true;
@@ -145,22 +145,12 @@ public:
 	}
 	}
 
 
 	/**
 	/**
-	 * Verify this capability's chain of custody
-	 *
-	 * This returns a tri-state result. A return value of zero indicates that
-	 * the chain of custody is valid and all signatures are okay. A positive
-	 * return value means at least one WHOIS was issued for a missing signing
-	 * identity and we should retry later. A negative return value means that
-	 * this chain or one of its signature is BAD and this capability should
-	 * be discarded.
-	 *
-	 * Note that the entire chain is checked regardless of verifyInChain.
+	 * Verify this capability's chain of custody and signatures
 	 *
 	 *
 	 * @param RR Runtime environment to provide for peer lookup, etc.
 	 * @param RR Runtime environment to provide for peer lookup, etc.
-	 * @param verifyInChain Also check to ensure that this capability was at some point properly issued to this peer (if non-null)
 	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
 	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
 	 */
 	 */
-	int verify(const RuntimeEnvironment *RR,const Address &verifyInChain) const;
+	int verify(const RuntimeEnvironment *RR) const;
 
 
 	template<unsigned int C>
 	template<unsigned int C>
 	static inline void serializeRules(Buffer<C> &b,const ZT_VirtualNetworkRule *rules,unsigned int ruleCount)
 	static inline void serializeRules(Buffer<C> &b,const ZT_VirtualNetworkRule *rules,unsigned int ruleCount)
@@ -403,9 +393,31 @@ public:
 		return (p - startAt);
 		return (p - startAt);
 	}
 	}
 
 
+	/**
+	 * Check to see if a given address is a 'to' address in the custody chain
+	 *
+	 * This does not actually do certificate checking. That must be done with verify().
+	 *
+	 * @param a Address to check
+	 * @return True if address is present
+	 */
+	inline bool wasIssuedTo(const Address &a) const
+	{
+		for(unsigned int i=0;i<ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH;++i) {
+			if (!_custody[i].to)
+				break;
+			else if (_custody[i].to == a)
+				return true;
+		}
+		return false;
+	}
+
 	// Provides natural sort order by ID
 	// Provides natural sort order by ID
 	inline bool operator<(const Capability &c) const { return (_id < c._id); }
 	inline bool operator<(const Capability &c) const { return (_id < c._id); }
 
 
+	inline bool operator==(const Capability &c) const { return (memcmp(this,&c,sizeof(Capability)) == 0); }
+	inline bool operator!=(const Capability &c) const { return (memcmp(this,&c,sizeof(Capability)) != 0); }
+
 private:
 private:
 	uint64_t _nwid;
 	uint64_t _nwid;
 	uint64_t _expiration;
 	uint64_t _expiration;

+ 15 - 18
node/CertificateOfMembership.cpp

@@ -17,6 +17,9 @@
  */
  */
 
 
 #include "CertificateOfMembership.hpp"
 #include "CertificateOfMembership.hpp"
+#include "RuntimeEnvironment.hpp"
+#include "Topology.hpp"
+#include "Switch.hpp"
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -182,7 +185,7 @@ bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) c
 
 
 bool CertificateOfMembership::sign(const Identity &with)
 bool CertificateOfMembership::sign(const Identity &with)
 {
 {
-	uint64_t *const buf = new uint64_t[_qualifierCount * 3];
+	uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
 	unsigned int ptr = 0;
 	unsigned int ptr = 0;
 	for(unsigned int i=0;i<_qualifierCount;++i) {
 	for(unsigned int i=0;i<_qualifierCount;++i) {
 		buf[ptr++] = Utils::hton(_qualifiers[i].id);
 		buf[ptr++] = Utils::hton(_qualifiers[i].id);
@@ -193,38 +196,32 @@ bool CertificateOfMembership::sign(const Identity &with)
 	try {
 	try {
 		_signature = with.sign(buf,ptr * sizeof(uint64_t));
 		_signature = with.sign(buf,ptr * sizeof(uint64_t));
 		_signedBy = with.address();
 		_signedBy = with.address();
-		delete [] buf;
 		return true;
 		return true;
 	} catch ( ... ) {
 	} catch ( ... ) {
 		_signedBy.zero();
 		_signedBy.zero();
-		delete [] buf;
 		return false;
 		return false;
 	}
 	}
 }
 }
 
 
-bool CertificateOfMembership::verify(const Identity &id) const
+int CertificateOfMembership::verify(const RuntimeEnvironment *RR) const
 {
 {
-	if (!_signedBy)
-		return false;
-	if (id.address() != _signedBy)
-		return false;
+	if ((!_signedBy)||(_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS))
+		return -1;
 
 
-	uint64_t *const buf = new uint64_t[_qualifierCount * 3];
+	const Identity id(RR->topology->getIdentity(_signedBy));
+	if (!id) {
+		RR->sw->requestWhois(_signedBy);
+		return 1;
+	}
+
+	uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3];
 	unsigned int ptr = 0;
 	unsigned int ptr = 0;
 	for(unsigned int i=0;i<_qualifierCount;++i) {
 	for(unsigned int i=0;i<_qualifierCount;++i) {
 		buf[ptr++] = Utils::hton(_qualifiers[i].id);
 		buf[ptr++] = Utils::hton(_qualifiers[i].id);
 		buf[ptr++] = Utils::hton(_qualifiers[i].value);
 		buf[ptr++] = Utils::hton(_qualifiers[i].value);
 		buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
 		buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta);
 	}
 	}
-
-	bool valid = false;
-	try {
-		valid = id.verify(buf,ptr * sizeof(uint64_t),_signature);
-		delete [] buf;
-	} catch ( ... ) {
-		delete [] buf;
-	}
-	return valid;
+	return (id.verify(buf,ptr * sizeof(uint64_t),_signature) ? 0 : -1);
 }
 }
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 7 - 5
node/CertificateOfMembership.hpp

@@ -46,10 +46,12 @@
 /**
 /**
  * Maximum number of qualifiers allowed in a COM (absolute max: 65535)
  * Maximum number of qualifiers allowed in a COM (absolute max: 65535)
  */
  */
-#define ZT_NETWORK_COM_MAX_QUALIFIERS 256
+#define ZT_NETWORK_COM_MAX_QUALIFIERS 8
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
+class RuntimeEnvironment;
+
 /**
 /**
  * Certificate of network membership
  * Certificate of network membership
  *
  *
@@ -275,12 +277,12 @@ public:
 	bool sign(const Identity &with);
 	bool sign(const Identity &with);
 
 
 	/**
 	/**
-	 * Verify certificate against an identity
+	 * Verify this COM and its signature
 	 *
 	 *
-	 * @param id Identity to verify against
-	 * @return True if certificate is signed by this identity and verification was successful
+	 * @param RR Runtime environment for looking up peers
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
 	 */
 	 */
-	bool verify(const Identity &id) const;
+	int verify(const RuntimeEnvironment *RR) const;
 
 
 	/**
 	/**
 	 * @return True if signed
 	 * @return True if signed

+ 19 - 8
node/IncomingPacket.cpp

@@ -443,11 +443,11 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p
 
 
 				unsigned int offset = 0;
 				unsigned int offset = 0;
 
 
-				if ((flags & 0x01) != 0) {
-					// OK(MULTICAST_FRAME) includes certificate of membership update
+				if ((flags & 0x01) != 0) { // deprecated but still used by older peers
 					CertificateOfMembership com;
 					CertificateOfMembership com;
 					offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS);
 					offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS);
-					peer->validateAndSetNetworkMembershipCertificate(nwid,com);
+					LockingPtr<Membership> m = peer->membership(com.networkId(),true);
+					if (m) m->addCredential(RR,RR->node->now(),com);
 				}
 				}
 
 
 				if ((flags & 0x02) != 0) {
 				if ((flags & 0x02) != 0) {
@@ -583,10 +583,11 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
 				const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS];
 				const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS];
 
 
 				unsigned int comLen = 0;
 				unsigned int comLen = 0;
-				if ((flags & 0x01) != 0) {
+				if ((flags & 0x01) != 0) { // deprecated but still used by old peers
 					CertificateOfMembership com;
 					CertificateOfMembership com;
 					comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM);
 					comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM);
-					peer->validateAndSetNetworkMembershipCertificate(network->id(),com);
+					LockingPtr<Membership> m = peer->membership(com.networkId(),true);
+					if (m) m->addCredential(RR,RR->node->now(),com);
 				}
 				}
 
 
 				if (!network->isAllowed(peer)) {
 				if (!network->isAllowed(peer)) {
@@ -698,6 +699,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
 bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
 bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer)
 {
 {
 	try {
 	try {
+		const uint64_t now = RR->node->now();
 		CertificateOfMembership com;
 		CertificateOfMembership com;
 		Capability cap;
 		Capability cap;
 		Tag tag;
 		Tag tag;
@@ -705,7 +707,9 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 		unsigned int p = ZT_PACKET_IDX_PAYLOAD;
 		unsigned int p = ZT_PACKET_IDX_PAYLOAD;
 		while ((p < size())&&((*this)[p])) {
 		while ((p < size())&&((*this)[p])) {
 			p += com.deserialize(*this,p);
 			p += com.deserialize(*this,p);
-			peer->validateAndSetNetworkMembershipCertificate(com.networkId(),com);
+			LockingPtr<Membership> m = peer->membership(com.networkId(),true);
+			if (!m) return true; // sanity check
+			m->addCredential(RR,now,com);
 		}
 		}
 		++p; // skip trailing 0 after COMs if present
 		++p; // skip trailing 0 after COMs if present
 
 
@@ -713,10 +717,16 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
 			const unsigned int numCapabilities = at<uint16_t>(p); p += 2;
 			const unsigned int numCapabilities = at<uint16_t>(p); p += 2;
 			for(unsigned int i=0;i<numCapabilities;++i) {
 			for(unsigned int i=0;i<numCapabilities;++i) {
 				p += cap.deserialize(*this,p);
 				p += cap.deserialize(*this,p);
+				LockingPtr<Membership> m = peer->membership(cap.networkId(),true);
+				if (!m) return true; // sanity check
+				m->addCredential(RR,now,cap);
 			}
 			}
 			const unsigned int numTags = at<uint16_t>(p); p += 2;
 			const unsigned int numTags = at<uint16_t>(p); p += 2;
 			for(unsigned int i=0;i<numTags;++i) {
 			for(unsigned int i=0;i<numTags;++i) {
 				p += tag.deserialize(*this,p);
 				p += tag.deserialize(*this,p);
+				LockingPtr<Membership> m = peer->membership(tag.networkId(),true);
+				if (!m) return true; // sanity check
+				m->addCredential(RR,now,tag);
 			}
 			}
 		}
 		}
 
 
@@ -854,10 +864,11 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,const Share
 			// Offset -- size of optional fields added to position of later fields
 			// Offset -- size of optional fields added to position of later fields
 			unsigned int offset = 0;
 			unsigned int offset = 0;
 
 
-			if ((flags & 0x01) != 0) {
+			if ((flags & 0x01) != 0) { // deprecated but still used by older peers
 				CertificateOfMembership com;
 				CertificateOfMembership com;
 				offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM);
 				offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM);
-				peer->validateAndSetNetworkMembershipCertificate(nwid,com);
+				LockingPtr<Membership> m = peer->membership(com.networkId(),true);
+				if (m) m->addCredential(RR,RR->node->now(),com);
 			}
 			}
 
 
 			// Check membership after we've read any included COM, since
 			// Check membership after we've read any included COM, since

+ 99 - 0
node/LockingPtr.hpp

@@ -0,0 +1,99 @@
+/*
+ * 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_LOCKINGPTR_HPP
+#define ZT_LOCKINGPTR_HPP
+
+#include "Mutex.hpp"
+
+namespace ZeroTier {
+
+/**
+ * A simple pointer that locks and holds a mutex until destroyed
+ *
+ * Care must be taken when using this. It's not very sophisticated and does
+ * not handle being copied except for the simple return use case. When it is
+ * copied it hands off the mutex to the copy and clears it in the original,
+ * meaning that the mutex is unlocked when the last LockingPtr<> in a chain
+ * of such handoffs is destroyed. If this chain of handoffs "forks" (more than
+ * one copy is made) then non-determinism may ensue.
+ *
+ * This does not delete or do anything else with the pointer. It also does not
+ * take care of locking the lock. That must be done beforehand.
+ */
+template<typename T>
+class LockingPtr
+{
+public:
+	LockingPtr() :
+		_ptr((T *)0),
+		_lock((Mutex *)0)
+	{
+	}
+
+	LockingPtr(T *obj,Mutex *lock) :
+		_ptr(obj),
+		_lock(lock)
+	{
+	}
+
+	LockingPtr(const LockingPtr &p) :
+		_ptr(p._ptr),
+		_lock(p._lock)
+	{
+		const_cast<LockingPtr *>(&p)->_lock = (Mutex *)0;
+	}
+
+	~LockingPtr()
+	{
+		if (_lock)
+			_lock->unlock();
+	}
+
+	inline LockingPtr &operator=(const LockingPtr &p)
+	{
+		_ptr = p._ptr;
+		_lock = p._lock;
+		const_cast<LockingPtr *>(&p)->_lock = (Mutex *)0;
+		return *this;
+	}
+
+	inline operator bool() const throw() { return (_ptr != (T *)0); }
+	inline T &operator*() const throw() { return *_ptr; }
+	inline T *operator->() const throw() { return _ptr; }
+
+	/**
+	 * @return Raw pointer to held object
+	 */
+	inline T *ptr() const throw() { return _ptr; }
+
+	inline bool operator==(const LockingPtr &sp) const throw() { return (_ptr == sp._ptr); }
+	inline bool operator!=(const LockingPtr &sp) const throw() { return (_ptr != sp._ptr); }
+	inline bool operator>(const LockingPtr &sp) const throw() { return (_ptr > sp._ptr); }
+	inline bool operator<(const LockingPtr &sp) const throw() { return (_ptr < sp._ptr); }
+	inline bool operator>=(const LockingPtr &sp) const throw() { return (_ptr >= sp._ptr); }
+	inline bool operator<=(const LockingPtr &sp) const throw() { return (_ptr <= sp._ptr); }
+
+private:
+	T *_ptr;
+	Mutex *_lock;
+};
+
+} // namespace ZeroTier
+
+#endif

+ 87 - 5
node/Membership.hpp

@@ -32,9 +32,16 @@
 #include "Hashtable.hpp"
 #include "Hashtable.hpp"
 #include "NetworkConfig.hpp"
 #include "NetworkConfig.hpp"
 
 
+// Expiration time for capability and tag cache
+#define ZT_MEMBERSHIP_STATE_EXPIRATION_TIME (ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA * 4)
+
+// Expiration time for Memberships (used in Peer::clean())
+#define ZT_MEMBERSHIP_EXPIRATION_TIME (ZT_MEMBERSHIP_STATE_EXPIRATION_TIME * 4)
+
 namespace ZeroTier {
 namespace ZeroTier {
 
 
 class Peer;
 class Peer;
+class RuntimeEnvironment;
 
 
 /**
 /**
  * Information related to a peer's participation on a network
  * Information related to a peer's participation on a network
@@ -81,15 +88,17 @@ public:
 	 * This checks last pushed times for our COM and for other credentials and
 	 * This checks last pushed times for our COM and for other credentials and
 	 * sends VERB_NETWORK_CREDENTIALS if the recipient might need them.
 	 * sends VERB_NETWORK_CREDENTIALS if the recipient might need them.
 	 *
 	 *
+	 * @param RR Runtime environment
+	 * @param now Current time
 	 * @param peer Peer that "owns" this membership
 	 * @param peer Peer that "owns" this membership
 	 * @param nconf Network configuration
 	 * @param nconf Network configuration
-	 * @param now Current time
 	 * @param capIds Capability IDs that this peer might need
 	 * @param capIds Capability IDs that this peer might need
 	 * @param capCount Number of capability IDs
 	 * @param capCount Number of capability IDs
 	 * @param tagIds Tag IDs that this peer might need
 	 * @param tagIds Tag IDs that this peer might need
 	 * @param tagCount Number of tag IDs
 	 * @param tagCount Number of tag IDs
+	 * @return True if we pushed something
 	 */
 	 */
-	void sendCredentialsIfNeeded(const Peer &peer,const NetworkConfig &nconf,const uint64_t now,const uint32_t *capIds,const unsigned int capCount,const uint32_t *tagIds,const unsigned int tagCount) const;
+	bool sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint64_t now,const Peer &peer,const NetworkConfig &nconf,const uint32_t *capIds,const unsigned int capCount,const uint32_t *tagIds,const unsigned int tagCount) const;
 
 
 	/**
 	/**
 	 * @param nconf Network configuration
 	 * @param nconf Network configuration
@@ -113,26 +122,99 @@ public:
 		return ((c) ? (((c->lastReceived != 0)&&(c->cap.expiration() < nconf.timestamp)) ? &(c->cap) : (const Capability *)0) : (const Capability *)0);
 		return ((c) ? (((c->lastReceived != 0)&&(c->cap.expiration() < nconf.timestamp)) ? &(c->cap) : (const Capability *)0) : (const Capability *)0);
 	}
 	}
 
 
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 *
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
+	 */
+	inline int addCredential(const RuntimeEnvironment *RR,const uint64_t now,const CertificateOfMembership &com)
+	{
+		if (com.issuedTo() != RR->identity.address())
+			return -1;
+		if (_com == com)
+			return 0;
+		const int vr = com.verify(RR);
+		if (vr == 0)
+			_com = com;
+		return vr;
+	}
+
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 *
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
+	 */
+	inline int addCredential(const RuntimeEnvironment *RR,const uint64_t now,const Tag &tag)
+	{
+		if (tag.issuedTo() != RR->identity.address())
+			return -1;
+		TState *t = _tags.get(tag.networkId());
+		if ((t)&&(t->lastReceived != 0)&&(t->tag == tag))
+			return 0;
+		const int vr = tag.verify(RR);
+		if (vr == 0) {
+			if (!t)
+				t = &(_tags[tag.networkId()]);
+			t->lastReceived = now;
+			t->tag = tag;
+		}
+		return vr;
+	}
+
+	/**
+	 * Validate and add a credential if signature is okay and it's otherwise good
+	 *
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
+	 */
+	inline int addCredential(const RuntimeEnvironment *RR,const uint64_t now,const Capability &cap)
+	{
+		if (!cap.wasIssuedTo(RR->identity.address()))
+			return -1;
+		CState *c = _caps.get(cap.networkId());
+		if ((c)&&(c->lastReceived != 0)&&(c->cap == cap))
+			return 0;
+		const int vr = cap.verify(RR);
+		if (vr == 0) {
+			if (!c)
+				c = &(_caps[cap.networkId()]);
+			c->lastReceived = now;
+			c->cap = cap;
+		}
+		return vr;
+	}
+
 	/**
 	/**
 	 * Clean up old or stale entries
 	 * Clean up old or stale entries
+	 *
+	 * @return Time of most recent activity in this Membership
 	 */
 	 */
-	inline void clean(const uint64_t now)
+	inline uint64_t clean(const uint64_t now)
 	{
 	{
+		uint64_t lastAct = _lastPushedCom;
+
 		uint32_t *i = (uint32_t *)0;
 		uint32_t *i = (uint32_t *)0;
 		CState *cs = (CState *)0;
 		CState *cs = (CState *)0;
 		Hashtable<uint32_t,CState>::Iterator csi(_caps);
 		Hashtable<uint32_t,CState>::Iterator csi(_caps);
 		while (csi.next(i,cs)) {
 		while (csi.next(i,cs)) {
-			if ((now - std::max(cs->lastPushed,cs->lastReceived)) > (ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA * 3))
+			const uint64_t la = std::max(cs->lastPushed,cs->lastReceived);
+			if ((now - la) > ZT_MEMBERSHIP_STATE_EXPIRATION_TIME)
 				_caps.erase(*i);
 				_caps.erase(*i);
+			else if (la > lastAct)
+				lastAct = la;
 		}
 		}
 
 
 		i = (uint32_t *)0;
 		i = (uint32_t *)0;
 		TState *ts = (TState *)0;
 		TState *ts = (TState *)0;
 		Hashtable<uint32_t,TState>::Iterator tsi(_tags);
 		Hashtable<uint32_t,TState>::Iterator tsi(_tags);
 		while (tsi.next(i,ts)) {
 		while (tsi.next(i,ts)) {
-			if ((now - std::max(ts->lastPushed,ts->lastReceived)) > (ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA * 3))
+			const uint64_t la = std::max(ts->lastPushed,ts->lastReceived);
+			if ((now - la) > ZT_MEMBERSHIP_STATE_EXPIRATION_TIME)
 				_tags.erase(*i);
 				_tags.erase(*i);
+			else if (la > lastAct)
+				lastAct = la;
 		}
 		}
+
+		return lastAct;
 	}
 	}
 
 
 private:
 private:

+ 33 - 0
node/Peer.hpp

@@ -40,8 +40,10 @@
 #include "SharedPtr.hpp"
 #include "SharedPtr.hpp"
 #include "AtomicCounter.hpp"
 #include "AtomicCounter.hpp"
 #include "Hashtable.hpp"
 #include "Hashtable.hpp"
+#include "Membership.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "NonCopyable.hpp"
 #include "NonCopyable.hpp"
+#include "LockingPtr.hpp"
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -384,6 +386,34 @@ public:
 		return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT);
 		return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT);
 	}
 	}
 
 
+	/**
+	 * Get the membership record for this network, possibly creating if missing
+	 *
+	 * @param networkId Network ID
+	 * @param createIfMissing If true, create a Membership record if there isn't one
+	 * @return Single-scope locking pointer (see LockingPtr.hpp) to Membership or NULL if not found and createIfMissing is false
+	 */
+	inline LockingPtr<Membership> membership(const uint64_t networkId,bool createIfMissing)
+	{
+		_memberships_m.lock();
+		try {
+			if (createIfMissing) {
+				return LockingPtr<Membership>(&(_memberships[networkId]),&_memberships_m);
+			} else {
+				Membership *m = _memberships.get(networkId);
+				if (m) {
+					return LockingPtr<Membership>(m,&_memberships_m);
+				} else {
+					_memberships_m.unlock();
+					return LockingPtr<Membership>();
+				}
+			}
+		} catch ( ... ) {
+			_memberships_m.unlock();
+			throw;
+		}
+	}
+
 	/**
 	/**
 	 * Find a common set of addresses by which two peers can link, if any
 	 * Find a common set of addresses by which two peers can link, if any
 	 *
 	 *
@@ -430,6 +460,9 @@ private:
 	unsigned int _latency;
 	unsigned int _latency;
 	unsigned int _directPathPushCutoffCount;
 	unsigned int _directPathPushCutoffCount;
 
 
+	Hashtable<uint64_t,Membership> _memberships;
+	Mutex _memberships_m;
+
 	AtomicCounter __refCount;
 	AtomicCounter __refCount;
 };
 };
 
 

+ 45 - 0
node/Tag.cpp

@@ -0,0 +1,45 @@
+/*
+ * 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/>.
+ */
+
+#include "Tag.hpp"
+#include "RuntimeEnvironment.hpp"
+#include "Identity.hpp"
+#include "Topology.hpp"
+#include "Switch.hpp"
+
+namespace ZeroTier {
+
+int Tag::verify(const RuntimeEnvironment *RR) const
+{
+	if (!_signedBy)
+		return -1;
+	const Identity id(RR->topology->getIdentity(_signedBy));
+	if (!id) {
+		RR->sw->requestWhois(_signedBy);
+		return 1;
+	}
+	try {
+		Buffer<(sizeof(Tag) * 2)> tmp;
+		this->serialize(tmp,true);
+		return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1);
+	} catch ( ... ) {
+		return -1;
+	}
+}
+
+} // namespace ZeroTier

+ 8 - 3
node/Tag.hpp

@@ -76,7 +76,6 @@ public:
 	{
 	{
 	}
 	}
 
 
-
 	inline uint64_t networkId() const { return _nwid; }
 	inline uint64_t networkId() const { return _nwid; }
 	inline uint64_t expiration() const { return _expiration; }
 	inline uint64_t expiration() const { return _expiration; }
 	inline uint32_t id() const { return _id; }
 	inline uint32_t id() const { return _id; }
@@ -106,9 +105,9 @@ public:
 	 * Check this tag's signature
 	 * Check this tag's signature
 	 *
 	 *
 	 * @param RR Runtime environment to allow identity lookup for signedBy
 	 * @param RR Runtime environment to allow identity lookup for signedBy
-	 * @return True if signature is present and valid
+	 * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or tag
 	 */
 	 */
-	bool verify(const RuntimeEnvironment *RR);
+	int verify(const RuntimeEnvironment *RR) const;
 
 
 	template<unsigned int C>
 	template<unsigned int C>
 	inline void serialize(Buffer<C> &b,const bool forSign = false) const
 	inline void serialize(Buffer<C> &b,const bool forSign = false) const
@@ -156,6 +155,12 @@ public:
 		return (p - startAt);
 		return (p - startAt);
 	}
 	}
 
 
+	// Provides natural sort order by ID
+	inline bool operator<(const Tag &t) const { return (_id < t._id); }
+
+	inline bool operator==(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) == 0); }
+	inline bool operator!=(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) != 0); }
+
 private:
 private:
 	uint64_t _nwid;
 	uint64_t _nwid;
 	uint64_t _expiration;
 	uint64_t _expiration;

+ 3 - 1
node/Topology.cpp

@@ -169,7 +169,9 @@ SharedPtr<Peer> Topology::getPeer(const Address &zta)
 
 
 Identity Topology::getIdentity(const Address &zta)
 Identity Topology::getIdentity(const Address &zta)
 {
 {
-	{
+	if (zta == RR->identity.address()) {
+		return RR->identity;
+	} else {
 		Mutex::Lock _l(_lock);
 		Mutex::Lock _l(_lock);
 		const SharedPtr<Peer> *const ap = _peers.get(zta);
 		const SharedPtr<Peer> *const ap = _peers.get(zta);
 		if (ap)
 		if (ap)

+ 3 - 0
objects.mk

@@ -1,5 +1,6 @@
 OBJS=\
 OBJS=\
 	node/C25519.o \
 	node/C25519.o \
+	node/Capability.o \
 	node/CertificateOfMembership.o \
 	node/CertificateOfMembership.o \
 	node/Cluster.o \
 	node/Cluster.o \
 	node/DeferredPackets.o \
 	node/DeferredPackets.o \
@@ -7,6 +8,7 @@ OBJS=\
 	node/Identity.o \
 	node/Identity.o \
 	node/IncomingPacket.o \
 	node/IncomingPacket.o \
 	node/InetAddress.o \
 	node/InetAddress.o \
+	node/Membership.o \
 	node/Multicaster.o \
 	node/Multicaster.o \
 	node/Network.o \
 	node/Network.o \
 	node/NetworkConfig.o \
 	node/NetworkConfig.o \
@@ -20,6 +22,7 @@ OBJS=\
 	node/SelfAwareness.o \
 	node/SelfAwareness.o \
 	node/SHA512.o \
 	node/SHA512.o \
 	node/Switch.o \
 	node/Switch.o \
+	node/Tag.o \
 	node/Topology.o \
 	node/Topology.o \
 	node/Utils.o \
 	node/Utils.o \
 	osdep/BackgroundResolver.o \
 	osdep/BackgroundResolver.o \