summaryrefslogtreecommitdiff
path: root/controller
diff options
context:
space:
mode:
Diffstat (limited to 'controller')
-rw-r--r--controller/SqliteNetworkController.cpp405
-rw-r--r--controller/SqliteNetworkController.hpp44
-rw-r--r--controller/schema.sql17
-rw-r--r--controller/schema.sql.c17
4 files changed, 329 insertions, 154 deletions
diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp
index 0e90163d..bef9cfc1 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,requestTime,clientMajorVersion,clientMinorVersion,clientRevision,fromAddress,networkRequestAuthorized FROM NodeHistory WHERE networkId = ? AND requestTime IN (SELECT MAX(requestTime) FROM NodeHistory WHERE networkId = ? AND requestTime >= ? GROUP BY nodeId) ORDER BY nodeId ASC,requestTime DESC",-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<Address,uint64_t>,_LLEntry >::const_iterator lli(_lastLog.find(std::pair<Address,uint64_t>(Address(address),nwid)));
- if (lli != _lastLog.end()) {
- const _LLEntry &lastLogEntry = lli->second;
- uint64_t eptr = lastLogEntry.totalRequests;
- for(int k=0;k<ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE;++k) {
- if (!eptr--)
- break;
- const unsigned long ptr = (unsigned long)eptr % ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE;
-
- char tsbuf[64];
- Utils::snprintf(tsbuf,sizeof(tsbuf),"%llu",(unsigned long long)lastLogEntry.l[ptr].ts);
-
- responseBody.append((k == 0) ? "{" : ",{");
- responseBody.append("\"ts\":");
- responseBody.append(tsbuf);
- responseBody.append(lastLogEntry.l[ptr].authorized ? ",\"authorized\":false,\"version\":" : ",\"authorized\":true,\"version\":");
- if (lastLogEntry.l[ptr].version[0]) {
- responseBody.push_back('"');
- responseBody.append(_jsonEscape(lastLogEntry.l[ptr].version));
- responseBody.append("\",\"fromAddr\":");
- } else responseBody.append("null,\"fromAddr\":");
- if (lastLogEntry.l[ptr].fromAddr) {
- responseBody.push_back('"');
- responseBody.append(_jsonEscape(lastLogEntry.l[ptr].fromAddr.toString()));
- responseBody.append("\"}");
- } else responseBody.append("null}");
- }
- }
+ sqlite3_reset(_sGetNodeHistory);
+ sqlite3_bind_text(_sGetNodeHistory,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetNodeHistory,2,addrs,10,SQLITE_STATIC);
+ bool firstHistory = true;
+ while (sqlite3_step(_sGetNodeHistory) == SQLITE_ROW) {
+ responseBody.append(firstHistory ? "{" : ",{");
+ responseBody.append("\"ts\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,2));
+ responseBody.append((sqlite3_column_int(_sGetNodeHistory,1) == 0) ? ",\"authorized\":false,\"clientMajorVersion\":" : ",\"authorized\":true,\"clientMajorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,3));
+ responseBody.append(",\"clientMinorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,4));
+ responseBody.append(",\"clientRevision\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetNodeHistory,5));
+ responseBody.append(",\"fromAddr\":");
+ const char *fa = (const char *)sqlite3_column_text(_sGetNodeHistory,7);
+ if (fa) {
+ responseBody.push_back('"');
+ responseBody.append(_jsonEscape(fa));
+ responseBody.append("\"}");
+ } else responseBody.append("null}");
+ firstHistory = false;
}
-
responseBody.append("]\n}\n");
responseContentType = "application/json";
@@ -1220,7 +1288,7 @@ unsigned int SqliteNetworkController::_doCPGet(
sqlite3_reset(_sListNetworkMembers);
sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC);
- responseBody.append("{");
+ responseBody.push_back('{');
bool firstMember = true;
while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) {
responseBody.append(firstMember ? "\"" : ",\"");
@@ -1235,10 +1303,70 @@ unsigned int SqliteNetworkController::_doCPGet(
}
+ } else if ((path[2] == "active")&&(path.size() == 3)) {
+
+ sqlite3_reset(_sGetActiveNodesOnNetwork);
+ sqlite3_bind_text(_sGetActiveNodesOnNetwork,1,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_text(_sGetActiveNodesOnNetwork,2,nwids,16,SQLITE_STATIC);
+ sqlite3_bind_int64(_sGetActiveNodesOnNetwork,3,(int64_t)(OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD));
+
+ responseBody.push_back('{');
+ bool firstMember = true;
+ uint64_t lastNodeId = 0;
+ while (sqlite3_step(_sGetActiveNodesOnNetwork) == SQLITE_ROW) {
+ const char *nodeId = (const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,0);
+ if (nodeId) {
+ const uint64_t nodeIdInt = Utils::hexStrToU64(nodeId);
+ if (nodeIdInt == lastNodeId) // technically that SQL query could (rarely) generate a duplicate for a given nodeId, in which case we want the first
+ continue;
+ lastNodeId = nodeIdInt;
+
+ responseBody.append(firstMember ? "\"" : ",\"");
+ firstMember = false;
+ responseBody.append(nodeId);
+ responseBody.append("\":{");
+ responseBody.append("\"ts\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,1));
+ responseBody.append((sqlite3_column_int(_sGetActiveNodesOnNetwork,6) > 0) ? ",\"authorized\":true" : ",\"authorized\":false");
+ responseBody.append(",\"clientMajorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,2));
+ responseBody.append(",\"clientMinorVersion\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,3));
+ responseBody.append(",\"clientRevision\":");
+ responseBody.append((const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,4));
+ const char *fromAddr = (const char *)sqlite3_column_text(_sGetActiveNodesOnNetwork,5);
+ if ((fromAddr)&&(fromAddr[0])) {
+ responseBody.append(",\"fromAddr\":\"");
+ responseBody.append(_jsonEscape(fromAddr));
+ responseBody.append("\"}");
+ } else {
+ responseBody.append(",\"fromAddr\":null}");
+ }
+ }
+ }
+ 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 +1669,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<Address,uint64_t>(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<uint64_t,uint64_t>(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 +1763,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
@@ -1701,6 +1861,7 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
netconf[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT] = ml;
}
+ bool amActiveBridge = false;
{
std::string activeBridges;
sqlite3_reset(_sGetActiveBridges);
@@ -1711,6 +1872,8 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
if (activeBridges.length())
activeBridges.push_back(',');
activeBridges.append(ab);
+ if (!strcmp(member.nodeId,ab))
+ amActiveBridge = true;
}
if (activeBridges.length() > 1024) // sanity check -- you can't have too many active bridges at the moment
break;
@@ -1832,7 +1995,7 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
}
}
- if (!haveStaticIpAssignment) {
+ if ((!haveStaticIpAssignment)&&(!amActiveBridge)) {
// Attempt to auto-assign an IPv4 address from an available routed pool
sqlite3_reset(_sGetIpAssignmentPools);
sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC);
@@ -1846,7 +2009,7 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
uint32_t ipRangeStart = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeStartB + 12)));
uint32_t ipRangeEnd = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeEndB + 12)));
- if (ipRangeEnd < ipRangeStart)
+ if ((ipRangeEnd <= ipRangeStart)||(ipRangeStart == 0))
continue;
uint32_t ipRangeLen = ipRangeEnd - ipRangeStart;
@@ -1910,7 +2073,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 +2093,67 @@ NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(c
void SqliteNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report)
{
- static Mutex circuitTestWriteLock;
-
- const uint64_t now = OSUtils::now();
+ char tmp[65535];
+ SqliteNetworkController *const self = reinterpret_cast<SqliteNetworkController *>(test->ptr);
- SqliteNetworkController *const c = reinterpret_cast<SqliteNetworkController *>(test->ptr);
- char tmp[128];
+ if (!test)
+ return;
+ if (!report)
+ return;
- 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(self->_lock);
+ std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId));
- {
- 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<const InetAddress *>(&(report->receivedOnLocalAddress))->toString().c_str(),
- reinterpret_cast<const InetAddress *>(&(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<const InetAddress *>(&(report->receivedOnLocalAddress))->toString().c_str(),
+ reinterpret_cast<const InetAddress *>(&(report->receivedFromRemoteAddress))->toString().c_str());
+
+ cte->second.jsonResults.append(tmp);
}
} // namespace ZeroTier
diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp
index 0e2bb63e..11be9db4 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
+ // Circuit tests outstanding
+ struct _CircuitTestEntry
{
- _LLEntry()
- {
- for(long i=0;i<ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE;++i)
- this->l[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;
+ ZT_CircuitTest *test;
+ std::string jsonResults;
};
+ std::map< uint64_t,_CircuitTestEntry > _circuitTests;
- // Last log entries by address and network ID pair
- std::map< std::pair<Address,uint64_t>,_LLEntry > _lastLog;
-
- // Circuit tests outstanding
- std::map< uint64_t,ZT_CircuitTest * > _circuitTests;
+ // Last request time by address, for rate limitation
+ std::map< std::pair<uint64_t,uint64_t>,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 b6db7fa4..aff08827 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 a5b9130b..4b524547 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"\