From 8f5b37bf8def1c15ab39b985ebe1913652b7cd2b Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:21:40 +0000 Subject: [PATCH] =?UTF-8?q?dnsbl:=20cap=20the=20number=20of=20blocklist=20?= =?UTF-8?q?zones=20checked=20per=20client=20at=2016=20=E2=80=94=20each=20z?= =?UTF-8?q?one=20is=20a=20serial=20blocking=20DNS=20lookup,=20so=20a=20lon?= =?UTF-8?q?g=20(mis)configured=20zone=20list=20could=20stall=20a=20connect?= =?UTF-8?q?ing=20client's=20registration=20for=20zones.len()*timeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/dnsbl.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/dnsbl.rs b/src/modules/dnsbl.rs index dc72109..fa91622 100644 --- a/src/modules/dnsbl.rs +++ b/src/modules/dnsbl.rs @@ -30,13 +30,19 @@ pub enum Outcome { Hit { zone: String, reply: Ipv4Addr }, } +/// Most blocklist zones consulted per connecting client (latency bound). +const MAX_ZONES: usize = 16; + /// Check `ip` against every blocklist `zone`; the first listing wins. Runs off the /// core thread (called from the resolver worker), so it may block on DNS. pub fn check(ip: IpAddr, zones: &[String], timeout: Duration) -> Outcome { if zones.is_empty() { return Outcome::Skipped; } - for zone in zones { + // Each zone is a serial blocking lookup, so total latency is bounded by the + // number checked × timeout; cap it so a long (mis)configured zone list can't + // stall a client's registration for a very long time. + for zone in zones.iter().take(MAX_ZONES) { let z = zone.trim().trim_end_matches('.'); let qname = format!("{}.{z}", resolver::reverse_labels(ip)); if let Some(reply) = resolver::a_lookup(&qname, timeout) {