Browse Source

Merge branch 'adamierymenko-dev' into android-jni

Grant Limberg 10 years ago
parent
commit
bc0954381c
3 changed files with 145 additions and 129 deletions
  1. 144 128
      controller/SqliteNetworkController.cpp
  2. 1 0
      controller/SqliteNetworkController.hpp
  3. 0 1
      service/README.md

+ 144 - 128
controller/SqliteNetworkController.cpp

@@ -175,7 +175,7 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 			||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"DELETE FROM IpAssignmentPool WHERE networkId = ?",-1,&_sDeleteIpAssignmentPoolsForNetwork,(const char **)0) != SQLITE_OK)
 
 
 			/* IpAssignment */
 			/* IpAssignment */
-			||(sqlite3_prepare_v2(_db,"SELECT \"type\",ip,ipNetmaskBits FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND ipVersion = ?",-1,&_sGetIpAssignmentsForNode,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT \"type\",ip,ipNetmaskBits FROM IpAssignment WHERE networkId = ? AND (nodeId = ? OR nodeId IS NULL) AND ipVersion = ?",-1,&_sGetIpAssignmentsForNode,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = ? ORDER BY ip ASC",-1,&_sGetIpAssignmentsForNode2,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId = ? AND \"type\" = ? ORDER BY ip ASC",-1,&_sGetIpAssignmentsForNode2,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId IS NULL AND \"type\" = ?",-1,&_sGetLocalRoutes,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT ip,ipNetmaskBits,ipVersion FROM IpAssignment WHERE networkId = ? AND nodeId IS NULL AND \"type\" = ?",-1,&_sGetLocalRoutes,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE networkId = ? AND ip = ? AND ipVersion = ? AND \"type\" = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT 1 FROM IpAssignment WHERE networkId = ? AND ip = ? AND ipVersion = ? AND \"type\" = ?",-1,&_sCheckIfIpIsAllocated,(const char **)0) != SQLITE_OK)
@@ -193,7 +193,7 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 			||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,m.memberRevision,n.identity FROM Member AS m JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT m.authorized,m.activeBridge,m.memberRevision,n.identity FROM Member AS m JOIN Node AS n ON n.id = m.nodeId WHERE m.networkId = ? AND m.nodeId = ?",-1,&_sGetMember2,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge,memberRevision) VALUES (?,?,?,0,(SELECT memberRevisionCounter FROM Network WHERE id = ?))",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"INSERT INTO Member (networkId,nodeId,authorized,activeBridge,memberRevision) VALUES (?,?,?,0,(SELECT memberRevisionCounter FROM Network WHERE id = ?))",-1,&_sCreateMember,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT nodeId FROM Member WHERE networkId = ? AND activeBridge > 0 AND authorized > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT nodeId FROM Member WHERE networkId = ? AND activeBridge > 0 AND authorized > 0",-1,&_sGetActiveBridges,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"SELECT m.nodeId FROM Member AS m WHERE m.networkId = ? ORDER BY m.nodeId ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"SELECT m.nodeId,m.memberRevision FROM Member AS m WHERE m.networkId = ? ORDER BY m.nodeId ASC",-1,&_sListNetworkMembers,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberAuthorized,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"UPDATE Member SET authorized = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberAuthorized,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberActiveBridge,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"UPDATE Member SET activeBridge = ?,memberRevision = (SELECT memberRevisionCounter FROM Network WHERE id = ?) WHERE rowid = ?",-1,&_sUpdateMemberActiveBridge,(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 Member WHERE networkId = ? AND nodeId = ?",-1,&_sDeleteMember,(const char **)0) != SQLITE_OK)
@@ -205,7 +205,7 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 
 
 			/* Config */
 			/* Config */
 			||(sqlite3_prepare_v2(_db,"SELECT \"v\" FROM \"Config\" WHERE \"k\" = ?",-1,&_sGetConfig,(const char **)0) != SQLITE_OK)
 			||(sqlite3_prepare_v2(_db,"SELECT \"v\" FROM \"Config\" WHERE \"k\" = ?",-1,&_sGetConfig,(const char **)0) != SQLITE_OK)
-			||(sqlite3_prepare_v2(_db,"INSERT INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK)
+			||(sqlite3_prepare_v2(_db,"INSERT OR REPLACE INTO \"Config\" (\"k\",\"v\") VALUES (?,?)",-1,&_sSetConfig,(const char **)0) != SQLITE_OK)
 
 
 		 ) {
 		 ) {
 		//printf("!!! %s\n",sqlite3_errmsg(_db));
 		//printf("!!! %s\n",sqlite3_errmsg(_db));
@@ -221,16 +221,19 @@ SqliteNetworkController::SqliteNetworkController(const char *dbPath) :
 	if (sqlite3_step(_sGetConfig) != SQLITE_ROW) {
 	if (sqlite3_step(_sGetConfig) != SQLITE_ROW) {
 		unsigned char sr[32];
 		unsigned char sr[32];
 		Utils::getSecureRandom(sr,32);
 		Utils::getSecureRandom(sr,32);
-		char instanceId[32];
 		for(unsigned int i=0;i<32;++i)
 		for(unsigned int i=0;i<32;++i)
-			instanceId[i] = "0123456789abcdef"[(unsigned int)sr[i] & 0xf];
+			_instanceId.push_back("0123456789abcdef"[(unsigned int)sr[i] & 0xf]);
+
 		sqlite3_reset(_sSetConfig);
 		sqlite3_reset(_sSetConfig);
 		sqlite3_bind_text(_sSetConfig,1,"instanceId",10,SQLITE_STATIC);
 		sqlite3_bind_text(_sSetConfig,1,"instanceId",10,SQLITE_STATIC);
-		sqlite3_bind_text(_sSetConfig,2,instanceId,32,SQLITE_STATIC);
-		if (sqlite3_step(_sSetConfig) != SQLITE_DONE) {
-			sqlite3_close(_db);
+		sqlite3_bind_text(_sSetConfig,2,_instanceId.c_str(),-1,SQLITE_STATIC);
+		if (sqlite3_step(_sSetConfig) != SQLITE_DONE)
 			throw std::runtime_error("SqliteNetworkController unable to read or initialize instanceId");
 			throw std::runtime_error("SqliteNetworkController unable to read or initialize instanceId");
-		}
+	} else {
+		const char *iid = reinterpret_cast<const char *>(sqlite3_column_text(_sGetConfig,0));
+		if (!iid)
+			throw std::runtime_error("SqliteNetworkController unable to read instanceId (it's NULL)");
+		_instanceId = iid;
 	}
 	}
 }
 }
 
 
@@ -326,7 +329,7 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 		sqlite3_bind_text(_sCreateNode,1,member.nodeId,10,SQLITE_STATIC);
 		sqlite3_bind_text(_sCreateNode,1,member.nodeId,10,SQLITE_STATIC);
 		sqlite3_bind_text(_sCreateNode,2,idstr.c_str(),-1,SQLITE_STATIC);
 		sqlite3_bind_text(_sCreateNode,2,idstr.c_str(),-1,SQLITE_STATIC);
 		if (sqlite3_step(_sCreateNode) != SQLITE_DONE) {
 		if (sqlite3_step(_sCreateNode) != SQLITE_DONE) {
-			netconf["error"] = "unable to create new node record";
+			netconf["error"] = "unable to create new Node record";
 			return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
 			return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
 		}
 		}
 	}
 	}
@@ -346,7 +349,9 @@ NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(co
 		network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
 		network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
 		network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8);
 		network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8);
 		network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,9);
 		network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,9);
-	} else return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
+	} else {
+		return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
+	}
 
 
 	// Fetch Member record
 	// Fetch Member record
 
 
@@ -1273,109 +1278,136 @@ unsigned int SqliteNetworkController::_doCPGet(
 			Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
 			Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
 
 
 			if (path.size() >= 3) {
 			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\"memberRevision\": %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",
-							(unsigned long long)sqlite3_column_int64(_sGetMember2,2),
-							_jsonEscape((const char *)sqlite3_column_text(_sGetMember2,3)).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);
-						sqlite3_bind_int(_sGetIpAssignmentsForNode2,3,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
-						bool firstIp = true;
-						while (sqlite3_step(_sGetIpAssignmentsForNode2) == SQLITE_ROW) {
-							int ipversion = sqlite3_column_int(_sGetIpAssignmentsForNode2,2);
-							char ipBlob[16];
-							memcpy(ipBlob,(const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode2,0),16);
-							InetAddress ip(
-								(const void *)(ipversion == 6 ? ipBlob : &ipBlob[12]),
-								(ipversion == 6 ? 16 : 4),
-								(unsigned int)sqlite3_column_int(_sGetIpAssignmentsForNode2,1)
-							);
-							responseBody.append(firstIp ? "\"" : ",\"");
-							firstIp = false;
-							responseBody.append(_jsonEscape(ip.toString()));
-							responseBody.push_back('"');
-						}
+				// /network/<nwid>/...
+
+				if (path[2] == "member") {
+
+					if (path.size() >= 4) {
+						// Get specific member info
+
+						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\"controllerInstanceId\": \"%s\",\n"
+								"\t\"authorized\": %s,\n"
+								"\t\"activeBridge\": %s,\n"
+								"\t\"memberRevision\": %llu,\n"
+								"\t\"identity\": \"%s\",\n"
+								"\t\"ipAssignments\": [",
+								nwids,
+								addrs,
+								_instanceId.c_str(),
+								(sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false",
+								(sqlite3_column_int(_sGetMember2,1) > 0) ? "true" : "false",
+								(unsigned long long)sqlite3_column_int64(_sGetMember2,2),
+								_jsonEscape((const char *)sqlite3_column_text(_sGetMember2,3)).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);
+							sqlite3_bind_int(_sGetIpAssignmentsForNode2,3,(int)ZT_IP_ASSIGNMENT_TYPE_ADDRESS);
+							bool firstIp = true;
+							while (sqlite3_step(_sGetIpAssignmentsForNode2) == SQLITE_ROW) {
+								int ipversion = sqlite3_column_int(_sGetIpAssignmentsForNode2,2);
+								char ipBlob[16];
+								memcpy(ipBlob,(const void *)sqlite3_column_blob(_sGetIpAssignmentsForNode2,0),16);
+								InetAddress ip(
+									(const void *)(ipversion == 6 ? ipBlob : &ipBlob[12]),
+									(ipversion == 6 ? 16 : 4),
+									(unsigned int)sqlite3_column_int(_sGetIpAssignmentsForNode2,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("]");
+
+							/* 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\"");
 									}
 									}
-									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\"");
 								}
 								}
-							} catch ( ... ) {
-								responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"unexpected exception\"");
 							}
 							}
-						}
 
 
-						responseBody.append("\n}\n");
+							responseBody.append("\n}\n");
+
+							responseContentType = "application/json";
+							return 200;
+						} // else 404
+
+					} else {
+						// List members
 
 
+						sqlite3_reset(_sListNetworkMembers);
+						sqlite3_bind_text(_sListNetworkMembers,1,nwids,16,SQLITE_STATIC);
+						while (sqlite3_step(_sListNetworkMembers) == SQLITE_ROW) {
+							responseBody.append((responseBody.length() > 0) ? ",\"" : "{\"");
+							responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,0));
+							responseBody.append("\":");
+							responseBody.append((const char *)sqlite3_column_text(_sListNetworkMembers,1));
+						}
+						responseBody.push_back('}');
 						responseContentType = "application/json";
 						responseContentType = "application/json";
 						return 200;
 						return 200;
-					} // else 404
+
+					}
+
 				} // else 404
 				} // else 404
+
 			} else {
 			} else {
 				// get network info
 				// get network info
 				sqlite3_reset(_sGetNetworkById);
 				sqlite3_reset(_sGetNetworkById);
@@ -1384,6 +1416,7 @@ unsigned int SqliteNetworkController::_doCPGet(
 					Utils::snprintf(json,sizeof(json),
 					Utils::snprintf(json,sizeof(json),
 						"{\n"
 						"{\n"
 						"\t\"nwid\": \"%s\",\n"
 						"\t\"nwid\": \"%s\",\n"
+						"\t\"controllerInstanceId\": \"%s\",\n"
 						"\t\"name\": \"%s\",\n"
 						"\t\"name\": \"%s\",\n"
 						"\t\"private\": %s,\n"
 						"\t\"private\": %s,\n"
 						"\t\"enableBroadcast\": %s,\n"
 						"\t\"enableBroadcast\": %s,\n"
@@ -1394,8 +1427,9 @@ unsigned int SqliteNetworkController::_doCPGet(
 						"\t\"creationTime\": %llu,\n"
 						"\t\"creationTime\": %llu,\n"
 						"\t\"revision\": %llu,\n"
 						"\t\"revision\": %llu,\n"
 						"\t\"memberRevisionCounter\": %llu,\n"
 						"\t\"memberRevisionCounter\": %llu,\n"
-						"\t\"members\": [",
+						"\t\"relays\": [",
 						nwids,
 						nwids,
+						_instanceId.c_str(),
 						_jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(),
 						_jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(),
 						(sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false",
 						(sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false",
 						(sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false",
 						(sqlite3_column_int(_sGetNetworkById,2) > 0) ? "true" : "false",
@@ -1408,20 +1442,6 @@ unsigned int SqliteNetworkController::_doCPGet(
 						(unsigned long long)sqlite3_column_int64(_sGetNetworkById,9));
 						(unsigned long long)sqlite3_column_int64(_sGetNetworkById,9));
 					responseBody = json;
 					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_reset(_sGetRelays);
 					sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC);
 					sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC);
 					bool firstRelay = true;
 					bool firstRelay = true;
@@ -1657,14 +1677,10 @@ unsigned int SqliteNetworkController::_doCPGet(
 
 
 	} else {
 	} else {
 		// GET /controller returns status and API version if controller is supported
 		// GET /controller returns status and API version if controller is supported
-		sqlite3_reset(_sGetConfig);
-		sqlite3_bind_text(_sGetConfig,1,"instanceId",10,SQLITE_STATIC);
-		if (sqlite3_step(_sGetConfig) == SQLITE_ROW) {
-			Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"instanceId\": \"%s\"\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),(const char *)sqlite3_column_text(_sGetConfig,0));
-			responseBody = json;
-			responseContentType = "applicaiton/json";
-			return 200;
-		} else return 500;
+		Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu,\n\t\"instanceId\": \"%s\"\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now(),_instanceId.c_str());
+		responseBody = json;
+		responseContentType = "applicaiton/json";
+		return 200;
 	}
 	}
 
 
 	return 404;
 	return 404;

+ 1 - 0
controller/SqliteNetworkController.hpp

@@ -96,6 +96,7 @@ private:
 		std::string &responseContentType);
 		std::string &responseContentType);
 
 
 	std::string _dbPath;
 	std::string _dbPath;
+	std::string _instanceId;
 	sqlite3 *_db;
 	sqlite3 *_db;
 
 
 	sqlite3_stmt *_sGetNetworkById;
 	sqlite3_stmt *_sGetNetworkById;

+ 0 - 1
service/README.md

@@ -173,7 +173,6 @@ To create a new network with a random last six digits safely and atomically, you
 <tr><td>creationTime</td><td>integer</td><td>Time network was created in ms since epoch</td><td>no</td></tr>
 <tr><td>creationTime</td><td>integer</td><td>Time network was created in ms since epoch</td><td>no</td></tr>
 <tr><td>revision</td><td>integer</td><td>Network config revision number</td><td>no</td></tr>
 <tr><td>revision</td><td>integer</td><td>Network config revision number</td><td>no</td></tr>
 <tr><td>memberRevisionCounter</td><td>integer</td><td>Current value of network revision counter (incremented after every member add or revision)</td><td>no</td></tr>
 <tr><td>memberRevisionCounter</td><td>integer</td><td>Current value of network revision counter (incremented after every member add or revision)</td><td>no</td></tr>
-<tr><td>members</td><td>[string]</td><td>Array of ZeroTier addresses of network members</td><td>no</td></tr>
 <tr><td>relays</td><td>[object]</td><td>Array of network-specific relay nodes (see below)</td><td>yes</td></tr>
 <tr><td>relays</td><td>[object]</td><td>Array of network-specific relay nodes (see below)</td><td>yes</td></tr>
 <tr><td>ipLocalRoutes</td><td>[string]</td><td>Array of IP network/netmask entries corresponding to networks routed directly via this interface (e.g. 10.0.0.0/8 to route 10.0.0.0 via this interface)</td></tr>
 <tr><td>ipLocalRoutes</td><td>[string]</td><td>Array of IP network/netmask entries corresponding to networks routed directly via this interface (e.g. 10.0.0.0/8 to route 10.0.0.0 via this interface)</td></tr>
 <tr><td>ipAssignmentPools</td><td>[object]</td><td>Array of IP auto-assignment pools for 'zt' assignment mode</td><td>yes</td></tr>
 <tr><td>ipAssignmentPools</td><td>[object]</td><td>Array of IP auto-assignment pools for 'zt' assignment mode</td><td>yes</td></tr>