Skip to content

Commit f693004

Browse files
committed
Fix DNS resolution in ephemeral guests
Configure QEMU user-mode networking to use host DNS servers from /etc/resolv.conf instead of the default 10.0.2.3, which doesn't work when QEMU runs inside containers. Signed-off-by: gursewak1997 <[email protected]>
1 parent b664ecb commit f693004

File tree

3 files changed

+115
-18
lines changed

3 files changed

+115
-18
lines changed

crates/kit/src/qemu.rs

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,38 @@ use nix::sys::socket::{accept, bind, getsockname, socket, AddressFamily, SockFla
2222
use tracing::{debug, trace, warn};
2323
use vsock::VsockAddr;
2424

25+
/// Read DNS servers from /etc/resolv.conf
26+
/// Returns a vector of DNS server IP addresses, or None if unable to read/parse
27+
pub fn read_dns_servers() -> Option<Vec<String>> {
28+
let resolv_conf = match std::fs::read_to_string("/etc/resolv.conf") {
29+
Ok(content) => content,
30+
Err(e) => {
31+
debug!("Failed to read /etc/resolv.conf: {}", e);
32+
return None;
33+
}
34+
};
35+
36+
let mut dns_servers = Vec::new();
37+
for line in resolv_conf.lines() {
38+
let line = line.trim();
39+
// Parse lines like "nameserver 8.8.8.8" or "nameserver 2001:4860:4860::8888"
40+
if let Some(server) = line.strip_prefix("nameserver ") {
41+
let server = server.trim();
42+
if !server.is_empty() {
43+
dns_servers.push(server.to_string());
44+
}
45+
}
46+
}
47+
48+
if dns_servers.is_empty() {
49+
debug!("No DNS servers found in /etc/resolv.conf");
50+
None
51+
} else {
52+
debug!("Found DNS servers: {:?}", dns_servers);
53+
Some(dns_servers)
54+
}
55+
}
56+
2557
/// The device for vsock allocation
2658
pub const VHOST_VSOCK: &str = "/dev/vhost-vsock";
2759

@@ -80,12 +112,17 @@ pub enum NetworkMode {
80112
User {
81113
/// Port forwarding rules: "tcp::2222-:22" format
82114
hostfwd: Vec<String>,
115+
/// DNS servers to use (if None, QEMU's default 10.0.2.3 will be used)
116+
dns_servers: Option<Vec<String>>,
83117
},
84118
}
85119

86120
impl Default for NetworkMode {
87121
fn default() -> Self {
88-
NetworkMode::User { hostfwd: vec![] }
122+
NetworkMode::User {
123+
hostfwd: vec![],
124+
dns_servers: None,
125+
}
89126
}
90127
}
91128

@@ -322,8 +359,13 @@ impl QemuConfig {
322359
pub fn enable_ssh_access(&mut self, host_port: Option<u16>) -> &mut Self {
323360
let port = host_port.unwrap_or(2222); // Default to port 2222 on host
324361
let hostfwd = format!("tcp::{}-:22", port); // Forward host port to guest port 22
362+
// Preserve existing DNS servers if any
363+
let dns_servers = match &self.network_mode {
364+
NetworkMode::User { dns_servers, .. } => dns_servers.clone(),
365+
};
325366
self.network_mode = NetworkMode::User {
326367
hostfwd: vec![hostfwd],
368+
dns_servers,
327369
};
328370
self
329371
}
@@ -522,23 +564,29 @@ fn spawn(
522564

523565
// Configure network (only User mode supported now)
524566
match &config.network_mode {
525-
NetworkMode::User { hostfwd } => {
526-
if hostfwd.is_empty() {
527-
cmd.args([
528-
"-netdev",
529-
"user,id=net0",
530-
"-device",
531-
"virtio-net-pci,netdev=net0",
532-
]);
533-
} else {
534-
let hostfwd_arg = format!("user,id=net0,hostfwd={}", hostfwd.join(",hostfwd="));
535-
cmd.args([
536-
"-netdev",
537-
&hostfwd_arg,
538-
"-device",
539-
"virtio-net-pci,netdev=net0",
540-
]);
567+
NetworkMode::User { hostfwd, dns_servers } => {
568+
let mut netdev_parts = vec!["user".to_string(), "id=net0".to_string()];
569+
570+
// Add DNS servers if specified
571+
if let Some(dns_list) = dns_servers {
572+
if !dns_list.is_empty() {
573+
let dns_arg = format!("dns={}", dns_list.join(","));
574+
netdev_parts.push(dns_arg);
575+
}
541576
}
577+
578+
// Add port forwarding rules
579+
for fwd in hostfwd {
580+
netdev_parts.push(format!("hostfwd={}", fwd));
581+
}
582+
583+
let netdev_arg = netdev_parts.join(",");
584+
cmd.args([
585+
"-netdev",
586+
&netdev_arg,
587+
"-device",
588+
"virtio-net-pci,netdev=net0",
589+
]);
542590
}
543591
}
544592

crates/kit/src/run_ephemeral.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@ pub struct RunEphemeralOpts {
283283

284284
#[clap(long = "karg", help = "Additional kernel command line arguments")]
285285
pub kernel_args: Vec<String>,
286+
287+
/// Host DNS servers (read on host, passed to container for QEMU configuration)
288+
/// Not a CLI option - populated automatically from host's /etc/resolv.conf
289+
#[serde(skip_serializing_if = "Option::is_none")]
290+
pub host_dns_servers: Option<Vec<String>>,
286291
}
287292

288293
/// Launch privileged container with QEMU+KVM for ephemeral VM, spawning as subprocess.
@@ -499,8 +504,20 @@ fn prepare_run_command_with_temp(
499504
cmd.args(["-v", &format!("{}:/run/systemd-units:ro", units_dir)]);
500505
}
501506

507+
// Read host DNS servers before entering container
508+
// QEMU's slirp will use these instead of container's unreachable bridge DNS servers
509+
let host_dns_servers = crate::qemu::read_dns_servers();
510+
if let Some(ref dns) = host_dns_servers {
511+
debug!("Read host DNS servers: {:?}", dns);
512+
} else {
513+
debug!("No DNS servers found in host /etc/resolv.conf, QEMU will use default 10.0.2.3");
514+
}
515+
502516
// Pass configuration as JSON via BCK_CONFIG environment variable
503-
let config = serde_json::to_string(&opts).unwrap();
517+
// Include host DNS servers in the config so they're available inside the container
518+
let mut opts_with_dns = opts.clone();
519+
opts_with_dns.host_dns_servers = host_dns_servers;
520+
let config = serde_json::to_string(&opts_with_dns).unwrap();
504521
cmd.args(["-e", &format!("BCK_CONFIG={config}")]);
505522

506523
// Handle --execute output files and virtio-serial devices
@@ -1229,13 +1246,44 @@ Options=
12291246
qemu_config.add_virtio_serial_out("org.bcvk.journal", "/run/journal.log".to_string(), false);
12301247
debug!("Added virtio-serial device for journal streaming to /run/journal.log");
12311248

1249+
// Configure DNS servers from host's /etc/resolv.conf
1250+
// This fixes DNS resolution issues when QEMU runs inside containers.
1251+
// QEMU's slirp reads /etc/resolv.conf from the container's network namespace,
1252+
// which contains unreachable bridge DNS servers (e.g., 169.254.1.1, 10.x.y.z).
1253+
// By passing host DNS servers via QEMU's dns= parameter, we bypass slirp's
1254+
// resolv.conf reading and use the host's actual DNS servers.
1255+
let dns_servers = opts.host_dns_servers.clone();
1256+
if let Some(ref dns) = dns_servers {
1257+
debug!("Using host DNS servers (from host /etc/resolv.conf): {:?}", dns);
1258+
} else {
1259+
debug!("No host DNS servers available, QEMU will use default 10.0.2.3");
1260+
}
1261+
1262+
// Configure DNS servers in network mode before enabling SSH (if needed)
1263+
if let Some(ref dns) = dns_servers {
1264+
match &mut qemu_config.network_mode {
1265+
crate::qemu::NetworkMode::User { dns_servers: dns_opt, .. } => {
1266+
*dns_opt = Some(dns.clone());
1267+
}
1268+
}
1269+
}
1270+
12321271
if opts.common.ssh_keygen {
12331272
qemu_config.enable_ssh_access(None); // Use default port 2222
12341273
debug!("Enabled SSH port forwarding: host port 2222 -> guest port 22");
12351274

12361275
// We need to extract the public key from the SSH credential to inject it via SMBIOS
12371276
// For now, the credential is already being passed via kernel cmdline
12381277
// TODO: Add proper SMBIOS credential injection if needed
1278+
} else {
1279+
// Even without SSH, ensure DNS is configured if we have DNS servers
1280+
if let Some(dns) = dns_servers {
1281+
match &mut qemu_config.network_mode {
1282+
crate::qemu::NetworkMode::User { dns_servers: dns_opt, .. } => {
1283+
*dns_opt = Some(dns);
1284+
}
1285+
}
1286+
}
12391287
}
12401288

12411289
// Set main virtiofs configuration for root filesystem (will be spawned by QEMU)

crates/kit/src/to_disk.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ pub fn run(opts: ToDiskOpts) -> Result<()> {
430430
// - Attach target disk via virtio-blk
431431
// - Disable networking (using local storage only)
432432
let ephemeral_opts = RunEphemeralOpts {
433+
host_dns_servers: None,
433434
image: opts.get_installer_image().to_string(),
434435
common: common_opts,
435436
podman: crate::run_ephemeral::CommonPodmanOptions {

0 commit comments

Comments
 (0)