Skip to content

Commit

Permalink
dns search domains (#67)
Browse files Browse the repository at this point in the history
* handle setting search domains

* macos fixes

* vector -> slice
  • Loading branch information
t-aleksander authored Sep 18, 2024
1 parent 4326eb4 commit bcdb7a5
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 34 deletions.
7 changes: 4 additions & 3 deletions examples/userspace.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration};
#[cfg(target_os = "macos")]
use defguard_wireguard_rs::{WireguardApiUserspace, WireguardInterfaceApi};
use std::{
io::{stdin, stdout, Read, Write},
net::SocketAddr,
str::FromStr,
};

use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration};
#[cfg(target_os = "macos")]
use defguard_wireguard_rs::{WireguardApiUserspace, WireguardInterfaceApi};
use x25519_dalek::{EphemeralSecret, PublicKey};

fn pause() {
Expand Down
16 changes: 8 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,9 @@ mod wireguard_interface;
#[macro_use]
extern crate log;

use serde::{Deserialize, Serialize};
use std::{fmt, process::Output};

use self::{
error::WireguardInterfaceError,
host::{Host, Peer},
key::Key,
net::IpAddrMask,
};

use serde::{Deserialize, Serialize};
// public re-exports
pub use wgapi::WGApi;
#[cfg(target_os = "freebsd")]
Expand All @@ -96,6 +89,13 @@ pub use wgapi_userspace::WireguardApiUserspace;
pub use wgapi_windows::WireguardApiWindows;
pub use wireguard_interface::WireguardInterfaceApi;

use self::{
error::WireguardInterfaceError,
host::{Host, Peer},
key::Key,
net::IpAddrMask,
};

// Internet Protocol (IP) address variant.
#[derive(Clone, Copy)]
pub enum IpVersion {
Expand Down
39 changes: 35 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ use crate::{check_command_output_status, netlink, IpVersion};
use crate::{Peer, WireguardInterfaceError};

#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
pub(crate) fn configure_dns(ifname: &str, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
pub(crate) fn configure_dns(
ifname: &str,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
// Build the resolvconf command
debug!("Setting up DNS");
let mut cmd = Command::new("resolvconf");
let args = ["-a", ifname, "-m", "0", "-x"];
let mut args = vec!["-a", ifname, "-m", "0"];
// Set the exclusive flag if no search domains are provided,
// making the DNS servers a preferred route for any domain
if search_domains.is_empty() {
args.push("-x");
}
debug!("Executing command resolvconf with args: {args:?}");
cmd.args(args);

Expand All @@ -31,6 +40,10 @@ pub(crate) fn configure_dns(ifname: &str, dns: &[IpAddr]) -> Result<(), Wireguar
debug!("Adding nameserver entry: {entry}");
writeln!(stdin, "nameserver {entry}")?;
}
for domain in search_domains {
debug!("Adding search domain entry: {domain}");
writeln!(stdin, "search {domain}")?;
}
}

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

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

let status = cmd.status()?;
if !status.success() {
warn!("Command `networksetup` failed while setting DNS servers for {service}");
}

// Set search domains, if empty, clear all search domains.
debug!("Setting search domains for {service}");
let mut cmd = Command::new("networksetup");
cmd.arg("-setsearchdomains").arg(&service);
if search_domains.is_empty() {
// This clears all search domains.
cmd.arg("Empty");
} else {
cmd.args(search_domains.iter());
}
if !cmd.status()?.success() {
warn!("Command `networksetup` failed for {service}");
warn!("Command `networksetup` failed while setting search domains for {service}");
}
}

Expand Down
11 changes: 8 additions & 3 deletions src/wgapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ impl WireguardInterfaceApi for WGApi {
&self,
config: &InterfaceConfiguration,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
self.0.configure_interface(config, dns)
self.0.configure_interface(config, dns, search_domains)
}

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

fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
self.0.configure_dns(dns)
fn configure_dns(
&self,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
self.0.configure_dns(dns, search_domains)
}

fn remove_peer(&self, peer_pubkey: &Key) -> Result<(), WireguardInterfaceError> {
Expand Down
8 changes: 6 additions & 2 deletions src/wgapi_freebsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ impl WireguardInterfaceApi for WireguardApiFreebsd {
/// It executes the `resolvconf` command with appropriate arguments to update DNS
/// configurations for the specified Wireguard interface. The DNS entries are filtered
/// for nameservers and search domains before being piped to the `resolvconf` command.
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
fn configure_dns(
&self,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
if dns.is_empty() {
warn!("Received empty DNS server list. Skipping DNS configuration...");
return Ok(());
Expand All @@ -123,7 +127,7 @@ impl WireguardInterfaceApi for WireguardApiFreebsd {
"Configuring DNS for interface {}, using address: {dns:?}",
self.ifname
);
configure_dns(&self.ifname, dns)?;
configure_dns(&self.ifname, dns, search_domains)?;
Ok(())
}
}
8 changes: 6 additions & 2 deletions src/wgapi_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,11 @@ impl WireguardInterfaceApi for WireguardApiLinux {
/// It executes the `resolvconf` command with appropriate arguments to update DNS
/// configurations for the specified Wireguard interface. The DNS entries are filtered
/// for nameservers and search domains before being piped to the `resolvconf` command.
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
fn configure_dns(
&self,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
if dns.is_empty() {
warn!("Received empty DNS server list. Skipping DNS configuration...");
return Ok(());
Expand All @@ -134,7 +138,7 @@ impl WireguardInterfaceApi for WireguardApiLinux {
"Configuring DNS for interface {}, using address: {dns:?}",
self.ifname
);
configure_dns(&self.ifname, dns)?;
configure_dns(&self.ifname, dns, search_domains)?;
Ok(())
}
}
12 changes: 8 additions & 4 deletions src/wgapi_userspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
///
/// - Linux
/// - FreeBSD
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
fn configure_dns(
&self,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
if dns.is_empty() {
warn!("Received empty DNS server list. Skipping DNS configuration...");
return Ok(());
Expand All @@ -142,11 +146,11 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
// Setting DNS is not supported for macOS.
#[cfg(target_os = "macos")]
{
configure_dns(dns)
configure_dns(dns, search_domains)
}
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))]
{
configure_dns(&self.ifname, dns)
configure_dns(&self.ifname, dns, search_domains)
}
}

Expand Down Expand Up @@ -242,7 +246,7 @@ impl WireguardInterfaceApi for WireguardApiUserspace {
fs::remove_file(self.socket_path())?;
#[cfg(target_os = "macos")]
{
configure_dns(&[])?;
configure_dns(&[], &[])?;
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
Expand Down
29 changes: 26 additions & 3 deletions src/wgapi_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl WireguardInterfaceApi for WireguardApiWindows {
&self,
config: &InterfaceConfiguration,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
info!(
"Configuring interface {} with config: {config:?}",
Expand All @@ -66,12 +67,30 @@ impl WireguardInterfaceApi for WireguardApiWindows {
);

if !dns.is_empty() {
// Format:
// DNS = <IP>,<IP>
// If search domains are present:
// DNS = <IP>,<IP>,<domain>,<domain>
let dns_addresses = format!(
"\nDNS = {}",
"\nDNS = {}{}",
// DNS addresses part
dns.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(",")
.join(","),
// Search domains part, optional
if !search_domains.is_empty() {
format!(
",{}",
search_domains
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(",")
)
} else {
""
}
);
wireguard_configuration.push_str(dns_addresses.as_str());
}
Expand Down Expand Up @@ -278,7 +297,11 @@ impl WireguardInterfaceApi for WireguardApiWindows {
Ok(host)
}

fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError> {
fn configure_dns(
&self,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError> {
info!(
"Configuring DNS for interface {}, using address: {dns:?}",
self.ifname
Expand Down
20 changes: 15 additions & 5 deletions src/wireguard_interface.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{error::WireguardInterfaceError, Host, InterfaceConfiguration, IpAddrMask, Key, Peer};
use std::net::IpAddr;

use crate::{error::WireguardInterfaceError, Host, InterfaceConfiguration, IpAddrMask, Key, Peer};

/// API for managing a WireGuard interface.
///
/// Specific interface being managed is identified by name.
Expand Down Expand Up @@ -28,6 +29,7 @@ pub trait WireguardInterfaceApi {
&self,
config: &InterfaceConfiguration,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError>;

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

/// Sets the DNS configuration for the WireGuard interface.
///
/// This function takes a vector of DNS server addresses (`dns`) and configures the
/// WireGuard interface to use these DNS servers. It is equivalent to specifying the
/// This function takes a slice of DNS server addresses (`dns`) and search domains (`search_domains`) and configures the
/// WireGuard interface to use them. If the search domain vector is empty it sets the "exclusive" flag making the DNS servers a
/// preferred route for any domain. This method is equivalent to specifying the
/// DNS section in a WireGuard configuration file and using `wg-quick` to apply the
/// configuration.
///
/// # Arguments
///
/// * `dns` - A vector of [`IpAddr`](std::net::IpAddr) representing the DNS server addresses to be set for
/// * `dns` - A slice of [`IpAddr`](std::net::IpAddr) representing the DNS server addresses to be set for
/// the WireGuard interface.
///
/// * `search_domains` - A slice of [`&str`](std::str) representing the search domains to be set for
/// the WireGuard interface.
///
/// # Returns
///
/// Returns `Ok(())` if the DNS configuration is successfully set, or an
/// `Err(WireguardInterfaceError)` if there is an error during the configuration process.
fn configure_dns(&self, dns: &[IpAddr]) -> Result<(), WireguardInterfaceError>;
fn configure_dns(
&self,
dns: &[IpAddr],
search_domains: &[&str],
) -> Result<(), WireguardInterfaceError>;
}

0 comments on commit bcdb7a5

Please sign in to comment.