Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rate limit incoming ips #12153

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/net/network-types/src/peers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub const DEFAULT_MAX_COUNT_PEERS_INBOUND: u32 = 30;
/// This restricts how many outbound dials can be performed concurrently.
pub const DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS: usize = 15;

/// A temporary timeout for ips on incoming connection attempts.
pub const INBOUND_IP_THROTTLE_DURATION: Duration = Duration::from_secs(30);

/// The durations to use when a backoff should be applied to a peer.
///
/// See also [`BackoffKind`].
Expand Down Expand Up @@ -155,6 +158,11 @@ pub struct PeersConfig {
///
/// The backoff duration increases with number of backoff attempts.
pub backoff_durations: PeerBackoffDurations,
/// How long to temporarily ban ips on incoming connection attempts.
///
/// This acts as an IP based rate limit.
#[cfg_attr(feature = "serde", serde(default, with = "humantime_serde"))]
pub incoming_ip_throttle_duration: Duration,
}

impl Default for PeersConfig {
Expand All @@ -171,6 +179,7 @@ impl Default for PeersConfig {
trusted_nodes_only: false,
basic_nodes: Default::default(),
max_backoff_count: 5,
incoming_ip_throttle_duration: INBOUND_IP_THROTTLE_DURATION,
}
}
}
Expand Down
63 changes: 54 additions & 9 deletions crates/net/network/src/peers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub struct PeersManager {
max_backoff_count: u8,
/// Tracks the connection state of the node
net_connection_state: NetworkConnectionState,
/// How long to temporarily ban ip on an incoming connection attempt.
incoming_ip_throttle_duration: Duration,
}

impl PeersManager {
Expand All @@ -100,6 +102,7 @@ impl PeersManager {
trusted_nodes_only,
basic_nodes,
max_backoff_count,
incoming_ip_throttle_duration,
} = config;
let (manager_tx, handle_rx) = mpsc::unbounded_channel();
let now = Instant::now();
Expand Down Expand Up @@ -148,6 +151,7 @@ impl PeersManager {
last_tick: Instant::now(),
max_backoff_count,
net_connection_state: NetworkConnectionState::default(),
incoming_ip_throttle_duration,
}
}

Expand Down Expand Up @@ -265,6 +269,9 @@ impl PeersManager {
return Err(InboundConnectionError::ExceedsCapacity)
}

// apply the rate limit
self.throttle_incoming_ip(addr);

self.connection_info.inc_pending_in();
Ok(())
}
Expand Down Expand Up @@ -383,6 +390,12 @@ impl PeersManager {
self.ban_list.ban_ip_until(ip, std::time::Instant::now() + self.ban_duration);
}

/// Bans the IP temporarily to rate limit inbound connection attempts per IP.
fn throttle_incoming_ip(&mut self, ip: IpAddr) {
self.ban_list
.ban_ip_until(ip, std::time::Instant::now() + self.incoming_ip_throttle_duration);
}

/// Temporarily puts the peer in timeout by inserting it into the backedoff peers set
fn backoff_peer_until(&mut self, peer_id: PeerId, until: std::time::Instant) {
trace!(target: "net::peers", ?peer_id, "backing off");
Expand Down Expand Up @@ -1129,15 +1142,6 @@ impl Display for InboundConnectionError {

#[cfg(test)]
mod tests {
use std::{
future::{poll_fn, Future},
io,
net::{IpAddr, Ipv4Addr, SocketAddr},
pin::Pin,
task::{Context, Poll},
time::Duration,
};

use alloy_primitives::B512;
use reth_eth_wire::{
errors::{EthHandshakeError, EthStreamError, P2PHandshakeError, P2PStreamError},
Expand All @@ -1149,6 +1153,14 @@ mod tests {
use reth_network_types::{
peers::reputation::DEFAULT_REPUTATION, BackoffKind, ReputationChangeKind,
};
use std::{
future::{poll_fn, Future},
io,
net::{IpAddr, Ipv4Addr, SocketAddr},
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use url::Host;

use super::PeersManager;
Expand Down Expand Up @@ -2330,6 +2342,39 @@ mod tests {
);
}

#[tokio::test]
async fn test_incoming_rate_limit() {
let config = PeersConfig {
incoming_ip_throttle_duration: Duration::from_millis(100),
..PeersConfig::test()
};
let mut peers = PeersManager::new(config);

let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(168, 0, 1, 2)), 8009);
assert!(peers.on_incoming_pending_session(addr.ip()).is_ok());
assert_eq!(
peers.on_incoming_pending_session(addr.ip()).unwrap_err(),
InboundConnectionError::IpBanned
);

peers.release_interval.reset_immediately();
tokio::time::sleep(peers.incoming_ip_throttle_duration).await;

// await unban
poll_fn(|cx| loop {
if peers.poll(cx).is_pending() {
return Poll::Ready(());
}
})
.await;

assert!(peers.on_incoming_pending_session(addr.ip()).is_ok());
assert_eq!(
peers.on_incoming_pending_session(addr.ip()).unwrap_err(),
InboundConnectionError::IpBanned
);
}

#[tokio::test]
async fn test_tick() {
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 1, 2));
Expand Down
Loading