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["length"] = (unsigned int)rule.v.fwd.length;
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:
r["type"] = "ACTION_REDIRECT";
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.length = (uint16_t)(_jI(r["length"],0ULL) & 0xffffULL);
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") {
rule.t |= ZT_NETWORK_RULE_ACTION_REDIRECT;
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,
/**
* 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)
*/
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)
*/
ZT_NETWORK_RULE_ACTION_DEBUG_LOG = 4,
ZT_NETWORK_RULE_ACTION_DEBUG_LOG = 5,
/**
* Maximum ID for an ACTION, anything higher is a MATCH

View File

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

View File

@ -39,6 +39,7 @@
#include "CertificateOfMembership.hpp"
#include "Capability.hpp"
#include "Tag.hpp"
#include "Revocation.hpp"
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
SharedPtr<Network> network(RR->node->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
const uint64_t now = RR->node->now();
if ( (network) && (network->config().com) && (peer->rateGateComRequest(now)) ) {
Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS);
network->config().com.serialize(outp);
outp.append((uint8_t)0);
outp.armor(peer->key(),true);
_path->send(RR,outp.data(),outp.size(),now);
}
if ( (network) && (network->config().com) && (peer->rateGateComRequest(now)) )
network->pushCredentialsNow(peer->address(),now);
} break;
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);
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 {
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);
@ -775,6 +779,7 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
CertificateOfMembership com;
Capability cap;
Tag tag;
Revocation revocation;
bool trustEstablished = false;
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()));
if (network) {
switch (network->addCredential(com)) {
case 0: trustEstablished = true; break;
case 1: return false; // wait for WHOIS
case Membership::ADD_REJECTED:
break;
case Membership::ADD_ACCEPTED_NEW:
case Membership::ADD_ACCEPTED_REDUNDANT:
trustEstablished = true;
break;
case Membership::ADD_DEFERRED_FOR_WHOIS:
return false;
}
} 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()));
if (network) {
switch (network->addCredential(cap)) {
case 0: trustEstablished = true; break;
case 1: return false; // wait for WHOIS
case Membership::ADD_REJECTED:
break;
case Membership::ADD_ACCEPTED_NEW:
case Membership::ADD_ACCEPTED_REDUNDANT:
trustEstablished = true;
break;
case Membership::ADD_DEFERRED_FOR_WHOIS:
return false;
}
}
}
@ -811,11 +828,25 @@ bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,const S
SharedPtr<Network> network(RR->node->network(tag.networkId()));
if (network) {
switch (network->addCredential(tag)) {
case 0: trustEstablished = true; break;
case 1: return false; // wait for WHOIS
case Membership::ADD_REJECTED:
break;
case Membership::ADD_ACCEPTED_NEW:
case Membership::ADD_ACCEPTED_REDUNDANT:
trustEstablished = true;
break;
case Membership::ADD_DEFERRED_FOR_WHOIS:
return false;
}
}
}
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);
@ -932,24 +963,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *RR,cons
const uint64_t nwid = at<uint64_t>(ZT_PACKET_IDX_PAYLOAD);
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);
} catch ( ... ) {

View File

@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include "Membership.hpp"
#include "RuntimeEnvironment.hpp"
#include "Peer.hpp"
@ -28,28 +30,43 @@
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;
_lastPushAttempt = now;
try {
bool unfinished;
unsigned int localTagPtr = 0;
bool needCom = ( (nconf.com) && ( ((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) );
do {
unfinished = false;
Buffer<ZT_PROTO_MAX_PACKET_LENGTH> capsAndTags;
unsigned int appendedCaps = 0;
if (cap) {
if (localCapabilityIndex >= 0) {
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)) {
cap->serialize(capsAndTags);
cs->second.lastPushed = now;
if ( (_localCaps[localCapabilityIndex].id != nconf.capabilities[localCapabilityIndex].id()) || ((now - _localCaps[localCapabilityIndex].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) {
_localCaps[localCapabilityIndex].lastPushed = now;
_localCaps[localCapabilityIndex].id = nconf.capabilities[localCapabilityIndex].id();
nconf.capabilities[localCapabilityIndex].serialize(capsAndTags);
++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 {
capsAndTags.append((uint16_t)0);
}
@ -57,22 +74,17 @@ void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
unsigned int appendedTags = 0;
const unsigned int tagCountPos = capsAndTags.size();
capsAndTags.addSize(2);
for(unsigned int i=0;i<nconf.tagCount;++i) {
TState *const ts = _tags.get(nconf.tags[i].id());
if ((now - ts->lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) {
if ((capsAndTags.size() + sizeof(Tag)) >= (ZT_PROTO_MAX_PACKET_LENGTH - sizeof(CertificateOfMembership))) {
unfinished = true;
for(;localTagPtr<nconf.tagCount;++localTagPtr) {
if ( (_localTags[localTagPtr].id != nconf.tags[localTagPtr].id()) || ((now - _localTags[localTagPtr].lastPushed) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) {
if ((capsAndTags.size() + sizeof(Tag)) >= (ZT_PROTO_MAX_PACKET_LENGTH - sizeof(CertificateOfMembership)))
break;
}
nconf.tags[i].serialize(capsAndTags);
ts->lastPushed = now;
nconf.tags[localTagPtr].serialize(capsAndTags);
++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);
if (needCom) {
nconf.com.serialize(outp);
@ -80,110 +92,148 @@ void Membership::sendCredentialsIfNeeded(const RuntimeEnvironment *RR,const uint
}
outp.append((uint8_t)0x00);
outp.append(capsAndTags.data(),capsAndTags.size());
outp.append((uint16_t)0); // no revocations, these propagate differently
outp.compress();
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 ( ... ) {
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) {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (redundant)",com.issuedTo().toString().c_str(),com.networkId());
return 0;
const _RemoteCapability *const *c = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)id,_RemoteCredentialSorter<_RemoteCapability>());
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);
}
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) {
if (com.timestamp().first >= _com.timestamp().first) {
switch(com.verify(RR)) {
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());
_com = com;
} else {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED but not used (OK but older than current)",com.issuedTo().toString().c_str(),com.networkId());
}
} else {
TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (%d)",com.issuedTo().toString().c_str(),com.networkId(),vr);
return ADD_ACCEPTED_NEW;
case 1:
return ADD_DEFERRED_FOR_WHOIS;
}
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());
if ((t)&&(t->lastReceived != 0)&&(t->tag == tag)) {
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (redundant)",tag.issuedTo().toString().c_str(),tag.networkId());
return 0;
_RemoteTag *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialSorter<_RemoteTag>());
_RemoteTag *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteTag *)0;
if (have) {
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) {
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
if (!t) {
while (_tags.size() >= ZT_MAX_NETWORK_TAGS) {
uint32_t oldest = 0;
uint64_t oldestLastReceived = 0xffffffffffffffffULL;
uint32_t *i = (uint32_t *)0;
TState *ts = (TState *)0;
Hashtable<uint32_t,TState>::Iterator tsi(_tags);
while (tsi.next(i,ts)) {
if (ts->lastReceived < oldestLastReceived) {
oldestLastReceived = ts->lastReceived;
oldest = *i;
switch(tag.verify(RR)) {
default:
TRACE("addCredential(Tag) for %s on %.16llx REJECTED (invalid)",tag.issuedTo().toString().c_str(),tag.networkId());
return ADD_REJECTED;
case 0:
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
if (have) {
have->lastReceived = RR->node->now();
have->tag = tag;
} else {
uint64_t minlr = 0xffffffffffffffffULL;
for(unsigned int i=0;i<ZT_MAX_NETWORK_TAGS;++i) {
if (_remoteTags[i]->id == 0xffffffffffffffffULL) {
have = _remoteTags[i];
break;
} else if (_remoteTags[i]->lastReceived <= minlr) {
have = _remoteTags[i];
minlr = _remoteTags[i]->lastReceived;
}
}
if (oldestLastReceived != 0xffffffffffffffffULL)
_tags.erase(oldest);
have->lastReceived = RR->node->now();
have->tag = tag;
std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialSorter<_RemoteTag>());
}
t = &(_tags[tag.id()]);
}
if (t->tag.timestamp() <= tag.timestamp()) {
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 ADD_ACCEPTED_NEW;
case 1:
return ADD_DEFERRED_FOR_WHOIS;
}
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()));
if ((c != _caps.end())&&(c->second.lastReceived != 0)&&(c->second.cap == cap)) {
TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (redundant)",cap.issuedTo().toString().c_str(),cap.networkId());
return 0;
_RemoteCapability *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialSorter<_RemoteCapability>());
_RemoteCapability *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCapability *)0;
if (have) {
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) {
TRACE("addCredential(Capability) for %s on %.16llx ACCEPTED (new)",cap.issuedTo().toString().c_str(),cap.networkId());
if (c == _caps.end()) {
while (_caps.size() >= ZT_MAX_NETWORK_CAPABILITIES) {
std::map<uint32_t,CState>::iterator oldest;
uint64_t oldestLastReceived = 0xffffffffffffffffULL;
for(std::map<uint32_t,CState>::iterator i(_caps.begin());i!=_caps.end();++i) {
if (i->second.lastReceived < oldestLastReceived) {
oldestLastReceived = i->second.lastReceived;
oldest = i;
switch(cap.verify(RR)) {
default:
TRACE("addCredential(Tag) for %s on %.16llx REJECTED (invalid)",tag.issuedTo().toString().c_str(),tag.networkId());
return ADD_REJECTED;
case 0:
TRACE("addCredential(Tag) for %s on %.16llx ACCEPTED (new)",tag.issuedTo().toString().c_str(),tag.networkId());
if (have) {
have->lastReceived = RR->node->now();
have->cap = cap;
} else {
uint64_t minlr = 0xffffffffffffffffULL;
for(unsigned int i=0;i<ZT_MAX_NETWORK_CAPABILITIES;++i) {
if (_remoteCaps[i]->id == 0xffffffffffffffffULL) {
have = _remoteCaps[i];
break;
} else if (_remoteCaps[i]->lastReceived <= minlr) {
have = _remoteCaps[i];
minlr = _remoteCaps[i]->lastReceived;
}
}
if (oldestLastReceived != 0xffffffffffffffffULL)
_caps.erase(oldest);
have->lastReceived = RR->node->now();
have->cap = cap;
std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialSorter<_RemoteCapability>());
}
CState &c2 = _caps[cap.id()];
c2.lastReceived = RR->node->now();
c2.cap = cap;
} 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 ADD_ACCEPTED_NEW;
case 1:
return ADD_DEFERRED_FOR_WHOIS;
}
return vr;
}
} // namespace ZeroTier

View File

@ -21,14 +21,12 @@
#include <stdint.h>
#include <map>
#include "Constants.hpp"
#include "../include/ZeroTierOne.h"
#include "CertificateOfMembership.hpp"
#include "Capability.hpp"
#include "Tag.hpp"
#include "Hashtable.hpp"
#include "Revocation.hpp"
#include "NetworkConfig.hpp"
namespace ZeroTier {
@ -40,77 +38,135 @@ class Network;
* 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
* 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
{
private:
// Tags and related state
struct TState
struct _RemoteTag
{
TState() : lastPushed(0),lastReceived(0) {}
// Last time we pushed OUR tag to this peer (with this ID)
uint64_t lastPushed;
_RemoteTag() : id(0xffffffffffffffffULL),lastReceived(0),revocationThreshold(0) {}
// Tag ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
uint64_t id;
// Last time we received THEIR tag (with this ID)
uint64_t lastReceived;
// Revocation blacklist threshold or 0 if none
uint64_t revocationThreshold;
// THEIR tag
Tag tag;
};
// Credentials and related state
struct CState
struct _RemoteCapability
{
CState() : lastPushed(0),lastReceived(0) {}
// Last time we pushed OUR capability to this peer (with this ID)
uint64_t lastPushed;
_RemoteCapability() : id(0xffffffffffffffffULL),lastReceived(0),revocationThreshold(0) {}
// Capability ID (last 32 bits, first 32 bits are set in unused entries to sort them to end)
uint64_t id;
// Last time we received THEIR capability (with this ID)
uint64_t lastReceived;
// Revocation blacklist threshold or 0 if none
uint64_t revocationThreshold;
// THEIR capability
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:
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
{
public:
CapabilityIterator(const Membership &m) :
_m(m),
_i(m._caps.begin()),
_e(m._caps.end())
{
}
CapabilityIterator(const Membership &m,const NetworkConfig &nconf) :
_m(&m),
_c(&nconf),
_i(&(m._remoteCaps[0])) {}
inline const Capability *next(const NetworkConfig &nconf)
inline const Capability *next()
{
while (_i != _e) {
if ((_i->second.lastReceived)&&(_m.isCredentialTimestampValid(nconf,_i->second.cap)))
return &((_i++)->second.cap);
else ++_i;
for(;;) {
if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != 0xffffffffffffffffULL)) {
const Capability *tmp = &((*_i)->cap);
if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) {
++_i;
return tmp;
} else ++_i;
} else {
return (const Capability *)0;
}
}
return (const Capability *)0;
}
private:
const Membership &_m;
std::map<uint32_t,CState>::const_iterator _i,_e;
const Membership *_m;
const NetworkConfig *_c;
const _RemoteCapability *const *_i;
};
friend class CapabilityIterator;
Membership() :
_lastUpdatedMulticast(0),
_lastPushAttempt(0),
_lastPushedCom(0),
_blacklistBefore(0),
_com(),
_caps(),
_tags(8)
/**
* Iterator to scan forward through tags in ascending order of ID
*/
class TagIterator
{
}
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
@ -122,9 +178,10 @@ public:
* @param now Current time
* @param peerAddress Address of member peer (the one that this Membership describes)
* @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
@ -142,6 +199,8 @@ public:
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
* @return True if this peer is allowed on this network at all
*/
@ -149,126 +208,48 @@ public:
{
if (nconf.isPublic())
return true;
if ((_blacklistBefore)&&(_com.timestamp().first <= _blacklistBefore))
if ((_comRevocationThreshold)&&(_com.timestamp().first <= _comRevocationThreshold))
return false;
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 id Capablity ID
* @return Pointer to capability or NULL if not found
*/
inline 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);
}
const Capability *getCapability(const NetworkConfig &nconf,const uint32_t id) const;
/**
* @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
*
* @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
*
* @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
*
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
*/
int addCredential(const RuntimeEnvironment *RR,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);
}
}
AddCredentialResult addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const Capability &cap);
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)
uint64_t _lastUpdatedMulticast;
@ -278,17 +259,23 @@ private:
// Last time we pushed our COM to this peer
uint64_t _lastPushedCom;
// Time before which to blacklist credentials from this peer
uint64_t _blacklistBefore;
// Revocation threshold for COM or 0 if none
uint64_t _comRevocationThreshold;
// COM from this peer
// Remote member's latest network COM
CertificateOfMembership _com;
// Capability-related state (we need an ordered container here, hence std::map)
std::map<uint32_t,CState> _caps;
// Sorted (in ascending order of ID) arrays of pointers to remote tags and capabilities
_RemoteTag *_remoteTags[ZT_MAX_NETWORK_TAGS];
_RemoteCapability *_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES];
// Tag-related state
Hashtable<uint32_t,TState> _tags;
// This is the RAM allocated for remote tags and capabilities from which the sorted arrays are populated
_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

View File

@ -36,7 +36,7 @@
#include "Peer.hpp"
// Uncomment to make the rules engine dump trace info to stdout
//#define ZT_RULES_ENGINE_DEBUGGING 1
#define ZT_RULES_ENGINE_DEBUGGING 1
namespace ZeroTier {
@ -155,24 +155,21 @@ enum _doZtFilterResult
static _doZtFilterResult _doZtFilter(
const RuntimeEnvironment *RR,
const NetworkConfig &nconf,
const Membership *membership, // can be NULL
const bool inbound,
const Address &ztSource,
Address &ztDest, // MUTABLE
Address &ztDest, // MUTABLE -- is changed on REDIRECT actions
const MAC &macSource,
const MAC &macDest,
const uint8_t *const frameData,
const unsigned int frameLen,
const unsigned int etherType,
const unsigned int vlanId,
const ZT_VirtualNetworkRule *rules,
const ZT_VirtualNetworkRule *rules, // cannot be NULL
const unsigned int ruleCount,
const Tag *localTags,
const unsigned int localTagCount,
const uint32_t *const remoteTagIds,
const uint32_t *const remoteTagValues,
const unsigned int remoteTagCount,
Address &cc, // MUTABLE
unsigned int &ccLength) // MUTABLE
Address &cc, // MUTABLE -- set to TEE destination if TEE action is taken or left alone otherwise
unsigned int &ccLength, // MUTABLE -- set to length of packet payload to TEE
bool &ccWatch) // MUTABLE -- set to true for WATCH target as opposed to normal TEE
{
#ifdef ZT_RULES_ENGINE_DEBUGGING
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
case ZT_NETWORK_RULE_ACTION_TEE:
case ZT_NETWORK_RULE_ACTION_WATCH:
case ZT_NETWORK_RULE_ACTION_REDIRECT: {
const Address fwdAddr(rules[rn].v.fwd.address);
if (fwdAddr == ztSource) {
@ -242,6 +240,7 @@ static _doZtFilterResult _doZtFilter(
#endif // ZT_RULES_ENGINE_DEBUGGING
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;
ccWatch = (rt == ZT_NETWORK_RULE_ACTION_WATCH);
}
}
} continue;
@ -508,25 +507,29 @@ static _doZtFilterResult _doZtFilter(
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: {
const Tag *lt = (const Tag *)0;
for(unsigned int i=0;i<localTagCount;++i) {
if (rules[rn].v.tag.id == localTags[i].id()) {
lt = &(localTags[i]);
break;
}
}
if (!lt) {
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);
} else {
const uint32_t *rtv = (const uint32_t *)0;
for(unsigned int i=0;i<remoteTagCount;++i) {
if (rules[rn].v.tag.id == remoteTagIds[i]) {
rtv = &(remoteTagValues[i]);
break;
const Tag *const localTag = std::lower_bound(&(nconf.tags[0]),&(nconf.tags[nconf.tagCount]),rules[rn].v.tag.id,Tag::IdComparePredicate());
if ((localTag != &(nconf.tags[nconf.tagCount]))&&(localTag->id() == rules[rn].v.tag.id)) {
const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0);
if (remoteTag) {
const uint32_t ltv = localTag->value();
const uint32_t rtv = remoteTag->value();
if (rt == ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE) {
const uint32_t diff = (ltv > rtv) ? (ltv - rtv) : (rtv - ltv);
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,ltv,rtv,diff,(unsigned int)rules[rn].v.tag.value,thisRuleMatches);
} else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND) {
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 if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR) {
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 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;
}
}
if (!rtv) {
} else {
if (inbound) {
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);
@ -534,24 +537,10 @@ static _doZtFilterResult _doZtFilter(
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);
}
} 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;
@ -582,7 +571,6 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) :
_portInitialized(false),
_inboundConfigPacketId(0),
_lastConfigUpdate(0),
_lastRequestedConfiguration(0),
_destroyed(false),
_netconfFailure(NETCONF_FAILURE_NONE),
_portError(0)
@ -598,7 +586,7 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) :
if (conf.length()) {
dconf->load(conf.c_str());
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
gotConf = true;
}
@ -646,32 +634,27 @@ bool Network::filterOutgoingPacket(
const unsigned int etherType,
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();
Address ztFinalDest(ztDest);
int localCapabilityIndex = -1;
bool accept = false;
Mutex::Lock _l(_lock);
Membership *m = (Membership *)0;
unsigned int remoteTagCount = 0;
if (ztDest) {
m = &(_memberships[ztDest]);
remoteTagCount = m->getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
}
Membership *const membership = (ztDest) ? _memberships.get(ztDest) : (Membership *)0;
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:
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;
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_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
break;
@ -679,16 +662,16 @@ bool Network::filterOutgoingPacket(
case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter()
case DOZTFILTER_ACCEPT:
case DOZTFILTER_SUPER_ACCEPT: // no difference in behavior on outbound side
relevantCap = &(_config.capabilities[c]);
localCapabilityIndex = (int)c;
accept = true;
if ((!noTee)&&(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);
outp.append(_id);
outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02
outp.append((uint8_t)(ccWatch2 ? 0x16 : 0x02));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@ -715,13 +698,16 @@ bool Network::filterOutgoingPacket(
}
if (accept) {
if (membership)
membership->pushCredentials(RR,now,ztDest,_config,localCapabilityIndex,false);
if ((!noTee)&&(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);
outp.append(_id);
outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02
outp.append((uint8_t)(ccWatch ? 0x16 : 0x02));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@ -732,11 +718,11 @@ bool Network::filterOutgoingPacket(
if ((ztDest != ztFinalDest)&&(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);
outp.append(_id);
outp.append((uint8_t)0x02); // TEE/REDIRECT from outbound side: 0x02
outp.append((uint8_t)0x04);
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@ -745,11 +731,9 @@ bool Network::filterOutgoingPacket(
RR->sw->send(outp,true);
return false; // DROP locally, since we redirected
} else if (m) {
m->sendCredentialsIfNeeded(RR,now,ztDest,_config,relevantCap);
} else {
return true;
}
return true;
} else {
return false;
}
@ -765,28 +749,27 @@ int Network::filterIncomingPacket(
const unsigned int etherType,
const unsigned int vlanId)
{
uint32_t remoteTagIds[ZT_MAX_NETWORK_TAGS];
uint32_t remoteTagValues[ZT_MAX_NETWORK_TAGS];
Address ztFinalDest(ztDest);
Address cc;
unsigned int ccLength = 0;
int accept = 0;
Mutex::Lock _l(_lock);
Membership &m = _membership(sourcePeer->address());
const unsigned int remoteTagCount = m.getAllTags(_config,remoteTagIds,remoteTagValues,ZT_MAX_NETWORK_TAGS);
Membership &membership = _membership(sourcePeer->address());
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: {
Membership::CapabilityIterator mci(m);
Membership::CapabilityIterator mci(membership,_config);
const Capability *c;
while ((c = mci.next(_config))) {
ztFinalDest = ztDest; // sanity check
while ((c = mci.next())) {
ztFinalDest = ztDest; // sanity check, should be unmodified if there was no match
Address cc2;
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_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern
break;
@ -801,11 +784,11 @@ int Network::filterIncomingPacket(
if (accept) {
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);
outp.append(_id);
outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06
outp.append((uint8_t)(ccWatch2 ? 0x1c : 0x08));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@ -832,11 +815,11 @@ int Network::filterIncomingPacket(
if (accept) {
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);
outp.append(_id);
outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06
outp.append((uint8_t)(ccWatch ? 0x1c : 0x08));
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@ -846,11 +829,11 @@ int Network::filterIncomingPacket(
}
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);
outp.append(_id);
outp.append((uint8_t)0x06); // TEE/REDIRECT from inbound side: 0x06
outp.append((uint8_t)0x0a);
macDest.appendTo(outp);
macSource.appendTo(outp);
outp.append((uint16_t)etherType);
@ -892,60 +875,6 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg)
_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)
{
std::string newConfig;
@ -979,7 +908,8 @@ void Network::handleInboundConfigChunk(const uint64_t inRePacketId,const void *d
Identity controllerId(RR->topology->getIdentity(this->controller()));
if (controllerId) {
if (nc->fromDictionary(*dict)) {
this->setConfiguration(*nc,true);
Mutex::Lock _l(_lock);
this->_setConfiguration(*nc,true);
} else {
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()
{
// 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());
Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
@ -1024,9 +948,10 @@ void Network::requestConfiguration()
if (RR->localNetworkController) {
NetworkConfig nconf;
switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,nconf)) {
case NetworkController::NETCONF_QUERY_OK:
this->setConfiguration(nconf,true);
return;
case NetworkController::NETCONF_QUERY_OK: {
Mutex::Lock _l(_lock);
this->_setConfiguration(nconf,true);
} return;
case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND:
this->setNotFound();
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 (!m)
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)) {
_announceMulticastGroupsTo(peer->address(),_allMulticastGroups());
m->likingMulticasts(now);
@ -1124,9 +1049,8 @@ void Network::clean()
Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships);
while (i.next(a,m)) {
if (RR->topology->getPeerNoCache(*a))
m->clean(_config);
else _memberships.erase(*a);
if (!RR->topology->getPeerNoCache(*a))
_memberships.erase(*a);
}
}
}
@ -1177,21 +1101,25 @@ void Network::learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now)
_sendUpdatesToMembers(&mg);
}
int Network::addCredential(const CertificateOfMembership &com)
Membership::AddCredentialResult Network::addCredential(const CertificateOfMembership &com)
{
if (com.networkId() != _id)
return -1;
return Membership::ADD_REJECTED;
const Address a(com.issuedTo());
Mutex::Lock _l(_lock);
Membership &m = _membership(a);
const int result = m.addCredential(RR,com);
if (result == 0) {
m.sendCredentialsIfNeeded(RR,RR->node->now(),a,_config,(const Capability *)0);
const Membership::AddCredentialResult result = m.addCredential(RR,_config,com);
if ((result == Membership::ADD_ACCEPTED_NEW)||(result == Membership::ADD_ACCEPTED_REDUNDANT)) {
m.pushCredentials(RR,RR->node->now(),a,_config,-1,false);
RR->mc->addCredential(com,true);
}
return result;
}
Membership::AddCredentialResult Network::addCredential(const Revocation &rev)
{
}
void Network::destroy()
{
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
{
// assumes _lock is locked
@ -1308,7 +1269,7 @@ void Network::_sendUpdatesToMembers(const MulticastGroup *const newMulticastGrou
Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships);
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->likingMulticasts(now);

View File

@ -62,6 +62,11 @@ public:
*/
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
*
@ -76,14 +81,24 @@ public:
~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
*
* This applies filters from our network config and, if that doesn't match,
* 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 ztDest Destination ZeroTier address
* @param macSource Ethernet layer source address
@ -134,42 +149,10 @@ public:
const unsigned int vlanId);
/**
* @return Network ID
*/
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();
}
/**
* Check whether we are subscribed to a 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
*/
bool subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const;
@ -188,37 +171,19 @@ public:
*/
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
*
* Only chunks whose inRePacketId matches the packet ID of the last request
* are handled. If this chunk completes the config, it is decoded and
* setConfiguration() is called.
* This is called from IncomingPacket when we receive a chunk from a network
* controller.
*
* @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 chunkSize Size of data[]
* @param chunkIndex Index of chunk in full 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
@ -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()
{
@ -240,10 +205,6 @@ public:
/**
* 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();
@ -251,7 +212,7 @@ public:
* Determine whether this peer is permitted to communicate on this network
*
* 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 verb Packet verb
@ -266,7 +227,7 @@ public:
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();
@ -279,46 +240,6 @@ public:
_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)
*
@ -349,44 +270,47 @@ public:
void learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now);
/**
* @param com Certificate of membership
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
* Validate a credential and learn it if it passes certificate and other checks
*/
int addCredential(const CertificateOfMembership &com);
Membership::AddCredentialResult addCredential(const CertificateOfMembership &com);
/**
* @param cap Capability
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
* Validate a credential and learn it if it passes certificate and other checks
*/
inline int addCredential(const Capability &cap)
inline Membership::AddCredentialResult addCredential(const Capability &cap)
{
if (cap.networkId() != _id)
return -1;
return Membership::ADD_REJECTED;
Mutex::Lock _l(_lock);
return _membership(cap.issuedTo()).addCredential(RR,cap);
return _membership(cap.issuedTo()).addCredential(RR,_config,cap);
}
/**
* @param cap Tag
* @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential
* Validate a credential and learn it if it passes certificate and other checks
*/
inline int addCredential(const Tag &tag)
inline Membership::AddCredentialResult addCredential(const Tag &tag)
{
if (tag.networkId() != _id)
return -1;
return Membership::ADD_REJECTED;
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
*
* @param ts Blacklist cutoff
* Validate a credential and learn it if it passes certificate and other checks
*/
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);
_membership(peerAddress).blacklistBefore(ts);
_membership(to).pushCredentials(RR,now,to,_config,-1,true);
}
/**
@ -399,11 +323,23 @@ public:
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; }
private:
int _setConfiguration(const NetworkConfig &nconf,bool saveToDisk);
ZT_VirtualNetworkStatus _status() const;
void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked
bool _gate(const SharedPtr<Peer> &peer);
@ -412,9 +348,9 @@ private:
std::vector<MulticastGroup> _allMulticastGroups() const;
Membership &_membership(const Address &a);
const RuntimeEnvironment *RR;
const RuntimeEnvironment *const RR;
void *_uPtr;
uint64_t _id;
const uint64_t _id;
uint64_t _lastAnnouncedMulticastGroupsUpstream;
MAC _mac; // local MAC address
volatile bool _portInitialized;
@ -428,7 +364,6 @@ private:
NetworkConfig _config;
volatile uint64_t _lastConfigUpdate;
volatile uint64_t _lastRequestedConfiguration;
volatile bool _destroyed;

View File

@ -655,15 +655,27 @@ public:
*
* Flags:
* 0x01 - Certificate of network membership attached (DEPRECATED)
* 0x02 - This is a TEE'd or REDIRECT'ed packet
* 0x04 - TEE/REDIRECT'ed packet is from inbound side
* 0x02 - Most significant bit of subtype (see below)
* 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
* superset of VERB_FRAME. They're used for bridging or when we
* want to attach a certificate since FRAME does not support that.
*
* ERROR may be generated if a membership certificate is needed for a
* closed network. Payload will be network ID.
* If the ACK flag (0x08) is set, an OK(EXT_FRAME) is sent with
* no payload to acknowledge receipt of the frame.
*/
VERB_EXT_FRAME = 0x07,
@ -698,7 +710,7 @@ public:
VERB_MULTICAST_LIKE = 0x09,
/**
* Network membership credential push:
* Network credentials push:
* <[...] serialized certificate of membership>
* [<[...] additional certificates of membership>]
* <[1] 0x00, null byte marking end of COM array>
@ -706,12 +718,12 @@ public:
* <[...] one or more serialized Capability>
* <[2] 16-bit number of 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
* be pushed at any other time to keep exchanged certificates up to date.
*
* COMs and other credentials need not be for the same network, since each
* includes its own network ID and signature.
* This can be sent by anyone at any time to push network credentials.
* These will of course only be accepted if they are properly signed.
* Credentials can be for any number of networks.
*
* OK/ERROR are not generated.
*/
@ -742,23 +754,18 @@ public:
VERB_NETWORK_CONFIG_REQUEST = 0x0b,
/**
* Network configuration update push:
* <[8] network ID to refresh>
* <[2] 16-bit number of address/timestamp pairs to blacklist>
* [<[5] ZeroTier address of peer being revoked>]
* [<[8] blacklist credentials older than this timestamp>]
* [<[...] additional address/timestamp pairs>]
* Network configuration push:
* <[8] 64-bit network ID>
* <[8] 64-bit value used to group chunks in this push>
* <[2] 16-bit length of network configuration dictionary chunk>
* <[...] network configuration dictionary (may be incomplete)>
* <[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
* config be updated and push instantaneous revocations of specific peers
* or peer credentials.
*
* 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.
* This is a direct push variant for network config updates. It otherwise
* carries the same payload as OK(NETWORK_CONFIG_REQUEST). There is an
* extra number after network ID in this version that is used in place of
* the in-re packet ID sent with OKs to group chunks together.
*/
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
{
if ((!_signedBy)||(_signedBy != Network::controllerFor(_nwid)))
if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId)))
return -1;
const Identity id(RR->topology->getIdentity(_signedBy));
if (!id) {

View File

@ -67,7 +67,7 @@ public:
* @param value Tag 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),
_id(id),
_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 uint32_t id() const { return _id; }
inline const uint32_t &value() const { return _value; }
@ -91,13 +91,13 @@ public:
*/
inline bool sign(const Identity &signer)
{
try {
Buffer<(sizeof(Tag) * 2)> tmp;
if (signer.hasPrivate()) {
Buffer<sizeof(Tag) + 64> tmp;
_signedBy = signer.address();
this->serialize(tmp,true);
_signature = signer.sign(tmp.data(),tmp.size());
return true;
} catch ( ... ) {}
}
return false;
}
@ -115,7 +115,7 @@ public:
if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL);
// These are the same between Tag and Capability
b.append(_nwid);
b.append(_networkId);
b.append(_ts);
b.append(_id);
@ -140,7 +140,7 @@ public:
unsigned int p = startAt;
// 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;
_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); }
// 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:
uint64_t _nwid;
uint64_t _networkId;
uint64_t _ts;
uint32_t _id;
uint32_t _value;

View File

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