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
|
@ -337,7 +337,6 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||||
strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6"));
|
strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6"));
|
||||||
strUsage += HelpMessageOpt("-whitelist=<netmask>", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") +
|
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"));
|
" " + _("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
|
#ifdef ENABLE_WALLET
|
||||||
strUsage += HelpMessageGroup(_("Wallet options:"));
|
strUsage += HelpMessageGroup(_("Wallet options:"));
|
||||||
|
|
|
@ -4937,6 +4937,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
||||||
if (pingUsecTime > 0) {
|
if (pingUsecTime > 0) {
|
||||||
// Successful ping time measurement, replace previous
|
// Successful ping time measurement, replace previous
|
||||||
pfrom->nPingUsecTime = pingUsecTime;
|
pfrom->nPingUsecTime = pingUsecTime;
|
||||||
|
pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime, pingUsecTime);
|
||||||
} else {
|
} else {
|
||||||
// This should never happen
|
// This should never happen
|
||||||
sProblem = "Timing mishap";
|
sProblem = "Timing mishap";
|
||||||
|
|
286
src/net.cpp
286
src/net.cpp
|
@ -671,6 +671,231 @@ void SocketSendData(CNode *pnode)
|
||||||
|
|
||||||
static list<CNode*> vNodesDisconnected;
|
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()
|
void ThreadSocketHandler()
|
||||||
{
|
{
|
||||||
unsigned int nPrevNodeCount = 0;
|
unsigned int nPrevNodeCount = 0;
|
||||||
|
@ -828,65 +1053,7 @@ void ThreadSocketHandler()
|
||||||
{
|
{
|
||||||
if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv))
|
if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv))
|
||||||
{
|
{
|
||||||
struct sockaddr_storage sockaddr;
|
AcceptConnection(hListenSocket);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1980,6 +2147,7 @@ CNode::CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn, bool fIn
|
||||||
nPingUsecStart = 0;
|
nPingUsecStart = 0;
|
||||||
nPingUsecTime = 0;
|
nPingUsecTime = 0;
|
||||||
fPingQueued = false;
|
fPingQueued = false;
|
||||||
|
nMinPingUsecTime = std::numeric_limits<int64_t>::max();
|
||||||
|
|
||||||
{
|
{
|
||||||
LOCK(cs_nLastNodeId);
|
LOCK(cs_nLastNodeId);
|
||||||
|
|
|
@ -140,6 +140,7 @@ extern bool fListen;
|
||||||
extern uint64_t nLocalServices;
|
extern uint64_t nLocalServices;
|
||||||
extern uint64_t nLocalHostNonce;
|
extern uint64_t nLocalHostNonce;
|
||||||
extern CAddrMan addrman;
|
extern CAddrMan addrman;
|
||||||
|
/** Maximum number of connections to simultaneously allow (aka connection slots) */
|
||||||
extern int nMaxConnections;
|
extern int nMaxConnections;
|
||||||
|
|
||||||
extern std::vector<CNode*> vNodes;
|
extern std::vector<CNode*> vNodes;
|
||||||
|
@ -318,6 +319,8 @@ public:
|
||||||
int64_t nPingUsecStart;
|
int64_t nPingUsecStart;
|
||||||
// Last measured round-trip time.
|
// Last measured round-trip time.
|
||||||
int64_t nPingUsecTime;
|
int64_t nPingUsecTime;
|
||||||
|
// Best measured round-trip time.
|
||||||
|
int64_t nMinPingUsecTime;
|
||||||
// Whether a ping is requested.
|
// Whether a ping is requested.
|
||||||
bool fPingQueued;
|
bool fPingQueued;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue