Skip to content
Open

Dns #14

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
34d95aa
feat(dns): prepare the dns enhance mode
MFSGA Mar 9, 2026
f04b960
feat(dns): add the relative trait
MFSGA Mar 9, 2026
42a9a48
feat(dns): add the nameserver and default_nameserver support
MFSGA Mar 9, 2026
9bbd282
feat(dns): add the fallback nameserver_policy support
MFSGA Mar 9, 2026
56a75e8
feat(dns): add the exchange support
MFSGA Mar 9, 2026
387b11f
feat(dns): add handle_request support
MFSGA Mar 9, 2026
33d4c27
feat(dns): support lru cache
MFSGA Mar 9, 2026
9852f3e
feat(dns): start to impl fake-ip
MFSGA Mar 9, 2026
091b635
feat(dns): support manual hosts
MFSGA Mar 9, 2026
57949a8
feat(dns): support exchange and optimize EnhancedResolver for exchange
MFSGA Mar 9, 2026
4df6727
feat(dns): support lookup_ip and exchange_no_cache
MFSGA Mar 9, 2026
7cbc799
feat(dns): support ip_exchange
MFSGA Mar 9, 2026
4493b37
feat(dns): support query_resolvers_by_priority
MFSGA Mar 9, 2026
9d47faf
feat(dns): support fallback_filter
MFSGA Mar 9, 2026
2736168
feat(dns): use StringTrie to improve the performance
MFSGA Mar 9, 2026
220574d
feat(dns): use StringTrie for DomainFilter
MFSGA Mar 9, 2026
a4fdc9a
feat(dns): use StringTrie for skipped_hostnames
MFSGA Mar 9, 2026
59904ff
feat(dns): support DnsClient
MFSGA Mar 10, 2026
3e701a9
feat(dns): optimize DnsClient and EnhancedResolver
MFSGA Mar 10, 2026
d016bc7
feat(dns): optimize DnsClient
MFSGA Mar 10, 2026
122f605
feat(dns): optimize edns_client_subnet
MFSGA Mar 10, 2026
f730b4d
feat(dns): optimize ensure_resolver
MFSGA Mar 10, 2026
cacbf32
feat(dns): add Multiple for DNSListen
MFSGA Mar 10, 2026
729a606
feat(dns): add EnhancedResolver as default_resolver
MFSGA Mar 10, 2026
af42848
feat(dns): solve the url dep error
MFSGA Mar 11, 2026
e8d2fc6
feat(DnsClient): add fw_mark bind_addr support
MFSGA Mar 11, 2026
2618c80
feat(DnsClient): add fw_mark bind_addr support
MFSGA Mar 11, 2026
d07a2c2
feat(DnsClient): support udp proxy
MFSGA Mar 11, 2026
f307ece
feat(dns): support udp and tcp to exchange
MFSGA Mar 11, 2026
c822a14
feat(dns): support dhcp
MFSGA Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 192 additions & 8 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions clash-dns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ futures = "0.3"
async-trait = "0.1"

hickory-server = { version = "0.25", default-features = false }
hickory-proto = "0.25"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
104 changes: 97 additions & 7 deletions clash-dns/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use std::time::Duration;
use crate::utils::new_io_error;
use crate::{DNSListenAddr, DnsMessageExchanger};
use async_trait::async_trait;
use hickory_proto::op::{Header, Message, ResponseCode};
use hickory_server::server::Request;
use hickory_server::{
ServerFuture,
authority::MessageResponseBuilder,
server::{RequestHandler, ResponseHandler, ResponseInfo},
};
use thiserror::Error;
Expand All @@ -24,10 +26,10 @@ struct DnsHandler<X> {
pub enum DNSError {
#[error(transparent)]
Io(#[from] std::io::Error),
/* #[error("invalid OP code: {0}")]
#[error("invalid OP query: {0}")]
InvalidOpQuery(String),
#[error("query failed: {0}")]
QueryFailed(String), */
QueryFailed(String),
}

#[async_trait]
Expand All @@ -38,9 +40,44 @@ where
async fn handle_request<H: ResponseHandler>(
&self,
request: &Request,
response_handle: H,
mut response_handle: H,
) -> ResponseInfo {
todo!()
let req = match to_dns_message(request) {
Ok(req) => req,
Err(err) => {
error!("failed to parse dns request: {}", err);
return servfail_info();
}
};

let resp = match self.exchanger.exchange(&req).await {
Ok(resp) => resp,
Err(err) => {
warn!("dns exchange failed: {}", err);
build_servfail_message(&req)
}
};

let mut builder = MessageResponseBuilder::from_message_request(request);
if let Some(edns) = resp.extensions().clone() {
builder.edns(edns);
}

let response = builder.build(
resp.header().clone(),
resp.answers(),
resp.name_servers(),
std::iter::empty::<&hickory_proto::rr::Record>(),
resp.additionals(),
);

match response_handle.send_response(response).await {
Ok(info) => info,
Err(err) => {
error!("failed to send dns response: {}", err);
servfail_info()
}
}
}
}

Expand Down Expand Up @@ -84,15 +121,18 @@ where
.is_ok();
}
if let Some(c) = listen.doh {
todo!()
let _ = c;
warn!("DoH listener is not implemented yet");
}

if let Some(c) = listen.dot {
todo!()
let _ = c;
warn!("DoT listener is not implemented yet");
}

if let Some(c) = listen.doh3 {
todo!()
let _ = c;
warn!("DoH3 listener is not implemented yet");
}

if !has_server {
Expand All @@ -109,3 +149,53 @@ where
})
}))
}

fn to_dns_message(request: &Request) -> Result<Message, DNSError> {
let mut message = Message::new();
message.set_id(request.id());
message.set_op_code(request.op_code());
message.set_message_type(request.message_type());
message.set_authoritative(request.authoritative());
message.set_truncated(request.truncated());
message.set_recursion_desired(request.recursion_desired());
message.set_recursion_available(request.recursion_available());
message.set_authentic_data(request.authentic_data());
message.set_checking_disabled(request.checking_disabled());
message.set_response_code(request.response_code());
message.add_queries(request.queries().iter().map(|q| q.original().clone()));
message.add_answers(request.answers().iter().cloned());
message.add_name_servers(request.name_servers().iter().cloned());
message.add_additionals(request.additionals().iter().cloned());
if let Some(edns) = request.edns().cloned() {
message.set_edns(edns);
}
Ok(message)
}

fn build_servfail_message(req: &Message) -> Message {
let mut header = Header::response_from_request(req.header());
header.set_response_code(ResponseCode::ServFail);

let mut message = Message::new();
message.set_id(header.id());
message.set_message_type(header.message_type());
message.set_op_code(header.op_code());
message.set_authoritative(header.authoritative());
message.set_truncated(header.truncated());
message.set_recursion_desired(header.recursion_desired());
message.set_recursion_available(header.recursion_available());
message.set_authentic_data(header.authentic_data());
message.set_checking_disabled(header.checking_disabled());
message.set_response_code(header.response_code());
message.add_queries(req.queries().iter().cloned());
if let Some(edns) = req.extensions().clone() {
message.set_edns(edns);
}
message
}

fn servfail_info() -> ResponseInfo {
let mut header = Header::new();
header.set_response_code(ResponseCode::ServFail);
header.into()
}
6 changes: 5 additions & 1 deletion clash-dns/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::{net::SocketAddr, path::Path};

use async_trait::async_trait;
use hickory_proto::op::Message;

mod handler;

mod utils;
Expand All @@ -26,7 +29,8 @@ pub struct DNSListenAddr {
pub doh3: Option<DoH3Config>,
}

#[async_trait]
pub trait DnsMessageExchanger: Send + Sync {
fn ipv6(&self) -> bool;
// async fn exchange(&self, message: &Message) -> Result<Message, DNSError>;
async fn exchange(&self, message: &Message) -> Result<Message, DNSError>;
}
6 changes: 4 additions & 2 deletions clash-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ h3-quinn = { version = "0.0.10", optional = true }
quinn-proto = { version = "0.11.13", default-features = false, optional = true }
maxminddb = "0.27"
hickory-proto = "0.25"
url = { version = "2", optional = true }
hickory-resolver = { version = "0.25", features = ["tokio", "system-config", "webpki-roots", "tls-aws-lc-rs", "https-aws-lc-rs"] }
url = { version = "2" }
ipnet = { version = "2" }
lru_time_cache = "0.11"
network-interface = { version = "2", optional = true }

serde = { version = "1", features = ["derive"] }
Expand Down Expand Up @@ -154,7 +156,7 @@ tun = [
"dep:watfaq-netstack",
"dep:smoltcp",
"dep:network-interface",
"dep:url",
# "dep:url",
]
tproxy = ["dep:etherparse"]
redir = []
Expand Down
28 changes: 26 additions & 2 deletions clash-lib/src/app/dispatcher/dispatcher_impl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{fmt, sync::Arc, time::Duration};

use tokio::{io::AsyncWriteExt, sync::RwLock};
use tracing::{Instrument, debug, info_span, instrument, trace, warn};
use tracing::{Instrument, debug, error, info_span, instrument, trace, warn};
use tracing_log::log;

use crate::{
Expand Down Expand Up @@ -228,7 +228,31 @@ async fn reverse_lookup(
) -> Option<SocksAddr> {
let dst = match dst {
crate::session::SocksAddr::Ip(socket_addr) => {
todo!()
if resolver.fake_ip_enabled() {
let ip = socket_addr.ip();
if resolver.is_fake_ip(ip).await {
trace!("looking up fake ip: {}", socket_addr.ip());
match resolver.reverse_lookup(ip).await {
Some(host) => (host, socket_addr.port())
.try_into()
.expect("must be valid domain"),
None => {
error!("failed to reverse lookup fake ip: {}", ip);
return None;
}
}
} else {
(*socket_addr).into()
}
} else {
trace!("looking up resolve cache ip: {}", socket_addr.ip());
match resolver.cached_for(socket_addr.ip()).await {
Some(host) => (host, socket_addr.port())
.try_into()
.expect("must be valid domain"),
None => (*socket_addr).into(),
}
}
}
crate::session::SocksAddr::Domain(host, port) => (host.to_owned(), *port)
.try_into()
Expand Down
Loading