use std::io;
use std::net::IpAddr;
use std::sync::OnceLock;
use libloading::os::windows::{Library, Symbol, LOAD_LIBRARY_SEARCH_SYSTEM32};
use windows_sys::core::GUID;
use windows_sys::Win32::NetworkManagement::IpHelper::{
DNS_INTERFACE_SETTINGS, DNS_INTERFACE_SETTINGS_VERSION1, DNS_SETTING_IPV6,
DNS_SETTING_NAMESERVER,
};
use windows_sys::Win32::NetworkManagement::Ndis::NET_LUID_LH;
use super::ffi;
use super::netsh;
type SetInterfaceDnsSettingsFn =
unsafe extern "system" fn(interface: GUID, settings: *const DNS_INTERFACE_SETTINGS) -> u32;
struct DnsApi {
set_interface_dns_settings: SetInterfaceDnsSettingsFn,
}
static DNS_API: OnceLock<Option<DnsApi>> = OnceLock::new();
impl DnsApi {
fn get() -> Option<&'static DnsApi> {
DNS_API.get_or_init(Self::load).as_ref()
}
fn load() -> Option<DnsApi> {
let library =
unsafe { Library::load_with_flags("iphlpapi.dll", LOAD_LIBRARY_SEARCH_SYSTEM32) }
.ok()?;
let func = unsafe {
let symbol: Symbol<SetInterfaceDnsSettingsFn> =
library.get(b"SetInterfaceDnsSettings\0").ok()?;
*symbol
};
std::mem::forget(library);
Some(DnsApi {
set_interface_dns_settings: func,
})
}
fn apply(&self, guid: &GUID, servers: &[IpAddr], is_ipv4: bool) -> io::Result<()> {
let nameserver = servers
.iter()
.map(|addr| addr.to_string())
.collect::<Vec<_>>()
.join(",");
let mut nameserver = ffi::encode_utf16(&nameserver);
let flags = if is_ipv4 {
DNS_SETTING_NAMESERVER
} else {
DNS_SETTING_NAMESERVER | DNS_SETTING_IPV6
};
let settings = DNS_INTERFACE_SETTINGS {
Version: DNS_INTERFACE_SETTINGS_VERSION1,
Flags: flags as u64,
NameServer: nameserver.as_mut_ptr(),
..Default::default()
};
let code = unsafe { (self.set_interface_dns_settings)(*guid, &settings) };
ffi::win_result(code)
}
}
pub fn set_dns_servers(index: u32, luid: &NET_LUID_LH, dns_servers: &[IpAddr]) -> io::Result<()> {
if dns_servers.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"DNS servers list cannot be empty",
));
}
let is_ipv4 = dns_servers[0].is_ipv4();
if !dns_servers.iter().all(|addr| addr.is_ipv4() == is_ipv4) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"All DNS servers must be either IPv4 or IPv6",
));
}
match DnsApi::get() {
Some(api) => {
let guid = ffi::luid_to_guid(luid)?;
api.apply(&guid, dns_servers, is_ipv4)?;
}
None => netsh::set_dns_servers(index, dns_servers)?,
}
flush_resolver_cache();
Ok(())
}
pub fn clear_dns_servers(index: u32, luid: &NET_LUID_LH, is_ipv4: bool) -> io::Result<()> {
match DnsApi::get() {
Some(api) => {
let guid = ffi::luid_to_guid(luid)?;
api.apply(&guid, &[], is_ipv4)?;
}
None => netsh::clear_dns_servers(index, is_ipv4)?,
}
flush_resolver_cache();
Ok(())
}
#[link(name = "dnsapi")]
extern "system" {
fn DnsFlushResolverCache() -> i32;
}
fn flush_resolver_cache() {
if unsafe { DnsFlushResolverCache() } == 0 {
log::warn!(
"DnsFlushResolverCache failed: {}",
io::Error::last_os_error()
);
}
}