From f693d4d0c8a06970bcd096df73471b4f32a544d8 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 15 May 2015 09:32:10 -0700 Subject: Network controller cleanup and an extra sanity check. --- controller/SqliteNetworkController.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'controller/SqliteNetworkController.cpp') diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index b9aebbb8..71978830 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -243,6 +243,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)); -- cgit v1.2.3 From 6d2376eb9cc7a3b0e909e0abb87bea5e7f42b717 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 15 May 2015 09:41:45 -0700 Subject: Controller API status message. --- controller/SqliteNetworkController.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'controller/SqliteNetworkController.cpp') diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index 71978830..d6f768a0 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -61,6 +61,9 @@ #define ZT_NETCONF_SQLITE_SCHEMA_VERSION 1 #define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "1" +// API version reported via JSON control plane +#define ZT_NETCONF_CONTROLLER_API_VERSION 1 + // Maximum age in ms for a cached netconf before we regenerate anyway (one hour) #define ZT_CACHED_NETCONF_MAX_AGE (60 * 60 * 1000) @@ -619,8 +622,8 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( if (sqlite3_step(_sGetMember2) == SQLITE_ROW) { Utils::snprintf(json,sizeof(json), "{\n" - "\taddress: \"%s\"" - "\tauthorized: %s," + "\taddress: \"%s\"\n" + "\tauthorized: %s,\n" "\tactiveBridge: %s,\n" "\tlastAt: \"%s\",\n" "\tlastSeen: %llu,\n" @@ -820,7 +823,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; } -- cgit v1.2.3 From e269846f84c1ecb4892659448ba1dfc97e6a4b42 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 15 May 2015 15:20:12 -0700 Subject: Netconf docs, add clock field to status, simplify netconf a bit by eliminating caching for now. We will re-add if it is needed. --- controller/SqliteNetworkController.cpp | 89 +++--------- controller/SqliteNetworkController.hpp | 2 - controller/schema.sql | 4 - service/ControlPlane.cpp | 6 +- service/README.md | 239 +++++++++++++++++++++++++++++++++ 5 files changed, 259 insertions(+), 81 deletions(-) create mode 100644 service/README.md (limited to 'controller/SqliteNetworkController.cpp') diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index d6f768a0..d9c9239b 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -64,9 +64,6 @@ // API version reported via JSON control plane #define ZT_NETCONF_CONTROLLER_API_VERSION 1 -// Maximum age in ms for a cached netconf before we regenerate anyway (one hour) -#define ZT_CACHED_NETCONF_MAX_AGE (60 * 60 * 1000) - namespace ZeroTier { namespace { @@ -96,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; }; @@ -156,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) @@ -170,10 +161,9 @@ 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,"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) @@ -205,7 +195,6 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sCreateNode); sqlite3_finalize(_sUpdateNode); sqlite3_finalize(_sUpdateNode2); - sqlite3_finalize(_sUpdateMemberClientReportedRevision); sqlite3_finalize(_sGetEtherTypesFromRuleTable); sqlite3_finalize(_sGetMulticastRates); sqlite3_finalize(_sGetActiveBridges); @@ -213,7 +202,6 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sGetIpAssignmentPools); sqlite3_finalize(_sCheckIfIpIsAllocated); sqlite3_finalize(_sAllocateIp); - sqlite3_finalize(_sCacheNetconf); sqlite3_finalize(_sGetRelays); sqlite3_finalize(_sListNetworks); sqlite3_finalize(_sListNetworkMembers); @@ -332,21 +320,13 @@ 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); @@ -364,32 +344,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); @@ -574,16 +537,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; @@ -622,7 +575,8 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( if (sqlite3_step(_sGetMember2) == SQLITE_ROW) { Utils::snprintf(json,sizeof(json), "{\n" - "\taddress: \"%s\"\n" + "\tnwid: \"%s\",\n" + "\taddress: \"%s\",\n" "\tauthorized: %s,\n" "\tactiveBridge: %s,\n" "\tlastAt: \"%s\",\n" @@ -630,6 +584,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( "\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", @@ -674,7 +629,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", @@ -691,23 +646,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: ["); diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp index c61829c3..593d3cd0 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,6 @@ private: sqlite3_stmt *_sGetIpAssignmentPools; sqlite3_stmt *_sCheckIfIpIsAllocated; sqlite3_stmt *_sAllocateIp; - sqlite3_stmt *_sCacheNetconf; sqlite3_stmt *_sGetRelays; sqlite3_stmt *_sListNetworks; sqlite3_stmt *_sListNetworkMembers; 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/service/ControlPlane.cpp b/service/ControlPlane.cpp index c92087f7..9173e483 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") { diff --git a/service/README.md b/service/README.md new file mode 100644 index 00000000..ff8b5fb7 --- /dev/null +++ b/service/README.md @@ -0,0 +1,239 @@ +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*. + +### JSON API + +The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for POST. + +Any JSON objects POSTed to the service are *extremely* type-sensitive due to the simple embedded C JSON parser that we use. If, for example, an integer field is quoted as a string, its contents may be ignored or an error 400 may be returned. Integer fields must be ASCII integers (no decimal point), and boolean fields must be *true* or *false*. + +Each request to the API 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 will be served without authentication but all other requests require it. In addition, a *jsonp* URL argument may be supplied to invoke JSONP response behavior. In this mode, JSON responses are passed into the function specified by the *jsonp* URL argument's value (e.g. '?jsonp=myJsonPHandler'). + +#### /status + + * Purpose: Get running node status and addressing info + * Methods: GET + * Returns: { object } + + + + + + + + + + + +
FieldTypeDescriptionWritable
addressstring10-digit hexadecimal ZeroTier address of this nodeno
publicIdentitystringFull public ZeroTier identity of this nodeno
onlinebooleanDoes this node appear to have upstream network access?no
versionMajorintegerZeroTier major versionno
versionMinorintegerZeroTier minor versionno
versionRevintegerZeroTier revisionno
versionstringVersion in major.minor.rev formatno
clockintegerNode system clock in ms since epochno
+ +#### /config + + * Purpose: Get or set local configuration + * Methods: GET, POST + * Returns: { object } + +No local configuration options are exposed yet. + + + +
FieldTypeDescriptionWritable
+ +#### /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/\ + + * 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. + + + + + + + + + + + + + + + + + +
FieldTypeDescriptionWritable
nwidstring16-digit hex network IDno
macstringEthernet MAC address of virtual network portno
namestringNetwork short name as configured on network controllerno
statusstringNetwork status: OK, ACCESS_DENIED, PORT_ERROR, etc.no
typestringNetwork type, currently PUBLIC or PRIVATEno
mtuintegerEthernet MTUno
dhcpbooleanIf true, DHCP may be used to obtain an IP addressno
bridgebooleanIf true, this node may bridge in other Ethernet devicesno
broadcastEnabledbooleanIs Ethernet broadcast (ff:ff:ff:ff:ff:ff) allowed?no
portErrorintegerError code (if any) returned by underlying OS "tap" driverno
netconfRevisionintegerNetwork configuration revision IDno
multicastSubscriptions[string]Multicast memberships as array of MAC/ADI tuplesno
assignedAddresses[string]ZeroTier-managed IP address assignments as array of IP/netmask bits tuplesno
portDeviceNamestringOS-specific network device name (if available)no
+ +#### /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/\ + + * Purpose: Get information about a peer + * Methods: GET + * Returns: { object } + + + + + + + + + + + + + +
FieldTypeDescriptionWritable
addressstring10-digit hex ZeroTier addressno
lastUnicastFrameintegerTime of last unicast frame in ms since epochno
lastMulticastFrameintegerTime of last multicast frame in ms since epochno
versionMajorintegerMajor version of remote if knownno
versionMinorintegerMinor version of remote if knownno
versionRevintegerRevision of remote if knownno
versionstringVersion in major.minor.rev formatno
latencyintegerLatency in milliseconds if knownno
rolestringLEAF, HUB, or SUPERNODEno
paths[object]Array of path objects (see below)no
+ +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: + + + + + + + + +
FieldTypeDescriptionWritable
addressstringPhysical socket address e.g. IP/port for UDPno
lastSendintegerLast send via this path in ms since epochno
lastReceiveintegerLast receive via this path in ms since epochno
fixedbooleanIf true, this is a statically-defined "fixed" pathno
preferredbooleanIf true, this is the current preferred pathno
+ +### 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 } + + + + + + +
FieldTypeDescriptionWritable
controllerbooleanAlways 'true' if controller is runningno
apiVersionintegerJSON API version, currently 1no
clockintegerController system clock in ms since epochno
+ +#### /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/\ + + * 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! + + + + + + + + + + + + + + + + + +
FieldTypeDescriptionWritable
nwidstring16-digit hex network IDno
namestringShort network name (max: 127 chars)yes
privatebooleanFalse if public network, true for access controlyes
enableBroadcastbooleanTrue to allow Ethernet broadcast (ff:ff:ff:ff:ff:ff)yes
allowPassiveBridgingbooleanTrue to allow any member to bridge (experimental!)yes
v4AssignModestring'none', 'zt', or 'dhcp' (see below)yes
v6AssignModestring'none', 'zt', or 'dhcp' (see below)yes
multicastLimitintegerMaximum number of multicast recipients per multicast/broadcast addressyes
creationTimeintegerTime network was created in ms since epochno
revisionintegerNetwork config revision numberno
members[string]Array of ZeroTier addresses of network membersno
relays[object]Array of network-specific relay nodes (see below)yes
ipAssignmentPools[object]Array of IP auto-assignment pools for 'zt' assignment modeyes
rules[object]Array of network flow rules (see below)yes
+ +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. + + + + + +
FieldTypeDescription
addressstring10-digit ZeroTier address of relay node
phyAddressstringFixed path address in IP/port format e.g. 192.168.1.1/9993
+ +**IP assignment pool object format:** + + + + + +
FieldTypeDescription
networkstringIP network e.g. 192.168.0.0
netmaskBitsintegerIP network netmask bits e.g. 16 for 255.255.0.0
+ +**Rule object format:** + + * **Note**: at the moment, only rules specifying allowed Ethernet types are used. The database supports a richer rule set, but this is not implemented yet in the client. Other types of rules will have no effect (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. + + + + + + + + + + + + + + + + + +
FieldTypeDescription
ruleIdintegerUser-defined rule ID and sort order
nodeIdstring10-digit hex ZeroTier address of node (a.k.a. "port on switch")
vlanIdintegerEthernet VLAN ID
vlanPcpintegerEthernet VLAN priority code point (PCP) ID
etherTypeintegerEthernet frame type
macSourcestringEthernet source MAC address
macDeststringEthernet destination MAC address
ipSourcestringSource IP address
ipDeststringDestination IP address
ipTosintegerIP TOS field
ipProtocolintegerIP protocol
ipSourcePortintegerIP source port
ipDestPortintegerIP destination port
actionstringRule action: accept, drop, etc.
+ +#### /controller/network/\/member/\ + + * Purpose: Create, authorize, or remove a network member + * Methods: GET, POST, DELETE + * Returns: { object } + + + + + + + + + + + + +
FieldTypeDescriptionWritable
nwidstring16-digit hex network IDno
addressstring10-digit hex ZeroTier addressno
authorizedbooleanIs member authorized?yes
activeBridgebooleanThis member is an active network bridgeyes
lastAtstringSocket address (e.g. IP/port) where member was last seenno
lastSeenintegerTimestamp of member's last request in ms since epochno
firstSeenintegerTimestamp member was first seen in ms since epochno
identitystringFull ZeroTier identity of memberno
ipAssignments[string]Array of IP/bits IP assignmentsyes
-- cgit v1.2.3 From 78769900a910326d799a581cc914ba282b59de25 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Sat, 16 May 2015 12:50:42 -0700 Subject: More network controller cleanup, and some features to permit scripted testing. --- controller/SqliteNetworkController.cpp | 174 ++++++++++++++++++++++++++++++--- controller/SqliteNetworkController.hpp | 2 + service/README.md | 10 +- 3 files changed, 166 insertions(+), 20 deletions(-) (limited to 'controller/SqliteNetworkController.cpp') diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index d9c9239b..16f8e5e9 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -161,6 +161,7 @@ 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,"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 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) @@ -178,6 +179,7 @@ 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_close(_db); throw std::runtime_error("SqliteNetworkController unable to initialize one or more prepared statements"); @@ -202,6 +204,7 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sGetIpAssignmentPools); sqlite3_finalize(_sCheckIfIpIsAllocated); sqlite3_finalize(_sAllocateIp); + sqlite3_finalize(_sDeleteIpAllocations); sqlite3_finalize(_sGetRelays); sqlite3_finalize(_sListNetworks); sqlite3_finalize(_sListNetworkMembers); @@ -219,6 +222,7 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork); sqlite3_finalize(_sDeleteRulesForNetwork); sqlite3_finalize(_sCreateIpAssignmentPool); + sqlite3_finalize(_sUpdateMemberField); sqlite3_close(_db); } } @@ -333,10 +337,11 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co 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 @@ -491,16 +496,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) { @@ -606,7 +615,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::const_iterator memids(urlArgs.find("memberIdentity")); + std::map::const_iterator sigids(urlArgs.find("signingIdentity")); + std::map::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; @@ -815,6 +875,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;ku.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;kku.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 @@ -841,43 +985,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 nodeIdToPhyAddress; @@ -891,7 +1035,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)) @@ -909,7 +1052,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 pools; @@ -923,7 +1066,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)) { @@ -947,7 +1089,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); @@ -1038,7 +1180,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( } } } - } else return 400; + } } } } diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp index 593d3cd0..d2fba0b6 100644 --- a/controller/SqliteNetworkController.hpp +++ b/controller/SqliteNetworkController.hpp @@ -101,6 +101,7 @@ private: sqlite3_stmt *_sGetIpAssignmentPools; sqlite3_stmt *_sCheckIfIpIsAllocated; sqlite3_stmt *_sAllocateIp; + sqlite3_stmt *_sDeleteIpAllocations; sqlite3_stmt *_sGetRelays; sqlite3_stmt *_sListNetworks; sqlite3_stmt *_sListNetworkMembers; @@ -118,6 +119,7 @@ private: sqlite3_stmt *_sDeleteIpAssignmentPoolsForNetwork; sqlite3_stmt *_sDeleteRulesForNetwork; sqlite3_stmt *_sCreateIpAssignmentPool; + sqlite3_stmt *_sUpdateMemberField; Mutex _lock; }; diff --git a/service/README.md b/service/README.md index ff8b5fb7..c347931f 100644 --- a/service/README.md +++ b/service/README.md @@ -5,13 +5,15 @@ This is the common background service implementation for ZeroTier One, the VPN-l 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*. -### JSON API +### Network Virtualization Service API -The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for POST. +The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for POST. Other methods including HEAD are not supported. -Any JSON objects POSTed to the service are *extremely* type-sensitive due to the simple embedded C JSON parser that we use. If, for example, an integer field is quoted as a string, its contents may be ignored or an error 400 may be returned. Integer fields must be ASCII integers (no decimal point), and boolean fields must be *true* or *false*. +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. -Each request to the API 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 will be served without authentication but all other requests require it. In addition, a *jsonp* URL argument may be supplied to invoke JSONP response behavior. In this mode, JSON responses are passed into the function specified by the *jsonp* URL argument's value (e.g. '?jsonp=myJsonPHandler'). +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 -- cgit v1.2.3 From 0bb92715f4f25cce00abeead44f07ba17ff7a529 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Sat, 16 May 2015 13:42:53 -0700 Subject: DELETE function in network controller JSON API, and a newIdentity convenience request in ControlPlane for scripted testing. --- controller/SqliteNetworkController.cpp | 43 ++++++++++++++++++++++++++++++++++ controller/SqliteNetworkController.hpp | 2 ++ service/ControlPlane.cpp | 8 +++++++ 3 files changed, 53 insertions(+) (limited to 'controller/SqliteNetworkController.cpp') diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index 16f8e5e9..92b8c32c 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -180,6 +180,8 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) : ||(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"); @@ -223,6 +225,7 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sDeleteRulesForNetwork); sqlite3_finalize(_sCreateIpAssignmentPool); sqlite3_finalize(_sUpdateMemberField); + sqlite3_finalize(_sDeleteNetworkAndRelated); sqlite3_close(_db); } } @@ -1214,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 d2fba0b6..c5d4c51a 100644 --- a/controller/SqliteNetworkController.hpp +++ b/controller/SqliteNetworkController.hpp @@ -120,6 +120,8 @@ private: sqlite3_stmt *_sDeleteRulesForNetwork; sqlite3_stmt *_sCreateIpAssignmentPool; sqlite3_stmt *_sUpdateMemberField; + sqlite3_stmt *_sDeleteMember; + sqlite3_stmt *_sDeleteNetworkAndRelated; Mutex _lock; }; diff --git a/service/ControlPlane.cpp b/service/ControlPlane.cpp index 9173e483..69c5d48d 100644 --- a/service/ControlPlane.cpp +++ b/service/ControlPlane.cpp @@ -435,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::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) -- cgit v1.2.3