upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbitflash <admin@bitflash.network>2026-08-18 10:04:28 -0300
committerbitflash <admin@bitflash.network>2026-08-18 10:04:28 -0300
commitdb84acfc5f99ac1ddf4962879b4cbaf1af9952dd (patch)
treeca7e4c67216880361dfd484c27fbf7b8c9d2d269
parenta6eb8dab830be3135c054ae3ae442886f1867b4e (diff)
net: cap inbound connections per address
A single IP could hold every inbound slot up to MAX_CONNECTIONS -- which is what a connection-exhaustion flood did to the rendezvous relays, one address parking hundreds of sockets and starving real peers. Add MAX_CONNECTIONS_PER_IP (32): the accept path counts existing connections from the same address and refuses a new one past the cap. The relays' firewall connlimit does the same a layer lower; this makes every build carry the defense, including future poll()-based relays with no FD ceiling.
-rw-r--r--src/net.cpp17
-rw-r--r--src/net.h9
2 files changed, 26 insertions, 0 deletions
diff --git a/src/net.cpp b/src/net.cpp
index 3660735..6e74cbb 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1622,12 +1622,29 @@ void ThreadSocketHandler2(void* parg)
1622 unsigned int nNodes = 0; 1622 unsigned int nNodes = 0;
1623 CRITICAL_BLOCK(cs_vNodes) 1623 CRITICAL_BLOCK(cs_vNodes)
1624 nNodes = (unsigned int)vNodes.size(); 1624 nNodes = (unsigned int)vNodes.size();
1625 // Also count how many connections this one address already
1626 // holds. A single IP that keeps opening sockets -- a broken
1627 // node or a deliberate connection-exhaustion flood -- must not
1628 // be able to take the whole table, which is what starved the
1629 // relays.
1630 unsigned int nFromThisIP = 0;
1631 CRITICAL_BLOCK(cs_vNodes)
1632 for (vector<CNode*>::iterator it = vNodes.begin(); it != vNodes.end(); ++it)
1633 if ((*it)->addr.ip == addr.ip)
1634 nFromThisIP++;
1635
1625 if (nNodes >= MAX_CONNECTIONS) 1636 if (nNodes >= MAX_CONNECTIONS)
1626 { 1637 {
1627 LogPrint("net", "refusing connection from %s, already at %u\n", 1638 LogPrint("net", "refusing connection from %s, already at %u\n",
1628 addr.ToString().c_str(), nNodes); 1639 addr.ToString().c_str(), nNodes);
1629 BtfCloseSocket(hSocket); 1640 BtfCloseSocket(hSocket);
1630 } 1641 }
1642 else if (nFromThisIP >= MAX_CONNECTIONS_PER_IP)
1643 {
1644 LogPrint("net", "refusing connection from %s, already %u from that address\n",
1645 addr.ToString().c_str(), nFromThisIP);
1646 BtfCloseSocket(hSocket);
1647 }
1631 else 1648 else
1632 { 1649 {
1633 LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str()); 1650 LogPrint("net", "accepted connection from %s\n", addr.ToString().c_str());
diff --git a/src/net.h b/src/net.h
index 984588b..4fdd1b3 100644
--- a/src/net.h
+++ b/src/net.h
@@ -112,6 +112,15 @@ static const int BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS = 2 * 60;
112// 125 is Bitcoin's number and remains a conservative public-node default. 112// 125 is Bitcoin's number and remains a conservative public-node default.
113static const unsigned int MAX_CONNECTIONS = 125; 113static const unsigned int MAX_CONNECTIONS = 125;
114 114
115// Cap inbound connections from any single address. Without it one source can
116// hold every slot up to MAX_CONNECTIONS -- exactly what happened to the
117// rendezvous relays, where a single IP parked hundreds of connections and
118// starved real peers. A legitimate peer, even a relay talking to a relay,
119// needs only a handful; 32 leaves generous room while denying any one address
120// more than a quarter of the table. The firewall connlimit on the relays does
121// the same thing a layer lower; this makes every build carry the defense.
122static const unsigned int MAX_CONNECTIONS_PER_IP = 32;
123
115// A node marked for disconnect is normally held until its buffers drain, so 124// A node marked for disconnect is normally held until its buffers drain, so
116// a last message still goes out. But vSend cannot drain through a socket 125// a last message still goes out. But vSend cannot drain through a socket
117// whose far end is gone, and that made such a node immortal: it stayed in 126// whose far end is gone, and that made such a node immortal: it stayed in