chore: sync konduit-platform

This commit is contained in:
Eugen Kaparulin
2026-06-08 09:11:15 +03:00
parent ee7898cfac
commit d3e6d89b6b
12 changed files with 2070 additions and 0 deletions

View 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(())
}