diff --git a/README.md b/README.md
index 123fbf019..1a93a4348 100644
--- a/README.md
+++ b/README.md
@@ -152,7 +152,7 @@ For most users, it just works.
If you are running a local system firewall, we recommend adding a rule permitting UDP port 9993 inbound and outbound. If you installed binaries for Windows this should be done automatically. Other platforms might require manual editing of local firewall rules depending on your configuration.
-The Mac firewall can be founder under "Security" in System Preferences. Linux has a variety of firewall configuration systems and tools. If you're using Ubuntu's *ufw*, you can do this:
+The Mac firewall can be found under "Security" in System Preferences. Linux has a variety of firewall configuration systems and tools. If you're using Ubuntu's *ufw*, you can do this:
sudo ufw allow 9993/udp
diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp
index 0e90163de..b0a826c01 100644
--- a/controller/SqliteNetworkController.cpp
+++ b/controller/SqliteNetworkController.cpp
@@ -61,18 +61,23 @@
// 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"
+#define ZT_NETCONF_SQLITE_SCHEMA_VERSION 2
+#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "2"
// API version reported via JSON control plane
#define ZT_NETCONF_CONTROLLER_API_VERSION 1
-// Drop requests for a given peer and network ID that occur more frequently
-// than this (ms).
+// 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 120000
+#define ZT_NETCONF_BACKUP_PERIOD 300000
+
+// Number of NodeHistory entries to maintain per node and network (can be changed)
+#define ZT_NETCONF_NODE_HISTORY_LENGTH 64
+
+// 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)
namespace ZeroTier {
@@ -134,6 +139,9 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
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;
@@ -146,8 +154,34 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
if (schemaVersion == -1234) {
sqlite3_close(_db);
throw std::runtime_error("SqliteNetworkController schemaVersion not found in Config table (init failure?)");
+ } else if (schemaVersion == 1) {
+ // 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"
+ "\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"
+ "\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 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");
}
@@ -177,6 +211,13 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
||(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)
+ /* NodeHistory */
+ ||(sqlite3_prepare_v2(_db,"SELECT IFNULL(MAX(networkVisitCounter),0) FROM NodeHistory WHERE networkId = ? AND nodeId = ?",-1,&_sGetMaxNodeHistoryNetworkVisitCounter,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"INSERT INTO NodeHistory (nodeId,networkId,networkVisitCounter,networkRequestAuthorized,requestTime,clientMajorVersion,clientMinorVersion,clientRevision,networkRequestMetaData,fromAddress) VALUES (?,?,?,?,?,?,?,?,?,?)",-1,&_sAddNodeHistoryEntry,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"DELETE FROM NodeHistory WHERE networkId = ? AND nodeId = ? AND networkVisitCounter <= ?",-1,&_sDeleteOldNodeHistoryEntries,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT nodeId,MAX(requestTime),MAX((((clientMajorVersion & 65535) << 32) | ((clientMinorVersion & 65535) << 16) | (clientRevision & 65535))) FROM NodeHistory WHERE networkId = ? AND requestTime >= ? AND networkRequestAuthorized > 0 GROUP BY nodeId",-1,&_sGetActiveNodesOnNetwork,(const char **)0) != SQLITE_OK)
+ ||(sqlite3_prepare_v2(_db,"SELECT networkVisitCounter,networkRequestAuthorized,requestTime,clientMajorVersion,clientMinorVersion,clientRevision,networkRequestMetaData,fromAddress FROM NodeHistory WHERE networkId = ? AND nodeId = ? ORDER BY requestTime DESC",-1,&_sGetNodeHistory,(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)
@@ -224,9 +265,9 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK)
) {
- //printf("%s\n",sqlite3_errmsg(_db));
+ std::string err(std::string("SqliteNetworkController unable to initialize one or more prepared statements: ") + sqlite3_errmsg(_db));
sqlite3_close(_db);
- throw std::runtime_error("SqliteNetworkController unable to initialize one or more prepared statements");
+ throw std::runtime_error(err);
}
/* Generate a 128-bit / 32-character "instance ID" if one isn't already
@@ -267,6 +308,11 @@ SqliteNetworkController::~SqliteNetworkController()
sqlite3_finalize(_sCreateMember);
sqlite3_finalize(_sGetNodeIdentity);
sqlite3_finalize(_sCreateOrReplaceNode);
+ sqlite3_finalize(_sGetMaxNodeHistoryNetworkVisitCounter);
+ sqlite3_finalize(_sAddNodeHistoryEntry);
+ sqlite3_finalize(_sDeleteOldNodeHistoryEntries);
+ sqlite3_finalize(_sGetActiveNodesOnNetwork);
+ sqlite3_finalize(_sGetNodeHistory);
sqlite3_finalize(_sGetEtherTypesFromRuleTable);
sqlite3_finalize(_sGetActiveBridges);
sqlite3_finalize(_sGetIpAssignmentsForNode);
@@ -555,9 +601,18 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
}
test->timestamp = OSUtils::now();
- _circuitTests[test->testId] = test;
+
+ _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
@@ -1003,8 +1058,28 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
void SqliteNetworkController::threadMain()
throw()
{
- uint64_t lastBackupTime = 0;
+ 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) {
lastBackupTime = OSUtils::now();
@@ -1049,6 +1124,7 @@ void SqliteNetworkController::threadMain()
OSUtils::rm(backupPath2);
::rename(backupPath,backupPath2);
}
+
Thread::sleep(250);
}
}
@@ -1171,44 +1247,36 @@ unsigned int SqliteNetworkController::_doCPGet(
(unsigned int)sqlite3_column_int(_sGetIpAssignmentsForNode2,1)
);
responseBody.append(firstIp ? "\"" : ",\"");
- firstIp = false;
responseBody.append(_jsonEscape(ip.toString()));
responseBody.push_back('"');
+ firstIp = false;
}
responseBody.append("],\n\t\"recentLog\": [");
- {
- std::map< std::pair
,_LLEntry >::const_iterator lli(_lastLog.find(std::pair(Address(address),nwid)));
- if (lli != _lastLog.end()) {
- const _LLEntry &lastLogEntry = lli->second;
- uint64_t eptr = lastLogEntry.totalRequests;
- for(int k=0;k= 0x0000000100010000ULL)
+ responseBody.append(",\"cts\":true");
+ else responseBody.append(",\"cts\":false");
+ responseBody.push_back('}');
+ firstMember = false;
+ }
+ }
+ 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 {
- // get network info
+
sqlite3_reset(_sGetNetworkById);
sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC);
if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
@@ -1541,12 +1650,15 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
}
- // Check rate limit circuit breaker to prevent flooding
const uint64_t now = OSUtils::now();
- _LLEntry &lastLogEntry = _lastLog[std::pair(identity.address(),nwid)];
- if ((now - lastLogEntry.lastRequestTime) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
- return NetworkController::NETCONF_QUERY_IGNORE;
- lastLogEntry.lastRequestTime = now;
+
+ // Check rate limit circuit breaker to prevent flooding
+ {
+ uint64_t &lrt = _lastRequestTime[std::pair(identity.address().toInt(),nwid)];
+ if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
+ return NetworkController::NETCONF_QUERY_IGNORE;
+ lrt = now;
+ }
NetworkRecord network;
memset(&network,0,sizeof(network));
@@ -1632,18 +1744,47 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
sqlite3_step(_sIncrementMemberRevisionCounter);
}
- // Add log entry to in-memory circular log
+ // Update NodeHistory with new log entry and delete expired entries
{
- const unsigned long ptr = (unsigned long)lastLogEntry.totalRequests % ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE;
- lastLogEntry.l[ptr].ts = now;
- lastLogEntry.l[ptr].fromAddr = fromAddr;
- if ((clientMajorVersion > 0)||(clientMinorVersion > 0)||(clientRevision > 0))
- Utils::snprintf(lastLogEntry.l[ptr].version,sizeof(lastLogEntry.l[ptr].version),"%u.%u.%u",clientMajorVersion,clientMinorVersion,clientRevision);
- else lastLogEntry.l[ptr].version[0] = (char)0;
- lastLogEntry.l[ptr].authorized = member.authorized;
- ++lastLogEntry.totalRequests;
- // TODO: push or save these somewhere
+ int64_t nextVC = 1;
+ sqlite3_reset(_sGetMaxNodeHistoryNetworkVisitCounter);
+ sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,2,member.nodeId,10,SQLITE_STATIC);
+ if (sqlite3_step(_sGetMaxNodeHistoryNetworkVisitCounter) == SQLITE_ROW) {
+ nextVC = (int64_t)sqlite3_column_int64(_sGetMaxNodeHistoryNetworkVisitCounter,0) + 1;
+ }
+
+ std::string mdstr(metaData.toString());
+ if (mdstr.length() > 1024)
+ mdstr = mdstr.substr(0,1024);
+ std::string fastr;
+ if (fromAddr)
+ fastr = fromAddr.toString();
+
+ sqlite3_reset(_sAddNodeHistoryEntry);
+ sqlite3_bind_text(_sAddNodeHistoryEntry,1,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_text(_sAddNodeHistoryEntry,2,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_int64(_sAddNodeHistoryEntry,3,nextVC);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,4,(member.authorized ? 1 : 0));
+ sqlite3_bind_int64(_sAddNodeHistoryEntry,5,(long long)now);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,6,(int)clientMajorVersion);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,7,(int)clientMinorVersion);
+ sqlite3_bind_int(_sAddNodeHistoryEntry,8,(int)clientRevision);
+ sqlite3_bind_text(_sAddNodeHistoryEntry,9,mdstr.c_str(),-1,SQLITE_STATIC);
+ if (fastr.length() > 0)
+ sqlite3_bind_text(_sAddNodeHistoryEntry,10,fastr.c_str(),-1,SQLITE_STATIC);
+ else sqlite3_bind_null(_sAddNodeHistoryEntry,10);
+ sqlite3_step(_sAddNodeHistoryEntry);
+
+ nextVC -= ZT_NETCONF_NODE_HISTORY_LENGTH;
+ if (nextVC >= 0) {
+ sqlite3_reset(_sDeleteOldNodeHistoryEntries);
+ sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,1,network.id,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,2,member.nodeId,10,SQLITE_STATIC);
+ sqlite3_bind_int64(_sDeleteOldNodeHistoryEntries,3,nextVC);
+ sqlite3_step(_sDeleteOldNodeHistoryEntries);
+ }
}
// Check member authorization
@@ -1910,7 +2051,7 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
}
if (network.isPrivate) {
- CertificateOfMembership com(now,ZT_NETWORK_AUTOCONF_DELAY + (ZT_NETWORK_AUTOCONF_DELAY / 2),nwid,identity.address());
+ CertificateOfMembership com(now,ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA,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 {
@@ -1930,73 +2071,67 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
void SqliteNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report)
{
- static Mutex circuitTestWriteLock;
+ char tmp[65535];
+ SqliteNetworkController *const self = reinterpret_cast(test->ptr);
- const uint64_t now = OSUtils::now();
+ if (!test)
+ return;
+ if (!report)
+ return;
- SqliteNetworkController *const c = reinterpret_cast(test->ptr);
- char tmp[128];
+ Mutex::Lock _l(self->_lock);
+ std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId));
- std::string reportSavePath(c->_circuitTestPath);
- OSUtils::mkdir(reportSavePath);
- Utils::snprintf(tmp,sizeof(tmp),ZT_PATH_SEPARATOR_S"%.16llx",test->credentialNetworkId);
- reportSavePath.append(tmp);
- OSUtils::mkdir(reportSavePath);
- Utils::snprintf(tmp,sizeof(tmp),ZT_PATH_SEPARATOR_S"%.16llx_%.16llx",test->timestamp,test->testId);
- reportSavePath.append(tmp);
- OSUtils::mkdir(reportSavePath);
- Utils::snprintf(tmp,sizeof(tmp),ZT_PATH_SEPARATOR_S"%.16llx_%.10llx_%.10llx",now,report->upstream,report->current);
- reportSavePath.append(tmp);
-
- {
- Mutex::Lock _l(circuitTestWriteLock);
- FILE *f = fopen(reportSavePath.c_str(),"a");
- if (!f)
- return;
- fseek(f,0,SEEK_END);
- fprintf(f,"%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
- "}",
- ((ftell(f) > 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)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(&(report->receivedOnLocalAddress))->toString().c_str(),
- reinterpret_cast(&(report->receivedFromRemoteAddress))->toString().c_str());
- fclose(f);
+ 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(&(report->receivedOnLocalAddress))->toString().c_str(),
+ reinterpret_cast(&(report->receivedFromRemoteAddress))->toString().c_str());
+
+ cte->second.jsonResults.append(tmp);
}
} // namespace ZeroTier
diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp
index 0e2bb63e7..11be9db47 100644
--- a/controller/SqliteNetworkController.hpp
+++ b/controller/SqliteNetworkController.hpp
@@ -44,8 +44,8 @@
// Number of in-memory last log entries to maintain per user
#define ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE 32
-// How long do circuit tests "live"? This is just to prevent buildup in memory.
-#define ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT 300000
+// How long do circuit tests last before they're forgotten?
+#define ZT_SQLITENETWORKCONTROLLER_CIRCUIT_TEST_TIMEOUT 60000
namespace ZeroTier {
@@ -123,37 +123,16 @@ private:
std::string _circuitTestPath;
std::string _instanceId;
- // A circular buffer last log
- struct _LLEntry
- {
- _LLEntry()
- {
- for(long i=0;il[i].ts = 0;
- this->lastRequestTime = 0;
- this->totalRequests = 0;
- }
-
- // Circular buffer of last log entries
- struct {
- uint64_t ts; // timestamp or 0 if circular buffer entry unused
- char version[64];
- InetAddress fromAddr;
- bool authorized;
- } l[ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE];
-
- // Time of last request whether successful or not
- uint64_t lastRequestTime;
-
- // Total requests by this address / network ID pair (also serves mod IN_MEMORY_LOG_SIZE as circular buffer ptr)
- uint64_t totalRequests;
- };
-
- // Last log entries by address and network ID pair
- std::map< std::pair,_LLEntry > _lastLog;
-
// Circuit tests outstanding
- std::map< uint64_t,ZT_CircuitTest * > _circuitTests;
+ 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 > _lastRequestTime;
sqlite3 *_db;
@@ -162,6 +141,11 @@ private:
sqlite3_stmt *_sCreateMember;
sqlite3_stmt *_sGetNodeIdentity;
sqlite3_stmt *_sCreateOrReplaceNode;
+ sqlite3_stmt *_sGetMaxNodeHistoryNetworkVisitCounter;
+ sqlite3_stmt *_sAddNodeHistoryEntry;
+ sqlite3_stmt *_sDeleteOldNodeHistoryEntries;
+ sqlite3_stmt *_sGetActiveNodesOnNetwork;
+ sqlite3_stmt *_sGetNodeHistory;
sqlite3_stmt *_sGetEtherTypesFromRuleTable;
sqlite3_stmt *_sGetActiveBridges;
sqlite3_stmt *_sGetIpAssignmentsForNode;
diff --git a/controller/schema.sql b/controller/schema.sql
index b6db7fa48..aff088273 100644
--- a/controller/schema.sql
+++ b/controller/schema.sql
@@ -34,6 +34,23 @@ CREATE TABLE Node (
identity varchar(4096) NOT NULL
);
+CREATE TABLE NodeHistory (
+ nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE,
+ networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
+ networkVisitCounter INTEGER NOT NULL DEFAULT(0),
+ networkRequestAuthorized INTEGER NOT NULL DEFAULT(0),
+ requestTime INTEGER NOT NULL DEFAULT(0),
+ clientMajorVersion INTEGER NOT NULL DEFAULT(0),
+ clientMinorVersion INTEGER NOT NULL DEFAULT(0),
+ clientRevision INTEGER NOT NULL DEFAULT(0),
+ networkRequestMetaData VARCHAR(1024),
+ fromAddress VARCHAR(128)
+);
+
+CREATE INDEX NodeHistory_nodeId ON NodeHistory (nodeId);
+CREATE INDEX NodeHistory_networkId ON NodeHistory (networkId);
+CREATE INDEX NodeHistory_requestTime ON NodeHistory (requestTime);
+
CREATE TABLE Gateway (
networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,
ip blob(16) NOT NULL,
diff --git a/controller/schema.sql.c b/controller/schema.sql.c
index a5b9130b5..4b524547e 100644
--- a/controller/schema.sql.c
+++ b/controller/schema.sql.c
@@ -35,6 +35,23 @@
" identity varchar(4096) NOT NULL\n"\
");\n"\
"\n"\
+"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"\
+"\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"\
+"\n"\
"CREATE TABLE Gateway (\n"\
" networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE,\n"\
" ip blob(16) NOT NULL,\n"\
diff --git a/examples/api/README.md b/examples/api/README.md
deleted file mode 100644
index 50b1b65e5..000000000
--- a/examples/api/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-API Examples
-======
-
-This folder contains examples that can be posted with curl or another http query utility to a local instance.
-
-To test querying with curl:
-
- curl -H 'X-ZT1-Auth:AUTHTOKEN' http://127.0.0.1:9993/status
-
-To create a public network on a local controller (service must be built with "make ZT\_ENABLE\_NETWORK\_CONTROLLER=1"):
-
- curl -H 'X-ZT1-Auth:AUTHTOKEN' -X POST -d @public.json http://127.0.0.1:9993/controller/network/################
-
-Replace AUTHTOKEN with the contents of this instance's authtoken.secret file and ################ with a valid network ID. Its first 10 hex digits must be the ZeroTier address of the controller itself, while the last 6 hex digits can be anything. Also be sure to change the port if you have this instance listening somewhere other than 9993.
-
-After POSTing you can double check the network config with:
-
- curl -H 'X-ZT1-Auth:AUTHTOKEN' http://127.0.0.1:9993/controller/network/################
-
-Once this network is created (and if your controller is online, etc.) you can then join this network from any device anywhere in the world and it will receive a valid network configuration.
-
----
-
-**public.json**: A valid configuration for a public network that allows IPv4 and IPv6 traffic.
-
-**circuit-test-pingpong.json**: An example circuit test that can be posted to /controller/network/################/test to order a test -- you will have to edit this to insert the hops you want since the two hard coded device IDs are from our own test instances.
diff --git a/examples/api/circuit-test-pingpong.json b/examples/api/circuit-test-pingpong.json
deleted file mode 100644
index 8fcc5d944..000000000
--- a/examples/api/circuit-test-pingpong.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "hops": [
- [ "4cbc810d4c" ],
- [ "868cd1664f" ],
- [ "4cbc810d4c" ],
- [ "868cd1664f" ],
- [ "4cbc810d4c" ],
- [ "868cd1664f" ],
- [ "4cbc810d4c" ],
- [ "868cd1664f" ]
- ],
- "reportAtEveryHop": true
-}
diff --git a/examples/api/public.json b/examples/api/public.json
deleted file mode 100644
index 4317bd3e2..000000000
--- a/examples/api/public.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "public_test_network",
- "private": false,
- "enableBroadcast": true,
- "allowPassiveBridging": false,
- "v4AssignMode": "zt",
- "v6AssignMode": "rfc4193",
- "multicastLimit": 32,
- "relays": [],
- "gateways": [],
- "ipLocalRoutes": ["10.66.0.0/16"],
- "ipAssignmentPools": [{"ipRangeStart":"10.66.0.1","ipRangeEnd":"10.66.255.254"}],
- "rules": [
- {
- "ruleNo": 10,
- "etherType": 2048,
- "action": "accept"
- },{
- "ruleNo": 20,
- "etherType": 2054,
- "action": "accept"
- },{
- "ruleNo": 30,
- "etherType": 34525,
- "action": "accept"
- }]
-}
diff --git a/examples/docker/Dockerfile b/examples/docker/Dockerfile
deleted file mode 100644
index f1ce6bb50..000000000
--- a/examples/docker/Dockerfile
+++ /dev/null
@@ -1,19 +0,0 @@
-FROM centos:7
-
-MAINTAINER https://www.zerotier.com/
-
-RUN yum -y update && yum install -y sqlite net-tools && yum clean all
-
-EXPOSE 9993/udp
-
-RUN mkdir -p /var/lib/zerotier-one
-RUN mkdir -p /var/lib/zerotier-one/networks.d
-RUN ln -sf /var/lib/zerotier-one/zerotier-one /usr/local/bin/zerotier-cli
-RUN ln -sf /var/lib/zerotier-one/zerotier-one /usr/local/bin/zerotier-idtool
-
-ADD zerotier-one /var/lib/zerotier-one/
-
-ADD main.sh /
-RUN chmod a+x /main.sh
-
-CMD ["./main.sh"]
diff --git a/examples/docker/README.md b/examples/docker/README.md
deleted file mode 100644
index fbc93481c..000000000
--- a/examples/docker/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-Simple Dockerfile Example
-======
-
-This is a simple Docker example using ZeroTier One in normal tun/tap mode. It uses a Dockerfile to build an image containing ZeroTier One and a main.sh that launches it with an identity supplied via the Docker environment via the ZEROTIER\_IDENTITY\_SECRET and ZEROTIER\_NETWORK variables. The Dockerfile assumes that the zerotier-one binary is in the build folder.
-
-This is not a very secure way to load an identity secret, but it's useful for testing since it allows you to repeatedly launch Docker containers with the same identity. For production we'd recommend using something like Hashicorp Vault, or modifying main.sh to leave identities unspecified and allow the container to generate a new identity at runtime. Then you could script approval of containers using the controller API, approving them as they launch, etc. (We are working on better ways of doing mass provisioning.)
-
-To use in normal tun/tap mode with Docker, containers must be run with the options "--device=/dev/net/tun --privileged". The main.sh script supplied here will complain and exit if these options are not present (no /dev/net/tun device).
diff --git a/examples/docker/main.sh b/examples/docker/main.sh
deleted file mode 100644
index 53fb65408..000000000
--- a/examples/docker/main.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-
-export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin
-
-if [ ! -c "/dev/net/tun" ]; then
- echo 'FATAL: must be docker run with: --device=/dev/net/tun --cap-add=NET_ADMIN'
- exit 1
-fi
-
-if [ -z "$ZEROTIER_IDENTITY_SECRET" ]; then
- echo 'FATAL: ZEROTIER_IDENTITY_SECRET not set -- aborting!'
- exit 1
-fi
-
-if [ -z "$ZEROTIER_NETWORK" ]; then
- echo 'Warning: ZEROTIER_NETWORK not set, you will need to docker exec zerotier-cli to join a network.'
-else
- # The existence of a .conf will cause the service to "remember" this network
- touch /var/lib/zerotier-one/networks.d/$ZEROTIER_NETWORK.conf
-fi
-
-rm -f /var/lib/zerotier-one/identity.*
-echo "$ZEROTIER_IDENTITY_SECRET" >/var/lib/zerotier-one/identity.secret
-
-/var/lib/zerotier-one/zerotier-one
diff --git a/examples/docker/maketestenv.sh b/examples/docker/maketestenv.sh
deleted file mode 100755
index 275692e15..000000000
--- a/examples/docker/maketestenv.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-if [ -z "$1" -o -z "$2" ]; then
- echo 'Usage: maketestenv.sh