Revocation work in progress, add WATCH which is TEE with implicit rate sync (thanks JG@DCVC!), and clean up some cruft in Network.

This commit is contained in:
Adam Ierymenko 2016-09-23 16:08:38 -07:00
parent 29711e123f
commit 1f74dd4589
14 changed files with 809 additions and 597 deletions

View File

@ -140,6 +140,12 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
r["flags"] = (unsigned int)rule.v.fwd.flags; r["flags"] = (unsigned int)rule.v.fwd.flags;
r["length"] = (unsigned int)rule.v.fwd.length; r["length"] = (unsigned int)rule.v.fwd.length;
break; break;
case ZT_NETWORK_RULE_ACTION_WATCH:
r["type"] = "ACTION_WATCH";
r["address"] = Address(rule.v.fwd.address).toString();
r["flags"] = (unsigned int)rule.v.fwd.flags;
r["length"] = (unsigned int)rule.v.fwd.length;
break;
case ZT_NETWORK_RULE_ACTION_REDIRECT: case ZT_NETWORK_RULE_ACTION_REDIRECT:
r["type"] = "ACTION_REDIRECT"; r["type"] = "ACTION_REDIRECT";
r["address"] = Address(rule.v.fwd.address).toString(); r["address"] = Address(rule.v.fwd.address).toString();
@ -303,6 +309,12 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
rule.v.fwd.flags = (uint32_t)(_jI(r["flags"],0ULL) & 0xffffffffULL); rule.v.fwd.flags = (uint32_t)(_jI(r["flags"],0ULL) & 0xffffffffULL);
rule.v.fwd.length = (uint16_t)(_jI(r["length"],0ULL) & 0xffffULL); rule.v.fwd.length = (uint16_t)(_jI(r["length"],0ULL) & 0xffffULL);
return true; return true;
} else if (t == "ACTION_WATCH") {
rule.t |= ZT_NETWORK_RULE_ACTION_WATCH;
rule.v.fwd.address = Utils::hexStrToU64(_jS(r["address"],"0").c_str()) & 0xffffffffffULL;
rule.v.fwd.flags = (uint32_t)(_jI(r["flags"],0ULL) & 0xffffffffULL);
rule.v.fwd.length = (uint16_t)(_jI(r["length"],0ULL) & 0xffffULL);
return true;
} else if (t == "ACTION_REDIRECT") { } else if (t == "ACTION_REDIRECT") {
rule.t |= ZT_NETWORK_RULE_ACTION_REDIRECT; rule.t |= ZT_NETWORK_RULE_ACTION_REDIRECT;
rule.v.fwd.address = Utils::hexStrToU64(_jS(r["zt"],"0").c_str()) & 0xffffffffffULL; rule.v.fwd.address = Utils::hexStrToU64(_jS(r["zt"],"0").c_str()) & 0xffffffffffULL;

View File

@ -516,15 +516,20 @@ enum ZT_VirtualNetworkRuleType
*/ */
ZT_NETWORK_RULE_ACTION_TEE = 2, ZT_NETWORK_RULE_ACTION_TEE = 2,
/**
* Exactly like TEE but frames are dropped if previous TEEs were not acknowledged by the observer
*/
ZT_NETWORK_RULE_ACTION_WATCH = 3,
/** /**
* Drop and redirect this frame to another node (by ZT address) * Drop and redirect this frame to another node (by ZT address)
*/ */
ZT_NETWORK_RULE_ACTION_REDIRECT = 3, ZT_NETWORK_RULE_ACTION_REDIRECT = 4,
/** /**
* Log if match and if rule debugging is enabled in the build, otherwise does nothing (for developers) * Log if match and if rule debugging is enabled in the build, otherwise does nothing (for developers)
*/ */
ZT_NETWORK_RULE_ACTION_DEBUG_LOG = 4, ZT_NETWORK_RULE_ACTION_DEBUG_LOG = 5,
/** /**
* Maximum ID for an ACTION, anything higher is a MATCH * Maximum ID for an ACTION, anything higher is a MATCH

View File

@ -174,6 +174,7 @@ public:
b.append((uint8_t)0); b.append((uint8_t)0);
break; break;
case ZT_NETWORK_RULE_ACTION_TEE: case ZT_NETWORK_RULE_ACTION_TEE:
case ZT_NETWORK_RULE_ACTION_WATCH:
case ZT_NETWORK_RULE_ACTION_REDIRECT: case ZT_NETWORK_RULE_ACTION_REDIRECT:
b.append((uint8_t)14); b.append((uint8_t)14);
b.append((uint64_t)rules[i].v.fwd.address); b.append((uint64_t)rules[i].v.fwd.address);
@ -270,6 +271,7 @@ public:
default: default:
break; break;
case ZT_NETWORK_RULE_ACTION_TEE: case ZT_NETWORK_RULE_ACTION_TEE:
case ZT_NETWORK_RULE_ACTION_WATCH:
case ZT_NETWORK_RULE_ACTION_REDIRECT: case ZT_NETWORK_RULE_ACTION_REDIRECT:
rules[ruleCount].v.fwd.address = b.template at<uint64_t>(p); rules[ruleCount].v.fwd.address = b.template at<uint64_t>(p);
rules[ruleCount].v.fwd.flags = b.template at<uint32_t>(p + 8); rules[ruleCount].v.fwd.flags = b.template at<uint32_t>(p + 8);

View File

@ -39,6 +39,7 @@
#include "CertificateOfMembership.hpp" #include "CertificateOfMembership.hpp"
#include "Capability.hpp" #include "Capability.hpp"
#include "Tag.hpp" #include "Tag.hpp"
#include "Revocation.hpp"
namespace ZeroTier { namespace ZeroTier {
@ -162,13 +163,8 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
// Peers can send this in response to frames if they do not have a recent enough COM from us // 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))); SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
const uint64_t now = RR->node->now(); const uint64_t now = RR->node->now();
if ( (network) && (network->config().com) && (peer->rateGateComRequest(now)) ) { if ( (network) && (network->config().com) && (peer->rateGateComRequest(now)) )
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); network->pushCredentialsNow(peer->address(),now);
network->config().com.serialize(outp);
outp.append((uint8_t)0);
outp.armor(peer->key(),true);
_path->send(RR,outp.data(),outp.size(),now);
}
} break; } break;
case Packet::ERROR_NETWORK_ACCESS_DENIED_: { case Packet::ERROR_NETWORK_ACCESS_DENIED_: {
@ -681,9 +677,17 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,const SharedPtr<P
RR->node->putFrame(nwid,network->userPtr(),from,to,etherType,0,(const void *)frameData,frameLen); RR->node->putFrame(nwid,network->userPtr(),from,to,etherType,0,(const void *)frameData,frameLen);
break; break;
} }
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true);
} }
if ((flags & 0x10) != 0) {
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK);
outp.append((uint8_t)Packet::VERB_EXT_FRAME);
outp.append((uint64_t)packetId());
outp.armor(peer->key(),true);
_path->send(RR,outp.data(),outp.size(),RR->node->now());
}
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true);
} else { } else {
TRACE("dropped EXT_FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_path->address().toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID)); TRACE("dropped EXT_FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_path->address().toString().c_str(),at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID));
peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false); peer->received(_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false);
@ -775,6 +779,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
CertificateOfMembership com; CertificateOfMembership com;
Capability cap; Capability cap;
Tag tag; Tag tag;
Revocation revocation;
bool trustEstablished = false; bool trustEstablished = false;
unsigned int p = ZT_PACKET_IDX_PAYLOAD; unsigned int p = ZT_PACKET_IDX_PAYLOAD;
@ -784,8 +789,14 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
SharedPtr<Network> network(RR->node->network(com.networkId())); SharedPtr<Network> network(RR->node->network(com.networkId()));
if (network) { if (network) {
switch (network->addCredential(com)) { switch (network->addCredential(com)) {
case 0: trustEstablished = true; break; case Membership::ADD_REJECTED:
case 1: return false; // wait for WHOIS break;
case Membership::ADD_ACCEPTED_NEW:
case Membership::ADD_ACCEPTED_REDUNDANT:
trustEstablished = true;
break;
case Membership::ADD_DEFERRED_FOR_WHOIS:
return false;
} }
} else RR->mc->addCredential(com,false); } else RR->mc->addCredential(com,false);
} }
@ -799,8 +810,14 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
SharedPtr<Network> network(RR->node->network(cap.networkId())); SharedPtr<Network> network(RR->node->network(cap.networkId()));
if (network) { if (network) {
switch (network->addCredential(cap)) { switch (network->addCredential(cap)) {
case 0: trustEstablished = true; break; case Membership::ADD_REJECTED:
case 1: return false; // wait for WHOIS break;
case Membership::ADD_ACCEPTED_NEW:
case Membership::ADD_ACCEPTED_REDUNDANT:
trustEstablished = true;
break;
case Membership::ADD_DEFERRED_FOR_WHOIS:
return false;
} }
} }
} }
@ -811,11 +828,25 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
SharedPtr<Network> network(RR->node->network(tag.networkId())); SharedPtr<Network> network(RR->node->network(tag.networkId()));
if (network) { if (network) {
switch (network->addCredential(tag)) { switch (network->addCredential(tag)) {
case 0: trustEstablished = true; break; case Membership::ADD_REJECTED:
case 1: return false; // wait for WHOIS break;
case Membership::ADD_ACCEPTED_NEW:
case Membership::ADD_ACCEPTED_REDUNDANT:
trustEstablished = true;
break;
case Membership::ADD_DEFERRED_FOR_WHOIS:
return false;
} }
} }
} }
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()));
if (network) {
}
}
} }
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished); peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished);
@ -932,24 +963,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,cons
const uint64_t nwid = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD); const uint64_t nwid = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);
bool trustEstablished = false; bool trustEstablished = false;
if (Network::controllerFor(nwid) == peer->address()) {
SharedPtr<Network> network(RR->node->network(nwid));
if (network) {
network->requestConfiguration();
trustEstablished = true;
} else {
TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): not a member of %.16llx",source().toString().c_str(),_path->address().toString().c_str(),nwid);
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,false);
return true;
}
const unsigned int blacklistCount = at<uint16_t>(ZT_PACKET_IDX_PAYLOAD + 8);
unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 10;
for(unsigned int i=0;i<blacklistCount;++i) {
network->blacklistBefore(Address(field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH),at<uint64_t>(ptr + 5));
ptr += 13;
}
}
peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,trustEstablished); peer->received(_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG_REFRESH,0,Packet::VERB_NOP,trustEstablished);
} catch ( ... ) { } catch ( ... ) {

View File

@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <algorithm>
#include "Membership.hpp" #include "Membership.hpp"
#include "RuntimeEnvironment.hpp" #include "RuntimeEnvironment.hpp"
#include "Peer.hpp" #include "Peer.hpp"
@ -28,28 +30,43 @@
namespace ZeroTier { namespace ZeroTier {
void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,const Capability *cap) Membership::Membership() :
_lastUpdatedMulticast(0),
_lastPushAttempt(0),
_lastPushedCom(0),
_comRevocationThreshold(0)
{ {
if ((now - _lastPushAttempt) < 2000ULL) for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) _remoteTags[i] = &(_tagMem[i]);
for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) _remoteCaps[i] = &(_capMem[i]);
}
void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force)
{
// This limits how often we go through this logic, which prevents us from
// doing all this for every single packet or other event.
if ( ((now - _lastPushAttempt) < 1000ULL) && (!force) )
return; return;
_lastPushAttempt = now; _lastPushAttempt = now;
try { try {
bool unfinished; unsigned int localTagPtr = 0;
bool needCom = ( (nconf.com) && ( ((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) );
do { do {
unfinished = false;
Buffer<ZT_PROTO_MAX_PACKET_LENGTH> capsAndTags; Buffer<ZT_PROTO_MAX_PACKET_LENGTH> capsAndTags;
unsigned int appendedCaps = 0; unsigned int appendedCaps = 0;
if (cap) { if (localCapabilityIndex >= 0) {
capsAndTags.addSize(2); capsAndTags.addSize(2);
std::map<uint32_t,CState>::iterator cs(_caps.find(cap->id()));
if ((cs != _caps.end())&&((now - cs->second.lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY)) { if ( (_localCaps[localCapabilityIndex].id != nconf.capabilities[localCapabilityIndex].id()) || ((now - _localCaps[localCapabilityIndex].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) {
cap->serialize(capsAndTags); _localCaps[localCapabilityIndex].lastPushed = now;
cs->second.lastPushed = now; _localCaps[localCapabilityIndex].id = nconf.capabilities[localCapabilityIndex].id();
nconf.capabilities[localCapabilityIndex].serialize(capsAndTags);
++appendedCaps; ++appendedCaps;
} }
capsAndTags.setAt<uint16_t>(0,(uint16_t)appendedCaps); capsAndTags.setAt<uint16_t>(0,(uint16_t)appendedCaps);
localCapabilityIndex = -1; // don't send this cap again on subsequent loops if force is true
} else { } else {
capsAndTags.append((uint16_t)0); capsAndTags.append((uint16_t)0);
} }
@ -57,22 +74,17 @@ void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
unsigned int appendedTags = 0; unsigned int appendedTags = 0;
const unsigned int tagCountPos = capsAndTags.size(); const unsigned int tagCountPos = capsAndTags.size();
capsAndTags.addSize(2); capsAndTags.addSize(2);
for(unsigned int i=0;i<nconf.tagCount;++i) { for(;localTagPtr<nconf.tagCount;++localTagPtr) {
TState *const ts = _tags.get(nconf.tags[i].id()); if ( (_localTags[localTagPtr].id != nconf.tags[localTagPtr].id()) || ((now - _localTags[localTagPtr].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) {
if ((now - ts->lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) { if ((capsAndTags.size() + sizeof(Tag)) >= (ZT_PROTO_MAX_PACKET_LENGTH - sizeof(CertificateOfMembership)))
if ((capsAndTags.size() + sizeof(Tag)) >= (ZT_PROTO_MAX_PACKET_LENGTH - sizeof(CertificateOfMembership))) {
unfinished = true;
break; break;
} nconf.tags[localTagPtr].serialize(capsAndTags);
nconf.tags[i].serialize(capsAndTags);
ts->lastPushed = now;
++appendedTags; ++appendedTags;
} }
} }
capsAndTags.setAt<uint16_t>(tagCountPos,(uint16_t)appendedTags); capsAndTags.setAt<uint16_t>(tagCountPos,(uint16_t)appendedTags);
const bool needCom = ((nconf.com)&&((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY)); if (needCom||appendedCaps||appendedTags) {
if ( (needCom) || (appendedCaps) || (appendedTags) ) {
Packet outp(peerAddress,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); Packet outp(peerAddress,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
if (needCom) { if (needCom) {
nconf.com.serialize(outp); nconf.com.serialize(outp);
@ -80,110 +92,148 @@ void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
} }
outp.append((uint8_t)0x00); outp.append((uint8_t)0x00);
outp.append(capsAndTags.data(),capsAndTags.size()); outp.append(capsAndTags.data(),capsAndTags.size());
outp.append((uint16_t)0); // no revocations, these propagate differently
outp.compress(); outp.compress();
RR->sw->send(outp,true); RR->sw->send(outp,true);
needCom = false; // don't send COM again on subsequent loops if force is true
} }
} while (unfinished); // if there are many tags, etc., we can send more than one } while (localTagPtr < nconf.tagCount);
} catch ( ... ) { } catch ( ... ) {
TRACE("unable to send credentials due to unexpected exception"); TRACE("unable to send credentials due to unexpected exception");
} }
} }
int Membership::addCredential(const RuntimeEnvironment *RR,const CertificateOfMembership &com) const Capability *Membership::getCapability(const NetworkConfig &nconf,const uint32_t id) const
{ {
if (_com == com) { const _RemoteCapability *const *c = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)id,_RemoteCredentialSorter<_RemoteCapability>());
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (redundant)",com.issuedTo().toString().c_str(),com.networkId()); return ( ((c != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*c)->id == (uint64_t)id)) ? ((((*c)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*c)->cap,**c))) ? &((*c)->cap) : (const Capability *)0) : (const Capability *)0);
return 0; }
const Tag *Membership::getTag(const NetworkConfig &nconf,const uint32_t id) const
{
const _RemoteTag *const *t = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)id,_RemoteCredentialSorter<_RemoteTag>());
return ( ((t != &(_remoteTags[ZT_MAX_NETWORK_CAPABILITIES]))&&((*t)->id == (uint64_t)id)) ? ((((*t)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*t)->tag,**t))) ? &((*t)->tag) : (const Tag *)0) : (const Tag *)0);
}
Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfMembership &com)
{
const uint64_t newts = com.timestamp().first;
if (newts <= _comRevocationThreshold) {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (revoked)",com.issuedTo().toString().c_str(),com.networkId());
return ADD_REJECTED;
} }
const int vr = com.verify(RR); const uint64_t oldts = _com.timestamp().first;
if (newts < oldts) {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (older than current)",com.issuedTo().toString().c_str(),com.networkId());
return ADD_REJECTED;
}
if ((newts == oldts)&&(_com == com)) {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (redundant)",com.issuedTo().toString().c_str(),com.networkId());
return ADD_ACCEPTED_REDUNDANT;
}
if (vr == 0) { switch(com.verify(RR)) {
if (com.timestamp().first >= _com.timestamp().first) { default:
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (invalid signature or object)",com.issuedTo().toString().c_str(),com.networkId());
return ADD_REJECTED;
case 0:
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (new)",com.issuedTo().toString().c_str(),com.networkId()); TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (new)",com.issuedTo().toString().c_str(),com.networkId());
_com = com; _com = com;
} else { return ADD_ACCEPTED_NEW;
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED but not used (OK but older than current)",com.issuedTo().toString().c_str(),com.networkId()); case 1:
} return ADD_DEFERRED_FOR_WHOIS;
} else {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (%d)",com.issuedTo().toString().c_str(),com.networkId(),vr);
} }
return vr;
} }
int Membership::addCredential(const RuntimeEnvironment *RR,const Tag &tag) Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Tag &tag)
{ {
TState *t = _tags.get(tag.id()); _RemoteTag *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialSorter<_RemoteTag>());
if ((t)&&(t->lastReceived != 0)&&(t->tag == tag)) { _RemoteTag *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteTag *)0;
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId()); if (have) {
return 0; if ( (!_isCredentialTimestampValid(nconf,tag,*have)) || (have->tag.timestamp() > tag.timestamp()) ) {
TRACE("addCredential(Tag) for %s on %.16llx REJECTED (revoked or too old)",tag.issuedTo().toString().c_str(),tag.networkId());
return ADD_REJECTED;
}
if (have->tag == tag) {
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId());
return ADD_ACCEPTED_REDUNDANT;
}
} }
const int vr = tag.verify(RR);
if (vr == 0) { switch(tag.verify(RR)) {
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId()); default:
if (!t) { TRACE("addCredential(Tag) for %s on %.16llx REJECTED (invalid)",tag.issuedTo().toString().c_str(),tag.networkId());
while (_tags.size() >= ZT_MAX_NETWORK_TAGS) { return ADD_REJECTED;
uint32_t oldest = 0; case 0:
uint64_t oldestLastReceived = 0xffffffffffffffffULL; TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
uint32_t *i = (uint32_t *)0; if (have) {
TState *ts = (TState *)0; have->lastReceived = RR->node->now();
Hashtable<uint32_t,TState>::Iterator tsi(_tags); have->tag = tag;
while (tsi.next(i,ts)) { } else {
if (ts->lastReceived < oldestLastReceived) { uint64_t minlr = 0xffffffffffffffffULL;
oldestLastReceived = ts->lastReceived; for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) {
oldest = *i; if (_remoteTags[i]->id == 0xffffffffffffffffULL) {
have = _remoteTags[i];
break;
} else if (_remoteTags[i]->lastReceived <= minlr) {
have = _remoteTags[i];
minlr = _remoteTags[i]->lastReceived;
} }
} }
if (oldestLastReceived != 0xffffffffffffffffULL) have->lastReceived = RR->node->now();
_tags.erase(oldest); have->tag = tag;
std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialSorter<_RemoteTag>());
} }
t = &(_tags[tag.id()]); return ADD_ACCEPTED_NEW;
} case 1:
if (t->tag.timestamp() <= tag.timestamp()) { return ADD_DEFERRED_FOR_WHOIS;
t->lastReceived = RR->node->now();
t->tag = tag;
}
} else {
TRACE("addCredential(Tag) for %s on %.16llx REJECTED (%d)",tag.issuedTo().toString().c_str(),tag.networkId(),vr);
} }
return vr;
} }
int Membership::addCredential(const RuntimeEnvironment *RR,const Capability &cap) Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Capability &cap)
{ {
std::map<uint32_t,CState>::iterator c(_caps.find(cap.id())); _RemoteCapability *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialSorter<_RemoteCapability>());
if ((c != _caps.end())&&(c->second.lastReceived != 0)&&(c->second.cap == cap)) { _RemoteCapability *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCapability *)0;
TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (redundant)",cap.issuedTo().toString().c_str(),cap.networkId()); if (have) {
return 0; if ( (!_isCredentialTimestampValid(nconf,cap,*have)) || (have->cap.timestamp() > cap.timestamp()) ) {
TRACE("addCredential(Tag) for %s on %.16llx REJECTED (revoked or too old)",tag.issuedTo().toString().c_str(),tag.networkId());
return ADD_REJECTED;
}
if (have->cap == cap) {
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId());
return ADD_ACCEPTED_REDUNDANT;
}
} }
const int vr = cap.verify(RR);
if (vr == 0) { switch(cap.verify(RR)) {
TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (new)",cap.issuedTo().toString().c_str(),cap.networkId()); default:
if (c == _caps.end()) { TRACE("addCredential(Tag) for %s on %.16llx REJECTED (invalid)",tag.issuedTo().toString().c_str(),tag.networkId());
while (_caps.size() >= ZT_MAX_NETWORK_CAPABILITIES) { return ADD_REJECTED;
std::map<uint32_t,CState>::iterator oldest; case 0:
uint64_t oldestLastReceived = 0xffffffffffffffffULL; TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
for(std::map<uint32_t,CState>::iterator i(_caps.begin());i!=_caps.end();++i) { if (have) {
if (i->second.lastReceived < oldestLastReceived) { have->lastReceived = RR->node->now();
oldestLastReceived = i->second.lastReceived; have->cap = cap;
oldest = i; } 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;
} }
} }
if (oldestLastReceived != 0xffffffffffffffffULL) have->lastReceived = RR->node->now();
_caps.erase(oldest); have->cap = cap;
std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialSorter<_RemoteCapability>());
} }
CState &c2 = _caps[cap.id()]; return ADD_ACCEPTED_NEW;
c2.lastReceived = RR->node->now(); case 1:
c2.cap = cap; return ADD_DEFERRED_FOR_WHOIS;
} else if (c->second.cap.timestamp() <= cap.timestamp()) {
c->second.lastReceived = RR->node->now();
c->second.cap = cap;
}
} else {
TRACE("addCredential(Capability) for %s on %.16llx REJECTED (%d)",cap.issuedTo().toString().c_str(),cap.networkId(),vr);
} }
return vr;
} }
} // namespace ZeroTier } // namespace ZeroTier

View File

@ -21,14 +21,12 @@
#include <stdint.h> #include <stdint.h>
#include <map>
#include "Constants.hpp" #include "Constants.hpp"
#include "../include/ZeroTierOne.h" #include "../include/ZeroTierOne.h"
#include "CertificateOfMembership.hpp" #include "CertificateOfMembership.hpp"
#include "Capability.hpp" #include "Capability.hpp"
#include "Tag.hpp" #include "Tag.hpp"
#include "Hashtable.hpp" #include "Revocation.hpp"
#include "NetworkConfig.hpp" #include "NetworkConfig.hpp"
namespace ZeroTier { namespace ZeroTier {
@ -40,77 +38,135 @@ class Network;
* A container for certificates of membership and other network credentials * A container for certificates of membership and other network credentials
* *
* This is kind of analogous to a join table between Peer and Network. It is * This is kind of analogous to a join table between Peer and Network. It is
* presently held by the Network object for each participating Peer. * held by the Network object for each participating Peer.
* *
* This is not thread safe. It must be locked externally. * This class is not thread safe. It must be locked externally.
*/ */
class Membership class Membership
{ {
private: private:
// Tags and related state // Tags and related state
struct TState struct _RemoteTag
{ {
TState() : lastPushed(0),lastReceived(0) {} _RemoteTag() : id(0xffffffffffffffffULL),lastReceived(0),revocationThreshold(0) {}
// Last time we pushed OUR tag to this peer (with this ID) // Tag ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
uint64_t lastPushed; uint64_t id;
// Last time we received THEIR tag (with this ID) // Last time we received THEIR tag (with this ID)
uint64_t lastReceived; uint64_t lastReceived;
// Revocation blacklist threshold or 0 if none
uint64_t revocationThreshold;
// THEIR tag // THEIR tag
Tag tag; Tag tag;
}; };
// Credentials and related state // Credentials and related state
struct CState struct _RemoteCapability
{ {
CState() : lastPushed(0),lastReceived(0) {} _RemoteCapability() : id(0xffffffffffffffffULL),lastReceived(0),revocationThreshold(0) {}
// Last time we pushed OUR capability to this peer (with this ID) // Capability ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
uint64_t lastPushed; uint64_t id;
// Last time we received THEIR capability (with this ID) // Last time we received THEIR capability (with this ID)
uint64_t lastReceived; uint64_t lastReceived;
// Revocation blacklist threshold or 0 if none
uint64_t revocationThreshold;
// THEIR capability // THEIR capability
Capability cap; Capability cap;
}; };
// Comparison operator for remote credential entries
template<typename T>
struct _RemoteCredentialSorter
{
inline bool operator()(const T *a,const T *b) const { return (a->id < b->id); }
inline bool operator()(const uint64_t a,const T *b) const { return (a < b->id); }
inline bool operator()(const T *a,const uint64_t b) const { return (a->id < b); }
inline bool operator()(const uint64_t a,const uint64_t b) const { return (a < b); }
};
// Used to track push state for network config tags[] and capabilities[] entries
struct _LocalCredentialPushState
{
_LocalCredentialPushState() : lastPushed(0),id(0) {}
uint64_t lastPushed;
uint32_t id;
};
public: public:
enum AddCredentialResult
{
ADD_REJECTED,
ADD_ACCEPTED_NEW,
ADD_ACCEPTED_REDUNDANT,
ADD_DEFERRED_FOR_WHOIS
};
/** /**
* A wrapper to iterate through member capabilities in ascending order of capability ID and return only valid ones * Iterator to scan forward through capabilities in ascending order of ID
*/ */
class CapabilityIterator class CapabilityIterator
{ {
public: public:
CapabilityIterator(const Membership &m) : CapabilityIterator(const Membership &m,const NetworkConfig &nconf) :
_m(m), _m(&m),
_i(m._caps.begin()), _c(&nconf),
_e(m._caps.end()) _i(&(m._remoteCaps[0])) {}
{
}
inline const Capability *next(const NetworkConfig &nconf) inline const Capability *next()
{ {
while (_i != _e) { for(;;) {
if ((_i->second.lastReceived)&&(_m.isCredentialTimestampValid(nconf,_i->second.cap))) if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != 0xffffffffffffffffULL)) {
return &((_i++)->second.cap); const Capability *tmp = &((*_i)->cap);
else ++_i; if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
++_i;
return tmp;
} else ++_i;
} else {
return (const Capability *)0;
}
} }
return (const Capability *)0;
} }
private: private:
const Membership &_m; const Membership *_m;
std::map<uint32_t,CState>::const_iterator _i,_e; const NetworkConfig *_c;
const _RemoteCapability *const *_i;
}; };
friend class CapabilityIterator; friend class CapabilityIterator;
Membership() : /**
_lastUpdatedMulticast(0), * Iterator to scan forward through tags in ascending order of ID
_lastPushAttempt(0), */
_lastPushedCom(0), class TagIterator
_blacklistBefore(0),
_com(),
_caps(),
_tags(8)
{ {
} public:
TagIterator(const Membership &m,const NetworkConfig &nconf) :
_m(&m),
_c(&nconf),
_i(&(m._remoteTags[0])) {}
inline const Tag *next()
{
for(;;) {
if ((_i != &(_m->_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*_i)->id != 0xffffffffffffffffULL)) {
const Tag *tmp = &((*_i)->tag);
if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
++_i;
return tmp;
} else ++_i;
} else {
return (const Tag *)0;
}
}
}
private:
const Membership *_m;
const NetworkConfig *_c;
const _RemoteTag *const *_i;
};
friend class TagIterator;
Membership();
/** /**
* Send COM and other credentials to this peer if needed * Send COM and other credentials to this peer if needed
@ -122,9 +178,10 @@ public:
* @param now Current time * @param now Current time
* @param peerAddress Address of member peer (the one that this Membership describes) * @param peerAddress Address of member peer (the one that this Membership describes)
* @param nconf My network config * @param nconf My network config
* @param cap Capability to send or 0 if none * @param localCapabilityIndex Index of local capability to include (in nconf.capabilities[]) or -1 if none
* @param force If true, send objects regardless of last push time
*/ */
void sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,const Capability *cap); void pushCredentials(const RuntimeEnvironment *RR,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force);
/** /**
* Check whether we should push MULTICAST_LIKEs to this peer * Check whether we should push MULTICAST_LIKEs to this peer
@ -142,6 +199,8 @@ public:
inline void likingMulticasts(const uint64_t now) { _lastUpdatedMulticast = now; } inline void likingMulticasts(const uint64_t now) { _lastUpdatedMulticast = now; }
/** /**
* Check whether the peer represented by this Membership should be allowed on this network at all
*
* @param nconf Our network config * @param nconf Our network config
* @return True if this peer is allowed on this network at all * @return True if this peer is allowed on this network at all
*/ */
@ -149,126 +208,48 @@ public:
{ {
if (nconf.isPublic()) if (nconf.isPublic())
return true; return true;
if ((_blacklistBefore)&&(_com.timestamp().first <= _blacklistBefore)) if ((_comRevocationThreshold)&&(_com.timestamp().first <= _comRevocationThreshold))
return false; return false;
return nconf.com.agreesWith(_com); return nconf.com.agreesWith(_com);
} }
/**
* Check whether a capability or tag is within its max delta from the timestamp of our network config and newer than any blacklist cutoff time
*
* @param cred Credential to check -- must have timestamp() accessor method
* @return True if credential is NOT expired
*/
template<typename C>
inline bool isCredentialTimestampValid(const NetworkConfig &nconf,const C &cred) const
{
const uint64_t ts = cred.timestamp();
const uint64_t delta = (ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts);
return ((delta <= nconf.credentialTimeMaxDelta)&&(ts > _blacklistBefore));
}
/**
* @param nconf Network configuration
* @param id Tag ID
* @return Pointer to tag or NULL if not found
*/
inline const Tag *getTag(const NetworkConfig &nconf,const uint32_t id) const
{
const TState *t = _tags.get(id);
return ((t) ? (((t->lastReceived != 0)&&(isCredentialTimestampValid(nconf,t->tag))) ? &(t->tag) : (const Tag *)0) : (const Tag *)0);
}
/**
* @param nconf Network configuration
* @param ids Array to store IDs into
* @param values Array to store values into
* @param maxTags Capacity of ids[] and values[]
* @return Number of tags added to arrays
*/
inline unsigned int getAllTags(const NetworkConfig &nconf,uint32_t *ids,uint32_t *values,unsigned int maxTags) const
{
unsigned int n = 0;
uint32_t *id = (uint32_t *)0;
TState *ts = (TState *)0;
Hashtable<uint32_t,TState>::Iterator i(const_cast<Membership *>(this)->_tags);
while (i.next(id,ts)) {
if ((ts->lastReceived)&&(isCredentialTimestampValid(nconf,ts->tag))) {
if (n >= maxTags)
return n;
ids[n] = *id;
values[n] = ts->tag.value();
}
}
return n;
}
/** /**
* @param nconf Network configuration * @param nconf Network configuration
* @param id Capablity ID * @param id Capablity ID
* @return Pointer to capability or NULL if not found * @return Pointer to capability or NULL if not found
*/ */
inline const Capability *getCapability(const NetworkConfig &nconf,const uint32_t id) const const Capability *getCapability(const NetworkConfig &nconf,const uint32_t id) const;
{
std::map<uint32_t,CState>::const_iterator c(_caps.find(id)); /**
return ((c != _caps.end()) ? (((c->second.lastReceived != 0)&&(isCredentialTimestampValid(nconf,c->second.cap))) ? &(c->second.cap) : (const Capability *)0) : (const Capability *)0); * @param nconf Network configuration
} * @param id Tag ID
* @return Pointer to tag or NULL if not found
*/
const Tag *getTag(const NetworkConfig &nconf,const uint32_t id) const;
/** /**
* Validate and add a credential if signature is okay and it's otherwise good * Validate and add a credential if signature is okay and it's otherwise good
*
* @param RR Runtime environment
* @param com Certificate of membership
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/ */
int addCredential(const RuntimeEnvironment *RR,const CertificateOfMembership &com); AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfMembership &com);
/** /**
* Validate and add a credential if signature is okay and it's otherwise good * 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
*/ */
int addCredential(const RuntimeEnvironment *RR,const Tag &tag); AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Tag &tag);
/** /**
* Validate and add a credential if signature is okay and it's otherwise good * 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
*/ */
int addCredential(const RuntimeEnvironment *RR,const Capability &cap); AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Capability &cap);
/**
* Blacklist COM, tags, and capabilities before this time
*
* @param ts Blacklist cutoff
*/
inline void blacklistBefore(const uint64_t ts) { _blacklistBefore = ts; }
/**
* Clean up old or stale entries
*
* @param nconf Network config
*/
inline void clean(const NetworkConfig &nconf)
{
for(std::map<uint32_t,CState>::iterator i(_caps.begin());i!=_caps.end();) {
if (!isCredentialTimestampValid(nconf,i->second.cap)) {
_caps.erase(i++);
} else {
++i;
}
}
uint32_t *i = (uint32_t *)0;
TState *ts = (TState *)0;
Hashtable<uint32_t,TState>::Iterator tsi(_tags);
while (tsi.next(i,ts)) {
if (!isCredentialTimestampValid(nconf,ts->tag))
_tags.erase(*i);
}
}
private: private:
template<typename C,typename CS>
inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const C &cred,const CS &state) const
{
const uint64_t ts = cred.timestamp();
return ( (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) && (ts > state.revocationThreshold) );
}
// Last time we pushed MULTICAST_LIKE(s) // Last time we pushed MULTICAST_LIKE(s)
uint64_t _lastUpdatedMulticast; uint64_t _lastUpdatedMulticast;
@ -278,17 +259,23 @@ private:
// Last time we pushed our COM to this peer // Last time we pushed our COM to this peer
uint64_t _lastPushedCom; uint64_t _lastPushedCom;
// Time before which to blacklist credentials from this peer // Revocation threshold for COM or 0 if none
uint64_t _blacklistBefore; uint64_t _comRevocationThreshold;
// COM from this peer // Remote member's latest network COM
CertificateOfMembership _com; CertificateOfMembership _com;
// Capability-related state (we need an ordered container here, hence std::map) // Sorted (in ascending order of ID) arrays of pointers to remote tags and capabilities
std::map<uint32_t,CState> _caps; _RemoteTag *_remoteTags[ZT_MAX_NETWORK_TAGS];
_RemoteCapability *_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES];
// Tag-related state // This is the RAM allocated for remote tags and capabilities from which the sorted arrays are populated
Hashtable<uint32_t,TState> _tags; _RemoteTag _tagMem[ZT_MAX_NETWORK_TAGS];
_RemoteCapability _capMem[ZT_MAX_NETWORK_CAPABILITIES];
// Local credential push state tracking
_LocalCredentialPushState _localTags[ZT_MAX_NETWORK_TAGS];
_LocalCredentialPushState _localCaps[ZT_MAX_NETWORK_CAPABILITIES];
}; };
} // namespace ZeroTier } // namespace ZeroTier

View File

@ -36,7 +36,7 @@
#include "Peer.hpp" #include "Peer.hpp"
// Uncomment to make the rules engine dump trace info to stdout // 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 { namespace ZeroTier {
@ -155,24 +155,21 @@ enum _doZtFilterResult
static _doZtFilterResult _doZtFilter( static _doZtFilterResult _doZtFilter(
const RuntimeEnvironment *RR, const RuntimeEnvironment *RR,
const NetworkConfig &nconf, const NetworkConfig &nconf,
const Membership *membership, // can be NULL
const bool inbound, const bool inbound,
const Address &ztSource, const Address &ztSource,
Address &ztDest, // MUTABLE Address &ztDest, // MUTABLE -- is changed on REDIRECT actions
const MAC &macSource, const MAC &macSource,
const MAC &macDest, const MAC &macDest,
const uint8_t *const frameData, const uint8_t *const frameData,
const unsigned int frameLen, const unsigned int frameLen,
const unsigned int etherType, const unsigned int etherType,
const unsigned int vlanId, const unsigned int vlanId,
const ZT_VirtualNetworkRule *rules, const ZT_VirtualNetworkRule *rules, // cannot be NULL
const unsigned int ruleCount, const unsigned int ruleCount,
const Tag *localTags, Address &cc, // MUTABLE -- set to TEE destination if TEE action is taken or left alone otherwise
const unsigned int localTagCount, unsigned int &ccLength, // MUTABLE -- set to length of packet payload to TEE
const uint32_t *const remoteTagIds, bool &ccWatch) // MUTABLE -- set to true for WATCH target as opposed to normal TEE
const uint32_t *const remoteTagValues,
const unsigned int remoteTagCount,
Address &cc, // MUTABLE
unsigned int &ccLength) // MUTABLE
{ {
#ifdef ZT_RULES_ENGINE_DEBUGGING #ifdef ZT_RULES_ENGINE_DEBUGGING
char dpbuf[1024]; // used by FILTER_TRACE macro char dpbuf[1024]; // used by FILTER_TRACE macro
@ -204,6 +201,7 @@ static _doZtFilterResult _doZtFilter(
// These are initially handled together since preliminary logic is common // These are initially handled together since preliminary logic is common
case ZT_NETWORK_RULE_ACTION_TEE: case ZT_NETWORK_RULE_ACTION_TEE:
case ZT_NETWORK_RULE_ACTION_WATCH:
case ZT_NETWORK_RULE_ACTION_REDIRECT: { case ZT_NETWORK_RULE_ACTION_REDIRECT: {
const Address fwdAddr(rules[rn].v.fwd.address); const Address fwdAddr(rules[rn].v.fwd.address);
if (fwdAddr == ztSource) { if (fwdAddr == ztSource) {
@ -242,6 +240,7 @@ static _doZtFilterResult _doZtFilter(
#endif // ZT_RULES_ENGINE_DEBUGGING #endif // ZT_RULES_ENGINE_DEBUGGING
cc = fwdAddr; cc = fwdAddr;
ccLength = (rules[rn].v.fwd.length != 0) ? ((frameLen < (unsigned int)rules[rn].v.fwd.length) ? frameLen : (unsigned int)rules[rn].v.fwd.length) : frameLen; ccLength = (rules[rn].v.fwd.length != 0) ? ((frameLen < (unsigned int)rules[rn].v.fwd.length) ? frameLen : (unsigned int)rules[rn].v.fwd.length) : frameLen;
ccWatch = (rt == ZT_NETWORK_RULE_ACTION_WATCH);
} }
} }
} continue; } continue;
@ -508,25 +507,29 @@ static _doZtFilterResult _doZtFilter(
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: { case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: {
const Tag *lt = (const Tag *)0; const Tag *const localTag = std::lower_bound(&(nconf.tags[0]),&(nconf.tags[nconf.tagCount]),rules[rn].v.tag.id,Tag::IdComparePredicate());
for(unsigned int i=0;i<localTagCount;++i) { if ((localTag != &(nconf.tags[nconf.tagCount]))&&(localTag->id() == rules[rn].v.tag.id)) {
if (rules[rn].v.tag.id == localTags[i].id()) { const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0);
lt = &(localTags[i]); if (remoteTag) {
break; const uint32_t ltv = localTag->value();
} const uint32_t rtv = remoteTag->value();
} if (rt == ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE) {
if (!lt) { const uint32_t diff = (ltv > rtv) ? (ltv - rtv) : (rtv - ltv);
thisRuleMatches = 0; thisRuleMatches = (uint8_t)(diff <= rules[rn].v.tag.value);
FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); FILTER_TRACE("%u %s %c TAG %u local:%u remote:%u difference:%u<=%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,diff,(unsigned int)rules[rn].v.tag.value,thisRuleMatches);
} else { } else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND) {
const uint32_t *rtv = (const uint32_t *)0; thisRuleMatches = (uint8_t)((ltv & rtv) == rules[rn].v.tag.value);
for(unsigned int i=0;i<remoteTagCount;++i) { FILTER_TRACE("%u %s %c TAG %u local:%.8x & remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
if (rules[rn].v.tag.id == remoteTagIds[i]) { } else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR) {
rtv = &(remoteTagValues[i]); thisRuleMatches = (uint8_t)((ltv | rtv) == rules[rn].v.tag.value);
break; FILTER_TRACE("%u %s %c TAG %u local:%.8x | remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR) {
thisRuleMatches = (uint8_t)((ltv ^ rtv) == rules[rn].v.tag.value);
FILTER_TRACE("%u %s %c TAG %u local:%.8x ^ remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else { // sanity check, can't really happen
thisRuleMatches = 0;
} }
} } else {
if (!rtv) {
if (inbound) { if (inbound) {
thisRuleMatches = 0; thisRuleMatches = 0;
FILTER_TRACE("%u %s %c remote tag %u not found -> 0 (inbound side is strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); FILTER_TRACE("%u %s %c remote tag %u not found -> 0 (inbound side is strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
@ -534,24 +537,10 @@ static _doZtFilterResult _doZtFilter(
thisRuleMatches = 1; thisRuleMatches = 1;
FILTER_TRACE("%u %s %c remote tag %u not found -> 1 (outbound side is not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); FILTER_TRACE("%u %s %c remote tag %u not found -> 1 (outbound side is not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
} }
} else {
if (rt == ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE) {
const uint32_t diff = (lt->value() > *rtv) ? (lt->value() - *rtv) : (*rtv - lt->value());
thisRuleMatches = (uint8_t)(diff <= rules[rn].v.tag.value);
FILTER_TRACE("%u %s %c TAG %u local:%u remote:%u difference:%u<=%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,diff,(unsigned int)rules[rn].v.tag.value,thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND) {
thisRuleMatches = (uint8_t)((lt->value() & *rtv) == rules[rn].v.tag.value);
FILTER_TRACE("%u %s %c TAG %u local:%.8x & remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR) {
thisRuleMatches = (uint8_t)((lt->value() | *rtv) == rules[rn].v.tag.value);
FILTER_TRACE("%u %s %c TAG %u local:%.8x | remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR) {
thisRuleMatches = (uint8_t)((lt->value() ^ *rtv) == rules[rn].v.tag.value);
FILTER_TRACE("%u %s %c TAG %u local:%.8x ^ remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,lt->value(),*rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
} else { // sanity check, can't really happen
thisRuleMatches = 0;
}
} }
} else {
thisRuleMatches = 0;
FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
} }
} break; } break;
@ -582,7 +571,6 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) :
_portInitialized(false), _portInitialized(false),
_inboundConfigPacketId(0), _inboundConfigPacketId(0),
_lastConfigUpdate(0), _lastConfigUpdate(0),
_lastRequestedConfiguration(0),
_destroyed(false), _destroyed(false),
_netconfFailure(NETCONF_FAILURE_NONE), _netconfFailure(NETCONF_FAILURE_NONE),
_portError(0) _portError(0)
@ -598,7 +586,7 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) :
if (conf.length()) { if (conf.length()) {
dconf->load(conf.c_str()); dconf->load(conf.c_str());
if (nconf->fromDictionary(*dconf)) { if (nconf->fromDictionary(*dconf)) {
this->setConfiguration(*nconf,false); this->_setConfiguration(*nconf,false);
_lastConfigUpdate = 0; // we still want to re-request a new config from the network _lastConfigUpdate = 0; // we still want to re-request a new config from the network
gotConf = true; gotConf = true;
} }
@ -646,32 +634,27 @@ bool Network::filterOutgoingPacket(
const unsigned int etherType, const unsigned int etherType,
const unsigned int vlanId) const unsigned int vlanId)
{ {
uint32_t remoteTagIds[ZT_MAX_NETWORK_TAGS];
uint32_t remoteTagValues[ZT_MAX_NETWORK_TAGS];
Address ztFinalDest(ztDest);
Address cc;
const Capability *relevantCap = (const Capability *)0;
unsigned int ccLength = 0;
bool accept = false;
const uint64_t now = RR->node->now(); const uint64_t now = RR->node->now();
Address ztFinalDest(ztDest);
int localCapabilityIndex = -1;
bool accept = false;
Mutex::Lock _l(_lock); Mutex::Lock _l(_lock);
Membership *m = (Membership *)0; Membership *const membership = (ztDest) ? _memberships.get(ztDest) : (Membership *)0;
unsigned int remoteTagCount = 0;
if (ztDest) {
m = &(_memberships[ztDest]);
remoteTagCount = m->getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
}
switch(_doZtFilter(RR,_config,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc,ccLength)) { Address cc;
unsigned int ccLength = 0;
bool ccWatch = false;
switch(_doZtFilter(RR,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch)) {
case DOZTFILTER_NO_MATCH: case DOZTFILTER_NO_MATCH:
for(unsigned int c=0;c<_config.capabilityCount;++c) { for(unsigned int c=0;c<_config.capabilityCount;++c) {
ztFinalDest = ztDest; // sanity check ztFinalDest = ztDest; // sanity check, shouldn't be possible if there was no match
Address cc2; Address cc2;
unsigned int ccLength2 = 0; unsigned int ccLength2 = 0;
switch (_doZtFilter(RR,_config,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.capabilities[c].rules(),_config.capabilities[c].ruleCount(),_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc2,ccLength2)) { bool ccWatch2 = false;
switch (_doZtFilter(RR,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.capabilities[c].rules(),_config.capabilities[c].ruleCount(),cc2,ccLength2,ccWatch2)) {
case DOZTFILTER_NO_MATCH: case DOZTFILTER_NO_MATCH:
case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
break; break;
@ -679,16 +662,16 @@ bool Network::filterOutgoingPacket(
case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
case DOZTFILTER_ACCEPT: case DOZTFILTER_ACCEPT:
case DOZTFILTER_SUPER_ACCEPT: // no difference in behavior on outbound side case DOZTFILTER_SUPER_ACCEPT: // no difference in behavior on outbound side
relevantCap = &(_config.capabilities[c]); localCapabilityIndex = (int)c;
accept = true; accept = true;
if ((!noTee)&&(cc2)) { if ((!noTee)&&(cc2)) {
Membership &m2 = _membership(cc2); Membership &m2 = _membership(cc2);
m2.sendCredentialsIfNeeded(RR,now,cc2,_config,relevantCap); m2.pushCredentials(RR,now,cc2,_config,localCapabilityIndex,false);
Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME); Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id); outp.append(_id);
outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02 outp.append((uint8_t)(ccWatch2 ? 0x16 : 0x02));
macDest.appendTo(outp); macDest.appendTo(outp);
macSource.appendTo(outp); macSource.appendTo(outp);
outp.append((uint16_t)etherType); outp.append((uint16_t)etherType);
@ -715,13 +698,16 @@ bool Network::filterOutgoingPacket(
} }
if (accept) { if (accept) {
if (membership)
membership->pushCredentials(RR,now,ztDest,_config,localCapabilityIndex,false);
if ((!noTee)&&(cc)) { if ((!noTee)&&(cc)) {
Membership &m2 = _membership(cc); Membership &m2 = _membership(cc);
m2.sendCredentialsIfNeeded(RR,now,cc,_config,relevantCap); m2.pushCredentials(RR,now,cc,_config,localCapabilityIndex,false);
Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id); outp.append(_id);
outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02 outp.append((uint8_t)(ccWatch ? 0x16 : 0x02));
macDest.appendTo(outp); macDest.appendTo(outp);
macSource.appendTo(outp); macSource.appendTo(outp);
outp.append((uint16_t)etherType); outp.append((uint16_t)etherType);
@ -732,11 +718,11 @@ bool Network::filterOutgoingPacket(
if ((ztDest != ztFinalDest)&&(ztFinalDest)) { if ((ztDest != ztFinalDest)&&(ztFinalDest)) {
Membership &m2 = _membership(ztFinalDest); Membership &m2 = _membership(ztFinalDest);
m2.sendCredentialsIfNeeded(RR,now,ztFinalDest,_config,relevantCap); m2.pushCredentials(RR,now,ztFinalDest,_config,localCapabilityIndex,false);
Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME); Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id); outp.append(_id);
outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02 outp.append((uint8_t)0x04);
macDest.appendTo(outp); macDest.appendTo(outp);
macSource.appendTo(outp); macSource.appendTo(outp);
outp.append((uint16_t)etherType); outp.append((uint16_t)etherType);
@ -745,11 +731,9 @@ bool Network::filterOutgoingPacket(
RR->sw->send(outp,true); RR->sw->send(outp,true);
return false; // DROP locally, since we redirected return false; // DROP locally, since we redirected
} else if (m) { } else {
m->sendCredentialsIfNeeded(RR,now,ztDest,_config,relevantCap); return true;
} }
return true;
} else { } else {
return false; return false;
} }
@ -765,28 +749,27 @@ int Network::filterIncomingPacket(
const unsigned int etherType, const unsigned int etherType,
const unsigned int vlanId) const unsigned int vlanId)
{ {
uint32_t remoteTagIds[ZT_MAX_NETWORK_TAGS];
uint32_t remoteTagValues[ZT_MAX_NETWORK_TAGS];
Address ztFinalDest(ztDest); Address ztFinalDest(ztDest);
Address cc;
unsigned int ccLength = 0;
int accept = 0; int accept = 0;
Mutex::Lock _l(_lock); Mutex::Lock _l(_lock);
Membership &m = _membership(sourcePeer->address()); Membership &membership = _membership(sourcePeer->address());
const unsigned int remoteTagCount = m.getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
switch (_doZtFilter(RR,_config,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc,ccLength)) { Address cc;
unsigned int ccLength = 0;
bool ccWatch = false;
switch (_doZtFilter(RR,_config,&membership,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch)) {
case DOZTFILTER_NO_MATCH: { case DOZTFILTER_NO_MATCH: {
Membership::CapabilityIterator mci(m); Membership::CapabilityIterator mci(membership,_config);
const Capability *c; const Capability *c;
while ((c = mci.next(_config))) { while ((c = mci.next())) {
ztFinalDest = ztDest; // sanity check ztFinalDest = ztDest; // sanity check, should be unmodified if there was no match
Address cc2; Address cc2;
unsigned int ccLength2 = 0; unsigned int ccLength2 = 0;
switch(_doZtFilter(RR,_config,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,c->rules(),c->ruleCount(),_config.tags,_config.tagCount,remoteTagIds,remoteTagValues,remoteTagCount,cc2,ccLength2)) { bool ccWatch2 = false;
switch(_doZtFilter(RR,_config,&membership,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,c->rules(),c->ruleCount(),cc2,ccLength2,ccWatch2)) {
case DOZTFILTER_NO_MATCH: case DOZTFILTER_NO_MATCH:
case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
break; break;
@ -801,11 +784,11 @@ int Network::filterIncomingPacket(
if (accept) { if (accept) {
if (cc2) { if (cc2) {
_membership(cc2).sendCredentialsIfNeeded(RR,RR->node->now(),cc2,_config,(const Capability *)0); _membership(cc2).pushCredentials(RR,RR->node->now(),cc2,_config,-1,false);
Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME); Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id); outp.append(_id);
outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06 outp.append((uint8_t)(ccWatch2 ? 0x1c : 0x08));
macDest.appendTo(outp); macDest.appendTo(outp);
macSource.appendTo(outp); macSource.appendTo(outp);
outp.append((uint16_t)etherType); outp.append((uint16_t)etherType);
@ -832,11 +815,11 @@ int Network::filterIncomingPacket(
if (accept) { if (accept) {
if (cc) { if (cc) {
_membership(cc).sendCredentialsIfNeeded(RR,RR->node->now(),cc,_config,(const Capability *)0); _membership(cc).pushCredentials(RR,RR->node->now(),cc,_config,-1,false);
Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id); outp.append(_id);
outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06 outp.append((uint8_t)(ccWatch ? 0x1c : 0x08));
macDest.appendTo(outp); macDest.appendTo(outp);
macSource.appendTo(outp); macSource.appendTo(outp);
outp.append((uint16_t)etherType); outp.append((uint16_t)etherType);
@ -846,11 +829,11 @@ int Network::filterIncomingPacket(
} }
if ((ztDest != ztFinalDest)&&(ztFinalDest)) { if ((ztDest != ztFinalDest)&&(ztFinalDest)) {
_membership(ztFinalDest).sendCredentialsIfNeeded(RR,RR->node->now(),ztFinalDest,_config,(const Capability *)0); _membership(ztFinalDest).pushCredentials(RR,RR->node->now(),ztFinalDest,_config,-1,false);
Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME); Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME);
outp.append(_id); outp.append(_id);
outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06 outp.append((uint8_t)0x0a);
macDest.appendTo(outp); macDest.appendTo(outp);
macSource.appendTo(outp); macSource.appendTo(outp);
outp.append((uint16_t)etherType); outp.append((uint16_t)etherType);
@ -892,60 +875,6 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg)
_myMulticastGroups.erase(i); _myMulticastGroups.erase(i);
} }
bool Network::applyConfiguration(const NetworkConfig &conf)
{
if (_destroyed) // sanity check
return false;
try {
if ((conf.networkId == _id)&&(conf.issuedTo == RR->identity.address())) {
ZT_VirtualNetworkConfig ctmp;
bool portInitialized;
{
Mutex::Lock _l(_lock);
_config = conf;
_lastConfigUpdate = RR->node->now();
_netconfFailure = NETCONF_FAILURE_NONE;
_externalConfig(&ctmp);
portInitialized = _portInitialized;
_portInitialized = true;
}
_portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(portInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
return true;
} else {
TRACE("ignored invalid configuration for network %.16llx (configuration contains mismatched network ID or issued-to address)",(unsigned long long)_id);
}
} catch (std::exception &exc) {
TRACE("ignored invalid configuration for network %.16llx (%s)",(unsigned long long)_id,exc.what());
} catch ( ... ) {
TRACE("ignored invalid configuration for network %.16llx (unknown exception)",(unsigned long long)_id);
}
return false;
}
int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
{
try {
{
Mutex::Lock _l(_lock);
if (_config == nconf)
return 1; // OK config, but duplicate of what we already have
}
if (applyConfiguration(nconf)) {
if (saveToDisk) {
char n[64];
Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> d;
if (nconf.toDictionary(d,false))
RR->node->dataStorePut(n,(const void *)d.data(),d.sizeBytes(),true);
}
return 2; // OK and configuration has changed
}
} catch ( ... ) {
TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id);
}
return 0;
}
void Network::handleInboundConfigChunk(const uint64_t inRePacketId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize) void Network::handleInboundConfigChunk(const uint64_t inRePacketId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize)
{ {
std::string newConfig; std::string newConfig;
@ -979,7 +908,8 @@ void Network::handleInboundConfigChunk(const uint64_t inRePacketId,const void *d
Identity controllerId(RR->topology->getIdentity(this->controller())); Identity controllerId(RR->topology->getIdentity(this->controller()));
if (controllerId) { if (controllerId) {
if (nc->fromDictionary(*dict)) { if (nc->fromDictionary(*dict)) {
this->setConfiguration(*nc,true); Mutex::Lock _l(_lock);
this->_setConfiguration(*nc,true);
} else { } else {
TRACE("error parsing new config with length %u: deserialization of NetworkConfig failed (certificate error?)",(unsigned int)newConfig.length()); TRACE("error parsing new config with length %u: deserialization of NetworkConfig failed (certificate error?)",(unsigned int)newConfig.length());
} }
@ -997,12 +927,6 @@ void Network::handleInboundConfigChunk(const uint64_t inRePacketId,const void *d
void Network::requestConfiguration() void Network::requestConfiguration()
{ {
// Sanity limit: do not request more often than once per second
const uint64_t now = RR->node->now();
if ((now - _lastRequestedConfiguration) < 1000ULL)
return;
_lastRequestedConfiguration = RR->node->now();
const Address ctrl(controller()); const Address ctrl(controller());
Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd; Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
@ -1024,9 +948,10 @@ void Network::requestConfiguration()
if (RR->localNetworkController) { if (RR->localNetworkController) {
NetworkConfig nconf; NetworkConfig nconf;
switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,nconf)) { switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,nconf)) {
case NetworkController::NETCONF_QUERY_OK: case NetworkController::NETCONF_QUERY_OK: {
this->setConfiguration(nconf,true); Mutex::Lock _l(_lock);
return; this->_setConfiguration(nconf,true);
} return;
case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND:
this->setNotFound(); this->setNotFound();
return; return;
@ -1073,7 +998,7 @@ bool Network::gate(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uin
if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config))) ) { if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config))) ) {
if (!m) if (!m)
m = &(_membership(peer->address())); m = &(_membership(peer->address()));
m->sendCredentialsIfNeeded(RR,now,peer->address(),_config,(const Capability *)0); m->pushCredentials(RR,now,peer->address(),_config,-1,false);
if (m->shouldLikeMulticasts(now)) { if (m->shouldLikeMulticasts(now)) {
_announceMulticastGroupsTo(peer->address(),_allMulticastGroups()); _announceMulticastGroupsTo(peer->address(),_allMulticastGroups());
m->likingMulticasts(now); m->likingMulticasts(now);
@ -1124,9 +1049,8 @@ void Network::clean()
Membership *m = (Membership *)0; Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships); Hashtable<Address,Membership>::Iterator i(_memberships);
while (i.next(a,m)) { while (i.next(a,m)) {
if (RR->topology->getPeerNoCache(*a)) if (!RR->topology->getPeerNoCache(*a))
m->clean(_config); _memberships.erase(*a);
else _memberships.erase(*a);
} }
} }
} }
@ -1177,21 +1101,25 @@ void Network::learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now)
_sendUpdatesToMembers(&mg); _sendUpdatesToMembers(&mg);
} }
int Network::addCredential(const CertificateOfMembership &com) Membership::AddCredentialResult Network::addCredential(const CertificateOfMembership &com)
{ {
if (com.networkId() != _id) if (com.networkId() != _id)
return -1; return Membership::ADD_REJECTED;
const Address a(com.issuedTo()); const Address a(com.issuedTo());
Mutex::Lock _l(_lock); Mutex::Lock _l(_lock);
Membership &m = _membership(a); Membership &m = _membership(a);
const int result = m.addCredential(RR,com); const Membership::AddCredentialResult result = m.addCredential(RR,_config,com);
if (result == 0) { if ((result == Membership::ADD_ACCEPTED_NEW)||(result == Membership::ADD_ACCEPTED_REDUNDANT)) {
m.sendCredentialsIfNeeded(RR,RR->node->now(),a,_config,(const Capability *)0); m.pushCredentials(RR,RR->node->now(),a,_config,-1,false);
RR->mc->addCredential(com,true); RR->mc->addCredential(com,true);
} }
return result; return result;
} }
Membership::AddCredentialResult Network::addCredential(const Revocation &rev)
{
}
void Network::destroy() void Network::destroy()
{ {
Mutex::Lock _l(_lock); Mutex::Lock _l(_lock);
@ -1215,6 +1143,39 @@ ZT_VirtualNetworkStatus Network::_status() const
} }
} }
int Network::_setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
{
// assumes _lock is locked
try {
if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id))
return 0;
if (_config == nconf)
return 1; // OK config, but duplicate of what we already have
ZT_VirtualNetworkConfig ctmp;
_config = nconf;
_lastConfigUpdate = RR->node->now();
_netconfFailure = NETCONF_FAILURE_NONE;
_externalConfig(&ctmp);
const bool oldPortInitialized = _portInitialized;
_portInitialized = true;
_portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
if (saveToDisk) {
char n[64];
Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> d;
if (nconf.toDictionary(d,false))
RR->node->dataStorePut(n,(const void *)d.data(),d.sizeBytes(),true);
}
return 2; // OK and configuration has changed
} catch ( ... ) {
TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id);
}
return 0;
}
void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const
{ {
// assumes _lock is locked // assumes _lock is locked
@ -1308,7 +1269,7 @@ void Network::_sendUpdatesToMembers(const MulticastGroup *const newMulticastGrou
Membership *m = (Membership *)0; Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships); Hashtable<Address,Membership>::Iterator i(_memberships);
while (i.next(a,m)) { while (i.next(a,m)) {
m->sendCredentialsIfNeeded(RR,now,*a,_config,(const Capability *)0); m->pushCredentials(RR,now,*a,_config,-1,false);
if ( ((newMulticastGroup)||(m->shouldLikeMulticasts(now))) && (m->isAllowedOnNetwork(_config)) ) { if ( ((newMulticastGroup)||(m->shouldLikeMulticasts(now))) && (m->isAllowedOnNetwork(_config)) ) {
if (!newMulticastGroup) if (!newMulticastGroup)
m->likingMulticasts(now); m->likingMulticasts(now);

View File

@ -62,6 +62,11 @@ public:
*/ */
static const MulticastGroup BROADCAST; static const MulticastGroup BROADCAST;
/**
* Compute primary controller device ID from network ID
*/
static inline Address controllerFor(uint64_t nwid) throw() { return Address(nwid >> 24); }
/** /**
* Construct a new network * Construct a new network
* *
@ -76,14 +81,24 @@ public:
~Network(); ~Network();
inline uint64_t id() const { return _id; }
inline Address controller() const { return Address(_id >> 24); }
inline bool multicastEnabled() const { return (_config.multicastLimit > 0); }
inline bool hasConfig() const { return (_config); }
inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; }
inline ZT_VirtualNetworkStatus status() const { Mutex::Lock _l(_lock); return _status(); }
inline const NetworkConfig &config() const { return _config; }
inline const MAC &mac() const { return _mac; }
/** /**
* Apply filters to an outgoing packet * Apply filters to an outgoing packet
* *
* This applies filters from our network config and, if that doesn't match, * This applies filters from our network config and, if that doesn't match,
* our capabilities in ascending order of capability ID. Additional actions * our capabilities in ascending order of capability ID. Additional actions
* such as TEE may be taken, and credentials may be pushed. * such as TEE may be taken, and credentials may be pushed, so this is not
* side-effect-free. It's basically step one in sending something over VL2.
* *
* @param noTee If true, do not TEE anything anywhere * @param noTee If true, do not TEE anything anywhere (for two-pass filtering as done with multicast and bridging)
* @param ztSource Source ZeroTier address * @param ztSource Source ZeroTier address
* @param ztDest Destination ZeroTier address * @param ztDest Destination ZeroTier address
* @param macSource Ethernet layer source address * @param macSource Ethernet layer source address
@ -134,42 +149,10 @@ public:
const unsigned int vlanId); const unsigned int vlanId);
/** /**
* @return Network ID * Check whether we are subscribed to a multicast group
*/ *
inline uint64_t id() const throw() { return _id; }
/**
* @return Address of network's controller (most significant 40 bits of ID)
*/
inline Address controller() const throw() { return Address(_id >> 24); }
/**
* @param nwid Network ID
* @return Address of network's controller
*/
static inline Address controllerFor(uint64_t nwid) throw() { return Address(nwid >> 24); }
/**
* @return Multicast group memberships for this network's port (local, not learned via bridging)
*/
inline std::vector<MulticastGroup> multicastGroups() const
{
Mutex::Lock _l(_lock);
return _myMulticastGroups;
}
/**
* @return All multicast groups including learned groups that are behind any bridges we're attached to
*/
inline std::vector<MulticastGroup> allMulticastGroups() const
{
Mutex::Lock _l(_lock);
return _allMulticastGroups();
}
/**
* @param mg Multicast group * @param mg Multicast group
* @param includeBridgedGroups If true, also include any groups we've learned via bridging * @param includeBridgedGroups If true, also check groups we've learned via bridging
* @return True if this network endpoint / peer is a member * @return True if this network endpoint / peer is a member
*/ */
bool subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const; bool subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const;
@ -188,37 +171,19 @@ public:
*/ */
void multicastUnsubscribe(const MulticastGroup &mg); void multicastUnsubscribe(const MulticastGroup &mg);
/**
* Apply a NetworkConfig to this network
*
* @param conf Configuration in NetworkConfig form
* @return True if configuration was accepted
*/
bool applyConfiguration(const NetworkConfig &conf);
/**
* Set or update this network's configuration
*
* @param nconf Network configuration
* @param saveToDisk IF true (default), write config to disk
* @return 0 -- rejected, 1 -- accepted but not new, 2 -- accepted new config
*/
int setConfiguration(const NetworkConfig &nconf,bool saveToDisk);
/** /**
* Handle an inbound network config chunk * Handle an inbound network config chunk
* *
* Only chunks whose inRePacketId matches the packet ID of the last request * This is called from IncomingPacket when we receive a chunk from a network
* are handled. If this chunk completes the config, it is decoded and * controller.
* setConfiguration() is called.
* *
* @param inRePacketId In-re packet ID from OK(NETWORK_CONFIG_REQUEST) * @param requestId An ID for grouping chunks, e.g. in-re packet ID for OK(NETWORK_CONFIG_REQUEST)
* @param data Chunk data * @param data Chunk data
* @param chunkSize Size of data[] * @param chunkSize Size of data[]
* @param chunkIndex Index of chunk in full config * @param chunkIndex Index of chunk in full config
* @param totalSize Total size of network config * @param totalSize Total size of network config
*/ */
void handleInboundConfigChunk(const uint64_t inRePacketId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize); void handleInboundConfigChunk(const uint64_t requestId,const void *data,unsigned int chunkSize,unsigned int chunkIndex,unsigned int totalSize);
/** /**
* Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this
@ -230,7 +195,7 @@ public:
} }
/** /**
* Set netconf failure to 'not found' -- called by PacketDecider when controller reports this * Set netconf failure to 'not found' -- called by IncomingPacket when controller reports this
*/ */
inline void setNotFound() inline void setNotFound()
{ {
@ -240,10 +205,6 @@ public:
/** /**
* Causes this network to request an updated configuration from its master node now * Causes this network to request an updated configuration from its master node now
*
* There is a circuit breaker here to prevent this from being done more often
* than once per second. This is to prevent things like NETWORK_CONFIG_REFRESH
* from causing multiple requests.
*/ */
void requestConfiguration(); void requestConfiguration();
@ -251,7 +212,7 @@ public:
* Determine whether this peer is permitted to communicate on this network * Determine whether this peer is permitted to communicate on this network
* *
* This also performs certain periodic actions such as pushing renewed * This also performs certain periodic actions such as pushing renewed
* credentials to peers or requesting them if not present. * credentials to peers, so like the filters it is not side-effect-free.
* *
* @param peer Peer to check * @param peer Peer to check
* @param verb Packet verb * @param verb Packet verb
@ -266,7 +227,7 @@ public:
bool gateMulticastGatherReply(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId); bool gateMulticastGatherReply(const SharedPtr<Peer> &peer,const Packet::Verb verb,const uint64_t packetId);
/** /**
* Perform cleanup and possibly save state * Do periodic cleanup and housekeeping tasks
*/ */
void clean(); void clean();
@ -279,46 +240,6 @@ public:
_sendUpdatesToMembers((const MulticastGroup *)0); _sendUpdatesToMembers((const MulticastGroup *)0);
} }
/**
* @return Time of last updated configuration or 0 if none
*/
inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; }
/**
* @return Status of this network
*/
inline ZT_VirtualNetworkStatus status() const
{
Mutex::Lock _l(_lock);
return _status();
}
/**
* @param ec Buffer to fill with externally-visible network configuration
*/
inline void externalConfig(ZT_VirtualNetworkConfig *ec) const
{
Mutex::Lock _l(_lock);
_externalConfig(ec);
}
/**
* Get current network config
*
* @return Network configuration (may be a null config if we don't have one yet)
*/
inline const NetworkConfig &config() const { return _config; }
/**
* @return True if this network has a valid config
*/
inline bool hasConfig() const { return (_config); }
/**
* @return Ethernet MAC address for this network's local interface
*/
inline const MAC &mac() const { return _mac; }
/** /**
* Find the node on this network that has this MAC behind it (if any) * Find the node on this network that has this MAC behind it (if any)
* *
@ -349,44 +270,47 @@ public:
void learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now); void learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now);
/** /**
* @param com Certificate of membership * Validate a credential and learn it if it passes certificate and other checks
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/ */
int addCredential(const CertificateOfMembership &com); Membership::AddCredentialResult addCredential(const CertificateOfMembership &com);
/** /**
* @param cap Capability * Validate a credential and learn it if it passes certificate and other checks
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/ */
inline int addCredential(const Capability &cap) inline Membership::AddCredentialResult addCredential(const Capability &cap)
{ {
if (cap.networkId() != _id) if (cap.networkId() != _id)
return -1; return Membership::ADD_REJECTED;
Mutex::Lock _l(_lock); Mutex::Lock _l(_lock);
return _membership(cap.issuedTo()).addCredential(RR,cap); return _membership(cap.issuedTo()).addCredential(RR,_config,cap);
} }
/** /**
* @param cap Tag * Validate a credential and learn it if it passes certificate and other checks
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/ */
inline int addCredential(const Tag &tag) inline Membership::AddCredentialResult addCredential(const Tag &tag)
{ {
if (tag.networkId() != _id) if (tag.networkId() != _id)
return -1; return Membership::ADD_REJECTED;
Mutex::Lock _l(_lock); Mutex::Lock _l(_lock);
return _membership(tag.issuedTo()).addCredential(RR,tag); return _membership(tag.issuedTo()).addCredential(RR,_config,tag);
} }
/** /**
* Blacklist COM, tags, and capabilities before this time * Validate a credential and learn it if it passes certificate and other checks
*
* @param ts Blacklist cutoff
*/ */
inline void blacklistBefore(const Address &peerAddress,const uint64_t ts) Membership::AddCredentialResult addCredential(const Revocation &rev);
/**
* Force push credentials (COM, etc.) to a peer now
*
* @param to Destination peer address
* @param now Current time
*/
inline void pushCredentialsNow(const Address &to,const uint64_t now)
{ {
Mutex::Lock _l(_lock); Mutex::Lock _l(_lock);
_membership(peerAddress).blacklistBefore(ts); _membership(to).pushCredentials(RR,now,to,_config,-1,true);
} }
/** /**
@ -399,11 +323,23 @@ public:
void destroy(); void destroy();
/** /**
* @return Pointer to user PTR (modifiable user ptr used in API) * Get this network's config for export via the ZT core API
*
* @param ec Buffer to fill with externally-visible network configuration
*/
inline void externalConfig(ZT_VirtualNetworkConfig *ec) const
{
Mutex::Lock _l(_lock);
_externalConfig(ec);
}
/**
* @return Externally usable pointer-to-pointer exported via the core API
*/ */
inline void **userPtr() throw() { return &_uPtr; } inline void **userPtr() throw() { return &_uPtr; }
private: private:
int _setConfiguration(const NetworkConfig &nconf,bool saveToDisk);
ZT_VirtualNetworkStatus _status() const; ZT_VirtualNetworkStatus _status() const;
void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked
bool _gate(const SharedPtr<Peer> &peer); bool _gate(const SharedPtr<Peer> &peer);
@ -412,9 +348,9 @@ private:
std::vector<MulticastGroup> _allMulticastGroups() const; std::vector<MulticastGroup> _allMulticastGroups() const;
Membership &_membership(const Address &a); Membership &_membership(const Address &a);
const RuntimeEnvironment *RR; const RuntimeEnvironment *const RR;
void *_uPtr; void *_uPtr;
uint64_t _id; const uint64_t _id;
uint64_t _lastAnnouncedMulticastGroupsUpstream; uint64_t _lastAnnouncedMulticastGroupsUpstream;
MAC _mac; // local MAC address MAC _mac; // local MAC address
volatile bool _portInitialized; volatile bool _portInitialized;
@ -428,7 +364,6 @@ private:
NetworkConfig _config; NetworkConfig _config;
volatile uint64_t _lastConfigUpdate; volatile uint64_t _lastConfigUpdate;
volatile uint64_t _lastRequestedConfiguration;
volatile bool _destroyed; volatile bool _destroyed;

View File

@ -655,15 +655,27 @@ public:
* *
* Flags: * Flags:
* 0x01 - Certificate of network membership attached (DEPRECATED) * 0x01 - Certificate of network membership attached (DEPRECATED)
* 0x02 - This is a TEE'd or REDIRECT'ed packet * 0x02 - Most significant bit of subtype (see below)
* 0x04 - TEE/REDIRECT'ed packet is from inbound side * 0x04 - Middle bit of subtype (see below)
* 0x08 - Least significant bit of subtype (see below)
* 0x10 - ACK requested in the form of OK(EXT_FRAME)
*
* Subtypes (0..7):
* 0x0 - Normal frame (bridging can be determined by checking MAC)
* 0x1 - TEEd outbound frame
* 0x2 - REDIRECTed outbound frame
* 0x3 - WATCHed outbound frame (TEE with ACK, ACK bit also set)
* 0x4 - TEEd inbound frame
* 0x5 - REDIRECTed inbound frame
* 0x6 - WATCHed inbound frame
* 0x7 - (reserved for future use)
* *
* An extended frame carries full MAC addressing, making them a * An extended frame carries full MAC addressing, making them a
* superset of VERB_FRAME. They're used for bridging or when we * superset of VERB_FRAME. They're used for bridging or when we
* want to attach a certificate since FRAME does not support that. * want to attach a certificate since FRAME does not support that.
* *
* ERROR may be generated if a membership certificate is needed for a * If the ACK flag (0x08) is set, an OK(EXT_FRAME) is sent with
* closed network. Payload will be network ID. * no payload to acknowledge receipt of the frame.
*/ */
VERB_EXT_FRAME = 0x07, VERB_EXT_FRAME = 0x07,
@ -698,7 +710,7 @@ public:
VERB_MULTICAST_LIKE = 0x09, VERB_MULTICAST_LIKE = 0x09,
/** /**
* Network membership credential push: * Network credentials push:
* <[...] serialized certificate of membership> * <[...] serialized certificate of membership>
* [<[...] additional certificates of membership>] * [<[...] additional certificates of membership>]
* <[1] 0x00, null byte marking end of COM array> * <[1] 0x00, null byte marking end of COM array>
@ -706,12 +718,12 @@ public:
* <[...] one or more serialized Capability> * <[...] one or more serialized Capability>
* <[2] 16-bit number of tags> * <[2] 16-bit number of tags>
* <[...] one or more serialized Tags> * <[...] one or more serialized Tags>
* <[2] 16-bit number of revocations>
* <[...] one or more serialized Revocations>
* *
* This is sent in response to ERROR_NEED_MEMBERSHIP_CERTIFICATE and may * This can be sent by anyone at any time to push network credentials.
* be pushed at any other time to keep exchanged certificates up to date. * These will of course only be accepted if they are properly signed.
* * Credentials can be for any number of networks.
* COMs and other credentials need not be for the same network, since each
* includes its own network ID and signature.
* *
* OK/ERROR are not generated. * OK/ERROR are not generated.
*/ */
@ -742,23 +754,18 @@ public:
VERB_NETWORK_CONFIG_REQUEST = 0x0b, VERB_NETWORK_CONFIG_REQUEST = 0x0b,
/** /**
* Network configuration update push: * Network configuration push:
* <[8] network ID to refresh> * <[8] 64-bit network ID>
* <[2] 16-bit number of address/timestamp pairs to blacklist> * <[8] 64-bit value used to group chunks in this push>
* [<[5] ZeroTier address of peer being revoked>] * <[2] 16-bit length of network configuration dictionary chunk>
* [<[8] blacklist credentials older than this timestamp>] * <[...] network configuration dictionary (may be incomplete)>
* [<[...] additional address/timestamp pairs>] * <[4] 32-bit total length of assembled dictionary>
* <[4] 32-bit index of chunk in this reply>
* *
* This can be sent by a network controller to both request that a network * This is a direct push variant for network config updates. It otherwise
* config be updated and push instantaneous revocations of specific peers * carries the same payload as OK(NETWORK_CONFIG_REQUEST). There is an
* or peer credentials. * 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.
* Specific revocations can be pushed to blacklist a specific peer's
* credentials (COM, tags, and capabilities) if older than a specified
* timestamp. This can be used to accomplish expedited revocation of
* a peer's access to things on a network or to the network itself among
* those other peers that can currently reach the controller. This is not
* the only mechanism for revocation of course, but it's the fastest.
*/ */
VERB_NETWORK_CONFIG_REFRESH = 0x0c, VERB_NETWORK_CONFIG_REFRESH = 0x0c,

46
node/Revocation.cpp Normal file
View File

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

178
node/Revocation.hpp Normal file
View File

@ -0,0 +1,178 @@
/*
* 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_REVOCATION_HPP
#define ZT_REVOCATION_HPP
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "Constants.hpp"
#include "../include/ZeroTierOne.h"
#include "Address.hpp"
#include "C25519.hpp"
#include "Utils.hpp"
#include "Buffer.hpp"
#include "Identity.hpp"
/**
* Flag: fast propagation via rumor mill algorithm
*/
#define ZT_REVOCATION_FLAG_FAST_PROPAGATE 0x1ULL
namespace ZeroTier {
class RuntimeEnvironment;
/**
* Revocation certificate to instantaneously revoke a COM, capability, or tag
*/
class Revocation
{
public:
enum CredentialType
{
CREDENTIAL_TYPE_NIL = 0,
CREDENTIAL_TYPE_COM = 1,
CREDENTIAL_TYPE_CAPABILITY = 2,
CREDENTIAL_TYPE_TAG = 3
};
Revocation()
{
memset(this,0,sizeof(Revocation));
}
Revocation(const uint64_t i,const uint64_t nwid,const uint64_t cid,const uint64_t thr,const uint64_t fl,const Address &tgt,const CredentialType ct) :
_id(i),
_networkId(nwid),
_credentialId(cid),
_threshold(thr),
_flags(fl),
_target(tgt),
_signedBy(),
_type(ct) {}
inline uint64_t id() const { return _id; }
inline uint64_t networkId() const { return _networkId; }
inline uint64_t credentialId() const { return _credentialId; }
inline uint64_t threshold() const { return _threshold; }
inline const Address &target() const { return _target; }
inline const Address &signer() const { return _signedBy; }
inline CredentialType type() const { return _type; }
inline bool fastPropagate() const { return ((_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0); }
/**
* @param signer Signing identity, must have private key
* @return True if signature was successful
*/
inline bool sign(const Identity &signer)
{
if (signer.hasPrivate()) {
Buffer<sizeof(Revocation) + 64> tmp;
this->serialize(tmp,true);
_signedBy = signer.address();
_signature = signer.sign(tmp.data(),tmp.size());
return true;
}
return false;
}
/**
* Verify this revocation's signature
*
* @param RR Runtime environment to provide for peer lookup, etc.
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain
*/
int verify(const RuntimeEnvironment *RR) const;
template<unsigned int C>
inline void serialize(Buffer<C> &b,const bool forSign = false) const
{
if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
b.append(_id);
b.append(_networkId);
b.append(_credentialId);
b.append(_threshold);
b.append(_flags);
_target.appendTo(b);
_signedBy.appendTo(b);
b.append((uint8_t)_type);
if (!forSign) {
b.append((uint8_t)1); // 1 == Ed25519 signature
b.append((uint16_t)ZT_C25519_SIGNATURE_LEN);
b.append(_signature.data,ZT_C25519_SIGNATURE_LEN);
}
// This is the size of any additional fields, currently 0.
b.append((uint16_t)0);
if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
}
template<unsigned int C>
inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0)
{
memset(this,0,sizeof(Revocation));
unsigned int p = startAt;
_id = b.template at<uint64_t>(p); p += 8;
_networkId = b.template at<uint64_t>(p); p += 8;
_credentialId = b.template at<uint64_t>(p); p += 8;
_threshold = b.template at<uint64_t>(p); p += 8;
_flags = b.template at<uint64_t>(p); p += 8;
_target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH;
_signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH;
_type = (CredentialType)b[p++];
if (b[p++] == 1) {
if (b.template at<uint16_t>(p) == ZT_C25519_SIGNATURE_LEN) {
p += 2;
memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN);
p += ZT_C25519_SIGNATURE_LEN;
} else throw std::runtime_error("invalid signature");
}
p += 2 + b.template at<uint16_t>(p);
if (p > b.size())
throw std::runtime_error("extended field overflow");
return (p - startAt);
}
private:
uint64_t _id;
uint64_t _networkId;
uint64_t _credentialId;
uint64_t _threshold;
uint64_t _flags;
Address _target;
Address _signedBy;
CredentialType _type;
C25519::Signature _signature;
};
} // namespace ZeroTier
#endif

View File

@ -27,7 +27,7 @@ namespace ZeroTier {
int Tag::verify(const RuntimeEnvironment *RR) const int Tag::verify(const RuntimeEnvironment *RR) const
{ {
if ((!_signedBy)||(_signedBy != Network::controllerFor(_nwid))) if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId)))
return -1; return -1;
const Identity id(RR->topology->getIdentity(_signedBy)); const Identity id(RR->topology->getIdentity(_signedBy));
if (!id) { if (!id) {

View File

@ -67,7 +67,7 @@ public:
* @param value Tag value * @param value Tag value
*/ */
Tag(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id,const uint32_t value) : Tag(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id,const uint32_t value) :
_nwid(nwid), _networkId(nwid),
_ts(ts), _ts(ts),
_id(id), _id(id),
_value(value), _value(value),
@ -76,7 +76,7 @@ public:
{ {
} }
inline uint64_t networkId() const { return _nwid; } inline uint64_t networkId() const { return _networkId; }
inline uint64_t timestamp() const { return _ts; } inline uint64_t timestamp() const { return _ts; }
inline uint32_t id() const { return _id; } inline uint32_t id() const { return _id; }
inline const uint32_t &value() const { return _value; } inline const uint32_t &value() const { return _value; }
@ -91,13 +91,13 @@ public:
*/ */
inline bool sign(const Identity &signer) inline bool sign(const Identity &signer)
{ {
try { if (signer.hasPrivate()) {
Buffer<(sizeof(Tag) * 2)> tmp; Buffer<sizeof(Tag) + 64> tmp;
_signedBy = signer.address(); _signedBy = signer.address();
this->serialize(tmp,true); this->serialize(tmp,true);
_signature = signer.sign(tmp.data(),tmp.size()); _signature = signer.sign(tmp.data(),tmp.size());
return true; return true;
} catch ( ... ) {} }
return false; return false;
} }
@ -115,7 +115,7 @@ public:
if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
// These are the same between Tag and Capability // These are the same between Tag and Capability
b.append(_nwid); b.append(_networkId);
b.append(_ts); b.append(_ts);
b.append(_id); b.append(_id);
@ -140,7 +140,7 @@ public:
unsigned int p = startAt; unsigned int p = startAt;
// These are the same between Tag and Capability // These are the same between Tag and Capability
_nwid = b.template at<uint64_t>(p); p += 8; _networkId = b.template at<uint64_t>(p); p += 8;
_ts = b.template at<uint64_t>(p); p += 8; _ts = b.template at<uint64_t>(p); p += 8;
_id = b.template at<uint32_t>(p); p += 4; _id = b.template at<uint32_t>(p); p += 4;
@ -168,8 +168,22 @@ public:
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); }
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); }
// For searching sorted arrays or lists of Tags by ID
struct IdComparePredicate
{
inline bool operator()(const Tag &a,const Tag &b) const { return (a.id() < b.id()); }
inline bool operator()(const uint32_t a,const Tag &b) const { return (a < b.id()); }
inline bool operator()(const Tag &a,const uint32_t b) const { return (a.id() < b); }
inline bool operator()(const Tag *a,const Tag *b) const { return (a->id() < b->id()); }
inline bool operator()(const Tag *a,const Tag &b) const { return (a->id() < b.id()); }
inline bool operator()(const Tag &a,const Tag *b) const { return (a.id() < b->id()); }
inline bool operator()(const uint32_t a,const Tag *b) const { return (a < b->id()); }
inline bool operator()(const Tag *a,const uint32_t b) const { return (a->id() < b); }
inline bool operator()(const uint32_t a,const uint32_t b) const { return (a < b); }
};
private: private:
uint64_t _nwid; uint64_t _networkId;
uint64_t _ts; uint64_t _ts;
uint32_t _id; uint32_t _id;
uint32_t _value; uint32_t _value;

View File

@ -17,6 +17,7 @@ OBJS=\
node/Path.o \ node/Path.o \
node/Peer.o \ node/Peer.o \
node/Poly1305.o \ node/Poly1305.o \
node/Revocation.o \
node/Salsa20.o \ node/Salsa20.o \
node/SelfAwareness.o \ node/SelfAwareness.o \
node/SHA512.o \ node/SHA512.o \