summaryrefslogtreecommitdiff
path: root/controller
diff options
context:
space:
mode:
Diffstat (limited to 'controller')
-rw-r--r--controller/EmbeddedNetworkController.cpp2062
-rw-r--r--controller/EmbeddedNetworkController.hpp138
-rw-r--r--controller/JSONDB.cpp184
-rw-r--r--controller/JSONDB.hpp118
-rw-r--r--controller/README.md23
-rw-r--r--controller/migrate-sqlite/migrate.js320
-rw-r--r--controller/migrate-sqlite/package.json15
7 files changed, 1823 insertions, 1037 deletions
diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp
index aa95ac64..b731db83 100644
--- a/controller/EmbeddedNetworkController.cpp
+++ b/controller/EmbeddedNetworkController.cpp
@@ -21,7 +21,10 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
+
+#ifndef _WIN32
#include <sys/time.h>
+#endif
#include <sys/types.h>
#include <algorithm>
@@ -60,74 +63,13 @@ using json = nlohmann::json;
namespace ZeroTier {
-// JSON blob I/O
-static json _readJson(const std::string &path)
-{
- std::string buf;
- if (OSUtils::readFile(path.c_str(),buf)) {
- try {
- return json::parse(buf);
- } catch ( ... ) {}
- }
- return json::object();
-}
-static bool _writeJson(const std::string &path,const json &obj)
-{
- return OSUtils::writeFile(path.c_str(),obj.dump(2));
-}
-
-// Get JSON values as unsigned integers, strings, or booleans, doing type conversion if possible
-static uint64_t _jI(const json &jv,const uint64_t dfl)
-{
- if (jv.is_number()) {
- return (uint64_t)jv;
- } else if (jv.is_string()) {
- std::string s = jv;
- return Utils::strToU64(s.c_str());
- } else if (jv.is_boolean()) {
- return ((bool)jv ? 1ULL : 0ULL);
- }
- return dfl;
-}
-static bool _jB(const json &jv,const bool dfl)
-{
- if (jv.is_boolean()) {
- return (bool)jv;
- } else if (jv.is_number()) {
- return ((uint64_t)jv > 0ULL);
- } else if (jv.is_string()) {
- std::string s = jv;
- if (s.length() > 0) {
- switch(s[0]) {
- case 't':
- case 'T':
- case '1':
- return true;
- }
- }
- return false;
- }
- return dfl;
-}
-static std::string _jS(const json &jv,const char *dfl)
-{
- if (jv.is_string()) {
- return jv;
- } else if (jv.is_number()) {
- char tmp[64];
- Utils::snprintf(tmp,sizeof(tmp),"%llu",(uint64_t)jv);
- return tmp;
- } else if (jv.is_boolean()) {
- return ((bool)jv ? std::string("1") : std::string("0"));
- }
- return std::string((dfl) ? dfl : "");
-}
-
static json _renderRule(ZT_VirtualNetworkRule &rule)
{
char tmp[128];
json r = json::object();
- switch((rule.t) & 0x7f) {
+ const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rule.t & 0x3f);
+
+ switch(rt) {
case ZT_NETWORK_RULE_ACTION_DROP:
r["type"] = "ACTION_DROP";
break;
@@ -151,149 +93,151 @@ static json _renderRule(ZT_VirtualNetworkRule &rule)
r["address"] = Address(rule.v.fwd.address).toString();
r["flags"] = (unsigned int)rule.v.fwd.flags;
break;
- case ZT_NETWORK_RULE_ACTION_DEBUG_LOG:
- r["type"] = "ACTION_DEBUG_LOG";
- break;
- case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
- r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS";
- r["not"] = ((rule.t & 0x80) != 0);
- r["zt"] = Address(rule.v.zt).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
- r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS";
- r["not"] = ((rule.t & 0x80) != 0);
- r["zt"] = Address(rule.v.zt).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_VLAN_ID:
- r["type"] = "MATCH_VLAN_ID";
- r["not"] = ((rule.t & 0x80) != 0);
- r["vlanId"] = (unsigned int)rule.v.vlanId;
- break;
- case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
- r["type"] = "MATCH_VLAN_PCP";
- r["not"] = ((rule.t & 0x80) != 0);
- r["vlanPcp"] = (unsigned int)rule.v.vlanPcp;
- break;
- case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
- r["type"] = "MATCH_VLAN_DEI";
- r["not"] = ((rule.t & 0x80) != 0);
- r["vlanDei"] = (unsigned int)rule.v.vlanDei;
- break;
- case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
- r["type"] = "MATCH_ETHERTYPE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["etherType"] = (unsigned int)rule.v.etherType;
- break;
- case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
- r["type"] = "MATCH_MAC_SOURCE";
- r["not"] = ((rule.t & 0x80) != 0);
- Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
- r["mac"] = tmp;
+ case ZT_NETWORK_RULE_ACTION_BREAK:
+ r["type"] = "ACTION_BREAK";
break;
- case ZT_NETWORK_RULE_MATCH_MAC_DEST:
- r["type"] = "MATCH_MAC_DEST";
- r["not"] = ((rule.t & 0x80) != 0);
- Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
- r["mac"] = tmp;
- break;
- case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
- r["type"] = "MATCH_IPV4_SOURCE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
- r["type"] = "MATCH_IPV4_DEST";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
- r["type"] = "MATCH_IPV6_SOURCE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
- r["type"] = "MATCH_IPV6_DEST";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
- break;
- case ZT_NETWORK_RULE_MATCH_IP_TOS:
- r["type"] = "MATCH_IP_TOS";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ipTos"] = (unsigned int)rule.v.ipTos;
- break;
- case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
- r["type"] = "MATCH_IP_PROTOCOL";
- r["not"] = ((rule.t & 0x80) != 0);
- r["ipProtocol"] = (unsigned int)rule.v.ipProtocol;
- break;
- case ZT_NETWORK_RULE_MATCH_ICMP:
- r["type"] = "MATCH_ICMP";
- r["not"] = ((rule.t & 0x80) != 0);
- r["type"] = (unsigned int)rule.v.icmp.type;
- if ((rule.v.icmp.flags & 0x01) != 0)
- r["code"] = (unsigned int)rule.v.icmp.code;
- else r["code"] = json();
- break;
- case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
- r["type"] = "MATCH_IP_SOURCE_PORT_RANGE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["start"] = (unsigned int)rule.v.port[0];
- r["end"] = (unsigned int)rule.v.port[1];
- break;
- case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
- r["type"] = "MATCH_IP_DEST_PORT_RANGE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["start"] = (unsigned int)rule.v.port[0];
- r["end"] = (unsigned int)rule.v.port[1];
- break;
- case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
- r["type"] = "MATCH_CHARACTERISTICS";
- r["not"] = ((rule.t & 0x80) != 0);
- Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics);
- r["mask"] = tmp;
+ default:
break;
- case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
- r["type"] = "MATCH_FRAME_SIZE_RANGE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["start"] = (unsigned int)rule.v.frameSize[0];
- r["end"] = (unsigned int)rule.v.frameSize[1];
- break;
- case ZT_NETWORK_RULE_MATCH_RANDOM:
- r["type"] = "MATCH_RANDOM";
- r["not"] = ((rule.t & 0x80) != 0);
- r["probability"] = (unsigned long)rule.v.randomProbability;
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
- r["type"] = "MATCH_TAGS_DIFFERENCE";
- r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
- r["type"] = "MATCH_TAGS_BITWISE_AND";
- r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
- r["type"] = "MATCH_TAGS_BITWISE_OR";
- r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
- r["type"] = "MATCH_TAGS_BITWISE_XOR";
- r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
- case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
- r["type"] = "MATCH_TAGS_EQUAL";
+ }
+
+ if (r.size() == 0) {
+ switch(rt) {
+ case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS:
+ r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS";
+ r["zt"] = Address(rule.v.zt).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS:
+ r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS";
+ r["zt"] = Address(rule.v.zt).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_VLAN_ID:
+ r["type"] = "MATCH_VLAN_ID";
+ r["vlanId"] = (unsigned int)rule.v.vlanId;
+ break;
+ case ZT_NETWORK_RULE_MATCH_VLAN_PCP:
+ r["type"] = "MATCH_VLAN_PCP";
+ r["vlanPcp"] = (unsigned int)rule.v.vlanPcp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_VLAN_DEI:
+ r["type"] = "MATCH_VLAN_DEI";
+ r["vlanDei"] = (unsigned int)rule.v.vlanDei;
+ break;
+ case ZT_NETWORK_RULE_MATCH_MAC_SOURCE:
+ r["type"] = "MATCH_MAC_SOURCE";
+ Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
+ r["mac"] = tmp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_MAC_DEST:
+ r["type"] = "MATCH_MAC_DEST";
+ Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]);
+ r["mac"] = tmp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE:
+ r["type"] = "MATCH_IPV4_SOURCE";
+ r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV4_DEST:
+ r["type"] = "MATCH_IPV4_DEST";
+ r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE:
+ r["type"] = "MATCH_IPV6_SOURCE";
+ r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IPV6_DEST:
+ r["type"] = "MATCH_IPV6_DEST";
+ r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_TOS:
+ r["type"] = "MATCH_IP_TOS";
+ r["mask"] = (unsigned int)rule.v.ipTos.mask;
+ r["start"] = (unsigned int)rule.v.ipTos.value[0];
+ r["end"] = (unsigned int)rule.v.ipTos.value[1];
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL:
+ r["type"] = "MATCH_IP_PROTOCOL";
+ r["ipProtocol"] = (unsigned int)rule.v.ipProtocol;
+ break;
+ case ZT_NETWORK_RULE_MATCH_ETHERTYPE:
+ r["type"] = "MATCH_ETHERTYPE";
+ r["etherType"] = (unsigned int)rule.v.etherType;
+ break;
+ case ZT_NETWORK_RULE_MATCH_ICMP:
+ r["type"] = "MATCH_ICMP";
+ r["icmpType"] = (unsigned int)rule.v.icmp.type;
+ if ((rule.v.icmp.flags & 0x01) != 0)
+ r["icmpCode"] = (unsigned int)rule.v.icmp.code;
+ else r["icmpCode"] = json();
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE:
+ r["type"] = "MATCH_IP_SOURCE_PORT_RANGE";
+ r["start"] = (unsigned int)rule.v.port[0];
+ r["end"] = (unsigned int)rule.v.port[1];
+ break;
+ case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE:
+ r["type"] = "MATCH_IP_DEST_PORT_RANGE";
+ r["start"] = (unsigned int)rule.v.port[0];
+ r["end"] = (unsigned int)rule.v.port[1];
+ break;
+ case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS:
+ r["type"] = "MATCH_CHARACTERISTICS";
+ Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics);
+ r["mask"] = tmp;
+ break;
+ case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE:
+ r["type"] = "MATCH_FRAME_SIZE_RANGE";
+ r["start"] = (unsigned int)rule.v.frameSize[0];
+ r["end"] = (unsigned int)rule.v.frameSize[1];
+ break;
+ case ZT_NETWORK_RULE_MATCH_RANDOM:
+ r["type"] = "MATCH_RANDOM";
+ r["probability"] = (unsigned long)rule.v.randomProbability;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE:
+ r["type"] = "MATCH_TAGS_DIFFERENCE";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND:
+ r["type"] = "MATCH_TAGS_BITWISE_AND";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR:
+ r["type"] = "MATCH_TAGS_BITWISE_OR";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR:
+ r["type"] = "MATCH_TAGS_BITWISE_XOR";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL:
+ r["type"] = "MATCH_TAGS_EQUAL";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAG_SENDER:
+ r["type"] = "MATCH_TAG_SENDER";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER:
+ r["type"] = "MATCH_TAG_RECEIVER";
+ r["id"] = rule.v.tag.id;
+ r["value"] = rule.v.tag.value;
+ break;
+ default:
+ break;
+ }
+
+ if (r.size() > 0) {
r["not"] = ((rule.t & 0x80) != 0);
- r["id"] = rule.v.tag.id;
- r["value"] = rule.v.tag.value;
- break;
+ r["or"] = ((rule.t & 0x40) != 0);
+ }
}
+
return r;
}
@@ -301,11 +245,17 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
{
if (!r.is_object())
return false;
- const std::string t(_jS(r["type"],""));
+
+ const std::string t(OSUtils::jsonString(r["type"],""));
memset(&rule,0,sizeof(ZT_VirtualNetworkRule));
- if (_jB(r["not"],false))
+
+ if (OSUtils::jsonBool(r["not"],false))
rule.t = 0x80;
else rule.t = 0x00;
+ if (OSUtils::jsonBool(r["or"],false))
+ rule.t |= 0x40;
+
+ bool tag = false;
if (t == "ACTION_DROP") {
rule.t |= ZT_NETWORK_RULE_ACTION_DROP;
return true;
@@ -314,115 +264,117 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
return true;
} else if (t == "ACTION_TEE") {
rule.t |= ZT_NETWORK_RULE_ACTION_TEE;
- 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);
+ rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL;
+ rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL);
+ rule.v.fwd.length = (uint16_t)(OSUtils::jsonInt(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);
+ rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL;
+ rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL);
+ rule.v.fwd.length = (uint16_t)(OSUtils::jsonInt(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;
- rule.v.fwd.flags = (uint32_t)(_jI(r["flags"],0ULL) & 0xffffffffULL);
+ rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL;
+ rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL);
return true;
- } else if (t == "ACTION_DEBUG_LOG") {
- rule.t |= ZT_NETWORK_RULE_ACTION_DEBUG_LOG;
+ } else if (t == "ACTION_BREAK") {
+ rule.t |= ZT_NETWORK_RULE_ACTION_BREAK;
return true;
} else if (t == "MATCH_SOURCE_ZEROTIER_ADDRESS") {
rule.t |= ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS;
- rule.v.zt = Utils::hexStrToU64(_jS(r["zt"],"0").c_str()) & 0xffffffffffULL;
+ rule.v.zt = Utils::hexStrToU64(OSUtils::jsonString(r["zt"],"0").c_str()) & 0xffffffffffULL;
return true;
} else if (t == "MATCH_DEST_ZEROTIER_ADDRESS") {
rule.t |= ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS;
- rule.v.zt = Utils::hexStrToU64(_jS(r["zt"],"0").c_str()) & 0xffffffffffULL;
+ rule.v.zt = Utils::hexStrToU64(OSUtils::jsonString(r["zt"],"0").c_str()) & 0xffffffffffULL;
return true;
} else if (t == "MATCH_VLAN_ID") {
rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_ID;
- rule.v.vlanId = (uint16_t)(_jI(r["vlanId"],0ULL) & 0xffffULL);
+ rule.v.vlanId = (uint16_t)(OSUtils::jsonInt(r["vlanId"],0ULL) & 0xffffULL);
return true;
} else if (t == "MATCH_VLAN_PCP") {
rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_PCP;
- rule.v.vlanPcp = (uint8_t)(_jI(r["vlanPcp"],0ULL) & 0xffULL);
+ rule.v.vlanPcp = (uint8_t)(OSUtils::jsonInt(r["vlanPcp"],0ULL) & 0xffULL);
return true;
} else if (t == "MATCH_VLAN_DEI") {
rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_DEI;
- rule.v.vlanDei = (uint8_t)(_jI(r["vlanDei"],0ULL) & 0xffULL);
- return true;
- } else if (t == "MATCH_ETHERTYPE") {
- rule.t |= ZT_NETWORK_RULE_MATCH_ETHERTYPE;
- rule.v.etherType = (uint16_t)(_jI(r["etherType"],0ULL) & 0xffffULL);
+ rule.v.vlanDei = (uint8_t)(OSUtils::jsonInt(r["vlanDei"],0ULL) & 0xffULL);
return true;
} else if (t == "MATCH_MAC_SOURCE") {
rule.t |= ZT_NETWORK_RULE_MATCH_MAC_SOURCE;
- const std::string mac(_jS(r["mac"],"0"));
+ const std::string mac(OSUtils::jsonString(r["mac"],"0"));
Utils::unhex(mac.c_str(),(unsigned int)mac.length(),rule.v.mac,6);
return true;
} else if (t == "MATCH_MAC_DEST") {
rule.t |= ZT_NETWORK_RULE_MATCH_MAC_DEST;
- const std::string mac(_jS(r["mac"],"0"));
+ const std::string mac(OSUtils::jsonString(r["mac"],"0"));
Utils::unhex(mac.c_str(),(unsigned int)mac.length(),rule.v.mac,6);
return true;
} else if (t == "MATCH_IPV4_SOURCE") {
rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_SOURCE;
- InetAddress ip(_jS(r["ip"],"0.0.0.0"));
+ InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0"));
rule.v.ipv4.ip = reinterpret_cast<struct sockaddr_in *>(&ip)->sin_addr.s_addr;
rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in *>(&ip)->sin_port) & 0xff;
if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32;
return true;
} else if (t == "MATCH_IPV4_DEST") {
rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_DEST;
- InetAddress ip(_jS(r["ip"],"0.0.0.0"));
+ InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0"));
rule.v.ipv4.ip = reinterpret_cast<struct sockaddr_in *>(&ip)->sin_addr.s_addr;
rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in *>(&ip)->sin_port) & 0xff;
if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32;
return true;
} else if (t == "MATCH_IPV6_SOURCE") {
rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_SOURCE;
- InetAddress ip(_jS(r["ip"],"::0"));
+ InetAddress ip(OSUtils::jsonString(r["ip"],"::0"));
memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff;
if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128;
return true;
} else if (t == "MATCH_IPV6_DEST") {
rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_DEST;
- InetAddress ip(_jS(r["ip"],"::0"));
+ InetAddress ip(OSUtils::jsonString(r["ip"],"::0"));
memcpy(rule.v.ipv6.ip,reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_addr.s6_addr,16);
rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast<struct sockaddr_in6 *>(&ip)->sin6_port) & 0xff;
if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128;
return true;
} else if (t == "MATCH_IP_TOS") {
rule.t |= ZT_NETWORK_RULE_MATCH_IP_TOS;
- rule.v.ipTos = (uint8_t)(_jI(r["ipTos"],0ULL) & 0xffULL);
+ rule.v.ipTos.mask = (uint8_t)(OSUtils::jsonInt(r["mask"],0ULL) & 0xffULL);
+ rule.v.ipTos.value[0] = (uint8_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffULL);
+ rule.v.ipTos.value[1] = (uint8_t)(OSUtils::jsonInt(r["end"],0ULL) & 0xffULL);
return true;
} else if (t == "MATCH_IP_PROTOCOL") {
rule.t |= ZT_NETWORK_RULE_MATCH_IP_PROTOCOL;
- rule.v.ipProtocol = (uint8_t)(_jI(r["ipProtocol"],0ULL) & 0xffULL);
+ rule.v.ipProtocol = (uint8_t)(OSUtils::jsonInt(r["ipProtocol"],0ULL) & 0xffULL);
+ return true;
+ } else if (t == "MATCH_ETHERTYPE") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_ETHERTYPE;
+ rule.v.etherType = (uint16_t)(OSUtils::jsonInt(r["etherType"],0ULL) & 0xffffULL);
return true;
} else if (t == "MATCH_ICMP") {
rule.t |= ZT_NETWORK_RULE_MATCH_ICMP;
- rule.v.icmp.type = (uint8_t)(_jI(r["type"],0ULL) & 0xffULL);
- json &code = r["code"];
+ rule.v.icmp.type = (uint8_t)(OSUtils::jsonInt(r["icmpType"],0ULL) & 0xffULL);
+ json &code = r["icmpCode"];
if (code.is_null()) {
rule.v.icmp.code = 0;
rule.v.icmp.flags = 0x00;
} else {
- rule.v.icmp.code = (uint8_t)(_jI(code,0ULL) & 0xffULL);
+ rule.v.icmp.code = (uint8_t)(OSUtils::jsonInt(code,0ULL) & 0xffULL);
rule.v.icmp.flags = 0x01;
}
return true;
} else if (t == "MATCH_IP_SOURCE_PORT_RANGE") {
rule.t |= ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE;
- rule.v.port[0] = (uint16_t)(_jI(r["start"],0ULL) & 0xffffULL);
- rule.v.port[1] = (uint16_t)(_jI(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL);
+ rule.v.port[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL);
+ rule.v.port[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL);
return true;
} else if (t == "MATCH_IP_DEST_PORT_RANGE") {
rule.t |= ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE;
- rule.v.port[0] = (uint16_t)(_jI(r["start"],0ULL) & 0xffffULL);
- rule.v.port[1] = (uint16_t)(_jI(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL);
+ rule.v.port[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL);
+ rule.v.port[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL);
return true;
} else if (t == "MATCH_CHARACTERISTICS") {
rule.t |= ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
@@ -438,548 +390,96 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
return true;
} else if (t == "MATCH_FRAME_SIZE_RANGE") {
rule.t |= ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE;
- rule.v.frameSize[0] = (uint16_t)(_jI(r["start"],0ULL) & 0xffffULL);
- rule.v.frameSize[1] = (uint16_t)(_jI(r["end"],(uint64_t)rule.v.frameSize[0]) & 0xffffULL);
+ rule.v.frameSize[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL);
+ rule.v.frameSize[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.frameSize[0]) & 0xffffULL);
return true;
} else if (t == "MATCH_RANDOM") {
rule.t |= ZT_NETWORK_RULE_MATCH_RANDOM;
- rule.v.randomProbability = (uint32_t)(_jI(r["probability"],0ULL) & 0xffffffffULL);
+ rule.v.randomProbability = (uint32_t)(OSUtils::jsonInt(r["probability"],0ULL) & 0xffffffffULL);
+ return true;
} else if (t == "MATCH_TAGS_DIFFERENCE") {
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE;
- rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
- rule.v.tag.value = (uint32_t)(_jI(r["value"],0ULL) & 0xffffffffULL);
- return true;
+ tag = true;
} else if (t == "MATCH_TAGS_BITWISE_AND") {
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND;
- rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
- rule.v.tag.value = (uint32_t)(_jI(r["value"],0ULL) & 0xffffffffULL);
- return true;
+ tag = true;
} else if (t == "MATCH_TAGS_BITWISE_OR") {
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR;
- rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
- rule.v.tag.value = (uint32_t)(_jI(r["value"],0ULL) & 0xffffffffULL);
- return true;
+ tag = true;
} else if (t == "MATCH_TAGS_BITWISE_XOR") {
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR;
- rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
- rule.v.tag.value = (uint32_t)(_jI(r["value"],0ULL) & 0xffffffffULL);
- return true;
+ tag = true;
} else if (t == "MATCH_TAGS_EQUAL") {
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_EQUAL;
- rule.v.tag.id = (uint32_t)(_jI(r["id"],0ULL) & 0xffffffffULL);
- rule.v.tag.value = (uint32_t)(_jI(r["value"],0ULL) & 0xffffffffULL);
+ tag = true;
+ } else if (t == "MATCH_TAG_SENDER") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAG_SENDER;
+ tag = true;
+ } else if (t == "MATCH_TAG_RECEIVER") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAG_RECEIVER;
+ tag = true;
+ }
+ if (tag) {
+ rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL);
+ rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL);
return true;
}
+
return false;
}
EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath) :
- _node(node),
- _path(dbPath),
- _daemonRun(true)
+ _threadsStarted(false),
+ _db(dbPath),
+ _node(node)
{
OSUtils::mkdir(dbPath);
OSUtils::lockDownFile(dbPath,true); // networks might contain auth tokens, etc., so restrict directory permissions
- _daemon = Thread::start(this);
}
EmbeddedNetworkController::~EmbeddedNetworkController()
{
+ Mutex::Lock _l(_threads_m);
+ if (_threadsStarted) {
+ for(int i=0;i<(ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT*2);++i)
+ _queue.post((_RQEntry *)0);
+ for(int i=0;i<ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT;++i)
+ Thread::join(_threads[i]);
+ }
}
-void EmbeddedNetworkController::threadMain()
- throw()
+void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
{
- uint64_t lastUpdatedNetworkMemberCache = 0;
- while (_daemonRun) {
- // Every 60 seconds we rescan the filesystem for network members and rebuild our cache
- if ((OSUtils::now() - lastUpdatedNetworkMemberCache) >= 60000) {
- const std::vector<std::string> networks(OSUtils::listSubdirectories((_path + ZT_PATH_SEPARATOR_S + "network").c_str()));
- for(auto n=networks.begin();n!=networks.end();++n) {
- if (n->length() == 16) {
- const std::vector<std::string> members(OSUtils::listSubdirectories((*n + ZT_PATH_SEPARATOR_S + "member").c_str()));
- std::map<Address,nlohmann::json> newCache;
- for(auto m=members.begin();m!=members.end();++m) {
- if (m->length() == ZT_ADDRESS_LENGTH_HEX) {
- const Address maddr(*m);
- try {
- const json mj(_readJson((_path + ZT_PATH_SEPARATOR_S + "network" + ZT_PATH_SEPARATOR_S + *n + ZT_PATH_SEPARATOR_S + "member" + ZT_PATH_SEPARATOR_S + *m + ZT_PATH_SEPARATOR_S + "config.json")));
- if ((mj.is_object())&&(mj.size() > 0)) {
- newCache[maddr] = mj;
- }
- } catch ( ... ) {}
- }
- }
- {
- Mutex::Lock _l(_networkMemberCache_m);
- _networkMemberCache[Utils::hexStrToU64(n->c_str())] = newCache;
- }
- }
- }
- lastUpdatedNetworkMemberCache = OSUtils::now();
- }
-
- { // Every 25ms we push up to 50 network refreshes, which amounts to a max of about 300-500kb/sec
- unsigned int count = 0;
- Mutex::Lock _l(_refreshQueue_m);
- while (_refreshQueue.size() > 0) {
- _Refresh &r = _refreshQueue.front();
- //if (_node)
- // _node->pushNetworkRefresh(r.dest,r.nwid,r.blacklistAddresses,r.blacklistThresholds,r.numBlacklistEntries);
- _refreshQueue.pop_front();
- if (++count >= 50)
- break;
- }
- }
-
- Thread::sleep(25);
- }
+ this->_sender = sender;
+ this->_signingId = signingId;
}
-NetworkController::ResultCode EmbeddedNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData,NetworkConfig &nc)
+void EmbeddedNetworkController::request(
+ uint64_t nwid,
+ const InetAddress &fromAddr,
+ uint64_t requestPacketId,
+ const Identity &identity,
+ const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData)
{
- if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) {
- return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
- }
-
- const uint64_t now = OSUtils::now();
-
- // Check rate limit circuit breaker to prevent flooding
- {
- Mutex::Lock _l(_lastRequestTime_m);
- uint64_t &lrt = _lastRequestTime[std::pair<uint64_t,uint64_t>(identity.address().toInt(),nwid)];
- if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
- return NetworkController::NETCONF_QUERY_IGNORE;
- lrt = now;
- }
-
- json network(_readJson(_networkJP(nwid,false)));
- if (!network.size())
- return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
-
- const std::string memberJP(_memberJP(nwid,identity.address(),true));
- json member(_readJson(memberJP));
- _initMember(member);
-
- {
- std::string haveIdStr(_jS(member["identity"],""));
- if (haveIdStr.length() > 0) {
- // If we already know this member's identity perform a full compare. This prevents
- // a "collision" from being able to auth onto our network in place of an already
- // known member.
- try {
- if (Identity(haveIdStr.c_str()) != identity)
- return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
- } catch ( ... ) {
- return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
- }
- } else {
- // If we do not yet know this member's identity, learn it.
- member["identity"] = identity.toString(false);
- }
- }
-
- // These are always the same, but make sure they are set
- member["id"] = identity.address().toString();
- member["address"] = member["id"];
- member["nwid"] = network["id"];
-
- // Determine whether and how member is authorized
- const char *authorizedBy = (const char *)0;
- if (_jB(member["authorized"],false)) {
- authorizedBy = "memberIsAuthorized";
- } else if (!_jB(network["private"],true)) {
- authorizedBy = "networkIsPublic";
- if (!member.count("authorized")) {
- member["authorized"] = true;
- json ah;
- ah["a"] = true;
- ah["by"] = authorizedBy;
- ah["ts"] = now;
- ah["ct"] = json();
- ah["c"] = json();
- member["authHistory"].push_back(ah);
- member["lastModified"] = now;
- json &revj = member["revision"];
- member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
- }
- } else {
- char presentedAuth[512];
- if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) {
- presentedAuth[511] = (char)0; // sanity check
-
- // Check for bearer token presented by member
- if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) {
- const char *const presentedToken = presentedAuth + 6;
-
- json &authTokens = network["authTokens"];
- if (authTokens.is_array()) {
- for(unsigned long i=0;i<authTokens.size();++i) {
- json &token = authTokens[i];
- if (token.is_object()) {
- const uint64_t expires = _jI(token["expires"],0ULL);
- const uint64_t maxUses = _jI(token["maxUsesPerMember"],0ULL);
- std::string tstr = _jS(token["token"],"");
-
- if (((expires == 0ULL)||(expires > now))&&(tstr == presentedToken)) {
- bool usable = (maxUses == 0);
- if (!usable) {
- uint64_t useCount = 0;
- json &ahist = member["authHistory"];
- if (ahist.is_array()) {
- for(unsigned long j=0;j<ahist.size();++j) {
- json &ah = ahist[j];
- if ((_jS(ah["ct"],"") == "token")&&(_jS(ah["c"],"") == tstr)&&(_jB(ah["a"],false)))
- ++useCount;
- }
- }
- usable = (useCount < maxUses);
- }
- if (usable) {
- authorizedBy = "token";
- member["authorized"] = true;
- json ah;
- ah["a"] = true;
- ah["by"] = authorizedBy;
- ah["ts"] = now;
- ah["ct"] = "token";
- ah["c"] = tstr;
- member["authHistory"].push_back(ah);
- member["lastModified"] = now;
- json &revj = member["revision"];
- member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
- }
- }
- }
- }
- }
- }
- }
- }
+ if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender))
+ return;
- // Log this request
{
- json rlEntry = json::object();
- rlEntry["ts"] = now;
- rlEntry["authorized"] = (authorizedBy) ? true : false;
- rlEntry["authorizedBy"] = (authorizedBy) ? authorizedBy : "";
- rlEntry["clientMajorVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0);
- rlEntry["clientMinorVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0);
- rlEntry["clientRevision"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0);
- rlEntry["clientProtocolVersion"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,0);
- if (fromAddr)
- rlEntry["fromAddr"] = fromAddr.toString();
-
- json recentLog = json::array();
- recentLog.push_back(rlEntry);
- json &oldLog = member["recentLog"];
- if (oldLog.is_array()) {
- for(unsigned long i=0;i<oldLog.size();++i) {
- recentLog.push_back(oldLog[i]);
- if (recentLog.size() >= ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH)
- break;
- }
+ Mutex::Lock _l(_threads_m);
+ if (!_threadsStarted) {
+ for(int i=0;i<ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT;++i)
+ _threads[i] = Thread::start(this);
}
- member["recentLog"] = recentLog;
- }
-
- // If they are not authorized, STOP!
- if (!authorizedBy) {
- _writeJson(memberJP,member);
- return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
- }
-
- // -------------------------------------------------------------------------
- // If we made it this far, they are authorized.
- // -------------------------------------------------------------------------
-
- _NetworkMemberInfo nmi;
- _getNetworkMemberInfo(now,nwid,nmi);
-
- // Compute credential TTL. This is the "moving window" for COM agreement and
- // the global TTL for Capability and Tag objects. (The same value is used
- // for both.) This is computed by reference to the last time we deauthorized
- // a member, since within the time period since this event any temporal
- // differences are not particularly relevant.
- uint64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA;
- if (now > nmi.mostRecentDeauthTime)
- credentialtmd += (now - nmi.mostRecentDeauthTime);
- if (credentialtmd > ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA)
- credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA;
-
- nc.networkId = nwid;
- nc.type = _jB(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
- nc.timestamp = now;
- nc.credentialTimeMaxDelta = credentialtmd;
- nc.revision = _jI(network["revision"],0ULL);
- nc.issuedTo = identity.address();
- if (_jB(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
- if (_jB(network["allowPassiveBridging"],false)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING;
- Utils::scopy(nc.name,sizeof(nc.name),_jS(network["name"],"").c_str());
- nc.multicastLimit = (unsigned int)_jI(network["multicastLimit"],32ULL);
-
- for(std::set<Address>::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab)
- nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE);
-
- json &v4AssignMode = network["v4AssignMode"];
- json &v6AssignMode = network["v6AssignMode"];
- json &ipAssignmentPools = network["ipAssignmentPools"];
- json &routes = network["routes"];
- json &rules = network["rules"];
- json &capabilities = network["capabilities"];
- json &memberCapabilities = member["capabilities"];
- json &memberTags = member["tags"];
-
- if (rules.is_array()) {
- for(unsigned long i=0;i<rules.size();++i) {
- if (nc.ruleCount >= ZT_MAX_NETWORK_RULES)
- break;
- if (_parseRule(rules[i],nc.rules[nc.ruleCount]))
- ++nc.ruleCount;
- }
- }
-
- if ((memberCapabilities.is_array())&&(memberCapabilities.size() > 0)&&(capabilities.is_array())) {
- std::map< uint64_t,json * > capsById;
- for(unsigned long i=0;i<capabilities.size();++i) {
- json &cap = capabilities[i];
- if (cap.is_object())
- capsById[_jI(cap["id"],0ULL) & 0xffffffffULL] = &cap;
- }
-
- for(unsigned long i=0;i<memberCapabilities.size();++i) {
- const uint64_t capId = _jI(memberCapabilities[i],0ULL) & 0xffffffffULL;
- json *cap = capsById[capId];
- if ((cap->is_object())&&(cap->size() > 0)) {
- ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES];
- unsigned int caprc = 0;
- json &caprj = (*cap)["rules"];
- if ((caprj.is_array())&&(caprj.size() > 0)) {
- for(unsigned long j=0;j<caprj.size();++j) {
- if (caprc >= ZT_MAX_CAPABILITY_RULES)
- break;
- if (_parseRule(caprj[j],capr[caprc]))
- ++caprc;
- }
- }
- nc.capabilities[nc.capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc);
- if (nc.capabilities[nc.capabilityCount].sign(signingId,identity.address()))
- ++nc.capabilityCount;
- if (nc.capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES)
- break;
- }
- }
- }
-
- if (memberTags.is_array()) {
- std::map< uint32_t,uint32_t > tagsById;
- for(unsigned long i=0;i<memberTags.size();++i) {
- json &t = memberTags[i];
- if ((t.is_array())&&(t.size() == 2))
- tagsById[(uint32_t)(_jI(t[0],0ULL) & 0xffffffffULL)] = (uint32_t)(_jI(t[1],0ULL) & 0xffffffffULL);
- }
- for(std::map< uint32_t,uint32_t >::const_iterator t(tagsById.begin());t!=tagsById.end();++t) {
- if (nc.tagCount >= ZT_MAX_NETWORK_TAGS)
- break;
- nc.tags[nc.tagCount] = Tag(nwid,now,identity.address(),t->first,t->second);
- if (nc.tags[nc.tagCount].sign(signingId))
- ++nc.tagCount;
- }
- }
-
- if (routes.is_array()) {
- for(unsigned long i=0;i<routes.size();++i) {
- if (nc.routeCount >= ZT_MAX_NETWORK_ROUTES)
- break;
- json &route = routes[i];
- json &target = route["target"];
- json &via = route["via"];
- if (target.is_string()) {
- const InetAddress t(target.get<std::string>());
- InetAddress v;
- if (via.is_string()) v.fromString(via.get<std::string>());
- if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) {
- ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]);
- *(reinterpret_cast<InetAddress *>(&(r->target))) = t;
- if (v.ss_family == t.ss_family)
- *(reinterpret_cast<InetAddress *>(&(r->via))) = v;
- ++nc.routeCount;
- }
- }
- }
- }
-
- const bool noAutoAssignIps = _jB(member["noAutoAssignIps"],false);
-
- if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) {
- if ((_jB(v6AssignMode["rfc4193"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
- nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt());
- nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
- }
- if ((_jB(v6AssignMode["6plane"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
- nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt());
- nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
- }
- }
-
- bool haveManagedIpv4AutoAssignment = false;
- bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count
- json ipAssignments = member["ipAssignments"]; // we want to make a copy
- if (ipAssignments.is_array()) {
- for(unsigned long i=0;i<ipAssignments.size();++i) {
- if (!ipAssignments[i].is_string())
- continue;
- std::string ips = ipAssignments[i];
- InetAddress ip(ips);
-
- // IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from
- // this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source
- // of user error / poor UX.
- int routedNetmaskBits = 0;
- for(unsigned int rk=0;rk<nc.routeCount;++rk) {
- if ( (!nc.routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip)) )
- routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits();
- }
-
- if (routedNetmaskBits > 0) {
- if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
- ip.setPort(routedNetmaskBits);
- nc.staticIps[nc.staticIpCount++] = ip;
- }
- if (ip.ss_family == AF_INET)
- haveManagedIpv4AutoAssignment = true;
- else if (ip.ss_family == AF_INET6)
- haveManagedIpv6AutoAssignment = true;
- }
- }
- } else {
- ipAssignments = json::array();
- }
-
- if ( (ipAssignmentPools.is_array()) && ((v6AssignMode.is_object())&&(_jB(v6AssignMode["zt"],false))) && (!haveManagedIpv6AutoAssignment) && (!noAutoAssignIps) ) {
- for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv6AutoAssignment));++p) {
- json &pool = ipAssignmentPools[p];
- if (pool.is_object()) {
- InetAddress ipRangeStart(_jS(pool["ipRangeStart"],""));
- InetAddress ipRangeEnd(_jS(pool["ipRangeEnd"],""));
- if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) {
- uint64_t s[2],e[2],x[2],xx[2];
- memcpy(s,ipRangeStart.rawIpData(),16);
- memcpy(e,ipRangeEnd.rawIpData(),16);
- s[0] = Utils::ntoh(s[0]);
- s[1] = Utils::ntoh(s[1]);
- e[0] = Utils::ntoh(e[0]);
- e[1] = Utils::ntoh(e[1]);
- x[0] = s[0];
- x[1] = s[1];
-
- for(unsigned int trialCount=0;trialCount<1000;++trialCount) {
- if ((trialCount == 0)&&(e[1] > s[1])&&((e[1] - s[1]) >= 0xffffffffffULL)) {
- // First see if we can just cram a ZeroTier ID into the higher 64 bits. If so do that.
- xx[0] = Utils::hton(x[0]);
- xx[1] = Utils::hton(x[1] + identity.address().toInt());
- } else {
- // Otherwise pick random addresses -- this technically doesn't explore the whole range if the lower 64 bit range is >= 1 but that won't matter since that would be huge anyway
- Utils::getSecureRandom((void *)xx,16);
- if ((e[0] > s[0]))
- xx[0] %= (e[0] - s[0]);
- else xx[0] = 0;
- if ((e[1] > s[1]))
- xx[1] %= (e[1] - s[1]);
- else xx[1] = 0;
- xx[0] = Utils::hton(x[0] + xx[0]);
- xx[1] = Utils::hton(x[1] + xx[1]);
- }
-
- InetAddress ip6((const void *)xx,16,0);
-
- // Check if this IP is within a local-to-Ethernet routed network
- int routedNetmaskBits = 0;
- for(unsigned int rk=0;rk<nc.routeCount;++rk) {
- if ( (!nc.routes[rk].via.ss_family) && (nc.routes[rk].target.ss_family == AF_INET6) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip6)) )
- routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits();
- }
-
- // If it's routed, then try to claim and assign it and if successful end loop
- if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip6))) {
- ipAssignments.push_back(ip6.toIpString());
- member["ipAssignments"] = ipAssignments;
- ip6.setPort((unsigned int)routedNetmaskBits);
- if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)
- nc.staticIps[nc.staticIpCount++] = ip6;
- haveManagedIpv6AutoAssignment = true;
- break;
- }
- }
- }
- }
- }
- }
-
- if ( (ipAssignmentPools.is_array()) && ((v4AssignMode.is_object())&&(_jB(v4AssignMode["zt"],false))) && (!haveManagedIpv4AutoAssignment) && (!noAutoAssignIps) ) {
- for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv4AutoAssignment));++p) {
- json &pool = ipAssignmentPools[p];
- if (pool.is_object()) {
- InetAddress ipRangeStartIA(_jS(pool["ipRangeStart"],""));
- InetAddress ipRangeEndIA(_jS(pool["ipRangeEnd"],""));
- if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) {
- uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr));
- uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeEndIA)->sin_addr.s_addr));
- if ((ipRangeEnd < ipRangeStart)||(ipRangeStart == 0))
- continue;
- uint32_t ipRangeLen = ipRangeEnd - ipRangeStart;
-
- // Start with the LSB of the member's address
- uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff);
-
- for(uint32_t k=ipRangeStart,trialCount=0;((k<=ipRangeEnd)&&(trialCount < 1000));++k,++trialCount) {
- uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart;
- ++ipTrialCounter;
- if ((ip & 0x000000ff) == 0x000000ff)
- continue; // don't allow addresses that end in .255
-
- // Check if this IP is within a local-to-Ethernet routed network
- int routedNetmaskBits = -1;
- for(unsigned int rk=0;rk<nc.routeCount;++rk) {
- if (nc.routes[rk].target.ss_family == AF_INET) {
- uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_addr.s_addr));
- int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_port));
- if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) {
- routedNetmaskBits = targetBits;
- break;
- }
- }
- }
-
- // If it's routed, then try to claim and assign it and if successful end loop
- const InetAddress ip4(Utils::hton(ip),0);
- if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip4))) {
- ipAssignments.push_back(ip4.toIpString());
- member["ipAssignments"] = ipAssignments;
- if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
- struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++]));
- v4ip->sin_family = AF_INET;
- v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits);
- v4ip->sin_addr.s_addr = Utils::hton(ip);
- }
- haveManagedIpv4AutoAssignment = true;
- break;
- }
- }
- }
- }
- }
- }
-
- CertificateOfMembership com(now,credentialtmd,nwid,identity.address());
- if (com.sign(signingId)) {
- nc.com = com;
- } else {
- return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ _threadsStarted = true;
}
- _writeJson(memberJP,member);
- return NetworkController::NETCONF_QUERY_OK;
+ _RQEntry *qe = new _RQEntry;
+ qe->nwid = nwid;
+ qe->requestPacketId = requestPacketId;
+ qe->fromAddr = fromAddr;
+ qe->identity = identity;
+ qe->metaData = metaData;
+ _queue.post(qe);
}
unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
@@ -997,7 +497,11 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
char nwids[24];
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
- json network(_readJson(_networkJP(nwid,false)));
+ json network;
+ {
+ Mutex::Lock _l(_db_m);
+ network = _db.get("network",nwids,0);
+ }
if (!network.size())
return 404;
@@ -1008,51 +512,40 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
if (path.size() >= 4) {
const uint64_t address = Utils::hexStrToU64(path[3].c_str());
- json member(_readJson(_memberJP(nwid,Address(address),false)));
+ json member;
+ {
+ Mutex::Lock _l(_db_m);
+ member = _db.get("network",nwids,"member",Address(address).toString(),0);
+ }
if (!member.size())
return 404;
_addMemberNonPersistedFields(member,OSUtils::now());
- responseBody = member.dump(2);
+ responseBody = OSUtils::jsonDump(member);
responseContentType = "application/json";
return 200;
} else {
+ Mutex::Lock _l(_db_m);
+
responseBody = "{";
- std::vector<std::string> members(OSUtils::listSubdirectories((_networkBP(nwid,false) + ZT_PATH_SEPARATOR_S + "member").c_str()));
- for(std::vector<std::string>::iterator i(members.begin());i!=members.end();++i) {
- if (i->length() == ZT_ADDRESS_LENGTH_HEX) {
- json member(_readJson(_memberJP(nwid,Address(Utils::hexStrToU64(i->c_str())),false)));
- if (member.size()) {
- responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
- responseBody.append(*i);
- responseBody.append("\":");
- responseBody.append(_jS(member["revision"],"0"));
- }
+ std::string pfx(std::string("network/") + nwids + "member/");
+ _db.filter(pfx,120000,[&responseBody](const std::string &n,const json &member) {
+ if (member.size() > 0) {
+ responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
+ responseBody.append(OSUtils::jsonString(member["id"],""));
+ responseBody.append("\":");
+ responseBody.append(OSUtils::jsonString(member["revision"],"0"));
}
- }
+ return true; // never delete
+ });
responseBody.push_back('}');
responseContentType = "application/json";
return 200;
}
- } else if ((path[2] == "test")&&(path.size() >= 4)) {
-
- Mutex::Lock _l(_circuitTests_m);
- std::map< uint64_t,_CircuitTestEntry >::iterator cte(_circuitTests.find(Utils::hexStrToU64(path[3].c_str())));
- if ((cte != _circuitTests.end())&&(cte->second.test)) {
-
- responseBody = "[";
- responseBody.append(cte->second.jsonResults);
- responseBody.push_back(']');
- responseContentType = "application/json";
-
- return 200;
-
- } // else 404
-
} // else 404
} else {
@@ -1061,21 +554,28 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
_NetworkMemberInfo nmi;
_getNetworkMemberInfo(now,nwid,nmi);
_addNetworkNonPersistedFields(network,now,nmi);
- responseBody = network.dump(2);
+ responseBody = OSUtils::jsonDump(network);
responseContentType = "application/json";
return 200;
}
} else if (path.size() == 1) {
- responseBody = "[";
- std::vector<std::string> networks(OSUtils::listSubdirectories((_path + ZT_PATH_SEPARATOR_S + "network").c_str()));
- for(auto i(networks.begin());i!=networks.end();++i) {
- if (i->length() == 16) {
- responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
- responseBody.append(*i);
- responseBody.append("\"");
- }
+ std::set<std::string> networkIds;
+ {
+ Mutex::Lock _l(_db_m);
+ _db.filter("network/",120000,[&networkIds](const std::string &n,const json &obj) {
+ if (n.length() == (16 + 8))
+ networkIds.insert(n.substr(8));
+ return true; // do not delete
+ });
+ }
+
+ responseBody.push_back('[');
+ for(std::set<std::string>::iterator i(networkIds.begin());i!=networkIds.end();++i) {
+ responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
+ responseBody.append(*i);
+ responseBody.append("\"");
}
responseBody.push_back(']');
responseContentType = "application/json";
@@ -1109,16 +609,12 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
json b;
try {
- b = json::parse(body);
+ b = OSUtils::jsonParse(body);
if (!b.is_object()) {
responseBody = "{ \"message\": \"body is not a JSON object\" }";
responseContentType = "application/json";
return 400;
}
- } catch (std::exception &exc) {
- responseBody = std::string("{ \"message\": \"body JSON is invalid: ") + exc.what() + "\" }";
- responseContentType = "application/json";
- return 400;
} catch ( ... ) {
responseBody = "{ \"message\": \"body JSON is invalid\" }";
responseContentType = "application/json";
@@ -1134,27 +630,30 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
if (path.size() >= 3) {
- json network(_readJson(_networkJP(nwid,false)));
- if (!network.size())
- return 404;
if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
uint64_t address = Utils::hexStrToU64(path[3].c_str());
char addrs[24];
Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address);
- json member(_readJson(_memberJP(nwid,Address(address),true)));
+ json member;
+ {
+ Mutex::Lock _l(_db_m);
+ member = _db.get("network",nwids,"member",Address(address).toString(),0);
+ }
+ json origMember(member); // for detecting changes
_initMember(member);
try {
- if (b.count("activeBridge")) member["activeBridge"] = _jB(b["activeBridge"],false);
- if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = _jB(b["noAutoAssignIps"],false);
- if ((b.count("identity"))&&(!member.count("identity"))) member["identity"] = _jS(b["identity"],""); // allow identity to be populated only if not already known
+ if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"],false);
+ if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"],false);
if (b.count("authorized")) {
- const bool newAuth = _jB(b["authorized"],false);
- if (newAuth != _jB(member["authorized"],false)) {
+ const bool newAuth = OSUtils::jsonBool(b["authorized"],false);
+ if (newAuth != OSUtils::jsonBool(member["authorized"],false)) {
member["authorized"] = newAuth;
+ member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now;
+
json ah;
ah["a"] = newAuth;
ah["by"] = "api";
@@ -1162,11 +661,23 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
ah["ct"] = json();
ah["c"] = json();
member["authHistory"].push_back(ah);
+
+ // Member is being de-authorized, so spray Revocation objects to all online members
+ if (!newAuth) {
+ _clearNetworkMemberInfoCache(nwid);
+ Revocation rev(_node->prng(),nwid,0,now,ZT_REVOCATION_FLAG_FAST_PROPAGATE,Address(address),Revocation::CREDENTIAL_TYPE_COM);
+ rev.sign(_signingId);
+ Mutex::Lock _l(_lastRequestTime_m);
+ for(std::map< std::pair<uint64_t,uint64_t>,uint64_t >::iterator i(_lastRequestTime.begin());i!=_lastRequestTime.end();++i) {
+ if ((now - i->second) < ZT_NETWORK_AUTOCONF_DELAY)
+ _node->ncSendRevocation(Address(i->first.first),rev);
+ }
+ }
}
}
if (b.count("ipAssignments")) {
- auto ipa = b["ipAssignments"];
+ json &ipa = b["ipAssignments"];
if (ipa.is_array()) {
json mipa(json::array());
for(unsigned long i=0;i<ipa.size();++i) {
@@ -1181,13 +692,13 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
}
if (b.count("tags")) {
- auto tags = b["tags"];
+ json &tags = b["tags"];
if (tags.is_array()) {
std::map<uint64_t,uint64_t> mtags;
for(unsigned long i=0;i<tags.size();++i) {
- auto tag = tags[i];
+ json &tag = tags[i];
if ((tag.is_array())&&(tag.size() == 2))
- mtags[_jI(tag[0],0ULL) & 0xffffffffULL] = _jI(tag[1],0ULL) & 0xffffffffULL;
+ mtags[OSUtils::jsonInt(tag[0],0ULL) & 0xffffffffULL] = OSUtils::jsonInt(tag[1],0ULL) & 0xffffffffULL;
}
json mtagsa = json::array();
for(std::map<uint64_t,uint64_t>::iterator t(mtags.begin());t!=mtags.end();++t) {
@@ -1201,11 +712,11 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
}
if (b.count("capabilities")) {
- auto capabilities = b["capabilities"];
+ json &capabilities = b["capabilities"];
if (capabilities.is_array()) {
json mcaps = json::array();
for(unsigned long i=0;i<capabilities.size();++i) {
- mcaps.push_back(_jI(capabilities[i],0ULL));
+ mcaps.push_back(OSUtils::jsonInt(capabilities[i],0ULL));
}
std::sort(mcaps.begin(),mcaps.end());
mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end());
@@ -1221,37 +732,30 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
member["id"] = addrs;
member["address"] = addrs; // legacy
member["nwid"] = nwids;
- member["lastModified"] = now;
- auto revj = member["revision"];
- member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
-
- _writeJson(_memberJP(nwid,Address(address),true).c_str(),member);
-
- {
- Mutex::Lock _l(_networkMemberCache_m);
- _networkMemberCache[nwid][Address(address)] = member;
- }
- {
- Mutex::Lock _l(_refreshQueue_m);
- _refreshQueue.push_back(_Refresh());
- _Refresh &r = _refreshQueue.back();
- r.dest = Address(address);
- r.nwid = nwid;
- r.numBlacklistEntries = 0;
+ if (member != origMember) {
+ member["lastModified"] = now;
+ json &revj = member["revision"];
+ member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
+ {
+ Mutex::Lock _l(_db_m);
+ _db.put("network",nwids,"member",Address(address).toString(),member);
+ }
+ _pushMemberUpdate(now,nwid,member);
}
// Add non-persisted fields
member["clock"] = now;
- responseBody = member.dump(2);
+ responseBody = OSUtils::jsonDump(member);
responseContentType = "application/json";
return 200;
} else if ((path.size() == 3)&&(path[2] == "test")) {
- Mutex::Lock _l(_circuitTests_m);
+ Mutex::Lock _l(_tests_m);
- ZT_CircuitTest *test = (ZT_CircuitTest *)malloc(sizeof(ZT_CircuitTest));
+ _tests.push_back(ZT_CircuitTest());
+ ZT_CircuitTest *const test = &(_tests.back());
memset(test,0,sizeof(ZT_CircuitTest));
Utils::getSecureRandom(&(test->testId),sizeof(test->testId));
@@ -1260,22 +764,24 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
json hops = b["hops"];
if (hops.is_array()) {
for(unsigned long i=0;i<hops.size();++i) {
- auto hops2 = hops[i];
+ json &hops2 = hops[i];
if (hops2.is_array()) {
for(unsigned long j=0;j<hops2.size();++j) {
std::string s = hops2[j];
test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL;
}
+ ++test->hopCount;
} else if (hops2.is_string()) {
std::string s = hops2;
test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL;
+ ++test->hopCount;
}
}
}
- test->reportAtEveryHop = (_jB(b["reportAtEveryHop"],true) ? 1 : 0);
+ test->reportAtEveryHop = (OSUtils::jsonBool(b["reportAtEveryHop"],true) ? 1 : 0);
if (!test->hopCount) {
- ::free((void *)test);
+ _tests.pop_back();
responseBody = "{ \"message\": \"a test must contain at least one hop\" }";
responseContentType = "application/json";
return 400;
@@ -1283,18 +789,18 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
test->timestamp = OSUtils::now();
- _CircuitTestEntry &te = _circuitTests[test->testId];
- te.test = test;
- te.jsonResults = "";
-
- if (_node)
+ if (_node) {
_node->circuitTestBegin(test,&(EmbeddedNetworkController::_circuitTestCallback));
- else return 500;
+ } else {
+ _tests.pop_back();
+ return 500;
+ }
- char json[1024];
- Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\"}",test->testId);
+ char json[512];
+ Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\",\"timestamp\":%llu}",test->testId,test->timestamp);
responseBody = json;
responseContentType = "application/json";
+
return 200;
} // else 404
@@ -1302,42 +808,48 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
} else {
// POST to network ID
- // Magic ID ending with ______ picks a random unused network ID
- if (path[1].substr(10) == "______") {
- nwid = 0;
- uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL;
- uint64_t nwidPostfix = 0;
- for(unsigned long k=0;k<100000;++k) { // sanity limit on trials
- Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
- uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
- if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
- Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid);
- if (!OSUtils::fileExists(_networkJP(tryNwid,false).c_str())) {
- nwid = tryNwid;
- break;
+ json network;
+ {
+ Mutex::Lock _l(_db_m);
+
+ // Magic ID ending with ______ picks a random unused network ID
+ if (path[1].substr(10) == "______") {
+ nwid = 0;
+ uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL;
+ uint64_t nwidPostfix = 0;
+ for(unsigned long k=0;k<100000;++k) { // sanity limit on trials
+ Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
+ uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
+ if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid);
+ if (_db.get("network",nwids,120000).size() <= 0) {
+ nwid = tryNwid;
+ break;
+ }
}
+ if (!nwid)
+ return 503;
}
- if (!nwid)
- return 503;
- }
- json network(_readJson(_networkJP(nwid,true)));
+ network = _db.get("network",nwids,0);
+ }
+ json origNetwork(network); // for detecting changes
_initNetwork(network);
try {
- if (b.count("name")) network["name"] = _jS(b["name"],"");
- if (b.count("private")) network["private"] = _jB(b["private"],true);
- if (b.count("enableBroadcast")) network["enableBroadcast"] = _jB(b["enableBroadcast"],false);
- if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = _jB(b["allowPassiveBridging"],false);
- if (b.count("multicastLimit")) network["multicastLimit"] = _jI(b["multicastLimit"],32ULL);
+ if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],"");
+ if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true);
+ if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false);
+ if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = OSUtils::jsonBool(b["allowPassiveBridging"],false);
+ if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL);
if (b.count("v4AssignMode")) {
json nv4m;
json &v4m = b["v4AssignMode"];
if (v4m.is_string()) { // backward compatibility
- nv4m["zt"] = (_jS(v4m,"") == "zt");
+ nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt");
} else if (v4m.is_object()) {
- nv4m["zt"] = _jB(v4m["zt"],false);
+ nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false);
} else nv4m["zt"] = false;
network["v4AssignMode"] = nv4m;
}
@@ -1347,7 +859,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
json &v6m = b["v6AssignMode"];
if (!nv6m.is_object()) nv6m = json::object();
if (v6m.is_string()) { // backward compatibility
- std::vector<std::string> v6ms(Utils::split(_jS(v6m,"").c_str(),",","",""));
+ std::vector<std::string> v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","",""));
std::sort(v6ms.begin(),v6ms.end());
v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end());
nv6m["rfc4193"] = false;
@@ -1362,9 +874,9 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
nv6m["6plane"] = true;
}
} else if (v6m.is_object()) {
- if (v6m.count("rfc4193")) nv6m["rfc4193"] = _jB(v6m["rfc4193"],false);
- if (v6m.count("zt")) nv6m["zt"] = _jB(v6m["zt"],false);
- if (v6m.count("6plane")) nv6m["6plane"] = _jB(v6m["6plane"],false);
+ if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false);
+ if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false);
+ if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false);
} else {
nv6m["rfc4193"] = false;
nv6m["zt"] = false;
@@ -1406,10 +918,10 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
if (ipp.is_array()) {
json nipp = json::array();
for(unsigned long i=0;i<ipp.size();++i) {
- auto ip = ipp[i];
+ json &ip = ipp[i];
if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("ipRangeEnd"))) {
- InetAddress f(_jS(ip["ipRangeStart"],""));
- InetAddress t(_jS(ip["ipRangeEnd"],""));
+ InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],""));
+ InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],""));
if ( ((f.ss_family == AF_INET)||(f.ss_family == AF_INET6)) && (f.ss_family == t.ss_family) ) {
json tmp = json::object();
tmp["ipRangeStart"] = f.toIpString();
@@ -1449,8 +961,8 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
if (tstr.length() > 0) {
json t = json::object();
t["token"] = tstr;
- t["expires"] = _jI(token["expires"],0ULL);
- t["maxUsesPerMember"] = _jI(token["maxUsesPerMember"],0ULL);
+ t["expires"] = OSUtils::jsonInt(token["expires"],0ULL);
+ t["maxUsesPerMember"] = OSUtils::jsonInt(token["maxUsesPerMember"],0ULL);
nat.push_back(t);
}
}
@@ -1467,14 +979,15 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
json &cap = capabilities[i];
if (cap.is_object()) {
json ncap = json::object();
- const uint64_t capId = _jI(cap["id"],0ULL);
+ const uint64_t capId = OSUtils::jsonInt(cap["id"],0ULL);
ncap["id"] = capId;
+ ncap["default"] = OSUtils::jsonBool(cap["default"],false);
json &rules = cap["rules"];
json nrules = json::array();
if (rules.is_array()) {
for(unsigned long i=0;i<rules.size();++i) {
- json rule = rules[i];
+ json &rule = rules[i];
if (rule.is_object()) {
ZT_VirtualNetworkRule ztr;
if (_parseRule(rule,ztr))
@@ -1494,6 +1007,31 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
network["capabilities"] = ncapsa;
}
}
+
+ if (b.count("tags")) {
+ json &tags = b["tags"];
+ if (tags.is_array()) {
+ std::map< uint64_t,json > ntags;
+ for(unsigned long i=0;i<tags.size();++i) {
+ json &tag = tags[i];
+ if (tag.is_object()) {
+ json ntag = json::object();
+ const uint64_t tagId = OSUtils::jsonInt(tag["id"],0ULL);
+ ntag["id"] = tagId;
+ if (tag.find("default") == tag.end())
+ ntag["default"] = json();
+ else ntag["default"] = OSUtils::jsonInt(tag["default"],0ULL);
+ ntags[tagId] = ntag;
+ }
+ }
+
+ json ntagsa = json::array();
+ for(std::map< uint64_t,json >::iterator t(ntags.begin());t!=ntags.end();++t)
+ ntagsa.push_back(t->second);
+ network["tags"] = ntagsa;
+ }
+ }
+
} catch ( ... ) {
responseBody = "{ \"message\": \"exception occurred while parsing body variables\" }";
responseContentType = "application/json";
@@ -1502,17 +1040,28 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
network["id"] = nwids;
network["nwid"] = nwids; // legacy
- auto rev = network["revision"];
- network["revision"] = (rev.is_number() ? ((uint64_t)rev + 1ULL) : 1ULL);
- network["lastModified"] = now;
- _writeJson(_networkJP(nwid,true),network);
+ if (network != origNetwork) {
+ json &revj = network["revision"];
+ network["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
+ network["lastModified"] = now;
+ {
+ Mutex::Lock _l(_db_m);
+ _db.put("network",nwids,network);
+ }
+
+ // Send an update to all members of the network
+ _db.filter((std::string("network/") + nwids + "/member/"),120000,[this,&now,&nwid](const std::string &n,const json &obj) {
+ _pushMemberUpdate(now,nwid,obj);
+ return true; // do not delete
+ });
+ }
_NetworkMemberInfo nmi;
_getNetworkMemberInfo(now,nwid,nmi);
_addNetworkNonPersistedFields(network,now,nmi);
- responseBody = network.dump(2);
+ responseBody = OSUtils::jsonDump(network);
responseContentType = "application/json";
return 200;
} // else 404
@@ -1539,7 +1088,13 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE(
if ((path.size() >= 2)&&(path[1].length() == 16)) {
const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
- json network(_readJson(_networkJP(nwid,false)));
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid);
+ json network;
+ {
+ Mutex::Lock _l(_db_m);
+ network = _db.get("network",nwids,0);
+ }
if (!network.size())
return 404;
@@ -1547,23 +1102,29 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE(
if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
const uint64_t address = Utils::hexStrToU64(path[3].c_str());
- json member(_readJson(_memberJP(nwid,Address(address),false)));
- if (!member.size())
- return 404;
+ Mutex::Lock _l(_db_m);
- OSUtils::rmDashRf(_memberBP(nwid,Address(address),false).c_str());
+ json member = _db.get("network",nwids,"member",Address(address).toString(),0);
+ _db.erase("network",nwids,"member",Address(address).toString());
- responseBody = member.dump(2);
+ if (!member.size())
+ return 404;
+ responseBody = OSUtils::jsonDump(member);
responseContentType = "application/json";
return 200;
}
} else {
- OSUtils::rmDashRf(_networkBP(nwid,false).c_str());
- {
- Mutex::Lock _l(_networkMemberCache_m);
- _networkMemberCache.erase(nwid);
- }
- responseBody = network.dump(2);
+ Mutex::Lock _l(_db_m);
+
+ std::string pfx("network/"); pfx.append(nwids);
+ _db.filter(pfx,120000,[](const std::string &n,const json &obj) {
+ return false; // delete
+ });
+
+ Mutex::Lock _l2(_nmiCache_m);
+ _nmiCache.erase(nwid);
+
+ responseBody = OSUtils::jsonDump(network);
responseContentType = "application/json";
return 200;
}
@@ -1574,52 +1135,70 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE(
return 404;
}
+void EmbeddedNetworkController::threadMain()
+ throw()
+{
+ uint64_t lastCircuitTestCheck = 0;
+ for(;;) {
+ _RQEntry *const qe = _queue.get(); // waits on next request
+ if (!qe) break; // enqueue a NULL to terminate threads
+ try {
+ _request(qe->nwid,qe->fromAddr,qe->requestPacketId,qe->identity,qe->metaData);
+ } catch ( ... ) {}
+ delete qe;
+
+ uint64_t now = OSUtils::now();
+ if ((now - lastCircuitTestCheck) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) {
+ lastCircuitTestCheck = now;
+ Mutex::Lock _l(_tests_m);
+ for(std::list< ZT_CircuitTest >::iterator i(_tests.begin());i!=_tests.end();) {
+ if ((now - i->timestamp) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) {
+ _node->circuitTestEnd(&(*i));
+ _tests.erase(i++);
+ } else ++i;
+ }
+ }
+ }
+}
+
void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report)
{
- char tmp[65535];
+ char tmp[1024],id[128];
EmbeddedNetworkController *const self = reinterpret_cast<EmbeddedNetworkController *>(test->ptr);
- if (!test)
- return;
- if (!report)
- return;
-
- Mutex::Lock _l(self->_circuitTests_m);
- std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId));
-
- if (cte == self->_circuitTests.end()) { // sanity check: a circuit test we didn't launch?
- self->_node->circuitTestEnd(test);
- ::free((void *)test);
- return;
- }
+ if ((!test)||(!report)||(!test->credentialNetworkId)) return; // sanity check
+ const uint64_t now = OSUtils::now();
+ Utils::snprintf(id,sizeof(id),"network/%.16llx/test/%.16llx-%.16llx-%.10llx-%.10llx",test->credentialNetworkId,test->testId,now,report->upstream,report->current);
Utils::snprintf(tmp,sizeof(tmp),
- "%s{\n"
- "\t\"timestamp\": %llu," ZT_EOL_S
- "\t\"testId\": \"%.16llx\"," ZT_EOL_S
- "\t\"upstream\": \"%.10llx\"," ZT_EOL_S
- "\t\"current\": \"%.10llx\"," ZT_EOL_S
- "\t\"receivedTimestamp\": %llu," ZT_EOL_S
- "\t\"sourcePacketId\": \"%.16llx\"," ZT_EOL_S
- "\t\"flags\": %llu," ZT_EOL_S
- "\t\"sourcePacketHopCount\": %u," ZT_EOL_S
- "\t\"errorCode\": %u," ZT_EOL_S
- "\t\"vendor\": %d," ZT_EOL_S
- "\t\"protocolVersion\": %u," ZT_EOL_S
- "\t\"majorVersion\": %u," ZT_EOL_S
- "\t\"minorVersion\": %u," ZT_EOL_S
- "\t\"revision\": %u," ZT_EOL_S
- "\t\"platform\": %d," ZT_EOL_S
- "\t\"architecture\": %d," ZT_EOL_S
- "\t\"receivedOnLocalAddress\": \"%s\"," ZT_EOL_S
- "\t\"receivedFromRemoteAddress\": \"%s\"" ZT_EOL_S
- "}",
- ((cte->second.jsonResults.length() > 0) ? ",\n" : ""),
- (unsigned long long)report->timestamp,
+ "{\"id\": \"%s\","
+ "\"timestamp\": %llu,"
+ "\"networkId\": \"%.16llx\","
+ "\"testId\": \"%.16llx\","
+ "\"upstream\": \"%.10llx\","
+ "\"current\": \"%.10llx\","
+ "\"receivedTimestamp\": %llu,"
+ "\"sourcePacketId\": \"%.16llx\","
+ "\"flags\": %llu,"
+ "\"sourcePacketHopCount\": %u,"
+ "\"errorCode\": %u,"
+ "\"vendor\": %d,"
+ "\"protocolVersion\": %u,"
+ "\"majorVersion\": %u,"
+ "\"minorVersion\": %u,"
+ "\"revision\": %u,"
+ "\"platform\": %d,"
+ "\"architecture\": %d,"
+ "\"receivedOnLocalAddress\": \"%s\","
+ "\"receivedFromRemoteAddress\": \"%s\","
+ "\"receivedFromLinkQuality\": %f}",
+ id + 30, // last bit only, not leading path
+ (unsigned long long)test->timestamp,
+ (unsigned long long)test->credentialNetworkId,
(unsigned long long)test->testId,
(unsigned long long)report->upstream,
(unsigned long long)report->current,
- (unsigned long long)OSUtils::now(),
+ (unsigned long long)now,
(unsigned long long)report->sourcePacketId,
(unsigned long long)report->flags,
report->sourcePacketHopCount,
@@ -1632,49 +1211,632 @@ void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTes
(int)report->platform,
(int)report->architecture,
reinterpret_cast<const InetAddress *>(&(report->receivedOnLocalAddress))->toString().c_str(),
- reinterpret_cast<const InetAddress *>(&(report->receivedFromRemoteAddress))->toString().c_str());
+ reinterpret_cast<const InetAddress *>(&(report->receivedFromRemoteAddress))->toString().c_str(),
+ ((double)report->receivedFromLinkQuality / (double)ZT_PATH_LINK_QUALITY_MAX));
- cte->second.jsonResults.append(tmp);
+ Mutex::Lock _l(self->_db_m);
+ self->_db.writeRaw(id,std::string(tmp));
}
-void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi)
+void EmbeddedNetworkController::_request(
+ uint64_t nwid,
+ const InetAddress &fromAddr,
+ uint64_t requestPacketId,
+ const Identity &identity,
+ const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData)
{
- Mutex::Lock _mcl(_networkMemberCache_m);
- std::map< Address,nlohmann::json > &memberCacheEntry = _networkMemberCache[nwid];
- nmi.totalMemberCount = memberCacheEntry.size();
- for(std::map< Address,nlohmann::json >::iterator nm(memberCacheEntry.begin());nm!=memberCacheEntry.end();++nm) {
- if (_jB(nm->second["authorized"],false)) {
- ++nmi.authorizedMemberCount;
-
- if (nm->second.count("recentLog")) {
- json &mlog = nm->second["recentLog"];
- if ((mlog.is_array())&&(mlog.size() > 0)) {
- json &mlog1 = mlog[0];
- if (mlog1.is_object()) {
- if ((now - _jI(mlog1["ts"],0ULL)) < ZT_NETCONF_NODE_ACTIVE_THRESHOLD)
- ++nmi.activeMemberCount;
+ if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender))
+ return;
+
+ const uint64_t now = OSUtils::now();
+
+ if (requestPacketId) {
+ Mutex::Lock _l(_lastRequestTime_m);
+ uint64_t &lrt = _lastRequestTime[std::pair<uint64_t,uint64_t>(identity.address().toInt(),nwid)];
+ if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
+ return;
+ lrt = now;
+ }
+
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid);
+ json network;
+ json member;
+ {
+ Mutex::Lock _l(_db_m);
+ network = _db.get("network",nwids,0);
+ member = _db.get("network",nwids,"member",identity.address().toString(),0);
+ }
+
+ if (!network.size()) {
+ _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND);
+ return;
+ }
+
+ const bool newMember = (member.size() == 0);
+
+ json origMember(member); // for detecting modification later
+ _initMember(member);
+
+ {
+ std::string haveIdStr(OSUtils::jsonString(member["identity"],""));
+ if (haveIdStr.length() > 0) {
+ // If we already know this member's identity perform a full compare. This prevents
+ // a "collision" from being able to auth onto our network in place of an already
+ // known member.
+ try {
+ if (Identity(haveIdStr.c_str()) != identity) {
+ _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED);
+ return;
+ }
+ } catch ( ... ) {
+ _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED);
+ return;
+ }
+ } else {
+ // If we do not yet know this member's identity, learn it.
+ member["identity"] = identity.toString(false);
+ }
+ }
+
+ // These are always the same, but make sure they are set
+ member["id"] = identity.address().toString();
+ member["address"] = member["id"];
+ member["nwid"] = nwids;
+
+ // Determine whether and how member is authorized
+ const char *authorizedBy = (const char *)0;
+ bool autoAuthorized = false;
+ json autoAuthCredentialType,autoAuthCredential;
+ if (OSUtils::jsonBool(member["authorized"],false)) {
+ authorizedBy = "memberIsAuthorized";
+ } else if (!OSUtils::jsonBool(network["private"],true)) {
+ authorizedBy = "networkIsPublic";
+ json &ahist = member["authHistory"];
+ if ((!ahist.is_array())||(ahist.size() == 0))
+ autoAuthorized = true;
+ } else {
+ char presentedAuth[512];
+ if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) {
+ presentedAuth[511] = (char)0; // sanity check
+
+ // Check for bearer token presented by member
+ if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) {
+ const char *const presentedToken = presentedAuth + 6;
+
+ json &authTokens = network["authTokens"];
+ if (authTokens.is_array()) {
+ for(unsigned long i=0;i<authTokens.size();++i) {
+ json &token = authTokens[i];
+ if (token.is_object()) {
+ const uint64_t expires = OSUtils::jsonInt(token["expires"],0ULL);
+ const uint64_t maxUses = OSUtils::jsonInt(token["maxUsesPerMember"],0ULL);
+ std::string tstr = OSUtils::jsonString(token["token"],"");
+
+ if (((expires == 0ULL)||(expires > now))&&(tstr == presentedToken)) {
+ bool usable = (maxUses == 0);
+ if (!usable) {
+ uint64_t useCount = 0;
+ json &ahist = member["authHistory"];
+ if (ahist.is_array()) {
+ for(unsigned long j=0;j<ahist.size();++j) {
+ json &ah = ahist[j];
+ if ((OSUtils::jsonString(ah["ct"],"") == "token")&&(OSUtils::jsonString(ah["c"],"") == tstr)&&(OSUtils::jsonBool(ah["a"],false)))
+ ++useCount;
+ }
+ }
+ usable = (useCount < maxUses);
+ }
+ if (usable) {
+ authorizedBy = "token";
+ autoAuthorized = true;
+ autoAuthCredentialType = "token";
+ autoAuthCredential = tstr;
+ }
+ }
+ }
}
}
}
+ }
+ }
- if (_jB(nm->second["activeBridge"],false)) {
- nmi.activeBridges.insert(nm->first);
+ // If we auto-authorized, update member record
+ if ((autoAuthorized)&&(authorizedBy)) {
+ member["authorized"] = true;
+ member["lastAuthorizedTime"] = now;
+
+ json ah;
+ ah["a"] = true;
+ ah["by"] = authorizedBy;
+ ah["ts"] = now;
+ ah["ct"] = autoAuthCredentialType;
+ ah["c"] = autoAuthCredential;
+ member["authHistory"].push_back(ah);
+
+ json &revj = member["revision"];
+ member["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL);
+ }
+
+ // Log this request
+ if (requestPacketId) { // only log if this is a request, not for generated pushes
+ json rlEntry = json::object();
+ rlEntry["ts"] = now;
+ rlEntry["auth"] = (authorizedBy) ? true : false;
+ rlEntry["authBy"] = (authorizedBy) ? authorizedBy : "";
+ rlEntry["vMajor"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0);
+ rlEntry["vMinor"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0);
+ rlEntry["vRev"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0);
+ rlEntry["vProto"] = metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,0);
+ if (fromAddr)
+ rlEntry["fromAddr"] = fromAddr.toString();
+
+ json recentLog = json::array();
+ recentLog.push_back(rlEntry);
+ json &oldLog = member["recentLog"];
+ if (oldLog.is_array()) {
+ for(unsigned long i=0;i<oldLog.size();++i) {
+ recentLog.push_back(oldLog[i]);
+ if (recentLog.size() >= ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH)
+ break;
}
+ }
+ member["recentLog"] = recentLog;
- if (nm->second.count("ipAssignments")) {
- json &mips = nm->second["ipAssignments"];
- if (mips.is_array()) {
- for(unsigned long i=0;i<mips.size();++i) {
- InetAddress mip(_jS(mips[i],""));
- if ((mip.ss_family == AF_INET)||(mip.ss_family == AF_INET6))
- nmi.allocatedIps.insert(mip);
+ // Also only do this on real requests
+ member["lastRequestMetaData"] = metaData.data();
+ }
+
+ // If they are not authorized, STOP!
+ if (!authorizedBy) {
+ if (origMember != member) {
+ member["lastModified"] = now;
+ Mutex::Lock _l(_db_m);
+ _db.put("network",nwids,"member",identity.address().toString(),member);
+ }
+ _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED);
+ return;
+ }
+
+ // -------------------------------------------------------------------------
+ // If we made it this far, they are authorized.
+ // -------------------------------------------------------------------------
+
+ NetworkConfig nc;
+ _NetworkMemberInfo nmi;
+ _getNetworkMemberInfo(now,nwid,nmi);
+
+ uint64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA;
+ if (now > nmi.mostRecentDeauthTime) {
+ // If we recently de-authorized a member, shrink credential TTL/max delta to
+ // be below the threshold required to exclude it. Cap this to a min/max to
+ // prevent jitter or absurdly large values.
+ const uint64_t deauthWindow = now - nmi.mostRecentDeauthTime;
+ if (deauthWindow < ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA) {
+ credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA;
+ } else if (deauthWindow < (ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA + 5000ULL)) {
+ credentialtmd = deauthWindow - 5000ULL;
+ }
+ }
+
+ nc.networkId = nwid;
+ nc.type = OSUtils::jsonBool(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
+ nc.timestamp = now;
+ nc.credentialTimeMaxDelta = credentialtmd;
+ nc.revision = OSUtils::jsonInt(network["revision"],0ULL);
+ nc.issuedTo = identity.address();
+ if (OSUtils::jsonBool(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
+ if (OSUtils::jsonBool(network["allowPassiveBridging"],false)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING;
+ Utils::scopy(nc.name,sizeof(nc.name),OSUtils::jsonString(network["name"],"").c_str());
+ nc.multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL);
+
+ for(std::set<Address>::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) {
+ nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE);
+ }
+
+ json &v4AssignMode = network["v4AssignMode"];
+ json &v6AssignMode = network["v6AssignMode"];
+ json &ipAssignmentPools = network["ipAssignmentPools"];
+ json &routes = network["routes"];
+ json &rules = network["rules"];
+ json &capabilities = network["capabilities"];
+ json &tags = network["tags"];
+ json &memberCapabilities = member["capabilities"];
+ json &memberTags = member["tags"];
+
+ if (metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,0) <= 0) {
+ // Old versions with no rules engine support get an allow everything rule.
+ // Since rules are enforced bidirectionally, newer versions *will* still
+ // enforce rules on the inbound side.
+ nc.ruleCount = 1;
+ nc.rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT;
+ } else {
+ if (rules.is_array()) {
+ for(unsigned long i=0;i<rules.size();++i) {
+ if (nc.ruleCount >= ZT_MAX_NETWORK_RULES)
+ break;
+ if (_parseRule(rules[i],nc.rules[nc.ruleCount]))
+ ++nc.ruleCount;
+ }
+ }
+
+ std::map< uint64_t,json * > capsById;
+ if (!memberCapabilities.is_array())
+ memberCapabilities = json::array();
+ if (capabilities.is_array()) {
+ for(unsigned long i=0;i<capabilities.size();++i) {
+ json &cap = capabilities[i];
+ if (cap.is_object()) {
+ const uint64_t id = OSUtils::jsonInt(cap["id"],0ULL) & 0xffffffffULL;
+ capsById[id] = &cap;
+ if ((newMember)&&(OSUtils::jsonBool(cap["default"],false))) {
+ bool have = false;
+ for(unsigned long i=0;i<memberCapabilities.size();++i) {
+ if (id == (OSUtils::jsonInt(memberCapabilities[i],0ULL) & 0xffffffffULL)) {
+ have = true;
+ break;
+ }
+ }
+ if (!have)
+ memberCapabilities.push_back(id);
}
}
}
- } else {
- nmi.mostRecentDeauthTime = std::max(nmi.mostRecentDeauthTime,_jI(nm->second["lastDeauthorizedTime"],0ULL));
+ }
+ for(unsigned long i=0;i<memberCapabilities.size();++i) {
+ const uint64_t capId = OSUtils::jsonInt(memberCapabilities[i],0ULL) & 0xffffffffULL;
+ std::map< uint64_t,json * >::const_iterator ctmp = capsById.find(capId);
+ if (ctmp != capsById.end()) {
+ json *cap = ctmp->second;
+ if ((cap)&&(cap->is_object())&&(cap->size() > 0)) {
+ ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES];
+ unsigned int caprc = 0;
+ json &caprj = (*cap)["rules"];
+ if ((caprj.is_array())&&(caprj.size() > 0)) {
+ for(unsigned long j=0;j<caprj.size();++j) {
+ if (caprc >= ZT_MAX_CAPABILITY_RULES)
+ break;
+ if (_parseRule(caprj[j],capr[caprc]))
+ ++caprc;
+ }
+ }
+ nc.capabilities[nc.capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc);
+ if (nc.capabilities[nc.capabilityCount].sign(_signingId,identity.address()))
+ ++nc.capabilityCount;
+ if (nc.capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES)
+ break;
+ }
+ }
+ }
+
+ std::map< uint32_t,uint32_t > memberTagsById;
+ if (memberTags.is_array()) {
+ for(unsigned long i=0;i<memberTags.size();++i) {
+ json &t = memberTags[i];
+ if ((t.is_array())&&(t.size() == 2))
+ memberTagsById[(uint32_t)(OSUtils::jsonInt(t[0],0ULL) & 0xffffffffULL)] = (uint32_t)(OSUtils::jsonInt(t[1],0ULL) & 0xffffffffULL);
+ }
+ }
+ if (tags.is_array()) { // check network tags array for defaults that are not present in member tags
+ for(unsigned long i=0;i<tags.size();++i) {
+ json &t = tags[i];
+ if (t.is_object()) {
+ const uint32_t id = (uint32_t)(OSUtils::jsonInt(t["id"],0) & 0xffffffffULL);
+ json &dfl = t["default"];
+ if ((dfl.is_number())&&(memberTagsById.find(id) == memberTagsById.end())) {
+ memberTagsById[id] = (uint32_t)(OSUtils::jsonInt(dfl,0) & 0xffffffffULL);
+ json mt = json::array();
+ mt.push_back(id);
+ mt.push_back(dfl);
+ memberTags.push_back(mt); // add default to member tags if not present
+ }
+ }
+ }
+ }
+ for(std::map< uint32_t,uint32_t >::const_iterator t(memberTagsById.begin());t!=memberTagsById.end();++t) {
+ if (nc.tagCount >= ZT_MAX_NETWORK_TAGS)
+ break;
+ nc.tags[nc.tagCount] = Tag(nwid,now,identity.address(),t->first,t->second);
+ if (nc.tags[nc.tagCount].sign(_signingId))
+ ++nc.tagCount;
+ }
+ }
+
+ if (routes.is_array()) {
+ for(unsigned long i=0;i<routes.size();++i) {
+ if (nc.routeCount >= ZT_MAX_NETWORK_ROUTES)
+ break;
+ json &route = routes[i];
+ json &target = route["target"];
+ json &via = route["via"];
+ if (target.is_string()) {
+ const InetAddress t(target.get<std::string>());
+ InetAddress v;
+ if (via.is_string()) v.fromString(via.get<std::string>());
+ if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) {
+ ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]);
+ *(reinterpret_cast<InetAddress *>(&(r->target))) = t;
+ if (v.ss_family == t.ss_family)
+ *(reinterpret_cast<InetAddress *>(&(r->via))) = v;
+ ++nc.routeCount;
+ }
+ }
}
}
+
+ const bool noAutoAssignIps = OSUtils::jsonBool(member["noAutoAssignIps"],false);
+
+ if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) {
+ if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
+ nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt());
+ nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
+ }
+ if ((OSUtils::jsonBool(v6AssignMode["6plane"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
+ nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt());
+ nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
+ }
+ }
+
+ bool haveManagedIpv4AutoAssignment = false;
+ bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count
+ json ipAssignments = member["ipAssignments"]; // we want to make a copy
+ if (ipAssignments.is_array()) {
+ for(unsigned long i=0;i<ipAssignments.size();++i) {
+ if (!ipAssignments[i].is_string())
+ continue;
+ std::string ips = ipAssignments[i];
+ InetAddress ip(ips);
+
+ // IP assignments are only pushed if there is a corresponding local route. We also now get the netmask bits from
+ // this route, ignoring the netmask bits field of the assigned IP itself. Using that was worthless and a source
+ // of user error / poor UX.
+ int routedNetmaskBits = 0;
+ for(unsigned int rk=0;rk<nc.routeCount;++rk) {
+ if ( (!nc.routes[rk].via.ss_family) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip)) )
+ routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits();
+ }
+
+ if (routedNetmaskBits > 0) {
+ if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
+ ip.setPort(routedNetmaskBits);
+ nc.staticIps[nc.staticIpCount++] = ip;
+ }
+ if (ip.ss_family == AF_INET)
+ haveManagedIpv4AutoAssignment = true;
+ else if (ip.ss_family == AF_INET6)
+ haveManagedIpv6AutoAssignment = true;
+ }
+ }
+ } else {
+ ipAssignments = json::array();
+ }
+
+ if ( (ipAssignmentPools.is_array()) && ((v6AssignMode.is_object())&&(OSUtils::jsonBool(v6AssignMode["zt"],false))) && (!haveManagedIpv6AutoAssignment) && (!noAutoAssignIps) ) {
+ for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv6AutoAssignment));++p) {
+ json &pool = ipAssignmentPools[p];
+ if (pool.is_object()) {
+ InetAddress ipRangeStart(OSUtils::jsonString(pool["ipRangeStart"],""));
+ InetAddress ipRangeEnd(OSUtils::jsonString(pool["ipRangeEnd"],""));
+ if ( (ipRangeStart.ss_family == AF_INET6) && (ipRangeEnd.ss_family == AF_INET6) ) {
+ uint64_t s[2],e[2],x[2],xx[2];
+ memcpy(s,ipRangeStart.rawIpData(),16);
+ memcpy(e,ipRangeEnd.rawIpData(),16);
+ s[0] = Utils::ntoh(s[0]);
+ s[1] = Utils::ntoh(s[1]);
+ e[0] = Utils::ntoh(e[0]);
+ e[1] = Utils::ntoh(e[1]);
+ x[0] = s[0];
+ x[1] = s[1];
+
+ for(unsigned int trialCount=0;trialCount<1000;++trialCount) {
+ if ((trialCount == 0)&&(e[1] > s[1])&&((e[1] - s[1]) >= 0xffffffffffULL)) {
+ // First see if we can just cram a ZeroTier ID into the higher 64 bits. If so do that.
+ xx[0] = Utils::hton(x[0]);
+ xx[1] = Utils::hton(x[1] + identity.address().toInt());
+ } else {
+ // Otherwise pick random addresses -- this technically doesn't explore the whole range if the lower 64 bit range is >= 1 but that won't matter since that would be huge anyway
+ Utils::getSecureRandom((void *)xx,16);
+ if ((e[0] > s[0]))
+ xx[0] %= (e[0] - s[0]);
+ else xx[0] = 0;
+ if ((e[1] > s[1]))
+ xx[1] %= (e[1] - s[1]);
+ else xx[1] = 0;
+ xx[0] = Utils::hton(x[0] + xx[0]);
+ xx[1] = Utils::hton(x[1] + xx[1]);
+ }
+
+ InetAddress ip6((const void *)xx,16,0);
+
+ // Check if this IP is within a local-to-Ethernet routed network
+ int routedNetmaskBits = 0;
+ for(unsigned int rk=0;rk<nc.routeCount;++rk) {
+ if ( (!nc.routes[rk].via.ss_family) && (nc.routes[rk].target.ss_family == AF_INET6) && (reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->containsAddress(ip6)) )
+ routedNetmaskBits = reinterpret_cast<const InetAddress *>(&(nc.routes[rk].target))->netmaskBits();
+ }
+
+ // If it's routed, then try to claim and assign it and if successful end loop
+ if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip6))) {
+ ipAssignments.push_back(ip6.toIpString());
+ member["ipAssignments"] = ipAssignments;
+ ip6.setPort((unsigned int)routedNetmaskBits);
+ if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)
+ nc.staticIps[nc.staticIpCount++] = ip6;
+ haveManagedIpv6AutoAssignment = true;
+ _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( (ipAssignmentPools.is_array()) && ((v4AssignMode.is_object())&&(OSUtils::jsonBool(v4AssignMode["zt"],false))) && (!haveManagedIpv4AutoAssignment) && (!noAutoAssignIps) ) {
+ for(unsigned long p=0;((p<ipAssignmentPools.size())&&(!haveManagedIpv4AutoAssignment));++p) {
+ json &pool = ipAssignmentPools[p];
+ if (pool.is_object()) {
+ InetAddress ipRangeStartIA(OSUtils::jsonString(pool["ipRangeStart"],""));
+ InetAddress ipRangeEndIA(OSUtils::jsonString(pool["ipRangeEnd"],""));
+ if ( (ipRangeStartIA.ss_family == AF_INET) && (ipRangeEndIA.ss_family == AF_INET) ) {
+ uint32_t ipRangeStart = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeStartIA)->sin_addr.s_addr));
+ uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast<struct sockaddr_in *>(&ipRangeEndIA)->sin_addr.s_addr));
+ if ((ipRangeEnd < ipRangeStart)||(ipRangeStart == 0))
+ continue;
+ uint32_t ipRangeLen = ipRangeEnd - ipRangeStart;
+
+ // Start with the LSB of the member's address
+ uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff);
+
+ for(uint32_t k=ipRangeStart,trialCount=0;((k<=ipRangeEnd)&&(trialCount < 1000));++k,++trialCount) {
+ uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart;
+ ++ipTrialCounter;
+ if ((ip & 0x000000ff) == 0x000000ff)
+ continue; // don't allow addresses that end in .255
+
+ // Check if this IP is within a local-to-Ethernet routed network
+ int routedNetmaskBits = -1;
+ for(unsigned int rk=0;rk<nc.routeCount;++rk) {
+ if (nc.routes[rk].target.ss_family == AF_INET) {
+ uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_addr.s_addr));
+ int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_port));
+ if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) {
+ routedNetmaskBits = targetBits;
+ break;
+ }
+ }
+ }
+
+ // If it's routed, then try to claim and assign it and if successful end loop
+ const InetAddress ip4(Utils::hton(ip),0);
+ if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip4))) {
+ ipAssignments.push_back(ip4.toIpString());
+ member["ipAssignments"] = ipAssignments;
+ if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
+ struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++]));
+ v4ip->sin_family = AF_INET;
+ v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits);
+ v4ip->sin_addr.s_addr = Utils::hton(ip);
+ }
+ haveManagedIpv4AutoAssignment = true;
+ _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Issue a certificate of ownership for all static IPs
+ if (nc.staticIpCount) {
+ nc.certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1);
+ for(unsigned int i=0;i<nc.staticIpCount;++i)
+ nc.certificatesOfOwnership[0].addThing(nc.staticIps[i]);
+ nc.certificatesOfOwnership[0].sign(_signingId);
+ nc.certificateOfOwnershipCount = 1;
+ }
+
+ CertificateOfMembership com(now,credentialtmd,nwid,identity.address());
+ if (com.sign(_signingId)) {
+ nc.com = com;
+ } else {
+ _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (member != origMember) {
+ member["lastModified"] = now;
+ Mutex::Lock _l(_db_m);
+ _db.put("network",nwids,"member",identity.address().toString(),member);
+ }
+
+ _sender->ncSendConfig(nwid,requestPacketId,identity.address(),nc,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6);
+}
+
+void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi)
+{
+ char pfx[256];
+ Utils::snprintf(pfx,sizeof(pfx),"network/%.16llx/member",nwid);
+
+ {
+ Mutex::Lock _l(_nmiCache_m);
+ std::map<uint64_t,_NetworkMemberInfo>::iterator c(_nmiCache.find(nwid));
+ if ((c != _nmiCache.end())&&((now - c->second.nmiTimestamp) < 1000)) { // a short duration cache but limits CPU use on big networks
+ nmi = c->second;
+ return;
+ }
+ }
+
+ {
+ Mutex::Lock _l(_db_m);
+ _db.filter(pfx,120000,[&nmi,&now](const std::string &n,const json &member) {
+ try {
+ if (OSUtils::jsonBool(member["authorized"],false)) {
+ ++nmi.authorizedMemberCount;
+
+ if (member.count("recentLog")) {
+ const json &mlog = member["recentLog"];
+ if ((mlog.is_array())&&(mlog.size() > 0)) {
+ const json &mlog1 = mlog[0];
+ if (mlog1.is_object()) {
+ if ((now - OSUtils::jsonInt(mlog1["ts"],0ULL)) < ZT_NETCONF_NODE_ACTIVE_THRESHOLD)
+ ++nmi.activeMemberCount;
+ }
+ }
+ }
+
+ if (OSUtils::jsonBool(member["activeBridge"],false)) {
+ nmi.activeBridges.insert(Address(Utils::hexStrToU64(OSUtils::jsonString(member["id"],"0000000000").c_str())));
+ }
+
+ if (member.count("ipAssignments")) {
+ const json &mips = member["ipAssignments"];
+ if (mips.is_array()) {
+ for(unsigned long i=0;i<mips.size();++i) {
+ InetAddress mip(OSUtils::jsonString(mips[i],""));
+ if ((mip.ss_family == AF_INET)||(mip.ss_family == AF_INET6))
+ nmi.allocatedIps.insert(mip);
+ }
+ }
+ }
+ } else {
+ nmi.mostRecentDeauthTime = std::max(nmi.mostRecentDeauthTime,OSUtils::jsonInt(member["lastDeauthorizedTime"],0ULL));
+ }
+ } catch ( ... ) {}
+ return true;
+ });
+ }
+ nmi.nmiTimestamp = now;
+
+ {
+ Mutex::Lock _l(_nmiCache_m);
+ _nmiCache[nwid] = nmi;
+ }
+}
+
+void EmbeddedNetworkController::_pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member)
+{
+ try {
+ const std::string idstr = member["identity"];
+ const std::string mdstr = member["lastRequestMetaData"];
+ if ((idstr.length() > 0)&&(mdstr.length() > 0)) {
+ const Identity id(idstr);
+ bool online;
+ {
+ Mutex::Lock _l(_lastRequestTime_m);
+ std::map< std::pair<uint64_t,uint64_t>,uint64_t >::iterator lrt(_lastRequestTime.find(std::pair<uint64_t,uint64_t>(id.address().toInt(),nwid)));
+ online = ( (lrt != _lastRequestTime.end()) && ((now - lrt->second) < ZT_NETWORK_AUTOCONF_DELAY) );
+ }
+ if (online) {
+ Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> *metaData = new Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY>(mdstr.c_str());
+ try {
+ this->request(nwid,InetAddress(),0,id,*metaData);
+ } catch ( ... ) {}
+ delete metaData;
+ }
+ }
+ } catch ( ... ) {}
}
} // namespace ZeroTier
diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp
index bce8890c..bca0956e 100644
--- a/controller/EmbeddedNetworkController.hpp
+++ b/controller/EmbeddedNetworkController.hpp
@@ -37,9 +37,18 @@
#include "../osdep/OSUtils.hpp"
#include "../osdep/Thread.hpp"
+#include "../osdep/BlockingQueue.hpp"
#include "../ext/json/json.hpp"
+#include "JSONDB.hpp"
+
+// Number of background threads to start -- not actually started until needed
+#define ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT 2
+
+// TTL for circuit tests
+#define ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION 120000
+
namespace ZeroTier {
class Node;
@@ -47,20 +56,21 @@ class Node;
class EmbeddedNetworkController : public NetworkController
{
public:
+ /**
+ * @param node Parent node
+ * @param dbPath Path to store data
+ */
EmbeddedNetworkController(Node *node,const char *dbPath);
virtual ~EmbeddedNetworkController();
- // Thread main method -- do not call directly
- void threadMain()
- throw();
+ virtual void init(const Identity &signingId,Sender *sender);
- virtual NetworkController::ResultCode doNetworkConfigRequest(
+ virtual void request(
+ uint64_t nwid,
const InetAddress &fromAddr,
- const Identity &signingId,
+ uint64_t requestPacketId,
const Identity &identity,
- uint64_t nwid,
- const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData,
- NetworkConfig &nc);
+ const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
unsigned int handleControlPlaneHttpGET(
const std::vector<std::string> &path,
@@ -84,49 +94,33 @@ public:
std::string &responseBody,
std::string &responseContentType);
+ void threadMain()
+ throw();
+
private:
static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report);
+ void _request(
+ uint64_t nwid,
+ const InetAddress &fromAddr,
+ uint64_t requestPacketId,
+ const Identity &identity,
+ const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
- // Network base path and network JSON path
- inline std::string _networkBP(const uint64_t nwid,bool create)
- {
- char tmp[64];
- std::string p(_path + ZT_PATH_SEPARATOR_S + "network");
- if (create) OSUtils::mkdir(p.c_str());
- p.push_back(ZT_PATH_SEPARATOR);
- Utils::snprintf(tmp,sizeof(tmp),"%.16llx",nwid);
- p.append(tmp);
- if (create) OSUtils::mkdir(p.c_str());
- return p;
- }
- inline std::string _networkJP(const uint64_t nwid,bool create)
- {
- return (_networkBP(nwid,create) + ZT_PATH_SEPARATOR + "config.json");
- }
-
- // Member base path and member JSON path
- inline std::string _memberBP(const uint64_t nwid,const Address &member,bool create)
+ struct _RQEntry
{
- std::string p(_networkBP(nwid,create));
- p.push_back(ZT_PATH_SEPARATOR);
- p.append("member");
- if (create) OSUtils::mkdir(p.c_str());
- p.push_back(ZT_PATH_SEPARATOR);
- p.append(member.toString());
- if (create) OSUtils::mkdir(p.c_str());
- return p;
- }
- inline std::string _memberJP(const uint64_t nwid,const Address &member,bool create)
- {
- return (_memberBP(nwid,member,create) + ZT_PATH_SEPARATOR + "config.json");
- }
+ uint64_t nwid;
+ uint64_t requestPacketId;
+ InetAddress fromAddr;
+ Identity identity;
+ Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData;
+ };
+ BlockingQueue<_RQEntry *> _queue;
- // In-memory cache of network members
- std::map< uint64_t,std::map< Address,nlohmann::json > > _networkMemberCache;
- Mutex _networkMemberCache_m;
+ Thread _threads[ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT];
+ bool _threadsStarted;
+ Mutex _threads_m;
// Gathers a bunch of statistics about members of a network, IP assignments, etc. that we need in various places
- // This does lock _networkMemberCache_m
struct _NetworkMemberInfo
{
_NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {}
@@ -136,8 +130,18 @@ private:
unsigned long activeMemberCount;
unsigned long totalMemberCount;
uint64_t mostRecentDeauthTime;
+ uint64_t nmiTimestamp; // time this NMI structure was computed
};
+ std::map<uint64_t,_NetworkMemberInfo> _nmiCache;
+ Mutex _nmiCache_m;
void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi);
+ inline void _clearNetworkMemberInfoCache(const uint64_t nwid)
+ {
+ Mutex::Lock _l(_nmiCache_m);
+ _nmiCache.erase(nwid);
+ }
+
+ void _pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member);
// These init objects with default and static/informational fields
inline void _initMember(nlohmann::json &member)
@@ -152,7 +156,8 @@ private:
if (!member.count("creationTime")) member["creationTime"] = OSUtils::now();
if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false;
if (!member.count("revision")) member["revision"] = 0ULL;
- if (!member.count("enableBroadcast")) member["enableBroadcast"] = true;
+ if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
+ if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
member["objtype"] = "member";
}
inline void _initNetwork(nlohmann::json &network)
@@ -161,17 +166,21 @@ private:
if (!network.count("creationTime")) network["creationTime"] = OSUtils::now();
if (!network.count("name")) network["name"] = "";
if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32;
+ if (!network.count("enableBroadcast")) network["enableBroadcast"] = true;
if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}};
if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}};
if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array();
if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array();
+ if (!network.count("tags")) network["tags"] = nlohmann::json::array();
+ if (!network.count("routes")) network["routes"] = nlohmann::json::array();
if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array();
if (!network.count("rules")) {
// If unspecified, rules are set to allow anything and behave like a flat L2 segment
- network["rules"] = {
+ network["rules"] = {{
{ "not",false },
+ { "or", false },
{ "type","ACTION_ACCEPT" }
- };
+ }};
}
network["objtype"] = "network";
}
@@ -187,37 +196,20 @@ private:
member["clock"] = now;
}
- // These are const after construction
+ JSONDB _db;
+ Mutex _db_m;
+
Node *const _node;
std::string _path;
- // Circuit tests outstanding
- struct _CircuitTestEntry
- {
- ZT_CircuitTest *test;
- std::string jsonResults;
- };
- std::map< uint64_t,_CircuitTestEntry > _circuitTests;
- Mutex _circuitTests_m;
-
- // Last request time by address, for rate limitation
- std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime;
- Mutex _lastRequestTime_m;
+ NetworkController::Sender *_sender;
+ Identity _signingId;
- // Queue of network member refreshes to be pushed
- struct _Refresh
- {
- Address dest;
- uint64_t nwid;
- uint64_t blacklistAddresses[64];
- uint64_t blacklistThresholds[64];
- unsigned int numBlacklistEntries;
- };
- std::list< _Refresh > _refreshQueue;
- Mutex _refreshQueue_m;
+ std::list< ZT_CircuitTest > _tests;
+ Mutex _tests_m;
- Thread _daemon;
- volatile bool _daemonRun;
+ std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime; // last request time by <address,networkId>
+ Mutex _lastRequestTime_m;
};
} // namespace ZeroTier
diff --git a/controller/JSONDB.cpp b/controller/JSONDB.cpp
new file mode 100644
index 00000000..1277aabb
--- /dev/null
+++ b/controller/JSONDB.cpp
@@ -0,0 +1,184 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2015 ZeroTier, Inc.
+ *
+ * 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 "JSONDB.hpp"
+
+namespace ZeroTier {
+
+static const nlohmann::json _EMPTY_JSON(nlohmann::json::object());
+
+bool JSONDB::writeRaw(const std::string &n,const std::string &obj)
+{
+ if (!_isValidObjectName(n))
+ return false;
+
+ const std::string path(_genPath(n,true));
+ if (!path.length())
+ return false;
+
+ const std::string buf(obj);
+ if (!OSUtils::writeFile(path.c_str(),buf))
+ return false;
+
+ return true;
+}
+
+bool JSONDB::put(const std::string &n,const nlohmann::json &obj)
+{
+ if (!_isValidObjectName(n))
+ return false;
+
+ const std::string path(_genPath(n,true));
+ if (!path.length())
+ return false;
+
+ const std::string buf(OSUtils::jsonDump(obj));
+ if (!OSUtils::writeFile(path.c_str(),buf))
+ return false;
+
+ _E &e = _db[n];
+ e.obj = obj;
+ e.lastModifiedOnDisk = OSUtils::getLastModified(path.c_str());
+ e.lastCheck = OSUtils::now();
+
+ return true;
+}
+
+const nlohmann::json &JSONDB::get(const std::string &n,unsigned long maxSinceCheck)
+{
+ if (!_isValidObjectName(n))
+ return _EMPTY_JSON;
+
+ const uint64_t now = OSUtils::now();
+ std::string buf;
+ std::map<std::string,_E>::iterator e(_db.find(n));
+
+ if (e != _db.end()) {
+ if ((now - e->second.lastCheck) <= (uint64_t)maxSinceCheck)
+ return e->second.obj;
+
+ const std::string path(_genPath(n,false));
+ if (!path.length()) // sanity check
+ return _EMPTY_JSON;
+
+ // We are somewhat tolerant to momentary disk failures here. This may
+ // occur over e.g. EC2's elastic filesystem (NFS).
+ const uint64_t lm = OSUtils::getLastModified(path.c_str());
+ if (e->second.lastModifiedOnDisk != lm) {
+ if (OSUtils::readFile(path.c_str(),buf)) {
+ try {
+ e->second.obj = OSUtils::jsonParse(buf);
+ e->second.lastModifiedOnDisk = lm; // don't update these if there is a parse error -- try again and again ASAP
+ e->second.lastCheck = now;
+ } catch ( ... ) {} // parse errors result in "holding pattern" behavior
+ }
+ }
+
+ return e->second.obj;
+ } else {
+ const std::string path(_genPath(n,false));
+ if (!path.length())
+ return _EMPTY_JSON;
+
+ if (!OSUtils::readFile(path.c_str(),buf))
+ return _EMPTY_JSON;
+
+ const uint64_t lm = OSUtils::getLastModified(path.c_str());
+ _E &e2 = _db[n];
+ try {
+ e2.obj = OSUtils::jsonParse(buf);
+ } catch ( ... ) {
+ e2.obj = _EMPTY_JSON;
+ buf = "{}";
+ }
+ e2.lastModifiedOnDisk = lm;
+ e2.lastCheck = now;
+
+ return e2.obj;
+ }
+}
+
+void JSONDB::erase(const std::string &n)
+{
+ if (!_isValidObjectName(n))
+ return;
+
+ std::string path(_genPath(n,true));
+ if (!path.length())
+ return;
+
+ OSUtils::rm(path.c_str());
+ _db.erase(n);
+}
+
+void JSONDB::_reload(const std::string &p)
+{
+ std::map<std::string,char> l(OSUtils::listDirectoryFull(p.c_str()));
+ for(std::map<std::string,char>::iterator li(l.begin());li!=l.end();++li) {
+ if (li->second == 'f') {
+ // assume p starts with _basePath, which it always does -- will throw otherwise
+ std::string n(p.substr(_basePath.length()));
+ while ((n.length() > 0)&&(n[0] == ZT_PATH_SEPARATOR)) n = n.substr(1);
+ if (ZT_PATH_SEPARATOR != '/') std::replace(n.begin(),n.end(),ZT_PATH_SEPARATOR,'/');
+ if ((n.length() > 0)&&(n[n.length() - 1] != '/')) n.push_back('/');
+ n.append(li->first);
+ if ((n.length() > 5)&&(n.substr(n.length() - 5) == ".json")) {
+ this->get(n.substr(0,n.length() - 5),0); // causes load and cache or update
+ }
+ } else if (li->second == 'd') {
+ this->_reload(p + ZT_PATH_SEPARATOR + li->first);
+ }
+ }
+}
+
+bool JSONDB::_isValidObjectName(const std::string &n)
+{
+ if (n.length() == 0)
+ return false;
+ const char *p = n.c_str();
+ char c;
+ // For security reasons we should not allow dots, backslashes, or other path characters or potential path characters.
+ while ((c = *(p++))) {
+ if (!( ((c >= 'a')&&(c <= 'z')) || ((c >= 'A')&&(c <= 'Z')) || ((c >= '0')&&(c <= '9')) || (c == '/') || (c == '_') || (c == '~') || (c == '-') ))
+ return false;
+ }
+ return true;
+}
+
+std::string JSONDB::_genPath(const std::string &n,bool create)
+{
+ std::vector<std::string> pt(OSUtils::split(n.c_str(),"/","",""));
+ if (pt.size() == 0)
+ return std::string();
+
+ std::string p(_basePath);
+ if (create) OSUtils::mkdir(p.c_str());
+ for(unsigned long i=0,j=(unsigned long)(pt.size()-1);i<j;++i) {
+ p.push_back(ZT_PATH_SEPARATOR);
+ p.append(pt[i]);
+ if (create) OSUtils::mkdir(p.c_str());
+ }
+
+ p.push_back(ZT_PATH_SEPARATOR);
+ p.append(pt[pt.size()-1]);
+ p.append(".json");
+
+ return p;
+}
+
+} // namespace ZeroTier
diff --git a/controller/JSONDB.hpp b/controller/JSONDB.hpp
new file mode 100644
index 00000000..5b7c5e50
--- /dev/null
+++ b/controller/JSONDB.hpp
@@ -0,0 +1,118 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2015 ZeroTier, Inc.
+ *
+ * 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_JSONDB_HPP
+#define ZT_JSONDB_HPP
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+#include <map>
+#include <stdexcept>
+#include <vector>
+#include <algorithm>
+
+#include "../node/Constants.hpp"
+#include "../node/Utils.hpp"
+#include "../ext/json/json.hpp"
+#include "../osdep/OSUtils.hpp"
+
+namespace ZeroTier {
+
+/**
+ * Hierarchical JSON store that persists into the filesystem
+ */
+class JSONDB
+{
+public:
+ JSONDB(const std::string &basePath) :
+ _basePath(basePath)
+ {
+ _reload(_basePath);
+ }
+
+ inline void reload()
+ {
+ _db.clear();
+ _reload(_basePath);
+ }
+
+ bool writeRaw(const std::string &n,const std::string &obj);
+
+ bool put(const std::string &n,const nlohmann::json &obj);
+
+ inline bool put(const std::string &n1,const std::string &n2,const nlohmann::json &obj) { return this->put((n1 + "/" + n2),obj); }
+ inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3),obj); }
+ inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4),obj); }
+ inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5),obj); }
+
+ const nlohmann::json &get(const std::string &n,unsigned long maxSinceCheck = 0);
+
+ inline const nlohmann::json &get(const std::string &n1,const std::string &n2,unsigned long maxSinceCheck = 0) { return this->get((n1 + "/" + n2),maxSinceCheck); }
+ inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,unsigned long maxSinceCheck = 0) { return this->get((n1 + "/" + n2 + "/" + n3),maxSinceCheck); }
+ inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,unsigned long maxSinceCheck = 0) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4),maxSinceCheck); }
+ inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5,unsigned long maxSinceCheck = 0) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5),maxSinceCheck); }
+
+ void erase(const std::string &n);
+
+ inline void erase(const std::string &n1,const std::string &n2) { this->erase(n1 + "/" + n2); }
+ inline void erase(const std::string &n1,const std::string &n2,const std::string &n3) { this->erase(n1 + "/" + n2 + "/" + n3); }
+ inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4); }
+ inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5); }
+
+ template<typename F>
+ inline void filter(const std::string &prefix,unsigned long maxSinceCheck,F func)
+ {
+ for(std::map<std::string,_E>::iterator i(_db.lower_bound(prefix));i!=_db.end();) {
+ if ((i->first.length() >= prefix.length())&&(!memcmp(i->first.data(),prefix.data(),prefix.length()))) {
+ if (!func(i->first,get(i->first,maxSinceCheck))) {
+ std::map<std::string,_E>::iterator i2(i); ++i2;
+ this->erase(i->first);
+ i = i2;
+ } else ++i;
+ } else break;
+ }
+ }
+
+ inline bool operator==(const JSONDB &db) const { return ((_basePath == db._basePath)&&(_db == db._db)); }
+ inline bool operator!=(const JSONDB &db) const { return (!(*this == db)); }
+
+private:
+ void _reload(const std::string &p);
+ bool _isValidObjectName(const std::string &n);
+ std::string _genPath(const std::string &n,bool create);
+
+ struct _E
+ {
+ nlohmann::json obj;
+ uint64_t lastModifiedOnDisk;
+ uint64_t lastCheck;
+
+ inline bool operator==(const _E &e) const { return (obj == e.obj); }
+ inline bool operator!=(const _E &e) const { return (obj != e.obj); }
+ };
+
+ std::string _basePath;
+ std::map<std::string,_E> _db;
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/controller/README.md b/controller/README.md
index 805641d9..db8d0153 100644
--- a/controller/README.md
+++ b/controller/README.md
@@ -7,7 +7,7 @@ As of ZeroTier One version 1.2.0 this code is included in normal builds for desk
Controller data is stored in JSON format under `controller.d` in the ZeroTier working directory. It can be copied, rsync'd, placed in `git`, etc. The files under `controller.d` should not be modified in place while the controller is running or data loss may result, and if they are edited directly take care not to save corrupt JSON since that can also lead to data loss when the controller is restarted. Going through the API is strongly preferred to directly modifying these files.
-### Upgrading from Older Versions
+### Upgrading from Older (1.1.14 or earlier) Versions
Older versions of this code used a SQLite database instead of in-filesystem JSON. A migration utility called `migrate-sqlite` is included here and *must* be used to migrate this data to the new format. If the controller is started with an old `controller.db` in its working directory it will terminate after printing an error to *stderr*. This is done to prevent "surprises" for those running DIY controllers using the old code.
@@ -15,15 +15,9 @@ The migration tool is written in nodeJS and can be used like this:
cd migrate-sqlite
npm install
- node migrate-sqlite.js <path to ZeroTier working directory>
+ node migrate.js </path/to/controller.db> </path/to/controller.d>
-You may need to `sudo node ...` if the ZeroTier working directory is owned by root.
-
-This code will dump the contents of any `controller.db` in the ZeroTier working directory and recreate its data in the form of JSON objects under `controller.d`. The old `controller.db` will then be renamed to `controller.db.migrated` and the controller will start normally.
-
-After migrating make sure that the contents of `controller.d` are owned and writable by the user that will be running the ZeroTier controller process! (Usually this is root but controllers that don't also join networks are sometimes run as unprivileged users.)
-
-If you don't have nodeJS on the machine running ZeroTier it is perfectly fine to just copy `controller.db` to a directory on another machine and run the migration tool there. After running your migration the contents of `controller.d` can be tar'd up and copied back over to the controller. Just remember to rename or remove `controller.db` on the controller machine afterwords so the controller will start.
+Very old versions of nodeJS may have issues. We tested it with version 7.
### Scalability and Reliability
@@ -243,11 +237,12 @@ Note that managed IP assignments are only used if they fall within a managed rou
| Field | Type | Description |
| --------------------- | ------------- | ------------------------------------------------- |
| ts | integer | Time of request, ms since epoch |
-| authorized | boolean | Was member authorized? |
-| clientMajorVersion | integer | Client major version or -1 if unknown |
-| clientMinorVersion | integer | Client minor version or -1 if unknown |
-| clientRevision | integer | Client revision or -1 if unknown |
-| clientProtocolVersion | integer | ZeroTier protocol version reported by client |
+| auth | boolean | Was member authorized? |
+| authBy | string | How was member authorized? |
+| vMajor | integer | Client major version or -1 if unknown |
+| vMinor | integer | Client minor version or -1 if unknown |
+| vRev | integer | Client revision or -1 if unknown |
+| vProto | integer | ZeroTier protocol version reported by client |
| fromAddr | string | Physical address if known |
The controller can only know a member's `fromAddr` if it's able to establish a direct path to it. Members behind very restrictive firewalls may not have this information since the controller will be receiving the member's requests by way of a relay. ZeroTier does not back-trace IP paths as packets are relayed since this would add a lot of protocol overhead.
diff --git a/controller/migrate-sqlite/migrate.js b/controller/migrate-sqlite/migrate.js
new file mode 100644
index 00000000..ac9678a7
--- /dev/null
+++ b/controller/migrate-sqlite/migrate.js
@@ -0,0 +1,320 @@
+'use strict';
+
+var sqlite3 = require('sqlite3').verbose();
+var fs = require('fs');
+var async = require('async');
+
+function blobToIPv4(b)
+{
+ if (!b)
+ return null;
+ if (b.length !== 16)
+ return null;
+ return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString();
+}
+function blobToIPv6(b)
+{
+ if (!b)
+ return null;
+ if (b.length !== 16)
+ return null;
+ var s = '';
+ for(var i=0;i<16;++i) {
+ var x = b.readUInt8(i).toString(16);
+ if (x.length === 1)
+ s += '0';
+ s += x;
+ if ((((i+1) & 1) === 0)&&(i !== 15))
+ s += ':';
+ }
+ return s;
+}
+
+if (process.argv.length !== 4) {
+ console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility');
+ console.log('(c)2017 ZeroTier, Inc. [GPL3]');
+ console.log('');
+ console.log('Usage: node migrate.js </path/to/controller.db> </path/to/controller.d>');
+ console.log('');
+ console.log('The first argument must be the path to the old Sqlite3 controller.db');
+ console.log('file. The second must be the path to the EMPTY controller.d database');
+ console.log('directory for a new (1.1.17 or newer) controller. If this path does');
+ console.log('not exist it will be created.');
+ console.log('');
+ console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.');
+ console.log('If your controller is old you should first upgrade to 1.1.14 and run the');
+ console.log('controller so that it will brings its Sqlite3 database up to the latest');
+ console.log('version before running this migration.');
+ console.log('');
+ process.exit(1);
+}
+
+var oldDbPath = process.argv[2];
+var newDbPath = process.argv[3];
+
+console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...');
+console.log('');
+
+var old = new sqlite3.Database(oldDbPath);
+
+var networks = {};
+
+var nodeIdentities = {};
+var networkCount = 0;
+var memberCount = 0;
+var routeCount = 0;
+var ipAssignmentPoolCount = 0;
+var ipAssignmentCount = 0;
+var ruleCount = 0;
+var oldSchemaVersion = -1;
+
+async.series([function(nextStep) {
+
+ old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) {
+ oldSchemaVersion = parseInt(row.v)||-1;
+ },nextStep);
+
+},function(nextStep) {
+
+ if (oldSchemaVersion !== 4) {
+ console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old');
+ console.log('controller to 1.1.14 first and run it once to bring its DB up to date.');
+ return process.exit(1);
+ }
+
+ console.log('Reading networks...');
+ old.each('SELECT * FROM Network',function(err,row) {
+ if ((typeof row.id === 'string')&&(row.id.length === 16)) {
+ var flags = parseInt(row.flags)||0;
+ networks[row.id] = {
+ id: row.id,
+ nwid: row.id,
+ objtype: 'network',
+ authTokens: [],
+ capabilities: [],
+ creationTime: parseInt(row.creationTime)||0,
+ enableBroadcast: !!row.enableBroadcast,
+ ipAssignmentPools: [],
+ lastModified: Date.now(),
+ multicastLimit: row.multicastLimit||32,
+ name: row.name||'',
+ private: !!row.private,
+ revision: parseInt(row.revision)||1,
+ rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all
+ routes: [],
+ v4AssignMode: {
+ 'zt': ((flags & 1) !== 0)
+ },
+ v6AssignMode: {
+ '6plane': ((flags & 4) !== 0),
+ 'rfc4193': ((flags & 2) !== 0),
+ 'zt': ((flags & 8) !== 0)
+ },
+ _members: {} // temporary
+ };
+ ++networkCount;
+ //console.log(networks[row.id]);
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+networkCount+' networks.');
+ console.log('Reading network route definitions...');
+ old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ var rt = {
+ target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits),
+ via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null)
+ };
+ network.routes.push(rt);
+ ++routeCount;
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+routeCount+' routes in '+networkCount+' networks.');
+ console.log('Reading IP assignment pools...');
+ old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ var p = {
+ ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)),
+ ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd))
+ };
+ network.ipAssignmentPools.push(p);
+ ++ipAssignmentPoolCount;
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.');
+ console.log('Reading known node identities...');
+ old.each('SELECT * FROM Node',function(err,row) {
+ nodeIdentities[row.id] = row.identity;
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+Object.keys(nodeIdentities).length+' known identities.');
+ console.log('Reading network members...');
+ old.each('SELECT * FROM Member',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ network._members[row.nodeId] = {
+ id: row.nodeId,
+ address: row.nodeId,
+ objtype: 'member',
+ authorized: !!row.authorized,
+ activeBridge: !!row.activeBridge,
+ authHistory: [],
+ capabilities: [],
+ creationTime: 0,
+ identity: nodeIdentities[row.nodeId]||null,
+ ipAssignments: [],
+ lastAuthorizedTime: (row.authorized) ? Date.now() : 0,
+ lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(),
+ lastModified: Date.now(),
+ lastRequestMetaData: '',
+ noAutoAssignIps: false,
+ nwid: row.networkId,
+ revision: parseInt(row.memberRevision)||1,
+ tags: [],
+ recentLog: []
+ };
+ ++memberCount;
+ //console.log(network._members[row.nodeId]);
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ console.log(' '+memberCount+' members of '+networkCount+' networks.');
+ console.log('Reading static IP assignments...');
+ old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
+ var network = networks[row.networkId];
+ if (network) {
+ var member = network._members[row.nodeId];
+ if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts
+ if (row.ipVersion == 4) {
+ member.ipAssignments.push(blobToIPv4(row.ip));
+ ++ipAssignmentCount;
+ } else if (row.ipVersion == 6) {
+ member.ipAssignments.push(blobToIPv6(row.ip));
+ ++ipAssignmentCount;
+ }
+ }
+ }
+ },nextStep);
+
+},function(nextStep) {
+
+ // Old versions only supported Ethertype whitelisting, so that's
+ // all we mirror forward. The other fields were always unused.
+
+ console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.');
+ console.log('Reading allowed Ethernet types (old basic rules)...');
+ var etherTypesByNetwork = {};
+ old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) {
+ if (row.networkId in networks) {
+ var et = parseInt(row.etherType)||0;
+ var ets = etherTypesByNetwork[row.networkId];
+ if (!ets)
+ etherTypesByNetwork[row.networkId] = [ et ];
+ else ets.push(et);
+ }
+ },function(err) {
+ if (err) return nextStep(err);
+ for(var nwid in etherTypesByNetwork) {
+ var ets = etherTypesByNetwork[nwid].sort();
+ var network = networks[nwid];
+ if (network) {
+ var rules = [];
+ if (ets.indexOf(0) >= 0) {
+ // If 0 is in the list, all Ethernet types are allowed so we accept all.
+ rules.push({ 'type': 'ACTION_ACCEPT' });
+ } else {
+ // Otherwise we whitelist.
+ for(var i=0;i<ets.length;++i) {
+ rules.push({
+ 'etherType': ets[i],
+ 'not': true,
+ 'or': false,
+ 'type': 'MATCH_ETHERTYPE'
+ });
+ }
+ rules.push({ 'type': 'ACTION_DROP' });
+ rules.push({ 'type': 'ACTION_ACCEPT' });
+ }
+ network.rules = rules;
+ ++ruleCount;
+ }
+ }
+ return nextStep(null);
+ });
+
+}],function(err) {
+
+ if (err) {
+ console.log('FATAL: '+err.toString());
+ return process.exit(1);
+ }
+
+ console.log(' '+ruleCount+' ethernet type whitelists converted to new format rules.');
+ old.close();
+ console.log('Done reading and converting Sqlite3 database! Writing JSONDB files...');
+
+ try {
+ fs.mkdirSync(newDbPath,0o700);
+ } catch (e) {}
+ var nwBase = newDbPath+'/network';
+ try {
+ fs.mkdirSync(nwBase,0o700);
+ } catch (e) {}
+ nwBase = nwBase + '/';
+ var nwids = Object.keys(networks).sort();
+ var fileCount = 0;
+ for(var ni=0;ni<nwids.length;++ni) {
+ var network = networks[nwids[ni]];
+
+ var mids = Object.keys(network._members).sort();
+ if (mids.length > 0) {
+ try {
+ fs.mkdirSync(nwBase+network.id);
+ } catch (e) {}
+ var mbase = nwBase+network.id+'/member';
+ try {
+ fs.mkdirSync(mbase,0o700);
+ } catch (e) {}
+ mbase = mbase + '/';
+
+ for(var mi=0;mi<mids.length;++mi) {
+ var member = network._members[mids[mi]];
+ fs.writeFileSync(mbase+member.id+'.json',JSON.stringify(member,null,1),{ mode: 0o600 });
+ ++fileCount;
+ //console.log(mbase+member.id+'.json');
+ }
+ }
+
+ delete network._members; // temporary field, not part of actual JSONDB, so don't write
+ fs.writeFileSync(nwBase+network.id+'.json',JSON.stringify(network,null,1),{ mode: 0o600 });
+ ++fileCount;
+ //console.log(nwBase+network.id+'.json');
+ }
+
+ console.log('');
+ console.log('SUCCESS! Wrote '+fileCount+' JSONDB files.');
+
+ console.log('');
+ console.log('You should still inspect the new DB before going live. Also be sure');
+ console.log('to "chown -R" and "chgrp -R" the new DB to the user and group under');
+ console.log('which the ZeroTier One instance acting as controller will be running.');
+ console.log('The controller must be able to read and write the DB, of course.');
+ console.log('');
+ console.log('Have fun!');
+
+ return process.exit(0);
+});
diff --git a/controller/migrate-sqlite/package.json b/controller/migrate-sqlite/package.json
new file mode 100644
index 00000000..0dac008f
--- /dev/null
+++ b/controller/migrate-sqlite/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "migrate-sqlite",
+ "version": "1.0.0",
+ "description": "Migrate old SQLite to new JSON filesystem DB for ZeroTier network controller",
+ "main": "migrate.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Adam Ierymenko <adam.ierymenko@zerotier.com>",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "async": "^2.1.4",
+ "sqlite3": "^3.1.8"
+ }
+}