From 3448e6fc76a19d8ff4b6ca6402b7636d5bc8cf55 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 14 Feb 2020 14:12:42 -0800 Subject: [PATCH] Yet MORE refactoring, including moving expected-reply-to out of Node and into its own class for cleanliness. --- node/AES.cpp | 26 ++++++++--- node/CMakeLists.txt | 1 + node/Expect.hpp | 86 +++++++++++++++++++++++++++++++++++++ node/InetAddress.cpp | 21 --------- node/InetAddress.hpp | 5 --- node/Meter.hpp | 3 +- node/Node.cpp | 4 -- node/Node.hpp | 61 -------------------------- node/RuntimeEnvironment.hpp | 3 ++ node/VL1.cpp | 4 -- 10 files changed, 110 insertions(+), 104 deletions(-) create mode 100644 node/Expect.hpp diff --git a/node/AES.cpp b/node/AES.cpp index b2e53ed57..149d9dba0 100644 --- a/node/AES.cpp +++ b/node/AES.cpp @@ -362,10 +362,12 @@ void AES::GMAC::finish(uint8_t tag[16]) noexcept __m128i t4 = _mm_clmulepi64_si128(h,y,0x11); encIV = _mm_aesenc_si128(encIV,k[1]); - encIV = _mm_aesenc_si128(encIV,k[2]); t2 = _mm_xor_si128(t2,t3); t3 = _mm_slli_si128(t2,8); + + encIV = _mm_aesenc_si128(encIV,k[2]); + t2 = _mm_srli_si128(t2,8); t1 = _mm_xor_si128(t1,t3); @@ -380,47 +382,57 @@ void AES::GMAC::finish(uint8_t tag[16]) noexcept t4 = _mm_slli_epi32(t4,1); t3 = _mm_srli_si128(t5,12); + + encIV = _mm_aesenc_si128(encIV,k[5]); + t6 = _mm_slli_si128(t6,4); t5 = _mm_slli_si128(t5,4); - encIV = _mm_aesenc_si128(encIV,k[5]); + encIV = _mm_aesenc_si128(encIV,k[6]); t1 = _mm_or_si128(t1,t5); t4 = _mm_or_si128(t4,t6); - encIV = _mm_aesenc_si128(encIV,k[6]); encIV = _mm_aesenc_si128(encIV,k[7]); t4 = _mm_or_si128(t4,t3); t5 = _mm_slli_epi32(t1,31); + + encIV = _mm_aesenc_si128(encIV,k[8]); + t6 = _mm_slli_epi32(t1,30); t3 = _mm_slli_epi32(t1,25); - encIV = _mm_aesenc_si128(encIV,k[8]); encIV = _mm_aesenc_si128(encIV,k[9]); t5 = _mm_xor_si128(t5,t6); t5 = _mm_xor_si128(t5,t3); encIV = _mm_aesenc_si128(encIV,k[10]); - encIV = _mm_aesenc_si128(encIV,k[11]); t6 = _mm_srli_si128(t5,4); t4 = _mm_xor_si128(t4,t6); + + encIV = _mm_aesenc_si128(encIV,k[11]); + t5 = _mm_slli_si128(t5,12); t1 = _mm_xor_si128(t1,t5); t4 = _mm_xor_si128(t4,t1); t5 = _mm_srli_epi32(t1,1); + + encIV = _mm_aesenc_si128(encIV,k[12]); + t2 = _mm_srli_epi32(t1,2); t3 = _mm_srli_epi32(t1,7); - encIV = _mm_aesenc_si128(encIV,k[12]); encIV = _mm_aesenc_si128(encIV,k[13]); - encIV = _mm_aesenclast_si128(encIV,k[14]); t4 = _mm_xor_si128(t4,t2); t4 = _mm_xor_si128(t4,t3); + + encIV = _mm_aesenclast_si128(encIV,k[14]); + t4 = _mm_xor_si128(t4,t5); _mm_storeu_si128(reinterpret_cast<__m128i *>(tag),_mm_xor_si128(_mm_shuffle_epi8(t4,s_shuf),encIV)); diff --git a/node/CMakeLists.txt b/node/CMakeLists.txt index aee722bc8..2cf3ba3a2 100644 --- a/node/CMakeLists.txt +++ b/node/CMakeLists.txt @@ -14,6 +14,7 @@ set(core_headers Defragmenter.hpp Dictionary.hpp ECC384.hpp + Expect.hpp FCV.hpp Hashtable.hpp Identity.hpp diff --git a/node/Expect.hpp b/node/Expect.hpp new file mode 100644 index 000000000..0607ea89f --- /dev/null +++ b/node/Expect.hpp @@ -0,0 +1,86 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2024-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_EXPECT_HPP +#define ZT_EXPECT_HPP + +#include "Constants.hpp" +#include "Utils.hpp" + +/** + * Number of buckets to use to maintain a list of expected replies. + * + * More buckets means less chance of two packets tagging the same + * bucket. This doesn't actually hurt anything since this class + * behaves like a bloom filter: you can have false positives but + * not false negatives. + * + * OKs are also cryptographically authenticated, so this is not a + * huge problem, but this helps harden the system against replay + * attacks for e.g. denial of service. + */ +#define ZT_EXPECT_BUCKETS 131072 + +/** + * 1/2 the TTL for expected replies in milliseconds + * + * Making this a power of two improves efficiency a little by allowing bit + * shift division. + */ +#define ZT_EXPECT_TTL 4096 + +namespace ZeroTier { + +/** + * Tracker for expected OK replies to packet IDs of sent packets + */ +class Expect +{ +public: + ZT_ALWAYS_INLINE Expect() : _salt(Utils::getSecureRandomU64()) {} + + /** + * Called by other code when something is sending a packet that may receive an OK response + * + * @param packetId Packet ID of packet being sent (be sure it's post-armor()) + * @param now Current time + */ + ZT_ALWAYS_INLINE void sending(const uint64_t packetId,const int64_t now) noexcept + { + _packetIdSent[Utils::hash64(packetId ^ _salt) % ZT_EXPECT_BUCKETS] = (int32_t)(now / ZT_EXPECT_TTL); + } + + /** + * Check whether an OK is expected for this packet + * + * @param inRePacketId + * @param now + * @return + */ + ZT_ALWAYS_INLINE bool expecting(const uint64_t inRePacketId,const int64_t now) const noexcept + { + return ((((int32_t)(now / ZT_EXPECT_TTL)) - _packetIdSent[Utils::hash64(inRePacketId ^ _salt) % ZT_EXPECT_BUCKETS]) <= 1); + } + +private: + // This is a static per-object salt that's XORed and mixed with the packet ID + // to make it difficult for a third party to predict expected-reply buckets. + const uint64_t _salt; + + // Each bucket contains a timestamp in units of the expect duration. + std::atomic _packetIdSent[ZT_EXPECT_TTL]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp index 8c413203b..b7c7e2f45 100644 --- a/node/InetAddress.cpp +++ b/node/InetAddress.cpp @@ -374,27 +374,6 @@ bool InetAddress::isNetwork() const noexcept return false; } -unsigned long InetAddress::rateGateHash() const noexcept -{ - unsigned long h = 0; - switch(ss_family) { - case AF_INET: - h = (Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr) & 0xffffff00U) >> 8U; - h ^= (h >> 14U); - break; - case AF_INET6: { - const uint8_t *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); - h = ((unsigned long)ip[0]); h <<= 1U; - h += ((unsigned long)ip[1]); h <<= 1U; - h += ((unsigned long)ip[2]); h <<= 1U; - h += ((unsigned long)ip[3]); h <<= 1U; - h += ((unsigned long)ip[4]); h <<= 1U; - h += ((unsigned long)ip[5]); - } break; - } - return (h & 0x3fffU); -} - int InetAddress::marshal(uint8_t data[ZT_INETADDRESS_MARSHAL_SIZE_MAX]) const noexcept { unsigned int port; diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index 215740677..3cbbfa54c 100644 --- a/node/InetAddress.hpp +++ b/node/InetAddress.hpp @@ -397,11 +397,6 @@ public: */ bool isNetwork() const noexcept; - /** - * @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 - */ - unsigned long rateGateHash() const noexcept; - /** * @return True if address family is non-zero */ diff --git a/node/Meter.hpp b/node/Meter.hpp index 056277994..70e9e648f 100644 --- a/node/Meter.hpp +++ b/node/Meter.hpp @@ -54,8 +54,7 @@ public: // the log size and then if it's a new bucket setting it or otherwise adding // to it. const unsigned long bucket = ((unsigned int)((uint64_t)(now / TUNIT))) % LSIZE; - const unsigned long prevBucket = _bucket.exchange(bucket); - if (prevBucket != bucket) + if (_bucket.exchange(bucket) != bucket) _counts[bucket].store((uint64_t)count); else _counts[bucket].fetch_add((uint64_t)count); } diff --git a/node/Node.cpp b/node/Node.cpp index 28c2e704d..4d240fc5f 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -48,10 +48,6 @@ Node::Node(void *uPtr,void *tPtr,const struct ZT_Node_Callbacks *callbacks,int64 { _networks.resize(64); // _networksMask + 1, must be power of two - memset((void *)_expectingRepliesToBucketPtr,0,sizeof(_expectingRepliesToBucketPtr)); - memset((void *)_expectingRepliesTo,0,sizeof(_expectingRepliesTo)); - memset((void *)_lastIdentityVerification,0,sizeof(_lastIdentityVerification)); - uint64_t idtmp[2]; idtmp[0] = 0; idtmp[1] = 0; std::vector data(stateObjectGet(tPtr,ZT_STATE_OBJECT_IDENTITY_SECRET,idtmp)); bool haveIdentity = false; diff --git a/node/Node.hpp b/node/Node.hpp index 250cfa963..11d3c4eef 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -290,65 +290,11 @@ public: */ ZT_ALWAYS_INLINE const Identity &identity() const noexcept { return _RR.identity; } - /** - * Register that we are expecting a reply to a packet ID - * - * This only uses the most significant bits of the packet ID, both to save space - * and to avoid using the higher bits that can be modified during armor() to - * mask against the packet send counter used for QoS detection. - * - * @param packetId Packet ID to expect reply to - */ - ZT_ALWAYS_INLINE void expectReplyTo(const uint64_t packetId) noexcept - { - const unsigned long pid2 = (unsigned long)(packetId >> 32U); - const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1); - _expectingRepliesTo[bucket][_expectingRepliesToBucketPtr[bucket]++ & ZT_EXPECTING_REPLIES_BUCKET_MASK2] = (uint32_t)pid2; - } - - /** - * Check whether a given packet ID is something we are expecting a reply to - * - * This only uses the most significant bits of the packet ID, both to save space - * and to avoid using the higher bits that can be modified during armor() to - * mask against the packet send counter used for QoS detection. - * - * @param packetId Packet ID to check - * @return True if we're expecting a reply - */ - ZT_ALWAYS_INLINE bool expectingReplyTo(const uint64_t packetId) const noexcept - { - const uint32_t pid2 = (uint32_t)(packetId >> 32); - const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1); - for(unsigned long i=0;i<=ZT_EXPECTING_REPLIES_BUCKET_MASK2;++i) { - if (_expectingRepliesTo[bucket][i] == pid2) - return true; - } - return false; - } - /** * @return True if aggressive NAT-traversal mechanisms like scanning of <1024 ports are enabled */ ZT_ALWAYS_INLINE bool natMustDie() const noexcept { return _natMustDie; } - /** - * 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 - */ - ZT_ALWAYS_INLINE bool rateGateIdentityVerification(const int64_t now,const InetAddress &from) noexcept - { - unsigned long iph = from.rateGateHash(); - if ((now - _lastIdentityVerification[iph]) >= ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT) { - _lastIdentityVerification[iph] = now; - return true; - } - return false; - } - /** * Wake any peers with the given address by calling their alarm() methods at or after the specified time * @@ -389,13 +335,6 @@ private: ZT_Node_Callbacks _cb; 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. - volatile uint8_t _expectingRepliesToBucketPtr[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1]; - volatile uint32_t _expectingRepliesTo[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1][ZT_EXPECTING_REPLIES_BUCKET_MASK2 + 1]; - - // Time of last identity verification indexed by InetAddress.rateGateHash() -- used in IncomingPacket::_doHELLO() via rateGateIdentityVerification() - volatile int64_t _lastIdentityVerification[16384]; - // Addresses of peers that want to have their alarm() function called at some point in the future. // These behave like weak references in that the node looks them up in Topology and calls alarm() // in each peer if that peer object is still held in memory. Calling alarm() unnecessarily on a peer diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index b1c5a3c6b..f9c8e2ff4 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -27,6 +27,7 @@ class Node; class NetworkController; class SelfAwareness; class Trace; +class Expect; /** * Holds global state for an instance of ZeroTier::Node @@ -39,6 +40,7 @@ public: localNetworkController(nullptr), rtmem(nullptr), t(nullptr), + expect(nullptr), vl2(nullptr), vl1(nullptr), topology(nullptr), @@ -69,6 +71,7 @@ public: * These are constant and never null after startup unless indicated. */ Trace *t; + Expect *expect; VL2 *vl2; VL1 *vl1; Topology *topology; diff --git a/node/VL1.cpp b/node/VL1.cpp index 4f7de7930..aabf07adb 100644 --- a/node/VL1.cpp +++ b/node/VL1.cpp @@ -598,10 +598,6 @@ bool VL1::_HELLO(void *tPtr,const SharedPtr &path,SharedPtr &peer,Bu const int64_t now = RR->node->now(); if (!peer) { - if (!RR->node->rateGateIdentityVerification(now,path->address())) { - RR->t->incomingPacketDropped(tPtr,0xaffa9ff7,p.h.packetId,0,id,path->address(),hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_RATE_LIMIT_EXCEEDED); - return false; - } if (!id.locallyValidate()) { RR->t->incomingPacketDropped(tPtr,0x2ff7a909,p.h.packetId,0,id,path->address(),hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); return false;