0.34.6_repo_cleanup

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 09:31:15 +02:00
1 parent ce1a90397c
commit 133d2f0f91
273 files changed
+5969 -6926

No files matched your search

+1 -12
View File
@@ -1,4 +1,3 @@
//! Bounded, fail-closed DNS/DDNS ACL address cache. No resolver runs in the request path.
use crate::config::Config;
use anyhow::{ensure, Result};
use arc_swap::ArcSwap;
@@ -36,8 +35,6 @@ pub(crate) fn matches(name: &str, ip: IpAddr) -> bool {
})
}
/// Strict ASCII DNS hostnames only (IDNs can be supplied in punycode).
/// A and AAAA results are eligible. IPs and CIDRs remain separate static rules.
pub(crate) fn hostname(value: &str) -> Result<String> {
let candidate = value.strip_suffix('.').unwrap_or(value);
ensure!(candidate.len() <= 253 && candidate.contains('.'), "ACL DNS name requires a fully qualified hostname");
@@ -53,8 +50,6 @@ pub(crate) fn hostname(value: &str) -> Result<String> {
Ok(candidate.to_ascii_lowercase())
}
/// Walk every ACL scope, including standalone host policies, ACL profiles,
/// inline vhost/location ACLs and Docker overrides. Limit process-wide DNS work.
pub(crate) fn configured_hostnames(config: &Config) -> Result<Vec<String>> {
let mut names = BTreeSet::new();
let mut inspect = |acl: Option<&crate::acl::Acl>| -> Result<()> {
@@ -63,7 +58,6 @@ pub(crate) fn configured_hostnames(config: &Config) -> Result<Vec<String>> {
if rule.network.parse::<ipnet::IpNet>().is_ok() || rule.network.parse::<IpAddr>().is_ok() {
continue;
}
// A slash is always a CIDR error, never a DNS name.
let name = hostname(&rule.network)?;
ensure!(rule.action == crate::acl::Action::Allow,
"dynamic DNS ACL rules only support allow; use static IP/CIDR for deny");
@@ -86,8 +80,6 @@ pub(crate) fn configured_hostnames(config: &Config) -> Result<Vec<String>> {
}
async fn resolve(name: String) -> (String, Option<Addresses>) {
// Keep the returned cache key owned independently of the DNS lookup future.
// The lookup result can retain the borrowed hostname until it is dropped.
let lookup_name = name.clone();
let answer = tokio::time::timeout(
Duration::from_secs(LOOKUP_SECONDS),
@@ -129,7 +121,6 @@ pub(crate) async fn poll_loop(
let snapshot = state.config.load();
let Ok(names) = configured_hostnames(&snapshot.envelope.config) else { continue; };
drop(snapshot);
// Resolve concurrently with hard bounds; write one immutable snapshot.
let results: HashMap<_, _> = tokio::select! {
_ = shutdown.cancelled() => break,
result = stream::iter(names.iter().cloned())
@@ -138,7 +129,6 @@ pub(crate) async fn poll_loop(
.filter_map(|(name, answer)| async move { answer.map(|value| (name, value)) })
.collect() => result,
};
// A concurrently changed configuration must not publish obsolete DNS data.
let current = state.config.load();
if configured_hostnames(&current.envelope.config).ok().as_ref() != Some(&names) {
CHANGED.notify_one();
@@ -146,9 +136,8 @@ pub(crate) async fn poll_loop(
}
CACHE.store(Arc::new(results));
}
// Do not retain permit decisions when the resolver lifecycle stops.
CACHE.store(Arc::new(HashMap::new()));
}
#[cfg(test)]
include!("dynamic/tests.rs");
include!("tests.rs");
-3
View File
@@ -13,19 +13,16 @@ mod tests {
let ip: IpAddr = "198.51.100.4".parse().unwrap();
let entry = Addresses { ips: vec![ip], valid_until: Instant::now() + Duration::from_secs(1) };
assert!(entry.valid_until > Instant::now());
// The request path accepts only cached exact addresses, never a subnet.
let mut cache = HashMap::new(); cache.insert(name.to_owned(), entry);
CACHE.store(Arc::new(cache));
assert!(matches(name, ip));
assert!(!matches(name, "198.51.100.5".parse().unwrap()));
// A formerly resolved IP cannot remain permitted after expiration.
let mut expired = HashMap::new();
expired.insert(name.to_owned(), Addresses {
ips: vec![ip], valid_until: Instant::now() - Duration::from_secs(1),
});
CACHE.store(Arc::new(expired));
assert!(!matches(name, ip));
// IPv6 addresses have the same exact-match and rotation semantics.
let new_ip: IpAddr = "2001:db8::8".parse().unwrap();
let mut rotated = HashMap::new();
rotated.insert(name.to_owned(), Addresses {
+8
View File
@@ -360,4 +360,12 @@ mod tests {
assert!(validate_profile_ids(Some("old"), &(0..8).map(|v| format!("profile{v}")).collect::<Vec<_>>()).is_err());
}
#[test]
fn percent_encoded_spaces_are_valid_request_paths() {
assert_eq!(
routing_path("/Ubuntu%2014.04%20LTS/").unwrap(),
"/Ubuntu 14.04 LTS/"
);
}
}