Deferred decode for HELLO to prevent HELLOcalypse under high load of new peers.

This commit is contained in:
Adam Ierymenko 2015-11-05 12:22:58 -08:00
parent a95ff21aaf
commit 8ef4edebbf
12 changed files with 463 additions and 73 deletions

View File

@ -1511,6 +1511,27 @@ void ZT_Node_clusterHandleIncomingMessage(ZT_Node *node,const void *msg,unsigned
*/
void ZT_Node_clusterStatus(ZT_Node *node,ZT_ClusterStatus *cs);
/**
* Do things in the background until Node dies
*
* This function can be called from one or more background threads to process
* certain tasks in the background to improve foreground performance. It will
* not return until the Node is shut down. If threading is not enabled in
* this build it will return immediately and will do nothing.
*
* This is completely optional. If this is never called, all processing is
* done in the foreground in the various processXXXX() methods.
*
* This does NOT replace or eliminate the need to call the normal
* processBackgroundTasks() function in your main loop. This mechanism is
* used to offload the processing of expensive mssages onto background
* handler threads to prevent foreground performance degradation under
* high load.
*
* @param node Node instance
*/
void ZT_Node_backgroundThreadMain(ZT_Node *node);
/**
* Get ZeroTier One version
*

106
node/BinarySemaphore.hpp Normal file
View File

@ -0,0 +1,106 @@
/*
* ZeroTier One - Network Virtualization Everywhere
* Copyright (C) 2011-2015 ZeroTier, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*
* ZeroTier may be used and distributed under the terms of the GPLv3, which
* are available at: http://www.gnu.org/licenses/gpl-3.0.html
*
* If you would like to embed ZeroTier into a commercial application or
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
#ifndef ZT_BINARYSEMAPHORE_HPP
#define ZT_BINARYSEMAPHORE_HPP
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "Constants.hpp"
#include "NonCopyable.hpp"
#ifdef __WINDOWS__
#include <Windows.h>
namespace ZeroTier {
class BinarySemaphore : NonCopyable
{
public:
BinarySemaphore() throw() { _sem = CreateSemaphore(NULL,0,1,NULL); }
~BinarySemaphore() { CloseHandle(_sem); }
inline void wait() { WaitForSingleObject(_sem,INFINITE); }
inline void post() { ReleaseSemaphore(_sem,1,NULL); }
private:
HANDLE _sem;
};
} // namespace ZeroTier
#else // !__WINDOWS__
#include <pthread.h>
namespace ZeroTier {
class BinarySemaphore : NonCopyable
{
public:
BinarySemaphore()
{
pthread_mutex_init(&_mh,(const pthread_mutexattr_t *)0);
pthread_cond_init(&_cond,(const pthread_condattr_t *)0);
_f = false;
}
~BinarySemaphore()
{
pthread_cond_destroy(&_cond);
pthread_mutex_destroy(&_mh);
}
inline void wait()
{
pthread_mutex_lock(const_cast <pthread_mutex_t *>(&_mh));
while (!_f)
pthread_cond_wait(const_cast <pthread_cond_t *>(&_cond),const_cast <pthread_mutex_t *>(&_mh));
_f = false;
pthread_mutex_unlock(const_cast <pthread_mutex_t *>(&_mh));
}
inline void post()
{
pthread_mutex_lock(const_cast <pthread_mutex_t *>(&_mh));
_f = true;
pthread_mutex_unlock(const_cast <pthread_mutex_t *>(&_mh));
pthread_cond_signal(const_cast <pthread_cond_t *>(&_cond));
}
private:
pthread_cond_t _cond;
pthread_mutex_t _mh;
volatile bool _f;
};
} // namespace ZeroTier
#endif // !__WINDOWS__
#endif

95
node/DeferredPackets.cpp Normal file
View File

@ -0,0 +1,95 @@
/*
* ZeroTier One - Network Virtualization Everywhere
* Copyright (C) 2011-2015 ZeroTier, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*
* ZeroTier may be used and distributed under the terms of the GPLv3, which
* are available at: http://www.gnu.org/licenses/gpl-3.0.html
*
* If you would like to embed ZeroTier into a commercial application or
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
#include "Constants.hpp"
#include "DeferredPackets.hpp"
#include "IncomingPacket.hpp"
#include "RuntimeEnvironment.hpp"
#include "Node.hpp"
namespace ZeroTier {
DeferredPackets::DeferredPackets(const RuntimeEnvironment *renv) :
RR(renv),
_readPtr(0),
_writePtr(0),
_die(false)
{
}
DeferredPackets::~DeferredPackets()
{
_q_m.lock();
_die = true;
_q_m.unlock();
_q_s.post();
}
bool DeferredPackets::enqueue(IncomingPacket *pkt)
{
_q_m.lock();
const unsigned long p = _writePtr % ZT_DEFFEREDPACKETS_MAX;
if (_q[p]) {
_q_m.unlock();
return false;
} else {
_q[p].setToUnsafe(pkt);
++_writePtr;
_q_m.unlock();
_q_s.post();
return true;
}
}
int DeferredPackets::process()
{
SharedPtr<IncomingPacket> pkt;
_q_m.lock();
if (_die) {
_q_m.unlock();
_q_s.post();
return -1;
}
while (_readPtr == _writePtr) {
_q_m.unlock();
_q_s.wait();
_q_m.lock();
if (_die) {
_q_m.unlock();
_q_s.post();
return -1;
}
}
pkt.swap(_q[_readPtr++ % ZT_DEFFEREDPACKETS_MAX]);
_q_m.unlock();
pkt->tryDecode(RR,true);
return 1;
}
} // namespace ZeroTier

98
node/DeferredPackets.hpp Normal file
View File

@ -0,0 +1,98 @@
/*
* ZeroTier One - Network Virtualization Everywhere
* Copyright (C) 2011-2015 ZeroTier, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*
* ZeroTier may be used and distributed under the terms of the GPLv3, which
* are available at: http://www.gnu.org/licenses/gpl-3.0.html
*
* If you would like to embed ZeroTier into a commercial application or
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
#ifndef ZT_DEFERREDPACKETS_HPP
#define ZT_DEFERREDPACKETS_HPP
#include "Constants.hpp"
#include "SharedPtr.hpp"
#include "Mutex.hpp"
#include "DeferredPackets.hpp"
#include "BinarySemaphore.hpp"
/**
* Maximum number of deferred packets
*/
#define ZT_DEFFEREDPACKETS_MAX 1024
namespace ZeroTier {
class IncomingPacket;
class RuntimeEnvironment;
/**
* Deferred packets
*
* IncomingPacket can defer its decoding this way by enqueueing itself here.
* When this is done, deferredDecode() is called later. This is done for
* operations that may be expensive to allow them to potentially be handled
* in the background or rate limited to maintain quality of service for more
* routine operations.
*/
class DeferredPackets
{
public:
DeferredPackets(const RuntimeEnvironment *renv);
~DeferredPackets();
/**
* Enqueue a packet
*
* Since packets enqueue themselves, they call it with 'this' and we wrap
* them in a SharedPtr<>. This is safe as SharedPtr<> is introspective and
* supports this. This should not be called from any other code outside
* IncomingPacket.
*
* @param pkt Packet to process later (possibly in the background)
* @return False if queue is full
*/
bool enqueue(IncomingPacket *pkt);
/**
* Wait for and then process a deferred packet
*
* If we are shutting down (in destructor), this returns -1 and should
* not be called again. Otherwise it returns the number of packets
* processed.
*
* @return Number processed or -1 if shutting down
*/
int process();
private:
SharedPtr<IncomingPacket> _q[ZT_DEFFEREDPACKETS_MAX];
const RuntimeEnvironment *const RR;
unsigned long _readPtr;
unsigned long _writePtr;
bool _die;
Mutex _q_m;
BinarySemaphore _q_s;
};
} // namespace ZeroTier
#endif

View File

@ -46,21 +46,31 @@
#include "Cluster.hpp"
#include "Node.hpp"
#include "AntiRecursion.hpp"
#include "DeferredPackets.hpp"
namespace ZeroTier {
bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,bool deferred)
{
const Address sourceAddress(source());
try {
if ((cipher() == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)&&(verb() == Packet::VERB_HELLO)) {
// Unencrypted HELLOs are handled here since they are used to
// populate our identity cache in the first place. _doHELLO() is special
// in that it contains its own authentication logic.
return _doHELLO(RR);
// Unencrypted HELLOs require some potentially expensive verification, so
// do this in the background if background processing is enabled.
DeferredPackets *const dp = RR->dp; // read volatile pointer
if ((dp)&&(!deferred)) {
dp->enqueue(this);
return true; // 'handled' via deferring to background thread(s)
} else {
// A null pointer for peer to _doHELLO() tells it to run its own
// special internal authentication logic. This is done for unencrypted
// HELLOs to learn new identities, etc.
SharedPtr<Peer> tmp;
return _doHELLO(RR,tmp);
}
}
SharedPtr<Peer> peer = RR->topology->getPeer(sourceAddress);
SharedPtr<Peer> peer(RR->topology->getPeer(sourceAddress));
if (peer) {
if (!dearmor(peer->key())) {
TRACE("dropped packet from %s(%s), MAC authentication failed (size: %u)",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),size());
@ -79,7 +89,8 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR)
default: // ignore unknown verbs, but if they pass auth check they are "received"
peer->received(RR,_localAddress,_remoteAddress,hops(),packetId(),v,0,Packet::VERB_NOP);
return true;
case Packet::VERB_HELLO: return _doHELLO(RR);
case Packet::VERB_HELLO: return _doHELLO(RR,peer);
case Packet::VERB_ERROR: return _doERROR(RR,peer);
case Packet::VERB_OK: return _doOK(RR,peer);
case Packet::VERB_WHOIS: return _doWHOIS(RR,peer);
@ -185,7 +196,7 @@ bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer>
return true;
}
bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR)
bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,SharedPtr<Peer> &peer)
{
/* Note: this is the only packet ever sent in the clear, and it's also
* the only packet that we authenticate via a different path. Authentication
@ -226,63 +237,65 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR)
return true;
}
SharedPtr<Peer> peer(RR->topology->getPeer(id.address()));
if (peer) {
// We already have an identity with this address -- check for collisions
if (!peer) {
peer = RR->topology->getPeer(id.address());
if (peer) {
// We already have an identity with this address -- check for collisions
if (peer->identity() != id) {
// Identity is different from the one we already have -- address collision
if (peer->identity() != id) {
// Identity is different from the one we already have -- address collision
unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
if (RR->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
if (dearmor(key)) { // ensure packet is authentic, otherwise drop
TRACE("rejected HELLO from %s(%s): address already claimed",id.address().toString().c_str(),_remoteAddress.toString().c_str());
Packet outp(id.address(),RR->identity.address(),Packet::VERB_ERROR);
outp.append((unsigned char)Packet::VERB_HELLO);
outp.append((uint64_t)pid);
outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION);
outp.armor(key,true);
RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size());
unsigned char key[ZT_PEER_SECRET_KEY_LENGTH];
if (RR->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) {
if (dearmor(key)) { // ensure packet is authentic, otherwise drop
TRACE("rejected HELLO from %s(%s): address already claimed",id.address().toString().c_str(),_remoteAddress.toString().c_str());
Packet outp(id.address(),RR->identity.address(),Packet::VERB_ERROR);
outp.append((unsigned char)Packet::VERB_HELLO);
outp.append((uint64_t)pid);
outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION);
outp.armor(key,true);
RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size());
} else {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
}
} else {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
TRACE("rejected HELLO from %s(%s): key agreement failed",id.address().toString().c_str(),_remoteAddress.toString().c_str());
}
return true;
} else {
TRACE("rejected HELLO from %s(%s): key agreement failed",id.address().toString().c_str(),_remoteAddress.toString().c_str());
// Identity is the same as the one we already have -- check packet integrity
if (!dearmor(peer->key())) {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
return true;
}
// Continue at // VALID
}
return true;
} else {
// Identity is the same as the one we already have -- check packet integrity
// We don't already have an identity with this address -- validate and learn it
if (!dearmor(peer->key())) {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
// Check identity proof of work
if (!id.locallyValidate()) {
TRACE("dropped HELLO from %s(%s): identity invalid",id.address().toString().c_str(),_remoteAddress.toString().c_str());
return true;
}
// Check packet integrity and authentication
SharedPtr<Peer> newPeer(new Peer(RR->identity,id));
if (!dearmor(newPeer->key())) {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
return true;
}
peer = RR->topology->addPeer(newPeer);
// Continue at // VALID
}
} else {
// We don't already have an identity with this address -- validate and learn it
// Check identity proof of work
if (!id.locallyValidate()) {
TRACE("dropped HELLO from %s(%s): identity invalid",id.address().toString().c_str(),_remoteAddress.toString().c_str());
return true;
}
// Check packet integrity and authentication
SharedPtr<Peer> newPeer(new Peer(RR->identity,id));
if (!dearmor(newPeer->key())) {
TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_remoteAddress.toString().c_str());
return true;
}
peer = RR->topology->addPeer(newPeer);
// Continue at // VALID
// VALID -- if we made it here, packet passed identity and authenticity checks!
}
// VALID -- if we made it here, packet passed identity and authenticity checks!
if (externalSurfaceAddress)
RR->sa->iam(id.address(),_remoteAddress,externalSurfaceAddress,RR->topology->isRoot(id),RR->node->now());

View File

@ -93,14 +93,21 @@ public:
* about whether the packet was valid. A rejection is 'complete.'
*
* Once true is returned, this must not be called again. The packet's state
* may no longer be valid.
* may no longer be valid. The only exception is deferred decoding. In this
* case true is returned to indicate to the normal decode path that it is
* finished with the packet. The packet will have added itself to the
* deferred queue and will expect tryDecode() to be called one more time
* with deferred set to true.
*
* Deferred decoding is performed by DeferredPackets.cpp and should not be
* done elsewhere. Under deferred decoding packets only get one shot and
* so the return value of tryDecode() is ignored.
*
* @param RR Runtime environment
* @param deferred If true, this is a deferred decode and the return is ignored
* @return True if decoding and processing is complete, false if caller should try again
* @throws std::out_of_range Range error processing packet (should be discarded)
* @throws std::runtime_error Other error processing packet (should be discarded)
*/
bool tryDecode(const RuntimeEnvironment *RR);
bool tryDecode(const RuntimeEnvironment *RR,bool deferred);
/**
* @return Time of packet receipt / start of decode
@ -132,7 +139,7 @@ private:
// These are called internally to handle packet contents once it has
// been authenticated, decrypted, decompressed, and classified.
bool _doERROR(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doHELLO(const RuntimeEnvironment *RR);
bool _doHELLO(const RuntimeEnvironment *RR,SharedPtr<Peer> &peer); // can be called with NULL peer, while all others cannot
bool _doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doWHOIS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);
bool _doRENDEZVOUS(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer);

View File

@ -47,6 +47,7 @@
#include "Identity.hpp"
#include "SelfAwareness.hpp"
#include "Cluster.hpp"
#include "DeferredPackets.hpp"
const struct sockaddr_storage ZT_SOCKADDR_NULL = {0};
@ -130,7 +131,14 @@ Node::Node(
Node::~Node()
{
Mutex::Lock _l(_networks_m);
_networks.clear(); // ensure that networks are destroyed before shutdown
Mutex::Lock _l2(RR->dpSetLock);
_networks.clear(); // ensure that networks are destroyed before shutdow
DeferredPackets *dp = RR->dp;
RR->dp = (DeferredPackets *)0;
delete dp;
delete RR->sa;
delete RR->topology;
delete RR->antiRec;
@ -637,6 +645,27 @@ void Node::clusterStatus(ZT_ClusterStatus *cs)
memset(cs,0,sizeof(ZT_ClusterStatus));
}
void Node::backgroundThreadMain()
{
RR->dpSetLock.lock();
if (!RR->dp) {
try {
RR->dp = new DeferredPackets(RR);
} catch ( ... ) { // sanity check -- could only really happen if out of memory
RR->dpSetLock.unlock();
return;
}
}
RR->dpSetLock.unlock();
for(;;) {
try {
if (RR->dp->process() < 0)
break;
} catch ( ... ) {} // sanity check -- should not throw
}
}
/****************************************************************************/
/* Node methods used only within node/ */
/****************************************************************************/
@ -978,6 +1007,13 @@ void ZT_Node_clusterStatus(ZT_Node *node,ZT_ClusterStatus *cs)
} catch ( ... ) {}
}
void ZT_Node_backgroundThreadMain(ZT_Node *node)
{
try {
reinterpret_cast<ZeroTier::Node *>(node)->backgroundThreadMain();
} catch ( ... ) {}
}
void ZT_version(int *major,int *minor,int *revision,unsigned long *featureFlags)
{
if (major) *major = ZEROTIER_ONE_VERSION_MAJOR;

View File

@ -125,6 +125,7 @@ public:
void clusterRemoveMember(unsigned int memberId);
void clusterHandleIncomingMessage(const void *msg,unsigned int len);
void clusterStatus(ZT_ClusterStatus *cs);
void backgroundThreadMain();
// Internal functions ------------------------------------------------------

View File

@ -32,6 +32,7 @@
#include "Constants.hpp"
#include "Identity.hpp"
#include "Mutex.hpp"
namespace ZeroTier {
@ -44,6 +45,7 @@ class AntiRecursion;
class NetworkController;
class SelfAwareness;
class Cluster;
class DeferredPackets;
/**
* Holds global state for an instance of ZeroTier::Node
@ -55,6 +57,7 @@ public:
node(n)
,identity()
,localNetworkController((NetworkController *)0)
,dp((DeferredPackets *)0)
,sw((Switch *)0)
,mc((Multicaster *)0)
,antiRec((AntiRecursion *)0)
@ -77,6 +80,10 @@ public:
// This is set externally to an instance of this base class
NetworkController *localNetworkController;
// This is created if background threads call Node::backgroundThreadMain().
DeferredPackets *volatile dp; // can be read without lock but not written
Mutex dpSetLock;
/*
* Order matters a bit here. These are constructed in this order
* and then deleted in the opposite order on Node exit. The order ensures

View File

@ -64,20 +64,6 @@ public:
++obj->__refCount;
}
SharedPtr(T *obj,bool runAwayFromZombies)
throw() :
_ptr(obj)
{
// HACK: this is used in "handlers" to take ownership of naked pointers,
// an ugly pattern that really ought to be factored out.
if (runAwayFromZombies) {
if ((int)(++obj->__refCount) < 2) {
--obj->__refCount;
_ptr = (T *)0;
}
} else ++obj->__refCount;
}
SharedPtr(const SharedPtr &sp)
throw() :
_ptr(sp._getAndInc())
@ -105,6 +91,25 @@ public:
return *this;
}
/**
* Set to a naked pointer and increment its reference count
*
* This assumes this SharedPtr is NULL and that ptr is not a 'zombie.' No
* checks are performed.
*
* @param ptr Naked pointer to assign
*/
inline void setToUnsafe(T *ptr)
{
++ptr->__refCount;
_ptr = ptr;
}
/**
* Swap with another pointer 'for free' without ref count overhead
*
* @param with Pointer to swap with
*/
inline void swap(SharedPtr &with)
throw()
{

View File

@ -475,7 +475,7 @@ void Switch::doAnythingWaitingForPeer(const SharedPtr<Peer> &peer)
{ // finish processing any packets waiting on peer's public key / identity
Mutex::Lock _l(_rxQueue_m);
for(std::list< SharedPtr<IncomingPacket> >::iterator rxi(_rxQueue.begin());rxi!=_rxQueue.end();) {
if ((*rxi)->tryDecode(RR))
if ((*rxi)->tryDecode(RR,false))
_rxQueue.erase(rxi++);
else ++rxi;
}
@ -672,7 +672,7 @@ void Switch::_handleRemotePacketFragment(const InetAddress &localAddr,const Inet
packet->append(dq.frags[f - 1].payload(),dq.frags[f - 1].payloadLength());
_defragQueue.erase(pid); // dq no longer valid after this
if (!packet->tryDecode(RR)) {
if (!packet->tryDecode(RR,false)) {
Mutex::Lock _l(_rxQueue_m);
_rxQueue.push_back(packet);
}
@ -746,7 +746,7 @@ void Switch::_handleRemotePacketHead(const InetAddress &localAddr,const InetAddr
packet->append(dq.frags[f - 1].payload(),dq.frags[f - 1].payloadLength());
_defragQueue.erase(pid); // dq no longer valid after this
if (!packet->tryDecode(RR)) {
if (!packet->tryDecode(RR,false)) {
Mutex::Lock _l(_rxQueue_m);
_rxQueue.push_back(packet);
}
@ -757,7 +757,7 @@ void Switch::_handleRemotePacketHead(const InetAddress &localAddr,const InetAddr
} // else this is a duplicate head, ignore
} else {
// Packet is unfragmented, so just process it
if (!packet->tryDecode(RR)) {
if (!packet->tryDecode(RR,false)) {
Mutex::Lock _l(_rxQueue_m);
_rxQueue.push_back(packet);
}

View File

@ -5,6 +5,7 @@ OBJS=\
node/C25519.o \
node/CertificateOfMembership.o \
node/Cluster.o \
node/DeferredPackets.o \
node/Dictionary.o \
node/Identity.o \
node/IncomingPacket.o \