From 2ea9f516e121ea6eb344a8d180a739a1d707aecb Mon Sep 17 00:00:00 2001
From: Adam Ierymenko <adam.ierymenko@gmail.com>
Date: Fri, 18 Nov 2016 12:59:04 -0800
Subject: [PATCH] Rate gate expensive validation of new identities in HELLO.

---
 node/Constants.hpp      | 20 ++++++++++++++++++++
 node/IncomingPacket.cpp | 10 +++++++++-
 node/InetAddress.hpp    | 24 ++++++++++++++++++++++++
 node/Node.cpp           |  1 +
 node/Node.hpp           | 22 ++++++++++++++++++++++
 selftest.cpp            | 11 +++++++++++
 6 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/node/Constants.hpp b/node/Constants.hpp
index 6400e2895..8803eceeb 100644
--- a/node/Constants.hpp
+++ b/node/Constants.hpp
@@ -375,6 +375,26 @@
  */
 #define ZT_PEER_GENERAL_RATE_LIMIT 1000
 
+/**
+ * Don't do expensive identity validation more often than this
+ *
+ * IPv4 and IPv6 address prefixes are hashed down to 14-bit (0-16383) integers
+ * using the first 24 bits for IPv4 or the first 48 bits for IPv6. These are
+ * then rate limited to one identity validation per this often milliseconds.
+ */
+#if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64) || defined(_M_AMD64))
+// AMD64 machines can do anywhere from one every 50ms to one every 10ms. This provides plenty of margin.
+#define ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT 2000
+#else
+#if (defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(_X86_) || defined(__I86__))
+// 32-bit Intel machines usually average about one every 100ms
+#define ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT 5000
+#else
+// This provides a safe margin for ARM, MIPS, etc. that usually average one every 250-400ms
+#define ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT 10000
+#endif
+#endif
+
 /**
  * How long is a path or peer considered to have a trust relationship with us (for e.g. relay policy) since last trusted established packet?
  */
diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp
index ee4d62c08..41f3e47d9 100644
--- a/node/IncomingPacket.cpp
+++ b/node/IncomingPacket.cpp
@@ -247,6 +247,10 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,const bool alreadyAut
 				if (peer->identity() != id) {
 					// Identity is different from the one we already have -- address collision
 
+					// Check rate limits
+					if (!RR->node->rateGateIdentityVerification(now,_path->address()))
+						return true;
+
 					uint8_t key[ZT_PEER_SECRET_KEY_LENGTH];
 					if (RR->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
 						if (dearmor(key)) { // ensure packet is authentic, otherwise drop
@@ -285,7 +289,11 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,const bool alreadyAut
 				return true;
 			}
 
-			// Check packet integrity and MAC
+			// Check rate limits
+			if (!RR->node->rateGateIdentityVerification(now,_path->address()))
+				return true;
+
+			// Check packet integrity and MAC (this is faster than locallyValidate() so do it first to filter out total crap)
 			SharedPtr<Peer> newPeer(new Peer(RR,RR->identity,id));
 			if (!dearmor(newPeer->key())) {
 				TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_path->address().toString().c_str());
diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp
index 6f070fbf5..1dff710d5 100644
--- a/node/InetAddress.hpp
+++ b/node/InetAddress.hpp
@@ -449,6 +449,30 @@ struct InetAddress : public sockaddr_storage
 	bool isNetwork() const
 		throw();
 
+	/**
+	 * @return 14-bit (0-16383) hash of this IP's first 24 or 48 bits (for V4 or V6) for rate limiting code, or 0 if non-IP
+	 */
+	inline unsigned long rateGateHash() const
+	{
+		unsigned long h = 0;
+		switch(ss_family) {
+			case AF_INET:
+				h = (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr) & 0xffffff00) >> 8;
+				h ^= (h >> 14);
+				break;
+			case AF_INET6: {
+				const uint8_t *ip = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr);
+				h = ((unsigned long)ip[0]); h <<= 1;
+				h += ((unsigned long)ip[1]); h <<= 1;
+				h += ((unsigned long)ip[2]); h <<= 1;
+				h += ((unsigned long)ip[3]); h <<= 1;
+				h += ((unsigned long)ip[4]); h <<= 1;
+				h += ((unsigned long)ip[5]);
+			}	break;
+		}
+		return (h & 0x3fff);
+	}
+
 	/**
 	 * @return True if address family is non-zero
 	 */
diff --git a/node/Node.cpp b/node/Node.cpp
index add3117e2..ec7196682 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -78,6 +78,7 @@ Node::Node(
 
 	memset(_expectingRepliesToBucketPtr,0,sizeof(_expectingRepliesToBucketPtr));
 	memset(_expectingRepliesTo,0,sizeof(_expectingRepliesTo));
+	memset(_lastIdentityVerification,0,sizeof(_lastIdentityVerification));
 
 	// Use Salsa20 alone as a high-quality non-crypto PRNG
 	{
diff --git a/node/Node.hpp b/node/Node.hpp
index e616da3d7..ee0d6c4c3 100644
--- a/node/Node.hpp
+++ b/node/Node.hpp
@@ -283,6 +283,24 @@ public:
 		return false;
 	}
 
+	/**
+	 * Check whether we should do potentially expensive identity verification (rate limit)
+	 *
+	 * @param now Current time
+	 * @param from Source address of packet
+	 * @return True if within rate limits
+	 */
+	inline bool rateGateIdentityVerification(const uint64_t now,const InetAddress &from)
+	{
+		unsigned long iph = from.rateGateHash();
+		printf("%s %.4lx\n",from.toString().c_str(),iph);
+		if ((now - _lastIdentityVerification[iph]) >= ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT) {
+			_lastIdentityVerification[iph] = now;
+			return true;
+		}
+		return false;
+	}
+
 	virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig);
 	virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode);
 
@@ -302,9 +320,13 @@ private:
 
 	void *_uPtr; // _uptr (lower case) is reserved in Visual Studio :P
 
+	// For tracking packet IDs to filter out OK/ERROR replies to packets we did not send
 	uint8_t _expectingRepliesToBucketPtr[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1];
 	uint64_t _expectingRepliesTo[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1][ZT_EXPECTING_REPLIES_BUCKET_MASK2 + 1];
 
+	// Time of last identity verification indexed by InetAddress.rateGateHash()
+	uint64_t _lastIdentityVerification[16384];
+
 	ZT_DataStoreGetFunction _dataStoreGetFunction;
 	ZT_DataStorePutFunction _dataStorePutFunction;
 	ZT_WirePacketSendFunction _wirePacketSendFunction;
diff --git a/selftest.cpp b/selftest.cpp
index 9992d7575..adac2f584 100644
--- a/selftest.cpp
+++ b/selftest.cpp
@@ -327,6 +327,17 @@ static int testCrypto()
 	}
 	std::cout << "PASS" << std::endl;
 
+	std::cout << "[crypto] Benchmarking C25519 ECC key agreement... "; std::cout.flush();
+	C25519::Pair bp[8];
+	for(int k=0;k<8;++k)
+		bp[k] = C25519::generate();
+	const uint64_t st = OSUtils::now();
+	for(unsigned int k=0;k<50;++k) {
+		C25519::agree(bp[~k & 7],bp[k & 7].pub,buf1,64);
+	}
+	const uint64_t et = OSUtils::now();
+	std::cout << ((double)(et - st) / 50.0) << "ms per agreement." << std::endl;
+
 	std::cout << "[crypto] Testing Ed25519 ECC signatures... "; std::cout.flush();
 	C25519::Pair didntSign = C25519::generate();
 	for(unsigned int i=0;i<10;++i) {