From df346a6df68eeb7adb4a0b0a3183ed80a77b631a Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 3 Feb 2020 13:00:13 -0800 Subject: [PATCH] Work in progress... clean up memcpy and create an annotation for that, lots more porting to new Buf/Protocol code, etc. --- attic/Fingerprint.hpp | 66 +++ node/Address.hpp | 3 +- node/AtomicCounter.hpp | 29 +- node/Buf.cpp | 2 +- node/Buf.hpp | 89 ++- node/CMakeLists.txt | 2 + node/Capability.hpp | 11 +- node/CertificateOfMembership.hpp | 4 +- node/CertificateOfOwnership.hpp | 7 +- node/Constants.hpp | 5 + node/Credential.cpp | 43 +- node/Credential.hpp | 3 +- node/Defragmenter.hpp | 383 +++++++++++++ node/Endpoint.hpp | 21 +- node/Identity.hpp | 7 +- node/IncomingPacket.cpp | 946 ++++++++++++++++++------------- node/IncomingPacket.hpp | 75 +-- node/InetAddress.hpp | 36 +- node/LZ4.hpp | 2 +- node/Locator.hpp | 3 +- node/MAC.hpp | 14 +- node/Meter.hpp | 4 +- node/MulticastGroup.hpp | 3 +- node/Network.cpp | 244 ++++---- node/Network.hpp | 25 +- node/NetworkConfig.cpp | 24 +- node/NetworkConfig.hpp | 22 +- node/NetworkController.hpp | 2 +- node/Path.hpp | 21 +- node/Peer.hpp | 12 +- node/Protocol.cpp | 13 +- node/Protocol.hpp | 331 ++++++----- node/Revocation.hpp | 15 +- node/Salsa20.hpp | 3 +- node/ScopedPtr.hpp | 4 +- node/SharedPtr.hpp | 19 +- node/Switch.hpp | 2 +- node/Tag.hpp | 9 +- node/Trace.cpp | 17 +- node/Trace.hpp | 1 - node/TriviallyCopyable.hpp | 174 ++++++ node/Utils.hpp | 58 -- 42 files changed, 1796 insertions(+), 958 deletions(-) create mode 100644 attic/Fingerprint.hpp create mode 100644 node/Defragmenter.hpp create mode 100644 node/TriviallyCopyable.hpp diff --git a/attic/Fingerprint.hpp b/attic/Fingerprint.hpp new file mode 100644 index 000000000..27d344c88 --- /dev/null +++ b/attic/Fingerprint.hpp @@ -0,0 +1,66 @@ +/* + * 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_FINGERPRINT_HPP +#define ZT_FINGERPRINT_HPP + +#include "Constants.hpp" +#include "Identity.hpp" + +namespace ZeroTier { + +/** + * A short address and a longer identity hash for extra confirmation of a node's identity. + */ +struct Fingerprint +{ + ZT_ALWAYS_INLINE Fingerprint() : address() { memset(hash,0,ZT_IDENTITY_HASH_SIZE); } + explicit ZT_ALWAYS_INLINE Fingerprint(const Identity &id) : address(id.address()) { memcpy(hash,id.hash(),ZT_IDENTITY_HASH_SIZE); } + + ZT_ALWAYS_INLINE Fingerprint &operator=(const Identity &id) + { + address = id.address(); + memcpy(hash,id.hash(),ZT_IDENTITY_HASH_SIZE); + return *this; + } + + ZT_ALWAYS_INLINE bool operator==(const Fingerprint &fp) const { return ((address == fp.address)&&(memcmp(hash,fp.hash,ZT_IDENTITY_HASH_SIZE) == 0)); } + ZT_ALWAYS_INLINE bool operator!=(const Fingerprint &fp) const { return ((address != fp.address)||(memcmp(hash,fp.hash,ZT_IDENTITY_HASH_SIZE) != 0)); } + ZT_ALWAYS_INLINE bool operator<(const Fingerprint &fp) const { return ((address < fp.address)||((address == fp.address)&&(memcmp(hash,fp.hash,ZT_IDENTITY_HASH_SIZE) < 0))); } + ZT_ALWAYS_INLINE bool operator>(const Fingerprint &fp) const { return (fp < *this); } + ZT_ALWAYS_INLINE bool operator<=(const Fingerprint &fp) const { return !(fp < *this); } + ZT_ALWAYS_INLINE bool operator>=(const Fingerprint &fp) const { return !(*this < fp); } + + ZT_ALWAYS_INLINE bool operator==(const Identity &id) const { return ((address == id.address())&&(memcmp(hash,id.hash(),ZT_IDENTITY_HASH_SIZE) == 0)); } + ZT_ALWAYS_INLINE bool operator!=(const Identity &id) const { return ((address != id.address())||(memcmp(hash,id.hash(),ZT_IDENTITY_HASH_SIZE) != 0)); } + ZT_ALWAYS_INLINE bool operator<(const Identity &id) const { return ((address < id.address())||((address == id.address())&&(memcmp(hash,id.hash(),ZT_IDENTITY_HASH_SIZE) < 0))); } + ZT_ALWAYS_INLINE bool operator>(const Identity &id) const { return (Fingerprint(id) < *this); } + ZT_ALWAYS_INLINE bool operator<=(const Identity &id) const { return !(Fingerprint(id) < *this); } + ZT_ALWAYS_INLINE bool operator>=(const Identity &id) const { return !(*this < id); } + + ZT_ALWAYS_INLINE operator bool() const { return (address); } + + /** + * Short ZeroTier address + */ + Address address; + + /** + * SHA-384 hash of public portions of identity key(s) + */ + uint8_t hash[ZT_IDENTITY_HASH_SIZE]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Address.hpp b/node/Address.hpp index c0f221992..9308410ff 100644 --- a/node/Address.hpp +++ b/node/Address.hpp @@ -26,13 +26,14 @@ #include "Constants.hpp" #include "Utils.hpp" +#include "TriviallyCopyable.hpp" namespace ZeroTier { /** * A ZeroTier address */ -class Address +class Address : public TriviallyCopyable { public: ZT_ALWAYS_INLINE Address() : _a(0) {} diff --git a/node/AtomicCounter.hpp b/node/AtomicCounter.hpp index 86182795c..6778cf89d 100644 --- a/node/AtomicCounter.hpp +++ b/node/AtomicCounter.hpp @@ -23,40 +23,43 @@ namespace ZeroTier { /** - * Simple atomic counter supporting increment and decrement + * Simple atomic counter * - * This is used as the reference counter in reference counted objects that - * work with SharedPtr<>. + * @tparam T Type of underlying integer (default: int) */ +template class AtomicCounter { public: - ZT_ALWAYS_INLINE AtomicCounter() : _v(0) {} + explicit ZT_ALWAYS_INLINE AtomicCounter(T iv = T(0)) : _v(iv) {} - ZT_ALWAYS_INLINE int load() const + ZT_ALWAYS_INLINE T load() const { #ifdef __GNUC__ - return _v; + return __sync_or_and_fetch(&_v,0); #else return _v.load(); #endif } - ZT_ALWAYS_INLINE void zero() { _v = 0; } + ZT_ALWAYS_INLINE void zero() + { + _v = T(0); + } - ZT_ALWAYS_INLINE int operator++() + ZT_ALWAYS_INLINE T operator++() { #ifdef __GNUC__ - return __sync_add_and_fetch((int *)&_v,1); + return __sync_add_and_fetch(&_v,1); #else return ++_v; #endif } - ZT_ALWAYS_INLINE int operator--() + ZT_ALWAYS_INLINE T operator--() { #ifdef __GNUC__ - return __sync_sub_and_fetch((int *)&_v,1); + return __sync_sub_and_fetch(&_v,1); #else return --_v; #endif @@ -67,9 +70,9 @@ private: ZT_ALWAYS_INLINE const AtomicCounter &operator=(const AtomicCounter &) { return *this; } #ifdef __GNUC__ - volatile int _v; + T _v; #else - std::atomic_int _v; + typename std::atomic _v; #endif }; diff --git a/node/Buf.cpp b/node/Buf.cpp index e41fe152a..3816471d8 100644 --- a/node/Buf.cpp +++ b/node/Buf.cpp @@ -68,7 +68,7 @@ void *_Buf_get() #endif b = (Buf<> *)malloc(sizeof(Buf<>)); if (!b) - return nullptr; + throw std::bad_alloc(); } else { b = (Buf<> *)bb; #ifdef __GNUC__ diff --git a/node/Buf.hpp b/node/Buf.hpp index a3bc7ad56..563a55b60 100644 --- a/node/Buf.hpp +++ b/node/Buf.hpp @@ -54,6 +54,11 @@ void *_Buf_get(); */ void freeBufPool(); +/** + * Macro to declare and get a new buffer templated with the given type + */ +#define ZT_GET_NEW_BUF(vvv,xxx) SharedPtr< Buf > vvv(reinterpret_cast< Buf * >(_Buf_get())) + /** * Buffer and methods for branch-free bounds-checked data assembly and parsing * @@ -102,28 +107,48 @@ class Buf friend void *_Buf_get(); friend void freeBufPool(); -private: - // Direct construction isn't allowed; use get(). +public: + static void operator delete(void *ptr,std::size_t sz) { _Buf_release(ptr,sz); } + + /** + * Slice is almost exactly like the built-in slice data structure in Go + */ + struct Slice + { + ZT_ALWAYS_INLINE Slice(const SharedPtr &b_,const unsigned int s_,const unsigned int e_) : b(b_),s(s_),e(e_) {} + ZT_ALWAYS_INLINE Slice() : b(),s(0),e(0) {} + + ZT_ALWAYS_INLINE operator bool() const { return (b); } + ZT_ALWAYS_INLINE unsigned int size() const { return (e - s); } + ZT_ALWAYS_INLINE void zero() { b.zero(); s = 0; e = 0; } + + /** + * Buffer holding slice data + */ + SharedPtr b; + + /** + * Index of start of data in slice + */ + unsigned int s; + + /** + * Index of end of data in slice (make sure it's greater than or equal to 's'!) + */ + unsigned int e; + }; + ZT_ALWAYS_INLINE Buf() {} template ZT_ALWAYS_INLINE Buf(const Buf &b) { memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE); } -public: - static void operator delete(void *ptr,std::size_t sz) { _Buf_release(ptr,sz); } - /** * Get obtains a buffer from the pool or allocates a new buffer if the pool is empty * * @return Buffer instance */ - static ZT_ALWAYS_INLINE SharedPtr< Buf > get() - { - void *const b = _Buf_get(); - if (b) - return SharedPtr((Buf *)b); - throw std::bad_alloc(); - } + static ZT_ALWAYS_INLINE SharedPtr< Buf > get() { return SharedPtr((Buf *)_Buf_get()); } /** * Check for overflow beyond the size of the buffer @@ -148,13 +173,6 @@ public: */ static ZT_ALWAYS_INLINE bool readOverflow(const int &ii,const unsigned int size) { return ((ii - (int)size) > 0); } - template - ZT_ALWAYS_INLINE Buf &operator=(const Buf &b) const - { - memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE); - return *this; - } - /** * Shortcut to cast between buffers whose data can be viewed through a different struct type * @@ -342,12 +360,11 @@ public: * @param len Length of buffer * @return Pointer to data or NULL on overflow or error */ - ZT_ALWAYS_INLINE void *rB(int &ii,void *bytes,unsigned int len) const + ZT_ALWAYS_INLINE uint8_t *rB(int &ii,void *bytes,unsigned int len) const { - const void *const b = (const void *)(data.bytes + ii); if ((ii += (int)len) <= ZT_BUF_MEM_SIZE) { - memcpy(bytes,b,len); - return bytes; + memcpy(bytes,data.bytes + ii,len); + return reinterpret_cast(bytes); } return nullptr; } @@ -365,9 +382,9 @@ public: * @param len Length of data field to obtain a pointer to * @return Pointer to field or NULL on overflow */ - ZT_ALWAYS_INLINE const void *rBnc(int &ii,unsigned int len) const + ZT_ALWAYS_INLINE const uint8_t *rBnc(int &ii,unsigned int len) const { - const void *const b = (const void *)(data.bytes + ii); + const uint8_t *const b = data.bytes + ii; return ((ii += (int)len) <= ZT_BUF_MEM_SIZE) ? b : nullptr; } @@ -498,6 +515,26 @@ public: memcpy(data.bytes + s,bytes,len); } + template + ZT_ALWAYS_INLINE Buf &operator=(const Buf &b) const + { + memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE); + return *this; + } + + template + ZT_ALWAYS_INLINE bool operator==(const Buf &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) == 0); } + template + ZT_ALWAYS_INLINE bool operator!=(const Buf &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) != 0); } + template + ZT_ALWAYS_INLINE bool operator<(const Buf &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) < 0); } + template + ZT_ALWAYS_INLINE bool operator<=(const Buf &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) <= 0); } + template + ZT_ALWAYS_INLINE bool operator>(const Buf &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) > 0); } + template + ZT_ALWAYS_INLINE bool operator>=(const Buf &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) >= 0); } + /** * Raw data and fields (if U template parameter is set) * @@ -511,7 +548,7 @@ public: private: volatile uintptr_t __nextInPool; // next item in free pool if this Buf is in Buf_pool - AtomicCounter __refCount; + AtomicCounter __refCount; }; } // namespace ZeroTier diff --git a/node/CMakeLists.txt b/node/CMakeLists.txt index 5dfc6b1bb..920efc2d6 100644 --- a/node/CMakeLists.txt +++ b/node/CMakeLists.txt @@ -12,6 +12,7 @@ set(core_headers CertificateOfOwnership.hpp Constants.hpp Credential.hpp + Defragmenter.hpp Dictionary.hpp ECC384.hpp Hashtable.hpp @@ -42,6 +43,7 @@ set(core_headers Tag.hpp Topology.hpp Trace.hpp + TriviallyCopyable.hpp Utils.hpp ) diff --git a/node/Capability.hpp b/node/Capability.hpp index 419c424d4..4931c0910 100644 --- a/node/Capability.hpp +++ b/node/Capability.hpp @@ -64,16 +64,7 @@ class Capability : public Credential public: static ZT_ALWAYS_INLINE ZT_CredentialType credentialType() { return ZT_CREDENTIAL_TYPE_CAPABILITY; } - ZT_ALWAYS_INLINE Capability() : - _nwid(0), - _ts(0), - _id(0), - _maxCustodyChainLength(0), - _ruleCount(0) - { - memset(_rules,0,sizeof(_rules)); - memset(_custody,0,sizeof(_custody)); - } + ZT_ALWAYS_INLINE Capability() { memoryZero(this); } /** * @param id Capability ID diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp index 2e2b59a36..dd9289215 100644 --- a/node/CertificateOfMembership.hpp +++ b/node/CertificateOfMembership.hpp @@ -102,9 +102,7 @@ public: /** * Create an empty certificate of membership */ - ZT_ALWAYS_INLINE CertificateOfMembership() : - _qualifierCount(0), - _signatureLength(0) {} + ZT_ALWAYS_INLINE CertificateOfMembership() { memoryZero(this); } /** * Create from required fields common to all networks diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp index 1c55a9ae2..279264525 100644 --- a/node/CertificateOfOwnership.hpp +++ b/node/CertificateOfOwnership.hpp @@ -57,10 +57,7 @@ public: THING_IPV6_ADDRESS = 3 }; - ZT_ALWAYS_INLINE CertificateOfOwnership() - { - memset(reinterpret_cast(this),0,sizeof(CertificateOfOwnership)); - } + ZT_ALWAYS_INLINE CertificateOfOwnership() { memoryZero(this); } ZT_ALWAYS_INLINE CertificateOfOwnership(const uint64_t nwid,const int64_t ts,const Address &issuedTo,const uint32_t id) { @@ -95,7 +92,7 @@ public: ZT_ALWAYS_INLINE bool owns(const MAC &mac) const { uint8_t tmp[6]; - mac.copyTo(tmp,6); + mac.copyTo(tmp); return this->_owns(THING_MAC_ADDRESS,tmp,6); } diff --git a/node/Constants.hpp b/node/Constants.hpp index 8371c97bc..f348523b0 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -56,6 +56,11 @@ */ #define ZT_MAX_PACKET_FRAGMENTS 11 +/** + * Sanity limit on the maximum size of a network config object + */ +#define ZT_MAX_NETWORK_CONFIG_BYTES 131072 + /** * Size of RX queue in packets */ diff --git a/node/Credential.cpp b/node/Credential.cpp index 1e87076fd..256b0a1cf 100644 --- a/node/Credential.cpp +++ b/node/Credential.cpp @@ -21,28 +21,50 @@ #include "Revocation.hpp" #include "Switch.hpp" #include "Network.hpp" -#include "ScopedPtr.hpp" + +// These are compile-time asserts to make sure temporary marshal buffers here and +// also in NtworkConfig.cpp are always large enough to marshal all credential types. +#if ZT_TAG_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE +#error ZT_TAG_MARSHAL_SIZE_MAX exceeds maximum buffer size +#endif +#if ZT_CAPABILITY_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE +#error ZT_CAPABILITY_MARSHAL_SIZE_MAX exceeds maximum buffer size +#endif +#if ZT_REVOCATION_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE +#error ZT_REVOCATION_MARSHAL_SIZE_MAX exceeds maximum buffer size +#endif +#if ZT_CERTIFICATEOFOWNERSHIP_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE +#error ZT_CERTIFICATEOFOWNERSHIP_MARSHAL_SIZE_MAX exceeds maximum buffer size +#endif +#if ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX > ZT_BUF_MEM_SIZE +#error ZT_CERTIFICATEOFMEMBERSHIP_MARSHAL_SIZE_MAX exceeds maximum buffer size +#endif namespace ZeroTier { template -static inline Credential::VerifyResult _credVerify(const RuntimeEnvironment *const RR,void *tPtr,CRED credential) +static ZT_ALWAYS_INLINE Credential::VerifyResult _credVerify(const RuntimeEnvironment *RR,void *tPtr,CRED credential) { + uint8_t tmp[ZT_BUF_MEM_SIZE + 16]; + const Address signedBy(credential.signer()); const uint64_t networkId = credential.networkId(); if ((!signedBy)||(signedBy != Network::controllerFor(networkId))) return Credential::VERIFY_BAD_SIGNATURE; + const SharedPtr peer(RR->topology->get(tPtr,signedBy)); if (!peer) { RR->sw->requestWhois(tPtr,RR->node->now(),signedBy); return Credential::VERIFY_NEED_IDENTITY; } + try { - ScopedPtr< Buffer<(sizeof(CRED) + 64)> > tmp(new Buffer<(sizeof(CRED) + 64)>()); - credential.serialize(*tmp,true); - const Credential::VerifyResult result = (peer->identity().verify(tmp->data(),tmp->size(),credential.signature(),credential.signatureLength()) ? Credential::VERIFY_OK : Credential::VERIFY_BAD_SIGNATURE); - return result; + int l = credential.marshal(tmp,true); + if (l <= 0) + return Credential::VERIFY_BAD_SIGNATURE; + return (peer->identity().verify(tmp,(unsigned int)l,credential.signature(),credential.signatureLength()) ? Credential::VERIFY_OK : Credential::VERIFY_BAD_SIGNATURE); } catch ( ... ) {} + return Credential::VERIFY_BAD_SIGNATURE; } @@ -74,14 +96,17 @@ Credential::VerifyResult Credential::_verify(const RuntimeEnvironment *const RR, Credential::VerifyResult Credential::_verify(const RuntimeEnvironment *RR,void *tPtr,const Capability &credential) const { + uint8_t tmp[ZT_CAPABILITY_MARSHAL_SIZE_MAX + 16]; try { // There must be at least one entry, and sanity check for bad chain max length if ((credential._maxCustodyChainLength < 1)||(credential._maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) return Credential::VERIFY_BAD_SIGNATURE; + int l = credential.marshal(tmp,true); + if (l <= 0) + return Credential::VERIFY_BAD_SIGNATURE; + // Validate all entries in chain of custody - Buffer<(sizeof(Capability) * 2)> tmp; - credential.serialize(tmp,true); for(unsigned int c=0;c peer(RR->topology->get(tPtr,credential._custody[c].from)); if (peer) { - if (!peer->identity().verify(tmp.data(),tmp.size(),credential._custody[c].signature,credential._custody[c].signatureLength)) + if (!peer->identity().verify(tmp,(unsigned int)l,credential._custody[c].signature,credential._custody[c].signatureLength)) return Credential::VERIFY_BAD_SIGNATURE; } else { RR->sw->requestWhois(tPtr,RR->node->now(),credential._custody[c].from); diff --git a/node/Credential.hpp b/node/Credential.hpp index 117234850..fa6c820d0 100644 --- a/node/Credential.hpp +++ b/node/Credential.hpp @@ -24,6 +24,7 @@ #include #include "Constants.hpp" +#include "TriviallyCopyable.hpp" namespace ZeroTier { @@ -37,7 +38,7 @@ class RuntimeEnvironment; /** * Base class for credentials */ -class Credential +class Credential : public TriviallyCopyable { public: /** diff --git a/node/Defragmenter.hpp b/node/Defragmenter.hpp new file mode 100644 index 000000000..6908150a4 --- /dev/null +++ b/node/Defragmenter.hpp @@ -0,0 +1,383 @@ +/* + * 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_DEFRAGMENTER_HPP +#define ZT_DEFRAGMENTER_HPP + +#include "Constants.hpp" +#include "Buf.hpp" +#include "AtomicCounter.hpp" +#include "SharedPtr.hpp" +#include "Hashtable.hpp" +#include "Mutex.hpp" +#include "Path.hpp" + +#include +#include +#include + +namespace ZeroTier { + +/** + * Generalized putter back together-er for fragmented messages + * + * This is used both for packet fragment assembly and multi-chunk network config + * assembly. This is abstracted out of the code that uses it because it's a bit of + * a hairy and difficult thing to get both correct and fast, and because its + * hairiness makes it very desirable to be able to test and fuzz this code + * independently. + * + * Here be dragons! + * + * @tparam MF Maximum number of fragments that each message can possess + */ +template +class Defragmenter +{ +public: + /** + * Error codes for assemble() + */ + enum ErrorCode + { + /** + * No error occurred + */ + ERR_NONE, + + /** + * This fragment duplicates another with the same fragment number for this message + */ + ERR_DUPLICATE_FRAGMENT, + + /** + * The fragment is invalid, such as e.g. having a fragment number beyond the expected count. + */ + ERR_INVALID_FRAGMENT, + + /** + * Too many fragments are in flight for this path + * + * The message will be marked as if it's done (all fragments received) but will + * be abandoned. Subsequent fragments will generate a DUPLICATE_FRAGMENT error. + * + * This is an anti-denial-of-service feature to limit the number of inbound + * fragments that can be in flight over a given physical network path. + */ + ERR_TOO_MANY_FRAGMENTS_FOR_PATH, + + /** + * Memory (or some other limit) exhausted + */ + ERR_OUT_OF_MEMORY + }; + + /** + * Return tuple for assemble() + */ + struct Result + { + ZT_ALWAYS_INLINE Result() : message(),messageFragmentCount(0),error(Defragmenter::ERR_NONE) {} + + /** + * Fully assembled message as a series of slices of fragments + */ + Buf<>::Slice message[MF]; + + /** + * Fully assembled message fragment count (number of slices) + * + * This will be nonzero if the message is fully assembled. + */ + unsigned int messageFragmentCount; + + /** + * Error code or ERR_NONE if none + */ + Defragmenter::ErrorCode error; + }; + + /** + * Process a fragment of a multi-part message + * + * The message ID is arbitrary but must be something that can uniquely + * group fragments for a given final message. The total fragments expected + * value is expectded to be the same for all fragments in a message. Results + * are undefined and probably wrong if this value changes across a message. + * Fragment numbers must be sequential starting with 0 and going up to + * one minus total fragments expected (non-inclusive range). + * + * Fragments can arrive in any order. Duplicates are dropped and ignored. + * + * It's the responsibility of the caller to do whatever validation needs to + * be done before considering a fragment valid and to make sure the fragment + * data index and size parameters are valid. + * + * The fragment supplied to this function is kept and held under the supplied + * message ID until or unless (1) the message is fully assembled, (2) the + * message is orphaned and its entry is taken by a new message, or (3) the + * clear() function is called to forget all incoming messages. The pointer + * at the 'fragment' reference will be zeroed since this pointer is handed + * off, so the SharedPtr<> passed in as 'fragment' will be NULL after this + * function is called. + * + * The result returned by this function is a structure containing a series + * of assembled and complete fragments, a fragment count, and an error. + * If the message fragment count is non-zero then the message has been + * successfully assembled. If the fragment count is zero then an error may + * have occurred or the message may simply not yet be complete. + * + * The calling code must decide what to do with the assembled and ordered + * fragments, such as memcpy'ing them into a contiguous buffer or handling + * them as a vector of fragments. + * + * The 'via' parameter causes this fragment to be registered with a path and + * unregistered when done or abandoned. It's only used the first time it's + * supplied (the first non-NULL) for a given message ID. This is a mitigation + * against memory exhausting DOS attacks. + * + * Lastly the message queue size target and GC trigger parameters control + * garbage collection of defragmenter message queue entries. If the size + * target parameter is non-zero then the message queue is cleaned when its + * size reaches the GC trigger parameter, which MUST be larger than the size + * target. Cleaning is done by sorting all entries by their last modified + * timestamp and removing the oldest N entries so as to bring the size down + * to under the size target. The use of a trigger size that is larger than + * the size target reduces CPU-wasting thrashing. A good value for the trigger + * is 2X the size target, causing cleanups to happen only occasionally. + * + * If the GC parameters are set to zero then clear() must be called from time + * to time or memory use will grow without bound. + * + * @tparam X Template parameter type for Buf<> containing fragment (inferred) + * @param messageId Message ID (a unique ID identifying this message) + * @param fragment Buffer containing fragment that will be filed under this message's ID + * @param fragmentDataIndex Index of data in fragment's data.bytes (fragment's data.fields type is ignored) + * @param fragmentDataSize Length of data in fragment's data.bytes (fragment's data.fields type is ignored) + * @param fragmentNo Number of fragment (0..totalFragmentsExpected, non-inclusive) + * @param totalFragmentsExpected Total number of expected fragments in this message + * @param now Current time + * @param via If non-NULL this is the path on which this message fragment was received + * @param maxIncomingFragmentsPerPath If via is non-NULL this is a cutoff for maximum fragments in flight via this path + * @param messageQueueSizeTarget If non-zero periodically clean the message queue to bring it under this size + * @param messageQueueSizeGCTrigger A value larger than messageQueueSizeTarget that is when cleaning is performed + * @return Result buffer (pointer to 'result' or newly allocated buffer) or NULL if message not complete + */ + ZT_ALWAYS_INLINE Result assemble( + const uint64_t messageId, + SharedPtr< Buf<> > &fragment, + const unsigned int fragmentDataIndex, + const unsigned int fragmentDataSize, + const unsigned int fragmentNo, + const unsigned int totalFragmentsExpected, + const int64_t now, + const SharedPtr< Path > &via, + const unsigned int maxIncomingFragmentsPerPath, + const unsigned long messageQueueSizeTarget, + const unsigned long messageQueueSizeGCTrigger) + { + Result r; + + // Sanity checks for malformed fragments or invalid input parameters. + if ((fragmentNo >= totalFragmentsExpected)||(totalFragmentsExpected > MF)||(totalFragmentsExpected == 0)) { + r.error = ERR_INVALID_FRAGMENT; + return r; + } + + // If there is only one fragment just return that fragment and we are done. + if (totalFragmentsExpected < 2) { + if (fragmentNo == 0) { + r.message[0].b.move(fragment); + r.message[0].s = fragmentDataIndex; + r.message[0].e = fragmentDataSize; + r.messageFragmentCount = 1; + return r; + } else { + r.error = ERR_INVALID_FRAGMENT; + return r; + } + } + + // Lock messages for read and look up current entry. Also check the + // GC trigger and if we've exceeded that threshold then older message + // entries are garbage collected. + _messages_l.rlock(); + if (messageQueueSizeTarget > 0) { + if (_messages.size() >= messageQueueSizeGCTrigger) { + try { + // Scan messages with read lock still locked first and make a sorted list of + // message entries by last modified time. Then lock for writing and delete + // the oldest entries to bring the size of the messages hash table down to + // under the target size. This tries to minimize the amount of time the write + // lock is held since many threads can hold the read lock but all threads must + // wait if someone holds the write lock. + std::vector< std::pair > messagesByLastUsedTime; + messagesByLastUsedTime.reserve(_messages.size()); + + typename Hashtable::Iterator i(_messages); + uint64_t *mk = nullptr; + _E *mv = nullptr; + while (i.next(mk,mv)) + messagesByLastUsedTime.push_back(std::pair(mv->lastUsed,*mk)); + + std::sort(messagesByLastUsedTime.begin(),messagesByLastUsedTime.end()); + + _messages_l.runlock(); + _messages_l.lock(); + for (unsigned long x = 0,y = (messagesByLastUsedTime.size() - messageQueueSizeTarget); x <= y; ++x) + _messages.erase(messagesByLastUsedTime[x].second); + _messages_l.unlock(); + _messages_l.rlock(); + } catch (...) { + // The only way something in that code can throw is if a bad_alloc occurs when + // reserve() is called in the vector. In this case we flush the entire queue + // and error out. This is very rare and on some platforms impossible. + _messages_l.runlock(); + _messages_l.lock(); + _messages.clear(); + _messages_l.unlock(); + r.error = ERR_OUT_OF_MEMORY; + return r; + } + } + } + _E *e = _messages.get(messageId); + _messages_l.runlock(); + + // If no entry exists we must briefly lock messages for write and create a new one. + if (!e) { + try { + RWMutex::Lock ml(_messages_l); + e = &(_messages[messageId]); + } catch ( ... ) { + r.error = ERR_OUT_OF_MEMORY; + return r; + } + e->id = messageId; + } + + // Now handle this fragment within this individual message entry. + Mutex::Lock el(e->lock); + + // Note: it's important that _messages_l is not locked while the entry + // is locked or a deadlock could occur due to GC or clear() being called + // in another thread. + + // If there is a path associated with this fragment make sure we've registered + // ourselves as in flight, check the limit, and abort if exceeded. + if ((via)&&(!e->via)) { + e->via = via; + bool tooManyPerPath = false; + via->_inboundFragmentedMessages_l.lock(); + try { + if (via->_inboundFragmentedMessages.size() < maxIncomingFragmentsPerPath) { + via->_inboundFragmentedMessages.insert(messageId); + } else { + tooManyPerPath = true; + } + } catch ( ... ) { + // This would indicate something like bad_alloc thrown by the set. Treat + // it as limit exceeded. + tooManyPerPath = true; + } + via->_inboundFragmentedMessages_l.unlock(); + if (tooManyPerPath) { + r.error = ERR_TOO_MANY_FRAGMENTS_FOR_PATH; + return r; + } + } + + // Update last-activity timestamp for this entry. + e->lastUsed = now; + + // If we already have fragment number X, abort. Note that we do not + // actually compare data here. Two same-numbered fragments with different + // data would just mean the transfer is corrupt and would be detected + // later e.g. by packet MAC check. Other use cases of this code like + // network configs check each fragment so this basically can't happen. + Buf<>::Slice &s = e->fragment[fragmentNo]; + if (s.b) { + r.error = ERR_DUPLICATE_FRAGMENT; + return r; + } + + // Take ownership of fragment, setting 'fragment' pointer to NULL. The simple + // transfer of the pointer avoids a synchronized increment/decrement of the object's + // reference count. + s.b.move(fragment); + s.s = fragmentDataIndex; + s.e = fragmentDataIndex + fragmentDataSize; + + // If we now have all fragments then assemble them. + if (++e->fragmentCount >= totalFragmentsExpected) { + // This message is done so de-register it with its path if one is associated. + if (e->via) { + e->via->_inboundFragmentedMessages_l.lock(); + e->via->_inboundFragmentedMessages.erase(messageId); + e->via->_inboundFragmentedMessages_l.unlock(); + e->via.zero(); + } + + // PERFORMANCE HACK: SharedPtr<> is introspective and only holds a pointer, so we + // can 'move' the pointers it holds very quickly by bulk copying the source + // slices and then zeroing the originals. This is only okay if the destination + // currently holds no pointers, which should always be the case. Don't try this + // at home kids. + unsigned int msize = e->fragmentCount * sizeof(Buf<>::Slice); + memcpy(reinterpret_cast(r.message),reinterpret_cast(e->fragment),msize); + memset(reinterpret_cast(e->fragment),0,msize); + r.messageFragmentCount = e->fragmentCount; + } + + return r; + } + + /** + * Erase all message entries in the internal queue + */ + ZT_ALWAYS_INLINE void clear() + { + RWMutex::Lock ml(_messages_l); + _messages.clear(); + } + +private: + struct _E + { + ZT_ALWAYS_INLINE _E() : id(0),lastUsed(0),via(),fragmentCount(0) {} + ZT_ALWAYS_INLINE ~_E() + { + // Ensure that this entry is not in use while it is being deleted! + lock.lock(); + if (via) { + via->_inboundFragmentedMessages_l.lock(); + via->_inboundFragmentedMessages.erase(id); + via->_inboundFragmentedMessages_l.unlock(); + } + lock.unlock(); + } + uint64_t id; + volatile int64_t lastUsed; + SharedPtr via; + Buf<>::Slice fragment[MF]; + unsigned int fragmentCount; + Mutex lock; + }; + + Hashtable< uint64_t,_E > _messages; + RWMutex _messages_l; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Endpoint.hpp b/node/Endpoint.hpp index 89e88377d..21921a4b8 100644 --- a/node/Endpoint.hpp +++ b/node/Endpoint.hpp @@ -23,6 +23,7 @@ #include "InetAddress.hpp" #include "Address.hpp" #include "Utils.hpp" +#include "TriviallyCopyable.hpp" // max name size + type byte + port (for DNS name/port) + 3x 16-bit coordinate for location #define ZT_ENDPOINT_MARSHAL_SIZE_MAX (ZT_ENDPOINT_MAX_NAME_SIZE+1+2+2+2+2) @@ -35,7 +36,7 @@ namespace ZeroTier { * This data structure supports a number of types that are not yet actually used: * DNSNAME, URL, and ETHERNET. These are present to reserve them for future use. */ -class Endpoint +class Endpoint : public TriviallyCopyable { public: enum Type @@ -50,15 +51,7 @@ public: UNRECOGNIZED = 255 // Unrecognized endpoint type encountered in stream }; - ZT_ALWAYS_INLINE Endpoint() - { - memset(reinterpret_cast(this),0,sizeof(Endpoint)); - } - - ZT_ALWAYS_INLINE Endpoint(const Endpoint &ep) - { - memcpy(reinterpret_cast(this),reinterpret_cast(&ep),sizeof(Endpoint)); - } + ZT_ALWAYS_INLINE Endpoint() { memoryZero(this); } explicit ZT_ALWAYS_INLINE Endpoint(const InetAddress &sa) { @@ -85,12 +78,6 @@ public: Utils::scopy(_v.url,sizeof(_v.url),url); } - ZT_ALWAYS_INLINE Endpoint &operator=(const Endpoint &ep) - { - memcpy(reinterpret_cast(this),&ep,sizeof(Endpoint)); - return *this; - } - ZT_ALWAYS_INLINE Endpoint &operator=(const InetAddress &sa) { switch(sa.ss_family) { @@ -159,7 +146,7 @@ public: static ZT_ALWAYS_INLINE int marshalSizeMax() { return ZT_ENDPOINT_MARSHAL_SIZE_MAX; } int marshal(uint8_t data[ZT_ENDPOINT_MARSHAL_SIZE_MAX]) const; - int unmarshal(const uint8_t *restrict data,const int len); + int unmarshal(const uint8_t *restrict data,int len); private: Type _t; diff --git a/node/Identity.hpp b/node/Identity.hpp index d98d9fa04..0edccc40f 100644 --- a/node/Identity.hpp +++ b/node/Identity.hpp @@ -23,6 +23,7 @@ #include "C25519.hpp" #include "SHA512.hpp" #include "ECC384.hpp" +#include "TriviallyCopyable.hpp" #define ZT_IDENTITY_STRING_BUFFER_LENGTH 1024 @@ -43,7 +44,7 @@ namespace ZeroTier { * search for a different public key that duplicates an existing address. (See * code for deriveAddress() for this algorithm.) */ -class Identity +class Identity : public TriviallyCopyable { public: /** @@ -55,7 +56,7 @@ public: P384 = ZT_CRYPTO_ALG_P384 // Type 1 -- NIST P-384 with linked Curve25519/Ed25519 secondaries (2.x+) }; - ZT_ALWAYS_INLINE Identity() { memset(reinterpret_cast(this),0,sizeof(Identity)); } + ZT_ALWAYS_INLINE Identity() { memoryZero(this); } ZT_ALWAYS_INLINE ~Identity() { Utils::burn(reinterpret_cast(&this->_priv),sizeof(this->_priv)); } /** @@ -71,7 +72,7 @@ public: /** * Set identity to NIL value (all zero) */ - ZT_ALWAYS_INLINE void zero() { memset(reinterpret_cast(this),0,sizeof(Identity)); } + ZT_ALWAYS_INLINE void zero() { memoryZero(this); } /** * @return Identity type (undefined if identity is null or invalid) diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index db2973abb..420502b7a 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -26,50 +26,69 @@ #include "Tag.hpp" #include "Revocation.hpp" #include "Trace.hpp" +#include "Buf.hpp" #include #include -#include +// Macro to avoid calling hton() on values known at compile time. +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define CONST_TO_BE_UINT16(x) ((uint16_t)((uint16_t)((uint16_t)(x) << 8U) | (uint16_t)((uint16_t)(x) >> 8U))) +#else +#define CONST_TO_BE_UINT16(x) ((uint16_t)(x)) +#endif namespace ZeroTier { namespace { -void _sendErrorNeedCredentials(IncomingPacket &pkt,const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid,const SharedPtr &path) +volatile uint16_t junk = 0; + +void _sendErrorNeedCredentials(IncomingPacket &p,const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid) { - Packet outp(pkt.source(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((uint8_t)pkt.verb()); - outp.append(pkt.packetId()); - outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE); - outp.append(nwid); - outp.armor(peer->key(),true); - path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + ZT_GET_NEW_BUF(outp,Protocol::ERROR::NEED_MEMBERSHIP_CERTIFICATE); + + outp->data.fields.h.packetId = Protocol::getPacketId(); + peer->address().copyTo(outp->data.fields.h.destination); + RR->identity.address().copyTo(outp->data.fields.h.source); + outp->data.fields.h.flags = 0; + outp->data.fields.h.verb = Protocol::VERB_ERROR; + + outp->data.fields.eh.inRePacketId = p.idBE; + outp->data.fields.eh.inReVerb = p.pkt->data.fields.verb; + outp->data.fields.eh.error = Protocol::ERROR_NEED_MEMBERSHIP_CERTIFICATE; + outp->data.fields.networkId = nwid; + + Protocol::armor(*outp,sizeof(Protocol::ERROR::NEED_MEMBERSHIP_CERTIFICATE),peer->key(),ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012); + p.path->send(RR,tPtr,outp->data.bytes,sizeof(Protocol::ERROR::NEED_MEMBERSHIP_CERTIFICATE),RR->node->now()); } -ZT_ALWAYS_INLINE bool _doHELLO(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const bool alreadyAuthenticated,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doHELLO(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const bool alreadyAuthenticated) { - const int64_t now = RR->node->now(); + if (p.size < sizeof(Protocol::HELLO)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,Identity(),p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Buf< Protocol::HELLO > &pkt = reinterpret_cast &>(*p.pkt); - const uint64_t pid = pkt.packetId(); - const Address fromAddress(pkt.source()); - const unsigned int protoVersion = pkt[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION]; - const unsigned int vMajor = pkt[ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION]; - const unsigned int vMinor = pkt[ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION]; - const unsigned int vRevision = pkt.at(ZT_PROTO_VERB_HELLO_IDX_REVISION); - const int64_t timestamp = pkt.at(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP); Identity id; - unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(pkt,ZT_PROTO_VERB_HELLO_IDX_IDENTITY); + int ptr = sizeof(Protocol::HELLO); + if (pkt.rO(ptr,id) < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,Identity(),p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + return true; + } - if (protoVersion < ZT_PROTO_VERSION_MIN) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_PEER_TOO_OLD); + if (pkt.data.fields.versionProtocol < ZT_PROTO_VERSION_MIN) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_PEER_TOO_OLD); return true; } - if (fromAddress != id.address()) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + if (Address(pkt.data.fields.h.source) != id.address()) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); return true; } + const int64_t now = RR->node->now(); + SharedPtr peer(RR->topology->get(tPtr,id.address())); if (peer) { // We already have an identity with this address -- check for collisions @@ -78,27 +97,28 @@ ZT_ALWAYS_INLINE bool _doHELLO(IncomingPacket &pkt,const RuntimeEnvironment *con // Identity is different from the one we already have -- address collision // Check rate limits - if (!RR->node->rateGateIdentityVerification(now,path->address())) + if (!RR->node->rateGateIdentityVerification(now,p.path->address())) return true; uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]; if (RR->identity.agree(id,key)) { - if (pkt.dearmor(key)) { // ensure packet is authentic, otherwise drop - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); - // TODO: we handle identity collisions differently now + if (Protocol::dearmor(pkt,p.size,key) < 0) { // ensure packet is authentic, otherwise drop + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); + return true; } else { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); + // TODO: we handle identity collisions differently now } } else { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); + return true; } return true; } else { // Identity is the same as the one we already have -- check packet integrity - if (!pkt.dearmor(peer->key())) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); + if (Protocol::dearmor(pkt,p.size,peer->key()) < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); return true; } @@ -110,30 +130,30 @@ ZT_ALWAYS_INLINE bool _doHELLO(IncomingPacket &pkt,const RuntimeEnvironment *con // Sanity check: this basically can't happen if (alreadyAuthenticated) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_UNSPECIFIED); + RR->t->incomingPacketDropped(tPtr,p.idBE,0,Identity(),p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_UNSPECIFIED); return true; } // Check rate limits - if (!RR->node->rateGateIdentityVerification(now,path->address())) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_RATE_LIMIT_EXCEEDED); + if (!RR->node->rateGateIdentityVerification(now,p.path->address())) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_RATE_LIMIT_EXCEEDED); return true; } // Check packet integrity and MAC (this is faster than locallyValidate() so do it first to filter out total crap) SharedPtr newPeer(new Peer(RR)); if (!newPeer->init(RR->identity,id)) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_UNSPECIFIED); + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); return true; } - if (!pkt.dearmor(newPeer->key())) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); + if (Protocol::dearmor(pkt,p.size,newPeer->key())) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); return true; } // Check that identity's address is valid as per the derivation function if (!id.locallyValidate()) { - RR->t->incomingPacketDropped(tPtr,pid,0,id,path->address(),pkt.hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); return true; } @@ -145,42 +165,59 @@ ZT_ALWAYS_INLINE bool _doHELLO(IncomingPacket &pkt,const RuntimeEnvironment *con // VALID -- if we made it here, packet passed identity and authenticity checks! // Get address to which this packet was sent to learn our external surface address if packet was direct. - if (pkt.hops() == 0) { - InetAddress externalSurfaceAddress; - if (ptr < pkt.size()) { - ptr += externalSurfaceAddress.deserialize(pkt,ptr); - if ((externalSurfaceAddress)&&(pkt.hops() == 0)) - RR->sa->iam(tPtr,id,path->localSocket(),path->address(),externalSurfaceAddress,RR->topology->isRoot(id),now); + InetAddress externalSurfaceAddress; + if (ptr < p.size) { + if (pkt.rO(ptr,externalSurfaceAddress) < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + return true; } + if ((p.hops == 0)&&(externalSurfaceAddress)) + RR->sa->iam(tPtr,id,p.path->localSocket(),p.path->address(),externalSurfaceAddress,RR->topology->isRoot(id),now); } // Send OK(HELLO) with an echo of the packet's timestamp and some of the same // information about us: version, sent-to address, etc. - Packet outp(id.address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_HELLO); - outp.append((uint64_t)pid); - outp.append((uint64_t)timestamp); - outp.append((unsigned char)ZT_PROTO_VERSION); - outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR); - outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR); - outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); - path->address().serialize(outp); - outp.armor(peer->key(),true); - path->send(RR,tPtr,outp.data(),outp.size(),now); + ZT_GET_NEW_BUF(outp,Protocol::OK::HELLO); - peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version - peer->received(tPtr,path,pkt.hops(),pid,pkt.payloadLength(),Packet::VERB_HELLO,0,Packet::VERB_NOP,0); + outp->data.fields.h.packetId = Protocol::getPacketId(); + peer->address().copyTo(outp->data.fields.h.destination); + RR->identity.address().copyTo(outp->data.fields.h.source); + outp->data.fields.h.flags = 0; + outp->data.fields.h.verb = Protocol::VERB_OK; + + outp->data.fields.oh.inReVerb = Protocol::VERB_HELLO; + outp->data.fields.oh.inRePacketId = p.idBE; + + outp->data.fields.timestampEcho = pkt.data.fields.timestamp; + outp->data.fields.versionProtocol = ZT_PROTO_VERSION; + outp->data.fields.versionMajor = ZEROTIER_ONE_VERSION_MAJOR; + outp->data.fields.versionMinor = ZEROTIER_ONE_VERSION_MINOR; + outp->data.fields.versionRev = CONST_TO_BE_UINT16(ZEROTIER_ONE_VERSION_REVISION); + + int outl = sizeof(Protocol::OK::HELLO); + outp->wO(outl,p.path->address()); + if (!Buf<>::writeOverflow(outl)) { + Protocol::armor(*outp,outl,peer->key(),ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012); + p.path->send(RR,tPtr,outp->data.bytes,outl,RR->node->now()); + } + + peer->setRemoteVersion(pkt.data.fields.versionProtocol,pkt.data.fields.versionMajor,pkt.data.fields.versionMinor,Utils::ntoh(pkt.data.fields.versionRev)); + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_HELLO,0,Protocol::VERB_NOP,0); return true; } -ZT_ALWAYS_INLINE bool _doERROR(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doERROR(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - const Packet::Verb inReVerb = (Packet::Verb)pkt[ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB]; - const uint64_t inRePacketId = pkt.at(ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID); - const Packet::ErrorCode errorCode = (Packet::ErrorCode)pkt[ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE]; + if (p.size < sizeof(Protocol::ERROR::Header)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_ERROR,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Buf< Protocol::ERROR::Header > &pkt = reinterpret_cast &>(*p.pkt); + uint64_t networkId = 0; + int ptr = sizeof(Protocol::ERROR::Header); /* Security note: we do not gate doERROR() with expectingReplyTo() to * avoid having to log every outgoing packet ID. Instead we put the @@ -188,42 +225,42 @@ ZT_ALWAYS_INLINE bool _doERROR(IncomingPacket &pkt,const RuntimeEnvironment *con * error handler. In most cases these are only trusted in specific * circumstances. */ - switch(errorCode) { + switch(pkt.data.fields.error) { - case Packet::ERROR_OBJ_NOT_FOUND: + case Protocol::ERROR_OBJ_NOT_FOUND: // Object not found, currently only meaningful from network controllers. - if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { - networkId = pkt.at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD); + if (pkt.data.fields.inReVerb == Protocol::VERB_NETWORK_CONFIG_REQUEST) { + networkId = pkt.rI64(ptr); const SharedPtr network(RR->node->network(networkId)); if ((network)&&(network->controller() == peer->address())) network->setNotFound(); } break; - case Packet::ERROR_UNSUPPORTED_OPERATION: + case Protocol::ERROR_UNSUPPORTED_OPERATION: // This can be sent in response to any operation, though right now we only // consider it meaningful from network controllers. This would indicate // that the queried node does not support acting as a controller. - if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { - networkId = pkt.at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD); + if (pkt.data.fields.inReVerb == Protocol::VERB_NETWORK_CONFIG_REQUEST) { + networkId = pkt.rI64(ptr); const SharedPtr network(RR->node->network(networkId)); if ((network)&&(network->controller() == peer->address())) network->setNotFound(); } break; - case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: { + case Protocol::ERROR_NEED_MEMBERSHIP_CERTIFICATE: { // Peers can send this to ask for a cert for a network. - networkId = pkt.at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD); + networkId = pkt.rI64(ptr); const SharedPtr network(RR->node->network(networkId)); const int64_t now = RR->node->now(); if ((network)&&(network->config().com)) network->pushCredentialsNow(tPtr,peer->address(),now); } break; - case Packet::ERROR_NETWORK_ACCESS_DENIED_: { + case Protocol::ERROR_NETWORK_ACCESS_DENIED_: { // Network controller: network access denied. - networkId = pkt.at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD); + networkId = pkt.rI64(ptr); const SharedPtr network(RR->node->network(networkId)); if ((network)&&(network->controller() == peer->address())) network->setAccessDenied(); @@ -232,368 +269,531 @@ ZT_ALWAYS_INLINE bool _doERROR(IncomingPacket &pkt,const RuntimeEnvironment *con default: break; } - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_ERROR,inRePacketId,inReVerb,networkId); + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_ERROR,pkt.data.fields.inRePacketId,(Protocol::Verb)pkt.data.fields.inReVerb,networkId); return true; } -ZT_ALWAYS_INLINE bool _doOK(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doOK(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - const Packet::Verb inReVerb = (Packet::Verb)pkt[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB]; - const uint64_t inRePacketId = pkt.at(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID); - uint64_t networkId = 0; + if (p.size < sizeof(Protocol::OK::Header)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_OK,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Buf< Protocol::OK::Header > &pkt = reinterpret_cast &>(*p.pkt); - if (!RR->node->expectingReplyTo(inRePacketId)) + uint64_t networkId = 0; + int ptr = sizeof(Protocol::OK::Header); + + if (!RR->node->expectingReplyTo(p.idBE)) return true; - switch(inReVerb) { + switch(pkt.data.fields.inReVerb) { - case Packet::VERB_HELLO: { - const uint64_t latency = RR->node->now() - pkt.at(ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP); - const unsigned int vProto = pkt[ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION]; - const unsigned int vMajor = pkt[ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION]; - const unsigned int vMinor = pkt[ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION]; - const unsigned int vRevision = pkt.at(ZT_PROTO_VERB_HELLO__OK__IDX_REVISION); - if (vProto < ZT_PROTO_VERSION_MIN) + case Protocol::VERB_HELLO: { + if (p.size < sizeof(Protocol::OK::HELLO)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); return true; - - if (pkt.hops() == 0) { - if ((ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2) < pkt.size()) { - InetAddress externalSurfaceAddress; - externalSurfaceAddress.deserialize(pkt,ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2); - if (externalSurfaceAddress) - RR->sa->iam(tPtr,peer->identity(),path->localSocket(),path->address(),externalSurfaceAddress,RR->topology->isRoot(peer->identity()),RR->node->now()); - } } + Buf< Protocol::OK::HELLO > &pkt2 = reinterpret_cast &>(pkt); - peer->updateLatency((unsigned int)latency); - peer->setRemoteVersion(vProto,vMajor,vMinor,vRevision); + if (pkt2.data.fields.versionProtocol < ZT_PROTO_VERSION_MIN) + return true; + peer->updateLatency((unsigned int)(p.receiveTime - Utils::ntoh(pkt2.data.fields.timestampEcho))); + peer->setRemoteVersion(pkt2.data.fields.versionProtocol,pkt2.data.fields.versionMajor,pkt2.data.fields.versionMinor,Utils::ntoh(pkt2.data.fields.versionRev)); + + ptr = sizeof(Protocol::OK::HELLO); + if (ptr < p.size) { + InetAddress externalSurfaceAddress; + if (pkt2.rO(ptr,externalSurfaceAddress) < 0) + return true; + if ((externalSurfaceAddress)&&(p.hops == 0)) + RR->sa->iam(tPtr,peer->identity(),p.path->localSocket(),p.path->address(),externalSurfaceAddress,RR->topology->isRoot(peer->identity()),RR->node->now()); + } } break; - case Packet::VERB_WHOIS: + case Protocol::VERB_WHOIS: if (RR->topology->isRoot(peer->identity())) { - unsigned int p = ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY; - while (p < pkt.size()) { - try { - Identity id; - p += id.deserialize(pkt,p); - if (id) { - SharedPtr ptmp(RR->topology->add(tPtr,SharedPtr(new Peer(RR)))); - ptmp->init(RR->identity,id); - RR->sw->doAnythingWaitingForPeer(tPtr,ptmp); - } - } catch ( ... ) { + while (ptr < p.size) { + Identity id; + if (pkt.rO(ptr,id) < 0) break; + Locator loc; + if (ptr < p.size) { // older nodes did not send the locator + if (pkt.rO(ptr,loc) < 0) + break; + } + if (id) { + SharedPtr ptmp(RR->topology->add(tPtr,SharedPtr(new Peer(RR)))); + ptmp->init(RR->identity,id); + RR->sw->doAnythingWaitingForPeer(tPtr,ptmp); } } } break; - case Packet::VERB_NETWORK_CONFIG_REQUEST: { - networkId = pkt.at(ZT_PROTO_VERB_OK_IDX_PAYLOAD); + case Protocol::VERB_NETWORK_CONFIG_REQUEST: { + networkId = pkt.rI64(ptr); const SharedPtr network(RR->node->network(networkId)); if (network) - network->handleConfigChunk(tPtr,pkt.packetId(),pkt.source(),pkt,ZT_PROTO_VERB_OK_IDX_PAYLOAD); + network->handleConfigChunk(tPtr,p.idBE,peer->address(),pkt,sizeof(Protocol::OK::Header),(int)p.size); } break; - case Packet::VERB_MULTICAST_GATHER: { + case Protocol::VERB_MULTICAST_GATHER: { + // TODO } break; default: break; } - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_OK,inRePacketId,inReVerb,networkId); + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_OK,pkt.data.fields.inRePacketId,(Protocol::Verb)pkt.data.fields.inReVerb,networkId); return true; } -ZT_ALWAYS_INLINE bool _doWHOIS(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doWHOIS(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - if (!peer->rateGateInboundWhoisRequest(RR->node->now())) + if (!peer->rateGateInboundWhoisRequest(RR->node->now())) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_WHOIS,ZT_TRACE_PACKET_DROP_REASON_RATE_LIMIT_EXCEEDED); return true; + } - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_WHOIS); - outp.append(pkt.packetId()); + ZT_GET_NEW_BUF(outp,Protocol::OK::WHOIS); - unsigned int count = 0; - unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; - while ((ptr + ZT_ADDRESS_LENGTH) <= pkt.size()) { - const Address addr(pkt.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); - ptr += ZT_ADDRESS_LENGTH; + outp->data.fields.h.packetId = Protocol::getPacketId(); + peer->address().copyTo(outp->data.fields.h.destination); + RR->identity.address().copyTo(outp->data.fields.h.source); + outp->data.fields.h.flags = 0; + outp->data.fields.h.verb = Protocol::VERB_OK; - const SharedPtr ptmp(RR->topology->get(tPtr,addr)); + outp->data.fields.oh.inReVerb = Protocol::VERB_WHOIS; + outp->data.fields.oh.inRePacketId = p.idBE; + + int ptr = sizeof(Protocol::Header); + int outl = sizeof(Protocol::OK::WHOIS); + while ((ptr + ZT_ADDRESS_LENGTH) <= p.size) { + const SharedPtr ptmp(RR->topology->get(tPtr,Address(p.pkt->data.bytes + ptr))); if (ptmp) { - ptmp->identity().serialize(outp,false); - ++count; - } else { - // Request unknown WHOIS from upstream from us (if we have one) - RR->sw->requestWhois(tPtr,RR->node->now(),addr); + outp->wO(outl,ptmp->identity()); + Locator loc(ptmp->locator()); + outp->wO(outl,loc); } + ptr += ZT_ADDRESS_LENGTH; } - if (count > 0) { - outp.armor(peer->key(),true); - path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + if ((outl > sizeof(Protocol::OK::WHOIS))&&(!Buf<>::writeOverflow(outl))) { + Protocol::armor(*outp,outl,peer->key(),ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012); + p.path->send(RR,tPtr,outp->data.bytes,outl,RR->node->now()); } - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_WHOIS,0,Packet::VERB_NOP,0); + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_WHOIS,0,Protocol::VERB_NOP,0); return true; } -ZT_ALWAYS_INLINE bool _doRENDEZVOUS(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doRENDEZVOUS(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { if (RR->topology->isRoot(peer->identity())) { - uint16_t junk = (uint16_t)Utils::random(); - const Address with(pkt.field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); - const SharedPtr rendezvousWith(RR->topology->get(tPtr,with)); - if (rendezvousWith) { - const unsigned int port = pkt.at(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT); - const unsigned int addrlen = pkt[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN]; - if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) { - InetAddress atAddr(pkt.field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port); - if (rendezvousWith->shouldTryPath(tPtr,RR->node->now(),peer,atAddr)) { - if (atAddr.isV4()) - RR->node->putPacket(tPtr,path->localSocket(),atAddr,&junk,2,2); // IPv4 "firewall opener" hack - rendezvousWith->sendHELLO(tPtr,path->localSocket(),atAddr,RR->node->now()); - RR->t->tryingNewPath(tPtr,rendezvousWith->identity(),atAddr,path->address(),pkt.packetId(),Packet::VERB_RENDEZVOUS,peer->address(),peer->identity().hash(),ZT_TRACE_TRYING_NEW_PATH_REASON_RENDEZVOUS); + if (p.size < sizeof(Protocol::RENDEZVOUS)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_RENDEZVOUS,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Buf< Protocol::RENDEZVOUS > &pkt = reinterpret_cast &>(*p.pkt); + + const SharedPtr with(RR->topology->get(tPtr,Address(pkt.data.fields.peerAddress))); + if (with) { + const unsigned int port = Utils::ntoh(pkt.data.fields.port); + if (port != 0) { + switch(pkt.data.fields.addressLength) { + case 4: + if ((sizeof(Protocol::RENDEZVOUS) + 4) <= p.size) { + InetAddress atAddr(pkt.data.fields.address,4,port); + ++junk; + RR->node->putPacket(tPtr,p.path->localSocket(),atAddr,(const void *)&junk,2,2); // IPv4 "firewall opener" hack + with->sendHELLO(tPtr,p.path->localSocket(),atAddr,RR->node->now()); + RR->t->tryingNewPath(tPtr,with->identity(),atAddr,p.path->address(),p.idBE,Protocol::VERB_RENDEZVOUS,peer->address(),peer->identity().hash(),ZT_TRACE_TRYING_NEW_PATH_REASON_RENDEZVOUS); + } + break; + case 16: + if ((sizeof(Protocol::RENDEZVOUS) + 16) <= p.size) { + InetAddress atAddr(pkt.data.fields.address,16,port); + with->sendHELLO(tPtr,p.path->localSocket(),atAddr,RR->node->now()); + RR->t->tryingNewPath(tPtr,with->identity(),atAddr,p.path->address(),p.idBE,Protocol::VERB_RENDEZVOUS,peer->address(),peer->identity().hash(),ZT_TRACE_TRYING_NEW_PATH_REASON_RENDEZVOUS); + } + break; } } } } - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP,0); + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_RENDEZVOUS,0,Protocol::VERB_NOP,0); return true; } -ZT_ALWAYS_INLINE bool _doFRAME(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doFRAME(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - const uint64_t nwid = pkt.at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID); - const SharedPtr network(RR->node->network(nwid)); + if (p.size < sizeof(Protocol::FRAME)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_EXT_FRAME,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Buf< Protocol::FRAME > &pkt = reinterpret_cast &>(*p.pkt); + + const SharedPtr network(RR->node->network(Utils::ntoh(pkt.data.fields.networkId))); if (network) { if (network->gate(tPtr,peer)) { - if (pkt.size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) { - const unsigned int etherType = pkt.at(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE); - const MAC sourceMac(peer->address(),nwid); - const unsigned int frameLen = pkt.size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; - const uint8_t *const frameData = reinterpret_cast(pkt.data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; - if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0) - RR->node->putFrame(tPtr,nwid,network->userPtr(),sourceMac,network->mac(),etherType,0,(const void *)frameData,frameLen); - } + const unsigned int etherType = Utils::ntoh(pkt.data.fields.etherType); + const MAC sourceMac(peer->address(),network->id()); + const unsigned int frameLen = (unsigned int)(p.size - sizeof(Protocol::FRAME)); + if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),sourceMac,network->mac(),pkt.data.fields.data,frameLen,etherType,0) > 0) + RR->node->putFrame(tPtr,network->id(),network->userPtr(),sourceMac,network->mac(),etherType,0,pkt.data.fields.data,frameLen); } else { - RR->t->incomingNetworkFrameDropped(tPtr,nwid,MAC(),MAC(),peer->identity(),path->address(),pkt.hops(),0,nullptr,Packet::VERB_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_PERMISSION_DENIED); - _sendErrorNeedCredentials(pkt,RR,tPtr,peer,nwid,path); - return false; + RR->t->incomingNetworkFrameDropped(tPtr,network->id(),MAC(),MAC(),peer->identity(),p.path->address(),p.hops,0,nullptr,Protocol::VERB_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_PERMISSION_DENIED); + _sendErrorNeedCredentials(p,RR,tPtr,peer,network->id()); + return false; // try to decode again after we get credentials? } } - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_FRAME,0,Packet::VERB_NOP,nwid); + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_FRAME,0,Protocol::VERB_NOP,0); return true; } -ZT_ALWAYS_INLINE bool _doEXT_FRAME(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doEXT_FRAME(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - const uint64_t nwid = pkt.at(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID); - const SharedPtr network(RR->node->network(nwid)); - if (network) { - const unsigned int flags = pkt[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS]; + if (p.size < sizeof(Protocol::EXT_FRAME)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_EXT_FRAME,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Buf< Protocol::EXT_FRAME > &pkt = reinterpret_cast &>(*p.pkt); - unsigned int comLen = 0; - if ((flags & 0x01) != 0) { // inline COM with EXT_FRAME is deprecated but still used with old peers + const SharedPtr network(RR->node->network(Utils::ntoh(pkt.data.fields.networkId))); + if (network) { + int ptr = sizeof(Protocol::EXT_FRAME); + const uint8_t flags = pkt.data.fields.flags; + + if ((flags & Protocol::EXT_FRAME_FLAG_COM_ATTACHED_deprecated) != 0) { CertificateOfMembership com; - comLen = com.deserialize(pkt,ZT_PROTO_VERB_EXT_FRAME_IDX_COM); + if (pkt.rO(ptr,com) < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_EXT_FRAME,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } if (com) network->addCredential(tPtr,peer->identity(),com); } if (!network->gate(tPtr,peer)) { - RR->t->incomingNetworkFrameDropped(tPtr,nwid,MAC(),MAC(),peer->identity(),path->address(),pkt.hops(),0,nullptr,Packet::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_PERMISSION_DENIED); - _sendErrorNeedCredentials(pkt,RR,tPtr,peer,nwid,path); - return false; + RR->t->incomingNetworkFrameDropped(tPtr,network->id(),MAC(),MAC(),peer->identity(),p.path->address(),p.hops,0,nullptr,Protocol::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_PERMISSION_DENIED); + _sendErrorNeedCredentials(p,RR,tPtr,peer,network->id()); + return false; // try to parse again if we get credentials } - if (pkt.size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) { - const unsigned int etherType = pkt.at(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE); - const MAC to(pkt.field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_TO,ZT_PROTO_VERB_EXT_FRAME_LEN_TO),ZT_PROTO_VERB_EXT_FRAME_LEN_TO); - const MAC from(pkt.field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_FROM,ZT_PROTO_VERB_EXT_FRAME_LEN_FROM),ZT_PROTO_VERB_EXT_FRAME_LEN_FROM); - const unsigned int frameLen = pkt.size() - (comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD); - const uint8_t *const frameData = (const uint8_t *)pkt.field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD,frameLen); + const MAC to(pkt.rBnc(ptr,6)); + const MAC from(pkt.rBnc(ptr,6)); + const unsigned int etherType = pkt.rI16(ptr); - if ((!from)||(from == network->mac())) { - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,nwid); - return true; - } + if ((from)&&(from != network->mac())&&(!Buf<>::readOverflow(ptr,p.size))) { + const int frameSize = (int)(p.size - ptr); + if (frameSize >= 0) { + const uint64_t nwid = network->id(); + const uint8_t *const frameData = pkt.data.bytes + ptr; + switch (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to,frameData,frameSize,etherType,0)) { - switch (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to,frameData,frameLen,etherType,0)) { - case 1: - if (from != MAC(peer->address(),nwid)) { - if (network->config().permitsBridging(peer->address())) { - network->learnBridgeRoute(from,peer->address()); - } else { - RR->t->incomingNetworkFrameDropped(tPtr,nwid,from,to,peer->identity(),path->address(),pkt.hops(),(uint16_t)frameLen,frameData,Packet::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_BRIDGING_NOT_ALLOWED_REMOTE); - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,nwid); - return true; - } - } else if (to != network->mac()) { - if (to.isMulticast()) { - if (network->config().multicastLimit == 0) { - RR->t->incomingNetworkFrameDropped(tPtr,nwid,from,to,peer->identity(),path->address(),pkt.hops(),(uint16_t)frameLen,frameData,Packet::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_MULTICAST_DISABLED); - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,nwid); - return true; + case 1: + if (from != MAC(peer->address(),nwid)) { + if (network->config().permitsBridging(peer->address())) { + network->learnBridgeRoute(from,peer->address()); + } else { + RR->t->incomingNetworkFrameDropped(tPtr,nwid,from,to,peer->identity(),p.path->address(),p.hops,(uint16_t)frameSize,frameData,Protocol::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_BRIDGING_NOT_ALLOWED_REMOTE); + goto packet_dropped; + } + } else if (to != network->mac()) { + if (to.isMulticast()) { + if (network->config().multicastLimit == 0) { + RR->t->incomingNetworkFrameDropped(tPtr,nwid,from,to,peer->identity(),p.path->address(),p.hops,(uint16_t)frameSize,frameData,Protocol::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_MULTICAST_DISABLED); + goto packet_dropped; + } + } else if (!network->config().permitsBridging(RR->identity.address())) { + RR->t->incomingNetworkFrameDropped(tPtr,nwid,from,to,peer->identity(),p.path->address(),p.hops,(uint16_t)frameSize,frameData,Protocol::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_BRIDGING_NOT_ALLOWED_LOCAL); + goto packet_dropped; } - } else if (!network->config().permitsBridging(RR->identity.address())) { - RR->t->incomingNetworkFrameDropped(tPtr,nwid,from,to,peer->identity(),path->address(),pkt.hops(),(uint16_t)frameLen,frameData,Packet::VERB_EXT_FRAME,true,ZT_TRACE_FRAME_DROP_REASON_BRIDGING_NOT_ALLOWED_LOCAL); - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,nwid); - return true; } - } - // fall through -- 2 means accept regardless of bridging checks or other restrictions - case 2: - RR->node->putFrame(tPtr,nwid,network->userPtr(),from,to,etherType,0,(const void *)frameData,frameLen); - break; + // fall through -- 2 means accept regardless of bridging checks or other restrictions + + case 2: + RR->node->putFrame(tPtr,nwid,network->userPtr(),from,to,etherType,0,frameData,frameSize); + break; + + } } } - if ((flags & 0x10U) != 0) { // ACK requested - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((uint8_t)Packet::VERB_EXT_FRAME); - outp.append((uint64_t)pkt.packetId()); - outp.append((uint64_t)nwid); - outp.armor(peer->key(),true); - path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); - } + if ((flags & Protocol::EXT_FRAME_FLAG_ACK_REQUESTED) != 0) { + ZT_GET_NEW_BUF(outp,Protocol::OK::EXT_FRAME); + outp->data.fields.h.packetId = Protocol::getPacketId(); + peer->address().copyTo(outp->data.fields.h.destination); + RR->identity.address().copyTo(outp->data.fields.h.source); + outp->data.fields.h.flags = 0; + outp->data.fields.h.verb = Protocol::VERB_OK; + + outp->data.fields.oh.inReVerb = Protocol::VERB_EXT_FRAME; + outp->data.fields.oh.inRePacketId = p.idBE; + + outp->data.fields.networkId = pkt.data.fields.networkId; + outp->data.fields.flags = 0; + to.copyTo(outp->data.fields.destMac); + from.copyTo(outp->data.fields.sourceMac); + outp->data.fields.etherType = Utils::hton((uint16_t)etherType); + + Protocol::armor(*outp,sizeof(Protocol::OK::EXT_FRAME),peer->key(),ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012); + p.path->send(RR,tPtr,outp->data.bytes,sizeof(Protocol::OK::EXT_FRAME),RR->node->now()); + } } - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,nwid); +packet_dropped: + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_EXT_FRAME,0,Protocol::VERB_NOP,0); return true; } -ZT_ALWAYS_INLINE bool _doECHO(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doECHO(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - if (!peer->rateGateEchoRequest(RR->node->now())) + if (!peer->rateGateEchoRequest(RR->node->now())) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_ECHO,ZT_TRACE_PACKET_DROP_REASON_RATE_LIMIT_EXCEEDED); return true; + } - const uint64_t pid = pkt.packetId(); - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_ECHO); - outp.append((uint64_t)pid); - if (pkt.size() > ZT_PACKET_IDX_PAYLOAD) - outp.append(reinterpret_cast(pkt.data()) + ZT_PACKET_IDX_PAYLOAD,pkt.size() - ZT_PACKET_IDX_PAYLOAD); - outp.armor(peer->key(),true); - path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + ZT_GET_NEW_BUF(outp,Protocol::OK::ECHO); - peer->received(tPtr,path,pkt.hops(),pid,pkt.payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,0); + outp->data.fields.h.packetId = Protocol::getPacketId(); + peer->address().copyTo(outp->data.fields.h.destination); + RR->identity.address().copyTo(outp->data.fields.h.source); + outp->data.fields.h.flags = 0; + outp->data.fields.h.verb = Protocol::VERB_OK; + + outp->data.fields.oh.inReVerb = Protocol::VERB_ECHO; + outp->data.fields.oh.inRePacketId = p.idBE; + + int outl = sizeof(Protocol::OK::ECHO); + if (p.size > sizeof(Protocol::Header)) { + outp->wB(outl,p.pkt->data.bytes + sizeof(Protocol::Header),p.size - sizeof(Protocol::Header)); + } + + if (!Buf<>::writeOverflow(outl)) { + Protocol::armor(*outp,outl,peer->key(),ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012); + p.path->send(RR,tPtr,outp->data.bytes,outl,RR->node->now()); + } + + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_ECHO,0,Protocol::VERB_NOP,0); return true; } -ZT_ALWAYS_INLINE bool _doNETWORK_CREDENTIALS(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doNETWORK_CREDENTIALS(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - CertificateOfMembership com; - Capability cap; - Tag tag; - Revocation revocation; - CertificateOfOwnership coo; + int ptr = sizeof(Protocol::Header); + const uint8_t *payload = p.pkt->data.bytes; SharedPtr network; - unsigned int p = ZT_PACKET_IDX_PAYLOAD; - while ((p < pkt.size())&&(pkt[p] != 0)) { - p += com.deserialize(pkt,p); - if (com) { - network = RR->node->network(com.networkId()); - if (network) { - if (network->addCredential(tPtr,peer->identity(),com) == Membership::ADD_DEFERRED_FOR_WHOIS) - return false; - } + // Early versions of ZeroTier sent only the certificate of membership. The COM always + // starts with a non-zero byte. To extend this message we then parse COMs until we find + // a zero byte, then parse the other types (which are prefaced by a count for better + // extensibility) if they are present. + + // Also note that technically these can be for different networks but in practice they + // are always for the same network (when talking with current nodes). This code therefore + // accepts different networks for each credential and ignores any credentials for + // networks that we've not currently joined. + + while ((ptr < p.size)&&(payload[ptr] != 0)) { + CertificateOfMembership com; + int l = com.unmarshal(payload + ptr,(int)(p.size - ptr)); + if (l < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + return true; + } + ptr += l; + + const uint64_t nwid = com.networkId(); + if ((!network)||(network->id() != nwid)) + network = RR->node->network(nwid); + if (network) { + if (network->addCredential(tPtr,peer->identity(),com) == Membership::ADD_DEFERRED_FOR_WHOIS) + return false; } } - ++p; // skip trailing 0 after COMs if present + ++ptr; // skip trailing 0 after COMs if present - if (p < pkt.size()) { // older ZeroTier versions do not send capabilities, tags, or revocations - const unsigned int numCapabilities = pkt.at(p); p += 2; - for(unsigned int i=0;iid() != cap.networkId())) - network = RR->node->network(cap.networkId()); - if (network) { - if (network->addCredential(tPtr,peer->identity(),cap) == Membership::ADD_DEFERRED_FOR_WHOIS) - return false; - } + // The following code is copypasta for each credential type: capability, tag, revocation, + // and certificate of ownership. Each type is prefaced by a count, but it's legal for the + // packet to terminate prematurely if all remaining counts are zero. + + if (ptr >= p.size) + return true; + + unsigned int count = p.pkt->rI16(ptr); + for(unsigned int i=0;i= p.size) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; } - - if (p >= pkt.size()) return true; - - const unsigned int numTags = pkt.at(p); p += 2; - for(unsigned int i=0;iid() != tag.networkId())) - network = RR->node->network(tag.networkId()); - if (network) { - if (network->addCredential(tPtr,peer->identity(),tag) == Membership::ADD_DEFERRED_FOR_WHOIS) - return false; - } + Capability cap; + int l = cap.unmarshal(payload + ptr,(int)(p.size - ptr)); + if (l < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + return true; } + ptr += l; - if (p >= pkt.size()) return true; - - const unsigned int numRevocations = pkt.at(p); p += 2; - for(unsigned int i=0;iid() != revocation.networkId())) - network = RR->node->network(revocation.networkId()); - if (network) { - if (network->addCredential(tPtr,peer->identity(),revocation) == Membership::ADD_DEFERRED_FOR_WHOIS) - return false; - } - } - - if (p >= pkt.size()) return true; - - const unsigned int numCoos = pkt.at(p); p += 2; - for(unsigned int i=0;iid() != coo.networkId())) - network = RR->node->network(coo.networkId()); - if (network) { - if (network->addCredential(tPtr,peer->identity(),coo) == Membership::ADD_DEFERRED_FOR_WHOIS) - return false; - } + const uint64_t nwid = cap.networkId(); + if ((!network)||(network->id() != nwid)) + network = RR->node->network(nwid); + if (network) { + if (network->addCredential(tPtr,peer->identity(),cap) == Membership::ADD_DEFERRED_FOR_WHOIS) + return false; } } - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,(network) ? network->id() : 0); + if (ptr >= p.size) + return true; + + count = p.pkt->rI16(ptr); + for(unsigned int i=0;i= p.size) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Tag tag; + int l = tag.unmarshal(payload + ptr,(int)(p.size - ptr)); + if (l < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + return true; + } + ptr += l; + + const uint64_t nwid = tag.networkId(); + if ((!network)||(network->id() != nwid)) + network = RR->node->network(nwid); + if (network) { + if (network->addCredential(tPtr,peer->identity(),tag) == Membership::ADD_DEFERRED_FOR_WHOIS) + return false; + } + } + + if (ptr >= p.size) + return true; + + count = p.pkt->rI16(ptr); + for(unsigned int i=0;i= p.size) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + Revocation rev; + int l = rev.unmarshal(payload + ptr,(int)(p.size - ptr)); + if (l < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + return true; + } + ptr += l; + + const uint64_t nwid = rev.networkId(); + if ((!network)||(network->id() != nwid)) + network = RR->node->network(nwid); + if (network) { + if (network->addCredential(tPtr,peer->identity(),rev) == Membership::ADD_DEFERRED_FOR_WHOIS) + return false; + } + } + + if (ptr >= p.size) + return true; + + count = p.pkt->rI16(ptr); + for(unsigned int i=0;i= p.size) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + CertificateOfOwnership coo; + int l = coo.unmarshal(payload + ptr,(int)(p.size - ptr)); + if (l < 0) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CREDENTIALS,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT); + return true; + } + ptr += l; + + const uint64_t nwid = coo.networkId(); + if ((!network)||(network->id() != nwid)) + network = RR->node->network(nwid); + if (network) { + if (network->addCredential(tPtr,peer->identity(),coo) == Membership::ADD_DEFERRED_FOR_WHOIS) + return false; + } + } + + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_NETWORK_CREDENTIALS,0,Protocol::VERB_NOP,(network) ? network->id() : 0); return true; } -ZT_ALWAYS_INLINE bool _doNETWORK_CONFIG_REQUEST(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doNETWORK_CONFIG_REQUEST(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - const uint64_t nwid = pkt.at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); - const unsigned int hopCount = pkt.hops(); - const uint64_t requestPacketId = pkt.packetId(); + int ptr = sizeof(Protocol::Header); + + const uint64_t nwid = p.pkt->rI64(ptr); + const unsigned int dictSize = p.pkt->rI16(ptr); + const uint8_t *dictData = p.pkt->rBnc(ptr,dictSize); + if (Buf<>::readOverflow(ptr,p.size)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CONFIG_REQUEST,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } if (RR->localNetworkController) { - const unsigned int metaDataLength = (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN <= pkt.size()) ? pkt.at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN) : 0; - const char *metaDataBytes = (metaDataLength != 0) ? (const char *)pkt.field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength) : (const char *)0; - const Dictionary metaData(metaDataBytes,metaDataLength); - RR->localNetworkController->request(nwid,(hopCount > 0) ? InetAddress() : path->address(),requestPacketId,peer->identity(),metaData); + Dictionary requestMetaData; + if ((dictSize > 0)&&(dictData)) { + if (!requestMetaData.decode(dictData,dictSize)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CONFIG_REQUEST,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + } + RR->localNetworkController->request(nwid,(p.hops > 0) ? InetAddress::NIL : p.path->address(),Utils::ntoh(p.idBE),peer->identity(),requestMetaData); } else { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(requestPacketId); - outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); - outp.append(nwid); - outp.armor(peer->key(),true); - path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + ZT_GET_NEW_BUF(outp,Protocol::ERROR::UNSUPPORTED_OPERATION__NETWORK_CONFIG_REQUEST); + + outp->data.fields.h.packetId = Protocol::getPacketId(); + peer->address().copyTo(outp->data.fields.h.destination); + RR->identity.address().copyTo(outp->data.fields.h.source); + outp->data.fields.h.flags = 0; + outp->data.fields.h.verb = Protocol::VERB_OK; + + outp->data.fields.eh.inReVerb = Protocol::VERB_NETWORK_CONFIG_REQUEST; + outp->data.fields.eh.inRePacketId = p.idBE; + outp->data.fields.eh.error = Protocol::ERROR_UNSUPPORTED_OPERATION; + + outp->data.fields.networkId = Utils::hton(nwid); + + Protocol::armor(*outp,sizeof(Protocol::ERROR::UNSUPPORTED_OPERATION__NETWORK_CONFIG_REQUEST),peer->key(),ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012); + p.path->send(RR,tPtr,outp->data.bytes,sizeof(Protocol::ERROR::UNSUPPORTED_OPERATION__NETWORK_CONFIG_REQUEST),RR->node->now()); } - peer->received(tPtr,path,hopCount,requestPacketId,pkt.payloadLength(),Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,nwid); - + // Note that NETWORK_CONFIG_REQUEST does not pertain to a network we have *joined*, but one + // we may control. The network ID parameter to peer->received() is therefore zero. + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_NETWORK_CONFIG_REQUEST,0,Protocol::VERB_NOP,0); return true; } -ZT_ALWAYS_INLINE bool _doNETWORK_CONFIG(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doNETWORK_CONFIG(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { + int ptr = sizeof(Protocol::Header); + + const uint64_t nwid = p.pkt->rI64(ptr); + if (Buf<>::readOverflow(ptr,p.size)) { + RR->t->incomingPacketDropped(tPtr,p.idBE,0,peer->identity(),p.path->address(),p.hops,Protocol::VERB_NETWORK_CONFIG_REQUEST,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET); + return true; + } + + const SharedPtr network(RR->node->network(nwid)); + if (network) { + } + + /* const SharedPtr network(RR->node->network(pkt.at(ZT_PACKET_IDX_PAYLOAD))); if (network) { const uint64_t configUpdateId = network->handleConfigChunk(tPtr,pkt.packetId(),pkt.source(),pkt,ZT_PACKET_IDX_PAYLOAD); @@ -608,61 +808,20 @@ ZT_ALWAYS_INLINE bool _doNETWORK_CONFIG(IncomingPacket &pkt,const RuntimeEnviron } } peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_NETWORK_CONFIG,0,Packet::VERB_NOP,(network) ? network->id() : 0); + */ + + peer->received(tPtr,p.path,p.hops,p.idBE,p.size,Protocol::VERB_NETWORK_CONFIG,0,Protocol::VERB_NOP,nwid); return true; } -ZT_ALWAYS_INLINE bool _doMULTICAST_GATHER(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doMULTICAST_GATHER(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { - const uint64_t nwid = pkt.at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID); - const unsigned int flags = pkt[ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS]; - const MulticastGroup mg(MAC(pkt.field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC,6),6),pkt.at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI)); - const unsigned int gatherLimit = pkt.at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT); - - const SharedPtr network(RR->node->network(nwid)); - - // LEGACY: older versions would send this - if ((flags & 0x01U) != 0) { - try { - CertificateOfMembership com; - com.deserialize(pkt,ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM); - if ((com)&&(network)) - network->addCredential(tPtr,peer->identity(),com); - } catch ( ... ) {} // discard invalid COMs - } - - if (network) { - if (!network->gate(tPtr,peer)) { - _sendErrorNeedCredentials(pkt,RR,tPtr,peer,nwid,path); - return false; - } - } - - const int64_t now = RR->node->now(); - if (gatherLimit) { - // TODO - /* - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_MULTICAST_GATHER); - outp.append(packetId()); - outp.append(nwid); - mg.mac().appendTo(outp); - outp.append((uint32_t)mg.adi()); - const unsigned int gatheredLocally = RR->mc->gather(peer->address(),nwid,mg,outp,gatherLimit); - if (gatheredLocally > 0) { - outp.armor(peer->key(),true); - _path->send(RR,tPtr,outp.data(),outp.size(),now); - } - */ - } - - peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP,nwid); - return true; } -volatile uint16_t junk = 0; -ZT_ALWAYS_INLINE bool _doPUSH_DIRECT_PATHS(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doPUSH_DIRECT_PATHS(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { + /* const int64_t now = RR->node->now(); if (peer->rateGateInboundPushDirectPaths(now)) { @@ -673,7 +832,8 @@ ZT_ALWAYS_INLINE bool _doPUSH_DIRECT_PATHS(IncomingPacket &pkt,const RuntimeEnvi unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 2; while (count--) { - /* unsigned int flags = (*this)[ptr++]; */ ++ptr; + // unsigned int flags = (*this)[ptr++]; + ++ptr; unsigned int extLen = pkt.at(ptr); ptr += 2; ptr += extLen; // unused right now unsigned int addrType = pkt[ptr++]; @@ -707,12 +867,13 @@ ZT_ALWAYS_INLINE bool _doPUSH_DIRECT_PATHS(IncomingPacket &pkt,const RuntimeEnvi } peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,0); - +*/ return true; } -ZT_ALWAYS_INLINE bool _doUSER_MESSAGE(IncomingPacket &pkt,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer,const SharedPtr &path) +ZT_ALWAYS_INLINE bool _doUSER_MESSAGE(IncomingPacket &p,const RuntimeEnvironment *const RR,void *const tPtr,const SharedPtr &peer) { + /* if (likely(pkt.size() >= (ZT_PACKET_IDX_PAYLOAD + 8))) { ZT_UserMessage um; um.id = (const ZT_Identity *)(&(peer->identity())); @@ -722,6 +883,7 @@ ZT_ALWAYS_INLINE bool _doUSER_MESSAGE(IncomingPacket &pkt,const RuntimeEnvironme RR->node->postEvent(tPtr,ZT_EVENT_USER_MESSAGE,reinterpret_cast(&um)); } peer->received(tPtr,path,pkt.hops(),pkt.packetId(),pkt.payloadLength(),Packet::VERB_USER_MESSAGE,0,Packet::VERB_NOP,0); + */ return true; } @@ -730,77 +892,77 @@ ZT_ALWAYS_INLINE bool _doUSER_MESSAGE(IncomingPacket &pkt,const RuntimeEnvironme bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) { - const Address sourceAddress(source()); - const SharedPtr peer(RR->topology->get(tPtr,sourceAddress)); + const Address source(pkt->data.fields.source); + const SharedPtr peer(RR->topology->get(tPtr,source)); try { // Check for trusted paths or unencrypted HELLOs (HELLO is the only packet sent in the clear) - const unsigned int c = cipher(); + const uint8_t c = Protocol::packetCipher(pkt->data.fields); bool trusted = false; if (c == ZT_PROTO_CIPHER_SUITE__NONE) { // If this is marked as a packet via a trusted path, check source address and path ID. // Obviously if no trusted paths are configured this always returns false and such // packets are dropped on the floor. - const uint64_t tpid = trustedPathId(); - if (RR->topology->shouldInboundPathBeTrusted(_path->address(),tpid)) { + const uint64_t tpid = Utils::ntoh(pkt->data.fields.mac); // the MAC is the trusted path ID on these packets + if (RR->topology->shouldInboundPathBeTrusted(path->address(),tpid)) { trusted = true; } else { if (peer) - RR->t->incomingPacketDropped(tPtr,packetId(),0,peer->identity(),_path->address(),hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); + RR->t->incomingPacketDropped(tPtr,idBE,0,peer->identity(),path->address(),hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); return true; } - } else if ((c == ZT_PROTO_CIPHER_SUITE__POLY1305_NONE)&&(verb() == Packet::VERB_HELLO)) { - // Only HELLO is allowed in the clear, but will still have a MAC - return _doHELLO(*this,RR,tPtr,false,_path); + } else if ((c == ZT_PROTO_CIPHER_SUITE__POLY1305_NONE)&&(pkt->data.fields.verb == Protocol::VERB_HELLO)) { + // Only HELLO is allowed in the clear, but the MAC is still checked in _doHELLO(). + return _doHELLO(*this,RR,tPtr,false); } if (!peer) { - RR->sw->requestWhois(tPtr,RR->node->now(),sourceAddress); + RR->sw->requestWhois(tPtr,RR->node->now(),source); return false; } if (!trusted) { if (!dearmor(peer->key())) { - RR->t->incomingPacketDropped(tPtr,packetId(),0,peer->identity(),_path->address(),hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); + RR->t->incomingPacketDropped(tPtr,idBE,0,peer->identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED); return true; } } if (!uncompress()) { - RR->t->incomingPacketDropped(tPtr,packetId(),0,peer->identity(),_path->address(),hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_INVALID_COMPRESSED_DATA); + RR->t->incomingPacketDropped(tPtr,idBE,0,peer->identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_INVALID_COMPRESSED_DATA); return true; } - const Packet::Verb v = verb(); + const Protocol::Verb verb = (Protocol::Verb)pkt->data.fields.verb; bool r = true; - switch(v) { + switch(verb) { default: // ignore unknown verbs, but if they pass auth check they are "received" and considered NOPs by peer->receive() - RR->t->incomingPacketDropped(tPtr,packetId(),0,peer->identity(),_path->address(),hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_UNRECOGNIZED_VERB); + RR->t->incomingPacketDropped(tPtr,idBE,0,peer->identity(),path->address(),hops,verb,ZT_TRACE_PACKET_DROP_REASON_UNRECOGNIZED_VERB); // fall through - case Packet::VERB_NOP: - peer->received(tPtr,_path,hops(),packetId(),payloadLength(),v,0,Packet::VERB_NOP,0); + case Protocol::VERB_NOP: + peer->received(tPtr,path,hops,idBE,size,Protocol::VERB_NOP,0,Protocol::VERB_NOP,0); break; - case Packet::VERB_HELLO: r = _doHELLO(*this,RR,tPtr,true,_path); break; - case Packet::VERB_ERROR: r = _doERROR(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_OK: r = _doOK(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_WHOIS: r = _doWHOIS(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_RENDEZVOUS: r = _doRENDEZVOUS(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_FRAME: r = _doFRAME(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_EXT_FRAME: r = _doEXT_FRAME(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_ECHO: r = _doECHO(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_NETWORK_CREDENTIALS: r = _doNETWORK_CREDENTIALS(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_NETWORK_CONFIG_REQUEST: r = _doNETWORK_CONFIG_REQUEST(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_NETWORK_CONFIG: r = _doNETWORK_CONFIG(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_MULTICAST_GATHER: r = _doMULTICAST_GATHER(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_PUSH_DIRECT_PATHS: r = _doPUSH_DIRECT_PATHS(*this,RR,tPtr,peer,_path); break; - case Packet::VERB_USER_MESSAGE: r = _doUSER_MESSAGE(*this,RR,tPtr,peer,_path); break; + case Protocol::VERB_HELLO: r = _doHELLO(*this,RR,tPtr,true); break; + case Protocol::VERB_ERROR: r = _doERROR(*this,RR,tPtr,peer); break; + case Protocol::VERB_OK: r = _doOK(*this,RR,tPtr,peer); break; + case Protocol::VERB_WHOIS: r = _doWHOIS(*this,RR,tPtr,peer); break; + case Protocol::VERB_RENDEZVOUS: r = _doRENDEZVOUS(*this,RR,tPtr,peer); break; + case Protocol::VERB_FRAME: r = _doFRAME(*this,RR,tPtr,peer); break; + case Protocol::VERB_EXT_FRAME: r = _doEXT_FRAME(*this,RR,tPtr,peer); break; + case Protocol::VERB_ECHO: r = _doECHO(*this,RR,tPtr,peer); break; + case Protocol::VERB_NETWORK_CREDENTIALS: r = _doNETWORK_CREDENTIALS(*this,RR,tPtr,peer); break; + case Protocol::VERB_NETWORK_CONFIG_REQUEST: r = _doNETWORK_CONFIG_REQUEST(*this,RR,tPtr,peer); break; + case Protocol::VERB_NETWORK_CONFIG: r = _doNETWORK_CONFIG(*this,RR,tPtr,peer); break; + case Protocol::VERB_MULTICAST_GATHER: r = _doMULTICAST_GATHER(*this,RR,tPtr,peer); break; + case Protocol::VERB_PUSH_DIRECT_PATHS: r = _doPUSH_DIRECT_PATHS(*this,RR,tPtr,peer); break; + case Protocol::VERB_USER_MESSAGE: r = _doUSER_MESSAGE(*this,RR,tPtr,peer); break; } return r; } catch (int ztExcCode) { } catch ( ... ) {} if (peer) - RR->t->incomingPacketDropped(tPtr,packetId(),0,peer->identity(),_path->address(),hops(),Packet::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_UNSPECIFIED); + RR->t->incomingPacketDropped(tPtr,idBE,0,peer->identity(),path->address(),hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_UNSPECIFIED); return true; } diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp index 3f65dbf00..8ce34c200 100644 --- a/node/IncomingPacket.hpp +++ b/node/IncomingPacket.hpp @@ -14,11 +14,12 @@ #ifndef ZT_INCOMINGPACKET_HPP #define ZT_INCOMINGPACKET_HPP -#include "Packet.hpp" #include "Path.hpp" #include "Utils.hpp" #include "MulticastGroup.hpp" #include "Peer.hpp" +#include "Buf.hpp" +#include "Protocol.hpp" /* * The big picture: @@ -41,41 +42,20 @@ namespace ZeroTier { class RuntimeEnvironment; class Network; -class IncomingPacket : public Packet +class IncomingPacket { public: - ZT_ALWAYS_INLINE IncomingPacket() : Packet(),_receiveTime(0),_path() {} + ZT_ALWAYS_INLINE IncomingPacket() {} - /** - * Create a new packet-in-decode - * - * @param data Packet data - * @param len Packet length - * @param path Path over which packet arrived - * @param now Current time - * @throws std::out_of_range Range error processing packet - */ - ZT_ALWAYS_INLINE IncomingPacket(const void *data,unsigned int len,const SharedPtr &path,int64_t now) : - Packet(data,len), - _receiveTime(now), - _path(path) + template + ZT_ALWAYS_INLINE void set(const SharedPtr< Buf > &pkt_,const unsigned int pktSize_,const SharedPtr &path_,const int64_t now_) { - } - - /** - * Init packet-in-decode in place - * - * @param data Packet data - * @param len Packet length - * @param path Path over which packet arrived - * @param now Current time - * @throws std::out_of_range Range error processing packet - */ - ZT_ALWAYS_INLINE void init(const void *data,unsigned int len,const SharedPtr &path,int64_t now) - { - copyFrom(data,len); - _receiveTime = now; - _path = path; + idBE = 0; // initially zero, set when decryption/auth occurs + receiveTime = now_; + path = path_; + pkt = reinterpret_cast< SharedPtr< Buf< Protocol::Header > > >(pkt_); + size = pktSize_; + hops = Protocol::packetHops(pkt->data.fields); } /** @@ -94,13 +74,34 @@ public: bool tryDecode(const RuntimeEnvironment *RR,void *tPtr); /** - * @return Time of packet receipt / start of decode + * Packet ID in big-endian byte order or 0 if not decrypted/dearmored yet */ - ZT_ALWAYS_INLINE uint64_t receiveTime() const { return _receiveTime; } + uint64_t idBE; -private: - uint64_t _receiveTime; - SharedPtr _path; + /** + * Time packet was received + */ + int64_t receiveTime; + + /** + * Path over which packet was received + */ + SharedPtr< Path > path; + + /** + * Packet itself + */ + SharedPtr< Buf< Protocol::Header > > pkt; + + /** + * Size of packet in bytes + */ + unsigned int size; + + /** + * Hop count for received packet + */ + uint8_t hops; }; } // namespace ZeroTier diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index 1a1e5873e..0b2de4d45 100644 --- a/node/InetAddress.hpp +++ b/node/InetAddress.hpp @@ -21,14 +21,10 @@ #include "Constants.hpp" #include "Utils.hpp" #include "MAC.hpp" +#include "TriviallyCopyable.hpp" namespace ZeroTier { -/** - * Maximum integer value of enum IpScope - */ -#define ZT_INETADDRESS_MAX_SCOPE 7 - #define ZT_INETADDRESS_MARSHAL_SIZE_MAX 19 /** @@ -39,15 +35,16 @@ namespace ZeroTier { * sockaddr_storage and used interchangeably. DO NOT change this by e.g. * adding non-static fields, since much code depends on this identity. */ -struct InetAddress : public sockaddr_storage +struct InetAddress : public sockaddr_storage,public TriviallyCopyable { private: + // Internal function to copy any sockaddr_X structure to this one even if it's smaller and unpadded. template ZT_ALWAYS_INLINE void copySockaddrToThis(const SA *sa) { memcpy(reinterpret_cast(this),sa,sizeof(SA)); if (sizeof(SA) < sizeof(InetAddress)) - memset(reinterpret_cast(this) + sizeof(SA),0,sizeof(InetAddress) - sizeof(SA)); + memset(reinterpret_cast(this) + sizeof(SA),0,sizeof(InetAddress) - sizeof(SA)); } public: @@ -88,7 +85,8 @@ public: // Hasher for unordered sets and maps in C++11 struct Hasher { ZT_ALWAYS_INLINE std::size_t operator()(const InetAddress &a) const { return (std::size_t)a.hashCode(); } }; - ZT_ALWAYS_INLINE InetAddress() { memset(reinterpret_cast(this),0,sizeof(InetAddress)); } + ZT_ALWAYS_INLINE InetAddress() { memoryZero(this); } + ZT_ALWAYS_INLINE InetAddress(const InetAddress &a) { memoryCopy(this,&a); } explicit ZT_ALWAYS_INLINE InetAddress(const struct sockaddr_storage &ss) { *this = ss; } explicit ZT_ALWAYS_INLINE InetAddress(const struct sockaddr_storage *ss) { *this = ss; } explicit ZT_ALWAYS_INLINE InetAddress(const struct sockaddr &sa) { *this = sa; } @@ -101,19 +99,19 @@ public: ZT_ALWAYS_INLINE InetAddress(const uint32_t ipv4,unsigned int port) { this->set(&ipv4,4,port); } explicit ZT_ALWAYS_INLINE InetAddress(const char *ipSlashPort) { this->fromString(ipSlashPort); } - ZT_ALWAYS_INLINE void clear() { memset(reinterpret_cast(this),0,sizeof(InetAddress)); } + ZT_ALWAYS_INLINE void clear() { memoryZero(this); } ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_storage &ss) { - memcpy(reinterpret_cast(this),&ss,sizeof(InetAddress)); + memoryCopyUnsafe(this,&ss); return *this; } ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_storage *ss) { if (ss) - memcpy(reinterpret_cast(this),ss,sizeof(InetAddress)); - else memset(reinterpret_cast(this),0,sizeof(InetAddress)); + memoryCopyUnsafe(this,ss); + else memoryZero(this); return *this; } @@ -162,9 +160,10 @@ public: copySockaddrToThis(reinterpret_cast(sa)); else if (sa->sa_family == AF_INET6) copySockaddrToThis(reinterpret_cast(sa)); - return *this; + else memoryZero(this); + } else { + memoryZero(this); } - memset(reinterpret_cast(this),0,sizeof(InetAddress)); return *this; } @@ -338,7 +337,7 @@ public: switch(ss_family) { case AF_INET: return (const void *)&(reinterpret_cast(this)->sin_addr.s_addr); case AF_INET6: return (const void *)(reinterpret_cast(this)->sin6_addr.s6_addr); - default: return 0; + default: return nullptr; } } @@ -449,11 +448,6 @@ public: } } - /** - * Set to null/zero - */ - ZT_ALWAYS_INLINE void zero() { memset(this,0,sizeof(InetAddress)); } - /** * Check whether this is a network/route rather than an IP assignment * @@ -495,7 +489,7 @@ public: static ZT_ALWAYS_INLINE int marshalSizeMax() { return ZT_INETADDRESS_MARSHAL_SIZE_MAX; } int marshal(uint8_t data[ZT_INETADDRESS_MARSHAL_SIZE_MAX]) const; - int unmarshal(const uint8_t *restrict data,const int len); + int unmarshal(const uint8_t *restrict data,int len); bool operator==(const InetAddress &a) const; bool operator<(const InetAddress &a) const; diff --git a/node/LZ4.hpp b/node/LZ4.hpp index 33b3cd077..45db20c48 100644 --- a/node/LZ4.hpp +++ b/node/LZ4.hpp @@ -18,7 +18,7 @@ namespace ZeroTier { -int LZ4_compress_fast(const char *source,char *dest,int inputSize,int maxOutputSize,int acceleration); +int LZ4_compress_fast(const char *source,char *dest,int inputSize,int maxOutputSize,int acceleration = 1); int LZ4_decompress_safe(const char *source,char *dest,int compressedSize,int maxDecompressedSize); } // namespace ZeroTier diff --git a/node/Locator.hpp b/node/Locator.hpp index 49e4a6223..c365d06ca 100644 --- a/node/Locator.hpp +++ b/node/Locator.hpp @@ -21,6 +21,7 @@ #include "Constants.hpp" #include "Endpoint.hpp" #include "Identity.hpp" +#include "TriviallyCopyable.hpp" #define ZT_LOCATOR_MAX_ENDPOINTS 8 #define ZT_LOCATOR_MARSHAL_SIZE_MAX (8 + 2 + (ZT_ENDPOINT_MARSHAL_SIZE_MAX * ZT_LOCATOR_MAX_ENDPOINTS) + 2 + 2 + ZT_SIGNATURE_BUFFER_SIZE) @@ -33,7 +34,7 @@ namespace ZeroTier { * A locator contains long-lived endpoints for a node such as IP/port pairs, * URLs, or other nodes, and is signed by the node it describes. */ -class Locator +class Locator : public TriviallyCopyable { public: ZT_ALWAYS_INLINE Locator() { this->clear(); } diff --git a/node/MAC.hpp b/node/MAC.hpp index 7c52e4997..46e293ec3 100644 --- a/node/MAC.hpp +++ b/node/MAC.hpp @@ -21,26 +21,22 @@ #include "Constants.hpp" #include "Utils.hpp" #include "Address.hpp" +#include "TriviallyCopyable.hpp" namespace ZeroTier { /** * 48-byte Ethernet MAC address */ -class MAC +class MAC : public TriviallyCopyable { public: ZT_ALWAYS_INLINE MAC() : _m(0ULL) {} - ZT_ALWAYS_INLINE MAC(const unsigned char a,const unsigned char b,const unsigned char c,const unsigned char d,const unsigned char e,const unsigned char f) : - _m( ((((uint64_t)a) & 0xffULL) << 40U) | - ((((uint64_t)b) & 0xffULL) << 32U) | - ((((uint64_t)c) & 0xffULL) << 24U) | - ((((uint64_t)d) & 0xffULL) << 16U) | - ((((uint64_t)e) & 0xffULL) << 8U) | - (((uint64_t)f) & 0xffULL) ) {} + ZT_ALWAYS_INLINE MAC(const uint8_t a,const uint8_t b,const uint8_t c,const uint8_t d,const uint8_t e,const uint8_t f) : + _m( (((uint64_t)a) << 40U) | (((uint64_t)b) << 32U) | (((uint64_t)c) << 24U) | (((uint64_t)d) << 16U) | (((uint64_t)e) << 8U) | ((uint64_t)f) ) {} + explicit ZT_ALWAYS_INLINE MAC(const uint64_t m) : _m(m & 0xffffffffffffULL) {} explicit ZT_ALWAYS_INLINE MAC(const uint8_t b[6]) { setTo(b); } ZT_ALWAYS_INLINE MAC(const Address &ztaddr,uint64_t nwid) { fromAddress(ztaddr,nwid); } - explicit ZT_ALWAYS_INLINE MAC(const uint64_t m) : _m(m & 0xffffffffffffULL) {} /** * @return MAC in 64-bit integer diff --git a/node/Meter.hpp b/node/Meter.hpp index 27b53c522..6592e9307 100644 --- a/node/Meter.hpp +++ b/node/Meter.hpp @@ -43,7 +43,7 @@ public: const int64_t since = now - _ts; if (since >= ZT_METER_HISTORY_TICK_DURATION) { _ts = now; - _history[(unsigned int)(++_hptr) % ZT_METER_HISTORY_LENGTH] = (double)_count / ((double)since / 1000.0); + _history[++_hptr % ZT_METER_HISTORY_LENGTH] = (double)_count / ((double)since / 1000.0); _count = (uint64_t)count; } else { _count += (uint64_t)count; @@ -69,7 +69,7 @@ private: volatile double _history[ZT_METER_HISTORY_LENGTH]; volatile int64_t _ts; volatile uint64_t _count; - AtomicCounter _hptr; + AtomicCounter _hptr; }; } // namespace ZeroTier diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp index 80317b487..2456e180c 100644 --- a/node/MulticastGroup.hpp +++ b/node/MulticastGroup.hpp @@ -20,6 +20,7 @@ #include "MAC.hpp" #include "InetAddress.hpp" #include "Utils.hpp" +#include "TriviallyCopyable.hpp" namespace ZeroTier { @@ -38,7 +39,7 @@ namespace ZeroTier { * * MulticastGroup behaves as an immutable value object. */ -class MulticastGroup +class MulticastGroup : public TriviallyCopyable { public: ZT_ALWAYS_INLINE MulticastGroup() : _mac(),_adi(0) {} diff --git a/node/Network.cpp b/node/Network.cpp index cd274ba84..b4acdffea 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -21,12 +21,11 @@ #include "Address.hpp" #include "InetAddress.hpp" #include "Switch.hpp" -#include "Buffer.hpp" -#include "Packet.hpp" #include "NetworkController.hpp" #include "Peer.hpp" #include "Trace.hpp" #include "ScopedPtr.hpp" +#include "Buf.hpp" #include @@ -202,10 +201,10 @@ _doZtFilterResult _doZtFilter( thisRuleMatches = (uint8_t)(rules[rn].v.vlanDei == 0); break; case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: - thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac,6) == macSource); + thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac) == macSource); break; case ZT_NETWORK_RULE_MATCH_MAC_DEST: - thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac,6) == macDest); + thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac) == macDest); break; case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { @@ -545,9 +544,6 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u _destroyed(false), _netconfFailure(NETCONF_FAILURE_NONE) { - for(int i=0;isetConfiguration(tPtr,*nconf,false); _lastConfigUpdate = 0; // still want to re-request since it's likely outdated @@ -556,15 +552,15 @@ Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *u tmp[0] = nwid; tmp[1] = 0; bool got = false; - ScopedPtr< Dictionary > dict(new Dictionary()); try { + Dictionary dict; std::vector nconfData(RR->node->stateObjectGet(tPtr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp)); if (nconfData.size() > 2) { nconfData.push_back(0); - if (dict->load((const char *)nconfData.data())) { + if (dict.decode(nconfData.data(),(unsigned int)nconfData.size())) { try { ScopedPtr nconf2(new NetworkConfig()); - if (nconf2->fromDictionary(*dict)) { + if (nconf2->fromDictionary(dict)) { this->setConfiguration(tPtr,*nconf2,false); _lastConfigUpdate = 0; // still want to re-request an update since it's likely outdated got = true; @@ -853,122 +849,134 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg) _myMulticastGroups.erase(i); } -uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) +uint64_t Network::handleConfigChunk(void *tPtr,uint64_t packetId,const Address &source,const Buf<> &chunk,int ptr,int size) { if (_destroyed) return 0; - const unsigned int start = ptr; - + const unsigned int chunkPayloadStart = ptr; ptr += 8; // skip network ID, which is already obviously known - const unsigned int chunkLen = chunk.at(ptr); ptr += 2; - const void *chunkData = chunk.field(ptr,chunkLen); ptr += chunkLen; + const unsigned int chunkLen = chunk.rI16(ptr); + const uint8_t *chunkData = chunk.rBnc(ptr,chunkLen); + if (Buf<>::readOverflow(ptr,size)) + return 0; + Mutex::Lock l1(_config_l); + + _IncomingConfigChunk *c = nullptr; uint64_t configUpdateId; - { - Mutex::Lock l1(_config_l); + int totalLength = 0,chunkIndex = 0; + if (ptr < size) { + // If there is more data after the chunk / dictionary, it means this is a new controller + // that sends signed chunks. We still support really old controllers, but probably not forever. + const bool fastPropagate = ((chunk.rI8(ptr) & Protocol::NETWORK_CONFIG_FLAG_FAST_PROPAGATE) != 0); + configUpdateId = chunk.rI64(ptr); + totalLength = chunk.rI32(ptr); + chunkIndex = chunk.rI32(ptr); + ++ptr; // skip unused signature type field + const unsigned int signatureSize = chunk.rI16(ptr); + const uint8_t *signature = chunk.rBnc(ptr,signatureSize); + if ((Buf<>::readOverflow(ptr,size))||((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_MAX_NETWORK_CONFIG_BYTES)||(signatureSize > ZT_SIGNATURE_BUFFER_SIZE)||(!signature)) + return 0; + const unsigned int chunkPayloadSize = (unsigned int)ptr - chunkPayloadStart; - _IncomingConfigChunk *c = nullptr; - uint64_t chunkId = 0; - unsigned long totalLength,chunkIndex; - if (ptr < chunk.size()) { - const bool fastPropagate = ((chunk[ptr++] & 0x01U) != 0); - configUpdateId = chunk.at(ptr); ptr += 8; - totalLength = chunk.at(ptr); ptr += 4; - chunkIndex = chunk.at(ptr); ptr += 4; + // Find existing or new slot for this update and its chunk(s). + for(int i=0;ichunks.find(chunkIndex) != c->chunks.end()) + return 0; // we already have this chunk! + break; + } else if ((!c)||(_incomingConfigChunks[i].touchCtr < c->touchCtr)) { + c = &(_incomingConfigChunks[i]); + } + } + if (!c) // sanity check; should not be possible + return 0; - if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) // >= since we need room for a null at the end - return 0; - if ((chunk[ptr] != 1)||(chunk.at(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) - return 0; - const uint8_t *sig = reinterpret_cast(chunk.field(ptr + 3,ZT_C25519_SIGNATURE_LEN)); + // Verify this chunk's signature + const SharedPtr controllerPeer(RR->topology->get(tPtr,controller())); + if ((!controllerPeer)||(!controllerPeer->identity().verify(chunk.data.bytes + chunkPayloadStart,chunkPayloadSize,signature,signatureSize))) + return 0; - // We can use the signature, which is unique per chunk, to get a per-chunk ID for local deduplication use - for(unsigned int i=0;i<16;++i) - reinterpret_cast(&chunkId)[i & 7] ^= sig[i]; + // New properly verified chunks can be flooded "virally" through the network via an aggressive + // exponential rumor mill algorithm. + if (fastPropagate) { + Mutex::Lock l2(_memberships_l); + Address *a = nullptr; + Membership *m = nullptr; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) { + if ((*a != source)&&(*a != controller())) { + ZT_GET_NEW_BUF(outp,Protocol::Header); - // Find existing or new slot for this update and check if this is a duplicate chunk - for(int i=0;idata.fields.packetId = Protocol::getPacketId(); + a->copyTo(outp->data.fields.destination); + RR->identity.address().copyTo(outp->data.fields.source); + outp->data.fields.flags = 0; + outp->data.fields.verb = Protocol::VERB_NETWORK_CONFIG; - for(unsigned long j=0;jhaveChunks;++j) { - if (c->haveChunkIds[j] == chunkId) - return 0; - } + int outl = sizeof(Protocol::Header); + outp->wB(outl,chunk.data.bytes + chunkPayloadStart,chunkPayloadSize); - break; - } else if ((!c)||(_incomingConfigChunks[i].ts < c->ts)) { - c = &(_incomingConfigChunks[i]); + if (Buf<>::writeOverflow(outl)) // sanity check... it fit before! + break; + + RR->sw->send(tPtr,outp,true); } } + } + } else if ((source == controller())||(!source)) { + // Legacy support for OK(NETWORK_CONFIG_REQUEST) from older controllers that don't sign chunks and don't + // support multiple chunks. Since old controllers don't sign chunks we only accept the message if it comes + // directly from the controller. + configUpdateId = packetId; + totalLength = (int)chunkLen; + if (totalLength > ZT_MAX_NETWORK_CONFIG_BYTES) + return 0; - // If it's not a duplicate, check chunk signature - const SharedPtr controllerPeer(RR->topology->get(tPtr,controller())); - if (!controllerPeer) - return 0; - if (!controllerPeer->identity().verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) - return 0; + for(int i=0;itouchCtr)) + c = &(_incomingConfigChunks[i]); + } + } else { + // Not signed, not from controller -> reject. + return 0; + } - // New properly verified chunks can be flooded "virally" through the network - if (fastPropagate) { - Mutex::Lock l2(_memberships_l); - Address *a = nullptr; - Membership *m = nullptr; - Hashtable::Iterator i(_memberships); - while (i.next(a,m)) { - if ((*a != source)&&(*a != controller())) { - Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CONFIG); - outp.append(reinterpret_cast(chunk.data()) + start,chunk.size() - start); - RR->sw->send(tPtr,outp,true); - } - } - } - } else if ((source == controller())||(!source)) { // since old chunks aren't signed, only accept from controller itself (or via cluster backplane) - // Legacy support for OK(NETWORK_CONFIG_REQUEST) from older controllers - chunkId = packetId; - configUpdateId = chunkId; - totalLength = chunkLen; - chunkIndex = 0; + try { + ++c->touchCtr; + if (c->updateId != configUpdateId) { + c->updateId = configUpdateId; + c->chunks.clear(); + } + c->chunks[chunkIndex].assign(chunkData,chunkData + chunkLen); - if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) - return 0; - - for(int i=0;its)) - c = &(_incomingConfigChunks[i]); - } - } else { - // Single-chunk unsigned legacy configs are only allowed from the controller itself + int haveLength = 0; + for(std::map< int,std::vector >::const_iterator ch(c->chunks.begin());ch!=c->chunks.end();++ch) + haveLength += (int)ch->second.size(); + if (haveLength > ZT_MAX_NETWORK_CONFIG_BYTES) { + c->touchCtr = 0; + c->updateId = 0; + c->chunks.clear(); return 0; } - ++c->ts; // newer is higher, that's all we need + if (haveLength == totalLength) { + std::vector assembledConfig; + for(std::map< int,std::vector >::const_iterator ch(c->chunks.begin());ch!=c->chunks.end();++ch) + assembledConfig.insert(assembledConfig.end(),ch->second.begin(),ch->second.end()); - if (c->updateId != configUpdateId) { - c->updateId = configUpdateId; - c->haveChunks = 0; - c->haveBytes = 0; - } - if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) - return false; - c->haveChunkIds[c->haveChunks++] = chunkId; - - memcpy(c->data.unsafeData() + chunkIndex,chunkData,chunkLen); - c->haveBytes += chunkLen; - - if (c->haveBytes == totalLength) { - c->data.unsafeData()[c->haveBytes] = (char)0; // ensure null terminated - - ScopedPtr nc(new NetworkConfig()); - try { - if (nc->fromDictionary(c->data)) { + Dictionary dict; + if (dict.decode(assembledConfig.data(),(unsigned int)assembledConfig.size())) { + ScopedPtr nc(new NetworkConfig()); + if (nc->fromDictionary(dict)) { this->setConfiguration(tPtr,*nc,true); return configUpdateId; } - } catch ( ... ) {} + } } - } + } catch (...) {} return 0; } @@ -1004,11 +1012,13 @@ int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToD if (saveToDisk) { try { - ScopedPtr< Dictionary > d(new Dictionary()); - if (nconf.toDictionary(*d,false)) { + Dictionary d; + if (nconf.toDictionary(d,false)) { uint64_t tmp[2]; tmp[0] = _id; tmp[1] = 0; - RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp,d->data(),d->sizeBytes()); + std::vector d2; + d.encode(d2); + RR->node->stateObjectPut(tPtr,ZT_STATE_OBJECT_NETWORK_CONFIG,tmp,d2.data(),(unsigned int)d2.size()); } } catch ( ... ) {} } @@ -1320,7 +1330,7 @@ void Network::_requestConfiguration(void *tPtr) nconf->name[nn++] = '0'; nconf->name[nn++] = '.'; nconf->name[nn++] = '0'; - nconf->name[nn++] = (char)0; + nconf->name[nn] = (char)0; this->setConfiguration(tPtr,*nconf,false); } @@ -1329,24 +1339,24 @@ void Network::_requestConfiguration(void *tPtr) const Address ctrl(controller()); - ScopedPtr< Dictionary > rmd(new Dictionary()); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR,(uint64_t)1); // 1 == ZeroTier, no other vendors at the moment - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,(uint64_t)ZT_PROTO_VERSION); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MAJOR); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MINOR); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,(uint64_t)ZEROTIER_ONE_VERSION_REVISION); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_RULES,(uint64_t)ZT_MAX_NETWORK_RULES); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_CAPABILITIES,(uint64_t)ZT_MAX_NETWORK_CAPABILITIES); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_CAPABILITY_RULES,(uint64_t)ZT_MAX_CAPABILITY_RULES); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS,(uint64_t)ZT_MAX_NETWORK_TAGS); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS,(uint64_t)0); - rmd->add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,(uint64_t)ZT_RULES_ENGINE_REVISION); + Dictionary rmd; + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR,(uint64_t)1); // 1 == ZeroTier, no other vendors at the moment + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,(uint64_t)ZT_PROTO_VERSION); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MAJOR); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MINOR); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,(uint64_t)ZEROTIER_ONE_VERSION_REVISION); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_RULES,(uint64_t)ZT_MAX_NETWORK_RULES); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_CAPABILITIES,(uint64_t)ZT_MAX_NETWORK_CAPABILITIES); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_CAPABILITY_RULES,(uint64_t)ZT_MAX_CAPABILITY_RULES); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS,(uint64_t)ZT_MAX_NETWORK_TAGS); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS,(uint64_t)0); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,(uint64_t)ZT_RULES_ENGINE_REVISION); RR->t->networkConfigRequestSent(tPtr,_id); if (ctrl == RR->identity.address()) { if (RR->localNetworkController) { - RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,*rmd); + RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,rmd); } else { this->setNotFound(); } diff --git a/node/Network.hpp b/node/Network.hpp index ef07ee6d5..1e89accd0 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -29,13 +29,13 @@ #include "AtomicCounter.hpp" #include "MulticastGroup.hpp" #include "MAC.hpp" +#include "Buf.hpp" #include "Dictionary.hpp" #include "Membership.hpp" #include "NetworkConfig.hpp" #include "CertificateOfMembership.hpp" #define ZT_NETWORK_MAX_INCOMING_UPDATES 3 -#define ZT_NETWORK_MAX_UPDATE_CHUNKS ((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1) namespace ZeroTier { @@ -181,20 +181,22 @@ public: void multicastUnsubscribe(const MulticastGroup &mg); /** - * Handle an inbound network config chunk + * Parse, verify, and handle an inbound network config chunk * * This is called from IncomingPacket to handle incoming network config - * chunks via OK(NETWORK_CONFIG_REQUEST) or NETWORK_CONFIG. It verifies - * each chunk and once assembled applies the configuration. + * chunks via OK(NETWORK_CONFIG_REQUEST) or NETWORK_CONFIG. It's a common + * bit of packet parsing code that also verifies chunks and replicates + * them (via rumor mill flooding) if their fast propagate flag is set. * * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call * @param packetId Packet ID or 0 if none (e.g. via cluster path) * @param source Address of sender of chunk or NULL if none (e.g. via cluster path) * @param chunk Buffer containing chunk - * @param ptr Index of chunk and related fields in packet + * @param ptr Index of chunk and related fields in packet (starting with network ID) + * @param size Size of data in chunk buffer (total, not relative to ptr) * @return Update ID if update was fully assembled and accepted or 0 otherwise */ - uint64_t handleConfigChunk(void *tPtr,uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr); + uint64_t handleConfigChunk(void *tPtr,uint64_t packetId,const Address &source,const Buf<> &chunk,int ptr,int size); /** * Set network configuration @@ -374,17 +376,14 @@ private: Hashtable< MAC,Address > _remoteBridgeRoutes; // remote addresses where given MACs are reachable (for tracking devices behind remote bridges) NetworkConfig _config; - volatile uint64_t _lastConfigUpdate; + volatile int64_t _lastConfigUpdate; struct _IncomingConfigChunk { - ZT_ALWAYS_INLINE _IncomingConfigChunk() : ts(0),updateId(0),haveChunks(0),haveBytes(0),data() {} - uint64_t ts; + ZT_ALWAYS_INLINE _IncomingConfigChunk() : touchCtr(0),updateId(0) {} + uint64_t touchCtr; uint64_t updateId; - uint64_t haveChunkIds[ZT_NETWORK_MAX_UPDATE_CHUNKS]; - unsigned long haveChunks; - unsigned long haveBytes; - Dictionary data; + std::map< int,std::vector > chunks; }; _IncomingConfigChunk _incomingConfigChunks[ZT_NETWORK_MAX_INCOMING_UPDATES]; diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp index 626dcb40c..48ef6bc9d 100644 --- a/node/NetworkConfig.cpp +++ b/node/NetworkConfig.cpp @@ -17,33 +17,13 @@ #include "NetworkConfig.hpp" #include "ScopedPtr.hpp" +#include "Buf.hpp" namespace ZeroTier { -NetworkConfig::NetworkConfig() : - networkId(0), - timestamp(0), - credentialTimeMaxDelta(0), - revision(0), - issuedTo(), - flags(0), - mtu(0), - multicastLimit(0), - specialistCount(0), - routeCount(0), - staticIpCount(0), - ruleCount(0), - capabilityCount(0), - tagCount(0), - certificateOfOwnershipCount(0), - type(ZT_NETWORK_TYPE_PRIVATE) -{ - name[0] = 0; -} - bool NetworkConfig::toDictionary(Dictionary &d,bool includeLegacy) const { - uint8_t tmp[16384]; + uint8_t tmp[ZT_BUF_MEM_SIZE]; try { d.clear(); diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp index 49c823fb9..ad4865e36 100644 --- a/node/NetworkConfig.hpp +++ b/node/NetworkConfig.hpp @@ -34,6 +34,7 @@ #include "Identity.hpp" #include "Utils.hpp" #include "Trace.hpp" +#include "TriviallyCopyable.hpp" namespace ZeroTier { @@ -79,16 +80,10 @@ namespace ZeroTier { #define ZT_NETWORKCONFIG_SPECIALIST_TYPE_MULTICAST_REPLICATOR 0x0000040000000000ULL /** - * Device that is allowed to remotely debug connectivity on this network + * Device that is allowed to remotely debug this network and query other peers for e.g. remote trace data */ #define ZT_NETWORKCONFIG_SPECIALIST_TYPE_DIAGNOSTICIAN 0x0000080000000000ULL -// Dictionary capacity needed for max size network config -#define ZT_NETWORKCONFIG_DICT_CAPACITY (1024 + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS) + (sizeof(CertificateOfOwnership) * ZT_MAX_CERTIFICATES_OF_OWNERSHIP)) - -// Dictionary capacity needed for max size network meta-data -#define ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY 8192 - // Fields for meta-data sent with network config requests // Protocol version (see Packet.hpp) @@ -164,9 +159,9 @@ namespace ZeroTier { * This is a memcpy()'able structure and is safe (in a crash sense) to modify * without locks. */ -struct NetworkConfig +struct NetworkConfig : TriviallyCopyable { - NetworkConfig(); + ZT_ALWAYS_INLINE NetworkConfig() { memoryZero(this); } /** * Write this network config to a dictionary for transport @@ -232,7 +227,7 @@ struct NetworkConfig * @param f Flags (OR of specialist role/type flags) * @return True if successfully masked or added */ - bool addSpecialist(const Address &a,const uint64_t f); + bool addSpecialist(const Address &a,uint64_t f); ZT_ALWAYS_INLINE const Capability *capability(const uint32_t id) const { @@ -277,6 +272,13 @@ struct NetworkConfig */ Address issuedTo; + /** + * Hash of identity public key(s) of node to whom this is issued + * + * TODO + */ + uint8_t issuedToIdentityHash[ZT_IDENTITY_HASH_SIZE]; + /** * Flags (64-bit) */ diff --git a/node/NetworkController.hpp b/node/NetworkController.hpp index fb093dcc9..a4f1934ce 100644 --- a/node/NetworkController.hpp +++ b/node/NetworkController.hpp @@ -103,7 +103,7 @@ public: const InetAddress &fromAddr, uint64_t requestPacketId, const Identity &identity, - const Dictionary &metaData) = 0; + const Dictionary &metaData) = 0; }; } // namespace ZeroTier diff --git a/node/Path.hpp b/node/Path.hpp index 4272a29ba..851a23991 100644 --- a/node/Path.hpp +++ b/node/Path.hpp @@ -17,9 +17,9 @@ #include #include #include - #include #include +#include #include "Constants.hpp" #include "InetAddress.hpp" @@ -32,6 +32,9 @@ namespace ZeroTier { class RuntimeEnvironment; +template +class Defragmenter; + /** * A path across the physical network */ @@ -39,6 +42,10 @@ class Path { friend class SharedPtr; + // Allow defragmenter to access fragment in flight info stored in Path for performance reasons. + template + friend class Defragmenter; + public: /** * Efficient unique key for paths in a Hashtable @@ -75,8 +82,7 @@ public: _localSocket(l), _lastIn(0), _lastOut(0), - _addr(r), - __refCount() + _addr(r) { } @@ -156,7 +162,14 @@ private: int64_t _lastIn; int64_t _lastOut; InetAddress _addr; - AtomicCounter __refCount; + + // These fields belong to Defragmenter but are kept in Path for performance + // as it's much faster this way than having Defragmenter maintain another + // mapping from paths to inbound message IDs. + std::set _inboundFragmentedMessages; + Mutex _inboundFragmentedMessages_l; + + AtomicCounter __refCount; }; } // namespace ZeroTier diff --git a/node/Peer.hpp b/node/Peer.hpp index b835685c5..d5d5a888d 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -22,7 +22,6 @@ #include "Utils.hpp" #include "Identity.hpp" #include "InetAddress.hpp" -#include "Packet.hpp" #include "SharedPtr.hpp" #include "AtomicCounter.hpp" #include "Hashtable.hpp" @@ -83,6 +82,15 @@ public: */ ZT_ALWAYS_INLINE const Identity &identity() const { return _id; } + /** + * @return Copy of current locator + */ + ZT_ALWAYS_INLINE Locator locator() const + { + RWMutex::RLock l(_lock); + return _locator; + } + /** * Log receipt of an authenticated packet * @@ -314,7 +322,7 @@ private: volatile int64_t _lastPrioritizedPaths; volatile unsigned int _latency; - AtomicCounter __refCount; + AtomicCounter __refCount; RWMutex _lock; // locks _alivePathCount, _paths, _locator, and _bootstrap. diff --git a/node/Protocol.cpp b/node/Protocol.cpp index c1f34262e..462af7d19 100644 --- a/node/Protocol.cpp +++ b/node/Protocol.cpp @@ -40,7 +40,7 @@ const uint64_t ZEROES32[4] = { 0,0,0,0 }; * @param in Input key (32 bytes) * @param out Output buffer (32 bytes) */ -ZT_ALWAYS_INLINE void _salsa20MangleKey(const uint8_t *const in,uint8_t *const out,const Buf< Header<> > &packet,const unsigned int packetSize) +ZT_ALWAYS_INLINE void _salsa20MangleKey(const uint8_t *const in,uint8_t *const out,const Buf< Header > &packet,const unsigned int packetSize) { // IV and source/destination addresses. Using the addresses divides the // key space into two halves-- A->B and B->A (since order will change). @@ -78,7 +78,7 @@ static std::atomic _packetIdCtr(_initPacketID()); } // anonymous namespace -void armor(Buf< Header<> > &packet,const unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],const uint8_t cipherSuite) +void _armor(Buf< Header > &packet,const unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],const uint8_t cipherSuite) { packet.data.fields.flags = (packet.data.fields.flags & 0xc7U) | ((cipherSuite << 3U) & 0x38U); // FFCCCHHH if (cipherSuite == ZT_PROTO_CIPHER_SUITE__AES_GCM) { @@ -102,7 +102,7 @@ void armor(Buf< Header<> > &packet,const unsigned int packetSize,const uint8_t k } } -int dearmor(Buf< Header<> > &packet,const unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]) +int _dearmor(Buf< Header > &packet,const unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]) { const int cipherSuite = (int)(packet.data.fields.flags & 0x38U); if (cipherSuite == ZT_PROTO_CIPHER_SUITE__AES_GCM) { @@ -128,7 +128,7 @@ int dearmor(Buf< Header<> > &packet,const unsigned int packetSize,const uint8_t return cipherSuite; } -unsigned int compress(Buf< Header<> > &packet,const unsigned int packetSize) +unsigned int _compress(Buf< Header > &packet,const unsigned int packetSize) { uint8_t tmp[ZT_BUF_MEM_SIZE + 32]; @@ -140,8 +140,7 @@ unsigned int compress(Buf< Header<> > &packet,const unsigned int packetSize) reinterpret_cast(packet.data.bytes + ZT_PROTO_PACKET_PAYLOAD_START), reinterpret_cast(tmp), (int)uncompressedLen, - sizeof(tmp) - ZT_PROTO_PACKET_PAYLOAD_START, - 2); + sizeof(tmp) - ZT_PROTO_PACKET_PAYLOAD_START); if ((compressedLen > 0)&&(compressedLen < uncompressedLen)) { packet.data.fields.verb |= ZT_PROTO_VERB_FLAG_COMPRESSED; memcpy(packet.data.bytes + ZT_PROTO_PACKET_PAYLOAD_START,tmp,compressedLen); @@ -151,7 +150,7 @@ unsigned int compress(Buf< Header<> > &packet,const unsigned int packetSize) return packetSize; } -int uncompress(Buf< Header<> > &packet,const unsigned int packetSize) +int _uncompress(Buf< Header > &packet,const unsigned int packetSize) { uint8_t tmp[ZT_BUF_MEM_SIZE]; diff --git a/node/Protocol.hpp b/node/Protocol.hpp index 9cb94b649..3583f2117 100644 --- a/node/Protocol.hpp +++ b/node/Protocol.hpp @@ -47,8 +47,7 @@ * + Tags and Capabilities * + inline push of CertificateOfMembership deprecated * 9 - 1.2.0 ... 1.2.14 - * 10 - 1.4.0 ... 1.6.0 - * + Multipath capability and load balancing + * 10 - 1.4.0 ... 1.4.6 * 11 - 2.0.0 ... CURRENT * + Peer-to-peer multicast replication * + Old planet/moon stuff is DEAD! @@ -58,13 +57,21 @@ */ #define ZT_PROTO_VERSION 11 +/** + * Minimum supported protocol version + * + * As of v2 we don't "officially" support anything older than 1.2.14, but this + * is the hard cutoff before which peers will be flat out rejected. + */ +#define ZT_PROTO_VERSION_MIN 6 + /** * Packet buffer size (can be changed) */ #define ZT_PROTO_MAX_PACKET_LENGTH (ZT_MAX_PACKET_FRAGMENTS * ZT_DEFAULT_PHYSMTU) /** - * Minimum viable packet length (a.k.a. header length) + * Minimum viable packet length (outer header + verb) */ #define ZT_PROTO_MIN_PACKET_LENGTH 28 @@ -73,7 +80,6 @@ */ #define ZT_PROTO_PACKET_ENCRYPTED_SECTION_START 27 - /** * Index at which packet payload begins (after verb) */ @@ -106,9 +112,9 @@ #define ZT_PROTO_CIPHER_SUITE__NONE 2 /** - * AES-GCM with AES-256 + * AES-GCM-NRH (AES-GCM with nonce reuse hardening) w/AES-256 */ -#define ZT_PROTO_CIPHER_SUITE__AES_GCM 3 +#define ZT_PROTO_CIPHER_SUITE__AES_GCM_NRH 3 /** * Magic number indicating a fragment @@ -116,7 +122,7 @@ #define ZT_PACKET_FRAGMENT_INDICATOR 0xff /** - * Minimum viable fragment length + * Minimum viable length for a fragment */ #define ZT_PROTO_MIN_FRAGMENT_LENGTH 16 @@ -136,37 +142,37 @@ #define ZT_PROTO_VERB_FLAG_COMPRESSED 0x80 /** - * Signed locator for this node + * HELLO exchange meta-data: signed locator for this node */ #define ZT_PROTO_HELLO_NODE_META_LOCATOR "l" /** - * Ephemeral C25519 public key + * HELLO exchange meta-data: ephemeral C25519 public key */ #define ZT_PROTO_HELLO_NODE_META_EPHEMERAL_KEY_C25519 "e0" /** - * Ephemeral NIST P-384 public key + * HELLO exchange meta-data: ephemeral NIST P-384 public key */ #define ZT_PROTO_HELLO_NODE_META_EPHEMERAL_KEY_P384 "e1" /** - * Addresses of ZeroTier nodes to whom this node will relay or one entry for 0000000000 if promiscuous. + * HELLO exchange meta-data: address(es) of nodes to whom this node will relay */ -#define ZT_PROTO_HELLO_NODE_META_WILL_RELAY_TO "r" +#define ZT_PROTO_HELLO_NODE_META_WILL_RELAY_TO "wr" /** - * X coordinate of your node (sent in OK(HELLO)) + * HELLO exchange meta-data: X coordinate of your node (sent in OK(HELLO)) */ #define ZT_PROTO_HELLO_NODE_META_LOCATION_X "gX" /** - * Y coordinate of your node (sent in OK(HELLO)) + * HELLO exchange meta-data: Y coordinate of your node (sent in OK(HELLO)) */ #define ZT_PROTO_HELLO_NODE_META_LOCATION_Y "gY" /** - * Z coordinate of your node (sent in OK(HELLO)) + * HELLO exchange meta-data: Z coordinate of your node (sent in OK(HELLO)) */ #define ZT_PROTO_HELLO_NODE_META_LOCATION_Z "gZ" @@ -206,11 +212,11 @@ * first fragment it receives. * * Fragments are sent with the following format: - * <[8] packet ID of packet whose fragment this belongs to> + * <[8] packet ID of packet to which this fragment belongs> * <[5] destination ZT address> - * <[1] 0xff, a reserved address, signals that this isn't a normal packet> + * <[1] 0xff here signals that this is a fragment> * <[1] total fragments (most significant 4 bits), fragment no (LS 4 bits)> - * <[1] ZT hop count (top 5 bits unused and must be zero)> + * <[1] ZT hop count (least significant 3 bits; others are reserved)> * <[...] fragment data> * * The protocol supports a maximum of 16 fragments. If a fragment is received @@ -452,38 +458,12 @@ enum Verb * <[8] 64-bit timestamp of netconf we currently have> * * This message requests network configuration from a node capable of - * providing it. - * - * Responses to this are always whole configs intended for the recipient. - * For patches and other updates a NETWORK_CONFIG is sent instead. - * - * It would be valid and correct as of 1.2.0 to use NETWORK_CONFIG always, - * but OK(NETWORK_CONFIG_REQUEST) should be sent for compatibility. + * providing it. Responses can be sent as OK(NETWORK_CONFIG_REQUEST) + * or NETWORK_CONFIG messages. NETWORK_CONFIG can also be sent by + * network controllers or other nodes unsolicited. * * OK response payload: - * <[8] 64-bit network ID> - * <[2] 16-bit length of network configuration dictionary chunk> - * <[...] network configuration dictionary (may be incomplete)> - * [ ... end of legacy single chunk response ... ] - * <[1] 8-bit flags> - * <[8] 64-bit config update ID (should never be 0)> - * <[4] 32-bit total length of assembled dictionary> - * <[4] 32-bit index of chunk> - * [ ... end signed portion ... ] - * <[1] 8-bit chunk signature type> - * <[2] 16-bit length of chunk signature> - * <[...] chunk signature> - * - * The chunk signature signs the entire payload of the OK response. - * Currently only one signature type is supported: ed25519 (1). - * - * Each config chunk is signed to prevent memory exhaustion or - * traffic crowding DOS attacks against config fragment assembly. - * - * If the packet is from the network controller it is permitted to end - * before the config update ID or other chunking related or signature - * fields. This is to support older controllers that don't include - * these fields and may be removed in the future. + * (same as VERB_NETWORK_CONFIG payload) * * ERROR response payload: * <[8] 64-bit network ID> @@ -500,19 +480,19 @@ enum Verb * <[4] 32-bit total length of assembled dictionary> * <[4] 32-bit index of chunk> * [ ... end signed portion ... ] - * <[1] 8-bit chunk signature type> + * <[1] 8-bit reserved field (legacy)> * <[2] 16-bit length of chunk signature> * <[...] chunk signature> * - * This is a direct push variant for network config updates. It otherwise - * carries the same payload as OK(NETWORK_CONFIG_REQUEST) and has the same - * semantics. - * - * The legacy mode missing the additional chunking fields is not supported - * here. + * Network configurations can come from network controllers or theoretically + * any other node, but each chunk must be signed by the network controller + * that generated it originally. The config update ID is arbitrary and is merely + * used by the receiver to group chunks. Chunk indexes must be sequential and + * the total delivered chunks must yield a total network config equal to the + * specified total length. * * Flags: - * 0x01 - Use fast propagation + * 0x01 - Use fast propagation -- rumor mill flood this chunk to other members * * An OK should be sent if the config is successfully received and * accepted. @@ -688,12 +668,15 @@ enum ErrorCode /* Tried to join network, but you're not a member */ ERROR_NETWORK_ACCESS_DENIED_ = 0x07, /* extra _ at end to avoid Windows name conflict */ - /* Cannot deliver a forwarded ZeroTier packet (e.g. hops exceeded, no routes) */ + /* Cannot deliver a forwarded ZeroTier packet (for any reason) */ ERROR_CANNOT_DELIVER = 0x09 }; /** * EXT_FRAME subtypes, which are packed into three bits in the flags field. + * + * This allows the node to know whether this is a normal frame or one generated + * by a special tee or redirect type flow rule. */ enum ExtFrameSubtype { @@ -711,11 +694,30 @@ enum ExtFrameSubtype */ enum ExtFrameFlag { + /** + * A certifiate of membership was included (no longer used but still accepted) + */ EXT_FRAME_FLAG_COM_ATTACHED_deprecated = 0x01, - // bits 0x02, 0x04, and 0x08 are occupied by the ExtFrameSubtype + + // bits 0x02, 0x04, and 0x08 are occupied by the 3-bit ExtFrameSubtype value. + + /** + * An OK(EXT_FRAME) acknowledgement was requested by the sender. + */ EXT_FRAME_FLAG_ACK_REQUESTED = 0x10 }; +/** + * NETWORK_CONFIG (or OK(NETWORK_CONFIG_REQUEST)) flags + */ +enum NetworkConfigFlag +{ + /** + * Indicates that this network config chunk should be fast propagated via rumor mill flooding. + */ + NETWORK_CONFIG_FLAG_FAST_PROPAGATE = 0x01 +}; + /****************************************************************************/ /* @@ -727,50 +729,114 @@ enum ExtFrameFlag * All fields larger than one byte are in big-endian byte order on the wire. */ -ZT_PACKED_STRUCT(struct HELLO { +/** + * Normal packet header + * + * @tparam PT Packet payload type (default: uint8_t[]) + */ +ZT_PACKED_STRUCT(struct Header +{ + uint64_t packetId; + uint8_t destination[5]; + uint8_t source[5]; + uint8_t flags; + uint64_t mac; + // --- begin encrypted envelope --- + uint8_t verb; +}); + +/** + * Packet fragment header + */ +ZT_PACKED_STRUCT(struct FragmentHeader +{ + uint64_t packetId; + uint8_t destination[5]; + uint8_t fragmentIndicator; // always 0xff for fragments + uint8_t counts; // total: most significant four bits, number: least significant four bits + uint8_t hops; // top 5 bits unused and must be zero + uint8_t p[]; +}); + +ZT_PACKED_STRUCT(struct HELLO +{ + Header h; uint8_t versionProtocol; uint8_t versionMajor; uint8_t versionMinor; uint16_t versionRev; uint64_t timestamp; + uint8_t p[]; }); -ZT_PACKED_STRUCT(struct RENDEZVOUS { +ZT_PACKED_STRUCT(struct RENDEZVOUS +{ + Header h; uint8_t flags; uint8_t peerAddress[5]; uint16_t port; uint8_t addressLength; - uint8_t address[16]; + uint8_t address[]; }); -ZT_PACKED_STRUCT(struct FRAME { +ZT_PACKED_STRUCT(struct FRAME +{ + Header h; uint64_t networkId; uint16_t etherType; uint8_t data[]; }); -ZT_PACKED_STRUCT(struct EXT_FRAME { +ZT_PACKED_STRUCT(struct EXT_FRAME +{ + Header h; uint64_t networkId; uint8_t flags; - uint8_t destMac[6]; - uint8_t sourceMac[6]; - uint16_t etherType; - uint8_t data[]; + uint8_t p[]; }); -ZT_PACKED_STRUCT(struct MULTICAST_LIKE_Entry { - uint64_t networkId; - uint8_t mac[6]; - uint32_t adi; -}); +ZT_PACKED_STRUCT(struct MULTICAST_LIKE +{ + ZT_PACKED_STRUCT(struct Entry + { + uint64_t networkId; + uint8_t mac[6]; + uint32_t adi; + }); -ZT_PACKED_STRUCT(struct MULTICAST_LIKE { - MULTICAST_LIKE_Entry groups[]; + Header h; + Entry groups[]; }); namespace OK { -ZT_PACKED_STRUCT(struct HELLO { +/** + * OK response header + * + * @tparam PT OK payload type (default: uint8_t[]) + */ +ZT_PACKED_STRUCT(struct Header +{ + uint8_t inReVerb; + uint64_t inRePacketId; +}); + +ZT_PACKED_STRUCT(struct WHOIS +{ + Protocol::Header h; + OK::Header oh; +}); + +ZT_PACKED_STRUCT(struct ECHO +{ + Protocol::Header h; + OK::Header oh; +}); + +ZT_PACKED_STRUCT(struct HELLO +{ + Protocol::Header h; + OK::Header oh; uint64_t timestampEcho; uint8_t versionProtocol; uint8_t versionMajor; @@ -778,7 +844,10 @@ ZT_PACKED_STRUCT(struct HELLO { uint16_t versionRev; }); -ZT_PACKED_STRUCT(struct EXT_FRAME { +ZT_PACKED_STRUCT(struct EXT_FRAME +{ + Protocol::Header h; + OK::Header oh; uint64_t networkId; uint8_t flags; uint8_t destMac[6]; @@ -786,20 +855,6 @@ ZT_PACKED_STRUCT(struct EXT_FRAME { uint16_t etherType; }); -/** - * OK response header - * - * The OK header comes after the packet header but before type-specific payloads. - * - * @tparam PT OK payload type (default: uint8_t[]) - */ -template -ZT_PACKED_STRUCT(struct Header { - uint8_t inReVerb; - uint64_t inRePacketId; - PT p; -}); - } // namespace OK namespace ERROR { @@ -811,68 +866,58 @@ namespace ERROR { * * @tparam PT Error payload type (default: uint8_t[]) */ -template -ZT_PACKED_STRUCT(struct Header { - uint8_t inReVerb; +ZT_PACKED_STRUCT(struct Header +{ + int8_t inReVerb; uint64_t inRePacketId; uint8_t error; - PT p; +}); + +ZT_PACKED_STRUCT(struct NEED_MEMBERSHIP_CERTIFICATE +{ + Protocol::Header h; + ERROR::Header eh; + uint64_t networkId; +}); + +ZT_PACKED_STRUCT(struct UNSUPPORTED_OPERATION__NETWORK_CONFIG_REQUEST +{ + Protocol::Header h; + ERROR::Header eh; + uint64_t networkId; }); } // namespace ERROR -/** - * Normal packet header - * - * @tparam PT Packet payload type (default: uint8_t[]) - */ -template -ZT_PACKED_STRUCT(struct Header { - uint64_t packetId; - uint8_t destination[5]; - uint8_t source[5]; - uint8_t flags; - uint64_t mac; - // --- begin encrypted envelope --- - uint8_t verb; - PT p; -}); - -/** - * Packet fragment header - */ -ZT_PACKED_STRUCT(struct FragmentHeader { - uint64_t packetId; - uint8_t destination[5]; - uint8_t fragmentIndicator; // always 0xff for fragments - uint8_t counts; // total: most significant four bits, number: least significant four bits - uint8_t hops; // top 5 bits unused and must be zero - uint8_t p[]; -}); - /****************************************************************************/ /** * Increment the 3-bit hops field embedded in the packet flags field - * - * @return New hop count (can be greater than allowed if there is an overflow) */ -template -ZT_ALWAYS_INLINE unsigned int incrementPacketHops(Buf< Header > &packet) +ZT_ALWAYS_INLINE unsigned int incrementPacketHops(Header &h) { - uint8_t f = packet.data.fields.flags; - uint8_t h = f; - f &= 0xf8U; - ++h; - packet.data.fields.flags = f | (h & 0x07U); - return h; + uint8_t flags = h.flags; + uint8_t hops = flags; + flags &= 0xf8U; + ++hops; + h.flags = flags | (hops & 0x07U); + return (unsigned int)hops; } /** * @return 3-bit hops field embedded in packet flags field */ -template -ZT_ALWAYS_INLINE unsigned int packetHops(Buf< Header > &packet) const { return (packet.data.fields.flags & 0x07U); } +ZT_ALWAYS_INLINE uint8_t packetHops(const Header &h) { return (h.flags & 0x07U); } + +/** + * @return 3-bit cipher field embedded in packet flags field + */ +ZT_ALWAYS_INLINE uint8_t packetCipher(const Header &h) { return ((h.flags >> 3U) & 0x07U); } + +void _armor(Buf< Header > &packet,unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],uint8_t cipherSuite); +int _dearmor(Buf< Header > &packet,unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]); +unsigned int _compress(Buf< Header > &packet,unsigned int packetSize); +int _uncompress(Buf< Header > &packet,unsigned int packetSize); /** * Armor a packet for transport @@ -882,7 +927,9 @@ ZT_ALWAYS_INLINE unsigned int packetHops(Buf< Header > &packet) const { retur * @param key 256-bit symmetric key * @param cipherSuite Cipher suite to apply */ -void armor(Buf< Header<> > &packet,unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],uint8_t cipherSuite); +template +static ZT_ALWAYS_INLINE void armor(Buf< X > &packet,unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],uint8_t cipherSuite) +{ _armor(reinterpret_cast< Buf< Header > & >(packet),packetSize,key,cipherSuite); } /** * Dearmor a packet and check message authentication code @@ -895,7 +942,9 @@ void armor(Buf< Header<> > &packet,unsigned int packetSize,const uint8_t key[ZT_ * @param key 256-bit symmetric key * @return Cipher suite or -1 if MAC validation failed */ -int dearmor(Buf< Header<> > &packet,unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]); +template +static ZT_ALWAYS_INLINE int dearmor(Buf< X > &packet,unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]) +{ return _dearmor(reinterpret_cast< Buf < Header > & >(packet),packetSize,key); } /** * Compress packet payload @@ -904,7 +953,9 @@ int dearmor(Buf< Header<> > &packet,unsigned int packetSize,const uint8_t key[ZT * @param packetSize Original packet size * @return New packet size (returns original size of compression didn't help, in which case packet is unmodified) */ -unsigned int compress(Buf< Header<> > &packet,unsigned int packetSize); +template +static ZT_ALWAYS_INLINE unsigned int compress(Buf< X > &packet,unsigned int packetSize) +{ return _compress(reinterpret_cast< Buf< Header > & >(packet),packetSize); } /** * Uncompress packet payload (if compressed) @@ -913,7 +964,9 @@ unsigned int compress(Buf< Header<> > &packet,unsigned int packetSize); * @param packetSize Original packet size * @return New packet size or -1 on decompression error (returns original packet size if packet wasn't compressed) */ -int uncompress(Buf< Header<> > &packet,unsigned int packetSize); +template +static ZT_ALWAYS_INLINE int uncompress(Buf< X > &packet,unsigned int packetSize) +{ return _uncompress(reinterpret_cast< Buf< Header > & >(packet),packetSize); } /** * Get a sequential non-repeating packet ID for the next packet (thread-safe) diff --git a/node/Revocation.hpp b/node/Revocation.hpp index 900a633fc..ba3668c07 100644 --- a/node/Revocation.hpp +++ b/node/Revocation.hpp @@ -47,18 +47,7 @@ class Revocation : public Credential public: static ZT_ALWAYS_INLINE ZT_CredentialType credentialType() { return ZT_CREDENTIAL_TYPE_REVOCATION; } - ZT_ALWAYS_INLINE Revocation() : - _id(0), - _credentialId(0), - _networkId(0), - _threshold(0), - _flags(0), - _target(), - _signedBy(), - _type(ZT_CREDENTIAL_TYPE_NULL), - _signatureLength(0) - { - } + ZT_ALWAYS_INLINE Revocation() { memoryZero(this); } /** * @param i ID (arbitrary for revocations, currently random) @@ -109,7 +98,7 @@ public: static ZT_ALWAYS_INLINE int marshalSizeMax() { return ZT_REVOCATION_MARSHAL_SIZE_MAX; } int marshal(uint8_t data[ZT_REVOCATION_MARSHAL_SIZE_MAX],bool forSign = false) const; - int unmarshal(const uint8_t *restrict data,const int len); + int unmarshal(const uint8_t *restrict data,int len); private: uint32_t _id; diff --git a/node/Salsa20.hpp b/node/Salsa20.hpp index 160d6d57f..4294a8d6c 100644 --- a/node/Salsa20.hpp +++ b/node/Salsa20.hpp @@ -20,6 +20,7 @@ #include "Constants.hpp" #include "Utils.hpp" +#include "TriviallyCopyable.hpp" #if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64)) #include @@ -32,7 +33,7 @@ namespace ZeroTier { /** * Salsa20 stream cipher */ -class Salsa20 +class Salsa20 : public TriviallyCopyable { public: ZT_ALWAYS_INLINE Salsa20() {} diff --git a/node/ScopedPtr.hpp b/node/ScopedPtr.hpp index 7d540d35d..9fd379146 100644 --- a/node/ScopedPtr.hpp +++ b/node/ScopedPtr.hpp @@ -15,6 +15,7 @@ #define ZT_SCOPEDPTR_HPP #include "Constants.hpp" +#include "TriviallyCopyable.hpp" namespace ZeroTier { @@ -24,7 +25,7 @@ namespace ZeroTier { * This is used in the core to avoid requiring C++11 and because auto_ptr is weird. */ template -class ScopedPtr +class ScopedPtr : public TriviallyCopyable { public: explicit ZT_ALWAYS_INLINE ScopedPtr(T *const p) : _p(p) {} @@ -51,6 +52,7 @@ private: ZT_ALWAYS_INLINE ScopedPtr() {} ZT_ALWAYS_INLINE ScopedPtr(const ScopedPtr &p) : _p(nullptr) {} ZT_ALWAYS_INLINE ScopedPtr &operator=(const ScopedPtr &p) { return *this; } + T *const _p; }; diff --git a/node/SharedPtr.hpp b/node/SharedPtr.hpp index 847b49ab6..39f324ee8 100644 --- a/node/SharedPtr.hpp +++ b/node/SharedPtr.hpp @@ -30,7 +30,7 @@ class SharedPtr { public: ZT_ALWAYS_INLINE SharedPtr() : _ptr((T *)0) {} - ZT_ALWAYS_INLINE SharedPtr(T *obj) : _ptr(obj) { ++obj->__refCount; } + explicit ZT_ALWAYS_INLINE SharedPtr(T *obj) : _ptr(obj) { ++obj->__refCount; } ZT_ALWAYS_INLINE SharedPtr(const SharedPtr &sp) : _ptr(sp._getAndInc()) {} ZT_ALWAYS_INLINE ~SharedPtr() @@ -81,7 +81,22 @@ public: with._ptr = tmp; } - ZT_ALWAYS_INLINE operator bool() const { return (_ptr != (T *)0); } + /** + * Set this value to one from another pointer and set that pointer to zero (avoids ref count changes) + * + * @param from Origin pointer; will be zeroed + */ + ZT_ALWAYS_INLINE void move(SharedPtr &from) + { + if (_ptr) { + if (--_ptr->__refCount <= 0) + delete _ptr; + } + _ptr = from._ptr; + from._ptr = nullptr; + } + + ZT_ALWAYS_INLINE operator bool() const { return (_ptr != nullptr); } ZT_ALWAYS_INLINE T &operator*() const { return *_ptr; } ZT_ALWAYS_INLINE T *operator->() const { return _ptr; } diff --git a/node/Switch.hpp b/node/Switch.hpp index fdce19555..72eae710b 100644 --- a/node/Switch.hpp +++ b/node/Switch.hpp @@ -147,7 +147,7 @@ private: Mutex lock; }; RXQueueEntry _rxQueue[ZT_RX_QUEUE_SIZE]; - AtomicCounter _rxQueuePtr; + AtomicCounter _rxQueuePtr; // Returns matching or next available RX queue entry ZT_ALWAYS_INLINE RXQueueEntry *_findRXQueueEntry(uint64_t packetId) diff --git a/node/Tag.hpp b/node/Tag.hpp index 32e4c7e6e..6f682e981 100644 --- a/node/Tag.hpp +++ b/node/Tag.hpp @@ -55,14 +55,7 @@ class Tag : public Credential public: static ZT_ALWAYS_INLINE ZT_CredentialType credentialType() { return ZT_CREDENTIAL_TYPE_TAG; } - ZT_ALWAYS_INLINE Tag() : - _id(0), - _value(0), - _networkId(0), - _ts(0), - _signatureLength(0) - { - } + ZT_ALWAYS_INLINE Tag() { memoryZero(this); } /** * @param nwid Network ID diff --git a/node/Trace.cpp b/node/Trace.cpp index 4d720453f..d49ca3f00 100644 --- a/node/Trace.cpp +++ b/node/Trace.cpp @@ -23,6 +23,8 @@ #define CONST_TO_BE_UINT16(x) ((uint16_t)(x)) #endif +// NOTE: packet IDs are always handled in network byte order, so no need to convert them. + namespace ZeroTier { Trace::Trace(const RuntimeEnvironment *renv) : @@ -72,7 +74,7 @@ void Trace::_tryingNewPath( memcpy(ev.identityHash,trying.hash(),48); physicalAddress.forTrace(ev.physicalAddress); triggerAddress.forTrace(ev.triggerAddress); - ev.triggeringPacketId = Utils::hton(triggeringPacketId); + ev.triggeringPacketId = triggeringPacketId; ev.triggeringPacketVerb = triggeringPacketVerb; ev.triggeredByAddress = Utils::hton(triggeredByAddress); if (triggeredByIdentityHash) @@ -93,7 +95,7 @@ void Trace::_learnedNewPath( ZT_TraceEvent_VL1_LEARNED_NEW_PATH ev; ev.evSize = CONST_TO_BE_UINT16(sizeof(ev)); ev.evType = CONST_TO_BE_UINT16(ZT_TRACE_VL1_LEARNED_NEW_PATH); - ev.packetId = Utils::hton(packetId); + ev.packetId = packetId; ev.address = Utils::hton(peerIdentity.address().toInt()); memcpy(ev.identityHash,peerIdentity.hash(),48); physicalAddress.forTrace(ev.physicalAddress); @@ -115,10 +117,15 @@ void Trace::_incomingPacketDropped( ZT_TraceEvent_VL1_INCOMING_PACKET_DROPPED ev; ev.evSize = CONST_TO_BE_UINT16(sizeof(ev)); ev.evType = CONST_TO_BE_UINT16(ZT_TRACE_VL1_INCOMING_PACKET_DROPPED); - ev.packetId = Utils::hton(packetId); + ev.packetId = packetId; ev.networkId = Utils::hton(networkId); - ev.address = Utils::hton(peerIdentity.address().toInt()); - memcpy(ev.identityHash,peerIdentity.hash(),48); + if (peerIdentity) { + ev.address = Utils::hton(peerIdentity.address().toInt()); + memcpy(ev.identityHash,peerIdentity.hash(),48); + } else { + ev.address = 0; + memset(ev.identityHash,0,48); + } physicalAddress.forTrace(ev.physicalAddress); ev.hops = hops; ev.verb = verb; diff --git a/node/Trace.hpp b/node/Trace.hpp index fe76b66dc..d337b98df 100644 --- a/node/Trace.hpp +++ b/node/Trace.hpp @@ -22,7 +22,6 @@ #include "Constants.hpp" #include "SharedPtr.hpp" #include "Mutex.hpp" -#include "Packet.hpp" #include "InetAddress.hpp" #include "Address.hpp" #include "MAC.hpp" diff --git a/node/TriviallyCopyable.hpp b/node/TriviallyCopyable.hpp new file mode 100644 index 000000000..2b22bf3bb --- /dev/null +++ b/node/TriviallyCopyable.hpp @@ -0,0 +1,174 @@ +/* + * 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_TRIVIALLYCOPYABLE_HPP +#define ZT_TRIVIALLYCOPYABLE_HPP + +#include "Constants.hpp" +#include "Utils.hpp" + +#include +#include + +namespace ZeroTier { + +/** + * This is a class that others can inherit from to tag themselves as safe to abuse in C-like ways with memcpy, etc. + * + * Later versions of C++ have a built-in auto-detected notion like this, but + * this is more explicit and its use will make audits for memory safety + * a lot easier. + */ +class TriviallyCopyable +{ +public: + /** + * Be absolutely sure a TriviallyCopyable object is zeroed using Utils::burn() + * + * @tparam T Automatically inferred type of object + * @param obj Any TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryBurn(T *obj) + { + TriviallyCopyable *const tmp = obj; + Utils::burn(tmp,sizeof(T)); + } + + /** + * Be absolutely sure a TriviallyCopyable object is zeroed using Utils::burn() + * + * @tparam T Automatically inferred type of object + * @param obj Any TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryBurn(T &obj) + { + TriviallyCopyable *const tmp = &obj; + Utils::burn(tmp,sizeof(T)); + } + + /** + * Zero a TriviallyCopyable object + * + * @tparam T Automatically inferred type of object + * @param obj Any TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryZero(T *obj) + { + TriviallyCopyable *const tmp = obj; + memset(tmp,0,sizeof(T)); + } + + /** + * Zero a TriviallyCopyable object + * + * @tparam T Automatically inferred type of object + * @param obj Any TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryZero(T &obj) + { + TriviallyCopyable *const tmp = &obj; + memset(tmp,0,sizeof(T)); + } + + /** + * Copy any memory over a TriviallyCopyable object + * + * @tparam T Automatically inferred type of destination + * @param dest Any TriviallyCopyable object + * @param src Source memory of same size or less than sizeof(dest) + */ + template + static ZT_ALWAYS_INLINE void memoryCopyUnsafe(T *dest,const void *src) + { + TriviallyCopyable *const tmp = dest; + memcpy(tmp,src,sizeof(T)); + } + + /** + * Copy any memory over a TriviallyCopyable object + * + * @tparam T Automatically inferred type of destination + * @param dest Any TriviallyCopyable object + * @param src Source memory of same size or less than sizeof(dest) + */ + template + static ZT_ALWAYS_INLINE void memoryCopyUnsafe(T &dest,const void *src) + { + TriviallyCopyable *const tmp = &dest; + memcpy(tmp,src,sizeof(T)); + } + + /** + * Copy a TriviallyCopyable object + * + * @tparam T Automatically inferred type of destination + * @param dest Destination TriviallyCopyable object + * @param src Source TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryCopy(T *dest,const T *src) + { + TriviallyCopyable *const tmp = dest; + memcpy(tmp,src,sizeof(T)); + } + + /** + * Copy a TriviallyCopyable object + * + * @tparam T Automatically inferred type of destination + * @param dest Destination TriviallyCopyable object + * @param src Source TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryCopy(T *dest,const T &src) + { + TriviallyCopyable *const tmp = dest; + memcpy(tmp,&src,sizeof(T)); + } + + /** + * Copy a TriviallyCopyable object + * + * @tparam T Automatically inferred type of destination + * @param dest Destination TriviallyCopyable object + * @param src Source TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryCopy(T &dest,const T *src) + { + TriviallyCopyable *const tmp = &dest; + memcpy(tmp,src,sizeof(T)); + } + + /** + * Copy a TriviallyCopyable object + * + * @tparam T Automatically inferred type of destination + * @param dest Destination TriviallyCopyable object + * @param src Source TriviallyCopyable object + */ + template + static ZT_ALWAYS_INLINE void memoryCopy(T &dest,const T &src) + { + TriviallyCopyable *const tmp = &dest; + memcpy(tmp,&src,sizeof(T)); + } +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Utils.hpp b/node/Utils.hpp index 2855151ed..5b5a141ef 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -195,24 +195,6 @@ static ZT_ALWAYS_INLINE char *stok(char *str,const char *delim,char **saveptr) #endif } -#if 0 -static ZT_ALWAYS_INLINE int strToInt(const char *s) { return (int)strtol(s,(char **)0,10); } -static ZT_ALWAYS_INLINE unsigned long strToULong(const char *s) { return strtoul(s,(char **)0,10); } -static ZT_ALWAYS_INLINE long strToLong(const char *s) { return strtol(s,(char **)0,10); } -static ZT_ALWAYS_INLINE long long strTo64(const char *s) -{ -#ifdef __WINDOWS__ - return (long long)_strtoi64(s,(char **)0,10); -#else - return strtoll(s,(char **)0,10); -#endif -} -static ZT_ALWAYS_INLINE unsigned int hexStrToUInt(const char *s) { return (unsigned int)strtoul(s,(char **)0,16); } -static ZT_ALWAYS_INLINE int hexStrToInt(const char *s) { return (int)strtol(s,(char **)0,16); } -static ZT_ALWAYS_INLINE unsigned long hexStrToULong(const char *s) { return strtoul(s,(char **)0,16); } -static ZT_ALWAYS_INLINE long hexStrToLong(const char *s) { return strtol(s,(char **)0,16); } -#endif - static ZT_ALWAYS_INLINE unsigned int strToUInt(const char *s) { return (unsigned int)strtoul(s,nullptr,10); } static ZT_ALWAYS_INLINE unsigned long long strToU64(const char *s) @@ -224,15 +206,6 @@ static ZT_ALWAYS_INLINE unsigned long long strToU64(const char *s) #endif } -static ZT_ALWAYS_INLINE long long hexStrTo64(const char *s) -{ -#ifdef __WINDOWS__ - return (long long)_strtoi64(s,(char **)0,16); -#else - return strtoll(s,nullptr,16); -#endif -} - static ZT_ALWAYS_INLINE unsigned long long hexStrToU64(const char *s) { #ifdef __WINDOWS__ @@ -426,37 +399,6 @@ static ZT_ALWAYS_INLINE void storeBigEndian(void *const p,const I i) #endif } -#if 0 -template -static ZT_ALWAYS_INLINE bool isPrimitiveType() { return false; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -template<> -ZT_ALWAYS_INLINE bool isPrimitiveType() { return true; } -#endif - } // namespace Utils } // namespace ZeroTier