diff options
-rwxr-xr-x | .gitignore | 1 | ||||
-rw-r--r-- | controller/SqliteNetworkController.cpp | 638 | ||||
-rw-r--r-- | controller/SqliteNetworkController.hpp | 22 | ||||
-rw-r--r-- | controller/schema.sql.c | 4 | ||||
-rw-r--r-- | nodejs-zt1-client/index.js | 137 | ||||
-rw-r--r-- | nodejs-zt1-client/package.json | 14 | ||||
-rw-r--r-- | nodejs-zt1-client/test.js | 33 | ||||
-rw-r--r-- | one.cpp | 18 | ||||
-rw-r--r-- | service/ControlPlane.cpp | 9 | ||||
-rw-r--r-- | service/ControlPlane.hpp | 6 | ||||
-rw-r--r-- | service/ControlPlaneSubsystem.hpp | 76 | ||||
-rw-r--r-- | service/OneService.cpp | 2 | ||||
-rw-r--r-- | service/README.md | 2 |
13 files changed, 544 insertions, 418 deletions
@@ -52,3 +52,4 @@ java/build_win32/ /ui/.module-cache /windows/WebUIWrapper/bin /windows/WebUIWrapper/obj +node_modules diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index 92b8c32c..4ac40fc4 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -169,20 +169,19 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) : ||(sqlite3_prepare_v2(_db,"SELECT ipNetwork,ipNetmaskBits,ipVersion FROM IpAssignmentPool WHERE networkId = ? ORDER BY ipNetwork ASC",-1,&_sGetIpAssignmentPools2,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT ruleId,nodeId,vlanId,vlanPcp,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"action\" FROM Rule WHERE networkId = ? ORDER BY ruleId ASC",-1,&_sListRules,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"INSERT INTO Rule (networkId,ruleId,nodeId,vlanId,vlanPcP,etherType,macSource,macDest,ipSource,ipDest,ipTos,ipProtocol,ipSourcePort,ipDestPort,\"action\") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",-1,&_sCreateRule,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"INSERT INTO Network (networkId,name,creationTime,revision) VALUES (?,?,?,1)",-1,&_sCreateNetwork,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Network SET ? = ? WHERE networkId = ?",-1,&_sUpdateNetworkField,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"INSERT INTO Network (id,name,creationTime,revision) VALUES (?,?,?,1)",-1,&_sCreateNetwork,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"SELECT revision FROM Network WHERE id = ?",-1,&_sGetNetworkRevision,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"UPDATE Network SET revision = ? WHERE id = ?",-1,&_sSetNetworkRevision,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ?",-1,&_sGetIpAssignmentsForNode2,(const char **)0) != SQLITE_OK) + ||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ? ORDER BY ip ASC",-1,&_sGetIpAssignmentsForNode2,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"DELETE FROM Relay WHERE networkId = ?",-1,&_sDeleteRelaysForNetwork,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"INSERT INTO Relay (networkId,nodeId,phyAddress) VALUES (?,?,?)",-1,&_sCreateRelay,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"DELETE FROM Rule WHERE networkId = ?",-1,&_sDeleteRulesForNetwork,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"INSERT INTO IpAssignmentPool (networkId,ipNetwork,ipNetmaskBits,ipVersion) VALUES (?,?,?,?)",-1,&_sCreateIpAssignmentPool,(const char **)0) != SQLITE_OK) - ||(sqlite3_prepare_v2(_db,"UPDATE Member SET ? = ? WHERE rowid = ?",-1,&_sUpdateMemberField,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"DELETE FROM Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK) ||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignment WHERE networkId = ?; DELETE FROM IpAssignmentPool WHERE networkId = ?; DELETE FROM Member WHERE networkId = ?; DELETE FROM MulticastRate WHERE networkId = ?; DELETE FROM Relay WHERE networkId = ?; DELETE FROM Rule WHERE networkId = ?; DELETE FROM Network WHERE id = ?;",-1,&_sDeleteNetworkAndRelated,(const char **)0) != SQLITE_OK) ) { + //printf("!!! %s\n",sqlite3_errmsg(_db)); sqlite3_close(_db); throw std::runtime_error("SqliteNetworkController unable to initialize one or more prepared statements"); } @@ -215,7 +214,6 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sListRules); sqlite3_finalize(_sCreateRule); sqlite3_finalize(_sCreateNetwork); - sqlite3_finalize(_sUpdateNetworkField); sqlite3_finalize(_sGetNetworkRevision); sqlite3_finalize(_sSetNetworkRevision); sqlite3_finalize(_sGetIpAssignmentsForNode2); @@ -224,7 +222,6 @@ SqliteNetworkController::~SqliteNetworkController() sqlite3_finalize(_sDeleteIpAssignmentPoolsForNetwork); sqlite3_finalize(_sDeleteRulesForNetwork); sqlite3_finalize(_sCreateIpAssignmentPool); - sqlite3_finalize(_sUpdateMemberField); sqlite3_finalize(_sDeleteNetworkAndRelated); sqlite3_close(_db); } @@ -562,282 +559,8 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( std::string &responseBody, std::string &responseContentType) { - char json[16384]; - - if (path.empty()) - return 404; Mutex::Lock _l(_lock); - - if (path[0] == "network") { - - 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); - - if (path.size() >= 3) { - if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { - uint64_t address = Utils::hexStrToU64(path[3].c_str()); - char addrs[24]; - Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address); - - 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) { - Utils::snprintf(json,sizeof(json), - "{\n" - "\tnwid: \"%s\",\n" - "\taddress: \"%s\",\n" - "\tauthorized: %s,\n" - "\tactiveBridge: %s,\n" - "\tlastAt: \"%s\",\n" - "\tlastSeen: %llu,\n" - "\tfirstSeen: %llu,\n" - "\tidentity: \"%s\",\n" - "\tipAssignments: [", - nwids, - addrs, - (sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false", - (sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false", - _jsonEscape((const char *)sqlite3_column_text(_sGetMember2,3)).c_str(), - (unsigned long long)sqlite3_column_int64(_sGetMember2,4), - (unsigned long long)sqlite3_column_int64(_sGetMember2,5), - _jsonEscape((const char *)sqlite3_column_text(_sGetMember2,2)).c_str()); - responseBody = json; - - sqlite3_reset(_sGetIpAssignmentsForNode2); - sqlite3_bind_text(_sGetIpAssignmentsForNode2,1,nwids,16,SQLITE_STATIC); - sqlite3_bind_text(_sGetIpAssignmentsForNode2,2,addrs,10,SQLITE_STATIC); - bool firstIp = true; - while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) { - InetAddress ip((const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode2,0),(sqlite3_column_int(_sGetIpAssignmentsForNode2,2) == 6) ? 16 : 4,(unsigned int)sqlite3_column_int(_sGetIpAssignmentPools2,1)); - responseBody.append(firstIp ? "\"" : ",\""); - firstIp = false; - responseBody.append(_jsonEscape(ip.toString())); - responseBody.push_back('"'); - } - - responseBody.append("]"); - - /* It's possible to get the actual netconf dictionary by including these - * three URL arguments. The member identity must be the string - * serialized identity of this member, and the signing identity must be - * the full secret identity of this network controller. The have revision - * is optional but would designate the revision our hypothetical client - * already has. - * - * This is primarily for testing and is not used in production. It makes - * it easy to test the entire network controller via its JSON API. - * - * If these arguments are included, three more object fields are returned: - * 'netconf', 'netconfResult', and 'netconfResultMessage'. These are all - * string fields and contain the actual netconf dictionary, the query - * result code, and any verbose message e.g. an error description. */ - std::map<std::string,std::string>::const_iterator memids(urlArgs.find("memberIdentity")); - std::map<std::string,std::string>::const_iterator sigids(urlArgs.find("signingIdentity")); - std::map<std::string,std::string>::const_iterator hrs(urlArgs.find("haveRevision")); - if ((memids != urlArgs.end())&&(sigids != urlArgs.end())) { - Dictionary netconf; - Identity memid,sigid; - try { - if (memid.fromString(memids->second)&&sigid.fromString(sigids->second)&&sigid.hasPrivate()) { - uint64_t hr = 0; - if (hrs != urlArgs.end()) - hr = Utils::strToU64(hrs->second.c_str()); - const char *result = ""; - switch(this->doNetworkConfigRequest(InetAddress(),sigid,memid,nwid,Dictionary(),hr,netconf)) { - case NetworkController::NETCONF_QUERY_OK: result = "OK"; break; - case NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER: result = "OK_BUT_NOT_NEWER"; break; - case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: result = "OBJECT_NOT_FOUND"; break; - case NetworkController::NETCONF_QUERY_ACCESS_DENIED: result = "ACCESS_DENIED"; break; - case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: result = "INTERNAL_SERVER_ERROR"; break; - default: result = "(unrecognized result code)"; break; - } - responseBody.append(",\n\tnetconf: \""); - responseBody.append(_jsonEscape(netconf.toString().c_str())); - responseBody.append("\",\n\tnetconfResult: \""); - responseBody.append(result); - responseBody.append("\",\n\tnetconfResultMessage: \""); - responseBody.append(_jsonEscape(netconf["error"].c_str())); - responseBody.append("\""); - } else { - responseBody.append(",\n\tnetconf: \"\",\n\tnetconfResult: \"INTERNAL_SERVER_ERROR\",\n\tnetconfResultMessage: \"invalid member or signing identity\""); - } - } catch ( ... ) { - responseBody.append(",\n\tnetconf: \"\",\n\tnetconfResult: \"INTERNAL_SERVER_ERROR\",\n\tnetconfResultMessage: \"unexpected exception\""); - } - } - - responseBody.append("\n}\n"); - - responseContentType = "application/json"; - return 200; - } // else 404 - } // else 404 - } else { - // get network info - sqlite3_reset(_sGetNetworkById); - sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC); - if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) { - Utils::snprintf(json,sizeof(json), - "{\n" - "\tnwid: \"%s\",\n" - "\tname: \"%s\",\n" - "\tprivate: %s,\n" - "\tenableBroadcast: %s,\n" - "\tallowPassiveBridging: %s,\n" - "\tv4AssignMode: \"%s\",\n" - "\tv6AssignMode: \"%s\",\n" - "\tmulticastLimit: %d,\n" - "\tcreationTime: %llu,\n", - "\trevision: %llu,\n" - "\amembers: [", - nwids, - _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(), - (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false", - (sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false", - (sqlite3_column_int(_sGetNetworkById,3) > 0) ? "true" : "false", - _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,4)).c_str(), - _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,5)).c_str(), - sqlite3_column_int(_sGetNetworkById,6), - (unsigned long long)sqlite3_column_int64(_sGetNetworkById,7), - (unsigned long long)sqlite3_column_int64(_sGetNetworkById,8)); - responseBody = json; - - sqlite3_reset(_sListNetworkMembers); - sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC); - bool firstMember = true; - while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) { - if (!firstMember) - responseBody.push_back(','); - responseBody.push_back('"'); - responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0)); - responseBody.push_back('"'); - firstMember = false; - } - responseBody.append("],\n\trelays: ["); - - sqlite3_reset(_sGetRelays); - sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC); - bool firstRelay = true; - while (sqlite3_step(_sGetRelays) == SQLITE_ROW) { - responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t"); - firstRelay = false; - responseBody.append("{address:\""); - responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0)); - responseBody.append("\",phyAddress:\""); - responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1))); - responseBody.append("\"}"); - } - responseBody.append("],\n\tipAssignmentPools: ["); - - sqlite3_reset(_sGetIpAssignmentPools2); - sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC); - bool firstIpAssignmentPool = true; - while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) { - responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t"); - firstIpAssignmentPool = false; - InetAddress ipp((const void *)sqlite3_column_blob(_sGetIpAssignmentPools2,0),(sqlite3_column_int(_sGetIpAssignmentPools2,2) == 6) ? 16 : 4,(unsigned int)sqlite3_column_int(_sGetIpAssignmentPools2,1)); - Utils::snprintf(json,sizeof(json),"{network:\"%s\",netmaskBits:%u}", - _jsonEscape(ipp.toIpString()).c_str(), - ipp.netmaskBits()); - responseBody.append(json); - } - responseBody.append("],\n\trules: ["); - - sqlite3_reset(_sListRules); - sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC); - bool firstRule = true; - while (sqlite3_step(_sListRules) == SQLITE_ROW) { - responseBody.append(firstRule ? "\n\t{\n" : ",{\n"); - Utils::snprintf(json,sizeof(json),"\t\truleId: %lld,\n",sqlite3_column_int64(_sListRules,0)); - responseBody.append(json); - if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tnodeId: \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tvlanId: %d,\n",sqlite3_column_int(_sListRules,2)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tvlanPcp: %d,\n",sqlite3_column_int(_sListRules,3)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tetherType: %d,\n",sqlite3_column_int(_sListRules,4)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tmacSource: \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,5)).toString().c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tmacDest: \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,6)).toString().c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipSource: \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,7)).c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipDest: \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,8)).c_str()); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipTos: %d,\n",sqlite3_column_int(_sListRules,9)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipProtocol: %d,\n",sqlite3_column_int(_sListRules,10)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipSourcePort: %d,\n",sqlite3_column_int(_sListRules,11)); - responseBody.append(json); - } - if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipDestPort: %d,\n",sqlite3_column_int(_sListRules,12)); - responseBody.append(json); - } - responseBody.append("\t\taction: \""); - responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sListRules,13))); - responseBody.append("\"\n\t}"); - } - - responseBody.append("]\n}\n"); - responseContentType = "application/json"; - return 200; - } // else 404 - } - } else if (path.size() == 1) { - // list networks - sqlite3_reset(_sListNetworks); - responseContentType = "application/json"; - responseBody = "["; - bool first = true; - while (sqlite3_step(_sListNetworks) == SQLITE_ROW) { - if (first) { - first = false; - responseBody.push_back('"'); - } else responseBody.append(",\""); - responseBody.append((const char *)sqlite3_column_text(_sListNetworks,0)); - responseBody.push_back('"'); - } - responseBody.push_back(']'); - return 200; - } // else 404 - - } else { - // GET /controller returns status and API version if controller is supported - Utils::snprintf(json,sizeof(json),"{\n\tcontroller: true,\n\tapiVersion: %d\n\tclock: %llu\n}",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); - responseBody = json; - responseContentType = "applicaiton/json"; - return 200; - } - - return 404; + return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType); } unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( @@ -902,20 +625,25 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( if (j) { if (j->type == json_object) { for(unsigned int k=0;k<j->u.object.length;++k) { - sqlite3_reset(_sUpdateMemberField); - sqlite3_bind_int64(_sUpdateMemberField,3,memberRowId); if (!strcmp(j->u.object.values[k].name,"authorized")) { if (j->u.object.values[k].value->type == json_boolean) { - sqlite3_bind_text(_sUpdateMemberField,1,"authorized",-1,SQLITE_STATIC); - sqlite3_bind_int(_sUpdateMemberField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - sqlite3_step(_sUpdateMemberField); + sqlite3_stmt *stmt = (sqlite3_stmt *)0; + if (sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ? WHERE rowid = ?",-1,&stmt,(const char **)0) == SQLITE_OK) + sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); + sqlite3_bind_int64(stmt,2,memberRowId); + sqlite3_step(stmt); + sqlite3_finalize(stmt); } } else if (!strcmp(j->u.object.values[k].name,"activeBridge")) { if (j->u.object.values[k].value->type == json_boolean) { - sqlite3_bind_text(_sUpdateMemberField,1,"activeBridge",-1,SQLITE_STATIC); - sqlite3_bind_int(_sUpdateMemberField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - sqlite3_step(_sUpdateMemberField); + sqlite3_stmt *stmt = (sqlite3_stmt *)0; + if (sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ? WHERE rowid = ?",-1,&stmt,(const char **)0) == SQLITE_OK) { + sqlite3_bind_int(stmt,1,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); + sqlite3_bind_int64(stmt,2,memberRowId); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } } } else if (!strcmp(j->u.object.values[k].name,"ipAssignments")) { if (j->u.object.values[k].value->type == json_array) { @@ -957,12 +685,13 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( } } } + } } json_value_free(j); } - return handleControlPlaneHttpGET(path,urlArgs,headers,body,responseBody,responseContentType); + return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType); } // else 404 } else { @@ -980,50 +709,42 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( if (j) { if (j->type == json_object) { for(unsigned int k=0;k<j->u.object.length;++k) { - sqlite3_reset(_sUpdateNetworkField); - sqlite3_bind_text(_sUpdateNetworkField,3,nwids,16,SQLITE_STATIC); + 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])) { - sqlite3_bind_text(_sUpdateNetworkField,1,"name",-1,SQLITE_STATIC); - sqlite3_bind_text(_sUpdateNetworkField,2,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC); - sqlite3_step(_sUpdateNetworkField); + 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) { - sqlite3_bind_text(_sUpdateNetworkField,1,"private",-1,SQLITE_STATIC); - sqlite3_bind_int(_sUpdateNetworkField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - sqlite3_step(_sUpdateNetworkField); + 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) { - sqlite3_bind_text(_sUpdateNetworkField,1,"enableBroadcast",-1,SQLITE_STATIC); - sqlite3_bind_int(_sUpdateNetworkField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - sqlite3_step(_sUpdateNetworkField); + 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) { - sqlite3_bind_text(_sUpdateNetworkField,1,"allowPassiveBridging",-1,SQLITE_STATIC); - sqlite3_bind_int(_sUpdateNetworkField,2,(j->u.object.values[k].value->u.boolean == 0) ? 0 : 1); - sqlite3_step(_sUpdateNetworkField); + 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) { - sqlite3_bind_text(_sUpdateNetworkField,1,"v4AssignMode",-1,SQLITE_STATIC); - sqlite3_bind_text(_sUpdateNetworkField,2,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC); - sqlite3_step(_sUpdateNetworkField); + if (sqlite3_prepare_v2(_db,"UPDATE Network SET v4AssignMode = ? 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,"v6AssignMode")) { if (j->u.object.values[k].value->type == json_string) { - sqlite3_bind_text(_sUpdateNetworkField,1,"v6AssignMode",-1,SQLITE_STATIC); - sqlite3_bind_text(_sUpdateNetworkField,2,j->u.object.values[k].value->u.string.ptr,-1,SQLITE_STATIC); - sqlite3_step(_sUpdateNetworkField); + if (sqlite3_prepare_v2(_db,"UPDATE Network SET v6AssignMode = ? 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,"multicastLimit")) { if (j->u.object.values[k].value->type == json_integer) { - sqlite3_bind_text(_sUpdateNetworkField,1,"multicastLimit",-1,SQLITE_STATIC); - sqlite3_bind_int(_sUpdateNetworkField,2,(int)j->u.object.values[k].value->u.integer); - sqlite3_step(_sUpdateNetworkField); + 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) { @@ -1185,6 +906,12 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( } } } + + if (stmt) { + sqlite3_bind_text(stmt,2,nwids,16,SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } } } json_value_free(j); @@ -1195,7 +922,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpPOST( sqlite3_bind_text(_sSetNetworkRevision,2,nwids,16,SQLITE_STATIC); sqlite3_step(_sSetNetworkRevision); - return handleControlPlaneHttpGET(path,urlArgs,headers,body,responseBody,responseContentType); + return _doCPGet(path,urlArgs,headers,body,responseBody,responseContentType); } } // else 404 @@ -1260,4 +987,287 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE( return 404; } +unsigned int SqliteNetworkController::_doCPGet( + const std::vector<std::string> &path, + const std::map<std::string,std::string> &urlArgs, + const std::map<std::string,std::string> &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + // Assumes _lock is locked + char json[16384]; + + if ((path.size() > 0)&&(path[0] == "network")) { + + 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); + + if (path.size() >= 3) { + if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { + uint64_t address = Utils::hexStrToU64(path[3].c_str()); + char addrs[24]; + Utils::snprintf(addrs,sizeof(addrs),"%.10llx",address); + + 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) { + Utils::snprintf(json,sizeof(json), + "{\n" + "\t\"nwid\": \"%s\",\n" + "\t\"address\": \"%s\",\n" + "\t\"authorized\": %s,\n" + "\t\"activeBridge\": %s,\n" + "\t\"lastAt\": \"%s\",\n" + "\t\"lastSeen\": %llu,\n" + "\t\"firstSeen\": %llu,\n" + "\t\"identity\": \"%s\",\n" + "\t\"ipAssignments\": [", + nwids, + addrs, + (sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false", + (sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false", + _jsonEscape((const char *)sqlite3_column_text(_sGetMember2,3)).c_str(), + (unsigned long long)sqlite3_column_int64(_sGetMember2,4), + (unsigned long long)sqlite3_column_int64(_sGetMember2,5), + _jsonEscape((const char *)sqlite3_column_text(_sGetMember2,2)).c_str()); + responseBody = json; + + sqlite3_reset(_sGetIpAssignmentsForNode2); + sqlite3_bind_text(_sGetIpAssignmentsForNode2,1,nwids,16,SQLITE_STATIC); + sqlite3_bind_text(_sGetIpAssignmentsForNode2,2,addrs,10,SQLITE_STATIC); + bool firstIp = true; + while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) { + InetAddress ip((const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode2,0),(sqlite3_column_int(_sGetIpAssignmentsForNode2,2) == 6) ? 16 : 4,(unsigned int)sqlite3_column_int(_sGetIpAssignmentPools2,1)); + responseBody.append(firstIp ? "\"" : ",\""); + firstIp = false; + responseBody.append(_jsonEscape(ip.toString())); + responseBody.push_back('"'); + } + + responseBody.append("]"); + + /* It's possible to get the actual netconf dictionary by including these + * three URL arguments. The member identity must be the string + * serialized identity of this member, and the signing identity must be + * the full secret identity of this network controller. The have revision + * is optional but would designate the revision our hypothetical client + * already has. + * + * This is primarily for testing and is not used in production. It makes + * it easy to test the entire network controller via its JSON API. + * + * If these arguments are included, three more object fields are returned: + * 'netconf', 'netconfResult', and 'netconfResultMessage'. These are all + * string fields and contain the actual netconf dictionary, the query + * result code, and any verbose message e.g. an error description. */ + std::map<std::string,std::string>::const_iterator memids(urlArgs.find("memberIdentity")); + std::map<std::string,std::string>::const_iterator sigids(urlArgs.find("signingIdentity")); + std::map<std::string,std::string>::const_iterator hrs(urlArgs.find("haveRevision")); + if ((memids != urlArgs.end())&&(sigids != urlArgs.end())) { + Dictionary netconf; + Identity memid,sigid; + try { + if (memid.fromString(memids->second)&&sigid.fromString(sigids->second)&&sigid.hasPrivate()) { + uint64_t hr = 0; + if (hrs != urlArgs.end()) + hr = Utils::strToU64(hrs->second.c_str()); + const char *result = ""; + switch(this->doNetworkConfigRequest(InetAddress(),sigid,memid,nwid,Dictionary(),hr,netconf)) { + case NetworkController::NETCONF_QUERY_OK: result = "OK"; break; + case NetworkController::NETCONF_QUERY_OK_BUT_NOT_NEWER: result = "OK_BUT_NOT_NEWER"; break; + case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: result = "OBJECT_NOT_FOUND"; break; + case NetworkController::NETCONF_QUERY_ACCESS_DENIED: result = "ACCESS_DENIED"; break; + case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: result = "INTERNAL_SERVER_ERROR"; break; + default: result = "(unrecognized result code)"; break; + } + responseBody.append(",\n\t\"netconf\": \""); + responseBody.append(_jsonEscape(netconf.toString().c_str())); + responseBody.append("\",\n\t\"netconfResult\": \""); + responseBody.append(result); + responseBody.append("\",\n\t\"netconfResultMessage\": \""); + responseBody.append(_jsonEscape(netconf["error"].c_str())); + responseBody.append("\""); + } else { + responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"invalid member or signing identity\""); + } + } catch ( ... ) { + responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"unexpected exception\""); + } + } + + responseBody.append("\n}\n"); + + responseContentType = "application/json"; + return 200; + } // else 404 + } // else 404 + } else { + // get network info + sqlite3_reset(_sGetNetworkById); + sqlite3_bind_text(_sGetNetworkById,1,nwids,16,SQLITE_STATIC); + if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) { + Utils::snprintf(json,sizeof(json), + "{\n" + "\t\"nwid\": \"%s\",\n" + "\t\"name\": \"%s\",\n" + "\t\"private\": %s,\n" + "\t\"enableBroadcast\": %s,\n" + "\t\"allowPassiveBridging\": %s,\n" + "\t\"v4AssignMode\": \"%s\",\n" + "\t\"v6AssignMode\": \"%s\",\n" + "\t\"multicastLimit\": %d,\n" + "\t\"creationTime\": %llu,\n" + "\t\"revision\": %llu,\n" + "\t\"members\": [", + nwids, + _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(), + (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false", + (sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false", + (sqlite3_column_int(_sGetNetworkById,3) > 0) ? "true" : "false", + _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,4)).c_str(), + _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,5)).c_str(), + sqlite3_column_int(_sGetNetworkById,6), + (unsigned long long)sqlite3_column_int64(_sGetNetworkById,7), + (unsigned long long)sqlite3_column_int64(_sGetNetworkById,8)); + responseBody = json; + + sqlite3_reset(_sListNetworkMembers); + sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC); + bool firstMember = true; + while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) { + if (!firstMember) + responseBody.push_back(','); + responseBody.push_back('"'); + responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0)); + responseBody.push_back('"'); + firstMember = false; + } + responseBody.append("],\n\t\"relays\": ["); + + sqlite3_reset(_sGetRelays); + sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC); + bool firstRelay = true; + while (sqlite3_step(_sGetRelays) == SQLITE_ROW) { + responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t"); + firstRelay = false; + responseBody.append("{\"address\":\""); + responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0)); + responseBody.append("\",\"phyAddress\":\""); + responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1))); + responseBody.append("\"}"); + } + responseBody.append("],\n\t\"ipAssignmentPools\": ["); + + sqlite3_reset(_sGetIpAssignmentPools2); + sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC); + bool firstIpAssignmentPool = true; + while (sqlite3_step(_sGetIpAssignmentPools2) == SQLITE_ROW) { + responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t"); + firstIpAssignmentPool = false; + InetAddress ipp((const void *)sqlite3_column_blob(_sGetIpAssignmentPools2,0),(sqlite3_column_int(_sGetIpAssignmentPools2,2) == 6) ? 16 : 4,(unsigned int)sqlite3_column_int(_sGetIpAssignmentPools2,1)); + Utils::snprintf(json,sizeof(json),"{\"network\":\"%s\",\"netmaskBits\":%u}", + _jsonEscape(ipp.toIpString()).c_str(), + ipp.netmaskBits()); + responseBody.append(json); + } + responseBody.append("],\n\t\"rules\": ["); + + sqlite3_reset(_sListRules); + sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC); + bool firstRule = true; + while (sqlite3_step(_sListRules) == SQLITE_ROW) { + responseBody.append(firstRule ? "\n\t{\n" : ",{\n"); + Utils::snprintf(json,sizeof(json),"\t\t\"ruleId\": %lld,\n",sqlite3_column_int64(_sListRules,0)); + responseBody.append(json); + if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1)); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"vlanId\": %d,\n",sqlite3_column_int(_sListRules,2)); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"vlanPcp\": %d,\n",sqlite3_column_int(_sListRules,3)); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"etherType\": %d,\n",sqlite3_column_int(_sListRules,4)); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"macSource\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,5)).toString().c_str()); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"macDest\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,6)).toString().c_str()); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"ipSource\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,7)).c_str()); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"ipDest\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,8)).c_str()); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"ipTos\": %d,\n",sqlite3_column_int(_sListRules,9)); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"ipProtocol\": %d,\n",sqlite3_column_int(_sListRules,10)); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"ipSourcePort\": %d,\n",sqlite3_column_int(_sListRules,11)); + responseBody.append(json); + } + if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) { + Utils::snprintf(json,sizeof(json),"\t\t\"ipDestPort\": %d,\n",sqlite3_column_int(_sListRules,12)); + responseBody.append(json); + } + responseBody.append("\t\t\"action\": \""); + responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sListRules,13))); + responseBody.append("\"\n\t}"); + } + + responseBody.append("]\n}\n"); + responseContentType = "application/json"; + return 200; + } // else 404 + } + } else if (path.size() == 1) { + // list networks + sqlite3_reset(_sListNetworks); + responseContentType = "application/json"; + responseBody = "["; + bool first = true; + while (sqlite3_step(_sListNetworks) == SQLITE_ROW) { + if (first) { + first = false; + responseBody.push_back('"'); + } else responseBody.append(",\""); + responseBody.append((const char *)sqlite3_column_text(_sListNetworks,0)); + responseBody.push_back('"'); + } + responseBody.push_back(']'); + return 200; + } // else 404 + + } else { + // GET /controller returns status and API version if controller is supported + Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); + responseBody = json; + responseContentType = "applicaiton/json"; + return 200; + } + + return 404; +} + } // namespace ZeroTier diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp index c5d4c51a..5c92cc0b 100644 --- a/controller/SqliteNetworkController.hpp +++ b/controller/SqliteNetworkController.hpp @@ -40,17 +40,14 @@ #include "../node/NetworkController.hpp" #include "../node/Mutex.hpp" -#include "../service/ControlPlaneSubsystem.hpp" - namespace ZeroTier { -class SqliteNetworkController : public NetworkController,public ControlPlaneSubsystem +class SqliteNetworkController : public NetworkController { public: SqliteNetworkController(const char *dbPath); virtual ~SqliteNetworkController(); - // NetworkController virtual NetworkController::ResultCode doNetworkConfigRequest( const InetAddress &fromAddr, const Identity &signingId, @@ -60,22 +57,21 @@ public: uint64_t haveRevision, Dictionary &netconf); - // ControlPlaneSubsystem - virtual unsigned int handleControlPlaneHttpGET( + unsigned int handleControlPlaneHttpGET( const std::vector<std::string> &path, const std::map<std::string,std::string> &urlArgs, const std::map<std::string,std::string> &headers, const std::string &body, std::string &responseBody, std::string &responseContentType); - virtual unsigned int handleControlPlaneHttpPOST( + unsigned int handleControlPlaneHttpPOST( const std::vector<std::string> &path, const std::map<std::string,std::string> &urlArgs, const std::map<std::string,std::string> &headers, const std::string &body, std::string &responseBody, std::string &responseContentType); - virtual unsigned int handleControlPlaneHttpDELETE( + unsigned int handleControlPlaneHttpDELETE( const std::vector<std::string> &path, const std::map<std::string,std::string> &urlArgs, const std::map<std::string,std::string> &headers, @@ -84,6 +80,14 @@ public: std::string &responseContentType); private: + unsigned int _doCPGet( + const std::vector<std::string> &path, + const std::map<std::string,std::string> &urlArgs, + const std::map<std::string,std::string> &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType); + std::string _dbPath; sqlite3 *_db; @@ -110,7 +114,6 @@ private: sqlite3_stmt *_sListRules; sqlite3_stmt *_sCreateRule; sqlite3_stmt *_sCreateNetwork; - sqlite3_stmt *_sUpdateNetworkField; sqlite3_stmt *_sGetNetworkRevision; sqlite3_stmt *_sSetNetworkRevision; sqlite3_stmt *_sGetIpAssignmentsForNode2; @@ -119,7 +122,6 @@ private: sqlite3_stmt *_sDeleteIpAssignmentPoolsForNetwork; sqlite3_stmt *_sDeleteRulesForNetwork; sqlite3_stmt *_sCreateIpAssignmentPool; - sqlite3_stmt *_sUpdateMemberField; sqlite3_stmt *_sDeleteMember; sqlite3_stmt *_sDeleteNetworkAndRelated; diff --git a/controller/schema.sql.c b/controller/schema.sql.c index f606b607..01b7c949 100644 --- a/controller/schema.sql.c +++ b/controller/schema.sql.c @@ -30,10 +30,6 @@ "CREATE TABLE Member (\n"\ " networkId char(16) NOT NULL,\n"\ " nodeId char(10) NOT NULL,\n"\ -" cachedNetconf blob(4096),\n"\ -" cachedNetconfRevision integer NOT NULL DEFAULT(0),\n"\ -" cachedNetconfTimestamp integer NOT NULL DEFAULT(0),\n"\ -" clientReportedRevision integer NOT NULL DEFAULT(0),\n"\ " authorized integer NOT NULL DEFAULT(0),\n"\ " activeBridge integer NOT NULL DEFAULT(0)\n"\ ");\n"\ diff --git a/nodejs-zt1-client/index.js b/nodejs-zt1-client/index.js new file mode 100644 index 00000000..f61e3b54 --- /dev/null +++ b/nodejs-zt1-client/index.js @@ -0,0 +1,137 @@ +'use strict' + +var request = require('request'); + +function ZT1Client(url,authToken) +{ + this.url = url; + this.authToken = authToken; +} + +ZT1Client.prototype._jsonGet = function(getPath,callback) +{ + request({ + url: this.url + getPath, + method: 'GET', + headers: { + 'X-ZT1-Auth': this.authToken + } + },function(error,response,body) { + if (error) + return callback(error,null); + if (response.statusCode !== 200) + return callback(new Error('server responded with error: '+response.statusCode),null); + return callback(null,(typeof body === 'string') ? JSON.parse(body) : null); + }); +}; + +ZT1Client.prototype.status = function(callback) +{ + request({ + url: this.url + 'controller', + method: 'GET', + headers: { + 'X-ZT1-Auth': this.authToken + } + },function(error,response,body) { + if (error) + return callback(error,{}); + var controllerStatus = {}; + if (typeof body === 'string') + controllerStatus = JSON.parse(body); + request({ + url: this.url + 'status', + method: 'GET', + headers: { + 'X-ZT1-Auth': this.authToken + } + },function(error,response,body) { + if (error) + return callback(error,{}); + if (response.statusCode !== 200) + return callback(new Error('server responded with '+response.statusCode),{}); + var nodeStatus = JSON.parse(body); + for(var k in controllerStatus) + nodeStatus[k] = controllerStatus[k]; + return callback(null,nodeStatus); + }.bind(this)); + }.bind(this)); +}; + +ZT1Client.prototype.getNetworks = function(callback) +{ + this._jsonGet('network',callback); +}; + +ZT1Client.prototype.getPeers = function(callback) +{ + this._jsonGet('peer',callback); +}; + +ZT1Client.prototype.listControllerNetworks = function(callback) +{ + this._jsonGet('controller/network',callback); +}; + +ZT1Client.prototype.getControllerNetwork = function(nwid,callback) +{ + this._jsonGet('controller/network/' + nwid,callback); +}; + +ZT1Client.prototype.saveControllerNetwork = function(network,callback) +{ + if ((typeof network.nwid !== 'string')||(network.nwid.length !== 16)) + return callback(new Error('Missing required field: nwid'),null); + + // The ZT1 service is type variation intolerant, so recreate our submission with the correct types + var n = { + nwid: network.nwid + }; + if (network.name) + n.name = network.name.toString(); + if ('private' in network) + n.private = (network.private) ? true : false; + if ('enableBroadcast' in network) + n.enableBroadcast = (network.enableBroadcast) ? true : false; + if ('allowPassiveBridging' in network) + n.allowPassiveBridging = (network.allowPassiveBridging) ? true : false; + if ('v4AssignMode' in network) { + if (network.v4AssignMode) + n.v4AssignMode = network.v4AssignMode.toString(); + else n.v4AssignMode = 'none'; + } + if ('v6AssignMode' in network) { + if (network.v6AssignMode) + n.v6AssignMode = network.v6AssignMode.toString(); + else n.v4AssignMode = 'none'; + } + if ('multicastLimit' in network) { + if (typeof network.multicastLimit === 'number') + n.multicastLimit = network.multicastLimit; + else n.multicastLimit = parseInt(network.multicastLimit.toString()); + } + if (Array.isArray(network.relays)) + n.relays = network.relays; + if (Array.isArray(network.ipAssignmentPools)) + n.ipAssignmentPools = network.ipAssignmentPools; + if (Array.isArray(network.rules)) + n.rules = network.rules; + + request({ + url: this.url + 'controller/network/' + n.nwid, + method: 'POST', + json: true, + body: n, + headers: { + 'X-ZT1-Auth': this.authToken + } + },function(err,response,body) { + if (err) + return callback(err,null); + if (response.statusCode !== 200) + return callback(new Error('server responded with error: '+response.statusCode),null); + return callback(null,(typeof body === 'string') ? JSON.parse(body) : body); + }); +}; + +exports.ZT1Client = ZT1Client; diff --git a/nodejs-zt1-client/package.json b/nodejs-zt1-client/package.json new file mode 100644 index 00000000..c840d8ae --- /dev/null +++ b/nodejs-zt1-client/package.json @@ -0,0 +1,14 @@ +{ + "name": "nodejs-zt1-client", + "version": "1.0.0", + "description": "ZeroTier One Network Virtualization Service JSON API Client", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ZeroTier, Inc.", + "license": "BSD", + "dependencies": { + "request": "^2.55.0" + } +} diff --git a/nodejs-zt1-client/test.js b/nodejs-zt1-client/test.js new file mode 100644 index 00000000..347ca1a1 --- /dev/null +++ b/nodejs-zt1-client/test.js @@ -0,0 +1,33 @@ +var ZT1Client = require('./index.js').ZT1Client; + +var zt1c = new ZT1Client('http://127.0.0.1:9993/','5d6181b71fae2684f9cc64ed'); + +zt1c.status(function(err,status) { + if (err) + console.log(err); + else console.log(status); + + zt1c.getNetworks(function(err,networks) { + if (err) + console.log(err); + else console.log(networks); + + zt1c.getPeers(function(err,peers) { + if (err) + console.log(err); + else console.log(peers); + + if (status.controller) { + zt1c.saveControllerNetwork({ + nwid: status.address + 'dead01', + name: 'test network', + private: true + },function(err,network) { + if (err) + console.log(err); + else console.log(network); + }); + } + }); + }); +}); @@ -881,6 +881,7 @@ static void printHelp(const char *cn,FILE *out) fprintf(out,"Available switches:"ZT_EOL_S); fprintf(out," -h - Display this help"ZT_EOL_S); fprintf(out," -v - Show version"ZT_EOL_S); + fprintf(out," -U - Run as unprivileged user (skip privilege check)"ZT_EOL_S); fprintf(out," -p<port> - Port for UDP and TCP/HTTP (default: 9993)"ZT_EOL_S); //fprintf(out," -T<path> - Override root topology, do not authenticate or update"ZT_EOL_S); #ifdef __UNIX_LIKE__ @@ -945,6 +946,7 @@ int main(int argc,char **argv) std::string overrideRootTopology; std::string homeDir; unsigned int port = ZT1_DEFAULT_PORT; + bool skipRootCheck = false; for(int i=1;i<argc;++i) { if (argv[i][0] == '-') { @@ -964,6 +966,10 @@ int main(int argc,char **argv) break; #endif // __UNIX_LIKE__ + case 'U': + skipRootCheck = true; + break; + case 'T': // Override root topology if (argv[i][2]) { if (!OSUtils::readFile(argv[i] + 2,overrideRootTopology)) { @@ -1082,11 +1088,10 @@ int main(int argc,char **argv) } #ifdef __UNIX_LIKE__ - if (getuid() != 0) { + if ((!skipRootCheck)&&(getuid() != 0)) { fprintf(stderr,"%s: must be run as root (uid 0)"ZT_EOL_S,argv[0]); return 1; } - if (runAsDaemon) { long p = (long)fork(); if (p < 0) { @@ -1102,10 +1107,13 @@ int main(int argc,char **argv) if (winRunFromCommandLine) { // Running in "interactive" mode (mostly for debugging) if (IsCurrentUserLocalAdministrator() != TRUE) { - fprintf(stderr,"%s: must be run as a local administrator."ZT_EOL_S,argv[0]); - return 1; + if (!skipRootCheck) { + fprintf(stderr,"%s: must be run as a local administrator."ZT_EOL_S,argv[0]); + return 1; + } + } else { + _winPokeAHole(); } - _winPokeAHole(); SetConsoleCtrlHandler(&_winConsoleCtrlHandler,TRUE); // continues on to ordinary command line execution code below... } else { diff --git a/service/ControlPlane.cpp b/service/ControlPlane.cpp index 69c5d48d..1d9f9b4a 100644 --- a/service/ControlPlane.cpp +++ b/service/ControlPlane.cpp @@ -26,7 +26,6 @@ */ #include "ControlPlane.hpp" -#include "ControlPlaneSubsystem.hpp" #include "OneService.hpp" #include "../version.h" @@ -34,6 +33,8 @@ #include "../ext/http-parser/http_parser.h" +#include "../controller/SqliteNetworkController.hpp" + #include "../node/InetAddress.hpp" #include "../node/Node.hpp" #include "../node/Utils.hpp" @@ -444,7 +445,7 @@ unsigned int ControlPlane::handleRequest( responseContentType = "text/plain"; scode = 200; } else { - std::map<std::string,ControlPlaneSubsystem *>::const_iterator ss(_subsystems.find(ps[0])); + std::map<std::string,SqliteNetworkController *>::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) scode = ss->second->handleControlPlaneHttpGET(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); else scode = 404; @@ -477,7 +478,7 @@ unsigned int ControlPlane::handleRequest( } else scode = 500; } } else { - std::map<std::string,ControlPlaneSubsystem *>::const_iterator ss(_subsystems.find(ps[0])); + std::map<std::string,SqliteNetworkController *>::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) scode = ss->second->handleControlPlaneHttpPOST(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); else scode = 404; @@ -509,7 +510,7 @@ unsigned int ControlPlane::handleRequest( _node->freeQueryResult((void *)nws); } else scode = 500; } else { - std::map<std::string,ControlPlaneSubsystem *>::const_iterator ss(_subsystems.find(ps[0])); + std::map<std::string,SqliteNetworkController *>::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) scode = ss->second->handleControlPlaneHttpDELETE(std::vector<std::string>(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); else scode = 404; diff --git a/service/ControlPlane.hpp b/service/ControlPlane.hpp index b7b0a857..fc8a0182 100644 --- a/service/ControlPlane.hpp +++ b/service/ControlPlane.hpp @@ -40,7 +40,7 @@ namespace ZeroTier { class OneService; class Node; -class ControlPlaneSubsystem; +class SqliteNetworkController; struct InetAddress; /** @@ -72,7 +72,7 @@ public: * @param prefix First element in URI path * @param subsys Object to call for results of GET and POST/PUT operations */ - inline void mount(const char *prefix,ControlPlaneSubsystem *subsys) + inline void mount(const char *prefix,SqliteNetworkController *subsys) { Mutex::Lock _l(_lock); _subsystems[std::string(prefix)] = subsys; @@ -104,7 +104,7 @@ private: Node *const _node; std::string _uiStaticPath; std::set<std::string> _authTokens; - std::map<std::string,ControlPlaneSubsystem *> _subsystems; + std::map<std::string,SqliteNetworkController *> _subsystems; Mutex _lock; }; diff --git a/service/ControlPlaneSubsystem.hpp b/service/ControlPlaneSubsystem.hpp deleted file mode 100644 index 9de875ca..00000000 --- a/service/ControlPlaneSubsystem.hpp +++ /dev/null @@ -1,76 +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/>. - * - * -- - * - * 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_CONTROLPLANESUBSYSTEM_HPP -#define ZT_CONTROLPLANESUBSYSTEM_HPP - -#include <map> -#include <vector> -#include <string> - -namespace ZeroTier { - -/** - * Base class for subsystems that can be mounted under the HTTP control plane - * - * Handlers should fill in responseBody and responseContentType and return - * a HTTP status code or 0 on other errors. - */ -class ControlPlaneSubsystem -{ -public: - ControlPlaneSubsystem() {} - virtual ~ControlPlaneSubsystem() {} - - virtual unsigned int handleControlPlaneHttpGET( - const std::vector<std::string> &path, - const std::map<std::string,std::string> &urlArgs, - const std::map<std::string,std::string> &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) = 0; - - virtual unsigned int handleControlPlaneHttpPOST( - const std::vector<std::string> &path, - const std::map<std::string,std::string> &urlArgs, - const std::map<std::string,std::string> &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) = 0; - - virtual unsigned int handleControlPlaneHttpDELETE( - const std::vector<std::string> &path, - const std::map<std::string,std::string> &urlArgs, - const std::map<std::string,std::string> &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) = 0; -}; - -} // namespace ZeroTier - -#endif diff --git a/service/OneService.cpp b/service/OneService.cpp index 99aaf77d..13108aa1 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -233,7 +233,7 @@ public: _controlPlane = new ControlPlane(this,_node,(_homePath + ZT_PATH_SEPARATOR_S + "ui").c_str()); _controlPlane->addAuthToken(authToken.c_str()); if (_master) - _controlPlane->mount("controller",reinterpret_cast<ControlPlaneSubsystem *>(_master)); + _controlPlane->mount("controller",reinterpret_cast<SqliteNetworkController *>(_master)); { // Remember networks from previous session std::vector<std::string> networksDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S + "networks.d").c_str())); diff --git a/service/README.md b/service/README.md index c347931f..ecab6e75 100644 --- a/service/README.md +++ b/service/README.md @@ -11,7 +11,7 @@ The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for Values POSTed to the JSON API are *extremely* type sensitive. Things *must* be of the indicated type, otherwise they will be ignored or will generate an error. Anything quoted is a string so booleans and integers must lack quotes. Booleans must be *true* or *false* and nothing else. Integers cannot contain decimal points or they are floats (and vice versa). If something seems to be getting ignored or set to a strange value, or if you receive errors, check the type of all JSON fields you are submitting against the types listed below. Unrecognized fields in JSON objects are also ignored. -API requests must be authenticated via an authentication token. ZeroTier One saves this token in the *authtoken.secret* file in its working directory. This token may be supplied via the *authToken* URL parameter (e.g. '?authToken=...') or via the *X-ZT1-Auth* HTTP request header. Static UI pages are the only thing the server will allow without authentication. +API requests must be authenticated via an authentication token. ZeroTier One saves this token in the *authtoken.secret* file in its working directory. This token may be supplied via the *auth* URL parameter (e.g. '?auth=...') or via the *X-ZT1-Auth* HTTP request header. Static UI pages are the only thing the server will allow without authentication. A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP response is sent as a script with its JSON response payload wrapped in a call to the function name supplied as the argument to *jsonp*. |