Forráskód Böngészése

Merge branch 'adamierymenko-dev' into android-jni

Grant Limberg 10 éve
szülő
commit
9a00366b18

+ 1 - 0
.gitignore

@@ -52,3 +52,4 @@ java/build_win32/
 /ui/.module-cache
 /windows/WebUIWrapper/bin
 /windows/WebUIWrapper/obj
+node_modules

+ 324 - 314
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

+ 12 - 10
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;
 

+ 0 - 4
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"\

+ 137 - 0
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;

+ 14 - 0
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"
+  }
+}

+ 33 - 0
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);
+				});
+			}
+		});
+	});
+});

+ 13 - 5
one.cpp

@@ -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 {

+ 5 - 4
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;

+ 3 - 3
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;
 };
 

+ 0 - 76
service/ControlPlaneSubsystem.hpp

@@ -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

+ 1 - 1
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()));

+ 1 - 1
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*.