remove old magic-dns

This commit is contained in:
Luna Yao
2026-04-06 11:54:47 +02:00
parent 8559de1857
commit d9d211c5a4
13 changed files with 7 additions and 1826 deletions
+1 -2
View File
@@ -17,7 +17,6 @@ use tokio::io::AsyncReadExt as _;
use crate::{ use crate::{
common::stun::StunInfoCollector, common::stun::StunInfoCollector,
dns::config::DnsConfig, dns::config::DnsConfig,
instance::dns_server::DEFAULT_ET_DNS_ZONE,
proto::{ proto::{
acl::Acl, acl::Acl,
common::{CompressionAlgoPb, PortForwardConfigPb, SecureModeConfig, SocketType}, common::{CompressionAlgoPb, PortForwardConfigPb, SecureModeConfig, SocketType},
@@ -66,7 +65,7 @@ pub fn gen_default_flags() -> Flags {
multi_thread_count: 2, multi_thread_count: 2,
encryption_algorithm: EncryptionAlgorithm::default().to_string(), encryption_algorithm: EncryptionAlgorithm::default().to_string(),
disable_sym_hole_punching: false, disable_sym_hole_punching: false,
tld_dns_zone: DEFAULT_ET_DNS_ZONE.to_string(), tld_dns_zone: "".to_string(),
quic_listen_port: u32::MAX, quic_listen_port: u32::MAX,
need_p2p: false, need_p2p: false,
-4
View File
@@ -1080,10 +1080,6 @@ impl NetworkOptions {
.enable_relay_foreign_network_quic .enable_relay_foreign_network_quic
.unwrap_or(f.enable_relay_foreign_network_quic); .unwrap_or(f.enable_relay_foreign_network_quic);
f.disable_sym_hole_punching = self.disable_sym_hole_punching.unwrap_or(false); f.disable_sym_hole_punching = self.disable_sym_hole_punching.unwrap_or(false);
// Configure tld_dns_zone: use provided value if set
if let Some(tld_dns_zone) = &self.tld_dns_zone {
f.tld_dns_zone = tld_dns_zone.clone();
}
cfg.set_flags(f); cfg.set_flags(f);
if !self.exit_nodes.is_empty() { if !self.exit_nodes.is_empty() {
-1
View File
@@ -15,7 +15,6 @@ use tokio::sync::{broadcast, Notify};
use tokio::task::JoinSet; use tokio::task::JoinSet;
use tokio::time::{sleep, sleep_until, Instant}; use tokio::time::{sleep, sleep_until, Instant};
use uuid::Uuid; use uuid::Uuid;
use crate::instance::instance::ArcNicCtx;
#[derive(Debug)] #[derive(Debug)]
pub struct DnsNode { pub struct DnsNode {
@@ -1,111 +0,0 @@
use std::{sync::Arc, time::Duration};
use tokio::task::JoinSet;
use crate::{
peers::peer_manager::PeerManager,
proto::{
api::instance::Route,
common::Void,
magic_dns::{
HandshakeRequest, MagicDnsServerRpc, MagicDnsServerRpcClientFactory,
UpdateDnsRecordRequest,
},
rpc_impl::standalone::StandAloneClient,
rpc_types::controller::BaseController,
},
tunnel::tcp::TcpTunnelConnector,
};
use super::MAGIC_DNS_INSTANCE_ADDR;
pub struct MagicDnsClientInstance {
rpc_client: StandAloneClient<TcpTunnelConnector>,
rpc_stub: Option<Box<dyn MagicDnsServerRpc<Controller = BaseController> + Send>>,
peer_mgr: Arc<PeerManager>,
tasks: JoinSet<()>,
}
impl MagicDnsClientInstance {
pub async fn new(peer_mgr: Arc<PeerManager>) -> Result<Self, anyhow::Error> {
let tcp_connector = TcpTunnelConnector::new(MAGIC_DNS_INSTANCE_ADDR.parse().unwrap());
let mut rpc_client = StandAloneClient::new(tcp_connector);
let rpc_stub = rpc_client
.scoped_client::<MagicDnsServerRpcClientFactory<BaseController>>("".to_string())
.await?;
Ok(MagicDnsClientInstance {
rpc_client,
rpc_stub: Some(rpc_stub),
peer_mgr,
tasks: JoinSet::new(),
})
}
async fn update_dns_task(
peer_mgr: Arc<PeerManager>,
rpc_stub: Box<dyn MagicDnsServerRpc<Controller = BaseController> + Send>,
) -> Result<(), anyhow::Error> {
let mut prev_last_update = None;
rpc_stub
.handshake(BaseController::default(), HandshakeRequest::default())
.await?;
loop {
rpc_stub
.heartbeat(BaseController::default(), Void::default())
.await?;
let last_update = peer_mgr.get_route_peer_info_last_update_time().await;
if Some(last_update) == prev_last_update {
tokio::time::sleep(Duration::from_millis(500)).await;
continue;
}
let mut routes = peer_mgr.list_routes().await;
// add self as a route
let ctx = peer_mgr.get_global_ctx();
routes.push(Route {
hostname: ctx.get_hostname(),
ipv4_addr: ctx.get_ipv4().map(Into::into),
..Default::default()
});
// Use configured tld_dns_zone (always set by default)
let flags = ctx.config.get_flags();
let req = UpdateDnsRecordRequest {
routes,
zone: flags.tld_dns_zone.clone(),
};
tracing::debug!(
"MagicDnsClientInstance::update_dns_task: update dns records: {:?}",
req
);
rpc_stub
.update_dns_record(BaseController::default(), req)
.await?;
let last_update_after_rpc = peer_mgr.get_route_peer_info_last_update_time().await;
if last_update_after_rpc == last_update {
prev_last_update = Some(last_update);
}
}
}
pub async fn run_and_wait(&mut self) {
let rpc_stub = self.rpc_stub.take().unwrap();
let peer_mgr = self.peer_mgr.clone();
self.tasks.spawn(async move {
let ret = Self::update_dns_task(peer_mgr, rpc_stub).await;
if let Err(e) = ret {
tracing::error!("MagicDnsServerInstanceData::run_and_wait: {:?}", e);
}
});
tokio::select! {
_ = self.tasks.join_next() => {
tracing::warn!("MagicDnsServerInstanceData::run_and_wait: dns record update task exited");
}
_ = self.rpc_client.wait() => {
tracing::warn!("MagicDnsServerInstanceData::run_and_wait: rpc client exited");
}
}
}
}
-193
View File
@@ -1,193 +0,0 @@
use hickory_proto::rr;
use hickory_proto::rr::RData;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr};
use std::str::FromStr;
use std::time::Duration;
#[derive(Serialize, Deserialize, Debug, Clone, derive_builder::Builder)]
pub struct RunConfig {
general: GeneralConfig,
#[builder(default = HashMap::new())]
zones: Zone,
#[builder(default = Vec::new())]
#[serde(default)]
excluded_forward_nameservers: Vec<IpAddr>,
}
impl RunConfig {
pub fn general(&self) -> &GeneralConfig {
&self.general
}
pub fn zones(&self) -> &Zone {
&self.zones
}
pub fn excluded_forward_nameservers(&self) -> &Vec<IpAddr> {
&self.excluded_forward_nameservers
}
}
#[derive(Serialize, Deserialize, Debug, Clone, derive_builder::Builder)]
pub struct GeneralConfig {
#[builder(setter(into, strip_option), default = None)]
listen_tcp: Option<String>,
#[builder(setter(into, strip_option), default = None)]
listen_udp: Option<String>,
}
impl GeneralConfig {
pub fn listen_tcp(&self) -> &Option<String> {
&self.listen_tcp
}
pub fn listen_udp(&self) -> &Option<String> {
&self.listen_udp
}
}
pub type Zone = HashMap<String, Vec<Record>>; // domain -> records
pub type RecordType = rr::RecordType;
#[derive(Serialize, Deserialize, Debug, Clone, derive_builder::Builder)]
pub struct Record {
#[serde(rename = "type")]
rr_type: RecordType,
name: String,
value: String,
#[serde(with = "humantime_serde")]
ttl: Duration,
}
impl Record {
pub fn name(&self) -> anyhow::Result<rr::Name> {
let name = rr::Name::from_str(self.name.as_str())?;
Ok(name)
}
fn rr_type(&self) -> rr::RecordType {
self.rr_type
}
}
impl TryFrom<Record> for rr::Record {
type Error = anyhow::Error;
fn try_from(value: Record) -> Result<Self, Self::Error> {
let r: rr::Record = (&value).try_into()?;
Ok(r)
}
}
impl TryFrom<&Record> for rr::Record {
type Error = anyhow::Error;
fn try_from(value: &Record) -> Result<Self, Self::Error> {
let name = value.name()?;
let mut record = Self::update0(name, value.ttl.as_secs() as u32, value.rr_type());
record.set_dns_class(rr::DNSClass::IN);
match value.rr_type {
RecordType::A => {
let addr: Ipv4Addr = value.value.parse()?;
record.set_data(RData::A(rr::rdata::a::A(addr)));
}
RecordType::SOA => {
let soa = value.value.split_whitespace().collect::<Vec<_>>();
if soa.len() != 7 {
return Err(anyhow::anyhow!("invalid SOA record"));
}
let mname = rr::Name::from_str(soa[0])?;
let rname = rr::Name::from_str(soa[1])?;
let serial: u32 = soa[2].parse()?;
let refresh: u32 = soa[3].parse()?;
let retry: u32 = soa[4].parse()?;
let expire: u32 = soa[5].parse()?;
let minimum: u32 = soa[6].parse()?;
record.set_data(RData::SOA(rr::rdata::soa::SOA::new(
mname,
rname,
serial,
refresh.try_into().unwrap(),
retry.try_into().unwrap(),
expire.try_into().unwrap(),
minimum,
)));
}
_ => todo!(),
}
Ok(record)
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
#[tokio::test]
async fn it_works() -> anyhow::Result<()> {
let text = r#"
[general]
listen_tcp = "127.0.0.1:5300"
listen_udp = "127.0.0.1:5353"
[[zones."et.internal"]]
type = "A"
name = "www"
value = "123.123.123.123"
ttl = "60s"
[[zones."et.top"]]
type = "A"
name = "@"
value = "100.100.100.100"
ttl = "61s"
"#;
let config = toml::from_str::<RunConfig>(text)?;
assert_eq!(
config.general.listen_tcp().clone().unwrap(),
"127.0.0.1:5300"
);
assert_eq!(
config.general.listen_udp().clone().unwrap(),
"127.0.0.1:5353"
);
assert_eq!(config.zones.len(), 2);
let (domain, records) = config
.zones
.get_key_value("et.internal")
.ok_or(anyhow!("et.internal not found"))?;
assert_eq!(domain, "et.internal");
assert_eq!(records.len(), 1);
let record = &records[0];
assert_eq!(record.rr_type, RecordType::A);
assert_eq!(record.name, "www");
assert_eq!(record.value, "123.123.123.123");
assert_eq!(record.ttl.as_secs(), 60);
let (domain, records) = config
.zones
.get_key_value("et.top")
.ok_or(anyhow!("et.top not found"))?;
assert_eq!(domain, "et.top");
assert_eq!(records.len(), 1);
let record = &records[0];
assert_eq!(record.rr_type, RecordType::A);
assert_eq!(record.name, "@");
assert_eq!(record.value, "100.100.100.100");
assert_eq!(record.ttl.as_secs(), 61);
Ok(())
}
}
-19
View File
@@ -1,19 +0,0 @@
// This module is copy and modified from https://github.com/fanyang89/libdns
#[cfg(feature = "magic-dns")]
pub(crate) mod config;
#[cfg(feature = "magic-dns")]
pub(crate) mod server;
#[cfg(feature = "magic-dns")]
pub mod client_instance;
#[cfg(feature = "magic-dns")]
pub mod runner;
#[cfg(feature = "magic-dns")]
pub mod server_instance;
#[cfg(all(test, feature = "tun", feature = "magic-dns"))]
pub mod tests;
pub static MAGIC_DNS_INSTANCE_ADDR: &str = "tcp://127.0.0.1:49813";
pub static MAGIC_DNS_FAKE_IP: &str = "100.100.100.101";
pub static DEFAULT_ET_DNS_ZONE: &str = "et.net.";
@@ -1,93 +0,0 @@
use cidr::Ipv4Inet;
use tokio_util::sync::CancellationToken;
use crate::peers::peer_manager::PeerManager;
use std::{net::Ipv4Addr, sync::Arc, time::Duration};
use super::{client_instance::MagicDnsClientInstance, server_instance::MagicDnsServerInstance};
static DEFAULT_ET_DNS_ZONE: &str = "et.net.";
pub struct DnsRunner {
client: Option<MagicDnsClientInstance>,
server: Option<MagicDnsServerInstance>,
peer_mgr: Arc<PeerManager>,
tun_dev: Option<String>,
tun_inet: Ipv4Inet,
fake_ip: Ipv4Addr,
}
impl DnsRunner {
pub fn new(
peer_mgr: Arc<PeerManager>,
tun_dev: Option<String>,
tun_inet: Ipv4Inet,
fake_ip: Ipv4Addr,
) -> Self {
Self {
client: None,
server: None,
peer_mgr,
tun_dev,
tun_inet,
fake_ip,
}
}
async fn clean_env(&mut self) {
if let Some(server) = self.server.take() {
server.clean_env().await;
}
self.client.take();
}
async fn run_once(&mut self) -> anyhow::Result<()> {
// try server first
match MagicDnsServerInstance::new(
self.peer_mgr.clone(),
self.tun_dev.clone(),
self.tun_inet,
self.fake_ip,
)
.await
{
Ok(server) => {
self.server = Some(server);
tracing::info!("DnsRunner::run_once: server started");
}
Err(e) => {
tracing::error!("DnsRunner::run_once: {:?}", e);
}
}
// every runner must run a client
let client = MagicDnsClientInstance::new(self.peer_mgr.clone()).await?;
self.client = Some(client);
self.client.as_mut().unwrap().run_and_wait().await;
Err(anyhow::anyhow!("Client instance exit"))
}
pub async fn run(&mut self, canel_token: CancellationToken) {
loop {
tracing::info!("DnsRunner::run: start");
tokio::select! {
_ = canel_token.cancelled() => {
self.clean_env().await;
tracing::info!("DnsRunner::run: cancelled");
return;
}
ret = self.run_once() => {
self.clean_env().await;
if let Err(e) = ret {
tracing::error!("DnsRunner::run: {:?}", e);
} else {
tracing::info!("DnsRunner::run: unexpected exit, server may be down");
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
}
}
}
-338
View File
@@ -1,338 +0,0 @@
use anyhow::{Context, Result};
use hickory_proto::op::Edns;
use hickory_proto::rr;
use hickory_proto::rr::LowerName;
use hickory_resolver::config::ResolverOpts;
use hickory_resolver::name_server::TokioConnectionProvider;
use hickory_resolver::system_conf::read_system_conf;
use hickory_server::authority::{AuthorityObject, Catalog, ZoneType};
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
use hickory_server::store::forwarder::ForwardConfig;
use hickory_server::store::{forwarder::ForwardAuthority, in_memory::InMemoryAuthority};
use hickory_server::ServerFuture;
use std::io;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use tokio::net::{TcpListener, UdpSocket};
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tokio::task::JoinSet;
use crate::common::dns::get_default_resolver_config;
use super::config::{GeneralConfig, Record, RunConfig};
pub struct Server {
server: ServerFuture<CatalogRequestHandler>,
catalog: Arc<RwLock<Catalog>>,
general_config: GeneralConfig,
udp_local_addr: Option<SocketAddr>,
tcp_local_addr: Option<SocketAddr>,
tasks: JoinSet<()>,
}
struct CatalogRequestHandler {
catalog: Arc<RwLock<Catalog>>,
}
impl CatalogRequestHandler {
fn new(catalog: Arc<RwLock<Catalog>>) -> CatalogRequestHandler {
// let system_conf = read_system_conf();
// let recursor = match system_conf {
// Ok((conf, _)) => RecursorBuilder::default().build(conf),
// Err(_) => RecursorBuilder::default().build(get_default_resolver_config()),
// }
// // policy is security unware, this will never return an error
// .unwrap();
Self { catalog }
}
}
#[async_trait::async_trait]
impl RequestHandler for CatalogRequestHandler {
async fn handle_request<R: ResponseHandler>(
&self,
request: &Request,
response_handle: R,
) -> ResponseInfo {
self.catalog
.read()
.await
.handle_request(request, response_handle)
.await
}
}
pub fn build_authority(domain: &str, records: &[Record]) -> Result<InMemoryAuthority> {
let zone = rr::Name::from_str(domain)?;
let mut authority = InMemoryAuthority::empty(zone, ZoneType::Primary, false);
for record in records.iter() {
let r = record.try_into()?;
authority.upsert_mut(r, 0);
}
Ok(authority)
}
impl Server {
pub fn new(config: RunConfig) -> Self {
Self::try_new(config).unwrap()
}
fn try_new(config: RunConfig) -> Result<Self> {
let mut catalog = Catalog::new();
for (domain, records) in config.zones().iter() {
let zone = rr::Name::from_str(domain.as_str())?;
let authroty = build_authority(domain, records)?;
catalog.upsert(zone.clone().into(), vec![Arc::new(authroty)]);
}
// use forwarder authority for the root zone
let system_conf =
read_system_conf().unwrap_or((get_default_resolver_config(), ResolverOpts::default()));
let forward_config = ForwardConfig {
name_servers: system_conf
.0
.name_servers()
.iter()
.filter(|&x| {
!config
.excluded_forward_nameservers()
.contains(&x.socket_addr.ip())
})
.cloned()
.collect::<Vec<_>>()
.into(),
options: Some(system_conf.1),
};
let auth = ForwardAuthority::builder_with_config(
forward_config,
TokioConnectionProvider::default(),
)
.build()
.unwrap();
catalog.upsert(rr::Name::from_str(".")?.into(), vec![Arc::new(auth)]);
let catalog = Arc::new(RwLock::new(catalog));
let handler = CatalogRequestHandler::new(catalog.clone());
let server = ServerFuture::new(handler);
Ok(Self {
server,
catalog,
general_config: config.general().clone(),
udp_local_addr: None,
tcp_local_addr: None,
tasks: JoinSet::new(),
})
}
pub fn udp_local_addr(&self) -> Option<SocketAddr> {
self.udp_local_addr
}
pub fn tcp_local_addr(&self) -> Option<SocketAddr> {
self.tcp_local_addr
}
pub async fn register_udp_socket(&mut self, address: String) -> Result<SocketAddr> {
let bind_addr = SocketAddr::from_str(&address)
.with_context(|| format!("DNS Server failed to parse address {}", address))?;
let socket = socket2::Socket::new(
socket2::Domain::IPV4,
socket2::Type::DGRAM,
Some(socket2::Protocol::UDP),
)
.with_context(|| {
format!(
"DNS Server failed to create UDP socket for address {}",
address
)
})?;
socket2::SockRef::from(&socket)
.set_reuse_address(true)
.with_context(|| {
format!(
"DNS Server failed to set reuse address on socket {}",
address
)
})?;
socket.bind(&bind_addr.into()).with_context(|| {
format!("DNS Server failed to bind socket to address {}", bind_addr)
})?;
socket
.set_nonblocking(true)
.with_context(|| "DNS Server failed to set socket to non-blocking".to_string())?;
let socket = UdpSocket::from_std(socket.into()).with_context(|| {
format!(
"DNS Server failed to convert socket to UdpSocket for address {}",
address
)
})?;
let local_addr = socket
.local_addr()
.with_context(|| "DNS Server failed to get local address".to_string())?;
self.server.register_socket(socket);
Ok(local_addr)
}
pub async fn run(&mut self) -> Result<()> {
if let Some(address) = self.general_config.listen_tcp() {
let tcp_listener = TcpListener::bind(address.clone())
.await
.with_context(|| format!("DNS Server failed to bind TCP address {}", address))?;
self.tcp_local_addr = Some(tcp_listener.local_addr()?);
self.server
.register_listener(tcp_listener, Duration::from_secs(5));
}
if let Some(address) = self.general_config.listen_udp() {
let local_addr = self.register_udp_socket(address.clone()).await?;
self.udp_local_addr = Some(local_addr);
};
Ok(())
}
pub async fn shutdown(&mut self) -> Result<()> {
self.server.shutdown_gracefully().await?;
Ok(())
}
pub async fn upsert(&self, name: LowerName, authority: Arc<dyn AuthorityObject>) {
self.catalog.write().await.upsert(name, vec![authority]);
}
pub async fn remove(&self, name: &LowerName) -> Option<Vec<Arc<dyn AuthorityObject>>> {
self.catalog.write().await.remove(name)
}
pub async fn update<R: ResponseHandler>(
&self,
update: &Request,
response_edns: Option<Edns>,
response_handle: R,
) -> io::Result<ResponseInfo> {
self.catalog
.write()
.await
.update(update, response_edns, response_handle)
.await
}
pub async fn contains(&self, name: &LowerName) -> bool {
self.catalog.read().await.contains(name)
}
pub async fn lookup<R: ResponseHandler>(
&self,
request: &Request,
response_edns: Option<Edns>,
response_handle: R,
) -> ResponseInfo {
self.catalog
.read()
.await
.lookup(request, response_edns, response_handle)
.await
}
pub async fn read_catalog(&self) -> RwLockReadGuard<'_, Catalog> {
self.catalog.read().await
}
pub async fn write_catalog(&self) -> RwLockWriteGuard<'_, Catalog> {
self.catalog.write().await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::instance::dns_server::config::{
GeneralConfigBuilder, RecordBuilder, RecordType, RunConfigBuilder,
};
use anyhow::Result;
use hickory_client::client::{Client, ClientHandle};
use hickory_proto::rr;
use hickory_proto::runtime::TokioRuntimeProvider;
use hickory_proto::udp::UdpClientStream;
use maplit::hashmap;
use std::time::Duration;
#[tokio::test]
async fn it_works() -> Result<()> {
let mut server = Server::new(
RunConfigBuilder::default()
.general(GeneralConfigBuilder::default().build()?)
.build()?,
);
server.run().await?;
server.shutdown().await?;
Ok(())
}
#[tokio::test]
async fn can_resolve_records() -> Result<()> {
let configured_record = RecordBuilder::default()
.rr_type(RecordType::A)
.name("www.et.internal.".to_string())
.value("123.123.123.123".to_string())
.ttl(Duration::from_secs(60))
.build()?;
let configured_record2 = RecordBuilder::default()
.rr_type(RecordType::A)
.name("中文.et.internal.".to_string())
.value("123.123.123.123".to_string())
.ttl(Duration::from_secs(60))
.build()?;
let soa_record = RecordBuilder::default()
.rr_type(RecordType::SOA)
.name("et.internal.".to_string())
.value(
"ns.et.internal. hostmaster.et.internal. 2023101001 7200 3600 1209600 86400"
.to_string(),
)
.ttl(Duration::from_secs(60))
.build()?;
let config = RunConfigBuilder::default()
.general(
GeneralConfigBuilder::default()
.listen_udp("127.0.0.1:0")
.build()?,
)
.zones(hashmap! {
"et.internal.".to_string() => vec![configured_record.clone(), soa_record.clone(), configured_record2.clone()],
})
.build()?;
let mut server = Server::new(config);
server.run().await?;
let local_addr = server.udp_local_addr().unwrap();
let stream = UdpClientStream::builder(local_addr, TokioRuntimeProvider::default()).build();
let (mut client, background) = Client::connect(stream).await?;
let background_task = tokio::spawn(background);
let response = client
.query(
rr::Name::from_str("www.et.internal")?,
rr::DNSClass::IN,
rr::RecordType::A,
)
.await?;
drop(background_task);
println!("Response: {:?}", response);
assert_eq!(response.answers().len(), 1);
let expected_record: rr::Record = configured_record.try_into()?;
assert_eq!(response.answers().first().unwrap(), &expected_record);
server.shutdown().await?;
Ok(())
}
}
@@ -1,611 +0,0 @@
// single-instance server in one machine, every easytier instance that has ip address and tun device will try to create a server instance.
// magic dns client will connect to this server to update the dns records.
// magic dns server will add the dns server ip address to the tun device, and forward the dns request to the dns server
// magic dns client will establish a long live tcp connection to the magic dns server, and when the server stops or crashes,
// all the clients will exit and let the easytier instance to launch a new server instance.
use super::{
config::{GeneralConfigBuilder, RunConfigBuilder},
server::Server,
MAGIC_DNS_INSTANCE_ADDR,
};
use crate::dns::system::{SystemConfig, SystemConfigurator};
use crate::{
common::{
ifcfg::{IfConfiger, IfConfiguerTrait},
PeerId,
},
instance::dns_server::{
config::{Record, RecordBuilder, RecordType},
server::build_authority,
},
peers::{peer_manager::PeerManager, NicPacketFilter},
proto::{
api::instance::Route,
common::{TunnelInfo, Void},
magic_dns::{
dns_record::{self},
DnsRecord, DnsRecordA, DnsRecordList, GetDnsRecordResponse, HandshakeRequest,
HandshakeResponse, MagicDnsServerRpc, MagicDnsServerRpcServer, UpdateDnsRecordRequest,
},
rpc_impl::standalone::{RpcServerHook, StandAloneServer},
rpc_types::controller::{BaseController, Controller},
},
tunnel::{packet_def::ZCPacket, tcp::TcpTunnelListener},
};
use anyhow::Context;
use cidr::Ipv4Inet;
use dashmap::DashMap;
use hickory_proto::rr::LowerName;
use hickory_proto::serialize::binary::{BinDecodable, BinEncoder};
use hickory_server::authority::{MessageRequest, MessageResponse};
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
use multimap::MultiMap;
use pnet::packet::icmp::{IcmpTypes, MutableIcmpPacket};
use pnet::packet::ipv4::Ipv4Packet;
use pnet::packet::udp::UdpPacket;
use pnet::packet::{
icmp,
ip::IpNextHeaderProtocols,
ipv4::{self, MutableIpv4Packet},
udp::{self, MutableUdpPacket},
MutablePacket, Packet,
};
use std::net::{SocketAddr, SocketAddrV4};
use std::sync::Mutex;
use std::{collections::BTreeMap, io, net::Ipv4Addr, str::FromStr, sync::Arc, time::Duration};
static NIC_PIPELINE_NAME: &str = "magic_dns_server";
pub(super) struct MagicDnsServerInstanceData {
dns_server: Server,
tun_dev: Option<String>,
tun_ip: Ipv4Addr,
fake_ip: Ipv4Addr,
my_peer_id: PeerId,
// zone -> (tunnel remote addr -> route)
route_infos: DashMap<String, MultiMap<url::Url, Route>>,
system_config: Option<Box<dyn SystemConfigurator>>,
}
impl MagicDnsServerInstanceData {
pub async fn update_dns_records<'a, T: Iterator<Item = &'a Route>>(
&self,
routes: T,
zone: &str,
) -> Result<(), anyhow::Error> {
let mut records: Vec<Record> = vec![];
for route in routes {
if route.hostname.is_empty() {
continue;
}
let Some(ipv4_addr) = route.ipv4_addr.unwrap_or_default().address else {
continue;
};
let record = RecordBuilder::default()
.rr_type(RecordType::A)
.name(format!("{}.{}", route.hostname, zone))
.value(ipv4_addr.to_string())
.ttl(Duration::from_secs(1))
.build()?;
// check record name valid for dns
if let Err(e) = record.name() {
tracing::error!("Invalid subdomain label: {}", e);
continue;
}
records.push(record);
}
let soa_record = RecordBuilder::default()
.rr_type(RecordType::SOA)
.name(zone.to_string())
.value(format!(
"ns.{} hostmaster.{} 2023101001 7200 3600 1209600 86400",
zone, zone
))
.ttl(Duration::from_secs(60))
.build()?;
records.push(soa_record);
let authority = build_authority(zone, &records)?;
self.dns_server
.upsert(
LowerName::from_str(zone)
.with_context(|| "Invalid zone name, expect format like \"et.net.\"")?,
Arc::new(authority),
)
.await;
tracing::debug!("Updated DNS records for zone {}: {:?}", zone, records);
Ok(())
}
pub async fn update(&self) {
for item in self.route_infos.iter() {
let zone = item.key();
let route_iter = item.value().flat_iter().map(|x| x.1);
if let Err(e) = self.update_dns_records(route_iter, zone).await {
tracing::error!("Failed to update DNS records for zone {}: {:?}", zone, e);
}
}
}
async fn keep_zone_authoritative(&self, zone: &str) {
if let Err(e) = self
.update_dns_records(std::iter::empty::<&Route>(), zone)
.await
{
tracing::error!(
"Failed to keep DNS zone {} authoritative after route prune: {:?}",
zone,
e
);
}
}
fn do_system_config(&self, zone: &str) -> Result<(), anyhow::Error> {
if let Some(c) = &self.system_config {
c.set_dns(&SystemConfig {
nameservers: vec![self.fake_ip.to_string()],
search_domains: vec![zone.to_string()],
match_domains: vec![zone.to_string()],
})?;
}
Ok(())
}
}
#[async_trait::async_trait]
impl MagicDnsServerRpc for MagicDnsServerInstanceData {
type Controller = BaseController;
async fn handshake(
&self,
_ctrl: Self::Controller,
_input: HandshakeRequest,
) -> crate::proto::rpc_types::error::Result<HandshakeResponse> {
Ok(Default::default())
}
async fn heartbeat(
&self,
_ctrl: Self::Controller,
_input: Void,
) -> crate::proto::rpc_types::error::Result<Void> {
Ok(Default::default())
}
async fn update_dns_record(
&self,
ctrl: Self::Controller,
input: UpdateDnsRecordRequest,
) -> crate::proto::rpc_types::error::Result<Void> {
let Some(tunnel_info) = ctrl.get_tunnel_info() else {
return Err(anyhow::anyhow!("No tunnel info").into());
};
let Some(remote_addr) = &tunnel_info.remote_addr else {
return Err(anyhow::anyhow!("No remote addr").into());
};
let zone = input.zone.clone();
let remote_addr: url::Url = remote_addr.clone().into();
let mut zone_removed = false;
if let Some(mut routes_by_addr) = self.route_infos.get_mut(&zone) {
routes_by_addr.remove(&remote_addr);
if !input.routes.is_empty() {
routes_by_addr.insert_many(remote_addr, input.routes);
}
zone_removed = routes_by_addr.is_empty();
} else if !input.routes.is_empty() {
let mut routes_by_addr = MultiMap::new();
routes_by_addr.insert_many(remote_addr, input.routes);
self.route_infos.insert(zone.clone(), routes_by_addr);
}
if zone_removed {
self.route_infos.remove(&zone);
self.keep_zone_authoritative(&zone).await;
}
self.update().await;
Ok(Default::default())
}
async fn get_dns_record(
&self,
_ctrl: Self::Controller,
_input: Void,
) -> crate::proto::rpc_types::error::Result<GetDnsRecordResponse> {
let mut ret = BTreeMap::new();
for item in self.route_infos.iter() {
let zone = item.key();
let routes = item.value();
let mut dns_records = DnsRecordList::default();
for route in routes.iter().map(|x| x.1) {
dns_records.records.push(DnsRecord {
record: Some(dns_record::Record::A(DnsRecordA {
name: format!("{}.{}", route.hostname, zone),
value: route.ipv4_addr.unwrap_or_default().address,
ttl: 1,
})),
});
}
ret.insert(zone.clone(), dns_records);
}
Ok(GetDnsRecordResponse { records: ret })
}
}
// This should only be used for UDP response.
// For other protocols, the variable `max_size` in `send_response` should be u16::MAX.
#[derive(Clone)]
struct ResponseWrapper {
response: Arc<Mutex<Vec<u8>>>,
}
trait RecordIter<'a>: Iterator<Item = &'a hickory_proto::rr::Record> + Send + 'a {}
impl<'a, T> RecordIter<'a> for T where T: Iterator<Item = &'a hickory_proto::rr::Record> + Send + 'a {}
#[async_trait::async_trait]
impl ResponseHandler for ResponseWrapper {
async fn send_response<'a>(
&mut self,
response: MessageResponse<
'_,
'a,
impl RecordIter<'a>,
impl RecordIter<'a>,
impl RecordIter<'a>,
impl RecordIter<'a>,
>,
) -> io::Result<ResponseInfo> {
let mut buffer = self
.response
.lock()
.map_err(|_| io::Error::other("lock poisoned"))?;
let mut encoder = BinEncoder::new(&mut buffer);
// `max_size` should be u16::MAX for protocol other than UDP.
let max_size = if let Some(edns) = response.get_edns() {
edns.max_payload()
} else {
hickory_proto::udp::MAX_RECEIVE_BUFFER_SIZE as u16
};
encoder.set_max_size(max_size);
response
.destructive_emit(&mut encoder)
.map_err(io::Error::other)
}
}
impl MagicDnsServerInstanceData {
/// Replace content of incoming UDP DNS request and ICMP echo request packet with reply data,
/// and swap source and destination IP addresses to send it back.
async fn handle_ip_packet(&self, zc_packet: &mut ZCPacket) -> Option<()> {
let (ip_header_length, ip_protocol, src_ip, dst_ip) = {
let ip_packet = Ipv4Packet::new(zc_packet.payload())?;
if ip_packet.get_version() != 4 {
return None;
}
(
ip_packet.get_header_length() as usize * 4,
ip_packet.get_next_level_protocol(),
ip_packet.get_source(),
ip_packet.get_destination(),
)
};
if dst_ip != self.fake_ip {
return None;
}
match ip_protocol {
IpNextHeaderProtocols::Udp => {
self.handle_udp_packet(zc_packet, ip_header_length, src_ip, dst_ip)
.await?;
}
IpNextHeaderProtocols::Icmp => {
self.handle_icmp_packet(zc_packet, ip_header_length)?;
}
_ => {
return None;
}
}
let mut ip_packet = MutableIpv4Packet::new(zc_packet.mut_payload())?;
ip_packet.set_source(dst_ip);
ip_packet.set_destination(src_ip);
ip_packet.set_checksum(ipv4::checksum(&ip_packet.to_immutable()));
zc_packet.mut_peer_manager_header().unwrap().to_peer_id = self.my_peer_id.into();
Some(())
}
/// Extract the DNS request message and send it to the hickory-dns server instance.
/// Replace the content of the UDP packet with the response message.
async fn handle_udp_packet(
&self,
zc_packet: &mut ZCPacket,
ip_header_length: usize,
src_ip: Ipv4Addr,
dst_ip: Ipv4Addr,
) -> Option<()> {
let (src_port, dst_port, request, request_length) = {
let udp_packet = UdpPacket::new(&zc_packet.payload()[ip_header_length..])?;
let src_port = udp_packet.get_source();
let dst_port = udp_packet.get_destination();
// Remove this to support any UDP port
if dst_port != 53 {
return None;
}
let request_payload = udp_packet.payload();
(
src_port,
dst_port,
Request::new(
MessageRequest::from_bytes(request_payload).ok()?,
SocketAddr::from(SocketAddrV4::new(src_ip, src_port)),
hickory_proto::xfer::Protocol::Udp,
),
request_payload.len(),
)
};
let response_payload = {
let response_payload_arc = Arc::new(Mutex::new(Vec::with_capacity(512)));
self.dns_server
.read_catalog()
.await
.handle_request(
&request,
ResponseWrapper {
response: response_payload_arc.clone(),
},
)
.await;
Arc::into_inner(response_payload_arc)?.into_inner().ok()?
};
let response_length = response_payload.len();
let delta_length = response_length as isize - request_length as isize;
let inner_length = (zc_packet.buf_len() as isize + delta_length) as usize;
if zc_packet.mut_inner().capacity() < inner_length {
let header_length = inner_length - response_length;
zc_packet.mut_inner().truncate(header_length);
}
zc_packet.mut_inner().resize(inner_length, 0);
let mut ip_packet = MutableIpv4Packet::new(zc_packet.mut_payload())?;
let ip_length = (ip_packet.get_total_length() as isize + delta_length) as u16;
ip_packet.set_total_length(ip_length);
let mut udp_packet = MutableUdpPacket::new(ip_packet.payload_mut())?;
let udp_length = (udp_packet.get_length() as isize + delta_length) as u16;
udp_packet.set_length(udp_length);
udp_packet.set_source(dst_port);
udp_packet.set_destination(src_port);
udp_packet.payload_mut().copy_from_slice(&response_payload);
udp_packet.set_checksum(udp::ipv4_checksum(
&udp_packet.to_immutable(),
&dst_ip,
&src_ip,
));
Some(())
}
fn handle_icmp_packet(&self, zc_packet: &mut ZCPacket, ip_header_length: usize) -> Option<()> {
let mut icmp_packet =
MutableIcmpPacket::new(&mut zc_packet.mut_payload()[ip_header_length..])?;
if icmp_packet.get_icmp_type() != IcmpTypes::EchoRequest {
return None;
}
icmp_packet.set_icmp_type(IcmpTypes::EchoReply);
icmp_packet.set_checksum(icmp::checksum(&icmp_packet.to_immutable()));
Some(())
}
}
#[async_trait::async_trait]
impl NicPacketFilter for MagicDnsServerInstanceData {
async fn try_process_packet_from_nic(&self, zc_packet: &mut ZCPacket) -> bool {
self.handle_ip_packet(zc_packet).await.is_some()
}
fn id(&self) -> String {
NIC_PIPELINE_NAME.to_string()
}
}
#[async_trait::async_trait]
impl RpcServerHook for MagicDnsServerInstanceData {
async fn on_new_client(
&self,
tunnel_info: Option<TunnelInfo>,
) -> Result<Option<TunnelInfo>, anyhow::Error> {
tracing::info!(?tunnel_info, "New client connected");
Ok(tunnel_info)
}
async fn on_client_disconnected(&self, tunnel_info: Option<TunnelInfo>) {
tracing::info!(?tunnel_info, "Client disconnected");
let Some(tunnel_info) = tunnel_info else {
return;
};
let Some(remote_addr) = tunnel_info.remote_addr else {
return;
};
let remote_addr = remote_addr.into();
let mut removed_zones = vec![];
for mut item in self.route_infos.iter_mut() {
item.value_mut().remove(&remote_addr);
if item.value().is_empty() {
removed_zones.push(item.key().clone());
}
}
for zone in &removed_zones {
self.route_infos.remove(zone);
}
for zone in removed_zones {
self.keep_zone_authoritative(&zone).await;
}
self.update().await;
}
}
pub struct MagicDnsServerInstance {
rpc_server: StandAloneServer<TcpTunnelListener>,
pub(super) data: Arc<MagicDnsServerInstanceData>,
peer_mgr: Arc<PeerManager>,
tun_inet: Ipv4Inet,
}
fn get_system_config(
_tun_name: Option<&str>,
) -> Result<Option<Box<dyn SystemConfigurator>>, anyhow::Error> {
#[cfg(target_os = "windows")]
{
use crate::dns::system::windows::WindowsDNSManager;
let tun_name = _tun_name.ok_or_else(|| anyhow::anyhow!("No tun name"))?;
return Ok(Some(Box::new(WindowsDNSManager::new(tun_name)?)));
}
#[cfg(all(target_os = "macos", not(feature = "macos-ne")))]
{
use crate::dns::system_config::darwin::DarwinConfigurator;
return Ok(Some(Box::new(DarwinConfigurator::new())));
}
#[allow(unreachable_code)]
Ok(None)
}
impl MagicDnsServerInstance {
pub async fn new(
peer_mgr: Arc<PeerManager>,
tun_dev: Option<String>,
tun_inet: Ipv4Inet,
fake_ip: Ipv4Addr,
) -> Result<Self, anyhow::Error> {
let tcp_listener = TcpTunnelListener::new(MAGIC_DNS_INSTANCE_ADDR.parse()?);
let mut rpc_server = StandAloneServer::new(tcp_listener);
rpc_server.serve().await?;
let dns_config = RunConfigBuilder::default()
.general(GeneralConfigBuilder::default().build()?)
.excluded_forward_nameservers(vec![fake_ip.into()])
.build()?;
let mut dns_server = Server::new(dns_config);
dns_server.run().await?;
if !tun_inet.contains(&fake_ip) {
if let Some(tun_dev_name) = &tun_dev {
let cost = if cfg!(target_os = "windows") {
Some(4)
} else {
None
};
let ifcfg = IfConfiger {};
ifcfg
.add_ipv4_route(tun_dev_name, fake_ip, 32, cost)
.await?;
}
}
let data = Arc::new(MagicDnsServerInstanceData {
dns_server,
tun_dev: tun_dev.clone(),
tun_ip: tun_inet.address(),
fake_ip,
my_peer_id: peer_mgr.my_peer_id(),
route_infos: DashMap::new(),
system_config: get_system_config(tun_dev.as_deref())?,
});
rpc_server
.registry()
.register(MagicDnsServerRpcServer::new_arc(data.clone()), "");
rpc_server.set_hook(data.clone());
peer_mgr
.add_nic_packet_process_pipeline(Box::new(data.clone()))
.await;
// Use configured tld_dns_zone or fall back to DEFAULT_ET_DNS_ZONE if empty
let flags = peer_mgr.get_global_ctx().config.get_flags();
let tld_dns_zone_clone = flags.tld_dns_zone.clone();
data.update_dns_records(std::iter::empty(), &tld_dns_zone_clone)
.await
.context("Failed to initialize DNS zone")?;
let data_clone = data.clone();
tokio::task::spawn_blocking(move || data_clone.do_system_config(&tld_dns_zone_clone))
.await
.context("Failed to configure system")??;
Ok(Self {
rpc_server,
data,
peer_mgr,
tun_inet,
})
}
pub async fn clean_env(&self) {
if let Some(configer) = &self.data.system_config {
let ret = configer.clean();
if let Err(e) = ret {
tracing::error!("Failed to close system config: {:?}", e);
}
if !self.tun_inet.contains(&self.data.fake_ip) {
if let Some(tun_dev_name) = &self.data.tun_dev {
let ifcfg = IfConfiger {};
let _ = ifcfg
.remove_ipv4_route(tun_dev_name, self.data.fake_ip, 32)
.await;
}
}
}
let _ = self
.peer_mgr
.remove_nic_packet_process_pipeline(NIC_PIPELINE_NAME.to_string())
.await;
}
}
impl Drop for MagicDnsServerInstance {
fn drop(&mut self) {
println!("MagicDnsServerInstance dropped");
}
}
-327
View File
@@ -1,327 +0,0 @@
use std::net::{Ipv4Addr, SocketAddr};
use std::str::FromStr as _;
use std::sync::Arc;
use std::time::Duration;
use cidr::Ipv4Inet;
use hickory_client::client::{Client, ClientHandle as _};
use hickory_proto::rr;
use hickory_proto::runtime::TokioRuntimeProvider;
use hickory_proto::udp::UdpClientStream;
use tokio::sync::Notify;
use tokio_util::sync::CancellationToken;
use crate::common::global_ctx::tests::get_mock_global_ctx;
use crate::connector::udp_hole_punch::tests::replace_stun_info_collector;
use crate::instance::dns_server::runner::DnsRunner;
use crate::instance::dns_server::server_instance::MagicDnsServerInstance;
use crate::instance::dns_server::{DEFAULT_ET_DNS_ZONE, MAGIC_DNS_FAKE_IP};
use crate::instance::virtual_nic::NicCtx;
use crate::peers::peer_manager::{PeerManager, RouteAlgoType};
use crate::peers::create_packet_recv_chan;
use crate::proto::api::instance::Route;
use crate::proto::common::NatType;
use crate::proto::magic_dns::{MagicDnsServerRpc as _, UpdateDnsRecordRequest};
use crate::proto::rpc_types::controller::{BaseController, Controller as _};
pub async fn prepare_env(dns_name: &str, tun_ip: Ipv4Inet) -> (Arc<PeerManager>, NicCtx) {
prepare_env_with_tld_dns_zone(dns_name, tun_ip, None).await
}
pub async fn prepare_env_with_tld_dns_zone(
dns_name: &str,
tun_ip: Ipv4Inet,
tld_dns_zone: Option<&str>,
) -> (Arc<PeerManager>, NicCtx) {
let ctx = get_mock_global_ctx();
ctx.set_hostname(dns_name.to_owned());
ctx.set_ipv4(Some(tun_ip));
if tld_dns_zone.is_some() {
let mut flags = ctx.config.get_flags();
flags.accept_dns = true; // Enable DNS
if let Some(zone) = tld_dns_zone {
flags.tld_dns_zone = zone.to_string();
}
ctx.set_flags(flags);
}
let (s, r) = create_packet_recv_chan();
let peer_mgr = Arc::new(PeerManager::new(RouteAlgoType::Ospf, ctx, s));
peer_mgr.run().await.unwrap();
replace_stun_info_collector(peer_mgr.clone(), NatType::PortRestricted);
let r = Arc::new(tokio::sync::Mutex::new(r));
let mut virtual_nic = NicCtx::new(
peer_mgr.get_global_ctx(),
&peer_mgr,
r,
Arc::new(Notify::new()),
);
virtual_nic.run(Some(tun_ip), None).await.unwrap();
(peer_mgr, virtual_nic)
}
pub async fn check_dns_record(fake_ip: &Ipv4Addr, domain: &str, expected_ip: &str) {
let stream = UdpClientStream::builder(
SocketAddr::new((*fake_ip).into(), 53),
TokioRuntimeProvider::default(),
)
.build();
let (mut client, background) = Client::connect(stream).await.unwrap();
let background_task = tokio::spawn(background);
let response = client
.query(
rr::Name::from_str(domain).unwrap(),
rr::DNSClass::IN,
rr::RecordType::A,
)
.await
.unwrap_or_else(|e| panic!("DNS query failed unexpectedly for domain '{domain}': {e}"));
background_task.abort();
let _ = background_task.await;
println!("Response: {:?}", response);
assert_eq!(response.answers().len(), 1, "{:?}", response.answers());
let resp = response.answers().first().unwrap();
assert_eq!(
resp.clone().into_parts().rdata.into_a().unwrap().0,
expected_ip.parse::<Ipv4Addr>().unwrap()
);
}
pub async fn check_dns_record_missing(fake_ip: &Ipv4Addr, domain: &str) {
let stream = UdpClientStream::builder(
SocketAddr::new((*fake_ip).into(), 53),
TokioRuntimeProvider::default(),
)
.build();
let (mut client, background) = Client::connect(stream).await.unwrap();
let background_task = tokio::spawn(background);
let response = client
.query(
rr::Name::from_str(domain).unwrap(),
rr::DNSClass::IN,
rr::RecordType::A,
)
.await
.unwrap_or_else(|e| {
panic!("DNS query for missing record failed unexpectedly for domain '{domain}': {e}")
});
background_task.abort();
let _ = background_task.await;
assert!(response.answers().is_empty(), "{:?}", response.answers());
}
#[tokio::test]
async fn test_magic_dns_server_instance() {
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();
let fake_ip = Ipv4Addr::from_str("100.100.100.101").unwrap();
let dns_server_inst =
MagicDnsServerInstance::new(peer_mgr.clone(), Some(tun_name), tun_ip, fake_ip)
.await
.unwrap();
let routes = [
Route {
hostname: "test1".to_string(),
ipv4_addr: Some(Ipv4Inet::from_str("8.8.8.8/24").unwrap().into()),
..Default::default()
},
Route {
hostname: "中文".to_string(),
ipv4_addr: Some(Ipv4Inet::from_str("8.8.8.8/24").unwrap().into()),
..Default::default()
},
Route {
hostname: ".invalid".to_string(),
ipv4_addr: Some(Ipv4Inet::from_str("8.8.8.8/24").unwrap().into()),
..Default::default()
},
];
dns_server_inst
.data
.update_dns_records(routes.iter(), DEFAULT_ET_DNS_ZONE)
.await
.unwrap();
check_dns_record(&fake_ip, "test1.et.net", "8.8.8.8").await;
check_dns_record(&fake_ip, "中文.et.net", "8.8.8.8").await;
}
#[tokio::test]
async fn test_magic_dns_runner() {
// Test first runner with default DNS settings
{
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();
let fake_ip = Ipv4Addr::from_str(MAGIC_DNS_FAKE_IP).unwrap();
let mut dns_runner = DnsRunner::new(peer_mgr, Some(tun_name), 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;
});
tokio::time::sleep(Duration::from_secs(3)).await;
// Test default settings: query should resolve test1.et.net to tunnel IP via default fake IP
check_dns_record(&fake_ip, "test1.et.net", "10.144.144.10").await;
cancel_token.cancel();
t.await.unwrap();
// Wait a bit for cleanup
tokio::time::sleep(Duration::from_secs(1)).await;
}
// Test second runner with different TLD zone
{
let tun_ip = Ipv4Inet::from_str("10.144.144.20/24").unwrap();
// NOTE: Using same fake IP to avoid system DNS configuration conflicts
let custom_tld_zone = "custom.local."; // Different TLD zone is safer
let (peer_mgr, virtual_nic) =
prepare_env_with_tld_dns_zone("test2", tun_ip, Some(custom_tld_zone)).await;
let tun_name = virtual_nic.ifname().await.unwrap();
let fake_ip = Ipv4Addr::from_str(MAGIC_DNS_FAKE_IP).unwrap();
let mut dns_runner = DnsRunner::new(peer_mgr, Some(tun_name), 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;
});
tokio::time::sleep(Duration::from_secs(3)).await;
// Test with same fake IP but different TLD zone
check_dns_record(&fake_ip, "test2.custom.local", "10.144.144.20").await;
cancel_token.cancel();
t.await.unwrap();
}
}
#[tokio::test]
async fn test_magic_dns_update_replaces_records_for_same_client() {
let tun_ip = Ipv4Inet::from_str("100.100.100.0/24").unwrap();
let ctx = get_mock_global_ctx();
ctx.set_hostname("test1".to_string());
ctx.set_ipv4(Some(tun_ip));
let (s, _r) = create_packet_recv_chan();
let peer_mgr = Arc::new(PeerManager::new(RouteAlgoType::Ospf, ctx, s));
peer_mgr.run().await.unwrap();
replace_stun_info_collector(peer_mgr.clone(), NatType::PortRestricted);
let fake_ip = Ipv4Addr::from_str(MAGIC_DNS_FAKE_IP).unwrap();
let dns_server_inst = MagicDnsServerInstance::new(peer_mgr.clone(), None, tun_ip, fake_ip)
.await
.unwrap();
let mut ctrl = BaseController::default();
ctrl.set_tunnel_info(Some(crate::proto::common::TunnelInfo {
tunnel_type: "tcp".to_string(),
local_addr: None,
remote_addr: Some(crate::proto::common::Url {
url: "tcp://127.0.0.1:54321".to_string(),
}),
resolved_remote_addr: None,
}));
dns_server_inst
.data
.update_dns_record(
ctrl.clone(),
UpdateDnsRecordRequest {
zone: DEFAULT_ET_DNS_ZONE.to_string(),
routes: vec![Route {
hostname: "test1".to_string(),
ipv4_addr: Some(Ipv4Inet::from_str("8.8.8.8/32").unwrap().into()),
..Default::default()
}],
},
)
.await
.unwrap();
dns_server_inst
.data
.update_dns_record(
ctrl,
UpdateDnsRecordRequest {
zone: DEFAULT_ET_DNS_ZONE.to_string(),
routes: vec![Route {
hostname: "test1".to_string(),
ipv4_addr: Some(Ipv4Inet::from_str("1.1.1.1/32").unwrap().into()),
..Default::default()
}],
},
)
.await
.unwrap();
let dns_records = dns_server_inst
.data
.get_dns_record(
BaseController::default(),
crate::proto::common::Void::default(),
)
.await
.unwrap();
let zone_records = dns_records.records.get(DEFAULT_ET_DNS_ZONE).unwrap();
let a_records = zone_records
.records
.iter()
.filter_map(|record| match record.record.as_ref() {
Some(crate::proto::magic_dns::dns_record::Record::A(a))
if a.name == "test1.et.net." =>
{
Some(a)
}
_ => None,
})
.collect::<Vec<_>>();
assert_eq!(a_records.len(), 1, "{a_records:?}");
let resolved_ip = Ipv4Addr::from(a_records[0].value.unwrap_or_default());
assert_eq!(resolved_ip, Ipv4Addr::new(1, 1, 1, 1));
let mut ctrl = BaseController::default();
ctrl.set_tunnel_info(Some(crate::proto::common::TunnelInfo {
tunnel_type: "tcp".to_string(),
local_addr: None,
remote_addr: Some(crate::proto::common::Url {
url: "tcp://127.0.0.1:54321".to_string(),
}),
resolved_remote_addr: None,
}));
dns_server_inst
.data
.update_dns_record(
ctrl,
UpdateDnsRecordRequest {
zone: DEFAULT_ET_DNS_ZONE.to_string(),
routes: vec![],
},
)
.await
.unwrap();
let dns_records = dns_server_inst
.data
.get_dns_record(
BaseController::default(),
crate::proto::common::Void::default(),
)
.await
.unwrap();
assert!(!dns_records.records.contains_key(DEFAULT_ET_DNS_ZONE));
}
+5 -125
View File
@@ -14,8 +14,6 @@ use futures::FutureExt;
use tokio::sync::{Mutex, Notify}; use tokio::sync::{Mutex, Notify};
#[cfg(feature = "tun")] #[cfg(feature = "tun")]
use tokio::{sync::oneshot, task::JoinSet}; use tokio::{sync::oneshot, task::JoinSet};
#[cfg(feature = "magic-dns")]
use tokio_util::sync::CancellationToken;
use crate::common::acl_processor::AclRuleBuilder; use crate::common::acl_processor::AclRuleBuilder;
use crate::common::config::ConfigLoader; use crate::common::config::ConfigLoader;
@@ -62,8 +60,6 @@ use crate::rpc_service::InstanceRpcService;
use crate::utils::weak_upgrade; use crate::utils::weak_upgrade;
use crate::vpn_portal::{self, VpnPortal}; use crate::vpn_portal::{self, VpnPortal};
#[cfg(feature = "magic-dns")]
use super::dns_server::{runner::DnsRunner, MAGIC_DNS_FAKE_IP};
use super::listeners::ListenerManager; use super::listeners::ListenerManager;
#[cfg(feature = "socks5")] #[cfg(feature = "socks5")]
@@ -133,63 +129,8 @@ impl IpProxy {
#[cfg(feature = "tun")] #[cfg(feature = "tun")]
type NicCtx = super::virtual_nic::NicCtx; type NicCtx = super::virtual_nic::NicCtx;
#[cfg(feature = "magic-dns")]
struct MagicDnsContainer {
dns_runner_task: ScopedTask<()>,
dns_runner_cancel_token: CancellationToken,
}
// nic container will be cleared when dhcp ip changed
#[cfg(feature = "tun")] #[cfg(feature = "tun")]
pub struct NicCtxContainer { type ArcNicCtx = Arc<Mutex<Option<Box<dyn Any + 'static + Send>>>>;
nic_ctx: Option<Box<dyn Any + 'static + Send>>,
#[cfg(feature = "magic-dns")]
magic_dns: Option<MagicDnsContainer>,
}
#[cfg(feature = "tun")]
impl NicCtxContainer {
#[cfg(not(feature = "magic-dns"))]
fn new(nic_ctx: NicCtx) -> Self {
Self {
nic_ctx: Some(Box::new(nic_ctx)),
}
}
#[cfg(feature = "magic-dns")]
fn new(nic_ctx: NicCtx, dns_runner: Option<DnsRunner>) -> Self {
if let Some(mut dns_runner) = dns_runner {
let token = CancellationToken::new();
let token_clone = token.clone();
let task = tokio::spawn(async move {
let _ = dns_runner.run(token_clone).await;
});
Self {
nic_ctx: Some(Box::new(nic_ctx)),
magic_dns: Some(MagicDnsContainer {
dns_runner_task: task.into(),
dns_runner_cancel_token: token,
}),
}
} else {
Self {
nic_ctx: Some(Box::new(nic_ctx)),
magic_dns: None,
}
}
}
fn new_with_any<T: 'static + Send>(ctx: T) -> Self {
Self {
nic_ctx: Some(Box::new(ctx)),
#[cfg(feature = "magic-dns")]
magic_dns: None,
}
}
}
#[cfg(feature = "tun")]
type ArcNicCtx = Arc<Mutex<Option<NicCtxContainer>>>;
pub struct InstanceRpcServerHook { pub struct InstanceRpcServerHook {
rpc_portal_whitelist: Vec<IpCidr>, rpc_portal_whitelist: Vec<IpCidr>,
@@ -666,16 +607,6 @@ impl Instance {
arc_nic_ctx: ArcNicCtx, arc_nic_ctx: ArcNicCtx,
packet_recv: Arc<Mutex<PacketRecvChanReceiver>>, packet_recv: Arc<Mutex<PacketRecvChanReceiver>>,
) { ) {
#[cfg(feature = "magic-dns")]
if let Some(old_ctx) = arc_nic_ctx.lock().await.take() {
if let Some(dns_runner) = old_ctx.magic_dns {
dns_runner.dns_runner_cancel_token.cancel();
tracing::debug!("cancelling dns runner task");
let ret = dns_runner.dns_runner_task.await;
tracing::debug!("dns runner task cancelled, ret: {:?}", ret);
}
};
let mut tasks = JoinSet::new(); let mut tasks = JoinSet::new();
tasks.spawn(async move { tasks.spawn(async move {
let mut packet_recv = packet_recv.lock().await; let mut packet_recv = packet_recv.lock().await;
@@ -683,46 +614,15 @@ impl Instance {
tracing::trace!("packet consumed by mock nic ctx: {:?}", packet); tracing::trace!("packet consumed by mock nic ctx: {:?}", packet);
} }
}); });
arc_nic_ctx arc_nic_ctx.lock().await.replace(Box::new(tasks));
.lock()
.await
.replace(NicCtxContainer::new_with_any(tasks));
tracing::debug!("nic ctx cleared."); tracing::debug!("nic ctx cleared.");
} }
#[cfg(feature = "magic-dns")]
fn create_magic_dns_runner(
peer_mgr: Arc<PeerManager>,
tun_dev: Option<String>,
tun_ip: Ipv4Inet,
) -> Option<DnsRunner> {
let ctx = peer_mgr.get_global_ctx();
if !ctx.config.get_flags().accept_dns {
return None;
}
let runner = DnsRunner::new(
peer_mgr,
tun_dev,
tun_ip,
MAGIC_DNS_FAKE_IP.parse().unwrap(),
);
Some(runner)
}
#[cfg(feature = "tun")] #[cfg(feature = "tun")]
async fn use_new_nic_ctx( async fn use_new_nic_ctx(arc_nic_ctx: ArcNicCtx, nic_ctx: NicCtx) {
arc_nic_ctx: ArcNicCtx,
nic_ctx: NicCtx,
#[cfg(feature = "magic-dns")] magic_dns: Option<DnsRunner>,
) {
let mut g = arc_nic_ctx.lock().await; let mut g = arc_nic_ctx.lock().await;
*g = Some(NicCtxContainer::new( *g = Some(Box::new(nic_ctx));
nic_ctx,
#[cfg(feature = "magic-dns")]
magic_dns,
));
tracing::debug!("nic ctx updated."); tracing::debug!("nic ctx updated.");
} }
@@ -826,15 +726,7 @@ impl Instance {
global_ctx_c.set_ipv4(None); global_ctx_c.set_ipv4(None);
continue; continue;
} }
#[cfg(feature = "magic-dns")] Self::use_new_nic_ctx(nic_ctx.clone(), new_nic_ctx).await;
let ifname = new_nic_ctx.ifname().await;
Self::use_new_nic_ctx(
nic_ctx.clone(),
new_nic_ctx,
#[cfg(feature = "magic-dns")]
Self::create_magic_dns_runner(peer_manager_c.clone(), ifname, ip),
)
.await;
} }
current_dhcp_ip = Some(ip); current_dhcp_ip = Some(ip);
@@ -895,18 +787,6 @@ impl Instance {
continue; continue;
} }
// Create Magic DNS runner only if we have IPv4
#[cfg(feature = "magic-dns")]
{
let ifname = new_nic_ctx.ifname().await;
let dns_runner = if let Some(ipv4) = ipv4_addr {
Self::create_magic_dns_runner(peer_mgr, ifname, ipv4)
} else {
None
};
Self::use_new_nic_ctx(nic_ctx.clone(), new_nic_ctx, dns_runner).await;
}
#[cfg(not(feature = "magic-dns"))]
Self::use_new_nic_ctx(nic_ctx.clone(), new_nic_ctx).await; Self::use_new_nic_ctx(nic_ctx.clone(), new_nic_ctx).await;
} }
-1
View File
@@ -1,4 +1,3 @@
pub mod dns_server;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
pub mod instance; pub mod instance;
+1 -1
View File
@@ -65,7 +65,7 @@ message FlagsInConfig {
bool disable_sym_hole_punching = 30; bool disable_sym_hole_punching = 30;
// tld dns zone for magic dns // tld dns zone for magic dns
string tld_dns_zone = 31; string tld_dns_zone = 31 [deprecated = true];
bool p2p_only = 32; bool p2p_only = 32;