Skip to content

Commit

Permalink
try to match hostnames when clash dns used
Browse files Browse the repository at this point in the history
  • Loading branch information
ibigbug committed Sep 26, 2024
1 parent afe4cb0 commit e30a188
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 172 deletions.
4 changes: 2 additions & 2 deletions clash/tests/data/config/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mixed-port: 8899
tun:
enable: true
device-id: "dev://utun1989"
route-all: true
route-all: false
gateway: "198.19.0.1/32"
so-mark: 3389
# routes:
Expand Down Expand Up @@ -53,7 +53,7 @@ dns:

allow-lan: true
mode: rule
log-level: debug
log-level: trace
external-controller: :9090
external-ui: "public"
# secret: "clash-rs"
Expand Down
97 changes: 55 additions & 42 deletions clash_lib/src/app/dispatcher/dispatcher_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
internal::proxy::{PROXY_DIRECT, PROXY_GLOBAL},
},
proxy::{datagram::UdpPacket, AnyInboundDatagram},
session::Session,
session::{Session, SocksAddr},
};
use futures::{SinkExt, StreamExt};
use std::{
Expand Down Expand Up @@ -75,40 +75,51 @@ impl Dispatcher {
}

#[instrument(skip(self, sess, lhs))]
pub async fn dispatch_stream<S>(&self, sess: Session, mut lhs: S)
pub async fn dispatch_stream<S>(&self, mut sess: Session, mut lhs: S)
where
S: AsyncRead + AsyncWrite + Unpin + Send,
{
let sess = if self.resolver.fake_ip_enabled() {
match sess.destination {
crate::session::SocksAddr::Ip(addr) => {
let ip = addr.ip();
let dest: SocksAddr = match &sess.destination {
crate::session::SocksAddr::Ip(socket_addr) => {
if self.resolver.fake_ip_enabled() {
trace!("looking up fake ip: {}", socket_addr.ip());
let ip = socket_addr.ip();
if self.resolver.is_fake_ip(ip).await {
let host = self.resolver.reverse_lookup(ip).await;
match host {
Some(host) => {
let mut sess = sess;
sess.destination = crate::session::SocksAddr::Domain(
host,
addr.port(),
);
sess
}
Some(host) => (host, socket_addr.port())
.try_into()
.expect("must be valid domain"),
None => {
error!("failed to reverse lookup fake ip: {}", ip);
return;
}
}
} else {
sess
(*socket_addr).into()
}
} else {
trace!("looking up resolve cache ip: {}", socket_addr.ip());
if let Some(resolved) =
self.resolver.cached_for(socket_addr.ip()).await
{
(resolved, socket_addr.port())
.try_into()
.expect("must be valid domain")
} else {
(*socket_addr).into()
}
}
crate::session::SocksAddr::Domain(..) => sess,
}
} else {
sess
crate::session::SocksAddr::Domain(host, port) => {
(host.to_owned(), *port)
.try_into()
.expect("must be valid domain")
}
};

sess.destination = dest.clone();

let mode = *self.mode.lock().unwrap();
let (outbound_name, rule) = match mode {
RunMode::Global => (PROXY_GLOBAL, None),
Expand Down Expand Up @@ -253,28 +264,17 @@ impl Dispatcher {
while let Some(packet) = local_r.next().await {
let mut sess = sess.clone();
sess.source = packet.src_addr.clone().must_into_socket_addr();
sess.destination = packet.dst_addr.clone();

// populate fake ip for route matching
let sess = if resolver.fake_ip_enabled() {
trace!("looking up fake ip for {sess}");
match sess.destination {
crate::session::SocksAddr::Ip(addr) => {
let ip = addr.ip();

let dest: SocksAddr = match &packet.dst_addr {
crate::session::SocksAddr::Ip(socket_addr) => {
if resolver.fake_ip_enabled() {
let ip = socket_addr.ip();
if resolver.is_fake_ip(ip).await {
trace!("fake ip detected");
let host = resolver.reverse_lookup(ip).await;
match host {
Some(host) => {
trace!("fake ip resolved to {}", host);
let mut sess = sess;
sess.destination =
crate::session::SocksAddr::Domain(
host,
addr.port(),
);
sess
}
Some(host) => (host, socket_addr.port())
.try_into()
.expect("must be valid domain"),
None => {
error!(
"failed to reverse lookup fake ip: {}",
Expand All @@ -284,18 +284,31 @@ impl Dispatcher {
}
}
} else {
sess
(*socket_addr).into()
}
} else {
if let Some(resolved) =
resolver.cached_for(socket_addr.ip()).await
{
(resolved, socket_addr.port())
.try_into()
.expect("must be valid domain")
} else {
(*socket_addr).into()
}
}
crate::session::SocksAddr::Domain(..) => sess,
}
} else {
sess
crate::session::SocksAddr::Domain(host, port) => {
(host.to_owned(), *port)
.try_into()
.expect("must be valid domain")
}
};
sess.destination = dest.clone();

// mutate packet for fake ip
let mut packet = packet;
packet.dst_addr = sess.destination.clone();
packet.dst_addr = dest;

let mode = *mode.lock().unwrap();

Expand Down
3 changes: 3 additions & 0 deletions clash_lib/src/app/dns/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ pub trait ClashResolver: Sync + Send {
enhanced: bool,
) -> anyhow::Result<Option<std::net::Ipv6Addr>>;

async fn cached_for(&self, ip: std::net::IpAddr) -> Option<String>;

/// Used for DNS Server
async fn exchange(&self, message: op::Message) -> anyhow::Result<op::Message>;

/// Only used for look up fake IP
Expand Down
Loading

0 comments on commit e30a188

Please sign in to comment.