Переглянути джерело

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

Adam Ierymenko 12 роки тому
батько
коміт
a53cfc9096
12 змінених файлів з 467 додано та 74 видалено
  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;