Skip to content

Commit a73dcb7

Browse files
committed
Improved handling of IP Banning (#2530)
This PR in general improves the handling around peer banning. Specifically there were issues when multiple peers under a single IP connected to us after we banned the IP for poor behaviour. This PR should now handle these peers gracefully as well as make some improvements around how we previously disconnected and banned peers. The logic now goes as follows: - Once a peer gets banned, its gets registered with its known IP addresses - Once enough banned peers exist under a single IP that IP is banned - We retain connections with existing peers under this IP - Any new connections under this IP are rejected
1 parent 64ad2af commit a73dcb7

File tree

5 files changed

+354
-174
lines changed

5 files changed

+354
-174
lines changed

beacon_node/eth2_libp2p/src/peer_manager/mod.rs

Lines changed: 104 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,8 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
171171
);
172172
}
173173

174-
// Update the peerdb and peer state accordingly
175-
if self
176-
.network_globals
177-
.peers
178-
.write()
179-
.disconnect_and_ban(peer_id)
180-
{
181-
// update the state of the peer.
182-
self.events
183-
.push(PeerManagerEvent::DisconnectPeer(*peer_id, reason));
184-
}
174+
// Update the peerdb and start the disconnection.
175+
self.ban_peer(peer_id, reason);
185176
}
186177

187178
/// Reports a peer for some action.
@@ -333,18 +324,23 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
333324
}
334325
}
335326

336-
// Should not be able to connect to a banned peer. Double check here
337-
if self.is_banned(&peer_id) {
338-
warn!(self.log, "Connected to a banned peer"; "peer_id" => %peer_id);
339-
self.events.push(PeerManagerEvent::DisconnectPeer(
340-
peer_id,
341-
GoodbyeReason::Banned,
342-
));
343-
self.network_globals
344-
.peers
345-
.write()
346-
.notify_disconnecting(peer_id, true);
347-
return;
327+
// Check to make sure the peer is not supposed to be banned
328+
match self.ban_status(&peer_id) {
329+
BanResult::BadScore => {
330+
// This is a faulty state
331+
error!(self.log, "Connected to a banned peer, re-banning"; "peer_id" => %peer_id);
332+
// Reban the peer
333+
self.goodbye_peer(&peer_id, GoodbyeReason::Banned, ReportSource::PeerManager);
334+
return;
335+
}
336+
BanResult::BannedIp(ip_addr) => {
337+
// A good peer has connected to us via a banned IP address. We ban the peer and
338+
// prevent future connections.
339+
debug!(self.log, "Peer connected via banned IP. Banning"; "peer_id" => %peer_id, "banned_ip" => %ip_addr);
340+
self.goodbye_peer(&peer_id, GoodbyeReason::BannedIP, ReportSource::PeerManager);
341+
return;
342+
}
343+
BanResult::NotBanned => {}
348344
}
349345

350346
// Check the connection limits
@@ -356,14 +352,8 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
356352
.peer_info(&peer_id)
357353
.map_or(true, |peer| !peer.has_future_duty())
358354
{
359-
self.events.push(PeerManagerEvent::DisconnectPeer(
360-
peer_id,
361-
GoodbyeReason::TooManyPeers,
362-
));
363-
self.network_globals
364-
.peers
365-
.write()
366-
.notify_disconnecting(peer_id, false);
355+
// Gracefully disconnect the peer.
356+
self.disconnect_peer(peer_id, GoodbyeReason::TooManyPeers);
367357
return;
368358
}
369359

@@ -465,8 +455,8 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
465455
/// Reports if a peer is banned or not.
466456
///
467457
/// This is used to determine if we should accept incoming connections.
468-
pub fn is_banned(&self, peer_id: &PeerId) -> bool {
469-
self.network_globals.peers.read().is_banned(peer_id)
458+
pub fn ban_status(&self, peer_id: &PeerId) -> BanResult {
459+
self.network_globals.peers.read().ban_status(peer_id)
470460
}
471461

472462
pub fn is_connected(&self, peer_id: &PeerId) -> bool {
@@ -707,7 +697,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
707697
let mut to_unban_peers = Vec::new();
708698

709699
{
710-
//collect peers with scores
700+
// collect peers with scores
711701
let mut guard = self.network_globals.peers.write();
712702
let mut peers: Vec<_> = guard
713703
.peers_mut()
@@ -793,10 +783,11 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
793783
.write()
794784
.inject_disconnect(peer_id)
795785
{
796-
self.ban_peer(peer_id);
786+
// The peer was awaiting a ban, continue to ban the peer.
787+
self.ban_peer(peer_id, GoodbyeReason::BadScore);
797788
}
798789

799-
// remove the ping and status timer for the peer
790+
// Remove the ping and status timer for the peer
800791
self.inbound_ping_peers.remove(peer_id);
801792
self.outbound_ping_peers.remove(peer_id);
802793
self.status_peers.remove(peer_id);
@@ -816,7 +807,7 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
816807
) -> bool {
817808
{
818809
let mut peerdb = self.network_globals.peers.write();
819-
if peerdb.is_banned(peer_id) {
810+
if !matches!(peerdb.ban_status(peer_id), BanResult::NotBanned) {
820811
// don't connect if the peer is banned
821812
slog::crit!(self.log, "Connection has been allowed to a banned peer"; "peer_id" => %peer_id);
822813
}
@@ -878,42 +869,45 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
878869
events: &mut SmallVec<[PeerManagerEvent; 16]>,
879870
log: &slog::Logger,
880871
) {
881-
if previous_state != info.score_state() {
882-
match info.score_state() {
883-
ScoreState::Banned => {
884-
debug!(log, "Peer has been banned"; "peer_id" => %peer_id, "score" => %info.score());
885-
to_ban_peers.push(*peer_id);
886-
}
887-
ScoreState::Disconnected => {
888-
debug!(log, "Peer transitioned to disconnect state"; "peer_id" => %peer_id, "score" => %info.score(), "past_state" => %previous_state);
889-
// disconnect the peer if it's currently connected or dialing
890-
if info.is_connected_or_dialing() {
891-
// Change the state to inform that we are disconnecting the peer.
892-
info.disconnecting(false);
893-
events.push(PeerManagerEvent::DisconnectPeer(
894-
*peer_id,
895-
GoodbyeReason::BadScore,
896-
));
897-
} else if info.is_banned() {
898-
to_unban_peers.push(*peer_id);
899-
}
900-
}
901-
ScoreState::Healthy => {
902-
debug!(log, "Peer transitioned to healthy state"; "peer_id" => %peer_id, "score" => %info.score(), "past_state" => %previous_state);
903-
// unban the peer if it was previously banned.
904-
if info.is_banned() {
905-
to_unban_peers.push(*peer_id);
906-
}
872+
match (info.score_state(), previous_state) {
873+
(ScoreState::Banned, ScoreState::Healthy | ScoreState::Disconnected) => {
874+
debug!(log, "Peer has been banned"; "peer_id" => %peer_id, "score" => %info.score());
875+
to_ban_peers.push(*peer_id);
876+
}
877+
(ScoreState::Disconnected, ScoreState::Banned | ScoreState::Healthy) => {
878+
debug!(log, "Peer transitioned to disconnect state"; "peer_id" => %peer_id, "score" => %info.score(), "past_state" => %previous_state);
879+
// disconnect the peer if it's currently connected or dialing
880+
if info.is_connected_or_dialing() {
881+
// Change the state to inform that we are disconnecting the peer.
882+
info.disconnecting(false);
883+
events.push(PeerManagerEvent::DisconnectPeer(
884+
*peer_id,
885+
GoodbyeReason::BadScore,
886+
));
887+
} else if previous_state == ScoreState::Banned {
888+
to_unban_peers.push(*peer_id);
907889
}
908890
}
891+
(ScoreState::Healthy, ScoreState::Disconnected) => {
892+
debug!(log, "Peer transitioned to healthy state"; "peer_id" => %peer_id, "score" => %info.score(), "past_state" => %previous_state);
893+
}
894+
(ScoreState::Healthy, ScoreState::Banned) => {
895+
debug!(log, "Peer transitioned to healthy state"; "peer_id" => %peer_id, "score" => %info.score(), "past_state" => %previous_state);
896+
// unban the peer if it was previously banned.
897+
to_unban_peers.push(*peer_id);
898+
}
899+
// Explicitly ignore states that haven't transitioned.
900+
(ScoreState::Healthy, ScoreState::Healthy) => {}
901+
(ScoreState::Disconnected, ScoreState::Disconnected) => {}
902+
(ScoreState::Banned, ScoreState::Banned) => {}
909903
}
910904
}
911905

912906
/// Updates the state of banned peers.
913907
fn ban_and_unban_peers(&mut self, to_ban_peers: Vec<PeerId>, to_unban_peers: Vec<PeerId>) {
914908
// process banning peers
915909
for peer_id in to_ban_peers {
916-
self.ban_peer(&peer_id);
910+
self.ban_peer(&peer_id, GoodbyeReason::BadScore);
917911
}
918912
// process unbanning peers
919913
for peer_id in to_unban_peers {
@@ -953,40 +947,49 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
953947
///
954948
/// Records updates the peers connection status and updates the peer db as well as blocks the
955949
/// peer from participating in discovery and removes them from the routing table.
956-
fn ban_peer(&mut self, peer_id: &PeerId) {
957-
{
958-
// write lock scope
959-
let mut peer_db = self.network_globals.peers.write();
950+
fn ban_peer(&mut self, peer_id: &PeerId, reason: GoodbyeReason) {
951+
// NOTE: When we ban a peer, its IP address can be banned. We do not recursively search
952+
// through all our connected peers banning all other peers that are using this IP address.
953+
// If these peers are behaving fine, we permit their current connections. However, if any new
954+
// nodes or current nodes try to reconnect on a banned IP, they will be instantly banned
955+
// and disconnected.
956+
957+
let mut peer_db = self.network_globals.peers.write();
960958

961-
if peer_db.disconnect_and_ban(peer_id) {
959+
match peer_db.disconnect_and_ban(peer_id) {
960+
BanOperation::DisconnectThePeer => {
962961
// The peer was currently connected, so we start a disconnection.
963-
self.events.push(PeerManagerEvent::DisconnectPeer(
964-
*peer_id,
965-
GoodbyeReason::BadScore,
966-
));
962+
// Once the peer has disconnected, this function will be called to again to ban
963+
// at the swarm level.
964+
self.events
965+
.push(PeerManagerEvent::DisconnectPeer(*peer_id, reason));
967966
}
968-
} // end write lock
969-
970-
// take a read lock
971-
let peer_db = self.network_globals.peers.read();
972-
973-
let banned_ip_addresses = peer_db
974-
.peer_info(peer_id)
975-
.map(|info| {
976-
info.seen_addresses()
977-
.filter(|ip| peer_db.is_ip_banned(ip))
978-
.collect::<Vec<_>>()
979-
})
980-
.unwrap_or_default();
967+
BanOperation::PeerDisconnecting => {
968+
// The peer is currently being disconnected and will be banned once the
969+
// disconnection completes.
970+
}
971+
BanOperation::ReadyToBan => {
972+
// The peer is not currently connected, we can safely ban it at the swarm
973+
// level.
974+
let banned_ip_addresses = peer_db
975+
.peer_info(peer_id)
976+
.map(|info| {
977+
info.seen_addresses()
978+
.filter(|ip| peer_db.is_ip_banned(ip))
979+
.collect::<Vec<_>>()
980+
})
981+
.unwrap_or_default();
981982

982-
// Inform the Swarm to ban the peer
983-
self.events
984-
.push(PeerManagerEvent::Banned(*peer_id, banned_ip_addresses));
983+
// Inform the Swarm to ban the peer
984+
self.events
985+
.push(PeerManagerEvent::Banned(*peer_id, banned_ip_addresses));
986+
}
987+
}
985988
}
986989

987990
/// Unbans a peer.
988991
///
989-
/// Records updates the peers connection status and updates the peer db as well as removes
992+
/// Updates the peer's connection status and updates the peer db as well as removes
990993
/// previous bans from discovery.
991994
fn unban_peer(&mut self, peer_id: &PeerId) -> Result<(), &'static str> {
992995
let mut peer_db = self.network_globals.peers.write();
@@ -1003,6 +1006,16 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
10031006
Ok(())
10041007
}
10051008

1009+
// Gracefully disconnects a peer without banning them.
1010+
fn disconnect_peer(&mut self, peer_id: PeerId, reason: GoodbyeReason) {
1011+
self.events
1012+
.push(PeerManagerEvent::DisconnectPeer(peer_id, reason));
1013+
self.network_globals
1014+
.peers
1015+
.write()
1016+
.notify_disconnecting(peer_id, false);
1017+
}
1018+
10061019
/// Run discovery query for additional sync committee peers if we fall below `TARGET_PEERS`.
10071020
fn maintain_sync_committee_peers(&mut self) {
10081021
// Remove expired entries
@@ -1101,13 +1114,8 @@ impl<TSpec: EthSpec> PeerManager<TSpec> {
11011114
}
11021115
}
11031116

1104-
let mut peer_db = self.network_globals.peers.write();
11051117
for peer_id in disconnecting_peers {
1106-
peer_db.notify_disconnecting(peer_id, false);
1107-
self.events.push(PeerManagerEvent::DisconnectPeer(
1108-
peer_id,
1109-
GoodbyeReason::TooManyPeers,
1110-
));
1118+
self.disconnect_peer(peer_id, GoodbyeReason::TooManyPeers);
11111119
}
11121120
}
11131121
}

beacon_node/eth2_libp2p/src/peer_manager/peer_info.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ impl<T: EthSpec> PeerInfo<T> {
183183

184184
/// Checks if the status is banned.
185185
pub fn is_banned(&self) -> bool {
186-
matches!(self.connection_status, PeerConnectionStatus::Banned { .. })
186+
matches!(self.score.state(), ScoreState::Banned)
187187
}
188188

189189
/// Checks if the status is disconnected.
@@ -208,7 +208,7 @@ impl<T: EthSpec> PeerInfo<T> {
208208

209209
/// Modifies the status to Disconnected and sets the last seen instant to now. Returns None if
210210
/// no changes were made. Returns Some(bool) where the bool represents if peer is to now be
211-
/// baned
211+
/// banned.
212212
pub fn notify_disconnect(&mut self) -> Option<bool> {
213213
match self.connection_status {
214214
Banned { .. } | Disconnected { .. } => None,
@@ -233,17 +233,18 @@ impl<T: EthSpec> PeerInfo<T> {
233233
self.connection_status = Disconnecting { to_ban }
234234
}
235235

236-
/// Modifies the status to Banned
237-
pub fn ban(&mut self) {
238-
self.connection_status = Banned {
239-
since: Instant::now(),
240-
};
241-
}
242-
243-
/// The score system has unbanned the peer. Update the connection status
244-
pub fn unban(&mut self) {
245-
if let PeerConnectionStatus::Banned { since, .. } = self.connection_status {
246-
self.connection_status = PeerConnectionStatus::Disconnected { since };
236+
/// Modifies the status to banned or unbanned based on the underlying score.
237+
pub fn update_state(&mut self) {
238+
match (&self.connection_status, self.score.state()) {
239+
(Disconnected { .. } | Unknown, ScoreState::Banned) => {
240+
self.connection_status = Banned {
241+
since: Instant::now(),
242+
}
243+
}
244+
(Banned { since }, ScoreState::Healthy | ScoreState::Disconnected) => {
245+
self.connection_status = Disconnected { since: *since }
246+
}
247+
(_, _) => {}
247248
}
248249
}
249250

@@ -358,7 +359,6 @@ pub enum PeerConnectionStatus {
358359
/// last time the peer was connected or discovered.
359360
since: Instant,
360361
},
361-
362362
/// The peer has been banned and is disconnected.
363363
Banned {
364364
/// moment when the peer was banned.

0 commit comments

Comments
 (0)