Skip to content

Commit fae2ca4

Browse files
committed
Fix DNS resolution for VPN and private network configurations
PR #167 introduced DNS filtering that excluded all private IP addresses (10.x, 172.16-31.x, 192.168.x, fc00::/7) assuming they would be unreachable from QEMU's slirp networking. However, this breaks VPN scenarios where private DNS servers are actually reachable. This change removes the overly aggressive private IP filtering, now only filtering out localhost and link-local addresses. Private network DNS servers are allowed through since they may be reachable (e.g., via VPN or air-gapped networks). If they're actually unreachable, DNS will fail naturally, which is better than prematurely filtering them out. Also downgraded the fallback warning from WARN to debug level since falling back to public DNS is a normal case, not an error condition. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: gursewak1997 <gursmangat@gmail.com>
1 parent 0fd3599 commit fae2ca4

File tree

1 file changed

+48
-16
lines changed

1 file changed

+48
-16
lines changed

crates/kit/src/run_ephemeral.rs

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -326,23 +326,21 @@ fn read_host_dns_servers() -> Option<Vec<String>> {
326326
Ok(content) => {
327327
let dns_servers = parse_resolv_conf(&content);
328328

329-
// Filter out localhost, link-local, and private network addresses
330-
// QEMU runs in user networking mode (slirp) inside a container, which cannot
331-
// reach private network addresses (10.x.x.x, 172.16-31.x.x, 192.168.x.x for IPv4,
332-
// fc00::/7 ULA for IPv6). These are often VPN-only DNS servers that won't work.
333-
// We'll fall back to public DNS (8.8.8.8, 1.1.1.1) which is more reliable.
329+
// Filter out localhost and link-local addresses only
330+
// Private network addresses (10.x, 172.16-31.x, 192.168.x, fc00::/7) are allowed
331+
// because they may be reachable from the container/VM (e.g., VPN DNS servers).
334332
let filtered_servers: Vec<String> = dns_servers
335333
.into_iter()
336334
.filter(|s| {
337335
// Try parsing as IPv4 first
338336
if let Ok(ip) = s.parse::<std::net::Ipv4Addr>() {
339-
// Reject loopback, link-local, and private addresses
340-
!ip.is_loopback() && !ip.is_link_local() && !ip.is_private()
337+
// Reject loopback and link-local addresses only
338+
!ip.is_loopback() && !ip.is_link_local()
341339
} else if let Ok(ip) = s.parse::<std::net::Ipv6Addr>() {
342-
// Reject loopback (::1), link-local (fe80::/10), ULA (fc00::/7), and multicast
343-
!ip.is_loopback() && !ip.is_multicast()
340+
// Reject loopback (::1), link-local (fe80::/10), and multicast
341+
!ip.is_loopback()
342+
&& !ip.is_multicast()
344343
&& !(ip.segments()[0] & 0xffc0 == 0xfe80) // link-local fe80::/10
345-
&& !(ip.segments()[0] & 0xfe00 == 0xfc00) // ULA fc00::/7 (private)
346344
} else {
347345
false // Reject invalid addresses
348346
}
@@ -585,12 +583,7 @@ fn prepare_run_command_with_temp(
585583
// QEMU's slirp reads /etc/resolv.conf from the container's network namespace,
586584
// which would otherwise contain unreachable bridge DNS servers (e.g., 169.254.1.1).
587585
// Using --dns properly configures /etc/resolv.conf in the container.
588-
let host_dns_servers = read_host_dns_servers().or_else(|| {
589-
// Fallback to public DNS if no usable DNS found in system configuration
590-
// This ensures DNS works even when host has broken/unreachable DNS config
591-
warn!("No usable DNS servers found in system configuration, falling back to public DNS (8.8.8.8, 1.1.1.1). This may not work in air-gapped environments.");
592-
Some(vec!["8.8.8.8".to_string(), "1.1.1.1".to_string()])
593-
});
586+
let host_dns_servers = read_host_dns_servers();
594587

595588
if let Some(ref dns) = host_dns_servers {
596589
debug!("Using DNS servers for ephemeral VM: {:?}", dns);
@@ -1475,3 +1468,42 @@ Options=
14751468

14761469
Ok(())
14771470
}
1471+
1472+
#[cfg(test)]
1473+
mod tests {
1474+
use super::*;
1475+
1476+
#[test]
1477+
fn test_parse_resolv_conf() {
1478+
let cases = vec![
1479+
// (input, expected)
1480+
("nameserver 8.8.8.8\n", vec!["8.8.8.8"]),
1481+
(
1482+
"nameserver 8.8.8.8\nnameserver 1.1.1.1\n",
1483+
vec!["8.8.8.8", "1.1.1.1"],
1484+
),
1485+
("# comment\nnameserver 8.8.8.8\n", vec!["8.8.8.8"]),
1486+
("nameserver 127.0.0.1\n", vec!["127.0.0.1"]),
1487+
("nameserver 169.254.1.1\n", vec!["169.254.1.1"]),
1488+
("nameserver 10.0.0.1\n", vec!["10.0.0.1"]),
1489+
(
1490+
"nameserver 2001:4860:4860::8888\n",
1491+
vec!["2001:4860:4860::8888"],
1492+
),
1493+
("nameserver ::1\n", vec!["::1"]),
1494+
("nameserver fe80::1\n", vec!["fe80::1"]),
1495+
("nameserver fc00::1\n", vec!["fc00::1"]),
1496+
("# only comments\n", vec![]),
1497+
("", vec![]),
1498+
];
1499+
1500+
for (input, expected) in cases {
1501+
assert_eq!(
1502+
parse_resolv_conf(input),
1503+
expected,
1504+
"failed for input: {:?}",
1505+
input
1506+
);
1507+
}
1508+
}
1509+
}

0 commit comments

Comments
 (0)