Quellcode durchsuchen

Network membership certificate work in progress... does not build yet.

Adam Ierymenko vor 12 Jahren
Ursprung
Commit
a53cfc9096
12 geänderte Dateien mit 467 neuen und 74 gelöschten Zeilen
  1. 1 1
      Makefile.linux
  2. 1 1
      Makefile.mac
  3. 29 5
      node/Address.hpp
  4. 72 1
      node/Dictionary.hpp
  5. 3 3
      node/Identity.cpp
  6. 83 4
      node/Network.cpp
  7. 215 47
      node/Network.hpp
  8. 1 1
      node/NodeConfig.cpp
  9. 2 1
      node/Packet.cpp
  10. 39 10
      node/Packet.hpp
  11. 18 0
      node/PacketDecoder.cpp
  12. 3 0
      node/PacketDecoder.hpp

+ 1 - 1
Makefile.linux

@@ -20,7 +20,7 @@ CXXFLAGS=$(CFLAGS) -fno-rtti
 # separate binaries for the RedHat and Debian universes to distribute via
 # auto-update. This way we get one Linux binary for all systems of a given
 # architecture.
-LIBS=ext/bin/libcrypto/linux-$(ARCH)/libcrypto.a -ldl
+LIBS=ext/bin/libcrypto/linux-$(ARCH)/libcrypto.a -lm
 
 include objects.mk
 

+ 1 - 1
Makefile.mac

@@ -13,7 +13,7 @@ STRIP=strip
 #STRIP=echo
 
 CXXFLAGS=$(CFLAGS) -fno-rtti
-LIBS=-lcrypto -ldl
+LIBS=-lcrypto -lm
 
 include objects.mk
 

+ 29 - 5
node/Address.hpp

@@ -64,13 +64,28 @@ public:
 	{
 	}
 
+	Address(const char *s)
+		throw()
+	{
+		unsigned char foo[ZT_ADDRESS_LENGTH];
+		setTo(foo,Utils::unhex(s,foo,ZT_ADDRESS_LENGTH));
+	}
+
+	Address(const std::string &s)
+		throw()
+	{
+		unsigned char foo[ZT_ADDRESS_LENGTH];
+		setTo(foo,Utils::unhex(s.c_str(),foo,ZT_ADDRESS_LENGTH));
+	}
+
 	/**
 	 * @param bits Raw address -- 5 bytes, big-endian byte order
+	 * @param len Length of array
 	 */
-	Address(const void *bits)
+	Address(const void *bits,unsigned int len)
 		throw()
 	{
-		setTo(bits);
+		setTo(bits,len);
 	}
 
 	inline Address &operator=(const Address &a)
@@ -89,10 +104,15 @@ public:
 
 	/**
 	 * @param bits Raw address -- 5 bytes, big-endian byte order
+	 * @param len Length of array
 	 */
-	inline void setTo(const void *bits)
+	inline void setTo(const void *bits,unsigned int len)
 		throw()
 	{
+		if (len < ZT_ADDRESS_LENGTH) {
+			_a = 0;
+			return;
+		}
 		const unsigned char *b = (const unsigned char *)bits;
 		uint64_t a = ((uint64_t)*b++) << 32;
 		a |= ((uint64_t)*b++) << 24;
@@ -104,10 +124,13 @@ public:
 
 	/**
 	 * @param bits Buffer to hold 5-byte address in big-endian byte order
+	 * @param len Length of array
 	 */
-	inline void copyTo(void *bits) const
+	inline void copyTo(void *bits,unsigned int len) const
 		throw()
 	{
+		if (len < ZT_ADDRESS_LENGTH)
+			return;
 		unsigned char *b = (unsigned char *)bits;
 		*(b++) = (unsigned char)((_a >> 32) & 0xff);
 		*(b++) = (unsigned char)((_a >> 24) & 0xff);
@@ -164,7 +187,8 @@ public:
 		throw()
 	{
 		MAC m;
-		copyTo(m.data);
+		m.data[0] = ZT_MAC_FIRST_OCTET;
+		copyTo(m.data + 1,ZT_ADDRESS_LENGTH);
 		return m;
 	}
 

+ 72 - 1
node/Dictionary.hpp

@@ -30,6 +30,7 @@
 
 #include <string>
 #include <map>
+#include <stdexcept>
 #include "Constants.hpp"
 
 namespace ZeroTier {
@@ -38,11 +39,76 @@ namespace ZeroTier {
  * Simple key/value dictionary with string serialization
  *
  * The serialization format is a flat key=value with backslash escape.
- * It does not support comments or other syntactic complexities.
+ * It does not support comments or other syntactic complexities. It is
+ * human-readable if the keys and values in the dictionary are also
+ * human-readable. Otherwise it might contain unprintable characters.
  */
 class Dictionary : public std::map<std::string,std::string>
 {
 public:
+	Dictionary()
+	{
+	}
+
+	/**
+	 * @param s String-serialized dictionary
+	 */
+	Dictionary(const char *s)
+	{
+		fromString(s);
+	}
+
+	/**
+	 * @param s String-serialized dictionary
+	 */
+	Dictionary(const std::string &s)
+	{
+		fromString(s.c_str());
+	}
+
+	/**
+	 * Get a key, throwing an exception if it is not present
+	 *
+	 * @param key Key to look up
+	 * @return Reference to value
+	 * @throws std::invalid_argument Key not found
+	 */
+	inline const std::string &get(const std::string &key) const
+		throw(std::invalid_argument)
+	{
+		const_iterator e(find(key));
+		if (e == end())
+			throw std::invalid_argument(std::string("missing required field: ")+key);
+		return e->second;
+	}
+
+	/**
+	 * Get a key, returning a default if not present
+	 *
+	 * @param key Key to look up
+	 * @param dfl Default if not present
+	 * @return Value or default
+	 */
+	inline const std::string &get(const std::string &key,const std::string &dfl) const
+	{
+		const_iterator e(find(key));
+		if (e == end())
+			return dfl;
+		return e->second;
+	}
+
+	/**
+	 * @param key Key to check
+	 * @return True if dictionary contains key
+	 */
+	inline bool contains(const std::string &key) const
+	{
+		return (find(key) != end());
+	}
+
+	/**
+	 * @return String-serialized dictionary
+	 */
 	inline std::string toString() const
 	{
 		std::string s;
@@ -57,6 +123,11 @@ public:
 		return s;
 	}
 
+	/**
+	 * Clear and initialize from a string
+	 *
+	 * @param s String-serialized dictionary
+	 */
 	inline void fromString(const char *s)
 	{
 		clear();

+ 3 - 3
node/Identity.cpp

@@ -58,7 +58,7 @@ void Identity::generate()
 	// invalid. Of course, deep verification of address/key relationship is
 	// required to cover the more elaborate address claim jump attempt case.
 	unsigned char atmp[ZT_ADDRESS_LENGTH];
-	_address.copyTo(atmp);
+	_address.copyTo(atmp,ZT_ADDRESS_LENGTH);
 	SHA256_CTX sha;
 	unsigned char dig[32];
 	unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0;
@@ -76,7 +76,7 @@ void Identity::generate()
 bool Identity::locallyValidate(bool doAddressDerivationCheck) const
 {
 	unsigned char atmp[ZT_ADDRESS_LENGTH];
-	_address.copyTo(atmp);
+	_address.copyTo(atmp,ZT_ADDRESS_LENGTH);
 	SHA256_CTX sha;
 	unsigned char dig[32];
 	unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0;
@@ -222,7 +222,7 @@ Address Identity::deriveAddress(const void *keyBytes,unsigned int keyLen)
 
 	delete [] ram;
 
-	return Address(dig); // first 5 bytes of dig[]
+	return Address(dig,ZT_ADDRESS_LENGTH); // first 5 bytes of dig[]
 }
 
 std::string Identity::encrypt(const Identity &to,const void *data,unsigned int len) const

+ 83 - 4
node/Network.cpp

@@ -25,19 +25,90 @@
  * LLC. Start here: http://www.zerotier.com/
  */
 
+#include <stdlib.h>
+#include <math.h>
+
+#include <openssl/sha.h>
+
+#include "RuntimeEnvironment.hpp"
+#include "NodeConfig.hpp"
 #include "Network.hpp"
 #include "Switch.hpp"
 
 namespace ZeroTier {
 
+void Network::Certificate::sign(const Identity &with)
+{
+	unsigned char dig[32];
+	SHA256_CTX sha;
+	SHA256_Init(&sha);
+	unsigned char zero = 0;
+	for(const_iterator i(begin());i!=end();++i) {
+		if (i->first != "sig") {
+			SHA256_Update(&sha,&zero,1);
+			SHA256_Update(&sha,(const unsigned char *)i->first.data(),i->first.length());
+			SHA256_Update(&sha,&zero,1);
+			SHA256_Update(&sha,(const unsigned char *)i->second.data(),i->second.length());
+			SHA256_Update(&sha,&zero,1);
+		}
+	}
+	SHA256_Final(dig,&sha);
+	(*this)["sig"] = with.sign(dig);
+}
+
+static const std::string _DELTA_PREFIX("~");
+bool Network::Certificate::qualifyMembership(const Network::Certificate &mc) const
+{
+	// Note: optimization probably needed here, probably via some kind of
+	// memoization / dynamic programming.
+
+	for(const_iterator myField(begin());myField!=end();++myField) {
+		if (!((myField->first.length() > 1)&&(myField->first[0] == '~'))) { // ~fields are max delta range specs
+			// If they lack the same field, comparison fails.
+			const_iterator theirField(mc.find(myField->first));
+			if (theirField == mc.end())
+				return false;
+
+			const_iterator deltaField(find(_DELTA_PREFIX + myField->first));
+			if (deltaField == end()) {
+				// If there is no delta, compare for equality (e.g. node, nwid)
+				if (myField->second != theirField->second)
+					return false;
+			} else {
+				// Otherwise compare range with max delta. Presence of a dot in delta
+				// indicates a floating point comparison. Otherwise an integer
+				// comparison occurs.
+				if (deltaField->second.find('.') != std::string::npos) {
+					double my = strtod(myField->second.c_str(),(char **)0);
+					double their = strtod(theirField->second.c_str(),(char **)0);
+					double delta = strtod(deltaField->second.c_str(),(char **)0);
+					if (fabs(my - their) > delta)
+						return false;
+				} else {
+					int64_t my = strtoll(myField->second.c_str(),(char **)0,10);
+					int64_t their = strtoll(theirField->second.c_str(),(char **)0,10);
+					int64_t delta = strtoll(deltaField->second.c_str(),(char **)0,10);
+					if (my > their) {
+						if ((my - their) > delta)
+							return false;
+					} else {
+						if ((their - my) > delta)
+							return false;
+					}
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
 Network::Network(const RuntimeEnvironment *renv,uint64_t id)
 	throw(std::runtime_error) :
 	_r(renv),
-	_id(id),
 	_tap(renv,renv->identity.address().toMAC(),ZT_IF_MTU,&_CBhandleTapData,this),
-	_members(),
-	_open(false),
-	_lock()
+	_id(id),
+	_isOpen(false)
 {
 }
 
@@ -45,6 +116,14 @@ Network::~Network()
 {
 }
 
+void Network::setConfiguration(const Network::Config &conf)
+{
+}
+
+void Network::requestConfiguration()
+{
+}
+
 void Network::_CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data)
 {
 	const RuntimeEnvironment *_r = ((Network *)arg)->_r;

+ 215 - 47
node/Network.hpp

@@ -30,28 +30,37 @@
 
 #include <string>
 #include <set>
+#include <map>
 #include <vector>
 #include <stdexcept>
+
+#include "Constants.hpp"
+#include "Utils.hpp"
 #include "EthernetTap.hpp"
 #include "Address.hpp"
 #include "Mutex.hpp"
-#include "InetAddress.hpp"
-#include "Constants.hpp"
 #include "SharedPtr.hpp"
 #include "AtomicCounter.hpp"
-#include "RuntimeEnvironment.hpp"
 #include "MulticastGroup.hpp"
 #include "NonCopyable.hpp"
 #include "MAC.hpp"
+#include "Dictionary.hpp"
+#include "Identity.hpp"
+#include "InetAddress.hpp"
 
 namespace ZeroTier {
 
+class RuntimeEnvironment;
 class NodeConfig;
 
 /**
  * A virtual LAN
  *
- * Networks can be open or closed.
+ * Networks can be open or closed. Each network has an ID whose most
+ * significant 40 bits are the ZeroTier address of the node that should
+ * be contacted for network configuration. The least significant 24
+ * bits are arbitrary, allowing up to 2^24 networks per managing
+ * node.
  *
  * Open networks do not track membership. Anyone is allowed to communicate
  * over them.
@@ -69,7 +78,183 @@ class Network : NonCopyable
 	friend class SharedPtr<Network>;
 	friend class NodeConfig;
 
+public:
+	/**
+	 * A certificate of network membership
+	 */
+	class Certificate : private Dictionary
+	{
+	public:
+		Certificate()
+		{
+		}
+
+		Certificate(const char *s) :
+			Dictionary(s)
+		{
+		}
+
+		Certificate(const std::string &s) :
+			Dictionary(s)
+		{
+		}
+
+		/**
+		 * @return Read-only underlying dictionary
+		 */
+		inline const Dictionary &dictionary() const { return *this; }
+
+		inline void setNetworkId(uint64_t id)
+		{
+			char buf[32];
+			sprintf(buf,"%llu",id);
+			(*this)["nwid"] = buf;
+		}
+
+		inline uint64_t networkId() const
+			throw(std::invalid_argument)
+		{
+			return strtoull(get("nwid").c_str(),(char **)0,10);
+		}
+
+		inline void setPeerAddress(Address &a)
+		{
+			(*this)["peer"] = a.toString();
+		}
+
+		inline Address peerAddress() const
+			throw(std::invalid_argument)
+		{
+			return Address(get("peer"));
+		}
+
+		/**
+		 * Set the timestamp and max-delta
+		 *
+		 * @param ts Timestamp in ms since epoch
+		 * @param maxDelta Maximum difference between two peers on the same network
+		 */
+		inline void setTimestamp(uint64_t ts,uint64_t maxDelta)
+		{
+			char foo[32];
+			sprintf(foo,"%llu",ts);
+			(*this)["ts"] = foo;
+			sprintf(foo,"%llu",maxDelta);
+			(*this)["~ts"] = foo;
+		}
+
+		/**
+		 * Set or update the sig field to contain a signature
+		 *
+		 * @param with Signing identity -- the identity of this network's controller
+		 */
+		void sign(const Identity &with);
+
+		/**
+		 * Check if another peer is indeed a current member of this network
+		 *
+		 * Fields with companion ~fields are compared with the defined maximum
+		 * delta in this certificate. Fields without ~fields are compared for
+		 * equality.
+		 *
+		 * This does not verify the certificate's signature! The signature
+		 * must be verified first.
+		 * 
+		 * @param mc Peer membership certificate
+		 * @return True if mc's membership in this network is current
+		 */
+		bool qualifyMembership(const Certificate &mc) const;
+	};
+
+	/**
+	 * A network configuration for a given node
+	 */
+	class Config : private Dictionary
+	{
+	public:
+		Config()
+		{
+		}
+
+		Config(const char *s) :
+			Dictionary(s)
+		{
+		}
+
+		Config(const std::string &s) :
+			Dictionary(s)
+		{
+		}
+
+		/**
+		 * @return Certificate of membership for this network, or empty cert if none
+		 */
+		inline Certificate certificateOfMembership() const
+		{
+			return Certificate(get("com",""));
+		}
+
+		/**
+		 * @return True if this is an open non-access-controlled network
+		 */
+		inline bool isOpen() const
+		{
+			return (get("isOpen","0") == "1");
+		}
+
+		/**
+		 * @return All static addresses / netmasks, IPv4 or IPv6
+		 */
+		inline std::set<InetAddress> staticAddresses() const
+		{
+			std::set<InetAddress> sa;
+			std::vector<std::string> ips(Utils::split(get("ipv4Static","").c_str(),",","",""));
+			for(std::vector<std::string>::const_iterator i(ips.begin());i!=ips.end();++i)
+				sa.insert(InetAddress(*i));
+			ips = Utils::split(get("ipv6Static","").c_str(),",","","");
+			for(std::vector<std::string>::const_iterator i(ips.begin());i!=ips.end();++i)
+				sa.insert(InetAddress(*i));
+			return sa;
+		}
+
+		/**
+		 * Set static IPv4 and IPv6 addresses
+		 *
+		 * This sets the ipv4Static and ipv6Static fields to comma-delimited
+		 * lists of assignments. The port field in InetAddress must be the
+		 * number of bits in the netmask.
+		 *
+		 * @param begin Start of container or array of addresses (InetAddress)
+		 * @param end End of container or array of addresses (InetAddress)
+		 * @tparam I Type of container or array
+		 */
+		template<typename I>
+		inline void setStaticInetAddresses(const I &begin,const I &end)
+		{
+			std::string v4;
+			std::string v6;
+			for(I i(begin);i!=end;++i) {
+				if (i->isV4()) {
+					if (v4.length())
+						v4.push_back(',');
+					v4.append(i->toString());
+				} else if (i->isV6()) {
+					if (v6.length())
+						v6.push_back(',');
+					v6.append(i->toString());
+				}
+			}
+			if (v4.length())
+				(*this)["ipv4Static"] = v4;
+			else erase("ipv4Static");
+			if (v6.length())
+				(*this)["ipv6Static"] = v6;
+			else erase("ipv6Static");
+		}
+	};
+
 private:
+	// Only NodeConfig can create, only SharedPtr can delete
 	Network(const RuntimeEnvironment *renv,uint64_t id)
 		throw(std::runtime_error);
 
@@ -87,56 +272,22 @@ public:
 	inline EthernetTap &tap() throw() { return _tap; }
 
 	/**
-	 * Get this network's members
-	 * 
-	 * If this is an open network, membership isn't relevant and this doesn't
-	 * mean much. If it's a closed network, frames will only be exchanged to/from
-	 * members.
-	 * 
-	 * @return Members of this network
+	 * @return Address of network's controlling node
 	 */
-	inline std::set<Address> members() const
-	{
-		Mutex::Lock _l(_lock);
-		return _members;
-	}
-
-	/**
-	 * @param addr Address to check
-	 * @return True if address is a member
-	 */
-	inline bool isMember(const Address &addr) const
-		throw()
-	{
-		Mutex::Lock _l(_lock);
-		return (_members.count(addr) > 0);
-	}
-
-	/**
-	 * Shortcut to check open() and then isMember()
-	 * 
-	 * @param addr Address to check
-	 * @return True if network is open or if address is a member
-	 */
-	inline bool isAllowed(const Address &addr) const
-		throw()
-	{
-		Mutex::Lock _l(_lock);
-		return ((_open)||(_members.count(addr) > 0));
-	}
+	inline Address controller() throw() { return Address(_id >> 24); }
 
 	/**
 	 * @return True if network is open (no membership required)
 	 */
-	inline bool open() const
+	inline bool isOpen() const
 		throw()
 	{
 		Mutex::Lock _l(_lock);
-		return _open;
+		return _isOpen;
 	}
 
 	/**
-	 * Update internal multicast group set and return true if changed
+	 * Update multicast groups for this network's tap
 	 *
 	 * @return True if internal multicast group set has changed
 	 */
@@ -147,7 +298,7 @@ public:
 	}
 
 	/**
-	 * @return Latest set of multicast groups
+	 * @return Latest set of multicast groups for this network's tap
 	 */
 	inline std::set<MulticastGroup> multicastGroups() const
 	{
@@ -155,15 +306,32 @@ public:
 		return _multicastGroups;
 	}
 
+	/**
+	 * Set or update this network's configuration
+	 *
+	 * This is called by PacketDecoder when an update comes over the wire, or
+	 * internally when an old config is reloaded from disk.
+	 *
+	 * @param conf Configuration in key/value dictionary form
+	 */
+	void setConfiguration(const Config &conf);
+
+	/**
+	 * Causes this network to request an updated configuration from its master node now
+	 */
+	void requestConfiguration();
+
 private:
 	static void _CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data);
 
 	const RuntimeEnvironment *_r;
-	uint64_t _id;
+
 	EthernetTap _tap;
-	std::set<Address> _members;
 	std::set<MulticastGroup> _multicastGroups;
-	bool _open;
+
+	uint64_t _id;
+	bool _isOpen;
+
 	Mutex _lock;
 
 	AtomicCounter __refCount;

+ 1 - 1
node/NodeConfig.cpp

@@ -126,7 +126,7 @@ std::vector<std::string> NodeConfig::execute(const char *command)
 			_P("200 listnetworks %llu %s %s",
 				nw->first,
 				nw->second->tap().deviceName().c_str(),
-				(nw->second->open() ? "public" : "private"));
+				(nw->second->isOpen() ? "public" : "private"));
 		}
 	} else if (cmd[0] == "join") {
 		_P("404 join Not implemented yet.");

+ 2 - 1
node/Packet.cpp

@@ -43,6 +43,8 @@ const char *Packet::verbString(Verb v)
 		case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME";
 		case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE";
 		case VERB_NETWORK_PERMISSION_CERTIFICATE: return "NETWORK_PERMISSION_CERTIFICATE";
+		case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST";
+		case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH";
 	}
 	return "(unknown)";
 }
@@ -59,7 +61,6 @@ const char *Packet::errorString(ErrorCode e)
 		case ERROR_IDENTITY_INVALID: return "IDENTITY_INVALID";
 		case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION";
 		case ERROR_NO_NETWORK_CERTIFICATE_ON_FILE: return "NO_NETWORK_CERTIFICATE_ON_FILE";
-		case ERROR_OBJECT_EXPIRED: return "OBJECT_EXPIRED";
 	}
 	return "(unknown)";
 }

+ 39 - 10
node/Packet.hpp

@@ -287,7 +287,7 @@ public:
 		 * 
 		 * @return Destination ZT address
 		 */
-		inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH)); }
+		inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
 
 		/**
 		 * @return True if fragment is of a valid length
@@ -468,8 +468,9 @@ public:
 		/* Network permission certificate:
 		 *   <[8] 64-bit network ID>
 		 *   <[1] flags (currently unused, must be 0)>
-		 *   <[8] certificate timestamp>
-		 *   <[8] 16-bit length of signature>
+		 *   <[2] 16-bit length of qualifying fields>
+		 *   <[...] string-serialized dictionary of qualifying fields>
+		 *   <[2] 16-bit length of signature>
 		 *   <[...] ECDSA signature of my binary serialized identity and timestamp>
 		 *
 		 * This message is used to send ahead of time a certificate proving
@@ -478,7 +479,38 @@ public:
 		 * OK is generated on acceptance. ERROR is returned on failure. In both
 		 * cases the payload is the network ID.
 		 */
-		VERB_NETWORK_PERMISSION_CERTIFICATE = 10
+		VERB_NETWORK_PERMISSION_CERTIFICATE = 10,
+
+		/* Network configuration request:
+		 *   <[8] 64-bit network ID>
+		 *
+		 * This message requests network configuration from a node capable of
+		 * providing it. Such nodes run the netconf service, which must be
+		 * installed into the ZeroTier home directory.
+		 *
+		 * OK response payload:
+		 *   <[8] 64-bit network ID>
+		 *   <[...] network configuration dictionary>
+		 *
+		 * OK returns a Dictionary (string serialized) containing the network's
+		 * configuration and IP address assignment information for the querying
+		 * node.
+		 *
+		 * ERROR may be NOT_FOUND if no such network is known, or
+		 * UNSUPPORTED_OPERATION if the netconf service isn't available. The
+		 * payload will be the network ID.
+		 */
+		VERB_NETWORK_CONFIG_REQUEST = 11,
+
+		/* Network configuration refresh request:
+		 *   <[8] 64-bit network ID>
+		 *
+		 * This message can be sent by the network configuration master node
+		 * to request that nodes refresh their network configuration.
+		 *
+		 * It is only a hint and does not presently elicit a response.
+		 */
+		VERB_NETWORK_CONFIG_REFRESH = 12
 	};
 
 	/**
@@ -508,10 +540,7 @@ public:
 		ERROR_UNSUPPORTED_OPERATION = 6,
 
 		/* Message to private network rejected -- no unexpired certificate on file */
-		ERROR_NO_NETWORK_CERTIFICATE_ON_FILE = 7,
-
-		/* Object is expired (e.g. network certificate) */
-		ERROR_OBJECT_EXPIRED = 8
+		ERROR_NO_NETWORK_CERTIFICATE_ON_FILE = 7
 	};
 
 	/**
@@ -624,14 +653,14 @@ public:
 	 * 
 	 * @return Destination ZT address
 	 */
-	inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH)); }
+	inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
 
 	/**
 	 * Get this packet's source
 	 * 
 	 * @return Source ZT address
 	 */
-	inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH)); }
+	inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
 
 	/**
 	 * @return True if packet is of valid length

+ 18 - 0
node/PacketDecoder.cpp

@@ -102,6 +102,12 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
 				return _doMULTICAST_LIKE(_r,peer);
 			case Packet::VERB_MULTICAST_FRAME:
 				return _doMULTICAST_FRAME(_r,peer);
+			case Packet::VERB_NETWORK_PERMISSION_CERTIFICATE:
+				return _doNETWORK_PERMISSION_CERTIFICATE(_r,peer);
+			case Packet::VERB_NETWORK_CONFIG_REQUEST:
+				return _doNETWORK_CONFIG_REQUEST(_r,peer);
+			case Packet::VERB_NETWORK_CONFIG_REFRESH:
+				return _doNETWORK_CONFIG_REFRESH(_r,peer);
 			default:
 				// This might be something from a new or old version of the protocol.
 				// Technically it passed HMAC so the packet is still valid, but we
@@ -538,4 +544,16 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
 	return true;
 }
 
+bool PacketDecoder::_doNETWORK_PERMISSION_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
+{
+}
+
+bool PacketDecoder::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
+{
+}
+
+bool PacketDecoder::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
+{
+}
+
 } // namespace ZeroTier

+ 3 - 0
node/PacketDecoder.hpp

@@ -122,6 +122,9 @@ private:
 	bool _doFRAME(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
 	bool _doMULTICAST_LIKE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
 	bool _doMULTICAST_FRAME(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
+	bool _doNETWORK_PERMISSION_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
+	bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
+	bool _doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
 
 	uint64_t _receiveTime;
 	Demarc::Port _localPort;