diff --git a/clash/tests/data/config/rules.yaml b/clash/tests/data/config/rules.yaml index 295281009..43cbae410 100644 --- a/clash/tests/data/config/rules.yaml +++ b/clash/tests/data/config/rules.yaml @@ -5,11 +5,12 @@ mixed-port: 8899 tproxy-port: 8900 tun: - enable: false + enable: true device-id: "dev://utun1989" - route-all: false + route-all: true gateway: "198.19.0.1/32" so-mark: 3389 + dns-hijack: true # routes: # - 0.0.0.0/1 # - 128.0.0.0/1 diff --git a/clash_lib/src/app/dns/dns_client.rs b/clash_lib/src/app/dns/dns_client.rs index f4864307e..fe1433f83 100644 --- a/clash_lib/src/app/dns/dns_client.rs +++ b/clash_lib/src/app/dns/dns_client.rs @@ -301,8 +301,9 @@ impl Client for DnsClient { } let mut req = DnsRequest::new(msg.clone(), DnsRequestOptions::default()); - req.set_id(rand::random::()); - + if req.id() == 0 { + req.set_id(rand::random::()); + } inner .c .as_ref() diff --git a/clash_lib/src/app/dns/resolver/enhanced.rs b/clash_lib/src/app/dns/resolver/enhanced.rs index 3a7612fae..673b02cb9 100644 --- a/clash_lib/src/app/dns/resolver/enhanced.rs +++ b/clash_lib/src/app/dns/resolver/enhanced.rs @@ -286,7 +286,10 @@ impl EnhancedResolver { if let Some(q) = message.query() { if let Some(lru) = &self.lru_cache { if let Some(cached) = lru.read().await.peek(q.to_string().as_str()) { - return Ok(cached.clone()); + trace!("dns query {} hit lru cache", q.to_string()); + let mut cached = cached.clone(); + cached.set_id(message.id()); + return Ok(cached); } } self.exchange_no_cache(message).await diff --git a/clash_lib/src/app/logging.rs b/clash_lib/src/app/logging.rs index 190fad47f..e9ad51f3d 100644 --- a/clash_lib/src/app/logging.rs +++ b/clash_lib/src/app/logging.rs @@ -169,11 +169,11 @@ pub fn setup_logging( tracing_subscriber::fmt::Layer::new() .with_ansi(std::io::stdout().is_terminal()) .compact() - .with_target(false) + .with_target(cfg!(debug_assertions)) .with_file(true) .with_line_number(true) .with_level(true) - .with_thread_ids(true) + .with_thread_ids(cfg!(debug_assertions)) .with_writer(std::io::stdout), ) .with(ios_os_log) diff --git a/clash_lib/src/config/def.rs b/clash_lib/src/config/def.rs index 411d77b78..d924403b3 100644 --- a/clash_lib/src/config/def.rs +++ b/clash_lib/src/config/def.rs @@ -25,6 +25,9 @@ pub struct TunConfig { pub so_mark: Option, /// policy routing table on Linux only pub route_table: Option, + /// Will hijack UDP:53 DNS queries to the Clash DNS server if set to true + #[serde(default)] + pub dns_hijack: bool, } #[derive(Serialize, Deserialize, Default, Copy, Clone)] diff --git a/clash_lib/src/config/internal/config.rs b/clash_lib/src/config/internal/config.rs index d10cc194c..19a76aae1 100644 --- a/clash_lib/src/config/internal/config.rs +++ b/clash_lib/src/config/internal/config.rs @@ -122,6 +122,7 @@ impl TryFrom for Config { mtu: t.mtu, so_mark: t.so_mark, route_table: t.route_table, + dns_hijack: t.dns_hijack, }, None => TunConfig::default(), }, @@ -308,6 +309,7 @@ pub struct TunConfig { pub mtu: Option, pub so_mark: Option, pub route_table: Option, + pub dns_hijack: bool, } #[derive(Clone, Default)] diff --git a/clash_lib/src/proxy/tun/inbound.rs b/clash_lib/src/proxy/tun/inbound.rs index ab35677e9..e20c2945b 100644 --- a/clash_lib/src/proxy/tun/inbound.rs +++ b/clash_lib/src/proxy/tun/inbound.rs @@ -15,7 +15,7 @@ use crate::{ datagram::UdpPacket, tun::routes::maybe_add_routes, utils::get_outbound_interface, }, - session::{Network, Session, SocksAddr, Type}, + session::{Network, Session, Type}, Error, Runner, }; @@ -57,10 +57,13 @@ async fn handle_inbound_datagram( dispatcher: Arc, resolver: ThreadSafeDNSResolver, so_mark: u32, + dns_hijack: bool, ) { // tun i/o let (ls, mut lr) = socket.split(); let ls = Arc::new(ls); + let ls_dns = ls.clone(); // for dns hijack + let resolver_dns = resolver.clone(); // for dns hijack // dispatcher <-> tun communications let (l_tx, mut l_rx) = tokio::sync::mpsc::channel::(32); @@ -91,26 +94,9 @@ async fn handle_inbound_datagram( let fut1 = tokio::spawn(async move { while let Some(pkt) = l_rx.recv().await { trace!("tun <- dispatcher: {:?}", pkt); - // populate the correct src_addr, though is it necessary? - let src_addr = match pkt.src_addr { - SocksAddr::Ip(ip) => ip, - SocksAddr::Domain(host, port) => { - match resolver.resolve(&host, resolver.fake_ip_enabled()).await { - Ok(Some(ip)) => (ip, port).into(), - Ok(None) => { - warn!("failed to resolve domain: {}", host); - continue; - } - Err(e) => { - warn!("failed to resolve domain: {}", e); - continue; - } - } - } - }; if let Err(e) = ls.send_to( &pkt.data[..], - &src_addr, + &pkt.src_addr.must_into_socket_addr(), &pkt.dst_addr.must_into_socket_addr(), ) { warn!("failed to send udp packet to netstack: {}", e); @@ -132,6 +118,67 @@ async fn handle_inbound_datagram( trace!("tun -> dispatcher: {:?}", pkt); + if dns_hijack && pkt.dst_addr.port() == 53 { + trace!("got dns packet: {:?}, returning from Clash DNS server", pkt); + + match hickory_proto::op::Message::from_vec(&pkt.data) { + Ok(msg) => { + trace!("hijack dns request: {:?}", msg); + let mut resp = match resolver_dns.exchange(&msg).await { + Ok(resp) => resp, + Err(e) => { + warn!("failed to exchange dns message: {}", e); + continue; + } + }; + // hickory mutates id sometimes, https://github.com/hickory-dns/hickory-dns/pull/2590 + resp.set_id(msg.id()); + + if let Some(edns) = msg.extensions() { + if edns + .option( + hickory_proto::rr::rdata::opt::EdnsCode::Padding, + ) + .is_none() + { + if let Some(edns) = resp.extensions_mut() { + edns.options_mut().remove( + hickory_proto::rr::rdata::opt::EdnsCode::Padding, + ); + } + } + } + trace!("hijack dns response: {:?}", resp); + + match resp.to_vec() { + Ok(data) => { + if let Err(e) = ls_dns.send_to( + &data, + &pkt.dst_addr.must_into_socket_addr(), + &pkt.src_addr.must_into_socket_addr(), + ) { + warn!( + "failed to send udp packet to netstack: {}", + e + ); + } + continue; + } + Err(e) => { + warn!("failed to serialize dns response: {}", e); + } + } + } + Err(e) => { + warn!( + "failed to parse dns packet: {}, putting it back to \ + stack", + e + ); + } + }; + } + match d_tx.send(pkt).await { Ok(_) => {} Err(e) => { @@ -292,7 +339,14 @@ pub fn get_runner( })); futs.push(Box::pin(async move { - handle_inbound_datagram(udp_socket, dispatcher, resolver, so_mark).await; + handle_inbound_datagram( + udp_socket, + dispatcher, + resolver, + so_mark, + cfg.dns_hijack, + ) + .await; Err(Error::Operation("tun stopped unexpectedly 3".to_string())) }));