From cdc99bfee10ac58a8dab1aabcb85e69f3862b7ad Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 27 Oct 2015 18:18:26 -0700 Subject: [PATCH] Add a circuit breaker for VERB_PUSH_DIRECT_PATHS. --- node/Constants.hpp | 26 ++++++++++++++++++++------ node/IncomingPacket.cpp | 5 +++++ node/Peer.cpp | 2 ++ node/Peer.hpp | 31 +++++++++++++++++++++++++++++-- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/node/Constants.hpp b/node/Constants.hpp index 4b06db449..53cc64c7e 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -319,11 +319,6 @@ */ #define ZT_MIN_PATH_CONFIRMATION_INTERVAL 1000 -/** - * Interval between direct path pushes in milliseconds - */ -#define ZT_DIRECT_PATH_PUSH_INTERVAL 120000 - /** * How long (max) to remember network certificates of membership? * @@ -347,10 +342,29 @@ */ #define ZT_MAX_BRIDGE_SPAM 16 +/** + * Interval between direct path pushes in milliseconds + */ +#define ZT_DIRECT_PATH_PUSH_INTERVAL 120000 + /** * Maximum number of endpoints to contact per address type (to limit pushes like GitHub issue #235) */ -#define ZT_PUSH_DIRECT_PATHS_MAX_ENDPOINTS_PER_TYPE 4 +#define ZT_PUSH_DIRECT_PATHS_MAX_ENDPOINTS_PER_TYPE 5 + +/** + * Time horizon for push direct paths cutoff + */ +#define ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME 60000 + +/** + * Maximum number of direct path pushes within cutoff time + * + * This limits response to PUSH_DIRECT_PATHS to CUTOFF_LIMIT responses + * per CUTOFF_TIME milliseconds per peer to prevent this from being + * useful for DOS amplification attacks. + */ +#define ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT 5 /** * A test pseudo-network-ID that can be joined diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index b1a9af3b0..e985b34c4 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -901,6 +901,11 @@ bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,const Sha { try { const uint64_t now = RR->node->now(); + if (!peer->shouldRespondToDirectPathPush(now)) { + TRACE("dropped PUSH_DIRECT_PATHS from %s(%s): circuit breaker tripped",source().toString().c_str(),_remoteAddress.toString().c_str()); + return true; + } + const Path *currentBest = peer->getBestPath(now); unsigned int count = at(ZT_PACKET_IDX_PAYLOAD); diff --git a/node/Peer.cpp b/node/Peer.cpp index 99eb32c71..976c7c449 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -55,6 +55,7 @@ Peer::Peer(const Identity &myIdentity,const Identity &peerIdentity) _lastAnnouncedTo(0), _lastPathConfirmationSent(0), _lastDirectPathPushSent(0), + _lastDirectPathPushReceive(0), _lastPathSort(0), _vProto(0), _vMajor(0), @@ -63,6 +64,7 @@ Peer::Peer(const Identity &myIdentity,const Identity &peerIdentity) _id(peerIdentity), _numPaths(0), _latency(0), + _directPathPushCutoffCount(0), _networkComs(4), _lastPushedComs(4) { diff --git a/node/Peer.hpp b/node/Peer.hpp index 69343f20c..d9ef5fcba 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -431,6 +431,27 @@ public: _numPaths = y; } + /** + * Update direct path push stats and return true if we should respond + * + * This is a circuit breaker to make VERB_PUSH_DIRECT_PATHS not particularly + * useful as a DDOS amplification attack vector. Otherwise a malicious peer + * could send loads of these and cause others to bombard arbitrary IPs with + * traffic. + * + * @param now Current time + * @return True if we should respond + */ + inline bool shouldRespondToDirectPathPush(const uint64_t now) + { + Mutex::Lock _l(_lock); + if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) + ++_directPathPushCutoffCount; + else _directPathPushCutoffCount = 0; + _lastDirectPathPushReceive = now; + return (_directPathPushCutoffCount >= ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT); + } + /** * Find a common set of addresses by which two peers can link, if any * @@ -459,7 +480,7 @@ public: const unsigned int recSizePos = b.size(); b.addSize(4); // space for uint32_t field length - b.append((uint16_t)0); // version of serialized Peer data + b.append((uint16_t)1); // version of serialized Peer data _id.serialize(b,false); @@ -470,12 +491,14 @@ public: b.append((uint64_t)_lastAnnouncedTo); b.append((uint64_t)_lastPathConfirmationSent); b.append((uint64_t)_lastDirectPathPushSent); + b.append((uint64_t)_lastDirectPathPushReceive); b.append((uint64_t)_lastPathSort); b.append((uint16_t)_vProto); b.append((uint16_t)_vMajor); b.append((uint16_t)_vMinor); b.append((uint16_t)_vRevision); b.append((uint32_t)_latency); + b.append((uint16_t)_directPathPushCutoffCount); b.append((uint16_t)_numPaths); for(unsigned int i=0;i<_numPaths;++i) @@ -521,7 +544,7 @@ public: const unsigned int recSize = b.template at(p); p += 4; if ((p + recSize) > b.size()) return SharedPtr(); // size invalid - if (b.template at(p) != 0) + if (b.template at(p) != 1) return SharedPtr(); // version mismatch p += 2; @@ -539,12 +562,14 @@ public: np->_lastAnnouncedTo = b.template at(p); p += 8; np->_lastPathConfirmationSent = b.template at(p); p += 8; np->_lastDirectPathPushSent = b.template at(p); p += 8; + np->_lastDirectPathPushReceive = b.template at(p); p += 8; np->_lastPathSort = b.template at(p); p += 8; np->_vProto = b.template at(p); p += 2; np->_vMajor = b.template at(p); p += 2; np->_vMinor = b.template at(p); p += 2; np->_vRevision = b.template at(p); p += 2; np->_latency = b.template at(p); p += 4; + np->_directPathPushCutoffCount = b.template at(p); p += 2; const unsigned int numPaths = b.template at(p); p += 2; for(unsigned int i=0;i