More work on tags and capabilities.

This commit is contained in:
Adam Ierymenko 2016-08-04 09:02:35 -07:00
parent 7e6e56e2bc
commit f057bb63cd
12 changed files with 397 additions and 54 deletions

52
node/Capability.cpp Normal file
View File

@ -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

View File

@ -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<ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH));++i) {
if (!(_custody[i].to)) {
_custody[i].to = to;
_custody[i].from = from.address();
Buffer<(sizeof(Capability) * 2)> 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<unsigned int C>
static inline void serializeRules(Buffer<C> &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_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
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:
uint64_t _nwid;
uint64_t _expiration;

View File

@ -17,6 +17,9 @@
*/
#include "CertificateOfMembership.hpp"
#include "RuntimeEnvironment.hpp"
#include "Topology.hpp"
#include "Switch.hpp"
namespace ZeroTier {
@ -182,7 +185,7 @@ bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) c
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;
for(unsigned int i=0;i<_qualifierCount;++i) {
buf[ptr++] = Utils::hton(_qualifiers[i].id);
@ -193,38 +196,32 @@ bool CertificateOfMembership::sign(const Identity &with)
try {
_signature = with.sign(buf,ptr * sizeof(uint64_t));
_signedBy = with.address();
delete [] buf;
return true;
} catch ( ... ) {
_signedBy.zero();
delete [] buf;
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;
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

View File

@ -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

View File

@ -443,11 +443,11 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &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<Membership> 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<P
const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS];
unsigned int comLen = 0;
if ((flags & 0x01) != 0) {
if ((flags & 0x01) != 0) { // deprecated but still used by old peers
CertificateOfMembership 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)) {
@ -698,6 +699,7 @@ bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,const Shared
bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &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<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
@ -713,10 +717,16 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
const unsigned int numCapabilities = at<uint16_t>(p); p += 2;
for(unsigned int i=0;i<numCapabilities;++i) {
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;
for(unsigned int i=0;i<numTags;++i) {
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
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<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

99
node/LockingPtr.hpp Normal file
View File

@ -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

View File

@ -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<uint32_t,CState>::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<uint32_t,TState>::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:

View File

@ -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> 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
*
@ -430,6 +460,9 @@ private:
unsigned int _latency;
unsigned int _directPathPushCutoffCount;
Hashtable<uint64_t,Membership> _memberships;
Mutex _memberships_m;
AtomicCounter __refCount;
};

45
node/Tag.cpp Normal file
View File

@ -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

View File

@ -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<unsigned int C>
inline void serialize(Buffer<C> &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;

View File

@ -169,7 +169,9 @@ SharedPtr<Peer> 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<Peer> *const ap = _peers.get(zta);
if (ap)

View File

@ -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 \