summaryrefslogtreecommitdiff
path: root/node/Network.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'node/Network.cpp')
-rw-r--r--node/Network.cpp362
1 files changed, 261 insertions, 101 deletions
diff --git a/node/Network.cpp b/node/Network.cpp
index 00c201ba..dd812cab 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -34,6 +34,7 @@
#include "NetworkController.hpp"
#include "Node.hpp"
#include "Peer.hpp"
+#include "Cluster.hpp"
// Uncomment to make the rules engine dump trace info to stdout
//#define ZT_RULES_ENGINE_DEBUGGING 1
@@ -52,13 +53,12 @@ static const char *_rtn(const ZT_VirtualNetworkRuleType rt)
case ZT_NETWORK_RULE_ACTION_TEE: return "ACTION_TEE";
case ZT_NETWORK_RULE_ACTION_WATCH: return "ACTION_WATCH";
case ZT_NETWORK_RULE_ACTION_REDIRECT: return "ACTION_REDIRECT";
- case ZT_NETWORK_RULE_ACTION_DEBUG_LOG: return "ACTION_DEBUG_LOG";
+ case ZT_NETWORK_RULE_ACTION_BREAK: return "ACTION_BREAK";
case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: return "MATCH_SOURCE_ZEROTIER_ADDRESS";
case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: return "MATCH_DEST_ZEROTIER_ADDRESS";
case ZT_NETWORK_RULE_MATCH_VLAN_ID: return "MATCH_VLAN_ID";
case ZT_NETWORK_RULE_MATCH_VLAN_PCP: return "MATCH_VLAN_PCP";
case ZT_NETWORK_RULE_MATCH_VLAN_DEI: return "MATCH_VLAN_DEI";
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE: return "MATCH_ETHERTYPE";
case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: return "MATCH_MAC_SOURCE";
case ZT_NETWORK_RULE_MATCH_MAC_DEST: return "MATCH_MAC_DEST";
case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: return "MATCH_IPV4_SOURCE";
@@ -67,6 +67,7 @@ static const char *_rtn(const ZT_VirtualNetworkRuleType rt)
case ZT_NETWORK_RULE_MATCH_IPV6_DEST: return "MATCH_IPV6_DEST";
case ZT_NETWORK_RULE_MATCH_IP_TOS: return "MATCH_IP_TOS";
case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: return "MATCH_IP_PROTOCOL";
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE: return "MATCH_ETHERTYPE";
case ZT_NETWORK_RULE_MATCH_ICMP: return "MATCH_ICMP";
case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: return "MATCH_IP_SOURCE_PORT_RANGE";
case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: return "MATCH_IP_DEST_PORT_RANGE";
@@ -111,6 +112,7 @@ static const void _dumpFilterTrace(const char *ruleName,uint8_t thisSetMatches,b
);
if (msg)
printf(" + (%s)" ZT_EOL_S,msg);
+ fflush(stdout);
}
#else
#define FILTER_TRACE(f,...) {}
@@ -177,12 +179,15 @@ static _doZtFilterResult _doZtFilter(
std::vector<std::string> dlog;
#endif // ZT_RULES_ENGINE_DEBUGGING
+ // Set to true if we are a TEE/REDIRECT/WATCH target
+ bool superAccept = false;
+
// The default match state for each set of entries starts as 'true' since an
// ACTION with no MATCH entries preceding it is always taken.
uint8_t thisSetMatches = 1;
for(unsigned int rn=0;rn<ruleCount;++rn) {
- const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[rn].t & 0x7f);
+ const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[rn].t & 0x3f);
// First check if this is an ACTION
if ((unsigned int)rt <= (unsigned int)ZT_NETWORK_RULE_ACTION__MAX_ID) {
@@ -198,7 +203,7 @@ static _doZtFilterResult _doZtFilter(
#ifdef ZT_RULES_ENGINE_DEBUGGING
_dumpFilterTrace("ACTION_ACCEPT",thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0);
#endif // ZT_RULES_ENGINE_DEBUGGING
- return DOZTFILTER_ACCEPT; // match, accept packet
+ return (superAccept ? DOZTFILTER_SUPER_ACCEPT : DOZTFILTER_ACCEPT); // match, accept packet
// These are initially handled together since preliminary logic is common
case ZT_NETWORK_RULE_ACTION_TEE:
@@ -236,7 +241,7 @@ static _doZtFilterResult _doZtFilter(
return DOZTFILTER_REDIRECT;
} else {
#ifdef ZT_RULES_ENGINE_DEBUGGING
- _dumpFilterTrace("ACTION_TEE",thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0);
+ _dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0);
dlog.clear();
#endif // ZT_RULES_ENGINE_DEBUGGING
cc = fwdAddr;
@@ -246,13 +251,12 @@ static _doZtFilterResult _doZtFilter(
}
} continue;
- // This is a no-op that exists for use with rules engine tracing and isn't for use in production
- case ZT_NETWORK_RULE_ACTION_DEBUG_LOG: // a no-op target specifically for debugging purposes
+ case ZT_NETWORK_RULE_ACTION_BREAK:
#ifdef ZT_RULES_ENGINE_DEBUGGING
- _dumpFilterTrace("ACTION_DEBUG_LOG",thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0);
+ _dumpFilterTrace("ACTION_BREAK",thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0);
dlog.clear();
#endif // ZT_RULES_ENGINE_DEBUGGING
- continue;
+ return DOZTFILTER_NO_MATCH;
// Unrecognized ACTIONs are ignored as no-ops
default:
@@ -263,6 +267,22 @@ static _doZtFilterResult _doZtFilter(
continue;
}
} else {
+ // If this is an incoming packet and we are a TEE or REDIRECT target, we should
+ // super-accept if we accept at all. This will cause us to accept redirected or
+ // tee'd packets in spite of MAC and ZT addressing checks.
+ if (inbound) {
+ switch(rt) {
+ case ZT_NETWORK_RULE_ACTION_TEE:
+ case ZT_NETWORK_RULE_ACTION_WATCH:
+ case ZT_NETWORK_RULE_ACTION_REDIRECT:
+ if (RR->identity.address() == rules[rn].v.fwd.address)
+ superAccept = true;
+ break;
+ default:
+ break;
+ }
+ }
+
#ifdef ZT_RULES_ENGINE_DEBUGGING
_dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0);
dlog.clear();
@@ -272,12 +292,14 @@ static _doZtFilterResult _doZtFilter(
}
}
- // Circuit breaker: skip further MATCH entries up to next ACTION if match state is false
- if (!thisSetMatches)
+ // Circuit breaker: no need to evaluate an AND if the set's match state
+ // is currently false since anything AND false is false.
+ if ((!thisSetMatches)&&(!(rules[rn].t & 0x40)))
continue;
// If this was not an ACTION evaluate next MATCH and update thisSetMatches with (AND [result])
uint8_t thisRuleMatches = 0;
+ uint64_t ownershipVerificationMask = 1; // this magic value means it hasn't been computed yet -- this is done lazily the first time it's needed
switch(rt) {
case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztSource.toInt());
@@ -301,10 +323,6 @@ static _doZtFilterResult _doZtFilter(
thisRuleMatches = (uint8_t)(rules[rn].v.vlanDei == 0);
FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.vlanDei,0,(unsigned int)thisRuleMatches);
break;
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
- thisRuleMatches = (uint8_t)(rules[rn].v.etherType == (uint16_t)etherType);
- FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.etherType,etherType,(unsigned int)thisRuleMatches);
- break;
case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac,6) == macSource);
FILTER_TRACE("%u %s %c %.12llx=%.12llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),rules[rn].v.mac,macSource.toInt(),(unsigned int)thisRuleMatches);
@@ -351,12 +369,14 @@ static _doZtFilterResult _doZtFilter(
break;
case ZT_NETWORK_RULE_MATCH_IP_TOS:
if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) {
- thisRuleMatches = (uint8_t)(rules[rn].v.ipTos == ((frameData[1] & 0xfc) >> 2));
- FILTER_TRACE("%u %s %c (IPv4) %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.ipTos,(unsigned int)((frameData[1] & 0xfc) >> 2),(unsigned int)thisRuleMatches);
+ //thisRuleMatches = (uint8_t)(rules[rn].v.ipTos == ((frameData[1] & 0xfc) >> 2));
+ const uint8_t tosMasked = frameData[1] & rules[rn].v.ipTos.mask;
+ thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0])&&(tosMasked <= rules[rn].v.ipTos.value[1]));
+ FILTER_TRACE("%u %s %c (IPv4) %u&%u==%u-%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)tosMasked,(unsigned int)rules[rn].v.ipTos.mask,(unsigned int)rules[rn].v.ipTos.value[0],(unsigned int)rules[rn].v.ipTos.value[1],(unsigned int)thisRuleMatches);
} else if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) {
- const uint8_t trafficClass = ((frameData[0] << 4) & 0xf0) | ((frameData[1] >> 4) & 0x0f);
- thisRuleMatches = (uint8_t)(rules[rn].v.ipTos == ((trafficClass & 0xfc) >> 2));
- FILTER_TRACE("%u %s %c (IPv6) %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.ipTos,(unsigned int)((trafficClass & 0xfc) >> 2),(unsigned int)thisRuleMatches);
+ const uint8_t tosMasked = (((frameData[0] << 4) & 0xf0) | ((frameData[1] >> 4) & 0x0f)) & rules[rn].v.ipTos.mask;
+ thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0])&&(tosMasked <= rules[rn].v.ipTos.value[1]));
+ FILTER_TRACE("%u %s %c (IPv4) %u&%u==%u-%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)tosMasked,(unsigned int)rules[rn].v.ipTos.mask,(unsigned int)rules[rn].v.ipTos.value[0],(unsigned int)rules[rn].v.ipTos.value[1],(unsigned int)thisRuleMatches);
} else {
thisRuleMatches = 0;
FILTER_TRACE("%u %s %c [frame not IP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='));
@@ -380,10 +400,14 @@ static _doZtFilterResult _doZtFilter(
FILTER_TRACE("%u %s %c [frame not IP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='));
}
break;
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+ thisRuleMatches = (uint8_t)(rules[rn].v.etherType == (uint16_t)etherType);
+ FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.etherType,etherType,(unsigned int)thisRuleMatches);
+ break;
case ZT_NETWORK_RULE_MATCH_ICMP:
if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) {
- if (frameData[9] == 0x01) {
- const unsigned int ihl = (frameData[0] & 0xf) * 32;
+ if (frameData[9] == 0x01) { // IP protocol == ICMP
+ const unsigned int ihl = (frameData[0] & 0xf) * 4;
if (frameLen >= (ihl + 2)) {
if (rules[rn].v.icmp.type == frameData[ihl]) {
if ((rules[rn].v.icmp.flags & 0x01) != 0) {
@@ -416,7 +440,7 @@ static _doZtFilterResult _doZtFilter(
} else {
thisRuleMatches = 0;
}
- FILTER_TRACE("%u %s %c (IPv4) icmp-type:%d==%d icmp-code:%d==%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(int)frameData[pos],(int)rules[rn].v.icmp.type,(int)frameData[pos+1],(((rules[rn].v.icmp.flags & 0x01) != 0) ? (int)rules[rn].v.icmp.code : -1),(unsigned int)thisRuleMatches);
+ FILTER_TRACE("%u %s %c (IPv6) icmp-type:%d==%d icmp-code:%d==%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(int)frameData[pos],(int)rules[rn].v.icmp.type,(int)frameData[pos+1],(((rules[rn].v.icmp.flags & 0x01) != 0) ? (int)rules[rn].v.icmp.code : -1),(unsigned int)thisRuleMatches);
} else {
thisRuleMatches = 0;
FILTER_TRACE("%u %s %c [frame not ICMPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='));
@@ -484,6 +508,47 @@ static _doZtFilterResult _doZtFilter(
uint64_t cf = (inbound) ? ZT_RULE_PACKET_CHARACTERISTICS_INBOUND : 0ULL;
if (macDest.isMulticast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST;
if (macDest.isBroadcast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST;
+ if (ownershipVerificationMask == 1) {
+ ownershipVerificationMask = 0;
+ InetAddress src;
+ if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) {
+ src.set((const void *)(frameData + 12),4,0);
+ } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) {
+ // IPv6 NDP requires special handling, since the src and dest IPs in the packet are empty or link-local.
+ if ( (frameLen >= (40 + 8 + 16)) && (frameData[6] == 0x3a) && ((frameData[40] == 0x87)||(frameData[40] == 0x88)) ) {
+ if (frameData[40] == 0x87) {
+ // Neighbor solicitations contain no reliable source address, so we implement a small
+ // hack by considering them authenticated. Otherwise you would pretty much have to do
+ // this manually in the rule set for IPv6 to work at all.
+ ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
+ } else {
+ // Neighbor advertisements on the other hand can absolutely be authenticated.
+ src.set((const void *)(frameData + 40 + 8),16,0);
+ }
+ } else {
+ // Other IPv6 packets can be handled normally
+ src.set((const void *)(frameData + 8),16,0);
+ }
+ } else if ((etherType == ZT_ETHERTYPE_ARP)&&(frameLen >= 28)) {
+ src.set((const void *)(frameData + 14),4,0);
+ }
+ if (inbound) {
+ if (membership) {
+ if ((src)&&(membership->hasCertificateOfOwnershipFor(nconf,src)))
+ ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
+ if (membership->hasCertificateOfOwnershipFor(nconf,macSource))
+ ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED;
+ }
+ } else {
+ for(unsigned int i=0;i<nconf.certificateOfOwnershipCount;++i) {
+ if ((src)&&(nconf.certificatesOfOwnership[i].owns(src)))
+ ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED;
+ if (nconf.certificatesOfOwnership[i].owns(macSource))
+ ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED;
+ }
+ }
+ }
+ cf |= ownershipVerificationMask;
if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)&&(frameData[9] == 0x06)) {
const unsigned int headerLen = 4 * (frameData[0] & 0xf);
cf |= (uint64_t)frameData[headerLen + 13];
@@ -497,8 +562,8 @@ static _doZtFilterResult _doZtFilter(
}
}
}
- thisRuleMatches = (uint8_t)((cf | rules[rn].v.characteristics) != 0);
- FILTER_TRACE("%u %s %c (%.16llx & %.16llx)==%.16llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),cf,rules[rn].v.characteristics[0],rules[rn].v.characteristics[1],(unsigned int)thisRuleMatches);
+ thisRuleMatches = (uint8_t)((cf & rules[rn].v.characteristics) != 0);
+ FILTER_TRACE("%u %s %c (%.16llx | %.16llx)!=0 -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),cf,rules[rn].v.characteristics,(unsigned int)thisRuleMatches);
} break;
case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
thisRuleMatches = (uint8_t)((frameLen >= (unsigned int)rules[rn].v.frameSize[0])&&(frameLen <= (unsigned int)rules[rn].v.frameSize[1]));
@@ -539,12 +604,17 @@ static _doZtFilterResult _doZtFilter(
thisRuleMatches = 0;
}
} else {
- if (inbound) {
+ if ((inbound)&&(!superAccept)) {
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);
} else {
+ // Outbound side is not strict since if we have to match both tags and
+ // we are sending a first packet to a recipient, we probably do not know
+ // about their tags yet. They will filter on inbound and we will filter
+ // once we get their tag. If we are a tee/redirect target we are also
+ // not strict since we likely do not have these tags.
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 and TEE/REDIRECT targets are not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
}
}
} else {
@@ -552,6 +622,38 @@ static _doZtFilterResult _doZtFilter(
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;
+ case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
+ case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: {
+ if (superAccept) {
+ thisRuleMatches = 1;
+ FILTER_TRACE("%u %s %c we are a TEE/REDIRECT target -> 1",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='));
+ } else if ( ((rt == ZT_NETWORK_RULE_MATCH_TAG_SENDER)&&(inbound)) || ((rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER)&&(!inbound)) ) {
+ const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0);
+ if (remoteTag) {
+ thisRuleMatches = (uint8_t)(remoteTag->value() == rules[rn].v.tag.value);
+ FILTER_TRACE("%u %s %c TAG %u %.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,remoteTag->value(),(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
+ } else {
+ if (rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER) {
+ // If we are checking the receiver and this is an outbound packet, we
+ // can't be strict since we may not yet know the receiver's tag.
+ thisRuleMatches = 1;
+ FILTER_TRACE("%u %s %c (inbound) remote tag %u not found -> 1 (outbound receiver match is not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
+ } else {
+ thisRuleMatches = 0;
+ FILTER_TRACE("%u %s %c (inbound) remote tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id);
+ }
+ }
+ } else { // sender and outbound or receiver and inbound
+ 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)) {
+ thisRuleMatches = (uint8_t)(localTag->value() == rules[rn].v.tag.value);
+ FILTER_TRACE("%u %s %c TAG %u %.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,localTag->value(),(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches);
+ } 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;
// The result of an unsupported MATCH is configurable at the network
// level via a flag.
@@ -560,8 +662,9 @@ static _doZtFilterResult _doZtFilter(
break;
}
- // State of equals state AND result of last MATCH (possibly NOTed depending on bit 0x80)
- thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
+ if ((rules[rn].t & 0x40))
+ thisSetMatches |= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
+ else thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1));
}
return DOZTFILTER_NO_MATCH;
@@ -597,7 +700,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;
}
@@ -886,7 +989,7 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg)
_myMulticastGroups.erase(i);
}
-uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
+uint64_t Network::handleConfigChunk(const uint64_t packetId,const Address &source,const Buffer<ZT_PROTO_MAX_PACKET_LENGTH> &chunk,unsigned int ptr)
{
const unsigned int start = ptr;
@@ -909,12 +1012,12 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
chunkIndex = chunk.at<uint32_t>(ptr); ptr += 4;
if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) { // >= since we need room for a null at the end
- TRACE("discarded chunk from %s: invalid length or length overflow",chunk.source().toString().c_str());
+ TRACE("discarded chunk from %s: invalid length or length overflow",source.toString().c_str());
return 0;
}
if ((chunk[ptr] != 1)||(chunk.at<uint16_t>(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) {
- TRACE("discarded chunk from %s: unrecognized signature type",chunk.source().toString().c_str());
+ TRACE("discarded chunk from %s: unrecognized signature type",source.toString().c_str());
return 0;
}
const uint8_t *sig = reinterpret_cast<const uint8_t *>(chunk.field(ptr + 3,ZT_C25519_SIGNATURE_LEN));
@@ -942,30 +1045,35 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
// If it's not a duplicate, check chunk signature
const Identity controllerId(RR->topology->getIdentity(controller()));
if (!controllerId) { // we should always have the controller identity by now, otherwise how would we have queried it the first time?
- TRACE("unable to verify chunk from %s: don't have controller identity",chunk.source().toString().c_str());
+ TRACE("unable to verify chunk from %s: don't have controller identity",source.toString().c_str());
return 0;
}
if (!controllerId.verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) {
- TRACE("discarded chunk from %s: signature check failed",chunk.source().toString().c_str());
+ TRACE("discarded chunk from %s: signature check failed",source.toString().c_str());
return 0;
}
+#ifdef ZT_ENABLE_CLUSTER
+ if ((source)&&(RR->cluster))
+ RR->cluster->broadcastNetworkConfigChunk(chunk.field(start,chunk.size() - start),chunk.size() - start);
+#endif
+
// New properly verified chunks can be flooded "virally" through the network
if (fastPropagate) {
Address *a = (Address *)0;
Membership *m = (Membership *)0;
Hashtable<Address,Membership>::Iterator i(_memberships);
while (i.next(a,m)) {
- if ((*a != chunk.source())&&(*a != controller())) {
+ if ((*a != source)&&(*a != controller())) {
Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CONFIG);
outp.append(reinterpret_cast<const uint8_t *>(chunk.data()) + start,chunk.size() - start);
RR->sw->send(outp,true);
}
}
}
- } else if (chunk.source() == controller()) {
+ } else if ((source == controller())||(!source)) { // since old chunks aren't signed, only accept from controller itself (or via cluster backplane)
// Legacy support for OK(NETWORK_CONFIG_REQUEST) from older controllers
- chunkId = chunk.packetId();
+ chunkId = packetId;
configUpdateId = chunkId;
totalLength = chunkLen;
chunkIndex = 0;
@@ -977,6 +1085,11 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
if ((!c)||(_incomingConfigChunks[i].ts < c->ts))
c = &(_incomingConfigChunks[i]);
}
+
+#ifdef ZT_ENABLE_CLUSTER
+ if ((source)&&(RR->cluster))
+ RR->cluster->broadcastNetworkConfigChunk(chunk.field(start,chunk.size() - start),chunk.size() - start);
+#endif
} else {
TRACE("discarded single-chunk unsigned legacy config: this is only allowed if the sender is the controller itself");
return 0;
@@ -1013,7 +1126,7 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
}
if (nc) {
- this->_setConfiguration(*nc,true);
+ this->setConfiguration(*nc,true);
delete nc;
return configUpdateId;
} else {
@@ -1023,8 +1136,114 @@ uint64_t Network::handleConfigChunk(const Packet &chunk,unsigned int ptr)
return 0;
}
+int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
+{
+ // _lock is NOT locked when this is called
+ 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;
+ bool oldPortInitialized;
+ {
+ Mutex::Lock _l(_lock);
+ _config = nconf;
+ _lastConfigUpdate = RR->node->now();
+ _netconfFailure = NETCONF_FAILURE_NONE;
+ oldPortInitialized = _portInitialized;
+ _portInitialized = true;
+ _externalConfig(&ctmp);
+ }
+ _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
+
+ if (saveToDisk) {
+ Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
+ try {
+ char n[64];
+ Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
+ if (nconf.toDictionary(*d,false))
+ RR->node->dataStorePut(n,(const void *)d->data(),d->sizeBytes(),true);
+ } catch ( ... ) {}
+ delete d;
+ }
+
+ return 2; // OK and configuration has changed
+ } catch ( ... ) {
+ TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id);
+ }
+ return 0;
+}
+
void Network::requestConfiguration()
{
+ /* ZeroTier addresses can't begin with 0xff, so this is used to mark controllerless
+ * network IDs. Controllerless network IDs only support unicast IPv6 using the 6plane
+ * addressing scheme and have the following format: 0xffSSSSEEEE000000 where SSSS
+ * is the 16-bit starting IP port range allowed and EEEE is the 16-bit ending IP port
+ * range allowed. Remaining digits are reserved for future use and must be zero. */
+ if ((_id >> 56) == 0xff) {
+ const uint16_t startPortRange = (uint16_t)((_id >> 40) & 0xffff);
+ const uint16_t endPortRange = (uint16_t)((_id >> 24) & 0xffff);
+ if (((_id & 0xffffff) == 0)&&(endPortRange >= startPortRange)) {
+ NetworkConfig *const nconf = new NetworkConfig();
+
+ nconf->networkId = _id;
+ nconf->timestamp = RR->node->now();
+ nconf->credentialTimeMaxDelta = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA;
+ nconf->revision = 1;
+ nconf->issuedTo = RR->identity.address();
+ nconf->flags = ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
+ nconf->staticIpCount = 1;
+ nconf->ruleCount = 14;
+ nconf->staticIps[0] = InetAddress::makeIpv66plane(_id,RR->identity.address().toInt());
+
+ // Drop everything but IPv6
+ nconf->rules[0].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE | 0x80; // NOT
+ nconf->rules[0].v.etherType = 0x86dd; // IPv6
+ nconf->rules[1].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP;
+
+ // Allow ICMPv6
+ nconf->rules[2].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
+ nconf->rules[2].v.ipProtocol = 0x3a; // ICMPv6
+ nconf->rules[3].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+ // Allow destination ports within range
+ nconf->rules[4].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
+ nconf->rules[4].v.ipProtocol = 0x11; // UDP
+ nconf->rules[5].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL | 0x40; // OR
+ nconf->rules[5].v.ipProtocol = 0x06; // TCP
+ nconf->rules[6].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE;
+ nconf->rules[6].v.port[0] = startPortRange;
+ nconf->rules[6].v.port[1] = endPortRange;
+ nconf->rules[7].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+ // Allow non-SYN TCP packets to permit non-connection-initiating traffic
+ nconf->rules[8].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS | 0x80; // NOT
+ nconf->rules[8].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN;
+ nconf->rules[9].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+ // Also allow SYN+ACK which are replies to SYN
+ nconf->rules[10].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
+ nconf->rules[10].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN;
+ nconf->rules[11].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
+ nconf->rules[11].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_ACK;
+ nconf->rules[12].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT;
+
+ nconf->rules[13].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP;
+
+ nconf->type = ZT_NETWORK_TYPE_PUBLIC;
+ Utils::snprintf(nconf->name,sizeof(nconf->name),"adhoc-%.04x-%.04x",(int)startPortRange,(int)endPortRange);
+
+ this->setConfiguration(*nconf,false);
+ delete nconf;
+ } else {
+ this->setNotFound();
+ }
+ return;
+ }
+
const Address ctrl(controller());
Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> rmd;
@@ -1040,30 +1259,10 @@ void Network::requestConfiguration()
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS,(uint64_t)ZT_MAX_NETWORK_TAGS);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS,(uint64_t)0);
rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,(uint64_t)ZT_RULES_ENGINE_REVISION);
- rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_RELAY_POLICY,(uint64_t)RR->node->relayPolicy());
if (ctrl == RR->identity.address()) {
if (RR->localNetworkController) {
- NetworkConfig *nconf = new NetworkConfig();
- try {
- switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,*nconf)) {
- case NetworkController::NETCONF_QUERY_OK:
- this->_setConfiguration(*nconf,true);
- break;
- case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND:
- this->setNotFound();
- break;
- case NetworkController::NETCONF_QUERY_ACCESS_DENIED:
- this->setAccessDenied();
- break;
- default:
- this->setNotFound();
- break;
- }
- } catch ( ... ) {
- this->setNotFound();
- }
- delete nconf;
+ RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,rmd);
} else {
this->setNotFound();
}
@@ -1098,8 +1297,8 @@ bool Network::gate(const SharedPtr<Peer> &peer)
if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config))) ) {
if (!m)
m = &(_membership(peer->address()));
- m->pushCredentials(RR,now,peer->address(),_config,-1,false);
if (m->shouldLikeMulticasts(now)) {
+ m->pushCredentials(RR,now,peer->address(),_config,-1,false);
_announceMulticastGroupsTo(peer->address(),_allMulticastGroups());
m->likingMulticasts(now);
}
@@ -1224,6 +1423,7 @@ Membership::AddCredentialResult Network::addCredential(const Address &sentFrom,c
outp.append((uint16_t)0); // no tags
outp.append((uint16_t)1); // one revocation!
rev.serialize(outp);
+ outp.append((uint16_t)0); // no certificates of ownership
RR->sw->send(outp,true);
}
}
@@ -1255,46 +1455,6 @@ ZT_VirtualNetworkStatus Network::_status() const
}
}
-int Network::_setConfiguration(const NetworkConfig &nconf,bool saveToDisk)
-{
- // _lock is NOT locked when this is called
- 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;
- bool oldPortInitialized;
- {
- Mutex::Lock _l(_lock);
- _config = nconf;
- _lastConfigUpdate = RR->node->now();
- _netconfFailure = NETCONF_FAILURE_NONE;
- oldPortInitialized = _portInitialized;
- _portInitialized = true;
- _externalConfig(&ctmp);
- }
- _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp);
-
- if (saveToDisk) {
- Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> *d = new Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY>();
- try {
- char n[64];
- Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id);
- if (nconf.toDictionary(*d,false))
- RR->node->dataStorePut(n,(const void *)d->data(),d->sizeBytes(),true);
- } catch ( ... ) {}
- delete d;
- }
-
- 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