reth_net_banlist/
lib.rs
1#![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
15pub 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#[derive(Debug, Clone, Default, PartialEq, Eq)]
31pub struct BanList {
32 banned_ips: HashMap<IpAddr, Option<Instant>>,
34 banned_peers: HashMap<PeerId, Option<Instant>>,
36}
37
38impl BanList {
39 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 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 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 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 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 #[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 #[inline]
105 pub fn is_banned_ip(&self, ip: &IpAddr) -> bool {
106 self.banned_ips.contains_key(ip)
107 }
108
109 #[inline]
111 pub fn is_banned_peer(&self, peer_id: &PeerId) -> bool {
112 self.banned_peers.contains_key(peer_id)
113 }
114
115 pub fn unban_ip(&mut self, ip: &IpAddr) {
117 self.banned_ips.remove(ip);
118 }
119
120 pub fn unban_peer(&mut self, peer_id: &PeerId) {
122 self.banned_peers.remove(peer_id);
123 }
124
125 pub fn ban_ip_until(&mut self, ip: IpAddr, until: Instant) {
129 self.ban_ip_with(ip, Some(until));
130 }
131
132 pub fn ban_peer_until(&mut self, node_id: PeerId, until: Instant) {
134 self.ban_peer_with(node_id, Some(until));
135 }
136
137 pub fn ban_ip(&mut self, ip: IpAddr) {
141 self.ban_ip_with(ip, None);
142 }
143
144 pub fn ban_peer(&mut self, node_id: PeerId) {
146 self.ban_peer_with(node_id, None);
147 }
148
149 pub fn ban_peer_with(&mut self, node_id: PeerId, until: Option<Instant>) {
151 self.banned_peers.insert(node_id, until);
152 }
153
154 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}