@@ -39,8 +39,8 @@ use networking::{
3939use p2p_types:: socket_address:: SocketAddress ;
4040use randomness:: { RngExt as _, make_pseudo_rng} ;
4141use utils:: {
42- atomics:: SeqCstAtomicBool , eventhandler :: EventsController , set_flag :: SetFlag ,
43- shallow_clone:: ShallowClone , tokio_spawn_in_current_tracing_span,
42+ atomics:: SeqCstAtomicBool , debug_panic_or_log , eventhandler :: EventsController ,
43+ set_flag :: SetFlag , shallow_clone:: ShallowClone , tokio_spawn_in_current_tracing_span,
4444} ;
4545
4646use crate :: {
@@ -108,6 +108,7 @@ struct PeerContext {
108108}
109109
110110/// Pending peer data (until handshake message is received)
111+ #[ derive( Debug ) ]
111112struct PendingPeerContext {
112113 handle : tokio:: task:: JoinHandle < ( ) > ,
113114
@@ -149,6 +150,9 @@ pub struct Backend<T: TransportSocket> {
149150 /// Pending connections
150151 pending_peers : HashMap < PeerId , PendingPeerContext > ,
151152
153+ /// The number of inbound peers in `pending_peers`
154+ pending_inbound_peer_count : usize ,
155+
152156 /// Map of streams for receiving events from peers.
153157 peer_event_stream_map : StreamMap < PeerId , ReceiverStream < PeerEvent > > ,
154158
@@ -210,6 +214,7 @@ where
210214 syncing_event_sender,
211215 peers : HashMap :: new ( ) ,
212216 pending_peers : HashMap :: new ( ) ,
217+ pending_inbound_peer_count : 0 ,
213218 peer_event_stream_map : StreamMap :: new ( ) ,
214219 command_queue : FuturesUnordered :: new ( ) ,
215220 shutdown,
@@ -347,21 +352,51 @@ where
347352 res = self . socket. accept( ) => {
348353 match res {
349354 Ok ( ( stream, address) ) => {
355+ // Note: this execution path is peer-triggerable, so we use debug level logging
356+ // until after `pending_inbound_peer_count` has been checked, to prevent malicious
357+ // peers from flooding the default logs.
350358 if !self . networking_enabled {
351- log:: info !( "Ignoring incoming connection from {address:?} because networking is disabled" ) ;
359+ log:: debug !( "Ignoring incoming connection from {address:?} because networking is disabled" ) ;
352360 } else {
353- self . create_pending_peer(
354- stream,
355- PeerId :: new( ) ,
356- ConnectionInfo :: Inbound ,
357- address. into( ) ,
358- ) ?;
361+ let max_pending_inbound_conn_count = * self . p2p_config. backend_config. max_pending_inbound_connections;
362+
363+ if self . pending_inbound_peer_count >= max_pending_inbound_conn_count {
364+ log:: debug!(
365+ concat!(
366+ "Ignoring incoming connection from {:?} because the maximum number " ,
367+ "of pending incoming connections has been reached {}"
368+ ) ,
369+ address,
370+ max_pending_inbound_conn_count
371+ ) ;
372+ } else {
373+ self . create_pending_peer(
374+ stream,
375+ PeerId :: new( ) ,
376+ ConnectionInfo :: Inbound ,
377+ address. into( ) ,
378+ ) ?;
379+ }
359380 }
360381 } ,
361382 Err ( err) => {
362- // Just log the error and let the node continue working
383+ // Note: this execution path is also peer-triggerable, though less reliably than the
384+ // successful path above, so we use debug-level logging here too. See also a similar
385+ // comment in `AdaptedListener::accept` in the transport layer.
386+ // TODO: malicious peers can still flood the default logs making successful connections
387+ // and dropping them immediately (though it will be less severe and more costly for the
388+ // attacker). Consider:
389+ // a) Using debug-level logging specifically for incoming connections (while keeping it
390+ // at "info" for outbound ones) in all (or most) connectivity-related messages (e.g.
391+ // "Peer disconnected", "Assigning peer id", "New peer accepted").
392+ // b) Implementing a rate-limiter for the log (e.g. using a custom tracing layer or filter),
393+ // either based on the source code location (Bitcoin does this) or on the specified
394+ // log target.
395+ // c) (In the case when some errors have been omitted or printed via debug!), printing
396+ // some kind of info/warn-level summary at regular intervals, e.g. "X incoming connections
397+ // ignored in the last Y seconds".
363398 if self . networking_enabled {
364- log:: error !( "Accepting a new connection failed unexpectedly: {err}" )
399+ log:: debug !( "Accepting a new connection failed unexpectedly: {err}" )
365400 } else {
366401 log:: debug!(
367402 "Ignoring failed incoming connection because networking is disabled (err = {err})" ,
@@ -426,7 +461,7 @@ where
426461 & format ! ( "Peer[id={peer_id}]" ) ,
427462 ) ;
428463
429- self . pending_peers . insert (
464+ self . insert_new_pending_peer (
430465 peer_id,
431466 PendingPeerContext {
432467 handle,
@@ -456,7 +491,7 @@ where
456491 bind_address,
457492 connection_info,
458493 backend_event_sender,
459- } = match self . pending_peers . remove ( & peer_id) {
494+ } = match self . remove_pending_peer ( peer_id) {
460495 Some ( pending_peer) => pending_peer,
461496 // Could be removed if self-connection was detected earlier
462497 None => return Ok ( ( ) ) ,
@@ -568,7 +603,7 @@ where
568603 . map ( |( peer_id, _pending) | * peer_id) ;
569604
570605 if let Some ( peer_id) = pending_outbound_peer_id {
571- let peer_ctx = self . pending_peers . remove ( & peer_id) . expect ( "peer must exist" ) ;
606+ let peer_ctx = self . remove_pending_peer ( peer_id) . expect ( "peer must exist" ) ;
572607
573608 log:: info!(
574609 "self-connection detected on address {:?}" ,
@@ -649,7 +684,7 @@ where
649684 }
650685
651686 PeerEvent :: ConnectionClosed => {
652- if let Some ( pending_peer) = self . pending_peers . remove ( & peer_id) {
687+ if let Some ( pending_peer) = self . remove_pending_peer ( peer_id) {
653688 // Note: we'll get here if handshake has failed, so no need to use log levels
654689 // higher that debug, because the error should have been logged properly already.
655690 match pending_peer. connection_info {
@@ -802,6 +837,57 @@ where
802837 Err ( _) => log:: error!( "sending syncing event from the backend failed unexpectedly" ) ,
803838 }
804839 }
840+
841+ fn insert_new_pending_peer ( & mut self , peer_id : PeerId , peer_context : PendingPeerContext ) {
842+ let is_inbound = peer_context. connection_info . is_inbound ( ) ;
843+
844+ if let Some ( old_context) = self . pending_peers . insert ( peer_id, peer_context) {
845+ debug_panic_or_log ! (
846+ "Pending peer context already exists for a new peer {peer_id}: {old_context:?}"
847+ ) ;
848+ } else if is_inbound {
849+ self . pending_inbound_peer_count += 1 ;
850+
851+ #[ cfg( test) ]
852+ self . assert_pending_inbound_peer_count_consistency ( ) ;
853+ }
854+
855+ if let Some ( observer) = & self . observer {
856+ observer. on_pending_peer_created ( peer_id) ;
857+ }
858+ }
859+
860+ fn remove_pending_peer ( & mut self , peer_id : PeerId ) -> Option < PendingPeerContext > {
861+ let peer_context = self . pending_peers . remove ( & peer_id) ;
862+
863+ if let Some ( peer_context) = & peer_context {
864+ if peer_context. connection_info . is_inbound ( ) {
865+ self . pending_inbound_peer_count -= 1 ;
866+
867+ #[ cfg( test) ]
868+ self . assert_pending_inbound_peer_count_consistency ( ) ;
869+ }
870+
871+ if let Some ( observer) = & self . observer {
872+ observer. on_pending_peer_removed ( peer_id) ;
873+ }
874+ }
875+
876+ peer_context
877+ }
878+
879+ #[ cfg( test) ]
880+ fn assert_pending_inbound_peer_count_consistency ( & self ) {
881+ let actual_pending_inbound_peer_count = self
882+ . pending_peers
883+ . values ( )
884+ . filter ( |context| context. connection_info . is_inbound ( ) )
885+ . count ( ) ;
886+ assert_eq ! (
887+ self . pending_inbound_peer_count,
888+ actual_pending_inbound_peer_count
889+ ) ;
890+ }
805891}
806892
807893// Some boilerplate types and a function for blocking tasks handling
0 commit comments