From f057bb63cdc4bebc4608f4f2ed6da4656ddbc8a9 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 4 Aug 2016 09:02:35 -0700 Subject: [PATCH] More work on tags and capabilities. --- node/Capability.cpp | 52 +++++++++++++++++ node/Capability.hpp | 38 +++++++----- node/CertificateOfMembership.cpp | 33 +++++------ node/CertificateOfMembership.hpp | 12 ++-- node/IncomingPacket.cpp | 27 ++++++--- node/LockingPtr.hpp | 99 ++++++++++++++++++++++++++++++++ node/Membership.hpp | 94 ++++++++++++++++++++++++++++-- node/Peer.hpp | 33 +++++++++++ node/Tag.cpp | 45 +++++++++++++++ node/Tag.hpp | 11 +++- node/Topology.cpp | 4 +- objects.mk | 3 + 12 files changed, 397 insertions(+), 54 deletions(-) create mode 100644 node/Capability.cpp create mode 100644 node/LockingPtr.hpp create mode 100644 node/Tag.cpp diff --git a/node/Capability.cpp b/node/Capability.cpp new file mode 100644 index 000000000..07eb41a97 --- /dev/null +++ b/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 . + */ + +#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;ctopology->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 diff --git a/node/Capability.hpp b/node/Capability.hpp index d050b2b8c..482827083 100644 --- a/node/Capability.hpp +++ b/node/Capability.hpp @@ -130,11 +130,11 @@ public: inline bool sign(const Identity &from,const Address &to) { try { - Buffer<(sizeof(Capability) * 2)> tmp; for(unsigned int i=0;((i<_maxCustodyChainLength)&&(i tmp; this->serialize(tmp,true); _custody[i].signature = from.sign(tmp.data(),tmp.size()); 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 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 */ - int verify(const RuntimeEnvironment *RR,const Address &verifyInChain) const; + int verify(const RuntimeEnvironment *RR) const; template static inline void serializeRules(Buffer &b,const ZT_VirtualNetworkRule *rules,unsigned int ruleCount) @@ -403,9 +393,31 @@ public: 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_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; for(unsigned int i=0;i<_qualifierCount;++i) { buf[ptr++] = Utils::hton(_qualifiers[i].id); buf[ptr++] = Utils::hton(_qualifiers[i].value); 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 diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index 8fae8b08e..a04f82558 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -46,10 +46,12 @@ /** * 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 { +class RuntimeEnvironment; + /** * Certificate of network membership * @@ -275,12 +277,12 @@ public: 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 diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 352e4faa8..6548bda66 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -443,11 +443,11 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr &p 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; offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS); - peer->validateAndSetNetworkMembershipCertificate(nwid,com); + LockingPtr m = peer->membership(com.networkId(),true); + if (m) m->addCredential(RR,RR->node->now(),com); } if ((flags & 0x02) != 0) { @@ -583,10 +583,11 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr

validateAndSetNetworkMembershipCertificate(network->id(),com); + LockingPtr m = peer->membership(com.networkId(),true); + if (m) m->addCredential(RR,RR->node->now(),com); } 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) { try { + const uint64_t now = RR->node->now(); CertificateOfMembership com; Capability cap; Tag tag; @@ -705,7 +707,9 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S unsigned int p = ZT_PACKET_IDX_PAYLOAD; while ((p < size())&&((*this)[p])) { p += com.deserialize(*this,p); - peer->validateAndSetNetworkMembershipCertificate(com.networkId(),com); + LockingPtr 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 @@ -713,10 +717,16 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S const unsigned int numCapabilities = at(p); p += 2; for(unsigned int i=0;i m = peer->membership(cap.networkId(),true); + if (!m) return true; // sanity check + m->addCredential(RR,now,cap); } const unsigned int numTags = at(p); p += 2; for(unsigned int i=0;i 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 unsigned int offset = 0; - if ((flags & 0x01) != 0) { + if ((flags & 0x01) != 0) { // deprecated but still used by older peers CertificateOfMembership com; offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM); - peer->validateAndSetNetworkMembershipCertificate(nwid,com); + LockingPtr 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 diff --git a/node/LockingPtr.hpp b/node/LockingPtr.hpp new file mode 100644 index 000000000..c373129ad --- /dev/null +++ b/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 . + */ + +#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 +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(&p)->_lock = (Mutex *)0; + } + + ~LockingPtr() + { + if (_lock) + _lock->unlock(); + } + + inline LockingPtr &operator=(const LockingPtr &p) + { + _ptr = p._ptr; + _lock = p._lock; + const_cast(&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 diff --git a/node/Membership.hpp b/node/Membership.hpp index 93d347e73..642d46c68 100644 --- a/node/Membership.hpp +++ b/node/Membership.hpp @@ -32,9 +32,16 @@ #include "Hashtable.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 { class Peer; +class RuntimeEnvironment; /** * 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 * 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 nconf Network configuration - * @param now Current time * @param capIds Capability IDs that this peer might need * @param capCount Number of capability IDs * @param tagIds Tag IDs that this peer might need * @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 @@ -114,25 +123,98 @@ public: } /** - * Clean up old or stale entries + * 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 void clean(const uint64_t now) + 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 + * + * @return Time of most recent activity in this Membership + */ + inline uint64_t clean(const uint64_t now) + { + uint64_t lastAct = _lastPushedCom; + uint32_t *i = (uint32_t *)0; CState *cs = (CState *)0; Hashtable::Iterator csi(_caps); 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); + else if (la > lastAct) + lastAct = la; } i = (uint32_t *)0; TState *ts = (TState *)0; Hashtable::Iterator tsi(_tags); 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); + else if (la > lastAct) + lastAct = la; } + + return lastAct; } private: diff --git a/node/Peer.hpp b/node/Peer.hpp index d8c44ebe4..8b50f4292 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -40,8 +40,10 @@ #include "SharedPtr.hpp" #include "AtomicCounter.hpp" #include "Hashtable.hpp" +#include "Membership.hpp" #include "Mutex.hpp" #include "NonCopyable.hpp" +#include "LockingPtr.hpp" namespace ZeroTier { @@ -384,6 +386,34 @@ public: 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(const uint64_t networkId,bool createIfMissing) + { + _memberships_m.lock(); + try { + if (createIfMissing) { + return LockingPtr(&(_memberships[networkId]),&_memberships_m); + } else { + Membership *m = _memberships.get(networkId); + if (m) { + return LockingPtr(m,&_memberships_m); + } else { + _memberships_m.unlock(); + return LockingPtr(); + } + } + } catch ( ... ) { + _memberships_m.unlock(); + throw; + } + } + /** * Find a common set of addresses by which two peers can link, if any * @@ -430,6 +460,9 @@ private: unsigned int _latency; unsigned int _directPathPushCutoffCount; + Hashtable _memberships; + Mutex _memberships_m; + AtomicCounter __refCount; }; diff --git a/node/Tag.cpp b/node/Tag.cpp new file mode 100644 index 000000000..1ad17251c --- /dev/null +++ b/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 . + */ + +#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 diff --git a/node/Tag.hpp b/node/Tag.hpp index a4bc4479c..dcf2eb200 100644 --- a/node/Tag.hpp +++ b/node/Tag.hpp @@ -76,7 +76,6 @@ public: { } - inline uint64_t networkId() const { return _nwid; } inline uint64_t expiration() const { return _expiration; } inline uint32_t id() const { return _id; } @@ -106,9 +105,9 @@ public: * Check this tag's signature * * @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 inline void serialize(Buffer &b,const bool forSign = false) const @@ -156,6 +155,12 @@ public: 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: uint64_t _nwid; uint64_t _expiration; diff --git a/node/Topology.cpp b/node/Topology.cpp index 725eed319..ef1c16983 100644 --- a/node/Topology.cpp +++ b/node/Topology.cpp @@ -169,7 +169,9 @@ SharedPtr Topology::getPeer(const Address &zta) Identity Topology::getIdentity(const Address &zta) { - { + if (zta == RR->identity.address()) { + return RR->identity; + } else { Mutex::Lock _l(_lock); const SharedPtr *const ap = _peers.get(zta); if (ap) diff --git a/objects.mk b/objects.mk index da4fda1c6..18e330b3c 100644 --- a/objects.mk +++ b/objects.mk @@ -1,5 +1,6 @@ OBJS=\ node/C25519.o \ + node/Capability.o \ node/CertificateOfMembership.o \ node/Cluster.o \ node/DeferredPackets.o \ @@ -7,6 +8,7 @@ OBJS=\ node/Identity.o \ node/IncomingPacket.o \ node/InetAddress.o \ + node/Membership.o \ node/Multicaster.o \ node/Network.o \ node/NetworkConfig.o \ @@ -20,6 +22,7 @@ OBJS=\ node/SelfAwareness.o \ node/SHA512.o \ node/Switch.o \ + node/Tag.o \ node/Topology.o \ node/Utils.o \ osdep/BackgroundResolver.o \