use std::path::{Path, PathBuf};
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 nm_conf = nm_conf_main();
let nm_available = NetworkManager::connect().is_ok();
let nm_rc_manager = nm_conf.rc_manager.unwrap_or_else(|| "symlink".to_string());
if nm_available
&& nm_conf.dns.as_deref() == Some("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 = nm_available
&& (file_owner == ResolvConfOwner::NetworkManager
|| nm_conf.dns.as_deref() == Some("dnsmasq")
|| matches!(nm_rc_manager.as_str(), "file" | "symlink" | "managed"));
if nm_owns_directly {
return Ok(Arc::new(NetworkManager::connect()?));
}
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()))
}
fn nm_conf_main() -> crate::platform::text_config::NmMainConf {
let mut conf = crate::platform::text_config::NmMainConf::default();
if let Ok(text) = std::fs::read_to_string("/etc/NetworkManager/NetworkManager.conf") {
conf = crate::platform::text_config::parse_nm_main_conf(&text);
}
if conf.dns.is_none()
&& let Ok(entries) = std::fs::read_dir("/etc/NetworkManager/conf.d")
{
let mut paths: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().map(|e| e == "conf").unwrap_or(false))
.collect();
paths.sort();
for path in paths {
if let Ok(text) = std::fs::read_to_string(&path) {
let parsed = crate::platform::text_config::parse_nm_main_conf(&text);
if parsed.dns.is_some() {
conf.dns = parsed.dns;
break;
}
if conf.rc_manager.is_none() && parsed.rc_manager.is_some() {
conf.rc_manager = parsed.rc_manager;
}
}
}
}
conf
}