mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-08-30 15:59:21 +00:00
move system_config to dns::system
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
pub mod config;
|
||||
mod node;
|
||||
mod node_mgr;
|
||||
pub mod config;
|
||||
mod peer_mgr;
|
||||
pub mod server;
|
||||
pub mod system;
|
||||
mod utils;
|
||||
pub mod zone;
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::{self, OpenOptions},
|
||||
io::{self, Write},
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use super::{OSConfig, SystemConfig};
|
||||
|
||||
const MAC_RESOLVER_FILE_HEADER: &str = "# Added by easytier\n";
|
||||
const ETC_RESOLVER: &str = "/etc/resolver";
|
||||
const ETC_RESOLV_CONF: &str = "/etc/resolv.conf";
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DarwinConfigurator {}
|
||||
impl DarwinConfigurator {
|
||||
pub fn new() -> Self {
|
||||
DarwinConfigurator {}
|
||||
}
|
||||
|
||||
pub fn do_close(&self) -> io::Result<()> {
|
||||
self.remove_resolver_files(|_| true)
|
||||
}
|
||||
|
||||
pub fn supports_split_dns(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn do_set_dns(&self, cfg: &OSConfig) -> io::Result<()> {
|
||||
fs::create_dir_all(ETC_RESOLVER)?;
|
||||
let mut keep = HashSet::new();
|
||||
|
||||
// 写 search.easytier 文件
|
||||
if !cfg.search_domains.is_empty() {
|
||||
let search_file = "search.easytier";
|
||||
keep.insert(search_file.to_string());
|
||||
let mut content = String::from(MAC_RESOLVER_FILE_HEADER);
|
||||
content.push_str("search");
|
||||
for domain in &cfg.search_domains {
|
||||
content.push(' ');
|
||||
content.push_str(domain.trim_end_matches('.'));
|
||||
}
|
||||
content.push('\n');
|
||||
Self::write_resolver_file(search_file, &content)?;
|
||||
}
|
||||
|
||||
// 写 match_domains 文件
|
||||
let mut ns_content = String::from(MAC_RESOLVER_FILE_HEADER);
|
||||
for ns in &cfg.nameservers {
|
||||
ns_content.push_str(&format!("nameserver {}\n", ns));
|
||||
}
|
||||
for domain in &cfg.match_domains {
|
||||
let file_base = domain.trim_end_matches('.');
|
||||
keep.insert(file_base.to_string());
|
||||
Self::write_resolver_file(file_base, &ns_content)?;
|
||||
}
|
||||
// 删除未保留的 resolver 文件
|
||||
self.remove_resolver_files(|domain| !keep.contains(domain))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_resolver_file(file_name: &str, content: &str) -> io::Result<()> {
|
||||
let path = Path::new(ETC_RESOLVER).join(file_name);
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&path)?;
|
||||
file.set_permissions(fs::Permissions::from_mode(0o644))?;
|
||||
file.write_all(content.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_resolver_files<F>(&self, should_delete: F) -> io::Result<()>
|
||||
where
|
||||
F: Fn(&str) -> bool,
|
||||
{
|
||||
let entries = match fs::read_dir(ETC_RESOLVER) {
|
||||
Ok(e) => e,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let file_type = entry.file_type()?;
|
||||
if !file_type.is_file() {
|
||||
continue;
|
||||
}
|
||||
let name = entry.file_name();
|
||||
let name_str = name.to_string_lossy();
|
||||
if !should_delete(&name_str) {
|
||||
continue;
|
||||
}
|
||||
let full_path = entry.path();
|
||||
let content = fs::read_to_string(&full_path)?;
|
||||
if !content.starts_with(MAC_RESOLVER_FILE_HEADER) {
|
||||
continue;
|
||||
}
|
||||
fs::remove_file(&full_path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemConfig for DarwinConfigurator {
|
||||
fn set_dns(&self, cfg: &OSConfig) -> io::Result<()> {
|
||||
self.do_set_dns(cfg)
|
||||
}
|
||||
|
||||
fn close(&self) -> io::Result<()> {
|
||||
self.do_close()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn set_dns_test() -> io::Result<()> {
|
||||
let config = OSConfig {
|
||||
nameservers: vec!["8.8.8.8".into()],
|
||||
search_domains: vec!["example.com".into()],
|
||||
match_domains: vec!["test.local".into()],
|
||||
};
|
||||
let configurator = DarwinConfigurator::new();
|
||||
|
||||
configurator.set_dns(&config)?;
|
||||
configurator.close()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// translated from tailscale #32ce1bdb48078ec4cedaeeb5b1b2ff9c0ef61a49
|
||||
|
||||
use crate::defer;
|
||||
use anyhow::{Context, Result};
|
||||
use dbus::blocking::stdintf::org_freedesktop_dbus::Properties as _;
|
||||
use std::fs;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use version_compare::Cmp;
|
||||
|
||||
// 声明依赖项(需要添加到Cargo.toml)
|
||||
// use dbus::blocking::Connection;
|
||||
// use nix::unistd::AccessFlags;
|
||||
// use resolv_conf::Resolver;
|
||||
|
||||
// 常量定义
|
||||
const RESOLV_CONF: &str = "/etc/resolv.conf";
|
||||
const PING_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
// 错误类型定义
|
||||
#[derive(Debug)]
|
||||
struct DNSConfigError {
|
||||
message: String,
|
||||
source: Option<anyhow::Error>,
|
||||
}
|
||||
|
||||
type DbusPingFn = dyn Fn(&str, &str) -> Result<()>;
|
||||
type DbusReadStringFn = dyn Fn(&str, &str, &str, &str) -> Result<String>;
|
||||
type NmIsUsingResolvedFn = dyn Fn() -> Result<()>;
|
||||
type NmVersionBetweenFn = dyn Fn(&str, &str) -> Result<bool>;
|
||||
type ResolvconfStyleFn = dyn Fn() -> String;
|
||||
|
||||
// 配置环境结构体
|
||||
struct OSConfigEnv {
|
||||
fs: Box<dyn FileSystem>,
|
||||
dbus_ping: Box<DbusPingFn>,
|
||||
dbus_read_string: Box<DbusReadStringFn>,
|
||||
nm_is_using_resolved: Box<NmIsUsingResolvedFn>,
|
||||
nm_version_between: Box<NmVersionBetweenFn>,
|
||||
resolvconf_style: Box<dyn Fn() -> String>,
|
||||
}
|
||||
|
||||
// DNS管理器trait
|
||||
trait OSConfigurator: Send + Sync {
|
||||
// 实现相关方法
|
||||
}
|
||||
|
||||
// 文件系统操作trait
|
||||
trait FileSystem {
|
||||
fn read_file(&self, path: &str) -> Result<Vec<u8>>;
|
||||
fn exists(&self, path: &str) -> bool;
|
||||
}
|
||||
|
||||
// 直接文件系统实现
|
||||
struct DirectFS;
|
||||
|
||||
impl FileSystem for DirectFS {
|
||||
fn read_file(&self, path: &str) -> Result<Vec<u8>> {
|
||||
fs::read(path).context("Failed to read file")
|
||||
}
|
||||
|
||||
fn exists(&self, path: &str) -> bool {
|
||||
Path::new(path).exists()
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查 NetworkManager 是否使用 systemd-resolved 作为 DNS 管理器
|
||||
pub fn nm_is_using_resolved() -> Result<()> {
|
||||
// 连接系统 D-Bus
|
||||
let conn = dbus::blocking::Connection::new_system().context("Failed to connect to D-Bus")?;
|
||||
|
||||
// 创建 NetworkManager DnsManager 对象代理
|
||||
let proxy = conn.with_proxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
"/org/freedesktop/NetworkManager/DnsManager",
|
||||
std::time::Duration::from_secs(1),
|
||||
);
|
||||
|
||||
// 获取 Mode 属性
|
||||
let (value,): (dbus::arg::Variant<Box<dyn dbus::arg::RefArg + 'static>>,) = proxy
|
||||
.method_call(
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"Get",
|
||||
("org.freedesktop.NetworkManager.DnsManager", "Mode"),
|
||||
)
|
||||
.context("Failed to get NM mode property")?;
|
||||
|
||||
// 检查 Mode 是否为 "systemd-resolved"
|
||||
if value.0.as_str() != Some("systemd-resolved") {
|
||||
return Err(anyhow::anyhow!(
|
||||
"NetworkManager is not using systemd-resolved, found: {:?}",
|
||||
value
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 返回系统中使用的 resolvconf 实现类型("debian" 或 "openresolv")
|
||||
pub fn resolvconf_style() -> String {
|
||||
// 检查 resolvconf 命令是否存在
|
||||
if which::which("resolvconf").is_err() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// 执行 resolvconf --version 命令
|
||||
let output = match Command::new("resolvconf").arg("--version").output() {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
// 处理命令执行错误
|
||||
if let Some(code) = e.raw_os_error() {
|
||||
// Debian 版本的 resolvconf 不支持 --version,返回特定错误码 99
|
||||
if code == 99 {
|
||||
return "debian".to_string();
|
||||
}
|
||||
}
|
||||
return String::new(); // 其他错误返回空字符串
|
||||
}
|
||||
};
|
||||
|
||||
// 检查输出是否以 "Debian resolvconf" 开头
|
||||
if output.stdout.starts_with(b"Debian resolvconf") {
|
||||
return "debian".to_string();
|
||||
}
|
||||
|
||||
// 默认视为 openresolv
|
||||
"openresolv".to_string()
|
||||
}
|
||||
|
||||
// 构建配置环境
|
||||
fn new_os_config_env() -> OSConfigEnv {
|
||||
OSConfigEnv {
|
||||
fs: Box::new(DirectFS),
|
||||
dbus_ping: Box::new(dbus_ping),
|
||||
dbus_read_string: Box::new(dbus_read_string),
|
||||
nm_is_using_resolved: Box::new(nm_is_using_resolved),
|
||||
nm_version_between: Box::new(nm_version_between),
|
||||
resolvconf_style: Box::new(resolvconf_style),
|
||||
}
|
||||
}
|
||||
|
||||
// 创建DNS配置器
|
||||
fn new_os_configurator(_interface_name: String) -> Result<()> {
|
||||
let env = new_os_config_env();
|
||||
|
||||
let mode = dns_mode(&env).context("Failed to detect DNS mode")?;
|
||||
|
||||
tracing::info!("dns: using {} mode", mode);
|
||||
|
||||
// match mode.as_str() {
|
||||
// "direct" => Ok(Box::new(DirectManager::new(env.fs)?)),
|
||||
// // "systemd-resolved" => Ok(Box::new(ResolvedManager::new(
|
||||
// // &logf,
|
||||
// // health,
|
||||
// // interface_name,
|
||||
// // )?)),
|
||||
// // "network-manager" => Ok(Box::new(NMManager::new(interface_name)?)),
|
||||
// // "debian-resolvconf" => Ok(Box::new(DebianResolvconfManager::new(&logf)?)),
|
||||
// // "openresolv" => Ok(Box::new(OpenresolvManager::new(&logf)?)),
|
||||
// _ => {
|
||||
// tracing::warn!("Unexpected DNS mode {}, using direct manager", mode);
|
||||
// Ok(Box::new(DirectManager::new(env.fs)?))
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
use std::io::{self, BufRead, Cursor};
|
||||
|
||||
/// 返回 `resolv.conf` 内容的拥有者("systemd-resolved"、"NetworkManager"、"resolvconf" 或空字符串)
|
||||
pub fn resolv_owner(bs: &[u8]) -> String {
|
||||
let mut likely = String::new();
|
||||
let cursor = Cursor::new(bs);
|
||||
let reader = io::BufReader::new(cursor);
|
||||
|
||||
for line_result in reader.lines() {
|
||||
match line_result {
|
||||
Ok(line) => {
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !line.starts_with('#') {
|
||||
// 第一个非注释且非空的行,直接返回当前结果
|
||||
return likely;
|
||||
}
|
||||
|
||||
// 检查注释行中的关键字
|
||||
if line.contains("systemd-resolved") {
|
||||
likely = "systemd-resolved".to_string();
|
||||
} else if line.contains("NetworkManager") {
|
||||
likely = "NetworkManager".to_string();
|
||||
} else if line.contains("resolvconf") {
|
||||
likely = "resolvconf".to_string();
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// 读取错误(如无效 UTF-8),直接返回当前结果
|
||||
return likely;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
likely
|
||||
}
|
||||
|
||||
// 检测DNS模式
|
||||
fn dns_mode(env: &OSConfigEnv) -> Result<String> {
|
||||
let debug = std::cell::RefCell::new(Vec::new());
|
||||
let dbg = |k: &str, v: &str| debug.borrow_mut().push((k.to_string(), v.to_string()));
|
||||
|
||||
// defer 日志记录
|
||||
defer! {
|
||||
if !debug.borrow().is_empty() {
|
||||
let log_entries: Vec<String> =
|
||||
debug.borrow().iter().map(|(k, v)| format!("{}={}", k, v)).collect();
|
||||
tracing::info!("dns: [{}]", log_entries.join(" "));
|
||||
}
|
||||
};
|
||||
|
||||
// 检查systemd-resolved状态
|
||||
let resolved_up =
|
||||
(env.dbus_ping)("org.freedesktop.resolve1", "/org/freedesktop/resolve1").is_ok();
|
||||
if resolved_up {
|
||||
dbg("resolved-ping", "yes");
|
||||
}
|
||||
|
||||
// 读取resolv.conf
|
||||
let content = match env.fs.read_file(RESOLV_CONF) {
|
||||
Ok(content) => content,
|
||||
Err(e) if e.to_string().contains("NotFound") => {
|
||||
dbg("rc", "missing");
|
||||
return Ok("direct".to_string());
|
||||
}
|
||||
Err(e) => return Err(e).context("reading /etc/resolv.conf"),
|
||||
};
|
||||
|
||||
// 检查resolv.conf所有者
|
||||
match resolv_owner(&content).as_str() {
|
||||
"systemd-resolved" => {
|
||||
dbg("rc", "resolved");
|
||||
// 检查是否实际使用resolved
|
||||
if let Err(e) = resolved_is_actually_resolver(env, &dbg, &content) {
|
||||
tracing::warn!("resolvedIsActuallyResolver error: {}", e);
|
||||
dbg("resolved", "not-in-use");
|
||||
return Ok("direct".to_string());
|
||||
}
|
||||
|
||||
// NetworkManager检查逻辑...
|
||||
|
||||
Ok("systemd-resolved".to_string())
|
||||
}
|
||||
"resolvconf" => {
|
||||
// resolvconf处理逻辑...
|
||||
Ok("debian-resolvconf".to_string())
|
||||
}
|
||||
"NetworkManager" => {
|
||||
// NetworkManager处理逻辑...
|
||||
Ok("systemd-resolved".to_string())
|
||||
}
|
||||
_ => Ok("direct".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
// D-Bus ping实现
|
||||
fn dbus_ping(name: &str, object_path: &str) -> Result<()> {
|
||||
let conn = dbus::blocking::Connection::new_system()?;
|
||||
let proxy = conn.with_proxy(name, object_path, PING_TIMEOUT);
|
||||
let _: () = proxy.method_call("org.freedesktop.DBus.Peer", "Ping", ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// D-Bus读取字符串实现
|
||||
fn dbus_read_string(name: &str, object_path: &str, iface: &str, member: &str) -> Result<String> {
|
||||
let conn = dbus::blocking::Connection::new_system()?;
|
||||
let proxy = conn.with_proxy(name, object_path, PING_TIMEOUT);
|
||||
let (value,): (String,) =
|
||||
proxy.method_call("org.freedesktop.DBus.Properties", "Get", (iface, member))?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
// NetworkManager版本检查
|
||||
fn nm_version_between(first: &str, last: &str) -> Result<bool> {
|
||||
let conn = dbus::blocking::Connection::new_system()?;
|
||||
let proxy = conn.with_proxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
"/org/freedesktop/NetworkManager",
|
||||
PING_TIMEOUT,
|
||||
);
|
||||
|
||||
let version: String = proxy.get("org.freedesktop.NetworkManager", "Version")?;
|
||||
let cmp_first = version_compare::compare(&version, first).unwrap_or(Cmp::Lt);
|
||||
let cmp_last = version_compare::compare(&version, last).unwrap_or(Cmp::Gt);
|
||||
Ok(cmp_first == Cmp::Ge && cmp_last == Cmp::Le)
|
||||
}
|
||||
|
||||
// 检查是否实际使用systemd-resolved
|
||||
fn resolved_is_actually_resolver(
|
||||
env: &OSConfigEnv,
|
||||
dbg: &dyn Fn(&str, &str),
|
||||
content: &[u8],
|
||||
) -> Result<()> {
|
||||
if is_libnss_resolve_used(env).is_ok() {
|
||||
dbg("resolved", "nss");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 解析resolv.conf内容
|
||||
let resolver = resolv_conf::Config::parse(content)?;
|
||||
|
||||
// 检查nameserver配置
|
||||
if resolver.nameservers.is_empty() {
|
||||
return Err(anyhow::anyhow!("resolv.conf has no nameservers"));
|
||||
}
|
||||
|
||||
for ns in resolver.nameservers {
|
||||
if ns != Ipv4Addr::new(127, 0, 0, 53).into() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"resolv.conf doesn't point to systemd-resolved"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
dbg("resolved", "file");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 检查是否使用libnss_resolve
|
||||
fn is_libnss_resolve_used(env: &OSConfigEnv) -> Result<()> {
|
||||
let content = env.fs.read_file("/etc/nsswitch.conf")?;
|
||||
|
||||
for line in String::from_utf8_lossy(&content).lines() {
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
if parts.first() == Some(&"hosts:") {
|
||||
for module in parts.iter().skip(1) {
|
||||
if *module == "dns" {
|
||||
return Err(anyhow::anyhow!("dns module has higher priority"));
|
||||
}
|
||||
if *module == "resolve" {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow::anyhow!("libnss_resolve not used"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dns_mode_test() {
|
||||
let env = new_os_config_env();
|
||||
let mode = dns_mode(&env).unwrap();
|
||||
println!("Detected DNS mode: {}", mode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(all(target_os = "macos", not(feature = "macos-ne")))]
|
||||
pub mod darwin;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct OSConfig {
|
||||
pub nameservers: Vec<String>,
|
||||
pub search_domains: Vec<String>,
|
||||
pub match_domains: Vec<String>,
|
||||
}
|
||||
|
||||
pub trait SystemConfig: Send + Sync {
|
||||
fn set_dns(&self, cfg: &OSConfig) -> std::io::Result<()>;
|
||||
fn close(&self) -> std::io::Result<()>;
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
use std::net::IpAddr;
|
||||
use std::process::Command;
|
||||
|
||||
use std::io;
|
||||
use winreg::RegKey;
|
||||
|
||||
use crate::common::ifcfg::RegistryManager;
|
||||
|
||||
use super::{OSConfig, SystemConfig};
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::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::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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemConfig for WindowsDNSManager {
|
||||
fn set_dns(&self, cfg: &OSConfig) -> io::Result<()> {
|
||||
self.set_primary_dns(
|
||||
&cfg.nameservers
|
||||
.iter()
|
||||
.map(|s| s.parse::<IpAddr>().unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
&cfg.match_domains,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close(&self) -> io::Result<()> {
|
||||
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