summaryrefslogtreecommitdiff
path: root/netconf
diff options
context:
space:
mode:
Diffstat (limited to 'netconf')
-rw-r--r--netconf/README.md8
-rw-r--r--netconf/SqliteNetworkConfigMaster.cpp279
-rw-r--r--netconf/SqliteNetworkConfigMaster.hpp46
-rw-r--r--netconf/netconf-schema.sql81
-rw-r--r--netconf/netconf.dbbin23552 -> 0 bytes
-rw-r--r--netconf/redis-schema.md122
6 files changed, 130 insertions, 406 deletions
diff --git a/netconf/README.md b/netconf/README.md
index fa7c369a..ec2cb104 100644
--- a/netconf/README.md
+++ b/netconf/README.md
@@ -11,9 +11,13 @@ By default this code is not built or included in the client. To build on Linux,
### Running
-When you run a node with netconf support, a SQLite3 database will be created in the ZeroTier One working directory. On Linux this is /var/lib/zerotier-one by default unless you run the service with a command line to specify something else.
+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.
-This database can be attached to and modified while the service is running as per SQLite3's rather awesome sharing capabilities. For now you're on your own in that department too, but in the future we might ship some code for this.
+To initialize a database run:
+
+ sqlite3 -init netconf-schema.sql netconf.db
+
+Then type '.quit' to exit the SQLite3 command shell.
### Reliability
diff --git a/netconf/SqliteNetworkConfigMaster.cpp b/netconf/SqliteNetworkConfigMaster.cpp
index f6d310dd..d4b552f9 100644
--- a/netconf/SqliteNetworkConfigMaster.cpp
+++ b/netconf/SqliteNetworkConfigMaster.cpp
@@ -37,40 +37,37 @@
#include <utility>
#include <stdexcept>
-#include "RedisNetworkConfigMaster.hpp"
+#include "SqliteNetworkConfigMaster.hpp"
#include "../node/Utils.hpp"
#include "../node/CertificateOfMembership.hpp"
#include "../node/NetworkConfig.hpp"
namespace ZeroTier {
-RedisNetworkConfigMaster::RedisNetworkConfigMaster(
- const Identity &signingId,
- const char *redisHost,
- unsigned int redisPort,
- const char *redisPassword,
- unsigned int redisDatabaseNumber) :
- _lock(),
+SqliteNetworkConfigMaster::SqliteNetworkConfigMaster(const Identity &signingId,const char *dbPath) :
_signingId(signingId),
- _redisHost(redisHost),
- _redisPassword((redisPassword) ? redisPassword : ""),
- _redisPort(redisPort),
- _redisDatabaseNumber(redisDatabaseNumber),
- _rc((redisContext *)0)
+ _dbPath(dbPath),
+ _db((sqlite3 *)0)
+ _lock()
{
if (!_signingId.hasPrivate())
- throw std::runtime_error("RedisNetworkConfigMaster signing identity must have a private key");
+ throw std::runtime_error("SqliteNetworkConfigMaster signing identity must have a private key");
+
+ if (sqlite3_open_v2(dbPath,&_db,SQLITE_OPEN_READWRITE,(const char *)0) != SQLITE_OK)
+ throw std::runtime_error("SqliteNetworkConfigMaster cannot open database file");
+ sqlite3_busy_timeout(_db,10000);
}
-RedisNetworkConfigMaster::~RedisNetworkConfigMaster()
+SqliteNetworkConfigMaster::~SqliteNetworkConfigMaster()
{
Mutex::Lock _l(_lock);
- if (_rc)
- redisFree(_rc);
+ if (_db)
+ sqlite3_close(_db);
}
-NetworkConfigMaster::ResultCode RedisNetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uint64_t packetId,const Identity &member,uint64_t nwid,const Dictionary &metaData,uint64_t haveTimestamp,Dictionary &netconf)
+NetworkConfigMaster::ResultCode SqliteNetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uint64_t packetId,const Identity &member,uint64_t nwid,const Dictionary &metaData,uint64_t haveTimestamp,Dictionary &netconf)
{
+#if 0
char memberKey[128],nwids[24],addrs[16],nwKey[128],revKey[128];
Dictionary memberRecord;
std::string revision,tmps2;
@@ -87,7 +84,7 @@ NetworkConfigMaster::ResultCode RedisNetworkConfigMaster::doNetworkConfigRequest
// Check to make sure network itself exists and is valid
if (!_hget(nwKey,"id",tmps2)) {
- netconf["error"] = "Redis error retrieving network record ID field";
+ netconf["error"] = "Sqlite error retrieving network record ID field";
return NetworkConfigMaster::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
}
if (tmps2 != nwids)
@@ -95,7 +92,7 @@ NetworkConfigMaster::ResultCode RedisNetworkConfigMaster::doNetworkConfigRequest
// Get network revision
if (!_get(revKey,revision)) {
- netconf["error"] = "Redis error retrieving network revision";
+ netconf["error"] = "Sqlite error retrieving network revision";
return NetworkConfigMaster::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
}
if (!revision.length())
@@ -103,7 +100,7 @@ NetworkConfigMaster::ResultCode RedisNetworkConfigMaster::doNetworkConfigRequest
// Get network member record for this peer
if (!_hgetall(memberKey,memberRecord)) {
- netconf["error"] = "Redis error retrieving member record";
+ netconf["error"] = "Sqlite error retrieving member record";
return NetworkConfigMaster::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
}
@@ -147,227 +144,12 @@ NetworkConfigMaster::ResultCode RedisNetworkConfigMaster::doNetworkConfigRequest
} else {
return NetworkConfigMaster::NETCONF_QUERY_ACCESS_DENIED;
}
+#endif
}
-bool RedisNetworkConfigMaster::_reconnect()
-{
- struct timeval tv;
-
- if (_rc)
- redisFree(_rc);
-
- tv.tv_sec = ZT_NETCONF_REDIS_TIMEOUT;
- tv.tv_usec = 0;
- _rc = redisConnectWithTimeout(_redisHost.c_str(),_redisPort,tv);
- if (!_rc)
- return false;
- if (_rc->err) {
- redisFree(_rc);
- _rc = (redisContext *)0;
- return false;
- }
- redisSetTimeout(_rc,tv); // necessary???
-
- // TODO: support AUTH and SELECT !!!
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_hgetall(const char *key,Dictionary &hdata)
-{
- if (!_rc) {
- if (!_reconnect())
- return false;
- }
-
- redisReply *reply = (redisReply *)redisCommand(_rc,"HGETALL %s",key);
- if (!reply) {
- if (_reconnect())
- return _hgetall(key,hdata);
- return false;
- }
-
- hdata.clear();
- if (reply->type == REDIS_REPLY_ARRAY) {
- for(long i=0;i<reply->elements;) {
- try {
- const char *k = reply->element[i]->str;
- if (++i >= reply->elements)
- break;
- if ((k)&&(reply->element[i]->str))
- hdata[k] = reply->element[i]->str;
- ++i;
- } catch ( ... ) {
- break; // memory safety
- }
- }
- }
-
- freeReplyObject(reply);
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_hmset(const char *key,const Dictionary &hdata)
-{
- const char *hargv[1024];
-
- if (!hdata.size())
- return true;
-
- if (!_rc) {
- if (!_reconnect())
- return false;
- }
-
- hargv[0] = "HMSET";
- hargv[1] = key;
- int hargc = 2;
- for(Dictionary::const_iterator i(hdata.begin());i!=hdata.end();++i) {
- if (hargc >= 1024)
- break;
- hargv[hargc++] = i->first.c_str();
- hargv[hargc++] = i->second.c_str();
- }
-
- redisReply *reply = (redisReply *)redisCommandArgv(_rc,hargc,hargv,(const size_t *)0);
- if (!reply) {
- if (_reconnect())
- return _hmset(key,hdata);
- return false;
- }
-
- if (reply->type == REDIS_REPLY_ERROR) {
- freeReplyObject(reply);
- return false;
- }
-
- freeReplyObject(reply);
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_hget(const char *key,const char *hashKey,std::string &value)
-{
- if (!_rc) {
- if (!_reconnect())
- return false;
- }
-
- redisReply *reply = (redisReply *)redisCommand(_rc,"HGET %s %s",key,hashKey);
- if (!reply) {
- if (_reconnect())
- return _hget(key,hashKey,value);
- return false;
- }
-
- if (reply->type == REDIS_REPLY_STRING)
- value = reply->str;
- else value = "";
-
- freeReplyObject(reply);
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_hset(const char *key,const char *hashKey,const char *value)
-{
- if (!_rc) {
- if (!_reconnect())
- return false;
- }
-
- redisReply *reply = (redisReply *)redisCommand(_rc,"HSET %s %s %s",key,hashKey,value);
- if (!reply) {
- if (_reconnect())
- return _hset(key,hashKey,value);
- return false;
- }
-
- if (reply->type == REDIS_REPLY_ERROR) {
- freeReplyObject(reply);
- return false;
- }
-
- freeReplyObject(reply);
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_get(const char *key,std::string &value)
-{
- if (!_rc) {
- if (!_reconnect())
- return false;
- }
-
- redisReply *reply = (redisReply *)redisCommand(_rc,"GET %s",key);
- if (!reply) {
- if (_reconnect())
- return _get(key,value);
- return false;
- }
-
- if ((reply->type == REDIS_REPLY_STRING)&&(reply->str))
- value = reply->str;
- else value = "";
-
- freeReplyObject(reply);
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_sadd(const char *key,const char *value)
-{
- if (!_rc) {
- if (!_reconnect())
- return false;
- }
-
- redisReply *reply = (redisReply *)redisCommand(_rc,"SADD %s %s",key,value);
- if (!reply) {
- if (_reconnect())
- return _sadd(key,value);
- return false;
- }
-
- if (reply->type == REDIS_REPLY_ERROR) {
- freeReplyObject(reply);
- return false;
- }
-
- freeReplyObject(reply);
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_smembers(const char *key,std::vector<std::string> &sdata)
-{
- if (!_rc) {
- if (!_reconnect())
- return false;
- }
-
- redisReply *reply = (redisReply *)redisCommand(_rc,"SMEMBERS %s",key);
- if (!reply) {
- if (_reconnect())
- return _smembers(key,sdata);
- return false;
- }
-
- sdata.clear();
- if (reply->type == REDIS_REPLY_ARRAY) {
- for(long i=0;i<reply->elements;++i) {
- if (reply->element[i]->str)
- sdata.push_back(reply->element[i]->str);
- }
- }
-
- return true;
-}
-
-bool RedisNetworkConfigMaster::_initNewMember(uint64_t nwid,const Identity &member,const Dictionary &metaData,Dictionary &memberRecord)
+bool SqliteNetworkConfigMaster::_initNewMember(uint64_t nwid,const Identity &member,const Dictionary &metaData,Dictionary &memberRecord)
{
+#if 0
char memberKey[128],nwids[24],addrs[16],nwKey[128],membersKey[128];
Dictionary networkRecord;
@@ -378,7 +160,7 @@ bool RedisNetworkConfigMaster::_initNewMember(uint64_t nwid,const Identity &memb
Utils::snprintf(membersKey,sizeof(membersKey),"zt1:network:%s:members",nwids);
if (!_hgetall(nwKey,networkRecord)) {
- //LOG("netconf: Redis error retrieving %s",nwKey);
+ //LOG("netconf: Sqlite error retrieving %s",nwKey);
return false;
}
if (networkRecord.get("id","") != nwids) {
@@ -399,10 +181,12 @@ bool RedisNetworkConfigMaster::_initNewMember(uint64_t nwid,const Identity &memb
return false;
return true;
+#endif
}
-bool RedisNetworkConfigMaster::_generateNetconf(uint64_t nwid,const Identity &member,const Dictionary &metaData,Dictionary &netconf,uint64_t &ts,std::string &errorMessage)
+bool SqliteNetworkConfigMaster::_generateNetconf(uint64_t nwid,const Identity &member,const Dictionary &metaData,Dictionary &netconf,uint64_t &ts,std::string &errorMessage)
{
+#if 0
char memberKey[256],nwids[24],addrs[16],tss[24],nwKey[256],revKey[128],abKey[128],ipaKey[128];
Dictionary networkRecord,memberRecord;
std::string revision;
@@ -416,7 +200,7 @@ bool RedisNetworkConfigMaster::_generateNetconf(uint64_t nwid,const Identity &me
Utils::snprintf(ipaKey,sizeof(revKey),"zt1:network:%s:ipAssignments",nwids);
if (!_hgetall(nwKey,networkRecord)) {
- errorMessage = "Redis error retrieving network record";
+ errorMessage = "Sqlite error retrieving network record";
return false;
}
if (networkRecord.get("id","") != nwids) {
@@ -425,12 +209,12 @@ bool RedisNetworkConfigMaster::_generateNetconf(uint64_t nwid,const Identity &me
}
if (!_hgetall(memberKey,memberRecord)) {
- errorMessage = "Redis error retrieving member record";
+ errorMessage = "Sqlite error retrieving member record";
return false;
}
if (!_get(revKey,revision)) {
- errorMessage = "Redis error retrieving network revision";
+ errorMessage = "Sqlite error retrieving network revision";
return false;
}
if (!revision.length())
@@ -462,7 +246,7 @@ bool RedisNetworkConfigMaster::_generateNetconf(uint64_t nwid,const Identity &me
std::string activeBridgeList;
std::vector<std::string> activeBridgeSet;
if (!_smembers(abKey,activeBridgeSet)) {
- errorMessage = "Redis error retrieving active bridge set";
+ errorMessage = "Sqlite error retrieving active bridge set";
return false;
}
std::sort(activeBridgeSet.begin(),activeBridgeSet.end());
@@ -531,7 +315,7 @@ bool RedisNetworkConfigMaster::_generateNetconf(uint64_t nwid,const Identity &me
// Is 'ip' already assigned to another node?
std::string assignment;
if (!_hget(ipaKey,ip.toString().c_str(),assignment)) {
- errorMessage = "Redis error while checking IP allocation";
+ errorMessage = "Sqlite error while checking IP allocation";
return false;
}
if ((assignment.length() != 10)||(assignment == member.address().toString())) {
@@ -620,12 +404,13 @@ bool RedisNetworkConfigMaster::_generateNetconf(uint64_t nwid,const Identity &me
upd.set("netconfTimestamp",ts);
upd["netconfRevision"] = revision;
if (!_hmset(memberKey,upd)) {
- errorMessage = "Redis error updating network record with new netconf dictionary";
+ errorMessage = "Sqlite error updating network record with new netconf dictionary";
return false;
}
}
return true;
+#endif
}
} // namespace ZeroTier
diff --git a/netconf/SqliteNetworkConfigMaster.hpp b/netconf/SqliteNetworkConfigMaster.hpp
index 83615896..514e33dd 100644
--- a/netconf/SqliteNetworkConfigMaster.hpp
+++ b/netconf/SqliteNetworkConfigMaster.hpp
@@ -25,10 +25,13 @@
* LLC. Start here: http://www.zerotier.com/
*/
-#ifndef ZT_REDISNETWORKCONFIGMASTER_HPP
-#define ZT_REDISNETWORKCONFIGMASTER_HPP
+#ifndef ZT_SQLITENETWORKCONFIGMASTER_HPP
+#define ZT_SQLITENETWORKCONFIGMASTER_HPP
#include <stdint.h>
+
+#include <sqlite3.h>
+
#include <string>
#include <map>
#include <vector>
@@ -37,25 +40,13 @@
#include "../node/NetworkConfigMaster.hpp"
#include "../node/Mutex.hpp"
-#include <hiredis/hiredis.h>
-
-// Redis timeout in seconds
-#define ZT_NETCONF_REDIS_TIMEOUT 10
-
namespace ZeroTier {
-class RedisNetworkConfigMaster : public NetworkConfigMaster
+class SqliteNetworkConfigMaster : public NetworkConfigMaster
{
public:
- RedisNetworkConfigMaster(
- const Identity &signingId,
- const char *redisHost,
- unsigned int redisPort,
- const char *redisPassword,
- unsigned int redisDatabaseNumber);
-
- virtual ~RedisNetworkConfigMaster();
-
+ SqliteNetworkConfigMaster(const Identity &signingId,const char *dbPath);
+ virtual ~SqliteNetworkConfigMaster();
virtual NetworkConfigMaster::ResultCode doNetworkConfigRequest(
const InetAddress &fromAddr,
uint64_t packetId,
@@ -66,29 +57,14 @@ public:
Dictionary &netconf);
private:
- // These assume _lock is locked
- bool _reconnect();
- bool _hgetall(const char *key,Dictionary &hdata);
- bool _hmset(const char *key,const Dictionary &hdata);
- bool _hget(const char *key,const char *hashKey,std::string &value);
- bool _hset(const char *key,const char *hashKey,const char *value);
- bool _get(const char *key,std::string &value);
- bool _sadd(const char *key,const char *value);
- bool _smembers(const char *key,std::vector<std::string> &sdata);
-
bool _initNewMember(uint64_t nwid,const Identity &member,const Dictionary &metaData,Dictionary &memberRecord);
bool _generateNetconf(uint64_t nwid,const Identity &member,const Dictionary &metaData,Dictionary &netconf,uint64_t &ts,std::string &errorMessage);
- Mutex _lock;
-
Identity _signingId;
+ std::string _dbPath;
+ sqlite3 *_db;
- std::string _redisHost;
- std::string _redisPassword;
- unsigned int _redisPort;
- unsigned int _redisDatabaseNumber;
-
- redisContext *_rc;
+ Mutex _lock;
};
} // namespace ZeroTier
diff --git a/netconf/netconf-schema.sql b/netconf/netconf-schema.sql
new file mode 100644
index 00000000..601323ae
--- /dev/null
+++ b/netconf/netconf-schema.sql
@@ -0,0 +1,81 @@
+CREATE TABLE Config (
+ k varchar(16) PRIMARY KEY NOT NULL,
+ v varchar(1024) NOT NULL
+) WITHOUT ROWID;
+
+CREATE TABLE IpAssignment (
+ networkId char(16) NOT NULL,
+ nodeId char(10) NOT NULL,
+ ip varchar(64) NOT NULL,
+ ipNetmaskBits integer(4) NOT NULL DEFAULT(0)
+);
+
+CREATE INDEX IpAssignment_networkId ON IpAssignment (networkId);
+
+CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId);
+
+CREATE UNIQUE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip);
+
+CREATE TABLE IpAssignmentPool (
+ networkId char(16) NOT NULL,
+ ipNetwork varchar(64) NOT NULL,
+ ipNetmaskBits integer(4) NOT NULL,
+ active integer(1) 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(32),
+ cachedNetconfTimestamp integer(32),
+ clientReportedTimestamp integer(32),
+ authorized integer(1) NOT NULL DEFAULT(0),
+ activeBridge integer(1) NOT NULL DEFAULT(0)
+);
+
+CREATE UNIQUE INDEX Member_networkId_nodeId ON Member (networkId, nodeId);
+
+CREATE INDEX Member_networkId ON Member (networkId ASC);
+
+CREATE TABLE Network (
+ id char(16) PRIMARY KEY NOT NULL,
+ name varchar(128) NOT NULL,
+ private integer(1) NOT NULL DEFAULT(1),
+ enableBroadcast integer(1) NOT NULL DEFAULT(1),
+ allowPassiveBridging integer(1) NOT NULL DEFAULT(0),
+ v4AssignMode varchar(8) NOT NULL DEFAULT('none'),
+ v6AssignMode varchar(8) NOT NULL DEFAULT('none'),
+ multicastLimit integer(8) NOT NULL DEFAULT(32),
+ creationTime integer(32) NOT NULL DEFAULT(0),
+ revision integer(32) NOT NULL DEFAULT(0)
+) WITHOUT ROWID;
+
+CREATE TABLE Node (
+ id char(10) PRIMARY KEY NOT NULL,
+ identity varchar(4096) NOT NULL,
+ lastAt varchar(64),
+ lastSeen integer(32) NOT NULL DEFAULT(0),
+ firstSeen integer(32) NOT NULL DEFAULT(0)
+) WITHOUT ROWID;
+
+CREATE TABLE Rule (
+ networkId char(16) NOT NULL,
+ nodeId char(10),
+ vlanId integer(4),
+ vlanPcp integer(4),
+ etherType integer(8),
+ macSource char(12),
+ macDest char(12),
+ ipSource varchar(64),
+ ipDest varchar(64),
+ ipTos integer(4),
+ ipProtocol integer(4),
+ ipSourcePort integer(8),
+ ipDestPort integer(8),
+ "action" varchar(4096) NOT NULL DEFAULT('accept')
+);
+
+CREATE INDEX Rule_networkId ON Rule (networkId); \ No newline at end of file
diff --git a/netconf/netconf.db b/netconf/netconf.db
deleted file mode 100644
index d9a23f63..00000000
--- a/netconf/netconf.db
+++ /dev/null
Binary files differ
diff --git a/netconf/redis-schema.md b/netconf/redis-schema.md
deleted file mode 100644
index fc939a2a..00000000
--- a/netconf/redis-schema.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# ZeroTier One Redis Database Schema
-
-This is the Redis database schema used for ZeroTier One network configuration masters.
-
-### Notes
-
-- A top-level record may have a :~ child containing a hash. This is the root hash and contains any simple key=value properties of the record.
-- Booleans: true is 1, all other values are false (unless otherwise indicated)
-- With the exception of network IDs and ZeroTier addresses and unless otherwise indicated, all integers are in ASCII decimal
-- 16-digit network IDs and 10-digit ZeroTier addresses must be exactly 16 and 10 digits by being left-zero padded as they are elsewhere.
-- Timestamps are in milliseconds since the epoch
-- IPv4 addresees are stored in standard dot notation e.g. 1.2.3.4
-- IPv6 addresses must be stored *without* shortening, e.g. with :0000: instead of ::. It must be possible to strip :'s from the address and get 128 bits of straight hex.
-- All hexadecimal numbers should be lower case
-
-### Field attribute flags used in this documentation (not in database)
-
-- **!** required
-- **M** mutable via user-facing API
-- **R** read-only via user-facing API
-- **+** used by network configuration master and possibly API (for optional fields)
-- **-** not used by network configuration master, API or UI only (for optional fields)
-- **~** cache field used by network configuration master (for optional fields)
-
-### Schema Versioning
-
-The *zt1:schema* value contains an integer database schema version. If it is not present it is assumed to be equal to zero. Implementations should check this and auto-upgrade and/or refuse to use an old database version. This value should only be changed on significant, incompatible changes.
-
-Current database version is **2**.
-
-# Networks
-
-Network records are used by the network configuration master to issue configurations and certificates to virtual network members. These are the record types you should be interested in if you want to run your own netconf node.
-
-### [Hash] zt1:network:\<nwid\>:~
-
-- !R id :: must be \<nwid\>
-- !M name :: network's globally unique short name, which can contain only characters valid in an e-mail address. It's the job of the code that populates this DB to ensure that this is globally unique.
-- +M private :: network requires authentication -- unlike other booleans this defaults to true unless value is exactly '0'
-- +M etherTypes :: comma-delimited list of HEX integers indicating Ethernet types permitted on network
-- +M enableBroadcast :: if true, ff:ff:ff:ff:ff:ff is enabled network-wide
-- +M v4AssignMode :: 'none' (or null/empty/etc.), 'zt', 'dhcp'
-- +M v4AssignPool :: network/bits from which to assign IPs
-- +M v6AssignMode :: 'none' (or null/empty/etc.), 'zt', 'v6native', 'dhcp6'
-- +M v6AssignPool :: network/bits from which to assign IPs
-- +M allowPassiveBridging :: if true, allow passive bridging
-- +M multicastLimit :: maximum number of recipients to receive a multicast on this network
-- +M multicastRates :: dictionary containing multicast rate limit settings
-- +M desc :: a longer network description
-- -R creationTime :: timestamp of network creation
-- -R owner :: id of user who owns this network
-- -R billingUser :: user paying for premium subscriptions
-- -R billingUserConfirmed :: if true, billingUser has confirmed and authorized billing
-- -R infrastructure :: if true, network can't be deleted through API or web UI
-- -M subscriptions :: comma-delimited list of billing subscriptions for this network
-- -M ui :: arbitrary field that can be used by the UI to store stuff
-
-Multicast rates are encoded as a dictionary. Each key is a multicast group in "MAC/ADI" format (e.g. *ff:ff:ff:ff:ff:ff/0*), and each value is a comma-delimited tuple of hex integer values: preload, max balance, and rate of accrual in bytes per second. An entry for *0* (or *0/0* or *00:00:00:00:00:00/0*) indicates the default setting for all unspecified multicast groups. Setting a rate limit like *ffffffff,ffffffff,ffffffff* as default will effectively turn off rate limits.
-
-### [Decimal Integer] zt1:network:\<nwid\>:revision
-
-The revision number holds a decimal integer that can be incremented with the INCR Redis command. It should be changed whenever any network or network member setting changes that impacts the network configuration that is sent to users.
-
-For private networks, the revision is used as part of the network membership certificate. *Certificates agree if their revision numbers differ by no more than one.* This has important implications. Generally speaking, you should INCR the revision *once* for most changes but *twice* when you de-authorize a member. This double increment may be performed with a time delay to allow the surviving members time to grab up to date network configurations before de-authorized members fall off the horizon.
-
-### [Hash] zt1:network:\<nwid\>:ipAssignments
-
-This is a hash mapping IP/netmask bits fields to 10-digit ZeroTier addresses of network members. IPv4 fields contain dots, e.g. "10.2.3.4/24" or "29.1.1.1/7". IPv6 fields contain colons. Note that IPv6 IP abbreviations must *not* be used; use \:0000\: instead of \:\: for zero parts of addresses. This is to simplify parser code and canonicalize for rapid search. All hex digits must be lower-case.
-
-This is only used if the network's IPv4 and/or IPv6 auto-assign mode is 'zt' for ZeroTier assignment. The netconf-master will auto-populate by choosing unused IPs, and it can be edited via the API as well.
-
-### [Set] zt1:network:\<nwid\>:members
-
-This set contains all members of this network.
-
-### [Hash] zt1:network:\<nwid\>:member:\<address\>:~
-
-Each member of a network has a hash containing its configuration and authorization information.
-
-- !R id :: must be \<address\>
-- !R nwid :: must be \<nwid\>
-- +M authorized :: true if node is authorized and will be issued valid certificates and network configurations
-- +R identity :: full identity of member (public key, etc.)
-- +R firstSeen :: time node was first seen
-- +R lastSeen :: time node was most recently seen
-- +R lastAt :: real Internet IP/port where node was most recently seen
-- +R ipAssignments :: comma-delimited list of IP address assignments (see below)
-- ~R netconf :: most recent network configuration dictionary (caching)
-- ~R netconfRevision :: network revision when netconf was generated
-- ~R netconfTimestamp :: timestamp from netconf dictionary
-- ~R netconfClientTimestamp :: timestamp client most recently reported
-- -M name :: name of member (user-defined)
-- -M notes :: annotation field (user-defined)
-- -R authorizedBy :: user ID of user who authorized membership
-- -R authorizedAt :: timestamp of authorization
-- -M ui :: arbitrary field that can be used by the UI to store stuff
-
-The netconf field contains the most recent network configuration dictionary for this member. It is updated whenever network configuration or member authorization is changed. It is sent to clients if authorized is true and if netconf itself contains a valid string-serialized dictionary.
-
-The ipAssignments field is re-generated whenever the zt1:network:\<nwid\>:ipAssignments hash is modified for this member. Both the API code and the netconf-master code must keep this in sync.
-
-### [Set] zt1:network:\<nwid\>:activeBridges
-
-This set contains all members of this network designated as active bridges.
-
-# Users
-
-This record type holds user records, billing information, subscriptions, etc. It's just documented here so all our Redis docs are in the same place. Users outside of ZeroTier, Inc. itself do not need any of this as it's not used by the netconf master.
-
-### [Hash] zt1:user:\<auth\>:\<authUserId\>:~
-
-- !R id :: must be auth:authUserId -- this is the full key for referencing a user
-- !R auth :: authentication type e.g. 'google' or 'local'
-- !R authUserId :: user ID under auth schema, like an e-mail address or a Google profile ID.
-- M email :: user's email address
-- R confirmed :: is e-mail confirmed?
-- R lastLogin :: timestamp of last login
-- R creationTime: :: timestamp of account creation
-- M displayName :: usually First Last, defaults to e-mail address for 'local' auth and whatever the OpenID API says for third party auth such as Google.
-- M defaultCard :: ID of default credit card (actual card objects are stored by Stripe, not in this database)
-- M ui :: arbitrary field that can be used by the UI to store stuff
-- R stripeCustomerId :: customer ID for Stripe credit card service if the user has cards on file (we don't store cards, we let Stripe do that)