reth_net_banlist/
lib.rs

1//! Support for banning peers.
2
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6    issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
7)]
8#![cfg_attr(not(test), warn(unused_crate_dependencies))]
9#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
10
11type PeerId = alloy_primitives::B512;
12
13use std::{collections::HashMap, net::IpAddr, time::Instant};
14
15/// Determines whether or not the IP is globally routable.
16/// Should be replaced with [`IpAddr::is_global`](std::net::IpAddr::is_global) once it is stable.
17pub const fn is_global(ip: &IpAddr) -> bool {
18    if ip.is_unspecified() || ip.is_loopback() {
19        return false
20    }
21
22    match ip {
23        IpAddr::V4(ip) => !ip.is_private() && !ip.is_link_local(),
24        IpAddr::V6(_) => true,
25    }
26}
27
28/// Stores peers that should be taken out of circulation either indefinitely or until a certain
29/// timestamp
30#[derive(Debug, Clone, Default, PartialEq, Eq)]
31pub struct BanList {
32    /// A set of IPs whose packets get dropped instantly.
33    banned_ips: HashMap<IpAddr, Option<Instant>>,
34    /// A set of [`PeerId`] whose packets get dropped instantly.
35    banned_peers: HashMap<PeerId, Option<Instant>>,
36}
37
38impl BanList {
39    /// Creates a new ban list that bans the given peers and ips indefinitely.
40    pub fn new(
41        banned_peers: impl IntoIterator<Item = PeerId>,
42        banned_ips: impl IntoIterator<Item = IpAddr>,
43    ) -> Self {
44        Self::new_with_timeout(
45            banned_peers.into_iter().map(|peer| (peer, None)).collect(),
46            banned_ips.into_iter().map(|ip| (ip, None)).collect(),
47        )
48    }
49
50    /// Creates a new ban list that bans the given peers and ips with an optional timeout.
51    pub const fn new_with_timeout(
52        banned_peers: HashMap<PeerId, Option<Instant>>,
53        banned_ips: HashMap<IpAddr, Option<Instant>>,
54    ) -> Self {
55        Self { banned_ips, banned_peers }
56    }
57
58    /// Removes all peers that are no longer banned.
59    pub fn evict_peers(&mut self, now: Instant) -> Vec<PeerId> {
60        let mut evicted = Vec::new();
61        self.banned_peers.retain(|peer, until| {
62            if let Some(until) = until {
63                if now > *until {
64                    evicted.push(*peer);
65                    return false
66                }
67            }
68            true
69        });
70        evicted
71    }
72
73    /// Removes all ip addresses that are no longer banned.
74    pub fn evict_ips(&mut self, now: Instant) -> Vec<IpAddr> {
75        let mut evicted = Vec::new();
76        self.banned_ips.retain(|peer, until| {
77            if let Some(until) = until {
78                if now > *until {
79                    evicted.push(*peer);
80                    return false
81                }
82            }
83            true
84        });
85        evicted
86    }
87
88    /// Removes all entries that should no longer be banned.
89    ///
90    /// Returns the evicted entries.
91    pub fn evict(&mut self, now: Instant) -> (Vec<IpAddr>, Vec<PeerId>) {
92        let ips = self.evict_ips(now);
93        let peers = self.evict_peers(now);
94        (ips, peers)
95    }
96
97    /// Returns true if either the given peer id _or_ ip address is banned.
98    #[inline]
99    pub fn is_banned(&self, peer_id: &PeerId, ip: &IpAddr) -> bool {
100        self.is_banned_peer(peer_id) || self.is_banned_ip(ip)
101    }
102
103    /// checks the ban list to see if it contains the given ip
104    #[inline]
105    pub fn is_banned_ip(&self, ip: &IpAddr) -> bool {
106        self.banned_ips.contains_key(ip)
107    }
108
109    /// checks the ban list to see if it contains the given ip
110    #[inline]
111    pub fn is_banned_peer(&self, peer_id: &PeerId) -> bool {
112        self.banned_peers.contains_key(peer_id)
113    }
114
115    /// Unbans the ip address
116    pub fn unban_ip(&mut self, ip: &IpAddr) {
117        self.banned_ips.remove(ip);
118    }
119
120    /// Unbans the ip address
121    pub fn unban_peer(&mut self, peer_id: &PeerId) {
122        self.banned_peers.remove(peer_id);
123    }
124
125    /// Bans the IP until the timestamp.
126    ///
127    /// This does not ban non-global IPs.
128    pub fn ban_ip_until(&mut self, ip: IpAddr, until: Instant) {
129        self.ban_ip_with(ip, Some(until));
130    }
131
132    /// Bans the peer until the timestamp
133    pub fn ban_peer_until(&mut self, node_id: PeerId, until: Instant) {
134        self.ban_peer_with(node_id, Some(until));
135    }
136
137    /// Bans the IP indefinitely.
138    ///
139    /// This does not ban non-global IPs.
140    pub fn ban_ip(&mut self, ip: IpAddr) {
141        self.ban_ip_with(ip, None);
142    }
143
144    /// Bans the peer indefinitely,
145    pub fn ban_peer(&mut self, node_id: PeerId) {
146        self.ban_peer_with(node_id, None);
147    }
148
149    /// Bans the peer indefinitely or until the given timeout.
150    pub fn ban_peer_with(&mut self, node_id: PeerId, until: Option<Instant>) {
151        self.banned_peers.insert(node_id, until);
152    }
153
154    /// Bans the ip indefinitely or until the given timeout.
155    ///
156    /// This does not ban non-global IPs.
157    pub fn ban_ip_with(&mut self, ip: IpAddr, until: Option<Instant>) {
158        if is_global(&ip) {
159            self.banned_ips.insert(ip, until);
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn can_ban_unban_peer() {
170        let peer = PeerId::random();
171        let mut banlist = BanList::default();
172        banlist.ban_peer(peer);
173        assert!(banlist.is_banned_peer(&peer));
174        banlist.unban_peer(&peer);
175        assert!(!banlist.is_banned_peer(&peer));
176    }
177
178    #[test]
179    fn can_ban_unban_ip() {
180        let ip = IpAddr::from([1, 1, 1, 1]);
181        let mut banlist = BanList::default();
182        banlist.ban_ip(ip);
183        assert!(banlist.is_banned_ip(&ip));
184        banlist.unban_ip(&ip);
185        assert!(!banlist.is_banned_ip(&ip));
186    }
187
188    #[test]
189    fn cannot_ban_non_global() {
190        let mut ip = IpAddr::from([0, 0, 0, 0]);
191        let mut banlist = BanList::default();
192        banlist.ban_ip(ip);
193        assert!(!banlist.is_banned_ip(&ip));
194
195        ip = IpAddr::from([10, 0, 0, 0]);
196        banlist.ban_ip(ip);
197        assert!(!banlist.is_banned_ip(&ip));
198
199        ip = IpAddr::from([127, 0, 0, 0]);
200        banlist.ban_ip(ip);
201        assert!(!banlist.is_banned_ip(&ip));
202
203        ip = IpAddr::from([172, 17, 0, 0]);
204        banlist.ban_ip(ip);
205        assert!(!banlist.is_banned_ip(&ip));
206
207        ip = IpAddr::from([172, 16, 0, 0]);
208        banlist.ban_ip(ip);
209        assert!(!banlist.is_banned_ip(&ip));
210    }
211}