use std::path::Path;
use std::sync::Arc;
use crate::error::Result;
use crate::platform::Backend;
use crate::platform::linux::direct::DirectResolvConf;
use crate::platform::linux::network_manager::NetworkManager;
use crate::platform::linux::resolvconf::{self, Resolvconf};
use crate::platform::linux::resolved::SystemdResolved;
const RESOLV_CONF: &str = crate::platform::linux::RESOLV_CONF_PATH;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ResolvConfOwner {
SystemdResolved,
NetworkManager,
Resolvconf,
Foreign,
}
fn resolv_conf_owner() -> ResolvConfOwner {
let path = Path::new(RESOLV_CONF);
let target = std::fs::read_link(path)
.map(|t| t.to_string_lossy().to_string())
.ok();
if let Some(target) = target {
if target.contains("systemd/resolve") {
return ResolvConfOwner::SystemdResolved;
}
if target.contains("NetworkManager") {
return ResolvConfOwner::NetworkManager;
}
if target.contains("resolvconf") {
return ResolvConfOwner::Resolvconf;
}
return ResolvConfOwner::Foreign;
}
if let Ok(bytes) = std::fs::read(path) {
let head = String::from_utf8_lossy(&bytes);
for line in head.lines().take(5) {
let line = line.to_ascii_lowercase();
if line.contains("generated by resolvconf") {
return ResolvConfOwner::Resolvconf;
}
if line.contains("generated by networkmanager") {
return ResolvConfOwner::NetworkManager;
}
if line.contains("generated by") && line.contains("resolved") {
return ResolvConfOwner::SystemdResolved;
}
}
}
ResolvConfOwner::Foreign
}
fn resolvconf_indicated() -> bool {
if resolvconf::probe().is_some() {
return true;
}
Path::new("/etc/resolvconf.conf").is_file()
}
pub(crate) fn select(owner: &str) -> Result<Arc<dyn Backend>> {
let file_owner = resolv_conf_owner();
let resolved = SystemdResolved::connect().ok();
let resolved_mode = resolved.as_ref().and_then(|backend| backend.mode_hint());
let resolved_owns = resolved.is_some()
&& (matches!(
resolved_mode.as_deref(),
Some("stub") | Some("managed") | Some("upstream") | Some("static")
) || file_owner == ResolvConfOwner::SystemdResolved);
if resolved_owns && let Some(backend) = resolved {
return Ok(Arc::new(backend));
}
let network_manager = NetworkManager::connect().ok();
let (nm_mode, nm_rc_manager) = network_manager
.as_ref()
.map(NetworkManager::dns_configuration)
.unwrap_or(("", ""));
if network_manager.is_some()
&& nm_mode == "systemd-resolved"
&& let Some(backend) = resolved
{
return Ok(Arc::new(backend));
}
if nm_rc_manager == "resolvconf"
&& let Some(probe) = resolvconf::probe()
{
return Ok(Arc::new(Resolvconf::new(probe, owner)));
}
let nm_owns_directly = network_manager.is_some()
&& (file_owner == ResolvConfOwner::NetworkManager
|| nm_mode == "dnsmasq"
|| matches!(nm_rc_manager, "file" | "symlink" | "managed"));
if nm_owns_directly {
return Ok(Arc::new(network_manager.expect("checked above")));
}
let resolvconf_owns = file_owner == ResolvConfOwner::Resolvconf || resolvconf_indicated();
if resolvconf_owns && let Some(probe) = resolvconf::probe() {
return Ok(Arc::new(Resolvconf::new(probe, owner)));
}
if let Some(backend) = resolved {
return Ok(Arc::new(backend));
}
Ok(Arc::new(DirectResolvConf::new()))
}