mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-02 09:09:17 +00:00
remove old magic-dns
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
@@ -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,4 +1,3 @@
|
|||||||
pub mod dns_server;
|
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user