Browse Source

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.
Grant Limberg 2 years ago
parent
commit
e5fc89821f

+ 6 - 3
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(;;) {

+ 470 - 534
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)

+ 7 - 21
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;

File diff suppressed because it is too large
+ 486 - 206
ext/cpp-httplib/httplib.h


File diff suppressed because it is too large
+ 594 - 543
service/OneService.cpp


Some files were not shown because too many files changed in this diff