summaryrefslogtreecommitdiff
path: root/controller
diff options
context:
space:
mode:
Diffstat (limited to 'controller')
-rw-r--r--controller/EmbeddedNetworkController.cpp1845
-rw-r--r--controller/EmbeddedNetworkController.hpp217
-rw-r--r--controller/JSONDB.cpp184
-rw-r--r--controller/JSONDB.hpp118
-rw-r--r--controller/README.md219
-rw-r--r--controller/SqliteNetworkController.cpp2195
-rw-r--r--controller/SqliteNetworkController.hpp181
-rw-r--r--controller/migrate-sqlite/migrate.js320
-rw-r--r--controller/migrate-sqlite/package.json15
-rw-r--r--controller/schema.sql119
-rw-r--r--controller/schema.sql.c121
-rwxr-xr-xcontroller/schema2c.sh8
12 files changed, 2802 insertions, 2740 deletions
diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp
new file mode 100644
index 00000000..d2f40b1c
--- /dev/null
+++ b/controller/EmbeddedNetworkController.cpp
@@ -0,0 +1,1845 @@
+/*
+ * 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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef _WIN32
+#include <sys/time.h>
+#endif
+#include <sys/types.h>
+
+#include <algorithm>
+#include <utility>
+#include <stdexcept>
+#include <set>
+#include <map>
+
+#include "../include/ZeroTierOne.h"
+#include "../node/Constants.hpp"
+
+#include "EmbeddedNetworkController.hpp"
+
+#include "../node/Node.hpp"
+#include "../node/Utils.hpp"
+#include "../node/CertificateOfMembership.hpp"
+#include "../node/NetworkConfig.hpp"
+#include "../node/Dictionary.hpp"
+#include "../node/InetAddress.hpp"
+#include "../node/MAC.hpp"
+#include "../node/Address.hpp"
+
+using json = nlohmann::json;
+
+// API version reported via JSON control plane
+#define ZT_NETCONF_CONTROLLER_API_VERSION 3
+
+// Number of requests to remember in member history
+#define ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH 2
+
+// Min duration between requests for an address/nwid combo to prevent floods
+#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000
+
+// Nodes are considered active if they've queried in less than this long
+#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD (ZT_NETWORK_AUTOCONF_DELAY * 2)
+
+// Timeout for disk read cache (ms)
+#define ZT_NETCONF_DB_CACHE_TTL 60000
+
+namespace ZeroTier {
+
+static json _renderRule(ZT_VirtualNetworkRule &rule)
+{
+ char tmp[128];
+ json r = json::object();
+ const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rule.t & 0x3f);
+
+ switch(rt) {
+ case ZT_NETWORK_RULE_ACTION_DROP:
+ r["type"] = "ACTION_DROP";
+ break;
+ case ZT_NETWORK_RULE_ACTION_ACCEPT:
+ r["type"] = "ACTION_ACCEPT";
+ break;
+ case ZT_NETWORK_RULE_ACTION_TEE:
+ r["type"] = "ACTION_TEE";
+ r["address"] = Address(rule.v.fwd.address).toString();
+ r["flags"] = (unsigned int)rule.v.fwd.flags;
+ r["length"] = (unsigned int)rule.v.fwd.length;
+ break;
+ case ZT_NETWORK_RULE_ACTION_WATCH:
+ r["type"] = "ACTION_WATCH";
+ r["address"] = Address(rule.v.fwd.address).toString();
+ r["flags"] = (unsigned int)rule.v.fwd.flags;
+ r["length"] = (unsigned int)rule.v.fwd.length;
+ break;
+ case ZT_NETWORK_RULE_ACTION_REDIRECT:
+ r["type"] = "ACTION_REDIRECT";
+ r["address"] = Address(rule.v.fwd.address).toString();
+ r["flags"] = (unsigned int)rule.v.fwd.flags;
+ break;
+ case ZT_NETWORK_RULE_ACTION_BREAK:
+ r["type"] = "ACTION_BREAK";
+ break;
+ default:
+ break;
+ }
+
+ 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["or"] = ((rule.t & 0x40) != 0);
+ }
+ }
+
+ return r;
+}
+
+static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
+{
+ if (!r.is_object())
+ return false;
+
+ const std::string t(OSUtils::jsonString(r["type"],""));
+ memset(&rule,0,sizeof(ZT_VirtualNetworkRule));
+
+ 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;
+ } else if (t == "ACTION_ACCEPT") {
+ rule.t |= ZT_NETWORK_RULE_ACTION_ACCEPT;
+ return true;
+ } else if (t == "ACTION_TEE") {
+ rule.t |= ZT_NETWORK_RULE_ACTION_TEE;
+ 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(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(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_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(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(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)(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)(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)(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(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(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(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(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(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(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.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)(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)(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)(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)(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)(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;
+ if (r.count("mask")) {
+ json &v = r["mask"];
+ if (v.is_number()) {
+ rule.v.characteristics = v;
+ } else {
+ std::string tmp = v;
+ rule.v.characteristics = Utils::hexStrToU64(tmp.c_str());
+ }
+ }
+ return true;
+ } else if (t == "MATCH_FRAME_SIZE_RANGE") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE;
+ 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)(OSUtils::jsonInt(r["probability"],0ULL) & 0xffffffffULL);
+ return true;
+ } else if (t == "MATCH_TAGS_DIFFERENCE") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE;
+ tag = true;
+ } else if (t == "MATCH_TAGS_BITWISE_AND") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND;
+ tag = true;
+ } else if (t == "MATCH_TAGS_BITWISE_OR") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR;
+ tag = true;
+ } else if (t == "MATCH_TAGS_BITWISE_XOR") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR;
+ tag = true;
+ } else if (t == "MATCH_TAGS_EQUAL") {
+ rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_EQUAL;
+ 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) :
+ _threadsStarted(false),
+ _db(dbPath),
+ _node(node)
+{
+ OSUtils::mkdir(dbPath);
+ OSUtils::lockDownFile(dbPath,true); // networks might contain auth tokens, etc., so restrict directory permissions
+}
+
+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::init(const Identity &signingId,Sender *sender)
+{
+ this->_sender = sender;
+ this->_signingId = signingId;
+}
+
+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))||(!_sender))
+ return;
+
+ {
+ Mutex::Lock _l(_threads_m);
+ if (!_threadsStarted) {
+ for(int i=0;i<ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT;++i)
+ _threads[i] = Thread::start(this);
+ }
+ _threadsStarted = true;
+ }
+
+ _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(
+ const std::vector<std::string> &path,
+ const std::map<std::string,std::string> &urlArgs,
+ const std::map<std::string,std::string> &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType)
+{
+ if ((path.size() > 0)&&(path[0] == "network")) {
+
+ if ((path.size() >= 2)&&(path[1].length() == 16)) {
+ const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+
+ json network;
+ {
+ Mutex::Lock _l(_db_m);
+ network = _db.get("network",nwids,ZT_NETCONF_DB_CACHE_TTL);
+ }
+ if (!network.size())
+ return 404;
+
+ if (path.size() >= 3) {
+
+ if (path[2] == "member") {
+
+ if (path.size() >= 4) {
+ const uint64_t address = Utils::hexStrToU64(path[3].c_str());
+
+ json member;
+ {
+ Mutex::Lock _l(_db_m);
+ member = _db.get("network",nwids,"member",Address(address).toString(),ZT_NETCONF_DB_CACHE_TTL);
+ }
+ if (!member.size())
+ return 404;
+
+ _addMemberNonPersistedFields(member,OSUtils::now());
+ responseBody = OSUtils::jsonDump(member);
+ responseContentType = "application/json";
+
+ return 200;
+ } else {
+
+ Mutex::Lock _l(_db_m);
+
+ responseBody = "{";
+ std::string pfx(std::string("network/") + nwids + "member/");
+ _db.filter(pfx,ZT_NETCONF_DB_CACHE_TTL,[&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 404
+
+ } else {
+
+ const uint64_t now = OSUtils::now();
+ _NetworkMemberInfo nmi;
+ _getNetworkMemberInfo(now,nwid,nmi);
+ _addNetworkNonPersistedFields(network,now,nmi);
+ responseBody = OSUtils::jsonDump(network);
+ responseContentType = "application/json";
+ return 200;
+
+ }
+ } else if (path.size() == 1) {
+
+ 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";
+ return 200;
+
+ } // else 404
+
+ } else {
+
+ char tmp[4096];
+ Utils::snprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now());
+ responseBody = tmp;
+ responseContentType = "application/json";
+ return 200;
+
+ }
+
+ return 404;
+}
+
+unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST(
+ const std::vector<std::string> &path,
+ const std::map<std::string,std::string> &urlArgs,
+ const std::map<std::string,std::string> &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType)
+{
+ if (path.empty())
+ return 404;
+
+ json b;
+ try {
+ b = OSUtils::jsonParse(body);
+ if (!b.is_object()) {
+ responseBody = "{ \"message\": \"body is not a JSON object\" }";
+ responseContentType = "application/json";
+ return 400;
+ }
+ } catch ( ... ) {
+ responseBody = "{ \"message\": \"body JSON is invalid\" }";
+ responseContentType = "application/json";
+ return 400;
+ }
+ const uint64_t now = OSUtils::now();
+
+ if (path[0] == "network") {
+
+ if ((path.size() >= 2)&&(path[1].length() == 16)) {
+ uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+
+ if (path.size() >= 3) {
+
+ 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;
+ {
+ Mutex::Lock _l(_db_m);
+ member = _db.get("network",nwids,"member",Address(address).toString(),ZT_NETCONF_DB_CACHE_TTL);
+ }
+ json origMember(member); // for detecting changes
+ _initMember(member);
+
+ try {
+ 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 = 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";
+ ah["ts"] = now;
+ 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")) {
+ json &ipa = b["ipAssignments"];
+ if (ipa.is_array()) {
+ json mipa(json::array());
+ for(unsigned long i=0;i<ipa.size();++i) {
+ std::string ips = ipa[i];
+ InetAddress ip(ips);
+ if ((ip.ss_family == AF_INET)||(ip.ss_family == AF_INET6)) {
+ mipa.push_back(ip.toIpString());
+ }
+ }
+ member["ipAssignments"] = mipa;
+ }
+ }
+
+ if (b.count("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) {
+ json &tag = tags[i];
+ if ((tag.is_array())&&(tag.size() == 2))
+ 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) {
+ json ta = json::array();
+ ta.push_back(t->first);
+ ta.push_back(t->second);
+ mtagsa.push_back(ta);
+ }
+ member["tags"] = mtagsa;
+ }
+ }
+
+ if (b.count("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(OSUtils::jsonInt(capabilities[i],0ULL));
+ }
+ std::sort(mcaps.begin(),mcaps.end());
+ mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end());
+ member["capabilities"] = mcaps;
+ }
+ }
+ } catch ( ... ) {
+ responseBody = "{ \"message\": \"exception while processing parameters in JSON body\" }";
+ responseContentType = "application/json";
+ return 400;
+ }
+
+ member["id"] = addrs;
+ member["address"] = addrs; // legacy
+ member["nwid"] = nwids;
+
+ 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 = OSUtils::jsonDump(member);
+ responseContentType = "application/json";
+ return 200;
+ } else if ((path.size() == 3)&&(path[2] == "test")) {
+
+ Mutex::Lock _l(_tests_m);
+
+ _tests.push_back(ZT_CircuitTest());
+ ZT_CircuitTest *const test = &(_tests.back());
+ memset(test,0,sizeof(ZT_CircuitTest));
+
+ Utils::getSecureRandom(&(test->testId),sizeof(test->testId));
+ test->credentialNetworkId = nwid;
+ test->ptr = (void *)this;
+ json hops = b["hops"];
+ if (hops.is_array()) {
+ for(unsigned long i=0;i<hops.size();++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 = (OSUtils::jsonBool(b["reportAtEveryHop"],true) ? 1 : 0);
+
+ if (!test->hopCount) {
+ _tests.pop_back();
+ responseBody = "{ \"message\": \"a test must contain at least one hop\" }";
+ responseContentType = "application/json";
+ return 400;
+ }
+
+ test->timestamp = OSUtils::now();
+
+ if (_node) {
+ _node->circuitTestBegin(test,&(EmbeddedNetworkController::_circuitTestCallback));
+ } else {
+ _tests.pop_back();
+ return 500;
+ }
+
+ 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
+
+ } else {
+ // POST to network ID
+
+ 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,ZT_NETCONF_DB_CACHE_TTL).size() <= 0) {
+ nwid = tryNwid;
+ break;
+ }
+ }
+ if (!nwid)
+ return 503;
+ }
+
+ network = _db.get("network",nwids,ZT_NETCONF_DB_CACHE_TTL);
+ }
+ json origNetwork(network); // for detecting changes
+ _initNetwork(network);
+
+ try {
+ 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"] = (OSUtils::jsonString(v4m,"") == "zt");
+ } else if (v4m.is_object()) {
+ nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false);
+ } else nv4m["zt"] = false;
+ network["v4AssignMode"] = nv4m;
+ }
+
+ if (b.count("v6AssignMode")) {
+ json nv6m;
+ json &v6m = b["v6AssignMode"];
+ if (!nv6m.is_object()) nv6m = json::object();
+ if (v6m.is_string()) { // backward compatibility
+ 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;
+ nv6m["zt"] = false;
+ nv6m["6plane"] = false;
+ for(std::vector<std::string>::iterator i(v6ms.begin());i!=v6ms.end();++i) {
+ if (*i == "rfc4193")
+ nv6m["rfc4193"] = true;
+ else if (*i == "zt")
+ nv6m["zt"] = true;
+ else if (*i == "6plane")
+ nv6m["6plane"] = true;
+ }
+ } else if (v6m.is_object()) {
+ 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;
+ nv6m["6plane"] = false;
+ }
+ network["v6AssignMode"] = nv6m;
+ }
+
+ if (b.count("routes")) {
+ json &rts = b["routes"];
+ if (rts.is_array()) {
+ json nrts = json::array();
+ for(unsigned long i=0;i<rts.size();++i) {
+ json &rt = rts[i];
+ if (rt.is_object()) {
+ json &target = rt["target"];
+ json &via = rt["via"];
+ if (target.is_string()) {
+ 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)) && (t.netmaskBitsValid()) ) {
+ json tmp;
+ tmp["target"] = t.toString();
+ if (v.ss_family == t.ss_family)
+ tmp["via"] = v.toIpString();
+ else tmp["via"] = json();
+ nrts.push_back(tmp);
+ }
+ }
+ }
+ }
+ network["routes"] = nrts;
+ }
+ }
+
+ if (b.count("ipAssignmentPools")) {
+ json &ipp = b["ipAssignmentPools"];
+ if (ipp.is_array()) {
+ json nipp = json::array();
+ for(unsigned long i=0;i<ipp.size();++i) {
+ json &ip = ipp[i];
+ if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("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();
+ tmp["ipRangeEnd"] = t.toIpString();
+ nipp.push_back(tmp);
+ }
+ }
+ }
+ network["ipAssignmentPools"] = nipp;
+ }
+ }
+
+ if (b.count("rules")) {
+ json &rules = b["rules"];
+ if (rules.is_array()) {
+ json nrules = json::array();
+ for(unsigned long i=0;i<rules.size();++i) {
+ json &rule = rules[i];
+ if (rule.is_object()) {
+ ZT_VirtualNetworkRule ztr;
+ if (_parseRule(rule,ztr))
+ nrules.push_back(_renderRule(ztr));
+ }
+ }
+ network["rules"] = nrules;
+ }
+ }
+
+ if (b.count("authTokens")) {
+ json &authTokens = b["authTokens"];
+ if (authTokens.is_array()) {
+ json nat = json::array();
+ for(unsigned long i=0;i<authTokens.size();++i) {
+ json &token = authTokens[i];
+ if (token.is_object()) {
+ std::string tstr = token["token"];
+ if (tstr.length() > 0) {
+ json t = json::object();
+ t["token"] = tstr;
+ t["expires"] = OSUtils::jsonInt(token["expires"],0ULL);
+ t["maxUsesPerMember"] = OSUtils::jsonInt(token["maxUsesPerMember"],0ULL);
+ nat.push_back(t);
+ }
+ }
+ }
+ network["authTokens"] = nat;
+ }
+ }
+
+ if (b.count("capabilities")) {
+ json &capabilities = b["capabilities"];
+ if (capabilities.is_array()) {
+ std::map< uint64_t,json > ncaps;
+ for(unsigned long i=0;i<capabilities.size();++i) {
+ json &cap = capabilities[i];
+ if (cap.is_object()) {
+ json ncap = json::object();
+ 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];
+ if (rule.is_object()) {
+ ZT_VirtualNetworkRule ztr;
+ if (_parseRule(rule,ztr))
+ nrules.push_back(_renderRule(ztr));
+ }
+ }
+ }
+ ncap["rules"] = nrules;
+
+ ncaps[capId] = ncap;
+ }
+ }
+
+ json ncapsa = json::array();
+ for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c)
+ ncapsa.push_back(c->second);
+ 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";
+ return 400;
+ }
+
+ network["id"] = nwids;
+ network["nwid"] = nwids; // legacy
+
+ 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 = OSUtils::jsonDump(network);
+ responseContentType = "application/json";
+ return 200;
+ } // else 404
+
+ } // else 404
+
+ } // else 404
+
+ return 404;
+}
+
+unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE(
+ const std::vector<std::string> &path,
+ const std::map<std::string,std::string> &urlArgs,
+ const std::map<std::string,std::string> &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType)
+{
+ if (path.empty())
+ return 404;
+
+ if (path[0] == "network") {
+ if ((path.size() >= 2)&&(path[1].length() == 16)) {
+ const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
+
+ char nwids[24];
+ Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid);
+ json network;
+ {
+ Mutex::Lock _l(_db_m);
+ network = _db.get("network",nwids,ZT_NETCONF_DB_CACHE_TTL);
+ }
+ if (!network.size())
+ return 404;
+
+ if (path.size() >= 3) {
+ if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
+ const uint64_t address = Utils::hexStrToU64(path[3].c_str());
+
+ Mutex::Lock _l(_db_m);
+
+ json member = _db.get("network",nwids,"member",Address(address).toString(),ZT_NETCONF_DB_CACHE_TTL);
+ _db.erase("network",nwids,"member",Address(address).toString());
+
+ if (!member.size())
+ return 404;
+ responseBody = OSUtils::jsonDump(member);
+ responseContentType = "application/json";
+ return 200;
+ }
+ } else {
+ 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;
+ }
+ } // else 404
+
+ } // else 404
+
+ 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[1024],id[128];
+ EmbeddedNetworkController *const self = reinterpret_cast<EmbeddedNetworkController *>(test->ptr);
+
+ 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),
+ "{\"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)now,
+ (unsigned long long)report->sourcePacketId,
+ (unsigned long long)report->flags,
+ report->sourcePacketHopCount,
+ report->errorCode,
+ (int)report->vendor,
+ report->protocolVersion,
+ report->majorVersion,
+ report->minorVersion,
+ report->revision,
+ (int)report->platform,
+ (int)report->architecture,
+ reinterpret_cast<const InetAddress *>(&(report->receivedOnLocalAddress))->toString().c_str(),
+ reinterpret_cast<const InetAddress *>(&(report->receivedFromRemoteAddress))->toString().c_str(),
+ ((double)report->receivedFromLinkQuality / (double)ZT_PATH_LINK_QUALITY_MAX));
+
+ Mutex::Lock _l(self->_db_m);
+ self->_db.writeRaw(id,std::string(tmp));
+}
+
+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))||(!_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,ZT_NETCONF_DB_CACHE_TTL);
+ member = _db.get("network",nwids,"member",identity.address().toString(),ZT_NETCONF_DB_CACHE_TTL);
+ }
+
+ 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 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;
+
+ // 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);
+ }
+ }
+ }
+ }
+ 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
new file mode 100644
index 00000000..a7277ace
--- /dev/null
+++ b/controller/EmbeddedNetworkController.hpp
@@ -0,0 +1,217 @@
+/*
+ * 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_SQLITENETWORKCONTROLLER_HPP
+#define ZT_SQLITENETWORKCONTROLLER_HPP
+
+#include <stdint.h>
+
+#include <string>
+#include <map>
+#include <vector>
+#include <set>
+#include <list>
+
+#include "../node/Constants.hpp"
+
+#include "../node/NetworkController.hpp"
+#include "../node/Mutex.hpp"
+#include "../node/Utils.hpp"
+#include "../node/Address.hpp"
+#include "../node/InetAddress.hpp"
+
+#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 4
+
+// TTL for circuit tests
+#define ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION 120000
+
+namespace ZeroTier {
+
+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();
+
+ virtual void init(const Identity &signingId,Sender *sender);
+
+ virtual void request(
+ uint64_t nwid,
+ const InetAddress &fromAddr,
+ uint64_t requestPacketId,
+ const Identity &identity,
+ const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
+
+ unsigned int handleControlPlaneHttpGET(
+ const std::vector<std::string> &path,
+ const std::map<std::string,std::string> &urlArgs,
+ const std::map<std::string,std::string> &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType);
+ unsigned int handleControlPlaneHttpPOST(
+ const std::vector<std::string> &path,
+ const std::map<std::string,std::string> &urlArgs,
+ const std::map<std::string,std::string> &headers,
+ const std::string &body,
+ std::string &responseBody,
+ std::string &responseContentType);
+ unsigned int handleControlPlaneHttpDELETE(
+ const std::vector<std::string> &path,
+ const std::map<std::string,std::string> &urlArgs,
+ const std::map<std::string,std::string> &headers,
+ const std::string &body,
+ 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);
+
+ struct _RQEntry
+ {
+ uint64_t nwid;
+ uint64_t requestPacketId;
+ InetAddress fromAddr;
+ Identity identity;
+ Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData;
+ };
+ BlockingQueue<_RQEntry *> _queue;
+
+ 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
+ struct _NetworkMemberInfo
+ {
+ _NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {}
+ std::set<Address> activeBridges;
+ std::set<InetAddress> allocatedIps;
+ unsigned long authorizedMemberCount;
+ 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)
+ {
+ if (!member.count("authorized")) member["authorized"] = false;
+ if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array();
+ if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array();
+ if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array();
+ if (!member.count("activeBridge")) member["activeBridge"] = false;
+ if (!member.count("tags")) member["tags"] = nlohmann::json::array();
+ if (!member.count("capabilities")) member["capabilities"] = nlohmann::json::array();
+ 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("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
+ if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
+ member["objtype"] = "member";
+ }
+ inline void _initNetwork(nlohmann::json &network)
+ {
+ if (!network.count("private")) network["private"] = true;
+ 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"] = {{
+ { "not",false },
+ { "or", false },
+ { "type","ACTION_ACCEPT" }
+ }};
+ }
+ network["objtype"] = "network";
+ }
+ inline void _addNetworkNonPersistedFields(nlohmann::json &network,uint64_t now,const _NetworkMemberInfo &nmi)
+ {
+ network["clock"] = now;
+ network["authorizedMemberCount"] = nmi.authorizedMemberCount;
+ network["activeMemberCount"] = nmi.activeMemberCount;
+ network["totalMemberCount"] = nmi.totalMemberCount;
+ }
+ inline void _addMemberNonPersistedFields(nlohmann::json &member,uint64_t now)
+ {
+ member["clock"] = now;
+ }
+
+ JSONDB _db;
+ Mutex _db_m;
+
+ Node *const _node;
+ std::string _path;
+
+ NetworkController::Sender *_sender;
+ Identity _signingId;
+
+ std::list< ZT_CircuitTest > _tests;
+ Mutex _tests_m;
+
+ std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime; // last request time by <address,networkId>
+ Mutex _lastRequestTime_m;
+};
+
+} // namespace ZeroTier
+
+#endif
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 8b789a3e..db8d0153 100644
--- a/controller/README.md
+++ b/controller/README.md
@@ -1,66 +1,43 @@
Network Controller Microservice
======
-ZeroTier's 16-digit network IDs are really just a concatenation of the 10-digit ZeroTier address of a network controller followed by a 6-digit (24-bit) network number on that controller. Fans of software defined networking will recognize this as a variation of the familiar [separation of data plane and control plane](http://sdntutorials.com/difference-between-control-plane-and-data-plane/) SDN design pattern.
+Every ZeroTier virtual network has a *network controller*. This is our reference implementation and is the same one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). Network controllers act as configuration servers and certificate authorities for the members of networks. Controllers are located on the network by simply parsing out the first 10 digits of a network's 16-digit network ID: these are the address of the controller.
-This code implements the *node/NetworkController.hpp* interface and provides a SQLite3-backed network controller microservice. Including it in the build allows ZeroTier One to act as a controller and create/manage networks.
+As of ZeroTier One version 1.2.0 this code is included in normal builds for desktop, laptop, and server (Linux, etc.) targets, allowing any device to create virtual networks without having to be rebuilt from source with special flags to enable this feature. While this does offer a convenient way to create ad-hoc networks or experiment, we recommend running a dedicated controller somewhere secure and stable for any "serious" use case.
-This is the same code we use to run [my.zerotier.com](https://my.zerotier.com/), which is a web UI and API that runs in front of a pool of controllers.
+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.
-### Building
+### Upgrading from Older (1.1.14 or earlier) Versions
-On Linux, Mac, or BSD you can create a controller-enabled build with:
+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.
- make ZT_ENABLE_NETWORK_CONTROLLER=1
+The migration tool is written in nodeJS and can be used like this:
-You will need the development headers and libraries for SQLite3 installed.
+ cd migrate-sqlite
+ npm install
+ node migrate.js </path/to/controller.db> </path/to/controller.d>
-### Running
+Very old versions of nodeJS may have issues. We tested it with version 7.
-After building and installing (`make install`) a controller-enabled build of ZeroTier One, start it and try:
+### Scalability and Reliability
- sudo zerotier-cli /controller
+Controllers can in theory host up to 2^24 networks and serve many millions of devices (or more), but we recommend spreading large numbers of networks across many controllers for load balancing and fault tolerance reasons. Since the controller uses the filesystem as its data store we recommend fast filesystems and fast SSD drives for heavily loaded controllers.
-You should see something like:
-
- {
- "controller": true,
- "apiVersion": 2,
- "clock": 1468002975497,
- "instanceId": "8ab354604debe1da27ee627c9ef94a48"
- }
-
-When started, a controller-enabled build of ZeroTier One will automatically create and initialize a `controller.db` file in its home folder. This is where all the controller's data and persistent state lives. If you're upgrading an old controller it will upgrade its database schema automatically on first launch. Make a backup of the old controller's database first since you can't go backward.
-
-Controllers periodically make backups of their database as `controller.db.backup`. This is done so that this file can be more easily copied/rsync'ed to other systems without worrying about corruption. SQLite3 supports multiple processes accessing the same database file, so `sqlite3 /path/to/controller.db .dump` also works but can be slow on a busy controller.
-
-Controllers can in theory host up to 2^24 networks and serve many millions of devices (or more), but we recommend running multiple controllers for a lot of networks to spread load and be more fault tolerant.
+Since ZeroTier nodes are mobile and do not need static IPs, implementing high availability fail-over for controllers is easy. Just replicate their working directories from master to backup and have something automatically fire up the backup if the master goes down. Many modern orchestration tools have built-in support for this. It would also be possible in theory to run controllers on a replicated or distributed filesystem, but we haven't tested this yet.
### Dockerizing Controllers
ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it.
-### Implementing High Availability Fail-Over
-
-ZeroTier network controllers are not single points of failure for networks-- in the sense that if a controller goes down *existing* members of a network can continue to communicate. But new members (or those that have been offline for a while) can't join, existing members can't be de-authorized, and other changes to the network's configuration can't be made. This means that short "glitches" in controller availability are not a major problem but long periods of unavailability can be.
-
-Because controllers are just regular ZeroTier nodes and controller queries are in-band, controllers can trivially be moved without worrying about changes to underlying physical IPs. This makes high-availability fail-over very easy to implement.
-
-Just set up two cloud hosts, preferably in different data centers (e.g. two different AWS regions or Digital Ocean SF and NYC). Now set up the hot spare controller to constantly mirror `controller.db.backup` from its active sibling.
-
-If the active controller goes down, rename `controller.db.backup` to `controller.db` on the hot spare and start the ZeroTier One service there. The spare will take over and has now become the active controller. If the original active node comes back, it should take on the role of spare and should not start its service. Instead it should start mirroring the active controller's backup and wait until it is needed.
-
-The details of actually implementing this kind of HA fail-over on Linux or other OSes are beyond the scope of these docs and there are many ways to do it. Docker orchestration tools like Kubernetes can also be used to accomplish this if you've dockerized your controller.
-
### Network Controller API
The controller API is hosted via the same JSON API endpoint that ZeroTier One uses for local control (usually at 127.0.0.1 port 9993). All controller options are routed under the `/controller` base path.
The controller microservice does not implement any fine-grained access control (authentication is via authtoken.secret just like the regular JSON API) or other complex mangement features. It just takes network and network member configurations and reponds to controller queries. We have an enterprise product called [ZeroTier Central](https://my.zerotier.com/) that we host as a service (and that companies can license to self-host) that does this.
-All working network IDs on a controller must begin with the controller's ZeroTier address. The API will *allow* "foreign" networks to be added but the controller will have no way of doing anything with them.
+All working network IDs on a controller must begin with the controller's ZeroTier address. The API will *allow* "foreign" networks to be added but the controller will have no way of doing anything with them since nobody will know to query it. (In the future we might support secondaries, which would make this relevant.)
-Also note that the API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrectly typed and unrecognized fields are just ignored.
+The JSON API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrectly typed and unrecognized fields may result in ignored fields or a 400 (bad request) error.
#### `/controller`
@@ -71,11 +48,8 @@ Also note that the API is *very* sensitive about types. Integers must be integer
| Field | Type | Description | Writable |
| ------------------ | ----------- | ------------------------------------------------- | -------- |
| controller | boolean | Always 'true' | no |
-| apiVersion | integer | Controller API version, currently 2 | no |
+| apiVersion | integer | Controller API version, currently 3 | no |
| clock | integer | Current clock on controller, ms since epoch | no |
-| instanceId | string | A random ID generated on first controller DB init | no |
-
-The instance ID can be used to check whether a controller's database has been reset or otherwise switched.
#### `/controller/network`
@@ -97,52 +71,50 @@ When POSTing new networks take care that their IDs are not in use, otherwise you
| Field | Type | Description | Writable |
| --------------------- | ------------- | ------------------------------------------------- | -------- |
-| nwid | string | 16-digit network ID | no |
-| controllerInstanceId | string | Controller database instance ID | no |
+| id | string | 16-digit network ID | no |
+| nwid | string | 16-digit network ID (old, but still around) | no |
| clock | integer | Current clock, ms since epoch | no |
| name | string | A short name for this network | YES |
| private | boolean | Is access control enabled? | YES |
| enableBroadcast | boolean | Ethernet ff:ff:ff:ff:ff:ff allowed? | YES |
| allowPassiveBridging | boolean | Allow any member to bridge (very experimental) | YES |
-| v4AssignMode | string | If 'zt', auto-assign IPv4 from pool(s) | YES |
-| v6AssignMode | string | IPv6 address auto-assign modes; see below | YES |
+| v4AssignMode | object | IPv4 management and assign options (see below) | YES |
+| v6AssignMode | object | IPv6 management and assign options (see below) | YES |
| multicastLimit | integer | Maximum recipients for a multicast packet | YES |
| creationTime | integer | Time network was first created | no |
| revision | integer | Network config revision counter | no |
-| memberRevisionCounter | integer | Network member revision counter | no |
| authorizedMemberCount | integer | Number of authorized members (for private nets) | no |
-| relays | array[object] | Alternative relays; see below | YES |
+| activeMemberCount | integer | Number of members that appear to be online | no |
+| totalMemberCount | integer | Total known members of this network | no |
| routes | array[object] | Managed IPv4 and IPv6 routes; see below | YES |
| ipAssignmentPools | array[object] | IP auto-assign ranges; see below | YES |
| rules | array[object] | Traffic rules; see below | YES |
-(The `ipLocalRoutes` field appeared in older versions but is no longer present. Routes will now show up in `routes`.)
+Recent changes:
-Two important things to know about networks:
+ * The `ipLocalRoutes` field appeared in older versions but is no longer present. Routes will now show up in `routes`.
+ * The `relays` field is gone since network preferred relays are gone. This capability is replaced by VL1 level federation ("federated roots").
- - Networks without rules won't carry any traffic. See below for an example with rules to permit IPv4 and IPv6.
- - Managed IP address assignments and IP assignment pools that do not fall within a route configured in `routes` are ignored and won't be used or sent to members.
- - The default for `private` is `true` and this is probably what you want. Turning `private` off means *anyone* can join your network with only its 16-digit network ID. It's also impossible to de-authorize a member as these networks don't issue or enforce certificates. Such "party line" networks are used for decentralized app backplanes, gaming, and testing but are not common in ordinary use.
+Other important points:
-**IPv6 Auto-Assign Modes:**
+ * Networks without rules won't carry any traffic. If you don't specify any on network creation an "accept anything" rule set will automatically be added.
+ * Managed IP address assignments and IP assignment pools that do not fall within a route configured in `routes` are ignored and won't be used or sent to members.
+ * The default for `private` is `true` and this is probably what you want. Turning `private` off means *anyone* can join your network with only its 16-digit network ID. It's also impossible to de-authorize a member as these networks don't issue or enforce certificates. Such "party line" networks are used for decentralized app backplanes, gaming, and testing but are otherwise not common.
-This field is (for legacy reasons) a comma-delimited list of strings. These can be `rfc4193`, `6plane`, and `zt`. RFC4193 and 6PLANE are special addressing modes that deterministically assign IPv6 addresses based on the network ID and the ZeroTier address of each member. The `zt` mode enables IPv6 auto-assignment from arbitrary IPv6 IP ranges configured in `ipAssignmentPools`.
+**Auto-Assign Modes:**
-**Relay object format:**
+Auto assign modes (`v4AssignMode` and `v6AssignMode`) contain objects that map assignment modes to booleans.
-Relay objects define network-specific preferred relay nodes. Traffic to peers on this network will preferentially use these relays if they are available, and otherwise will fall back to the global rootserver infrastructure.
+For IPv4 the only valid setting is `zt` which, if true, causes IPv4 addresses to be auto-assigned from `ipAssignmentPools` to members that do not have an IPv4 assignment. Note that active bridges are exempt and will not get auto-assigned IPs since this can interfere with bridging. (You can still manually assign one if you want.)
-| Field | Type | Description | Writable |
-| --------------------- | ------------- | ------------------------------------------------- | -------- |
-| address | string | 10-digit ZeroTier address of relay | YES |
-| phyAddress | string | Optional IP/port suggestion for finding relay | YES |
+IPv6 includes this option and two others: `6plane` and `rfc4193`. These assign private IPv6 addresses to each member based on a deterministic assignment scheme that allows members to emulate IPv6 NDP to skip multicast for better performance and scalability. The `rfc4193` mode gives every member a /128 on a /88 network, while `6plane` gives every member a /80 within a /40 network but uses NDP emulation to route *all* IPs under that /80 to its owner. The `6plane` mode is great for use cases like Docker since it allows every member to assign IPv6 addresses within its /80 that just work instantly and globally across the network.
**IP assignment pool object format:**
-| Field | Type | Description | Writable |
-| --------------------- | ------------- | ------------------------------------------------- | -------- |
-| ipRangeStart | string | Starting IP address in range | YES |
-| ipRangeEnd | string | Ending IP address in range (inclusive) | YES |
+| Field | Type | Description |
+| --------------------- | ------------- | ------------------------------------------------- |
+| ipRangeStart | string | Starting IP address in range |
+| ipRangeEnd | string | Ending IP address in range (inclusive) |
Pools are only used if auto-assignment is on for the given address type (IPv4 or IPv6) and if the entire range falls within a managed route.
@@ -159,57 +131,68 @@ That defines a range within network `fd00:feed:feed:beef::/64` that contains up
**Rule object format:**
-Rules are matched in order of ruleNo. If no rules match, the default action is `drop`. To allow all traffic, create a single rule with all *null* fields and an action of `accept`.
+Each rule is actually a sequence of zero or more `MATCH_` entries in the rule array followed by an `ACTION_` entry that describes what to do if all the preceding entries match. An `ACTION_` without any preceding `MATCH_` entries is always taken, so setting a single `ACTION_ACCEPT` rule yields a network that allows all traffic. If no rules are present the default action is `ACTION_DROP`.
-In the future there will be many, many more types of rules. As of today only filtering by Ethernet packet type is supported.
+Rules are evaluated in the order in which they appear in the array. There is currently a limit of 256 entries per network. Capabilities should be used if a larger and more complex rule set is needed since they allow rules to be grouped by purpose and only shipped to members that need them.
-| Field | Type | Description | Writable |
-| --------------------- | ------------- | ------------------------------------------------- | -------- |
-| ruleNo | integer | Rule sorting key | YES |
-| etherType | integer | Ethernet frame type (e.g. 34525 for IPv6) | YES |
-| action | string | Currently either `allow` or `drop` | YES |
+Each rule table entry has two common fields.
-**An Example: The Configuration for Earth**
-
-Here is an example of a correctly configured ZeroTier network with IPv4 auto-assigned addresses from 28.0.0.0/7 (a "de-facto private" space) and RFC4193 IPv6 addressing. Users might recognize this as *Earth*, our public "global LAN party" that's used for demos and testing and occasionally gaming.
+| Field | Type | Description |
+| --------------------- | ------------- | ------------------------------------------------- |
+| type | string | Entry type (all caps, case sensitive) |
+| not | boolean | If true, MATCHes match if they don't match |
-For your own networks you'll probably want to change `private` to `true` unless you like company. These rules on the other hand probably are what you want. These allow IPv4, IPv4 ARP, and IPv6 Ethernet frames. To allow only IPv4 omit the one for Ethernet type 34525 (IPv6).
+The following fields may or may not be present depending on rule type:
- {
- "nwid": "8056c2e21c000001",
- "controllerInstanceId": "8ab354604debe1da27ee627c9ef94a48",
- "clock": 1468004857100,
- "name": "earth.zerotier.net",
- "private": false,
- "enableBroadcast": false,
- "allowPassiveBridging": false,
- "v4AssignMode": "zt",
- "v6AssignMode": "rfc4193",
- "multicastLimit": 64,
- "creationTime": 1442292573165,
- "revision": 234,
- "memberRevisionCounter": 3326,
- "authorizedMemberCount": 2873,
- "relays": [],
- "routes": [
- {"target":"28.0.0.0/7","via":null,"flags":0,"metric":0}],
- "ipAssignmentPools": [
- {"ipRangeStart":"28.0.0.1","ipRangeEnd":"29.255.255.254"}],
- "rules": [
- {
- "ruleNo": 20,
- "etherType": 2048,
- "action": "accept"
- },{
- "ruleNo": 21,
- "etherType": 2054,
- "action": "accept"
- },{
- "ruleNo": 30,
- "etherType": 34525,
- "action": "accept"
- }]
- }
+| Field | Type | Description |
+| --------------------- | ------------- | ------------------------------------------------- |
+| zt | string | 10-digit hex ZeroTier address |
+| etherType | integer | Ethernet frame type |
+| mac | string | Hex MAC address (with or without :'s) |
+| ip | string | IPv4 or IPv6 address |
+| ipTos | integer | IP type of service |
+| ipProtocol | integer | IP protocol (e.g. TCP) |
+| start | integer | Start of an integer range (e.g. port range) |
+| end | integer | End of an integer range (inclusive) |
+| id | integer | Tag ID |
+| value | integer | Tag value or comparison value |
+| mask | integer | Bit mask (for characteristics flags) |
+
+The entry types and their additional fields are:
+
+| Entry type | Description | Fields |
+| ------------------------------- | ----------------------------------------------------------------- | -------------- |
+| `ACTION_DROP` | Drop any packets matching this rule | (none) |
+| `ACTION_ACCEPT` | Accept any packets matching this rule | (none) |
+| `ACTION_TEE` | Send a copy of this packet to a node (rule parsing continues) | `zt` |
+| `ACTION_REDIRECT` | Redirect this packet to another node | `zt` |
+| `ACTION_DEBUG_LOG` | Output debug info on match (if built with rules engine debug) | (none) |
+| `MATCH_SOURCE_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of packet sender. | `zt` |
+| `MATCH_DEST_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of recipient | `zt` |
+| `MATCH_ETHERTYPE` | Match Ethernet frame type | `etherType` |
+| `MATCH_MAC_SOURCE` | Match source Ethernet MAC address | `mac` |
+| `MATCH_MAC_DEST` | Match destination Ethernet MAC address | `mac` |
+| `MATCH_IPV4_SOURCE` | Match source IPv4 address | `ip` |
+| `MATCH_IPV4_DEST` | Match destination IPv4 address | `ip` |
+| `MATCH_IPV6_SOURCE` | Match source IPv6 address | `ip` |
+| `MATCH_IPV6_DEST` | Match destination IPv6 address | `ip` |
+| `MATCH_IP_TOS` | Match IP TOS field | `ipTos` |
+| `MATCH_IP_PROTOCOL` | Match IP protocol field | `ipProtocol` |
+| `MATCH_IP_SOURCE_PORT_RANGE` | Match a source IP port range | `start`,`end` |
+| `MATCH_IP_DEST_PORT_RANGE` | Match a destination IP port range | `start`,`end` |
+| `MATCH_CHARACTERISTICS` | Match on characteristics flags | `mask`,`value` |
+| `MATCH_FRAME_SIZE_RANGE` | Match a range of Ethernet frame sizes | `start`,`end` |
+| `MATCH_TAGS_SAMENESS` | Match if both sides' tags differ by no more than value | `id`,`value` |
+| `MATCH_TAGS_BITWISE_AND` | Match if both sides' tags AND to value | `id`,`value` |
+| `MATCH_TAGS_BITWISE_OR` | Match if both sides' tags OR to value | `id`,`value` |
+| `MATCH_TAGS_BITWISE_XOR` | Match if both sides` tags XOR to value | `id`,`value` |
+
+Important notes about rules engine behavior:
+
+ * IPv4 and IPv6 IP address rules do not match for frames that are not IPv4 or IPv6 respectively.
+ * `ACTION_DEBUG_LOG` is a no-op on nodes not built with `ZT_RULES_ENGINE_DEBUGGING` enabled (see Network.cpp). If that is enabled nodes will dump a trace of rule evaluation results to *stdout* when this action is encountered but will otherwise keep evaluating rules. This is used for basic "smoke testing" of the rules engine.
+ * Multicast packets and packets destined for bridged devices treated a little differently. They are matched more than once. They are matched at the point of send with a NULL ZeroTier destination address, meaning that `MATCH_DEST_ZEROTIER_ADDRESS` is useless. That's because the true VL1 destination is not yet known. Then they are matched again for each true VL1 destination. On these later subsequent matches TEE actions are ignored and REDIRECT rules are interpreted as DROPs. This prevents multiple TEE or REDIRECT packets from being sent to third party devices.
+ * Rules in capabilities are always matched as if the current device is the sender (inbound == false). A capability specifies sender side rules that can be enforced on both sides.
#### `/controller/network/<network ID>/member`
@@ -235,10 +218,12 @@ This returns an object containing all currently online members and the most rece
| Field | Type | Description | Writable |
| --------------------- | ------------- | ------------------------------------------------- | -------- |
+| id | string | Member's 10-digit ZeroTier address | no |
+| address | string | Member's 10-digit ZeroTier address | no |
| nwid | string | 16-digit network ID | no |
| clock | integer | Current clock, ms since epoch | no |
-| address | string | Member's 10-digit ZeroTier address | no |
| authorized | boolean | Is member authorized? (for private networks) | YES |
+| authHistory | array[object] | History of auth changes, latest at end | no |
| activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES |
| identity | string | Member's public ZeroTier identity (if known) | no |
| ipAssignments | array[string] | Managed IP address assignments | YES |
@@ -252,10 +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 |
+| 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/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp
deleted file mode 100644
index 65051744..00000000
--- a/controller/SqliteNetworkController.cpp
+++ /dev/null
@@ -1,2195 +0,0 @@
-/*
- * 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/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <sys/time.h>
-#include <sys/types.h>
-
-#include <algorithm>
-#include <utility>
-#include <stdexcept>
-#include <set>
-
-#include "../include/ZeroTierOne.h"
-#include "../node/Constants.hpp"
-
-#ifdef ZT_USE_SYSTEM_JSON_PARSER
-#include <json-parser/json.h>
-#else
-#include "../ext/json-parser/json.h"
-#endif
-
-#include "SqliteNetworkController.hpp"
-
-#include "../node/Node.hpp"
-#include "../node/Utils.hpp"
-#include "../node/CertificateOfMembership.hpp"
-#include "../node/NetworkConfig.hpp"
-#include "../node/Dictionary.hpp"
-#include "../node/InetAddress.hpp"
-#include "../node/MAC.hpp"
-#include "../node/Address.hpp"
-
-#include "../osdep/OSUtils.hpp"
-
-// Include ZT_NETCONF_SCHEMA_SQL constant to init database
-#include "schema.sql.c"
-
-// Stored in database as schemaVersion key in Config.
-// If not present, database is assumed to be empty and at the current schema version
-// and this key/value is added automatically.
-#define ZT_NETCONF_SQLITE_SCHEMA_VERSION 4
-#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "4"
-
-// API version reported via JSON control plane
-#define ZT_NETCONF_CONTROLLER_API_VERSION 2
-
-// Number of requests to remember in member history
-#define ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH 8
-
-// Min duration between requests for an address/nwid combo to prevent floods
-#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000
-
-// Delay between backups in milliseconds
-#define ZT_NETCONF_BACKUP_PERIOD 300000
-
-// Nodes are considered active if they've queried in less than this long
-#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD ((ZT_NETWORK_AUTOCONF_DELAY * 2) + 5000)
-
-// Flags for Network 'flags' field in table
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN 1
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193 2
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE 4
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN 8
-
-// Flags with all V6 managed mode flags flipped off -- for masking in update operation and in string form for SQL building
-#define ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_MASK_S "268435441"
-
-// Uncomment to trace Sqlite for debugging
-//#define ZT_NETCONF_SQLITE_TRACE 1
-
-namespace ZeroTier {
-
-namespace {
-
-static std::string _jsonEscape(const char *s)
-{
- if (!s)
- return std::string();
- std::string buf;
- for(const char *p=s;(*p);++p) {
- switch(*p) {
- case '\t': buf.append("\\t"); break;
- case '\b': buf.append("\\b"); break;
- case '\r': buf.append("\\r"); break;
- case '\n': buf.append("\\n"); break;
- case '\f': buf.append("\\f"); break;
- case '"': buf.append("\\\""); break;
- case '\\': buf.append("\\\\"); break;
- case '/': buf.append("\\/"); break;
- default: buf.push_back(*p); break;
- }
- }
- return buf;
-}
-static std::string _jsonEscape(const std::string &s) { return _jsonEscape(s.c_str()); }
-
-// Converts an InetAddress to a blob and an int for storage in database
-static void _ipToBlob(const InetAddress &a,char *ipBlob,int &ipVersion) /* blob[16] */
-{
- switch(a.ss_family) {
- case AF_INET:
- memset(ipBlob,0,12);
- memcpy(ipBlob + 12,a.rawIpData(),4);
- ipVersion = 4;
- break;
- case AF_INET6:
- memcpy(ipBlob,a.rawIpData(),16);
- ipVersion = 6;
- break;
- }
-}
-
-// Member.recentHistory is stored in a BLOB as an array of strings containing JSON objects.
-// This is kind of hacky but efficient and quick to parse and send to the client.
-class MemberRecentHistory : public std::list<std::string>
-{
-public:
- inline std::string toBlob() const
- {
- std::string b;
- for(const_iterator i(begin());i!=end();++i) {
- b.append(*i);
- b.push_back((char)0);
- }
- return b;
- }
-
- inline void fromBlob(const char *blob,unsigned int len)
- {
- for(unsigned int i=0,k=0;i<len;++i) {
- if (!blob[i]) {
- push_back(std::string(blob + k,i - k));
- k = i + 1;
- }
- }
- }
-};
-
-struct MemberRecord
-{
- sqlite3_int64 rowid;
- char nodeId[16];
- bool authorized;
- bool activeBridge;
- uint64_t lastRequestTime;
- MemberRecentHistory recentHistory;
-
- MemberRecord() :
- rowid(0),
- authorized(false),
- activeBridge(false),
- lastRequestTime(0)
- {
- nodeId[0] = (char)0;
- }
-};
-
-struct NetworkRecord
-{
- char id[24];
- const char *name;
- int flags;
- bool isPrivate;
- bool enableBroadcast;
- bool allowPassiveBridging;
- int multicastLimit;
- uint64_t creationTime;
- uint64_t revision;
- uint64_t memberRevisionCounter;
-
- NetworkRecord() :
- name((const char *)0),
- flags(0),
- isPrivate(true),
- enableBroadcast(false),
- allowPassiveBridging(false),
- multicastLimit(0),
- creationTime(0),
- revision(0),
- memberRevisionCounter(0)
- {
- id[0] = (char)0;
- }
-};
-
-#ifdef ZT_NETCONF_SQLITE_TRACE
-static void sqliteTraceFunc(void *ptr,const char *s)
-{
- fprintf(stderr,"SQLITE: %s\n",s);
-}
-#endif
-
-} // anonymous namespace
-
-SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath) :
- _node(node),
- _backupThreadRun(true),
- _backupNeeded(true),
- _dbPath(dbPath),
- _circuitTestPath(circuitTestPath),
- _db((sqlite3 *)0)
-{
- if (sqlite3_open_v2(dbPath,&_db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK)
- throw std::runtime_error("SqliteNetworkController cannot open database file");
- sqlite3_busy_timeout(_db,10000);
-
- sqlite3_exec(_db,"PRAGMA synchronous = OFF",0,0,0);
- sqlite3_exec(_db,"PRAGMA journal_mode = MEMORY",0,0,0);
-
- sqlite3_stmt *s = (sqlite3_stmt *)0;
- if ((sqlite3_prepare_v2(_db,"SELECT v FROM Config WHERE k = 'schemaVersion';",-1,&s,(const char **)0) == SQLITE_OK)&&(s)) {
- int schemaVersion = -1234;
- if (sqlite3_step(s) == SQLITE_ROW) {
- schemaVersion = sqlite3_column_int(s,0);
- }
-
- sqlite3_finalize(s);
-
- if (schemaVersion == -1234) {
- sqlite3_close(_db);
- throw std::runtime_error("SqliteNetworkController schemaVersion not found in Config table (init failure?)");
- }
-
- if (schemaVersion < 2) {
- // Create NodeHistory table to upgrade from version 1 to version 2
- if (sqlite3_exec(_db,
- "CREATE TABLE NodeHistory (\n"
- " nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"
- " networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"
- " networkVisitCounter INTEGER NOT NULL DEFAULT(0),\n"
- " networkRequestAuthorized INTEGER NOT NULL DEFAULT(0),\n"
- " requestTime INTEGER NOT NULL DEFAULT(0),\n"
- " clientMajorVersion INTEGER NOT NULL DEFAULT(0),\n"
- " clientMinorVersion INTEGER NOT NULL DEFAULT(0),\n"
- " clientRevision INTEGER NOT NULL DEFAULT(0),\n"
- " networkRequestMetaData VARCHAR(1024),\n"
- " fromAddress VARCHAR(128)\n"
- ");\n"
- "CREATE INDEX NodeHistory_nodeId ON NodeHistory (nodeId);\n"
- "CREATE INDEX NodeHistory_networkId ON NodeHistory (networkId);\n"
- "CREATE INDEX NodeHistory_requestTime ON NodeHistory (requestTime);\n"
- "UPDATE \"Config\" SET \"v\" = 2 WHERE \"k\" = 'schemaVersion';\n"
- ,0,0,0) != SQLITE_OK) {
- char err[1024];
- Utils::snprintf(err,sizeof(err),"SqliteNetworkController cannot upgrade the database to version 2: %s",sqlite3_errmsg(_db));
- sqlite3_close(_db);
- throw std::runtime_error(err);
- } else {
- schemaVersion = 2;
- }
- }
-
- if (schemaVersion < 3) {
- // Create Route table to upgrade from version 2 to version 3 and migrate old
- // data. Also delete obsolete Gateway table that was never actually used, and
- // migrate Network flags to a bitwise flags field instead of ASCII cruft.
- if (sqlite3_exec(_db,
- "DROP TABLE Gateway;\n"
- "CREATE TABLE Route (\n"
- " networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"
- " target blob(16) NOT NULL,\n"
- " via blob(16),\n"
- " targetNetmaskBits integer NOT NULL,\n"
- " ipVersion integer NOT NULL,\n"
- " flags integer NOT NULL,\n"
- " metric integer NOT NULL\n"
- ");\n"
- "CREATE INDEX Route_networkId ON Route (networkId);\n"
- "INSERT INTO Route SELECT DISTINCT networkId,\"ip\" AS \"target\",NULL AS \"via\",ipNetmaskBits AS targetNetmaskBits,ipVersion,0 AS \"flags\",0 AS \"metric\" FROM IpAssignment WHERE nodeId IS NULL AND \"type\" = 1;\n"
- "ALTER TABLE Network ADD COLUMN \"flags\" integer NOT NULL DEFAULT(0);\n"
- "UPDATE Network SET \"flags\" = (\"flags\" | 1) WHERE v4AssignMode = 'zt';\n"
- "UPDATE Network SET \"flags\" = (\"flags\" | 2) WHERE v6AssignMode = 'rfc4193';\n"
- "UPDATE Network SET \"flags\" = (\"flags\" | 4) WHERE v6AssignMode = '6plane';\n"
- "ALTER TABLE Member ADD COLUMN \"flags\" integer NOT NULL DEFAULT(0);\n"
- "DELETE FROM IpAssignment WHERE nodeId IS NULL AND \"type\" = 1;\n"
- "UPDATE \"Config\" SET \"v\" = 3 WHERE \"k\" = 'schemaVersion';\n"
- ,0,0,0) != SQLITE_OK) {
- char err[1024];
- Utils::snprintf(err,sizeof(err),"SqliteNetworkController cannot upgrade the database to version 3: %s",sqlite3_errmsg(_db));
- sqlite3_close(_db);
- throw std::runtime_error(err);
- } else {
- schemaVersion = 3;
- }
- }
-
- if (schemaVersion < 4) {
- // Turns out this was overkill and a huge performance drag. Will be revisiting this
- // more later but for now a brief snapshot of recent history stored in Member is fine.
- // Also prepare for implementation of proof of work requests.
- if (sqlite3_exec(_db,
- "DROP TABLE NodeHistory;\n"
- "ALTER TABLE Member ADD COLUMN lastRequestTime integer NOT NULL DEFAULT(0);\n"
- "ALTER TABLE Member ADD COLUMN lastPowDifficulty integer NOT NULL DEFAULT(0);\n"
- "ALTER TABLE Member ADD COLUMN lastPowTime integer NOT NULL DEFAULT(0);\n"
- "ALTER TABLE Member ADD COLUMN recentHistory blob;\n"
- "CREATE INDEX Member_networkId_lastRequestTime ON Member(networkId, lastRequestTime);\n"
- "UPDATE \"Config\" SET \"v\" = 4 WHERE \"k\" = 'schemaVersion';\n"
- ,0,0,0) != SQLITE_OK) {
- char err[1024];
- Utils::snprintf(err,sizeof(err),"SqliteNetworkController cannot upgrade the database to version 3: %s",sqlite3_errmsg(_db));
- sqlite3_close(_db);
- throw std::runtime_error(err);
- } else {
- schemaVersion = 4;
- }
- }
-
- if (schemaVersion != ZT_NETCONF_SQLITE_SCHEMA_VERSION) {
- sqlite3_close(_db);
- throw std::runtime_error("SqliteNetworkController database schema version mismatch");
- }
- } else {
- // Prepare statement will fail if Config table doesn't exist, which means our DB
- // needs to be initialized.
- if (sqlite3_exec(_db,ZT_NETCONF_SCHEMA_SQL"INSERT INTO Config (k,v) VALUES ('schemaVersion',"ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR");",0,0,0) != SQLITE_OK) {
- char err[1024];
- Utils::snprintf(err,sizeof(err),"SqliteNetworkController cannot initialize database and/or insert schemaVersion into Config table: %s",sqlite3_errmsg(_db));
- sqlite3_close(_db);
- throw std::runtime_error(err);
- }
- }
-
- if (
-
- /* Network */
- (sqlite3_prepare_v2(_db,"SELECT name,private,enableBroadcast,allowPassiveBridging,\"flags\",multicastLimit,creationTime,revision,memberRevisionCounter,(SELECT COUNT(1) FROM Member WHERE Member.networkId = Network.id AND Member.authorized > 0) FROM Network WHERE id = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT revision FROM Network WHERE id = ?",-1,&_sGetNetworkRevision,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"UPDATE Network SET revision = ? WHERE id = ?",-1,&_sSetNetworkRevision,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT INTO Network (id,name,creationTime,revision) VALUES (?,?,?,1)",-1,&_sCreateNetwork,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM Network WHERE id = ?",-1,&_sDeleteNetwork,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT id FROM Network ORDER BY id ASC",-1,&_sListNetworks,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"UPDATE Network SET memberRevisionCounter = (memberRevisionCounter + 1) WHERE id = ?",-1,&_sIncrementMemberRevisionCounter,(const char **)0) != SQLITE_OK)
-
- /* Node */
- ||(sqlite3_prepare_v2(_db,"SELECT identity FROM Node WHERE id = ?",-1,&_sGetNodeIdentity,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO Node (id,identity) VALUES (?,?)",-1,&_sCreateOrReplaceNode,(const char **)0) != SQLITE_OK)
-
- /* Rule */
- ||(sqlite3_prepare_v2(_db,"SELECT etherType FROM Rule WHERE networkId = ? AND \"action\" = 'accept'",-1,&_sGetEtherTypesFromRuleTable,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT INTO Rule (networkId,ruleNo,nodeId,sourcePort,destPort,vlanId,vlanPcP,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,flags,invFlags,\"action\") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",-1,&_sCreateRule,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT ruleNo,nodeId,sourcePort,destPort,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"flags\",invFlags,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleNo ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM Rule WHERE networkId = ?",-1,&_sDeleteRulesForNetwork,(const char **)0) != SQLITE_OK)
-
- /* IpAssignmentPool */
- ||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd FROM IpAssignmentPool WHERE networkId = ? AND ipVersion = ?",-1,&_sGetIpAssignmentPools,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT ipRangeStart,ipRangeEnd,ipVersion FROM IpAssignmentPool WHERE networkId = ? ORDER BY ipRangeStart ASC",-1,&_sGetIpAssignmentPools2,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignmentPool (networkId,ipRangeStart,ipRangeEnd,ipVersion) VALUES (?,?,?,?)",-1,&_sCreateIpAssignmentPool,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK)
-
- /* IpAssignment */
- ||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = 0 ORDER BY ip ASC",-1,&_sGetIpAssignmentsForNode,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE networkId = ? AND ip = ? AND ipVersion = ? AND \"type\" = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignment (networkId,nodeId,\"type\",ip,ipNetmaskBits,ipVersion) VALUES (?,?,?,?,?,?)",-1,&_sAllocateIp,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = ?",-1,&_sDeleteIpAllocations,(const char **)0) != SQLITE_OK)
-
- /* Relay */
- ||(sqlite3_prepare_v2(_db,"SELECT \"address\",\"phyAddress\" FROM Relay WHERE \"networkId\" = ? ORDER BY \"address\" ASC",-1,&_sGetRelays,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM Relay WHERE networkId = ?",-1,&_sDeleteRelaysForNetwork,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT INTO Relay (\"networkId\",\"address\",\"phyAddress\") VALUES (?,?,?)",-1,&_sCreateRelay,(const char **)0) != SQLITE_OK)
-
- /* Member */
- ||(sqlite3_prepare_v2(_db,"SELECT rowid,authorized,activeBridge,memberRevision,\"flags\",lastRequestTime,recentHistory FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sGetMember,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,m.memberRevision,n.identity,m.flags,m.lastRequestTime,m.recentHistory FROM Member AS m LEFT OUTER JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge,memberRevision) VALUES (?,?,?,0,(SELECT memberRevisionCounter FROM Network WHERE id = ?))",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT nodeId FROM Member WHERE networkId = ? AND activeBridge > 0 AND authorized > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT m.nodeId,m.memberRevision FROM Member AS m WHERE m.networkId = ? ORDER BY m.nodeId ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberAuthorized,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberActiveBridge,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"UPDATE Member SET \"lastRequestTime\" = ?, \"recentHistory\" = ? WHERE rowid = ?",-1,&_sUpdateMemberHistory,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ?",-1,&_sDeleteAllNetworkMembers,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT nodeId,recentHistory FROM Member WHERE networkId = ? AND lastRequestTime >= ?",-1,&_sGetActiveNodesOnNetwork,(const char **)0) != SQLITE_OK)
-
- /* Route */
- ||(sqlite3_prepare_v2(_db,"INSERT INTO Route (networkId,target,via,targetNetmaskBits,ipVersion,flags,metric) VALUES (?,?,?,?,?,?,?)",-1,&_sCreateRoute,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"SELECT DISTINCT target,via,targetNetmaskBits,ipVersion,flags,metric FROM \"Route\" WHERE networkId = ? ORDER BY ipVersion,target,via",-1,&_sGetRoutes,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"DELETE FROM \"Route\" WHERE networkId = ?",-1,&_sDeleteRoutes,(const char **)0) != SQLITE_OK)
-
- /* Config */
- ||(sqlite3_prepare_v2(_db,"SELECT \"v\" FROM \"Config\" WHERE \"k\" = ?",-1,&_sGetConfig,(const char **)0) != SQLITE_OK)
- ||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK)
-
- ) {
- std::string err(std::string("SqliteNetworkController unable to initialize one or more prepared statements: ") + sqlite3_errmsg(_db));
- sqlite3_close(_db);
- throw std::runtime_error(err);
- }
-
- /* Generate a 128-bit / 32-character "instance ID" if one isn't already
- * defined. Clients can use this to determine if this is the same controller
- * database they know and love. */
- sqlite3_reset(_sGetConfig);
- sqlite3_bind_text(_sGetConfig,1,"instanceId",10,SQLITE_STATIC);
- if (sqlite3_step(_sGetConfig) != SQLITE_ROW) {
- unsigned char sr[32];
- Utils::getSecureRandom(sr,32);
- for(unsigned int i=0;i<32;++i)
- _instanceId.push_back("0123456789abcdef"[(unsigned int)sr[i] & 0xf]);
-
- sqlite3_reset(_sSetConfig);
- sqlite3_bind_text(_sSetConfig,1,"instanceId",10,SQLITE_STATIC);
- sqlite3_bind_text(_sSetConfig,2,_instanceId.c_str(),-1,SQLITE_STATIC);
- if (sqlite3_step(_sSetConfig) != SQLITE_DONE)
- throw std::runtime_error("SqliteNetworkController unable to read or initialize instanceId");
- } else {
- const char *iid = reinterpret_cast<const char *>(sqlite3_column_text(_sGetConfig,0));
- if (!iid)
- throw std::runtime_error("SqliteNetworkController unable to read instanceId (it's NULL)");
- _instanceId = iid;
- }
-
-#ifdef ZT_NETCONF_SQLITE_TRACE
- sqlite3_trace(_db,sqliteTraceFunc,(void *)0);
-#endif
-
- _backupThread = Thread::start(this);
-}
-
-SqliteNetworkController::~SqliteNetworkController()
-{
- _backupThreadRun = false;
- Thread::join(_backupThread);
-
- Mutex::Lock _l(_lock);
- if (_db) {
- sqlite3_finalize(_sGetNetworkById);
- sqlite3_finalize(_sGetMember);
- sqlite3_finalize(_sCreateMember);
- sqlite3_finalize(_sGetNodeIdentity);
- sqlite3_finalize(_sCreateOrReplaceNode);
- sqlite3_finalize(_sGetEtherTypesFromRuleTable);
- sqlite3_finalize(_sGetActiveBridges);
- sqlite3_finalize(_sGetIpAssignmentsForNode);
- sqlite3_finalize(_sGetIpAssignmentPools);
- sqlite3_finalize(_sCheckIfIpIsAllocated);
- sqlite3_finalize(_sAllocateIp);
- sqlite3_finalize(_sDeleteIpAllocations);
- sqlite3_finalize(_sGetRelays);
- sqlite3_finalize(_sListNetworks);
- sqlite3_finalize(_sListNetworkMembers);
- sqlite3_finalize(_sGetMember2);
- sqlite3_finalize(_sGetIpAssignmentPools2);
- sqlite3_finalize(_sListRules);
- sqlite3_finalize(_sCreateRule);
- sqlite3_finalize(_sCreateNetwork);
- sqlite3_finalize(_sGetNetworkRevision);
- sqlite3_finalize(_sSetNetworkRevision);
- sqlite3_finalize(_sDeleteRelaysForNetwork);
- sqlite3_finalize(_sCreateRelay);
- sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork);
- sqlite3_finalize(_sDeleteRulesForNetwork);
- sqlite3_finalize(_sCreateIpAssignmentPool);
- sqlite3_finalize(_sUpdateMemberAuthorized);
- sqlite3_finalize(_sUpdateMemberActiveBridge);
- sqlite3_finalize(_sUpdateMemberHistory);
- sqlite3_finalize(_sDeleteMember);
- sqlite3_finalize(_sDeleteAllNetworkMembers);
- sqlite3_finalize(_sGetActiveNodesOnNetwork);
- sqlite3_finalize(_sDeleteNetwork);
- sqlite3_finalize(_sCreateRoute);
- sqlite3_finalize(_sGetRoutes);
- sqlite3_finalize(_sDeleteRoutes);
- sqlite3_finalize(_sIncrementMemberRevisionCounter);
- sqlite3_finalize(_sGetConfig);
- sqlite3_finalize(_sSetConfig);
- sqlite3_close(_db);
- }
-}
-
-NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &metaData,NetworkConfig &nc)
-{
- if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) {
- return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
- }
-
- const uint64_t now = OSUtils::now();
-
- NetworkRecord network;
- Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid);
-
- MemberRecord member;
- Utils::snprintf(member.nodeId,sizeof(member.nodeId),"%.10llx",(unsigned long long)identity.address().toInt());
-
- { // begin lock
- Mutex::Lock _l(_lock);
-
- // Check rate limit circuit breaker to prevent flooding
- {
- 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;
- }
-
- _backupNeeded = true;
-
- // Create Node record or do full identity check if we already have one
-
- sqlite3_reset(_sGetNodeIdentity);
- sqlite3_bind_text(_sGetNodeIdentity,1,member.nodeId,10,SQLITE_STATIC);
- if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) {
- try {
- Identity alreadyKnownIdentity((const char *)sqlite3_column_text(_sGetNodeIdentity,0));
- if (alreadyKnownIdentity != identity)
- return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
- } catch ( ... ) { // identity stored in database is not valid or is NULL
- return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
- }
- } else {
- std::string idstr(identity.toString(false));
- sqlite3_reset(_sCreateOrReplaceNode);
- sqlite3_bind_text(_sCreateOrReplaceNode,1,member.nodeId,10,SQLITE_STATIC);
- sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr.c_str(),-1,SQLITE_STATIC);
- if (sqlite3_step(_sCreateOrReplaceNode) != SQLITE_DONE) {
- return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
- }
- }
-
- // Fetch Network record
-
- sqlite3_reset(_sGetNetworkById);
- sqlite3_bind_text(_sGetNetworkById,1,network.id,16,SQLITE_STATIC);
- if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
- network.name = (const char *)sqlite3_column_text(_sGetNetworkById,0);
- network.isPrivate = (sqlite3_column_int(_sGetNetworkById,1) > 0);
- network.enableBroadcast = (sqlite3_column_int(_sGetNetworkById,2) > 0);
- network.allowPassiveBridging = (sqlite3_column_int(_sGetNetworkById,3) > 0);
- network.flags = sqlite3_column_int(_sGetNetworkById,4);
- network.multicastLimit = sqlite3_column_int(_sGetNetworkById,5);
- network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,6);
- network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
- network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8);
- } else {
- return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
- }
-
- // Fetch or create Member record
-
- sqlite3_reset(_sGetMember);
- sqlite3_bind_text(_sGetMember,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_text(_sGetMember,2,member.nodeId,10,SQLITE_STATIC);
- if (sqlite3_step(_sGetMember) == SQLITE_ROW) {
- member.rowid = sqlite3_column_int64(_sGetMember,0);
- member.authorized = (sqlite3_column_int(_sGetMember,1) > 0);
- member.activeBridge = (sqlite3_column_int(_sGetMember,2) > 0);
- member.lastRequestTime = (uint64_t)sqlite3_column_int64(_sGetMember,5);
- const char *rhblob = (const char *)sqlite3_column_blob(_sGetMember,6);
- if (rhblob)
- member.recentHistory.fromBlob(rhblob,(unsigned int)sqlite3_column_bytes(_sGetMember,6));
- } else {
- member.authorized = (network.isPrivate ? false : true);
- member.activeBridge = false;
- sqlite3_reset(_sCreateMember);
- sqlite3_bind_text(_sCreateMember,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_text(_sCreateMember,2,member.nodeId,10,SQLITE_STATIC);
- sqlite3_bind_int(_sCreateMember,3,(member.authorized ? 1 : 0));
- sqlite3_bind_text(_sCreateMember,4,network.id,16,SQLITE_STATIC);
- if (sqlite3_step(_sCreateMember) != SQLITE_DONE) {
- return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
- }
- member.rowid = sqlite3_last_insert_rowid(_db);
-
- sqlite3_reset(_sIncrementMemberRevisionCounter);
- sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,network.id,16,SQLITE_STATIC);
- sqlite3_step(_sIncrementMemberRevisionCounter);
- }
-
- // Update Member.history
-
- {
- char mh[1024];
- Utils::snprintf(mh,sizeof(mh),
- "{\"ts\":%llu,\"authorized\":%s,\"clientMajorVersion\":%u,\"clientMinorVersion\":%u,\"clientRevision\":%u,\"fromAddr\":",
- (unsigned long long)now,
- ((member.authorized) ? "true" : "false"),
- metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0),
- metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0),
- metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0));
- member.recentHistory.push_front(std::string(mh));
- if (fromAddr) {
- member.recentHistory.front().push_back('"');
- member.recentHistory.front().append(_jsonEscape(fromAddr.toString()));
- member.recentHistory.front().append("\"}");
- } else {
- member.recentHistory.front().append("null}");
- }
-
- while (member.recentHistory.size() > ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH)
- member.recentHistory.pop_back();
- std::string rhblob(member.recentHistory.toBlob());
-
- sqlite3_reset(_sUpdateMemberHistory);
- sqlite3_clear_bindings(_sUpdateMemberHistory);
- sqlite3_bind_int64(_sUpdateMemberHistory,1,(sqlite3_int64)now);
- sqlite3_bind_blob(_sUpdateMemberHistory,2,(const void *)rhblob.data(),(int)rhblob.length(),SQLITE_STATIC);
- sqlite3_bind_int64(_sUpdateMemberHistory,3,member.rowid);
- sqlite3_step(_sUpdateMemberHistory);
- }
-
- // Don't proceed if member is not authorized! ---------------------------
-
- if (!member.authorized)
- return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
-
- // Create network configuration -- we create both legacy and new types and send both for backward compatibility
-
- // New network config structure
- nc.networkId = Utils::hexStrToU64(network.id);
- nc.type = network.isPrivate ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
- nc.timestamp = now;
- nc.revision = network.revision;
- nc.issuedTo = member.nodeId;
- if (network.enableBroadcast) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
- if (network.allowPassiveBridging) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING;
- memcpy(nc.name,network.name,std::min((unsigned int)ZT_MAX_NETWORK_SHORT_NAME_LENGTH,(unsigned int)strlen(network.name)));
-
- { // TODO: right now only etherTypes are supported in rules
- std::vector<int> allowedEtherTypes;
- sqlite3_reset(_sGetEtherTypesFromRuleTable);
- sqlite3_bind_text(_sGetEtherTypesFromRuleTable,1,network.id,16,SQLITE_STATIC);
- while (sqlite3_step(_sGetEtherTypesFromRuleTable) == SQLITE_ROW) {
- if (sqlite3_column_type(_sGetEtherTypesFromRuleTable,0) == SQLITE_NULL) {
- allowedEtherTypes.clear();
- allowedEtherTypes.push_back(0); // NULL 'allow' matches ANY
- break;
- } else {
- int et = sqlite3_column_int(_sGetEtherTypesFromRuleTable,0);
- if ((et >= 0)&&(et <= 0xffff))
- allowedEtherTypes.push_back(et);
- }
- }
- std::sort(allowedEtherTypes.begin(),allowedEtherTypes.end());
- allowedEtherTypes.erase(std::unique(allowedEtherTypes.begin(),allowedEtherTypes.end()),allowedEtherTypes.end());
-
- for(long i=0;i<(long)allowedEtherTypes.size();++i) {
- if ((nc.ruleCount + 2) > ZT_MAX_NETWORK_RULES)
- break;
- if (allowedEtherTypes[i] > 0) {
- nc.rules[nc.ruleCount].t = ZT_NETWORK_RULE_MATCH_ETHERTYPE;
- nc.rules[nc.ruleCount].v.etherType = (uint16_t)allowedEtherTypes[i];
- ++nc.ruleCount;
- }
- nc.rules[nc.ruleCount++].t = ZT_NETWORK_RULE_ACTION_ACCEPT;
- }
- }
-
- nc.multicastLimit = network.multicastLimit;
-
- bool amActiveBridge = false;
- {
- sqlite3_reset(_sGetActiveBridges);
- sqlite3_bind_text(_sGetActiveBridges,1,network.id,16,SQLITE_STATIC);
- while (sqlite3_step(_sGetActiveBridges) == SQLITE_ROW) {
- const char *ab = (const char *)sqlite3_column_text(_sGetActiveBridges,0);
- if ((ab)&&(strlen(ab) == 10)) {
- const uint64_t ab2 = Utils::hexStrToU64(ab);
- nc.addSpecialist(Address(ab2),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE);
- if (!strcmp(member.nodeId,ab))
- amActiveBridge = true;
- }
- }
- }
-
- // Do not send relays to 1.1.0 since it had a serious bug in using them
- // 1.1.0 will still work, it'll just fall back to roots instead of using network preferred relays
- if (!((metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0) == 0))) {
- sqlite3_reset(_sGetRelays);
- sqlite3_bind_text(_sGetRelays,1,network.id,16,SQLITE_STATIC);
- while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
- const char *n = (const char *)sqlite3_column_text(_sGetRelays,0);
- const char *a = (const char *)sqlite3_column_text(_sGetRelays,1);
- if ((n)&&(a)) {
- Address node(n);
- InetAddress addr(a);
- if (node)
- nc.addSpecialist(node,ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY);
- }
- }
- }
-
- sqlite3_reset(_sGetRoutes);
- sqlite3_bind_text(_sGetRoutes,1,network.id,16,SQLITE_STATIC);
- while ((sqlite3_step(_sGetRoutes) == SQLITE_ROW)&&(nc.routeCount < ZT_MAX_NETWORK_ROUTES)) {
- ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]);
- memset(r,0,sizeof(ZT_VirtualNetworkRoute));
- switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
- case 4:
- *(reinterpret_cast<InetAddress *>(&(r->target))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,0) + 12),4,(unsigned int)sqlite3_column_int(_sGetRoutes,2));
- break;
- case 6:
- *(reinterpret_cast<InetAddress *>(&(r->target))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,0),16,(unsigned int)sqlite3_column_int(_sGetRoutes,2));
- break;
- default:
- continue;
- }
- if (sqlite3_column_type(_sGetRoutes,1) != SQLITE_NULL) {
- switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
- case 4:
- *(reinterpret_cast<InetAddress *>(&(r->via))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,1) + 12),4,0);
- break;
- case 6:
- *(reinterpret_cast<InetAddress *>(&(r->via))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,1),16,0);
- break;
- default:
- continue;
- }
- }
- r->flags = (uint16_t)sqlite3_column_int(_sGetRoutes,4);
- r->metric = (uint16_t)sqlite3_column_int(_sGetRoutes,5);
- ++nc.routeCount;
- }
-
- // Assign special IPv6 addresses if these are enabled
- if (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193) != 0)&&(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 (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE) != 0)&&(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;
- }
-
- // Get managed addresses that are assigned to this member
- bool haveManagedIpv4AutoAssignment = false;
- bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count
- sqlite3_reset(_sGetIpAssignmentsForNode);
- sqlite3_bind_text(_sGetIpAssignmentsForNode,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_text(_sGetIpAssignmentsForNode,2,member.nodeId,10,SQLITE_STATIC);
- while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) {
- const unsigned char *const ipbytes = (const unsigned char *)sqlite3_column_blob(_sGetIpAssignmentsForNode,0);
- if ((!ipbytes)||(sqlite3_column_bytes(_sGetIpAssignmentsForNode,0) != 16))
- continue;
- //const int ipNetmaskBits = sqlite3_column_int(_sGetIpAssignmentsForNode,1);
- const int ipVersion = sqlite3_column_int(_sGetIpAssignmentsForNode,2);
-
- InetAddress ip;
- if (ipVersion == 4)
- ip = InetAddress(ipbytes + 12,4,0);
- else if (ipVersion == 6)
- ip = InetAddress(ipbytes,16,0);
- else continue;
-
- // 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 (ipVersion == 4)
- haveManagedIpv4AutoAssignment = true;
- else if (ipVersion == 6)
- haveManagedIpv6AutoAssignment = true;
- }
- }
-
- // Auto-assign IPv6 address if auto-assignment is enabled and it's needed
- if ( ((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN) != 0) && (!haveManagedIpv6AutoAssignment) && (!amActiveBridge) ) {
- sqlite3_reset(_sGetIpAssignmentPools);
- sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_int(_sGetIpAssignmentPools,2,6); // 6 == IPv6
- while (sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW) {
- const uint8_t *const ipRangeStartB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,0));
- const uint8_t *const ipRangeEndB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,1));
- if ((!ipRangeStartB)||(!ipRangeEndB)||(sqlite3_column_bytes(_sGetIpAssignmentPools,0) != 16)||(sqlite3_column_bytes(_sGetIpAssignmentPools,1) != 16))
- continue;
-
- uint64_t s[2],e[2],x[2],xx[2];
- memcpy(s,ipRangeStartB,16);
- memcpy(e,ipRangeEndB,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) {
- sqlite3_reset(_sCheckIfIpIsAllocated);
- sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ip6.rawIpData(),16,SQLITE_STATIC);
- sqlite3_bind_int(_sCheckIfIpIsAllocated,3,6); // 6 == IPv6
- sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
- if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) {
- // No rows returned, so the IP is available
- sqlite3_reset(_sAllocateIp);
- sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
- sqlite3_bind_blob(_sAllocateIp,4,(const void *)ip6.rawIpData(),16,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route
- sqlite3_bind_int(_sAllocateIp,6,6); // 6 == IPv6
- if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) {
- ip6.setPort(routedNetmaskBits);
- if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)
- nc.staticIps[nc.staticIpCount++] = ip6;
- break;
- }
- }
- }
- }
- }
- }
-
- // Auto-assign IPv4 address if auto-assignment is enabled and it's needed
- if ( ((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN) != 0) && (!haveManagedIpv4AutoAssignment) && (!amActiveBridge) ) {
- sqlite3_reset(_sGetIpAssignmentPools);
- sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_int(_sGetIpAssignmentPools,2,4); // 4 == IPv4
- while (sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW) {
- const unsigned char *ipRangeStartB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,0));
- const unsigned char *ipRangeEndB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,1));
- if ((!ipRangeStartB)||(!ipRangeEndB)||(sqlite3_column_bytes(_sGetIpAssignmentPools,0) != 16)||(sqlite3_column_bytes(_sGetIpAssignmentPools,1) != 16))
- continue;
-
- uint32_t ipRangeStart = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeStartB + 12)));
- uint32_t ipRangeEnd = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeEndB + 12)));
- 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 = 0;
- for(unsigned int rk=0;rk<nc.routeCount;++rk) {
- if ((!nc.routes[rk].via.ss_family)&&(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
- if (routedNetmaskBits > 0) {
- uint32_t ipBlob[4]; // actually a 16-byte blob, we put IPv4s in the last 4 bytes
- ipBlob[0] = 0; ipBlob[1] = 0; ipBlob[2] = 0; ipBlob[3] = Utils::hton(ip);
- sqlite3_reset(_sCheckIfIpIsAllocated);
- sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ipBlob,16,SQLITE_STATIC);
- sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4
- sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
- if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) {
- // No rows returned, so the IP is available
- sqlite3_reset(_sAllocateIp);
- sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC);
- sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
- sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route
- sqlite3_bind_int(_sAllocateIp,6,4); // 4 == IPv4
- if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) {
- 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);
- }
- break;
- }
- }
- }
- }
- }
- }
- } // end lock
-
- // Perform signing outside lock to enable concurrency
- if (network.isPrivate) {
- CertificateOfMembership com(now,ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA,nwid,identity.address());
- if (com.sign(signingId)) {
- nc.com = com;
- } else {
- return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
- }
- }
-
- return NetworkController::NETCONF_QUERY_OK;
-}
-
-unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType)
-{
- Mutex::Lock _l(_lock);
- return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType);
-}
-
-unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType)
-{
- if (path.empty())
- return 404;
- Mutex::Lock _l(_lock);
-
- _backupNeeded = true;
-
- if (path[0] == "network") {
-
- if ((path.size() >= 2)&&(path[1].length() == 16)) {
- uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
- char nwids[24];
- Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
-
- int64_t revision = 0;
- sqlite3_reset(_sGetNetworkRevision);
- sqlite3_bind_text(_sGetNetworkRevision,1,nwids,16,SQLITE_STATIC);
- bool networkExists = false;
- if (sqlite3_step(_sGetNetworkRevision) == SQLITE_ROW) {
- networkExists = true;
- revision = sqlite3_column_int64(_sGetNetworkRevision,0);
- }
-
- if (path.size() >= 3) {
-
- if (!networkExists)
- 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",address);
-
- int64_t addToNetworkRevision = 0;
-
- int64_t memberRowId = 0;
- sqlite3_reset(_sGetMember);
- sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC);
- bool memberExists = false;
- if (sqlite3_step(_sGetMember) == SQLITE_ROW) {
- memberExists = true;
- memberRowId = sqlite3_column_int64(_sGetMember,0);
- }
-
- if (!memberExists) {
- sqlite3_reset(_sCreateMember);
- sqlite3_bind_text(_sCreateMember,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sCreateMember,2,addrs,10,SQLITE_STATIC);
- sqlite3_bind_int(_sCreateMember,3,0);
- sqlite3_bind_text(_sCreateMember,4,nwids,16,SQLITE_STATIC);
- if (sqlite3_step(_sCreateMember) != SQLITE_DONE)
- return 500;
- memberRowId = (int64_t)sqlite3_last_insert_rowid(_db);
-
- sqlite3_reset(_sIncrementMemberRevisionCounter);
- sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sIncrementMemberRevisionCounter);
- addToNetworkRevision = 1;
- }
-
- json_value *j = json_parse(body.c_str(),body.length());
- if (j) {
- if (j->type == json_object) {
- for(unsigned int k=0;k<j->u.object.length;++k) {
-
- if (!strcmp(j->u.object.values[k].name,"authorized")) {
- if (j->u.object.values[k].value->type == json_boolean) {
- sqlite3_reset(_sUpdateMemberAuthorized);
- sqlite3_bind_int(_sUpdateMemberAuthorized,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
- sqlite3_bind_text(_sUpdateMemberAuthorized,2,nwids,16,SQLITE_STATIC);
- sqlite3_bind_int64(_sUpdateMemberAuthorized,3,memberRowId);
- if (sqlite3_step(_sUpdateMemberAuthorized) != SQLITE_DONE)
- return 500;
-
- sqlite3_reset(_sIncrementMemberRevisionCounter);
- sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sIncrementMemberRevisionCounter);
- addToNetworkRevision = 1;
- }
- } else if (!strcmp(j->u.object.values[k].name,"activeBridge")) {
- if (j->u.object.values[k].value->type == json_boolean) {
- sqlite3_reset(_sUpdateMemberActiveBridge);
- sqlite3_bind_int(_sUpdateMemberActiveBridge,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
- sqlite3_bind_text(_sUpdateMemberActiveBridge,2,nwids,16,SQLITE_STATIC);
- sqlite3_bind_int64(_sUpdateMemberActiveBridge,3,memberRowId);
- if (sqlite3_step(_sUpdateMemberActiveBridge) != SQLITE_DONE)
- return 500;
-
- sqlite3_reset(_sIncrementMemberRevisionCounter);
- sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sIncrementMemberRevisionCounter);
- addToNetworkRevision = 1;
- }
- } else if (!strcmp(j->u.object.values[k].name,"ipAssignments")) {
- if (j->u.object.values[k].value->type == json_array) {
- sqlite3_reset(_sDeleteIpAllocations);
- sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC);
- sqlite3_bind_int(_sDeleteIpAllocations,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
- if (sqlite3_step(_sDeleteIpAllocations) != SQLITE_DONE)
- return 500;
- for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
- json_value *ipalloc = j->u.object.values[k].value->u.array.values[kk];
- if (ipalloc->type == json_string) {
- InetAddress a(ipalloc->u.string.ptr);
- char ipBlob[16];
- int ipVersion = 0;
- _ipToBlob(a,ipBlob,ipVersion);
- if (ipVersion > 0) {
- sqlite3_reset(_sAllocateIp);
- sqlite3_bind_text(_sAllocateIp,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sAllocateIp,2,addrs,10,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
- sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
- sqlite3_bind_int(_sAllocateIp,5,(int)a.netmaskBits()); // NOTE: this field is now ignored but set it anyway
- sqlite3_bind_int(_sAllocateIp,6,ipVersion);
- if (sqlite3_step(_sAllocateIp) != SQLITE_DONE)
- return 500;
- }
- }
- }
- addToNetworkRevision = 1;
- }
- } else if (!strcmp(j->u.object.values[k].name,"identity")) {
- // Identity is technically an immutable field, but if the member's Node has
- // no identity we allow it to be populated. This is primarily for migrating
- // node data from another controller.
- json_value *idstr = j->u.object.values[k].value;
- if (idstr->type == json_string) {
- bool alreadyHaveIdentity = false;
-
- sqlite3_reset(_sGetNodeIdentity);
- sqlite3_bind_text(_sGetNodeIdentity,1,addrs,10,SQLITE_STATIC);
- if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) {
- const char *tmp2 = (const char *)sqlite3_column_text(_sGetNodeIdentity,0);
- if ((tmp2)&&(tmp2[0]))
- alreadyHaveIdentity = true;
- }
-
- if (!alreadyHaveIdentity) {
- try {
- Identity id2(idstr->u.string.ptr);
- if (id2) {
- std::string idstr2(id2.toString(false)); // object must persist until after sqlite3_step() for SQLITE_STATIC
- sqlite3_reset(_sCreateOrReplaceNode);
- sqlite3_bind_text(_sCreateOrReplaceNode,1,addrs,10,SQLITE_STATIC);
- sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr2.c_str(),-1,SQLITE_STATIC);
- sqlite3_step(_sCreateOrReplaceNode);
- }
- } catch ( ... ) {} // ignore invalid identities
- }
- }
- }
-
- }
- }
- json_value_free(j);
- }
-
- if ((addToNetworkRevision > 0)&&(revision > 0)) {
- sqlite3_reset(_sSetNetworkRevision);
- sqlite3_bind_int64(_sSetNetworkRevision,1,revision + addToNetworkRevision);
- sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sSetNetworkRevision);
- }
-
- return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType);
- } else if ((path.size() == 3)&&(path[2] == "test")) {
- ZT_CircuitTest *test = (ZT_CircuitTest *)malloc(sizeof(ZT_CircuitTest));
- memset(test,0,sizeof(ZT_CircuitTest));
-
- Utils::getSecureRandom(&(test->testId),sizeof(test->testId));
- test->credentialNetworkId = nwid;
- test->ptr = (void *)this;
-
- json_value *j = json_parse(body.c_str(),body.length());
- if (j) {
- if (j->type == json_object) {
- for(unsigned int k=0;k<j->u.object.length;++k) {
-
- if (!strcmp(j->u.object.values[k].name,"hops")) {
- if (j->u.object.values[k].value->type == json_array) {
- for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
- json_value *hop = j->u.object.values[k].value->u.array.values[kk];
- if (hop->type == json_array) {
- for(unsigned int kkk=0;kkk<hop->u.array.length;++kkk) {
- if (hop->u.array.values[kkk]->type == json_string) {
- test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(hop->u.array.values[kkk]->u.string.ptr) & 0xffffffffffULL;
- }
- }
- ++test->hopCount;
- }
- }
- }
- } else if (!strcmp(j->u.object.values[k].name,"reportAtEveryHop")) {
- if (j->u.object.values[k].value->type == json_boolean)
- test->reportAtEveryHop = (j->u.object.values[k].value->u.boolean == 0) ? 0 : 1;
- }
-
- }
- }
- json_value_free(j);
- }
-
- if (!test->hopCount) {
- ::free((void *)test);
- return 500;
- }
-
- test->timestamp = OSUtils::now();
-
- _CircuitTestEntry &te = _circuitTests[test->testId];
- te.test = test;
- te.jsonResults = "";
-
- _node->circuitTestBegin(test,&(SqliteNetworkController::_circuitTestCallback));
-
- char json[1024];
- Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\"}",test->testId);
- responseBody = json;
- responseContentType = "application/json";
-
- return 200;
- } // else 404
-
- } else {
- std::vector<std::string> path_copy(path);
-
- if (!networkExists) {
- if (path[1].substr(10) == "______") {
- // A special POST /network/##########______ feature lets users create a network
- // with an arbitrary unused network number at this controller.
- nwid = 0;
-
- uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL;
- uint64_t nwidPostfix = 0;
- Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
- uint64_t nwidOriginalPostfix = nwidPostfix;
- do {
- uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
- if (!nwidPostfix)
- tryNwid |= 1;
- Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid);
-
- sqlite3_reset(_sGetNetworkRevision);
- sqlite3_bind_text(_sGetNetworkRevision,1,nwids,16,SQLITE_STATIC);
- if (sqlite3_step(_sGetNetworkRevision) != SQLITE_ROW) {
- nwid = tryNwid;
- break;
- }
-
- ++nwidPostfix;
- } while (nwidPostfix != nwidOriginalPostfix);
-
- // 503 means we have no more free IDs for this prefix. You shouldn't host anywhere
- // near 16 million networks on the same controller, so shouldn't happen.
- if (!nwid)
- return 503;
- }
-
- sqlite3_reset(_sCreateNetwork);
- sqlite3_bind_text(_sCreateNetwork,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sCreateNetwork,2,"",0,SQLITE_STATIC);
- sqlite3_bind_int64(_sCreateNetwork,3,(long long)OSUtils::now());
- if (sqlite3_step(_sCreateNetwork) != SQLITE_DONE)
- return 500;
- path_copy[1].assign(nwids);
- }
-
- json_value *j = json_parse(body.c_str(),body.length());
- if (j) {
- if (j->type == json_object) {
- for(unsigned int k=0;k<j->u.object.length;++k) {
- sqlite3_stmt *stmt = (sqlite3_stmt *)0;
-
- if (!strcmp(j->u.object.values[k].name,"name")) {
- if ((j->u.object.values[k].value->type == json_string)&&(j->u.object.values[k].value->u.string.ptr[0])) {
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"name\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_text(stmt,1,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC);
- }
- } else if (!strcmp(j->u.object.values[k].name,"private")) {
- if (j->u.object.values[k].value->type == json_boolean) {
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"private\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
- }
- } else if (!strcmp(j->u.object.values[k].name,"enableBroadcast")) {
- if (j->u.object.values[k].value->type == json_boolean) {
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET enableBroadcast = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
- }
- } else if (!strcmp(j->u.object.values[k].name,"allowPassiveBridging")) {
- if (j->u.object.values[k].value->type == json_boolean) {
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET allowPassiveBridging = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
- }
- } else if (!strcmp(j->u.object.values[k].name,"v4AssignMode")) {
- if ((j->u.object.values[k].value->type == json_string)&&(!strcmp(j->u.object.values[k].value->u.string.ptr,"zt"))) {
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = (\"flags\" | ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_int(stmt,1,(int)ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN);
- } else {
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = (\"flags\" & ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_int(stmt,1,(int)(ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN ^ 0xfffffff));
- }
- } else if (!strcmp(j->u.object.values[k].name,"v6AssignMode")) {
- int fl = 0;
- if (j->u.object.values[k].value->type == json_string) {
- char *saveptr = (char *)0;
- for(char *f=Utils::stok(j->u.object.values[k].value->u.string.ptr,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) {
- if (!strcmp(f,"rfc4193"))
- fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193;
- else if (!strcmp(f,"6plane"))
- fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE;
- else if (!strcmp(f,"zt"))
- fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN;
- }
- }
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = ((\"flags\" & " ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_MASK_S ") | ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_int(stmt,1,fl);
- } else if (!strcmp(j->u.object.values[k].name,"multicastLimit")) {
- if (j->u.object.values[k].value->type == json_integer) {
- if (sqlite3_prepare_v2(_db,"UPDATE Network SET multicastLimit = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
- sqlite3_bind_int(stmt,1,(int)j->u.object.values[k].value->u.integer);
- }
- } else if (!strcmp(j->u.object.values[k].name,"relays")) {
- if (j->u.object.values[k].value->type == json_array) {
- std::map<Address,InetAddress> nodeIdToPhyAddress;
- for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
- json_value *relay = j->u.object.values[k].value->u.array.values[kk];
- const char *address = (const char *)0;
- const char *phyAddress = (const char *)0;
- if ((relay)&&(relay->type == json_object)) {
- for(unsigned int rk=0;rk<relay->u.object.length;++rk) {
- if ((!strcmp(relay->u.object.values[rk].name,"address"))&&(relay->u.object.values[rk].value->type == json_string))
- address = relay->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(relay->u.object.values[rk].name,"phyAddress"))&&(relay->u.object.values[rk].value->type == json_string))
- phyAddress = relay->u.object.values[rk].value->u.string.ptr;
- }
- }
- if ((address)&&(phyAddress))
- nodeIdToPhyAddress[Address(address)] = InetAddress(phyAddress);
- }
-
- sqlite3_reset(_sDeleteRelaysForNetwork);
- sqlite3_bind_text(_sDeleteRelaysForNetwork,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sDeleteRelaysForNetwork);
-
- for(std::map<Address,InetAddress>::iterator rl(nodeIdToPhyAddress.begin());rl!=nodeIdToPhyAddress.end();++rl) {
- sqlite3_reset(_sCreateRelay);
- sqlite3_bind_text(_sCreateRelay,1,nwids,16,SQLITE_STATIC);
- std::string a(rl->first.toString()),b(rl->second.toString()); // don't destroy strings until sqlite3_step()
- sqlite3_bind_text(_sCreateRelay,2,a.c_str(),-1,SQLITE_STATIC);
- sqlite3_bind_text(_sCreateRelay,3,b.c_str(),-1,SQLITE_STATIC);
- sqlite3_step(_sCreateRelay);
- }
- }
- } else if (!strcmp(j->u.object.values[k].name,"routes")) {
- sqlite3_reset(_sDeleteRoutes);
- sqlite3_bind_text(_sDeleteRoutes,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sDeleteRoutes);
- if (j->u.object.values[k].value->type == json_array) {
- for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
- json_value *r = j->u.object.values[k].value->u.array.values[kk];
- if ((r)&&(r->type == json_object)) {
- InetAddress r_target,r_via;
- int r_flags = 0;
- int r_metric = 0;
- for(unsigned int rk=0;rk<r->u.object.length;++rk) {
- if ((!strcmp(r->u.object.values[rk].name,"target"))&&(r->u.object.values[rk].value->type == json_string))
- r_target = InetAddress(std::string(r->u.object.values[rk].value->u.string.ptr));
- else if ((!strcmp(r->u.object.values[rk].name,"via"))&&(r->u.object.values[rk].value->type == json_string))
- r_via = InetAddress(std::string(r->u.object.values[rk].value->u.string.ptr),0);
- else if ((!strcmp(r->u.object.values[rk].name,"flags"))&&(r->u.object.values[rk].value->type == json_integer))
- r_flags = (int)(r->u.object.values[rk].value->u.integer & 0xffff);
- else if ((!strcmp(r->u.object.values[rk].name,"metric"))&&(r->u.object.values[rk].value->type == json_integer))
- r_metric = (int)(r->u.object.values[rk].value->u.integer & 0xffff);
- }
- if ((r_target)&&((!r_via)||(r_via.ss_family == r_target.ss_family))) {
- int r_ipVersion = 0;
- char r_targetBlob[16];
- char r_viaBlob[16];
- _ipToBlob(r_target,r_targetBlob,r_ipVersion);
- if (r_ipVersion) {
- int r_targetNetmaskBits = r_target.netmaskBits();
- if ((r_ipVersion == 4)&&(r_targetNetmaskBits > 32)) r_targetNetmaskBits = 32;
- else if ((r_ipVersion == 6)&&(r_targetNetmaskBits > 128)) r_targetNetmaskBits = 128;
- sqlite3_reset(_sCreateRoute);
- sqlite3_bind_text(_sCreateRoute,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_blob(_sCreateRoute,2,(const void *)r_targetBlob,16,SQLITE_STATIC);
- if (r_via) {
- _ipToBlob(r_via,r_viaBlob,r_ipVersion);
- sqlite3_bind_blob(_sCreateRoute,3,(const void *)r_viaBlob,16,SQLITE_STATIC);
- } else {
- sqlite3_bind_null(_sCreateRoute,3);
- }
- sqlite3_bind_int(_sCreateRoute,4,r_targetNetmaskBits);
- sqlite3_bind_int(_sCreateRoute,5,r_ipVersion);
- sqlite3_bind_int(_sCreateRoute,6,r_flags);
- sqlite3_bind_int(_sCreateRoute,7,r_metric);
- sqlite3_step(_sCreateRoute);
- }
- }
- }
- }
- }
- } else if (!strcmp(j->u.object.values[k].name,"ipAssignmentPools")) {
- if (j->u.object.values[k].value->type == json_array) {
- std::vector< std::pair<InetAddress,InetAddress> > pools;
- for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
- json_value *pool = j->u.object.values[k].value->u.array.values[kk];
- const char *iprs = (const char *)0;
- const char *ipre = (const char *)0;
- if ((pool)&&(pool->type == json_object)) {
- for(unsigned int rk=0;rk<pool->u.object.length;++rk) {
- if ((!strcmp(pool->u.object.values[rk].name,"ipRangeStart"))&&(pool->u.object.values[rk].value->type == json_string))
- iprs = pool->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(pool->u.object.values[rk].name,"ipRangeEnd"))&&(pool->u.object.values[rk].value->type == json_string))
- ipre = pool->u.object.values[rk].value->u.string.ptr;
- }
- }
- if ((iprs)&&(ipre)) {
- InetAddress iprs2(iprs);
- InetAddress ipre2(ipre);
- if (iprs2.ss_family == ipre2.ss_family) {
- iprs2.setPort(0);
- ipre2.setPort(0);
- pools.push_back(std::pair<InetAddress,InetAddress>(iprs2,ipre2));
- }
- }
- }
- std::sort(pools.begin(),pools.end());
- pools.erase(std::unique(pools.begin(),pools.end()),pools.end());
-
- sqlite3_reset(_sDeleteIpAssignmentPoolsForNetwork);
- sqlite3_bind_text(_sDeleteIpAssignmentPoolsForNetwork,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sDeleteIpAssignmentPoolsForNetwork);
-
- for(std::vector< std::pair<InetAddress,InetAddress> >::const_iterator p(pools.begin());p!=pools.end();++p) {
- char ipBlob1[16],ipBlob2[16];
- sqlite3_reset(_sCreateIpAssignmentPool);
- sqlite3_bind_text(_sCreateIpAssignmentPool,1,nwids,16,SQLITE_STATIC);
- if (p->first.ss_family == AF_INET) {
- memset(ipBlob1,0,12);
- memcpy(ipBlob1 + 12,p->first.rawIpData(),4);
- memset(ipBlob2,0,12);
- memcpy(ipBlob2 + 12,p->second.rawIpData(),4);
- sqlite3_bind_blob(_sCreateIpAssignmentPool,2,(const void *)ipBlob1,16,SQLITE_STATIC);
- sqlite3_bind_blob(_sCreateIpAssignmentPool,3,(const void *)ipBlob2,16,SQLITE_STATIC);
- sqlite3_bind_int(_sCreateIpAssignmentPool,4,4);
- } else if (p->first.ss_family == AF_INET6) {
- sqlite3_bind_blob(_sCreateIpAssignmentPool,2,p->first.rawIpData(),16,SQLITE_STATIC);
- sqlite3_bind_blob(_sCreateIpAssignmentPool,3,p->second.rawIpData(),16,SQLITE_STATIC);
- sqlite3_bind_int(_sCreateIpAssignmentPool,4,6);
- } else continue;
- sqlite3_step(_sCreateIpAssignmentPool);
- }
- }
- } else if (!strcmp(j->u.object.values[k].name,"rules")) {
- if (j->u.object.values[k].value->type == json_array) {
- sqlite3_reset(_sDeleteRulesForNetwork);
- sqlite3_bind_text(_sDeleteRulesForNetwork,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sDeleteRulesForNetwork);
-
- for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
- json_value *rj = j->u.object.values[k].value->u.array.values[kk];
- if ((rj)&&(rj->type == json_object)) {
- struct { // NULL pointers indicate missing or NULL -- wildcards
- const json_int_t *ruleNo;
- const char *nodeId;
- const char *sourcePort;
- const char *destPort;
- const json_int_t *vlanId;
- const json_int_t *vlanPcp;
- const json_int_t *etherType;
- const char *macSource;
- const char *macDest;
- const char *ipSource;
- const char *ipDest;
- const json_int_t *ipTos;
- const json_int_t *ipProtocol;
- const json_int_t *ipSourcePort;
- const json_int_t *ipDestPort;
- const json_int_t *flags;
- const json_int_t *invFlags;
- const char *action;
- } rule;
- memset(&rule,0,sizeof(rule));
-
- for(unsigned int rk=0;rk<rj->u.object.length;++rk) {
- if ((!strcmp(rj->u.object.values[rk].name,"ruleNo"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.ruleNo = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"nodeId"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.nodeId = rj->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(rj->u.object.values[rk].name,"sourcePort"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.sourcePort = rj->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(rj->u.object.values[rk].name,"destPort"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.destPort = rj->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(rj->u.object.values[rk].name,"vlanId"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.vlanId = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"vlanPcp"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.vlanPcp = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"etherType"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.etherType = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"macSource"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.macSource = rj->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(rj->u.object.values[rk].name,"macDest"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.macDest = rj->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(rj->u.object.values[rk].name,"ipSource"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.ipSource = rj->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(rj->u.object.values[rk].name,"ipDest"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.ipDest = rj->u.object.values[rk].value->u.string.ptr;
- else if ((!strcmp(rj->u.object.values[rk].name,"ipTos"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.ipTos = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"ipProtocol"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.ipProtocol = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"ipSourcePort"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.ipSourcePort = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"ipDestPort"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.ipDestPort = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"flags"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.flags = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"invFlags"))&&(rj->u.object.values[rk].value->type == json_integer))
- rule.invFlags = &(rj->u.object.values[rk].value->u.integer);
- else if ((!strcmp(rj->u.object.values[rk].name,"action"))&&(rj->u.object.values[rk].value->type == json_string))
- rule.action = rj->u.object.values[rk].value->u.string.ptr;
- }
-
- if ((rule.ruleNo)&&(rule.action)&&(rule.action[0])) {
- char mactmp1[16],mactmp2[16];
- sqlite3_reset(_sCreateRule);
- sqlite3_bind_text(_sCreateRule,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_int64(_sCreateRule,2,*rule.ruleNo);
-
- // Optional values: null by default
- for(int i=3;i<=18;++i)
- sqlite3_bind_null(_sCreateRule,i);
- if ((rule.nodeId)&&(strlen(rule.nodeId) == 10)) sqlite3_bind_text(_sCreateRule,3,rule.nodeId,10,SQLITE_STATIC);
- if ((rule.sourcePort)&&(strlen(rule.sourcePort) == 10)) sqlite3_bind_text(_sCreateRule,4,rule.sourcePort,10,SQLITE_STATIC);
- if ((rule.destPort)&&(strlen(rule.destPort) == 10)) sqlite3_bind_text(_sCreateRule,5,rule.destPort,10,SQLITE_STATIC);
- if (rule.vlanId) sqlite3_bind_int(_sCreateRule,6,(int)*rule.vlanId);
- if (rule.vlanPcp) sqlite3_bind_int(_sCreateRule,7,(int)*rule.vlanPcp);
- if (rule.etherType) sqlite3_bind_int(_sCreateRule,8,(int)*rule.etherType & (int)0xffff);
- if (rule.macSource) {
- MAC m(rule.macSource);
- Utils::snprintf(mactmp1,sizeof(mactmp1),"%.12llx",(unsigned long long)m.toInt());
- sqlite3_bind_text(_sCreateRule,9,mactmp1,-1,SQLITE_STATIC);
- }
- if (rule.macDest) {
- MAC m(rule.macDest);
- Utils::snprintf(mactmp2,sizeof(mactmp2),"%.12llx",(unsigned long long)m.toInt());
- sqlite3_bind_text(_sCreateRule,10,mactmp2,-1,SQLITE_STATIC);
- }
- if (rule.ipSource) sqlite3_bind_text(_sCreateRule,11,rule.ipSource,-1,SQLITE_STATIC);
- if (rule.ipDest) sqlite3_bind_text(_sCreateRule,12,rule.ipDest,-1,SQLITE_STATIC);
- if (rule.ipTos) sqlite3_bind_int(_sCreateRule,13,(int)*rule.ipTos);
- if (rule.ipProtocol) sqlite3_bind_int(_sCreateRule,14,(int)*rule.ipProtocol);
- if (rule.ipSourcePort) sqlite3_bind_int(_sCreateRule,15,(int)*rule.ipSourcePort & (int)0xffff);
- if (rule.ipDestPort) sqlite3_bind_int(_sCreateRule,16,(int)*rule.ipDestPort & (int)0xffff);
- if (rule.flags) sqlite3_bind_int64(_sCreateRule,17,(int64_t)*rule.flags);
- if (rule.invFlags) sqlite3_bind_int64(_sCreateRule,18,(int64_t)*rule.invFlags);
-
- sqlite3_bind_text(_sCreateRule,19,rule.action,-1,SQLITE_STATIC);
- sqlite3_step(_sCreateRule);
- }
- }
- }
- }
- }
-
- if (stmt) {
- sqlite3_bind_text(stmt,2,nwids,16,SQLITE_STATIC);
- sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- }
- }
- }
- json_value_free(j);
- }
-
- sqlite3_reset(_sSetNetworkRevision);
- sqlite3_bind_int64(_sSetNetworkRevision,1,revision += 1);
- sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sSetNetworkRevision);
-
- return _doCPGet(path_copy,urlArgs,headers,body,responseBody,responseContentType);
- }
-
- } // else 404
-
- } // else 404
-
- return 404;
-}
-
-unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType)
-{
- if (path.empty())
- return 404;
- Mutex::Lock _l(_lock);
-
- _backupNeeded = true;
-
- if (path[0] == "network") {
-
- if ((path.size() >= 2)&&(path[1].length() == 16)) {
- uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
- char nwids[24];
- Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
-
- sqlite3_reset(_sGetNetworkById);
- sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC);
- if (sqlite3_step(_sGetNetworkById) != SQLITE_ROW)
- return 404;
-
- if (path.size() >= 3) {
-
- 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",address);
-
- sqlite3_reset(_sGetMember);
- sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC);
- if (sqlite3_step(_sGetMember) != SQLITE_ROW)
- return 404;
-
- sqlite3_reset(_sDeleteIpAllocations);
- sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC);
- sqlite3_bind_int(_sDeleteIpAllocations,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
- if (sqlite3_step(_sDeleteIpAllocations) == SQLITE_DONE) {
- sqlite3_reset(_sDeleteMember);
- sqlite3_bind_text(_sDeleteMember,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sDeleteMember,2,addrs,10,SQLITE_STATIC);
- if (sqlite3_step(_sDeleteMember) != SQLITE_DONE)
- return 500;
- } else return 500;
-
- return 200;
- }
-
- } else {
-
- sqlite3_reset(_sDeleteNetwork);
- sqlite3_bind_text(_sDeleteNetwork,1,nwids,16,SQLITE_STATIC);
- if (sqlite3_step(_sDeleteNetwork) == SQLITE_DONE) {
- sqlite3_reset(_sDeleteAllNetworkMembers);
- sqlite3_bind_text(_sDeleteAllNetworkMembers,1,nwids,16,SQLITE_STATIC);
- sqlite3_step(_sDeleteAllNetworkMembers);
- return 200;
- } else return 500;
-
- }
- } // else 404
-
- } // else 404
-
- return 404;
-}
-
-void SqliteNetworkController::threadMain()
- throw()
-{
- uint64_t lastBackupTime = OSUtils::now();
- uint64_t lastCleanupTime = OSUtils::now();
-
- while (_backupThreadRun) {
- if ((OSUtils::now() - lastCleanupTime) >= 5000) {
- const uint64_t now = OSUtils::now();
- lastCleanupTime = now;
-
- Mutex::Lock _l(_lock);
-
- // Clean out really old circuit tests to prevent memory build-up
- for(std::map< uint64_t,_CircuitTestEntry >::iterator ct(_circuitTests.begin());ct!=_circuitTests.end();) {
- if (!ct->second.test) {
- _circuitTests.erase(ct++);
- } else if ((now - ct->second.test->timestamp) >= ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT) {
- _node->circuitTestEnd(ct->second.test);
- ::free((void *)ct->second.test);
- _circuitTests.erase(ct++);
- } else ++ct;
- }
- }
-
- if (((OSUtils::now() - lastBackupTime) >= ZT_NETCONF_BACKUP_PERIOD)&&(_backupNeeded)) {
- lastBackupTime = OSUtils::now();
-
- char backupPath[4096],backupPath2[4096];
- Utils::snprintf(backupPath,sizeof(backupPath),"%s.backupInProgress",_dbPath.c_str());
- Utils::snprintf(backupPath2,sizeof(backupPath),"%s.backup",_dbPath.c_str());
- OSUtils::rm(backupPath); // delete any unfinished backups
-
- sqlite3 *bakdb = (sqlite3 *)0;
- sqlite3_backup *bak = (sqlite3_backup *)0;
- if (sqlite3_open_v2(backupPath,&bakdb,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK) {
- fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_open_v2()"ZT_EOL_S);
- continue;
- }
- bak = sqlite3_backup_init(bakdb,"main",_db,"main");
- if (!bak) {
- sqlite3_close(bakdb);
- OSUtils::rm(backupPath); // delete any unfinished backups
- fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_backup_init()"ZT_EOL_S);
- continue;
- }
-
- int rc = SQLITE_OK;
- for(;;) {
- if (!_backupThreadRun) {
- sqlite3_backup_finish(bak);
- sqlite3_close(bakdb);
- OSUtils::rm(backupPath);
- return;
- }
- _lock.lock();
- rc = sqlite3_backup_step(bak,64);
- _lock.unlock();
- if ((rc == SQLITE_OK)||(rc == SQLITE_LOCKED)||(rc == SQLITE_BUSY))
- Thread::sleep(50);
- else break;
- }
-
- sqlite3_backup_finish(bak);
- sqlite3_close(bakdb);
-
- OSUtils::rm(backupPath2);
- ::rename(backupPath,backupPath2);
-
- _backupNeeded = false;
- }
-
- Thread::sleep(250);
- }
-}
-
-unsigned int SqliteNetworkController::_doCPGet(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType)
-{
- // Assumes _lock is locked
- char json[65536];
-
- if ((path.size() > 0)&&(path[0] == "network")) {
-
- if ((path.size() >= 2)&&(path[1].length() == 16)) {
- uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
- char nwids[24];
- Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
-
- if (path.size() >= 3) {
- // /network/<nwid>/...
-
- if (path[2] == "member") {
-
- if (path.size() >= 4) {
- // Get specific member info
-
- uint64_t address = Utils::hexStrToU64(path[3].c_str());
- char addrs[24];
- Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
-
- sqlite3_reset(_sGetMember2);
- sqlite3_bind_text(_sGetMember2,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sGetMember2,2,addrs,10,SQLITE_STATIC);
- if (sqlite3_step(_sGetMember2) == SQLITE_ROW) {
- const char *memberIdStr = (const char *)sqlite3_column_text(_sGetMember2,3);
-
- Utils::snprintf(json,sizeof(json),
- "{\n"
- "\t\"nwid\": \"%s\",\n"
- "\t\"address\": \"%s\",\n"
- "\t\"controllerInstanceId\": \"%s\",\n"
- "\t\"authorized\": %s,\n"
- "\t\"activeBridge\": %s,\n"
- "\t\"memberRevision\": %llu,\n"
- "\t\"clock\": %llu,\n"
- "\t\"identity\": \"%s\",\n"
- "\t\"ipAssignments\": [",
- nwids,
- addrs,
- _instanceId.c_str(),
- (sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false",
- (sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false",
- (unsigned long long)sqlite3_column_int64(_sGetMember2,2),
- (unsigned long long)OSUtils::now(),
- _jsonEscape(memberIdStr).c_str());
- responseBody = json;
-
- sqlite3_reset(_sGetIpAssignmentsForNode);
- sqlite3_bind_text(_sGetIpAssignmentsForNode,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_text(_sGetIpAssignmentsForNode,2,addrs,10,SQLITE_STATIC);
- bool firstIp = true;
- while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) {
- int ipversion = sqlite3_column_int(_sGetIpAssignmentsForNode,2);
- char ipBlob[16];
- memcpy(ipBlob,(const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode,0),16);
- InetAddress ip(
- (const void *)(ipversion == 6 ? ipBlob : &ipBlob[12]),
- (ipversion == 6 ? 16 : 4),
- (unsigned int)sqlite3_column_int(_sGetIpAssignmentsForNode,1)
- );
- responseBody.append(firstIp ? "\"" : ",\"");
- responseBody.append(_jsonEscape(ip.toIpString()));
- responseBody.push_back('"');
- firstIp = false;
- }
-
- responseBody.append("],\n\t\"recentLog\": [");
-
- const void *histb = sqlite3_column_blob(_sGetMember2,6);
- if (histb) {
- MemberRecentHistory rh;
- rh.fromBlob((const char *)histb,sqlite3_column_bytes(_sGetMember2,6));
- for(MemberRecentHistory::const_iterator i(rh.begin());i!=rh.end();++i) {
- if (i != rh.begin())
- responseBody.push_back(',');
- responseBody.append(*i);
- }
- }
-
- responseBody.append("]\n}\n");
-
- responseContentType = "application/json";
- return 200;
- } // else 404
-
- } else {
- // List members
-
- sqlite3_reset(_sListNetworkMembers);
- sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC);
- responseBody.push_back('{');
- bool firstMember = true;
- while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) {
- responseBody.append(firstMember ? "\"" : ",\"");
- firstMember = false;
- responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0));
- responseBody.append("\":");
- responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,1));
- }
- responseBody.push_back('}');
- responseContentType = "application/json";
- return 200;
-
- }
-
- } else if ((path[2] == "active")&&(path.size() == 3)) {
-
- sqlite3_reset(_sGetActiveNodesOnNetwork);
- sqlite3_bind_text(_sGetActiveNodesOnNetwork,1,nwids,16,SQLITE_STATIC);
- sqlite3_bind_int64(_sGetActiveNodesOnNetwork,2,(int64_t)(OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD));
-
- responseBody.push_back('{');
- bool firstActiveMember = true;
- while (sqlite3_step(_sGetActiveNodesOnNetwork) == SQLITE_ROW) {
- const char *nodeId = (const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,0);
- const char *rhblob = (const char *)sqlite3_column_blob(_sGetActiveNodesOnNetwork,1);
- if ((nodeId)&&(rhblob)) {
- MemberRecentHistory rh;
- rh.fromBlob(rhblob,sqlite3_column_bytes(_sGetActiveNodesOnNetwork,1));
- if (rh.size() > 0) {
- if (firstActiveMember) {
- firstActiveMember = false;
- } else {
- responseBody.push_back(',');
- }
- responseBody.push_back('"');
- responseBody.append(nodeId);
- responseBody.append("\":");
- responseBody.append(rh.front());
- }
- }
- }
- responseBody.push_back('}');
-
- responseContentType = "application/json";
- return 200;
-
- } else if ((path[2] == "test")&&(path.size() >= 4)) {
-
- 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 {
-
- sqlite3_reset(_sGetNetworkById);
- sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC);
- if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
- unsigned int fl = (unsigned int)sqlite3_column_int(_sGetNetworkById,4);
- std::string v6modes;
- if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193) != 0)
- v6modes.append("rfc4193");
- if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE) != 0) {
- if (v6modes.length() > 0)
- v6modes.push_back(',');
- v6modes.append("6plane");
- }
- if ((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN) != 0) {
- if (v6modes.length() > 0)
- v6modes.push_back(',');
- v6modes.append("zt");
- }
-
- Utils::snprintf(json,sizeof(json),
- "{\n"
- "\t\"nwid\": \"%s\",\n"
- "\t\"controllerInstanceId\": \"%s\",\n"
- "\t\"clock\": %llu,\n"
- "\t\"name\": \"%s\",\n"
- "\t\"private\": %s,\n"
- "\t\"enableBroadcast\": %s,\n"
- "\t\"allowPassiveBridging\": %s,\n"
- "\t\"v4AssignMode\": \"%s\",\n"
- "\t\"v6AssignMode\": \"%s\",\n"
- "\t\"multicastLimit\": %d,\n"
- "\t\"creationTime\": %llu,\n"
- "\t\"revision\": %llu,\n"
- "\t\"memberRevisionCounter\": %llu,\n"
- "\t\"authorizedMemberCount\": %llu,\n"
- "\t\"relays\": [",
- nwids,
- _instanceId.c_str(),
- (unsigned long long)OSUtils::now(),
- _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(),
- (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false",
- (sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false",
- (sqlite3_column_int(_sGetNetworkById,3) > 0) ? "true" : "false",
- (((fl & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN) != 0) ? "zt" : ""),
- v6modes.c_str(),
- sqlite3_column_int(_sGetNetworkById,5),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,6),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,7),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,8),
- (unsigned long long)sqlite3_column_int64(_sGetNetworkById,9));
- responseBody = json;
-
- sqlite3_reset(_sGetRelays);
- sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC);
- bool firstRelay = true;
- while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
- responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t");
- firstRelay = false;
- responseBody.append("{\"address\":\"");
- responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0));
- responseBody.append("\",\"phyAddress\":\"");
- responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1)));
- responseBody.append("\"}");
- }
-
- responseBody.append("],\n\t\"routes\": [");
-
- sqlite3_reset(_sGetRoutes);
- sqlite3_bind_text(_sGetRoutes,1,nwids,16,SQLITE_STATIC);
- bool firstRoute = true;
- while (sqlite3_step(_sGetRoutes) == SQLITE_ROW) {
- responseBody.append(firstRoute ? "\n\t\t" : ",\n\t\t");
- firstRoute = false;
- responseBody.append("{\"target\":");
- char tmp[128];
- const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetRoutes,0);
- switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
- case 4:
- Utils::snprintf(tmp,sizeof(tmp),"\"%d.%d.%d.%d/%d\"",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],sqlite3_column_int(_sGetRoutes,2));
- break;
- case 6:
- Utils::snprintf(tmp,sizeof(tmp),"\"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d\"",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],(int)ip[4],(int)ip[5],(int)ip[6],(int)ip[7],(int)ip[8],(int)ip[9],(int)ip[10],(int)ip[11],(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15],sqlite3_column_int(_sGetRoutes,2));
- break;
- }
- responseBody.append(tmp);
- if (sqlite3_column_type(_sGetRoutes,1) == SQLITE_NULL) {
- responseBody.append(",\"via\":null");
- } else {
- responseBody.append(",\"via\":");
- ip = (const unsigned char *)sqlite3_column_blob(_sGetRoutes,1);
- switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
- case 4:
- Utils::snprintf(tmp,sizeof(tmp),"\"%d.%d.%d.%d\"",(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15]);
- break;
- case 6:
- Utils::snprintf(tmp,sizeof(tmp),"\"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\"",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],(int)ip[4],(int)ip[5],(int)ip[6],(int)ip[7],(int)ip[8],(int)ip[9],(int)ip[10],(int)ip[11],(int)ip[12],(int)ip[13],(int)ip[14],(int)ip[15]);
- break;
- }
- responseBody.append(tmp);
- }
- responseBody.append(",\"flags\":");
- responseBody.append((const char *)sqlite3_column_text(_sGetRoutes,4));
- responseBody.append(",\"metric\":");
- responseBody.append((const char *)sqlite3_column_text(_sGetRoutes,5));
- responseBody.push_back('}');
- }
-
- responseBody.append("],\n\t\"ipAssignmentPools\": [");
-
- sqlite3_reset(_sGetIpAssignmentPools2);
- sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC);
- bool firstIpAssignmentPool = true;
- while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) {
- const char *ipRangeStartB = reinterpret_cast<const char *>(sqlite3_column_blob(_sGetIpAssignmentPools2,0));
- const char *ipRangeEndB = reinterpret_cast<const char *>(sqlite3_column_blob(_sGetIpAssignmentPools2,1));
- if ((ipRangeStartB)&&(ipRangeEndB)) {
- InetAddress ipps,ippe;
- int ipVersion = sqlite3_column_int(_sGetIpAssignmentPools2,2);
- if (ipVersion == 4) {
- ipps.set((const void *)(ipRangeStartB + 12),4,0);
- ippe.set((const void *)(ipRangeEndB + 12),4,0);
- } else if (ipVersion == 6) {
- ipps.set((const void *)ipRangeStartB,16,0);
- ippe.set((const void *)ipRangeEndB,16,0);
- }
- if (ipps) {
- responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t");
- firstIpAssignmentPool = false;
- Utils::snprintf(json,sizeof(json),"{\"ipRangeStart\":\"%s\",\"ipRangeEnd\":\"%s\"}",
- _jsonEscape(ipps.toIpString()).c_str(),
- _jsonEscape(ippe.toIpString()).c_str());
- responseBody.append(json);
- }
- }
- }
-
- responseBody.append("],\n\t\"rules\": [");
-
- sqlite3_reset(_sListRules);
- sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC);
- bool firstRule = true;
- while (sqlite3_step(_sListRules) == SQLITE_ROW) {
- responseBody.append(firstRule ? "\n\t{\n" : ",{\n");
- firstRule = false;
- Utils::snprintf(json,sizeof(json),"\t\t\"ruleNo\": %lld,\n",sqlite3_column_int64(_sListRules,0));
- responseBody.append(json);
- if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"sourcePort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,2));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"destPort\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,3));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"vlanId\": %d,\n",sqlite3_column_int(_sListRules,4));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"vlanPcp\": %d,\n",sqlite3_column_int(_sListRules,5));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"etherType\": %d,\n",sqlite3_column_int(_sListRules,6));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"macSource\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,7)).toString().c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"macDest\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,8)).toString().c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipSource\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,9)).c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipDest\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,10)).c_str());
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipTos\": %d,\n",sqlite3_column_int(_sListRules,11));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipProtocol\": %d,\n",sqlite3_column_int(_sListRules,12));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,13) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipSourcePort\": %d,\n",sqlite3_column_int(_sListRules,13));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,14) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"ipDestPort\": %d,\n",sqlite3_column_int(_sListRules,14));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,15) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"flags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,15));
- responseBody.append(json);
- }
- if (sqlite3_column_type(_sListRules,16) != SQLITE_NULL) {
- Utils::snprintf(json,sizeof(json),"\t\t\"invFlags\": %lu,\n",(unsigned long)sqlite3_column_int64(_sListRules,16));
- responseBody.append(json);
- }
- responseBody.append("\t\t\"action\": \"");
- responseBody.append(_jsonEscape( (sqlite3_column_type(_sListRules,17) == SQLITE_NULL) ? "drop" : (const char *)sqlite3_column_text(_sListRules,17) ));
- responseBody.append("\"\n\t}");
- }
-
- responseBody.append("]\n}\n");
- responseContentType = "application/json";
- return 200;
- } // else 404
- }
- } else if (path.size() == 1) {
- // list networks
- sqlite3_reset(_sListNetworks);
- responseContentType = "application/json";
- responseBody = "[";
- bool first = true;
- while (sqlite3_step(_sListNetworks) == SQLITE_ROW) {
- if (first) {
- first = false;
- responseBody.push_back('"');
- } else responseBody.append(",\"");
- responseBody.append((const char *)sqlite3_column_text(_sListNetworks,0));
- responseBody.push_back('"');
- }
- responseBody.push_back(']');
- return 200;
- } // else 404
-
- } else {
- // GET /controller returns status and API version if controller is supported
- Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"instanceId\": \"%s\"\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),_instanceId.c_str());
- responseBody = json;
- responseContentType = "application/json";
- return 200;
- }
-
- return 404;
-}
-
-void SqliteNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report)
-{
- char tmp[65535];
- SqliteNetworkController *const self = reinterpret_cast<SqliteNetworkController *>(test->ptr);
-
- if (!test)
- return;
- if (!report)
- return;
-
- Mutex::Lock _l(self->_lock);
- 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;
- }
-
- 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\"remoteTimestamp\": %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,
- (unsigned long long)test->testId,
- (unsigned long long)report->upstream,
- (unsigned long long)report->current,
- (unsigned long long)OSUtils::now(),
- (unsigned long long)report->remoteTimestamp,
- (unsigned long long)report->sourcePacketId,
- (unsigned long long)report->flags,
- report->sourcePacketHopCount,
- report->errorCode,
- (int)report->vendor,
- report->protocolVersion,
- report->majorVersion,
- report->minorVersion,
- report->revision,
- (int)report->platform,
- (int)report->architecture,
- reinterpret_cast<const InetAddress *>(&(report->receivedOnLocalAddress))->toString().c_str(),
- reinterpret_cast<const InetAddress *>(&(report->receivedFromRemoteAddress))->toString().c_str());
-
- cte->second.jsonResults.append(tmp);
-}
-
-} // namespace ZeroTier
diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp
deleted file mode 100644
index 145788c7..00000000
--- a/controller/SqliteNetworkController.hpp
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#ifndef ZT_SQLITENETWORKCONTROLLER_HPP
-#define ZT_SQLITENETWORKCONTROLLER_HPP
-
-#include <stdint.h>
-
-#include <sqlite3.h>
-
-#include <string>
-#include <map>
-#include <vector>
-
-#include "../node/Constants.hpp"
-#include "../node/NetworkController.hpp"
-#include "../node/Mutex.hpp"
-#include "../osdep/Thread.hpp"
-
-// Number of in-memory last log entries to maintain per user
-#define ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE 32
-
-// How long do circuit tests last before they're forgotten?
-#define ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT 60000
-
-namespace ZeroTier {
-
-class Node;
-
-class SqliteNetworkController : public NetworkController
-{
-public:
- SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath);
- virtual ~SqliteNetworkController();
-
- virtual NetworkController::ResultCode doNetworkConfigRequest(
- const InetAddress &fromAddr,
- const Identity &signingId,
- const Identity &identity,
- uint64_t nwid,
- const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &metaData,
- NetworkConfig &nc);
-
- unsigned int handleControlPlaneHttpGET(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType);
- unsigned int handleControlPlaneHttpPOST(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType);
- unsigned int handleControlPlaneHttpDELETE(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType);
-
- // threadMain() for backup thread -- do not call directly
- void threadMain()
- throw();
-
-private:
- /* deprecated
- enum IpAssignmentType {
- // IP assignment is a static IP address
- ZT_IP_ASSIGNMENT_TYPE_ADDRESS = 0,
- // IP assignment is a network -- a route via this interface, not an address
- ZT_IP_ASSIGNMENT_TYPE_NETWORK = 1
- };
- */
-
- unsigned int _doCPGet(
- const std::vector<std::string> &path,
- const std::map<std::string,std::string> &urlArgs,
- const std::map<std::string,std::string> &headers,
- const std::string &body,
- std::string &responseBody,
- std::string &responseContentType);
-
- static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report);
-
- Node *_node;
- Thread _backupThread;
- volatile bool _backupThreadRun;
- volatile bool _backupNeeded;
- std::string _dbPath;
- std::string _circuitTestPath;
- std::string _instanceId;
-
- // Circuit tests outstanding
- struct _CircuitTestEntry
- {
- ZT_CircuitTest *test;
- std::string jsonResults;
- };
- std::map< uint64_t,_CircuitTestEntry > _circuitTests;
-
- // Last request time by address, for rate limitation
- std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime;
-
- sqlite3 *_db;
-
- sqlite3_stmt *_sGetNetworkById;
- sqlite3_stmt *_sGetMember;
- sqlite3_stmt *_sCreateMember;
- sqlite3_stmt *_sGetNodeIdentity;
- sqlite3_stmt *_sCreateOrReplaceNode;
- sqlite3_stmt *_sGetEtherTypesFromRuleTable;
- sqlite3_stmt *_sGetActiveBridges;
- sqlite3_stmt *_sGetIpAssignmentsForNode;
- sqlite3_stmt *_sGetIpAssignmentPools;
- sqlite3_stmt *_sCheckIfIpIsAllocated;
- sqlite3_stmt *_sAllocateIp;
- sqlite3_stmt *_sDeleteIpAllocations;
- sqlite3_stmt *_sGetRelays;
- sqlite3_stmt *_sListNetworks;
- sqlite3_stmt *_sListNetworkMembers;
- sqlite3_stmt *_sGetMember2;
- sqlite3_stmt *_sGetIpAssignmentPools2;
- sqlite3_stmt *_sListRules;
- sqlite3_stmt *_sCreateRule;
- sqlite3_stmt *_sCreateNetwork;
- sqlite3_stmt *_sGetNetworkRevision;
- sqlite3_stmt *_sSetNetworkRevision;
- sqlite3_stmt *_sDeleteRelaysForNetwork;
- sqlite3_stmt *_sCreateRelay;
- sqlite3_stmt *_sDeleteIpAssignmentPoolsForNetwork;
- sqlite3_stmt *_sDeleteRulesForNetwork;
- sqlite3_stmt *_sCreateIpAssignmentPool;
- sqlite3_stmt *_sUpdateMemberAuthorized;
- sqlite3_stmt *_sUpdateMemberActiveBridge;
- sqlite3_stmt *_sUpdateMemberHistory;
- sqlite3_stmt *_sDeleteMember;
- sqlite3_stmt *_sDeleteAllNetworkMembers;
- sqlite3_stmt *_sGetActiveNodesOnNetwork;
- sqlite3_stmt *_sDeleteNetwork;
- sqlite3_stmt *_sCreateRoute;
- sqlite3_stmt *_sGetRoutes;
- sqlite3_stmt *_sDeleteRoutes;
- sqlite3_stmt *_sIncrementMemberRevisionCounter;
- sqlite3_stmt *_sGetConfig;
- sqlite3_stmt *_sSetConfig;
-
- Mutex _lock;
-};
-
-} // namespace ZeroTier
-
-#endif
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"
+ }
+}
diff --git a/controller/schema.sql b/controller/schema.sql
deleted file mode 100644
index 105db924..00000000
--- a/controller/schema.sql
+++ /dev/null
@@ -1,119 +0,0 @@
-CREATE TABLE Config (
- k varchar(16) PRIMARY KEY NOT NULL,
- v varchar(1024) NOT NULL
-);
-
-CREATE TABLE Network (
- id char(16) PRIMARY KEY NOT NULL,
- name varchar(128) NOT NULL,
- private integer NOT NULL DEFAULT(1),
- enableBroadcast integer NOT NULL DEFAULT(1),
- allowPassiveBridging integer NOT NULL DEFAULT(0),
- multicastLimit integer NOT NULL DEFAULT(32),
- creationTime integer NOT NULL DEFAULT(0),
- revision integer NOT NULL DEFAULT(1),
- memberRevisionCounter integer NOT NULL DEFAULT(1),
- flags integer NOT NULL DEFAULT(0)
-);
-
-CREATE TABLE AuthToken (
- id integer PRIMARY KEY NOT NULL,
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- authMode integer NOT NULL DEFAULT(1),
- useCount integer NOT NULL DEFAULT(0),
- maxUses integer NOT NULL DEFAULT(0),
- expiresAt integer NOT NULL DEFAULT(0),
- token varchar(256) NOT NULL
-);
-
-CREATE INDEX AuthToken_networkId_token ON AuthToken(networkId,token);
-
-CREATE TABLE Node (
- id char(10) PRIMARY KEY NOT NULL,
- identity varchar(4096) NOT NULL
-);
-
-CREATE TABLE IpAssignment (
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- nodeId char(10) REFERENCES Node(id) ON DELETE CASCADE,
- type integer NOT NULL DEFAULT(0),
- ip blob(16) NOT NULL,
- ipNetmaskBits integer NOT NULL DEFAULT(0),
- ipVersion integer NOT NULL DEFAULT(4)
-);
-
-CREATE UNIQUE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);
-
-CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);
-
-CREATE TABLE IpAssignmentPool (
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- ipRangeStart blob(16) NOT NULL,
- ipRangeEnd blob(16) NOT NULL,
- ipVersion integer NOT NULL DEFAULT(4)
-);
-
-CREATE UNIQUE INDEX IpAssignmentPool_networkId_ipRangeStart ON IpAssignmentPool (networkId,ipRangeStart);
-
-CREATE TABLE Member (
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
- authorized integer NOT NULL DEFAULT(0),
- activeBridge integer NOT NULL DEFAULT(0),
- memberRevision integer NOT NULL DEFAULT(0),
- flags integer NOT NULL DEFAULT(0),
- lastRequestTime integer NOT NULL DEFAULT(0),
- lastPowDifficulty integer NOT NULL DEFAULT(0),
- lastPowTime integer NOT NULL DEFAULT(0),
- recentHistory blob,
- PRIMARY KEY (networkId, nodeId)
-);
-
-CREATE INDEX Member_networkId_nodeId ON Member(networkId,nodeId);
-CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);
-CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision);
-CREATE INDEX Member_networkId_lastRequestTime ON Member(networkId, lastRequestTime);
-
-CREATE TABLE Route (
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- target blob(16) NOT NULL,
- via blob(16),
- targetNetmaskBits integer NOT NULL,
- ipVersion integer NOT NULL,
- flags integer NOT NULL,
- metric integer NOT NULL
-);
-
-CREATE INDEX Route_networkId ON Route (networkId);
-
-CREATE TABLE Relay (
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- address char(10) NOT NULL,
- phyAddress varchar(64) NOT NULL
-);
-
-CREATE UNIQUE INDEX Relay_networkId_address ON Relay (networkId,address);
-
-CREATE TABLE Rule (
- networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
- ruleNo integer NOT NULL,
- nodeId char(10) REFERENCES Node(id),
- sourcePort char(10),
- destPort char(10),
- vlanId integer,
- vlanPcp integer,
- etherType integer,
- macSource char(12),
- macDest char(12),
- ipSource varchar(64),
- ipDest varchar(64),
- ipTos integer,
- ipProtocol integer,
- ipSourcePort integer,
- ipDestPort integer,
- flags integer,
- invFlags integer,
- "action" varchar(4096) NOT NULL DEFAULT('accept')
-);
-
-CREATE UNIQUE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo);
diff --git a/controller/schema.sql.c b/controller/schema.sql.c
deleted file mode 100644
index dab34138..00000000
--- a/controller/schema.sql.c
+++ /dev/null
@@ -1,121 +0,0 @@
-#define ZT_NETCONF_SCHEMA_SQL \
-"CREATE TABLE Config (\n"\
-" k varchar(16) PRIMARY KEY NOT NULL,\n"\
-" v varchar(1024) NOT NULL\n"\
-");\n"\
-"\n"\
-"CREATE TABLE Network (\n"\
-" id char(16) PRIMARY KEY NOT NULL,\n"\
-" name varchar(128) NOT NULL,\n"\
-" private integer NOT NULL DEFAULT(1),\n"\
-" enableBroadcast integer NOT NULL DEFAULT(1),\n"\
-" allowPassiveBridging integer NOT NULL DEFAULT(0),\n"\
-" multicastLimit integer NOT NULL DEFAULT(32),\n"\
-" creationTime integer NOT NULL DEFAULT(0),\n"\
-" revision integer NOT NULL DEFAULT(1),\n"\
-" memberRevisionCounter integer NOT NULL DEFAULT(1),\n"\
-" flags integer NOT NULL DEFAULT(0)\n"\
-");\n"\
-"\n"\
-"CREATE TABLE AuthToken (\n"\
-" id integer PRIMARY KEY NOT NULL,\n"\
-" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
-" authMode integer NOT NULL DEFAULT(1),\n"\
-" useCount integer NOT NULL DEFAULT(0),\n"\
-" maxUses integer NOT NULL DEFAULT(0),\n"\
-" expiresAt integer NOT NULL DEFAULT(0),\n"\
-" token varchar(256) NOT NULL\n"\
-");\n"\
-"\n"\
-"CREATE INDEX AuthToken_networkId_token ON AuthToken(networkId,token);\n"\
-"\n"\
-"CREATE TABLE Node (\n"\
-" id char(10) PRIMARY KEY NOT NULL,\n"\
-" identity varchar(4096) NOT NULL\n"\
-");\n"\
-"\n"\
-"CREATE TABLE IpAssignment (\n"\
-" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
-" nodeId char(10) REFERENCES Node(id) ON DELETE CASCADE,\n"\
-" type integer NOT NULL DEFAULT(0),\n"\
-" ip blob(16) NOT NULL,\n"\
-" ipNetmaskBits integer NOT NULL DEFAULT(0),\n"\
-" ipVersion integer NOT NULL DEFAULT(4)\n"\
-");\n"\
-"\n"\
-"CREATE UNIQUE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);\n"\
-"\n"\
-"CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);\n"\
-"\n"\
-"CREATE TABLE IpAssignmentPool (\n"\
-" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
-" ipRangeStart blob(16) NOT NULL,\n"\
-" ipRangeEnd blob(16) NOT NULL,\n"\
-" ipVersion integer NOT NULL DEFAULT(4)\n"\
-");\n"\
-"\n"\
-"CREATE UNIQUE INDEX IpAssignmentPool_networkId_ipRangeStart ON IpAssignmentPool (networkId,ipRangeStart);\n"\
-"\n"\
-"CREATE TABLE Member (\n"\
-" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
-" nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,\n"\
-" authorized integer NOT NULL DEFAULT(0),\n"\
-" activeBridge integer NOT NULL DEFAULT(0),\n"\
-" memberRevision integer NOT NULL DEFAULT(0),\n"\
-" flags integer NOT NULL DEFAULT(0),\n"\
-" lastRequestTime integer NOT NULL DEFAULT(0),\n"\
-" lastPowDifficulty integer NOT NULL DEFAULT(0),\n"\
-" lastPowTime integer NOT NULL DEFAULT(0),\n"\
-" recentHistory blob,\n"\
-" PRIMARY KEY (networkId, nodeId)\n"\
-");\n"\
-"\n"\
-"CREATE INDEX Member_networkId_nodeId ON Member(networkId,nodeId);\n"\
-"CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge);\n"\
-"CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision);\n"\
-"CREATE INDEX Member_networkId_lastRequestTime ON Member(networkId, lastRequestTime);\n"\
-"\n"\
-"CREATE TABLE Route (\n"\
-" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
-" target blob(16) NOT NULL,\n"\
-" via blob(16),\n"\
-" targetNetmaskBits integer NOT NULL,\n"\
-" ipVersion integer NOT NULL,\n"\
-" flags integer NOT NULL,\n"\
-" metric integer NOT NULL\n"\
-");\n"\
-"\n"\
-"CREATE INDEX Route_networkId ON Route (networkId);\n"\
-"\n"\
-"CREATE TABLE Relay (\n"\
-" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
-" address char(10) NOT NULL,\n"\
-" phyAddress varchar(64) NOT NULL\n"\
-");\n"\
-"\n"\
-"CREATE UNIQUE INDEX Relay_networkId_address ON Relay (networkId,address);\n"\
-"\n"\
-"CREATE TABLE Rule (\n"\
-" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
-" ruleNo integer NOT NULL,\n"\
-" nodeId char(10) REFERENCES Node(id),\n"\
-" sourcePort char(10),\n"\
-" destPort char(10),\n"\
-" vlanId integer,\n"\
-" vlanPcp integer,\n"\
-" etherType integer,\n"\
-" macSource char(12),\n"\
-" macDest char(12),\n"\
-" ipSource varchar(64),\n"\
-" ipDest varchar(64),\n"\
-" ipTos integer,\n"\
-" ipProtocol integer,\n"\
-" ipSourcePort integer,\n"\
-" ipDestPort integer,\n"\
-" flags integer,\n"\
-" invFlags integer,\n"\
-" \"action\" varchar(4096) NOT NULL DEFAULT('accept')\n"\
-");\n"\
-"\n"\
-"CREATE UNIQUE INDEX Rule_networkId_ruleNo ON Rule (networkId, ruleNo);\n"\
-""
diff --git a/controller/schema2c.sh b/controller/schema2c.sh
deleted file mode 100755
index 4f4f1647..00000000
--- a/controller/schema2c.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-# Run this file to package the .sql file into a .c file whenever the SQL changes.
-
-rm -f schema.sql.c
-echo '#define ZT_NETCONF_SCHEMA_SQL \' >schema.sql.c
-cat schema.sql | sed 's/"/\\"/g' | sed 's/^/"/' | sed 's/$/\\n"\\/' >>schema.sql.c
-echo '""' >>schema.sql.c