mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 10:14:35 +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:
@@ -36,6 +36,7 @@ pub fn gen_default_flags() -> Flags {
|
||||
enable_kcp_proxy: false,
|
||||
disable_kcp_input: false,
|
||||
disable_relay_kcp: true,
|
||||
accept_dns: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ pub struct GlobalCtx {
|
||||
|
||||
ip_collector: Arc<IPCollector>,
|
||||
|
||||
hostname: String,
|
||||
hostname: Mutex<String>,
|
||||
|
||||
stun_info_collection: Box<dyn StunInfoCollectorTrait>,
|
||||
|
||||
@@ -122,7 +122,7 @@ impl GlobalCtx {
|
||||
|
||||
ip_collector: Arc::new(IPCollector::new(net_ns, stun_info_collection.clone())),
|
||||
|
||||
hostname,
|
||||
hostname: Mutex::new(hostname),
|
||||
|
||||
stun_info_collection: Box::new(stun_info_collection),
|
||||
|
||||
@@ -219,7 +219,11 @@ impl GlobalCtx {
|
||||
}
|
||||
|
||||
pub fn get_hostname(&self) -> String {
|
||||
return self.hostname.clone();
|
||||
return self.hostname.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
pub fn set_hostname(&self, hostname: String) {
|
||||
*self.hostname.lock().unwrap() = hostname;
|
||||
}
|
||||
|
||||
pub fn get_stun_info_collector(&self) -> impl StunInfoCollectorTrait + '_ {
|
||||
@@ -300,7 +304,10 @@ impl GlobalCtx {
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use crate::common::{config::TomlConfigLoader, new_peer_id};
|
||||
use crate::{
|
||||
common::{config::TomlConfigLoader, new_peer_id, stun::MockStunInfoCollector},
|
||||
proto::common::NatType,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -340,7 +347,12 @@ pub mod tests {
|
||||
let config_fs = TomlConfigLoader::default();
|
||||
config_fs.set_inst_name(format!("test_{}", config_fs.get_id()));
|
||||
config_fs.set_network_identity(network_identy.unwrap_or(NetworkIdentity::default()));
|
||||
std::sync::Arc::new(GlobalCtx::new(config_fs))
|
||||
|
||||
let ctx = Arc::new(GlobalCtx::new(config_fs));
|
||||
ctx.replace_stun_info_collector(Box::new(MockStunInfoCollector {
|
||||
udp_nat_type: NatType::Unknown,
|
||||
}));
|
||||
ctx
|
||||
}
|
||||
|
||||
pub fn get_mock_global_ctx() -> ArcGlobalCtx {
|
||||
|
||||
@@ -12,13 +12,15 @@ impl IfConfiguerTrait for MacIfConfiger {
|
||||
name: &str,
|
||||
address: Ipv4Addr,
|
||||
cidr_prefix: u8,
|
||||
cost: Option<i32>,
|
||||
) -> Result<(), Error> {
|
||||
run_shell_cmd(
|
||||
format!(
|
||||
"route -n add {} -netmask {} -interface {} -hopcount 7",
|
||||
"route -n add {} -netmask {} -interface {} -hopcount {}",
|
||||
address,
|
||||
cidr_to_subnet_mask(cidr_prefix),
|
||||
name
|
||||
name,
|
||||
cost.unwrap_or(7)
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ pub trait IfConfiguerTrait: Send + Sync {
|
||||
_name: &str,
|
||||
_address: Ipv4Addr,
|
||||
_cidr_prefix: u8,
|
||||
_cost: Option<i32>,
|
||||
) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -125,3 +126,6 @@ pub type IfConfiger = windows::WindowsIfConfiger;
|
||||
target_os = "freebsd",
|
||||
)))]
|
||||
pub type IfConfiger = DummyIfConfiger;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::RegistryManager;
|
||||
|
||||
@@ -350,6 +350,7 @@ impl IfConfiguerTrait for NetlinkIfConfiger {
|
||||
name: &str,
|
||||
address: Ipv4Addr,
|
||||
cidr_prefix: u8,
|
||||
cost: Option<i32>,
|
||||
) -> Result<(), Error> {
|
||||
let mut message = RouteMessage::default();
|
||||
|
||||
@@ -359,7 +360,9 @@ impl IfConfiguerTrait for NetlinkIfConfiger {
|
||||
message.header.kind = RouteType::Unicast;
|
||||
message.header.address_family = AddressFamily::Inet;
|
||||
// metric
|
||||
message.attributes.push(RouteAttribute::Priority(65535));
|
||||
message
|
||||
.attributes
|
||||
.push(RouteAttribute::Priority(cost.unwrap_or(65535) as u32));
|
||||
// output interface
|
||||
message
|
||||
.attributes
|
||||
@@ -550,7 +553,7 @@ mod tests {
|
||||
ifcfg.set_link_status(DUMMY_IFACE_NAME, true).await.unwrap();
|
||||
|
||||
ifcfg
|
||||
.add_ipv4_route(DUMMY_IFACE_NAME, "10.5.5.0".parse().unwrap(), 24)
|
||||
.add_ipv4_route(DUMMY_IFACE_NAME, "10.5.5.0".parse().unwrap(), 24, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::net::Ipv4Addr;
|
||||
use std::{io, net::Ipv4Addr};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use winreg::{
|
||||
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE},
|
||||
RegKey,
|
||||
};
|
||||
|
||||
use super::{cidr_to_subnet_mask, run_shell_cmd, Error, IfConfiguerTrait};
|
||||
|
||||
@@ -59,16 +63,18 @@ impl IfConfiguerTrait for WindowsIfConfiger {
|
||||
name: &str,
|
||||
address: Ipv4Addr,
|
||||
cidr_prefix: u8,
|
||||
cost: Option<i32>,
|
||||
) -> Result<(), Error> {
|
||||
let Some(idx) = Self::get_interface_index(name) else {
|
||||
return Err(Error::NotFound);
|
||||
};
|
||||
run_shell_cmd(
|
||||
format!(
|
||||
"route ADD {} MASK {} 10.1.1.1 IF {} METRIC 9000",
|
||||
"route ADD {} MASK {} 10.1.1.1 IF {} METRIC {}",
|
||||
address,
|
||||
cidr_to_subnet_mask(cidr_prefix),
|
||||
idx
|
||||
idx,
|
||||
cost.unwrap_or(9000)
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
@@ -164,3 +170,220 @@ impl IfConfiguerTrait for WindowsIfConfiger {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegistryManager;
|
||||
|
||||
impl RegistryManager {
|
||||
pub const IPV4_TCPIP_INTERFACE_PREFIX: &str =
|
||||
r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\";
|
||||
pub const IPV6_TCPIP_INTERFACE_PREFIX: &str =
|
||||
r"SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\";
|
||||
pub const NETBT_INTERFACE_PREFIX: &str =
|
||||
r"SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces\Tcpip_";
|
||||
|
||||
pub fn reg_delete_obsoleted_items(dev_name: &str) -> io::Result<()> {
|
||||
use winreg::{enums::HKEY_LOCAL_MACHINE, enums::KEY_ALL_ACCESS, RegKey};
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let profiles_key = hklm.open_subkey_with_flags(
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles",
|
||||
KEY_ALL_ACCESS,
|
||||
)?;
|
||||
let unmanaged_key = hklm.open_subkey_with_flags(
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Signatures\\Unmanaged",
|
||||
KEY_ALL_ACCESS,
|
||||
)?;
|
||||
// collect subkeys to delete
|
||||
let mut keys_to_delete = Vec::new();
|
||||
let mut keys_to_delete_unmanaged = Vec::new();
|
||||
for subkey_name in profiles_key.enum_keys().filter_map(Result::ok) {
|
||||
let subkey = profiles_key.open_subkey(&subkey_name)?;
|
||||
// check if ProfileName contains "et"
|
||||
match subkey.get_value::<String, _>("ProfileName") {
|
||||
Ok(profile_name) => {
|
||||
if profile_name.contains("et_")
|
||||
|| (!dev_name.is_empty() && dev_name == profile_name)
|
||||
{
|
||||
keys_to_delete.push(subkey_name);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to read ProfileName for subkey {}: {}",
|
||||
subkey_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for subkey_name in unmanaged_key.enum_keys().filter_map(Result::ok) {
|
||||
let subkey = unmanaged_key.open_subkey(&subkey_name)?;
|
||||
// check if ProfileName contains "et"
|
||||
match subkey.get_value::<String, _>("Description") {
|
||||
Ok(profile_name) => {
|
||||
if profile_name.contains("et_")
|
||||
|| (!dev_name.is_empty() && dev_name == profile_name)
|
||||
{
|
||||
keys_to_delete_unmanaged.push(subkey_name);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to read ProfileName for subkey {}: {}",
|
||||
subkey_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// delete collected subkeys
|
||||
if !keys_to_delete.is_empty() {
|
||||
for subkey_name in keys_to_delete {
|
||||
match profiles_key.delete_subkey_all(&subkey_name) {
|
||||
Ok(_) => tracing::trace!("Successfully deleted subkey: {}", subkey_name),
|
||||
Err(e) => tracing::error!("Failed to delete subkey {}: {}", subkey_name, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
if !keys_to_delete_unmanaged.is_empty() {
|
||||
for subkey_name in keys_to_delete_unmanaged {
|
||||
match unmanaged_key.delete_subkey_all(&subkey_name) {
|
||||
Ok(_) => tracing::trace!("Successfully deleted subkey: {}", subkey_name),
|
||||
Err(e) => tracing::error!("Failed to delete subkey {}: {}", subkey_name, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reg_change_catrgory_in_profile(dev_name: &str) -> io::Result<()> {
|
||||
use winreg::{enums::HKEY_LOCAL_MACHINE, enums::KEY_ALL_ACCESS, RegKey};
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let profiles_key = hklm.open_subkey_with_flags(
|
||||
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles",
|
||||
KEY_ALL_ACCESS,
|
||||
)?;
|
||||
|
||||
for subkey_name in profiles_key.enum_keys().filter_map(Result::ok) {
|
||||
let subkey = profiles_key.open_subkey_with_flags(&subkey_name, KEY_ALL_ACCESS)?;
|
||||
match subkey.get_value::<String, _>("ProfileName") {
|
||||
Ok(profile_name) => {
|
||||
if !dev_name.is_empty() && dev_name == profile_name {
|
||||
match subkey.set_value("Category", &1u32) {
|
||||
Ok(_) => tracing::trace!("Successfully set Category in registry"),
|
||||
Err(e) => tracing::error!("Failed to set Category in registry: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to read ProfileName for subkey {}: {}",
|
||||
subkey_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 根据接口名称查找 GUID
|
||||
pub fn find_interface_guid(interface_name: &str) -> io::Result<String> {
|
||||
// 注册表路径:所有网络接口的根目录
|
||||
let network_key_path =
|
||||
r"SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}";
|
||||
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let network_key = hklm.open_subkey_with_flags(network_key_path, KEY_READ)?;
|
||||
|
||||
// 遍历该路径下的所有 GUID 子键
|
||||
for guid in network_key.enum_keys().map_while(Result::ok) {
|
||||
if let Ok(guid_key) = network_key.open_subkey_with_flags(&guid, KEY_READ) {
|
||||
// 检查 Connection/Name 是否匹配目标接口名
|
||||
if let Ok(conn_key) = guid_key.open_subkey_with_flags("Connection", KEY_READ) {
|
||||
if let Ok(name) = conn_key.get_value::<String, _>("Name") {
|
||||
if name == interface_name {
|
||||
return Ok(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到对应的接口
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Interface not found",
|
||||
))
|
||||
}
|
||||
|
||||
// 打开注册表键
|
||||
pub fn open_interface_key(interface_guid: &str, prefix: &str) -> io::Result<RegKey> {
|
||||
let path = format!(r"{}{}", prefix, interface_guid);
|
||||
let hkey_local_machine = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
hkey_local_machine.open_subkey_with_flags(&path, KEY_WRITE)
|
||||
}
|
||||
|
||||
// 禁用动态 DNS 更新
|
||||
// disableDynamicUpdates sets the appropriate registry values to prevent the
|
||||
// Windows DHCP client from sending dynamic DNS updates for our interface to
|
||||
// AD domain controllers.
|
||||
pub fn disable_dynamic_updates(interface_guid: &str) -> io::Result<()> {
|
||||
let prefixes = [
|
||||
Self::IPV4_TCPIP_INTERFACE_PREFIX,
|
||||
Self::IPV6_TCPIP_INTERFACE_PREFIX,
|
||||
];
|
||||
|
||||
for prefix in &prefixes {
|
||||
let key = match Self::open_interface_key(interface_guid, prefix) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
// 模拟 mute-key-not-found-if-closing 行为
|
||||
if matches!(e.kind(), io::ErrorKind::NotFound) {
|
||||
continue;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
key.set_value("RegistrationEnabled", &0u32)?;
|
||||
key.set_value("DisableDynamicUpdate", &1u32)?;
|
||||
key.set_value("MaxNumberOfAddressesToRegister", &0u32)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 设置单个 DWORD 值到指定的注册表路径下
|
||||
fn set_single_dword(
|
||||
interface_guid: &str,
|
||||
prefix: &str,
|
||||
value_name: &str,
|
||||
data: u32,
|
||||
) -> io::Result<()> {
|
||||
let key = match Self::open_interface_key(interface_guid, prefix) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
// 模拟 muteKeyNotFoundIfClosing 行为:忽略 Key Not Found 错误
|
||||
return if matches!(e.kind(), io::ErrorKind::NotFound) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
key.set_value(value_name, &data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 禁用 NetBIOS 名称解析请求
|
||||
pub fn disable_netbios(interface_guid: &str) -> io::Result<()> {
|
||||
Self::set_single_dword(
|
||||
interface_guid,
|
||||
Self::NETBT_INTERFACE_PREFIX,
|
||||
"NetbiosOptions",
|
||||
2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user