mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2024-12-19 04:57:53 +00:00
More controller work -- it builds!
This commit is contained in:
parent
bd15262e54
commit
b08ca49580
@ -53,8 +53,6 @@
|
||||
#include "../node/MAC.hpp"
|
||||
#include "../node/Address.hpp"
|
||||
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
|
||||
// offbase includes and builds upon nlohmann::json
|
||||
using json = nlohmann::json;
|
||||
|
||||
@ -73,18 +71,15 @@ using json = nlohmann::json;
|
||||
// Min duration between requests for an address/nwid combo to prevent floods
|
||||
#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000
|
||||
|
||||
// Delay between backups in milliseconds
|
||||
//#define ZT_NETCONF_BACKUP_PERIOD 300000
|
||||
|
||||
// Nodes are considered active if they've queried in less than this long
|
||||
#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD ((ZT_NETWORK_AUTOCONF_DELAY * 2) + 5000)
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
struct json::object _renderRule(ZT_VirtualNetworkRule &rule)
|
||||
static json _renderRule(ZT_VirtualNetworkRule &rule)
|
||||
{
|
||||
char tmp[128];
|
||||
json::object r;
|
||||
json r = json::object();
|
||||
r["not"] = ((rule.t & 0x80) != 0);
|
||||
switch((rule.t) & 0x7f) {
|
||||
case ZT_NETWORK_RULE_ACTION_DROP:
|
||||
@ -205,8 +200,10 @@ struct json::object _renderRule(ZT_VirtualNetworkRule &rule)
|
||||
return r;
|
||||
}
|
||||
|
||||
struct bool _parseRule(const json::object &r,ZT_VirtualNetworkRule &rule)
|
||||
static bool _parseRule(const json &r,ZT_VirtualNetworkRule &rule)
|
||||
{
|
||||
if (r.is_object())
|
||||
return false;
|
||||
std::string t = r["type"];
|
||||
memset(&rule,0,sizeof(ZT_VirtualNetworkRule));
|
||||
if (r.value("not",false))
|
||||
@ -220,19 +217,19 @@ struct bool _parseRule(const json::object &r,ZT_VirtualNetworkRule &rule)
|
||||
return true;
|
||||
} else if (t == "ACTION_TEE") {
|
||||
rule.t |= ZT_NETWORK_RULE_ACTION_TEE;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0")) & 0xffffffffffULL;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0").c_str()) & 0xffffffffffULL;
|
||||
return true;
|
||||
} else if (t == "ACTION_REDIRECT") {
|
||||
rule.t |= ZT_NETWORK_RULE_ACTION_REDIRECT;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0")) & 0xffffffffffULL;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0").c_str()) & 0xffffffffffULL;
|
||||
return true;
|
||||
} else if (t == "MATCH_SOURCE_ZEROTIER_ADDRESS") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0")) & 0xffffffffffULL;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0").c_str()) & 0xffffffffffULL;
|
||||
return true;
|
||||
} else if (t == "MATCH_DEST_ZEROTIER_ADDRESS") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0")) & 0xffffffffffULL;
|
||||
rule.v.zt = Utils::hexStrToU64(r.value("zt","0").c_str()) & 0xffffffffffULL;
|
||||
return true;
|
||||
} else if (t == "MATCH_VLAN_ID") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_ID;
|
||||
@ -309,48 +306,58 @@ struct bool _parseRule(const json::object &r,ZT_VirtualNetworkRule &rule)
|
||||
} else if (t == "MATCH_CHARACTERISTICS") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_CHARACTERISTICS;
|
||||
if (r.count("mask")) {
|
||||
auto mask = r["mask"];
|
||||
rule.v.characteristics[0] = (mask.is_integer() ? (uint64_t)mask : Utils::hexStrToU64(mask));
|
||||
auto v = r["mask"];
|
||||
if (v.is_number()) {
|
||||
rule.v.characteristics[0] = v;
|
||||
} else {
|
||||
std::string tmp = v;
|
||||
rule.v.characteristics[0] = Utils::hexStrToU64(tmp.c_str());
|
||||
}
|
||||
}
|
||||
if (r.count("value")) {
|
||||
auto value = r["value"];
|
||||
rule.v.characteristics[1] = (value.is_integer() ? (uint64_t)value : Utils::hexStrToU64(value));
|
||||
auto v = r["value"];
|
||||
if (v.is_number()) {
|
||||
rule.v.characteristics[1] = v;
|
||||
} else {
|
||||
std::string tmp = v;
|
||||
rule.v.characteristics[1] = Utils::hexStrToU64(tmp.c_str());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (t == "MATCH_FRAME_SIZE_RANGE") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE;
|
||||
rule.v.frameSize[0] = (uint16_t)(Utils::hexStrToU64(r.value("start","0")) & 0xffffULL);
|
||||
rule.v.frameSize[1] = (uint16_t)(Utils::hexStrToU64(r.value("end","0")) & 0xffffULL);
|
||||
rule.v.frameSize[0] = (uint16_t)(Utils::hexStrToU64(r.value("start","0").c_str()) & 0xffffULL);
|
||||
rule.v.frameSize[1] = (uint16_t)(Utils::hexStrToU64(r.value("end","0").c_str()) & 0xffffULL);
|
||||
return true;
|
||||
} else if (t == "MATCH_TAGS_SAMENESS") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_SAMENESS;
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0")) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0")) & 0xffffffffULL);
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0").c_str()) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0").c_str()) & 0xffffffffULL);
|
||||
return true;
|
||||
} else if (t == "MATCH_TAGS_BITWISE_AND") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND;
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0")) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0")) & 0xffffffffULL);
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0").c_str()) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0").c_str()) & 0xffffffffULL);
|
||||
return true;
|
||||
} else if (t == "MATCH_TAGS_BITWISE_OR") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR;
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0")) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0")) & 0xffffffffULL);
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0").c_str()) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0").c_str()) & 0xffffffffULL);
|
||||
return true;
|
||||
} else if (t == "MATCH_TAGS_BITWISE_XOR") {
|
||||
rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR;
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0")) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0")) & 0xffffffffULL);
|
||||
rule.v.tag.id = (uint32_t)(Utils::hexStrToU64(r.value("id","0").c_str()) & 0xffffffffULL);
|
||||
rule.v.tag.value = (uint32_t)(Utils::hexStrToU64(r.value("value","0").c_str()) & 0xffffffffULL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath) :
|
||||
SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath) :
|
||||
_node(node),
|
||||
_db(dbPath),
|
||||
_dbCommitThreadRun(true)
|
||||
_path(dbPath)
|
||||
{
|
||||
OSUtils::mkdir(dbPath);
|
||||
/*
|
||||
if (sqlite3_open_v2(dbPath,&_db,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK)
|
||||
throw std::runtime_error("SqliteNetworkController cannot open database file");
|
||||
@ -592,16 +599,10 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c
|
||||
|
||||
_backupThread = Thread::start(this);
|
||||
*/
|
||||
|
||||
_dbCommitThread = Thread::start(this);
|
||||
}
|
||||
|
||||
SqliteNetworkController::~SqliteNetworkController()
|
||||
{
|
||||
_lock.lock();
|
||||
_dbCommitThreadRun = false;
|
||||
_lock.unlock();
|
||||
Thread::join(_dbCommitThread);
|
||||
}
|
||||
|
||||
NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData,NetworkConfig &nc)
|
||||
@ -1068,7 +1069,6 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
|
||||
std::string &responseBody,
|
||||
std::string &responseContentType)
|
||||
{
|
||||
Mutex::Lock _l(_lock);
|
||||
return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType);
|
||||
}
|
||||
|
||||
@ -1082,41 +1082,21 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
{
|
||||
if (path.empty())
|
||||
return 404;
|
||||
Mutex::Lock _l(_lock);
|
||||
|
||||
if (path[0] == "network") {
|
||||
json &networks = _db["network"];
|
||||
if (!networks.is_object()) networks = json::object();
|
||||
|
||||
if ((path.size() >= 2)&&(path[1].length() == 16)) {
|
||||
uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
|
||||
char nwids[24];
|
||||
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
|
||||
|
||||
/*
|
||||
int64_t revision = 0;
|
||||
sqlite3_reset(_sGetNetworkRevision);
|
||||
sqlite3_bind_text(_sGetNetworkRevision,1,nwids,16,SQLITE_STATIC);
|
||||
bool networkExists = false;
|
||||
if (sqlite3_step(_sGetNetworkRevision) == SQLITE_ROW) {
|
||||
networkExists = true;
|
||||
revision = sqlite3_column_int64(_sGetNetworkRevision,0);
|
||||
}
|
||||
*/
|
||||
|
||||
if (path.size() >= 3) {
|
||||
auto network = networks.get<const json::object_t *>(nwids);
|
||||
if (!network) return 404;
|
||||
json network(_readJson(_networkJP(nwid,false)));
|
||||
if (!network.size())
|
||||
return 404;
|
||||
|
||||
if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
|
||||
json &members = (*network)["member"];
|
||||
if (!members.is_object()) members = json::object();
|
||||
|
||||
uint64_t address = Utils::hexStrToU64(path[3].c_str());
|
||||
char addrs[24];
|
||||
Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
|
||||
json &member = members[addrs];
|
||||
if (!member.is_object()) member = json::object();
|
||||
|
||||
/*
|
||||
int64_t addToNetworkRevision = 0;
|
||||
@ -1256,6 +1236,9 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
|
||||
return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType);
|
||||
} else if ((path.size() == 3)&&(path[2] == "test")) {
|
||||
|
||||
Mutex::Lock _l(_circuitTests_m);
|
||||
|
||||
ZT_CircuitTest *test = (ZT_CircuitTest *)malloc(sizeof(ZT_CircuitTest));
|
||||
memset(test,0,sizeof(ZT_CircuitTest));
|
||||
|
||||
@ -1263,6 +1246,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
test->credentialNetworkId = nwid;
|
||||
test->ptr = (void *)this;
|
||||
|
||||
// TODO TODO
|
||||
/*
|
||||
json_value *j = json_parse(body.c_str(),body.length());
|
||||
if (j) {
|
||||
@ -1312,12 +1296,13 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
responseBody = json;
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
|
||||
} // else 404
|
||||
|
||||
} else {
|
||||
// POST to network ID
|
||||
|
||||
json::object b;
|
||||
json b;
|
||||
try {
|
||||
b = json::parse(body);
|
||||
} catch ( ... ) {
|
||||
@ -1334,7 +1319,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
|
||||
if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
|
||||
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid);
|
||||
if (!networks.get<const json::object_t *>(nwids)) {
|
||||
if (!OSUtils::fileExists(_networkJP(tryNwid,false).c_str())) {
|
||||
nwid = tryNwid;
|
||||
break;
|
||||
}
|
||||
@ -1343,24 +1328,23 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
return 503;
|
||||
}
|
||||
|
||||
json::object &network = networks[nwids];
|
||||
if (!network.is_object()) network = json::object();
|
||||
json network(_readJson(_networkJP(nwid,true)));
|
||||
|
||||
try {
|
||||
if (b.count("name")) network["name"] = b.get<std::string>("name");
|
||||
if (b.count("private")) network["private"] = b.get<bool>("private");
|
||||
if (b.count("enableBroadcast")) network["enableBroadcast"] = b.get<bool>("enableBroadcast");
|
||||
if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = b.get<bool>("allowPassiveBridging");
|
||||
if (b.count("multicastLimit")) network["multicastLimit"] = b.get<uint64_t>("multicastLimit");
|
||||
if (b.count("name")) network["name"] = b.value("name","");
|
||||
if (b.count("private")) network["private"] = b.value("private",true);
|
||||
if (b.count("enableBroadcast")) network["enableBroadcast"] = b.value("enableBroadcast",false);
|
||||
if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = b.value("allowPassiveBridging",false);
|
||||
if (b.count("multicastLimit")) network["multicastLimit"] = b.value("multicastLimit",32ULL);
|
||||
|
||||
if (b.count("v4AssignMode")) {
|
||||
auto nv4m = network["v4AssignMode"];
|
||||
if (!nv4m.is_object()) nv6m = json::object();
|
||||
if (!nv4m.is_object()) nv4m = json::object();
|
||||
if (b["v4AssignMode"].is_string()) { // backward compatibility
|
||||
nv4m["zt"] = (b.get<std::string>("v4AssignMode") == "zt");
|
||||
nv4m["zt"] = (b.value("v4AssignMode","") == "zt");
|
||||
} else if (b["v4AssignMode"].is_object()) {
|
||||
auto v4m = b["v4AssignMode"];
|
||||
if (v4m.count("zt")) nv4m["zt"] = v4m.get<bool>("zt");
|
||||
if (v4m.count("zt")) nv4m["zt"] = v4m.value("zt",false);
|
||||
}
|
||||
if (!nv4m.count("zt")) nv4m["zt"] = false;
|
||||
}
|
||||
@ -1369,7 +1353,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
auto nv6m = network["v6AssignMode"];
|
||||
if (!nv6m.is_object()) nv6m = json::object();
|
||||
if (b["v6AssignMode"].is_string()) { // backward compatibility
|
||||
std::vector<std::string> v6m(Utils::split(b.get<std::string>("v6AssignMode").c_str(),",","",""));
|
||||
std::vector<std::string> v6m(Utils::split(b.value("v6AssignMode","").c_str(),",","",""));
|
||||
std::sort(v6m.begin(),v6m.end());
|
||||
v6m.erase(std::unique(v6m.begin(),v6m.end()),v6m.end());
|
||||
for(std::vector<std::string>::iterator i(v6m.begin());i!=v6m.end();++i) {
|
||||
@ -1382,9 +1366,9 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
}
|
||||
} else if (b["v6AssignMode"].is_object()) {
|
||||
auto v6m = b["v6AssignMode"];
|
||||
if (v6m.count("rfc4193")) nv6m["rfc4193"] = v6m.get<bool>("rfc4193");
|
||||
if (v6m.count("zt")) nv6m["rfc4193"] = v6m.get<bool>("zt");
|
||||
if (v6m.count("6plane")) nv6m["rfc4193"] = v6m.get<bool>("6plane");
|
||||
if (v6m.count("rfc4193")) nv6m["rfc4193"] = v6m.value("rfc4193",false);
|
||||
if (v6m.count("zt")) nv6m["rfc4193"] = v6m.value("zt",false);
|
||||
if (v6m.count("6plane")) nv6m["rfc4193"] = v6m.value("6plane",false);
|
||||
}
|
||||
if (!nv6m.count("rfc4193")) nv6m["rfc4193"] = false;
|
||||
if (!nv6m.count("zt")) nv6m["zt"] = false;
|
||||
@ -1397,14 +1381,14 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
for(unsigned long i=0;i<rts.size();++i) {
|
||||
auto rt = rts[i];
|
||||
if ((rt.is_object())&&(rt.count("target"))&&(rt.count("via"))) {
|
||||
InetAddress t(rt.get<std::string>("target"));
|
||||
InetAddress v(rt.get<std::string>("via"));
|
||||
InetAddress t(rt.value("target",""));
|
||||
InetAddress v(rt.value("via",""));
|
||||
if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.ss_family == v.ss_family) && (t.netmaskBitsValid()) ) {
|
||||
auto nrts = network["routes"];
|
||||
if (!nrts.is_array()) nrts = json::array();
|
||||
json::object tmp;
|
||||
tmp["target"] = target.toString();
|
||||
tmp["via"] = target.toIpString();
|
||||
json tmp;
|
||||
tmp["target"] = t.toString();
|
||||
tmp["via"] = v.toIpString();
|
||||
nrts.push_back(tmp);
|
||||
}
|
||||
}
|
||||
@ -1418,12 +1402,12 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
for(unsigned long i=0;i<ipp.size();++i) {
|
||||
auto ip = ipp[i];
|
||||
if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("ipRangeEnd"))) {
|
||||
InetAddress f(ip.get<std::string>("ipRangeStart"));
|
||||
InetAddress t(ip.get<std::string>("ipRangeEnd"));
|
||||
InetAddress f(ip.value("ipRangeStart",""));
|
||||
InetAddress t(ip.value("ipRangeEnd",""));
|
||||
if ( ((f.ss_family == AF_INET)||(f.ss_family == AF_INET6)) && (f.ss_family == t.ss_family) ) {
|
||||
auto nipp = network["ipAssignmentPools"];
|
||||
if (!nipp.is_array()) nipp = json::array();
|
||||
json::object tmp;
|
||||
json tmp;
|
||||
tmp["ipRangeStart"] = f.toIpString();
|
||||
tmp["ipRangeEnd"] = t.toIpString();
|
||||
nipp.push_back(tmp);
|
||||
@ -1461,328 +1445,14 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST(
|
||||
}
|
||||
|
||||
network["lastModified"] = OSUtils::now();
|
||||
network["revision"] = network.get<uint64_t>("revision") + 1ULL;
|
||||
network["revision"] = network.value("revision",0ULL) + 1ULL;
|
||||
|
||||
return _doCPGet(path_copy,urlArgs,headers,body,responseBody,responseContentType);
|
||||
_writeJson(_networkJP(nwid,true),network);
|
||||
|
||||
/*
|
||||
json_value *j = json_parse(body.c_str(),body.length());
|
||||
if (j) {
|
||||
if (j->type == json_object) {
|
||||
for(unsigned int k=0;k<j->u.object.length;++k) {
|
||||
sqlite3_stmt *stmt = (sqlite3_stmt *)0;
|
||||
|
||||
if (!strcmp(j->u.object.values[k].name,"name")) {
|
||||
if ((j->u.object.values[k].value->type == json_string)&&(j->u.object.values[k].value->u.string.ptr[0])) {
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"name\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_text(stmt,1,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC);
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"private")) {
|
||||
if (j->u.object.values[k].value->type == json_boolean) {
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"private\" = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"enableBroadcast")) {
|
||||
if (j->u.object.values[k].value->type == json_boolean) {
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET enableBroadcast = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"allowPassiveBridging")) {
|
||||
if (j->u.object.values[k].value->type == json_boolean) {
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET allowPassiveBridging = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1);
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"v4AssignMode")) {
|
||||
if ((j->u.object.values[k].value->type == json_string)&&(!strcmp(j->u.object.values[k].value->u.string.ptr,"zt"))) {
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = (\"flags\" | ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_int(stmt,1,(int)ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN);
|
||||
} else {
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = (\"flags\" & ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_int(stmt,1,(int)(ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4_AUTO_ASSIGN ^ 0xfffffff));
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"v6AssignMode")) {
|
||||
int fl = 0;
|
||||
if (j->u.object.values[k].value->type == json_string) {
|
||||
char *saveptr = (char *)0;
|
||||
for(char *f=Utils::stok(j->u.object.values[k].value->u.string.ptr,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) {
|
||||
if (!strcmp(f,"rfc4193"))
|
||||
fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193;
|
||||
else if (!strcmp(f,"6plane"))
|
||||
fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE;
|
||||
else if (!strcmp(f,"zt"))
|
||||
fl |= ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_AUTO_ASSIGN;
|
||||
}
|
||||
}
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET \"flags\" = ((\"flags\" & " ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_MASK_S ") | ?) WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_int(stmt,1,fl);
|
||||
} else if (!strcmp(j->u.object.values[k].name,"multicastLimit")) {
|
||||
if (j->u.object.values[k].value->type == json_integer) {
|
||||
if (sqlite3_prepare_v2(_db,"UPDATE Network SET multicastLimit = ? WHERE id = ?",-1,&stmt,(const char **)0) == SQLITE_OK)
|
||||
sqlite3_bind_int(stmt,1,(int)j->u.object.values[k].value->u.integer);
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"relays")) {
|
||||
if (j->u.object.values[k].value->type == json_array) {
|
||||
std::map<Address,InetAddress> nodeIdToPhyAddress;
|
||||
for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
|
||||
json_value *relay = j->u.object.values[k].value->u.array.values[kk];
|
||||
const char *address = (const char *)0;
|
||||
const char *phyAddress = (const char *)0;
|
||||
if ((relay)&&(relay->type == json_object)) {
|
||||
for(unsigned int rk=0;rk<relay->u.object.length;++rk) {
|
||||
if ((!strcmp(relay->u.object.values[rk].name,"address"))&&(relay->u.object.values[rk].value->type == json_string))
|
||||
address = relay->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(relay->u.object.values[rk].name,"phyAddress"))&&(relay->u.object.values[rk].value->type == json_string))
|
||||
phyAddress = relay->u.object.values[rk].value->u.string.ptr;
|
||||
}
|
||||
}
|
||||
if ((address)&&(phyAddress))
|
||||
nodeIdToPhyAddress[Address(address)] = InetAddress(phyAddress);
|
||||
}
|
||||
|
||||
sqlite3_reset(_sDeleteRelaysForNetwork);
|
||||
sqlite3_bind_text(_sDeleteRelaysForNetwork,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_step(_sDeleteRelaysForNetwork);
|
||||
|
||||
for(std::map<Address,InetAddress>::iterator rl(nodeIdToPhyAddress.begin());rl!=nodeIdToPhyAddress.end();++rl) {
|
||||
sqlite3_reset(_sCreateRelay);
|
||||
sqlite3_bind_text(_sCreateRelay,1,nwids,16,SQLITE_STATIC);
|
||||
std::string a(rl->first.toString()),b(rl->second.toString()); // don't destroy strings until sqlite3_step()
|
||||
sqlite3_bind_text(_sCreateRelay,2,a.c_str(),-1,SQLITE_STATIC);
|
||||
sqlite3_bind_text(_sCreateRelay,3,b.c_str(),-1,SQLITE_STATIC);
|
||||
sqlite3_step(_sCreateRelay);
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"routes")) {
|
||||
sqlite3_reset(_sDeleteRoutes);
|
||||
sqlite3_bind_text(_sDeleteRoutes,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_step(_sDeleteRoutes);
|
||||
if (j->u.object.values[k].value->type == json_array) {
|
||||
for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
|
||||
json_value *r = j->u.object.values[k].value->u.array.values[kk];
|
||||
if ((r)&&(r->type == json_object)) {
|
||||
InetAddress r_target,r_via;
|
||||
int r_flags = 0;
|
||||
int r_metric = 0;
|
||||
for(unsigned int rk=0;rk<r->u.object.length;++rk) {
|
||||
if ((!strcmp(r->u.object.values[rk].name,"target"))&&(r->u.object.values[rk].value->type == json_string))
|
||||
r_target = InetAddress(std::string(r->u.object.values[rk].value->u.string.ptr));
|
||||
else if ((!strcmp(r->u.object.values[rk].name,"via"))&&(r->u.object.values[rk].value->type == json_string))
|
||||
r_via = InetAddress(std::string(r->u.object.values[rk].value->u.string.ptr),0);
|
||||
else if ((!strcmp(r->u.object.values[rk].name,"flags"))&&(r->u.object.values[rk].value->type == json_integer))
|
||||
r_flags = (int)(r->u.object.values[rk].value->u.integer & 0xffff);
|
||||
else if ((!strcmp(r->u.object.values[rk].name,"metric"))&&(r->u.object.values[rk].value->type == json_integer))
|
||||
r_metric = (int)(r->u.object.values[rk].value->u.integer & 0xffff);
|
||||
}
|
||||
if ((r_target)&&((!r_via)||(r_via.ss_family == r_target.ss_family))) {
|
||||
int r_ipVersion = 0;
|
||||
char r_targetBlob[16];
|
||||
char r_viaBlob[16];
|
||||
_ipToBlob(r_target,r_targetBlob,r_ipVersion);
|
||||
if (r_ipVersion) {
|
||||
int r_targetNetmaskBits = r_target.netmaskBits();
|
||||
if ((r_ipVersion == 4)&&(r_targetNetmaskBits > 32)) r_targetNetmaskBits = 32;
|
||||
else if ((r_ipVersion == 6)&&(r_targetNetmaskBits > 128)) r_targetNetmaskBits = 128;
|
||||
sqlite3_reset(_sCreateRoute);
|
||||
sqlite3_bind_text(_sCreateRoute,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_bind_blob(_sCreateRoute,2,(const void *)r_targetBlob,16,SQLITE_STATIC);
|
||||
if (r_via) {
|
||||
_ipToBlob(r_via,r_viaBlob,r_ipVersion);
|
||||
sqlite3_bind_blob(_sCreateRoute,3,(const void *)r_viaBlob,16,SQLITE_STATIC);
|
||||
} else {
|
||||
sqlite3_bind_null(_sCreateRoute,3);
|
||||
}
|
||||
sqlite3_bind_int(_sCreateRoute,4,r_targetNetmaskBits);
|
||||
sqlite3_bind_int(_sCreateRoute,5,r_ipVersion);
|
||||
sqlite3_bind_int(_sCreateRoute,6,r_flags);
|
||||
sqlite3_bind_int(_sCreateRoute,7,r_metric);
|
||||
sqlite3_step(_sCreateRoute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"ipAssignmentPools")) {
|
||||
if (j->u.object.values[k].value->type == json_array) {
|
||||
std::vector< std::pair<InetAddress,InetAddress> > pools;
|
||||
for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
|
||||
json_value *pool = j->u.object.values[k].value->u.array.values[kk];
|
||||
const char *iprs = (const char *)0;
|
||||
const char *ipre = (const char *)0;
|
||||
if ((pool)&&(pool->type == json_object)) {
|
||||
for(unsigned int rk=0;rk<pool->u.object.length;++rk) {
|
||||
if ((!strcmp(pool->u.object.values[rk].name,"ipRangeStart"))&&(pool->u.object.values[rk].value->type == json_string))
|
||||
iprs = pool->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(pool->u.object.values[rk].name,"ipRangeEnd"))&&(pool->u.object.values[rk].value->type == json_string))
|
||||
ipre = pool->u.object.values[rk].value->u.string.ptr;
|
||||
}
|
||||
}
|
||||
if ((iprs)&&(ipre)) {
|
||||
InetAddress iprs2(iprs);
|
||||
InetAddress ipre2(ipre);
|
||||
if (iprs2.ss_family == ipre2.ss_family) {
|
||||
iprs2.setPort(0);
|
||||
ipre2.setPort(0);
|
||||
pools.push_back(std::pair<InetAddress,InetAddress>(iprs2,ipre2));
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(pools.begin(),pools.end());
|
||||
pools.erase(std::unique(pools.begin(),pools.end()),pools.end());
|
||||
|
||||
sqlite3_reset(_sDeleteIpAssignmentPoolsForNetwork);
|
||||
sqlite3_bind_text(_sDeleteIpAssignmentPoolsForNetwork,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_step(_sDeleteIpAssignmentPoolsForNetwork);
|
||||
|
||||
for(std::vector< std::pair<InetAddress,InetAddress> >::const_iterator p(pools.begin());p!=pools.end();++p) {
|
||||
char ipBlob1[16],ipBlob2[16];
|
||||
sqlite3_reset(_sCreateIpAssignmentPool);
|
||||
sqlite3_bind_text(_sCreateIpAssignmentPool,1,nwids,16,SQLITE_STATIC);
|
||||
if (p->first.ss_family == AF_INET) {
|
||||
memset(ipBlob1,0,12);
|
||||
memcpy(ipBlob1 + 12,p->first.rawIpData(),4);
|
||||
memset(ipBlob2,0,12);
|
||||
memcpy(ipBlob2 + 12,p->second.rawIpData(),4);
|
||||
sqlite3_bind_blob(_sCreateIpAssignmentPool,2,(const void *)ipBlob1,16,SQLITE_STATIC);
|
||||
sqlite3_bind_blob(_sCreateIpAssignmentPool,3,(const void *)ipBlob2,16,SQLITE_STATIC);
|
||||
sqlite3_bind_int(_sCreateIpAssignmentPool,4,4);
|
||||
} else if (p->first.ss_family == AF_INET6) {
|
||||
sqlite3_bind_blob(_sCreateIpAssignmentPool,2,p->first.rawIpData(),16,SQLITE_STATIC);
|
||||
sqlite3_bind_blob(_sCreateIpAssignmentPool,3,p->second.rawIpData(),16,SQLITE_STATIC);
|
||||
sqlite3_bind_int(_sCreateIpAssignmentPool,4,6);
|
||||
} else continue;
|
||||
sqlite3_step(_sCreateIpAssignmentPool);
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(j->u.object.values[k].name,"rules")) {
|
||||
if (j->u.object.values[k].value->type == json_array) {
|
||||
sqlite3_reset(_sDeleteRulesForNetwork);
|
||||
sqlite3_bind_text(_sDeleteRulesForNetwork,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_step(_sDeleteRulesForNetwork);
|
||||
|
||||
for(unsigned int kk=0;kk<j->u.object.values[k].value->u.array.length;++kk) {
|
||||
json_value *rj = j->u.object.values[k].value->u.array.values[kk];
|
||||
if ((rj)&&(rj->type == json_object)) {
|
||||
struct { // NULL pointers indicate missing or NULL -- wildcards
|
||||
const json_int_t *ruleNo;
|
||||
const char *nodeId;
|
||||
const char *sourcePort;
|
||||
const char *destPort;
|
||||
const json_int_t *vlanId;
|
||||
const json_int_t *vlanPcp;
|
||||
const json_int_t *etherType;
|
||||
const char *macSource;
|
||||
const char *macDest;
|
||||
const char *ipSource;
|
||||
const char *ipDest;
|
||||
const json_int_t *ipTos;
|
||||
const json_int_t *ipProtocol;
|
||||
const json_int_t *ipSourcePort;
|
||||
const json_int_t *ipDestPort;
|
||||
const json_int_t *flags;
|
||||
const json_int_t *invFlags;
|
||||
const char *action;
|
||||
} rule;
|
||||
memset(&rule,0,sizeof(rule));
|
||||
|
||||
for(unsigned int rk=0;rk<rj->u.object.length;++rk) {
|
||||
if ((!strcmp(rj->u.object.values[rk].name,"ruleNo"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.ruleNo = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"nodeId"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.nodeId = rj->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"sourcePort"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.sourcePort = rj->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"destPort"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.destPort = rj->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"vlanId"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.vlanId = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"vlanPcp"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.vlanPcp = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"etherType"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.etherType = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"macSource"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.macSource = rj->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"macDest"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.macDest = rj->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"ipSource"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.ipSource = rj->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"ipDest"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.ipDest = rj->u.object.values[rk].value->u.string.ptr;
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"ipTos"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.ipTos = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"ipProtocol"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.ipProtocol = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"ipSourcePort"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.ipSourcePort = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"ipDestPort"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.ipDestPort = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"flags"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.flags = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"invFlags"))&&(rj->u.object.values[rk].value->type == json_integer))
|
||||
rule.invFlags = &(rj->u.object.values[rk].value->u.integer);
|
||||
else if ((!strcmp(rj->u.object.values[rk].name,"action"))&&(rj->u.object.values[rk].value->type == json_string))
|
||||
rule.action = rj->u.object.values[rk].value->u.string.ptr;
|
||||
}
|
||||
|
||||
if ((rule.ruleNo)&&(rule.action)&&(rule.action[0])) {
|
||||
char mactmp1[16],mactmp2[16];
|
||||
sqlite3_reset(_sCreateRule);
|
||||
sqlite3_bind_text(_sCreateRule,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_bind_int64(_sCreateRule,2,*rule.ruleNo);
|
||||
|
||||
// Optional values: null by default
|
||||
for(int i=3;i<=18;++i)
|
||||
sqlite3_bind_null(_sCreateRule,i);
|
||||
if ((rule.nodeId)&&(strlen(rule.nodeId) == 10)) sqlite3_bind_text(_sCreateRule,3,rule.nodeId,10,SQLITE_STATIC);
|
||||
if ((rule.sourcePort)&&(strlen(rule.sourcePort) == 10)) sqlite3_bind_text(_sCreateRule,4,rule.sourcePort,10,SQLITE_STATIC);
|
||||
if ((rule.destPort)&&(strlen(rule.destPort) == 10)) sqlite3_bind_text(_sCreateRule,5,rule.destPort,10,SQLITE_STATIC);
|
||||
if (rule.vlanId) sqlite3_bind_int(_sCreateRule,6,(int)*rule.vlanId);
|
||||
if (rule.vlanPcp) sqlite3_bind_int(_sCreateRule,7,(int)*rule.vlanPcp);
|
||||
if (rule.etherType) sqlite3_bind_int(_sCreateRule,8,(int)*rule.etherType & (int)0xffff);
|
||||
if (rule.macSource) {
|
||||
MAC m(rule.macSource);
|
||||
Utils::snprintf(mactmp1,sizeof(mactmp1),"%.12llx",(unsigned long long)m.toInt());
|
||||
sqlite3_bind_text(_sCreateRule,9,mactmp1,-1,SQLITE_STATIC);
|
||||
}
|
||||
if (rule.macDest) {
|
||||
MAC m(rule.macDest);
|
||||
Utils::snprintf(mactmp2,sizeof(mactmp2),"%.12llx",(unsigned long long)m.toInt());
|
||||
sqlite3_bind_text(_sCreateRule,10,mactmp2,-1,SQLITE_STATIC);
|
||||
}
|
||||
if (rule.ipSource) sqlite3_bind_text(_sCreateRule,11,rule.ipSource,-1,SQLITE_STATIC);
|
||||
if (rule.ipDest) sqlite3_bind_text(_sCreateRule,12,rule.ipDest,-1,SQLITE_STATIC);
|
||||
if (rule.ipTos) sqlite3_bind_int(_sCreateRule,13,(int)*rule.ipTos);
|
||||
if (rule.ipProtocol) sqlite3_bind_int(_sCreateRule,14,(int)*rule.ipProtocol);
|
||||
if (rule.ipSourcePort) sqlite3_bind_int(_sCreateRule,15,(int)*rule.ipSourcePort & (int)0xffff);
|
||||
if (rule.ipDestPort) sqlite3_bind_int(_sCreateRule,16,(int)*rule.ipDestPort & (int)0xffff);
|
||||
if (rule.flags) sqlite3_bind_int64(_sCreateRule,17,(int64_t)*rule.flags);
|
||||
if (rule.invFlags) sqlite3_bind_int64(_sCreateRule,18,(int64_t)*rule.invFlags);
|
||||
|
||||
sqlite3_bind_text(_sCreateRule,19,rule.action,-1,SQLITE_STATIC);
|
||||
sqlite3_step(_sCreateRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stmt) {
|
||||
sqlite3_bind_text(stmt,2,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
json_value_free(j);
|
||||
}
|
||||
|
||||
sqlite3_reset(_sSetNetworkRevision);
|
||||
sqlite3_bind_int64(_sSetNetworkRevision,1,revision += 1);
|
||||
sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_step(_sSetNetworkRevision);
|
||||
|
||||
return _doCPGet(path_copy,urlArgs,headers,body,responseBody,responseContentType);
|
||||
}
|
||||
*/
|
||||
responseBody = network.dump(2);
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
} // else 404
|
||||
|
||||
} // else 404
|
||||
|
||||
@ -1801,38 +1471,32 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
|
||||
{
|
||||
if (path.empty())
|
||||
return 404;
|
||||
Mutex::Lock _l(_lock);
|
||||
|
||||
if (path[0] == "network") {
|
||||
auto networks = _db.get<const json::object_t *>("network");
|
||||
if (!networks) return 404;
|
||||
|
||||
if ((path.size() >= 2)&&(path[1].length() == 16)) {
|
||||
const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
|
||||
char nwids[24];
|
||||
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
|
||||
auto network = _db.get<const json::object_t *>(nwids);
|
||||
if (!network) return 404;
|
||||
|
||||
json network(_readJson(_networkJP(nwid,false)));
|
||||
if (!network.size())
|
||||
return 404;
|
||||
|
||||
if (path.size() >= 3) {
|
||||
if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) {
|
||||
auto members = network->get<const json::object_t *>("member");
|
||||
if (!members) return 404;
|
||||
|
||||
const uint64_t address = Utils::hexStrToU64(path[3].c_str());
|
||||
char addrs[24];
|
||||
Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
|
||||
auto member = members->get<const json::object_t *>(addrs);
|
||||
if (!member) return 404;
|
||||
|
||||
members->erase(addrs);
|
||||
responseBody = member->dump(2);
|
||||
json member(_readJson(_memberJP(nwid,Address(address),false)));
|
||||
if (!member.size())
|
||||
return 404;
|
||||
|
||||
OSUtils::rmDashRf(_memberBP(nwid,Address(address),false).c_str());
|
||||
|
||||
responseBody = member.dump(2);
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
}
|
||||
} else {
|
||||
networks->erase(nwids);
|
||||
responseBody = network->dump(2);
|
||||
OSUtils::rmDashRf(_networkBP(nwid,false).c_str());
|
||||
responseBody = network.dump(2);
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
}
|
||||
@ -1843,27 +1507,6 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE(
|
||||
return 404;
|
||||
}
|
||||
|
||||
void SqliteNetworkController::threadMain()
|
||||
throw()
|
||||
{
|
||||
uint64_t lastCommit = OSUtils::now();
|
||||
while(_dbCommitThreadRun) {
|
||||
Thread::sleep(200);
|
||||
if ((OSUtils::now() - lastCommit) > 2000) {
|
||||
lastCommit = OSUtils::now();
|
||||
try {
|
||||
std::vector<std::string> errors;
|
||||
Mutex::Lock _l(_lock);
|
||||
if (!_db.commit(&errors)) {
|
||||
// TODO: handle anything really bad
|
||||
}
|
||||
} catch ( ... ) {
|
||||
// TODO: handle anything really bad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int SqliteNetworkController::_doCPGet(
|
||||
const std::vector<std::string> &path,
|
||||
const std::map<std::string,std::string> &urlArgs,
|
||||
@ -1874,146 +1517,89 @@ unsigned int SqliteNetworkController::_doCPGet(
|
||||
{
|
||||
// Assumes _lock is locked
|
||||
if ((path.size() > 0)&&(path[0] == "network")) {
|
||||
auto networks = _db.get<const json::object_t *>("network");
|
||||
if (!networks) return 404;
|
||||
|
||||
if ((path.size() >= 2)&&(path[1].length() == 16)) {
|
||||
const uint64_t nwid = Utils::hexStrToU64(path[1].c_str());
|
||||
char nwids[24];
|
||||
Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
|
||||
auto network = _db.get<const json::object_t *>(nwids);
|
||||
if (!network) return 404;
|
||||
|
||||
json network(_readJson(_networkJP(nwid,false)));
|
||||
if (!network.size())
|
||||
return 404;
|
||||
|
||||
if (path.size() >= 3) {
|
||||
|
||||
if (path[2] == "member") {
|
||||
auto members = network->get<const json::object_t *>("member");
|
||||
if (!members) return 404;
|
||||
|
||||
if (path.size() >= 4) {
|
||||
const uint64_t address = Utils::hexStrToU64(path[3].c_str());
|
||||
|
||||
json member(_readJson(_memberJP(nwid,Address(address),false)));
|
||||
if (!member.size())
|
||||
return 404;
|
||||
|
||||
char addrs[24];
|
||||
Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address);
|
||||
auto member = members->get<const json::object_t *>(addrs);
|
||||
if (!member) return 404;
|
||||
|
||||
nlohmann::json o(member);
|
||||
json o(member);
|
||||
o["nwid"] = nwids;
|
||||
o["address"] = addrs;
|
||||
o["controllerInstanceId"] = _instanceId;
|
||||
o["clock"] = OSUtils::now();
|
||||
responseBody = o.dump(2);
|
||||
responseContentType = "application/json";
|
||||
|
||||
return 200;
|
||||
|
||||
/*
|
||||
sqlite3_reset(_sGetMember2);
|
||||
sqlite3_bind_text(_sGetMember2,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_bind_text(_sGetMember2,2,addrs,10,SQLITE_STATIC);
|
||||
if (sqlite3_step(_sGetMember2) == SQLITE_ROW) {
|
||||
const char *memberIdStr = (const char *)sqlite3_column_text(_sGetMember2,3);
|
||||
|
||||
Utils::snprintf(json,sizeof(json),
|
||||
"{\n"
|
||||
"\t\"nwid\": \"%s\",\n"
|
||||
"\t\"address\": \"%s\",\n"
|
||||
"\t\"controllerInstanceId\": \"%s\",\n"
|
||||
"\t\"authorized\": %s,\n"
|
||||
"\t\"activeBridge\": %s,\n"
|
||||
"\t\"memberRevision\": %llu,\n"
|
||||
"\t\"clock\": %llu,\n"
|
||||
"\t\"identity\": \"%s\",\n"
|
||||
"\t\"ipAssignments\": [",
|
||||
nwids,
|
||||
addrs,
|
||||
_instanceId.c_str(),
|
||||
(sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false",
|
||||
(sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false",
|
||||
(unsigned long long)sqlite3_column_int64(_sGetMember2,2),
|
||||
(unsigned long long)OSUtils::now(),
|
||||
_jsonEscape(memberIdStr).c_str());
|
||||
responseBody = json;
|
||||
|
||||
sqlite3_reset(_sGetIpAssignmentsForNode);
|
||||
sqlite3_bind_text(_sGetIpAssignmentsForNode,1,nwids,16,SQLITE_STATIC);
|
||||
sqlite3_bind_text(_sGetIpAssignmentsForNode,2,addrs,10,SQLITE_STATIC);
|
||||
bool firstIp = true;
|
||||
while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) {
|
||||
int ipversion = sqlite3_column_int(_sGetIpAssignmentsForNode,2);
|
||||
char ipBlob[16];
|
||||
memcpy(ipBlob,(const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode,0),16);
|
||||
InetAddress ip(
|
||||
(const void *)(ipversion == 6 ? ipBlob : &ipBlob[12]),
|
||||
(ipversion == 6 ? 16 : 4),
|
||||
(unsigned int)sqlite3_column_int(_sGetIpAssignmentsForNode,1)
|
||||
);
|
||||
responseBody.append(firstIp ? "\"" : ",\"");
|
||||
responseBody.append(_jsonEscape(ip.toIpString()));
|
||||
responseBody.push_back('"');
|
||||
firstIp = false;
|
||||
}
|
||||
|
||||
responseBody.append("],\n\t\"recentLog\": [");
|
||||
|
||||
const void *histb = sqlite3_column_blob(_sGetMember2,6);
|
||||
if (histb) {
|
||||
MemberRecentHistory rh;
|
||||
rh.fromBlob((const char *)histb,sqlite3_column_bytes(_sGetMember2,6));
|
||||
for(MemberRecentHistory::const_iterator i(rh.begin());i!=rh.end();++i) {
|
||||
if (i != rh.begin())
|
||||
responseBody.push_back(',');
|
||||
responseBody.append(*i);
|
||||
}
|
||||
}
|
||||
|
||||
responseBody.append("]\n}\n");
|
||||
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
} // else 404
|
||||
*/
|
||||
|
||||
} else {
|
||||
|
||||
responseBody.push_back('{');
|
||||
for(auto i(members->begin());i!=members->end();++i) {
|
||||
responseBody.append((i == members->begin()) ? "\"" : ",\"");
|
||||
responseBody.append(i->key());
|
||||
responseBody.append("\":\"");
|
||||
const std::string rc = i->value().value("memberRevision","0");
|
||||
responseBody = "{";
|
||||
std::vector<std::string> members(OSUtils::listSubdirectories((_networkBP(nwid,false) + ZT_PATH_SEPARATOR_S + "member").c_str()));
|
||||
for(std::vector<std::string>::iterator i(members.begin());i!=members.end();++i) {
|
||||
if (i->length() == ZT_ADDRESS_LENGTH_HEX) {
|
||||
json member(_readJson(_memberJP(nwid,Address(Utils::hexStrToU64(i->c_str())),false)));
|
||||
if (member.size()) {
|
||||
responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
|
||||
responseBody.append(*i);
|
||||
responseBody.append("\":");
|
||||
const std::string rc = member.value("memberRevision","0");
|
||||
responseBody.append(rc);
|
||||
responseBody.append('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
responseBody.push_back('}');
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
} else if ((path[2] == "active")&&(path.size() == 3)) {
|
||||
|
||||
responseBody.push_back('{');
|
||||
bool firstMember = true;
|
||||
responseBody = "{";
|
||||
std::vector<std::string> members(OSUtils::listSubdirectories((_networkBP(nwid,false) + ZT_PATH_SEPARATOR_S + "member").c_str()));
|
||||
const uint64_t threshold = OSUtils::now() - ZT_NETCONF_NODE_ACTIVE_THRESHOLD;
|
||||
for(auto i(members->begin());i!=members->end();++i) {
|
||||
auto recentLog = i->value()->get<const json::array_t *>("recentLog");
|
||||
if ((recentLog)&&(recentLog.size() > 0)) {
|
||||
for(std::vector<std::string>::iterator i(members.begin());i!=members.end();++i) {
|
||||
if (i->length() == ZT_ADDRESS_LENGTH_HEX) {
|
||||
json member(_readJson(_memberJP(nwid,Address(Utils::hexStrToU64(i->c_str())),false)));
|
||||
if (member.size()) {
|
||||
auto recentLog = member["recentLog"];
|
||||
if ((recentLog.is_array())&&(recentLog.size() > 0)) {
|
||||
auto mostRecentLog = recentLog[0];
|
||||
if ((mostRecentLog.is_object())&&((uint64_t)mostRecentLog.value("ts",0ULL) >= threshold)) {
|
||||
responseBody.append((firstMember) ? "\"" : ",\"");
|
||||
firstMember = false;
|
||||
responseBody.append(i->key());
|
||||
responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
|
||||
responseBody.append(*i);
|
||||
responseBody.append("\":");
|
||||
responseBody.append(mostRecentLog.dump());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
responseBody.push_back('}');
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
|
||||
} else if ((path[2] == "test")&&(path.size() >= 4)) {
|
||||
|
||||
Mutex::Lock _l(_circuitTests_m);
|
||||
std::map< uint64_t,_CircuitTestEntry >::iterator cte(_circuitTests.find(Utils::hexStrToU64(path[3].c_str())));
|
||||
if ((cte != _circuitTests.end())&&(cte->second.test)) {
|
||||
|
||||
@ -2032,7 +1618,6 @@ unsigned int SqliteNetworkController::_doCPGet(
|
||||
|
||||
nlohmann::json o(network);
|
||||
o["nwid"] = nwids;
|
||||
o["controllerInstanceId"] = _instanceId;
|
||||
o["clock"] = OSUtils::now();
|
||||
responseBody = o.dump(2);
|
||||
responseContentType = "application/json";
|
||||
@ -2042,27 +1627,25 @@ unsigned int SqliteNetworkController::_doCPGet(
|
||||
} else if (path.size() == 1) {
|
||||
|
||||
responseBody = "[";
|
||||
for(auto i(networks->begin());i!=networks.end();++i) {
|
||||
responseBody.append((i == networks->begin()) ? "\"" : ",\"");
|
||||
responseBody.append(i->key());
|
||||
std::vector<std::string> networks(OSUtils::listSubdirectories((_path + ZT_PATH_SEPARATOR_S + "network").c_str()));
|
||||
for(auto i(networks.begin());i!=networks.end();++i) {
|
||||
if (i->length() == 16) {
|
||||
responseBody.append((responseBody.length() == 1) ? "\"" : ",\"");
|
||||
responseBody.append(*i);
|
||||
responseBody.append("\"");
|
||||
}
|
||||
}
|
||||
responseBody.push_back(']');
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
|
||||
} // else 404
|
||||
|
||||
} else if ((path.size() > 0)&&(path[0] == "_dump")) {
|
||||
|
||||
responseBody = _db.dump(2);
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
|
||||
} else {
|
||||
|
||||
Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"instanceId\": \"%s\"\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),_instanceId.c_str());
|
||||
responseBody = json;
|
||||
char tmp[4096];
|
||||
Utils::snprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now());
|
||||
responseBody = tmp;
|
||||
responseContentType = "application/json";
|
||||
return 200;
|
||||
|
||||
@ -2081,7 +1664,7 @@ void SqliteNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest
|
||||
if (!report)
|
||||
return;
|
||||
|
||||
Mutex::Lock _l(self->_lock);
|
||||
Mutex::Lock _l(self->_circuitTests_m);
|
||||
std::map< uint64_t,_CircuitTestEntry >::iterator cte(self->_circuitTests.find(test->testId));
|
||||
|
||||
if (cte == self->_circuitTests.end()) { // sanity check: a circuit test we didn't launch?
|
||||
|
@ -14,15 +14,6 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* --
|
||||
*
|
||||
* ZeroTier may be used and distributed under the terms of the GPLv3, which
|
||||
* are available at: http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*
|
||||
* If you would like to embed ZeroTier into a commercial application or
|
||||
* redistribute it in a modified binary form, please contact ZeroTier Networks
|
||||
* LLC. Start here: http://www.zerotier.com/
|
||||
*/
|
||||
|
||||
#ifndef ZT_SQLITENETWORKCONTROLLER_HPP
|
||||
@ -38,9 +29,11 @@
|
||||
|
||||
#include "../node/NetworkController.hpp"
|
||||
#include "../node/Mutex.hpp"
|
||||
#include "../osdep/Thread.hpp"
|
||||
#include "../node/Utils.hpp"
|
||||
|
||||
#include "../ext/offbase/offbase.hpp"
|
||||
#include "../osdep/OSUtils.hpp"
|
||||
|
||||
#include "../ext/json/json.hpp"
|
||||
|
||||
namespace ZeroTier {
|
||||
|
||||
@ -82,10 +75,6 @@ public:
|
||||
std::string &responseBody,
|
||||
std::string &responseContentType);
|
||||
|
||||
// threadMain() for backup thread -- do not call directly
|
||||
void threadMain()
|
||||
throw();
|
||||
|
||||
private:
|
||||
unsigned int _doCPGet(
|
||||
const std::vector<std::string> &path,
|
||||
@ -97,11 +86,57 @@ private:
|
||||
|
||||
static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report);
|
||||
|
||||
inline nlohmann::json _readJson(const std::string &path)
|
||||
{
|
||||
std::string buf;
|
||||
if (OSUtils::readFile(path.c_str(),buf)) {
|
||||
try {
|
||||
return nlohmann::json::parse(buf);
|
||||
} catch ( ... ) {}
|
||||
}
|
||||
return nlohmann::json::object();
|
||||
}
|
||||
|
||||
inline bool _writeJson(const std::string &path,const nlohmann::json &obj)
|
||||
{
|
||||
std::string buf(obj.dump(2));
|
||||
return OSUtils::writeFile(path.c_str(),buf);
|
||||
}
|
||||
|
||||
inline std::string _networkBP(const uint64_t nwid,bool create)
|
||||
{
|
||||
char tmp[64];
|
||||
Utils::snprintf(tmp,sizeof(tmp),"%.16llx",nwid);
|
||||
std::string p(_path + ZT_PATH_SEPARATOR_S + "network");
|
||||
if (create) OSUtils::mkdir(p.c_str());
|
||||
p.push_back(ZT_PATH_SEPARATOR);
|
||||
p.append(tmp);
|
||||
if (create) OSUtils::mkdir(p.c_str());
|
||||
return p;
|
||||
}
|
||||
inline std::string _networkJP(const uint64_t nwid,bool create)
|
||||
{
|
||||
return (_networkBP(nwid,create) + ZT_PATH_SEPARATOR + "config.json");
|
||||
}
|
||||
inline std::string _memberBP(const uint64_t nwid,const Address &member,bool create)
|
||||
{
|
||||
std::string p(_networkBP(nwid,create));
|
||||
p.push_back(ZT_PATH_SEPARATOR);
|
||||
p.append("member");
|
||||
if (create) OSUtils::mkdir(p.c_str());
|
||||
p.push_back(ZT_PATH_SEPARATOR);
|
||||
p.append(member.toString());
|
||||
if (create) OSUtils::mkdir(p.c_str());
|
||||
return p;
|
||||
}
|
||||
inline std::string _memberJP(const uint64_t nwid,const Address &member,bool create)
|
||||
{
|
||||
return (_memberBP(nwid,member,create) + ZT_PATH_SEPARATOR + "config.json");
|
||||
}
|
||||
|
||||
// These are const after construction
|
||||
Node *const _node;
|
||||
std::string _instanceId;
|
||||
offbase _db;
|
||||
Thread _dbCommitThread;
|
||||
volatile bool _dbCommitThreadRun;
|
||||
std::string _path;
|
||||
|
||||
// Circuit tests outstanding
|
||||
struct _CircuitTestEntry
|
||||
@ -110,11 +145,11 @@ private:
|
||||
std::string jsonResults;
|
||||
};
|
||||
std::map< uint64_t,_CircuitTestEntry > _circuitTests;
|
||||
Mutex _circuitTests_m;
|
||||
|
||||
// Last request time by address, for rate limitation
|
||||
std::map< std::pair<uint64_t,uint64_t>,uint64_t > _lastRequestTime;
|
||||
|
||||
Mutex _lock;
|
||||
Mutex _lastRequestTime_m;
|
||||
};
|
||||
|
||||
} // namespace ZeroTier
|
||||
|
@ -1,59 +0,0 @@
|
||||
A super-minimal in-filesystem persistent JSON object
|
||||
======
|
||||
|
||||
[We](https://www.zerotier.com/) like minimalism.
|
||||
|
||||
Offbase is an extension of the excellent [nlohmann/json](https://github.com/nlohmann/json) C++11 JSON class that adds simple object persistence to/from the filesystem. Objects are stored into a directory hierarchy in fully "expanded" form with each field/value being represented by a separate file.
|
||||
|
||||
Features:
|
||||
|
||||
- Very easy to use
|
||||
- Minimal!
|
||||
- Easy to understand and maintain
|
||||
- Trivial to implement in other languages
|
||||
- No dependencies beyond standard libraries
|
||||
- Small code footprint in both source and binary form
|
||||
- Easy to port to other platforms
|
||||
- Exactly reproduces JSON object hierarchies including all JSON type information
|
||||
- Database can be explored from the shell, browsed in a web browser or file explorer, scanned with `find` and `grep`, etc.
|
||||
- Database can be backed up, restored, versioned, etc. with tools like `git`, `rsync`, `duplicity`, etc.
|
||||
- Alien files like `.git` or `.DS_Store` are harmlessly ignored if present
|
||||
- Saving only changes what's changed to reduce I/O overhead and SSD wear
|
||||
|
||||
Limitations and shortcomings:
|
||||
|
||||
- This creates a lot of tiny files, which is inefficient on some filesystems and might run into inode limits in extreme cases. For data sets with more than, say, a million items we recommend a filesystem like `btrfs` or `reiserfs`. Things like [redisfs](https://steve.fi/Software/redisfs/) are also worth exploring. On Linux another alternative is to put the database into `/dev/shm` (RAM disk) and then regularly back it up with `duplicity` or similar.
|
||||
- The whole JSON object is held in memory *twice* for diffing purposes.
|
||||
- Diffing traverses the whole tree and then updates the shadow copy, which makes `commit()` slow for huge data sets. This is not suitable for "big" data where "big" here is probably more than a few hundred megabytes.
|
||||
- Recursion is used, so if you have object hierarchies that are incredibly deep (hundreds or more) it might be possible to overflow your stack and crash your app.
|
||||
- This is not thread safe and must be guarded by a mutex if used in a threaded app.
|
||||
|
||||
Caveats:
|
||||
|
||||
- Key names are escaped for safety in the filesystem, but we still don't recommend allowing external users to set just anything into your JSON store. See the point about recursion under limitations.
|
||||
|
||||
Future:
|
||||
|
||||
- It would not be too hard to tie this into a filesystem change monitoring API and automatically read changes from disk if they are detected. This would allow the database to be edited "live" in the filesystem.
|
||||
- In theory this could provide replication or clustering through distributed filesystems, file syncing, or things like [Amazon Elastic Filesystem](https://aws.amazon.com/efs/).
|
||||
- Recursion could be factored out to get rid of any object hierarchy depth constraints.
|
||||
- Mutexes could be integrated somehow to allow for finer grained locking in multithreaded apps.
|
||||
- Diffing and selective updates could be made more memory and CPU efficient using hashes, etc.
|
||||
|
||||
## How to Use
|
||||
|
||||
The `offbase` class just extends [nlohmann::json](https://github.com/nlohmann/json) and gives you a JSON object. Take care to make sure you don't change the type of the 'root' object represented by the 'offbase' instance from JSON 'object'. Anything under it can of course be any JSON type, including any object.
|
||||
|
||||
Just put data into the object and then periodically call `commit()` to persist changes to disk. The `commit()` method diffs the current contents of the object with what it knows to have been previously persisted to disk and modifies the representation on disk to match. This can be done after writes or periodically in a background thread.
|
||||
|
||||
See comments in `offbase.hpp` for full documentation including details about error handling, etc.
|
||||
|
||||
## Persistence format
|
||||
|
||||
The base object represented by the `offbase` instance is persisted into a directory hierarchy under its base path. Files and directories are named according to a simple convention of `keyname.typecode` where `keyname` is an escaped key name (or hex array index in the case of arrays) and `typecode` is a single character indicating whether the item is a JSON value, array, or object.
|
||||
|
||||
- `*.V`: JSON values (actual value type is inferred during JSON parse)
|
||||
- `*.O`: JSON objects (these are subdirectories)
|
||||
- `*.A`: JSON arrays (also subdirectories containing items by hex array index)
|
||||
|
||||
There are in theory simpler ways to represent JSON in a filesystem, such as the "flattened" JSON "pointer" format, but this has the disadvantage of not disambiguating objects vs. arrays. Offbase's persistence format is designed to perfectly reproduce the exact same JSON tree on load as was most recently committed.
|
@ -1,393 +0,0 @@
|
||||
/*
|
||||
* Offbase: a super-minimal in-filesystem JSON object persistence store
|
||||
*/
|
||||
|
||||
#ifndef OFFBASE_HPP__
|
||||
#define OFFBASE_HPP__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
#define OFFBASE_PATH_SEP "/"
|
||||
|
||||
/**
|
||||
* A super-minimal in-filesystem JSON object persistence store
|
||||
*/
|
||||
class offbase : public nlohmann::json
|
||||
{
|
||||
public:
|
||||
offbase(const char *p) :
|
||||
nlohmann::json(nlohmann::json::object()),
|
||||
_path(p),
|
||||
_saved(nlohmann::json::object())
|
||||
{
|
||||
this->load();
|
||||
}
|
||||
|
||||
~offbase()
|
||||
{
|
||||
this->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load this instance from disk, clearing any existing contents first
|
||||
*
|
||||
* If the 'errors' vector is NULL, false is returned and reading aborts
|
||||
* on any error. If this parameter is non-NULL the paths of errors will
|
||||
* be added to the vector and reading will continue. False will only be
|
||||
* returned on really big errors like no path being defined.
|
||||
*
|
||||
* @param errors If specified, fill this vector with the paths to any objects that fail read
|
||||
* @return True on success, false on fatal error
|
||||
*/
|
||||
inline bool load(std::vector<std::string> *errors = (std::vector<std::string> *)0)
|
||||
{
|
||||
if (!_path.length())
|
||||
return false;
|
||||
*this = nlohmann::json::object();
|
||||
if (!_loadObj(_path,*this,errors))
|
||||
return false;
|
||||
_saved = *(reinterpret_cast<nlohmann::json *>(this));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit any pending changes to this object to disk
|
||||
*
|
||||
* @return True on success or false if an I/O error occurred
|
||||
*/
|
||||
inline bool commit(std::vector<std::string> *errors = (std::vector<std::string> *)0)
|
||||
{
|
||||
if (!_path.length())
|
||||
return false;
|
||||
if (!_commitObj(_path,*this,&_saved,errors))
|
||||
return false;
|
||||
_saved = *(reinterpret_cast<nlohmann::json *>(this));
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline std::string escapeKey(const std::string &k)
|
||||
{
|
||||
std::string e;
|
||||
const char *ptr = k.data();
|
||||
const char *eof = ptr + k.length();
|
||||
char tmp[8];
|
||||
while (ptr != eof) {
|
||||
if ( ((*ptr >= 'a')&&(*ptr <= 'z')) || ((*ptr >= 'A')&&(*ptr <= 'Z')) || ((*ptr >= '0')&&(*ptr <= '9')) || (*ptr == '.') || (*ptr == '_') || (*ptr == '-') || (*ptr == ',') )
|
||||
e.push_back(*ptr);
|
||||
else {
|
||||
snprintf(tmp,sizeof(tmp),"~%.2x",(unsigned int)*ptr);
|
||||
e.append(tmp);
|
||||
}
|
||||
++ptr;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static inline std::string unescapeKey(const std::string &k)
|
||||
{
|
||||
std::string u;
|
||||
const char *ptr = k.data();
|
||||
const char *eof = ptr + k.length();
|
||||
char tmp[8];
|
||||
while (ptr != eof) {
|
||||
if (*ptr == '~') {
|
||||
if (++ptr == eof) break;
|
||||
tmp[0] = *ptr;
|
||||
if (++ptr == eof) break;
|
||||
tmp[1] = *(ptr++);
|
||||
tmp[2] = (char)0;
|
||||
u.push_back((char)strtol(tmp,(char **)0,16));
|
||||
} else {
|
||||
u.push_back(*(ptr++));
|
||||
}
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
private:
|
||||
static inline bool _readFile(const char *path,std::string &buf)
|
||||
{
|
||||
char tmp[4096];
|
||||
FILE *f = fopen(path,"rb");
|
||||
if (f) {
|
||||
for(;;) {
|
||||
long n = (long)fread(tmp,1,sizeof(tmp),f);
|
||||
if (n > 0)
|
||||
buf.append(tmp,n);
|
||||
else break;
|
||||
}
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool _loadArr(const std::string &path,nlohmann::json &arr,std::vector<std::string> *errors)
|
||||
{
|
||||
std::map<unsigned long,nlohmann::json> atmp; // place into an ordered container first because filesystem does not guarantee this
|
||||
|
||||
struct dirent dbuf;
|
||||
struct dirent *de;
|
||||
DIR *d = opendir(path.c_str());
|
||||
if (d) {
|
||||
while (!readdir_d(d,&dbuf,&de)) {
|
||||
if (!de) break;
|
||||
const std::string name(de->d_name);
|
||||
if (name.length() != 12) continue; // array entries are XXXXXXXXXX.T
|
||||
if (name[name.length()-2] == '.') {
|
||||
if (name[name.length()-1] == 'V') {
|
||||
std::string buf;
|
||||
if (_readFile((path + OFFBASE_PATH_SEP + name).c_str(),buf)) {
|
||||
try {
|
||||
atmp[strtoul(name.substr(0,10).c_str(),(char **)0,16)] = nlohmann::json::parse(buf);
|
||||
} catch ( ... ) {
|
||||
if (errors) {
|
||||
errors->push_back(path + OFFBASE_PATH_SEP + name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (errors) {
|
||||
errors->push_back(path + OFFBASE_PATH_SEP + name);
|
||||
} else return false;
|
||||
} else if (name[name.length()-1] == 'O') {
|
||||
if (!_loadObj(path + OFFBASE_PATH_SEP + name,atmp[strtoul(name.substr(0,10).c_str(),(char **)0,16)] = nlohmann::json::object(),errors))
|
||||
return false;
|
||||
} else if (name[name.length()-1] == 'A') {
|
||||
if (!_loadArr(path + OFFBASE_PATH_SEP + name,atmp[strtoul(name.substr(0,10).c_str(),(char **)0,16)] = nlohmann::json::array(),errors))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
} else if (errors) {
|
||||
errors->push_back(path);
|
||||
} else return false;
|
||||
|
||||
if (atmp.size() > 0) {
|
||||
unsigned long lasti = 0;
|
||||
for(std::map<unsigned long,nlohmann::json>::iterator i(atmp.begin());i!=atmp.end();++i) {
|
||||
for(unsigned long k=lasti;k<i->first;++k) // fill any gaps with nulls
|
||||
arr.push_back(nlohmann::json(std::nullptr_t));
|
||||
lasti = i->first;
|
||||
arr.push_back(i->second);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool _loadObj(const std::string &path,nlohmann::json &obj,std::vector<std::string> *errors)
|
||||
{
|
||||
struct dirent dbuf;
|
||||
struct dirent *de;
|
||||
DIR *d = opendir(path.c_str());
|
||||
if (d) {
|
||||
while (!readdir_d(d,&dbuf,&de)) {
|
||||
if (!de) break;
|
||||
if ((strcmp(de->d_name,".") == 0)||(strcmp(de->d_name,"..") == 0)) continue; // sanity check
|
||||
const std::string name(de->d_name);
|
||||
if (name.length() <= 2) continue;
|
||||
if (name[name.length()-2] == '.') {
|
||||
if (name[name.length()-1] == 'V') {
|
||||
std::string buf;
|
||||
if (_readFile((path + OFFBASE_PATH_SEP + name).c_str(),buf)) {
|
||||
try {
|
||||
obj[unescapeKey(name)] = nlohmann::json::parse(buf);
|
||||
} catch ( ... ) {
|
||||
if (errors) {
|
||||
errors->push_back(path + OFFBASE_PATH_SEP + name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (errors) {
|
||||
errors->push_back(path + OFFBASE_PATH_SEP + name);
|
||||
} else return false;
|
||||
} else if (name[name.length()-1] == 'O') {
|
||||
if (!_loadObj(path + OFFBASE_PATH_SEP + name,obj[unescapeKey(name)] = nlohmann::json::object(),errors))
|
||||
return false;
|
||||
} else if (name[name.length()-1] == 'A') {
|
||||
if (!_loadArr(path + OFFBASE_PATH_SEP + name,obj[unescapeKey(name)] = nlohmann::json::array(),errors))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
} else if (errors) {
|
||||
errors->push_back(path);
|
||||
} else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void _rmDashRf(const std::string &path)
|
||||
{
|
||||
struct dirent dbuf;
|
||||
struct dirent *de;
|
||||
DIR *d = opendir(path.c_str());
|
||||
if (d) {
|
||||
while (!readdir_r(d,&dbuf,&de)) {
|
||||
if (!de) break;
|
||||
if ((strcmp(de->d_name,".") == 0)||(strcmp(de->d_name,"..") == 0)) continue; // sanity check
|
||||
const std::string full(path + OFFBASE_PATH_SEP + de->d_name);
|
||||
if (unlink(full.c_str())) {
|
||||
_rmDashRf(full);
|
||||
rmdir(full.c_str());
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
rmdir(path.c_str());
|
||||
}
|
||||
|
||||
static inline bool _commitArr(const std::string &path,const nlohmann::json &arr,const nlohmann::json *previous,std::vector<std::string> *errors)
|
||||
{
|
||||
char tmp[32];
|
||||
|
||||
if (!arr.is_array())
|
||||
return false;
|
||||
|
||||
mkdir(path.c_str(),0755);
|
||||
|
||||
for(unsigned long i=0;i<(unsigned long)arr.size();++i) {
|
||||
const nlohmann::json &value = arr[i];
|
||||
|
||||
const nlohmann::json *next = (const nlohmann::json *)0;
|
||||
if ((previous)&&(previous->is_array())&&(i < previous->size())) {
|
||||
next = &((*previous)[i]);
|
||||
if (*next == value)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value.is_object()) {
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
|
||||
if (!_commitObj(path + tmp,value,next,errors))
|
||||
return false;
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
|
||||
unlink((path + tmp).c_str());
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
|
||||
_rmDashRf(path + tmp);
|
||||
} else if (value.is_array()) {
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
|
||||
if (!_commitArr(path + tmp,value,next,errors))
|
||||
return false;
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
|
||||
_rmDashRf(path + tmp);
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
|
||||
unlink((path + tmp).c_str());
|
||||
} else {
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
|
||||
FILE *f = fopen((path + tmp).c_str(),"w");
|
||||
if (f) {
|
||||
const std::string v(value.dump());
|
||||
if (fwrite(v.c_str(),v.length(),1,f) != 1) {
|
||||
fclose(f);
|
||||
return false;
|
||||
} else {
|
||||
fclose(f);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
|
||||
_rmDashRf(path + tmp);
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
|
||||
_rmDashRf(path + tmp);
|
||||
}
|
||||
}
|
||||
|
||||
if ((previous)&&(previous->is_array())) {
|
||||
for(unsigned long i=(unsigned long)arr.size();i<(unsigned long)previous->size();++i) {
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.V",OFFBASE_PATH_SEP,i);
|
||||
unlink((path + tmp).c_str());
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.A",OFFBASE_PATH_SEP,i);
|
||||
_rmDashRf(path + tmp);
|
||||
snprintf(tmp,sizeof(tmp),"%s%.10lx.O",OFFBASE_PATH_SEP,i);
|
||||
_rmDashRf(path + tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool _commitObj(const std::string &path,const nlohmann::json &obj,const nlohmann::json *previous,std::vector<std::string> *errors)
|
||||
{
|
||||
if (!obj.is_object())
|
||||
return false;
|
||||
|
||||
mkdir(path.c_str(),0755);
|
||||
|
||||
for(nlohmann::json::const_iterator i(obj.begin());i!=obj.end();++i) {
|
||||
if (i.key().length() == 0)
|
||||
continue;
|
||||
|
||||
const nlohmann::json *next = (const nlohmann::json *)0;
|
||||
if ((previous)&&(previous->is_object())) {
|
||||
nlohmann::json::const_iterator saved(previous->find(i.key()));
|
||||
if (saved != previous->end()) {
|
||||
next = &(saved.value());
|
||||
if (i.value() == *next)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string keyp(path + OFFBASE_PATH_SEP + escapeKey(i.key()));
|
||||
if (i.value().is_object()) {
|
||||
if (!_commitObj(keyp + ".O",i.value(),next,errors))
|
||||
return false;
|
||||
unlink((keyp + ".V").c_str());
|
||||
_rmDashRf(keyp + ".A");
|
||||
} else if (i.value().is_array()) {
|
||||
if (!_commitArr(keyp + ".A",i.value(),next,errors))
|
||||
return false;
|
||||
unlink((keyp + ".V").c_str());
|
||||
_rmDashRf(keyp + ".O");
|
||||
} else {
|
||||
FILE *f = fopen((keyp + ".V").c_str(),"w");
|
||||
if (f) {
|
||||
const std::string v(i.value().dump());
|
||||
if (fwrite(v.c_str(),v.length(),1,f) != 1) {
|
||||
fclose(f);
|
||||
return false;
|
||||
} else {
|
||||
fclose(f);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
_rmDashRf(keyp + ".A");
|
||||
_rmDashRf(keyp + ".O");
|
||||
}
|
||||
}
|
||||
|
||||
if ((previous)&&(previous->is_object())) {
|
||||
for(nlohmann::json::const_iterator i(previous->begin());i!=previous->end();++i) {
|
||||
if ((i.key().length() > 0)&&(obj.find(i.key()) == obj.end())) {
|
||||
const std::string keyp(path + OFFBASE_PATH_SEP + escapeKey(i.key()));
|
||||
unlink((keyp + ".V").c_str());
|
||||
_rmDashRf(keyp + ".A");
|
||||
_rmDashRf(keyp + ".O");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string _path;
|
||||
nlohmann::json _saved;
|
||||
};
|
||||
|
||||
#endif
|
@ -65,7 +65,7 @@ else
|
||||
STRIP=strip
|
||||
endif
|
||||
|
||||
CXXFLAGS=$(CFLAGS) -fno-rtti
|
||||
CXXFLAGS=$(CFLAGS) -fno-rtti -mmacosx-version-min=10.7 -std=c++11 -stdlib=libc++
|
||||
|
||||
all: one
|
||||
|
||||
@ -78,7 +78,7 @@ one: $(OBJS) service/OneService.o one.o
|
||||
$(CODESIGN) -vvv zerotier-one
|
||||
|
||||
cli: FORCE
|
||||
$(CXX) -Os -mmacosx-version-min=10.7 -std=c++11 -stdlib=libc++ -o zerotier cli/zerotier.cpp osdep/OSUtils.cpp node/InetAddress.cpp node/Utils.cpp node/Salsa20.cpp node/Identity.cpp node/SHA512.cpp node/C25519.cpp -lcurl
|
||||
$(CXX) $(CXXFLAGS) -o zerotier cli/zerotier.cpp osdep/OSUtils.cpp node/InetAddress.cpp node/Utils.cpp node/Salsa20.cpp node/Identity.cpp node/SHA512.cpp node/C25519.cpp -lcurl
|
||||
$(STRIP) zerotier
|
||||
|
||||
selftest: $(OBJS) selftest.o
|
||||
|
@ -113,7 +113,7 @@ void InetAddress::set(const std::string &ip,unsigned int port)
|
||||
sin6->sin6_port = Utils::hton((uint16_t)port);
|
||||
if (inet_pton(AF_INET6,ip.c_str(),(void *)&(sin6->sin6_addr.s6_addr)) <= 0)
|
||||
memset(this,0,sizeof(InetAddress));
|
||||
} else {
|
||||
} else if (ip.find('.') != std::string::npos) {
|
||||
struct sockaddr_in *sin = reinterpret_cast<struct sockaddr_in *>(this);
|
||||
ss_family = AF_INET;
|
||||
sin->sin_port = Utils::hton((uint16_t)port);
|
||||
|
@ -107,6 +107,86 @@ std::vector<std::string> OSUtils::listDirectory(const char *path)
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<std::string> OSUtils::listSubdirectories(const char *path)
|
||||
{
|
||||
std::vector<std::string> r;
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
HANDLE hFind;
|
||||
WIN32_FIND_DATAA ffd;
|
||||
if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((strcmp(ffd.cFileName,"."))&&(strcmp(ffd.cFileName,".."))&&((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0))
|
||||
r.push_back(std::string(ffd.cFileName));
|
||||
} while (FindNextFileA(hFind,&ffd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
#else
|
||||
struct dirent de;
|
||||
struct dirent *dptr;
|
||||
DIR *d = opendir(path);
|
||||
if (!d)
|
||||
return r;
|
||||
dptr = (struct dirent *)0;
|
||||
for(;;) {
|
||||
if (readdir_r(d,&de,&dptr))
|
||||
break;
|
||||
if (dptr) {
|
||||
if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&(dptr->d_type == DT_DIR))
|
||||
r.push_back(std::string(dptr->d_name));
|
||||
} else break;
|
||||
}
|
||||
closedir(d);
|
||||
#endif
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
bool OSUtils::rmDashRf(const char *path)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
HANDLE hFind;
|
||||
WIN32_FIND_DATAA ffd;
|
||||
if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((strcmp(ffd.cFileName,".") != 0)&&(strcmp(ffd.cFileName,"..") != 0)) {
|
||||
if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
if (DeleteFileA((std::string(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str()) == FALSE)
|
||||
return false;
|
||||
} else {
|
||||
if (!rmDashRf((std::string(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str()))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (FindNextFileA(hFind,&ffd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
return (RemoveDirectoryA(path) != FALSE);
|
||||
#else
|
||||
struct dirent de;
|
||||
struct dirent *dptr;
|
||||
DIR *d = opendir(path);
|
||||
if (!d)
|
||||
return true;
|
||||
dptr = (struct dirent *)0;
|
||||
for(;;) {
|
||||
if (readdir_r(d,&de,&dptr))
|
||||
break;
|
||||
if ((dptr)&&(strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)) {
|
||||
std::string p(path);
|
||||
p.push_back(ZT_PATH_SEPARATOR);
|
||||
p.append(dptr->d_name);
|
||||
if (unlink(p.c_str()) != 0) {
|
||||
if (!rmDashRf(p.c_str()))
|
||||
return false;
|
||||
}
|
||||
} else break;
|
||||
}
|
||||
closedir(d);
|
||||
return (rmdir(path) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OSUtils::lockDownFile(const char *path,bool isDir)
|
||||
{
|
||||
#ifdef __UNIX_LIKE__
|
||||
|
@ -105,10 +105,26 @@ public:
|
||||
* This returns only files, not sub-directories.
|
||||
*
|
||||
* @param path Path to list
|
||||
* @return Names of files in directory
|
||||
* @return Names of files in directory (without path prepended)
|
||||
*/
|
||||
static std::vector<std::string> listDirectory(const char *path);
|
||||
|
||||
/**
|
||||
* List a directory's subdirectories
|
||||
*
|
||||
* @param path Path to list
|
||||
* @return Names of subdirectories (without path prepended)
|
||||
*/
|
||||
static std::vector<std::string> listSubdirectories(const char *path);
|
||||
|
||||
/**
|
||||
* Delete a directory and all its files and subdirectories recursively
|
||||
*
|
||||
* @param path Path to delete
|
||||
* @return True on success
|
||||
*/
|
||||
static bool rmDashRf(const char *path);
|
||||
|
||||
/**
|
||||
* Set modes on a file to something secure
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user