A bunch more refactoring including splitting Switch into VL1 and VL2

This commit is contained in:
Adam Ierymenko 2020-02-06 18:06:50 -08:00
parent 7d11522768
commit 84619a7788
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
18 changed files with 1287 additions and 384 deletions

View file

@ -36,7 +36,7 @@ void _Buf_release(void *ptr,std::size_t sz)
break; break;
} }
((Buf<> *)ptr)->__nextInPool = bb; ((Buf *)ptr)->__nextInPool = bb;
#ifdef __GNUC__ #ifdef __GNUC__
__sync_fetch_and_and(&_Buf_pool,(uintptr_t)ptr); __sync_fetch_and_and(&_Buf_pool,(uintptr_t)ptr);
#else #else
@ -59,18 +59,18 @@ void *_Buf_get()
break; break;
} }
Buf<> *b; Buf *b;
if (bb == 0) { if (bb == 0) {
#ifdef __GNUC__ #ifdef __GNUC__
__sync_fetch_and_and(&_Buf_pool,bb); __sync_fetch_and_and(&_Buf_pool,bb);
#else #else
s_pool.store(bb); s_pool.store(bb);
#endif #endif
b = (Buf<> *)malloc(sizeof(Buf<>)); b = (Buf *)malloc(sizeof(Buf<>));
if (!b) if (!b)
throw std::bad_alloc(); throw std::bad_alloc();
} else { } else {
b = (Buf<> *)bb; b = (Buf *)bb;
#ifdef __GNUC__ #ifdef __GNUC__
__sync_fetch_and_and(&_Buf_pool,b->__nextInPool); __sync_fetch_and_and(&_Buf_pool,b->__nextInPool);
#else #else
@ -103,7 +103,7 @@ void freeBufPool()
#endif #endif
while (bb != 0) { while (bb != 0) {
uintptr_t next = ((Buf<> *)bb)->__nextInPool; uintptr_t next = ((Buf *)bb)->__nextInPool;
free((void *)bb); free((void *)bb);
bb = next; bb = next;
} }

View file

@ -20,11 +20,14 @@
#include "SharedPtr.hpp" #include "SharedPtr.hpp"
#include "Mutex.hpp" #include "Mutex.hpp"
#include "TriviallyCopyable.hpp" #include "TriviallyCopyable.hpp"
#include "FCV.hpp"
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#include <stdexcept> #include <stdexcept>
#include <utility>
#include <algorithm>
#ifndef __GNUC__ #ifndef __GNUC__
#include <atomic> #include <atomic>
@ -47,7 +50,7 @@ void _Buf_release(void *ptr,std::size_t sz);
void *_Buf_get(); void *_Buf_get();
/** /**
* Free buffers in the pool * Free all instances of Buf in shared pool.
* *
* New buffers will be created and the pool repopulated if get() is called * New buffers will be created and the pool repopulated if get() is called
* and outstanding buffers will still be returned to the pool. This just * and outstanding buffers will still be returned to the pool. This just
@ -55,11 +58,6 @@ void *_Buf_get();
*/ */
void freeBufPool(); void freeBufPool();
/**
* Macro to declare and get a new buffer templated with the given type
*/
#define ZT_GET_NEW_BUF(vvv,xxx) SharedPtr< Buf<xxx> > vvv(reinterpret_cast< Buf<xxx> * >(_Buf_get()))
/** /**
* Buffer and methods for branch-free bounds-checked data assembly and parsing * Buffer and methods for branch-free bounds-checked data assembly and parsing
* *
@ -100,10 +98,9 @@ void freeBufPool();
* *
* @tparam U Type to overlap with data bytes in data union (can't be larger than ZT_BUF_MEM_SIZE) * @tparam U Type to overlap with data bytes in data union (can't be larger than ZT_BUF_MEM_SIZE)
*/ */
template<typename U = int>
class Buf class Buf
{ {
friend class SharedPtr< Buf<U> >; friend class SharedPtr< Buf >;
friend void _Buf_release(void *,std::size_t); friend void _Buf_release(void *,std::size_t);
friend void *_Buf_get(); friend void *_Buf_get();
friend void freeBufPool(); friend void freeBufPool();
@ -139,17 +136,65 @@ public:
unsigned int e; unsigned int e;
}; };
ZT_ALWAYS_INLINE Buf() {} /**
* Assemble all slices in a vector into a single slice starting at position 0
*
* The returned slice will start at 0 and contain the entire vector unless the
* vector is too large to fit in a single buffer. If that or any other error
* occurs the returned slice will be empty and contain a NULL Buf.
*
* The vector may be modified by this function and should be considered
* undefined after it is called.
*
* @tparam FCVC Capacity of FCV (generally inferred automatically)
* @param fcv FCV containing one or more slices
* @return Single slice containing fully assembled buffer (empty on error)
*/
template<unsigned int FCVC>
static ZT_ALWAYS_INLINE Buf::Slice assembleSliceVector(FCV<Buf::Slice,FCVC> &fcv)
{
Buf::Slice r;
template<typename X> typename FCV<Buf::Slice,FCVC>::const_iterator s(fcv.begin());
ZT_ALWAYS_INLINE Buf(const Buf<X> &b) { memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE); } unsigned int l = s->e - s->s;
if (l <= ZT_BUF_MEM_SIZE) {
r.b.move(s->b);
if (s->s > 0)
memmove(r.b->b,r.b->b + s->s,l);
r.e = l;
while (++s != fcv.end()) {
l = s->e - s->s;
if (l > (ZT_BUF_MEM_SIZE - r.e)) {
r.b.zero();
r.e = 0;
break;
}
memcpy(r.b->b + r.e,s->b->b + s->s,l);
s->b.zero(); // let go of buffer in vector as soon as possible
r.e += l;
}
}
return r;
}
ZT_ALWAYS_INLINE Buf() {}
ZT_ALWAYS_INLINE Buf(const Buf &b2) { memcpy(b,b2.b,ZT_BUF_MEM_SIZE); }
ZT_ALWAYS_INLINE Buf &operator=(const Buf &b2)
{
if (this != &b2)
memcpy(b,b2.b,ZT_BUF_MEM_SIZE);
return *this;
}
/** /**
* Get obtains a buffer from the pool or allocates a new buffer if the pool is empty * Get obtains a buffer from the pool or allocates a new buffer if the pool is empty
* *
* @return Buffer instance * @return Buffer instance
*/ */
static ZT_ALWAYS_INLINE SharedPtr< Buf<U> > get() { return SharedPtr<Buf>((Buf *)_Buf_get()); } static ZT_ALWAYS_INLINE SharedPtr< Buf > get() { return SharedPtr<Buf>((Buf *)_Buf_get()); }
/** /**
* Check for overflow beyond the size of the buffer * Check for overflow beyond the size of the buffer
@ -175,29 +220,14 @@ public:
static ZT_ALWAYS_INLINE bool readOverflow(const int &ii,const unsigned int size) { return ((ii - (int)size) > 0); } static ZT_ALWAYS_INLINE bool readOverflow(const int &ii,const unsigned int size) { return ((ii - (int)size) > 0); }
/** /**
* Shortcut to cast between buffers whose data can be viewed through a different struct type * Set all memory to zero
*
* @tparam X A packed struct or other primitive type that should be placed in the data union
* @return Reference to this Buf templated with the supplied parameter
*/ */
template<typename X> ZT_ALWAYS_INLINE void clear() { memset(b,0,ZT_BUF_MEM_SIZE); }
ZT_ALWAYS_INLINE Buf<X> &view() { return *reinterpret_cast< Buf<X> * >(this); }
/** /**
* Shortcut to cast between buffers whose data can be viewed through a different struct type * Zero security critical data using Utils::burn() to ensure it's never optimized out.
*
* @tparam X A packed struct or other primitive type that should be placed in the data union
* @return Reference to this Buf templated with the supplied parameter
*/ */
template<typename X> ZT_ALWAYS_INLINE void burn() { Utils::burn(b,ZT_BUF_MEM_SIZE); }
ZT_ALWAYS_INLINE const Buf<X> &view() const { return *reinterpret_cast< Buf<X> * >(this); }
/**
* Zero memory
*
* For performance reasons Buf does not do this on every get().
*/
ZT_ALWAYS_INLINE void clear() { memset(data.bytes,0,ZT_BUF_MEM_SIZE); }
/** /**
* Read a byte * Read a byte
@ -208,7 +238,7 @@ public:
ZT_ALWAYS_INLINE uint8_t rI8(int &ii) const ZT_ALWAYS_INLINE uint8_t rI8(int &ii) const
{ {
const int s = ii++; const int s = ii++;
return data.bytes[(unsigned int)s & ZT_BUF_MEM_MASK]; return b[(unsigned int)s & ZT_BUF_MEM_MASK];
} }
/** /**
@ -226,7 +256,7 @@ public:
((uint16_t)data.bytes[s] << 8U) | ((uint16_t)data.bytes[s] << 8U) |
(uint16_t)data.bytes[s + 1]); (uint16_t)data.bytes[s + 1]);
#else #else
return Utils::ntoh(*reinterpret_cast<const uint16_t *>(data.bytes + s)); return Utils::ntoh(*reinterpret_cast<const uint16_t *>(b + s));
#endif #endif
} }
@ -247,7 +277,7 @@ public:
((uint32_t)data.bytes[s + 2] << 8U) | ((uint32_t)data.bytes[s + 2] << 8U) |
(uint32_t)data.bytes[s + 3]); (uint32_t)data.bytes[s + 3]);
#else #else
return Utils::ntoh(*reinterpret_cast<const uint32_t *>(data.bytes + s)); return Utils::ntoh(*reinterpret_cast<const uint32_t *>(b + s));
#endif #endif
} }
@ -272,7 +302,7 @@ public:
((uint64_t)data.bytes[s + 6] << 8U) | ((uint64_t)data.bytes[s + 6] << 8U) |
(uint64_t)data.bytes[s + 7]); (uint64_t)data.bytes[s + 7]);
#else #else
return Utils::ntoh(*reinterpret_cast<const uint64_t *>(data.bytes + s)); return Utils::ntoh(*reinterpret_cast<const uint64_t *>(b + s));
#endif #endif
} }
@ -295,7 +325,7 @@ public:
ZT_ALWAYS_INLINE int rO(int &ii,T &obj) const ZT_ALWAYS_INLINE int rO(int &ii,T &obj) const
{ {
if (ii < ZT_BUF_MEM_SIZE) { if (ii < ZT_BUF_MEM_SIZE) {
int ms = obj.unmarshal(data.bytes + ii,ZT_BUF_MEM_SIZE - ii); int ms = obj.unmarshal(b + ii,ZT_BUF_MEM_SIZE - ii);
if (ms > 0) if (ms > 0)
ii += ms; ii += ms;
return ms; return ms;
@ -316,10 +346,10 @@ public:
*/ */
ZT_ALWAYS_INLINE char *rS(int &ii,char *const buf,const unsigned int bufSize) const ZT_ALWAYS_INLINE char *rS(int &ii,char *const buf,const unsigned int bufSize) const
{ {
const char *const s = (const char *)(data.bytes + ii); const char *const s = (const char *)(b + ii);
const int sii = ii; const int sii = ii;
while (ii < ZT_BUF_MEM_SIZE) { while (ii < ZT_BUF_MEM_SIZE) {
if (data.bytes[ii++] == 0) { if (b[ii++] == 0) {
memcpy(buf,s,ii - sii); memcpy(buf,s,ii - sii);
return buf; return buf;
} }
@ -342,9 +372,9 @@ public:
*/ */
ZT_ALWAYS_INLINE const char *rSnc(int &ii) const ZT_ALWAYS_INLINE const char *rSnc(int &ii) const
{ {
const char *const s = (const char *)(data.bytes + ii); const char *const s = (const char *)(b + ii);
while (ii < ZT_BUF_MEM_SIZE) { while (ii < ZT_BUF_MEM_SIZE) {
if (data.bytes[ii++] == 0) if (b[ii++] == 0)
return s; return s;
} }
return nullptr; return nullptr;
@ -364,7 +394,7 @@ public:
ZT_ALWAYS_INLINE uint8_t *rB(int &ii,void *bytes,unsigned int len) const ZT_ALWAYS_INLINE uint8_t *rB(int &ii,void *bytes,unsigned int len) const
{ {
if ((ii += (int)len) <= ZT_BUF_MEM_SIZE) { if ((ii += (int)len) <= ZT_BUF_MEM_SIZE) {
memcpy(bytes,data.bytes + ii,len); memcpy(bytes,b + ii,len);
return reinterpret_cast<uint8_t *>(bytes); return reinterpret_cast<uint8_t *>(bytes);
} }
return nullptr; return nullptr;
@ -385,7 +415,7 @@ public:
*/ */
ZT_ALWAYS_INLINE const uint8_t *rBnc(int &ii,unsigned int len) const ZT_ALWAYS_INLINE const uint8_t *rBnc(int &ii,unsigned int len) const
{ {
const uint8_t *const b = data.bytes + ii; const uint8_t *const b = b + ii;
return ((ii += (int)len) <= ZT_BUF_MEM_SIZE) ? b : nullptr; return ((ii += (int)len) <= ZT_BUF_MEM_SIZE) ? b : nullptr;
} }
@ -398,7 +428,7 @@ public:
ZT_ALWAYS_INLINE void wI(int &ii,uint8_t n) ZT_ALWAYS_INLINE void wI(int &ii,uint8_t n)
{ {
const int s = ii++; const int s = ii++;
data[(unsigned int)s & ZT_BUF_MEM_MASK] = n; b[(unsigned int)s & ZT_BUF_MEM_MASK] = n;
} }
/** /**
@ -412,10 +442,10 @@ public:
const unsigned int s = ((unsigned int)ii) & ZT_BUF_MEM_MASK; const unsigned int s = ((unsigned int)ii) & ZT_BUF_MEM_MASK;
ii += 2; ii += 2;
#ifdef ZT_NO_UNALIGNED_ACCESS #ifdef ZT_NO_UNALIGNED_ACCESS
data[s] = (uint8_t)(n >> 8U); b[s] = (uint8_t)(n >> 8U);
data[s + 1] = (uint8_t)n; b[s + 1] = (uint8_t)n;
#else #else
*reinterpret_cast<uint16_t *>(data.bytes + s) = Utils::hton(n); *reinterpret_cast<uint16_t *>(b + s) = Utils::hton(n);
#endif #endif
} }
@ -430,12 +460,12 @@ public:
const unsigned int s = ((unsigned int)ii) & ZT_BUF_MEM_MASK; const unsigned int s = ((unsigned int)ii) & ZT_BUF_MEM_MASK;
ii += 4; ii += 4;
#ifdef ZT_NO_UNALIGNED_ACCESS #ifdef ZT_NO_UNALIGNED_ACCESS
data[s] = (uint8_t)(n >> 24U); b[s] = (uint8_t)(n >> 24U);
data[s + 1] = (uint8_t)(n >> 16U); b[s + 1] = (uint8_t)(n >> 16U);
data[s + 2] = (uint8_t)(n >> 8U); b[s + 2] = (uint8_t)(n >> 8U);
data[s + 3] = (uint8_t)n; b[s + 3] = (uint8_t)n;
#else #else
*reinterpret_cast<uint32_t *>(data.bytes + s) = Utils::hton(n); *reinterpret_cast<uint32_t *>(b + s) = Utils::hton(n);
#endif #endif
} }
@ -450,16 +480,16 @@ public:
const unsigned int s = ((unsigned int)ii) & ZT_BUF_MEM_MASK; const unsigned int s = ((unsigned int)ii) & ZT_BUF_MEM_MASK;
ii += 8; ii += 8;
#ifdef ZT_NO_UNALIGNED_ACCESS #ifdef ZT_NO_UNALIGNED_ACCESS
data[s] = (uint8_t)(n >> 56U); b[s] = (uint8_t)(n >> 56U);
data[s + 1] = (uint8_t)(n >> 48U); b[s + 1] = (uint8_t)(n >> 48U);
data[s + 2] = (uint8_t)(n >> 40U); b[s + 2] = (uint8_t)(n >> 40U);
data[s + 3] = (uint8_t)(n >> 32U); b[s + 3] = (uint8_t)(n >> 32U);
data[s + 4] = (uint8_t)(n >> 24U); b[s + 4] = (uint8_t)(n >> 24U);
data[s + 5] = (uint8_t)(n >> 16U); b[s + 5] = (uint8_t)(n >> 16U);
data[s + 6] = (uint8_t)(n >> 8U); b[s + 6] = (uint8_t)(n >> 8U);
data[s + 7] = (uint8_t)n; b[s + 7] = (uint8_t)n;
#else #else
*reinterpret_cast<uint64_t *>(data.bytes + s) = Utils::hton(n); *reinterpret_cast<uint64_t *>(b + s) = Utils::hton(n);
#endif #endif
} }
@ -475,7 +505,7 @@ public:
{ {
const int s = ii; const int s = ii;
if ((s + T::marshalSizeMax()) <= ZT_BUF_MEM_SIZE) { if ((s + T::marshalSizeMax()) <= ZT_BUF_MEM_SIZE) {
int ms = t.marshal(data.bytes + s); int ms = t.marshal(b + s);
if (ms > 0) if (ms > 0)
ii += ms; ii += ms;
} else { } else {
@ -513,42 +543,63 @@ public:
{ {
const int s = ii; const int s = ii;
if ((ii += (int)len) <= ZT_BUF_MEM_SIZE) if ((ii += (int)len) <= ZT_BUF_MEM_SIZE)
memcpy(data.bytes + s,bytes,len); memcpy(b + s,bytes,len);
} }
template<typename X>
ZT_ALWAYS_INLINE Buf &operator=(const Buf<X> &b) const
{
memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE);
return *this;
}
template<typename X>
ZT_ALWAYS_INLINE bool operator==(const Buf<X> &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) == 0); }
template<typename X>
ZT_ALWAYS_INLINE bool operator!=(const Buf<X> &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) != 0); }
template<typename X>
ZT_ALWAYS_INLINE bool operator<(const Buf<X> &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) < 0); }
template<typename X>
ZT_ALWAYS_INLINE bool operator<=(const Buf<X> &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) <= 0); }
template<typename X>
ZT_ALWAYS_INLINE bool operator>(const Buf<X> &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) > 0); }
template<typename X>
ZT_ALWAYS_INLINE bool operator>=(const Buf<X> &b) const { return (memcmp(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE) >= 0); }
/** /**
* Raw data and fields (if U template parameter is set) * @return Capacity of this buffer (usable size of data.bytes)
*
* The extra eight bytes permit silent overflow of integer types without reading or writing
* beyond Buf's memory and without branching or extra masks. They can be ignored otherwise.
*/ */
ZT_PACKED_STRUCT(union { static constexpr unsigned int capacity() { return ZT_BUF_MEM_SIZE; }
uint8_t bytes[ZT_BUF_MEM_SIZE + 8];
U fields; /**
}) data; * Cast data in 'b' to a (usually packed) structure type
*
* Warning: this does no bounds checking. It should only be used with packed
* struct types designed for use in packet decoding such as those in
* Protocol.hpp, and if 'i' is non-zero the caller must check bounds.
*
* @tparam T Structure type to cast 'b' to
* @param i Index of start of structure (default: 0)
* @return Reference to 'b' cast to type T
*/
template<typename T>
ZT_ALWAYS_INLINE T &as(const unsigned int i = 0) { return *reinterpret_cast<T *>(b + i); }
/**
* Cast data in 'b' to a (usually packed) structure type (const)
*
* Warning: this does no bounds checking. It should only be used with packed
* struct types designed for use in packet decoding such as those in
* Protocol.hpp, and if 'i' is non-zero the caller must check bounds.
*
* @tparam T Structure type to cast 'b' to
* @param i Index of start of structure (default: 0)
* @return Reference to 'b' cast to type T
*/
template<typename T>
ZT_ALWAYS_INLINE const T &as(const unsigned int i = 0) const { return *reinterpret_cast<const T *>(b + i); }
ZT_ALWAYS_INLINE bool operator==(const Buf &b2) const { return (memcmp(b,b2.b,ZT_BUF_MEM_SIZE) == 0); }
ZT_ALWAYS_INLINE bool operator!=(const Buf &b2) const { return (memcmp(b,b2.b,ZT_BUF_MEM_SIZE) != 0); }
ZT_ALWAYS_INLINE bool operator<(const Buf &b2) const { return (memcmp(b,b2.b,ZT_BUF_MEM_SIZE) < 0); }
ZT_ALWAYS_INLINE bool operator<=(const Buf &b2) const { return (memcmp(b,b2.b,ZT_BUF_MEM_SIZE) <= 0); }
ZT_ALWAYS_INLINE bool operator>(const Buf &b2) const { return (memcmp(b,b2.b,ZT_BUF_MEM_SIZE) > 0); }
ZT_ALWAYS_INLINE bool operator>=(const Buf &b2) const { return (memcmp(b,b2.b,ZT_BUF_MEM_SIZE) >= 0); }
/**
* Raw data held in buffer
*
* The additional eight bytes should not be used and should be considered undefined.
* They exist to allow reads and writes of integer types to silently overflow if a
* read or write is performed at the end of the buffer.
*/
uint8_t b[ZT_BUF_MEM_SIZE + 8];
private: private:
volatile uintptr_t __nextInPool; // next item in free pool if this Buf is in Buf_pool // Next item in free buffer pool linked list if Buf is placed in pool, undefined and unused otherwise
volatile uintptr_t __nextInPool;
// Reference counter for SharedPtr<>
AtomicCounter<int> __refCount; AtomicCounter<int> __refCount;
}; };

View file

@ -15,6 +15,7 @@ set(core_headers
Defragmenter.hpp Defragmenter.hpp
Dictionary.hpp Dictionary.hpp
ECC384.hpp ECC384.hpp
FCV.hpp
Hashtable.hpp Hashtable.hpp
Identity.hpp Identity.hpp
InetAddress.hpp InetAddress.hpp
@ -45,6 +46,8 @@ set(core_headers
Trace.hpp Trace.hpp
TriviallyCopyable.hpp TriviallyCopyable.hpp
Utils.hpp Utils.hpp
VL1.hpp
VL2.hpp
) )
set(core_src set(core_src
@ -59,7 +62,6 @@ set(core_src
ECC384.cpp ECC384.cpp
Endpoint.cpp Endpoint.cpp
Identity.cpp Identity.cpp
IncomingPacket.cpp
InetAddress.cpp InetAddress.cpp
Locator.cpp Locator.cpp
LZ4.cpp LZ4.cpp
@ -80,6 +82,7 @@ set(core_src
Topology.cpp Topology.cpp
Trace.cpp Trace.cpp
Utils.cpp Utils.cpp
VL1.cpp
) )
add_library(${PROJECT_NAME} STATIC ${core_src} ${core_headers}) add_library(${PROJECT_NAME} STATIC ${core_src} ${core_headers})

View file

@ -56,6 +56,11 @@
*/ */
#define ZT_MAX_PACKET_FRAGMENTS 11 #define ZT_MAX_PACKET_FRAGMENTS 11
/**
* Anti-DOS limit on the maximum incoming fragments per path
*/
#define ZT_MAX_INCOMING_FRAGMENTS_PER_PATH 32
/** /**
* Sanity limit on the maximum size of a network config object * Sanity limit on the maximum size of a network config object
*/ */

View file

@ -21,6 +21,7 @@
#include "Hashtable.hpp" #include "Hashtable.hpp"
#include "Mutex.hpp" #include "Mutex.hpp"
#include "Path.hpp" #include "Path.hpp"
#include "FCV.hpp"
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
@ -40,20 +41,27 @@ namespace ZeroTier {
* Here be dragons! * Here be dragons!
* *
* @tparam MF Maximum number of fragments that each message can possess * @tparam MF Maximum number of fragments that each message can possess
* @tparam GCS Garbage collection target size for the incoming message queue
* @tparam GCT Garbage collection trigger threshold, usually 2X GCS
*/ */
template<unsigned int MF> template<unsigned int MF,unsigned int GCS = 32,unsigned int GCT = 64>
class Defragmenter class Defragmenter
{ {
public: public:
/** /**
* Error codes for assemble() * Return values from assemble()
*/ */
enum ErrorCode enum ResultCode
{ {
/** /**
* No error occurred * No error occurred, fragment accepted
*/ */
ERR_NONE, OK,
/**
* Message fully assembled and placed in message vector
*/
COMPLETE,
/** /**
* This fragment duplicates another with the same fragment number for this message * This fragment duplicates another with the same fragment number for this message
@ -82,32 +90,6 @@ public:
ERR_OUT_OF_MEMORY ERR_OUT_OF_MEMORY
}; };
/**
* Return tuple for assemble()
*/
struct Result
{
ZT_ALWAYS_INLINE Result() : message(),messageFragmentCount(0),error(Defragmenter::ERR_NONE) {}
explicit ZT_ALWAYS_INLINE Result(const Defragmenter::ErrorCode e) : message(),messageFragmentCount(0),error(e) {}
/**
* 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 * Process a fragment of a multi-part message
* *
@ -132,119 +114,76 @@ public:
* off, so the SharedPtr<> passed in as 'fragment' will be NULL after this * off, so the SharedPtr<> passed in as 'fragment' will be NULL after this
* function is called. * 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 * 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 * 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 * supplied (the first non-NULL) for a given message ID. This is a mitigation
* against memory exhausting DOS attacks. * 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) * @tparam X Template parameter type for Buf<> containing fragment (inferred)
* @param messageId Message ID (a unique ID identifying this message) * @param messageId Message ID (a unique ID identifying this message)
* @param message Fixed capacity vector that will be filled with the result if result code is DONE
* @param fragment Buffer containing fragment that will be filed under this message's ID * @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 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 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 fragmentNo Number of fragment (0..totalFragmentsExpected, non-inclusive)
* @param totalFragmentsExpected Total number of expected fragments in this message * @param totalFragmentsExpected Total number of expected fragments in this message or 0 to use cached value
* @param now Current time * @param now Current time
* @param via If non-NULL this is the path on which this message fragment was received * @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 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 * @return Result code
* @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( ZT_ALWAYS_INLINE ResultCode assemble(
const uint64_t messageId, const uint64_t messageId,
SharedPtr< Buf<> > &fragment, FCV< Buf::Slice,MF > &message,
SharedPtr<Buf> &fragment,
const unsigned int fragmentDataIndex, const unsigned int fragmentDataIndex,
const unsigned int fragmentDataSize, const unsigned int fragmentDataSize,
const unsigned int fragmentNo, const unsigned int fragmentNo,
const unsigned int totalFragmentsExpected, const unsigned int totalFragmentsExpected,
const int64_t now, const int64_t now,
const SharedPtr< Path > &via, const SharedPtr< Path > &via,
const unsigned int maxIncomingFragmentsPerPath, const unsigned int maxIncomingFragmentsPerPath)
const unsigned long messageQueueSizeTarget,
const unsigned long messageQueueSizeGCTrigger)
{ {
// Sanity checks for malformed fragments or invalid input parameters. // Sanity checks for malformed fragments or invalid input parameters.
if ((fragmentNo >= totalFragmentsExpected)||(totalFragmentsExpected > MF)||(totalFragmentsExpected == 0)) if ((fragmentNo >= totalFragmentsExpected)||(totalFragmentsExpected > MF)||(totalFragmentsExpected == 0))
return Result(ERR_INVALID_FRAGMENT); return ERR_INVALID_FRAGMENT;
// If there is only one fragment just return that fragment and we are done.
if (totalFragmentsExpected < 2) {
if (fragmentNo == 0) {
Result r;
r.message[0].b.move(fragment);
r.message[0].s = fragmentDataIndex;
r.message[0].e = fragmentDataSize;
r.messageFragmentCount = 1;
return r;
} else {
return Result(ERR_INVALID_FRAGMENT);
}
}
// Lock messages for read and look up current entry. Also check the // Lock messages for read and look up current entry. Also check the
// GC trigger and if we've exceeded that threshold then older message // GC trigger and if we've exceeded that threshold then older message
// entries are garbage collected. // entries are garbage collected.
_messages_l.rlock(); _messages_l.rlock();
if (messageQueueSizeTarget > 0) { if (_messages.size() >= GCT) {
if (_messages.size() >= messageQueueSizeGCTrigger) { try {
try { // Scan messages with read lock still locked first and make a sorted list of
// 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
// 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
// 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
// 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
// lock is held since many threads can hold the read lock but all threads must // wait if someone holds the write lock.
// wait if someone holds the write lock. std::vector< std::pair<int64_t,uint64_t> > messagesByLastUsedTime;
std::vector< std::pair<int64_t,uint64_t> > messagesByLastUsedTime; messagesByLastUsedTime.reserve(_messages.size());
messagesByLastUsedTime.reserve(_messages.size());
typename Hashtable<uint64_t,_E>::Iterator i(_messages); typename Hashtable<uint64_t,_E>::Iterator i(_messages);
uint64_t *mk = nullptr; uint64_t *mk = nullptr;
_E *mv = nullptr; _E *mv = nullptr;
while (i.next(mk,mv)) while (i.next(mk,mv))
messagesByLastUsedTime.push_back(std::pair<int64_t,uint64_t>(mv->lastUsed,*mk)); messagesByLastUsedTime.push_back(std::pair<int64_t,uint64_t>(mv->lastUsed,*mk));
std::sort(messagesByLastUsedTime.begin(),messagesByLastUsedTime.end()); std::sort(messagesByLastUsedTime.begin(),messagesByLastUsedTime.end());
_messages_l.runlock(); _messages_l.runlock(); _messages_l.lock();
_messages_l.lock(); for (unsigned long x = 0,y = (messagesByLastUsedTime.size() - GCS); x <= y; ++x)
for (unsigned long x = 0,y = (messagesByLastUsedTime.size() - messageQueueSizeTarget); x <= y; ++x) _messages.erase(messagesByLastUsedTime[x].second);
_messages.erase(messagesByLastUsedTime[x].second); _messages_l.unlock(); _messages_l.rlock();
_messages_l.unlock(); } catch (...) {
_messages_l.rlock(); // The only way something in that code can throw is if a bad_alloc occurs when
} catch (...) { // reserve() is called in the vector. In this case we flush the entire queue
// The only way something in that code can throw is if a bad_alloc occurs when // and error out. This is very rare and on some platforms impossible.
// reserve() is called in the vector. In this case we flush the entire queue _messages_l.runlock();
// and error out. This is very rare and on some platforms impossible. _messages_l.lock();
_messages_l.runlock(); _messages.clear();
_messages_l.lock(); _messages_l.unlock();
_messages.clear(); return ERR_OUT_OF_MEMORY;
_messages_l.unlock();
return Result(ERR_OUT_OF_MEMORY);
}
} }
} }
_E *e = _messages.get(messageId); _E *e = _messages.get(messageId);
@ -256,7 +195,7 @@ public:
RWMutex::Lock ml(_messages_l); RWMutex::Lock ml(_messages_l);
e = &(_messages[messageId]); e = &(_messages[messageId]);
} catch ( ... ) { } catch ( ... ) {
return Result(ERR_OUT_OF_MEMORY); return ERR_OUT_OF_MEMORY;
} }
e->id = messageId; e->id = messageId;
} }
@ -268,6 +207,19 @@ public:
// is locked or a deadlock could occur due to GC or clear() being called // is locked or a deadlock could occur due to GC or clear() being called
// in another thread. // in another thread.
// This magic value means this message has already been assembled and is done.
if (e->lastUsed < 0)
return ERR_DUPLICATE_FRAGMENT;
// Update last-activity timestamp for this entry.
e->lastUsed = now;
// Learn total fragments expected if a value is given. Otherwise the cached
// value gets used. This is to support the implementation of fragmentation
// in the ZT protocol where only fragments carry the total.
if (totalFragmentsExpected > 0)
e->totalFragmentsExpected = totalFragmentsExpected;
// If there is a path associated with this fragment make sure we've registered // 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. // ourselves as in flight, check the limit, and abort if exceeded.
if ((via)&&(!e->via)) { if ((via)&&(!e->via)) {
@ -286,22 +238,18 @@ public:
tooManyPerPath = true; tooManyPerPath = true;
} }
via->_inboundFragmentedMessages_l.unlock(); via->_inboundFragmentedMessages_l.unlock();
if (tooManyPerPath) { if (tooManyPerPath)
return Result(ERR_TOO_MANY_FRAGMENTS_FOR_PATH); return ERR_TOO_MANY_FRAGMENTS_FOR_PATH;
}
} }
// Update last-activity timestamp for this entry.
e->lastUsed = now;
// If we already have fragment number X, abort. Note that we do not // If we already have fragment number X, abort. Note that we do not
// actually compare data here. Two same-numbered fragments with different // actually compare data here. Two same-numbered fragments with different
// data would just mean the transfer is corrupt and would be detected // 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 // 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. // network configs check each fragment so this basically can't happen.
Buf<>::Slice &s = e->result.message[fragmentNo]; Buf::Slice &s = e->message.at(fragmentNo);
if (s.b) if (s.b)
return Result(ERR_DUPLICATE_FRAGMENT); return ERR_DUPLICATE_FRAGMENT;
// Take ownership of fragment, setting 'fragment' pointer to NULL. The simple // Take ownership of fragment, setting 'fragment' pointer to NULL. The simple
// transfer of the pointer avoids a synchronized increment/decrement of the object's // transfer of the pointer avoids a synchronized increment/decrement of the object's
@ -311,7 +259,7 @@ public:
s.e = fragmentDataIndex + fragmentDataSize; s.e = fragmentDataIndex + fragmentDataSize;
// If we now have all fragments then assemble them. // If we now have all fragments then assemble them.
if (++e->result.messageFragmentCount >= totalFragmentsExpected) { if ((e->message.size() >= e->totalFragmentsExpected)&&(e->totalFragmentsExpected > 0)) {
// This message is done so de-register it with its path if one is associated. // This message is done so de-register it with its path if one is associated.
if (e->via) { if (e->via) {
e->via->_inboundFragmentedMessages_l.lock(); e->via->_inboundFragmentedMessages_l.lock();
@ -320,10 +268,15 @@ public:
e->via.zero(); e->via.zero();
} }
return e->result; // Slices are TriviallyCopyable and so may be memcpy'd from e->message to
// the result parameter. This is fast.
e->message.unsafeMoveTo(message);
e->message.lastUsed = -1; // mark as "done" and force GC to collect
return COMPLETE;
} }
return Result(ERR_NONE); return OK;
} }
/** /**
@ -338,7 +291,7 @@ public:
private: private:
struct _E struct _E
{ {
ZT_ALWAYS_INLINE _E() : id(0),lastUsed(0),via() {} ZT_ALWAYS_INLINE _E() : id(0),lastUsed(0),totalFragmentsExpected(0),via(),message(),lock() {}
ZT_ALWAYS_INLINE ~_E() ZT_ALWAYS_INLINE ~_E()
{ {
// Ensure that this entry is not in use while it is being deleted! // Ensure that this entry is not in use while it is being deleted!
@ -352,8 +305,9 @@ private:
} }
uint64_t id; uint64_t id;
volatile int64_t lastUsed; volatile int64_t lastUsed;
unsigned int totalFragmentsExpected;
SharedPtr<Path> via; SharedPtr<Path> via;
Result result; FCV< Buf::Slice,MF > message;
Mutex lock; Mutex lock;
}; };

278
node/FCV.hpp Normal file
View file

@ -0,0 +1,278 @@
/*
* 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_FCV_HPP
#define ZT_FCV_HPP
#include "Constants.hpp"
#include <iterator>
#include <algorithm>
#include <memory>
#include <cstring>
#include <cstdlib>
namespace ZeroTier {
/**
* FCV is a Fixed Capacity Vector
*
* Attempts to resize, push, or access this vector beyond its capacity will
* silently fail. The [] operator is NOT bounds checked!
*
* This doesn't implement everything in std::vector, just what we need. It
* also adds a few special things for use in ZT core code.
*
* @tparam T Type to contain
* @tparam C Maximum capacity of vector
*/
template<typename T,unsigned int C>
class FCV
{
public:
typedef T * iterator;
typedef const T * const_iterator;
ZT_ALWAYS_INLINE FCV() : _s(0) {}
template<unsigned int C2>
ZT_ALWAYS_INLINE FCV(const FCV<T,C2> &v) : _s(0) { *this = v; }
ZT_ALWAYS_INLINE ~FCV() { this->clear(); }
template<unsigned int C2>
ZT_ALWAYS_INLINE FCV &operator=(const FCV<T,C2> &v)
{
if ((C != C2)||(&v != this)) {
this->clear();
const unsigned int vs = ((C2 > C) && (v._s > C)) ? C : v._s;
_s = vs;
for (unsigned int i = 0; i < vs; ++i)
new(reinterpret_cast<T *>(_m) + i) T(*(reinterpret_cast<const T *>(v._m) + i));
}
return *this;
}
/**
* Clear this vector, destroying all content objects
*/
ZT_ALWAYS_INLINE void clear()
{
const unsigned int s = _s;
_s = 0;
for(unsigned int i=0;i<s;++i)
(reinterpret_cast<T *>(_m) + i)->~T();
}
/**
* Clear without calling destructors (same as unsafeResize(0))
*/
ZT_ALWAYS_INLINE void unsafeClear() { _s = 0; }
/**
* This does a straight copy of one vector's data to another
*
* If the other vector is larger than this one's capacity the data is
* silently truncated. This is unsafe in that it does not call any
* constructors or destructors and copies data with memcpy, so it can
* only be used with primitive types or TriviallyCopyable objects.
*
* @tparam C2 Inferred capacity of other vector
* @param v Other vector to copy to this one
*/
template<unsigned int C2>
ZT_ALWAYS_INLINE void unsafeAssign(const FCV<T,C2> &v)
{
_s = ((C2 > C)&&(v._s > C)) ? C : v._s;
memcpy(_m,v._m,_s * sizeof(T));
}
/**
* Move contents from this vector to another and clear this vector
*
* This uses a straight memcpy and so is only safe for primitive types or
* types that are TriviallyCopyable.
*
* @param v Target vector
*/
ZT_ALWAYS_INLINE void unsafeMoveTo(FCV &v)
{
memcpy(v._m,_m,(v._s = _s) * sizeof(T));
_s = 0;
}
ZT_ALWAYS_INLINE iterator begin() { return reinterpret_cast<T *>(_m); }
ZT_ALWAYS_INLINE const_iterator begin() const { return reinterpret_cast<const T *>(_m); }
ZT_ALWAYS_INLINE iterator end() { return reinterpret_cast<T *>(_m) + _s; }
ZT_ALWAYS_INLINE const_iterator end() const { return reinterpret_cast<const T *>(_m) + _s; }
ZT_ALWAYS_INLINE T &operator[](const unsigned int i) { return reinterpret_cast<T *>(_m)[i]; }
ZT_ALWAYS_INLINE const T &operator[](const unsigned int i) const { return reinterpret_cast<T *>(_m)[i]; }
ZT_ALWAYS_INLINE unsigned int size() const { return _s; }
ZT_ALWAYS_INLINE bool empty() const { return (_s == 0); }
static constexpr unsigned int capacity() { return C; }
/**
* Push a value onto the back of this vector
*
* If the vector is at capacity this silently fails.
*
* @param v Value to push
*/
ZT_ALWAYS_INLINE void push_back(const T &v)
{
if (_s < C)
new (reinterpret_cast<T *>(_m) + _s++) T(v);
}
/**
* Push a new value onto the vector and return it, or return last item if capacity is reached
*
* @return Reference to new item
*/
ZT_ALWAYS_INLINE T &push()
{
if (_s < C) {
return *(new(reinterpret_cast<T *>(_m) + _s++) T());
} else {
return *(reinterpret_cast<T *>(_m) + (C - 1));
}
}
/**
* Push a new value onto the vector and return it, or return last item if capacity is reached
*
* @return Reference to new item
*/
ZT_ALWAYS_INLINE T &push(const T &v)
{
if (_s < C) {
return *(new(reinterpret_cast<T *>(_m) + _s++) T(v));
} else {
T &tmp = *(reinterpret_cast<T *>(_m) + (C - 1));
tmp = v;
return tmp;
}
}
/**
* Remove the last element if this vector is not empty
*/
ZT_ALWAYS_INLINE void pop_back()
{
if (_s != 0)
(reinterpret_cast<T *>(_m) + --_s)->~T();
}
/**
* Resize vector
*
* @param ns New size (clipped to C if larger than capacity)
*/
ZT_ALWAYS_INLINE void resize(unsigned int ns)
{
if (ns > C)
ns = C;
unsigned int s = _s;
while (s < ns)
new(reinterpret_cast<T *>(_m) + s++) T();
while (s > ns)
(reinterpret_cast<T *>(_m) + --s)->~T();
_s = s;
}
/**
* Resize without calling any constructors or destructors on T
*
* This must only be called if T is a primitive type or is TriviallyCopyable and
* safe to initialize from undefined contents.
*
* @param ns New size (clipped to C if larger than capacity)
*/
ZT_ALWAYS_INLINE void unsafeResize(const unsigned int ns) { _s = (ns > C) ? C : ns; }
/**
* This is a bounds checked auto-resizing variant of the [] operator
*
* If 'i' is out of bounds vs the current size of the vector, the vector is
* resized. If that size would exceed C (capacity), 'i' is clipped to C-1.
*
* @param i Index to obtain as a reference, resizing if needed
* @return Reference to value at this index
*/
ZT_ALWAYS_INLINE T &at(unsigned int i)
{
if (i >= _s) {
if (unlikely(i >= C))
i = C - 1;
do {
new(reinterpret_cast<T *>(_m) + _s++) T();
} while (i >= _s);
}
return *(reinterpret_cast<T *>(_m) + i);
}
/**
* Assign this vector's contents from a range of pointers or iterators
*
* If the range is larger than C it is truncated at C.
*
* @tparam X Inferred type of interators or pointers
* @param start Starting iterator
* @param end Ending iterator (must be greater than start)
*/
template<typename X>
ZT_ALWAYS_INLINE void assign(X start,const X &end)
{
const int l = std::min((int)std::distance(start,end),(int)C);
if (l > 0) {
this->resize((unsigned int)l);
for(int i=0;i<l;++i)
reinterpret_cast<T *>(_m)[i] = *(start++);
} else {
this->clear();
}
}
template<unsigned int C2>
ZT_ALWAYS_INLINE bool operator==(const FCV<T,C2> &v) const
{
if (_s == v._s) {
for(unsigned int i=0;i<_s;++i) {
if (!(*(reinterpret_cast<const T *>(_m) + i) == *(reinterpret_cast<const T *>(v._m) + i)))
return false;
}
return true;
}
return false;
}
template<unsigned int C2>
ZT_ALWAYS_INLINE bool operator!=(const FCV<T,C2> &v) const { return (!(*this == v)); }
template<unsigned int C2>
ZT_ALWAYS_INLINE bool operator<(const FCV<T,C2> &v) const { return std::lexicographical_compare(begin(),end(),v.begin(),v.end()); }
template<unsigned int C2>
ZT_ALWAYS_INLINE bool operator>(const FCV<T,C2> &v) const { return (v < *this); }
template<unsigned int C2>
ZT_ALWAYS_INLINE bool operator<=(const FCV<T,C2> &v) const { return !(v < *this); }
template<unsigned int C2>
ZT_ALWAYS_INLINE bool operator>=(const FCV<T,C2> &v) const { return !(*this < v); }
private:
unsigned int _s;
uint8_t _m[sizeof(T) * C];
};
} // namespace ZeroTier
#endif

View file

@ -106,7 +106,6 @@ public:
memoryCopyUnsafe(this,&ss); memoryCopyUnsafe(this,&ss);
return *this; return *this;
} }
ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_storage *ss) ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_storage *ss)
{ {
if (ss) if (ss)
@ -114,13 +113,11 @@ public:
else memoryZero(this); else memoryZero(this);
return *this; return *this;
} }
ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in &sa) ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in &sa)
{ {
copySockaddrToThis(&sa); copySockaddrToThis(&sa);
return *this; return *this;
} }
ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in *sa) ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in *sa)
{ {
if (sa) if (sa)
@ -128,13 +125,11 @@ public:
else memset(reinterpret_cast<void *>(this),0,sizeof(InetAddress)); else memset(reinterpret_cast<void *>(this),0,sizeof(InetAddress));
return *this; return *this;
} }
ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in6 &sa) ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in6 &sa)
{ {
copySockaddrToThis(&sa); copySockaddrToThis(&sa);
return *this; return *this;
} }
ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in6 *sa) ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr_in6 *sa)
{ {
if (sa) if (sa)
@ -142,7 +137,6 @@ public:
else memset(reinterpret_cast<void *>(this),0,sizeof(InetAddress)); else memset(reinterpret_cast<void *>(this),0,sizeof(InetAddress));
return *this; return *this;
} }
ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr &sa) ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr &sa)
{ {
if (sa.sa_family == AF_INET) if (sa.sa_family == AF_INET)
@ -152,7 +146,6 @@ public:
else memset(reinterpret_cast<void *>(this),0,sizeof(InetAddress)); else memset(reinterpret_cast<void *>(this),0,sizeof(InetAddress));
return *this; return *this;
} }
ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr *sa) ZT_ALWAYS_INLINE InetAddress &operator=(const struct sockaddr *sa)
{ {
if (sa) { if (sa) {
@ -239,9 +232,9 @@ public:
ZT_ALWAYS_INLINE unsigned int port() const ZT_ALWAYS_INLINE unsigned int port() const
{ {
switch(ss_family) { switch(ss_family) {
case AF_INET: return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(this)->sin_port)); case AF_INET: return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in *>(this)->sin_port));
case AF_INET6: return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_port)); case AF_INET6: return Utils::ntoh((uint16_t)(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_port));
default: return 0; default: return 0;
} }
} }

View file

@ -111,11 +111,16 @@ Node::~Node()
for(std::vector< SharedPtr<Network> >::iterator i(_networks.begin());i!=_networks.end();++i) for(std::vector< SharedPtr<Network> >::iterator i(_networks.begin());i!=_networks.end();++i)
i->zero(); i->zero();
} }
if (RR->sa) RR->sa->~SelfAwareness(); if (RR->sa) RR->sa->~SelfAwareness();
if (RR->topology) RR->topology->~Topology(); if (RR->topology) RR->topology->~Topology();
if (RR->sw) RR->sw->~Switch(); if (RR->sw) RR->sw->~Switch();
if (RR->t) RR->t->~Trace(); if (RR->t) RR->t->~Trace();
free(RR->rtmem); free(RR->rtmem);
// Let go of cached Buf objects. This does no harm if other nodes are running
// but usually that won't be the case.
freeBufPool();
} }
void Node::shutdown(void *tPtr) void Node::shutdown(void *tPtr)

View file

@ -144,6 +144,7 @@
#endif #endif
#ifndef __CPP11__ #ifndef __CPP11__
#define nullptr (0) #define nullptr (0)
#define constexpr ZT_ALWAYS_INLINE
#endif #endif
#ifdef SOCKET #ifdef SOCKET

View file

@ -32,7 +32,7 @@ namespace ZeroTier {
class RuntimeEnvironment; class RuntimeEnvironment;
template<unsigned int MF> template<unsigned int MF,unsigned int GCT,unsigned int GCS>
class Defragmenter; class Defragmenter;
/** /**
@ -43,7 +43,7 @@ class Path
friend class SharedPtr<Path>; friend class SharedPtr<Path>;
// Allow defragmenter to access fragment in flight info stored in Path for performance reasons. // Allow defragmenter to access fragment in flight info stored in Path for performance reasons.
template<unsigned int MF> template<unsigned int MF,unsigned int GCT,unsigned int GCS>
friend class Defragmenter; friend class Defragmenter;
public: public:

View file

@ -15,6 +15,9 @@
#include "Buf.hpp" #include "Buf.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include <cstdlib>
#include <stdexcept>
#if defined(__GCC__) && (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64)) #if defined(__GCC__) && (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64))
#define ZT_PACKET_USE_ATOMIC_INTRINSICS #define ZT_PACKET_USE_ATOMIC_INTRINSICS
#endif #endif
@ -27,41 +30,6 @@ namespace Protocol {
namespace { namespace {
const uint64_t ZEROES32[4] = { 0,0,0,0 };
/**
* Deterministically mangle a 256-bit crypto key based on packet
*
* This uses extra data from the packet to mangle the secret, giving us an
* effective IV that is somewhat more than 64 bits. This is "free" for
* Salsa20 since it has negligible key setup time so using a different
* key each time is fine.
*
* @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)
{
// IV and source/destination addresses. Using the addresses divides the
// key space into two halves-- A->B and B->A (since order will change).
for(int i=0;i<18;++i) // 8 + (ZT_ADDRESS_LENGTH * 2) == 18
out[i] = in[i] ^ packet.data.bytes[i];
// Flags, but with hop count masked off. Hop count is altered by forwarding
// nodes. It's one of the only parts of a packet modifiable by people
// without the key.
out[18] = in[18] ^ (packet.data.fields.flags & 0xf8U);
// Raw packet size in bytes -- thus each packet size defines a new
// key space.
out[19] = in[19] ^ (uint8_t)packetSize;
out[20] = in[20] ^ (uint8_t)(packetSize >> 8U); // little endian
// Rest of raw key is used unchanged
for(int i=21;i<32;++i)
out[i] = in[i];
}
unsigned long long _initPacketID() unsigned long long _initPacketID()
{ {
unsigned long long tmp = 0; unsigned long long tmp = 0;
@ -76,8 +44,29 @@ unsigned long long _packetIdCtr = _initPacketID();
static std::atomic<unsigned long long> _packetIdCtr(_initPacketID()); static std::atomic<unsigned long long> _packetIdCtr(_initPacketID());
#endif #endif
uintptr_t _checkStructureSizing()
{
if (sizeof(Header) != ZT_PROTO_MIN_PACKET_LENGTH)
throw std::runtime_error("sizeof(Header) != ZT_PROTO_MIN_PACKET_LENGTH");
if (sizeof(FragmentHeader) != ZT_PROTO_MIN_FRAGMENT_LENGTH)
throw std::runtime_error("sizeof(FragmentHeader) != ZT_PROTO_MIN_FRAGMENT_LENGTH");
return (uintptr_t)Utils::getSecureRandomU64(); // also prevents compiler from optimizing out
}
} // anonymous namespace } // anonymous namespace
volatile uintptr_t _compileTimeStructCheckHappened = _checkStructureSizing();
uint64_t getPacketId()
{
#ifdef ZT_PACKET_USE_ATOMIC_INTRINSICS
return __sync_add_and_fetch(&_packetIdCtr,1ULL);
#else
return ++_packetIdCtr;
#endif
}
#if 0
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 packet.data.fields.flags = (packet.data.fields.flags & 0xc7U) | ((cipherSuite << 3U) & 0x38U); // FFCCCHHH
@ -102,32 +91,6 @@ void _armor(Buf< Header > &packet,const unsigned int packetSize,const uint8_t ke
} }
} }
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) {
// TODO
} else if (cipherSuite != ZT_PROTO_CIPHER_SUITE__NONE) {
uint8_t mangledKey[ZT_PEER_SECRET_KEY_LENGTH],macKey[ZT_POLY1305_KEY_LEN];
uint64_t mac[2];
_salsa20MangleKey(key,mangledKey,packet,packetSize);
Salsa20 s20(mangledKey,&(packet.data.fields.packetId));
s20.crypt12(ZEROES32,macKey,sizeof(macKey));
uint8_t *payload = packet.data.bytes + ZT_PROTO_PACKET_ENCRYPTED_SECTION_START;
const unsigned int payloadLen = packetSize - ZT_PROTO_PACKET_ENCRYPTED_SECTION_START;
if (cipherSuite == ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012)
s20.crypt12(payload,payload,payloadLen);
poly1305(mac,payload,payloadLen,macKey);
if (packet.data.fields.mac != mac[0])
return -1;
}
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]; uint8_t tmp[ZT_BUF_MEM_SIZE + 32];
@ -149,36 +112,7 @@ unsigned int _compress(Buf< Header > &packet,const unsigned int packetSize)
return packetSize; return packetSize;
} }
int _uncompress(Buf< Header > &packet,const unsigned int packetSize)
{
uint8_t tmp[ZT_BUF_MEM_SIZE];
if ((packet.data.fields.verb & ZT_PROTO_VERB_FLAG_COMPRESSED) == 0)
return (int)packetSize;
const int uncompressedLen = LZ4_decompress_safe(
reinterpret_cast<const char *>(packet.data.bytes + ZT_PROTO_PACKET_PAYLOAD_START),
reinterpret_cast<char *>(tmp),
(int)(packetSize - ZT_PROTO_PACKET_PAYLOAD_START),
sizeof(tmp) - ZT_PROTO_PACKET_PAYLOAD_START);
if ((uncompressedLen > 0)&&(uncompressedLen <= (sizeof(tmp) - ZT_PROTO_PACKET_PAYLOAD_START))) {
packet.data.fields.verb &= (uint8_t)(~((uint8_t)ZT_PROTO_VERB_FLAG_COMPRESSED));
memcpy(packet.data.bytes + ZT_PROTO_PACKET_PAYLOAD_START,tmp,uncompressedLen);
return uncompressedLen + ZT_PROTO_PACKET_PAYLOAD_START;
}
return -1;
}
uint64_t getPacketId()
{
#ifdef ZT_PACKET_USE_ATOMIC_INTRINSICS
return __sync_add_and_fetch(&_packetIdCtr,1ULL);
#else
return ++_packetIdCtr;
#endif #endif
}
} // namespace Protocol } // namespace Protocol
} // namespace ZeroTier } // namespace ZeroTier

View file

@ -119,7 +119,12 @@
/** /**
* Magic number indicating a fragment * Magic number indicating a fragment
*/ */
#define ZT_PACKET_FRAGMENT_INDICATOR 0xff #define ZT_PROTO_PACKET_FRAGMENT_INDICATOR 0xff
/**
* Index at which fragment indicator is found in fragments
*/
#define ZT_PROTO_PACKET_FRAGMENT_INDICATOR_INDEX 13
/** /**
* Minimum viable length for a fragment * Minimum viable length for a fragment
@ -129,17 +134,17 @@
/** /**
* Index at which packet fragment payload starts * Index at which packet fragment payload starts
*/ */
#define ZT_PROTO_PACKET_FRAGMENT_PAYLOAD_START_AT 16 #define ZT_PROTO_PACKET_FRAGMENT_PAYLOAD_START_AT ZT_PROTO_MIN_FRAGMENT_LENGTH
/** /**
* Header flag indicating that a packet is fragmented and more fragments should be expected * Header flag indicating that a packet is fragmented and more fragments should be expected
*/ */
#define ZT_PROTO_FLAG_FRAGMENTED 0x40 #define ZT_PROTO_FLAG_FRAGMENTED 0x40U
/** /**
* Verb flag indicating payload is compressed with LZ4 * Verb flag indicating payload is compressed with LZ4
*/ */
#define ZT_PROTO_VERB_FLAG_COMPRESSED 0x80 #define ZT_PROTO_VERB_FLAG_COMPRESSED 0x80U
/** /**
* HELLO exchange meta-data: signed locator for this node * HELLO exchange meta-data: signed locator for this node
@ -203,10 +208,6 @@
* For unencrypted packets, MAC is computed on plaintext. Only HELLO is ever * For unencrypted packets, MAC is computed on plaintext. Only HELLO is ever
* sent in the clear, as it's the "here is my public key" message. * sent in the clear, as it's the "here is my public key" message.
* *
* Fragments are sent if a packet is larger than UDP MTU. The first fragment
* is sent with its normal header with the fragmented flag set. Remaining
* fragments are sent this way.
*
* The fragmented bit indicates that there is at least one fragment. Fragments * The fragmented bit indicates that there is at least one fragment. Fragments
* themselves contain the total, so the receiver must "learn" this from the * themselves contain the total, so the receiver must "learn" this from the
* first fragment it receives. * first fragment it receives.
@ -755,7 +756,6 @@ ZT_PACKED_STRUCT(struct FragmentHeader
uint8_t fragmentIndicator; // always 0xff for fragments uint8_t fragmentIndicator; // always 0xff for fragments
uint8_t counts; // total: most significant four bits, number: least significant four bits 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 hops; // top 5 bits unused and must be zero
uint8_t p[];
}); });
ZT_PACKED_STRUCT(struct HELLO ZT_PACKED_STRUCT(struct HELLO
@ -766,7 +766,6 @@ ZT_PACKED_STRUCT(struct HELLO
uint8_t versionMinor; uint8_t versionMinor;
uint16_t versionRev; uint16_t versionRev;
uint64_t timestamp; uint64_t timestamp;
uint8_t p[];
}); });
ZT_PACKED_STRUCT(struct RENDEZVOUS ZT_PACKED_STRUCT(struct RENDEZVOUS
@ -776,7 +775,6 @@ ZT_PACKED_STRUCT(struct RENDEZVOUS
uint8_t peerAddress[5]; uint8_t peerAddress[5];
uint16_t port; uint16_t port;
uint8_t addressLength; uint8_t addressLength;
uint8_t address[];
}); });
ZT_PACKED_STRUCT(struct FRAME ZT_PACKED_STRUCT(struct FRAME
@ -784,7 +782,6 @@ ZT_PACKED_STRUCT(struct FRAME
Header h; Header h;
uint64_t networkId; uint64_t networkId;
uint16_t etherType; uint16_t etherType;
uint8_t data[];
}); });
ZT_PACKED_STRUCT(struct EXT_FRAME ZT_PACKED_STRUCT(struct EXT_FRAME
@ -792,7 +789,6 @@ ZT_PACKED_STRUCT(struct EXT_FRAME
Header h; Header h;
uint64_t networkId; uint64_t networkId;
uint8_t flags; uint8_t flags;
uint8_t p[];
}); });
ZT_PACKED_STRUCT(struct MULTICAST_LIKE ZT_PACKED_STRUCT(struct MULTICAST_LIKE
@ -805,7 +801,6 @@ ZT_PACKED_STRUCT(struct MULTICAST_LIKE
}); });
Header h; Header h;
Entry groups[];
}); });
namespace OK { namespace OK {
@ -922,59 +917,48 @@ ZT_ALWAYS_INLINE uint8_t packetHops(const Header &h) { return (h.flags & 0x07U);
*/ */
ZT_ALWAYS_INLINE uint8_t packetCipher(const Header &h) { return ((h.flags >> 3U) & 0x07U); } 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 * Deterministically mangle a 256-bit crypto key based on packet characteristics
* *
* @param packet Packet to armor * This uses extra data from the packet to mangle the secret, yielding when
* @param packetSize Size of data in packet (must be at least the minimum packet size) * combined with Salsa20's conventional 64-bit nonce an effective nonce that's
* @param key 256-bit symmetric key * more like 68 bits.
* @param cipherSuite Cipher suite to apply *
* @param in Input key (32 bytes)
* @param out Output buffer (32 bytes)
*/ */
template<typename X> ZT_ALWAYS_INLINE void salsa2012DeriveKey(const uint8_t *const in,uint8_t *const out,const Buf &packet,const unsigned int packetSize)
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); } // IV and source/destination addresses. Using the addresses divides the
// key space into two halves-- A->B and B->A (since order will change).
#ifdef ZT_NO_UNALIGNED_ACCESS
for(int i=0;i<18;++i)
out[i] = in[i] ^ packet.b[i];
#else
*reinterpret_cast<uint64_t *>(out) = *reinterpret_cast<const uint64_t *>(in) ^ *reinterpret_cast<const uint64_t *>(packet.b);
*reinterpret_cast<uint64_t *>(out + 8) = *reinterpret_cast<const uint64_t *>(in + 8) ^ *reinterpret_cast<const uint64_t *>(packet.b + 8);
*reinterpret_cast<uint16_t *>(out + 16) = *reinterpret_cast<const uint16_t *>(in + 16) ^ *reinterpret_cast<const uint16_t *>(packet.b + 16);
#endif
/** // Flags, but with hop count masked off. Hop count is altered by forwarding
* Dearmor a packet and check message authentication code // nodes and is the only field that is mutable by unauthenticated third parties.
* out[18] = in[18] ^ (packet.b[18] & 0xf8U);
* If the packet is valid and MAC (if indicated) passes, the cipher suite
* is returned. Otherwise -1 is returned to indicate a MAC failure.
*
* @param packet Packet to dearmor
* @param packetSize Size of data in packet (must be at least the minimum packet size)
* @param key 256-bit symmetric key
* @return Cipher suite or -1 if MAC validation failed
*/
template<typename X>
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); }
/** // Raw packet size in bytes -- thus each packet size defines a new key space.
* Compress packet payload out[19] = in[19] ^ (uint8_t)packetSize;
* out[20] = in[20] ^ (uint8_t)(packetSize >> 8U); // little endian
* @param packet Packet to compress
* @param packetSize Original packet size
* @return New packet size (returns original size of compression didn't help, in which case packet is unmodified)
*/
template<typename X>
static ZT_ALWAYS_INLINE unsigned int compress(Buf< X > &packet,unsigned int packetSize)
{ return _compress(reinterpret_cast< Buf< Header > & >(packet),packetSize); }
/** // Rest of raw key is used unchanged
* Uncompress packet payload (if compressed) #ifdef ZT_NO_UNALIGNED_ACCESS
* for(int i=21;i<32;++i)
* @param packet Packet to uncompress out[i] = in[i];
* @param packetSize Original packet size #else
* @return New packet size or -1 on decompression error (returns original packet size if packet wasn't compressed) out[21] = in[21];
*/ out[22] = in[22];
template<typename X> out[23] = in[23];
static ZT_ALWAYS_INLINE int uncompress(Buf< X > &packet,unsigned int packetSize) *reinterpret_cast<uint64_t *>(out + 24) = *reinterpret_cast<const uint64_t *>(in + 24);
{ return _uncompress(reinterpret_cast< Buf< Header > & >(packet),packetSize); } #endif
}
/** /**
* Get a sequential non-repeating packet ID for the next packet (thread-safe) * Get a sequential non-repeating packet ID for the next packet (thread-safe)
@ -983,6 +967,12 @@ static ZT_ALWAYS_INLINE int uncompress(Buf< X > &packet,unsigned int packetSize)
*/ */
uint64_t getPacketId(); uint64_t getPacketId();
void armor(Buf &packet,unsigned int packetSize,const uint8_t key[ZT_PEER_SECRET_KEY_LENGTH],uint8_t cipherSuite);
unsigned int compress(Buf &packet,unsigned int packetSize);
int uncompress(Buf &packet,unsigned int packetSize);
} // namespace Protocol } // namespace Protocol
} // namespace ZeroTier } // namespace ZeroTier

View file

@ -25,6 +25,7 @@
#if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64)) #if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64))
#include <xmmintrin.h> #include <xmmintrin.h>
#include <emmintrin.h> #include <emmintrin.h>
#include <immintrin.h>
#define ZT_SALSA20_SSE 1 #define ZT_SALSA20_SSE 1
#endif #endif

546
node/VL1.cpp Normal file
View file

@ -0,0 +1,546 @@
/*
* 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.
*/
/****/
#include "VL1.hpp"
#include "RuntimeEnvironment.hpp"
#include "Topology.hpp"
#include "VL2.hpp"
#include "Salsa20.hpp"
#include "LZ4.hpp"
#include "Poly1305.hpp"
namespace ZeroTier {
namespace {
#if 0
ZT_ALWAYS_INLINE bool _doHELLO(SharedPtr<Buf> &pkt,int len,const RuntimeEnvironment *const RR,void *const tPtr,const bool alreadyAuthenticated)
{
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<Buf< Protocol::HELLO > &>(*p.pkt);
Identity id;
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 (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 (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> peer(RR->topology->get(tPtr,id.address()));
if (peer) {
// We already have an identity with this address -- check for collisions
if (!alreadyAuthenticated) {
if (peer->identity() != id) {
// Identity is different from the one we already have -- address collision
// Check rate limits
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 (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 {
// TODO: we handle identity collisions differently now
}
} else {
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 (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;
}
// Continue at // VALID
}
} // else if alreadyAuthenticated then continue at // VALID
} else {
// We don't already have an identity with this address -- validate and learn it
// Sanity check: this basically can't happen
if (alreadyAuthenticated) {
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,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<Peer> newPeer(new Peer(RR));
if (!newPeer->init(RR->identity,id)) {
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 (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,p.idBE,0,id,p.path->address(),p.hops,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT);
return true;
}
peer = RR->topology->add(tPtr,newPeer);
// Continue at // VALID
}
// 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.
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.
ZT_GET_NEW_BUF(outp,Protocol::OK::HELLO);
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;
}
#endif
} // anonymous namespace
VL1::VL1(const RuntimeEnvironment *renv) :
RR(renv),
_vl2(nullptr)
{
}
VL1::~VL1()
{
}
void VL1::onRemotePacket(void *const tPtr,const int64_t localSocket,const InetAddress &fromAddr,SharedPtr<Buf> &data,const unsigned int len)
{
static const uint64_t ZEROES32[4] = { 0,0,0,0 };
const int64_t now = RR->node->now();
const SharedPtr<Path> path(RR->topology->getPath(localSocket,fromAddr));
path->received(now);
if (len < ZT_PROTO_MIN_FRAGMENT_LENGTH)
return;
try {
FCV<Buf::Slice,ZT_MAX_PACKET_FRAGMENTS> pktv;
Address destination;
if (data->b[ZT_PROTO_PACKET_FRAGMENT_INDICATOR_INDEX] == ZT_PROTO_PACKET_FRAGMENT_INDICATOR) {
// Fragment -----------------------------------------------------------------------------------------------------
const Protocol::FragmentHeader &fh = data->as<Protocol::FragmentHeader>();
destination.setTo(fh.destination);
if (destination != RR->identity.address()) {
// Fragment is not address to this node -----------------------------------------------------------------------
_relay(tPtr,path,destination,data,len);
return;
}
switch (_inputPacketAssembler.assemble(
fh.packetId,
pktv,
data,
ZT_PROTO_PACKET_FRAGMENT_PAYLOAD_START_AT,
(unsigned int)(len - ZT_PROTO_PACKET_FRAGMENT_PAYLOAD_START_AT),
fh.counts & 0xfU, // fragment number
fh.counts >> 4U, // total number of fragments in message is specified in each fragment
now,
path,
ZT_MAX_INCOMING_FRAGMENTS_PER_PATH)) {
case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::COMPLETE:
break;
default:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::OK:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_DUPLICATE_FRAGMENT:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_INVALID_FRAGMENT:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_TOO_MANY_FRAGMENTS_FOR_PATH:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_OUT_OF_MEMORY:
return;
}
} else {
// Not fragment, meaning whole packet or head of series with fragments ------------------------------------------
if (len < ZT_PROTO_MIN_PACKET_LENGTH)
return;
const Protocol::Header &ph = data->as<Protocol::Header>();
destination.setTo(ph.destination);
if (destination != RR->identity.address()) {
// Packet or packet head is not address to this node ----------------------------------------------------------
_relay(tPtr,path,destination,data,len);
return;
}
if ((ph.flags & ZT_PROTO_FLAG_FRAGMENTED) != 0) {
// Head of fragmented packet ----------------------------------------------------------------------------------
switch (_inputPacketAssembler.assemble(
ph.packetId,
pktv,
data,
0,
len,
0, // always the zero'eth fragment
0, // this is specified in fragments, not in the head
now,
path,
ZT_MAX_INCOMING_FRAGMENTS_PER_PATH)) {
case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::COMPLETE:
break;
default:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::OK:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_DUPLICATE_FRAGMENT:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_INVALID_FRAGMENT:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_TOO_MANY_FRAGMENTS_FOR_PATH:
//case Defragmenter<ZT_MAX_PACKET_FRAGMENTS>::ERR_OUT_OF_MEMORY:
return;
}
} else {
// Unfragmented packet, skip defrag engine and just handle it -------------------------------------------------
Buf::Slice &s = pktv.push();
s.b = data;
s.s = 0;
s.e = len;
}
}
// Packet defragmented and apparently addressed to this node ------------------------------------------------------
// Subject pktv to a few sanity checks just to make sure Defragmenter worked correctly and
// there is enough room in each slice to shift their contents to sizes that are multiples
// of 64 if needed for crypto.
if ((pktv.empty()) || (((int)pktv[0].e - (int)pktv[0].s) < sizeof(Protocol::Header)))
return;
for(FCV<Buf::Slice,ZT_MAX_PACKET_FRAGMENTS>::const_iterator s(pktv.begin());s!=pktv.end();++s) {
if ((s->e > (ZT_BUF_MEM_SIZE - 64))||(s->s > s->e))
return;
}
Protocol::Header *ph = &(pktv[0].b->as<Protocol::Header>(pktv[0].s));
const Address source(ph->source);
if (source == RR->identity.address())
return;
SharedPtr<Peer> peer(RR->topology->get(tPtr,source));
Buf::Slice pkt;
bool authenticated = false;
const uint8_t hops = Protocol::packetHops(*ph);
const uint8_t cipher = Protocol::packetCipher(*ph);
unsigned int packetSize = pktv[0].e - pktv[0].s;
for(FCV<Buf::Slice,ZT_MAX_PACKET_FRAGMENTS>::const_iterator s(pktv.begin()+1);s!=pktv.end();++s)
packetSize += s->e - s->s;
if (packetSize > ZT_PROTO_MAX_PACKET_LENGTH) {
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,Identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET);
return;
}
// If we don't know this peer and this is not a HELLO, issue a WHOIS and enqueue this packet to try again.
if ((!peer)&&(!(((cipher == ZT_PROTO_CIPHER_SUITE__POLY1305_NONE)||(cipher == ZT_PROTO_CIPHER_SUITE__NONE))&&((ph->verb & 0x1fU) == Protocol::VERB_HELLO)))) {
pkt = Buf::assembleSliceVector(pktv);
if (pkt.e < ZT_PROTO_MIN_PACKET_LENGTH) {
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,Identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET);
return;
}
{
Mutex::Lock wl(_whoisQueue_l);
_WhoisQueueItem &wq = _whoisQueue[source];
wq.inboundPackets.push_back(pkt);
if (wq.retries == 0) {
wq.retries = 1;
_sendPendingWhois();
}
}
return;
}
switch(cipher) {
case ZT_PROTO_CIPHER_SUITE__POLY1305_NONE:
if (peer) {
pkt = Buf::assembleSliceVector(pktv);
if (pkt.e < ZT_PROTO_MIN_PACKET_LENGTH)
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,Identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET);
ph = &(pkt.b->as<Protocol::Header>());
// Generate one-time-use MAC key using Salsa20.
uint8_t perPacketKey[ZT_PEER_SECRET_KEY_LENGTH];
uint8_t macKey[ZT_POLY1305_KEY_LEN];
Protocol::salsa2012DeriveKey(peer->key(),perPacketKey,*pktv[0].b,packetSize);
Salsa20(perPacketKey,&ph->packetId).crypt12(ZEROES32,macKey,ZT_POLY1305_KEY_LEN);
// Verify packet MAC.
uint64_t mac[2];
poly1305(mac,pkt.b->b,pkt.e,macKey);
if (ph->mac != mac[0]) {
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,peer->identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED);
return;
}
authenticated = true;
}
break;
case ZT_PROTO_CIPHER_SUITE__POLY1305_SALSA2012:
if (peer) {
// Derive per-packet key using symmetric key plus some data from the packet header.
uint8_t perPacketKey[ZT_PEER_SECRET_KEY_LENGTH];
Protocol::salsa2012DeriveKey(peer->key(),perPacketKey,*pktv[0].b,packetSize);
Salsa20 s20(perPacketKey,&ph->packetId);
// Do one Salsa20 block to generate the one-time-use Poly1305 key.
uint8_t macKey[ZT_POLY1305_KEY_LEN];
s20.crypt12(ZEROES32,macKey,ZT_POLY1305_KEY_LEN);
// Get a buffer to store the decrypted and fully contiguous packet.
pkt.b = Buf::get();
if (!pkt.b) // only possible on out of memory condition
return;
// Salsa20 is a stream cipher but it's only seekable to multiples of 64 bytes.
// This moves data in slices around so that all slices have sizes that are
// multiples of 64 except the last slice. Note that this does not corrupt
// the assembled slice vector, just moves data around.
if (pktv.size() > 1) {
unsigned int prevOverflow,i;
for (typename FCV<Buf::Slice,ZT_MAX_PACKET_FRAGMENTS>::iterator ps(pktv.begin()),s(ps + 1);s!=pktv.end();) {
prevOverflow = (ps->e - ps->s) & 63U; // amount by which previous exceeds a multiple of 64
for(i=0;i<prevOverflow;++i) {
if (s->s >= s->e)
goto next_slice;
ps->b->b[ps->e++] = s->b->b[s->s++]; // move from head of current to end of previous
}
next_slice: ps = s++;
}
}
// Simultaneously decrypt and assemble packet into a contiguous buffer.
memcpy(pkt.b->b,ph,sizeof(Protocol::Header));
pkt.e = sizeof(Protocol::Header);
for(FCV<Buf::Slice,ZT_MAX_PACKET_FRAGMENTS>::iterator s(pktv.begin());s!=pktv.end();++s) {
const unsigned int sliceSize = s->e - s->s;
s20.crypt12(s->b->b + s->s,pkt.b->b + pkt.e,sliceSize);
pkt.e += sliceSize;
}
ph = &(pkt.b->as<Protocol::Header>());
// Verify packet MAC.
uint64_t mac[2];
poly1305(mac,pkt.b->b,pkt.e,macKey);
if (ph->mac != mac[0]) {
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,peer->identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED);
return;
}
authenticated = true;
} else {
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,Identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED);
return;
}
break;
case ZT_PROTO_CIPHER_SUITE__NONE: {
// CIPHER_SUITE__NONE is only used with trusted paths. Verification is performed by
// checking the address and the presence of its corresponding trusted path ID in the
// packet header's MAC field.
pkt = Buf::assembleSliceVector(pktv);
if (pkt.e < ZT_PROTO_MIN_PACKET_LENGTH)
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,Identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET);
ph = &(pkt.b->as<Protocol::Header>());
if (RR->topology->shouldInboundPathBeTrusted(path->address(),Utils::ntoh(ph->mac))) {
authenticated = true;
} else {
if (peer)
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,peer->identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_MAC_FAILED);
return;
}
} break;
//case ZT_PROTO_CIPHER_SUITE__AES_GCM_NRH:
// if (peer) {
// }
// break;
default:
if (peer)
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,peer->identity(),path->address(),hops,Protocol::VERB_NOP,ZT_TRACE_PACKET_DROP_REASON_INVALID_OBJECT);
return;
}
// Packet fully assembled and may be authenticated ----------------------------------------------------------------
// Return any still held buffers in pktv to the buffer pool.
pktv.clear();
// Decompress packet payload if compressed.
if (((ph->verb & ZT_PROTO_VERB_FLAG_COMPRESSED) != 0)&&(authenticated)) {
SharedPtr<Buf> nb(Buf::get());
if (!nb) // can only happen if we're out of memory
return;
const int uncompressedLen = LZ4_decompress_safe(
reinterpret_cast<const char *>(pkt.b->b + ZT_PROTO_PACKET_PAYLOAD_START),
reinterpret_cast<char *>(nb->b),
(int)(packetSize - ZT_PROTO_PACKET_PAYLOAD_START),
ZT_BUF_MEM_SIZE - ZT_PROTO_PACKET_PAYLOAD_START);
if ((uncompressedLen > 0)&&(uncompressedLen <= (ZT_BUF_MEM_SIZE - ZT_PROTO_PACKET_PAYLOAD_START))) {
pkt.b.swap(nb);
pkt.e = packetSize = (unsigned int)uncompressedLen;
} else {
if (peer)
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,peer->identity(),path->address(),hops,(Protocol::Verb)(ph->verb & 0x1fU),ZT_TRACE_PACKET_DROP_REASON_INVALID_COMPRESSED_DATA);
return;
}
}
const Protocol::Verb verb = (Protocol::Verb)(ph->verb & 0x1fU);
switch(verb) {
case Protocol::VERB_NOP:
peer->received(tPtr,path,hops,ph->packetId,packetSize - ZT_PROTO_PACKET_PAYLOAD_START,Protocol::VERB_NOP,0,Protocol::VERB_NOP,0);
break;
case Protocol::VERB_HELLO: _HELLO(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_ERROR: _ERROR(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_OK: _OK(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_WHOIS: _WHOIS(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_RENDEZVOUS: _RENDEZVOUS(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_FRAME: _vl2->_FRAME(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_EXT_FRAME: _vl2->_EXT_FRAME(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_ECHO: _ECHO(tPtr,path,peer,*pkt.b,packetSize,authenticated);
case Protocol::VERB_MULTICAST_LIKE: _vl2->_MULTICAST_LIKE(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_NETWORK_CREDENTIALS: _vl2->_NETWORK_CREDENTIALS(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_NETWORK_CONFIG_REQUEST: _vl2->_NETWORK_CONFIG_REQUEST(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_NETWORK_CONFIG: _vl2->_NETWORK_CONFIG(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_MULTICAST_GATHER: _vl2->_MULTICAST_LIKE(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_MULTICAST_FRAME_deprecated: _vl2->_MULTICAST_FRAME_deprecated(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_PUSH_DIRECT_PATHS: _PUSH_DIRECT_PATHS(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_USER_MESSAGE: _USER_MESSAGE(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_MULTICAST: _vl2->_MULTICAST(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
case Protocol::VERB_ENCAP: _ENCAP(tPtr,path,peer,*pkt.b,packetSize,authenticated); break;
default:
if (peer)
RR->t->incomingPacketDropped(tPtr,ph->packetId,0,peer->identity(),path->address(),hops,verb,ZT_TRACE_PACKET_DROP_REASON_UNRECOGNIZED_VERB);
break;
}
} catch ( ... ) {
uint64_t packetId = 0;
if (len >= 8) {
for(int i=0;i<8;++i)
reinterpret_cast<uint8_t *>(&packetId)[i] = data->b[i];
}
RR->t->incomingPacketDropped(tPtr,packetId,0,Identity(),path->address(),0,Protocol::VERB_HELLO,ZT_TRACE_PACKET_DROP_REASON_MALFORMED_PACKET);
}
}
void VL1::_relay(void *tPtr,const SharedPtr<Path> &path,const Address &destination,SharedPtr<Buf> &data,unsigned int len)
{
}
void VL1::_sendPendingWhois()
{
// assume _whoisQueue_l locked
}
void VL1::_HELLO(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated)
{
}
void VL1::_ERROR(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated)
{
}
void VL1::_OK(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated)
{
}
void VL1::_WHOIS(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated)
{
}
void VL1::_PUSH_DIRECT_PATHS(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated)
{
}
void VL1::_USER_MESSAGE(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated)
{
}
void VL1::_ENCAP(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated)
{
}
} // namespace ZeroTier

88
node/VL1.hpp Normal file
View file

@ -0,0 +1,88 @@
/*
* 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_VL1_HPP
#define ZT_VL1_HPP
#include "Constants.hpp"
#include "Defragmenter.hpp"
#include "Buf.hpp"
#include "Address.hpp"
#include "Protocol.hpp"
#include "Hashtable.hpp"
#include "Mutex.hpp"
#include "FCV.hpp"
#include <vector>
namespace ZeroTier {
class RuntimeEnvironment;
class Peer;
class VL2;
/**
* VL1 (virtual layer 1) packet I/O and messaging
*/
class VL1
{
public:
explicit VL1(const RuntimeEnvironment *renv);
~VL1();
/**
* Called when a packet is received from the real network
*
* @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call
* @param localSocket Local I/O socket as supplied by external code
* @param fromAddr Internet IP address of origin
* @param data Packet data
* @param len Packet length
*/
void onRemotePacket(void *tPtr,int64_t localSocket,const InetAddress &fromAddr,SharedPtr<Buf> &data,unsigned int len);
private:
void _relay(void *tPtr,const SharedPtr<Path> &path,const Address &destination,SharedPtr<Buf> &data,unsigned int len);
void _sendPendingWhois();
// Handlers for VL1 verbs
void _HELLO(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _ERROR(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _OK(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _WHOIS(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _RENDEZVOUS(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _ECHO(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _PUSH_DIRECT_PATHS(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _USER_MESSAGE(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _ENCAP(void *tPtr,const SharedPtr<Path> &path,const SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
const RuntimeEnvironment *RR;
VL2 *const _vl2;
struct _WhoisQueueItem
{
ZT_ALWAYS_INLINE _WhoisQueueItem() : lastRetry(0),inboundPackets(),retries(0) {}
int64_t lastRetry;
FCV<Buf::Slice,32> inboundPackets; // capacity can be changed but this should be plenty
unsigned int retries;
};
Defragmenter<ZT_MAX_PACKET_FRAGMENTS> _inputPacketAssembler;
Hashtable<Address,_WhoisQueueItem> _whoisQueue;
Mutex _whoisQueue_l;
};
} // namespace ZeroTier
#endif

54
node/VL2.hpp Normal file
View file

@ -0,0 +1,54 @@
/*
* 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_VL2_HPP
#define ZT_VL2_HPP
#include "Constants.hpp"
#include "Buf.hpp"
#include "Address.hpp"
#include "Protocol.hpp"
#include "Hashtable.hpp"
#include "Mutex.hpp"
#include "FCV.hpp"
namespace ZeroTier {
class RuntimeEnvironment;
class VL1;
class VL2
{
friend class VL1;
public:
VL1(const RuntimeEnvironment *renv);
~VL1();
protected:
void _FRAME(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _EXT_FRAME(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _MULTICAST_LIKE(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _NETWORK_CREDENTIALS(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _NETWORK_CONFIG_REQUEST(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _NETWORK_CONFIG(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _MULTICAST_GATHER(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _MULTICAST_FRAME_deprecated(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
void _MULTICAST(void *tPtr,const SharedPtr<Path> &path,SharedPtr<Peer> &peer,Buf &pkt,unsigned int len,bool authenticated);
private:
};
} // namespace ZeroTier
#endif