Merge branch 'adamierymenko-dev' into android-jni

This commit is contained in:
Grant Limberg 2015-05-16 14:35:35 -07:00
commit d0935f667f
16 changed files with 608 additions and 175 deletions

View File

@ -13,4 +13,6 @@ endif
ifeq ($(OSTYPE),FreeBSD)
include make-freebsd.mk
endif
ifeq ($(OSTYPE),OpenBSD)
include make-freebsd.mk
endif

View File

@ -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:

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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"

View File

@ -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)
);

View File

@ -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

View File

@ -60,7 +60,7 @@
#include <endian.h>
#endif
#ifdef __FreeBSD__
#if defined(__FreeBSD__) || defined(__OpenBSD__)
#ifndef __UNIX_LIKE__
#define __UNIX_LIKE__
#endif

View File

@ -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)";
}

View File

@ -98,6 +98,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.
*
@ -114,13 +125,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
*/
@ -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;

View File

@ -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__

View File

@ -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 (

View File

@ -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)) {

View File

@ -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())

View File

@ -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)

241
service/README.md Normal file
View File

@ -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>