Fixes to control plane, API, eliminate problematic inheritance pattern, and start on a NodeJS class for talking to the network controller.

This commit is contained in:
Adam Ierymenko 2015-05-16 16:09:28 -07:00
parent 27c5f04d68
commit a187d290f1
11 changed files with 109 additions and 143 deletions

1
.gitignore vendored
View File

@ -43,3 +43,4 @@
/ui/.module-cache
/windows/WebUIWrapper/bin
/windows/WebUIWrapper/obj
node_modules

View File

@ -560,12 +560,9 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
std::string &responseContentType)
{
char json[16384];
if (path.empty())
return 404;
Mutex::Lock _l(_lock);
if (path[0] == "network") {
if ((path.size() > 0)&&(path[0] == "network")) {
if ((path.size() >= 2)&&(path[1].length() == 16)) {
uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
@ -584,15 +581,15 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
if (sqlite3_step(_sGetMember2) == SQLITE_ROW) {
Utils::snprintf(json,sizeof(json),
"{\n"
"\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: [",
"\t\"nwid\": \"%s\",\n"
"\t\"address\": \"%s\",\n"
"\t\"authorized\": %s,\n"
"\t\"activeBridge\": %s,\n"
"\t\"lastAt\": \"%s\",\n"
"\t\"lastSeen\": %llu,\n"
"\t\"firstSeen\": %llu,\n"
"\t\"identity\": \"%s\",\n"
"\t\"ipAssignments\": [",
nwids,
addrs,
(sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false",
@ -651,18 +648,18 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: result = "INTERNAL_SERVER_ERROR"; break;
default: result = "(unrecognized result code)"; break;
}
responseBody.append(",\n\tnetconf: \"");
responseBody.append(",\n\t\"netconf\": \"");
responseBody.append(_jsonEscape(netconf.toString().c_str()));
responseBody.append("\",\n\tnetconfResult: \"");
responseBody.append("\",\n\t\"netconfResult\": \"");
responseBody.append(result);
responseBody.append("\",\n\tnetconfResultMessage: \"");
responseBody.append("\",\n\t\"netconfResultMessage\": \"");
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\"");
responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"invalid member or signing identity\"");
}
} catch ( ... ) {
responseBody.append(",\n\tnetconf: \"\",\n\tnetconfResult: \"INTERNAL_SERVER_ERROR\",\n\tnetconfResultMessage: \"unexpected exception\"");
responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"unexpected exception\"");
}
}
@ -679,17 +676,17 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
Utils::snprintf(json,sizeof(json),
"{\n"
"\tnwid: \"%s\",\n"
"\tname: \"%s\",\n"
"\tprivate: %s,\n"
"\tenableBroadcast: %s,\n"
"\tallowPassiveBridging: %s,\n"
"\tv4AssignMode: \"%s\",\n"
"\tv6AssignMode: \"%s\",\n"
"\tmulticastLimit: %d,\n"
"\tcreationTime: %llu,\n",
"\trevision: %llu,\n"
"\amembers: [",
"\t\"nwid\": \"%s\",\n"
"\t\"name\": \"%s\",\n"
"\t\"private\": %s,\n"
"\t\"enableBroadcast\": %s,\n"
"\t\"allowPassiveBridging\": %s,\n"
"\t\"v4AssignMode\": \"%s\",\n"
"\t\"v6AssignMode\": \"%s\",\n"
"\t\"multicastLimit\": %d,\n"
"\t\"creationTime\": %llu,\n",
"\t\"revision\": %llu,\n"
"\a\"members\": [",
nwids,
_jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(),
(sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false",
@ -713,7 +710,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
responseBody.push_back('"');
firstMember = false;
}
responseBody.append("],\n\trelays: [");
responseBody.append("],\n\t\"relays\": [");
sqlite3_reset(_sGetRelays);
sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC);
@ -721,13 +718,13 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t");
firstRelay = false;
responseBody.append("{address:\"");
responseBody.append("{\"address\":\"");
responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0));
responseBody.append("\",phyAddress:\"");
responseBody.append("\",\"phyAddress\":\"");
responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1)));
responseBody.append("\"}");
}
responseBody.append("],\n\tipAssignmentPools: [");
responseBody.append("],\n\t\"ipAssignmentPools\": [");
sqlite3_reset(_sGetIpAssignmentPools2);
sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC);
@ -736,69 +733,69 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t");
firstIpAssignmentPool = false;
InetAddress ipp((const void *)sqlite3_column_blob(_sGetIpAssignmentPools2,0),(sqlite3_column_int(_sGetIpAssignmentPools2,2) == 6) ? 16 : 4,(unsigned int)sqlite3_column_int(_sGetIpAssignmentPools2,1));
Utils::snprintf(json,sizeof(json),"{network:\"%s\",netmaskBits:%u}",
Utils::snprintf(json,sizeof(json),"{\"network\":\"%s\",\"netmaskBits\":%u}",
_jsonEscape(ipp.toIpString()).c_str(),
ipp.netmaskBits());
responseBody.append(json);
}
responseBody.append("],\n\trules: [");
responseBody.append("],\n\t\"rules\": [");
sqlite3_reset(_sListRules);
sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC);
bool firstRule = true;
while (sqlite3_step(_sListRules) == SQLITE_ROW) {
responseBody.append(firstRule ? "\n\t{\n" : ",{\n");
Utils::snprintf(json,sizeof(json),"\t\truleId: %lld,\n",sqlite3_column_int64(_sListRules,0));
Utils::snprintf(json,sizeof(json),"\t\t\"ruleId\": %lld,\n",sqlite3_column_int64(_sListRules,0));
responseBody.append(json);
if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tnodeId: \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1));
Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1));
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tvlanId: %d,\n",sqlite3_column_int(_sListRules,2));
Utils::snprintf(json,sizeof(json),"\t\t\"vlanId\": %d,\n",sqlite3_column_int(_sListRules,2));
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tvlanPcp: %d,\n",sqlite3_column_int(_sListRules,3));
Utils::snprintf(json,sizeof(json),"\t\t\"vlanPcp\": %d,\n",sqlite3_column_int(_sListRules,3));
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tetherType: %d,\n",sqlite3_column_int(_sListRules,4));
Utils::snprintf(json,sizeof(json),"\t\t\"etherType\": %d,\n",sqlite3_column_int(_sListRules,4));
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tmacSource: \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,5)).toString().c_str());
Utils::snprintf(json,sizeof(json),"\t\t\"macSource\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,5)).toString().c_str());
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tmacDest: \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,6)).toString().c_str());
Utils::snprintf(json,sizeof(json),"\t\t\"macDest\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,6)).toString().c_str());
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tipSource: \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,7)).c_str());
Utils::snprintf(json,sizeof(json),"\t\t\"ipSource\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,7)).c_str());
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tipDest: \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,8)).c_str());
Utils::snprintf(json,sizeof(json),"\t\t\"ipDest\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,8)).c_str());
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tipTos: %d,\n",sqlite3_column_int(_sListRules,9));
Utils::snprintf(json,sizeof(json),"\t\t\"ipTos\": %d,\n",sqlite3_column_int(_sListRules,9));
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tipProtocol: %d,\n",sqlite3_column_int(_sListRules,10));
Utils::snprintf(json,sizeof(json),"\t\t\"ipProtocol\": %d,\n",sqlite3_column_int(_sListRules,10));
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tipSourcePort: %d,\n",sqlite3_column_int(_sListRules,11));
Utils::snprintf(json,sizeof(json),"\t\t\"ipSourcePort\": %d,\n",sqlite3_column_int(_sListRules,11));
responseBody.append(json);
}
if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) {
Utils::snprintf(json,sizeof(json),"\t\tipDestPort: %d,\n",sqlite3_column_int(_sListRules,12));
Utils::snprintf(json,sizeof(json),"\t\t\"ipDestPort\": %d,\n",sqlite3_column_int(_sListRules,12));
responseBody.append(json);
}
responseBody.append("\t\taction: \"");
responseBody.append("\t\t\"action\": \"");
responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sListRules,13)));
responseBody.append("\"\n\t}");
}
@ -828,7 +825,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
} 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());
Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now());
responseBody = json;
responseContentType = "applicaiton/json";
return 200;

View File

@ -40,17 +40,14 @@
#include "../node/NetworkController.hpp"
#include "../node/Mutex.hpp"
#include "../service/ControlPlaneSubsystem.hpp"
namespace ZeroTier {
class SqliteNetworkController : public NetworkController,public ControlPlaneSubsystem
class SqliteNetworkController : public NetworkController
{
public:
SqliteNetworkController(const char *dbPath);
virtual ~SqliteNetworkController();
// NetworkController
virtual NetworkController::ResultCode doNetworkConfigRequest(
const InetAddress &fromAddr,
const Identity &signingId,
@ -60,22 +57,21 @@ public:
uint64_t haveRevision,
Dictionary &netconf);
// ControlPlaneSubsystem
virtual unsigned int handleControlPlaneHttpGET(
unsigned int handleControlPlaneHttpGET(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,
const std::map<std::string,std::string> &headers,
const std::string &body,
std::string &responseBody,
std::string &responseContentType);
virtual unsigned int handleControlPlaneHttpPOST(
unsigned int handleControlPlaneHttpPOST(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,
const std::map<std::string,std::string> &headers,
const std::string &body,
std::string &responseBody,
std::string &responseContentType);
virtual unsigned int handleControlPlaneHttpDELETE(
unsigned int handleControlPlaneHttpDELETE(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,
const std::map<std::string,std::string> &headers,

View File

@ -0,0 +1,26 @@
'use strict'
var request = require('request');
function ZT1ControllerClient(url,authToken)
{
this.url = url;
this.authToken = authToken;
}
ZT1ControllerClient.prototype.status = function(callback)
{
request({
url: this.url + 'controller',
method: 'GET',
headers: {
'X-ZT1-Auth': this.authToken
}
},function(error,response,body) {
if ((error)||(response.statusCode !== 200))
return callback(error,{});
return callback(null,JSON.parse(body));
});
};
exports.ZT1ControllerClient = ZT1ControllerClient;

View File

@ -0,0 +1,14 @@
{
"name": "zt1-controller-client",
"version": "1.0.0",
"description": "ZeroTier One network controller client for NodeJS",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "ZeroTier, Inc.",
"license": "BSD",
"dependencies": {
"request": "^2.55.0"
}
}

View File

@ -0,0 +1,7 @@
var ZT1ControllerClient = require('./index.js').ZT1ControllerClient;
var zt1c = new ZT1ControllerClient('http://127.0.0.1:9993/','5d6181b71fae2684f9cc64ed');
zt1c.status(function(err,status) {
console.log(status);
});

View File

@ -26,7 +26,6 @@
*/
#include "ControlPlane.hpp"
#include "ControlPlaneSubsystem.hpp"
#include "OneService.hpp"
#include "../version.h"
@ -34,6 +33,8 @@
#include "../ext/http-parser/http_parser.h"
#include "../controller/SqliteNetworkController.hpp"
#include "../node/InetAddress.hpp"
#include "../node/Node.hpp"
#include "../node/Utils.hpp"
@ -444,7 +445,7 @@ unsigned int ControlPlane::handleRequest(
responseContentType = "text/plain";
scode = 200;
} else {
std::map<std::string,ControlPlaneSubsystem *>::const_iterator ss(_subsystems.find(ps[0]));
std::map<std::string,SqliteNetworkController *>::const_iterator ss(_subsystems.find(ps[0]));
if (ss != _subsystems.end())
scode = ss->second->handleControlPlaneHttpGET(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
else scode = 404;
@ -477,7 +478,7 @@ unsigned int ControlPlane::handleRequest(
} else scode = 500;
}
} else {
std::map<std::string,ControlPlaneSubsystem *>::const_iterator ss(_subsystems.find(ps[0]));
std::map<std::string,SqliteNetworkController *>::const_iterator ss(_subsystems.find(ps[0]));
if (ss != _subsystems.end())
scode = ss->second->handleControlPlaneHttpPOST(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
else scode = 404;
@ -509,7 +510,7 @@ unsigned int ControlPlane::handleRequest(
_node->freeQueryResult((void *)nws);
} else scode = 500;
} else {
std::map<std::string,ControlPlaneSubsystem *>::const_iterator ss(_subsystems.find(ps[0]));
std::map<std::string,SqliteNetworkController *>::const_iterator ss(_subsystems.find(ps[0]));
if (ss != _subsystems.end())
scode = ss->second->handleControlPlaneHttpDELETE(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType);
else scode = 404;

View File

@ -40,7 +40,7 @@ namespace ZeroTier {
class OneService;
class Node;
class ControlPlaneSubsystem;
class SqliteNetworkController;
struct InetAddress;
/**
@ -72,7 +72,7 @@ public:
* @param prefix First element in URI path
* @param subsys Object to call for results of GET and POST/PUT operations
*/
inline void mount(const char *prefix,ControlPlaneSubsystem *subsys)
inline void mount(const char *prefix,SqliteNetworkController *subsys)
{
Mutex::Lock _l(_lock);
_subsystems[std::string(prefix)] = subsys;
@ -104,7 +104,7 @@ private:
Node *const _node;
std::string _uiStaticPath;
std::set<std::string> _authTokens;
std::map<std::string,ControlPlaneSubsystem *> _subsystems;
std::map<std::string,SqliteNetworkController *> _subsystems;
Mutex _lock;
};

View File

@ -1,76 +0,0 @@
/*
* ZeroTier One - Network Virtualization Everywhere
* Copyright (C) 2011-2015 ZeroTier, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*
* ZeroTier may be used and distributed under the terms of the GPLv3, which
* are available at: http://www.gnu.org/licenses/gpl-3.0.html
*
* If you would like to embed ZeroTier into a commercial application or
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
#ifndef ZT_CONTROLPLANESUBSYSTEM_HPP
#define ZT_CONTROLPLANESUBSYSTEM_HPP
#include <map>
#include <vector>
#include <string>
namespace ZeroTier {
/**
* Base class for subsystems that can be mounted under the HTTP control plane
*
* Handlers should fill in responseBody and responseContentType and return
* a HTTP status code or 0 on other errors.
*/
class ControlPlaneSubsystem
{
public:
ControlPlaneSubsystem() {}
virtual ~ControlPlaneSubsystem() {}
virtual unsigned int handleControlPlaneHttpGET(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,
const std::map<std::string,std::string> &headers,
const std::string &body,
std::string &responseBody,
std::string &responseContentType) = 0;
virtual unsigned int handleControlPlaneHttpPOST(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,
const std::map<std::string,std::string> &headers,
const std::string &body,
std::string &responseBody,
std::string &responseContentType) = 0;
virtual unsigned int handleControlPlaneHttpDELETE(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,
const std::map<std::string,std::string> &headers,
const std::string &body,
std::string &responseBody,
std::string &responseContentType) = 0;
};
} // namespace ZeroTier
#endif

View File

@ -233,7 +233,7 @@ public:
_controlPlane = new ControlPlane(this,_node,(_homePath + ZT_PATH_SEPARATOR_S + "ui").c_str());
_controlPlane->addAuthToken(authToken.c_str());
if (_master)
_controlPlane->mount("controller",reinterpret_cast<ControlPlaneSubsystem *>(_master));
_controlPlane->mount("controller",reinterpret_cast<SqliteNetworkController *>(_master));
{ // Remember networks from previous session
std::vector<std::string> networksDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S + "networks.d").c_str()));

View File

@ -11,7 +11,7 @@ The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for
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.
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 *auth* URL parameter (e.g. '?auth=...') 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*.