Browse Source

Lots of cleanup, more work on certificates, some security fixes.

Adam Ierymenko 11 years ago
parent
commit
46f868bd4f

+ 85 - 46
netconf-service/netconf.cpp

@@ -161,19 +161,23 @@ int main(int argc,char **argv)
 		try {
 		try {
 			const std::string &reqType = request.get("type");
 			const std::string &reqType = request.get("type");
 			if (reqType == "netconf-request") { // NETWORK_CONFIG_REQUEST packet
 			if (reqType == "netconf-request") { // NETWORK_CONFIG_REQUEST packet
+				// Deserialize querying peer identity and network ID
 				Identity peerIdentity(request.get("peerId"));
 				Identity peerIdentity(request.get("peerId"));
 				uint64_t nwid = strtoull(request.get("nwid").c_str(),(char **)0,16);
 				uint64_t nwid = strtoull(request.get("nwid").c_str(),(char **)0,16);
+				std::string fromAddr(request.get("from"));
+
+				// Meta-information from node, such as (future) geo-location stuff
 				Dictionary meta;
 				Dictionary meta;
 				if (request.contains("meta"))
 				if (request.contains("meta"))
 					meta.fromString(request.get("meta"));
 					meta.fromString(request.get("meta"));
 
 
-				// Do quick signature check / sanity check
+				// Check validity of node's identity, ignore request on failure
 				if (!peerIdentity.locallyValidate()) {
 				if (!peerIdentity.locallyValidate()) {
 					fprintf(stderr,"identity failed validity check: %s\n",peerIdentity.toString(false).c_str());
 					fprintf(stderr,"identity failed validity check: %s\n",peerIdentity.toString(false).c_str());
 					continue;
 					continue;
 				}
 				}
 
 
-				// Save identity if unknown
+				// Save node's identity if unknown
 				{
 				{
 					Query q = dbCon->query();
 					Query q = dbCon->query();
 					q << "SELECT identity FROM Node WHERE id = " << peerIdentity.address().toInt();
 					q << "SELECT identity FROM Node WHERE id = " << peerIdentity.address().toInt();
@@ -196,17 +200,21 @@ int main(int argc,char **argv)
 					}
 					}
 				}
 				}
 
 
-				// Update lastSeen
+				// Update lastSeen for Node, which is always updated on a netconf request
 				{
 				{
 					Query q = dbCon->query();
 					Query q = dbCon->query();
 					q << "UPDATE Node SET lastSeen = " << Utils::now() << " WHERE id = " << peerIdentity.address().toInt();
 					q << "UPDATE Node SET lastSeen = " << Utils::now() << " WHERE id = " << peerIdentity.address().toInt();
 					q.exec();
 					q.exec();
 				}
 				}
 
 
+				// Look up core network information
 				bool isOpen = false;
 				bool isOpen = false;
-				unsigned int mpb = 0;
-				unsigned int md = 0;
-				std::string name,desc;
+				unsigned int multicastPrefixBits = 0;
+				unsigned int multicastDepth = 0;
+				bool emulateArp = false;
+				bool emulateNdp = false;
+				std::string name;
+				std::string desc;
 				{
 				{
 					Query q = dbCon->query();
 					Query q = dbCon->query();
 					q << "SELECT name,`desc`,isOpen,multicastPrefixBits,multicastDepth FROM Network WHERE id = " << nwid;
 					q << "SELECT name,`desc`,isOpen,multicastPrefixBits,multicastDepth FROM Network WHERE id = " << nwid;
@@ -215,8 +223,10 @@ int main(int argc,char **argv)
 						name = rs[0]["name"].c_str();
 						name = rs[0]["name"].c_str();
 						desc = rs[0]["desc"].c_str();
 						desc = rs[0]["desc"].c_str();
 						isOpen = ((int)rs[0]["isOpen"] > 0);
 						isOpen = ((int)rs[0]["isOpen"] > 0);
-						mpb = (unsigned int)rs[0]["multicastPrefixBits"];
-						md = (unsigned int)rs[0]["multicastDepth"];
+						emulateArp = ((int)rs[0]["emulateArp"] > 0);
+						emulateNdp = ((int)rs[0]["emulateNdp"] > 0);
+						multicastPrefixBits = (unsigned int)rs[0]["multicastPrefixBits"];
+						multicastDepth = (unsigned int)rs[0]["multicastDepth"];
 					} else {
 					} else {
 						Dictionary response;
 						Dictionary response;
 						response["peer"] = peerIdentity.address().toString();
 						response["peer"] = peerIdentity.address().toString();
@@ -231,10 +241,34 @@ int main(int argc,char **argv)
 						write(STDOUT_FILENO,&respml,4);
 						write(STDOUT_FILENO,&respml,4);
 						write(STDOUT_FILENO,respm.data(),respm.length());
 						write(STDOUT_FILENO,respm.data(),respm.length());
 						stdoutWriteLock.unlock();
 						stdoutWriteLock.unlock();
-						continue;
+						continue; // ABORT, wait for next request
 					}
 					}
 				}
 				}
 
 
+				// Check membership if this is a closed network
+				if (!isOpen) {
+					Query q = dbCon->query();
+					q << "SELECT Node_id FROM NetworkNodes WHERE Network_id = " << nwid << " AND Node_id = " << peerIdentity.address().toInt();
+					StoreQueryResult rs = q.store();
+					if (!rs.num_rows()) {
+						Dictionary response;
+						response["peer"] = peerIdentity.address().toString();
+						response["nwid"] = request.get("nwid");
+						response["type"] = "netconf-response";
+						response["requestId"] = request.get("requestId");
+						response["error"] = "ACCESS_DENIED";
+						std::string respm = response.toString();
+						uint32_t respml = (uint32_t)htonl((uint32_t)respm.length());
+
+						stdoutWriteLock.lock();
+						write(STDOUT_FILENO,&respml,4);
+						write(STDOUT_FILENO,respm.data(),respm.length());
+						stdoutWriteLock.unlock();
+						continue; // ABORT, wait for next request
+					}
+				}
+
+				// Get list of etherTypes in comma-delimited hex format
 				std::string etherTypeWhitelist;
 				std::string etherTypeWhitelist;
 				{
 				{
 					Query q = dbCon->query();
 					Query q = dbCon->query();
@@ -247,6 +281,7 @@ int main(int argc,char **argv)
 					}
 					}
 				}
 				}
 
 
+				// Get multicast group rates in dictionary format
 				Dictionary multicastRates;
 				Dictionary multicastRates;
 				{
 				{
 					Query q = dbCon->query();
 					Query q = dbCon->query();
@@ -267,40 +302,16 @@ int main(int argc,char **argv)
 						if (mac) {
 						if (mac) {
 							sprintf(buf,"%.12llx/%lx",(mac & 0xffffffffffffULL),(unsigned long)rs[i]["multicastGroupAdi"]);
 							sprintf(buf,"%.12llx/%lx",(mac & 0xffffffffffffULL),(unsigned long)rs[i]["multicastGroupAdi"]);
 							multicastRates[buf] = buf2;
 							multicastRates[buf] = buf2;
-						} else multicastRates["*"] = buf2;
+						} else { // zero MAC indicates default for unmatching multicast groups
+							multicastRates["*"] = buf2;
+						}
 					}
 					}
 				}
 				}
 
 
-				Dictionary netconf;
-
-				sprintf(buf,"%.16llx",(unsigned long long)nwid);
-				netconf["nwid"] = buf;
-				netconf["o"] = (isOpen ? "1" : "0");
-				netconf["name"] = name;
-				netconf["desc"] = desc;
-				netconf["et"] = etherTypeWhitelist;
-				netconf["mr"] = multicastRates.toString();
-				sprintf(buf,"%llx",(unsigned long long)Utils::now());
-				netconf["ts"] = buf;
-				netconf["peer"] = peerIdentity.address().toString();
-				if (mpb) {
-					sprintf(buf,"%x",mpb);
-					netconf["mpb"] = buf;
-				}
-				if (md) {
-					sprintf(buf,"%x",md);
-					netconf["md"] = buf;
-				}
-
-				if (!isOpen) {
-					// TODO: handle closed networks, look up private membership,
-					// generate signed cert.
-				}
-
-				std::string ipv4Static,ipv6Static;
-
+				// Check for (or assign?) static IP address assignments
+				std::string ipv4Static;
+				std::string ipv6Static;
 				{
 				{
-					// Check for IPv4 static assignments
 					Query q = dbCon->query();
 					Query q = dbCon->query();
 					q << "SELECT INET_NTOA(ip) AS ip,netmaskBits FROM IPv4Static WHERE Node_id = " << peerIdentity.address().toInt() << " AND Network_id = " << nwid;
 					q << "SELECT INET_NTOA(ip) AS ip,netmaskBits FROM IPv4Static WHERE Node_id = " << peerIdentity.address().toInt() << " AND Network_id = " << nwid;
 					StoreQueryResult rs = q.store();
 					StoreQueryResult rs = q.store();
@@ -363,16 +374,42 @@ int main(int argc,char **argv)
 					}
 					}
 				}
 				}
 
 
-				// Add static assignments to netconf, if any
-				if (ipv4Static.length()) {
-					netconf["ipv4Static"] = ipv4Static; // TODO: remove, old name
-					netconf["v4s"] = ipv4Static;
+				// Update activity table for this network to indicate peer's participation
+				{
+					Query q = dbCon->query();
+					q << "INSERT INTO NetworkActivity (Network_id,Node_id,lastActivityTime,lastActivityFrom) VALUES (" << nwid << "," << peerIdentity.address().toInt() << "," << Utils::now() << "," << fromAddr << ") ON DUPLICATE KEY UPDATE lastActivityTime = VALUES(lastActivityTime),lastActivityFrom = VALUES(lastActivityFrom)";
+					q.exec();
 				}
 				}
-				if (ipv6Static.length()) {
-					netconf["v6s"] = ipv6Static;
+
+				// Assemble response dictionary to send to peer
+				Dictionary netconf;
+				sprintf(buf,"%.16llx",(unsigned long long)nwid);
+				netconf["nwid"] = buf;
+				netconf["peer"] = peerIdentity.address().toString();
+				netconf["name"] = name;
+				netconf["desc"] = desc;
+				netconf["o"] = (isOpen ? "1" : "0");
+				netconf["et"] = etherTypeWhitelist;
+				netconf["mr"] = multicastRates.toString();
+				sprintf(buf,"%llx",(unsigned long long)Utils::now());
+				netconf["ts"] = buf;
+				netconf["eARP"] = (emulateArp ? "1" : "0");
+				netconf["eNDP"] = (emulateNdp ? "1" : "0");
+				if (multicastPrefixBits) {
+					sprintf(buf,"%x",multicastPrefixBits);
+					netconf["mpb"] = buf;
+				}
+				if (multicastDepth) {
+					sprintf(buf,"%x",multicastDepth);
+					netconf["md"] = buf;
 				}
 				}
+				if (ipv4Static.length())
+					netconf["v4s"] = ipv4Static;
+				if (ipv6Static.length())
+					netconf["v6s"] = ipv6Static;
 
 
-				{ // Create and send service bus response with payload attached as 'netconf'
+				// Send netconf as service bus response
+				{
 					Dictionary response;
 					Dictionary response;
 					response["peer"] = peerIdentity.address().toString();
 					response["peer"] = peerIdentity.address().toString();
 					response["nwid"] = request.get("nwid");
 					response["nwid"] = request.get("nwid");
@@ -386,6 +423,8 @@ int main(int argc,char **argv)
 					write(STDOUT_FILENO,&respml,4);
 					write(STDOUT_FILENO,&respml,4);
 					write(STDOUT_FILENO,respm.data(),respm.length());
 					write(STDOUT_FILENO,respm.data(),respm.length());
 					stdoutWriteLock.unlock();
 					stdoutWriteLock.unlock();
+
+					// LOOP, wait for next request
 				}
 				}
 			}
 			}
 		} catch (std::exception &exc) {
 		} catch (std::exception &exc) {

+ 4 - 9
node/CertificateOfMembership.cpp

@@ -163,15 +163,10 @@ bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) c
 
 
 		// Compare to determine if the absolute value of the difference
 		// Compare to determine if the absolute value of the difference
 		// between these two parameters is within our maxDelta.
 		// between these two parameters is within our maxDelta.
-		uint64_t a = _qualifiers[myidx].value;
-		uint64_t b = other._qualifiers[myidx].value;
-		if (a >= b) {
-			if ((a - b) > _qualifiers[myidx].maxDelta)
-				return false;
-		} else {
-			if ((b - a) > _qualifiers[myidx].maxDelta)
-				return false;
-		}
+		const uint64_t a = _qualifiers[myidx].value;
+		const uint64_t b = other._qualifiers[myidx].value;
+		if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[myidx].maxDelta)
+			return false;
 
 
 		++myidx;
 		++myidx;
 	}
 	}

+ 139 - 11
node/CertificateOfMembership.hpp

@@ -50,10 +50,8 @@ namespace ZeroTier {
  * These contain an id, a value, and a maximum delta.
  * These contain an id, a value, and a maximum delta.
  *
  *
  * The ID is arbitrary and should be assigned using a scheme that makes
  * The ID is arbitrary and should be assigned using a scheme that makes
- * every ID globally unique. ID 0 is reserved for the always-present
- * validity timestamp and range, and ID 1 is reserved for the always-present
- * network ID. IDs less than 65536 are reserved for future global
- * assignment.
+ * every ID globally unique. IDs beneath 65536 are reserved for global
+ * assignment by ZeroTier Networks.
  *
  *
  * The value's meaning is ID-specific and isn't important here. What's
  * The value's meaning is ID-specific and isn't important here. What's
  * important is the value and the third member of the tuple: the maximum
  * important is the value and the third member of the tuple: the maximum
@@ -83,21 +81,107 @@ public:
 	};
 	};
 
 
 	/**
 	/**
-	 * Reserved COM IDs
+	 * Reserved qualifier IDs
 	 *
 	 *
 	 * IDs below 65536 should be considered reserved for future global
 	 * IDs below 65536 should be considered reserved for future global
 	 * assignment here.
 	 * assignment here.
+	 *
+	 * Addition of new required fields requires that code in hasRequiredFields
+	 * be updated as well.
 	 */
 	 */
 	enum ReservedId
 	enum ReservedId
 	{
 	{
-		COM_RESERVED_ID_TIMESTAMP = 0, // timestamp, max delta defines cert life
-		COM_RESERVED_ID_NETWORK_ID = 1 // network ID, max delta always 0
+		/**
+		 * Timestamp of certificate issue in milliseconds since epoch
+		 *
+		 * maxDelta here defines certificate lifetime, and certs are lazily
+		 * pushed to other peers on a net with a frequency of 1/2 this time.
+		 */
+		COM_RESERVED_ID_TIMESTAMP = 0,
+
+		/**
+		 * Network ID for which certificate was issued
+		 *
+		 * maxDelta here is zero, since this must match.
+		 */
+		COM_RESERVED_ID_NETWORK_ID = 1,
+
+		/**
+		 * ZeroTier address to whom certificate was issued
+		 *
+		 * maxDelta will be 0xffffffffffffffff here since it's permitted to differ
+		 * from peers obviously.
+		 */
+		COM_RESERVED_ID_ISSUED_TO = 2
 	};
 	};
 
 
+	/**
+	 * Create an empty certificate
+	 */
 	CertificateOfMembership() { memset(_signature.data,0,_signature.size()); }
 	CertificateOfMembership() { memset(_signature.data,0,_signature.size()); }
+
+	/**
+	 * Create from required fields common to all networks
+	 *
+	 * @param timestamp Timestamp of cert
+	 * @param timestampMaxDelta Maximum variation between timestamps on this net
+	 * @param nwid Network ID
+	 * @param issuedTo Certificate recipient
+	 */
+	CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Address &issuedTo)
+	{
+		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_TIMESTAMP,timestamp,timestampMaxDelta));
+		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_NETWORK_ID,nwid,0));
+		_qualifiers.push_back(_Qualifier(COM_RESERVED_ID_ISSUED_TO,issuedTo.toInt(),0xffffffffffffffffULL));
+		memset(_signature.data,0,_signature.size());
+	}
+
+	/**
+	 * Create from string-serialized data
+	 *
+	 * @param s String-serialized COM
+	 */
 	CertificateOfMembership(const char *s) { fromString(s); }
 	CertificateOfMembership(const char *s) { fromString(s); }
+
+	/**
+	 * Create from string-serialized data
+	 *
+	 * @param s String-serialized COM
+	 */
 	CertificateOfMembership(const std::string &s) { fromString(s.c_str()); }
 	CertificateOfMembership(const std::string &s) { fromString(s.c_str()); }
 
 
+	/**
+	 * Create from binary-serialized COM in buffer
+	 *
+	 * @param b Buffer to deserialize from
+	 * @param startAt Position to start in buffer
+	 */
+	template<unsigned int C>
+	CertificateOfMembership(const Buffer<C> &b,unsigned int startAt = 0)
+		throw(std::out_of_range,std::invalid_argument)
+	{
+		deserialize(b,startAt);
+	}
+
+	/**
+	 * Check for presence of all required fields common to all networks
+	 *
+	 * @return True if all required fields are present
+	 */
+	inline bool hasRequiredFields() const
+		throw()
+	{
+		if (_qualifiers.size() < 3)
+			return false;
+		if (_qualifiers[0].id != COM_RESERVED_ID_TIMESTAMP)
+			return false;
+		if (_qualifiers[1].id != COM_RESERVED_ID_NETWORK_ID)
+			return false;
+		if (_qualifiers[2].id != COM_RESERVED_ID_ISSUED_TO)
+			return false;
+		return true;
+	}
+
 	/**
 	/**
 	 * @return Maximum delta for mandatory timestamp field or 0 if field missing
 	 * @return Maximum delta for mandatory timestamp field or 0 if field missing
 	 */
 	 */
@@ -111,6 +195,45 @@ public:
 		return 0ULL;
 		return 0ULL;
 	}
 	}
 
 
+	/**
+	 * @return Timestamp for this cert in ms since epoch (according to netconf's clock)
+	 */
+	inline Address timestamp() const
+		throw()
+	{
+		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
+			if (q->id == COM_RESERVED_ID_TIMESTAMP)
+				return Address(q->value);
+		}
+		return Address();
+	}
+
+	/**
+	 * @return Address to which this cert was issued
+	 */
+	inline Address issuedTo() const
+		throw()
+	{
+		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
+			if (q->id == COM_RESERVED_ID_ISSUED_TO)
+				return Address(q->value);
+		}
+		return Address();
+	}
+
+	/**
+	 * @return Network ID for which this cert was issued
+	 */
+	inline uint64_t networkId() const
+		throw()
+	{
+		for(std::vector<_Qualifier>::const_iterator q(_qualifiers.begin());q!=_qualifiers.end();++q) {
+			if (q->id == COM_RESERVED_ID_NETWORK_ID)
+				return q->value;
+		}
+		return 0ULL;
+	}
+
 	/**
 	/**
 	 * Add or update a qualifier in this certificate
 	 * Add or update a qualifier in this certificate
 	 *
 	 *
@@ -186,7 +309,7 @@ public:
 		throw(std::out_of_range)
 		throw(std::out_of_range)
 	{
 	{
 		b.append((unsigned char)COM_UINT64_ED25519);
 		b.append((unsigned char)COM_UINT64_ED25519);
-		b.append((uint32_t)_qualifiers.size());
+		b.append((uint16_t)_qualifiers.size());
 		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) {
 			b.append(q->id);
 			b.append(q->id);
 			b.append(q->value);
 			b.append(q->value);
@@ -209,10 +332,15 @@ public:
 		if (b[p++] != COM_UINT64_ED25519)
 		if (b[p++] != COM_UINT64_ED25519)
 			throw std::invalid_argument("unknown certificate of membership type");
 			throw std::invalid_argument("unknown certificate of membership type");
 
 
-		unsigned int numq = b.template at<uint32_t>(p); p += sizeof(uint32_t);
+		unsigned int numq = b.template at<uint16_t>(p); p += sizeof(uint16_t);
+		uint64_t lastId = 0;
 		for(unsigned int i=0;i<numq;++i) {
 		for(unsigned int i=0;i<numq;++i) {
+			uint64_t tmp = b.template at<uint64_t>(p);
+			if (tmp < lastId)
+				throw std::invalid_argument("certificate qualifiers are not sorted");
+			else lastId = tmp;
 			_qualifiers.push_back(_Qualifier(
 			_qualifiers.push_back(_Qualifier(
-					b.template at<uint64_t>(p),
+					tmp,
 					b.template at<uint64_t>(p + 8),
 					b.template at<uint64_t>(p + 8),
 					b.template at<uint64_t>(p + 16)
 					b.template at<uint64_t>(p + 16)
 				));
 				));
@@ -247,8 +375,8 @@ private:
 		inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // for sort
 		inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // for sort
 	};
 	};
 
 
-	std::vector<_Qualifier> _qualifiers; // sorted by id and unique
 	Address _signedBy;
 	Address _signedBy;
+	std::vector<_Qualifier> _qualifiers; // sorted by id and unique
 	C25519::Signature _signature;
 	C25519::Signature _signature;
 };
 };
 
 

+ 164 - 84
node/Network.cpp

@@ -39,6 +39,9 @@
 #include "Switch.hpp"
 #include "Switch.hpp"
 #include "Packet.hpp"
 #include "Packet.hpp"
 #include "Utils.hpp"
 #include "Utils.hpp"
+#include "Buffer.hpp"
+
+#define ZT_NETWORK_CERT_WRITE_BUF_SIZE 524288
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -51,6 +54,7 @@ const char *Network::statusString(const Status s)
 		case NETWORK_WAITING_FOR_FIRST_AUTOCONF: return "WAITING_FOR_FIRST_AUTOCONF";
 		case NETWORK_WAITING_FOR_FIRST_AUTOCONF: return "WAITING_FOR_FIRST_AUTOCONF";
 		case NETWORK_OK: return "OK";
 		case NETWORK_OK: return "OK";
 		case NETWORK_ACCESS_DENIED: return "ACCESS_DENIED";
 		case NETWORK_ACCESS_DENIED: return "ACCESS_DENIED";
+		case NETWORK_NOT_FOUND: return "NOT_FOUND";
 	}
 	}
 	return "(invalid)";
 	return "(invalid)";
 }
 }
@@ -58,15 +62,13 @@ const char *Network::statusString(const Status s)
 Network::~Network()
 Network::~Network()
 {
 {
 	delete _tap;
 	delete _tap;
-
 	if (_destroyOnDelete) {
 	if (_destroyOnDelete) {
-		std::string confPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".conf");
-		std::string mcdbPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".mcerts");
-		Utils::rm(confPath);
-		Utils::rm(mcdbPath);
+		Utils::rm(std::string(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".conf"));
+		Utils::rm(std::string(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".mcerts"));
 	} else {
 	} else {
 		// Causes flush of membership certs to disk
 		// Causes flush of membership certs to disk
 		clean();
 		clean();
+		_dumpMulticastCerts();
 	}
 	}
 }
 }
 
 
@@ -87,21 +89,24 @@ SharedPtr<Network> Network::newInstance(const RuntimeEnvironment *renv,uint64_t
 	nw->_r = renv;
 	nw->_r = renv;
 	nw->_tap = new EthernetTap(renv,tag,renv->identity.address().toMAC(),ZT_IF_MTU,&_CBhandleTapData,nw.ptr());
 	nw->_tap = new EthernetTap(renv,tag,renv->identity.address().toMAC(),ZT_IF_MTU,&_CBhandleTapData,nw.ptr());
 	nw->_isOpen = false;
 	nw->_isOpen = false;
+	nw->_emulateArp = false;
+	nw->_emulateNdp = false;
 	nw->_multicastPrefixBits = ZT_DEFAULT_MULTICAST_PREFIX_BITS;
 	nw->_multicastPrefixBits = ZT_DEFAULT_MULTICAST_PREFIX_BITS;
 	nw->_multicastDepth = ZT_DEFAULT_MULTICAST_DEPTH;
 	nw->_multicastDepth = ZT_DEFAULT_MULTICAST_DEPTH;
+	nw->_status = NETWORK_WAITING_FOR_FIRST_AUTOCONF;
 	memset(nw->_etWhitelist,0,sizeof(nw->_etWhitelist));
 	memset(nw->_etWhitelist,0,sizeof(nw->_etWhitelist));
 	nw->_id = id;
 	nw->_id = id;
 	nw->_lastConfigUpdate = 0;
 	nw->_lastConfigUpdate = 0;
 	nw->_destroyOnDelete = false;
 	nw->_destroyOnDelete = false;
-	if (nw->controller() == renv->identity.address()) // sanity check, this isn't supported for now
-		throw std::runtime_error("cannot add a network for which I am the netconf master");
+	if (nw->controller() == renv->identity.address()) // netconf masters can't really join networks
+		throw std::runtime_error("cannot join a network for which I am the netconf master");
 	nw->_restoreState();
 	nw->_restoreState();
 	nw->_ready = true; // enable handling of Ethernet frames
 	nw->_ready = true; // enable handling of Ethernet frames
 	nw->requestConfiguration();
 	nw->requestConfiguration();
 	return nw;
 	return nw;
 }
 }
 
 
-void Network::setConfiguration(const Network::Config &conf)
+void Network::setConfiguration(const Network::Config &conf,bool saveToDisk)
 {
 {
 	Mutex::Lock _l(_lock);
 	Mutex::Lock _l(_lock);
 	try {
 	try {
@@ -113,6 +118,8 @@ void Network::setConfiguration(const Network::Config &conf)
 			_mcRates = conf.multicastRates();
 			_mcRates = conf.multicastRates();
 			_staticAddresses = conf.staticAddresses();
 			_staticAddresses = conf.staticAddresses();
 			_isOpen = conf.isOpen();
 			_isOpen = conf.isOpen();
+			_emulateArp = conf.emulateArp();
+			_emulateNdp = conf.emulateNdp();
 			_multicastPrefixBits = conf.multicastPrefixBits();
 			_multicastPrefixBits = conf.multicastPrefixBits();
 			_multicastDepth = conf.multicastDepth();
 			_multicastDepth = conf.multicastDepth();
 
 
@@ -121,16 +128,19 @@ void Network::setConfiguration(const Network::Config &conf)
 			_tap->setIps(_staticAddresses);
 			_tap->setIps(_staticAddresses);
 			_tap->setDisplayName((std::string("ZeroTier One [") + conf.name() + "]").c_str());
 			_tap->setDisplayName((std::string("ZeroTier One [") + conf.name() + "]").c_str());
 
 
-			// Expand ethertype whitelist into fast-lookup bit field
+			// Expand ethertype whitelist into fast-lookup bit field (more memoization)
 			memset(_etWhitelist,0,sizeof(_etWhitelist));
 			memset(_etWhitelist,0,sizeof(_etWhitelist));
 			std::set<unsigned int> wl(conf.etherTypes());
 			std::set<unsigned int> wl(conf.etherTypes());
 			for(std::set<unsigned int>::const_iterator t(wl.begin());t!=wl.end();++t)
 			for(std::set<unsigned int>::const_iterator t(wl.begin());t!=wl.end();++t)
 				_etWhitelist[*t / 8] |= (unsigned char)(1 << (*t % 8));
 				_etWhitelist[*t / 8] |= (unsigned char)(1 << (*t % 8));
 
 
-			// Save most recent configuration to disk in networks.d
-			std::string confPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".conf");
-			if (!Utils::writeFile(confPath.c_str(),conf.toString())) {
-				LOG("error: unable to write network configuration file at: %s",confPath.c_str());
+			_status = NETWORK_OK;
+
+			if (saveToDisk) {
+				std::string confPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".conf");
+				if (!Utils::writeFile(confPath.c_str(),conf.toString())) {
+					LOG("error: unable to write network configuration file at: %s",confPath.c_str());
+				}
 			}
 			}
 		}
 		}
 	} catch ( ... ) {
 	} catch ( ... ) {
@@ -141,6 +151,9 @@ void Network::setConfiguration(const Network::Config &conf)
 		_mcRates = MulticastRates();
 		_mcRates = MulticastRates();
 		_staticAddresses.clear();
 		_staticAddresses.clear();
 		_isOpen = false;
 		_isOpen = false;
+		_emulateArp = false;
+		_emulateNdp = false;
+		_status = NETWORK_WAITING_FOR_FIRST_AUTOCONF;
 
 
 		_lastConfigUpdate = 0;
 		_lastConfigUpdate = 0;
 		LOG("unexpected exception handling config for network %.16llx, retrying fetch...",(unsigned long long)_id);
 		LOG("unexpected exception handling config for network %.16llx, retrying fetch...",(unsigned long long)_id);
@@ -150,7 +163,7 @@ void Network::setConfiguration(const Network::Config &conf)
 void Network::requestConfiguration()
 void Network::requestConfiguration()
 {
 {
 	if (controller() == _r->identity.address()) {
 	if (controller() == _r->identity.address()) {
-		// FIXME: Right now the netconf master cannot be a member of its own nets
+		// netconf master cannot be a member of its own nets
 		LOG("unable to request network configuration for network %.16llx: I am the network master, cannot query self",(unsigned long long)_id);
 		LOG("unable to request network configuration for network %.16llx: I am the network master, cannot query self",(unsigned long long)_id);
 		return;
 		return;
 	}
 	}
@@ -162,11 +175,13 @@ void Network::requestConfiguration()
 	_r->sw->send(outp,true);
 	_r->sw->send(outp,true);
 }
 }
 
 
-void Network::addMembershipCertificate(const Address &peer,const CertificateOfMembership &cert)
+void Network::addMembershipCertificate(const CertificateOfMembership &cert)
 {
 {
 	Mutex::Lock _l(_lock);
 	Mutex::Lock _l(_lock);
-	if (!_isOpen)
-		_membershipCertificates[peer] = cert;
+	// We go ahead and accept certs provisionally even if _isOpen is true, since
+	// that might be changed in short order if the user is fiddling in the UI.
+	// These will be purged on clean() for open networks eventually.
+	_membershipCertificates[cert.issuedTo()] = cert;
 }
 }
 
 
 bool Network::isAllowed(const Address &peer) const
 bool Network::isAllowed(const Address &peer) const
@@ -175,80 +190,55 @@ bool Network::isAllowed(const Address &peer) const
 	try {
 	try {
 		Mutex::Lock _l(_lock);
 		Mutex::Lock _l(_lock);
 		if (_isOpen)
 		if (_isOpen)
-			return true;
+			return true; // network is public
 		std::map<Address,CertificateOfMembership>::const_iterator pc(_membershipCertificates.find(peer));
 		std::map<Address,CertificateOfMembership>::const_iterator pc(_membershipCertificates.find(peer));
 		if (pc == _membershipCertificates.end())
 		if (pc == _membershipCertificates.end())
-			return false;
-		return _myCertificate.agreesWith(pc->second);
+			return false; // no certificate on file
+		return _myCertificate.agreesWith(pc->second); // is other cert valid against ours?
 	} catch (std::exception &exc) {
 	} catch (std::exception &exc) {
 		TRACE("isAllowed() check failed for peer %s: unexpected exception: %s",peer.toString().c_str(),exc.what());
 		TRACE("isAllowed() check failed for peer %s: unexpected exception: %s",peer.toString().c_str(),exc.what());
 	} catch ( ... ) {
 	} catch ( ... ) {
 		TRACE("isAllowed() check failed for peer %s: unexpected exception: unknown exception",peer.toString().c_str());
 		TRACE("isAllowed() check failed for peer %s: unexpected exception: unknown exception",peer.toString().c_str());
 	}
 	}
-	return false;
+	return false; // default position on any failure
 }
 }
 
 
 void Network::clean()
 void Network::clean()
 {
 {
-	std::string mcdbPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".mcerts");
-
 	Mutex::Lock _l(_lock);
 	Mutex::Lock _l(_lock);
-
-	if ((!_id)||(_isOpen)) {
+	uint64_t timestampMaxDelta = _myCertificate.timestampMaxDelta();
+	if (_isOpen) {
+		// Open (public) networks do not track certs or cert pushes at all.
 		_membershipCertificates.clear();
 		_membershipCertificates.clear();
-		Utils::rm(mcdbPath);
-	} else {
-		FILE *mcdb = fopen(mcdbPath.c_str(),"wb");
-		bool writeError = false;
-		if (!mcdb) {
-			LOG("error: unable to open membership cert database at: %s",mcdbPath.c_str());
-		} else {
-			if ((writeError)||(fwrite("MCDB0",5,1,mcdb) != 1)) // version
-				writeError = true;
+		_lastPushedMembershipCertificate.clear();
+	} else if (timestampMaxDelta) {
+		// Clean certificates that are no longer valid from the cache.
+		for(std::map<Address,CertificateOfMembership>::iterator c=(_membershipCertificates.begin());c!=_membershipCertificates.end();) {
+			if (_myCertificate.agreesWith(c->second))
+				++c;
+			else _membershipCertificates.erase(c++);
 		}
 		}
 
 
-		for(std::map<Address,CertificateOfMembership>::iterator i=(_membershipCertificates.begin());i!=_membershipCertificates.end();) {
-			if (_myCertificate.agreesWith(i->second)) {
-				if ((!writeError)&&(mcdb)) {
-					char tmp[ZT_ADDRESS_LENGTH];
-					i->first.copyTo(tmp,ZT_ADDRESS_LENGTH);
-					if ((writeError)||(fwrite(tmp,ZT_ADDRESS_LENGTH,1,mcdb) != 1))
-						writeError = true;
-					std::string c(i->second.toString());
-					uint32_t cl = Utils::hton((uint32_t)c.length());
-					if ((writeError)||(fwrite(&cl,sizeof(cl),1,mcdb) != 1))
-						writeError = true;
-					if ((writeError)||(fwrite(c.data(),c.length(),1,mcdb) != 1))
-						writeError = true;
-				}
-				++i;
-			} else _membershipCertificates.erase(i++);
-		}
-
-		if (mcdb)
-			fclose(mcdb);
-		if (writeError) {
-			Utils::rm(mcdbPath);
-			LOG("error: unable to write to membership cert database at: %s",mcdbPath.c_str());
+		// Clean entries from the last pushed tracking map if they're so old as
+		// to be no longer relevant.
+		uint64_t forgetIfBefore = Utils::now() - (timestampMaxDelta * 3);
+		for(std::map<Address,uint64_t>::iterator lp(_lastPushedMembershipCertificate.begin());lp!=_lastPushedMembershipCertificate.end();) {
+			if (lp->second < forgetIfBefore)
+				_lastPushedMembershipCertificate.erase(lp++);
+			else ++lp;
 		}
 		}
 	}
 	}
 }
 }
 
 
-Network::Status Network::status() const
-{
-	Mutex::Lock _l(_lock);
-	if (_configuration)
-		return NETWORK_OK;
-	return NETWORK_WAITING_FOR_FIRST_AUTOCONF;
-}
-
 void Network::_CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data)
 void Network::_CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data)
 {
 {
-	if (!((Network *)arg)->_ready)
+	if (!((Network *)arg)->isUp())
 		return;
 		return;
+
 	const RuntimeEnvironment *_r = ((Network *)arg)->_r;
 	const RuntimeEnvironment *_r = ((Network *)arg)->_r;
 	if (_r->shutdownInProgress)
 	if (_r->shutdownInProgress)
 		return;
 		return;
+
 	try {
 	try {
 		_r->sw->onLocalEthernet(SharedPtr<Network>((Network *)arg),from,to,etherType,data);
 		_r->sw->onLocalEthernet(SharedPtr<Network>((Network *)arg),from,to,etherType,data);
 	} catch (std::exception &exc) {
 	} catch (std::exception &exc) {
@@ -261,17 +251,14 @@ void Network::_CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned
 void Network::_pushMembershipCertificate(const Address &peer,bool force,uint64_t now)
 void Network::_pushMembershipCertificate(const Address &peer,bool force,uint64_t now)
 {
 {
 	uint64_t timestampMaxDelta = _myCertificate.timestampMaxDelta();
 	uint64_t timestampMaxDelta = _myCertificate.timestampMaxDelta();
-	if (!timestampMaxDelta) {
-		LOG("unable to push my certificate to %s for network %.16llx: certificate invalid, missing required timestamp field",peer.toString().c_str(),_id);
-		return; // required field missing!
-	}
+	if (!timestampMaxDelta)
+		return; // still waiting on my own cert
 
 
 	uint64_t &lastPushed = _lastPushedMembershipCertificate[peer];
 	uint64_t &lastPushed = _lastPushedMembershipCertificate[peer];
 	if ((force)||((now - lastPushed) > (timestampMaxDelta / 2))) {
 	if ((force)||((now - lastPushed) > (timestampMaxDelta / 2))) {
 		lastPushed = now;
 		lastPushed = now;
 
 
 		Packet outp(peer,_r->identity.address(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE);
 		Packet outp(peer,_r->identity.address(),Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE);
-		outp.append((uint64_t)_id);
 		_myCertificate.serialize(outp);
 		_myCertificate.serialize(outp);
 		_r->sw->send(outp,true);
 		_r->sw->send(outp,true);
 	}
 	}
@@ -279,21 +266,114 @@ void Network::_pushMembershipCertificate(const Address &peer,bool force,uint64_t
 
 
 void Network::_restoreState()
 void Network::_restoreState()
 {
 {
-	std::string confPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".conf");
-	std::string confs;
-	if (Utils::readFile(confPath.c_str(),confs)) {
+	if (!_id)
+		return; // sanity check
+
+	Buffer<ZT_NETWORK_CERT_WRITE_BUF_SIZE> buf;
+
+	std::string idstr(idString());
+	std::string confPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idstr + ".conf");
+	std::string mcdbPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idstr + ".mcerts");
+
+	// Read configuration file containing last config from netconf master
+	{
+		std::string confs;
+		if (Utils::readFile(confPath.c_str(),confs)) {
+			try {
+				if (confs.length())
+					setConfiguration(Config(confs),false);
+			} catch ( ... ) {} // ignore invalid config on disk, we will re-request from netconf master
+		} else {
+			// If the conf file isn't present, "touch" it so we'll remember
+			// the existence of this network.
+			FILE *tmp = fopen(confPath.c_str(),"wb");
+			if (tmp)
+				fclose(tmp);
+		}
+	}
+
+	// Read most recent multicast cert dump
+	if ((!_isOpen)&&(Utils::fileExists(mcdbPath.c_str()))) {
+		CertificateOfMembership com;
+		Mutex::Lock _l(_lock);
+
+		_membershipCertificates.clear();
+
 		try {
 		try {
-			if (confs.length())
-				setConfiguration(Config(confs));
-		} catch ( ... ) {} // ignore invalid config on disk, we will re-request
-	} else {
-		// If the conf file isn't present, "touch" it so we'll remember
-		// the existence of this network.
-		FILE *tmp = fopen(confPath.c_str(),"w");
-		if (tmp)
-			fclose(tmp);
+			FILE *mcdb = fopen(mcdbPath.c_str(),"rb");
+			if (mcdb) {
+				for(;;) {
+					long rlen = (long)fread(buf.data() + buf.size(),1,ZT_NETWORK_CERT_WRITE_BUF_SIZE - buf.size(),mcdb);
+					if (rlen <= 0)
+						break;
+					buf.setSize(buf.size() + (unsigned int)rlen);
+					unsigned int ptr = 0;
+					while ((ptr < (ZT_NETWORK_CERT_WRITE_BUF_SIZE / 2))&&(ptr < buf.size())) {
+						ptr += com.deserialize(buf,ptr);
+						if (com.issuedTo())
+							_membershipCertificates[com.issuedTo()] = com;
+					}
+					if (ptr) {
+						memmove(buf.data(),buf.data() + ptr,buf.size() - ptr);
+						buf.setSize(buf.size() - ptr);
+					}
+				}
+			}
+		} catch ( ... ) {
+			// Membership cert dump file invalid. We'll re-learn them off the net.
+			_membershipCertificates.clear();
+			Utils::rm(mcdbPath);
+		}
 	}
 	}
-	// TODO: restore membership certs
+}
+
+void Network::_dumpMulticastCerts()
+{
+	Buffer<ZT_NETWORK_CERT_WRITE_BUF_SIZE> buf;
+	std::string mcdbPath(_r->homePath + ZT_PATH_SEPARATOR_S + "networks.d" + ZT_PATH_SEPARATOR_S + idString() + ".mcerts");
+	Mutex::Lock _l(_lock);
+
+	if ((!_id)||(_isOpen)) {
+		Utils::rm(mcdbPath);
+		return;
+	}
+
+	FILE *mcdb = fopen(mcdbPath.c_str(),"wb");
+	if (!mcdb)
+		return;
+	if (fwrite("ZTMCD0",6,1,mcdb) != 1) {
+		Utils::rm(mcdbPath);
+		return;
+	}
+
+	for(std::map<Address,CertificateOfMembership>::iterator c=(_membershipCertificates.begin());c!=_membershipCertificates.end();++c) {
+		try {
+			c->second.serialize(buf);
+			if (buf.size() >= (ZT_NETWORK_CERT_WRITE_BUF_SIZE / 2)) {
+				if (fwrite(buf.data(),buf.size(),1,mcdb) != 1) {
+					fclose(mcdb);
+					Utils::rm(mcdbPath);
+					return;
+				}
+				buf.clear();
+			}
+		} catch ( ... ) {
+			// Sanity check... no cert will ever be big enough to overflow buf
+			fclose(mcdb);
+			Utils::rm(mcdbPath);
+			return;
+		}
+	}
+
+	if (buf.size()) {
+		if (fwrite(buf.data(),buf.size(),1,mcdb) != 1) {
+			fclose(mcdb);
+			Utils::rm(mcdbPath);
+			return;
+		}
+	}
+
+	fclose(mcdb);
 }
 }
 
 
 } // namespace ZeroTier
 } // namespace ZeroTier

+ 83 - 8
node/Network.hpp

@@ -250,6 +250,28 @@ public:
 			else return CertificateOfMembership(cm->second);
 			else return CertificateOfMembership(cm->second);
 		}
 		}
 
 
+		/**
+		 * @return True if this network emulates IPv4 ARP for assigned addresses
+		 */
+		inline bool emulateArp() const
+		{
+			const_iterator e(find("eARP"));
+			if (e == end())
+				return false;
+			else return (e->second == "1");
+		}
+
+		/**
+		 * @return True if this network emulates IPv6 NDP for assigned addresses
+		 */
+		inline bool emulateNdp() const
+		{
+			const_iterator e(find("eNDP"));
+			if (e == end())
+				return false;
+			else return (e->second == "1");
+		}
+
 		/**
 		/**
 		 * @return Multicast rates for this network
 		 * @return Multicast rates for this network
 		 */
 		 */
@@ -343,7 +365,8 @@ public:
 	{
 	{
 		NETWORK_WAITING_FOR_FIRST_AUTOCONF,
 		NETWORK_WAITING_FOR_FIRST_AUTOCONF,
 		NETWORK_OK,
 		NETWORK_OK,
-		NETWORK_ACCESS_DENIED
+		NETWORK_ACCESS_DENIED,
+		NETWORK_NOT_FOUND
 	};
 	};
 
 
 	/**
 	/**
@@ -424,6 +447,26 @@ public:
 		return _isOpen;
 		return _isOpen;
 	}
 	}
 
 
+	/**
+	 * @return True if this network emulates IPv4 ARP for assigned addresses
+	 */
+	inline bool emulateArp() const
+		throw()
+	{
+		Mutex::Lock _l(_lock);
+		return _emulateArp;
+	}
+
+	/**
+	 * @return True if this network emulates IPv6 NDP for assigned addresses
+	 */
+	inline bool emulateNdp() const
+		throw()
+	{
+		Mutex::Lock _l(_lock);
+		return _emulateNdp;
+	}
+
 	/**
 	/**
 	 * Update multicast groups for this network's tap
 	 * Update multicast groups for this network's tap
 	 *
 	 *
@@ -451,8 +494,9 @@ public:
 	 * internally when an old config is reloaded from disk.
 	 * internally when an old config is reloaded from disk.
 	 *
 	 *
 	 * @param conf Configuration in key/value dictionary form
 	 * @param conf Configuration in key/value dictionary form
+	 * @param saveToDisk IF true (default), write config to disk
 	 */
 	 */
-	void setConfiguration(const Config &conf);
+	void setConfiguration(const Config &conf,bool saveToDisk = true);
 
 
 	/**
 	/**
 	 * Causes this network to request an updated configuration from its master node now
 	 * Causes this network to request an updated configuration from its master node now
@@ -460,14 +504,13 @@ public:
 	void requestConfiguration();
 	void requestConfiguration();
 
 
 	/**
 	/**
-	 * Add or update a peer's membership certificate
+	 * Add or update a membership certificate
 	 *
 	 *
 	 * The certificate must already have been validated via signature checking.
 	 * The certificate must already have been validated via signature checking.
 	 *
 	 *
-	 * @param peer Peer that owns certificate
-	 * @param cert Certificate itself
+	 * @param cert Certificate of membership
 	 */
 	 */
-	void addMembershipCertificate(const Address &peer,const CertificateOfMembership &cert);
+	void addMembershipCertificate(const CertificateOfMembership &cert);
 
 
 	/**
 	/**
 	 * Push our membership certificate to a peer
 	 * Push our membership certificate to a peer
@@ -523,10 +566,35 @@ public:
 	 */
 	 */
 	inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; }
 	inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; }
 
 
+	/** 
+	 * Force this network's status to a particular state based on config reply
+	 */
+	inline void forceStatusTo(const Status s)
+		throw()
+	{
+		Mutex::Lock _l(_lock);
+		_status = s;
+	}
+
 	/**
 	/**
 	 * @return Status of this network
 	 * @return Status of this network
 	 */
 	 */
-	Status status() const;
+	inline Status status() const
+		throw()
+	{
+		Mutex::Lock _l(_lock);
+		return _status;
+	}
+
+	/**
+	 * @return True if this network is in "OK" status and can accept traffic from us
+	 */
+	inline bool isUp() const
+		throw()
+	{
+		Mutex::Lock _l(_lock);
+		return ((_status == NETWORK_OK)&&(_ready));
+	}
 
 
 	/**
 	/**
 	 * Determine whether frames of a given ethernet type are allowed on this network
 	 * Determine whether frames of a given ethernet type are allowed on this network
@@ -567,9 +635,10 @@ public:
 	}
 	}
 
 
 	/**
 	/**
+	 * @param fromPeer Peer attempting to bridge other Ethernet peers onto network
 	 * @return True if this network allows bridging
 	 * @return True if this network allows bridging
 	 */
 	 */
-	inline bool permitsBridging() const
+	inline bool permitsBridging(const Address &fromPeer) const
 		throw()
 		throw()
 	{
 	{
 		return false; // TODO: bridging not implemented yet
 		return false; // TODO: bridging not implemented yet
@@ -589,6 +658,7 @@ private:
 	static void _CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data);
 	static void _CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data);
 	void _pushMembershipCertificate(const Address &peer,bool force,uint64_t now);
 	void _pushMembershipCertificate(const Address &peer,bool force,uint64_t now);
 	void _restoreState();
 	void _restoreState();
+	void _dumpMulticastCerts();
 
 
 	const RuntimeEnvironment *_r;
 	const RuntimeEnvironment *_r;
 
 
@@ -612,9 +682,14 @@ private:
 	MulticastRates _mcRates;
 	MulticastRates _mcRates;
 	std::set<InetAddress> _staticAddresses;
 	std::set<InetAddress> _staticAddresses;
 	bool _isOpen;
 	bool _isOpen;
+	bool _emulateArp;
+	bool _emulateNdp;
 	unsigned int _multicastPrefixBits;
 	unsigned int _multicastPrefixBits;
 	unsigned int _multicastDepth;
 	unsigned int _multicastDepth;
 
 
+	// Network status
+	Status _status;
+
 	// Ethertype whitelist bit field, set from config, for really fast lookup
 	// Ethertype whitelist bit field, set from config, for really fast lookup
 	unsigned char _etWhitelist[65536 / 8];
 	unsigned char _etWhitelist[65536 / 8];
 
 

+ 2 - 0
node/Node.cpp

@@ -246,6 +246,8 @@ static void _netconfServiceMessageHandler(void *renv,Service &svc,const Dictiona
 					const std::string &err = msg.get("error");
 					const std::string &err = msg.get("error");
 					if (err == "OBJ_NOT_FOUND")
 					if (err == "OBJ_NOT_FOUND")
 						errCode = Packet::ERROR_OBJ_NOT_FOUND;
 						errCode = Packet::ERROR_OBJ_NOT_FOUND;
+					else if (err == "ACCESS_DENIED")
+						errCode = Packet::ERROR_NETWORK_ACCESS_DENIED;
 
 
 					Packet outp(peerAddress,_r->identity.address(),Packet::VERB_ERROR);
 					Packet outp(peerAddress,_r->identity.address(),Packet::VERB_ERROR);
 					outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
 					outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);

+ 19 - 8
node/NodeConfig.cpp

@@ -125,7 +125,7 @@ void NodeConfig::clean()
 		n->second->clean();
 		n->second->clean();
 }
 }
 
 
-// Macro used in execute()
+// Macro used in execute() to push lines onto the return packet
 #undef _P
 #undef _P
 #define _P(f,...) { r.push_back(std::string()); Utils::stdsprintf(r.back(),(f),##__VA_ARGS__); }
 #define _P(f,...) { r.push_back(std::string()); Utils::stdsprintf(r.back(),(f),##__VA_ARGS__); }
 
 
@@ -161,18 +161,30 @@ std::vector<std::string> NodeConfig::execute(const char *command)
 	std::vector<std::string> r;
 	std::vector<std::string> r;
 	std::vector<std::string> cmd(Utils::split(command,"\r\n \t","\\","'"));
 	std::vector<std::string> cmd(Utils::split(command,"\r\n \t","\\","'"));
 
 
-	//
-	// Not coincidentally, response type codes correspond with HTTP
-	// status codes.
-	//
+	/* Not coincidentally, response type codes correspond with HTTP
+	 * status codes. Technically a little arbitrary, but would maybe
+	 * make things easier if we wanted to slap some kind of web API
+	 * in front of this thing. */
 
 
 	if ((cmd.empty())||(cmd[0] == "help")) {
 	if ((cmd.empty())||(cmd[0] == "help")) {
 		_P("200 help help");
 		_P("200 help help");
+		_P("200 help info");
 		_P("200 help listpeers");
 		_P("200 help listpeers");
 		_P("200 help listnetworks");
 		_P("200 help listnetworks");
 		_P("200 help join <network ID>");
 		_P("200 help join <network ID>");
 		_P("200 help leave <network ID>");
 		_P("200 help leave <network ID>");
 		_P("200 help terminate [<reason>]");
 		_P("200 help terminate [<reason>]");
+	} else if (cmd[0] == "info") {
+		bool isOnline = false;
+		uint64_t now = Utils::now();
+		std::vector< SharedPtr<Peer> > snp(_r->topology->supernodePeers());
+		for(std::vector< SharedPtr<Peer> >::const_iterator sn(snp.begin());sn!=snp.end();++sn) {
+			if ((*sn)->hasActiveDirectPath(now)) {
+				isOnline = true;
+				break;
+			}
+		}
+		_P("200 info %s %s %s",_r->identity.address().toString().c_str(),(isOnline ? "ONLINE" : "OFFLINE"),Node::versionString());
 	} else if (cmd[0] == "listpeers") {
 	} else if (cmd[0] == "listpeers") {
 		_P("200 listpeers <ztaddr> <ipv4> <ipv6> <latency> <version>");
 		_P("200 listpeers <ztaddr> <ipv4> <ipv6> <latency> <version>");
 		_r->topology->eachPeer(_DumpPeerStatistics(r));
 		_r->topology->eachPeer(_DumpPeerStatistics(r));
@@ -187,8 +199,7 @@ std::vector<std::string> NodeConfig::execute(const char *command)
 					tmp.push_back(',');
 					tmp.push_back(',');
 				tmp.append(i->toString());
 				tmp.append(i->toString());
 			}
 			}
-			// TODO: display network status, such as "permission denied to closed
-			// network" or "waiting".
+
 			_P("200 listnetworks %.16llx %s %s %s %s",
 			_P("200 listnetworks %.16llx %s %s %s %s",
 				(unsigned long long)nw->first,
 				(unsigned long long)nw->first,
 				Network::statusString(nw->second->status()),
 				Network::statusString(nw->second->status()),
@@ -202,7 +213,7 @@ std::vector<std::string> NodeConfig::execute(const char *command)
 			if (nwid > 0) {
 			if (nwid > 0) {
 				Mutex::Lock _l(_networks_m);
 				Mutex::Lock _l(_networks_m);
 				if (_networks.count(nwid)) {
 				if (_networks.count(nwid)) {
-					_P("400 already a member of %.16llx",(unsigned long long)nwid);
+					_P("409 already a member of %.16llx",(unsigned long long)nwid);
 				} else {
 				} else {
 					try {
 					try {
 						SharedPtr<Network> nw(Network::newInstance(_r,nwid));
 						SharedPtr<Network> nw(Network::newInstance(_r,nwid));

+ 1 - 0
node/Packet.cpp

@@ -63,6 +63,7 @@ const char *Packet::errorString(ErrorCode e)
 		case ERROR_IDENTITY_COLLISION: return "IDENTITY_COLLISION";
 		case ERROR_IDENTITY_COLLISION: return "IDENTITY_COLLISION";
 		case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION";
 		case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION";
 		case ERROR_NEED_MEMBERSHIP_CERTIFICATE: return "NEED_MEMBERSHIP_CERTIFICATE";
 		case ERROR_NEED_MEMBERSHIP_CERTIFICATE: return "NEED_MEMBERSHIP_CERTIFICATE";
+		case ERROR_NETWORK_ACCESS_DENIED: return "NETWORK_ACCESS_DENIED";
 	}
 	}
 	return "(unknown)";
 	return "(unknown)";
 }
 }

+ 13 - 5
node/Packet.hpp

@@ -56,6 +56,9 @@
  *   * New crypto completely changes key agreement cipher
  *   * New crypto completely changes key agreement cipher
  * 4 - 0.6.0 ...
  * 4 - 0.6.0 ...
  *   * New identity format based on hashcash design
  *   * New identity format based on hashcash design
+ *
+ * This isn't going to change again for a long time unless your
+ * author wakes up again at 4am with another great idea. :P
  */
  */
 #define ZT_PROTO_VERSION 4
 #define ZT_PROTO_VERSION 4
 
 
@@ -196,6 +199,8 @@
 #define ZT_PROTO_VERB_MULTICAST_FRAME_LEN_FRAME_LEN 2
 #define ZT_PROTO_VERB_MULTICAST_FRAME_LEN_FRAME_LEN 2
 #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME_LEN + ZT_PROTO_VERB_MULTICAST_FRAME_LEN_FRAME_LEN)
 #define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME_LEN + ZT_PROTO_VERB_MULTICAST_FRAME_LEN_FRAME_LEN)
 
 
+#define ZT_PROTO_VERB_NETWORK_MEMBERSHIP_CERTIFICATE_IDX_CERTIFICATE (ZT_PACKET_IDX_PAYLOAD)
+
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID + 8)
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID + 8)
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN + 2)
 #define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN + 2)
@@ -551,12 +556,12 @@ public:
 		 */
 		 */
 		VERB_MULTICAST_LIKE = 9,
 		VERB_MULTICAST_LIKE = 9,
 
 
-		/* Network member certificate for sending peer:
-		 *   <[8] 64-bit network ID>
+		/* Network member certificate:
 		 *   <[...] serialized certificate of membership>
 		 *   <[...] serialized certificate of membership>
 		 *
 		 *
-		 * OK is generated on acceptance. ERROR is returned on failure. In both
-		 * cases the payload is the network ID.
+		 * Certificate contains network ID, peer it was issued for, etc.
+		 *
+		 * OK/ERROR are not generated.
 		 */
 		 */
 		VERB_NETWORK_MEMBERSHIP_CERTIFICATE = 10,
 		VERB_NETWORK_MEMBERSHIP_CERTIFICATE = 10,
 
 
@@ -623,7 +628,10 @@ public:
 		ERROR_UNSUPPORTED_OPERATION = 5,
 		ERROR_UNSUPPORTED_OPERATION = 5,
 
 
 		/* Message to private network rejected -- no unexpired certificate on file */
 		/* Message to private network rejected -- no unexpired certificate on file */
-		ERROR_NEED_MEMBERSHIP_CERTIFICATE = 6
+		ERROR_NEED_MEMBERSHIP_CERTIFICATE = 6,
+
+		/* Tried to join network, but you're not a member */
+		ERROR_NETWORK_ACCESS_DENIED = 7
 	};
 	};
 
 
 	/**
 	/**

+ 106 - 43
node/PacketDecoder.cpp

@@ -64,6 +64,10 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
 			// packet and are waiting for the lookup of the original sender
 			// packet and are waiting for the lookup of the original sender
 			// for a multicast frame. So check to see if we've got it.
 			// for a multicast frame. So check to see if we've got it.
 			return _doMULTICAST_FRAME(_r,peer);
 			return _doMULTICAST_FRAME(_r,peer);
+		} else if (_step == DECODE_WAITING_FOR_NETWORK_MEMBERSHIP_CERTIFICATE_SIGNER_LOOKUP) {
+			// In this state we have already authenticated and decoded the
+			// packet and we're waiting for the identity of the cert's signer.
+			return _doNETWORK_MEMBERSHIP_CERTIFICATE(_r,peer);
 		}
 		}
 
 
 		if (!dearmor(peer->key())) {
 		if (!dearmor(peer->key())) {
@@ -134,15 +138,22 @@ bool PacketDecoder::_doERROR(const RuntimeEnvironment *_r,const SharedPtr<Peer>
 		switch(errorCode) {
 		switch(errorCode) {
 			case Packet::ERROR_OBJ_NOT_FOUND:
 			case Packet::ERROR_OBJ_NOT_FOUND:
 				if (inReVerb == Packet::VERB_WHOIS) {
 				if (inReVerb == Packet::VERB_WHOIS) {
-					// TODO: abort WHOIS if sender is a supernode
+					if (_r->topology->isSupernode(source()))
+						_r->sw->cancelWhoisRequest(Address(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH));
 				}
 				}
 				break;
 				break;
 			case Packet::ERROR_IDENTITY_COLLISION:
 			case Packet::ERROR_IDENTITY_COLLISION:
 				// TODO: if it comes from a supernode, regenerate a new identity
 				// TODO: if it comes from a supernode, regenerate a new identity
+				// if (_r->topology->isSupernode(source())) {}
 				break;
 				break;
-			case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE:
-				// TODO: send member certificate
-				break;
+			case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: {
+				// TODO: this allows anyone to request a membership cert, which is
+				// harmless until these contain possibly privacy-sensitive info.
+				// Then we'll need to be more careful.
+				SharedPtr<Network> network(_r->nc->network(at<uint64_t>(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD)));
+				if (network)
+					network->pushMembershipCertificate(source(),true,Utils::now());
+			}	break;
 			default:
 			default:
 				break;
 				break;
 		}
 		}
@@ -177,6 +188,9 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
 		SharedPtr<Peer> peer(_r->topology->getPeer(id.address()));
 		SharedPtr<Peer> peer(_r->topology->getPeer(id.address()));
 		if (peer) {
 		if (peer) {
 			if (peer->identity() != id) {
 			if (peer->identity() != id) {
+				// Sorry, someone beat you to that address. What are the odds?
+				// Well actually they're around two in 2^40. You should play
+				// the lottery.
 				unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
 				unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
 				if (_r->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
 				if (_r->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
 					TRACE("rejected HELLO from %s(%s): address already claimed",source().toString().c_str(),_remoteAddress.toString().c_str());
 					TRACE("rejected HELLO from %s(%s): address already claimed",source().toString().c_str(),_remoteAddress.toString().c_str());
@@ -189,8 +203,11 @@ bool PacketDecoder::_doHELLO(const RuntimeEnvironment *_r)
 					_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
 					_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
 				}
 				}
 				return true;
 				return true;
-			}
-		} else peer = _r->topology->addPeer(SharedPtr<Peer>(new Peer(_r->identity,id)));
+			} // else continue and send OK since we already know thee...
+		} else {
+			// Learn a new peer
+			peer = _r->topology->addPeer(SharedPtr<Peer>(new Peer(_r->identity,id)));
+		}
 
 
 		peer->onReceive(_r,_localPort,_remoteAddress,hops(),Packet::VERB_HELLO,Utils::now());
 		peer->onReceive(_r,_localPort,_remoteAddress,hops(),Packet::VERB_HELLO,Utils::now());
 		peer->setRemoteVersion(vMajor,vMinor,vRevision);
 		peer->setRemoteVersion(vMajor,vMinor,vRevision);
@@ -217,6 +234,7 @@ bool PacketDecoder::_doOK(const RuntimeEnvironment *_r,const SharedPtr<Peer> &pe
 {
 {
 	try {
 	try {
 		Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB];
 		Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB];
+		//TRACE("%s(%s): OK(%s)",source().toString().c_str(),_remoteAddress.toString().c_str(),Packet::verbString(inReVerb));
 		switch(inReVerb) {
 		switch(inReVerb) {
 			case Packet::VERB_HELLO: {
 			case Packet::VERB_HELLO: {
 				// OK from HELLO permits computation of latency.
 				// OK from HELLO permits computation of latency.
@@ -252,9 +270,7 @@ bool PacketDecoder::_doOK(const RuntimeEnvironment *_r,const SharedPtr<Peer> &pe
 					}
 					}
 				}
 				}
 			}	break;
 			}	break;
-			default:
-				//TRACE("%s(%s): OK(%s)",source().toString().c_str(),_remoteAddress.toString().c_str(),Packet::verbString(inReVerb));
-				break;
+			default: break;
 		}
 		}
 	} catch (std::exception &ex) {
 	} catch (std::exception &ex) {
 		TRACE("dropped OK from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
 		TRACE("dropped OK from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
@@ -412,12 +428,19 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
 		const unsigned int signatureLen = at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME + frameLen);
 		const unsigned int signatureLen = at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME + frameLen);
 		const unsigned char *const signature = field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME + frameLen + 2,signatureLen);
 		const unsigned char *const signature = field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME + frameLen + 2,signatureLen);
 
 
+		// Check multicast signature to verify original sender
 		const unsigned int signedPartLen = (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME - ZT_PROTO_VERB_MULTICAST_FRAME_IDX__START_OF_SIGNED_PORTION) + frameLen;
 		const unsigned int signedPartLen = (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME - ZT_PROTO_VERB_MULTICAST_FRAME_IDX__START_OF_SIGNED_PORTION) + frameLen;
 		if (!originPeer->identity().verify(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX__START_OF_SIGNED_PORTION,signedPartLen),signedPartLen,signature,signatureLen)) {
 		if (!originPeer->identity().verify(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX__START_OF_SIGNED_PORTION,signedPartLen),signedPartLen,signature,signatureLen)) {
 			TRACE("dropped MULTICAST_FRAME from %s(%s): failed signature verification, claims to be from %s",source().toString().c_str(),_remoteAddress.toString().c_str(),origin.toString().c_str());
 			TRACE("dropped MULTICAST_FRAME from %s(%s): failed signature verification, claims to be from %s",source().toString().c_str(),_remoteAddress.toString().c_str(),origin.toString().c_str());
 			return true;
 			return true;
 		}
 		}
 
 
+		// Security check to prohibit multicasts that are really Ethernet unicasts
+		if (!dest.mac().isMulticast()) {
+			TRACE("dropped MULTICAST_FRAME from %s(%s): %s is not a multicast/broadcast address",source().toString().c_str(),_remoteAddress.toString().c_str(),dest.mac().toString().c_str());
+			return true;
+		}
+
 #ifdef ZT_TRACE_MULTICAST
 #ifdef ZT_TRACE_MULTICAST
 		char mct[256];
 		char mct[256];
 		unsigned int startingFifoItems = 0;
 		unsigned int startingFifoItems = 0;
@@ -430,18 +453,11 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
 		_r->demarc->send(Demarc::ANY_PORT,ZT_DEFAULTS.multicastTraceWatcher,mct,strlen(mct),-1);
 		_r->demarc->send(Demarc::ANY_PORT,ZT_DEFAULTS.multicastTraceWatcher,mct,strlen(mct),-1);
 #endif
 #endif
 
 
-		// Security check to prohibit multicasts that are really Ethernet unicasts
-		if (!dest.mac().isMulticast()) {
-			TRACE("dropped MULTICAST_FRAME from %s(%s): %s is not a multicast/broadcast address",source().toString().c_str(),_remoteAddress.toString().c_str(),dest.mac().toString().c_str());
-			return true;
-		}
-
-		bool rateLimitsExceeded = false;
 		unsigned int maxDepth = ZT_MULTICAST_GLOBAL_MAX_DEPTH;
 		unsigned int maxDepth = ZT_MULTICAST_GLOBAL_MAX_DEPTH;
 		SharedPtr<Network> network(_r->nc->network(nwid));
 		SharedPtr<Network> network(_r->nc->network(nwid));
 
 
 		if ((origin == _r->identity.address())||(_r->mc->deduplicate(nwid,guid))) {
 		if ((origin == _r->identity.address())||(_r->mc->deduplicate(nwid,guid))) {
-			// Ordinary frames will drop duplicates. Supernodes keep propagating
+			// Ordinary nodes will drop duplicates. Supernodes keep propagating
 			// them since they're used as hubs to link disparate clusters of
 			// them since they're used as hubs to link disparate clusters of
 			// members of the same multicast group.
 			// members of the same multicast group.
 			if (!_r->topology->amSupernode()) {
 			if (!_r->topology->amSupernode()) {
@@ -453,16 +469,19 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
 				return true;
 				return true;
 			}
 			}
 		} else {
 		} else {
-			// Supernodes however won't do this more than once. If the supernode
-			// does happen to be a member of the network -- which is usually not
-			// true -- we don't want to see a ton of copies of the same frame on
-			// its tap device. Also double or triple counting bandwidth metrics
-			// for the same frame would not be fair.
+			// If we are actually a member of this network (will just about always
+			// be the case unless we're a supernode), check to see if we should
+			// inject the packet. This also gives us an opportunity to check things
+			// like multicast bandwidth constraints.
 			if (network) {
 			if (network) {
 				maxDepth = std::min((unsigned int)ZT_MULTICAST_GLOBAL_MAX_DEPTH,network->multicastDepth());
 				maxDepth = std::min((unsigned int)ZT_MULTICAST_GLOBAL_MAX_DEPTH,network->multicastDepth());
+				if (!maxDepth)
+					maxDepth = ZT_MULTICAST_GLOBAL_MAX_DEPTH;
+
 				if (!network->isAllowed(origin)) {
 				if (!network->isAllowed(origin)) {
 					TRACE("didn't inject MULTICAST_FRAME from %s(%s) into %.16llx: sender %s not allowed or we don't have a certificate",source().toString().c_str(),nwid,_remoteAddress.toString().c_str(),origin.toString().c_str());
 					TRACE("didn't inject MULTICAST_FRAME from %s(%s) into %.16llx: sender %s not allowed or we don't have a certificate",source().toString().c_str(),nwid,_remoteAddress.toString().c_str(),origin.toString().c_str());
 
 
+					// Tell them we need a certificate
 					Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
 					Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
 					outp.append((unsigned char)Packet::VERB_FRAME);
 					outp.append((unsigned char)Packet::VERB_FRAME);
 					outp.append(packetId());
 					outp.append(packetId());
@@ -473,32 +492,35 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
 
 
 					// We do not terminate here, since if the member just has an out of
 					// We do not terminate here, since if the member just has an out of
 					// date cert or hasn't sent us a cert yet we still want to propagate
 					// date cert or hasn't sent us a cert yet we still want to propagate
-					// the message so multicast works.
-				} else if ((!network->permitsBridging())&&(!origin.wouldHaveMac(sourceMac))) {
-					TRACE("didn't inject MULTICAST_FRAME from %s(%s) into %.16llx: source mac %s doesn't belong to %s, and bridging is not supported on network",source().toString().c_str(),nwid,_remoteAddress.toString().c_str(),sourceMac.toString().c_str(),origin.toString().c_str());
+					// the message so multicast keeps working downstream.
+				} else if ((!network->permitsBridging(origin))&&(!origin.wouldHaveMac(sourceMac))) {
+					// This *does* terminate propagation, since it's technically a
+					// security violation of the network's bridging policy. But if we
+					// were to keep propagating it wouldn't hurt anything, just waste
+					// bandwidth as everyone else would reject it too.
+					TRACE("dropped MULTICAST_FRAME from %s(%s) into %.16llx: source mac %s doesn't belong to %s, and bridging is not supported on network",source().toString().c_str(),nwid,_remoteAddress.toString().c_str(),sourceMac.toString().c_str(),origin.toString().c_str());
+					return true;
 				} else if (!network->permitsEtherType(etherType)) {
 				} else if (!network->permitsEtherType(etherType)) {
-					TRACE("didn't inject MULTICAST_FRAME from %s(%s) into %.16llx: ethertype %u is not allowed",source().toString().c_str(),nwid,_remoteAddress.toString().c_str(),etherType);
+					// Ditto for this-- halt propagation if this is for an ethertype
+					// this network doesn't allow. Same principle as bridging test.
+					TRACE("dropped MULTICAST_FRAME from %s(%s) into %.16llx: ethertype %u is not allowed",source().toString().c_str(),nwid,_remoteAddress.toString().c_str(),etherType);
+					return true;
 				} else if (!network->updateAndCheckMulticastBalance(origin,dest,frameLen)) {
 				} else if (!network->updateAndCheckMulticastBalance(origin,dest,frameLen)) {
-					rateLimitsExceeded = true;
+					// Rate limits can only be checked by members of this network, but
+					// there should be enough of them that over-limit multicasts get
+					// their propagation aborted.
+#ifdef ZT_TRACE_MULTICAST
+					Utils::snprintf(mct,sizeof(mct),"%c %s dropped %.16llx: rate limits exceeded",(_r->topology->amSupernode() ? 'S' : '-'),_r->identity.address().toString().c_str(),guid);
+					_r->demarc->send(Demarc::ANY_PORT,ZT_DEFAULTS.multicastTraceWatcher,mct,strlen(mct),-1);
+#endif
+					TRACE("dropped MULTICAST_FRAME from %s(%s): rate limits exceeded for sender %s",source().toString().c_str(),_remoteAddress.toString().c_str(),origin.toString().c_str());
+					return true;
 				} else {
 				} else {
 					network->tap().put(sourceMac,dest.mac(),etherType,frame,frameLen);
 					network->tap().put(sourceMac,dest.mac(),etherType,frame,frameLen);
 				}
 				}
 			}
 			}
 		}
 		}
 
 
-		// We can only really know if rate limit was exceeded if we're a member of
-		// this network. This will nearly always be true for anyone getting a
-		// multicast except supernodes, so the net effect will be to truncate
-		// multicast propagation if the rate limit is exceeded.
-		if (rateLimitsExceeded) {
-#ifdef ZT_TRACE_MULTICAST
-			Utils::snprintf(mct,sizeof(mct),"%c %s dropped %.16llx: rate limits exceeded",(_r->topology->amSupernode() ? 'S' : '-'),_r->identity.address().toString().c_str(),guid);
-			_r->demarc->send(Demarc::ANY_PORT,ZT_DEFAULTS.multicastTraceWatcher,mct,strlen(mct),-1);
-#endif
-			TRACE("dropped MULTICAST_FRAME from %s(%s): rate limits exceeded for sender %s",source().toString().c_str(),_remoteAddress.toString().c_str(),origin.toString().c_str());
-			return true;
-		}
-
 		if (depth == 0xffff) {
 		if (depth == 0xffff) {
 #ifdef ZT_TRACE_MULTICAST
 #ifdef ZT_TRACE_MULTICAST
 			Utils::snprintf(mct,sizeof(mct),"%c %s not forwarding %.16llx: depth == 0xffff (do not forward)",(_r->topology->amSupernode() ? 'S' : '-'),_r->identity.address().toString().c_str(),guid);
 			Utils::snprintf(mct,sizeof(mct),"%c %s not forwarding %.16llx: depth == 0xffff (do not forward)",(_r->topology->amSupernode() ? 'S' : '-'),_r->identity.address().toString().c_str(),guid);
@@ -550,7 +572,8 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
 			*(newFifoPtr++) = (unsigned char)0;
 			*(newFifoPtr++) = (unsigned char)0;
 
 
 		// If we're forwarding a packet within a private network that we are
 		// If we're forwarding a packet within a private network that we are
-		// a member of, also propagate our cert forward if needed.
+		// a member of, also propagate our cert if needed. This propagates
+		// it to everyone including people who will receive this multicast.
 		if (network)
 		if (network)
 			network->pushMembershipCertificate(newFifo,sizeof(newFifo),false,Utils::now());
 			network->pushMembershipCertificate(newFifo,sizeof(newFifo),false,Utils::now());
 
 
@@ -616,13 +639,52 @@ bool PacketDecoder::_doMULTICAST_LIKE(const RuntimeEnvironment *_r,const SharedP
 	} catch ( ... ) {
 	} catch ( ... ) {
 		TRACE("dropped MULTICAST_LIKE from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str());
 		TRACE("dropped MULTICAST_LIKE from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str());
 	}
 	}
-
 	return true;
 	return true;
 }
 }
 
 
 bool PacketDecoder::_doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
 bool PacketDecoder::_doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
 {
 {
-	// TODO: not implemented yet, will be needed for private networks.
+	try {
+		CertificateOfMembership com(*this,ZT_PROTO_VERB_NETWORK_MEMBERSHIP_CERTIFICATE_IDX_CERTIFICATE);
+		if (!com.hasRequiredFields()) {
+			TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): invalid cert: at least one required field is missing",source().toString().c_str(),_remoteAddress.toString().c_str());
+			return true;
+		} else if (com.signedBy()) {
+			SharedPtr<Peer> signer(_r->topology->getPeer(com.signedBy()));
+			if (signer) {
+				if (com.verify(signer->identity())) {
+					uint64_t nwid = com.networkId();
+					SharedPtr<Network> network(_r->nc->network(nwid));
+					if (network) {
+						if (network->controller() == signer) {
+							network->addMembershipCertificate(com);
+							return true;
+						} else {
+							TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): signer %s is not the controller for network %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),signer->address().toString().c_str(),(unsigned long long)nwid);
+							return true;
+						}
+					} else {
+						TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): not a member of network %.16llx",source().toString().c_str(),_remoteAddress.toString().c_str(),(unsigned long long)nwid);
+						return true;
+					}
+				} else {
+					TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): failed signature verification for signer %s",source().toString().c_str(),_remoteAddress.toString().c_str(),signer->address().toString().c_str());
+					return true;
+				}
+			} else {
+				_r->sw->requestWhois(com.signedBy());
+				_step = DECODE_WAITING_FOR_NETWORK_MEMBERSHIP_CERTIFICATE_SIGNER_LOOKUP;
+				return false;
+			}
+		} else {
+			TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): invalid cert: no signature",source().toString().c_str(),_remoteAddress.toString().c_str());
+			return true;
+		}
+	} catch (std::exception &ex) {
+		TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),ex.what());
+	} catch ( ... ) {
+		TRACE("dropped NETWORK_MEMBERSHIP_CERTIFICATE from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str());
+	}
 	return true;
 	return true;
 }
 }
 
 
@@ -644,6 +706,7 @@ bool PacketDecoder::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const
 			request["nwid"] = tmp;
 			request["nwid"] = tmp;
 			Utils::snprintf(tmp,sizeof(tmp),"%llx",(unsigned long long)packetId());
 			Utils::snprintf(tmp,sizeof(tmp),"%llx",(unsigned long long)packetId());
 			request["requestId"] = tmp;
 			request["requestId"] = tmp;
+			request["from"] = _remoteAddress.toString();
 			//TRACE("to netconf:\n%s",request.toString().c_str());
 			//TRACE("to netconf:\n%s",request.toString().c_str());
 			_r->netconfService->send(request);
 			_r->netconfService->send(request);
 		} else {
 		} else {

+ 1 - 0
node/PacketDecoder.hpp

@@ -131,6 +131,7 @@ private:
 	enum {
 	enum {
 		DECODE_WAITING_FOR_SENDER_LOOKUP, // on initial receipt, we need peer's identity
 		DECODE_WAITING_FOR_SENDER_LOOKUP, // on initial receipt, we need peer's identity
 		DECODE_WAITING_FOR_MULTICAST_FRAME_ORIGINAL_SENDER_LOOKUP,
 		DECODE_WAITING_FOR_MULTICAST_FRAME_ORIGINAL_SENDER_LOOKUP,
+		DECODE_WAITING_FOR_NETWORK_MEMBERSHIP_CERTIFICATE_SIGNER_LOOKUP
 	} _step;
 	} _step;
 
 
 	AtomicCounter __refCount;
 	AtomicCounter __refCount;

+ 6 - 0
node/Switch.cpp

@@ -466,6 +466,12 @@ void Switch::requestWhois(const Address &addr)
 		_sendWhoisRequest(addr,(const Address *)0,0);
 		_sendWhoisRequest(addr,(const Address *)0,0);
 }
 }
 
 
+void Switch::cancelWhoisRequest(const Address &addr)
+{
+	Mutex::Lock _l(_outstandingWhoisRequests_m);
+	_outstandingWhoisRequests.erase(addr);
+}
+
 void Switch::doAnythingWaitingForPeer(const SharedPtr<Peer> &peer)
 void Switch::doAnythingWaitingForPeer(const SharedPtr<Peer> &peer)
 {
 {
 	{
 	{

+ 7 - 0
node/Switch.hpp

@@ -179,6 +179,13 @@ public:
 	 */
 	 */
 	void requestWhois(const Address &addr);
 	void requestWhois(const Address &addr);
 
 
+	/**
+	 * Cancel WHOIS for an address
+	 *
+	 * @param addr Address to cancel
+	 */
+	void cancelWhoisRequest(const Address &addr);
+
 	/**
 	/**
 	 * Run any processes that are waiting for this peer
 	 * Run any processes that are waiting for this peer
 	 *
 	 *