Update to 1.4.2
This commit is contained in:
parent
0708629155
commit
4f9274484a
506 changed files with 101463 additions and 33922 deletions
365
controller/DB.cpp
Normal file
365
controller/DB.cpp
Normal file
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "DB.hpp"
|
||||
#include "EmbeddedNetworkController.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
void DB::initNetwork(nlohmann::json &network)
|
||||
{
|
||||
if (!network.count("private")) network["private"] = true;
|
||||
if (!network.count("creationTime")) network["creationTime"] = OSUtils::now();
|
||||
if (!network.count("name")) network["name"] = "";
|
||||
if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32;
|
||||
if (!network.count("enableBroadcast")) network["enableBroadcast"] = true;
|
||||
if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}};
|
||||
if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}};
|
||||
if (!network.count("authTokens")) network["authTokens"] = {{}};
|
||||
if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array();
|
||||
if (!network.count("tags")) network["tags"] = nlohmann::json::array();
|
||||
if (!network.count("routes")) network["routes"] = nlohmann::json::array();
|
||||
if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array();
|
||||
if (!network.count("mtu")) network["mtu"] = ZT_DEFAULT_MTU;
|
||||
if (!network.count("remoteTraceTarget")) network["remoteTraceTarget"] = nlohmann::json();
|
||||
if (!network.count("removeTraceLevel")) network["remoteTraceLevel"] = 0;
|
||||
if (!network.count("rulesSource")) network["rulesSource"] = "";
|
||||
if (!network.count("rules")) {
|
||||
// If unspecified, rules are set to allow anything and behave like a flat L2 segment
|
||||
network["rules"] = {{
|
||||
{ "not",false },
|
||||
{ "or", false },
|
||||
{ "type","ACTION_ACCEPT" }
|
||||
}};
|
||||
}
|
||||
network["objtype"] = "network";
|
||||
}
|
||||
|
||||
void DB::initMember(nlohmann::json &member)
|
||||
{
|
||||
if (!member.count("authorized")) member["authorized"] = false;
|
||||
if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array();
|
||||
if (!member.count("activeBridge")) member["activeBridge"] = false;
|
||||
if (!member.count("tags")) member["tags"] = nlohmann::json::array();
|
||||
if (!member.count("capabilities")) member["capabilities"] = nlohmann::json::array();
|
||||
if (!member.count("creationTime")) member["creationTime"] = OSUtils::now();
|
||||
if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false;
|
||||
if (!member.count("revision")) member["revision"] = 0ULL;
|
||||
if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
|
||||
if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
|
||||
if (!member.count("lastAuthorizedCredentialType")) member["lastAuthorizedCredentialType"] = nlohmann::json();
|
||||
if (!member.count("lastAuthorizedCredential")) member["lastAuthorizedCredential"] = nlohmann::json();
|
||||
if (!member.count("vMajor")) member["vMajor"] = -1;
|
||||
if (!member.count("vMinor")) member["vMinor"] = -1;
|
||||
if (!member.count("vRev")) member["vRev"] = -1;
|
||||
if (!member.count("vProto")) member["vProto"] = -1;
|
||||
if (!member.count("remoteTraceTarget")) member["remoteTraceTarget"] = nlohmann::json();
|
||||
if (!member.count("removeTraceLevel")) member["remoteTraceLevel"] = 0;
|
||||
member["objtype"] = "member";
|
||||
}
|
||||
|
||||
void DB::cleanNetwork(nlohmann::json &network)
|
||||
{
|
||||
network.erase("clock");
|
||||
network.erase("authorizedMemberCount");
|
||||
network.erase("activeMemberCount");
|
||||
network.erase("totalMemberCount");
|
||||
network.erase("lastModified");
|
||||
}
|
||||
|
||||
void DB::cleanMember(nlohmann::json &member)
|
||||
{
|
||||
member.erase("clock");
|
||||
member.erase("physicalAddr");
|
||||
member.erase("recentLog");
|
||||
member.erase("lastModified");
|
||||
member.erase("lastRequestMetaData");
|
||||
}
|
||||
|
||||
DB::DB() {}
|
||||
DB::~DB() {}
|
||||
|
||||
bool DB::get(const uint64_t networkId,nlohmann::json &network)
|
||||
{
|
||||
waitForReady();
|
||||
std::shared_ptr<_Network> nw;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
auto nwi = _networks.find(networkId);
|
||||
if (nwi == _networks.end())
|
||||
return false;
|
||||
nw = nwi->second;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> l2(nw->lock);
|
||||
network = nw->config;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member)
|
||||
{
|
||||
waitForReady();
|
||||
std::shared_ptr<_Network> nw;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
auto nwi = _networks.find(networkId);
|
||||
if (nwi == _networks.end())
|
||||
return false;
|
||||
nw = nwi->second;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> l2(nw->lock);
|
||||
network = nw->config;
|
||||
auto m = nw->members.find(memberId);
|
||||
if (m == nw->members.end())
|
||||
return false;
|
||||
member = m->second;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DB::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,NetworkSummaryInfo &info)
|
||||
{
|
||||
waitForReady();
|
||||
std::shared_ptr<_Network> nw;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
auto nwi = _networks.find(networkId);
|
||||
if (nwi == _networks.end())
|
||||
return false;
|
||||
nw = nwi->second;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> l2(nw->lock);
|
||||
network = nw->config;
|
||||
_fillSummaryInfo(nw,info);
|
||||
auto m = nw->members.find(memberId);
|
||||
if (m == nw->members.end())
|
||||
return false;
|
||||
member = m->second;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DB::get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members)
|
||||
{
|
||||
waitForReady();
|
||||
std::shared_ptr<_Network> nw;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
auto nwi = _networks.find(networkId);
|
||||
if (nwi == _networks.end())
|
||||
return false;
|
||||
nw = nwi->second;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> l2(nw->lock);
|
||||
network = nw->config;
|
||||
for(auto m=nw->members.begin();m!=nw->members.end();++m)
|
||||
members.push_back(m->second);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DB::networks(std::set<uint64_t> &networks)
|
||||
{
|
||||
waitForReady();
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
for(auto n=_networks.begin();n!=_networks.end();++n)
|
||||
networks.insert(n->first);
|
||||
}
|
||||
|
||||
void DB::_memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners)
|
||||
{
|
||||
uint64_t memberId = 0;
|
||||
uint64_t networkId = 0;
|
||||
bool isAuth = false;
|
||||
bool wasAuth = false;
|
||||
std::shared_ptr<_Network> nw;
|
||||
|
||||
if (old.is_object()) {
|
||||
memberId = OSUtils::jsonIntHex(old["id"],0ULL);
|
||||
networkId = OSUtils::jsonIntHex(old["nwid"],0ULL);
|
||||
if ((memberId)&&(networkId)) {
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
auto nw2 = _networks.find(networkId);
|
||||
if (nw2 != _networks.end())
|
||||
nw = nw2->second;
|
||||
}
|
||||
if (nw) {
|
||||
std::lock_guard<std::mutex> l(nw->lock);
|
||||
if (OSUtils::jsonBool(old["activeBridge"],false))
|
||||
nw->activeBridgeMembers.erase(memberId);
|
||||
wasAuth = OSUtils::jsonBool(old["authorized"],false);
|
||||
if (wasAuth)
|
||||
nw->authorizedMembers.erase(memberId);
|
||||
json &ips = old["ipAssignments"];
|
||||
if (ips.is_array()) {
|
||||
for(unsigned long i=0;i<ips.size();++i) {
|
||||
json &ipj = ips[i];
|
||||
if (ipj.is_string()) {
|
||||
const std::string ips = ipj;
|
||||
InetAddress ipa(ips.c_str());
|
||||
ipa.setPort(0);
|
||||
nw->allocatedIps.erase(ipa);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (memberConfig.is_object()) {
|
||||
if (!nw) {
|
||||
memberId = OSUtils::jsonIntHex(memberConfig["id"],0ULL);
|
||||
networkId = OSUtils::jsonIntHex(memberConfig["nwid"],0ULL);
|
||||
if ((!memberId)||(!networkId))
|
||||
return;
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
std::shared_ptr<_Network> &nw2 = _networks[networkId];
|
||||
if (!nw2)
|
||||
nw2.reset(new _Network);
|
||||
nw = nw2;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> l(nw->lock);
|
||||
|
||||
nw->members[memberId] = memberConfig;
|
||||
|
||||
if (OSUtils::jsonBool(memberConfig["activeBridge"],false))
|
||||
nw->activeBridgeMembers.insert(memberId);
|
||||
isAuth = OSUtils::jsonBool(memberConfig["authorized"],false);
|
||||
if (isAuth)
|
||||
nw->authorizedMembers.insert(memberId);
|
||||
json &ips = memberConfig["ipAssignments"];
|
||||
if (ips.is_array()) {
|
||||
for(unsigned long i=0;i<ips.size();++i) {
|
||||
json &ipj = ips[i];
|
||||
if (ipj.is_string()) {
|
||||
const std::string ips = ipj;
|
||||
InetAddress ipa(ips.c_str());
|
||||
ipa.setPort(0);
|
||||
nw->allocatedIps.insert(ipa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAuth) {
|
||||
const int64_t ldt = (int64_t)OSUtils::jsonInt(memberConfig["lastDeauthorizedTime"],0ULL);
|
||||
if (ldt > nw->mostRecentDeauthTime)
|
||||
nw->mostRecentDeauthTime = ldt;
|
||||
}
|
||||
}
|
||||
|
||||
if (notifyListeners) {
|
||||
std::lock_guard<std::mutex> ll(_changeListeners_l);
|
||||
for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
|
||||
(*i)->onNetworkMemberUpdate(this,networkId,memberId,memberConfig);
|
||||
}
|
||||
}
|
||||
} else if (memberId) {
|
||||
if (nw) {
|
||||
std::lock_guard<std::mutex> l(nw->lock);
|
||||
nw->members.erase(memberId);
|
||||
}
|
||||
if (networkId) {
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
auto er = _networkByMember.equal_range(memberId);
|
||||
for(auto i=er.first;i!=er.second;++i) {
|
||||
if (i->second == networkId) {
|
||||
_networkByMember.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((notifyListeners)&&((wasAuth)&&(!isAuth)&&(networkId)&&(memberId))) {
|
||||
std::lock_guard<std::mutex> ll(_changeListeners_l);
|
||||
for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
|
||||
(*i)->onNetworkMemberDeauthorize(this,networkId,memberId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DB::_networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners)
|
||||
{
|
||||
if (networkConfig.is_object()) {
|
||||
const std::string ids = networkConfig["id"];
|
||||
const uint64_t networkId = Utils::hexStrToU64(ids.c_str());
|
||||
if (networkId) {
|
||||
std::shared_ptr<_Network> nw;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
std::shared_ptr<_Network> &nw2 = _networks[networkId];
|
||||
if (!nw2)
|
||||
nw2.reset(new _Network);
|
||||
nw = nw2;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> l2(nw->lock);
|
||||
nw->config = networkConfig;
|
||||
}
|
||||
if (notifyListeners) {
|
||||
std::lock_guard<std::mutex> ll(_changeListeners_l);
|
||||
for(auto i=_changeListeners.begin();i!=_changeListeners.end();++i) {
|
||||
(*i)->onNetworkUpdate(this,networkId,networkConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (old.is_object()) {
|
||||
const std::string ids = old["id"];
|
||||
const uint64_t networkId = Utils::hexStrToU64(ids.c_str());
|
||||
if (networkId) {
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
_networks.erase(networkId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DB::_fillSummaryInfo(const std::shared_ptr<_Network> &nw,NetworkSummaryInfo &info)
|
||||
{
|
||||
for(auto ab=nw->activeBridgeMembers.begin();ab!=nw->activeBridgeMembers.end();++ab)
|
||||
info.activeBridges.push_back(Address(*ab));
|
||||
std::sort(info.activeBridges.begin(),info.activeBridges.end());
|
||||
for(auto ip=nw->allocatedIps.begin();ip!=nw->allocatedIps.end();++ip)
|
||||
info.allocatedIps.push_back(*ip);
|
||||
std::sort(info.allocatedIps.begin(),info.allocatedIps.end());
|
||||
info.authorizedMemberCount = (unsigned long)nw->authorizedMembers.size();
|
||||
info.totalMemberCount = (unsigned long)nw->members.size();
|
||||
info.mostRecentDeauthTime = nw->mostRecentDeauthTime;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
177
controller/DB.hpp
Normal file
177
controller/DB.hpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CONTROLLER_DB_HPP
|
||||
#define ZT_CONTROLLER_DB_HPP
|
||||
|
||||
//#define ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Identity.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
#include "../osdep/BlockingQueue.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include "../ext/json/json.hpp"
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
/**
|
||||
* Base class with common infrastructure for all controller DB implementations
|
||||
*/
|
||||
class DB
|
||||
{
|
||||
public:
|
||||
class ChangeListener
|
||||
{
|
||||
public:
|
||||
ChangeListener() {}
|
||||
virtual ~ChangeListener() {}
|
||||
virtual void onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network) {}
|
||||
virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member) {}
|
||||
virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId) {}
|
||||
};
|
||||
|
||||
struct NetworkSummaryInfo
|
||||
{
|
||||
NetworkSummaryInfo() : authorizedMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {}
|
||||
std::vector<Address> activeBridges;
|
||||
std::vector<InetAddress> allocatedIps;
|
||||
unsigned long authorizedMemberCount;
|
||||
unsigned long totalMemberCount;
|
||||
int64_t mostRecentDeauthTime;
|
||||
};
|
||||
|
||||
static void initNetwork(nlohmann::json &network);
|
||||
static void initMember(nlohmann::json &member);
|
||||
static void cleanNetwork(nlohmann::json &network);
|
||||
static void cleanMember(nlohmann::json &member);
|
||||
|
||||
DB();
|
||||
virtual ~DB();
|
||||
|
||||
virtual bool waitForReady() = 0;
|
||||
virtual bool isReady() = 0;
|
||||
|
||||
inline bool hasNetwork(const uint64_t networkId) const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_networks_l);
|
||||
return (_networks.find(networkId) != _networks.end());
|
||||
}
|
||||
|
||||
bool get(const uint64_t networkId,nlohmann::json &network);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,NetworkSummaryInfo &info);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members);
|
||||
|
||||
void networks(std::set<uint64_t> &networks);
|
||||
|
||||
template<typename F>
|
||||
inline void each(F f)
|
||||
{
|
||||
nlohmann::json nullJson;
|
||||
std::lock_guard<std::mutex> lck(_networks_l);
|
||||
for(auto nw=_networks.begin();nw!=_networks.end();++nw) {
|
||||
f(nw->first,nw->second->config,0,nullJson); // first provide network with 0 for member ID
|
||||
for(auto m=nw->second->members.begin();m!=nw->second->members.end();++m) {
|
||||
f(nw->first,nw->second->config,m->first,m->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool save(nlohmann::json &record,bool notifyListeners) = 0;
|
||||
|
||||
virtual void eraseNetwork(const uint64_t networkId) = 0;
|
||||
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId) = 0;
|
||||
|
||||
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) = 0;
|
||||
|
||||
inline void addListener(DB::ChangeListener *const listener)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_changeListeners_l);
|
||||
_changeListeners.push_back(listener);
|
||||
}
|
||||
|
||||
protected:
|
||||
static inline bool _compareRecords(const nlohmann::json &a,const nlohmann::json &b)
|
||||
{
|
||||
if (a.is_object() == b.is_object()) {
|
||||
if (a.is_object()) {
|
||||
if (a.size() != b.size())
|
||||
return false;
|
||||
auto amap = a.get<nlohmann::json::object_t>();
|
||||
auto bmap = b.get<nlohmann::json::object_t>();
|
||||
for(auto ai=amap.begin();ai!=amap.end();++ai) {
|
||||
if (ai->first != "revision") { // ignore revision, compare only non-revision-counter fields
|
||||
auto bi = bmap.find(ai->first);
|
||||
if ((bi == bmap.end())||(bi->second != ai->second))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return (a == b);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct _Network
|
||||
{
|
||||
_Network() : mostRecentDeauthTime(0) {}
|
||||
nlohmann::json config;
|
||||
std::unordered_map<uint64_t,nlohmann::json> members;
|
||||
std::unordered_set<uint64_t> activeBridgeMembers;
|
||||
std::unordered_set<uint64_t> authorizedMembers;
|
||||
std::unordered_set<InetAddress,InetAddress::Hasher> allocatedIps;
|
||||
int64_t mostRecentDeauthTime;
|
||||
std::mutex lock;
|
||||
};
|
||||
|
||||
void _memberChanged(nlohmann::json &old,nlohmann::json &memberConfig,bool notifyListeners);
|
||||
void _networkChanged(nlohmann::json &old,nlohmann::json &networkConfig,bool notifyListeners);
|
||||
void _fillSummaryInfo(const std::shared_ptr<_Network> &nw,NetworkSummaryInfo &info);
|
||||
|
||||
std::vector<DB::ChangeListener *> _changeListeners;
|
||||
std::unordered_map< uint64_t,std::shared_ptr<_Network> > _networks;
|
||||
std::unordered_multimap< uint64_t,uint64_t > _networkByMember;
|
||||
mutable std::mutex _changeListeners_l;
|
||||
mutable std::mutex _networks_l;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
244
controller/DBMirrorSet.cpp
Normal file
244
controller/DBMirrorSet.cpp
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "DBMirrorSet.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
DBMirrorSet::DBMirrorSet(DB::ChangeListener *listener) :
|
||||
_listener(listener),
|
||||
_running(true)
|
||||
{
|
||||
_syncCheckerThread = std::thread([this]() {
|
||||
for(;;) {
|
||||
for(int i=0;i<120;++i) { // 1 minute delay between checks
|
||||
if (!_running)
|
||||
return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
|
||||
std::vector< std::shared_ptr<DB> > dbs;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
if (_dbs.size() <= 1)
|
||||
continue; // no need to do this if there's only one DB, so skip the iteration
|
||||
dbs = _dbs;
|
||||
}
|
||||
|
||||
for(auto db=dbs.begin();db!=dbs.end();++db) {
|
||||
(*db)->each([this,&dbs,&db](uint64_t networkId,const nlohmann::json &network,uint64_t memberId,const nlohmann::json &member) {
|
||||
try {
|
||||
if (network.is_object()) {
|
||||
if (memberId == 0) {
|
||||
for(auto db2=dbs.begin();db2!=dbs.end();++db2) {
|
||||
if (db->get() != db2->get()) {
|
||||
nlohmann::json nw2;
|
||||
if ((!(*db2)->get(networkId,nw2))||((nw2.is_object())&&(OSUtils::jsonInt(nw2["revision"],0) < OSUtils::jsonInt(network["revision"],0)))) {
|
||||
nw2 = network;
|
||||
(*db2)->save(nw2,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (member.is_object()) {
|
||||
for(auto db2=dbs.begin();db2!=dbs.end();++db2) {
|
||||
if (db->get() != db2->get()) {
|
||||
nlohmann::json nw2,m2;
|
||||
if ((!(*db2)->get(networkId,nw2,memberId,m2))||((m2.is_object())&&(OSUtils::jsonInt(m2["revision"],0) < OSUtils::jsonInt(member["revision"],0)))) {
|
||||
m2 = member;
|
||||
(*db2)->save(m2,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ( ... ) {} // skip entries that generate JSON errors
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
DBMirrorSet::~DBMirrorSet()
|
||||
{
|
||||
_running = false;
|
||||
_syncCheckerThread.join();
|
||||
}
|
||||
|
||||
bool DBMirrorSet::hasNetwork(const uint64_t networkId) const
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if ((*d)->hasNetwork(networkId))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if ((*d)->get(networkId,network)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if ((*d)->get(networkId,network,memberId,member))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,DB::NetworkSummaryInfo &info)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if ((*d)->get(networkId,network,memberId,member,info))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBMirrorSet::get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if ((*d)->get(networkId,network,members))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DBMirrorSet::networks(std::set<uint64_t> &networks)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
(*d)->networks(networks);
|
||||
}
|
||||
}
|
||||
|
||||
bool DBMirrorSet::waitForReady()
|
||||
{
|
||||
bool r = false;
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
r |= (*d)->waitForReady();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
bool DBMirrorSet::isReady()
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if (!(*d)->isReady())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBMirrorSet::save(nlohmann::json &record,bool notifyListeners)
|
||||
{
|
||||
std::vector< std::shared_ptr<DB> > dbs;
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
dbs = _dbs;
|
||||
}
|
||||
if (notifyListeners) {
|
||||
for(auto d=dbs.begin();d!=dbs.end();++d) {
|
||||
if ((*d)->save(record,true))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
bool modified = false;
|
||||
for(auto d=dbs.begin();d!=dbs.end();++d) {
|
||||
modified |= (*d)->save(record,false);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
}
|
||||
|
||||
void DBMirrorSet::eraseNetwork(const uint64_t networkId)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
(*d)->eraseNetwork(networkId);
|
||||
}
|
||||
}
|
||||
|
||||
void DBMirrorSet::eraseMember(const uint64_t networkId,const uint64_t memberId)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
(*d)->eraseMember(networkId,memberId);
|
||||
}
|
||||
}
|
||||
|
||||
void DBMirrorSet::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
(*d)->nodeIsOnline(networkId,memberId,physicalAddress);
|
||||
}
|
||||
}
|
||||
|
||||
void DBMirrorSet::onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network)
|
||||
{
|
||||
nlohmann::json record(network);
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if (d->get() != db) {
|
||||
(*d)->save(record,false);
|
||||
}
|
||||
}
|
||||
_listener->onNetworkUpdate(this,networkId,network);
|
||||
}
|
||||
|
||||
void DBMirrorSet::onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member)
|
||||
{
|
||||
nlohmann::json record(member);
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
for(auto d=_dbs.begin();d!=_dbs.end();++d) {
|
||||
if (d->get() != db) {
|
||||
(*d)->save(record,false);
|
||||
}
|
||||
}
|
||||
_listener->onNetworkMemberUpdate(this,networkId,memberId,member);
|
||||
}
|
||||
|
||||
void DBMirrorSet::onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId)
|
||||
{
|
||||
_listener->onNetworkMemberDeauthorize(this,networkId,memberId);
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
84
controller/DBMirrorSet.hpp
Normal file
84
controller/DBMirrorSet.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_DBMIRRORSET_HPP
|
||||
#define ZT_DBMIRRORSET_HPP
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class DBMirrorSet : public DB::ChangeListener
|
||||
{
|
||||
public:
|
||||
DBMirrorSet(DB::ChangeListener *listener);
|
||||
virtual ~DBMirrorSet();
|
||||
|
||||
bool hasNetwork(const uint64_t networkId) const;
|
||||
|
||||
bool get(const uint64_t networkId,nlohmann::json &network);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,const uint64_t memberId,nlohmann::json &member,DB::NetworkSummaryInfo &info);
|
||||
bool get(const uint64_t networkId,nlohmann::json &network,std::vector<nlohmann::json> &members);
|
||||
|
||||
void networks(std::set<uint64_t> &networks);
|
||||
|
||||
bool waitForReady();
|
||||
bool isReady();
|
||||
bool save(nlohmann::json &record,bool notifyListeners);
|
||||
void eraseNetwork(const uint64_t networkId);
|
||||
void eraseMember(const uint64_t networkId,const uint64_t memberId);
|
||||
void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
|
||||
|
||||
// These are called by various DB instances when changes occur.
|
||||
virtual void onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network);
|
||||
virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member);
|
||||
virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId);
|
||||
|
||||
inline void addDB(const std::shared_ptr<DB> &db)
|
||||
{
|
||||
db->addListener(this);
|
||||
std::lock_guard<std::mutex> l(_dbs_l);
|
||||
_dbs.push_back(db);
|
||||
}
|
||||
|
||||
private:
|
||||
DB::ChangeListener *const _listener;
|
||||
std::atomic_bool _running;
|
||||
std::thread _syncCheckerThread;
|
||||
std::vector< std::shared_ptr< DB > > _dbs;
|
||||
mutable std::mutex _dbs_l;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2015 ZeroTier, Inc.
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -13,7 +13,15 @@
|
|||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_SQLITENETWORKCONTROLLER_HPP
|
||||
|
@ -26,11 +34,12 @@
|
|||
#include <vector>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
|
||||
#include "../node/NetworkController.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/Address.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
|
@ -41,26 +50,23 @@
|
|||
|
||||
#include "../ext/json/json.hpp"
|
||||
|
||||
#include "JSONDB.hpp"
|
||||
|
||||
// Number of background threads to start -- not actually started until needed
|
||||
#define ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT 4
|
||||
|
||||
// TTL for circuit tests
|
||||
#define ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION 120000
|
||||
#include "DB.hpp"
|
||||
#include "DBMirrorSet.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
class Node;
|
||||
|
||||
class EmbeddedNetworkController : public NetworkController
|
||||
struct MQConfig;
|
||||
|
||||
class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param node Parent node
|
||||
* @param dbPath Path to store data
|
||||
* @param dbPath Database path (file path or database credentials)
|
||||
*/
|
||||
EmbeddedNetworkController(Node *node,const char *dbPath);
|
||||
EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, MQConfig *mqc = NULL);
|
||||
virtual ~EmbeddedNetworkController();
|
||||
|
||||
virtual void init(const Identity &signingId,Sender *sender);
|
||||
|
@ -94,10 +100,16 @@ public:
|
|||
std::string &responseBody,
|
||||
std::string &responseContentType);
|
||||
|
||||
void threadMain()
|
||||
throw();
|
||||
void handleRemoteTrace(const ZT_RemoteTrace &rt);
|
||||
|
||||
virtual void onNetworkUpdate(const void *db,uint64_t networkId,const nlohmann::json &network);
|
||||
virtual void onNetworkMemberUpdate(const void *db,uint64_t networkId,uint64_t memberId,const nlohmann::json &member);
|
||||
virtual void onNetworkMemberDeauthorize(const void *db,uint64_t networkId,uint64_t memberId);
|
||||
|
||||
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();
|
||||
|
||||
struct _RQEntry
|
||||
{
|
||||
uint64_t nwid;
|
||||
|
@ -105,104 +117,54 @@ private:
|
|||
InetAddress fromAddr;
|
||||
Identity identity;
|
||||
Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> metaData;
|
||||
enum {
|
||||
RQENTRY_TYPE_REQUEST = 0
|
||||
} type;
|
||||
};
|
||||
|
||||
// Gathers a bunch of statistics about members of a network, IP assignments, etc. that we need in various places
|
||||
struct _NetworkMemberInfo
|
||||
struct _MemberStatusKey
|
||||
{
|
||||
_NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {}
|
||||
std::set<Address> activeBridges;
|
||||
std::set<InetAddress> allocatedIps;
|
||||
unsigned long authorizedMemberCount;
|
||||
unsigned long activeMemberCount;
|
||||
unsigned long totalMemberCount;
|
||||
uint64_t mostRecentDeauthTime;
|
||||
uint64_t nmiTimestamp; // time this NMI structure was computed
|
||||
_MemberStatusKey() : networkId(0),nodeId(0) {}
|
||||
_MemberStatusKey(const uint64_t nwid,const uint64_t nid) : networkId(nwid),nodeId(nid) {}
|
||||
uint64_t networkId;
|
||||
uint64_t nodeId;
|
||||
inline bool operator==(const _MemberStatusKey &k) const { return ((k.networkId == networkId)&&(k.nodeId == nodeId)); }
|
||||
};
|
||||
|
||||
static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report);
|
||||
void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
|
||||
void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi);
|
||||
inline void _clearNetworkMemberInfoCache(const uint64_t nwid) { Mutex::Lock _l(_nmiCache_m); _nmiCache.erase(nwid); }
|
||||
void _pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member);
|
||||
|
||||
// These init objects with default and static/informational fields
|
||||
inline void _initMember(nlohmann::json &member)
|
||||
struct _MemberStatus
|
||||
{
|
||||
if (!member.count("authorized")) member["authorized"] = false;
|
||||
if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array();
|
||||
if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array();
|
||||
if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array();
|
||||
if (!member.count("activeBridge")) member["activeBridge"] = false;
|
||||
if (!member.count("tags")) member["tags"] = nlohmann::json::array();
|
||||
if (!member.count("capabilities")) member["capabilities"] = nlohmann::json::array();
|
||||
if (!member.count("creationTime")) member["creationTime"] = OSUtils::now();
|
||||
if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false;
|
||||
if (!member.count("revision")) member["revision"] = 0ULL;
|
||||
if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL;
|
||||
if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL;
|
||||
member["objtype"] = "member";
|
||||
}
|
||||
inline void _initNetwork(nlohmann::json &network)
|
||||
_MemberStatus() : lastRequestTime(0),vMajor(-1),vMinor(-1),vRev(-1),vProto(-1) {}
|
||||
uint64_t lastRequestTime;
|
||||
int vMajor,vMinor,vRev,vProto;
|
||||
Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> lastRequestMetaData;
|
||||
Identity identity;
|
||||
inline bool online(const int64_t now) const { return ((now - lastRequestTime) < (ZT_NETWORK_AUTOCONF_DELAY * 2)); }
|
||||
};
|
||||
struct _MemberStatusHash
|
||||
{
|
||||
if (!network.count("private")) network["private"] = true;
|
||||
if (!network.count("creationTime")) network["creationTime"] = OSUtils::now();
|
||||
if (!network.count("name")) network["name"] = "";
|
||||
if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32;
|
||||
if (!network.count("enableBroadcast")) network["enableBroadcast"] = true;
|
||||
if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}};
|
||||
if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}};
|
||||
if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array();
|
||||
if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array();
|
||||
if (!network.count("tags")) network["tags"] = nlohmann::json::array();
|
||||
if (!network.count("routes")) network["routes"] = nlohmann::json::array();
|
||||
if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array();
|
||||
if (!network.count("rules")) {
|
||||
// If unspecified, rules are set to allow anything and behave like a flat L2 segment
|
||||
network["rules"] = {{
|
||||
{ "not",false },
|
||||
{ "or", false },
|
||||
{ "type","ACTION_ACCEPT" }
|
||||
}};
|
||||
inline std::size_t operator()(const _MemberStatusKey &networkIdNodeId) const
|
||||
{
|
||||
return (std::size_t)(networkIdNodeId.networkId + networkIdNodeId.nodeId);
|
||||
}
|
||||
network["objtype"] = "network";
|
||||
}
|
||||
inline void _addNetworkNonPersistedFields(nlohmann::json &network,uint64_t now,const _NetworkMemberInfo &nmi)
|
||||
{
|
||||
network["clock"] = now;
|
||||
network["authorizedMemberCount"] = nmi.authorizedMemberCount;
|
||||
network["activeMemberCount"] = nmi.activeMemberCount;
|
||||
network["totalMemberCount"] = nmi.totalMemberCount;
|
||||
}
|
||||
inline void _addMemberNonPersistedFields(nlohmann::json &member,uint64_t now)
|
||||
{
|
||||
member["clock"] = now;
|
||||
}
|
||||
|
||||
const uint64_t _startTime;
|
||||
|
||||
BlockingQueue<_RQEntry *> _queue;
|
||||
Thread _threads[ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT];
|
||||
bool _threadsStarted;
|
||||
Mutex _threads_m;
|
||||
|
||||
std::map<uint64_t,_NetworkMemberInfo> _nmiCache;
|
||||
Mutex _nmiCache_m;
|
||||
|
||||
JSONDB _db;
|
||||
Mutex _db_m;
|
||||
};
|
||||
|
||||
const int64_t _startTime;
|
||||
int _listenPort;
|
||||
Node *const _node;
|
||||
std::string _ztPath;
|
||||
std::string _path;
|
||||
|
||||
NetworkController::Sender *_sender;
|
||||
Identity _signingId;
|
||||
std::string _signingIdAddressString;
|
||||
NetworkController::Sender *_sender;
|
||||
|
||||
std::list< ZT_CircuitTest > _tests;
|
||||
Mutex _tests_m;
|
||||
DBMirrorSet _db;
|
||||
BlockingQueue< _RQEntry * > _queue;
|
||||
|
||||
std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime; // last request time by <address,networkId>
|
||||
Mutex _lastRequestTime_m;
|
||||
std::vector<std::thread> _threads;
|
||||
std::mutex _threads_l;
|
||||
|
||||
std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus;
|
||||
std::mutex _memberStatus_l;
|
||||
|
||||
MQConfig *_mqc;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
|
175
controller/FileDB.cpp
Normal file
175
controller/FileDB.cpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "FileDB.hpp"
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
FileDB::FileDB(const char *path) :
|
||||
DB(),
|
||||
_path(path),
|
||||
_networksPath(_path + ZT_PATH_SEPARATOR_S + "network"),
|
||||
_tracePath(_path + ZT_PATH_SEPARATOR_S + "trace"),
|
||||
_running(true)
|
||||
{
|
||||
OSUtils::mkdir(_path.c_str());
|
||||
OSUtils::lockDownFile(_path.c_str(),true);
|
||||
OSUtils::mkdir(_networksPath.c_str());
|
||||
OSUtils::mkdir(_tracePath.c_str());
|
||||
|
||||
std::vector<std::string> networks(OSUtils::listDirectory(_networksPath.c_str(),false));
|
||||
std::string buf;
|
||||
for(auto n=networks.begin();n!=networks.end();++n) {
|
||||
buf.clear();
|
||||
if ((n->length() == 21)&&(OSUtils::readFile((_networksPath + ZT_PATH_SEPARATOR_S + *n).c_str(),buf))) {
|
||||
try {
|
||||
nlohmann::json network(OSUtils::jsonParse(buf));
|
||||
const std::string nwids = network["id"];
|
||||
if (nwids.length() == 16) {
|
||||
nlohmann::json nullJson;
|
||||
_networkChanged(nullJson,network,false);
|
||||
std::string membersPath(_networksPath + ZT_PATH_SEPARATOR_S + nwids + ZT_PATH_SEPARATOR_S "member");
|
||||
std::vector<std::string> members(OSUtils::listDirectory(membersPath.c_str(),false));
|
||||
for(auto m=members.begin();m!=members.end();++m) {
|
||||
buf.clear();
|
||||
if ((m->length() == 15)&&(OSUtils::readFile((membersPath + ZT_PATH_SEPARATOR_S + *m).c_str(),buf))) {
|
||||
try {
|
||||
nlohmann::json member(OSUtils::jsonParse(buf));
|
||||
const std::string addrs = member["id"];
|
||||
if (addrs.length() == 10) {
|
||||
nlohmann::json nullJson2;
|
||||
_memberChanged(nullJson2,member,false);
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch ( ... ) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileDB::~FileDB()
|
||||
{
|
||||
try {
|
||||
_online_l.lock();
|
||||
_running = false;
|
||||
_online_l.unlock();
|
||||
_onlineUpdateThread.join();
|
||||
} catch ( ... ) {}
|
||||
}
|
||||
|
||||
bool FileDB::waitForReady() { return true; }
|
||||
bool FileDB::isReady() { return true; }
|
||||
|
||||
bool FileDB::save(nlohmann::json &record,bool notifyListeners)
|
||||
{
|
||||
char p1[4096],p2[4096],pb[4096];
|
||||
bool modified = false;
|
||||
try {
|
||||
const std::string objtype = record["objtype"];
|
||||
if (objtype == "network") {
|
||||
|
||||
const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL);
|
||||
if (nwid) {
|
||||
nlohmann::json old;
|
||||
get(nwid,old);
|
||||
if ((!old.is_object())||(!_compareRecords(old,record))) {
|
||||
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
|
||||
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),nwid);
|
||||
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1)))
|
||||
fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1);
|
||||
_networkChanged(old,record,notifyListeners);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (objtype == "member") {
|
||||
|
||||
const uint64_t id = OSUtils::jsonIntHex(record["id"],0ULL);
|
||||
const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"],0ULL);
|
||||
if ((id)&&(nwid)) {
|
||||
nlohmann::json network,old;
|
||||
get(nwid,network,id,old);
|
||||
if ((!old.is_object())||(!_compareRecords(old,record))) {
|
||||
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
|
||||
OSUtils::ztsnprintf(pb,sizeof(pb),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)nwid);
|
||||
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json",pb,(unsigned long long)id);
|
||||
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) {
|
||||
OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.16llx",_networksPath.c_str(),(unsigned long long)nwid);
|
||||
OSUtils::mkdir(p2);
|
||||
OSUtils::mkdir(pb);
|
||||
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1)))
|
||||
fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1);
|
||||
}
|
||||
_memberChanged(old,record,notifyListeners);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch ( ... ) {} // drop invalid records missing fields
|
||||
return modified;
|
||||
}
|
||||
|
||||
void FileDB::eraseNetwork(const uint64_t networkId)
|
||||
{
|
||||
nlohmann::json network,nullJson;
|
||||
get(networkId,network);
|
||||
char p[16384];
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),networkId);
|
||||
OSUtils::rm(p);
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)networkId);
|
||||
OSUtils::rmDashRf(p);
|
||||
_networkChanged(network,nullJson,true);
|
||||
std::lock_guard<std::mutex> l(this->_online_l);
|
||||
this->_online.erase(networkId);
|
||||
}
|
||||
|
||||
void FileDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
|
||||
{
|
||||
nlohmann::json network,member,nullJson;
|
||||
get(networkId,network);
|
||||
get(memberId,member);
|
||||
char p[4096];
|
||||
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member" ZT_PATH_SEPARATOR_S "%.10llx.json",_networksPath.c_str(),networkId,memberId);
|
||||
OSUtils::rm(p);
|
||||
_memberChanged(member,nullJson,true);
|
||||
std::lock_guard<std::mutex> l(this->_online_l);
|
||||
this->_online[networkId].erase(memberId);
|
||||
}
|
||||
|
||||
void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
|
||||
{
|
||||
char mid[32],atmp[64];
|
||||
OSUtils::ztsnprintf(mid,sizeof(mid),"%.10llx",(unsigned long long)memberId);
|
||||
physicalAddress.toString(atmp);
|
||||
std::lock_guard<std::mutex> l(this->_online_l);
|
||||
this->_online[networkId][memberId][OSUtils::now()] = physicalAddress;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
60
controller/FileDB.hpp
Normal file
60
controller/FileDB.hpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CONTROLLER_FILEDB_HPP
|
||||
#define ZT_CONTROLLER_FILEDB_HPP
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
class FileDB : public DB
|
||||
{
|
||||
public:
|
||||
FileDB(const char *path);
|
||||
virtual ~FileDB();
|
||||
|
||||
virtual bool waitForReady();
|
||||
virtual bool isReady();
|
||||
virtual bool save(nlohmann::json &record,bool notifyListeners);
|
||||
virtual void eraseNetwork(const uint64_t networkId);
|
||||
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId);
|
||||
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
|
||||
|
||||
protected:
|
||||
std::string _path;
|
||||
std::string _networksPath;
|
||||
std::string _tracePath;
|
||||
std::thread _onlineUpdateThread;
|
||||
std::map< uint64_t,std::map<uint64_t,std::map<int64_t,InetAddress> > > _online;
|
||||
std::mutex _online_l;
|
||||
bool _running;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2015 ZeroTier, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JSONDB.hpp"
|
||||
|
||||
#define ZT_JSONDB_HTTP_TIMEOUT 60000
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
static const nlohmann::json _EMPTY_JSON(nlohmann::json::object());
|
||||
static const std::map<std::string,std::string> _ZT_JSONDB_GET_HEADERS;
|
||||
|
||||
JSONDB::JSONDB(const std::string &basePath) :
|
||||
_basePath(basePath),
|
||||
_ready(false)
|
||||
{
|
||||
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
|
||||
}
|
||||
_ready = _reload(_basePath,std::string());
|
||||
}
|
||||
|
||||
bool JSONDB::writeRaw(const std::string &n,const std::string &obj)
|
||||
{
|
||||
if (!_isValidObjectName(n))
|
||||
return false;
|
||||
if (_httpAddr) {
|
||||
std::map<std::string,std::string> headers;
|
||||
std::string body;
|
||||
std::map<std::string,std::string> 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<const struct sockaddr *>(&_httpAddr),(_basePath+"/"+n).c_str(),reqHeaders,obj.data(),(unsigned long)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)
|
||||
{
|
||||
const bool r = writeRaw(n,OSUtils::jsonDump(obj));
|
||||
_db[n].obj = obj;
|
||||
return r;
|
||||
}
|
||||
|
||||
const nlohmann::json &JSONDB::get(const std::string &n)
|
||||
{
|
||||
while (!_ready) {
|
||||
Thread::sleep(250);
|
||||
_ready = _reload(_basePath,std::string());
|
||||
}
|
||||
|
||||
if (!_isValidObjectName(n))
|
||||
return _EMPTY_JSON;
|
||||
std::map<std::string,_E>::iterator e(_db.find(n));
|
||||
if (e != _db.end())
|
||||
return e->second.obj;
|
||||
|
||||
std::string buf;
|
||||
if (_httpAddr) {
|
||||
std::map<std::string,std::string> headers;
|
||||
const unsigned int sc = Http::GET(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!_isValidObjectName(n))
|
||||
return;
|
||||
|
||||
if (_httpAddr) {
|
||||
std::string body;
|
||||
std::map<std::string,std::string> headers;
|
||||
Http::DEL(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_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());
|
||||
}
|
||||
|
||||
_db.erase(n);
|
||||
}
|
||||
|
||||
bool JSONDB::_reload(const std::string &p,const std::string &b)
|
||||
{
|
||||
if (_httpAddr) {
|
||||
std::string body;
|
||||
std::map<std::string,std::string> headers;
|
||||
const unsigned int sc = Http::GET(2147483647,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast<const struct sockaddr *>(&_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();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch ( ... ) {} // invalid JSON, so maybe incomplete request
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
std::vector<std::string> dl(OSUtils::listDirectory(p.c_str(),true));
|
||||
for(std::vector<std::string>::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));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool JSONDB::_isValidObjectName(const std::string &n)
|
||||
{
|
||||
if (n.length() == 0)
|
||||
return false;
|
||||
const char *p = n.c_str();
|
||||
char c;
|
||||
// For security reasons we should not allow dots, backslashes, or other path characters or potential path characters.
|
||||
while ((c = *(p++))) {
|
||||
if (!( ((c >= 'a')&&(c <= 'z')) || ((c >= 'A')&&(c <= 'Z')) || ((c >= '0')&&(c <= '9')) || (c == '/') || (c == '_') || (c == '~') || (c == '-') ))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string JSONDB::_genPath(const std::string &n,bool create)
|
||||
{
|
||||
std::vector<std::string> pt(OSUtils::split(n.c_str(),"/","",""));
|
||||
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<j;++i) {
|
||||
p.push_back(sep);
|
||||
p.append(pt[i]);
|
||||
if (create) OSUtils::mkdir(p.c_str());
|
||||
}
|
||||
|
||||
p.push_back(sep);
|
||||
p.append(pt[pt.size()-1]);
|
||||
p.append(".json");
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2015 ZeroTier, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ZT_JSONDB_HPP
|
||||
#define ZT_JSONDB_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../node/Constants.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
#include "../node/InetAddress.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../ext/json/json.hpp"
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
#include "../osdep/Http.hpp"
|
||||
#include "../osdep/Thread.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* Hierarchical JSON store that persists into the filesystem or via HTTP
|
||||
*/
|
||||
class JSONDB
|
||||
{
|
||||
public:
|
||||
JSONDB(const std::string &basePath);
|
||||
|
||||
bool writeRaw(const std::string &n,const std::string &obj);
|
||||
|
||||
bool put(const std::string &n,const nlohmann::json &obj);
|
||||
|
||||
inline bool put(const std::string &n1,const std::string &n2,const nlohmann::json &obj) { return this->put((n1 + "/" + n2),obj); }
|
||||
inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3),obj); }
|
||||
inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4),obj); }
|
||||
inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5),obj); }
|
||||
|
||||
const nlohmann::json &get(const std::string &n);
|
||||
|
||||
inline const nlohmann::json &get(const std::string &n1,const std::string &n2) { return this->get((n1 + "/" + n2)); }
|
||||
inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3) { return this->get((n1 + "/" + n2 + "/" + n3)); }
|
||||
inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4)); }
|
||||
inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5)); }
|
||||
|
||||
void erase(const std::string &n);
|
||||
|
||||
inline void erase(const std::string &n1,const std::string &n2) { this->erase(n1 + "/" + n2); }
|
||||
inline void erase(const std::string &n1,const std::string &n2,const std::string &n3) { this->erase(n1 + "/" + n2 + "/" + n3); }
|
||||
inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4); }
|
||||
inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5); }
|
||||
|
||||
template<typename F>
|
||||
inline void filter(const std::string &prefix,F func)
|
||||
{
|
||||
while (!_ready) {
|
||||
Thread::sleep(250);
|
||||
_ready = _reload(_basePath,std::string());
|
||||
}
|
||||
|
||||
for(std::map<std::string,_E>::iterator i(_db.lower_bound(prefix));i!=_db.end();) {
|
||||
if ((i->first.length() >= prefix.length())&&(!memcmp(i->first.data(),prefix.data(),prefix.length()))) {
|
||||
if (!func(i->first,get(i->first))) {
|
||||
std::map<std::string,_E>::iterator i2(i); ++i2;
|
||||
this->erase(i->first);
|
||||
i = i2;
|
||||
} else ++i;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool operator==(const JSONDB &db) const { return ((_basePath == db._basePath)&&(_db == db._db)); }
|
||||
inline bool operator!=(const JSONDB &db) const { return (!(*this == db)); }
|
||||
|
||||
private:
|
||||
bool _reload(const std::string &p,const std::string &b);
|
||||
bool _isValidObjectName(const std::string &n);
|
||||
std::string _genPath(const std::string &n,bool create);
|
||||
|
||||
struct _E
|
||||
{
|
||||
nlohmann::json obj;
|
||||
inline bool operator==(const _E &e) const { return (obj == e.obj); }
|
||||
inline bool operator!=(const _E &e) const { return (obj != e.obj); }
|
||||
};
|
||||
|
||||
InetAddress _httpAddr;
|
||||
std::string _basePath;
|
||||
std::map<std::string,_E> _db;
|
||||
volatile bool _ready;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
419
controller/LFDB.cpp
Normal file
419
controller/LFDB.cpp
Normal file
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "LFDB.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
#include "../ext/cpp-httplib/httplib.h"
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState) :
|
||||
DB(),
|
||||
_myId(myId),
|
||||
_lfOwnerPrivate((lfOwnerPrivate) ? lfOwnerPrivate : ""),
|
||||
_lfOwnerPublic((lfOwnerPublic) ? lfOwnerPublic : ""),
|
||||
_lfNodeHost((lfNodeHost) ? lfNodeHost : "127.0.0.1"),
|
||||
_lfNodePort(((lfNodePort > 0)&&(lfNodePort < 65536)) ? lfNodePort : 9980),
|
||||
_running(true),
|
||||
_ready(false),
|
||||
_storeOnlineState(storeOnlineState)
|
||||
{
|
||||
_syncThread = std::thread([this]() {
|
||||
char controllerAddress[24];
|
||||
const uint64_t controllerAddressInt = _myId.address().toInt();
|
||||
_myId.address().toString(controllerAddress);
|
||||
std::string networksSelectorName("com.zerotier.controller.lfdb:"); networksSelectorName.append(controllerAddress); networksSelectorName.append("/network");
|
||||
|
||||
// LF record masking key is the first 32 bytes of SHA512(controller private key) in hex,
|
||||
// hiding record values from anything but the controller or someone who has its key.
|
||||
uint8_t sha512pk[64];
|
||||
_myId.sha512PrivateKey(sha512pk);
|
||||
char maskingKey [128];
|
||||
Utils::hex(sha512pk,32,maskingKey);
|
||||
|
||||
httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort,600);
|
||||
int64_t timeRangeStart = 0;
|
||||
while (_running.load()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> sl(_state_l);
|
||||
for(auto ns=_state.begin();ns!=_state.end();++ns) {
|
||||
if (ns->second.dirty) {
|
||||
nlohmann::json network;
|
||||
if (get(ns->first,network)) {
|
||||
nlohmann::json newrec,selector0;
|
||||
selector0["Name"] = networksSelectorName;
|
||||
selector0["Ordinal"] = ns->first;
|
||||
newrec["Selectors"].push_back(selector0);
|
||||
newrec["Value"] = network.dump();
|
||||
newrec["OwnerPrivate"] = _lfOwnerPrivate;
|
||||
newrec["MaskingKey"] = maskingKey;
|
||||
newrec["PulseIfUnchanged"] = true;
|
||||
try {
|
||||
auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
ns->second.dirty = false;
|
||||
//printf("SET network %.16llx %s\n",ns->first,resp->body.c_str());
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (create/update network): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update network): %s" ZT_EOL_S,e.what());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update network): unknown exception" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto ms=ns->second.members.begin();ms!=ns->second.members.end();++ms) {
|
||||
if ((_storeOnlineState)&&(ms->second.lastOnlineDirty)&&(ms->second.lastOnlineAddress)) {
|
||||
nlohmann::json newrec,selector0,selector1,selectors,ip;
|
||||
char tmp[1024],tmp2[128];
|
||||
OSUtils::ztsnprintf(tmp,sizeof(tmp),"com.zerotier.controller.lfdb:%s/network/%.16llx/online",controllerAddress,(unsigned long long)ns->first);
|
||||
ms->second.lastOnlineAddress.toIpString(tmp2);
|
||||
selector0["Name"] = tmp;
|
||||
selector0["Ordinal"] = ms->first;
|
||||
selector1["Name"] = tmp2;
|
||||
selector1["Ordinal"] = 0;
|
||||
selectors.push_back(selector0);
|
||||
selectors.push_back(selector1);
|
||||
newrec["Selectors"] = selectors;
|
||||
const uint8_t *const rawip = (const uint8_t *)ms->second.lastOnlineAddress.rawIpData();
|
||||
switch(ms->second.lastOnlineAddress.ss_family) {
|
||||
case AF_INET:
|
||||
for(int j=0;j<4;++j)
|
||||
ip.push_back((unsigned int)rawip[j]);
|
||||
break;
|
||||
case AF_INET6:
|
||||
for(int j=0;j<16;++j)
|
||||
ip.push_back((unsigned int)rawip[j]);
|
||||
break;
|
||||
default:
|
||||
ip = tmp2; // should never happen since only IP transport is currently supported
|
||||
break;
|
||||
}
|
||||
newrec["Value"] = ip;
|
||||
newrec["OwnerPrivate"] = _lfOwnerPrivate;
|
||||
newrec["MaskingKey"] = maskingKey;
|
||||
newrec["Timestamp"] = ms->second.lastOnlineTime;
|
||||
newrec["PulseIfUnchanged"] = true;
|
||||
try {
|
||||
auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
ms->second.lastOnlineDirty = false;
|
||||
//printf("SET member online %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (create/update member online status): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member online status): %s" ZT_EOL_S,e.what());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member online status): unknown exception" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
|
||||
if (ms->second.dirty) {
|
||||
nlohmann::json network,member;
|
||||
if (get(ns->first,network,ms->first,member)) {
|
||||
nlohmann::json newrec,selector0,selector1,selectors;
|
||||
selector0["Name"] = networksSelectorName;
|
||||
selector0["Ordinal"] = ns->first;
|
||||
selector1["Name"] = "member";
|
||||
selector1["Ordinal"] = ms->first;
|
||||
selectors.push_back(selector0);
|
||||
selectors.push_back(selector1);
|
||||
newrec["Selectors"] = selectors;
|
||||
newrec["Value"] = member.dump();
|
||||
newrec["OwnerPrivate"] = _lfOwnerPrivate;
|
||||
newrec["MaskingKey"] = maskingKey;
|
||||
newrec["PulseIfUnchanged"] = true;
|
||||
try {
|
||||
auto resp = htcli.Post("/makerecord",newrec.dump(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
ms->second.dirty = false;
|
||||
//printf("SET member %.16llx %.10llx %s\n",ns->first,ms->first,resp->body.c_str());
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (create/update member): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member): %s" ZT_EOL_S,e.what());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (create/update member): unknown exception" ZT_EOL_S);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
std::ostringstream query;
|
||||
query <<
|
||||
"{"
|
||||
"\"Ranges\":[{"
|
||||
"\"Name\":\"" << networksSelectorName << "\","
|
||||
"\"Range\":[0,18446744073709551615]"
|
||||
"}],"
|
||||
"\"TimeRange\":[" << timeRangeStart << ",9223372036854775807],"
|
||||
"\"MaskingKey\":\"" << maskingKey << "\","
|
||||
"\"Owners\":[\"" << _lfOwnerPublic << "\"]"
|
||||
"}";
|
||||
auto resp = htcli.Post("/query",query.str(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
nlohmann::json results(OSUtils::jsonParse(resp->body));
|
||||
if ((results.is_array())&&(results.size() > 0)) {
|
||||
for(std::size_t ri=0;ri<results.size();++ri) {
|
||||
nlohmann::json &rset = results[ri];
|
||||
if ((rset.is_array())&&(rset.size() > 0)) {
|
||||
|
||||
nlohmann::json &result = rset[0];
|
||||
if (result.is_object()) {
|
||||
nlohmann::json &record = result["Record"];
|
||||
if (record.is_object()) {
|
||||
const std::string recordValue = result["Value"];
|
||||
//printf("GET network %s\n",recordValue.c_str());
|
||||
nlohmann::json network(OSUtils::jsonParse(recordValue));
|
||||
if (network.is_object()) {
|
||||
const std::string idstr = network["id"];
|
||||
const uint64_t id = Utils::hexStrToU64(idstr.c_str());
|
||||
if ((id >> 24) == controllerAddressInt) { // sanity check
|
||||
|
||||
nlohmann::json oldNetwork;
|
||||
if ((timeRangeStart > 0)&&(get(id,oldNetwork))) {
|
||||
const uint64_t revision = network["revision"];
|
||||
const uint64_t prevRevision = oldNetwork["revision"];
|
||||
if (prevRevision < revision) {
|
||||
_networkChanged(oldNetwork,network,timeRangeStart > 0);
|
||||
}
|
||||
} else {
|
||||
nlohmann::json nullJson;
|
||||
_networkChanged(nullJson,network,timeRangeStart > 0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (check for network updates): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for network updates): %s" ZT_EOL_S,e.what());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for network updates): unknown exception" ZT_EOL_S);
|
||||
}
|
||||
|
||||
try {
|
||||
std::ostringstream query;
|
||||
query <<
|
||||
"{"
|
||||
"\"Ranges\":[{"
|
||||
"\"Name\":\"" << networksSelectorName << "\","
|
||||
"\"Range\":[0,18446744073709551615]"
|
||||
"},{"
|
||||
"\"Name\":\"member\","
|
||||
"\"Range\":[0,18446744073709551615]"
|
||||
"}],"
|
||||
"\"TimeRange\":[" << timeRangeStart << ",9223372036854775807],"
|
||||
"\"MaskingKey\":\"" << maskingKey << "\","
|
||||
"\"Owners\":[\"" << _lfOwnerPublic << "\"]"
|
||||
"}";
|
||||
auto resp = htcli.Post("/query",query.str(),"application/json");
|
||||
if (resp) {
|
||||
if (resp->status == 200) {
|
||||
nlohmann::json results(OSUtils::jsonParse(resp->body));
|
||||
if ((results.is_array())&&(results.size() > 0)) {
|
||||
for(std::size_t ri=0;ri<results.size();++ri) {
|
||||
nlohmann::json &rset = results[ri];
|
||||
if ((rset.is_array())&&(rset.size() > 0)) {
|
||||
|
||||
nlohmann::json &result = rset[0];
|
||||
if (result.is_object()) {
|
||||
nlohmann::json &record = result["Record"];
|
||||
if (record.is_object()) {
|
||||
const std::string recordValue = result["Value"];
|
||||
//printf("GET member %s\n",recordValue.c_str());
|
||||
nlohmann::json member(OSUtils::jsonParse(recordValue));
|
||||
if (member.is_object()) {
|
||||
const std::string nwidstr = member["nwid"];
|
||||
const std::string idstr = member["id"];
|
||||
const uint64_t nwid = Utils::hexStrToU64(nwidstr.c_str());
|
||||
const uint64_t id = Utils::hexStrToU64(idstr.c_str());
|
||||
if ((id)&&((nwid >> 24) == controllerAddressInt)) { // sanity check
|
||||
|
||||
nlohmann::json network,oldMember;
|
||||
if ((timeRangeStart > 0)&&(get(nwid,network,id,oldMember))) {
|
||||
const uint64_t revision = member["revision"];
|
||||
const uint64_t prevRevision = oldMember["revision"];
|
||||
if (prevRevision < revision)
|
||||
_memberChanged(oldMember,member,timeRangeStart > 0);
|
||||
} else if (hasNetwork(nwid)) {
|
||||
nlohmann::json nullJson;
|
||||
_memberChanged(nullJson,member,timeRangeStart > 0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: %d from node (check for member updates): %s" ZT_EOL_S,resp->status,resp->body.c_str());
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr,"ERROR: LFDB: node is offline" ZT_EOL_S);
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for member updates): %s" ZT_EOL_S,e.what());
|
||||
} catch ( ... ) {
|
||||
fprintf(stderr,"ERROR: LFDB: unexpected exception querying node (check for member updates): unknown exception" ZT_EOL_S);
|
||||
}
|
||||
|
||||
timeRangeStart = time(nullptr) - 120; // start next query 2m before now to avoid losing updates
|
||||
_ready.store(true);
|
||||
|
||||
for(int k=0;k<4;++k) { // 2s delay between queries for remotely modified networks or members
|
||||
if (!_running.load())
|
||||
return;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LFDB::~LFDB()
|
||||
{
|
||||
_running.store(false);
|
||||
_syncThread.join();
|
||||
}
|
||||
|
||||
bool LFDB::waitForReady()
|
||||
{
|
||||
while (!_ready.load()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LFDB::isReady()
|
||||
{
|
||||
return (_ready.load());
|
||||
}
|
||||
|
||||
bool LFDB::save(nlohmann::json &record,bool notifyListeners)
|
||||
{
|
||||
bool modified = false;
|
||||
const std::string objtype = record["objtype"];
|
||||
if (objtype == "network") {
|
||||
const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL);
|
||||
if (nwid) {
|
||||
nlohmann::json old;
|
||||
get(nwid,old);
|
||||
if ((!old.is_object())||(!_compareRecords(old,record))) {
|
||||
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
|
||||
_networkChanged(old,record,notifyListeners);
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_state_l);
|
||||
_state[nwid].dirty = true;
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
} else if (objtype == "member") {
|
||||
const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"],0ULL);
|
||||
const uint64_t id = OSUtils::jsonIntHex(record["id"],0ULL);
|
||||
if ((id)&&(nwid)) {
|
||||
nlohmann::json network,old;
|
||||
get(nwid,network,id,old);
|
||||
if ((!old.is_object())||(!_compareRecords(old,record))) {
|
||||
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1ULL;
|
||||
_memberChanged(old,record,notifyListeners);
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_state_l);
|
||||
_state[nwid].members[id].dirty = true;
|
||||
}
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
void LFDB::eraseNetwork(const uint64_t networkId)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void LFDB::eraseMember(const uint64_t networkId,const uint64_t memberId)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_state_l);
|
||||
auto nw = _state.find(networkId);
|
||||
if (nw != _state.end()) {
|
||||
auto m = nw->second.members.find(memberId);
|
||||
if (m != nw->second.members.end()) {
|
||||
m->second.lastOnlineTime = OSUtils::now();
|
||||
if (physicalAddress)
|
||||
m->second.lastOnlineAddress = physicalAddress;
|
||||
m->second.lastOnlineDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZeroTier
|
102
controller/LFDB.hpp
Normal file
102
controller/LFDB.hpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CONTROLLER_LFDB_HPP
|
||||
#define ZT_CONTROLLER_LFDB_HPP
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
/**
|
||||
* DB implementation for controller that stores data in LF
|
||||
*/
|
||||
class LFDB : public DB
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param myId This controller's identity
|
||||
* @param path Base path for ZeroTier node itself
|
||||
* @param lfOwnerPrivate LF owner private in PEM format
|
||||
* @param lfOwnerPublic LF owner public in @base62 format
|
||||
* @param lfNodeHost LF node host
|
||||
* @param lfNodePort LF node http (not https) port
|
||||
* @param storeOnlineState If true, store online/offline state and IP info in LF (a lot of data, only for private networks!)
|
||||
*/
|
||||
LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState);
|
||||
virtual ~LFDB();
|
||||
|
||||
virtual bool waitForReady();
|
||||
virtual bool isReady();
|
||||
virtual bool save(nlohmann::json &record,bool notifyListeners);
|
||||
virtual void eraseNetwork(const uint64_t networkId);
|
||||
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId);
|
||||
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress);
|
||||
|
||||
protected:
|
||||
const Identity _myId;
|
||||
|
||||
std::string _lfOwnerPrivate,_lfOwnerPublic;
|
||||
std::string _lfNodeHost;
|
||||
int _lfNodePort;
|
||||
|
||||
struct _MemberState
|
||||
{
|
||||
_MemberState() :
|
||||
lastOnlineAddress(),
|
||||
lastOnlineTime(0),
|
||||
dirty(false),
|
||||
lastOnlineDirty(false) {}
|
||||
InetAddress lastOnlineAddress;
|
||||
int64_t lastOnlineTime;
|
||||
bool dirty;
|
||||
bool lastOnlineDirty;
|
||||
};
|
||||
struct _NetworkState
|
||||
{
|
||||
_NetworkState() :
|
||||
members(),
|
||||
dirty(false) {}
|
||||
std::unordered_map<uint64_t,_MemberState> members;
|
||||
bool dirty;
|
||||
};
|
||||
std::unordered_map<uint64_t,_NetworkState> _state;
|
||||
std::mutex _state_l;
|
||||
|
||||
std::atomic_bool _running;
|
||||
std::atomic_bool _ready;
|
||||
std::thread _syncThread;
|
||||
bool _storeOnlineState;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif
|
1493
controller/PostgreSQL.cpp
Normal file
1493
controller/PostgreSQL.cpp
Normal file
File diff suppressed because it is too large
Load diff
119
controller/PostgreSQL.hpp
Normal file
119
controller/PostgreSQL.hpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#ifndef ZT_CONTROLLER_LIBPQ_HPP
|
||||
#define ZT_CONTROLLER_LIBPQ_HPP
|
||||
|
||||
#define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
|
||||
|
||||
extern "C" {
|
||||
typedef struct pg_conn PGconn;
|
||||
}
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
struct MQConfig;
|
||||
|
||||
/**
|
||||
* A controller database driver that talks to PostgreSQL
|
||||
*
|
||||
* This is for use with ZeroTier Central. Others are free to build and use it
|
||||
* but be aware taht we might change it at any time.
|
||||
*/
|
||||
class PostgreSQL : public DB
|
||||
{
|
||||
public:
|
||||
PostgreSQL(const Identity &myId, const char *path, int listenPort, MQConfig *mqc = NULL);
|
||||
virtual ~PostgreSQL();
|
||||
|
||||
virtual bool waitForReady();
|
||||
virtual bool isReady();
|
||||
virtual bool save(nlohmann::json &record,bool notifyListeners);
|
||||
virtual void eraseNetwork(const uint64_t networkId);
|
||||
virtual void eraseMember(const uint64_t networkId, const uint64_t memberId);
|
||||
virtual void nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress);
|
||||
|
||||
protected:
|
||||
struct _PairHasher
|
||||
{
|
||||
inline std::size_t operator()(const std::pair<uint64_t,uint64_t> &p) const { return (std::size_t)(p.first ^ p.second); }
|
||||
};
|
||||
|
||||
private:
|
||||
void initializeNetworks(PGconn *conn);
|
||||
void initializeMembers(PGconn *conn);
|
||||
void heartbeat();
|
||||
void membersDbWatcher();
|
||||
void _membersWatcher_Postgres(PGconn *conn);
|
||||
void _membersWatcher_RabbitMQ();
|
||||
void networksDbWatcher();
|
||||
void _networksWatcher_Postgres(PGconn *conn);
|
||||
void _networksWatcher_RabbitMQ();
|
||||
|
||||
void commitThread();
|
||||
void onlineNotificationThread();
|
||||
|
||||
enum OverrideMode {
|
||||
ALLOW_PGBOUNCER_OVERRIDE = 0,
|
||||
NO_OVERRIDE = 1
|
||||
};
|
||||
|
||||
PGconn * getPgConn( OverrideMode m = ALLOW_PGBOUNCER_OVERRIDE );
|
||||
|
||||
const Identity _myId;
|
||||
const Address _myAddress;
|
||||
std::string _myAddressStr;
|
||||
std::string _connString;
|
||||
|
||||
BlockingQueue< std::pair<nlohmann::json,bool> > _commitQueue;
|
||||
|
||||
std::thread _heartbeatThread;
|
||||
std::thread _membersDbWatcher;
|
||||
std::thread _networksDbWatcher;
|
||||
std::thread _commitThread[ZT_CENTRAL_CONTROLLER_COMMIT_THREADS];
|
||||
std::thread _onlineNotificationThread;
|
||||
|
||||
std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > _lastOnline;
|
||||
|
||||
mutable std::mutex _lastOnline_l;
|
||||
mutable std::mutex _readyLock;
|
||||
std::atomic<int> _ready, _connected, _run;
|
||||
mutable volatile bool _waitNoticePrinted;
|
||||
|
||||
int _listenPort;
|
||||
|
||||
MQConfig *_mqc;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
||||
#endif // ZT_CONTROLLER_LIBPQ_HPP
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_LIBPQ
|
|
@ -1,12 +1,28 @@
|
|||
Network Controller Microservice
|
||||
======
|
||||
|
||||
Every ZeroTier virtual network has a *network controller*. This is our reference implementation and is the same one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). Network controllers act as configuration servers and certificate authorities for the members of networks. Controllers are located on the network by simply parsing out the first 10 digits of a network's 16-digit network ID: these are the address of the controller.
|
||||
Every ZeroTier virtual network has a *network controller* responsible for admitting members to the network, issuing certificates, and issuing default configuration information.
|
||||
|
||||
As of ZeroTier One version 1.2.0 this code is included in normal builds for desktop, laptop, and server (Linux, etc.) targets, allowing any device to create virtual networks without having to be rebuilt from source with special flags to enable this feature. While this does offer a convenient way to create ad-hoc networks or experiment, we recommend running a dedicated controller somewhere secure and stable for any "serious" use case.
|
||||
This is our reference controller implementation and is the same one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). As of ZeroTier One version 1.2.0 this code is included in normal builds for desktop, laptop, and server (Linux, etc.) targets.
|
||||
|
||||
Controller data is stored in JSON format under `controller.d` in the ZeroTier working directory. It can be copied, rsync'd, placed in `git`, etc. The files under `controller.d` should not be modified in place while the controller is running or data loss may result, and if they are edited directly take care not to save corrupt JSON since that can also lead to data loss when the controller is restarted. Going through the API is strongly preferred to directly modifying these files.
|
||||
|
||||
See the API section below for information about controlling the controller.
|
||||
|
||||
### Scalability and Reliability
|
||||
|
||||
Controllers can in theory host up to 2^24 networks and serve many millions of devices (or more), but we recommend spreading large numbers of networks across many controllers for load balancing and fault tolerance reasons. Since the controller uses the filesystem as its data store we recommend fast filesystems and fast SSD drives for heavily loaded controllers.
|
||||
|
||||
Since ZeroTier nodes are mobile and do not need static IPs, implementing high availability fail-over for controllers is easy. Just replicate their working directories from master to backup and have something automatically fire up the backup if the master goes down. Modern orchestration tools like Nomad and Kubernetes can be of help here.
|
||||
|
||||
### Dockerizing Controllers
|
||||
|
||||
ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it.
|
||||
|
||||
### PostgreSQL Database Implementation
|
||||
|
||||
The default controller stores its data in the filesystem in `controller.d` under ZeroTier's home folder. There's an alternative implementation that stores data in PostgreSQL that can be built with `make central-controller`. Right now this is only guaranteed to build and run on Centos 7 Linux with PostgreSQL 10 installed via the [PostgreSQL Yum Repository](https://www.postgresql.org/download/linux/redhat/) and is designed for use with [ZeroTier Central](https://my.zerotier.com/). You're welcome to use it but we don't "officially" support it for end-user use and it could change at any time.
|
||||
|
||||
### Upgrading from Older (1.1.14 or earlier) Versions
|
||||
|
||||
Older versions of this code used a SQLite database instead of in-filesystem JSON. A migration utility called `migrate-sqlite` is included here and *must* be used to migrate this data to the new format. If the controller is started with an old `controller.db` in its working directory it will terminate after printing an error to *stderr*. This is done to prevent "surprises" for those running DIY controllers using the old code.
|
||||
|
@ -17,27 +33,15 @@ The migration tool is written in nodeJS and can be used like this:
|
|||
npm install
|
||||
node migrate.js </path/to/controller.db> </path/to/controller.d>
|
||||
|
||||
Very old versions of nodeJS may have issues. We tested it with version 7.
|
||||
|
||||
### Scalability and Reliability
|
||||
|
||||
Controllers can in theory host up to 2^24 networks and serve many millions of devices (or more), but we recommend spreading large numbers of networks across many controllers for load balancing and fault tolerance reasons. Since the controller uses the filesystem as its data store we recommend fast filesystems and fast SSD drives for heavily loaded controllers.
|
||||
|
||||
Since ZeroTier nodes are mobile and do not need static IPs, implementing high availability fail-over for controllers is easy. Just replicate their working directories from master to backup and have something automatically fire up the backup if the master goes down. Many modern orchestration tools have built-in support for this. It would also be possible in theory to run controllers on a replicated or distributed filesystem, but we haven't tested this yet.
|
||||
|
||||
### Dockerizing Controllers
|
||||
|
||||
ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it.
|
||||
|
||||
### Network Controller API
|
||||
|
||||
The controller API is hosted via the same JSON API endpoint that ZeroTier One uses for local control (usually at 127.0.0.1 port 9993). All controller options are routed under the `/controller` base path.
|
||||
|
||||
The controller microservice does not implement any fine-grained access control (authentication is via authtoken.secret just like the regular JSON API) or other complex mangement features. It just takes network and network member configurations and reponds to controller queries. We have an enterprise product called [ZeroTier Central](https://my.zerotier.com/) that we host as a service (and that companies can license to self-host) that does this.
|
||||
The controller microservice itself does not implement any fine-grained access control. Access control is via the ZeroTier control interface itself and `authtoken.secret`. This can be sent as the `X-ZT1-Auth` HTTP header field or appended to the URL as `?auth=<token>`. Take care when doing the latter that request URLs are not being logged.
|
||||
|
||||
All working network IDs on a controller must begin with the controller's ZeroTier address. The API will *allow* "foreign" networks to be added but the controller will have no way of doing anything with them since nobody will know to query it. (In the future we might support secondaries, which would make this relevant.)
|
||||
While networks with any valid ID can be added to the controller's database, it will only actually work to control networks whose first 10 hex digits correspond with the network controller's ZeroTier ID. See [section 2.2.1 of the ZeroTier manual](https://zerotier.com/manual.shtml#2_2_1).
|
||||
|
||||
The JSON API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrectly typed and unrecognized fields may result in ignored fields or a 400 (bad request) error.
|
||||
The controller JSON API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrect types may be ignored, set to default values, or set to undefined values.
|
||||
|
||||
#### `/controller`
|
||||
|
||||
|
@ -69,37 +73,39 @@ By making queries to this path you can create, configure, and delete networks. D
|
|||
|
||||
When POSTing new networks take care that their IDs are not in use, otherwise you may overwrite an existing one. To create a new network with a random unused ID, POST to `/controller/network/##########______`. The #'s are the controller's 10-digit ZeroTier address and they're followed by six underscores. Check the `nwid` field of the returned JSON object for your network's newly allocated ID. Subsequent POSTs to this network must refer to its actual path.
|
||||
|
||||
Example:
|
||||
|
||||
`curl -X POST --header "X-ZT1-Auth: secret" -d '{"name":"my network"}' http://localhost:9993/controller/network/305f406058______`
|
||||
|
||||
**Network object format:**
|
||||
|
||||
| Field | Type | Description | Writable |
|
||||
| --------------------- | ------------- | ------------------------------------------------- | -------- |
|
||||
| id | string | 16-digit network ID | no |
|
||||
| nwid | string | 16-digit network ID (old, but still around) | no |
|
||||
| clock | integer | Current clock, ms since epoch | no |
|
||||
| nwid | string | 16-digit network ID (legacy) | no |
|
||||
| objtype | string | Always "network" | no |
|
||||
| name | string | A short name for this network | YES |
|
||||
| creationTime | integer | Time network record was created (ms since epoch) | no |
|
||||
| private | boolean | Is access control enabled? | YES |
|
||||
| enableBroadcast | boolean | Ethernet ff:ff:ff:ff:ff:ff allowed? | YES |
|
||||
| allowPassiveBridging | boolean | Allow any member to bridge (very experimental) | YES |
|
||||
| v4AssignMode | object | IPv4 management and assign options (see below) | YES |
|
||||
| v6AssignMode | object | IPv6 management and assign options (see below) | YES |
|
||||
| mtu | integer | Network MTU (default: 2800) | YES |
|
||||
| multicastLimit | integer | Maximum recipients for a multicast packet | YES |
|
||||
| creationTime | integer | Time network was first created | no |
|
||||
| revision | integer | Network config revision counter | no |
|
||||
| authorizedMemberCount | integer | Number of authorized members (for private nets) | no |
|
||||
| activeMemberCount | integer | Number of members that appear to be online | no |
|
||||
| totalMemberCount | integer | Total known members of this network | no |
|
||||
| routes | array[object] | Managed IPv4 and IPv6 routes; see below | YES |
|
||||
| ipAssignmentPools | array[object] | IP auto-assign ranges; see below | YES |
|
||||
| rules | array[object] | Traffic rules; see below | YES |
|
||||
|
||||
Recent changes:
|
||||
|
||||
* The `ipLocalRoutes` field appeared in older versions but is no longer present. Routes will now show up in `routes`.
|
||||
* The `relays` field is gone since network preferred relays are gone. This capability is replaced by VL1 level federation ("federated roots").
|
||||
|
||||
Other important points:
|
||||
| capabilities | array[object] | Array of capability objects (see below) | YES |
|
||||
| tags | array[object] | Array of tag objects (see below) | YES |
|
||||
| remoteTraceTarget | string | 10-digit ZeroTier ID of remote trace target | YES |
|
||||
| remoteTraceLevel | integer | Remote trace verbosity level | YES |
|
||||
|
||||
* Networks without rules won't carry any traffic. If you don't specify any on network creation an "accept anything" rule set will automatically be added.
|
||||
* Managed IP address assignments and IP assignment pools that do not fall within a route configured in `routes` are ignored and won't be used or sent to members.
|
||||
* The default for `private` is `true` and this is probably what you want. Turning `private` off means *anyone* can join your network with only its 16-digit network ID. It's also impossible to de-authorize a member as these networks don't issue or enforce certificates. Such "party line" networks are used for decentralized app backplanes, gaming, and testing but are otherwise not common.
|
||||
* Changing the MTU can be disruptive and on some operating systems may require a leave/rejoin of the network or a restart of the ZeroTier service.
|
||||
|
||||
**Auto-Assign Modes:**
|
||||
|
||||
|
@ -185,7 +191,7 @@ The entry types and their additional fields are:
|
|||
| `MATCH_TAGS_SAMENESS` | Match if both sides' tags differ by no more than value | `id`,`value` |
|
||||
| `MATCH_TAGS_BITWISE_AND` | Match if both sides' tags AND to value | `id`,`value` |
|
||||
| `MATCH_TAGS_BITWISE_OR` | Match if both sides' tags OR to value | `id`,`value` |
|
||||
| `MATCH_TAGS_BITWISE_XOR` | Match if both sides` tags XOR to value | `id`,`value` |
|
||||
| `MATCH_TAGS_BITWISE_XOR` | Match if both sides' tags XOR to value | `id`,`value` |
|
||||
|
||||
Important notes about rules engine behavior:
|
||||
|
||||
|
@ -202,14 +208,6 @@ Important notes about rules engine behavior:
|
|||
|
||||
This returns a JSON object containing all member IDs as keys and their `memberRevisionCounter` values as values.
|
||||
|
||||
#### `/controller/network/<network ID>/active`
|
||||
|
||||
* Purpose: Get a set of all active members on this network
|
||||
* Methods: GET
|
||||
* Returns: { object }
|
||||
|
||||
This returns an object containing all currently online members and the most recent `recentLog` entries for their last request.
|
||||
|
||||
#### `/controller/network/<network ID>/member/<address>`
|
||||
|
||||
* Purpose: Create, authorize, or remove a network member
|
||||
|
@ -221,28 +219,15 @@ This returns an object containing all currently online members and the most rece
|
|||
| id | string | Member's 10-digit ZeroTier address | no |
|
||||
| address | string | Member's 10-digit ZeroTier address | no |
|
||||
| nwid | string | 16-digit network ID | no |
|
||||
| clock | integer | Current clock, ms since epoch | no |
|
||||
| authorized | boolean | Is member authorized? (for private networks) | YES |
|
||||
| authHistory | array[object] | History of auth changes, latest at end | no |
|
||||
| activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES |
|
||||
| identity | string | Member's public ZeroTier identity (if known) | no |
|
||||
| ipAssignments | array[string] | Managed IP address assignments | YES |
|
||||
| memberRevision | integer | Member revision counter | no |
|
||||
| recentLog | array[object] | Recent member activity log; see below | no |
|
||||
| revision | integer | Member revision counter | no |
|
||||
| vMajor | integer | Most recently known major version | no |
|
||||
| vMinor | integer | Most recently known minor version | no |
|
||||
| vRev | integer | Most recently known revision | no |
|
||||
| vProto | integer | Most recently known protocl version | no |
|
||||
|
||||
Note that managed IP assignments are only used if they fall within a managed route. Otherwise they are ignored.
|
||||
|
||||
**Recent log object format:**
|
||||
|
||||
| Field | Type | Description |
|
||||
| --------------------- | ------------- | ------------------------------------------------- |
|
||||
| ts | integer | Time of request, ms since epoch |
|
||||
| auth | boolean | Was member authorized? |
|
||||
| authBy | string | How was member authorized? |
|
||||
| vMajor | integer | Client major version or -1 if unknown |
|
||||
| vMinor | integer | Client minor version or -1 if unknown |
|
||||
| vRev | integer | Client revision or -1 if unknown |
|
||||
| vProto | integer | ZeroTier protocol version reported by client |
|
||||
| fromAddr | string | Physical address if known |
|
||||
|
||||
The controller can only know a member's `fromAddr` if it's able to establish a direct path to it. Members behind very restrictive firewalls may not have this information since the controller will be receiving the member's requests by way of a relay. ZeroTier does not back-trace IP paths as packets are relayed since this would add a lot of protocol overhead.
|
||||
|
|
134
controller/RabbitMQ.cpp
Normal file
134
controller/RabbitMQ.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
|
||||
#include "RabbitMQ.hpp"
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#include <amqp.h>
|
||||
#include <amqp_tcp_socket.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
RabbitMQ::RabbitMQ(MQConfig *cfg, const char *queueName)
|
||||
: _mqc(cfg)
|
||||
, _qName(queueName)
|
||||
, _socket(NULL)
|
||||
, _status(0)
|
||||
{
|
||||
}
|
||||
|
||||
RabbitMQ::~RabbitMQ()
|
||||
{
|
||||
amqp_channel_close(_conn, _channel, AMQP_REPLY_SUCCESS);
|
||||
amqp_connection_close(_conn, AMQP_REPLY_SUCCESS);
|
||||
amqp_destroy_connection(_conn);
|
||||
}
|
||||
|
||||
void RabbitMQ::init()
|
||||
{
|
||||
struct timeval tval;
|
||||
memset(&tval, 0, sizeof(struct timeval));
|
||||
tval.tv_sec = 5;
|
||||
|
||||
fprintf(stderr, "Initializing RabbitMQ %s\n", _qName);
|
||||
_conn = amqp_new_connection();
|
||||
_socket = amqp_tcp_socket_new(_conn);
|
||||
if (!_socket) {
|
||||
throw std::runtime_error("Can't create socket for RabbitMQ");
|
||||
}
|
||||
|
||||
_status = amqp_socket_open_noblock(_socket, _mqc->host, _mqc->port, &tval);
|
||||
if (_status) {
|
||||
throw std::runtime_error("Can't connect to RabbitMQ");
|
||||
}
|
||||
|
||||
amqp_rpc_reply_t r = amqp_login(_conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN,
|
||||
_mqc->username, _mqc->password);
|
||||
if (r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("RabbitMQ Login Error");
|
||||
}
|
||||
|
||||
static int chan = 0;
|
||||
{
|
||||
Mutex::Lock l(_chan_m);
|
||||
_channel = ++chan;
|
||||
}
|
||||
amqp_channel_open(_conn, _channel);
|
||||
r = amqp_get_rpc_reply(_conn);
|
||||
if(r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("Error opening communication channel");
|
||||
}
|
||||
|
||||
_q = amqp_queue_declare(_conn, _channel, amqp_cstring_bytes(_qName), 0, 0, 0, 0, amqp_empty_table);
|
||||
r = amqp_get_rpc_reply(_conn);
|
||||
if (r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("Error declaring queue " + std::string(_qName));
|
||||
}
|
||||
|
||||
amqp_basic_consume(_conn, _channel, amqp_cstring_bytes(_qName), amqp_empty_bytes, 0, 1, 0, amqp_empty_table);
|
||||
r = amqp_get_rpc_reply(_conn);
|
||||
if (r.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
throw std::runtime_error("Error consuming queue " + std::string(_qName));
|
||||
}
|
||||
fprintf(stderr, "RabbitMQ Init OK %s\n", _qName);
|
||||
}
|
||||
|
||||
std::string RabbitMQ::consume()
|
||||
{
|
||||
amqp_rpc_reply_t res;
|
||||
amqp_envelope_t envelope;
|
||||
amqp_maybe_release_buffers(_conn);
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
res = amqp_consume_message(_conn, &envelope, &timeout, 0);
|
||||
if (res.reply_type != AMQP_RESPONSE_NORMAL) {
|
||||
if (res.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION && res.library_error == AMQP_STATUS_TIMEOUT) {
|
||||
// timeout waiting for message. Return empty string
|
||||
return "";
|
||||
} else {
|
||||
throw std::runtime_error("Error getting message");
|
||||
}
|
||||
}
|
||||
|
||||
std::string msg(
|
||||
(const char*)envelope.message.body.bytes,
|
||||
envelope.message.body.len
|
||||
);
|
||||
amqp_destroy_envelope(&envelope);
|
||||
return msg;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_LIBPQ
|
81
controller/RabbitMQ.hpp
Normal file
81
controller/RabbitMQ.hpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* ZeroTier One - Network Virtualization Everywhere
|
||||
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* You can be released from the requirements of the license by purchasing
|
||||
* a commercial license. Buying such a license is mandatory as soon as you
|
||||
* develop commercial closed-source software that incorporates or links
|
||||
* directly against ZeroTier software without disclosing the source code
|
||||
* of your own application.
|
||||
*/
|
||||
|
||||
#ifndef ZT_CONTROLLER_RABBITMQ_HPP
|
||||
#define ZT_CONTROLLER_RABBITMQ_HPP
|
||||
|
||||
#include "DB.hpp"
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
struct MQConfig {
|
||||
const char *host;
|
||||
int port;
|
||||
const char *username;
|
||||
const char *password;
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#include "../node/Mutex.hpp"
|
||||
|
||||
#include <amqp.h>
|
||||
#include <amqp_tcp_socket.h>
|
||||
#include <string>
|
||||
|
||||
namespace ZeroTier
|
||||
{
|
||||
|
||||
class RabbitMQ {
|
||||
public:
|
||||
RabbitMQ(MQConfig *cfg, const char *queueName);
|
||||
~RabbitMQ();
|
||||
|
||||
void init();
|
||||
|
||||
std::string consume();
|
||||
|
||||
private:
|
||||
MQConfig *_mqc;
|
||||
const char *_qName;
|
||||
|
||||
amqp_socket_t *_socket;
|
||||
amqp_connection_state_t _conn;
|
||||
amqp_queue_declare_ok_t *_q;
|
||||
int _status;
|
||||
|
||||
int _channel;
|
||||
|
||||
Mutex _chan_m;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // ZT_CONTROLLER_USE_LIBPQ
|
||||
|
||||
#endif // ZT_CONTROLLER_RABBITMQ_HPP
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var sqlite3 = require('sqlite3').verbose();
|
||||
var fs = require('fs');
|
||||
var async = require('async');
|
||||
|
||||
function blobToIPv4(b)
|
||||
{
|
||||
if (!b)
|
||||
return null;
|
||||
if (b.length !== 16)
|
||||
return null;
|
||||
return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString();
|
||||
}
|
||||
function blobToIPv6(b)
|
||||
{
|
||||
if (!b)
|
||||
return null;
|
||||
if (b.length !== 16)
|
||||
return null;
|
||||
var s = '';
|
||||
for(var i=0;i<16;++i) {
|
||||
var x = b.readUInt8(i).toString(16);
|
||||
if (x.length === 1)
|
||||
s += '0';
|
||||
s += x;
|
||||
if ((((i+1) & 1) === 0)&&(i !== 15))
|
||||
s += ':';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility');
|
||||
console.log('(c)2017 ZeroTier, Inc. [GPL3]');
|
||||
console.log('');
|
||||
console.log('Usage: node migrate.js </path/to/controller.db> </path/to/controller.d>');
|
||||
console.log('');
|
||||
console.log('The first argument must be the path to the old Sqlite3 controller.db');
|
||||
console.log('file. The second must be the path to the EMPTY controller.d database');
|
||||
console.log('directory for a new (1.1.17 or newer) controller. If this path does');
|
||||
console.log('not exist it will be created.');
|
||||
console.log('');
|
||||
console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.');
|
||||
console.log('If your controller is old you should first upgrade to 1.1.14 and run the');
|
||||
console.log('controller so that it will brings its Sqlite3 database up to the latest');
|
||||
console.log('version before running this migration.');
|
||||
console.log('');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var oldDbPath = process.argv[2];
|
||||
var newDbPath = process.argv[3];
|
||||
|
||||
console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...');
|
||||
console.log('');
|
||||
|
||||
var old = new sqlite3.Database(oldDbPath);
|
||||
|
||||
var networks = {};
|
||||
|
||||
var nodeIdentities = {};
|
||||
var networkCount = 0;
|
||||
var memberCount = 0;
|
||||
var routeCount = 0;
|
||||
var ipAssignmentPoolCount = 0;
|
||||
var ipAssignmentCount = 0;
|
||||
var ruleCount = 0;
|
||||
var oldSchemaVersion = -1;
|
||||
|
||||
async.series([function(nextStep) {
|
||||
|
||||
old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) {
|
||||
oldSchemaVersion = parseInt(row.v)||-1;
|
||||
},nextStep);
|
||||
|
||||
},function(nextStep) {
|
||||
|
||||
if (oldSchemaVersion !== 4) {
|
||||
console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old');
|
||||
console.log('controller to 1.1.14 first and run it once to bring its DB up to date.');
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Reading networks...');
|
||||
old.each('SELECT * FROM Network',function(err,row) {
|
||||
if ((typeof row.id === 'string')&&(row.id.length === 16)) {
|
||||
var flags = parseInt(row.flags)||0;
|
||||
networks[row.id] = {
|
||||
id: row.id,
|
||||
nwid: row.id,
|
||||
objtype: 'network',
|
||||
authTokens: [],
|
||||
capabilities: [],
|
||||
creationTime: parseInt(row.creationTime)||0,
|
||||
enableBroadcast: !!row.enableBroadcast,
|
||||
ipAssignmentPools: [],
|
||||
lastModified: Date.now(),
|
||||
multicastLimit: row.multicastLimit||32,
|
||||
name: row.name||'',
|
||||
private: !!row.private,
|
||||
revision: parseInt(row.revision)||1,
|
||||
rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all
|
||||
routes: [],
|
||||
v4AssignMode: {
|
||||
'zt': ((flags & 1) !== 0)
|
||||
},
|
||||
v6AssignMode: {
|
||||
'6plane': ((flags & 4) !== 0),
|
||||
'rfc4193': ((flags & 2) !== 0),
|
||||
'zt': ((flags & 8) !== 0)
|
||||
},
|
||||
_members: {} // temporary
|
||||
};
|
||||
++networkCount;
|
||||
//console.log(networks[row.id]);
|
||||
}
|
||||
},nextStep);
|
||||
|
||||
},function(nextStep) {
|
||||
|
||||
console.log(' '+networkCount+' networks.');
|
||||
console.log('Reading network route definitions...');
|
||||
old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
|
||||
var network = networks[row.networkId];
|
||||
if (network) {
|
||||
var rt = {
|
||||
target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits),
|
||||
via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null)
|
||||
};
|
||||
network.routes.push(rt);
|
||||
++routeCount;
|
||||
}
|
||||
},nextStep);
|
||||
|
||||
},function(nextStep) {
|
||||
|
||||
console.log(' '+routeCount+' routes in '+networkCount+' networks.');
|
||||
console.log('Reading IP assignment pools...');
|
||||
old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
|
||||
var network = networks[row.networkId];
|
||||
if (network) {
|
||||
var p = {
|
||||
ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)),
|
||||
ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd))
|
||||
};
|
||||
network.ipAssignmentPools.push(p);
|
||||
++ipAssignmentPoolCount;
|
||||
}
|
||||
},nextStep);
|
||||
|
||||
},function(nextStep) {
|
||||
|
||||
console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.');
|
||||
console.log('Reading known node identities...');
|
||||
old.each('SELECT * FROM Node',function(err,row) {
|
||||
nodeIdentities[row.id] = row.identity;
|
||||
},nextStep);
|
||||
|
||||
},function(nextStep) {
|
||||
|
||||
console.log(' '+Object.keys(nodeIdentities).length+' known identities.');
|
||||
console.log('Reading network members...');
|
||||
old.each('SELECT * FROM Member',function(err,row) {
|
||||
var network = networks[row.networkId];
|
||||
if (network) {
|
||||
network._members[row.nodeId] = {
|
||||
id: row.nodeId,
|
||||
address: row.nodeId,
|
||||
objtype: 'member',
|
||||
authorized: !!row.authorized,
|
||||
activeBridge: !!row.activeBridge,
|
||||
authHistory: [],
|
||||
capabilities: [],
|
||||
creationTime: 0,
|
||||
identity: nodeIdentities[row.nodeId]||null,
|
||||
ipAssignments: [],
|
||||
lastAuthorizedTime: (row.authorized) ? Date.now() : 0,
|
||||
lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(),
|
||||
lastModified: Date.now(),
|
||||
lastRequestMetaData: '',
|
||||
noAutoAssignIps: false,
|
||||
nwid: row.networkId,
|
||||
revision: parseInt(row.memberRevision)||1,
|
||||
tags: [],
|
||||
recentLog: []
|
||||
};
|
||||
++memberCount;
|
||||
//console.log(network._members[row.nodeId]);
|
||||
}
|
||||
},nextStep);
|
||||
|
||||
},function(nextStep) {
|
||||
|
||||
console.log(' '+memberCount+' members of '+networkCount+' networks.');
|
||||
console.log('Reading static IP assignments...');
|
||||
old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
|
||||
var network = networks[row.networkId];
|
||||
if (network) {
|
||||
var member = network._members[row.nodeId];
|
||||
if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts
|
||||
if (row.ipVersion == 4) {
|
||||
member.ipAssignments.push(blobToIPv4(row.ip));
|
||||
++ipAssignmentCount;
|
||||
} else if (row.ipVersion == 6) {
|
||||
member.ipAssignments.push(blobToIPv6(row.ip));
|
||||
++ipAssignmentCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
},nextStep);
|
||||
|
||||
},function(nextStep) {
|
||||
|
||||
// Old versions only supported Ethertype whitelisting, so that's
|
||||
// all we mirror forward. The other fields were always unused.
|
||||
|
||||
console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.');
|
||||
console.log('Reading allowed Ethernet types (old basic rules)...');
|
||||
var etherTypesByNetwork = {};
|
||||
old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) {
|
||||
if (row.networkId in networks) {
|
||||
var et = parseInt(row.etherType)||0;
|
||||
var ets = etherTypesByNetwork[row.networkId];
|
||||
if (!ets)
|
||||
etherTypesByNetwork[row.networkId] = [ et ];
|
||||
else ets.push(et);
|
||||
}
|
||||
},function(err) {
|
||||
if (err) return nextStep(err);
|
||||
for(var nwid in etherTypesByNetwork) {
|
||||
var ets = etherTypesByNetwork[nwid].sort();
|
||||
var network = networks[nwid];
|
||||
if (network) {
|
||||
var rules = [];
|
||||
if (ets.indexOf(0) >= 0) {
|
||||
// If 0 is in the list, all Ethernet types are allowed so we accept all.
|
||||
rules.push({ 'type': 'ACTION_ACCEPT' });
|
||||
} else {
|
||||
// Otherwise we whitelist.
|
||||
for(var i=0;i<ets.length;++i) {
|
||||
rules.push({
|
||||
'etherType': ets[i],
|
||||
'not': true,
|
||||
'or': false,
|
||||
'type': 'MATCH_ETHERTYPE'
|
||||
});
|
||||
}
|
||||
rules.push({ 'type': 'ACTION_DROP' });
|
||||
rules.push({ 'type': 'ACTION_ACCEPT' });
|
||||
}
|
||||
network.rules = rules;
|
||||
++ruleCount;
|
||||
}
|
||||
}
|
||||
return nextStep(null);
|
||||
});
|
||||
|
||||
}],function(err) {
|
||||
|
||||
if (err) {
|
||||
console.log('FATAL: '+err.toString());
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
console.log(' '+ruleCount+' ethernet type whitelists converted to new format rules.');
|
||||
old.close();
|
||||
console.log('Done reading and converting Sqlite3 database! Writing JSONDB files...');
|
||||
|
||||
try {
|
||||
fs.mkdirSync(newDbPath,0o700);
|
||||
} catch (e) {}
|
||||
var nwBase = newDbPath+'/network';
|
||||
try {
|
||||
fs.mkdirSync(nwBase,0o700);
|
||||
} catch (e) {}
|
||||
nwBase = nwBase + '/';
|
||||
var nwids = Object.keys(networks).sort();
|
||||
var fileCount = 0;
|
||||
for(var ni=0;ni<nwids.length;++ni) {
|
||||
var network = networks[nwids[ni]];
|
||||
|
||||
var mids = Object.keys(network._members).sort();
|
||||
if (mids.length > 0) {
|
||||
try {
|
||||
fs.mkdirSync(nwBase+network.id);
|
||||
} catch (e) {}
|
||||
var mbase = nwBase+network.id+'/member';
|
||||
try {
|
||||
fs.mkdirSync(mbase,0o700);
|
||||
} catch (e) {}
|
||||
mbase = mbase + '/';
|
||||
|
||||
for(var mi=0;mi<mids.length;++mi) {
|
||||
var member = network._members[mids[mi]];
|
||||
fs.writeFileSync(mbase+member.id+'.json',JSON.stringify(member,null,1),{ mode: 0o600 });
|
||||
++fileCount;
|
||||
//console.log(mbase+member.id+'.json');
|
||||
}
|
||||
}
|
||||
|
||||
delete network._members; // temporary field, not part of actual JSONDB, so don't write
|
||||
fs.writeFileSync(nwBase+network.id+'.json',JSON.stringify(network,null,1),{ mode: 0o600 });
|
||||
++fileCount;
|
||||
//console.log(nwBase+network.id+'.json');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('SUCCESS! Wrote '+fileCount+' JSONDB files.');
|
||||
|
||||
console.log('');
|
||||
console.log('You should still inspect the new DB before going live. Also be sure');
|
||||
console.log('to "chown -R" and "chgrp -R" the new DB to the user and group under');
|
||||
console.log('which the ZeroTier One instance acting as controller will be running.');
|
||||
console.log('The controller must be able to read and write the DB, of course.');
|
||||
console.log('');
|
||||
console.log('Have fun!');
|
||||
|
||||
return process.exit(0);
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"name": "migrate-sqlite",
|
||||
"version": "1.0.0",
|
||||
"description": "Migrate old SQLite to new JSON filesystem DB for ZeroTier network controller",
|
||||
"main": "migrate.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Adam Ierymenko <adam.ierymenko@zerotier.com>",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"async": "^2.1.4",
|
||||
"sqlite3": "^3.1.8"
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue