chore: sync konduit-platform
This commit is contained in:
226
konduit-platform/src/dns/linux.rs
Normal file
226
konduit-platform/src/dns/linux.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
use super::DnsState;
|
||||
use anyhow::{Context, Result};
|
||||
use std::net::IpAddr;
|
||||
use tracing::{info, warn};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// systemd-resolved D-Bus proxy
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[zbus::proxy(
|
||||
interface = "org.freedesktop.resolve1.Manager",
|
||||
default_service = "org.freedesktop.resolve1",
|
||||
default_path = "/org/freedesktop/resolve1"
|
||||
)]
|
||||
trait Resolve1Manager {
|
||||
// a(iay): array of (address_family, raw_bytes)
|
||||
#[zbus(name = "SetLinkDNS")]
|
||||
fn set_link_dns(
|
||||
&self,
|
||||
ifindex: i32,
|
||||
addresses: Vec<(i32, Vec<u8>)>,
|
||||
) -> zbus::Result<()>;
|
||||
|
||||
// a(sb): array of (domain, is_routing_domain)
|
||||
fn set_link_domains(
|
||||
&self,
|
||||
ifindex: i32,
|
||||
domains: Vec<(String, bool)>,
|
||||
) -> zbus::Result<()>;
|
||||
|
||||
fn revert_link(&self, ifindex: i32) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub async fn set_dns(
|
||||
interface: &str,
|
||||
if_index: Option<u32>,
|
||||
dns_servers: &[IpAddr],
|
||||
_assigned_ip: IpAddr,
|
||||
state: &mut DnsState,
|
||||
) -> Result<()> {
|
||||
if dns_servers.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Prefer in-process D-Bus (no polkit when caller has CAP_NET_ADMIN).
|
||||
// Fall back to spawning resolvectl for environments without systemd-resolved.
|
||||
let result = match if_index {
|
||||
Some(idx) => set_dns_dbus(idx, dns_servers).await,
|
||||
None => {
|
||||
warn!("Interface index unknown, falling back to resolvectl subprocess");
|
||||
set_dns_resolvectl(interface, dns_servers).await
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
state.configured = true;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"D-Bus DNS config failed ({:#}), trying resolvectl subprocess",
|
||||
e
|
||||
);
|
||||
match set_dns_resolvectl(interface, dns_servers).await {
|
||||
Ok(_) => {
|
||||
state.configured = true;
|
||||
}
|
||||
Err(e2) => {
|
||||
warn!("Could not configure DNS: {}", e2);
|
||||
warn!("VPN DNS servers: {:?}", dns_servers);
|
||||
warn!(
|
||||
"Configure manually: resolvectl dns {} {}",
|
||||
interface,
|
||||
dns_servers
|
||||
.iter()
|
||||
.map(|ip| ip.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn restore(
|
||||
interface: &str,
|
||||
if_index: Option<u32>,
|
||||
state: &mut DnsState,
|
||||
) -> Result<()> {
|
||||
if !state.configured {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("Restoring DNS settings...");
|
||||
|
||||
let result = match if_index {
|
||||
Some(idx) => revert_link_dbus(idx).await,
|
||||
None => revert_resolvectl(interface).await,
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
warn!("D-Bus DNS revert failed ({}), trying resolvectl", e);
|
||||
revert_resolvectl(interface).await.ok();
|
||||
}
|
||||
|
||||
state.configured = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// D-Bus implementation (no subprocess, no polkit with CAP_NET_ADMIN)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async fn set_dns_dbus(if_index: u32, dns_servers: &[IpAddr]) -> Result<()> {
|
||||
let conn = zbus::Connection::system()
|
||||
.await
|
||||
.context("Failed to connect to D-Bus system bus")?;
|
||||
|
||||
let proxy = Resolve1ManagerProxy::new(&conn)
|
||||
.await
|
||||
.context("Failed to create resolve1 proxy")?;
|
||||
|
||||
// Encode addresses as (address_family: i32, raw_bytes: Vec<u8>)
|
||||
let addrs: Vec<(i32, Vec<u8>)> = dns_servers
|
||||
.iter()
|
||||
.map(|ip| match ip {
|
||||
IpAddr::V4(v4) => (2i32, v4.octets().to_vec()),
|
||||
IpAddr::V6(v6) => (10i32, v6.octets().to_vec()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
proxy
|
||||
.set_link_dns(if_index as i32, addrs)
|
||||
.await
|
||||
.context("SetLinkDNS failed")?;
|
||||
|
||||
// Route all queries through the VPN link.
|
||||
// D-Bus SetLinkDomains takes (domain, is_routing_only): pass root domain "."
|
||||
// with is_routing_only=true, which systemd-resolved displays as "~." and marks
|
||||
// the link as DefaultRoute:yes. Passing "~." with true double-flags it and
|
||||
// results in a malformed domain that shows as DefaultRoute:no.
|
||||
proxy
|
||||
.set_link_domains(if_index as i32, vec![(".".to_string(), true)])
|
||||
.await
|
||||
.context("SetLinkDomains failed")?;
|
||||
|
||||
info!(
|
||||
"DNS configured via D-Bus for index {}: {}",
|
||||
if_index,
|
||||
dns_servers
|
||||
.iter()
|
||||
.map(|ip| ip.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn revert_link_dbus(if_index: u32) -> Result<()> {
|
||||
let conn = zbus::Connection::system()
|
||||
.await
|
||||
.context("Failed to connect to D-Bus system bus")?;
|
||||
|
||||
let proxy = Resolve1ManagerProxy::new(&conn)
|
||||
.await
|
||||
.context("Failed to create resolve1 proxy")?;
|
||||
|
||||
proxy
|
||||
.revert_link(if_index as i32)
|
||||
.await
|
||||
.context("RevertLink failed")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// resolvectl subprocess fallback
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async fn set_dns_resolvectl(interface: &str, dns_servers: &[IpAddr]) -> Result<()> {
|
||||
let dns_strs: Vec<String> = dns_servers.iter().map(|ip| ip.to_string()).collect();
|
||||
|
||||
let status = tokio::process::Command::new("resolvectl")
|
||||
.arg("dns")
|
||||
.arg(interface)
|
||||
.args(&dns_strs)
|
||||
.status()
|
||||
.await
|
||||
.context("resolvectl not found")?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("resolvectl dns failed");
|
||||
}
|
||||
|
||||
let status = tokio::process::Command::new("resolvectl")
|
||||
.args(["domain", interface, "~."])
|
||||
.status()
|
||||
.await
|
||||
.context("resolvectl domain failed")?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("resolvectl domain failed");
|
||||
}
|
||||
|
||||
info!(
|
||||
"DNS configured via resolvectl for {}: {}",
|
||||
interface,
|
||||
dns_strs.join(", ")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn revert_resolvectl(interface: &str) -> Result<()> {
|
||||
let _ = tokio::process::Command::new("resolvectl")
|
||||
.args(["revert", interface])
|
||||
.status()
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user