From bc61357a44cf4906dda2b30c4474ae891982e620 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 18 Apr 2017 17:37:44 -0700 Subject: [PATCH] HTTP backend support for JSONDB --- controller/EmbeddedNetworkController.cpp | 2 - controller/JSONDB.cpp | 167 ++++++++++++++++------- controller/JSONDB.hpp | 12 +- osdep/Http.hpp | 33 +++++ service/OneService.cpp | 24 +++- 5 files changed, 178 insertions(+), 60 deletions(-) diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 0884dedae..597bc9c9a 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -434,8 +434,6 @@ EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPa _db(dbPath), _node(node) { - OSUtils::mkdir(dbPath); - OSUtils::lockDownFile(dbPath,true); // networks might contain auth tokens, etc., so restrict directory permissions } EmbeddedNetworkController::~EmbeddedNetworkController() diff --git a/controller/JSONDB.cpp b/controller/JSONDB.cpp index 756664eb4..afc0631d8 100644 --- a/controller/JSONDB.cpp +++ b/controller/JSONDB.cpp @@ -18,43 +18,67 @@ #include "JSONDB.hpp" +#define ZT_JSONDB_HTTP_TIMEOUT 60000 + namespace ZeroTier { static const nlohmann::json _EMPTY_JSON(nlohmann::json::object()); +static const std::map _ZT_JSONDB_GET_HEADERS; + +JSONDB::JSONDB(const std::string &basePath) : + _basePath(basePath) +{ + if ((_basePath.length() > 7)&&(_basePath.substr(0,7) == "http://")) { + // TODO: this doesn't yet support IPv6 since bracketed address notiation isn't supported. + // Typically it's used with 127.0.0.1 anyway. + std::string hn = _basePath.substr(7); + std::size_t hnend = hn.find_first_of('/'); + if (hnend != std::string::npos) + hn = hn.substr(0,hnend); + std::size_t hnsep = hn.find_last_of(':'); + if (hnsep != std::string::npos) + hn[hnsep] = '/'; + _httpAddr.fromString(hn); + if (hnend != std::string::npos) + _basePath = _basePath.substr(7 + hnend); + if (_basePath.length() == 0) + _basePath = "/"; + if (_basePath[0] != '/') + _basePath = std::string("/") + _basePath; + } else { + OSUtils::mkdir(_basePath.c_str()); + OSUtils::lockDownFile(_basePath.c_str(),true); // networks might contain auth tokens, etc., so restrict directory permissions + } + _reload(_basePath,std::string()); +} bool JSONDB::writeRaw(const std::string &n,const std::string &obj) { if (!_isValidObjectName(n)) return false; - - const std::string path(_genPath(n,true)); - if (!path.length()) - return false; - - const std::string buf(obj); - if (!OSUtils::writeFile(path.c_str(),buf)) - return false; - - return true; + if (_httpAddr) { + std::map headers; + std::string body; + std::map reqHeaders; + char tmp[64]; + Utils::snprintf(tmp,sizeof(tmp),"%lu",(unsigned long)obj.length()); + reqHeaders["Content-Length"] = tmp; + reqHeaders["Content-Type"] = "application/json"; + const unsigned int sc = Http::PUT(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),reqHeaders,obj.data(),obj.length(),headers,body); + return (sc == 200); + } else { + const std::string path(_genPath(n,true)); + if (!path.length()) + return false; + return OSUtils::writeFile(path.c_str(),obj); + } } bool JSONDB::put(const std::string &n,const nlohmann::json &obj) { - if (!_isValidObjectName(n)) - return false; - - const std::string path(_genPath(n,true)); - if (!path.length()) - return false; - - const std::string buf(OSUtils::jsonDump(obj)); - if (!OSUtils::writeFile(path.c_str(),buf)) - return false; - - _E &e = _db[n]; - e.obj = obj; - - return true; + const bool r = writeRaw(n,OSUtils::jsonDump(obj)); + _db[n].obj = obj; + return r; } const nlohmann::json &JSONDB::get(const std::string &n) @@ -66,22 +90,28 @@ const nlohmann::json &JSONDB::get(const std::string &n) if (e != _db.end()) return e->second.obj; - const std::string path(_genPath(n,false)); - if (!path.length()) - return _EMPTY_JSON; std::string buf; - if (!OSUtils::readFile(path.c_str(),buf)) - return _EMPTY_JSON; - - _E &e2 = _db[n]; - try { - e2.obj = OSUtils::jsonParse(buf); - } catch ( ... ) { - e2.obj = _EMPTY_JSON; - buf = "{}"; + if (_httpAddr) { + std::map headers; + const unsigned int sc = Http::GET(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,buf); + if (sc != 200) + return _EMPTY_JSON; + } else { + const std::string path(_genPath(n,false)); + if (!path.length()) + return _EMPTY_JSON; + if (!OSUtils::readFile(path.c_str(),buf)) + return _EMPTY_JSON; } - return e2.obj; + try { + _E &e2 = _db[n]; + e2.obj = OSUtils::jsonParse(buf); + return e2.obj; + } catch ( ... ) { + _db.erase(n); + return _EMPTY_JSON; + } } void JSONDB::erase(const std::string &n) @@ -89,23 +119,50 @@ void JSONDB::erase(const std::string &n) if (!_isValidObjectName(n)) return; - std::string path(_genPath(n,true)); - if (!path.length()) - return; + if (_httpAddr) { + std::string body; + std::map headers; + Http::DEL(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + } else { + std::string path(_genPath(n,true)); + if (!path.length()) + return; + OSUtils::rm(path.c_str()); + } - OSUtils::rm(path.c_str()); _db.erase(n); } void JSONDB::_reload(const std::string &p,const std::string &b) { - std::vector dl(OSUtils::listDirectory(p.c_str(),true)); - for(std::vector::const_iterator di(dl.begin());di!=dl.end();++di) { - printf("%s\n",di->c_str()); - if ((di->length() > 5)&&(di->substr(di->length() - 5) == ".json")) { - this->get(b + di->substr(0,di->length() - 5)); - } else { - this->_reload((p + ZT_PATH_SEPARATOR + *di),(b + *di + ZT_PATH_SEPARATOR)); + if (_httpAddr) { + std::string body; + std::map headers; + const unsigned int sc = Http::GET(2147483647,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),_basePath.c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + if (sc == 200) { + try { + nlohmann::json dbImg(OSUtils::jsonParse(body)); + std::string tmp; + if (dbImg.is_object()) { + for(nlohmann::json::iterator i(dbImg.begin());i!=dbImg.end();++i) { + if (i.value().is_object()) { + tmp = i.key(); + _db[tmp].obj = i.value(); + } + } + } + } catch ( ... ) { + // TODO: report error? + } + } + } else { + std::vector dl(OSUtils::listDirectory(p.c_str(),true)); + for(std::vector::const_iterator di(dl.begin());di!=dl.end();++di) { + if ((di->length() > 5)&&(di->substr(di->length() - 5) == ".json")) { + this->get(b + di->substr(0,di->length() - 5)); + } else { + this->_reload((p + ZT_PATH_SEPARATOR + *di),(b + *di + ZT_PATH_SEPARATOR)); + } } } } @@ -130,15 +187,23 @@ std::string JSONDB::_genPath(const std::string &n,bool create) if (pt.size() == 0) return std::string(); + char sep; + if (_httpAddr) { + sep = '/'; + create = false; + } else { + sep = ZT_PATH_SEPARATOR; + } + std::string p(_basePath); if (create) OSUtils::mkdir(p.c_str()); for(unsigned long i=0,j=(unsigned long)(pt.size()-1);i _db; }; diff --git a/osdep/Http.hpp b/osdep/Http.hpp index 1ecf4eece..e7d4d03e4 100644 --- a/osdep/Http.hpp +++ b/osdep/Http.hpp @@ -135,6 +135,39 @@ public: responseBody); } + /** + * Make HTTP PUT request + * + * It is the responsibility of the caller to set all headers. With PUT, the + * Content-Length and Content-Type headers must be set or the PUT will not + * work. + * + * @return HTTP status code or 0 on error (responseBody will contain error message) + */ + static inline unsigned int PUT( + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + const void *postData, + unsigned long postDataLength, + std::map &responseHeaders, + std::string &responseBody) + { + return _do( + "PUT", + maxResponseSize, + timeout, + remoteAddress, + path, + requestHeaders, + postData, + postDataLength, + responseHeaders, + responseBody); + } + private: static unsigned int _do( const char *method, diff --git a/service/OneService.cpp b/service/OneService.cpp index c07b3ba4d..5f2adfe35 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -381,6 +381,7 @@ public: const std::string _homePath; std::string _authToken; + std::string _controllerDbPath; EmbeddedNetworkController *_controller; Phy _phy; Node *_node; @@ -482,6 +483,7 @@ public: OneServiceImpl(const char *hp,unsigned int port) : _homePath((hp) ? hp : ".") + ,_controllerDbPath(_homePath + ZT_PATH_SEPARATOR_S ZT_CONTROLLER_DB_PATH) ,_controller((EmbeddedNetworkController *)0) ,_phy(this,false,true) ,_node((Node *)0) @@ -747,7 +749,7 @@ public: for(int i=0;i<3;++i) _portsBE[i] = Utils::hton((uint16_t)_ports[i]); - _controller = new EmbeddedNetworkController(_node,(_homePath + ZT_PATH_SEPARATOR_S ZT_CONTROLLER_DB_PATH).c_str()); + _controller = new EmbeddedNetworkController(_node,_controllerDbPath.c_str()); _node->setNetconfMaster((void *)_controller); #ifdef ZT_ENABLE_CLUSTER @@ -1522,6 +1524,26 @@ public: _allowManagementFrom.push_back(nw); } } + + json &controllerDbHttpHost = settings["controllerDbHttpHost"]; + json &controllerDbHttpPort = settings["controllerDbHttpPort"]; + json &controllerDbHttpPath = settings["controllerDbHttpPath"]; + if ((controllerDbHttpHost.is_string())&&(controllerDbHttpPort.is_number())) { + _controllerDbPath = "http://"; + _controllerDbPath.append(controllerDbHttpHost); + char dbp[128]; + Utils::snprintf(dbp,sizeof(dbp),"%d",(int)controllerDbHttpPort); + _controllerDbPath.push_back(':'); + _controllerDbPath.append(dbp); + if (controllerDbHttpPath.is_string()) { + std::string p = controllerDbHttpPath; + if ((p.length() == 0)||(p[0] != '/')) + _controllerDbPath.push_back('/'); + _controllerDbPath.append(p); + } else { + _controllerDbPath.push_back('/'); + } + } } // Checks if a managed IP or route target is allowed