summaryrefslogtreecommitdiff
path: root/controller
diff options
context:
space:
mode:
authorAdam Ierymenko <adam.ierymenko@gmail.com>2015-04-15 15:12:09 -0700
committerAdam Ierymenko <adam.ierymenko@gmail.com>2015-04-15 15:12:09 -0700
commit6369c264e2b54f7eb65a9f0f071ef7599ec7b20a (patch)
treeb2570a21eb6d02040256d8a84855361da8c19e75 /controller
parent871473255b7b0c5ad6507f0fe62ca6998a41f678 (diff)
downloadinfinitytier-6369c264e2b54f7eb65a9f0f071ef7599ec7b20a.tar.gz
infinitytier-6369c264e2b54f7eb65a9f0f071ef7599ec7b20a.zip
Rename netconf to controller and NetworkConfigMaster to NetworkController for consistency.
Diffstat (limited to 'controller')
-rw-r--r--controller/README.md38
-rw-r--r--controller/SqliteNetworkController.cpp479
-rw-r--r--controller/SqliteNetworkController.hpp108
-rw-r--r--controller/schema.sql93
-rw-r--r--controller/schema.sql.c95
-rwxr-xr-xcontroller/schema2c.sh8
6 files changed, 821 insertions, 0 deletions
diff --git a/controller/README.md b/controller/README.md
new file mode 100644
index 00000000..6037424e
--- /dev/null
+++ b/controller/README.md
@@ -0,0 +1,38 @@
+Network Controller Implementation
+======
+
+This folder contains code implementing the node/NetworkController.hpp interface to allow ZeroTier nodes to create and manage virtual networks.
+
+The standard implementation uses SQLite3 with the attached schema. A separate service (not included here yet) is used to administrate that database and configure networks.
+
+### Building
+
+By default this code is not built or included in the client. To build on Linux, BSD, or Mac add ZT_ENABLE_NETCONF_MASTER=1 to the make command line. It could be built on Windows as well, but you're on your own there. You'd have to build SQLite3 first, or get a pre-built copy somewhere.
+
+### Running
+
+To enable netconf functionality, place a properly initialized SQLite3 database called **netconf.db** into the ZeroTier working directory of the node you wish to serve network configurations and restart it. If that file is present it will be opened and the network configuration master function will be enabled. You will see this in the log file.
+
+To initialize a database run:
+
+ sqlite3 -init netconf-schema.sql netconf.db
+
+Then type '.quit' to exit the SQLite3 command shell.
+
+Since SQLite3 supports multiple concurrent processes attached to the same database, it's easy to have another process administrate network details while the ZeroTier One service serves them. The schema is simple. Folks with some sysadmin expertise should be able to figure out how to populate a database and get something running. We'll probably publish some code for this at some point in the future, but for now it's all tied up with our zerotier.com web backend.
+
+One important detail you'll need to know:
+
+Whenever a network (including associated tables) is changed in any way, its revision number must be incremented. For private networks this is part of the certificate. Certificates are permitted to differ by up to 16 revisions. Therefore, to explicitly and rapidly de-authorize someone you should do a *two-step increment*. This is done with a time delay. First de-authorize the user and increment the revision by one. Then wait 30-60 seconds and increment it by 15. This gives all running clients a chance to get updated certificates before the now-excluded node falls off the revision number horizon. All other changes need only increment once, since a few nodes briefly having a slightly out of date config won't cause any harm.
+
+### Reliability
+
+Network configuration masters can go offline without affecting already-configured members of running networks. You just won't be able to add new members, de-authorize members, or otherwise change any network configuration while the master is offline.
+
+High-availability can be implemented through fail-over. A simple method involves making a frequent backup of the SQLite database (use the SQLite command line client to do this safely) and the network configuration master's working directory. Then, if the master goes down, another instance of it can rapidly be provisioned elsewhere. Since ZeroTier addresses are mobile, the new instance will quickly take over for the old one and service requests.
+
+### Limits
+
+A single network configuration master can administrate up to 2^24 networks as per the ZeroTier protocol limit. The number of clients is theoretically unlimited, but in practice is limited by network bandwidth.
+
+You should keep an eye on CPU utilization and stop adding networks/users to a network configuration master if it gets too high. The bottleneck here is not the SQLite database but the CPU overhead of signing certificates of membership. You'll hit limits there long before hitting any limit associated with SQLite.
diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp
new file mode 100644
index 00000000..c2e1a168
--- /dev/null
+++ b/controller/SqliteNetworkController.cpp
@@ -0,0 +1,479 @@
+/*
+ * 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 "SqliteNetworkController.hpp"
+#include "../node/Utils.hpp"
+#include "../node/CertificateOfMembership.hpp"
+#include "../node/NetworkConfig.hpp"
+
+// Include ZT_NETCONF_SCHEMA_SQL constant to init database
+#include "netconf-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 1
+#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "1"
+
+namespace ZeroTier {
+
+SqliteNetworkController::SqliteNetworkController(const Identity &signingId,const char *dbPath) :
+ _signingId(signingId),
+ _dbPath(dbPath),
+ _db((sqlite3 *)0)
+{
+ if (!_signingId.hasPrivate())
+ throw std::runtime_error("SqliteNetworkController signing identity must have a private key");
+
+ 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_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?)");
+ } else if (schemaVersion != ZT_NETCONF_SQLITE_SCHEMA_VERSION) {
+ // Note -- this will eventually run auto-upgrades so this isn't how it'll work going forward
+ 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) {
+ sqlite3_close(_db);
+ throw std::runtime_error("SqliteNetworkController cannot initialize database and/or insert schemaVersion into Config table");
+ }
+ }
+
+ if (
+ (sqlite3_prepare_v2(_db,"SELECT 'name','private','enableBroadcast','allowPassiveBridging','v4AssignMode','v6AssignMode','multicastLimit','revision' FROM Network WHERE 'id' = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT rowid,'cachedNetconf','cachedNetconfRevision','clientReportedRevision','authorized','activeBridge' FROM Member WHERE 'networkId' = ? AND 'nodeId' = ?",-1,&_sGetMemberByNetworkAndNodeId,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO Member ('networkId','nodeId','cachedNetconfRevision','clientReportedRevision','authorized','activeBridge') VALUES (?,?,0,0,?,0)",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT 'identity' FROM Node WHERE 'id' = ?",-1,&_sGetNodeIdentity,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO Node ('id','identity','lastAt','lastSeen','firstSeen') VALUES (?,?,?,?,?)",-1,&_sCreateNode,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Node SET 'lastAt' = ?,'lastSeen' = ? WHERE 'id' = ?",-1,&_sUpdateNode,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Node SET 'lastSeen' = ? WHERE 'id' = ?",-1,&_sUpdateNode2,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Member SET 'clientReportedRevision' = ? WHERE rowid = ?",-1,&_sUpdateMemberClientReportedRevision,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT 'etherType' FROM Rule WHERE 'networkId' = ? AND 'action' = 'accept'",-1,&_sGetEtherTypesFromRuleTable,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT 'mgMac','mgAdi','preload','maxBalance','accrual' FROM MulticastRate WHERE 'networkId' = ?",-1,&_sGetMulticastRates,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT 'nodeId' FROM Member WHERE 'networkId' = ? AND 'authorized' > 0 AND 'activeBridge' > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT DISTINCT 'ip','ipNetmaskBits' FROM IpAssignment WHERE 'networkId' = ? AND 'nodeId' = ? AND 'ipVersion' = ?",-1,&_sGetIpAssignmentsForNode,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT DISTINCT 'ipNetwork','ipNetmaskBits' FROM IpAssignmentPool WHERE 'networkId' = ? AND 'ipVersion' = ? AND 'active' > 0",-1,&_sGetIpAssignmentPools,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE 'networkId' = ? AND 'ip' = ? AND 'ipVersion' = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignment ('networkId','nodeId','ip','ipNetmaskBits','ipVersion') VALUES (?,?,?,?,?)",-1,&_sAllocateIp,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"UPDATE Member SET 'cachedNetconf' = ?,'cachedNetconfRevision' = ? WHERE rowid = ?",-1,&_sCacheNetconf,(const char **)0) != SQLITE_OK)
+ ) {
+ sqlite3_close(_db);
+ throw std::runtime_error("SqliteNetworkController unable to initialize one or more prepared statements");
+ }
+}
+
+SqliteNetworkController::~SqliteNetworkController()
+{
+ Mutex::Lock _l(_lock);
+ if (_db) {
+ sqlite3_finalize(_sGetNetworkById);
+ sqlite3_finalize(_sGetMemberByNetworkAndNodeId);
+ sqlite3_finalize(_sCreateMember);
+ sqlite3_finalize(_sGetNodeIdentity);
+ sqlite3_finalize(_sCreateNode);
+ sqlite3_finalize(_sUpdateNode);
+ sqlite3_finalize(_sUpdateNode2);
+ sqlite3_finalize(_sUpdateMemberClientReportedRevision);
+ sqlite3_finalize(_sGetEtherTypesFromRuleTable);
+ sqlite3_finalize(_sGetMulticastRates);
+ sqlite3_finalize(_sGetActiveBridges);
+ sqlite3_finalize(_sGetIpAssignmentsForNode);
+ sqlite3_finalize(_sGetIpAssignmentPools);
+ sqlite3_finalize(_sCheckIfIpIsAllocated);
+ sqlite3_finalize(_sAllocateIp);
+ sqlite3_finalize(_sCacheNetconf);
+ sqlite3_close(_db);
+ }
+}
+
+NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &identity,uint64_t nwid,const Dictionary &metaData,uint64_t haveRevision,Dictionary &netconf)
+{
+ Mutex::Lock _l(_lock);
+
+ // Note: we can't reuse prepared statements that return const char * pointers without
+ // making our own copy in e.g. a std::string first.
+
+ struct {
+ char id[24];
+ const char *name;
+ const char *v4AssignMode;
+ const char *v6AssignMode;
+ bool isPrivate;
+ bool enableBroadcast;
+ bool allowPassiveBridging;
+ int multicastLimit;
+ uint64_t revision;
+ } network;
+ memset(&network,0,sizeof(network));
+ Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid);
+
+ struct {
+ int64_t rowid;
+ char nodeId[16];
+ int cachedNetconfBytes;
+ const void *cachedNetconf;
+ uint64_t cachedNetconfRevision;
+ uint64_t clientReportedRevision;
+ bool authorized;
+ bool activeBridge;
+ } member;
+ memset(&member,0,sizeof(member));
+ Utils::snprintf(member.nodeId,sizeof(member.nodeId),"%.10llx",(unsigned long long)identity.address().toInt());
+
+ // Create/update Node record and check identity fully -- identities are first-come-first-claim
+
+ 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) {
+ char lastSeen[64];
+ Utils::snprintf(lastSeen,sizeof(lastSeen),"%llu",(unsigned long long)Utils::now());
+ if (fromAddr) {
+ std::string lastAt(fromAddr.toString());
+ sqlite3_reset(_sUpdateNode);
+ sqlite3_bind_text(_sUpdateNode,1,lastAt.c_str(),-1,SQLITE_STATIC);
+ sqlite3_bind_text(_sUpdateNode,2,lastSeen,-1,SQLITE_STATIC);
+ sqlite3_bind_text(_sUpdateNode,3,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_step(_sUpdateNode);
+ } else { // fromAddr is empty, which means this was a relayed packet -- so don't update lastAt
+ sqlite3_reset(_sUpdateNode2);
+ sqlite3_bind_text(_sUpdateNode2,1,lastSeen,-1,SQLITE_STATIC);
+ sqlite3_bind_text(_sUpdateNode2,2,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_step(_sUpdateNode2);
+ }
+ } else {
+ 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));
+ std::string lastAt;
+ if (fromAddr)
+ lastAt = fromAddr.toString();
+ char lastSeen[64];
+ Utils::snprintf(lastSeen,sizeof(lastSeen),"%llu",(unsigned long long)Utils::now());
+ sqlite3_reset(_sCreateNode);
+ sqlite3_bind_text(_sCreateNode,1,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateNode,2,idstr.c_str(),-1,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateNode,3,lastAt.c_str(),-1,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateNode,4,lastSeen,-1,SQLITE_STATIC);
+ sqlite3_bind_text(_sCreateNode,5,lastSeen,-1,SQLITE_STATIC);
+ if (sqlite3_step(_sCreateNode) != SQLITE_DONE) {
+ netconf["error"] = "unable to create new node record";
+ return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ // Fetch Network record
+
+ bool foundNetwork = false;
+ sqlite3_reset(_sGetNetworkById);
+ sqlite3_bind_text(_sGetNetworkById,1,network.id,16,SQLITE_STATIC);
+ if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
+ foundNetwork = true;
+ 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.v4AssignMode = (const char *)sqlite3_column_text(_sGetNetworkById,4);
+ network.v6AssignMode = (const char *)sqlite3_column_text(_sGetNetworkById,5);
+ network.multicastLimit = sqlite3_column_int(_sGetNetworkById,6);
+ network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
+ }
+ if (!foundNetwork)
+ return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
+
+ // Fetch Member record
+
+ bool foundMember = false;
+ sqlite3_reset(_sGetMemberByNetworkAndNodeId);
+ sqlite3_bind_text(_sGetMemberByNetworkAndNodeId,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetMemberByNetworkAndNodeId,2,member.nodeId,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetMemberByNetworkAndNodeId) == SQLITE_ROW) {
+ foundMember = true;
+ member.rowid = (int64_t)sqlite3_column_int64(_sGetMemberByNetworkAndNodeId,0);
+ member.cachedNetconfBytes = sqlite3_column_bytes(_sGetMemberByNetworkAndNodeId,1);
+ member.cachedNetconf = sqlite3_column_blob(_sGetMemberByNetworkAndNodeId,1);
+ member.cachedNetconfRevision = (uint64_t)sqlite3_column_int64(_sGetMemberByNetworkAndNodeId,2);
+ member.clientReportedRevision = (uint64_t)sqlite3_column_int64(_sGetMemberByNetworkAndNodeId,3);
+ member.authorized = (sqlite3_column_int(_sGetMemberByNetworkAndNodeId,4) > 0);
+ member.activeBridge = (sqlite3_column_int(_sGetMemberByNetworkAndNodeId,5) > 0);
+ }
+
+ // Create Member record for unknown nodes, auto-authorizing if network is public
+
+ if (!foundMember) {
+ member.cachedNetconfBytes = 0;
+ member.cachedNetconfRevision = 0;
+ member.clientReportedRevision = 0;
+ 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 ? 0 : 1));
+ if ( (sqlite3_step(_sCreateMember) != SQLITE_DONE) && ((member.rowid = (int64_t)sqlite3_last_insert_rowid(_db)) > 0) ) {
+ netconf["error"] = "unable to create new member record";
+ return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ // Check member authorization
+
+ if (!member.authorized)
+ return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
+
+ // Update client's currently reported haveRevision in Member record
+
+ if (member.rowid > 0) {
+ sqlite3_reset(_sUpdateMemberClientReportedRevision);
+ sqlite3_bind_int64(_sUpdateMemberClientReportedRevision,1,(sqlite3_int64)haveRevision);
+ sqlite3_bind_int64(_sUpdateMemberClientReportedRevision,2,member.rowid);
+ sqlite3_step(_sUpdateMemberClientReportedRevision);
+ }
+
+ // If netconf is unchanged from client reported revision, just tell client they're up to date
+
+ if ((haveRevision > 0)&&(haveRevision == network.revision))
+ return NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER;
+
+ // Generate or retrieve cached netconf
+
+ netconf.clear();
+ if ((member.cachedNetconfRevision == network.revision)&&(member.cachedNetconfBytes > 0)) {
+ // Use cached copy
+ std::string tmp((const char *)member.cachedNetconf,member.cachedNetconfBytes);
+ netconf.fromString(tmp);
+ } else {
+ // Create and sign a new netconf, and save in database to re-use in the future
+
+ char tss[24],rs[24];
+ Utils::snprintf(tss,sizeof(tss),"%.16llx",(unsigned long long)Utils::now());
+ Utils::snprintf(rs,sizeof(rs),"%.16llx",(unsigned long long)network.revision);
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP] = tss;
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_REVISION] = rs;
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID] = network.id;
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO] = member.nodeId;
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_PRIVATE] = network.isPrivate ? "1" : "0";
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_NAME] = (network.name) ? network.name : "";
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST] = network.enableBroadcast ? "1" : "0";
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING] = network.allowPassiveBridging ? "1" : "0";
+
+ {
+ std::vector<int> allowedEtherTypes;
+ sqlite3_reset(_sGetEtherTypesFromRuleTable);
+ sqlite3_bind_text(_sGetEtherTypesFromRuleTable,1,network.id,16,SQLITE_STATIC);
+ while (sqlite3_step(_sGetEtherTypesFromRuleTable) == SQLITE_ROW) {
+ int et = sqlite3_column_int(_sGetEtherTypesFromRuleTable,0);
+ if ((et >= 0)&&(et <= 0xffff))
+ allowedEtherTypes.push_back(et);
+ }
+ std::sort(allowedEtherTypes.begin(),allowedEtherTypes.end());
+ std::unique(allowedEtherTypes.begin(),allowedEtherTypes.end());
+ std::string allowedEtherTypesCsv;
+ for(std::vector<int>::const_iterator i(allowedEtherTypes.begin());i!=allowedEtherTypes.end();++i) {
+ if (allowedEtherTypesCsv.length())
+ allowedEtherTypesCsv.push_back(',');
+ char tmp[16];
+ Utils::snprintf(tmp,sizeof(tmp),"%.4x",(unsigned int)*i);
+ allowedEtherTypesCsv.append(tmp);
+ }
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES] = allowedEtherTypesCsv;
+ }
+
+ {
+ std::string multicastRates;
+ sqlite3_reset(_sGetMulticastRates);
+ sqlite3_bind_text(_sGetMulticastRates,1,network.id,16,SQLITE_STATIC);
+ while (sqlite3_step(_sGetMulticastRates) == SQLITE_ROW) {
+ const char *mac = (const char *)sqlite3_column_text(_sGetMulticastRates,0);
+ if ((mac)&&(strlen(mac) == 12)) {
+ unsigned long adi = ((unsigned long)sqlite3_column_int64(_sGetMulticastRates,1)) & 0xffffffff;
+ char tmp[256];
+ Utils::snprintf(tmp,sizeof(tmp),"%s/%.4lx=%x,%x,%x\n",mac,adi,sqlite3_column_int(_sGetMulticastRates,2),sqlite3_column_int(_sGetMulticastRates,3),sqlite3_column_int(_sGetMulticastRates,4));
+ multicastRates.append(tmp);
+ }
+ }
+ if (multicastRates.length() > 0)
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES] = multicastRates;
+ if (network.multicastLimit > 0) {
+ char ml[16];
+ Utils::snprintf(ml,sizeof(ml),"%lx",(unsigned long)network.multicastLimit);
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT] = ml;
+ }
+ }
+
+ {
+ std::string activeBridges;
+ 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)) {
+ if (activeBridges.length())
+ activeBridges.push_back(',');
+ activeBridges.append(ab);
+ }
+ if (activeBridges.length() > 1024) // sanity check -- you can't have too many active bridges at the moment
+ break;
+ }
+ if (activeBridges.length())
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES] = activeBridges;
+ }
+
+ if ((network.v4AssignMode)&&(!strcmp(network.v4AssignMode,"zt"))) {
+ std::string v4s;
+
+ sqlite3_reset(_sGetIpAssignmentsForNode);
+ sqlite3_bind_text(_sGetIpAssignmentsForNode,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetIpAssignmentsForNode,2,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_int(_sGetIpAssignmentsForNode,3,4); // 4 == IPv4
+ while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) {
+ const unsigned char *ip = (const unsigned char *)sqlite3_column_blob(_sGetIpAssignmentsForNode,0);
+ int ipNetmaskBits = sqlite3_column_int(_sGetIpAssignmentsForNode,1);
+ if ((ip)&&(sqlite3_column_bytes(_sGetIpAssignmentsForNode,0) >= 4)&&(ipNetmaskBits > 0)&&(ipNetmaskBits <= 32)) {
+ char tmp[32];
+ Utils::snprintf(tmp,sizeof(tmp),"%d.%d.%d.%d/%d",(int)ip[0],(int)ip[1],(int)ip[2],(int)ip[3],ipNetmaskBits);
+ if (v4s.length())
+ v4s.push_back(',');
+ v4s.append(tmp);
+ }
+ }
+
+ if (!v4s.length()) {
+ // Attempt to auto-assign an IPv4 address from an available pool if one isn't assigned already
+ sqlite3_reset(_sGetIpAssignmentPools);
+ sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_int(_sGetIpAssignmentPools,2,4); // 4 == IPv4
+ while ((!v4s.length())&&(sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW)) {
+ const void *ipNetwork = sqlite3_column_blob(_sGetIpAssignmentPools,0);
+ int ipNetmaskBits = sqlite3_column_int(_sGetIpAssignmentPools,1);
+ if ((ipNetwork)&&(sqlite3_column_bytes(_sGetIpAssignmentPools,0) >= 4)&&(ipNetmaskBits > 0)&&(ipNetmaskBits < 32)) {
+ uint32_t n = Utils::ntoh(*((const uint32_t *)ipNetwork)); // network in host byte order e.g. 192.168.0.0
+ uint32_t m = 0xffffffff << (32 - ipNetmaskBits); // netmask e.g. 0xffffff00 for '24' since 32 - 24 == 8
+ uint32_t im = ~m; // inverse mask, e.g. 0x000000ff for a netmask of 0xffffff00
+ uint32_t abits = (uint32_t)(identity.address().toInt() & 0xffffffff); // least significant bits of member ZT address
+
+ for(uint32_t k=0;k<=im;++k) { // try up to the number of IPs possible in this network
+ uint32_t ip = ( ((abits + k) & im) | (n & m) ); // build IP using bits from ZT address of member + k
+ if ((ip & 0x000000ff) == 0x00) continue; // no IPs ending in .0 allowed
+ if ((ip & 0x000000ff) == 0xff) continue; // no IPs ending in .255 allowed
+
+ uint32_t nip = Utils::hton(ip); // IP in big-endian "network" byte order
+ sqlite3_reset(_sCheckIfIpIsAllocated);
+ sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)&nip,4,SQLITE_STATIC);
+ sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4
+ 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_blob(_sAllocateIp,3,(const void *)&nip,4,SQLITE_STATIC);
+ sqlite3_bind_int(_sAllocateIp,4,ipNetmaskBits);
+ sqlite3_bind_int(_sAllocateIp,5,4); // 4 == IPv4
+ if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) {
+ char tmp[32];
+ Utils::snprintf(tmp,sizeof(tmp),"%d.%d.%d.%d/%d",(int)((ip >> 24) & 0xff),(int)((ip >> 16) & 0xff),(int)((ip >> 8) & 0xff),(int)(ip & 0xff),ipNetmaskBits);
+ if (v4s.length())
+ v4s.push_back(',');
+ v4s.append(tmp);
+ break; // IP found and reserved! v4s containing something will cause outer while() to break.
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (v4s.length())
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = v4s;
+ }
+
+ // TODO: IPv6 auto-assign once it's supported in UI
+
+ if (network.isPrivate) {
+ CertificateOfMembership com(network.revision,16,nwid,identity.address());
+ if (com.sign(_signingId)) // basically can't fail unless our identity is invalid
+ netconf[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = com.toString();
+ else {
+ netconf["error"] = "unable to sign COM";
+ return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ if (!netconf.sign(_signingId)) {
+ netconf["error"] = "unable to sign netconf dictionary";
+ return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+ }
+
+ // Save serialized netconf for future re-use
+ std::string netconfSerialized(netconf.toString());
+ if (netconfSerialized.length() < 4096) { // sanity check
+ sqlite3_reset(_sCacheNetconf);
+ sqlite3_bind_blob(_sCacheNetconf,1,(const void *)netconfSerialized.data(),netconfSerialized.length(),SQLITE_STATIC);
+ sqlite3_bind_int64(_sCacheNetconf,2,(sqlite3_int64)network.revision);
+ sqlite3_bind_int64(_sCacheNetconf,3,member.rowid);
+ sqlite3_step(_sCacheNetconf);
+ }
+ }
+
+ return NetworkController::NETCONF_QUERY_OK;
+}
+
+} // namespace ZeroTier
diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp
new file mode 100644
index 00000000..566e97d1
--- /dev/null
+++ b/controller/SqliteNetworkController.hpp
@@ -0,0 +1,108 @@
+/*
+ * 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 "../node/NonCopyable.hpp"
+
+namespace ZeroTier {
+
+class SqliteNetworkController : public NetworkController
+{
+public:
+ class DBC;
+ friend class SqliteNetworkController::DBC;
+
+ SqliteNetworkController(const Identity &signingId,const char *dbPath);
+ virtual ~SqliteNetworkController();
+
+ virtual NetworkController::ResultCode doNetworkConfigRequest(
+ const InetAddress &fromAddr,
+ const Identity &identity,
+ uint64_t nwid,
+ const Dictionary &metaData,
+ uint64_t haveRevision,
+ Dictionary &netconf);
+
+private:
+ Identity _signingId;
+ std::string _dbPath;
+ sqlite3 *_db;
+
+ sqlite3_stmt *_sGetNetworkById;
+ sqlite3_stmt *_sGetMemberByNetworkAndNodeId;
+ sqlite3_stmt *_sCreateMember;
+ sqlite3_stmt *_sGetNodeIdentity;
+ sqlite3_stmt *_sCreateNode;
+ sqlite3_stmt *_sUpdateNode;
+ sqlite3_stmt *_sUpdateNode2;
+ sqlite3_stmt *_sUpdateMemberClientReportedRevision;
+ sqlite3_stmt *_sGetEtherTypesFromRuleTable;
+ sqlite3_stmt *_sGetMulticastRates;
+ sqlite3_stmt *_sGetActiveBridges;
+ sqlite3_stmt *_sGetIpAssignmentsForNode;
+ sqlite3_stmt *_sGetIpAssignmentPools;
+ sqlite3_stmt *_sCheckIfIpIsAllocated;
+ sqlite3_stmt *_sAllocateIp;
+ sqlite3_stmt *_sCacheNetconf;
+
+ Mutex _lock;
+
+public:
+ /**
+ * Provides a safe interface for direct access to this master's database
+ *
+ * This acts as both a contextual lock of the master's Mutex and a pointer
+ * to the Sqlite3 database instance. Dereferencing this with * yields the
+ * sqlite3* pointer. Create on parent with DBC(SqliteNetworkController &).
+ */
+ class DBC : NonCopyable
+ {
+ public:
+ DBC(SqliteNetworkController &nc) : _p(&nc) { nc._lock.lock(); }
+ ~DBC() { _p->_lock.unlock(); }
+ inline sqlite3 *operator*() const throw() { return _p->_db; }
+ private:
+ SqliteNetworkController *const _p;
+ };
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/controller/schema.sql b/controller/schema.sql
new file mode 100644
index 00000000..f5981f1a
--- /dev/null
+++ b/controller/schema.sql
@@ -0,0 +1,93 @@
+CREATE TABLE Config (
+ k varchar(16) PRIMARY KEY NOT NULL,
+ v varchar(1024) NOT NULL
+);
+
+CREATE TABLE IpAssignment (
+ networkId char(16) NOT NULL,
+ nodeId char(10) NOT NULL,
+ ip blob(16) NOT NULL,
+ ipNetmaskBits integer NOT NULL DEFAULT(0),
+ ipVersion integer NOT NULL DEFAULT(4)
+);
+
+CREATE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);
+
+CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);
+
+CREATE INDEX IpAssignment_networkId ON IpAssignment (networkId);
+
+CREATE TABLE IpAssignmentPool (
+ networkId char(16) NOT NULL,
+ ipNetwork blob(16) NOT NULL,
+ ipNetmaskBits integer NOT NULL,
+ ipVersion integer NOT NULL DEFAULT(4),
+ active integer NOT NULL DEFAULT(1)
+);
+
+CREATE INDEX IpAssignmentPool_networkId ON IpAssignmentPool (networkId);
+
+CREATE TABLE Member (
+ networkId char(16) NOT NULL,
+ nodeId char(10) NOT NULL,
+ cachedNetconf blob(4096),
+ cachedNetconfRevision integer NOT NULL DEFAULT(0),
+ clientReportedRevision integer NOT NULL DEFAULT(0),
+ authorized integer NOT NULL DEFAULT(0),
+ activeBridge integer NOT NULL DEFAULT(0)
+);
+
+CREATE INDEX Member_networkId ON Member (networkId);
+
+CREATE UNIQUE INDEX Member_networkId_nodeId ON Member (networkId, nodeId);
+
+CREATE TABLE MulticastRate (
+ networkId char(16) NOT NULL,
+ mgMac char(12) NOT NULL,
+ mgAdi integer NOT NULL DEFAULT(0),
+ preload integer NOT NULL,
+ maxBalance integer NOT NULL,
+ accrual integer NOT NULL
+);
+
+CREATE INDEX MulticastRate_networkId ON MulticastRate (networkId);
+
+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),
+ v4AssignMode varchar(8) NOT NULL DEFAULT('none'),
+ v6AssignMode varchar(8) NOT NULL DEFAULT('none'),
+ multicastLimit integer NOT NULL DEFAULT(32),
+ creationTime integer NOT NULL DEFAULT(0),
+ revision integer NOT NULL DEFAULT(1)
+);
+
+CREATE TABLE Node (
+ id char(10) PRIMARY KEY NOT NULL,
+ identity varchar(4096) NOT NULL,
+ lastAt varchar(64),
+ lastSeen integer NOT NULL DEFAULT(0),
+ firstSeen integer NOT NULL DEFAULT(0)
+);
+
+CREATE TABLE Rule (
+ networkId char(16) NOT NULL,
+ nodeId 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,
+ "action" varchar(4096) NOT NULL DEFAULT('accept')
+);
+
+CREATE INDEX Rule_networkId ON Rule (networkId); \ No newline at end of file
diff --git a/controller/schema.sql.c b/controller/schema.sql.c
new file mode 100644
index 00000000..fdd51360
--- /dev/null
+++ b/controller/schema.sql.c
@@ -0,0 +1,95 @@
+#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 IpAssignment (\n"\
+" networkId char(16) NOT NULL,\n"\
+" nodeId char(10) NOT NULL,\n"\
+" ip blob(16) NOT NULL,\n"\
+" ipNetmaskBits integer NOT NULL DEFAULT(0),\n"\
+" ipVersion integer NOT NULL DEFAULT(4)\n"\
+");\n"\
+"\n"\
+"CREATE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);\n"\
+"\n"\
+"CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);\n"\
+"\n"\
+"CREATE INDEX IpAssignment_networkId ON IpAssignment (networkId);\n"\
+"\n"\
+"CREATE TABLE IpAssignmentPool (\n"\
+" networkId char(16) NOT NULL,\n"\
+" ipNetwork blob(16) NOT NULL,\n"\
+" ipNetmaskBits integer NOT NULL,\n"\
+" ipVersion integer NOT NULL DEFAULT(4),\n"\
+" active integer NOT NULL DEFAULT(1)\n"\
+");\n"\
+"\n"\
+"CREATE INDEX IpAssignmentPool_networkId ON IpAssignmentPool (networkId);\n"\
+"\n"\
+"CREATE TABLE Member (\n"\
+" networkId char(16) NOT NULL,\n"\
+" nodeId char(10) NOT NULL,\n"\
+" cachedNetconf blob(4096),\n"\
+" cachedNetconfRevision integer NOT NULL DEFAULT(0),\n"\
+" clientReportedRevision integer NOT NULL DEFAULT(0),\n"\
+" authorized integer NOT NULL DEFAULT(0),\n"\
+" activeBridge integer NOT NULL DEFAULT(0)\n"\
+");\n"\
+"\n"\
+"CREATE INDEX Member_networkId ON Member (networkId);\n"\
+"\n"\
+"CREATE UNIQUE INDEX Member_networkId_nodeId ON Member (networkId, nodeId);\n"\
+"\n"\
+"CREATE TABLE MulticastRate (\n"\
+" networkId char(16) NOT NULL,\n"\
+" mgMac char(12) NOT NULL,\n"\
+" mgAdi integer NOT NULL DEFAULT(0),\n"\
+" preload integer NOT NULL,\n"\
+" maxBalance integer NOT NULL,\n"\
+" accrual integer NOT NULL\n"\
+");\n"\
+"\n"\
+"CREATE INDEX MulticastRate_networkId ON MulticastRate (networkId);\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"\
+" v4AssignMode varchar(8) NOT NULL DEFAULT('none'),\n"\
+" v6AssignMode varchar(8) NOT NULL DEFAULT('none'),\n"\
+" multicastLimit integer NOT NULL DEFAULT(32),\n"\
+" creationTime integer NOT NULL DEFAULT(0),\n"\
+" revision integer NOT NULL DEFAULT(1)\n"\
+");\n"\
+"\n"\
+"CREATE TABLE Node (\n"\
+" id char(10) PRIMARY KEY NOT NULL,\n"\
+" identity varchar(4096) NOT NULL,\n"\
+" lastAt varchar(64),\n"\
+" lastSeen integer NOT NULL DEFAULT(0),\n"\
+" firstSeen integer NOT NULL DEFAULT(0)\n"\
+");\n"\
+"\n"\
+"CREATE TABLE Rule (\n"\
+" networkId char(16) NOT NULL,\n"\
+" nodeId 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"\
+" \"action\" varchar(4096) NOT NULL DEFAULT('accept')\n"\
+");\n"\
+"\n"\
+"CREATE INDEX Rule_networkId ON Rule (networkId);\n"\
+""
diff --git a/controller/schema2c.sh b/controller/schema2c.sh
new file mode 100755
index 00000000..4f4f1647
--- /dev/null
+++ b/controller/schema2c.sh
@@ -0,0 +1,8 @@
+#!/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