Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 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
85ee4af
feat(api): support unix port
MFSGA Mar 11, 2026
7628593
feat(api): support patch_configs
MFSGA Mar 11, 2026
da0b529
feat(andriod): add andriod platform support
MFSGA Mar 13, 2026
10515fc
feat(tun): support udp
MFSGA Mar 13, 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 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 @@
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 @@
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(),

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / i686-unknown-linux-musl

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / armv7-unknown-linux-gnueabi

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / armv7-unknown-linux-gnueabihf

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-linux-gnu

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / aarch64-unknown-linux-gnu

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-linux-musl

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc

using `clone` on type `Header` which implements the `Copy` trait

Check failure on line 67 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / aarch64-pc-windows-msvc

using `clone` on type `Header` which implements the `Copy` trait
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 All @@ -49,7 +86,7 @@
pub async fn get_dns_listener<X>(
listen: DNSListenAddr,
exchanger: X,
cwd: &std::path::Path,

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / i686-unknown-linux-musl

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / armv7-unknown-linux-gnueabi

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / armv7-unknown-linux-gnueabihf

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-linux-gnu

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / aarch64-unknown-linux-gnu

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-linux-musl

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc

unused variable: `cwd`

Check failure on line 89 in clash-dns/src/handler.rs

View workflow job for this annotation

GitHub Actions / aarch64-pc-windows-msvc

unused variable: `cwd`
) -> Option<futures::future::BoxFuture<'static, Result<(), DNSError>>>
where
X: DnsMessageExchanger + Sync + Send + Unpin + 'static,
Expand Down Expand Up @@ -84,15 +121,18 @@
.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 @@
})
}))
}

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};

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / i686-unknown-linux-musl

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / armv7-unknown-linux-gnueabi

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / armv7-unknown-linux-gnueabihf

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-linux-gnu

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / aarch64-unknown-linux-gnu

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / x86_64-unknown-linux-musl

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / i686-pc-windows-msvc

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / x86_64-pc-windows-msvc

unused import: `path::Path`

Check failure on line 1 in clash-dns/src/lib.rs

View workflow job for this annotation

GitHub Actions / aarch64-pc-windows-msvc

unused import: `path::Path`

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

mod handler;

mod utils;
Expand All @@ -26,7 +29,8 @@
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
103 changes: 100 additions & 3 deletions clash-lib/src/app/api/handlers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ use crate::{
dispatcher::Dispatcher,
dns::ThreadSafeDNSResolver,
inbound::manager::{InboundManager, Ports},
logging,
},
config::{
def::{self},
internal::config::BindAddress,
},
config::def::{self, LogLevel, RunMode},
};

#[derive(Clone)]
Expand All @@ -36,7 +40,10 @@ pub fn routes(
dns_resolver: ThreadSafeDNSResolver,
) -> Router<Arc<AppState>> {
Router::new()
.route("/", get(get_configs).put(update_configs))
.route(
"/",
get(get_configs).put(update_configs).patch(patch_configs),
)
.with_state(ConfigState {
inbound_manager,
dispatcher,
Expand All @@ -45,7 +52,7 @@ pub fn routes(
})
}

#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct PatchConfigRequest {
port: Option<u16>,
Expand All @@ -60,6 +67,26 @@ struct PatchConfigRequest {
allow_lan: Option<bool>,
}

impl PatchConfigRequest {
fn rebuild_listeners(&self) -> bool {
self.port.is_some()
|| self.socks_port.is_some()
|| self.redir_port.is_some()
|| self.tproxy_port.is_some()
|| self.mixed_port.is_some()
|| self.bind_address.is_some()
}
}

fn parse_bind_address(value: &str) -> Result<BindAddress, ()> {
match value {
"*" => Ok(BindAddress::all_v4()),
"localhost" => Ok(BindAddress::local()),
"[::]" | "::" => Ok(BindAddress::dual_stack()),
_ => value.parse().map(BindAddress).map_err(|_| ()),
}
}

async fn get_configs(State(state): State<ConfigState>) -> impl IntoResponse {
let run_mode = state.dispatcher.get_mode().await;
let global_state = state.global_state.lock().await;
Expand Down Expand Up @@ -156,3 +183,73 @@ async fn update_configs(
}
}
}

async fn patch_configs(
State(state): State<ConfigState>,
Json(payload): Json<PatchConfigRequest>,
) -> impl IntoResponse {
let inbound_manager = state.inbound_manager.clone();
let mut need_restart = false;

if let Some(bind_address) = payload.bind_address.clone() {
match parse_bind_address(&bind_address) {
Ok(bind_address) => {
inbound_manager.set_bind_address(bind_address).await;
need_restart = true;
}
Err(_) => {
return (
StatusCode::BAD_REQUEST,
format!("invalid bind address: {bind_address}"),
)
.into_response();
}
}
}

let mut global_state = state.global_state.lock().await;

if payload.rebuild_listeners() {
let ports = Ports {
port: payload.port,
socks_port: payload.socks_port,
redir_port: payload.redir_port,
tproxy_port: payload.tproxy_port,
mixed_port: payload.mixed_port,
};
inbound_manager.change_ports(ports).await;
need_restart = true;
}

if let Some(allow_lan) = payload.allow_lan
&& allow_lan != inbound_manager.get_allow_lan().await
{
inbound_manager.set_allow_lan(allow_lan).await;
need_restart = true;
}

if need_restart {
inbound_manager.restart().await;
}

if let Some(mode) = payload.mode {
state.dispatcher.set_mode(mode).await;
}

if let Some(log_level) = payload.log_level {
global_state.log_level = log_level;
if let Err(err) = logging::set_log_level(log_level) {
return (
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to update log level: {err}"),
)
.into_response();
}
}

if let Some(ipv6) = payload.ipv6 {
state.dns_resolver.set_ipv6(ipv6);
}

StatusCode::ACCEPTED.into_response()
}
22 changes: 17 additions & 5 deletions clash-lib/src/app/api/handlers/traffic.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::{net::SocketAddr, sync::Arc};
use std::sync::Arc;

use axum::{
extract::{ConnectInfo, State, WebSocketUpgrade, ws::Message},
body::Body,
extract::{FromRequest, Request, State, WebSocketUpgrade, ws::Message},
http::StatusCode,
response::IntoResponse,
};
use serde::Serialize;
Expand All @@ -16,12 +18,22 @@ struct TrafficResponse {
}

pub async fn handle(
ws: WebSocketUpgrade,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
State(state): State<Arc<AppState>>,
req: Request<Body>,
) -> impl IntoResponse {
let ws = match WebSocketUpgrade::from_request(req, &state).await {
Ok(ws) => ws,
Err(_) => {
return (
StatusCode::BAD_REQUEST,
"the /traffic endpoint requires websocket upgrade",
)
.into_response();
}
};

ws.on_failed_upgrade(move |e| {
warn!("ws upgrade error: {} with {}", e, addr);
warn!("ws upgrade error: {}", e);
})
.on_upgrade(move |mut socket| async move {
let mgr = state.statistics_manager.clone();
Expand Down
Loading
Loading