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))]
10
11type PeerId = alloy_primitives::B512;
12
13use std::{collections::HashMap, net::IpAddr, str::FromStr, 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 now > *until
64 {
65 evicted.push(*peer);
66 return false
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 now > *until
79 {
80 evicted.push(*peer);
81 return false
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) {
130 self.ban_ip_with(ip, Some(until));
131 }
132
133 pub fn ban_peer_until(&mut self, node_id: PeerId, until: Instant) {
137 self.ban_peer_with(node_id, Some(until));
138 }
139
140 pub fn ban_ip(&mut self, ip: IpAddr) {
144 self.ban_ip_with(ip, None);
145 }
146
147 pub fn ban_peer(&mut self, node_id: PeerId) {
149 self.ban_peer_with(node_id, None);
150 }
151
152 pub fn ban_peer_with(&mut self, node_id: PeerId, until: Option<Instant>) {
156 self.banned_peers.insert(node_id, until);
157 }
158
159 pub fn ban_ip_with(&mut self, ip: IpAddr, until: Option<Instant>) {
164 if is_global(&ip) {
165 self.banned_ips.insert(ip, until);
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn can_ban_unban_peer() {
176 let peer = PeerId::new([1; 64]);
177 let mut banlist = BanList::default();
178 banlist.ban_peer(peer);
179 assert!(banlist.is_banned_peer(&peer));
180 banlist.unban_peer(&peer);
181 assert!(!banlist.is_banned_peer(&peer));
182 }
183
184 #[test]
185 fn can_ban_unban_ip() {
186 let ip = IpAddr::from([1, 1, 1, 1]);
187 let mut banlist = BanList::default();
188 banlist.ban_ip(ip);
189 assert!(banlist.is_banned_ip(&ip));
190 banlist.unban_ip(&ip);
191 assert!(!banlist.is_banned_ip(&ip));
192 }
193
194 #[test]
195 fn cannot_ban_non_global() {
196 let mut ip = IpAddr::from([0, 0, 0, 0]);
197 let mut banlist = BanList::default();
198 banlist.ban_ip(ip);
199 assert!(!banlist.is_banned_ip(&ip));
200
201 ip = IpAddr::from([10, 0, 0, 0]);
202 banlist.ban_ip(ip);
203 assert!(!banlist.is_banned_ip(&ip));
204
205 ip = IpAddr::from([127, 0, 0, 0]);
206 banlist.ban_ip(ip);
207 assert!(!banlist.is_banned_ip(&ip));
208
209 ip = IpAddr::from([172, 17, 0, 0]);
210 banlist.ban_ip(ip);
211 assert!(!banlist.is_banned_ip(&ip));
212
213 ip = IpAddr::from([172, 16, 0, 0]);
214 banlist.ban_ip(ip);
215 assert!(!banlist.is_banned_ip(&ip));
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct IpFilter {
222 allowed_networks: Vec<ipnet::IpNet>,
225}
226
227impl IpFilter {
228 pub const fn new(allowed_networks: Vec<ipnet::IpNet>) -> Self {
232 Self { allowed_networks }
233 }
234
235 pub fn from_cidr_string(cidrs: &str) -> Result<Self, ipnet::AddrParseError> {
241 if cidrs.is_empty() {
242 return Ok(Self::allow_all())
243 }
244
245 let networks = cidrs
246 .split(',')
247 .map(|s| s.trim())
248 .filter(|s| !s.is_empty())
249 .map(ipnet::IpNet::from_str)
250 .collect::<Result<Vec<_>, _>>()?;
251
252 Ok(Self::new(networks))
253 }
254
255 pub const fn allow_all() -> Self {
257 Self { allowed_networks: Vec::new() }
258 }
259
260 pub fn is_allowed(&self, ip: &IpAddr) -> bool {
265 if self.allowed_networks.is_empty() {
267 return true
268 }
269
270 self.allowed_networks.iter().any(|net| net.contains(ip))
272 }
273
274 pub const fn has_restrictions(&self) -> bool {
276 !self.allowed_networks.is_empty()
277 }
278
279 pub fn allowed_networks(&self) -> &[ipnet::IpNet] {
281 &self.allowed_networks
282 }
283}
284
285impl Default for IpFilter {
286 fn default() -> Self {
287 Self::allow_all()
288 }
289}
290
291#[cfg(test)]
292mod ip_filter_tests {
293 use super::*;
294
295 #[test]
296 fn test_allow_all_filter() {
297 let filter = IpFilter::allow_all();
298 assert!(filter.is_allowed(&IpAddr::from([192, 168, 1, 1])));
299 assert!(filter.is_allowed(&IpAddr::from([10, 0, 0, 1])));
300 assert!(filter.is_allowed(&IpAddr::from([8, 8, 8, 8])));
301 assert!(!filter.has_restrictions());
302 }
303
304 #[test]
305 fn test_single_network_filter() {
306 let filter = IpFilter::from_cidr_string("192.168.0.0/16").unwrap();
307 assert!(filter.is_allowed(&IpAddr::from([192, 168, 1, 1])));
308 assert!(filter.is_allowed(&IpAddr::from([192, 168, 255, 255])));
309 assert!(!filter.is_allowed(&IpAddr::from([192, 169, 1, 1])));
310 assert!(!filter.is_allowed(&IpAddr::from([10, 0, 0, 1])));
311 assert!(filter.has_restrictions());
312 }
313
314 #[test]
315 fn test_multiple_networks_filter() {
316 let filter = IpFilter::from_cidr_string("192.168.0.0/16,10.0.0.0/8").unwrap();
317 assert!(filter.is_allowed(&IpAddr::from([192, 168, 1, 1])));
318 assert!(filter.is_allowed(&IpAddr::from([10, 5, 10, 20])));
319 assert!(filter.is_allowed(&IpAddr::from([10, 255, 255, 255])));
320 assert!(!filter.is_allowed(&IpAddr::from([172, 16, 0, 1])));
321 assert!(!filter.is_allowed(&IpAddr::from([8, 8, 8, 8])));
322 }
323
324 #[test]
325 fn test_ipv6_filter() {
326 let filter = IpFilter::from_cidr_string("2001:db8::/32").unwrap();
327 let ipv6_in_range: IpAddr = "2001:db8::1".parse().unwrap();
328 let ipv6_out_range: IpAddr = "2001:db9::1".parse().unwrap();
329
330 assert!(filter.is_allowed(&ipv6_in_range));
331 assert!(!filter.is_allowed(&ipv6_out_range));
332 }
333
334 #[test]
335 fn test_mixed_ipv4_ipv6_filter() {
336 let filter = IpFilter::from_cidr_string("192.168.0.0/16,2001:db8::/32").unwrap();
337
338 assert!(filter.is_allowed(&IpAddr::from([192, 168, 1, 1])));
339 let ipv6_in_range: IpAddr = "2001:db8::1".parse().unwrap();
340 assert!(filter.is_allowed(&ipv6_in_range));
341
342 assert!(!filter.is_allowed(&IpAddr::from([10, 0, 0, 1])));
343 let ipv6_out_range: IpAddr = "2001:db9::1".parse().unwrap();
344 assert!(!filter.is_allowed(&ipv6_out_range));
345 }
346
347 #[test]
348 fn test_empty_string() {
349 let filter = IpFilter::from_cidr_string("").unwrap();
350 assert!(filter.is_allowed(&IpAddr::from([192, 168, 1, 1])));
351 assert!(!filter.has_restrictions());
352 }
353
354 #[test]
355 fn test_invalid_cidr() {
356 assert!(IpFilter::from_cidr_string("invalid").is_err());
357 assert!(IpFilter::from_cidr_string("192.168.0.0/33").is_err());
358 assert!(IpFilter::from_cidr_string("192.168.0.0,10.0.0.0").is_err());
359 }
360
361 #[test]
362 fn test_whitespace_handling() {
363 let filter = IpFilter::from_cidr_string(" 192.168.0.0/16 , 10.0.0.0/8 ").unwrap();
364 assert!(filter.is_allowed(&IpAddr::from([192, 168, 1, 1])));
365 assert!(filter.is_allowed(&IpAddr::from([10, 0, 0, 1])));
366 assert!(!filter.is_allowed(&IpAddr::from([172, 16, 0, 1])));
367 }
368
369 #[test]
370 fn test_single_ip_as_cidr() {
371 let filter = IpFilter::from_cidr_string("192.168.1.100/32").unwrap();
372 assert!(filter.is_allowed(&IpAddr::from([192, 168, 1, 100])));
373 assert!(!filter.is_allowed(&IpAddr::from([192, 168, 1, 101])));
374 }
375}