Skip to content

Commit 93f174d

Browse files
Fix resolvconf on debian 13 (#108)
* fix resolvconf * Update Cargo.toml * Update utils.rs * Update src/utils.rs Co-authored-by: Adam <[email protected]> --------- Co-authored-by: Adam <[email protected]>
1 parent 9af5e79 commit 93f174d

File tree

4 files changed

+109
-37
lines changed

4 files changed

+109
-37
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ windows = { version = "0.62", features = [
3838
"Win32_NetworkManagement_Ndis",
3939
"Win32_Networking_WinSock",
4040
"Win32_System_Com",
41-
]}
41+
] }
4242
wireguard-nt = "0.5.0"
4343

4444
[target.'cfg(target_os = "linux")'.dependencies]
@@ -49,6 +49,9 @@ netlink-packet-utils = "0.6"
4949
netlink-packet-wireguard = "0.2"
5050
netlink-sys = "0.8"
5151

52+
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
53+
regex = "1.12"
54+
5255
[features]
5356
default = ["serde"]
5457
check_dependencies = []

src/dependencies.rs

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::env;
22

3-
use crate::error::WireguardInterfaceError;
3+
use crate::{error::WireguardInterfaceError, utils::get_command_path};
44

55
#[cfg(target_os = "linux")]
66
const COMMANDS: [&str; 2] = ["resolvconf", "ip"];
@@ -16,37 +16,19 @@ const COMMANDS: [&str; 1] = ["resolvconf"];
1616

1717
pub(crate) fn check_external_dependencies() -> Result<(), WireguardInterfaceError> {
1818
debug!("Checking if all commands required by wireguard-rs are available");
19-
let paths = env::var_os("PATH").ok_or(WireguardInterfaceError::MissingDependency(
20-
"Environment variable `PATH` not found".into(),
21-
))?;
22-
23-
// Find the missing command to provide a more informative error message later.
24-
let missing = COMMANDS.iter().find(|cmd| {
25-
!env::split_paths(&paths).any(|dir| {
26-
trace!("Trying to find {cmd} in {dir:?}");
27-
match dir.join(cmd).try_exists() {
28-
Ok(true) => {
29-
debug!("{cmd} found in {dir:?}");
30-
true
31-
}
32-
Ok(false) => {
33-
trace!("{cmd} not found in {dir:?}");
34-
false
35-
}
36-
Err(err) => {
37-
warn!("Error while checking for {cmd} in {dir:?}: {err}");
38-
false
39-
}
40-
}
41-
})
19+
let paths = env::var_os("PATH").ok_or_else(|| {
20+
WireguardInterfaceError::MissingDependency("Environment variable `PATH` not found".into())
4221
});
4322

44-
if let Some(cmd) = missing {
45-
Err(WireguardInterfaceError::MissingDependency(format!(
46-
"Command `{cmd}` required by wireguard-rs couldn't be found. The following directories were checked: {paths:?}"
47-
)))
48-
} else {
49-
debug!("All commands required by wireguard-rs are available");
50-
Ok(())
51-
}
23+
// Find the missing command to provide a more informative error message later.
24+
let missing_command = COMMANDS
25+
.iter()
26+
.find(|cmd| get_command_path(cmd).map_or(true, |path_opt| path_opt.is_none()));
27+
28+
missing_command.map_or_else(|| {
29+
debug!("All commands required by wireguard-rs are available");
30+
Ok(())
31+
}, |cmd| Err(WireguardInterfaceError::MissingDependency(format!(
32+
"Command `{cmd}` required by wireguard-rs couldn't be found. The following directories were checked: {paths:?}"
33+
))))
5234
}

src/utils.rs

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
#[cfg(target_os = "macos")]
2-
use std::io::{BufRead, BufReader, Cursor, Error as IoError};
2+
use std::io::{Cursor, Error as IoError};
33
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))]
44
use std::net::{Ipv4Addr, Ipv6Addr};
5-
use std::net::{SocketAddr, ToSocketAddrs};
5+
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
6+
use std::path::Path;
67
#[cfg(target_os = "linux")]
78
use std::{collections::HashSet, fs::OpenOptions};
89
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
910
use std::{io::Write, process::Stdio};
11+
use std::{
12+
io::{BufRead, BufReader},
13+
net::{SocketAddr, ToSocketAddrs},
14+
};
1015
#[cfg(not(target_os = "windows"))]
1116
use std::{net::IpAddr, process::Command};
1217

@@ -24,6 +29,55 @@ use crate::{
2429
#[cfg(target_os = "linux")]
2530
use crate::{IpVersion, check_command_output_status, netlink};
2631

32+
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
33+
const IFACE_ORDER_PATH: &str = "/etc/resolvconf/interface-order";
34+
35+
/// Constructs the resolvconf interface name for manipulating DNS settings.
36+
/// Resolvconf may be symlinked to resolvectl on some systems that use systemd-resolved.
37+
/// In such cases there is no need to prefix the interface name and this function just returns
38+
/// the base interface name.
39+
///
40+
/// On other systems, especially those that don't use systemd-resolved (Debian 13)
41+
/// resolvconf may be a binary from the "resolvconf" package. In such cases, this function
42+
/// reads the interface-order file to find a highest priority interface prefix and constructs
43+
/// the full interface name prefixing the base interface name with the found prefix.
44+
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
45+
fn construct_resolvconf_ifname(base_ifname: &str) -> String {
46+
let iface_order = Path::new(IFACE_ORDER_PATH);
47+
if iface_order.exists() {
48+
// Check if resolvconf command is a symlink or a binary
49+
if let Ok(Some(resolvconf_path)) = get_command_path("resolvconf") {
50+
if let Ok(metadata) = std::fs::symlink_metadata(&resolvconf_path) {
51+
if !metadata.file_type().is_symlink() {
52+
// It's a binary, proceed to read interface_order file
53+
let iface_regex = regex::Regex::new(r"^([A-Za-z0-9-]+)\*$").unwrap();
54+
if let Ok(file) = std::fs::File::open(iface_order) {
55+
let reader = BufReader::new(file);
56+
if let Some(constructed_ifname) = reader.lines().map_while(Result::ok).find_map(|line| {
57+
let iface = line.trim();
58+
iface_regex.captures(iface).and_then(|captures| {
59+
captures.get(1).map(|matched_iface| {
60+
// Output format: <highest_priority_iface>.<base_ifname>
61+
let constructed_ifname =
62+
format!("{}.{base_ifname}", matched_iface.as_str());
63+
debug!(
64+
"Constructed interface name from interface_order: {constructed_ifname}"
65+
);
66+
constructed_ifname
67+
})
68+
})
69+
}) {
70+
return constructed_ifname;
71+
}
72+
}
73+
}
74+
}
75+
}
76+
}
77+
78+
base_ifname.into()
79+
}
80+
2781
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
2882
pub(crate) fn configure_dns(
2983
ifname: &str,
@@ -36,7 +90,8 @@ pub(crate) fn configure_dns(
3690
domains: {search_domains:?}"
3791
);
3892
let mut cmd = Command::new("resolvconf");
39-
let mut args = vec!["-a", ifname, "-m", "0"];
93+
let ifname = construct_resolvconf_ifname(ifname);
94+
let mut args = vec!["-a", &ifname, "-m", "0"];
4095
// Set the exclusive flag if no search domains are provided,
4196
// making the DNS servers a preferred route for any domain
4297
if search_domains.is_empty() {
@@ -170,7 +225,8 @@ pub(crate) fn configure_dns(
170225
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
171226
pub(crate) fn clear_dns(ifname: &str) -> Result<(), WireguardInterfaceError> {
172227
debug!("Removing DNS configuration for interface {ifname}");
173-
let args = ["-d", ifname, "-f"];
228+
let ifname = construct_resolvconf_ifname(ifname);
229+
let args = ["-d", &ifname, "-f"];
174230
debug!("Executing resolvconf with args: {args:?}");
175231
let mut cmd = Command::new("resolvconf");
176232
let output = cmd.args(args).output()?;
@@ -537,3 +593,33 @@ pub(crate) fn resolve(addr: &str) -> Result<SocketAddr, WireguardInterfaceError>
537593
.next()
538594
.ok_or_else(error)
539595
}
596+
597+
pub(crate) fn get_command_path(
598+
command: &str,
599+
) -> Result<Option<std::path::PathBuf>, WireguardInterfaceError> {
600+
use std::env;
601+
602+
debug!("Searching for command {command} in PATH");
603+
let paths = env::var_os("PATH").ok_or_else(|| {
604+
WireguardInterfaceError::MissingDependency("Environment variable `PATH` not found".into())
605+
})?;
606+
debug!("PATH variable: {paths:?}");
607+
608+
Ok(env::split_paths(&paths).find_map(|dir| {
609+
let full_path = dir.join(command);
610+
match full_path.try_exists() {
611+
Ok(true) => {
612+
debug!("Command {command} found in {dir:?}");
613+
Some(full_path)
614+
}
615+
Ok(false) => {
616+
debug!("Command {command} not found in {dir:?}");
617+
None
618+
}
619+
Err(err) => {
620+
warn!("Error while checking for {command} in {dir:?}: {err}");
621+
None
622+
}
623+
}
624+
}))
625+
}

0 commit comments

Comments
 (0)