mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-02 09:09:17 +00:00
magic dns (#813)
This patch implements: 1. A dns server that handles .et.net. zone in local and forward all other queries to system dns server. 2. A dns server instance which is a singleton in one machine, using one specific tcp port to be exclusive with each other. this instance is responsible for config system dns and run the dns server to handle dns queries. 3. A dns client instance that all easytier instance will run one, this instance will try to connect to dns server instance, and update the dns record in the dns server instance. this pr only implements the system config for windows. linux & mac will do later.
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
use std::net::IpAddr;
|
||||
use std::process::Command;
|
||||
|
||||
use std::io;
|
||||
use winreg::RegKey;
|
||||
|
||||
use crate::common::ifcfg::RegistryManager;
|
||||
|
||||
pub fn is_windows_10_or_better() -> io::Result<bool> {
|
||||
let hklm = winreg::enums::HKEY_LOCAL_MACHINE;
|
||||
let key_path = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
|
||||
let key = winreg::RegKey::predef(hklm).open_subkey(key_path)?;
|
||||
|
||||
// check CurrentMajorVersionNumber, which only exists on Windows 10 and later
|
||||
let value_name = "CurrentMajorVersionNumber";
|
||||
key.get_raw_value(value_name).map(|_| true)
|
||||
}
|
||||
|
||||
// 假设 interface_guid 是你的网络接口 GUID
|
||||
pub struct InterfaceControl {
|
||||
interface_guid: String,
|
||||
}
|
||||
|
||||
impl InterfaceControl {
|
||||
// 构造函数
|
||||
pub fn new(interface_guid: &str) -> Self {
|
||||
InterfaceControl {
|
||||
interface_guid: interface_guid.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// 删除注册表值(模拟 delValue)
|
||||
fn delete_value(key: &RegKey, value_name: &str) -> io::Result<()> {
|
||||
match key.delete_value(value_name) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
if matches!(e.kind(), io::ErrorKind::NotFound) {
|
||||
Ok(()) // 忽略不存在的值
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_primary_dns(&self, resolvers: &[IpAddr], domains: &[String]) -> io::Result<()> {
|
||||
let (ipsv4, ipsv6): (Vec<String>, Vec<String>) = resolvers
|
||||
.iter()
|
||||
.map(|ip| ip.to_string())
|
||||
.partition(|ip| ip.contains('.'));
|
||||
|
||||
let dom_strs: Vec<String> = domains
|
||||
.iter()
|
||||
.map(|d| d.trim_end_matches('.').to_string())
|
||||
.collect();
|
||||
|
||||
// IPv4 处理
|
||||
if let Ok(key4) = RegistryManager::open_interface_key(
|
||||
&self.interface_guid,
|
||||
RegistryManager::IPV4_TCPIP_INTERFACE_PREFIX,
|
||||
) {
|
||||
if ipsv4.is_empty() {
|
||||
Self::delete_value(&key4, "NameServer")?;
|
||||
} else {
|
||||
key4.set_value("NameServer", &ipsv4.join(","))?;
|
||||
}
|
||||
|
||||
if dom_strs.is_empty() {
|
||||
Self::delete_value(&key4, "SearchList")?;
|
||||
} else {
|
||||
key4.set_value("SearchList", &dom_strs.join(","))?;
|
||||
}
|
||||
|
||||
// 禁用 LLMNR(通过 DisableMulticast)
|
||||
key4.set_value("EnableMulticast", &0u32)?;
|
||||
}
|
||||
|
||||
// IPv6 处理
|
||||
if let Ok(key6) = RegistryManager::open_interface_key(
|
||||
&self.interface_guid,
|
||||
RegistryManager::IPV6_TCPIP_INTERFACE_PREFIX,
|
||||
) {
|
||||
if ipsv6.is_empty() {
|
||||
Self::delete_value(&key6, "NameServer")?;
|
||||
} else {
|
||||
key6.set_value("NameServer", &ipsv6.join(","))?;
|
||||
}
|
||||
|
||||
if dom_strs.is_empty() {
|
||||
Self::delete_value(&key6, "SearchList")?;
|
||||
} else {
|
||||
key6.set_value("SearchList", &dom_strs.join(","))?;
|
||||
}
|
||||
key6.set_value("EnableMulticast", &0u32)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush_dns(&self) -> io::Result<()> {
|
||||
// 刷新 DNS 缓存
|
||||
let output = Command::new("ipconfig")
|
||||
.arg("/flushdns")
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
if !output.status.success() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Failed to flush DNS cache",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// re-register DNS
|
||||
pub fn re_register_dns(&self) -> io::Result<()> {
|
||||
// ipconfig /registerdns
|
||||
let output = Command::new("ipconfig")
|
||||
.arg("/registerdns")
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
if !output.status.success() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Failed to register DNS",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowsDNSManager {
|
||||
tun_dev_name: String,
|
||||
interface_control: InterfaceControl,
|
||||
}
|
||||
|
||||
impl WindowsDNSManager {
|
||||
pub fn new(tun_dev_name: &str) -> io::Result<Self> {
|
||||
let interface_guid = RegistryManager::find_interface_guid(tun_dev_name)?;
|
||||
Ok(WindowsDNSManager {
|
||||
tun_dev_name: tun_dev_name.to_string(),
|
||||
interface_control: InterfaceControl::new(&interface_guid),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_primary_dns(&self, resolvers: &[IpAddr], domains: &[String]) -> io::Result<()> {
|
||||
self.interface_control.set_primary_dns(resolvers, domains)?;
|
||||
self.interface_control.flush_dns()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use cidr::Ipv4Inet;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tokio::test]
|
||||
async fn test_windows_set_primary_server() {
|
||||
use std::{net::Ipv4Addr, str::FromStr as _, time::Duration};
|
||||
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::instance::dns_server::{
|
||||
runner::DnsRunner,
|
||||
tests::{check_dns_record, prepare_env},
|
||||
};
|
||||
|
||||
let tun_ip = Ipv4Inet::from_str("10.144.144.10/24").unwrap();
|
||||
let (peer_mgr, virtual_nic) = prepare_env("test1", tun_ip).await;
|
||||
let tun_name = virtual_nic.ifname().await.unwrap();
|
||||
|
||||
println!("dev_name: {}", tun_name);
|
||||
let fake_ip = Ipv4Addr::from_str("100.100.100.101").unwrap();
|
||||
let mut dns_runner = DnsRunner::new(peer_mgr, Some(tun_name.clone()), tun_ip, fake_ip);
|
||||
|
||||
let cancel_token = CancellationToken::new();
|
||||
let cancel_token_clone = cancel_token.clone();
|
||||
let t = tokio::spawn(async move {
|
||||
dns_runner.run(cancel_token_clone).await;
|
||||
});
|
||||
|
||||
// windows is slow to add a ip address, wait for a longer time for dns server ready ,with ping
|
||||
let now = std::time::Instant::now();
|
||||
while now.elapsed() < Duration::from_secs(15) {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
if let Ok(o) = tokio::process::Command::new("ping")
|
||||
.arg("-n")
|
||||
.arg("1")
|
||||
.arg("-w")
|
||||
.arg("100")
|
||||
.arg(&fake_ip.to_string())
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
if o.status.success() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check_dns_record(&fake_ip, "test1.et.net", "10.144.144.10").await;
|
||||
|
||||
let dns_mgr = super::WindowsDNSManager::new(&tun_name).unwrap();
|
||||
println!("dev_name: {}", tun_name);
|
||||
println!("guid: {}", dns_mgr.interface_control.interface_guid);
|
||||
|
||||
dns_mgr
|
||||
.interface_control
|
||||
.set_primary_dns(
|
||||
&["100.100.100.101".parse().unwrap()],
|
||||
&[".et.net.".to_string()],
|
||||
)
|
||||
.unwrap();
|
||||
dns_mgr.interface_control.flush_dns().unwrap();
|
||||
|
||||
tracing::info!("check dns record with nslookup");
|
||||
|
||||
// nslookup should return 10.144.144.10
|
||||
let ret = tokio::process::Command::new("nslookup")
|
||||
.arg("test1.et.net")
|
||||
.output()
|
||||
.await
|
||||
.expect("failed to execute process");
|
||||
assert!(ret.status.success());
|
||||
let output = String::from_utf8_lossy(&ret.stdout);
|
||||
println!("nslookup output: {}", output);
|
||||
assert!(output.contains("10.144.144.10"));
|
||||
|
||||
cancel_token.cancel();
|
||||
let _ = t.await;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user