diff --git a/src/ctx.rs b/src/ctx.rs index 644eb39..5ecb150 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -348,11 +348,15 @@ fn is_special_use_domain(qname: &str) -> bool { { return true; } - // 172.16-31.x.x (RFC 1918) — check each /16 reverse zone - for octet in 16..=31u8 { - let suffix = format!(".{}.172.in-addr.arpa", octet); - if qname.ends_with(&suffix) { - return true; + // 172.16-31.x.x (RFC 1918) — extract second octet from reverse name + if qname.ends_with(".172.in-addr.arpa") { + if let Some(octet_str) = qname + .strip_suffix(".172.in-addr.arpa") + .and_then(|s| s.rsplit('.').next()) + { + if let Ok(octet) = octet_str.parse::() { + return (16..=31).contains(&octet); + } } } return false; diff --git a/src/dnssec.rs b/src/dnssec.rs index b500e46..9910784 100644 --- a/src/dnssec.rs +++ b/src/dnssec.rs @@ -889,11 +889,15 @@ fn group_rrsets(records: &[DnsRecord]) -> Vec<(String, QueryType, Vec<&DnsRecord } fn is_rrsig_time_valid(expiration: u32, inception: u32) -> bool { + const FUDGE: u32 = 300; // 5-minute clock skew tolerance (BIND uses 300s) let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() as u32; - now >= inception && now <= expiration + // RFC 4034 §3.1.5: use serial number arithmetic for wrap-safe comparison + let inception_ok = now.wrapping_sub(inception) < (1u32 << 31); + let expiration_ok = expiration.wrapping_sub(now) < (1u32 << 31); + (inception_ok || now.wrapping_add(FUDGE) >= inception) && expiration_ok } // -- NSEC/NSEC3 denial of existence -- diff --git a/src/recursive.rs b/src/recursive.rs index 6f3a71e..cf922ee 100644 --- a/src/recursive.rs +++ b/src/recursive.rs @@ -31,13 +31,13 @@ fn dns_addr(ip: impl Into) -> SocketAddr { } pub fn reset_udp_state() { - UDP_DISABLED.store(false, Ordering::Relaxed); - UDP_FAILURES.store(0, Ordering::Relaxed); + UDP_DISABLED.store(false, Ordering::Release); + UDP_FAILURES.store(0, Ordering::Release); } /// Probe whether UDP works again. Called periodically from the network watch loop. pub async fn probe_udp(root_hints: &[SocketAddr]) { - if !UDP_DISABLED.load(Ordering::Relaxed) { + if !UDP_DISABLED.load(Ordering::Acquire) { return; } let hint = match root_hints.first() { @@ -405,18 +405,11 @@ fn resolve_ns_addrs_from_glue( cache_glue(&mut cache_w, response, ns_names); } for ns_name in ns_names { - let glue = glue_addrs_for(response, ns_name); - if !glue.is_empty() { - addrs.extend_from_slice(&glue); - break; - } + addrs.extend_from_slice(&glue_addrs_for(response, ns_name)); } if addrs.is_empty() { for ns_name in ns_names { addrs.extend(addrs_from_cache(cache, ns_name)); - if !addrs.is_empty() { - break; - } } } addrs @@ -586,7 +579,7 @@ async fn send_query(qname: &str, qtype: QueryType, server: SocketAddr) -> crate: } // If UDP has been detected as blocked, go TCP-first - if UDP_DISABLED.load(Ordering::Relaxed) { + if UDP_DISABLED.load(Ordering::Acquire) { return crate::forward::forward_tcp(&query, server, TCP_TIMEOUT).await; } @@ -597,13 +590,13 @@ async fn send_query(qname: &str, qtype: QueryType, server: SocketAddr) -> crate: } Ok(resp) => { // UDP works — reset failure counter - UDP_FAILURES.store(0, Ordering::Relaxed); + UDP_FAILURES.store(0, Ordering::Release); Ok(resp) } Err(e) => { - let fails = UDP_FAILURES.fetch_add(1, Ordering::Relaxed) + 1; - if fails >= UDP_FAIL_THRESHOLD && !UDP_DISABLED.load(Ordering::Relaxed) { - UDP_DISABLED.store(true, Ordering::Relaxed); + let fails = UDP_FAILURES.fetch_add(1, Ordering::AcqRel) + 1; + if fails >= UDP_FAIL_THRESHOLD && !UDP_DISABLED.load(Ordering::Acquire) { + UDP_DISABLED.store(true, Ordering::Release); info!( "send_query: {} consecutive UDP failures — switching to TCP-first", fails @@ -883,7 +876,7 @@ mod tests { #[tokio::test] async fn tcp_fallback_resolves_when_udp_blocked() { UDP_DISABLED.store(false, Ordering::Relaxed); - UDP_FAILURES.store(0, Ordering::Relaxed); + UDP_FAILURES.store(0, Ordering::Release); let server_addr = spawn_tcp_dns_server(|query| { let mut resp = DnsPacket::response_from(query, ResultCode::NOERROR); @@ -916,7 +909,7 @@ mod tests { /// The mock plays both roles (returns referral for NS queries, answer for A queries). #[tokio::test] async fn tcp_only_iterative_resolution() { - UDP_DISABLED.store(true, Ordering::Relaxed); // Skip UDP entirely for speed + UDP_DISABLED.store(true, Ordering::Release); // Skip UDP entirely for speed let server_addr = spawn_tcp_dns_server(|query| { let q = match query.questions.first() { @@ -964,7 +957,7 @@ mod tests { #[tokio::test] async fn tcp_fallback_handles_nxdomain() { UDP_DISABLED.store(false, Ordering::Relaxed); - UDP_FAILURES.store(0, Ordering::Relaxed); + UDP_FAILURES.store(0, Ordering::Release); let server_addr = spawn_tcp_dns_server(|query| { let mut resp = DnsPacket::response_from(query, ResultCode::NXDOMAIN); @@ -986,12 +979,12 @@ mod tests { #[tokio::test] async fn udp_auto_disable_resets() { - UDP_DISABLED.store(true, Ordering::Relaxed); + UDP_DISABLED.store(true, Ordering::Release); UDP_FAILURES.store(5, Ordering::Relaxed); reset_udp_state(); - assert!(!UDP_DISABLED.load(Ordering::Relaxed)); + assert!(!UDP_DISABLED.load(Ordering::Acquire)); assert_eq!(UDP_FAILURES.load(Ordering::Relaxed), 0); }