connectban: never flood-ban loopback (127.0.0.1/::1) and add a connectban_exempt glob/cidr list

This commit is contained in:
Jean Chevronnet 2026-08-15 23:25:16 +00:00
parent 59b0439b79
commit 7d57e4ed34
3 changed files with 19 additions and 0 deletions

View file

@ -421,6 +421,8 @@ amu_target = both
# connectban_ipv4cidr = 32 # range width for IPv4 counting (default /32) # connectban_ipv4cidr = 32 # range width for IPv4 counting (default /32)
# connectban_ipv6cidr = 128 # range width for IPv6 counting (default /128) # connectban_ipv6cidr = 128 # range width for IPv6 counting (default /128)
# connectban_banmessage = Too many connections from your address # connectban_banmessage = Too many connections from your address
# connectban_exempt = 10.0.0.0/8 # never ban this glob/CIDR (repeatable);
# # loopback (127.0.0.0/8, ::1) is always exempt
# --- hashident: replace ident with a stable opaque token per IP --- # --- hashident: replace ident with a stable opaque token per IP ---
# hashident = yes # hashident = yes
# hashident_key = CHANGE_THIS_SECRET # HMAC key; makes the mapping unforgeable # hashident_key = CHANGE_THIS_SECRET # HMAC key; makes the mapping unforgeable

View file

@ -67,12 +67,25 @@ fn range_of(ip: IpAddr, v4cidr: u8, v6cidr: u8) -> (String, String) {
} }
} }
/// Whether `ip` matches a configured `connectban_exempt` glob/CIDR (repeatable).
fn connectban_exempt(s: &Server, ip: IpAddr) -> bool {
let ipstr = ip.to_string();
s.conf_all("connectban_exempt")
.iter()
.any(|m| crate::modules::connclass::ip_matches(m, &ipstr))
}
/// Record a new connection from `ip`, z-lining its range if it crosses the limit. /// Record a new connection from `ip`, z-lining its range if it crosses the limit.
/// No-op when connectban is disabled or still inside the boot-grace window. /// No-op when connectban is disabled or still inside the boot-grace window.
pub fn on_connect(s: &mut Server, ip: IpAddr) { pub fn on_connect(s: &mut Server, ip: IpAddr) {
if !s.conf_bool("connectban", false) { if !s.conf_bool("connectban", false) {
return; return;
} }
// never connect-ban loopback (local services, bridges, admin tooling all dial in
// over 127.0.0.1 / ::1) or an admin-configured exempt range
if ip.is_loopback() || connectban_exempt(s, ip) {
return;
}
let threshold = s.conf_num("connectban_threshold", 10u32).max(2); let threshold = s.conf_num("connectban_threshold", 10u32).max(2);
let v4 = s.conf_num("connectban_ipv4cidr", 32u8).clamp(1, 32); let v4 = s.conf_num("connectban_ipv4cidr", 32u8).clamp(1, 32);
let v6 = s.conf_num("connectban_ipv6cidr", 128u8).clamp(1, 128); let v6 = s.conf_num("connectban_ipv6cidr", 128u8).clamp(1, 128);

View file

@ -24,6 +24,10 @@ fn cfg(s: &Server) -> Option<(u32, u64)> {
/// Record a connection from `ip`; returns true when it exceeds the limit (the /// Record a connection from `ip`; returns true when it exceeds the limit (the
/// caller should refuse it). No-op → false when connflood is unconfigured. /// caller should refuse it). No-op → false when connflood is unconfigured.
pub fn over_limit(s: &mut Server, ip: IpAddr) -> bool { pub fn over_limit(s: &mut Server, ip: IpAddr) -> bool {
// loopback (local services / bridges / admin) is never connection-throttled
if ip.is_loopback() {
return false;
}
let Some((max, secs)) = cfg(s) else { let Some((max, secs)) = cfg(s) else {
return false; return false;
}; };