Просмотр исходного кода

Allow further concurrency on network controller.

Adam Ierymenko 9 лет назад
Родитель
Сommit
972bbb7e06
1 измененных файлов с 374 добавлено и 381 удалено
  1. 374 381
      controller/SqliteNetworkController.cpp

+ 374 - 381
controller/SqliteNetworkController.cpp

@@ -418,8 +418,380 @@ SqliteNetworkController::~SqliteNetworkController()
 
 NetworkController::ResultCode SqliteNetworkController::doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &metaData,NetworkConfig &nc)
 {
-	Mutex::Lock _l(_lock);
-	return _doNetworkConfigRequest(fromAddr,signingId,identity,nwid,metaData,nc);
+	if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) {
+		return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+	}
+
+	const uint64_t now = OSUtils::now();
+
+	NetworkRecord network;
+	memset(&network,0,sizeof(network));
+	Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid);
+
+	MemberRecord member;
+	memset(&member,0,sizeof(member));
+	Utils::snprintf(member.nodeId,sizeof(member.nodeId),"%.10llx",(unsigned long long)identity.address().toInt());
+
+	{ // begin lock
+		Mutex::Lock _l(_lock);
+
+		// Check rate limit circuit breaker to prevent flooding
+		{
+			uint64_t &lrt = _lastRequestTime[std::pair<uint64_t,uint64_t>(identity.address().toInt(),nwid)];
+			if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
+				return NetworkController::NETCONF_QUERY_IGNORE;
+			lrt = now;
+		}
+
+		_backupNeeded = true;
+
+		// Create Node record or do full identity check if we already have one
+
+		sqlite3_reset(_sGetNodeIdentity);
+		sqlite3_bind_text(_sGetNodeIdentity,1,member.nodeId,10,SQLITE_STATIC);
+		if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) {
+			try {
+				Identity alreadyKnownIdentity((const char *)sqlite3_column_text(_sGetNodeIdentity,0));
+				if (alreadyKnownIdentity != identity)
+					return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
+			} catch ( ... ) { // identity stored in database is not valid or is NULL
+				return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
+			}
+		} else {
+			std::string idstr(identity.toString(false));
+			sqlite3_reset(_sCreateOrReplaceNode);
+			sqlite3_bind_text(_sCreateOrReplaceNode,1,member.nodeId,10,SQLITE_STATIC);
+			sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr.c_str(),-1,SQLITE_STATIC);
+			if (sqlite3_step(_sCreateOrReplaceNode) != SQLITE_DONE) {
+				return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+			}
+		}
+
+		// Fetch Network record
+
+		sqlite3_reset(_sGetNetworkById);
+		sqlite3_bind_text(_sGetNetworkById,1,network.id,16,SQLITE_STATIC);
+		if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
+			network.name = (const char *)sqlite3_column_text(_sGetNetworkById,0);
+			network.isPrivate = (sqlite3_column_int(_sGetNetworkById,1) > 0);
+			network.enableBroadcast = (sqlite3_column_int(_sGetNetworkById,2) > 0);
+			network.allowPassiveBridging = (sqlite3_column_int(_sGetNetworkById,3) > 0);
+			network.flags = sqlite3_column_int(_sGetNetworkById,4);
+			network.multicastLimit = sqlite3_column_int(_sGetNetworkById,5);
+			network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,6);
+			network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
+			network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8);
+		} else {
+			return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
+		}
+
+		// Fetch Member record
+
+		bool foundMember = false;
+		sqlite3_reset(_sGetMember);
+		sqlite3_bind_text(_sGetMember,1,network.id,16,SQLITE_STATIC);
+		sqlite3_bind_text(_sGetMember,2,member.nodeId,10,SQLITE_STATIC);
+		if (sqlite3_step(_sGetMember) == SQLITE_ROW) {
+			foundMember = true;
+			member.rowid = (int64_t)sqlite3_column_int64(_sGetMember,0);
+			member.authorized = (sqlite3_column_int(_sGetMember,1) > 0);
+			member.activeBridge = (sqlite3_column_int(_sGetMember,2) > 0);
+		}
+
+		// Create Member record for unknown nodes, auto-authorizing if network is public
+
+		if (!foundMember) {
+			member.authorized = (network.isPrivate ? false : true);
+			member.activeBridge = false;
+			sqlite3_reset(_sCreateMember);
+			sqlite3_bind_text(_sCreateMember,1,network.id,16,SQLITE_STATIC);
+			sqlite3_bind_text(_sCreateMember,2,member.nodeId,10,SQLITE_STATIC);
+			sqlite3_bind_int(_sCreateMember,3,(member.authorized ? 1 : 0));
+			sqlite3_bind_text(_sCreateMember,4,network.id,16,SQLITE_STATIC);
+			if (sqlite3_step(_sCreateMember) != SQLITE_DONE) {
+				return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+			}
+			member.rowid = (int64_t)sqlite3_last_insert_rowid(_db);
+
+			sqlite3_reset(_sIncrementMemberRevisionCounter);
+			sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,network.id,16,SQLITE_STATIC);
+			sqlite3_step(_sIncrementMemberRevisionCounter);
+		}
+
+		// Update NodeHistory with new log entry and delete expired entries
+
+		{
+			int64_t nextVC = 1;
+			sqlite3_reset(_sGetMaxNodeHistoryNetworkVisitCounter);
+			sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,1,network.id,16,SQLITE_STATIC);
+			sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,2,member.nodeId,10,SQLITE_STATIC);
+			if (sqlite3_step(_sGetMaxNodeHistoryNetworkVisitCounter) == SQLITE_ROW) {
+				nextVC = (int64_t)sqlite3_column_int64(_sGetMaxNodeHistoryNetworkVisitCounter,0) + 1;
+			}
+
+			std::string fastr;
+			if (fromAddr)
+				fastr = fromAddr.toString();
+
+			sqlite3_reset(_sAddNodeHistoryEntry);
+			sqlite3_bind_text(_sAddNodeHistoryEntry,1,member.nodeId,10,SQLITE_STATIC);
+			sqlite3_bind_text(_sAddNodeHistoryEntry,2,network.id,16,SQLITE_STATIC);
+			sqlite3_bind_int64(_sAddNodeHistoryEntry,3,nextVC);
+			sqlite3_bind_int(_sAddNodeHistoryEntry,4,(member.authorized ? 1 : 0));
+			sqlite3_bind_int64(_sAddNodeHistoryEntry,5,(long long)now);
+			sqlite3_bind_int(_sAddNodeHistoryEntry,6,(int)metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0));
+			sqlite3_bind_int(_sAddNodeHistoryEntry,7,(int)metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0));
+			sqlite3_bind_int(_sAddNodeHistoryEntry,8,(int)metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0));
+			sqlite3_bind_text(_sAddNodeHistoryEntry,9,metaData.data(),-1,SQLITE_STATIC);
+			if (fastr.length() > 0)
+				sqlite3_bind_text(_sAddNodeHistoryEntry,10,fastr.c_str(),-1,SQLITE_STATIC);
+			else sqlite3_bind_null(_sAddNodeHistoryEntry,10);
+			sqlite3_step(_sAddNodeHistoryEntry);
+
+			nextVC -= ZT_NETCONF_NODE_HISTORY_LENGTH;
+			if (nextVC >= 0) {
+				sqlite3_reset(_sDeleteOldNodeHistoryEntries);
+				sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,1,network.id,16,SQLITE_STATIC);
+				sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,2,member.nodeId,10,SQLITE_STATIC);
+				sqlite3_bind_int64(_sDeleteOldNodeHistoryEntries,3,nextVC);
+				sqlite3_step(_sDeleteOldNodeHistoryEntries);
+			}
+		}
+
+		// Check member authorization
+
+		if (!member.authorized)
+			return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
+
+		// Create network configuration -- we create both legacy and new types and send both for backward compatibility
+
+		// New network config structure
+		nc.networkId = Utils::hexStrToU64(network.id);
+		nc.type = network.isPrivate ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
+		nc.timestamp = now;
+		nc.revision = network.revision;
+		nc.issuedTo = member.nodeId;
+		if (network.enableBroadcast) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
+		if (network.allowPassiveBridging) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING;
+		memcpy(nc.name,network.name,std::min((unsigned int)ZT_MAX_NETWORK_SHORT_NAME_LENGTH,(unsigned int)strlen(network.name)));
+
+		{	// TODO: right now only etherTypes are supported in rules
+			std::vector<int> allowedEtherTypes;
+			sqlite3_reset(_sGetEtherTypesFromRuleTable);
+			sqlite3_bind_text(_sGetEtherTypesFromRuleTable,1,network.id,16,SQLITE_STATIC);
+			while (sqlite3_step(_sGetEtherTypesFromRuleTable) == SQLITE_ROW) {
+				if (sqlite3_column_type(_sGetEtherTypesFromRuleTable,0) == SQLITE_NULL) {
+					allowedEtherTypes.clear();
+					allowedEtherTypes.push_back(0); // NULL 'allow' matches ANY
+					break;
+				} else {
+					int et = sqlite3_column_int(_sGetEtherTypesFromRuleTable,0);
+					if ((et >= 0)&&(et <= 0xffff))
+						allowedEtherTypes.push_back(et);
+				}
+			}
+			std::sort(allowedEtherTypes.begin(),allowedEtherTypes.end());
+			allowedEtherTypes.erase(std::unique(allowedEtherTypes.begin(),allowedEtherTypes.end()),allowedEtherTypes.end());
+
+			for(long i=0;i<(long)allowedEtherTypes.size();++i) {
+				if ((nc.ruleCount + 2) > ZT_MAX_NETWORK_RULES)
+					break;
+				if (allowedEtherTypes[i] > 0) {
+					nc.rules[nc.ruleCount].t = ZT_NETWORK_RULE_MATCH_ETHERTYPE;
+					nc.rules[nc.ruleCount].v.etherType = (uint16_t)allowedEtherTypes[i];
+					++nc.ruleCount;
+				}
+				nc.rules[nc.ruleCount++].t = ZT_NETWORK_RULE_ACTION_ACCEPT;
+			}
+		}
+
+		nc.multicastLimit = network.multicastLimit;
+
+		bool amActiveBridge = false;
+		{
+			sqlite3_reset(_sGetActiveBridges);
+			sqlite3_bind_text(_sGetActiveBridges,1,network.id,16,SQLITE_STATIC);
+			while (sqlite3_step(_sGetActiveBridges) == SQLITE_ROW) {
+				const char *ab = (const char *)sqlite3_column_text(_sGetActiveBridges,0);
+				if ((ab)&&(strlen(ab) == 10)) {
+					const uint64_t ab2 = Utils::hexStrToU64(ab);
+					nc.addSpecialist(Address(ab2),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE);
+					if (!strcmp(member.nodeId,ab))
+						amActiveBridge = true;
+				}
+			}
+		}
+
+		// Do not send relays to 1.1.0 since it had a serious bug in using them
+		// 1.1.0 will still work, it'll just fall back to roots instead of using network preferred relays
+		if (!((metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0) == 0))) {
+			sqlite3_reset(_sGetRelays);
+			sqlite3_bind_text(_sGetRelays,1,network.id,16,SQLITE_STATIC);
+			while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
+				const char *n = (const char *)sqlite3_column_text(_sGetRelays,0);
+				const char *a = (const char *)sqlite3_column_text(_sGetRelays,1);
+				if ((n)&&(a)) {
+					Address node(n);
+					InetAddress addr(a);
+					if (node)
+						nc.addSpecialist(node,ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY);
+				}
+			}
+		}
+
+		sqlite3_reset(_sGetRoutes);
+		sqlite3_bind_text(_sGetRoutes,1,network.id,16,SQLITE_STATIC);
+		while ((sqlite3_step(_sGetRoutes) == SQLITE_ROW)&&(nc.routeCount < ZT_MAX_NETWORK_ROUTES)) {
+			ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]);
+			memset(r,0,sizeof(ZT_VirtualNetworkRoute));
+			switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
+				case 4:
+					*(reinterpret_cast<InetAddress *>(&(r->target))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,0) + 12),4,(unsigned int)sqlite3_column_int(_sGetRoutes,2));
+					break;
+				case 6:
+					*(reinterpret_cast<InetAddress *>(&(r->target))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,0),16,(unsigned int)sqlite3_column_int(_sGetRoutes,2));
+					break;
+				default:
+					continue;
+			}
+			if (sqlite3_column_type(_sGetRoutes,1) != SQLITE_NULL) {
+				switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
+					case 4:
+						*(reinterpret_cast<InetAddress *>(&(r->via))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,1) + 12),4,0);
+						break;
+					case 6:
+						*(reinterpret_cast<InetAddress *>(&(r->via))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,1),16,0);
+						break;
+					default:
+						continue;
+				}
+			}
+			r->flags = (uint16_t)sqlite3_column_int(_sGetRoutes,4);
+			r->metric = (uint16_t)sqlite3_column_int(_sGetRoutes,5);
+			++nc.routeCount;
+		}
+
+		if (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193) != 0)&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
+			nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt());
+			nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
+		}
+		if (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE) != 0)&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
+			nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt());
+			nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
+		}
+
+		if ((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4) != 0) {
+			bool haveStaticIpAssignment = false;
+
+			sqlite3_reset(_sGetIpAssignmentsForNode);
+			sqlite3_bind_text(_sGetIpAssignmentsForNode,1,network.id,16,SQLITE_STATIC);
+			sqlite3_bind_text(_sGetIpAssignmentsForNode,2,member.nodeId,10,SQLITE_STATIC);
+			sqlite3_bind_int(_sGetIpAssignmentsForNode,3,4); // 4 == IPv4
+			while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) {
+				const unsigned char *const ip = (const unsigned char *)sqlite3_column_blob(_sGetIpAssignmentsForNode,1);
+				if ((!ip)||(sqlite3_column_bytes(_sGetIpAssignmentsForNode,1) != 16))
+					continue;
+				int ipNetmaskBits = sqlite3_column_int(_sGetIpAssignmentsForNode,2);
+				if ((ipNetmaskBits <= 0)||(ipNetmaskBits > 32))
+					continue;
+				if (sqlite3_column_int(_sGetIpAssignmentsForNode,0) == 0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/) {
+					if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
+						struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++]));
+						v4ip->sin_family = AF_INET;
+						v4ip->sin_port = Utils::hton((uint16_t)ipNetmaskBits);
+						memcpy(&(v4ip->sin_addr.s_addr),ip + 12,4);
+					}
+					haveStaticIpAssignment = true;
+				}
+			}
+
+			if ((!haveStaticIpAssignment)&&(!amActiveBridge)) {
+				// Attempt to auto-assign an IPv4 address from an available routed pool if there is one
+				sqlite3_reset(_sGetIpAssignmentPools);
+				sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC);
+				sqlite3_bind_int(_sGetIpAssignmentPools,2,4); // 4 == IPv4
+
+				while (sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW) {
+					const unsigned char *ipRangeStartB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,0));
+					const unsigned char *ipRangeEndB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,1));
+					if ((!ipRangeStartB)||(!ipRangeEndB)||(sqlite3_column_bytes(_sGetIpAssignmentPools,0) != 16)||(sqlite3_column_bytes(_sGetIpAssignmentPools,1) != 16))
+						continue;
+
+					uint32_t ipRangeStart = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeStartB + 12)));
+					uint32_t ipRangeEnd = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeEndB + 12)));
+					if ((ipRangeEnd <= ipRangeStart)||(ipRangeStart == 0))
+						continue;
+					uint32_t ipRangeLen = ipRangeEnd - ipRangeStart;
+
+					// Start with the LSB of the member's address
+					uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff);
+
+					for(uint32_t k=ipRangeStart,l=0;(k<=ipRangeEnd)&&(l < 1000000);++k,++l) {
+						uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart;
+						++ipTrialCounter;
+						if ((ip & 0x000000ff) == 0x000000ff)
+							continue; // don't allow addresses that end in .255
+
+						// Check if this IPv4 IP is within a local-to-Ethernet routed network
+						int routedNetmaskBits = 0;
+						for(unsigned int rk=0;rk<nc.routeCount;++rk) {
+							if ((!nc.routes[rk].via.ss_family)&&(nc.routes[rk].target.ss_family == AF_INET)) {
+								uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_addr.s_addr));
+								int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_port));
+								if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) {
+									routedNetmaskBits = targetBits;
+									break;
+								}
+							}
+						}
+
+						// If it's routed, then try to claim and assign it and if successful end loop
+						if (routedNetmaskBits > 0) {
+							uint32_t ipBlob[4]; // actually a 16-byte blob, we put IPv4s in the last 4 bytes
+							ipBlob[0] = 0; ipBlob[1] = 0; ipBlob[2] = 0; ipBlob[3] = Utils::hton(ip);
+							sqlite3_reset(_sCheckIfIpIsAllocated);
+							sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
+							sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ipBlob,16,SQLITE_STATIC);
+							sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4
+							sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
+							if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) {
+								// No rows returned, so the IP is available
+								sqlite3_reset(_sAllocateIp);
+								sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC);
+								sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC);
+								sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
+								sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
+								sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route
+								sqlite3_bind_int(_sAllocateIp,6,4); // 4 == IPv4
+								if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) {
+									if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
+										struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++]));
+										v4ip->sin_family = AF_INET;
+										v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits);
+										v4ip->sin_addr.s_addr = Utils::hton(ip);
+									}
+									haveStaticIpAssignment = true;
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	} // end lock
+
+	// Perform signing outside lock to enable concurrency
+	if (network.isPrivate) {
+		CertificateOfMembership com(now,ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA,nwid,identity.address());
+		if (com.sign(signingId)) {
+			nc.com = com;
+		} else {
+			return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
+		}
+	}
+
+	return NetworkController::NETCONF_QUERY_OK;
 }
 
 unsigned int SqliteNetworkController::handleControlPlaneHttpGET(
@@ -1619,385 +1991,6 @@ unsigned int SqliteNetworkController::_doCPGet(
 	return 404;
 }
 
-NetworkController::ResultCode SqliteNetworkController::_doNetworkConfigRequest(const InetAddress &fromAddr,const Identity &signingId,const Identity &identity,uint64_t nwid,const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &metaData,NetworkConfig &nc)
-{
-	// Assumes _lock is locked
-
-	if (((!signingId)||(!signingId.hasPrivate()))||(signingId.address().toInt() != (nwid >> 24))) {
-		return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
-	}
-
-	// Note: we can't reuse prepared statements that return const char * pointers without
-	// making our own copy in e.g. a std::string first.
-
-	//const bool clientIs104 = (Utils::compareVersion(metaData.majorVersion,metaData.minorVersion,metaData.revision,1,0,4) >= 0);
-	const uint64_t now = OSUtils::now();
-
-	// Check rate limit circuit breaker to prevent flooding
-	{
-		uint64_t &lrt = _lastRequestTime[std::pair<uint64_t,uint64_t>(identity.address().toInt(),nwid)];
-		if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD)
-			return NetworkController::NETCONF_QUERY_IGNORE;
-		lrt = now;
-	}
-
-	_backupNeeded = true;
-
-	NetworkRecord network;
-	memset(&network,0,sizeof(network));
-	Utils::snprintf(network.id,sizeof(network.id),"%.16llx",(unsigned long long)nwid);
-
-	MemberRecord member;
-	memset(&member,0,sizeof(member));
-	Utils::snprintf(member.nodeId,sizeof(member.nodeId),"%.10llx",(unsigned long long)identity.address().toInt());
-
-	// Create Node record or do full identity check if we already have one
-
-	sqlite3_reset(_sGetNodeIdentity);
-	sqlite3_bind_text(_sGetNodeIdentity,1,member.nodeId,10,SQLITE_STATIC);
-	if (sqlite3_step(_sGetNodeIdentity) == SQLITE_ROW) {
-		try {
-			Identity alreadyKnownIdentity((const char *)sqlite3_column_text(_sGetNodeIdentity,0));
-			if (alreadyKnownIdentity != identity)
-				return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
-		} catch ( ... ) { // identity stored in database is not valid or is NULL
-			return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
-		}
-	} else {
-		std::string idstr(identity.toString(false));
-		sqlite3_reset(_sCreateOrReplaceNode);
-		sqlite3_bind_text(_sCreateOrReplaceNode,1,member.nodeId,10,SQLITE_STATIC);
-		sqlite3_bind_text(_sCreateOrReplaceNode,2,idstr.c_str(),-1,SQLITE_STATIC);
-		if (sqlite3_step(_sCreateOrReplaceNode) != SQLITE_DONE) {
-			return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
-		}
-	}
-
-	// Fetch Network record
-
-	sqlite3_reset(_sGetNetworkById);
-	sqlite3_bind_text(_sGetNetworkById,1,network.id,16,SQLITE_STATIC);
-	if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) {
-		network.name = (const char *)sqlite3_column_text(_sGetNetworkById,0);
-		network.isPrivate = (sqlite3_column_int(_sGetNetworkById,1) > 0);
-		network.enableBroadcast = (sqlite3_column_int(_sGetNetworkById,2) > 0);
-		network.allowPassiveBridging = (sqlite3_column_int(_sGetNetworkById,3) > 0);
-		network.flags = sqlite3_column_int(_sGetNetworkById,4);
-		network.multicastLimit = sqlite3_column_int(_sGetNetworkById,5);
-		network.creationTime = (uint64_t)sqlite3_column_int64(_sGetNetworkById,6);
-		network.revision = (uint64_t)sqlite3_column_int64(_sGetNetworkById,7);
-		network.memberRevisionCounter = (uint64_t)sqlite3_column_int64(_sGetNetworkById,8);
-	} else {
-		return NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND;
-	}
-
-	// Fetch Member record
-
-	bool foundMember = false;
-	sqlite3_reset(_sGetMember);
-	sqlite3_bind_text(_sGetMember,1,network.id,16,SQLITE_STATIC);
-	sqlite3_bind_text(_sGetMember,2,member.nodeId,10,SQLITE_STATIC);
-	if (sqlite3_step(_sGetMember) == SQLITE_ROW) {
-		foundMember = true;
-		member.rowid = (int64_t)sqlite3_column_int64(_sGetMember,0);
-		member.authorized = (sqlite3_column_int(_sGetMember,1) > 0);
-		member.activeBridge = (sqlite3_column_int(_sGetMember,2) > 0);
-	}
-
-	// Create Member record for unknown nodes, auto-authorizing if network is public
-
-	if (!foundMember) {
-		member.authorized = (network.isPrivate ? false : true);
-		member.activeBridge = false;
-		sqlite3_reset(_sCreateMember);
-		sqlite3_bind_text(_sCreateMember,1,network.id,16,SQLITE_STATIC);
-		sqlite3_bind_text(_sCreateMember,2,member.nodeId,10,SQLITE_STATIC);
-		sqlite3_bind_int(_sCreateMember,3,(member.authorized ? 1 : 0));
-		sqlite3_bind_text(_sCreateMember,4,network.id,16,SQLITE_STATIC);
-		if (sqlite3_step(_sCreateMember) != SQLITE_DONE) {
-			return NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR;
-		}
-		member.rowid = (int64_t)sqlite3_last_insert_rowid(_db);
-
-		sqlite3_reset(_sIncrementMemberRevisionCounter);
-		sqlite3_bind_text(_sIncrementMemberRevisionCounter,1,network.id,16,SQLITE_STATIC);
-		sqlite3_step(_sIncrementMemberRevisionCounter);
-	}
-
-	// Update NodeHistory with new log entry and delete expired entries
-
-	{
-		int64_t nextVC = 1;
-		sqlite3_reset(_sGetMaxNodeHistoryNetworkVisitCounter);
-		sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,1,network.id,16,SQLITE_STATIC);
-		sqlite3_bind_text(_sGetMaxNodeHistoryNetworkVisitCounter,2,member.nodeId,10,SQLITE_STATIC);
-		if (sqlite3_step(_sGetMaxNodeHistoryNetworkVisitCounter) == SQLITE_ROW) {
-			nextVC = (int64_t)sqlite3_column_int64(_sGetMaxNodeHistoryNetworkVisitCounter,0) + 1;
-		}
-
-		std::string fastr;
-		if (fromAddr)
-			fastr = fromAddr.toString();
-
-		sqlite3_reset(_sAddNodeHistoryEntry);
-		sqlite3_bind_text(_sAddNodeHistoryEntry,1,member.nodeId,10,SQLITE_STATIC);
-		sqlite3_bind_text(_sAddNodeHistoryEntry,2,network.id,16,SQLITE_STATIC);
-		sqlite3_bind_int64(_sAddNodeHistoryEntry,3,nextVC);
-		sqlite3_bind_int(_sAddNodeHistoryEntry,4,(member.authorized ? 1 : 0));
-		sqlite3_bind_int64(_sAddNodeHistoryEntry,5,(long long)now);
-		sqlite3_bind_int(_sAddNodeHistoryEntry,6,(int)metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0));
-		sqlite3_bind_int(_sAddNodeHistoryEntry,7,(int)metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0));
-		sqlite3_bind_int(_sAddNodeHistoryEntry,8,(int)metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0));
-		sqlite3_bind_text(_sAddNodeHistoryEntry,9,metaData.data(),-1,SQLITE_STATIC);
-		if (fastr.length() > 0)
-			sqlite3_bind_text(_sAddNodeHistoryEntry,10,fastr.c_str(),-1,SQLITE_STATIC);
-		else sqlite3_bind_null(_sAddNodeHistoryEntry,10);
-		sqlite3_step(_sAddNodeHistoryEntry);
-
-		nextVC -= ZT_NETCONF_NODE_HISTORY_LENGTH;
-		if (nextVC >= 0) {
-			sqlite3_reset(_sDeleteOldNodeHistoryEntries);
-			sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,1,network.id,16,SQLITE_STATIC);
-			sqlite3_bind_text(_sDeleteOldNodeHistoryEntries,2,member.nodeId,10,SQLITE_STATIC);
-			sqlite3_bind_int64(_sDeleteOldNodeHistoryEntries,3,nextVC);
-			sqlite3_step(_sDeleteOldNodeHistoryEntries);
-		}
-	}
-
-	// Check member authorization
-
-	if (!member.authorized)
-		return NetworkController::NETCONF_QUERY_ACCESS_DENIED;
-
-	// Create network configuration -- we create both legacy and new types and send both for backward compatibility
-
-	// New network config structure
-	nc.networkId = Utils::hexStrToU64(network.id);
-	nc.type = network.isPrivate ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC;
-	nc.timestamp = now;
-	nc.revision = network.revision;
-	nc.issuedTo = member.nodeId;
-	if (network.enableBroadcast) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST;
-	if (network.allowPassiveBridging) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING;
-	memcpy(nc.name,network.name,std::min((unsigned int)ZT_MAX_NETWORK_SHORT_NAME_LENGTH,(unsigned int)strlen(network.name)));
-
-	{	// TODO: right now only etherTypes are supported in rules
-		std::vector<int> allowedEtherTypes;
-		sqlite3_reset(_sGetEtherTypesFromRuleTable);
-		sqlite3_bind_text(_sGetEtherTypesFromRuleTable,1,network.id,16,SQLITE_STATIC);
-		while (sqlite3_step(_sGetEtherTypesFromRuleTable) == SQLITE_ROW) {
-			if (sqlite3_column_type(_sGetEtherTypesFromRuleTable,0) == SQLITE_NULL) {
-				allowedEtherTypes.clear();
-				allowedEtherTypes.push_back(0); // NULL 'allow' matches ANY
-				break;
-			} else {
-				int et = sqlite3_column_int(_sGetEtherTypesFromRuleTable,0);
-				if ((et >= 0)&&(et <= 0xffff))
-					allowedEtherTypes.push_back(et);
-			}
-		}
-		std::sort(allowedEtherTypes.begin(),allowedEtherTypes.end());
-		allowedEtherTypes.erase(std::unique(allowedEtherTypes.begin(),allowedEtherTypes.end()),allowedEtherTypes.end());
-
-		for(long i=0;i<(long)allowedEtherTypes.size();++i) {
-			if ((nc.ruleCount + 2) > ZT_MAX_NETWORK_RULES)
-				break;
-			if (allowedEtherTypes[i] > 0) {
-				nc.rules[nc.ruleCount].t = ZT_NETWORK_RULE_MATCH_ETHERTYPE;
-				nc.rules[nc.ruleCount].v.etherType = (uint16_t)allowedEtherTypes[i];
-				++nc.ruleCount;
-			}
-			nc.rules[nc.ruleCount++].t = ZT_NETWORK_RULE_ACTION_ACCEPT;
-		}
-	}
-
-	nc.multicastLimit = network.multicastLimit;
-
-	bool amActiveBridge = false;
-	{
-		sqlite3_reset(_sGetActiveBridges);
-		sqlite3_bind_text(_sGetActiveBridges,1,network.id,16,SQLITE_STATIC);
-		while (sqlite3_step(_sGetActiveBridges) == SQLITE_ROW) {
-			const char *ab = (const char *)sqlite3_column_text(_sGetActiveBridges,0);
-			if ((ab)&&(strlen(ab) == 10)) {
-				const uint64_t ab2 = Utils::hexStrToU64(ab);
-				nc.addSpecialist(Address(ab2),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE);
-				if (!strcmp(member.nodeId,ab))
-					amActiveBridge = true;
-			}
-		}
-	}
-
-	// Do not send relays to 1.1.0 since it had a serious bug in using them
-	// 1.1.0 will still work, it'll just fall back to roots instead of using network preferred relays
-	if (!((metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0) == 1)&&(metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0) == 0))) {
-		sqlite3_reset(_sGetRelays);
-		sqlite3_bind_text(_sGetRelays,1,network.id,16,SQLITE_STATIC);
-		while (sqlite3_step(_sGetRelays) == SQLITE_ROW) {
-			const char *n = (const char *)sqlite3_column_text(_sGetRelays,0);
-			const char *a = (const char *)sqlite3_column_text(_sGetRelays,1);
-			if ((n)&&(a)) {
-				Address node(n);
-				InetAddress addr(a);
-				if (node)
-					nc.addSpecialist(node,ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY);
-			}
-		}
-	}
-
-	sqlite3_reset(_sGetRoutes);
-	sqlite3_bind_text(_sGetRoutes,1,network.id,16,SQLITE_STATIC);
-	while ((sqlite3_step(_sGetRoutes) == SQLITE_ROW)&&(nc.routeCount < ZT_MAX_NETWORK_ROUTES)) {
-		ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]);
-		memset(r,0,sizeof(ZT_VirtualNetworkRoute));
-		switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
-			case 4:
-				*(reinterpret_cast<InetAddress *>(&(r->target))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,0) + 12),4,(unsigned int)sqlite3_column_int(_sGetRoutes,2));
-				break;
-			case 6:
-				*(reinterpret_cast<InetAddress *>(&(r->target))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,0),16,(unsigned int)sqlite3_column_int(_sGetRoutes,2));
-				break;
-			default:
-				continue;
-		}
-		if (sqlite3_column_type(_sGetRoutes,1) != SQLITE_NULL) {
-			switch(sqlite3_column_int(_sGetRoutes,3)) { // ipVersion
-				case 4:
-					*(reinterpret_cast<InetAddress *>(&(r->via))) = InetAddress((const void *)((const char *)sqlite3_column_blob(_sGetRoutes,1) + 12),4,0);
-					break;
-				case 6:
-					*(reinterpret_cast<InetAddress *>(&(r->via))) = InetAddress((const void *)sqlite3_column_blob(_sGetRoutes,1),16,0);
-					break;
-				default:
-					continue;
-			}
-		}
-		r->flags = (uint16_t)sqlite3_column_int(_sGetRoutes,4);
-		r->metric = (uint16_t)sqlite3_column_int(_sGetRoutes,5);
-		++nc.routeCount;
-	}
-
-	if (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_RFC4193) != 0)&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
-		nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt());
-		nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
-	}
-	if (((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V6_6PLANE) != 0)&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
-		nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt());
-		nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION;
-	}
-
-	if ((network.flags & ZT_DB_NETWORK_FLAG_ZT_MANAGED_V4) != 0) {
-		bool haveStaticIpAssignment = false;
-
-		sqlite3_reset(_sGetIpAssignmentsForNode);
-		sqlite3_bind_text(_sGetIpAssignmentsForNode,1,network.id,16,SQLITE_STATIC);
-		sqlite3_bind_text(_sGetIpAssignmentsForNode,2,member.nodeId,10,SQLITE_STATIC);
-		sqlite3_bind_int(_sGetIpAssignmentsForNode,3,4); // 4 == IPv4
-		while (sqlite3_step(_sGetIpAssignmentsForNode) == SQLITE_ROW) {
-			const unsigned char *const ip = (const unsigned char *)sqlite3_column_blob(_sGetIpAssignmentsForNode,1);
-			if ((!ip)||(sqlite3_column_bytes(_sGetIpAssignmentsForNode,1) != 16))
-				continue;
-			int ipNetmaskBits = sqlite3_column_int(_sGetIpAssignmentsForNode,2);
-			if ((ipNetmaskBits <= 0)||(ipNetmaskBits > 32))
-				continue;
-			if (sqlite3_column_int(_sGetIpAssignmentsForNode,0) == 0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/) {
-				if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
-					struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++]));
-					v4ip->sin_family = AF_INET;
-					v4ip->sin_port = Utils::hton((uint16_t)ipNetmaskBits);
-					memcpy(&(v4ip->sin_addr.s_addr),ip + 12,4);
-				}
-				haveStaticIpAssignment = true;
-			}
-		}
-
-		if ((!haveStaticIpAssignment)&&(!amActiveBridge)) {
-			// Attempt to auto-assign an IPv4 address from an available routed pool if there is one
-			sqlite3_reset(_sGetIpAssignmentPools);
-			sqlite3_bind_text(_sGetIpAssignmentPools,1,network.id,16,SQLITE_STATIC);
-			sqlite3_bind_int(_sGetIpAssignmentPools,2,4); // 4 == IPv4
-
-			while (sqlite3_step(_sGetIpAssignmentPools) == SQLITE_ROW) {
-				const unsigned char *ipRangeStartB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,0));
-				const unsigned char *ipRangeEndB = reinterpret_cast<const unsigned char *>(sqlite3_column_blob(_sGetIpAssignmentPools,1));
-				if ((!ipRangeStartB)||(!ipRangeEndB)||(sqlite3_column_bytes(_sGetIpAssignmentPools,0) != 16)||(sqlite3_column_bytes(_sGetIpAssignmentPools,1) != 16))
-					continue;
-
-				uint32_t ipRangeStart = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeStartB + 12)));
-				uint32_t ipRangeEnd = Utils::ntoh(*(reinterpret_cast<const uint32_t *>(ipRangeEndB + 12)));
-				if ((ipRangeEnd <= ipRangeStart)||(ipRangeStart == 0))
-					continue;
-				uint32_t ipRangeLen = ipRangeEnd - ipRangeStart;
-
-				// Start with the LSB of the member's address
-				uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff);
-
-				for(uint32_t k=ipRangeStart,l=0;(k<=ipRangeEnd)&&(l < 1000000);++k,++l) {
-					uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart;
-					++ipTrialCounter;
-					if ((ip & 0x000000ff) == 0x000000ff)
-						continue; // don't allow addresses that end in .255
-
-					// Check if this IPv4 IP is within a local-to-Ethernet routed network
-					int routedNetmaskBits = 0;
-					for(unsigned int rk=0;rk<nc.routeCount;++rk) {
-						if ((!nc.routes[rk].via.ss_family)&&(nc.routes[rk].target.ss_family == AF_INET)) {
-							uint32_t targetIp = Utils::ntoh((uint32_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_addr.s_addr));
-							int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(&(nc.routes[rk].target))->sin_port));
-							if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) {
-								routedNetmaskBits = targetBits;
-								break;
-							}
-						}
-					}
-
-					// If it's routed, then try to claim and assign it and if successful end loop
-					if (routedNetmaskBits > 0) {
-						uint32_t ipBlob[4]; // actually a 16-byte blob, we put IPv4s in the last 4 bytes
-						ipBlob[0] = 0; ipBlob[1] = 0; ipBlob[2] = 0; ipBlob[3] = Utils::hton(ip);
-						sqlite3_reset(_sCheckIfIpIsAllocated);
-						sqlite3_bind_text(_sCheckIfIpIsAllocated,1,network.id,16,SQLITE_STATIC);
-						sqlite3_bind_blob(_sCheckIfIpIsAllocated,2,(const void *)ipBlob,16,SQLITE_STATIC);
-						sqlite3_bind_int(_sCheckIfIpIsAllocated,3,4); // 4 == IPv4
-						sqlite3_bind_int(_sCheckIfIpIsAllocated,4,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
-						if (sqlite3_step(_sCheckIfIpIsAllocated) != SQLITE_ROW) {
-							// No rows returned, so the IP is available
-							sqlite3_reset(_sAllocateIp);
-							sqlite3_bind_text(_sAllocateIp,1,network.id,16,SQLITE_STATIC);
-							sqlite3_bind_text(_sAllocateIp,2,member.nodeId,10,SQLITE_STATIC);
-							sqlite3_bind_int(_sAllocateIp,3,(int)0 /*ZT_IP_ASSIGNMENT_TYPE_ADDRESS*/);
-							sqlite3_bind_blob(_sAllocateIp,4,(const void *)ipBlob,16,SQLITE_STATIC);
-							sqlite3_bind_int(_sAllocateIp,5,routedNetmaskBits); // IP netmask bits from matching route
-							sqlite3_bind_int(_sAllocateIp,6,4); // 4 == IPv4
-							if (sqlite3_step(_sAllocateIp) == SQLITE_DONE) {
-								if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) {
-									struct sockaddr_in *const v4ip = reinterpret_cast<struct sockaddr_in *>(&(nc.staticIps[nc.staticIpCount++]));
-									v4ip->sin_family = AF_INET;
-									v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits);
-									v4ip->sin_addr.s_addr = Utils::hton(ip);
-								}
-								haveStaticIpAssignment = true;
-								break;
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-
-	if (network.isPrivate) {
-		CertificateOfMembership com(now,ZT_NETWORK_COM_DEFAULT_REVISION_MAX_DELTA,nwid,identity.address());
-		if (com.sign(signingId)) {
-			nc.com = com;
-		} else {
-			return NETCONF_QUERY_INTERNAL_SERVER_ERROR;
-		}
-	}
-
-	return NetworkController::NETCONF_QUERY_OK;
-}
-
 void SqliteNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report)
 {
 	char tmp[65535];