Skip to content

Commit 0bcbd38

Browse files
authored
Merge pull request #40 from hyperlight-dev/fix/windows-dns-resolvers
fix: AllowList DNS exemption for guest-hardcoded resolvers
2 parents 110b50d + bf35761 commit 0bcbd38

3 files changed

Lines changed: 74 additions & 22 deletions

File tree

host/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

host/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hyperlight-unikraft-host"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2021"
55
description = "Embedded Hyperlight host for running Unikraft unikernels"
66
license = "MIT OR Apache-2.0"

host/src/lib.rs

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,25 @@ impl BlockList {
304304
}
305305
}
306306

307+
/// DNS resolver IPs that the AllowList exempts on port 53.
308+
///
309+
/// Includes the host's configured resolvers (from `/etc/resolv.conf` on
310+
/// Unix, `ipconfig /all` on Windows) **plus** well-known public DNS
311+
/// servers (Google, Cloudflare) that the guest may hardcode in its own
312+
/// `/etc/resolv.conf`.
307313
fn dns_resolvers() -> &'static HashSet<IpAddr> {
308314
static RESOLVERS: std::sync::OnceLock<HashSet<IpAddr>> = std::sync::OnceLock::new();
309315
RESOLVERS.get_or_init(|| {
316+
let mut set = HashSet::new();
317+
// Well-known public DNS that the guest's initrd may hardcode.
318+
for ip in [
319+
"8.8.8.8", "8.8.4.4", // Google
320+
"1.1.1.1", "1.0.0.1", // Cloudflare
321+
] {
322+
set.insert(ip.parse::<IpAddr>().unwrap());
323+
}
310324
#[cfg(unix)]
311325
{
312-
let mut set = HashSet::new();
313326
if let Ok(contents) = std::fs::read_to_string("/etc/resolv.conf") {
314327
for line in contents.lines() {
315328
let line = line.trim();
@@ -322,12 +335,31 @@ fn dns_resolvers() -> &'static HashSet<IpAddr> {
322335
}
323336
}
324337
}
325-
set
326338
}
327-
#[cfg(not(unix))]
339+
#[cfg(windows)]
328340
{
329-
HashSet::new()
341+
if let Ok(output) = std::process::Command::new("ipconfig").arg("/all").output() {
342+
let text = String::from_utf8_lossy(&output.stdout);
343+
let mut in_dns_block = false;
344+
for line in text.lines() {
345+
let trimmed = line.trim();
346+
if let Some(rest) = trimmed.strip_prefix("DNS Servers") {
347+
in_dns_block = true;
348+
let value = rest.trim_start_matches(['.', ' ', ':']);
349+
if let Ok(ip) = value.parse::<IpAddr>() {
350+
set.insert(ip);
351+
}
352+
} else if in_dns_block {
353+
if let Ok(ip) = trimmed.parse::<IpAddr>() {
354+
set.insert(ip);
355+
} else {
356+
in_dns_block = false;
357+
}
358+
}
359+
}
360+
}
330361
}
362+
set
331363
})
332364
}
333365

@@ -2572,34 +2604,43 @@ mod tests {
25722604
}
25732605

25742606
#[test]
2575-
fn test_port53_arbitrary_ip_blocked() {
2576-
// RFC5737 TEST-NET-2 address — guaranteed not in /etc/resolv.conf.
2607+
fn allowlist_permits_dns_to_well_known_resolvers() {
2608+
// AllowList exempts port 53 to well-known public DNS and host
2609+
// resolvers so the guest's hardcoded nameservers (8.8.8.8 etc.)
2610+
// work even when the host uses different resolvers.
2611+
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
2612+
let policy = NetworkPolicy::AllowList(al);
2613+
for ip in ["8.8.8.8:53", "8.8.4.4:53", "1.1.1.1:53", "1.0.0.1:53"] {
2614+
let addr: std::net::SocketAddr = ip.parse().unwrap();
2615+
assert!(
2616+
policy.check(&addr).is_ok(),
2617+
"AllowList must permit DNS (port 53) to well-known resolver {}",
2618+
ip
2619+
);
2620+
}
2621+
}
2622+
2623+
#[test]
2624+
fn allowlist_denies_port53_to_arbitrary_ip() {
2625+
// Port 53 is NOT a blanket bypass — only known DNS resolvers
2626+
// are exempted. RFC5737 TEST-NET addresses are never resolvers.
25772627
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
25782628
let policy = NetworkPolicy::AllowList(al);
25792629
let addr: std::net::SocketAddr = "198.51.100.99:53".parse().unwrap();
25802630
assert!(
25812631
policy.check(&addr).is_err(),
2582-
"port 53 to a non-resolver IP must be denied"
2632+
"port 53 to an unknown IP must still be denied"
25832633
);
25842634
}
25852635

2586-
#[cfg(unix)]
25872636
#[test]
2588-
fn test_port53_real_resolver_allowed() {
2589-
let resolvers = dns_resolvers();
2590-
if resolvers.is_empty() {
2591-
eprintln!("skipping: no resolvers found in /etc/resolv.conf");
2592-
return;
2593-
}
2594-
let resolver_ip = *resolvers.iter().next().unwrap();
2595-
// Allowlist uses a TEST-NET IP that won't match the resolver.
2637+
fn allowlist_denies_non_dns_to_unlisted_ip() {
25962638
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
25972639
let policy = NetworkPolicy::AllowList(al);
2598-
let addr = std::net::SocketAddr::new(resolver_ip, 53);
2640+
let addr: std::net::SocketAddr = "198.51.100.99:443".parse().unwrap();
25992641
assert!(
2600-
policy.check(&addr).is_ok(),
2601-
"port 53 to a configured resolver ({}) must be allowed",
2602-
resolver_ip
2642+
policy.check(&addr).is_err(),
2643+
"AllowList must deny non-DNS traffic to unlisted IPs"
26032644
);
26042645
}
26052646

@@ -2658,6 +2699,17 @@ mod tests {
26582699
);
26592700
}
26602701

2702+
#[test]
2703+
fn blocklist_permits_dns_to_non_blocked_ip() {
2704+
let bl = BlockList::from_hosts(&["203.0.113.1"]).unwrap();
2705+
let policy = NetworkPolicy::BlockList(bl);
2706+
let addr: std::net::SocketAddr = "8.8.8.8:53".parse().unwrap();
2707+
assert!(
2708+
policy.check(&addr).is_ok(),
2709+
"BlockList must allow DNS to non-blocked IPs"
2710+
);
2711+
}
2712+
26612713
#[test]
26622714
fn blocklist_resolves_hostnames() {
26632715
let bl = BlockList::from_hosts(&["localhost"]).unwrap();

0 commit comments

Comments
 (0)