diff options
| author | Grant Limberg <[email protected]> | 2023-04-28 11:03:28 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-04-28 11:03:28 -0700 |
| commit | e5fc89821f1ef46de6f4aa9664d2207e5745553b (patch) | |
| tree | a819d05f9d9423e02b4752e53ed6428402f15cb9 /controller | |
| parent | 411e54023aa0b83d0c79ef91bbe2ddaec9422cd2 (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.cpp | 9 | ||||
| -rw-r--r-- | controller/EmbeddedNetworkController.cpp | 1004 | ||||
| -rw-r--r-- | controller/EmbeddedNetworkController.hpp | 28 |
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; |
