1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//! IP ignore list — CIDR allowlists and local IP detection.
//!
//! IPs in the ignore list are never banned, even if they match failure patterns.
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use ipnet::IpNet;
use crate::error::Result;
/// Ignore list with CIDR networks and optional self-detection.
#[derive(Debug, Clone)]
pub struct IgnoreList {
networks: Vec<IpNet>,
}
impl IgnoreList {
/// Build an ignore list from CIDR strings, optionally adding local IPs.
pub fn new(cidrs: &[String], ignoreself: bool) -> Result<Self> {
let mut networks: Vec<IpNet> = cidrs
.iter()
.map(|s| {
s.parse::<IpNet>()
.map_err(|_| crate::error::Error::config(format!("invalid CIDR: {s}")))
})
.collect::<Result<Vec<_>>>()?;
if ignoreself {
networks.extend(local_addresses());
}
Ok(Self { networks })
}
/// Check if an IP should be ignored (never banned).
pub fn is_ignored(&self, ip: &IpAddr) -> bool {
self.networks.iter().any(|net| net.contains(ip))
}
/// Number of networks in the list.
pub fn len(&self) -> usize {
self.networks.len()
}
/// Whether the list is empty.
pub fn is_empty(&self) -> bool {
self.networks.is_empty()
}
}
/// Detect local IP addresses on this machine.
///
/// Uses `getifaddrs(2)` to enumerate all network interfaces, which works
/// on Linux, macOS, and BSD. Falls back to loopback addresses only if
/// the syscall fails.
fn local_addresses() -> Vec<IpNet> {
let mut addrs = Vec::new();
// Always include loopback.
addrs.push(IpNet::from(IpAddr::V4(Ipv4Addr::LOCALHOST)));
addrs.push(IpNet::from(IpAddr::V6(Ipv6Addr::LOCALHOST)));
// Enumerate interface addresses via getifaddrs(2).
if let Ok(ifaddrs) = nix::ifaddrs::getifaddrs() {
for ifa in ifaddrs {
if let Some(addr) = ifa.address
&& let Some(ip) = sockaddr_to_ip(&addr)
{
addrs.push(IpNet::from(ip));
}
}
}
addrs
}
/// Extract an IP address from a `nix::sys::socket::SockaddrStorage`.
fn sockaddr_to_ip(addr: &nix::sys::socket::SockaddrStorage) -> Option<IpAddr> {
if let Some(v4) = addr.as_sockaddr_in() {
Some(IpAddr::V4(v4.ip()))
} else {
addr.as_sockaddr_in6().map(|v6| IpAddr::V6(v6.ip()))
}
}