Skip to content

Commit bcdb7a5

Browse files
authored
dns search domains (#67)
* handle setting search domains * macos fixes * vector -> slice
1 parent 4326eb4 commit bcdb7a5

File tree

9 files changed

+116
-34
lines changed

9 files changed

+116
-34
lines changed

examples/userspace.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration};
2-
#[cfg(target_os = "macos")]
3-
use defguard_wireguard_rs::{WireguardApiUserspace, WireguardInterfaceApi};
41
use std::{
52
io::{stdin, stdout, Read, Write},
63
net::SocketAddr,
74
str::FromStr,
85
};
6+
7+
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration};
8+
#[cfg(target_os = "macos")]
9+
use defguard_wireguard_rs::{WireguardApiUserspace, WireguardInterfaceApi};
910
use x25519_dalek::{EphemeralSecret, PublicKey};
1011

1112
fn pause() {

src/lib.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,9 @@ mod wireguard_interface;
7474
#[macro_use]
7575
extern crate log;
7676

77-
use serde::{Deserialize, Serialize};
7877
use std::{fmt, process::Output};
7978

80-
use self::{
81-
error::WireguardInterfaceError,
82-
host::{Host, Peer},
83-
key::Key,
84-
net::IpAddrMask,
85-
};
86-
79+
use serde::{Deserialize, Serialize};
8780
// public re-exports
8881
pub use wgapi::WGApi;
8982
#[cfg(target_os = "freebsd")]
@@ -96,6 +89,13 @@ pub use wgapi_userspace::WireguardApiUserspace;
9689
pub use wgapi_windows::WireguardApiWindows;
9790
pub use wireguard_interface::WireguardInterfaceApi;
9891

92+
use self::{
93+
error::WireguardInterfaceError,
94+
host::{Host, Peer},
95+
key::Key,
96+
net::IpAddrMask,
97+
};
98+
9999
// Internet Protocol (IP) address variant.
100100
#[derive(Clone, Copy)]
101101
pub enum IpVersion {

src/utils.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,20 @@ use crate::{check_command_output_status, netlink, IpVersion};
1616
use crate::{Peer, WireguardInterfaceError};
1717

1818
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
19-
pub(crate) fn configure_dns(ifname: &str, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
19+
pub(crate) fn configure_dns(
20+
ifname: &str,
21+
dns: &[IpAddr],
22+
search_domains: &[&str],
23+
) -> Result<(), WireguardInterfaceError> {
2024
// Build the resolvconf command
2125
debug!("Setting up DNS");
2226
let mut cmd = Command::new("resolvconf");
23-
let args = ["-a", ifname, "-m", "0", "-x"];
27+
let mut args = vec!["-a", ifname, "-m", "0"];
28+
// Set the exclusive flag if no search domains are provided,
29+
// making the DNS servers a preferred route for any domain
30+
if search_domains.is_empty() {
31+
args.push("-x");
32+
}
2433
debug!("Executing command resolvconf with args: {args:?}");
2534
cmd.args(args);
2635

@@ -31,6 +40,10 @@ pub(crate) fn configure_dns(ifname: &str, dns: &[IpAddr]) -> Result<(), Wireguar
3140
debug!("Adding nameserver entry: {entry}");
3241
writeln!(stdin, "nameserver {entry}")?;
3342
}
43+
for domain in search_domains {
44+
debug!("Adding search domain entry: {domain}");
45+
writeln!(stdin, "search {domain}")?;
46+
}
3447
}
3548

3649
let status = child.wait().expect("Failed to wait for command");
@@ -65,7 +78,10 @@ fn network_services() -> Result<Vec<String>, IoError> {
6578
}
6679

6780
#[cfg(target_os = "macos")]
68-
pub(crate) fn configure_dns(dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
81+
pub(crate) fn configure_dns(
82+
dns: &[IpAddr],
83+
search_domains: &[&str],
84+
) -> Result<(), WireguardInterfaceError> {
6985
for service in network_services()? {
7086
debug!("Setting DNS entries for {service}");
7187
let mut cmd = Command::new("networksetup");
@@ -77,8 +93,23 @@ pub(crate) fn configure_dns(dns: &[IpAddr]) -> Result<(), WireguardInterfaceErro
7793
cmd.args(dns.iter().map(ToString::to_string));
7894
}
7995

96+
let status = cmd.status()?;
97+
if !status.success() {
98+
warn!("Command `networksetup` failed while setting DNS servers for {service}");
99+
}
100+
101+
// Set search domains, if empty, clear all search domains.
102+
debug!("Setting search domains for {service}");
103+
let mut cmd = Command::new("networksetup");
104+
cmd.arg("-setsearchdomains").arg(&service);
105+
if search_domains.is_empty() {
106+
// This clears all search domains.
107+
cmd.arg("Empty");
108+
} else {
109+
cmd.args(search_domains.iter());
110+
}
80111
if !cmd.status()?.success() {
81-
warn!("Command `networksetup` failed for {service}");
112+
warn!("Command `networksetup` failed while setting search domains for {service}");
82113
}
83114
}
84115

src/wgapi.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ impl WireguardInterfaceApi for WGApi {
8282
&self,
8383
config: &InterfaceConfiguration,
8484
dns: &[IpAddr],
85+
search_domains: &[&str],
8586
) -> Result<(), WireguardInterfaceError> {
86-
self.0.configure_interface(config, dns)
87+
self.0.configure_interface(config, dns, search_domains)
8788
}
8889

8990
fn remove_interface(&self) -> Result<(), WireguardInterfaceError> {
@@ -94,8 +95,12 @@ impl WireguardInterfaceApi for WGApi {
9495
self.0.configure_peer(peer)
9596
}
9697

97-
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
98-
self.0.configure_dns(dns)
98+
fn configure_dns(
99+
&self,
100+
dns: &[IpAddr],
101+
search_domains: &[&str],
102+
) -> Result<(), WireguardInterfaceError> {
103+
self.0.configure_dns(dns, search_domains)
99104
}
100105

101106
fn remove_peer(&self, peer_pubkey: &Key) -> Result<(), WireguardInterfaceError> {

src/wgapi_freebsd.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ impl WireguardInterfaceApi for WireguardApiFreebsd {
114114
/// It executes the `resolvconf` command with appropriate arguments to update DNS
115115
/// configurations for the specified Wireguard interface. The DNS entries are filtered
116116
/// for nameservers and search domains before being piped to the `resolvconf` command.
117-
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
117+
fn configure_dns(
118+
&self,
119+
dns: &[IpAddr],
120+
search_domains: &[&str],
121+
) -> Result<(), WireguardInterfaceError> {
118122
if dns.is_empty() {
119123
warn!("Received empty DNS server list. Skipping DNS configuration...");
120124
return Ok(());
@@ -123,7 +127,7 @@ impl WireguardInterfaceApi for WireguardApiFreebsd {
123127
"Configuring DNS for interface {}, using address: {dns:?}",
124128
self.ifname
125129
);
126-
configure_dns(&self.ifname, dns)?;
130+
configure_dns(&self.ifname, dns, search_domains)?;
127131
Ok(())
128132
}
129133
}

src/wgapi_linux.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ impl WireguardInterfaceApi for WireguardApiLinux {
125125
/// It executes the `resolvconf` command with appropriate arguments to update DNS
126126
/// configurations for the specified Wireguard interface. The DNS entries are filtered
127127
/// for nameservers and search domains before being piped to the `resolvconf` command.
128-
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
128+
fn configure_dns(
129+
&self,
130+
dns: &[IpAddr],
131+
search_domains: &[&str],
132+
) -> Result<(), WireguardInterfaceError> {
129133
if dns.is_empty() {
130134
warn!("Received empty DNS server list. Skipping DNS configuration...");
131135
return Ok(());
@@ -134,7 +138,7 @@ impl WireguardInterfaceApi for WireguardApiLinux {
134138
"Configuring DNS for interface {}, using address: {dns:?}",
135139
self.ifname
136140
);
137-
configure_dns(&self.ifname, dns)?;
141+
configure_dns(&self.ifname, dns, search_domains)?;
138142
Ok(())
139143
}
140144
}

src/wgapi_userspace.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,11 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
130130
///
131131
/// - Linux
132132
/// - FreeBSD
133-
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
133+
fn configure_dns(
134+
&self,
135+
dns: &[IpAddr],
136+
search_domains: &[&str],
137+
) -> Result<(), WireguardInterfaceError> {
134138
if dns.is_empty() {
135139
warn!("Received empty DNS server list. Skipping DNS configuration...");
136140
return Ok(());
@@ -142,11 +146,11 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
142146
// Setting DNS is not supported for macOS.
143147
#[cfg(target_os = "macos")]
144148
{
145-
configure_dns(dns)
149+
configure_dns(dns, search_domains)
146150
}
147151
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
148152
{
149-
configure_dns(&self.ifname, dns)
153+
configure_dns(&self.ifname, dns, search_domains)
150154
}
151155
}
152156

@@ -242,7 +246,7 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
242246
fs::remove_file(self.socket_path())?;
243247
#[cfg(target_os = "macos")]
244248
{
245-
configure_dns(&[])?;
249+
configure_dns(&[], &[])?;
246250
}
247251
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
248252
{

src/wgapi_windows.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl WireguardInterfaceApi for WireguardApiWindows {
4444
&self,
4545
config: &InterfaceConfiguration,
4646
dns: &[IpAddr],
47+
search_domains: &[&str],
4748
) -> Result<(), WireguardInterfaceError> {
4849
info!(
4950
"Configuring interface {} with config: {config:?}",
@@ -66,12 +67,30 @@ impl WireguardInterfaceApi for WireguardApiWindows {
6667
);
6768

6869
if !dns.is_empty() {
70+
// Format:
71+
// DNS = <IP>,<IP>
72+
// If search domains are present:
73+
// DNS = <IP>,<IP>,<domain>,<domain>
6974
let dns_addresses = format!(
70-
"\nDNS = {}",
75+
"\nDNS = {}{}",
76+
// DNS addresses part
7177
dns.iter()
7278
.map(|v| v.to_string())
7379
.collect::<Vec<String>>()
74-
.join(",")
80+
.join(","),
81+
// Search domains part, optional
82+
if !search_domains.is_empty() {
83+
format!(
84+
",{}",
85+
search_domains
86+
.iter()
87+
.map(|v| v.to_string())
88+
.collect::<Vec<String>>()
89+
.join(",")
90+
)
91+
} else {
92+
""
93+
}
7594
);
7695
wireguard_configuration.push_str(dns_addresses.as_str());
7796
}
@@ -278,7 +297,11 @@ impl WireguardInterfaceApi for WireguardApiWindows {
278297
Ok(host)
279298
}
280299

281-
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
300+
fn configure_dns(
301+
&self,
302+
dns: &[IpAddr],
303+
search_domains: &[&str],
304+
) -> Result<(), WireguardInterfaceError> {
282305
info!(
283306
"Configuring DNS for interface {}, using address: {dns:?}",
284307
self.ifname

src/wireguard_interface.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::{error::WireguardInterfaceError, Host, InterfaceConfiguration, IpAddrMask, Key, Peer};
21
use std::net::IpAddr;
32

3+
use crate::{error::WireguardInterfaceError, Host, InterfaceConfiguration, IpAddrMask, Key, Peer};
4+
45
/// API for managing a WireGuard interface.
56
///
67
/// Specific interface being managed is identified by name.
@@ -28,6 +29,7 @@ pub trait WireguardInterfaceApi {
2829
&self,
2930
config: &InterfaceConfiguration,
3031
dns: &[IpAddr],
32+
search_domains: &[&str],
3133
) -> Result<(), WireguardInterfaceError>;
3234

3335
/// Removes the WireGuard interface being managed.
@@ -48,19 +50,27 @@ pub trait WireguardInterfaceApi {
4850

4951
/// Sets the DNS configuration for the WireGuard interface.
5052
///
51-
/// This function takes a vector of DNS server addresses (`dns`) and configures the
52-
/// WireGuard interface to use these DNS servers. It is equivalent to specifying the
53+
/// This function takes a slice of DNS server addresses (`dns`) and search domains (`search_domains`) and configures the
54+
/// WireGuard interface to use them. If the search domain vector is empty it sets the "exclusive" flag making the DNS servers a
55+
/// preferred route for any domain. This method is equivalent to specifying the
5356
/// DNS section in a WireGuard configuration file and using `wg-quick` to apply the
5457
/// configuration.
5558
///
5659
/// # Arguments
5760
///
58-
/// * `dns` - A vector of [`IpAddr`](std::net::IpAddr) representing the DNS server addresses to be set for
61+
/// * `dns` - A slice of [`IpAddr`](std::net::IpAddr) representing the DNS server addresses to be set for
5962
/// the WireGuard interface.
63+
///
64+
/// * `search_domains` - A slice of [`&str`](std::str) representing the search domains to be set for
65+
/// the WireGuard interface.
6066
///
6167
/// # Returns
6268
///
6369
/// Returns `Ok(())` if the DNS configuration is successfully set, or an
6470
/// `Err(WireguardInterfaceError)` if there is an error during the configuration process.
65-
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError>;
71+
fn configure_dns(
72+
&self,
73+
dns: &[IpAddr],
74+
search_domains: &[&str],
75+
) -> Result<(), WireguardInterfaceError>;
6676
}

0 commit comments

Comments
 (0)