Auto merge of #1258 - ThisIsNotOfficialCodeItsJustForks:t1251-upstream-anti-dos, r=daira
Pull in some DoS mitigations from upstream Closes #1251. **WARNING: I force pushed**
This commit is contained in:
commit
af9898eee5
|
@ -338,7 +338,6 @@ std::string HelpMessage(HelpMessageMode mode)
|
|||
strUsage += HelpMessageOpt("-whitelist=<netmask>", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") +
|
||||
" " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway"));
|
||||
|
||||
|
||||
#ifdef ENABLE_WALLET
|
||||
strUsage += HelpMessageGroup(_("Wallet options:"));
|
||||
strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
|
||||
|
|
|
@ -4937,6 +4937,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
|||
if (pingUsecTime > 0) {
|
||||
// Successful ping time measurement, replace previous
|
||||
pfrom->nPingUsecTime = pingUsecTime;
|
||||
pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime, pingUsecTime);
|
||||
} else {
|
||||
// This should never happen
|
||||
sProblem = "Timing mishap";
|
||||
|
|
286
src/net.cpp
286
src/net.cpp
|
@ -671,6 +671,231 @@ void SocketSendData(CNode *pnode)
|
|||
|
||||
static list<CNode*> vNodesDisconnected;
|
||||
|
||||
class CNodeRef {
|
||||
public:
|
||||
CNodeRef(CNode *pnode) : _pnode(pnode) {
|
||||
LOCK(cs_vNodes);
|
||||
_pnode->AddRef();
|
||||
}
|
||||
|
||||
~CNodeRef() {
|
||||
LOCK(cs_vNodes);
|
||||
_pnode->Release();
|
||||
}
|
||||
|
||||
CNode& operator *() const {return *_pnode;};
|
||||
CNode* operator ->() const {return _pnode;};
|
||||
|
||||
CNodeRef& operator =(const CNodeRef& other)
|
||||
{
|
||||
if (this != &other) {
|
||||
LOCK(cs_vNodes);
|
||||
|
||||
_pnode->Release();
|
||||
_pnode = other._pnode;
|
||||
_pnode->AddRef();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CNodeRef(const CNodeRef& other):
|
||||
_pnode(other._pnode)
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
_pnode->AddRef();
|
||||
}
|
||||
private:
|
||||
CNode *_pnode;
|
||||
};
|
||||
|
||||
static bool ReverseCompareNodeMinPingTime(const CNodeRef &a, const CNodeRef &b)
|
||||
{
|
||||
return a->nMinPingUsecTime > b->nMinPingUsecTime;
|
||||
}
|
||||
|
||||
static bool ReverseCompareNodeTimeConnected(const CNodeRef &a, const CNodeRef &b)
|
||||
{
|
||||
return a->nTimeConnected > b->nTimeConnected;
|
||||
}
|
||||
|
||||
class CompareNetGroupKeyed
|
||||
{
|
||||
std::vector<unsigned char> vchSecretKey;
|
||||
public:
|
||||
CompareNetGroupKeyed()
|
||||
{
|
||||
vchSecretKey.resize(32, 0);
|
||||
GetRandBytes(vchSecretKey.data(), vchSecretKey.size());
|
||||
}
|
||||
|
||||
bool operator()(const CNodeRef &a, const CNodeRef &b)
|
||||
{
|
||||
std::vector<unsigned char> vchGroupA, vchGroupB;
|
||||
CSHA256 hashA, hashB;
|
||||
std::vector<unsigned char> vchA(32), vchB(32);
|
||||
|
||||
vchGroupA = a->addr.GetGroup();
|
||||
vchGroupB = b->addr.GetGroup();
|
||||
|
||||
hashA.Write(begin_ptr(vchGroupA), vchGroupA.size());
|
||||
hashB.Write(begin_ptr(vchGroupB), vchGroupB.size());
|
||||
|
||||
hashA.Write(begin_ptr(vchSecretKey), vchSecretKey.size());
|
||||
hashB.Write(begin_ptr(vchSecretKey), vchSecretKey.size());
|
||||
|
||||
hashA.Finalize(begin_ptr(vchA));
|
||||
hashB.Finalize(begin_ptr(vchB));
|
||||
|
||||
return vchA < vchB;
|
||||
}
|
||||
};
|
||||
|
||||
static bool AttemptToEvictConnection(bool fPreferNewConnection) {
|
||||
std::vector<CNodeRef> vEvictionCandidates;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
|
||||
BOOST_FOREACH(CNode *node, vNodes) {
|
||||
if (node->fWhitelisted)
|
||||
continue;
|
||||
if (!node->fInbound)
|
||||
continue;
|
||||
if (node->fDisconnect)
|
||||
continue;
|
||||
if (node->addr.IsLocal())
|
||||
continue;
|
||||
vEvictionCandidates.push_back(CNodeRef(node));
|
||||
}
|
||||
}
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
// Protect connections with certain characteristics
|
||||
|
||||
// Deterministically select 4 peers to protect by netgroup.
|
||||
// An attacker cannot predict which netgroups will be protected.
|
||||
static CompareNetGroupKeyed comparerNetGroupKeyed;
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), comparerNetGroupKeyed);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
// Protect the 8 nodes with the best ping times.
|
||||
// An attacker cannot manipulate this metric without physically moving nodes closer to the target.
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeMinPingTime);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(8, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
// Protect the half of the remaining nodes which have been connected the longest.
|
||||
// This replicates the existing implicit behavior.
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - static_cast<int>(vEvictionCandidates.size() / 2), vEvictionCandidates.end());
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
// Identify the network group with the most connections
|
||||
std::vector<unsigned char> naMostConnections;
|
||||
unsigned int nMostConnections = 0;
|
||||
std::map<std::vector<unsigned char>, std::vector<CNodeRef> > mapAddrCounts;
|
||||
BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) {
|
||||
mapAddrCounts[node->addr.GetGroup()].push_back(node);
|
||||
|
||||
if (mapAddrCounts[node->addr.GetGroup()].size() > nMostConnections) {
|
||||
nMostConnections = mapAddrCounts[node->addr.GetGroup()].size();
|
||||
naMostConnections = node->addr.GetGroup();
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce to the network group with the most connections
|
||||
vEvictionCandidates = mapAddrCounts[naMostConnections];
|
||||
|
||||
// Do not disconnect peers if there is only 1 connection from their network group
|
||||
if (vEvictionCandidates.size() <= 1)
|
||||
// unless we prefer the new connection (for whitelisted peers)
|
||||
if (!fPreferNewConnection)
|
||||
return false;
|
||||
|
||||
// Disconnect the most recent connection from the network group with the most connections
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
|
||||
vEvictionCandidates[0]->fDisconnect = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void AcceptConnection(const ListenSocket& hListenSocket) {
|
||||
struct sockaddr_storage sockaddr;
|
||||
socklen_t len = sizeof(sockaddr);
|
||||
SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
|
||||
CAddress addr;
|
||||
int nInbound = 0;
|
||||
int nMaxInbound = nMaxConnections - MAX_OUTBOUND_CONNECTIONS;
|
||||
|
||||
if (hSocket != INVALID_SOCKET)
|
||||
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr))
|
||||
LogPrintf("Warning: Unknown socket family\n");
|
||||
|
||||
bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr);
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
if (pnode->fInbound)
|
||||
nInbound++;
|
||||
}
|
||||
|
||||
if (hSocket == INVALID_SOCKET)
|
||||
{
|
||||
int nErr = WSAGetLastError();
|
||||
if (nErr != WSAEWOULDBLOCK)
|
||||
LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsSelectableSocket(hSocket))
|
||||
{
|
||||
LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString());
|
||||
CloseSocket(hSocket);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CNode::IsBanned(addr) && !whitelisted)
|
||||
{
|
||||
LogPrintf("connection from %s dropped (banned)\n", addr.ToString());
|
||||
CloseSocket(hSocket);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nInbound >= nMaxInbound)
|
||||
{
|
||||
if (!AttemptToEvictConnection(whitelisted)) {
|
||||
// No connection to evict, disconnect the new connection
|
||||
LogPrint("net", "failed to find an eviction candidate - connection dropped (full)\n");
|
||||
CloseSocket(hSocket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// According to the internet TCP_NODELAY is not carried into accepted sockets
|
||||
// on all platforms. Set it again here just to be sure.
|
||||
int set = 1;
|
||||
#ifdef WIN32
|
||||
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&set, sizeof(int));
|
||||
#else
|
||||
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (void*)&set, sizeof(int));
|
||||
#endif
|
||||
|
||||
CNode* pnode = new CNode(hSocket, addr, "", true);
|
||||
pnode->AddRef();
|
||||
pnode->fWhitelisted = whitelisted;
|
||||
|
||||
LogPrint("net", "connection from %s accepted\n", addr.ToString());
|
||||
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
vNodes.push_back(pnode);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadSocketHandler()
|
||||
{
|
||||
unsigned int nPrevNodeCount = 0;
|
||||
|
@ -828,65 +1053,7 @@ void ThreadSocketHandler()
|
|||
{
|
||||
if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv))
|
||||
{
|
||||
struct sockaddr_storage sockaddr;
|
||||
socklen_t len = sizeof(sockaddr);
|
||||
SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
|
||||
CAddress addr;
|
||||
int nInbound = 0;
|
||||
|
||||
if (hSocket != INVALID_SOCKET)
|
||||
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr))
|
||||
LogPrintf("Warning: Unknown socket family\n");
|
||||
|
||||
bool whitelisted = hListenSocket.whitelisted || CNode::IsWhitelistedRange(addr);
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
BOOST_FOREACH(CNode* pnode, vNodes)
|
||||
if (pnode->fInbound)
|
||||
nInbound++;
|
||||
}
|
||||
|
||||
if (hSocket == INVALID_SOCKET)
|
||||
{
|
||||
int nErr = WSAGetLastError();
|
||||
if (nErr != WSAEWOULDBLOCK)
|
||||
LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr));
|
||||
}
|
||||
else if (!IsSelectableSocket(hSocket))
|
||||
{
|
||||
LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString());
|
||||
CloseSocket(hSocket);
|
||||
}
|
||||
else if (nInbound >= nMaxConnections - MAX_OUTBOUND_CONNECTIONS)
|
||||
{
|
||||
LogPrint("net", "connection from %s dropped (full)\n", addr.ToString());
|
||||
CloseSocket(hSocket);
|
||||
}
|
||||
else if (CNode::IsBanned(addr) && !whitelisted)
|
||||
{
|
||||
LogPrintf("connection from %s dropped (banned)\n", addr.ToString());
|
||||
CloseSocket(hSocket);
|
||||
}
|
||||
else
|
||||
{
|
||||
// According to the internet TCP_NODELAY is not carried into accepted sockets
|
||||
// on all platforms. Set it again here just to be sure.
|
||||
int set = 1;
|
||||
#ifdef WIN32
|
||||
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&set, sizeof(int));
|
||||
#else
|
||||
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (void*)&set, sizeof(int));
|
||||
#endif
|
||||
|
||||
CNode* pnode = new CNode(hSocket, addr, "", true);
|
||||
pnode->AddRef();
|
||||
pnode->fWhitelisted = whitelisted;
|
||||
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
vNodes.push_back(pnode);
|
||||
}
|
||||
}
|
||||
AcceptConnection(hListenSocket);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1980,6 +2147,7 @@ CNode::CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn, bool fIn
|
|||
nPingUsecStart = 0;
|
||||
nPingUsecTime = 0;
|
||||
fPingQueued = false;
|
||||
nMinPingUsecTime = std::numeric_limits<int64_t>::max();
|
||||
|
||||
{
|
||||
LOCK(cs_nLastNodeId);
|
||||
|
|
|
@ -140,6 +140,7 @@ extern bool fListen;
|
|||
extern uint64_t nLocalServices;
|
||||
extern uint64_t nLocalHostNonce;
|
||||
extern CAddrMan addrman;
|
||||
/** Maximum number of connections to simultaneously allow (aka connection slots) */
|
||||
extern int nMaxConnections;
|
||||
|
||||
extern std::vector<CNode*> vNodes;
|
||||
|
@ -318,6 +319,8 @@ public:
|
|||
int64_t nPingUsecStart;
|
||||
// Last measured round-trip time.
|
||||
int64_t nPingUsecTime;
|
||||
// Best measured round-trip time.
|
||||
int64_t nMinPingUsecTime;
|
||||
// Whether a ping is requested.
|
||||
bool fPingQueued;
|
||||
|
||||
|
|
Loading…
Reference in New Issue