diff options
author | Grant Limberg <glimberg@gmail.com> | 2015-05-16 14:35:35 -0700 |
---|---|---|
committer | Grant Limberg <glimberg@gmail.com> | 2015-05-16 14:35:35 -0700 |
commit | d0935f667fd809a32cb4184a2c422ff79dc6d933 (patch) | |
tree | 8dd92dfbe2fdd30375993a21619f701d48060340 | |
parent | 4a0280686c7a74b58a060375ffce385554d90040 (diff) | |
parent | 38243e5eff71b76d35904afffd2b21fe84ed958a (diff) | |
download | infinitytier-d0935f667fd809a32cb4184a2c422ff79dc6d933.tar.gz infinitytier-d0935f667fd809a32cb4184a2c422ff79dc6d933.zip |
Merge branch 'adamierymenko-dev' into android-jni
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | controller/README.md | 2 | ||||
-rw-r--r-- | controller/SqliteNetworkController.cpp | 321 | ||||
-rw-r--r-- | controller/SqliteNetworkController.hpp | 6 | ||||
-rwxr-xr-x | controller/controller-api-test.sh | 42 | ||||
-rw-r--r-- | controller/schema.sql | 4 | ||||
-rw-r--r-- | include/ZeroTierOne.h | 2 | ||||
-rw-r--r-- | node/Constants.hpp | 2 | ||||
-rw-r--r-- | node/Packet.cpp | 1 | ||||
-rw-r--r-- | node/Packet.hpp | 109 | ||||
-rw-r--r-- | node/Utils.cpp | 9 | ||||
-rw-r--r-- | node/Utils.hpp | 4 | ||||
-rw-r--r-- | selftest.cpp | 18 | ||||
-rw-r--r-- | service/ControlPlane.cpp | 14 | ||||
-rw-r--r-- | service/OneService.cpp | 4 | ||||
-rw-r--r-- | service/README.md | 241 |
16 files changed, 608 insertions, 175 deletions
@@ -13,4 +13,6 @@ endif ifeq ($(OSTYPE),FreeBSD) include make-freebsd.mk endif - +ifeq ($(OSTYPE),OpenBSD) + include make-freebsd.mk +endif diff --git a/controller/README.md b/controller/README.md index ee176d38..1d4fef9a 100644 --- a/controller/README.md +++ b/controller/README.md @@ -9,7 +9,7 @@ The standard implementation uses SQLite3 with the attached schema. A separate se By default this code is not built or included in the client. To build on Linux, BSD, or Mac add ZT_ENABLE_NETCONF_MASTER=1 to the make command line. It could be built on Windows as well, but you're on your own there. You'd have to build SQLite3 first, or get a pre-built copy somewhere. -### Createing databases +### Creating databases If you execute a network controller enabled build of the ZeroTier One service, a *controller.db* will automatically be created and initialize. You can also create one manually with: diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index b9aebbb8..92b8c32c 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -61,8 +61,8 @@ #define ZT_NETCONF_SQLITE_SCHEMA_VERSION 1 #define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "1" -// Maximum age in ms for a cached netconf before we regenerate anyway (one hour) -#define ZT_CACHED_NETCONF_MAX_AGE (60 * 60 * 1000) +// API version reported via JSON control plane +#define ZT_NETCONF_CONTROLLER_API_VERSION 1 namespace ZeroTier { @@ -93,11 +93,6 @@ static std::string _jsonEscape(const std::string &s) { return _jsonEscape(s.c_st struct MemberRecord { int64_t rowid; char nodeId[16]; - int cachedNetconfBytes; - const void *cachedNetconf; - uint64_t cachedNetconfRevision; - uint64_t cachedNetconfTimestamp; - uint64_t clientReportedRevision; bool authorized; bool activeBridge; }; @@ -153,13 +148,12 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) : if ( (sqlite3_prepare_v2(_db,"SELECT name,private,enableBroadcast,allowPassiveBridging,v4AssignMode,v6AssignMode,multicastLimit,creationTime,revision FROM Network WHERE id = ?",-1,&_sGetNetworkById,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT rowid,cachedNetconf,cachedNetconfRevision,cachedNetconfTimestamp,clientReportedRevision,authorized,activeBridge FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sGetMember,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,cachedNetconfRevision,clientReportedRevision,authorized,activeBridge) VALUES (?,?,0,0,?,0)",-1,&_sCreateMember,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"SELECT rowid,authorized,activeBridge FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sGetMember,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge) VALUES (?,?,?,0)",-1,&_sCreateMember,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT identity FROM Node WHERE id = ?",-1,&_sGetNodeIdentity,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"INSERT INTO Node (id,identity,lastAt,lastSeen,firstSeen) VALUES (?,?,?,?,?)",-1,&_sCreateNode,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"UPDATE Node SET lastAt = ?,lastSeen = ? WHERE id = ?",-1,&_sUpdateNode,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"UPDATE Node SET lastSeen = ? WHERE id = ?",-1,&_sUpdateNode2,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Member SET clientReportedRevision = ? WHERE rowid = ?",-1,&_sUpdateMemberClientReportedRevision,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT etherType FROM Rule WHERE networkId = ? AND \"action\" = 'accept'",-1,&_sGetEtherTypesFromRuleTable,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT mgMac,mgAdi,preload,maxBalance,accrual FROM MulticastRate WHERE networkId = ?",-1,&_sGetMulticastRates,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT nodeId FROM Member WHERE networkId = ? AND activeBridge > 0 AND authorized > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK) @@ -167,10 +161,10 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) : ||(sqlite3_prepare_v2(_db,"SELECT ipNetwork,ipNetmaskBits FROM IpAssignmentPool WHERE networkId = ? AND ipVersion = ?",-1,&_sGetIpAssignmentPools,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE networkId = ? AND ip = ? AND ipVersion = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignment (networkId,nodeId,ip,ipNetmaskBits,ipVersion) VALUES (?,?,?,?,?)",-1,&_sAllocateIp,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Member SET cachedNetconf = ?,cachedNetconfRevision = ? WHERE rowid = ?",-1,&_sCacheNetconf,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteIpAllocations,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT nodeId,phyAddress FROM Relay WHERE networkId = ? ORDER BY nodeId ASC",-1,&_sGetRelays,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT id FROM Network ORDER BY id ASC",-1,&_sListNetworks,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,n.id,n.lastAt,n.lastSeen,n.firstSeen FROM Member AS m,Node AS n WHERE m.networkId = ? AND n.id = m.nodeId ORDER BY n.id ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"SELECT n.id FROM Member AS m,Node AS n WHERE m.networkId = ? AND n.id = m.nodeId ORDER BY n.id ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,n.identity,n.lastAt,n.lastSeen,n.firstSeen FROM Member AS m,Node AS n WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT ipNetwork,ipNetmaskBits,ipVersion FROM IpAssignmentPool WHERE networkId = ? ORDER BY ipNetwork ASC",-1,&_sGetIpAssignmentPools2,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT ruleId,nodeId,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleId ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK) @@ -185,6 +179,9 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) : ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"DELETE FROM Rule WHERE networkId = ?",-1,&_sDeleteRulesForNetwork,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignmentPool (networkId,ipNetwork,ipNetmaskBits,ipVersion) VALUES (?,?,?,?)",-1,&_sCreateIpAssignmentPool,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"UPDATE Member SET ? = ? WHERE rowid = ?",-1,&_sUpdateMemberField,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ?; DELETE FROM IpAssignmentPool WHERE networkId = ?; DELETE FROM Member WHERE networkId = ?; DELETE FROM MulticastRate WHERE networkId = ?; DELETE FROM Relay WHERE networkId = ?; DELETE FROM Rule WHERE networkId = ?; DELETE FROM Network WHERE id = ?;",-1,&_sDeleteNetworkAndRelated,(const char **)0) != SQLITE_OK) ) { sqlite3_close(_db); throw std::runtime_error("SqliteNetworkController unable to initialize one or more prepared statements"); @@ -202,7 +199,6 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sCreateNode); sqlite3_finalize(_sUpdateNode); sqlite3_finalize(_sUpdateNode2); - sqlite3_finalize(_sUpdateMemberClientReportedRevision); sqlite3_finalize(_sGetEtherTypesFromRuleTable); sqlite3_finalize(_sGetMulticastRates); sqlite3_finalize(_sGetActiveBridges); @@ -210,7 +206,7 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sGetIpAssignmentPools); sqlite3_finalize(_sCheckIfIpIsAllocated); sqlite3_finalize(_sAllocateIp); - sqlite3_finalize(_sCacheNetconf); + sqlite3_finalize(_sDeleteIpAllocations); sqlite3_finalize(_sGetRelays); sqlite3_finalize(_sListNetworks); sqlite3_finalize(_sListNetworkMembers); @@ -228,6 +224,8 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork); sqlite3_finalize(_sDeleteRulesForNetwork); sqlite3_finalize(_sCreateIpAssignmentPool); + sqlite3_finalize(_sUpdateMemberField); + sqlite3_finalize(_sDeleteNetworkAndRelated); sqlite3_close(_db); } } @@ -243,6 +241,10 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co netconf["error"] = "signing identity invalid or lacks private key"; return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR; } + if (signingId.address().toInt() != (nwid >> 24)) { + netconf["error"] = "signing identity address does not match most significant 40 bits of network ID"; + return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR; + } NetworkRecord network; memset(&network,0,sizeof(network)); @@ -325,31 +327,24 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co if (sqlite3_step(_sGetMember) == SQLITE_ROW) { foundMember = true; member.rowid = (int64_t)sqlite3_column_int64(_sGetMember,0); - member.cachedNetconfBytes = sqlite3_column_bytes(_sGetMember,1); - member.cachedNetconf = sqlite3_column_blob(_sGetMember,1); - member.cachedNetconfRevision = (uint64_t)sqlite3_column_int64(_sGetMember,2); - member.cachedNetconfTimestamp = (uint64_t)sqlite3_column_int64(_sGetMember,3); - member.clientReportedRevision = (uint64_t)sqlite3_column_int64(_sGetMember,4); - member.authorized = (sqlite3_column_int(_sGetMember,5) > 0); - member.activeBridge = (sqlite3_column_int(_sGetMember,6) > 0); + member.authorized = (sqlite3_column_int(_sGetMember,1) > 0); + member.activeBridge = (sqlite3_column_int(_sGetMember,2) > 0); } // Create Member record for unknown nodes, auto-authorizing if network is public if (!foundMember) { - member.cachedNetconfBytes = 0; - member.cachedNetconfRevision = 0; - member.clientReportedRevision = 0; member.authorized = (network.isPrivate ? false : true); member.activeBridge = false; sqlite3_reset(_sCreateMember); sqlite3_bind_text(_sCreateMember,1,network.id,16,SQLITE_STATIC); sqlite3_bind_text(_sCreateMember,2,member.nodeId,10,SQLITE_STATIC); sqlite3_bind_int(_sCreateMember,3,(member.authorized ? 0 : 1)); - if ( (sqlite3_step(_sCreateMember) != SQLITE_DONE) && ((member.rowid = (int64_t)sqlite3_last_insert_rowid(_db)) > 0) ) { + if (sqlite3_step(_sCreateMember) != SQLITE_DONE) { netconf["error"] = "unable to create new member record"; return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR; } + member.rowid = (int64_t)sqlite3_last_insert_rowid(_db); } // Check member authorization @@ -357,32 +352,15 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co if (!member.authorized) return NetworkController::NETCONF_QUERY_ACCESS_DENIED; - // Update client's currently reported haveRevision in Member record - - if (member.rowid > 0) { - sqlite3_reset(_sUpdateMemberClientReportedRevision); - sqlite3_bind_int64(_sUpdateMemberClientReportedRevision,1,(sqlite3_int64)haveRevision); - sqlite3_bind_int64(_sUpdateMemberClientReportedRevision,2,member.rowid); - sqlite3_step(_sUpdateMemberClientReportedRevision); - } - // If netconf is unchanged from client reported revision, just tell client they're up to date if ((haveRevision > 0)&&(haveRevision == network.revision)) return NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER; - // Generate or retrieve cached netconf + // Create and sign netconf netconf.clear(); - if ( (member.cachedNetconfBytes > 0)&& - (member.cachedNetconfRevision == network.revision)&& - ((OSUtils::now() - member.cachedNetconfTimestamp) < ZT_CACHED_NETCONF_MAX_AGE) ) { - // Use cached copy - std::string tmp((const char *)member.cachedNetconf,member.cachedNetconfBytes); - netconf.fromString(tmp); - } else { - // Create and sign a new netconf, and save in database to re-use in the future - + { char tss[24],rs[24]; Utils::snprintf(tss,sizeof(tss),"%.16llx",(unsigned long long)OSUtils::now()); Utils::snprintf(rs,sizeof(rs),"%.16llx",(unsigned long long)network.revision); @@ -521,16 +499,20 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co if (ip == (n | im)) continue; // broadcast address e.g. 10.0.0.255 for 10.0.0.0/255.255.255.0 uint32_t nip = Utils::hton(ip); // IP in big-endian "network" byte order + char ipBlob[16]; + memset(ipBlob,0,12); + memcpy(ipBlob + 12,&nip,4); + sqlite3_reset(_sCheckIfIpIsAllocated); sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC); - sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)&nip,4,SQLITE_STATIC); + sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ipBlob,16,SQLITE_STATIC); sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4 if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) { // No rows returned, so the IP is available sqlite3_reset(_sAllocateIp); sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC); sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC); - sqlite3_bind_blob(_sAllocateIp,3,(const void *)&nip,4,SQLITE_STATIC); + sqlite3_bind_blob(_sAllocateIp,3,(const void *)ipBlob,16,SQLITE_STATIC); sqlite3_bind_int(_sAllocateIp,4,ipNetmaskBits); sqlite3_bind_int(_sAllocateIp,5,4); // 4 == IPv4 if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) { @@ -567,16 +549,6 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co netconf["error"] = "unable to sign netconf dictionary"; return NETCONF_QUERY_INTERNAL_SERVER_ERROR; } - - // Save serialized netconf for future re-use - std::string netconfSerialized(netconf.toString()); - if (netconfSerialized.length() < 4096) { // sanity check - sqlite3_reset(_sCacheNetconf); - sqlite3_bind_blob(_sCacheNetconf,1,(const void *)netconfSerialized.data(),netconfSerialized.length(),SQLITE_STATIC); - sqlite3_bind_int64(_sCacheNetconf,2,(sqlite3_int64)network.revision); - sqlite3_bind_int64(_sCacheNetconf,3,member.rowid); - sqlite3_step(_sCacheNetconf); - } } return NetworkController::NETCONF_QUERY_OK; @@ -615,14 +587,16 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( if (sqlite3_step(_sGetMember2) == SQLITE_ROW) { Utils::snprintf(json,sizeof(json), "{\n" - "\taddress: \"%s\"" - "\tauthorized: %s," + "\tnwid: \"%s\",\n" + "\taddress: \"%s\",\n" + "\tauthorized: %s,\n" "\tactiveBridge: %s,\n" "\tlastAt: \"%s\",\n" "\tlastSeen: %llu,\n" "\tfirstSeen: %llu,\n" "\tidentity: \"%s\",\n" "\tipAssignments: [", + nwids, addrs, (sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false", (sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false", @@ -644,7 +618,58 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( responseBody.push_back('"'); } - responseBody.append("]\n}\n"); + responseBody.append("]"); + + /* It's possible to get the actual netconf dictionary by including these + * three URL arguments. The member identity must be the string + * serialized identity of this member, and the signing identity must be + * the full secret identity of this network controller. The have revision + * is optional but would designate the revision our hypothetical client + * already has. + * + * This is primarily for testing and is not used in production. It makes + * it easy to test the entire network controller via its JSON API. + * + * If these arguments are included, three more object fields are returned: + * 'netconf', 'netconfResult', and 'netconfResultMessage'. These are all + * string fields and contain the actual netconf dictionary, the query + * result code, and any verbose message e.g. an error description. */ + std::map<std::string,std::string>::const_iterator memids(urlArgs.find("memberIdentity")); + std::map<std::string,std::string>::const_iterator sigids(urlArgs.find("signingIdentity")); + std::map<std::string,std::string>::const_iterator hrs(urlArgs.find("haveRevision")); + if ((memids != urlArgs.end())&&(sigids != urlArgs.end())) { + Dictionary netconf; + Identity memid,sigid; + try { + if (memid.fromString(memids->second)&&sigid.fromString(sigids->second)&&sigid.hasPrivate()) { + uint64_t hr = 0; + if (hrs != urlArgs.end()) + hr = Utils::strToU64(hrs->second.c_str()); + const char *result = ""; + switch(this->doNetworkConfigRequest(InetAddress(),sigid,memid,nwid,Dictionary(),hr,netconf)) { + case NetworkController::NETCONF_QUERY_OK: result = "OK"; break; + case NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER: result = "OK_BUT_NOT_NEWER"; break; + case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: result = "OBJECT_NOT_FOUND"; break; + case NetworkController::NETCONF_QUERY_ACCESS_DENIED: result = "ACCESS_DENIED"; break; + case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: result = "INTERNAL_SERVER_ERROR"; break; + default: result = "(unrecognized result code)"; break; + } + responseBody.append(",\n\tnetconf: \""); + responseBody.append(_jsonEscape(netconf.toString().c_str())); + responseBody.append("\",\n\tnetconfResult: \""); + responseBody.append(result); + responseBody.append("\",\n\tnetconfResultMessage: \""); + responseBody.append(_jsonEscape(netconf["error"].c_str())); + responseBody.append("\""); + } else { + responseBody.append(",\n\tnetconf: \"\",\n\tnetconfResult: \"INTERNAL_SERVER_ERROR\",\n\tnetconfResultMessage: \"invalid member or signing identity\""); + } + } catch ( ... ) { + responseBody.append(",\n\tnetconf: \"\",\n\tnetconfResult: \"INTERNAL_SERVER_ERROR\",\n\tnetconfResultMessage: \"unexpected exception\""); + } + } + + responseBody.append("\n}\n"); responseContentType = "application/json"; return 200; @@ -667,7 +692,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( "\tmulticastLimit: %d,\n" "\tcreationTime: %llu,\n", "\trevision: %llu,\n" - "\tmembers: [", + "\amembers: [", nwids, _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(), (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false", @@ -684,23 +709,11 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC); bool firstMember = true; while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) { - Utils::snprintf(json,sizeof(json), - "%s{\n" - "\t\taddress: \"%s\",\n" - "\t\tauthorized: %s,\n" - "\t\tactiveBridge: %s,\n" - "\t\tlastAt: \"%s\",\n" - "\t\tlastSeen: %llu,\n" - "\t\tfirstSeen: %llu\n" - "\t}", - firstMember ? "\n\t" : ",", - (const char *)sqlite3_column_text(_sListNetworkMembers,2), - (sqlite3_column_int(_sListNetworkMembers,0) > 0) ? "true" : "false", - (sqlite3_column_int(_sListNetworkMembers,1) > 0) ? "true" : "false", - _jsonEscape((const char *)sqlite3_column_text(_sListNetworkMembers,3)).c_str(), - (unsigned long long)sqlite3_column_int64(_sListNetworkMembers,4), - (unsigned long long)sqlite3_column_int64(_sListNetworkMembers,5)); - responseBody.append(json); + if (!firstMember) + responseBody.push_back(','); + responseBody.push_back('"'); + responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0)); + responseBody.push_back('"'); firstMember = false; } responseBody.append("],\n\trelays: ["); @@ -816,7 +829,13 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( return 200; } // else 404 - } // else 404 + } else { + // GET /controller returns status and API version if controller is supported + Utils::snprintf(json,sizeof(json),"{\n\tcontroller: true,\n\tapiVersion: %d\n\tclock: %llu\n}",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); + responseBody = json; + responseContentType = "applicaiton/json"; + return 200; + } return 404; } @@ -859,6 +878,90 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( char addrs[24]; Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address); + int64_t memberRowId = 0; + sqlite3_reset(_sGetMember); + sqlite3_bind_text(_sGetMember,1,nwids,16,SQLITE_STATIC); + sqlite3_bind_text(_sGetMember,2,addrs,10,SQLITE_STATIC); + bool memberExists = false; + if (sqlite3_step(_sGetMember) == SQLITE_ROW) { + memberExists = true; + memberRowId = sqlite3_column_int64(_sGetMember,0); + } + + if (!memberExists) { + sqlite3_reset(_sCreateMember); + sqlite3_bind_text(_sCreateMember,1,nwids,16,SQLITE_STATIC); + sqlite3_bind_text(_sCreateMember,2,addrs,10,SQLITE_STATIC); + sqlite3_bind_int(_sCreateMember,3,0); + if (sqlite3_step(_sCreateMember) != SQLITE_DONE) + return 500; + memberRowId = (int64_t)sqlite3_last_insert_rowid(_db); + } + + json_value *j = json_parse(body.c_str(),body.length()); + if (j) { + if (j->type == json_object) { + for(unsigned int k=0;k<j->u.object.length;++k) { + sqlite3_reset(_sUpdateMemberField); + sqlite3_bind_int64(_sUpdateMemberField,3,memberRowId); + + if (!strcmp(j->u.object.values[k].name,"authorized")) { + if (j->u.object.values[k].value->type == json_boolean) { + sqlite3_bind_text(_sUpdateMemberField,1,"authorized",-1,SQLITE_STATIC); + sqlite3_bind_int(_sUpdateMemberField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); + sqlite3_step(_sUpdateMemberField); + } + } else if (!strcmp(j->u.object.values[k].name,"activeBridge")) { + if (j->u.object.values[k].value->type == json_boolean) { + sqlite3_bind_text(_sUpdateMemberField,1,"activeBridge",-1,SQLITE_STATIC); + sqlite3_bind_int(_sUpdateMemberField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); + sqlite3_step(_sUpdateMemberField); + } + } else if (!strcmp(j->u.object.values[k].name,"ipAssignments")) { + if (j->u.object.values[k].value->type == json_array) { + sqlite3_reset(_sDeleteIpAllocations); + sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC); + sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC); + sqlite3_step(_sDeleteIpAllocations); + for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) { + json_value *ipalloc = j->u.object.values[k].value->u.array.values[kk]; + if (ipalloc->type == json_string) { + InetAddress a(ipalloc->u.string.ptr); + char ipBlob[16]; + int ipVersion = 0; + switch(a.ss_family) { + case AF_INET: + if ((a.netmaskBits() > 0)&&(a.netmaskBits() <= 32)) { + memset(ipBlob,0,12); + memcpy(ipBlob + 12,a.rawIpData(),4); + ipVersion = 4; + } + break; + case AF_INET6: + if ((a.netmaskBits() > 0)&&(a.netmaskBits() <= 128)) { + memcpy(ipBlob,a.rawIpData(),16); + ipVersion = 6; + } + break; + } + if (ipVersion > 0) { + sqlite3_reset(_sAllocateIp); + sqlite3_bind_text(_sAllocateIp,1,nwids,16,SQLITE_STATIC); + sqlite3_bind_text(_sAllocateIp,2,addrs,10,SQLITE_STATIC); + sqlite3_bind_blob(_sAllocateIp,3,(const void *)ipBlob,16,SQLITE_STATIC); + sqlite3_bind_int(_sAllocateIp,4,(int)a.netmaskBits()); + sqlite3_bind_int(_sAllocateIp,5,ipVersion); + sqlite3_step(_sAllocateIp); + } + } + } + } + } + } + } + json_value_free(j); + } + return handleControlPlaneHttpGET(path,urlArgs,headers,body,responseBody,responseContentType); } // else 404 @@ -885,43 +988,43 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( sqlite3_bind_text(_sUpdateNetworkField,1,"name",-1,SQLITE_STATIC); sqlite3_bind_text(_sUpdateNetworkField,2,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC); sqlite3_step(_sUpdateNetworkField); - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"private")) { if (j->u.object.values[k].value->type == json_boolean) { sqlite3_bind_text(_sUpdateNetworkField,1,"private",-1,SQLITE_STATIC); sqlite3_bind_int(_sUpdateNetworkField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); sqlite3_step(_sUpdateNetworkField); - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"enableBroadcast")) { if (j->u.object.values[k].value->type == json_boolean) { sqlite3_bind_text(_sUpdateNetworkField,1,"enableBroadcast",-1,SQLITE_STATIC); sqlite3_bind_int(_sUpdateNetworkField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); sqlite3_step(_sUpdateNetworkField); - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"allowPassiveBridging")) { if (j->u.object.values[k].value->type == json_boolean) { sqlite3_bind_text(_sUpdateNetworkField,1,"allowPassiveBridging",-1,SQLITE_STATIC); sqlite3_bind_int(_sUpdateNetworkField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); sqlite3_step(_sUpdateNetworkField); - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"v4AssignMode")) { if (j->u.object.values[k].value->type == json_string) { sqlite3_bind_text(_sUpdateNetworkField,1,"v4AssignMode",-1,SQLITE_STATIC); sqlite3_bind_text(_sUpdateNetworkField,2,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC); sqlite3_step(_sUpdateNetworkField); - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"v6AssignMode")) { if (j->u.object.values[k].value->type == json_string) { sqlite3_bind_text(_sUpdateNetworkField,1,"v6AssignMode",-1,SQLITE_STATIC); sqlite3_bind_text(_sUpdateNetworkField,2,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC); sqlite3_step(_sUpdateNetworkField); - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"multicastLimit")) { if (j->u.object.values[k].value->type == json_integer) { sqlite3_bind_text(_sUpdateNetworkField,1,"multicastLimit",-1,SQLITE_STATIC); sqlite3_bind_int(_sUpdateNetworkField,2,(int)j->u.object.values[k].value->u.integer); sqlite3_step(_sUpdateNetworkField); - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"relays")) { if (j->u.object.values[k].value->type == json_array) { std::map<Address,InetAddress> nodeIdToPhyAddress; @@ -935,7 +1038,6 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( address = relay->u.object.values[rk].value->u.string.ptr; else if ((!strcmp(relay->u.object.values[rk].name,"phyAddress"))&&(relay->u.object.values[rk].value->type == json_string)) phyAddress = relay->u.object.values[rk].value->u.string.ptr; - else return 400; } } if ((address)&&(phyAddress)) @@ -953,7 +1055,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( sqlite3_bind_text(_sCreateRelay,3,rl->second.toString().c_str(),-1,SQLITE_STATIC); sqlite3_step(_sCreateRelay); } - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"ipAssignmentPools")) { if (j->u.object.values[k].value->type == json_array) { std::set<InetAddress> pools; @@ -967,7 +1069,6 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( net = pool->u.object.values[rk].value->u.string.ptr; else if ((!strcmp(pool->u.object.values[rk].name,"netmaskBits"))&&(pool->u.object.values[rk].value->type == json_integer)) bits = (int)pool->u.object.values[rk].value->u.integer; - else return 400; } } if ((net)&&(bits > 0)) { @@ -991,7 +1092,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( sqlite3_step(_sCreateIpAssignmentPool); } } - } else return 400; + } } else if (!strcmp(j->u.object.values[k].name,"rules")) { if (j->u.object.values[k].value->type == json_array) { sqlite3_reset(_sDeleteRulesForNetwork); @@ -1082,7 +1183,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( } } } - } else return 400; + } } } } @@ -1116,6 +1217,46 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE( return 404; Mutex::Lock _l(_lock); + if (path[0] == "network") { + + if ((path.size() >= 2)&&(path[1].length() == 16)) { + uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + + if (path.size() >= 3) { + + if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { + uint64_t address = Utils::hexStrToU64(path[3].c_str()); + char addrs[24]; + Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address); + + sqlite3_reset(_sDeleteIpAllocations); + sqlite3_bind_text(_sDeleteIpAllocations,1,nwids,16,SQLITE_STATIC); + sqlite3_bind_text(_sDeleteIpAllocations,2,addrs,10,SQLITE_STATIC); + if (sqlite3_step(_sDeleteIpAllocations) == SQLITE_DONE) { + sqlite3_reset(_sDeleteMember); + sqlite3_bind_text(_sDeleteMember,1,nwids,16,SQLITE_STATIC); + sqlite3_bind_text(_sDeleteMember,2,addrs,10,SQLITE_STATIC); + if (sqlite3_step(_sDeleteMember) != SQLITE_DONE) + return 500; + } else return 500; + + return 200; + } + + } else { + + sqlite3_reset(_sDeleteNetworkAndRelated); + for(int i=1;i<=7;++i) + sqlite3_bind_text(_sDeleteNetworkAndRelated,i,nwids,16,SQLITE_STATIC); + return ((sqlite3_step(_sDeleteNetworkAndRelated) == SQLITE_DONE) ? 200 : 500); + + } + } // else 404 + + } // else 404 + return 404; } diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp index c61829c3..c5d4c51a 100644 --- a/controller/SqliteNetworkController.hpp +++ b/controller/SqliteNetworkController.hpp @@ -94,7 +94,6 @@ private: sqlite3_stmt *_sCreateNode; sqlite3_stmt *_sUpdateNode; sqlite3_stmt *_sUpdateNode2; - sqlite3_stmt *_sUpdateMemberClientReportedRevision; sqlite3_stmt *_sGetEtherTypesFromRuleTable; sqlite3_stmt *_sGetMulticastRates; sqlite3_stmt *_sGetActiveBridges; @@ -102,7 +101,7 @@ private: sqlite3_stmt *_sGetIpAssignmentPools; sqlite3_stmt *_sCheckIfIpIsAllocated; sqlite3_stmt *_sAllocateIp; - sqlite3_stmt *_sCacheNetconf; + sqlite3_stmt *_sDeleteIpAllocations; sqlite3_stmt *_sGetRelays; sqlite3_stmt *_sListNetworks; sqlite3_stmt *_sListNetworkMembers; @@ -120,6 +119,9 @@ private: sqlite3_stmt *_sDeleteIpAssignmentPoolsForNetwork; sqlite3_stmt *_sDeleteRulesForNetwork; sqlite3_stmt *_sCreateIpAssignmentPool; + sqlite3_stmt *_sUpdateMemberField; + sqlite3_stmt *_sDeleteMember; + sqlite3_stmt *_sDeleteNetworkAndRelated; Mutex _lock; }; diff --git a/controller/controller-api-test.sh b/controller/controller-api-test.sh deleted file mode 100755 index 934685b3..00000000 --- a/controller/controller-api-test.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -if [ "$#" -ne "2" ]; then - echo 'Usage: controller-api-test.sh <network ID to create> <local TCP port for HTTP API>' - exit 1 -fi - -network_json=$(cat <<EOF -{ - name: "test network", - private: true, - v4AssignMode: "zt", - v6AssignMode: "none", - multicastLimit: 100, - ipAssignmentPools: [ - { - network: "10.1.2.0", - netmaskBits: 24 - } - ], - rules: [ - { - ruleId: 100, - etherType: 0x0800, - action: "accept" - }, - { - ruleId: 200, - etherType: 0x0806, - action: "accept" - }, - { - ruleId: 300, - etherType: 0x86dd, - action: "accept" - } - ] -} -EOF -) - -echo "$network_json" | curl -d - -v "http://127.0.0.1:$2/controller/network/$1" diff --git a/controller/schema.sql b/controller/schema.sql index dba93039..8d93a4dc 100644 --- a/controller/schema.sql +++ b/controller/schema.sql @@ -29,10 +29,6 @@ CREATE INDEX IpAssignmentPool_networkId ON IpAssignmentPool (networkId); CREATE TABLE Member ( networkId char(16) NOT NULL, nodeId char(10) NOT NULL, - cachedNetconf blob(4096), - cachedNetconfRevision integer NOT NULL DEFAULT(0), - cachedNetconfTimestamp integer NOT NULL DEFAULT(0), - clientReportedRevision integer NOT NULL DEFAULT(0), authorized integer NOT NULL DEFAULT(0), activeBridge integer NOT NULL DEFAULT(0) ); diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index ecc2edef..dd7ccfa1 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -43,6 +43,8 @@ #else /* not Windows */ #include <arpa/inet.h> #include <netinet/in.h> +#include <sys/types.h> +#include <sys/socket.h> #endif /* Windows or not */ #ifdef __cplusplus diff --git a/node/Constants.hpp b/node/Constants.hpp index 10c48c20..3bda685d 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -60,7 +60,7 @@ #include <endian.h> #endif -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__OpenBSD__) #ifndef __UNIX_LIKE__ #define __UNIX_LIKE__ #endif diff --git a/node/Packet.cpp b/node/Packet.cpp index f72f64b2..a81873ff 100644 --- a/node/Packet.cpp +++ b/node/Packet.cpp @@ -50,6 +50,7 @@ const char *Packet::verbString(Verb v) case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH"; case VERB_MULTICAST_GATHER: return "MULTICAST_GATHER"; case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME"; + case VERB_SET_EPHEMERAL_KEY: return "SET_EPHEMERAL_KEY"; } return "(unknown)"; } diff --git a/node/Packet.hpp b/node/Packet.hpp index 76f84996..2dfb75e4 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -99,6 +99,17 @@ #define ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 1 /** + * Cipher suite: PFS negotiated ephemeral cipher suite and authentication + * + * This message is encrypted with the latest negotiated ephemeral (PFS) + * key pair and cipher suite. If authentication fails, VERB_SET_EPHEMERAL_KEY + * may be sent to renegotiate ephemeral keys. To prevent attacks, this + * attempted renegotiation should be limited to some sane rate such as + * once per second. + */ +#define ZT_PROTO_CIPHER_SUITE__EPHEMERAL 7 + +/** * DEPRECATED payload encrypted flag, will be removed for re-use soon. * * This has been replaced by the two-bit cipher suite selection field where @@ -115,13 +126,6 @@ #define ZT_PROTO_FLAG_FRAGMENTED 0x40 /** - * Flag indicating encryption with a PFS session key - * - * Not used yet -- for future PFS session re-keying support. - */ -#define ZT_PROTO_FLAG_PFS_SESSION 0x20 - -/** * Verb flag indicating payload is compressed with LZ4 */ #define ZT_PROTO_VERB_FLAG_COMPRESSED 0x80 @@ -186,6 +190,17 @@ #define ZT_PROTO_DEST_ADDRESS_TYPE_IPV4 4 #define ZT_PROTO_DEST_ADDRESS_TYPE_IPV6 6 +// Ephemeral key record flags +#define ZT_PROTO_EPHEMERAL_KEY_FLAG_FIPS 0x01 + +// Ephemeral key record symmetric cipher types +#define ZT_PROTO_EPHEMERAL_KEY_SYMMETRIC_CIPHER_SALSA2012_POLY1305 0x01 +#define ZT_PROTO_EPHEMERAL_KEY_SYMMETRIC_CIPHER_AES256_GCM 0x02 + +// Ephemeral key record public key types +#define ZT_PROTO_EPHEMERAL_KEY_PK_C25519 0x01 +#define ZT_PROTO_EPHEMERAL_KEY_PK_NISTP256 0x02 + // Field incides for parsing verbs ------------------------------------------- // Some verbs have variable-length fields. Those aren't fully defined here @@ -298,8 +313,8 @@ namespace ZeroTier { * * Packets smaller than 28 bytes are invalid and silently discarded. * - * The flags/cipher/hops bit field is: FFFCCHHH where C is a 2-bit cipher - * selection allowing up to 4 cipher suites, F is outside-envelope flags, + * The flags/cipher/hops bit field is: FFCCCHHH where C is a 3-bit cipher + * selection allowing up to 7 cipher suites, F is outside-envelope flags, * and H is hop count. * * The three-bit hop count is the only part of a packet that is mutable in @@ -752,7 +767,54 @@ public: * <[6] multicast group MAC> * <[4] 32-bit multicast group ADI> */ - VERB_MULTICAST_FRAME = 14 + VERB_MULTICAST_FRAME = 14, + + /* Ephemeral (PFS) key push: + * <[8] 64-bit PFS key set ID sender holds for recipient (0==none)> + * <[8] 64-bit PFS key set ID of this key set> + * [... begin PFS key record ...] + * <[1] flags> + * <[1] symmetric cipher ID> + * <[1] public key type ID> + * <[2] public key length in bytes> + * <[...] public key> + * [... additional records may follow up to max packet length ...] + * + * This message is sent to negotiate an ephemeral key. If the recipient's + * current key pair for the sender does not match the one the sender + * claims to have on file, it must respond with its own SET_EPHEMERAL_KEY. + * + * PFS key IDs are random and must not be zero, since zero indicates that + * the sender does not have an ephemeral key on file for the recipient. + * + * One or more records may be sent. If multiple records are present, + * the first record with common symmetric cipher, public key type, + * and relevant flags must be used. + * + * Flags (all unspecified flags must be zero): + * 0x01 - FIPS mode, only use record if FIPS compliant crypto in use + * + * Symmetric cipher IDs: + * 0x01 - Salsa20/12 with Poly1305 authentication (ZT default) + * 0x02 - AES256-GCM combined crypto and authentication + * + * Public key types: + * 0x01 - Curve25519 ECDH with SHA-512 KDF + * 0x02 - NIST P-256 ECDH with SHA-512 KDF + * + * Once both peers have a PFS key, they will attempt to send PFS key + * encrypted messages with the PFS flag set using the negotiated + * cipher/auth type. + * + * Note: most of these features such as FIPS and other cipher suites are + * not implemented yet. They're just specified in the protocol for future + * use to support e.g. FIPS requirements. + * + * OK response payload: + * <[8] PFS key set ID of received key set> + * <[1] index in record list of chosen key record> + */ + VERB_SET_EPHEMERAL_KEY = 15 }; /** @@ -824,7 +886,7 @@ public: Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(ZT_PROTO_MIN_PACKET_LENGTH) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); - (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops } /** @@ -873,7 +935,7 @@ public: Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); setDestination(dest); setSource(source); - (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops setVerb(v); } @@ -884,34 +946,21 @@ public: * technically different but otherwise identical copies of the same * packet. */ - inline void newInitializationVector() - { - Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); - } + inline void newInitializationVector() { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); } /** * Set this packet's destination * * @param dest ZeroTier address of destination */ - inline void setDestination(const Address &dest) - { - unsigned char *d = field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH); - for(unsigned int i=0;i<ZT_ADDRESS_LENGTH;++i) - d[i] = dest[i]; - } + inline void setDestination(const Address &dest) { dest.copyTo(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } /** * Set this packet's source * * @param source ZeroTier address of source */ - inline void setSource(const Address &source) - { - unsigned char *s = field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH); - for(unsigned int i=0;i<ZT_ADDRESS_LENGTH;++i) - s[i] = source[i]; - } + inline void setSource(const Address &source) { source.copyTo(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } /** * Get this packet's destination @@ -974,7 +1023,7 @@ public: inline unsigned int cipher() const { // Note: this uses the new cipher spec field, which is incompatible with <1.0.0 peers - return (((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x18) >> 3); + return (((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x38) >> 3); } /** @@ -983,7 +1032,7 @@ public: inline void setCipher(unsigned int c) { unsigned char &b = (*this)[ZT_PACKET_IDX_FLAGS]; - b = (b & 0xe7) | (unsigned char)((c << 3) & 0x18); // bits: FFFCCHHH + b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38); // bits: FFCCCHHH // DEPRECATED "encrypted" flag -- used by pre-1.0.3 peers if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) b |= ZT_PROTO_FLAG_ENCRYPTED; diff --git a/node/Utils.cpp b/node/Utils.cpp index 3380b324..9630e6b3 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -49,6 +49,7 @@ #include "Utils.hpp" #include "Mutex.hpp" +#include "Salsa20.hpp" namespace ZeroTier { @@ -152,6 +153,7 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) static HCRYPTPROV cryptProvider = NULL; static Mutex globalLock; + static Salsa20 s20; Mutex::Lock _l(globalLock); @@ -161,12 +163,19 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) exit(1); return; } + char s20key[32]; + if (!CryptGenRandom(cryptProvider,(DWORD)sizeof(s20key),(BYTE *)s20key)) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n"); + exit(1); + } + s20.init(s20key,256,s20key,8); } if (!CryptGenRandom(cryptProvider,(DWORD)bytes,(BYTE *)buf)) { fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n"); exit(1); } + s20.encrypt(buf,buf,bytes); #else // not __WINDOWS__ diff --git a/node/Utils.hpp b/node/Utils.hpp index 585c7f5b..bdd673a9 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -331,7 +331,7 @@ public: throw() { #if __BYTE_ORDER == __LITTLE_ENDIAN -#ifdef __GNUC__ +#if defined(__GNUC__) && (!defined(__OpenBSD__)) return __builtin_bswap64(n); #else return ( @@ -361,7 +361,7 @@ public: throw() { #if __BYTE_ORDER == __LITTLE_ENDIAN -#ifdef __GNUC__ +#if defined(__GNUC__) && !defined(__OpenBSD__) return __builtin_bswap64(n); #else return ( diff --git a/selftest.cpp b/selftest.cpp index 319271f3..5d5067fd 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -204,6 +204,24 @@ static int testCrypto() ::free((void *)bb); } + std::cout << "[crypto] Benchmarking Salsa20/20... "; std::cout.flush(); + { + unsigned char *bb = (unsigned char *)::malloc(1234567); + for(unsigned int i=0;i<1234567;++i) + bb[i] = (unsigned char)i; + Salsa20 s20(s20TV0Key,256,s20TV0Iv,20); + double bytes = 0.0; + uint64_t start = OSUtils::now(); + for(unsigned int i=0;i<200;++i) { + s20.encrypt(bb,bb,1234567); + bytes += 1234567.0; + } + uint64_t end = OSUtils::now(); + SHA512::hash(buf1,bb,1234567); + std::cout << ((bytes / 1048576.0) / ((double)(end - start) / 1000.0)) << " MiB/second (" << Utils::hex(buf1,16) << ')' << std::endl; + ::free((void *)bb); + } + std::cout << "[crypto] Testing SHA-512... "; std::cout.flush(); SHA512::hash(buf1,sha512TV0Input,(unsigned int)strlen(sha512TV0Input)); if (memcmp(buf1,sha512TV0Digest,64)) { diff --git a/service/ControlPlane.cpp b/service/ControlPlane.cpp index c92087f7..69c5d48d 100644 --- a/service/ControlPlane.cpp +++ b/service/ControlPlane.cpp @@ -360,7 +360,8 @@ unsigned int ControlPlane::handleRequest( "\t\"versionMajor\":%d,\n" "\t\"versionMinor\":%d,\n" "\t\"versionRev\":%d,\n" - "\t\"version\":\"%d.%d.%d\"\n" + "\t\"version\":\"%d.%d.%d\",\n" + "\t\"clock\": %llu\n" "}\n", status.address, status.publicIdentity, @@ -368,7 +369,8 @@ unsigned int ControlPlane::handleRequest( ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION, - ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION, + (unsigned long long)OSUtils::now()); responseBody = json; scode = 200; } else if (ps[0] == "config") { @@ -433,6 +435,14 @@ unsigned int ControlPlane::handleRequest( } // else 404 _node->freeQueryResult((void *)pl); } else scode = 500; + } else if (ps[0] == "newIdentity") { + // Return a newly generated ZeroTier identity -- this is primarily for debugging + // and testing to make it easy for automated test scripts to generate test IDs. + Identity newid; + newid.generate(); + responseBody = newid.toString(true); + responseContentType = "text/plain"; + scode = 200; } else { std::map<std::string,ControlPlaneSubsystem *>::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) diff --git a/service/OneService.cpp b/service/OneService.cpp index 56b1d979..99aaf77d 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -70,6 +70,10 @@ namespace ZeroTier { typedef LinuxEthernetTap EthernetTap; } #include "../osdep/WindowsEthernetTap.hpp" namespace ZeroTier { typedef WindowsEthernetTap EthernetTap; } #endif +#if defined(__BSD__) && (!defined(__APPLE__)) +#include "../osdep/BSDEthernetTap.hpp" +namespace ZeroTier { typedef BSDEthernetTap EthernetTap; } +#endif // Sanity limits for HTTP #define ZT_MAX_HTTP_MESSAGE_SIZE (1024 * 1024 * 8) diff --git a/service/README.md b/service/README.md new file mode 100644 index 00000000..c347931f --- /dev/null +++ b/service/README.md @@ -0,0 +1,241 @@ +ZeroTier One Network Virtualization Service +====== + +This is the common background service implementation for ZeroTier One, the VPN-like OS-level network virtualization service. + +It provides a ready-made core I/O loop and a local HTTP-based JSON control bus for controlling the service. This control bus HTTP server can also serve the files in ui/ if this folder's contents are installed in the ZeroTier home folder. The ui/ implements a React-based HTML5 user interface which is then wrappered for various platforms via MacGap, Windows .NET WebControl, etc. It can also be used locally from scripts or via *curl*. + +### Network Virtualization Service API + +The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for POST. Other methods including HEAD are not supported. + +Values POSTed to the JSON API are *extremely* type sensitive. Things *must* be of the indicated type, otherwise they will be ignored or will generate an error. Anything quoted is a string so booleans and integers must lack quotes. Booleans must be *true* or *false* and nothing else. Integers cannot contain decimal points or they are floats (and vice versa). If something seems to be getting ignored or set to a strange value, or if you receive errors, check the type of all JSON fields you are submitting against the types listed below. Unrecognized fields in JSON objects are also ignored. + +API requests must be authenticated via an authentication token. ZeroTier One saves this token in the *authtoken.secret* file in its working directory. This token may be supplied via the *authToken* URL parameter (e.g. '?authToken=...') or via the *X-ZT1-Auth* HTTP request header. Static UI pages are the only thing the server will allow without authentication. + +A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP response is sent as a script with its JSON response payload wrapped in a call to the function name supplied as the argument to *jsonp*. + +#### /status + + * Purpose: Get running node status and addressing info + * Methods: GET + * Returns: { object } + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>address</td><td>string</td><td>10-digit hexadecimal ZeroTier address of this node</td><td>no</td></tr> +<tr><td>publicIdentity</td><td>string</td><td>Full public ZeroTier identity of this node</td><td>no</td></tr> +<tr><td>online</td><td>boolean</td><td>Does this node appear to have upstream network access?</td><td>no</td></tr> +<tr><td>versionMajor</td><td>integer</td><td>ZeroTier major version</td><td>no</td></tr> +<tr><td>versionMinor</td><td>integer</td><td>ZeroTier minor version</td><td>no</td></tr> +<tr><td>versionRev</td><td>integer</td><td>ZeroTier revision</td><td>no</td></tr> +<tr><td>version</td><td>string</td><td>Version in major.minor.rev format</td><td>no</td></tr> +<tr><td>clock</td><td>integer</td><td>Node system clock in ms since epoch</td><td>no</td></tr> +</table> + +#### /config + + * Purpose: Get or set local configuration + * Methods: GET, POST + * Returns: { object } + +No local configuration options are exposed yet. + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +</table> + +#### /network + + * Purpose: Get all network memberships + * Methods: GET + * Returns: [ {object}, ... ] + +Getting /network returns an array of all networks that this node has joined. See below for network object format. + +#### /network/\<network ID\> + + * Purpose: Get, join, or leave a network + * Methods: GET, POST, DELETE + * Returns: { object } + +To join a network, POST to it. POST data is optional and may be omitted. Example: POST to /network/8056c2e21c000001 to join the public "Earth" network. To leave a network, DELETE it e.g. DELETE /network/8056c2e21c000001. + +Most network settings are not writable, as they are defined by the network controller. + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>nwid</td><td>string</td><td>16-digit hex network ID</td><td>no</td></tr> +<tr><td>mac</td><td>string</td><td>Ethernet MAC address of virtual network port</td><td>no</td></tr> +<tr><td>name</td><td>string</td><td>Network short name as configured on network controller</td><td>no</td></tr> +<tr><td>status</td><td>string</td><td>Network status: OK, ACCESS_DENIED, PORT_ERROR, etc.</td><td>no</td></tr> +<tr><td>type</td><td>string</td><td>Network type, currently PUBLIC or PRIVATE</td><td>no</td></tr> +<tr><td>mtu</td><td>integer</td><td>Ethernet MTU</td><td>no</td></tr> +<tr><td>dhcp</td><td>boolean</td><td>If true, DHCP may be used to obtain an IP address</td><td>no</td></tr> +<tr><td>bridge</td><td>boolean</td><td>If true, this node may bridge in other Ethernet devices</td><td>no</td></tr> +<tr><td>broadcastEnabled</td><td>boolean</td><td>Is Ethernet broadcast (ff:ff:ff:ff:ff:ff) allowed?</td><td>no</td></tr> +<tr><td>portError</td><td>integer</td><td>Error code (if any) returned by underlying OS "tap" driver</td><td>no</td></tr> +<tr><td>netconfRevision</td><td>integer</td><td>Network configuration revision ID</td><td>no</td></tr> +<tr><td>multicastSubscriptions</td><td>[string]</td><td>Multicast memberships as array of MAC/ADI tuples</td><td>no</td></tr> +<tr><td>assignedAddresses</td><td>[string]</td><td>ZeroTier-managed IP address assignments as array of IP/netmask bits tuples</td><td>no</td></tr> +<tr><td>portDeviceName</td><td>string</td><td>OS-specific network device name (if available)</td><td>no</td></tr> +</table> + +#### /peer + + * Purpose: Get all peers + * Methods: GET + * Returns: [ {object}, ... ] + +Getting /peer returns an array of peer objects for all current peers. See below for peer object format. + +#### /peer/\<address\> + + * Purpose: Get information about a peer + * Methods: GET + * Returns: { object } + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>address</td><td>string</td><td>10-digit hex ZeroTier address</td><td>no</td></tr> +<tr><td>lastUnicastFrame</td><td>integer</td><td>Time of last unicast frame in ms since epoch</td><td>no</td></tr> +<tr><td>lastMulticastFrame</td><td>integer</td><td>Time of last multicast frame in ms since epoch</td><td>no</td></tr> +<tr><td>versionMajor</td><td>integer</td><td>Major version of remote if known</td><td>no</td></tr> +<tr><td>versionMinor</td><td>integer</td><td>Minor version of remote if known</td><td>no</td></tr> +<tr><td>versionRev</td><td>integer</td><td>Revision of remote if known</td><td>no</td></tr> +<tr><td>version</td><td>string</td><td>Version in major.minor.rev format</td><td>no</td></tr> +<tr><td>latency</td><td>integer</td><td>Latency in milliseconds if known</td><td>no</td></tr> +<tr><td>role</td><td>string</td><td>LEAF, HUB, or SUPERNODE</td><td>no</td></tr> +<tr><td>paths</td><td>[object]</td><td>Array of path objects (see below)</td><td>no</td></tr> +</table> + +Path objects describe direct physical paths to peer. If no path objects are listed, peer is only reachable via indirect relay fallback. Path object format is: + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>address</td><td>string</td><td>Physical socket address e.g. IP/port for UDP</td><td>no</td></tr> +<tr><td>lastSend</td><td>integer</td><td>Last send via this path in ms since epoch</td><td>no</td></tr> +<tr><td>lastReceive</td><td>integer</td><td>Last receive via this path in ms since epoch</td><td>no</td></tr> +<tr><td>fixed</td><td>boolean</td><td>If true, this is a statically-defined "fixed" path</td><td>no</td></tr> +<tr><td>preferred</td><td>boolean</td><td>If true, this is the current preferred path</td><td>no</td></tr> +</table> + +### Network Controller API + +If ZeroTier One was built with *ZT\_ENABLE\_NETWORK\_CONTROLLER* defined, the following API paths are available. Otherwise these paths will return 404. + +#### /controller + + * Purpose: Check for controller function and return controller status + * Methods: GET + * Returns: { object } + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>controller</td><td>boolean</td><td>Always 'true' if controller is running</td><td>no</td></tr> +<tr><td>apiVersion</td><td>integer</td><td>JSON API version, currently 1</td><td>no</td></tr> +<tr><td>clock</td><td>integer</td><td>Controller system clock in ms since epoch</td><td>no</td></tr> +</table> + +#### /controller/network + + * Purpose: List all networks hosted by this controller + * Methods: GET + * Returns: [ string, ... ] + +This returns an array of 16-digit hexadecimal network IDs. Unlike /network under the top-level API, it does not dump full network information for all networks as this may be quite large for a large controller. + +#### /controller/network/\<network ID\> + + * Purpose: Create, configure, and delete hosted networks + * Methods: GET, POST, DELETE + * Returns: { object } + +DELETE for networks is final. Don't do this unless you really mean it! + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>nwid</td><td>string</td><td>16-digit hex network ID</td><td>no</td></tr> +<tr><td>name</td><td>string</td><td>Short network name (max: 127 chars)</td><td>yes</td></tr> +<tr><td>private</td><td>boolean</td><td>False if public network, true for access control</td><td>yes</td></tr> +<tr><td>enableBroadcast</td><td>boolean</td><td>True to allow Ethernet broadcast (ff:ff:ff:ff:ff:ff)</td><td>yes</td></tr> +<tr><td>allowPassiveBridging</td><td>boolean</td><td>True to allow any member to bridge (experimental!)</td><td>yes</td></tr> +<tr><td>v4AssignMode</td><td>string</td><td>'none', 'zt', or 'dhcp' (see below)</td><td>yes</td></tr> +<tr><td>v6AssignMode</td><td>string</td><td>'none', 'zt', or 'dhcp' (see below)</td><td>yes</td></tr> +<tr><td>multicastLimit</td><td>integer</td><td>Maximum number of multicast recipients per multicast/broadcast address</td><td>yes</td></tr> +<tr><td>creationTime</td><td>integer</td><td>Time network was created in ms since epoch</td><td>no</td></tr> +<tr><td>revision</td><td>integer</td><td>Network config revision number</td><td>no</td></tr> +<tr><td>members</td><td>[string]</td><td>Array of ZeroTier addresses of network members</td><td>no</td></tr> +<tr><td>relays</td><td>[object]</td><td>Array of network-specific relay nodes (see below)</td><td>yes</td></tr> +<tr><td>ipAssignmentPools</td><td>[object]</td><td>Array of IP auto-assignment pools for 'zt' assignment mode</td><td>yes</td></tr> +<tr><td>rules</td><td>[object]</td><td>Array of network flow rules (see below)</td><td>yes</td></tr> +</table> + +The network member list includes both authorized and unauthorized members. DELETE unauthorized members to remove them from the list. + +Relays, IP assignment pools, and rules are edited via direct POSTs to the network object. New values replace all previous values. + +**Relay object format:** + +Relay objects define network-specific preferred relay nodes. Traffic to peers on this network will preferentially use these relays if they are available, and otherwise will fall back to the global supernode infrastructure. + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr> +<tr><td>address</td><td>string</td><td>10-digit ZeroTier address of relay node</td></tr> +<tr><td>phyAddress</td><td>string</td><td>Fixed path address in IP/port format e.g. 192.168.1.1/9993</td></tr> +</table> + +**IP assignment pool object format:** + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr> +<tr><td>network</td><td>string</td><td>IP network e.g. 192.168.0.0</td></tr> +<tr><td>netmaskBits</td><td>integer</td><td>IP network netmask bits e.g. 16 for 255.255.0.0</td></tr> +</table> + +**Rule object format:** + + * **Note**: at the moment, <u>only rules specifying allowed Ethernet types are used</u>. The database supports a richer rule set, but this is not implemented yet in the client. <u>Other types of rules will have no effect</u> (yet). + +Rules are matched in order of ruleId. If no rules match, the default action is 'drop'. To allow all traffic, create a single rule with all *null* fields and an action of 'accept'. + +Rule object fields can be *null*, in which case they are omitted from the object. A null field indicates "no match on this criteria." + +IP related fields apply only to Ethernet frames of type IPv4 or IPV6. Otherwise they are ignored. + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr> +<tr><td>ruleId</td><td>integer</td><td>User-defined rule ID and sort order</td></tr> +<tr><td>nodeId</td><td>string</td><td>10-digit hex ZeroTier address of node (a.k.a. "port on switch")</td></tr> +<tr><td>vlanId</td><td>integer</td><td>Ethernet VLAN ID</td></tr> +<tr><td>vlanPcp</td><td>integer</td><td>Ethernet VLAN priority code point (PCP) ID</td></tr> +<tr><td>etherType</td><td>integer</td><td>Ethernet frame type</td></tr> +<tr><td>macSource</td><td>string</td><td>Ethernet source MAC address</td></tr> +<tr><td>macDest</td><td>string</td><td>Ethernet destination MAC address</td></tr> +<tr><td>ipSource</td><td>string</td><td>Source IP address</td></tr> +<tr><td>ipDest</td><td>string</td><td>Destination IP address</td></tr> +<tr><td>ipTos</td><td>integer</td><td>IP TOS field</td></tr> +<tr><td>ipProtocol</td><td>integer</td><td>IP protocol</td></tr> +<tr><td>ipSourcePort</td><td>integer</td><td>IP source port</td></tr> +<tr><td>ipDestPort</td><td>integer</td><td>IP destination port</td></tr> +<tr><td>action</td><td>string</td><td>Rule action: accept, drop, etc.</td></tr> +</table> + +#### /controller/network/\<network ID\>/member/\<address\> + + * Purpose: Create, authorize, or remove a network member + * Methods: GET, POST, DELETE + * Returns: { object } + +<table> +<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr> +<tr><td>nwid</td><td>string</td><td>16-digit hex network ID</td><td>no</td></tr> +<tr><td>address</td><td>string</td><td>10-digit hex ZeroTier address</td><td>no</td></tr> +<tr><td>authorized</td><td>boolean</td><td>Is member authorized?</td><td>yes</td></tr> +<tr><td>activeBridge</td><td>boolean</td><td>This member is an active network bridge</td><td>yes</td></tr> +<tr><td>lastAt</td><td>string</td><td>Socket address (e.g. IP/port) where member was last seen</td><td>no</td></tr> +<tr><td>lastSeen</td><td>integer</td><td>Timestamp of member's last request in ms since epoch</td><td>no</td></tr> +<tr><td>firstSeen</td><td>integer</td><td>Timestamp member was first seen in ms since epoch</td><td>no</td></tr> +<tr><td>identity</td><td>string</td><td>Full ZeroTier identity of member</td><td>no</td></tr> +<tr><td>ipAssignments</td><td>[string]</td><td>Array of IP/bits IP assignments</td><td>yes</td></tr> +</table> |