Allow configuring the DNS resolver

This commit is contained in:
Christopher Brull
2026-07-23 06:13:54 +00:00
parent 42b6b3dc0e
commit 577f097c03
7 changed files with 52 additions and 7 deletions
Generated
+1 -1
View File
@@ -1476,7 +1476,7 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "shuru-cli"
version = "0.6.2"
version = "0.6.3"
dependencies = [
"anyhow",
"base64",
+3
View File
@@ -44,6 +44,9 @@ shuru run --allow-net
# Restrict to specific hosts
shuru run --allow-net --allow-host api.openai.com --allow-host registry.npmjs.org
# Use a specific DNS resolver
shuru run --allow-net --dns-resolver 1.1.1.1 -- curl https://example.com
# Custom resources
shuru run --cpus 4 --memory 4096 --disk-size 8192 -- make -j4
```
+6
View File
@@ -1,3 +1,5 @@
use std::net::Ipv4Addr;
use clap::Parser;
#[derive(clap::Args)]
@@ -30,6 +32,10 @@ pub(crate) struct VmArgs {
#[arg(long)]
pub allow_net: bool,
/// IPv4 DNS resolver to use for guest queries (default: 8.8.8.8)
#[arg(long, value_name = "IP")]
pub dns_resolver: Option<Ipv4Addr>,
/// Allow mounts to write to host filesystem (required for :rw mounts)
#[arg(long)]
pub allow_host_writes: bool,
+4
View File
@@ -70,6 +70,10 @@ pub(crate) fn prepare_vm(
let proxy_config = if allow_net {
let mut proxy = cfg.to_proxy_config();
if let Some(resolver) = vm.dns_resolver {
proxy.dns_resolver = resolver;
}
// Merge --secret flags: NAME=ENV_VAR@host1,host2
for s in &vm.secret {
let (name, from, hosts) = parse_secret_flag(s)
+22 -1
View File
@@ -11,8 +11,10 @@ pub struct ExposeHostMapping {
}
/// Configuration for the proxy engine.
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone)]
pub struct ProxyConfig {
/// IPv4 DNS server used for upstream queries.
pub dns_resolver: Ipv4Addr,
/// Secrets to inject. Key is the env var name visible to the guest.
/// The guest gets a random placeholder token; the proxy substitutes
/// the real value only when the request targets an allowed host.
@@ -23,6 +25,17 @@ pub struct ProxyConfig {
pub expose_host: Vec<ExposeHostMapping>,
}
impl Default for ProxyConfig {
fn default() -> Self {
Self {
dns_resolver: Ipv4Addr::new(8, 8, 8, 8),
secrets: HashMap::new(),
network: NetworkConfig::default(),
expose_host: Vec::new(),
}
}
}
/// A secret that the proxy injects into HTTP requests.
#[derive(Debug, Clone)]
pub struct SecretConfig {
@@ -183,4 +196,12 @@ mod tests {
};
assert!(with_entries.has_allowlist());
}
#[test]
fn default_dns_resolver_is_google_public_dns() {
assert_eq!(
ProxyConfig::default().dns_resolver,
Ipv4Addr::new(8, 8, 8, 8)
);
}
}
+10 -5
View File
@@ -74,10 +74,11 @@ async fn resolve_query(
return build_refused_response(query_bytes);
}
// Resolve on the host by forwarding to system resolver
// Resolve on the host by forwarding to the configured upstream resolver.
let response_bytes = tokio::task::spawn_blocking({
let query = query_bytes.to_vec();
move || forward_to_system_resolver(&query)
let resolver = config.dns_resolver;
move || forward_to_system_resolver(&query, resolver)
})
.await??;
@@ -112,11 +113,15 @@ fn pin_resolved_ips(response: &[u8], allowed_ips: &AllowedIps) {
}
}
/// Forward a raw DNS query to the system resolver and return the raw response.
fn forward_to_system_resolver(query: &[u8]) -> anyhow::Result<Vec<u8>> {
/// Forward a raw DNS query over UDP to the configured IPv4 resolver on port 53
/// and return the raw response. This does not consult the host resolver API.
///
/// The name is intentional: using the host's resolver configuration remains
/// the desired long-term behavior for this operation.
fn forward_to_system_resolver(query: &[u8], resolver: Ipv4Addr) -> anyhow::Result<Vec<u8>> {
let sock = UdpSocket::bind("0.0.0.0:0")?;
sock.set_read_timeout(Some(std::time::Duration::from_secs(5)))?;
sock.send_to(query, "8.8.8.8:53")?;
sock.send_to(query, (resolver, 53))?;
let mut buf = [0u8; 4096];
let n = sock.recv(&mut buf)?;
Ok(buf[..n].to_vec())
+6
View File
@@ -2,6 +2,9 @@
Networking is **off by default**. Pass `--allow-net` to enable it.
Use `--dns-resolver IP` to select the IPv4 DNS server used for upstream guest
queries. The default is `8.8.8.8`.
## How It Works
All guest network traffic goes through a userspace proxy on the host (no NAT, no direct internet access). The proxy:
@@ -19,6 +22,9 @@ ICMP (ping) is not supported — only TCP traffic is proxied.
shuru run --allow-net -- sh -c 'apt-get install -y curl && curl https://example.com'
```
For environments that require a specific DNS server, select it with
`--dns-resolver`, for example `shuru run --allow-net --dns-resolver 10.0.0.53`.
Or set it in `shuru.json`:
```json