diff --git a/.gitignore b/.gitignore index a8da43b76..27b3e1353 100755 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ /testnet/local-testnet/*/root-topology /testnet/local-testnet/*/local.conf /testnet/local-testnet/*/networks.d +/netconf/netconf.db diff --git a/netconf/redis-schema.md b/attic/redis-schema.md similarity index 100% rename from netconf/redis-schema.md rename to attic/redis-schema.md diff --git a/make-freebsd.mk b/make-freebsd.mk index 69f094abb..6a034dc71 100644 --- a/make-freebsd.mk +++ b/make-freebsd.mk @@ -6,8 +6,8 @@ DEFS= LIBS= include objects.mk -OBJS+=osnet/BSDEthernetTapFactory.o osnet/BSDEthernetTap.o osnet/BSDRoutingTable.o -TESTNET_OBJS=testnet/SimNet.o testnet/SimNetSocketManager.o testnet/TestEthernetTap.o testnet/TestEthernetTapFactory.o testnet/TestRoutingTable.o +OBJS+=osnet/BSDEthernetTapFactory.o osnet/BSDEthernetTap.o osnet/BSDRoutingTable.o +TESTNET_OBJS=testnet/SimNet.o testnet/SimNetSocketManager.o testnet/TestEthernetTap.o testnet/TestEthernetTapFactory.o testnet/TestRoutingTable.o # Enable SSE-optimized Salsa20 on x86 and x86_64 machines MACHINE=$(shell uname -m) @@ -30,6 +30,13 @@ ifeq ($(MACHINE),x86) DEFS+=-DZT_SALSA20_SSE endif +# Build with ZT_ENABLE_NETCONF_MASTER=1 to build with NetworkConfigMaster enabled +ifeq ($(ZT_ENABLE_NETCONF_MASTER),1) + DEFS+=-DZT_ENABLE_NETCONF_MASTER + LIBS+=-lsqlite3 + OBJS+=netconf/SqliteNetworkConfigMaster.o +endif + # "make official" is a shortcut for this ifeq ($(ZT_OFFICIAL_RELEASE),1) ZT_AUTO_UPDATE=1 diff --git a/make-linux.mk b/make-linux.mk index d91284dbc..67a63a7bd 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -7,8 +7,8 @@ DEFS= LIBS= include objects.mk -OBJS+=osnet/LinuxRoutingTable.o osnet/LinuxEthernetTap.o osnet/LinuxEthernetTapFactory.o -TESTNET_OBJS=testnet/SimNet.o testnet/SimNetSocketManager.o testnet/TestEthernetTap.o testnet/TestEthernetTapFactory.o testnet/TestRoutingTable.o +OBJS+=osnet/LinuxRoutingTable.o osnet/LinuxEthernetTap.o osnet/LinuxEthernetTapFactory.o +TESTNET_OBJS=testnet/SimNet.o testnet/SimNetSocketManager.o testnet/TestEthernetTap.o testnet/TestEthernetTapFactory.o testnet/TestRoutingTable.o # Enable SSE-optimized Salsa20 on x86 and x86_64 machines MACHINE=$(shell uname -m) @@ -34,7 +34,8 @@ endif # Build with ZT_ENABLE_NETCONF_MASTER=1 to build with NetworkConfigMaster enabled ifeq ($(ZT_ENABLE_NETCONF_MASTER),1) DEFS+=-DZT_ENABLE_NETCONF_MASTER - LIBS+=-lhiredis + LIBS+=-lsqlite3 + OBJS+=netconf/SqliteNetworkConfigMaster.o endif # "make official" is a shortcut for this diff --git a/make-mac.mk b/make-mac.mk index fd2622de0..bdacae79c 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -29,8 +29,9 @@ endif # Build with ZT_ENABLE_NETCONF_MASTER=1 to build with NetworkConfigMaster enabled ifeq ($(ZT_ENABLE_NETCONF_MASTER),1) DEFS+=-DZT_ENABLE_NETCONF_MASTER - LIBS+=-L/usr/local/lib -lhiredis + LIBS+=-L/usr/local/lib -lsqlite3 ARCH_FLAGS=-arch x86_64 + OBJS+=netconf/SqliteNetworkConfigMaster.o endif # Enable SSE-optimized Salsa20 -- all Intel macs support SSE2 diff --git a/netconf/README.md b/netconf/README.md index fa7c369ac..ec2cb104a 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 f6d310dd4..d4b552f94 100644 --- a/netconf/SqliteNetworkConfigMaster.cpp +++ b/netconf/SqliteNetworkConfigMaster.cpp @@ -37,40 +37,37 @@ #include #include -#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;ielements;) { - 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 &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;ielements;++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 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 836158968..514e33dd0 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 + +#include + #include #include #include @@ -37,25 +40,13 @@ #include "../node/NetworkConfigMaster.hpp" #include "../node/Mutex.hpp" -#include - -// 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 &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 000000000..601323ae8 --- /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 d9a23f630..000000000 Binary files a/netconf/netconf.db and /dev/null differ