summaryrefslogtreecommitdiff
path: root/controller
diff options
context:
space:
mode:
authorGrant Limberg <[email protected]>2023-04-28 11:03:28 -0700
committerGitHub <[email protected]>2023-04-28 11:03:28 -0700
commite5fc89821f1ef46de6f4aa9664d2207e5745553b (patch)
treea819d05f9d9423e02b4752e53ed6428402f15cb9 /controller
parent411e54023aa0b83d0c79ef91bbe2ddaec9422cd2 (diff)
use cpp-httplib for HTTP control plane (#1979)
refactored the old control plane code to use [cpp-httplib](https://github.com/yhirose/cpp-httplib) instead of a hand rolled HTTP server. Makes the control plane code much more legible. Also no longer randomly stops responding.
Diffstat (limited to 'controller')
-rw-r--r--controller/DBMirrorSet.cpp9
-rw-r--r--controller/EmbeddedNetworkController.cpp1004
-rw-r--r--controller/EmbeddedNetworkController.hpp28
3 files changed, 483 insertions, 558 deletions
diff --git a/controller/DBMirrorSet.cpp b/controller/DBMirrorSet.cpp
index fd7f32a2..5d64ebf0 100644
--- a/controller/DBMirrorSet.cpp
+++ b/controller/DBMirrorSet.cpp
@@ -15,9 +15,12 @@
namespace ZeroTier {
-DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) :
- _listener(listener),
- _running(true)
+DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener)
+ : _listener(listener)
+ , _running(true)
+ , _syncCheckerThread()
+ , _dbs()
+ , _dbs_l()
{
_syncCheckerThread = std::thread([this]() {
for(;;) {
diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp
index 2ccc16b8..1d5cee01 100644
--- a/controller/EmbeddedNetworkController.cpp
+++ b/controller/EmbeddedNetworkController.cpp
@@ -16,6 +16,7 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#include <type_traits>
#ifndef _WIN32
#include <sys/time.h>
@@ -553,593 +554,528 @@ void EmbeddedNetworkController::request(
_queue.post(qe);
}
-unsigned int EmbeddedNetworkController::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)
+std::string EmbeddedNetworkController::networkUpdateFromPostData(uint64_t networkID, const std::string &body)
{
- if ((!path.empty())&&(path[0] == "network")) {
+ json b = OSUtils::jsonParse(body);
- if ((path.size() >= 2)&&(path[1].length() == 16)) {
- const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
- json network;
- if (!_db.get(nwid,network))
- return 404;
+ char nwids[24];
+ OSUtils::ztsnprintf(nwids, sizeof(nwids), "%.16llx", networkID);
+
+ json network;
+ _db.get(networkID, network);
+ DB::initNetwork(network);
+ if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],"");
+ if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true);
+ if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false);
+ if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL);
+ if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU);
+
+ if (b.count("remoteTraceTarget")) {
+ const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],""));
+ if (rtt.length() == 10) {
+ network["remoteTraceTarget"] = rtt;
+ } else {
+ network["remoteTraceTarget"] = json();
+ }
+ }
+ if (b.count("remoteTraceLevel")) network["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL);
+
+ if (b.count("v4AssignMode")) {
+ json nv4m;
+ json &v4m = b["v4AssignMode"];
+ if (v4m.is_string()) { // backward compatibility
+ nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt");
+ } else if (v4m.is_object()) {
+ nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false);
+ } else nv4m["zt"] = false;
+ network["v4AssignMode"] = nv4m;
+ }
- if (path.size() >= 3) {
+ if (b.count("v6AssignMode")) {
+ json nv6m;
+ json &v6m = b["v6AssignMode"];
+ if (!nv6m.is_object()) nv6m = json::object();
+ if (v6m.is_string()) { // backward compatibility
+ std::vector<std::string> v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","",""));
+ std::sort(v6ms.begin(),v6ms.end());
+ v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end());
+ nv6m["rfc4193"] = false;
+ nv6m["zt"] = false;
+ nv6m["6plane"] = false;
+ for(std::vector<std::string>::iterator i(v6ms.begin());i!=v6ms.end();++i) {
+ if (*i == "rfc4193")
+ nv6m["rfc4193"] = true;
+ else if (*i == "zt")
+ nv6m["zt"] = true;
+ else if (*i == "6plane")
+ nv6m["6plane"] = true;
+ }
+ } else if (v6m.is_object()) {
+ if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false);
+ if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false);
+ if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false);
+ } else {
+ nv6m["rfc4193"] = false;
+ nv6m["zt"] = false;
+ nv6m["6plane"] = false;
+ }
+ network["v6AssignMode"] = nv6m;
+ }
- if (path[2] == "member") {
+ if (b.count("routes")) {
+ json &rts = b["routes"];
+ if (rts.is_array()) {
+ json nrts = json::array();
+ for(unsigned long i=0;i<rts.size();++i) {
+ json &rt = rts[i];
+ if (rt.is_object()) {
+ json &target = rt["target"];
+ json &via = rt["via"];
+ if (target.is_string()) {
+ InetAddress t(target.get<std::string>().c_str());
+ InetAddress v;
+ if (via.is_string()) v.fromString(via.get<std::string>().c_str());
+ if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) {
+ json tmp;
+ char tmp2[64];
+ tmp["target"] = t.toString(tmp2);
+ if (v.ss_family == t.ss_family)
+ tmp["via"] = v.toIpString(tmp2);
+ else tmp["via"] = json();
+ nrts.push_back(tmp);
+ if (nrts.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
+ }
+ }
+ }
+ }
+ network["routes"] = nrts;
+ }
+ }
+
+ if (b.count("ipAssignmentPools")) {
+ json &ipp = b["ipAssignmentPools"];
+ if (ipp.is_array()) {
+ json nipp = json::array();
+ for(unsigned long i=0;i<ipp.size();++i) {
+ json &ip = ipp[i];
+ if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("ipRangeEnd"))) {
+ InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],"").c_str());
+ InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],"").c_str());
+ if ( ((f.ss_family == AF_INET)||(f.ss_family == AF_INET6)) && (f.ss_family == t.ss_family) ) {
+ json tmp = json::object();
+ char tmp2[64];
+ tmp["ipRangeStart"] = f.toIpString(tmp2);
+ tmp["ipRangeEnd"] = t.toIpString(tmp2);
+ nipp.push_back(tmp);
+ if (nipp.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
+ }
+ }
+ }
+ network["ipAssignmentPools"] = nipp;
+ }
+ }
- if (path.size() >= 4) {
- // Get member
+ if (b.count("rules")) {
+ json &rules = b["rules"];
+ if (rules.is_array()) {
+ json nrules = json::array();
+ for(unsigned long i=0;i<rules.size();++i) {
+ json &rule = rules[i];
+ if (rule.is_object()) {
+ ZT_VirtualNetworkRule ztr;
+ if (_parseRule(rule,ztr)) {
+ nrules.push_back(_renderRule(ztr));
+ if (nrules.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
+ }
+ }
+ }
+ network["rules"] = nrules;
+ }
+ }
- const uint64_t address = Utils::hexStrToU64(path[3].c_str());
- json member;
- if (!_db.get(nwid,network,address,member))
- return 404;
- responseBody = OSUtils::jsonDump(member);
- responseContentType = "application/json";
+ if (b.count("authTokens")) {
+ json &authTokens = b["authTokens"];
+ if (authTokens.is_object()) {
+ json nat;
+ for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) {
+ if ((t.value().is_number())&&(t.value() >= 0))
+ nat[t.key()] = t.value();
+ }
+ network["authTokens"] = nat;
+ } else {
+ network["authTokens"] = {{}};
+ }
+ }
- } else {
- // List members and their revisions
-
- responseBody = "{";
- std::vector<json> members;
- if (_db.get(nwid,network,members)) {
- responseBody.reserve((members.size() + 2) * 32);
- std::string mid;
- for(auto member=members.begin();member!=members.end();++member) {
- mid = OSUtils::jsonString((*member)["id"], "");
- char tmp[128];
- OSUtils::ztsnprintf(tmp,sizeof(tmp),"%s\"%s\":%llu",(responseBody.length() > 1) ? "," : "",mid.c_str(),(unsigned long long)OSUtils::jsonInt((*member)["revision"],0));
- responseBody.append(tmp);
+ if (b.count("capabilities")) {
+ json &capabilities = b["capabilities"];
+ if (capabilities.is_array()) {
+ std::map< uint64_t,json > ncaps;
+ for(unsigned long i=0;i<capabilities.size();++i) {
+ json &cap = capabilities[i];
+ if (cap.is_object()) {
+ json ncap = json::object();
+ const uint64_t capId = OSUtils::jsonInt(cap["id"],0ULL);
+ ncap["id"] = capId;
+ ncap["default"] = OSUtils::jsonBool(cap["default"],false);
+
+ json &rules = cap["rules"];
+ json nrules = json::array();
+ if (rules.is_array()) {
+ for(unsigned long i=0;i<rules.size();++i) {
+ json &rule = rules[i];
+ if (rule.is_object()) {
+ ZT_VirtualNetworkRule ztr;
+ if (_parseRule(rule,ztr)) {
+ nrules.push_back(_renderRule(ztr));
+ if (nrules.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
+ }
}
}
- responseBody.push_back('}');
- responseContentType = "application/json";
-
}
- return 200;
+ ncap["rules"] = nrules;
- } // else 404
-
- } else {
- // Get network
-
- responseBody = OSUtils::jsonDump(network);
- responseContentType = "application/json";
- return 200;
+ ncaps[capId] = ncap;
+ }
+ }
+ json ncapsa = json::array();
+ for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c) {
+ ncapsa.push_back(c->second);
+ if (ncapsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
}
- } else if (path.size() == 1) {
- // List networks
-
- std::set<uint64_t> networkIds;
- _db.networks(networkIds);
- char tmp[64];
- responseBody = "[";
- responseBody.reserve((networkIds.size() + 1) * 24);
- for(std::set<uint64_t>::const_iterator i(networkIds.begin());i!=networkIds.end();++i) {
- if (responseBody.length() > 1)
- responseBody.push_back(',');
- OSUtils::ztsnprintf(tmp,sizeof(tmp),"\"%.16llx\"",(unsigned long long)*i);
- responseBody.append(tmp);
+ network["capabilities"] = ncapsa;
+ }
+ }
+
+ if (b.count("tags")) {
+ json &tags = b["tags"];
+ if (tags.is_array()) {
+ std::map< uint64_t,json > ntags;
+ for(unsigned long i=0;i<tags.size();++i) {
+ json &tag = tags[i];
+ if (tag.is_object()) {
+ json ntag = json::object();
+ const uint64_t tagId = OSUtils::jsonInt(tag["id"],0ULL);
+ ntag["id"] = tagId;
+ json &dfl = tag["default"];
+ if (dfl.is_null())
+ ntag["default"] = dfl;
+ else ntag["default"] = OSUtils::jsonInt(dfl,0ULL);
+ ntags[tagId] = ntag;
+ }
}
- responseBody.push_back(']');
- responseContentType = "application/json";
- return 200;
+ json ntagsa = json::array();
+ for(std::map< uint64_t,json >::iterator t(ntags.begin());t!=ntags.end();++t) {
+ ntagsa.push_back(t->second);
+ if (ntagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
+ }
+ network["tags"] = ntagsa;
+ }
+ }
- } // else 404
+ if (b.count("dns")) {
+ json &dns = b["dns"];
+ if (dns.is_object()) {
+ json nd;
- } else {
- // Controller status
+ nd["domain"] = dns["domain"];
- char tmp[4096];
- const bool dbOk = _db.isReady();
- OSUtils::ztsnprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"databaseReady\": %s\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),dbOk ? "true" : "false");
- responseBody = tmp;
- responseContentType = "application/json";
- return dbOk ? 200 : 503;
+ json &srv = dns["servers"];
+ if (srv.is_array()) {
+ json ns = json::array();
+ for(unsigned int i=0;i<srv.size();++i) {
+ ns.push_back(srv[i]);
+ }
+ nd["servers"] = ns;
+ }
+ network["dns"] = nd;
+ }
}
- return 404;
+ network["id"] = nwids;
+ network["nwid"] = nwids;
+
+ DB::cleanNetwork(network);
+ _db.save(network, true);
+
+ return network.dump();
}
-unsigned int EmbeddedNetworkController::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)
+void EmbeddedNetworkController::configureHTTPControlPlane(
+ httplib::Server &s,
+ const std::function<void(const httplib::Request&, httplib::Response&, std::string)> setContent)
{
- if (path.empty())
- return 404;
-
- json b;
- try {
- b = OSUtils::jsonParse(body);
- if (!b.is_object()) {
- responseBody = "{ \"message\": \"body is not a JSON object\" }";
- responseContentType = "application/json";
- return 400;
+ s.Get("/controller/network", [&](const httplib::Request &req, httplib::Response &res) {
+ std::set<uint64_t> networkIds;
+ _db.networks(networkIds);
+ char tmp[64];
+
+ auto out = json::array();
+ for(std::set<uint64_t>::const_iterator i(networkIds.begin()); i != networkIds.end(); ++i) {
+ OSUtils::ztsnprintf(tmp, sizeof(tmp), "%.16llx", *i);
+ out.push_back(tmp);
}
- } catch ( ... ) {
- responseBody = "{ \"message\": \"body JSON is invalid\" }";
- responseContentType = "application/json";
- return 400;
- }
- const int64_t now = OSUtils::now();
-
- if (path[0] == "network") {
-
- if ((path.size() >= 2)&&(path[1].length() == 16)) {
- uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
- char nwids[24];
- OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
- if (path.size() >= 3) {
+ setContent(req, res, out.dump());
+ });
- if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
- uint64_t address = Utils::hexStrToU64(path[3].c_str());
- char addrs[24];
- OSUtils::ztsnprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address);
+ s.Get("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) {
+ auto networkID = req.matches[1];
+ uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str());
+ json network;
+ if (!_db.get(nwid, network)) {
+ res.status = 404;
+ return;
+ }
- json member,network;
- _db.get(nwid,network,address,member);
- DB::initMember(member);
+ setContent(req, res, network.dump());
+ });
+
+ auto createNewNetwork = [&](const httplib::Request &req, httplib::Response &res) {
+ fprintf(stderr, "creating new network (new style)\n");
+ uint64_t nwid = 0;
+ uint64_t nwidPrefix = (Utils::hexStrToU64(_signingIdAddressString.c_str()) << 24) & 0xffffffffff000000ULL;
+ uint64_t nwidPostfix = 0;
+ for(unsigned long k=0;k<100000;++k) { // sanity limit on trials
+ Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
+ uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
+ if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
+ if (!_db.hasNetwork(tryNwid)) {
+ nwid = tryNwid;
+ break;
+ }
+ }
+ if (!nwid) {
+ res.status = 503;
+ return;
+ }
- try {
- if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"], false);
- if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"], false);
- if (b.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = (uint64_t)OSUtils::jsonInt(b["authenticationExpiryTime"], 0ULL);
- if (b.count("authenticationURL")) member["authenticationURL"] = OSUtils::jsonString(b["authenticationURL"], "");
+ setContent(req, res, networkUpdateFromPostData(nwid, req.body));
+ };
+ s.Put("/controller/network", createNewNetwork);
+ s.Post("/controller/network", createNewNetwork);
- if (b.count("remoteTraceTarget")) {
- const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],""));
- if (rtt.length() == 10) {
- member["remoteTraceTarget"] = rtt;
- } else {
- member["remoteTraceTarget"] = json();
- }
- }
- if (b.count("remoteTraceLevel")) member["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL);
-
- if (b.count("authorized")) {
- const bool newAuth = OSUtils::jsonBool(b["authorized"],false);
- if (newAuth != OSUtils::jsonBool(member["authorized"],false)) {
- member["authorized"] = newAuth;
- member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now;
- if (newAuth) {
- member["lastAuthorizedCredentialType"] = "api";
- member["lastAuthorizedCredential"] = json();
- }
- }
- }
+ auto createNewNetworkOldAndBusted = [&](const httplib::Request &req, httplib::Response &res) {
+ auto inID = req.matches[1].str();
- if (b.count("ipAssignments")) {
- json &ipa = b["ipAssignments"];
- if (ipa.is_array()) {
- json mipa(json::array());
- for(unsigned long i=0;i<ipa.size();++i) {
- std::string ips = ipa[i];
- InetAddress ip(ips.c_str());
- if ((ip.ss_family == AF_INET)||(ip.ss_family == AF_INET6)) {
- char tmpip[64];
- mipa.push_back(ip.toIpString(tmpip));
- if (mipa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- }
- member["ipAssignments"] = mipa;
- }
- }
+ if (inID != _signingIdAddressString) {
+ res.status = 400;
+ return;
+ }
- if (b.count("tags")) {
- json &tags = b["tags"];
- if (tags.is_array()) {
- std::map<uint64_t,uint64_t> mtags;
- for(unsigned long i=0;i<tags.size();++i) {
- json &tag = tags[i];
- if ((tag.is_array())&&(tag.size() == 2))
- mtags[OSUtils::jsonInt(tag[0],0ULL) & 0xffffffffULL] = OSUtils::jsonInt(tag[1],0ULL) & 0xffffffffULL;
- }
- json mtagsa = json::array();
- for(std::map<uint64_t,uint64_t>::iterator t(mtags.begin());t!=mtags.end();++t) {
- json ta = json::array();
- ta.push_back(t->first);
- ta.push_back(t->second);
- mtagsa.push_back(ta);
- if (mtagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- member["tags"] = mtagsa;
- }
- }
+ uint64_t nwid = 0;
+ uint64_t nwidPrefix = (Utils::hexStrToU64(inID.c_str()) << 24) & 0xffffffffff000000ULL;
+ uint64_t nwidPostfix = 0;
+ for(unsigned long k=0;k<100000;++k) { // sanity limit on trials
+ Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
+ uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
+ if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
+ if (!_db.hasNetwork(tryNwid)) {
+ nwid = tryNwid;
+ break;
+ }
+ }
+ if (!nwid) {
+ res.status = 503;
+ return;
+ }
+ setContent(req, res, networkUpdateFromPostData(nwid, req.body));
+ };
+ s.Put("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted);
+ s.Post("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted);
+
+ s.Delete("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) {
+ auto networkID = req.matches[1].str();
+ uint64_t nwid = Utils::hexStrToU64(networkID.c_str());
+
+ json network;
+ if (!_db.get(nwid,network)) {
+ res.status = 404;
+ return;
+ }
- if (b.count("capabilities")) {
- json &capabilities = b["capabilities"];
- if (capabilities.is_array()) {
- json mcaps = json::array();
- for(unsigned long i=0;i<capabilities.size();++i) {
- mcaps.push_back(OSUtils::jsonInt(capabilities[i],0ULL));
- if (mcaps.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- std::sort(mcaps.begin(),mcaps.end());
- mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end());
- member["capabilities"] = mcaps;
- }
- }
- } catch ( ... ) {
- responseBody = "{ \"message\": \"exception while processing parameters in JSON body\" }";
- responseContentType = "application/json";
- return 400;
- }
+ _db.eraseNetwork(nwid);
+ setContent(req, res, network.dump());
+ });
- member["id"] = addrs;
- member["address"] = addrs; // legacy
- member["nwid"] = nwids;
+ s.Get("/controller/network/([0-9a-fA-F]{16})/member", [&](const httplib::Request &req, httplib::Response &res) {
+ auto networkID = req.matches[1];
+ uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str());
+ json network;
+ if (!_db.get(nwid, network)) {
+ res.status = 404;
+ return;
+ }
- DB::cleanMember(member);
- _db.save(member,true);
- responseBody = OSUtils::jsonDump(member);
- responseContentType = "application/json";
+ json out = json::array();
+ std::vector<json> memTmp;
+ if (_db.get(nwid, network, memTmp)) {
+ for (auto m = memTmp.begin(); m != memTmp.end(); ++m) {
+ int revision = OSUtils::jsonInt((*m)["revsision"], 0);
+ std::string id = OSUtils::jsonString((*m)["id"], "");
+ if (id.length() == 10) {
+ json tmp = json::object();
+ tmp[id] = revision;
+ out.push_back(tmp);
+ }
+ }
+ }
- return 200;
- } // else 404
+ setContent(req, res, out.dump());
+ });
+
+ s.Get("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) {
+ auto networkID = req.matches[1];
+ auto memberID = req.matches[2];
+ uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str());
+ uint64_t memid = Utils::hexStrToU64(memberID.str().c_str());
+ json network;
+ json member;
+ if (!_db.get(nwid, network, memid, member)) {
+ res.status = 404;
+ return;
+ }
+ setContent(req, res, member.dump());
+ });
+
+ auto memberPost = [&](const httplib::Request &req, httplib::Response &res) {
+ auto networkID = req.matches[1].str();
+ auto memberID = req.matches[2].str();
+ uint64_t nwid = Utils::hexStrToU64(networkID.c_str());
+ uint64_t memid = Utils::hexStrToU64(memberID.c_str());
+ json network;
+ json member;
+ _db.get(nwid, network, memid, member);
+ DB::initMember(member);
+
+ json b = OSUtils::jsonParse(req.body);
+
+ if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"], false);
+ if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"], false);
+ if (b.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = (uint64_t)OSUtils::jsonInt(b["authenticationExpiryTime"], 0ULL);
+ if (b.count("authenticationURL")) member["authenticationURL"] = OSUtils::jsonString(b["authenticationURL"], "");
+
+ if (b.count("remoteTraceTarget")) {
+ const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],""));
+ if (rtt.length() == 10) {
+ member["remoteTraceTarget"] = rtt;
} else {
- // POST to network ID
-
- // Magic ID ending with ______ picks a random unused network ID
- if (path[1].substr(10) == "______") {
- nwid = 0;
- uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL;
- uint64_t nwidPostfix = 0;
- for(unsigned long k=0;k<100000;++k) { // sanity limit on trials
- Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
- uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
- if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
- if (!_db.hasNetwork(tryNwid)) {
- nwid = tryNwid;
- break;
- }
- }
- if (!nwid)
- return 503;
+ member["remoteTraceTarget"] = json();
+ }
+ }
+ if (b.count("remoteTraceLevel")) member["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL);
+
+ if (b.count("authorized")) {
+ const bool newAuth = OSUtils::jsonBool(b["authorized"],false);
+ if (newAuth != OSUtils::jsonBool(member["authorized"],false)) {
+ member["authorized"] = newAuth;
+ member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = OSUtils::now();
+ if (newAuth) {
+ member["lastAuthorizedCredentialType"] = "api";
+ member["lastAuthorizedCredential"] = json();
}
- OSUtils::ztsnprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
-
- json network;
- _db.get(nwid,network);
- DB::initNetwork(network);
-
- try {
- if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],"");
- if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true);
- if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false);
- if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL);
- if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU);
-
- if (b.count("remoteTraceTarget")) {
- const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],""));
- if (rtt.length() == 10) {
- network["remoteTraceTarget"] = rtt;
- } else {
- network["remoteTraceTarget"] = json();
- }
- }
- if (b.count("remoteTraceLevel")) network["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL);
-
- if (b.count("v4AssignMode")) {
- json nv4m;
- json &v4m = b["v4AssignMode"];
- if (v4m.is_string()) { // backward compatibility
- nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt");
- } else if (v4m.is_object()) {
- nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false);
- } else nv4m["zt"] = false;
- network["v4AssignMode"] = nv4m;
- }
-
- if (b.count("v6AssignMode")) {
- json nv6m;
- json &v6m = b["v6AssignMode"];
- if (!nv6m.is_object()) nv6m = json::object();
- if (v6m.is_string()) { // backward compatibility
- std::vector<std::string> v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","",""));
- std::sort(v6ms.begin(),v6ms.end());
- v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end());
- nv6m["rfc4193"] = false;
- nv6m["zt"] = false;
- nv6m["6plane"] = false;
- for(std::vector<std::string>::iterator i(v6ms.begin());i!=v6ms.end();++i) {
- if (*i == "rfc4193")
- nv6m["rfc4193"] = true;
- else if (*i == "zt")
- nv6m["zt"] = true;
- else if (*i == "6plane")
- nv6m["6plane"] = true;
- }
- } else if (v6m.is_object()) {
- if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false);
- if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false);
- if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false);
- } else {
- nv6m["rfc4193"] = false;
- nv6m["zt"] = false;
- nv6m["6plane"] = false;
- }
- network["v6AssignMode"] = nv6m;
- }
-
- if (b.count("routes")) {
- json &rts = b["routes"];
- if (rts.is_array()) {
- json nrts = json::array();
- for(unsigned long i=0;i<rts.size();++i) {
- json &rt = rts[i];
- if (rt.is_object()) {
- json &target = rt["target"];
- json &via = rt["via"];
- if (target.is_string()) {
- InetAddress t(target.get<std::string>().c_str());
- InetAddress v;
- if (via.is_string()) v.fromString(via.get<std::string>().c_str());
- if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) {
- json tmp;
- char tmp2[64];
- tmp["target"] = t.toString(tmp2);
- if (v.ss_family == t.ss_family)
- tmp["via"] = v.toIpString(tmp2);
- else tmp["via"] = json();
- nrts.push_back(tmp);
- if (nrts.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- }
- }
- }
- network["routes"] = nrts;
- }
- }
-
- if (b.count("ipAssignmentPools")) {
- json &ipp = b["ipAssignmentPools"];
- if (ipp.is_array()) {
- json nipp = json::array();
- for(unsigned long i=0;i<ipp.size();++i) {
- json &ip = ipp[i];
- if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("ipRangeEnd"))) {
- InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],"").c_str());
- InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],"").c_str());
- if ( ((f.ss_family == AF_INET)||(f.ss_family == AF_INET6)) && (f.ss_family == t.ss_family) ) {
- json tmp = json::object();
- char tmp2[64];
- tmp["ipRangeStart"] = f.toIpString(tmp2);
- tmp["ipRangeEnd"] = t.toIpString(tmp2);
- nipp.push_back(tmp);
- if (nipp.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- }
- }
- network["ipAssignmentPools"] = nipp;
- }
- }
-
- if (b.count("rules")) {
- json &rules = b["rules"];
- if (rules.is_array()) {
- json nrules = json::array();
- for(unsigned long i=0;i<rules.size();++i) {
- json &rule = rules[i];
- if (rule.is_object()) {
- ZT_VirtualNetworkRule ztr;
- if (_parseRule(rule,ztr)) {
- nrules.push_back(_renderRule(ztr));
- if (nrules.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- }
- }
- network["rules"] = nrules;
- }
- }
-
- if (b.count("authTokens")) {
- json &authTokens = b["authTokens"];
- if (authTokens.is_object()) {
- json nat;
- for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) {
- if ((t.value().is_number())&&(t.value() >= 0))
- nat[t.key()] = t.value();
- }
- network["authTokens"] = nat;
- } else {
- network["authTokens"] = {{}};
- }
- }
-
- if (b.count("capabilities")) {
- json &capabilities = b["capabilities"];
- if (capabilities.is_array()) {
- std::map< uint64_t,json > ncaps;
- for(unsigned long i=0;i<capabilities.size();++i) {
- json &cap = capabilities[i];
- if (cap.is_object()) {
- json ncap = json::object();
- const uint64_t capId = OSUtils::jsonInt(cap["id"],0ULL);
- ncap["id"] = capId;
- ncap["default"] = OSUtils::jsonBool(cap["default"],false);
-
- json &rules = cap["rules"];
- json nrules = json::array();
- if (rules.is_array()) {
- for(unsigned long i=0;i<rules.size();++i) {
- json &rule = rules[i];
- if (rule.is_object()) {
- ZT_VirtualNetworkRule ztr;
- if (_parseRule(rule,ztr)) {
- nrules.push_back(_renderRule(ztr));
- if (nrules.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- }
- }
- }
- ncap["rules"] = nrules;
-
- ncaps[capId] = ncap;
- }
- }
-
- json ncapsa = json::array();
- for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c) {
- ncapsa.push_back(c->second);
- if (ncapsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- network["capabilities"] = ncapsa;
- }
- }
-
- if (b.count("tags")) {
- json &tags = b["tags"];
- if (tags.is_array()) {
- std::map< uint64_t,json > ntags;
- for(unsigned long i=0;i<tags.size();++i) {
- json &tag = tags[i];
- if (tag.is_object()) {
- json ntag = json::object();
- const uint64_t tagId = OSUtils::jsonInt(tag["id"],0ULL);
- ntag["id"] = tagId;
- json &dfl = tag["default"];
- if (dfl.is_null())
- ntag["default"] = dfl;
- else ntag["default"] = OSUtils::jsonInt(dfl,0ULL);
- ntags[tagId] = ntag;
- }
- }
-
- json ntagsa = json::array();
- for(std::map< uint64_t,json >::iterator t(ntags.begin());t!=ntags.end();++t) {
- ntagsa.push_back(t->second);
- if (ntagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
- break;
- }
- network["tags"] = ntagsa;
- }
- }
-
- if (b.count("dns")) {
- json &dns = b["dns"];
- if (dns.is_object()) {
- json nd;
-
- nd["domain"] = dns["domain"];
-
- json &srv = dns["servers"];
- if (srv.is_array()) {
- json ns = json::array();
- for(unsigned int i=0;i<srv.size();++i) {
- ns.push_back(srv[i]);
- }
- nd["servers"] = ns;
- }
+ }
+ }
- network["dns"] = nd;
- }
+ if (b.count("ipAssignments")) {
+ json &ipa = b["ipAssignments"];
+ if (ipa.is_array()) {
+ json mipa(json::array());
+ for(unsigned long i=0;i<ipa.size();++i) {
+ std::string ips = ipa[i];
+ InetAddress ip(ips.c_str());
+ if ((ip.ss_family == AF_INET)||(ip.ss_family == AF_INET6)) {
+ char tmpip[64];
+ mipa.push_back(ip.toIpString(tmpip));
+ if (mipa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
}
-
- } catch ( ... ) {
- responseBody = "{ \"message\": \"exception occurred while parsing body variables\" }";
- responseContentType = "application/json";
- return 400;
}
+ member["ipAssignments"] = mipa;
+ }
+ }
- network["id"] = nwids;
- network["nwid"] = nwids; // legacy
-
- DB::cleanNetwork(network);
- _db.save(network,true);
+ if (b.count("tags")) {
+ json &tags = b["tags"];
+ if (tags.is_array()) {
+ std::map<uint64_t,uint64_t> mtags;
+ for(unsigned long i=0;i<tags.size();++i) {
+ json &tag = tags[i];
+ if ((tag.is_array())&&(tag.size() == 2))
+ mtags[OSUtils::jsonInt(tag[0],0ULL) & 0xffffffffULL] = OSUtils::jsonInt(tag[1],0ULL) & 0xffffffffULL;
+ }
+ json mtagsa = json::array();
+ for(std::map<uint64_t,uint64_t>::iterator t(mtags.begin());t!=mtags.end();++t) {
+ json ta = json::array();
+ ta.push_back(t->first);
+ ta.push_back(t->second);
+ mtagsa.push_back(ta);
+ if (mtagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
+ }
+ member["tags"] = mtagsa;
+ }
+ }
- responseBody = OSUtils::jsonDump(network);
- responseContentType = "application/json";
- return 200;
- } // else 404
+ if (b.count("capabilities")) {
+ json &capabilities = b["capabilities"];
+ if (capabilities.is_array()) {
+ json mcaps = json::array();
+ for(unsigned long i=0;i<capabilities.size();++i) {
+ mcaps.push_back(OSUtils::jsonInt(capabilities[i],0ULL));
+ if (mcaps.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
+ break;
+ }
+ std::sort(mcaps.begin(),mcaps.end());
+ mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end());
+ member["capabilities"] = mcaps;
+ }
+ }
- } // else 404
+ member["id"] = memberID;
+ member["address"] = memberID;
+ member["nwid"] = networkID;
- }
+ DB::cleanMember(member);
+ _db.save(member, true);
- return 404;
-}
+ setContent(req, res, member.dump());
+ };
+ s.Put("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost);
+ s.Post("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost);
-unsigned int EmbeddedNetworkController::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)
-{
- if (path.empty())
- return 404;
-
- if (path[0] == "network") {
- if ((path.size() >= 2)&&(path[1].length() == 16)) {
- const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
- if (path.size() >= 3) {
- if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
- const uint64_t address = Utils::hexStrToU64(path[3].c_str());
-
- json network,member;
- _db.get(nwid,network,address,member);
- _db.eraseMember(nwid, address);
-
- {
- std::lock_guard<std::mutex> l(_memberStatus_l);
- _memberStatus.erase(_MemberStatusKey(nwid,address));
- }
+ s.Delete("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) {
+ auto networkID = req.matches[1].str();
+ auto memberID = req.matches[2].str();
- if (!member.size())
- return 404;
- responseBody = OSUtils::jsonDump(member);
- responseContentType = "application/json";
- return 200;
- }
- } else {
- json network;
- _db.get(nwid,network);
- _db.eraseNetwork(nwid);
+ uint64_t nwid = Utils::hexStrToU64(networkID.c_str());
+ uint64_t address = Utils::hexStrToU64(memberID.c_str());
+ json network, member;
- {
- std::lock_guard<std::mutex> l(_memberStatus_l);
- for(auto i=_memberStatus.begin();i!=_memberStatus.end();) {
- if (i->first.networkId == nwid)
- _memberStatus.erase(i++);
- else ++i;
- }
- }
+ if (!_db.get(nwid, network, address, member)) {
+ res.status = 404;
+ return;
+ }
- if (!network.size())
- return 404;
- responseBody = OSUtils::jsonDump(network);
- responseContentType = "application/json";
- return 200;
- }
- } // else 404
+ if (!member.size()) {
+ res.status = 404;
+ return;
+ }
- } // else 404
+ _db.eraseMember(nwid, address);
- return 404;
+ setContent(req, res, member.dump());
+ });
}
void EmbeddedNetworkController::handleRemoteTrace(const ZT_RemoteTrace &rt)
diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp
index bc95acb5..4f2e20e0 100644
--- a/controller/EmbeddedNetworkController.hpp
+++ b/controller/EmbeddedNetworkController.hpp
@@ -37,6 +37,8 @@
#include <nlohmann/json.hpp>
+#include <cpp-httplib/httplib.h>
+
#include "DB.hpp"
#include "DBMirrorSet.hpp"
@@ -66,27 +68,9 @@ public:
const Identity &identity,
const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
- 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);
- 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);
- 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);
+ void configureHTTPControlPlane(
+ httplib::Server &s,
+ const std::function<void(const httplib::Request&, httplib::Response&, std::string)>);
void handleRemoteTrace(const ZT_RemoteTrace &rt);
@@ -98,6 +82,8 @@ private:
void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
void _startThreads();
+ std::string networkUpdateFromPostData(uint64_t networkID, const std::string &body);
+
struct _RQEntry
{
uint64_t nwid;