Browse Source

Cleanup, new C++ netconf code is almost ready to test!

Adam Ierymenko 10 years ago
parent
commit
60fb28a90a

+ 1 - 1
make-mac.mk

@@ -1,7 +1,7 @@
 CC=clang
 CC=clang
 CXX=clang++
 CXX=clang++
 
 
-INCLUDES=
+INCLUDES=-I/usr/local/include
 DEFS=
 DEFS=
 LIBS=
 LIBS=
 
 

+ 14 - 14
node/CertificateOfMembership.hpp

@@ -92,12 +92,12 @@ public:
 	enum ReservedId
 	enum ReservedId
 	{
 	{
 		/**
 		/**
-		 * Timestamp of certificate issue in milliseconds since epoch
+		 * Revision number of certificate
 		 *
 		 *
-		 * maxDelta here defines certificate lifetime, and certs are lazily
-		 * pushed to other peers on a net with a frequency of 1/2 this time.
+		 * Certificates may differ in revision number by a designated max
+		 * delta. Differences wider than this cause certificates not to agree.
 		 */
 		 */
-		COM_RESERVED_ID_TIMESTAMP = 0,
+		COM_RESERVED_ID_REVISION = 0,
 
 
 		/**
 		/**
 		 * Network ID for which certificate was issued
 		 * Network ID for which certificate was issued
@@ -123,14 +123,14 @@ public:
 	/**
 	/**
 	 * Create from required fields common to all networks
 	 * Create from required fields common to all networks
 	 *
 	 *
-	 * @param timestamp Timestamp of cert
+	 * @param revision Revision number of certificate
 	 * @param timestampMaxDelta Maximum variation between timestamps on this net
 	 * @param timestampMaxDelta Maximum variation between timestamps on this net
 	 * @param nwid Network ID
 	 * @param nwid Network ID
 	 * @param issuedTo Certificate recipient
 	 * @param issuedTo Certificate recipient
 	 */
 	 */
-	CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Address &issuedTo)
+	CertificateOfMembership(uint64_t revision,uint64_t revisionMaxDelta,uint64_t nwid,const Address &issuedTo)
 	{
 	{
-		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_TIMESTAMP,timestamp,timestampMaxDelta));
+		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_REVISION,revision,revisionMaxDelta));
 		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_NETWORK_ID,nwid,0));
 		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_NETWORK_ID,nwid,0));
 		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_ISSUED_TO,issuedTo.toInt(),0xffffffffffffffffULL));
 		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_ISSUED_TO,issuedTo.toInt(),0xffffffffffffffffULL));
 		memset(_signature.data,0,_signature.size());
 		memset(_signature.data,0,_signature.size());
@@ -182,7 +182,7 @@ public:
 	{
 	{
 		if (_qualifiers.size() < 3)
 		if (_qualifiers.size() < 3)
 			return false;
 			return false;
-		if (_qualifiers[0].id != COM_RESERVED_ID_TIMESTAMP)
+		if (_qualifiers[0].id != COM_RESERVED_ID_REVISION)
 			return false;
 			return false;
 		if (_qualifiers[1].id != COM_RESERVED_ID_NETWORK_ID)
 		if (_qualifiers[1].id != COM_RESERVED_ID_NETWORK_ID)
 			return false;
 			return false;
@@ -192,26 +192,26 @@ public:
 	}
 	}
 
 
 	/**
 	/**
-	 * @return Maximum delta for mandatory timestamp field or 0 if field missing
+	 * @return Maximum delta for mandatory revision field or 0 if field missing
 	 */
 	 */
-	inline uint64_t timestampMaxDelta() const
+	inline uint64_t revisionMaxDelta() const
 		throw()
 		throw()
 	{
 	{
 		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
 		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
-			if (q->id == COM_RESERVED_ID_TIMESTAMP)
+			if (q->id == COM_RESERVED_ID_REVISION)
 				return q->maxDelta;
 				return q->maxDelta;
 		}
 		}
 		return 0ULL;
 		return 0ULL;
 	}
 	}
 
 
 	/**
 	/**
-	 * @return Timestamp for this cert in ms since epoch (according to netconf's clock)
+	 * @return Revision number for this cert
 	 */
 	 */
-	inline uint64_t timestamp() const
+	inline uint64_t revision() const
 		throw()
 		throw()
 	{
 	{
 		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
 		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
-			if (q->id == COM_RESERVED_ID_TIMESTAMP)
+			if (q->id == COM_RESERVED_ID_REVISION)
 				return q->value;
 				return q->value;
 		}
 		}
 		return 0ULL;
 		return 0ULL;

+ 0 - 8
node/Constants.hpp

@@ -369,14 +369,6 @@
  */
  */
 #define ZT_ANTIRECURSION_HISTORY_SIZE 16
 #define ZT_ANTIRECURSION_HISTORY_SIZE 16
 
 
-/**
- * TTL for certificates of membership on private networks
- *
- * This is the max delta for the timestamp field of a COM, so it's a window
- * plus or minus the certificate's timestamp. In milliseconds.
- */
-#define ZT_NETWORK_CERTIFICATE_TTL_WINDOW (ZT_NETWORK_AUTOCONF_DELAY * 4)
-
 /**
 /**
  * How often to broadcast beacons over physical local LANs
  * How often to broadcast beacons over physical local LANs
  */
  */

+ 123 - 0
node/Dictionary.hpp

@@ -35,6 +35,7 @@
 #include <stdexcept>
 #include <stdexcept>
  
  
 #include "Constants.hpp"
 #include "Constants.hpp"
+#include "Utils.hpp"
 
 
 // Three fields are added/updated by sign()
 // Three fields are added/updated by sign()
 #define ZT_DICTIONARY_SIGNATURE "~!ed25519"
 #define ZT_DICTIONARY_SIGNATURE "~!ed25519"
@@ -107,6 +108,128 @@ public:
 		return e->second;
 		return e->second;
 	}
 	}
 
 
+	/**
+	 * @param key Key to get
+	 * @param dfl Default boolean result if key not found or empty (default: false)
+	 * @return Boolean value of key
+	 */
+	inline bool getBoolean(const std::string &key,bool dfl = false) const
+	{
+		const_iterator e(find(key));
+		if (e == end())
+			return dfl;
+		if (e->second.length() < 1)
+			return dfl;
+		switch(e->second[0]) {
+			case '1':
+			case 't':
+			case 'T':
+			case 'y':
+			case 'Y':
+				return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @param key Key to get
+	 * @param dfl Default value if not present (default: 0)
+	 * @return Value converted to unsigned 64-bit int or 0 if not found
+	 */
+	inline uint64_t getUInt(const std::string &key,uint64_t dfl = 0) const
+	{
+		const_iterator e(find(key));
+		if (e == end())
+			return dfl;
+		return Utils::strToU64(e->second.c_str());
+	}
+
+	/**
+	 * @param key Key to get
+	 * @param dfl Default value if not present (default: 0)
+	 * @return Value converted to unsigned 64-bit int or 0 if not found
+	 */
+	inline uint64_t getHexUInt(const std::string &key,uint64_t dfl = 0) const
+	{
+		const_iterator e(find(key));
+		if (e == end())
+			return dfl;
+		return Utils::hexStrToU64(e->second.c_str());
+	}
+
+	/**
+	 * @param key Key to get
+	 * @param dfl Default value if not present (default: 0)
+	 * @return Value converted to signed 64-bit int or 0 if not found
+	 */
+	inline int64_t getInt(const std::string &key,int64_t dfl = 0) const
+	{
+		const_iterator e(find(key));
+		if (e == end())
+			return dfl;
+		return Utils::strTo64(e->second.c_str());
+	}
+
+	/**
+	 * @param key Key to set
+	 * @param value String value
+	 */
+	inline void set(const std::string &key,const char *value)
+	{
+		(*this)[key] = value;
+	}
+
+	/**
+	 * @param key Key to set
+	 * @param value String value
+	 */
+	inline void set(const std::string &key,const std::string &value)
+	{
+		(*this)[key] = value;
+	}
+
+	/**
+	 * @param key Key to set
+	 * @param value Boolean value
+	 */
+	inline void set(const std::string &key,bool value)
+	{
+		(*this)[key] = ((value) ? "1" : "0");
+	}
+
+	/**
+	 * @param key Key to set
+	 * @param value Integer value
+	 */
+	inline void set(const std::string &key,uint64_t value)
+	{
+		char tmp[24];
+		Utils::snprintf(tmp,sizeof(tmp),"%llu",(unsigned long long)value);
+		(*this)[key] = tmp;
+	}
+
+	/**
+	 * @param key Key to set
+	 * @param value Integer value
+	 */
+	inline void set(const std::string &key,int64_t value)
+	{
+		char tmp[24];
+		Utils::snprintf(tmp,sizeof(tmp),"%lld",(long long)value);
+		(*this)[key] = tmp;
+	}
+
+	/**
+	 * @param key Key to set
+	 * @param value Integer value
+	 */
+	inline void setHex(const std::string &key,uint64_t value)
+	{
+		char tmp[24];
+		Utils::snprintf(tmp,sizeof(tmp),"%llx",(unsigned long long)value);
+		(*this)[key] = tmp;
+	}
+
 	/**
 	/**
 	 * @param key Key to check
 	 * @param key Key to check
 	 * @return True if dictionary contains key
 	 * @return True if dictionary contains key

+ 6 - 13
node/Network.cpp

@@ -352,7 +352,7 @@ void Network::addMembershipCertificate(const CertificateOfMembership &cert,bool
 	}
 	}
 
 
 	// If we made it past authentication, update cert
 	// If we made it past authentication, update cert
-	if (cert.timestamp() >= old.timestamp())
+	if (cert.revision() != old.revision())
 		old = cert;
 		old = cert;
 }
 }
 
 
@@ -360,17 +360,10 @@ bool Network::peerNeedsOurMembershipCertificate(const Address &to,uint64_t now)
 {
 {
 	Mutex::Lock _l(_lock);
 	Mutex::Lock _l(_lock);
 	if ((_config)&&(!_config->isPublic())&&(_config->com())) {
 	if ((_config)&&(!_config->isPublic())&&(_config->com())) {
-		uint64_t pushInterval = _config->com().timestampMaxDelta() / 2;
-		if (pushInterval) {
-			// Give a 1s margin around +/- 1/2 max delta to account for network latency
-			if (pushInterval > 1000)
-				pushInterval -= 1000;
-
-			uint64_t &lastPushed = _lastPushedMembershipCertificate[to];
-			if ((now - lastPushed) > pushInterval) {
-				lastPushed = now;
-				return true;
-			}
+		uint64_t &lastPushed = _lastPushedMembershipCertificate[to];
+		if ((now - lastPushed) > (ZT_NETWORK_AUTOCONF_DELAY / 2)) {
+			lastPushed = now;
+			return true;
 		}
 		}
 	}
 	}
 	return false;
 	return false;
@@ -421,7 +414,7 @@ void Network::clean()
 
 
 		// Clean entries from the last pushed tracking map if they're so old as
 		// Clean entries from the last pushed tracking map if they're so old as
 		// to be no longer relevant.
 		// to be no longer relevant.
-		uint64_t forgetIfBefore = now - (_config->com().timestampMaxDelta() * 3ULL);
+		uint64_t forgetIfBefore = now - (ZT_PEER_ACTIVITY_TIMEOUT * 16); // arbitrary reasonable cutoff
 		for(std::map<Address,uint64_t>::iterator lp(_lastPushedMembershipCertificate.begin());lp!=_lastPushedMembershipCertificate.end();) {
 		for(std::map<Address,uint64_t>::iterator lp(_lastPushedMembershipCertificate.begin());lp!=_lastPushedMembershipCertificate.end();) {
 			if (lp->second < forgetIfBefore)
 			if (lp->second < forgetIfBefore)
 				_lastPushedMembershipCertificate.erase(lp++);
 				_lastPushedMembershipCertificate.erase(lp++);

+ 206 - 16
node/NetworkConfigMaster.cpp

@@ -26,6 +26,7 @@
  */
  */
 
 
 #include "Constants.hpp"
 #include "Constants.hpp"
+#include "NetworkConfigMaster.hpp"
 
 
 #ifdef ZT_ENABLE_NETCONF_MASTER
 #ifdef ZT_ENABLE_NETCONF_MASTER
 
 
@@ -37,7 +38,6 @@
 #include <sys/time.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/types.h>
 
 
-#include "NetworkConfigMaster.hpp"
 #include "RuntimeEnvironment.hpp"
 #include "RuntimeEnvironment.hpp"
 #include "Switch.hpp"
 #include "Switch.hpp"
 #include "Packet.hpp"
 #include "Packet.hpp"
@@ -45,6 +45,9 @@
 #include "Utils.hpp"
 #include "Utils.hpp"
 #include "Node.hpp"
 #include "Node.hpp"
 #include "Logger.hpp"
 #include "Logger.hpp"
+#include "Topology.hpp"
+#include "Peer.hpp"
+#include "CertificateOfMembership.hpp"
 
 
 // Redis timeout in seconds
 // Redis timeout in seconds
 #define ZT_NETCONF_REDIS_TIMEOUT 10
 #define ZT_NETCONF_REDIS_TIMEOUT 10
@@ -74,13 +77,93 @@ NetworkConfigMaster::~NetworkConfigMaster()
 		redisFree(_rc);
 		redisFree(_rc);
 }
 }
 
 
-void NetworkConfigMaster::doNetworkConfigRequest(
-	uint64_t packetId,
-	const Address &from,
-	uint64_t nwid,
-	const Dictionary &metaData,
-	uint64_t haveTimestamp)
+void NetworkConfigMaster::doNetworkConfigRequest(const InetAddress &fromAddr,uint64_t packetId,const Address &member,uint64_t nwid,const Dictionary &metaData,uint64_t haveTimestamp)
 {
 {
+	char memberKey[256],nwids[24],addrs[16],nwKey[256];
+	Dictionary memberRecord;
+	std::string revision,tmps2;
+
+	Mutex::Lock _l(_lock);
+
+	Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+	Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)member.toInt());
+	Utils::snprintf(memberKey,sizeof(memberKey),"zt1:network:%s:member:%s:~",nwids,addrs);
+	Utils::snprintf(nwKey,sizeof(nwKey),"zt1:network:%s:~",nwids);
+
+	TRACE("netconf: request from %s for %s (if newer than %llu)",addrs,nwids,(unsigned long long)haveTimestamp);
+
+	if (!_hget(nwKey,"id",tmps2)) {
+		LOG("netconf: Redis error retrieving %s/id",nwKey);
+		return;
+	}
+	if (tmps2 != nwids) {
+		TRACE("netconf: network %s not found",nwids);
+		Packet outp(member,RR->identity.address(),Packet::VERB_ERROR);
+		outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+		outp.append(packetId);
+		outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND);
+		outp.append(nwid);
+		RR->sw->send(outp,true);
+		return;
+	}
+
+	if (!_hget(nwKey,"revision",revision)) {
+		LOG("netconf: Redis error retrieving %s/revision",nwKey);
+		return;
+	}
+	if (!revision.length())
+		revision = "0";
+
+	if (!_hgetall(memberKey,memberRecord)) {
+		LOG("netconf: Redis error retrieving %s",memberKey);
+		return;
+	}
+
+	if ((memberRecord.size() == 0)||(memberRecord.get("id","") != addrs)||(memberRecord.get("nwid","") != nwids)) {
+		if (!_initNewMember(nwid,member,metaData,memberRecord))
+			return;
+	}
+
+	if (memberRecord.getBoolean("authorized")) {
+		uint64_t ts = memberRecord.getHexUInt("netconfTimestamp",0);
+		std::string netconf(memberRecord.get("netconf",""));
+
+		Dictionary upd;
+		upd.setHex("netconfClientTimestamp",haveTimestamp);
+		if (fromAddr)
+			upd.set("lastAt",fromAddr.toString());
+		upd.setHex("lastSeen",Utils::now());
+		_hmset(memberKey,upd);
+
+		if (((ts == 0)||(netconf.length() == 0))||(memberRecord.get("netconfRevision","") != revision)) {
+			if (!_generateNetconf(nwid,member,metaData,netconf,ts))
+				return;
+		}
+
+		if (ts > haveTimestamp) {
+			TRACE("netconf: sending %u bytes of netconf data to %s",netconf.length(),addrs);
+			Packet outp(member,RR->identity.address(),Packet::VERB_OK);
+			outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+			outp.append(packetId);
+			outp.append(nwid);
+			outp.append((uint16_t)netconf.length());
+			outp.append(netconf.data(),netconf.length());
+			outp.compress();
+			if (outp.size() > ZT_PROTO_MAX_PACKET_LENGTH) { // sanity check -- this would be weird
+				TRACE("netconf: compressed packet exceeds ZT_PROTO_MAX_PACKET_LENGTH!");
+				return;
+			}
+			RR->sw->send(outp,true);
+		}
+	} else {
+		TRACE("netconf: access denied for %s on %s",addrs,nwids);
+		Packet outp(member,RR->identity.address(),Packet::VERB_ERROR);
+		outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+		outp.append(packetId);
+		outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_);
+		outp.append(nwid);
+		RR->sw->send(outp,true);
+	}
 }
 }
 
 
 bool NetworkConfigMaster::_reconnect()
 bool NetworkConfigMaster::_reconnect()
@@ -92,7 +175,7 @@ bool NetworkConfigMaster::_reconnect()
 
 
 	tv.tv_sec = ZT_NETCONF_REDIS_TIMEOUT;
 	tv.tv_sec = ZT_NETCONF_REDIS_TIMEOUT;
 	tv.tv_usec = 0;
 	tv.tv_usec = 0;
-	_rc = redisConnectWithTimeout(_redisHost.c_str(),_redisPort,&tv);
+	_rc = redisConnectWithTimeout(_redisHost.c_str(),_redisPort,tv);
 	if (!_rc)
 	if (!_rc)
 		return false;
 		return false;
 	if (_rc->err) {
 	if (_rc->err) {
@@ -100,14 +183,14 @@ bool NetworkConfigMaster::_reconnect()
 		_rc = (redisContext *)0;
 		_rc = (redisContext *)0;
 		return false;
 		return false;
 	}
 	}
-	redisSetTimeout(_rc,&tv); // necessary???
+	redisSetTimeout(_rc,tv); // necessary???
 
 
 	// TODO: support AUTH and SELECT !!!
 	// TODO: support AUTH and SELECT !!!
 
 
 	return true;
 	return true;
 }
 }
 
 
-bool NetworkConfigMaster::_hgetall(const char *key,std::map<std::string,std::string> &hdata)
+bool NetworkConfigMaster::_hgetall(const char *key,Dictionary &hdata)
 {
 {
 	if (!_rc) {
 	if (!_rc) {
 		if (!_reconnect())
 		if (!_reconnect())
@@ -125,11 +208,11 @@ bool NetworkConfigMaster::_hgetall(const char *key,std::map<std::string,std::str
 	if (reply->type == REDIS_REPLY_ARRAY) {
 	if (reply->type == REDIS_REPLY_ARRAY) {
 		for(long i=0;i<reply->elements;) {
 		for(long i=0;i<reply->elements;) {
 			try {
 			try {
-				const char *k = reply->elements[i]->str;
+				const char *k = reply->element[i]->str;
 				if (++i >= reply->elements)
 				if (++i >= reply->elements)
 					break;
 					break;
-				if ((k)&&(reply->elements[i]->str))
-					hdata[k] = reply->elements[i]->str;
+				if ((k)&&(reply->element[i]->str))
+					hdata[k] = reply->element[i]->str;
 				++i;
 				++i;
 			} catch ( ... ) {
 			} catch ( ... ) {
 				break; // memory safety
 				break; // memory safety
@@ -142,9 +225,9 @@ bool NetworkConfigMaster::_hgetall(const char *key,std::map<std::string,std::str
 	return true;
 	return true;
 }
 }
 
 
-bool NetworkConfigMaster::_hmset(const char *key,const std::map<std::string,std::string> &hdata)
+bool NetworkConfigMaster::_hmset(const char *key,const Dictionary &hdata)
 {
 {
-	const const char *hargv[1024];
+	const char *hargv[1024];
 
 
 	if (!hdata.size())
 	if (!hdata.size())
 		return true;
 		return true;
@@ -157,7 +240,7 @@ bool NetworkConfigMaster::_hmset(const char *key,const std::map<std::string,std:
 	hargv[0] = "HMSET";
 	hargv[0] = "HMSET";
 	hargv[1] = key;
 	hargv[1] = key;
 	int hargc = 2;
 	int hargc = 2;
-	for(std::map<std::string,std::string>::const_iterator i(hdata.begin());i!=hdata.end();++i) {
+	for(Dictionary::const_iterator i(hdata.begin());i!=hdata.end();++i) {
 		if (hargc >= 1024)
 		if (hargc >= 1024)
 			break;
 			break;
 		hargv[hargc++] = i->first.c_str();
 		hargv[hargc++] = i->first.c_str();
@@ -228,6 +311,113 @@ bool NetworkConfigMaster::_hset(const char *key,const char *hashKey,const char *
 	return true;
 	return true;
 }
 }
 
 
+bool NetworkConfigMaster::_initNewMember(uint64_t nwid,const Address &member,const Dictionary &metaData,Dictionary &memberRecord)
+{
+	char memberKey[256],nwids[24],addrs[16],nwKey[256];
+	Dictionary networkRecord;
+
+	Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+	Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)member.toInt());
+	Utils::snprintf(memberKey,sizeof(memberKey),"zt1:network:%s:member:%s:~",nwids,addrs);
+	Utils::snprintf(nwKey,sizeof(nwKey),"zt1:network:%s:~",nwids);
+
+	if (!_hgetall(nwKey,networkRecord)) {
+		LOG("netconf: Redis error retrieving %s",nwKey);
+		return false;
+	}
+	if (networkRecord.get("id","") != nwids) {
+		TRACE("netconf: network %s not found (initNewMember)",nwids);
+		return false;
+	}
+
+	memberRecord.clear();
+	memberRecord["id"] = addrs;
+	memberRecord["nwid"] = nwids;
+	memberRecord["authorized"] = (networkRecord.getBoolean("private",true) ? "0" : "1"); // auto-authorize on public networks
+	memberRecord.setHex("firstSeen",Utils::now());
+	{
+		SharedPtr<Peer> peer(RR->topology->getPeer(member));
+		if (peer)
+			memberRecord["identity"] = peer->identity().toString(false);
+	}
+
+	if (!_hmset(memberKey,memberRecord)) {
+		LOG("netconf: Redis error storing %s for new member %s",memberKey,addrs);
+		return false;
+	}
+
+	return true;
+}
+
+bool NetworkConfigMaster::_generateNetconf(uint64_t nwid,const Address &member,const Dictionary &metaData,std::string &netconf,uint64_t &ts)
+{
+	char memberKey[256],nwids[24],addrs[16],tss[24],nwKey[256];
+	Dictionary networkRecord,memberRecord,nc;
+
+	Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid);
+	Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)member.toInt());
+	Utils::snprintf(memberKey,sizeof(memberKey),"zt1:network:%s:member:%s:~",nwids,addrs);
+	Utils::snprintf(nwKey,sizeof(nwKey),"zt1:network:%s:~",nwids);
+
+	if (!_hgetall(nwKey,networkRecord)) {
+		LOG("netconf: Redis error retrieving %s",nwKey);
+		return false;
+	}
+	if (networkRecord.get("id","") != nwids) {
+		TRACE("netconf: network %s not found (generateNetconf)",nwids);
+		return false;
+	}
+
+	if (!_hgetall(memberKey,memberRecord)) {
+		LOG("netconf: Redis error retrieving %s",memberKey);
+		return false;
+	}
+
+	uint64_t revision = networkRecord.getHexUInt("revision",0);
+	bool isPrivate = networkRecord.getBoolean("private",true);
+	ts = Utils::now();
+	Utils::snprintf(tss,sizeof(tss),"%llx",ts);
+
+	nc[ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP] = tss;
+	nc[ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID] = nwids;
+	nc[ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO] = addrs;
+	nc[ZT_NETWORKCONFIG_DICT_KEY_PRIVATE] = isPrivate ? "1" : "0";
+	nc[ZT_NETWORKCONFIG_DICT_KEY_NAME] = networkRecord.get("name",nwids);
+	nc[ZT_NETWORKCONFIG_DICT_KEY_DESC] = networkRecord.get("desc","");
+	nc[ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST] = networkRecord.getBoolean("enableBroadcast",true) ? "1" : "0";
+	nc[ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING] = networkRecord.getBoolean("allowPassiveBridging",false) ? "1" : "0";
+	nc[ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES] = networkRecord.get("etherTypes","");
+	nc[ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_RATES] = networkRecord.get("multicastRates","");
+
+	uint64_t ml = networkRecord.getHexUInt("multicastLimit",0);
+	if (ml > 0)
+		nc.setHex(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,ml);
+
+	std::string activeBridgeList;
+	if (activeBridgeList.length() > 0)
+		nc[ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES] = activeBridgeList;
+
+	std::string v4s,v6s;
+	if (v4s.length())
+		nc[ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC] = v4s;
+	if (v6s.length())
+		nc[ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC] = v6s;
+
+	if (isPrivate) {
+		CertificateOfMembership com(revision,2,nwid,member);
+		if (com.sign(RR->identity))
+			nc[ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP] = com.toString();
+	}
+
+	netconf = nc.toString();
+
+	_hset(memberKey,"netconf",netconf.c_str());
+	_hset(memberKey,"netconfTimestamp",tss);
+	_hset(memberKey,"netconfRevision",networkRecord.get("revision","0").c_str());
+
+	return true;
+}
+
 } // namespace ZeroTier
 } // namespace ZeroTier
 
 
 #endif // ZT_ENABLE_NETCONF_MASTER
 #endif // ZT_ENABLE_NETCONF_MASTER

+ 10 - 4
node/NetworkConfigMaster.hpp

@@ -41,6 +41,7 @@
 #include "Address.hpp"
 #include "Address.hpp"
 #include "Dictionary.hpp"
 #include "Dictionary.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
+#include "InetAddress.hpp"
 
 
 #include <hiredis/hiredis.h>
 #include <hiredis/hiredis.h>
 
 
@@ -82,15 +83,17 @@ public:
 	 * This is a blocking call, so rate is limited by Redis. It will fail
 	 * This is a blocking call, so rate is limited by Redis. It will fail
 	 * and log its failure if the Redis server is not available or times out.
 	 * and log its failure if the Redis server is not available or times out.
 	 *
 	 *
+	 * @param fromAddr Originating IP address
 	 * @param packetId 64-bit packet ID
 	 * @param packetId 64-bit packet ID
-	 * @param from Originating peer ZeroTier address
+	 * @param member Originating peer ZeroTier address
 	 * @param nwid 64-bit network ID
 	 * @param nwid 64-bit network ID
 	 * @param metaData Meta-data bundled with request (empty if none)
 	 * @param metaData Meta-data bundled with request (empty if none)
 	 * @param haveTimestamp Timestamp requesting peer has or 0 if none or not included
 	 * @param haveTimestamp Timestamp requesting peer has or 0 if none or not included
 	 */
 	 */
 	void doNetworkConfigRequest(
 	void doNetworkConfigRequest(
+		const InetAddress &fromAddr,
 		uint64_t packetId,
 		uint64_t packetId,
-		const Address &from,
+		const Address &member,
 		uint64_t nwid,
 		uint64_t nwid,
 		const Dictionary &metaData,
 		const Dictionary &metaData,
 		uint64_t haveTimestamp);
 		uint64_t haveTimestamp);
@@ -98,11 +101,14 @@ public:
 private:
 private:
 	// These assume _lock is locked
 	// These assume _lock is locked
 	bool _reconnect();
 	bool _reconnect();
-	bool _hgetall(const char *key,std::map<std::string,std::string> &hdata);
-	bool _hmset(const char *key,const std::map<std::string,std::string> &hdata);
+	bool _hgetall(const char *key,Dictionary &hdata);
+	bool _hmset(const char *key,const Dictionary &hdata);
 	bool _hget(const char *key,const char *hashKey,std::string &value);
 	bool _hget(const char *key,const char *hashKey,std::string &value);
 	bool _hset(const char *key,const char *hashKey,const char *value);
 	bool _hset(const char *key,const char *hashKey,const char *value);
 
 
+	bool _initNewMember(uint64_t nwid,const Address &member,const Dictionary &metaData,Dictionary &memberRecord);
+	bool _generateNetconf(uint64_t nwid,const Address &member,const Dictionary &metaData,std::string &netconf,uint64_t &ts);
+
 	Mutex _lock;
 	Mutex _lock;
 
 
 	std::string _redisHost;
 	std::string _redisHost;

+ 10 - 7
redis-schema.md

@@ -5,7 +5,8 @@
 - : is used as the key namespace separator as per de-facto Redis standard.
 - : is used as the key namespace separator as per de-facto Redis standard.
 - A top-level record may have a :~ child containing a hash. This is the root hash and contains any simple key=value properties of the record.
 - A top-level record may have a :~ child containing a hash. This is the root hash and contains any simple key=value properties of the record.
 - Booleans: any value other than "1" or "true" is false.
 - Booleans: any value other than "1" or "true" is false.
-- Timestamps are in milliseconds since the epoch and are stored as base-10 integers.
+- Unless otherwise indicated *all integer values are in hexadecimal!*
+- Timestamps are in milliseconds since the epoch
 - IPv4 addresees: stored in standard dot notation e.g. 1.2.3.4
 - IPv4 addresees: stored in standard dot notation e.g. 1.2.3.4
 - IPv6 addresses: :'s are optional and addresses must be stored *without* shortening, e.g. with :0000: instead of ::. It must be possible to strip :'s from the address and get 128 bits of straight hex.
 - IPv6 addresses: :'s are optional and addresses must be stored *without* shortening, e.g. with :0000: instead of ::. It must be possible to strip :'s from the address and get 128 bits of straight hex.
 - Hexadecimal: all hex values must be lower case
 - Hexadecimal: all hex values must be lower case
@@ -23,8 +24,6 @@ Network records are used by the network configuration master to issue configurat
 
 
 ### zt1:network:\<nwid\>:~
 ### zt1:network:\<nwid\>:~
 
 
-Each network has a network record indexed by its 64-bit network ID in lower-case hexadecimal. Unless otherwise indicated all integer values are in hexadecimal.
-
 - !R id :: must be \<nwid\>
 - !R id :: must be \<nwid\>
 - !M name :: network's globally unique short name, which can contain only characters valid in an e-mail address. It's the job of the code that populates this DB to ensure that this is globally unique.
 - !M name :: network's globally unique short name, which can contain only characters valid in an e-mail address. It's the job of the code that populates this DB to ensure that this is globally unique.
 - R owner :: id of user who owns this network (not used by netconf master, only for web UI and web API)
 - R owner :: id of user who owns this network (not used by netconf master, only for web UI and web API)
@@ -34,7 +33,7 @@ Each network has a network record indexed by its 64-bit network ID in lower-case
 - R infrastructure :: if true, network can't be deleted through API or web UI
 - R infrastructure :: if true, network can't be deleted through API or web UI
 - M private :: if true, network requires authentication
 - M private :: if true, network requires authentication
 - R creationTime :: timestamp of network creation
 - R creationTime :: timestamp of network creation
-- M etherTypes :: comma-delimited list of integers indicating Ethernet types permitted on network
+- M etherTypes :: comma-delimited list of HEX integers indicating Ethernet types permitted on network
 - M enableBroadcast :: if true, ff:ff:ff:ff:ff:ff is enabled network-wide
 - M enableBroadcast :: if true, ff:ff:ff:ff:ff:ff is enabled network-wide
 - M v4AssignMode :: 'none' (or null/empty/etc.), 'zt', 'dhcp'
 - M v4AssignMode :: 'none' (or null/empty/etc.), 'zt', 'dhcp'
 - M v4AssignPool :: network/bits from which to assign IPs
 - M v4AssignPool :: network/bits from which to assign IPs
@@ -42,11 +41,14 @@ Each network has a network record indexed by its 64-bit network ID in lower-case
 - M v6AssignPool :: network/bits from which to assign IPs
 - M v6AssignPool :: network/bits from which to assign IPs
 - M allowPassiveBridging :: if true, allow passive bridging
 - M allowPassiveBridging :: if true, allow passive bridging
 - M multicastLimit :: maximum number of recipients to receive a multicast on this network
 - M multicastLimit :: maximum number of recipients to receive a multicast on this network
-- M multicastRates :: packed JSON containing multicast rates (see below)
+- M multicastRates :: string-encoded dictionary containing multicast groups and rates (see below)
 - M subscriptions :: comma-delimited list of subscriptions for this network
 - M subscriptions :: comma-delimited list of subscriptions for this network
+- M revision :: network revision number
 - M ui :: arbitrary field that can be used by the UI to store stuff
 - M ui :: arbitrary field that can be used by the UI to store stuff
 
 
-Multicast rates are encoded as a JSON document. Each key is a multicast group in "MAC/ADI" format (e.g. *ff:ff:ff:ff:ff:ff/0*), and each value is a comma-delimited tuple of hex integer values: preload, max balance, and rate of accrual in bytes per second. An entry for *0* (or *0/0* or *00:00:00:00:00:00/0*) indicates the default setting for all unspecified multicast groups. Setting a rate limit like *ffffffff,ffffffff,ffffffff* as default will effectively turn off rate limits.
+Multicast rates are encoded as a dictionary. Each key is a multicast group in "MAC/ADI" format (e.g. *ff:ff:ff:ff:ff:ff/0*), and each value is a comma-delimited tuple of hex integer values: preload, max balance, and rate of accrual in bytes per second. An entry for *0* (or *0/0* or *00:00:00:00:00:00/0*) indicates the default setting for all unspecified multicast groups. Setting a rate limit like *ffffffff,ffffffff,ffffffff* as default will effectively turn off rate limits.
+
+Incrementing the network's revision number causes network configurations to be regenerated automatically on next query by a peer. It's important to note that certificates of membership for private networks permit revision numbers to vary by up to **2**. Thus, revision should be incremented once for changes that do not have authorization implications and twice when de-authorizing a member from a network. For better continuity this double-increment can happen with a time delay between each increment to give still-authorized peers more time to get an updated certificate.
 
 
 ### zt1:network:\<nwid\>:member:\<address\>:~
 ### zt1:network:\<nwid\>:member:\<address\>:~
 
 
@@ -65,7 +67,8 @@ Each member of a network has a hash containing its configuration and authorizati
 - R lastSeen :: time node was most recently seen
 - R lastSeen :: time node was most recently seen
 - R lastAt :: real Internet IP/port where node was most recently seen
 - R lastAt :: real Internet IP/port where node was most recently seen
 - R ipAssignments :: comma-delimited list of IP address assignments (see below)
 - R ipAssignments :: comma-delimited list of IP address assignments (see below)
-- R netconf :: most recent network configuration dictionary (updated on changes)
+- R netconf :: most recent network configuration dictionary
+- R netconfRevision :: revision of network when most recent netconf was generated
 - R netconfTimestamp :: timestamp from netconf dictionary
 - R netconfTimestamp :: timestamp from netconf dictionary
 - R netconfClientTimestamp :: timestamp client most recently reported
 - R netconfClientTimestamp :: timestamp client most recently reported
 - M ui :: string-serialized JSON blob for use by the user interface (unused by netconf-master)
 - M ui :: string-serialized JSON blob for use by the user interface (unused by netconf-master)