Skip to content

Commit

Permalink
feat(dns): support tun dns hijack (#651)
Browse files Browse the repository at this point in the history
* dns hijack

* up
  • Loading branch information
ibigbug authored Nov 19, 2024
1 parent d261865 commit bc7c20b
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 27 deletions.
5 changes: 3 additions & 2 deletions clash/tests/data/config/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions clash_lib/src/app/dns/dns_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,9 @@ impl Client for DnsClient {
}

let mut req = DnsRequest::new(msg.clone(), DnsRequestOptions::default());
req.set_id(rand::random::<u16>());

if req.id() == 0 {
req.set_id(rand::random::<u16>());
}
inner
.c
.as_ref()
Expand Down
5 changes: 4 additions & 1 deletion clash_lib/src/app/dns/resolver/enhanced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions clash_lib/src/app/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions clash_lib/src/config/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub struct TunConfig {
pub so_mark: Option<u32>,
/// policy routing table on Linux only
pub route_table: Option<u32>,
/// 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)]
Expand Down
2 changes: 2 additions & 0 deletions clash_lib/src/config/internal/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl TryFrom<def::Config> for Config {
mtu: t.mtu,
so_mark: t.so_mark,
route_table: t.route_table,
dns_hijack: t.dns_hijack,
},
None => TunConfig::default(),
},
Expand Down Expand Up @@ -308,6 +309,7 @@ pub struct TunConfig {
pub mtu: Option<i32>,
pub so_mark: Option<u32>,
pub route_table: Option<u32>,
pub dns_hijack: bool,
}

#[derive(Clone, Default)]
Expand Down
94 changes: 74 additions & 20 deletions clash_lib/src/proxy/tun/inbound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -57,10 +57,13 @@ async fn handle_inbound_datagram(
dispatcher: Arc<Dispatcher>,
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::<UdpPacket>(32);
Expand Down Expand Up @@ -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);
Expand All @@ -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) => {
Expand Down Expand Up @@ -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()))
}));

Expand Down

0 comments on commit bc7c20b

Please sign in to comment.