net: only allow 8 simultaneous forced inbound connections

Github-Pull: #27600
Rebased-From: 75868022a904c1f77871abf962bf9b88a9c5faf6
This commit is contained in:
Matthew Zipkin 2023-07-28 14:47:27 -04:00 committed by Luke Dashjr
parent 0c50a71fe9
commit ae08cb4b89
3 changed files with 36 additions and 10 deletions

View File

@ -989,6 +989,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
const CAddress& addr)
{
int nInbound = 0;
int nForced{0};
int nMaxInbound = nMaxConnections - m_max_outbound;
AddWhitelistPermissionFlags(permission_flags, addr, vWhitelistedRange);
@ -998,6 +999,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
LOCK(m_nodes_mutex);
for (const CNode* pnode : m_nodes) {
if (pnode->IsInboundConn()) nInbound++;
if (pnode->m_forced_inbound) nForced++;
}
}
@ -1035,8 +1037,15 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
return;
}
bool forced{false};
if (nInbound >= nMaxInbound)
{
// Protect from force evicting everyone
if (nForced >= MAX_FORCED_INBOUND_CONNECTIONS) {
LogPrint(BCLog::NET, "connection from %s dropped (too many forced inbound)\n", addr.ToStringAddrPort());
return;
}
// If the inbound connection attempt is granted ForceInbound permission, try a little harder
// to make room by evicting a peer we may not have otherwise evicted.
if (!AttemptToEvictConnection(NetPermissions::HasFlag(permission_flags, NetPermissionFlags::ForceInbound))) {
@ -1044,6 +1053,9 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
LogPrint(BCLog::NET, "failed to find an eviction candidate - connection dropped (full)\n");
return;
}
// We kicked someone out
forced = true;
}
NodeId id = GetNewNodeId();
@ -1064,6 +1076,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
CNodeOptions{
.permission_flags = permission_flags,
.prefer_evict = discouraged,
.forced_inbound = forced,
.recv_flood_size = nReceiveFloodSize,
});
pnode->AddRef();
@ -2802,6 +2815,7 @@ CNode::CNode(NodeId idIn,
m_addr_name{addrNameIn.empty() ? addr.ToStringAddrPort() : addrNameIn},
m_inbound_onion{inbound_onion},
m_prefer_evict{node_opts.prefer_evict},
m_forced_inbound{node_opts.forced_inbound},
nKeyedNetGroup{nKeyedNetGroupIn},
m_conn_type{conn_type_in},
id{idIn},

View File

@ -84,6 +84,8 @@ static const bool DEFAULT_BLOCKSONLY = false;
static const int64_t DEFAULT_PEER_CONNECT_TIMEOUT = 60;
/** Number of file descriptors required for message capture **/
static const int NUM_FDS_MESSAGE_CAPTURE = 1;
/** Maximum number of forced inbound connections **/
static const int MAX_FORCED_INBOUND_CONNECTIONS{8};
static constexpr bool DEFAULT_FORCEDNSSEED{false};
static constexpr bool DEFAULT_DNSSEED{true};
@ -350,6 +352,8 @@ struct CNodeOptions
NetPermissionFlags permission_flags = NetPermissionFlags::None;
std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr;
bool prefer_evict = false;
// True if ForceInbound connection required evicting a peer
bool forced_inbound{false};
size_t recv_flood_size{DEFAULT_MAXRECEIVEBUFFER * 1000};
};
@ -404,6 +408,7 @@ public:
*/
std::string cleanSubVer GUARDED_BY(m_subver_mutex){};
const bool m_prefer_evict{false}; // This peer is preferred for eviction.
const bool m_forced_inbound{false}; // This peer forced an inbound connection
bool HasPermission(NetPermissionFlags permission) const {
return NetPermissions::HasFlag(m_permission_flags, permission);
}

View File

@ -123,16 +123,19 @@ class P2PEvict(BitcoinTestFramework):
assert evicted_peers[0] not in protected_peers
self.log.info("Test that whitebind inbounds get extra eviction power")
# Only allow 1 inbound connection, but set whitebind
self.restart_node(0, extra_args=['-maxconnections=12', '-whitebind=127.0.0.1:30201', '-whitebind=forceinbound@127.0.0.1:30202'])
self.log.debug("Connect 1 peer")
node.add_p2p_connection(P2PInterface())
# Allow 10 inbound connections, set whitebind and forceinbound
self.restart_node(0, extra_args=['-maxconnections=21', '-whitebind=127.0.0.1:30201', '-whitebind=forceinbound@127.0.0.1:30202'])
self.log.debug("Fill connections with unprivileged peers")
for i in range(10):
node.add_p2p_connection(P2PInterface())
# Create a peer that expects to be rejected
class RejectedPeer(P2PInterface):
def connection_lost(self, exc):
return
allowed_peers = []
self.log.debug("Generic inbound gets rejected when full")
with node.assert_debug_log(["failed to find an eviction candidate - connection dropped (full)"]):
node.add_p2p_connection(RejectedPeer(), wait_for_verack=False)
@ -142,20 +145,24 @@ class P2PEvict(BitcoinTestFramework):
node.add_p2p_connection(RejectedPeer(), wait_for_verack=False, dstport=30201)
self.log.debug("ForceInbound whitebind inbound gets connected, even when full")
allowed_peer = node.add_p2p_connection(P2PInterface(), dstport=30202)
allowed_peers.append(node.add_p2p_connection(P2PInterface(), dstport=30202))
assert_equal(len(node.getpeerinfo()), 1)
assert_equal(len(node.getpeerinfo()), 10)
self.log.debug("Generic inbound gets rejected when whitebind peer is filling inbound slot")
with node.assert_debug_log(["failed to find an eviction candidate - connection dropped (full)"]):
node.add_p2p_connection(RejectedPeer(), wait_for_verack=False)
self.log.debug("ForceInbound whitebind inbound gets rejected when another whitebind peer is filling inbound slot")
with node.assert_debug_log(["failed to find an eviction candidate - connection dropped (full)"]):
self.log.debug("Fill force_inbound slots")
for i in range(7):
allowed_peers.append(node.add_p2p_connection(P2PInterface(), dstport=30202))
self.log.debug("ForceInbound gets rejected after 8 evictions")
with node.assert_debug_log([f"dropped (too many forced inbound)"]):
node.add_p2p_connection(RejectedPeer(), dstport=30202, wait_for_verack=False)
assert_equal(len(node.getpeerinfo()), 1)
assert allowed_peer.is_connected
assert_equal(len(node.getpeerinfo()), 10)
assert [peer.is_connected for peer in allowed_peers]
if __name__ == '__main__':
P2PEvict().main()