Skip to content

Commit b230d50

Browse files
authored
fix: scope DNS port-53 exemption to configured resolver IPs (#32)
* fix: scope DNS port-53 exemption to configured resolver IPs Signed-off-by: danbugs <danilochiarlone@gmail.com> * fix: cache resolvers via OnceLock, blocklist takes precedence, use TEST-NET IPs in tests Signed-off-by: danbugs <danilochiarlone@gmail.com> --------- Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent 54cdcfc commit b230d50

1 file changed

Lines changed: 82 additions & 9 deletions

File tree

host/src/lib.rs

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -304,22 +304,47 @@ impl BlockList {
304304
}
305305
}
306306

307+
fn dns_resolvers() -> &'static HashSet<IpAddr> {
308+
static RESOLVERS: std::sync::OnceLock<HashSet<IpAddr>> = std::sync::OnceLock::new();
309+
RESOLVERS.get_or_init(|| {
310+
#[cfg(unix)]
311+
{
312+
let mut set = HashSet::new();
313+
if let Ok(contents) = std::fs::read_to_string("/etc/resolv.conf") {
314+
for line in contents.lines() {
315+
let line = line.trim();
316+
if let Some(rest) = line.strip_prefix("nameserver") {
317+
if let Some(ip_str) = rest.split_whitespace().next() {
318+
if let Ok(ip) = ip_str.parse::<IpAddr>() {
319+
set.insert(ip);
320+
}
321+
}
322+
}
323+
}
324+
}
325+
set
326+
}
327+
#[cfg(not(unix))]
328+
{
329+
HashSet::new()
330+
}
331+
})
332+
}
333+
307334
impl NetworkPolicy {
308335
fn check(&self, addr: &std::net::SocketAddr) -> Result<()> {
309336
match self {
310337
NetworkPolicy::AllowAll => Ok(()),
311338
NetworkPolicy::AllowList(al) => {
312-
if addr.port() == 53 || al.is_allowed(&addr.ip()) {
339+
if al.is_allowed(&addr.ip())
340+
|| (addr.port() == 53 && dns_resolvers().contains(&addr.ip()))
341+
{
313342
Ok(())
314343
} else {
315344
Err(anyhow!("network policy denies connection to {}", addr))
316345
}
317346
}
318347
NetworkPolicy::BlockList(bl) => {
319-
// DNS (port 53) is always allowed so the guest can resolve names.
320-
if addr.port() == 53 {
321-
return Ok(());
322-
}
323348
if bl.is_blocked(&addr.ip()) {
324349
Err(anyhow!("network policy denies connection to {}", addr))
325350
} else {
@@ -2737,6 +2762,50 @@ mod tests {
27372762
assert!(err.to_string().contains("network policy denies"), "{err}");
27382763
}
27392764

2765+
#[test]
2766+
fn test_port53_arbitrary_ip_blocked() {
2767+
// RFC5737 TEST-NET-2 address — guaranteed not in /etc/resolv.conf.
2768+
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
2769+
let policy = NetworkPolicy::AllowList(al);
2770+
let addr: std::net::SocketAddr = "198.51.100.99:53".parse().unwrap();
2771+
assert!(
2772+
policy.check(&addr).is_err(),
2773+
"port 53 to a non-resolver IP must be denied"
2774+
);
2775+
}
2776+
2777+
#[cfg(unix)]
2778+
#[test]
2779+
fn test_port53_real_resolver_allowed() {
2780+
let resolvers = dns_resolvers();
2781+
if resolvers.is_empty() {
2782+
eprintln!("skipping: no resolvers found in /etc/resolv.conf");
2783+
return;
2784+
}
2785+
let resolver_ip = *resolvers.iter().next().unwrap();
2786+
// Allowlist uses a TEST-NET IP that won't match the resolver.
2787+
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
2788+
let policy = NetworkPolicy::AllowList(al);
2789+
let addr = std::net::SocketAddr::new(resolver_ip, 53);
2790+
assert!(
2791+
policy.check(&addr).is_ok(),
2792+
"port 53 to a configured resolver ({}) must be allowed",
2793+
resolver_ip
2794+
);
2795+
}
2796+
2797+
#[test]
2798+
fn test_port53_blocklist_enforced() {
2799+
// RFC5737 TEST-NET-1 address — blocklist always wins.
2800+
let bl = BlockList::from_hosts(&["192.0.2.1"]).unwrap();
2801+
let policy = NetworkPolicy::BlockList(bl);
2802+
let addr: std::net::SocketAddr = "192.0.2.1:53".parse().unwrap();
2803+
assert!(
2804+
policy.check(&addr).is_err(),
2805+
"blocklisted IP must be denied even on port 53"
2806+
);
2807+
}
2808+
27402809
#[test]
27412810
fn allowlist_resolves_hostnames() {
27422811
let al = AllowList::from_hosts(&["localhost"]).unwrap();
@@ -2769,11 +2838,15 @@ mod tests {
27692838
}
27702839

27712840
#[test]
2772-
fn network_policy_blocklist_allows_dns() {
2773-
let bl = BlockList::from_hosts(&["1.2.3.4"]).unwrap();
2841+
fn network_policy_blocklist_denies_blocked_ip_on_port53() {
2842+
// RFC5737 TEST-NET-3 — blocklist always wins over DNS exemption.
2843+
let bl = BlockList::from_hosts(&["203.0.113.1"]).unwrap();
27742844
let policy = NetworkPolicy::BlockList(bl);
2775-
let addr: std::net::SocketAddr = "1.2.3.4:53".parse().unwrap();
2776-
assert!(policy.check(&addr).is_ok());
2845+
let addr: std::net::SocketAddr = "203.0.113.1:53".parse().unwrap();
2846+
assert!(
2847+
policy.check(&addr).is_err(),
2848+
"blocked IP must be denied even on port 53"
2849+
);
27772850
}
27782851

27792852
#[test]

0 commit comments

Comments
 (0)