From e30a188386c8e6287bb0acb22597caf0a48e792c Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 00:47:14 +1000 Subject: [PATCH 1/8] try to match hostnames when clash dns used --- clash/tests/data/config/rules.yaml | 4 +- .../src/app/dispatcher/dispatcher_impl.rs | 97 ++++---- clash_lib/src/app/dns/mod.rs | 3 + clash_lib/src/app/dns/resolver/enhanced.rs | 234 +++++++++++------- .../src/app/dns/resolver/system_linux.rs | 4 + .../src/app/dns/resolver/system_non_linux.rs | 4 + clash_lib/src/app/router/mod.rs | 5 - clash_lib/src/proxy/shadowsocks/datagram.rs | 42 +--- clash_lib/src/proxy/shadowsocks/mod.rs | 17 +- clash_lib/src/proxy/utils/mod.rs | 4 +- 10 files changed, 242 insertions(+), 172 deletions(-) diff --git a/clash/tests/data/config/rules.yaml b/clash/tests/data/config/rules.yaml index 1ea9cbf46..874b2139f 100644 --- a/clash/tests/data/config/rules.yaml +++ b/clash/tests/data/config/rules.yaml @@ -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: @@ -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" diff --git a/clash_lib/src/app/dispatcher/dispatcher_impl.rs b/clash_lib/src/app/dispatcher/dispatcher_impl.rs index 52cb517c1..c0bf618aa 100644 --- a/clash_lib/src/app/dispatcher/dispatcher_impl.rs +++ b/clash_lib/src/app/dispatcher/dispatcher_impl.rs @@ -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::{ @@ -75,40 +75,51 @@ impl Dispatcher { } #[instrument(skip(self, sess, lhs))] - pub async fn dispatch_stream(&self, sess: Session, mut lhs: S) + pub async fn dispatch_stream(&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), @@ -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: {}", @@ -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(); diff --git a/clash_lib/src/app/dns/mod.rs b/clash_lib/src/app/dns/mod.rs index 09b63aed0..ff1309046 100644 --- a/clash_lib/src/app/dns/mod.rs +++ b/clash_lib/src/app/dns/mod.rs @@ -63,6 +63,9 @@ pub trait ClashResolver: Sync + Send { enhanced: bool, ) -> anyhow::Result>; + async fn cached_for(&self, ip: std::net::IpAddr) -> Option; + + /// Used for DNS Server async fn exchange(&self, message: op::Message) -> anyhow::Result; /// Only used for look up fake IP diff --git a/clash_lib/src/app/dns/resolver/enhanced.rs b/clash_lib/src/app/dns/resolver/enhanced.rs index 218004adf..5443669fa 100644 --- a/clash_lib/src/app/dns/resolver/enhanced.rs +++ b/clash_lib/src/app/dns/resolver/enhanced.rs @@ -10,7 +10,7 @@ use std::{ time::Duration, }; use tokio::sync::RwLock; -use tracing::{debug, error, instrument, warn}; +use tracing::{debug, error, instrument, trace, warn}; use hickory_proto::{op, rr}; @@ -46,6 +46,9 @@ pub struct EnhancedResolver { policy: Option>>, fake_dns: Option, + + reverse_lookup_cache: + Option>>>, } impl EnhancedResolver { @@ -75,6 +78,8 @@ impl EnhancedResolver { policy: None, fake_dns: None, + + reverse_lookup_cache: None, } } @@ -94,6 +99,8 @@ impl EnhancedResolver { policy: None, fake_dns: None, + + reverse_lookup_cache: None, }); Self { @@ -199,6 +206,121 @@ impl EnhancedResolver { } _ => None, }, + + reverse_lookup_cache: Some(Arc::new(RwLock::new( + lru_time_cache::LruCache::with_expiry_duration_and_capacity( + Duration::from_secs(3), /* should be shorter than TTL so + * client won't be connecting to a + * different server after the ip is + * reverse mapped to hostname and + * being resolved again */ + 4096, + ), + ))), + } + } + + async fn resolve_inner( + &self, + host: &str, + enhanced: bool, + ) -> anyhow::Result> { + match self.ipv6.load(Relaxed) { + true => { + let fut1 = self + .resolve_v6(host, enhanced) + .map(|x| x.map(|v6| v6.map(net::IpAddr::from))); + let fut2 = self + .resolve_v4(host, enhanced) + .map(|x| x.map(|v4| v4.map(net::IpAddr::from))); + + let futs = vec![fut1.boxed(), fut2.boxed()]; + let r = futures::future::select_ok(futs).await?; + if r.0.is_some() { + return Ok(r.0); + } + let r = futures::future::select_all(r.1).await; + return r.0; + } + false => self + .resolve_v4(host, enhanced) + .await + .map(|ip| ip.map(net::IpAddr::from)), + } + } + + async fn resolve_v4_inner( + &self, + host: &str, + enhanced: bool, + ) -> anyhow::Result> { + if enhanced { + if let Some(hosts) = &self.hosts { + if let Some(v) = hosts.search(host) { + return Ok(v.get_data().map(|v| match v { + net::IpAddr::V4(v4) => *v4, + _ => unreachable!("invalid IP family"), + })); + } + } + } + + if let Ok(ip) = host.parse::() { + return Ok(Some(ip)); + } + + if enhanced && self.fake_ip_enabled() { + let mut fake_dns = self.fake_dns.as_ref().unwrap().write().await; + if !fake_dns.should_skip(host) { + let ip = fake_dns.lookup(host).await; + debug!("fake dns lookup: {} -> {:?}", host, ip); + match ip { + net::IpAddr::V4(v4) => return Ok(Some(v4)), + _ => unreachable!("invalid IP family"), + } + } + } + + match self.lookup_ip(host, rr::RecordType::A).await { + Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { + net::IpAddr::V4(v4) => Ok(Some(*v4)), + _ => unreachable!("invalid IP family"), + }, + Err(e) => Err(e), + } + } + + async fn resolve_v6_inner( + &self, + host: &str, + enhanced: bool, + ) -> anyhow::Result> { + if !self.ipv6.load(Relaxed) { + return Err(Error::DNSError("ipv6 disabled".into()).into()); + } + + if enhanced { + if let Some(hosts) = &self.hosts { + if let Some(v) = hosts.search(host) { + return Ok(v.get_data().map(|v| match v { + net::IpAddr::V6(v6) => *v6, + _ => unreachable!("invalid IP family"), + })); + } + } + } + + if let Ok(ip) = host.parse::() { + return Ok(Some(ip)); + } + + match self.lookup_ip(host, rr::RecordType::AAAA).await { + Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { + net::IpAddr::V6(v6) => Ok(Some(*v6)), + _ => unreachable!("invalid IP family"), + }, + + Err(e) => Err(e), } } @@ -251,7 +373,7 @@ impl EnhancedResolver { m.add_query(q); m.set_recursion_desired(true); - match self.exchange(m).await { + match self.exchange(&m).await { Ok(result) => { let ip_list = EnhancedResolver::ip_list_of_message(&result); if !ip_list.is_empty() { @@ -264,7 +386,7 @@ impl EnhancedResolver { } } - async fn exchange(&self, message: op::Message) -> anyhow::Result { + async fn exchange(&self, message: &op::Message) -> anyhow::Result { 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()) { @@ -436,6 +558,13 @@ impl EnhancedResolver { }) .collect() } + + async fn save_reverse_lookup(&self, ip: net::IpAddr, domain: String) { + if let Some(lru) = &self.reverse_lookup_cache { + trace!("reverse lookup cache insert: {} -> {}", ip, domain); + lru.write().await.insert(ip, domain); + } + } } #[async_trait] @@ -446,28 +575,7 @@ impl ClashResolver for EnhancedResolver { host: &str, enhanced: bool, ) -> anyhow::Result> { - match self.ipv6.load(Relaxed) { - true => { - let fut1 = self - .resolve_v6(host, enhanced) - .map(|x| x.map(|v6| v6.map(net::IpAddr::from))); - let fut2 = self - .resolve_v4(host, enhanced) - .map(|x| x.map(|v4| v4.map(net::IpAddr::from))); - - let futs = vec![fut1.boxed(), fut2.boxed()]; - let r = futures::future::select_ok(futs).await?; - if r.0.is_some() { - return Ok(r.0); - } - let r = futures::future::select_all(r.1).await; - return r.0; - } - false => self - .resolve_v4(host, enhanced) - .await - .map(|ip| ip.map(net::IpAddr::from)), - } + self.resolve_inner(host, enhanced).await } async fn resolve_v4( @@ -475,40 +583,7 @@ impl ClashResolver for EnhancedResolver { host: &str, enhanced: bool, ) -> anyhow::Result> { - if enhanced { - if let Some(hosts) = &self.hosts { - if let Some(v) = hosts.search(host) { - return Ok(v.get_data().map(|v| match v { - net::IpAddr::V4(v4) => *v4, - _ => unreachable!("invalid IP family"), - })); - } - } - } - - if let Ok(ip) = host.parse::() { - return Ok(Some(ip)); - } - - if enhanced && self.fake_ip_enabled() { - let mut fake_dns = self.fake_dns.as_ref().unwrap().write().await; - if !fake_dns.should_skip(host) { - let ip = fake_dns.lookup(host).await; - debug!("fake dns lookup: {} -> {:?}", host, ip); - match ip { - net::IpAddr::V4(v4) => return Ok(Some(v4)), - _ => unreachable!("invalid IP family"), - } - } - } - - match self.lookup_ip(host, rr::RecordType::A).await { - Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { - net::IpAddr::V4(v4) => Ok(Some(*v4)), - _ => unreachable!("invalid IP family"), - }, - Err(e) => Err(e), - } + self.resolve_v4_inner(host, enhanced).await } async fn resolve_v6( @@ -516,37 +591,30 @@ impl ClashResolver for EnhancedResolver { host: &str, enhanced: bool, ) -> anyhow::Result> { - if !self.ipv6.load(Relaxed) { - return Err(Error::DNSError("ipv6 disabled".into()).into()); - } + self.resolve_v6_inner(host, enhanced).await + } - if enhanced { - if let Some(hosts) = &self.hosts { - if let Some(v) = hosts.search(host) { - return Ok(v.get_data().map(|v| match v { - net::IpAddr::V6(v6) => *v6, - _ => unreachable!("invalid IP family"), - })); - } + async fn cached_for(&self, ip: net::IpAddr) -> Option { + if let Some(lru) = &self.reverse_lookup_cache { + if let Some(cached) = lru.read().await.peek(&ip) { + trace!("reverse lookup cache hit: {} -> {}", ip, cached); + return Some(cached.clone()); } } - if let Ok(ip) = host.parse::() { - return Ok(Some(ip)); - } - - match self.lookup_ip(host, rr::RecordType::AAAA).await { - Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { - net::IpAddr::V6(v6) => Ok(Some(*v6)), - _ => unreachable!("invalid IP family"), - }, - - Err(e) => Err(e), - } + None } async fn exchange(&self, message: op::Message) -> anyhow::Result { - self.exchange(message).await + let rv = self.exchange(&message).await?; + let hostname = message.query().unwrap().name().to_ascii(); + let ip_list = EnhancedResolver::ip_list_of_message(&rv); + if !ip_list.is_empty() { + for ip in ip_list { + self.save_reverse_lookup(ip, hostname.clone()).await; + } + } + Ok(rv) } fn ipv6(&self) -> bool { diff --git a/clash_lib/src/app/dns/resolver/system_linux.rs b/clash_lib/src/app/dns/resolver/system_linux.rs index c16ce87fe..44567ccd8 100644 --- a/clash_lib/src/app/dns/resolver/system_linux.rs +++ b/clash_lib/src/app/dns/resolver/system_linux.rs @@ -56,6 +56,10 @@ impl ClashResolver for SystemResolver { Ok(response.iter().map(|x| x.0).choose(&mut rand::thread_rng())) } + async fn cached_for(&self, _: std::net::IpAddr) -> Option { + None + } + async fn exchange( &self, _: hickory_proto::op::Message, diff --git a/clash_lib/src/app/dns/resolver/system_non_linux.rs b/clash_lib/src/app/dns/resolver/system_non_linux.rs index 081b08cff..c306860ae 100644 --- a/clash_lib/src/app/dns/resolver/system_non_linux.rs +++ b/clash_lib/src/app/dns/resolver/system_non_linux.rs @@ -75,6 +75,10 @@ impl ClashResolver for SystemResolver { Ok(response.into_iter().choose(&mut rand::thread_rng())) } + async fn cached_for(&self, _: std::net::IpAddr) -> Option { + None + } + async fn exchange( &self, _: hickory_proto::op::Message, diff --git a/clash_lib/src/app/router/mod.rs b/clash_lib/src/app/router/mod.rs index d53c8dedc..ff9019aa5 100644 --- a/clash_lib/src/app/router/mod.rs +++ b/clash_lib/src/app/router/mod.rs @@ -93,10 +93,6 @@ impl Router { && r.should_resolve_ip() && !sess_resolved { - debug!( - "rule `{r}` resolving domain {} locally", - sess.destination.domain().unwrap() - ); if let Ok(Some(ip)) = self .dns_resolver .resolve(sess.destination.domain().unwrap(), false) @@ -114,7 +110,6 @@ impl Router { r.target(), r.type_name() ); - debug!("matched rule details: {}", r); return (r.target(), Some(r)); } } diff --git a/clash_lib/src/proxy/shadowsocks/datagram.rs b/clash_lib/src/proxy/shadowsocks/datagram.rs index af96acdc1..c6acf251d 100644 --- a/clash_lib/src/proxy/shadowsocks/datagram.rs +++ b/clash_lib/src/proxy/shadowsocks/datagram.rs @@ -1,5 +1,6 @@ use std::{ io, + net::SocketAddr, pin::Pin, task::{Context, Poll}, }; @@ -15,10 +16,9 @@ use shadowsocks::{ ProxySocket, }; use tokio::io::ReadBuf; -use tracing::{debug, instrument, trace}; +use tracing::{debug, instrument}; use crate::{ - app::dns::ThreadSafeDNSResolver, common::errors::new_io_error, proxy::{datagram::UdpPacket, AnyOutboundDatagram}, session::SocksAddr, @@ -28,26 +28,20 @@ use crate::{ /// as underlying I/O pub struct OutboundDatagramShadowsocks { inner: ProxySocket, - remote_addr: SocksAddr, + remote_addr: SocketAddr, flushed: bool, pkt: Option, buf: Vec, - resolver: ThreadSafeDNSResolver, } impl OutboundDatagramShadowsocks { - pub fn new( - inner: ProxySocket, - remote_addr: (String, u16), - resolver: ThreadSafeDNSResolver, - ) -> Self { + pub fn new(inner: ProxySocket, remote_addr: SocketAddr) -> Self { Self { inner, flushed: true, pkt: None, remote_addr: remote_addr.try_into().expect("must into socks addr"), buf: vec![0u8; 65535], - resolver, } } } @@ -93,33 +87,9 @@ where ref mut pkt, ref remote_addr, ref mut flushed, - ref mut resolver, .. } = *self; - let dst = match remote_addr.to_owned() { - SocksAddr::Domain(domain, port) => { - let domain = domain.to_string(); - let port = port.to_owned(); - - let mut fut = resolver.resolve(domain.as_str(), false); - let ip = ready!(fut.as_mut().poll(cx).map_err(|_| { - io::Error::new(io::ErrorKind::Other, "resolve domain failed") - }))?; - - if let Some(ip) = ip { - trace!("resolve domain {} to {}", domain, ip); - (ip, port).into() - } else { - return Poll::Ready(Err(io::Error::new( - io::ErrorKind::Other, - format!("resolve domain failed: {}", domain), - ))); - } - } - SocksAddr::Ip(addr) => addr, - }; - let pkt_container = pkt; if let Some(pkt) = pkt_container { @@ -127,12 +97,12 @@ where let addr: shadowsocks::relay::Address = (pkt.dst_addr.host(), pkt.dst_addr.port()).into(); - let n = ready!(inner.poll_send_to(dst, &addr, data, cx))?; + let n = ready!(inner.poll_send_to(*remote_addr, &addr, data, cx))?; debug!( "send udp packet to remote ss server, len: {}, remote_addr: {}, \ dst_addr: {}", - n, dst, addr + n, remote_addr, addr ); let wrote_all = n == data.len(); diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 1de33dbd4..a7a8dd023 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -17,6 +17,7 @@ use crate::{ }, dns::ThreadSafeDNSResolver, }, + common::errors::new_io_error, impl_default_connector, proxy::{HandlerCommonOptions, OutboundHandler}, session::Session, @@ -277,10 +278,22 @@ impl OutboundHandler for Handler { &cfg, ShadowsocksUdpIo::new(socket), ); + let server_addr = resolver + .resolve(&self.opts.server, false) + .await + .map_err(|x| { + new_io_error(format!( + "failed to resolve {}: {}", + self.opts.server, x + )) + })? + .ok_or(new_io_error(format!( + "failed to resolve {}", + self.opts.server + )))?; let d = OutboundDatagramShadowsocks::new( socket, - (self.opts.server.to_owned(), self.opts.port), - resolver, + (server_addr, self.opts.port).into(), ); let d = ChainedDatagramWrapper::new(d); d.append_to_chain(self.name()).await; diff --git a/clash_lib/src/proxy/utils/mod.rs b/clash_lib/src/proxy/utils/mod.rs index 52d3599cb..a4dea3f9b 100644 --- a/clash_lib/src/proxy/utils/mod.rs +++ b/clash_lib/src/proxy/utils/mod.rs @@ -17,7 +17,7 @@ pub use proxy_connector::*; use serde::{Deserialize, Serialize}; pub use socket_helpers::*; -use tracing::{debug, trace}; +use tracing::trace; #[derive(Debug)] pub struct OutboundInterface { @@ -101,7 +101,7 @@ pub fn get_outbound_interface() -> Option { left.cmp(&right) }); - debug!( + trace!( "sorted outbound interfaces: {:?}, took: {}ms", all_outbounds, now.elapsed().as_millis() From deb38736d5079f943d3cdb6e8bce5133d1891d88 Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 00:50:36 +1000 Subject: [PATCH 2/8] reset --- clash_lib/src/app/dns/resolver/enhanced.rs | 190 +++++++++------------ 1 file changed, 83 insertions(+), 107 deletions(-) diff --git a/clash_lib/src/app/dns/resolver/enhanced.rs b/clash_lib/src/app/dns/resolver/enhanced.rs index 5443669fa..4b8fbb422 100644 --- a/clash_lib/src/app/dns/resolver/enhanced.rs +++ b/clash_lib/src/app/dns/resolver/enhanced.rs @@ -220,110 +220,6 @@ impl EnhancedResolver { } } - async fn resolve_inner( - &self, - host: &str, - enhanced: bool, - ) -> anyhow::Result> { - match self.ipv6.load(Relaxed) { - true => { - let fut1 = self - .resolve_v6(host, enhanced) - .map(|x| x.map(|v6| v6.map(net::IpAddr::from))); - let fut2 = self - .resolve_v4(host, enhanced) - .map(|x| x.map(|v4| v4.map(net::IpAddr::from))); - - let futs = vec![fut1.boxed(), fut2.boxed()]; - let r = futures::future::select_ok(futs).await?; - if r.0.is_some() { - return Ok(r.0); - } - let r = futures::future::select_all(r.1).await; - return r.0; - } - false => self - .resolve_v4(host, enhanced) - .await - .map(|ip| ip.map(net::IpAddr::from)), - } - } - - async fn resolve_v4_inner( - &self, - host: &str, - enhanced: bool, - ) -> anyhow::Result> { - if enhanced { - if let Some(hosts) = &self.hosts { - if let Some(v) = hosts.search(host) { - return Ok(v.get_data().map(|v| match v { - net::IpAddr::V4(v4) => *v4, - _ => unreachable!("invalid IP family"), - })); - } - } - } - - if let Ok(ip) = host.parse::() { - return Ok(Some(ip)); - } - - if enhanced && self.fake_ip_enabled() { - let mut fake_dns = self.fake_dns.as_ref().unwrap().write().await; - if !fake_dns.should_skip(host) { - let ip = fake_dns.lookup(host).await; - debug!("fake dns lookup: {} -> {:?}", host, ip); - match ip { - net::IpAddr::V4(v4) => return Ok(Some(v4)), - _ => unreachable!("invalid IP family"), - } - } - } - - match self.lookup_ip(host, rr::RecordType::A).await { - Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { - net::IpAddr::V4(v4) => Ok(Some(*v4)), - _ => unreachable!("invalid IP family"), - }, - Err(e) => Err(e), - } - } - - async fn resolve_v6_inner( - &self, - host: &str, - enhanced: bool, - ) -> anyhow::Result> { - if !self.ipv6.load(Relaxed) { - return Err(Error::DNSError("ipv6 disabled".into()).into()); - } - - if enhanced { - if let Some(hosts) = &self.hosts { - if let Some(v) = hosts.search(host) { - return Ok(v.get_data().map(|v| match v { - net::IpAddr::V6(v6) => *v6, - _ => unreachable!("invalid IP family"), - })); - } - } - } - - if let Ok(ip) = host.parse::() { - return Ok(Some(ip)); - } - - match self.lookup_ip(host, rr::RecordType::AAAA).await { - Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { - net::IpAddr::V6(v6) => Ok(Some(*v6)), - _ => unreachable!("invalid IP family"), - }, - - Err(e) => Err(e), - } - } - pub async fn batch_exchange( clients: &Vec, message: &op::Message, @@ -575,7 +471,28 @@ impl ClashResolver for EnhancedResolver { host: &str, enhanced: bool, ) -> anyhow::Result> { - self.resolve_inner(host, enhanced).await + match self.ipv6.load(Relaxed) { + true => { + let fut1 = self + .resolve_v6(host, enhanced) + .map(|x| x.map(|v6| v6.map(net::IpAddr::from))); + let fut2 = self + .resolve_v4(host, enhanced) + .map(|x| x.map(|v4| v4.map(net::IpAddr::from))); + + let futs = vec![fut1.boxed(), fut2.boxed()]; + let r = futures::future::select_ok(futs).await?; + if r.0.is_some() { + return Ok(r.0); + } + let r = futures::future::select_all(r.1).await; + return r.0; + } + false => self + .resolve_v4(host, enhanced) + .await + .map(|ip| ip.map(net::IpAddr::from)), + } } async fn resolve_v4( @@ -583,7 +500,40 @@ impl ClashResolver for EnhancedResolver { host: &str, enhanced: bool, ) -> anyhow::Result> { - self.resolve_v4_inner(host, enhanced).await + if enhanced { + if let Some(hosts) = &self.hosts { + if let Some(v) = hosts.search(host) { + return Ok(v.get_data().map(|v| match v { + net::IpAddr::V4(v4) => *v4, + _ => unreachable!("invalid IP family"), + })); + } + } + } + + if let Ok(ip) = host.parse::() { + return Ok(Some(ip)); + } + + if enhanced && self.fake_ip_enabled() { + let mut fake_dns = self.fake_dns.as_ref().unwrap().write().await; + if !fake_dns.should_skip(host) { + let ip = fake_dns.lookup(host).await; + debug!("fake dns lookup: {} -> {:?}", host, ip); + match ip { + net::IpAddr::V4(v4) => return Ok(Some(v4)), + _ => unreachable!("invalid IP family"), + } + } + } + + match self.lookup_ip(host, rr::RecordType::A).await { + Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { + net::IpAddr::V4(v4) => Ok(Some(*v4)), + _ => unreachable!("invalid IP family"), + }, + Err(e) => Err(e), + } } async fn resolve_v6( @@ -591,7 +541,33 @@ impl ClashResolver for EnhancedResolver { host: &str, enhanced: bool, ) -> anyhow::Result> { - self.resolve_v6_inner(host, enhanced).await + if !self.ipv6.load(Relaxed) { + return Err(Error::DNSError("ipv6 disabled".into()).into()); + } + + if enhanced { + if let Some(hosts) = &self.hosts { + if let Some(v) = hosts.search(host) { + return Ok(v.get_data().map(|v| match v { + net::IpAddr::V6(v6) => *v6, + _ => unreachable!("invalid IP family"), + })); + } + } + } + + if let Ok(ip) = host.parse::() { + return Ok(Some(ip)); + } + + match self.lookup_ip(host, rr::RecordType::AAAA).await { + Ok(result) => match result.choose(&mut rand::thread_rng()).unwrap() { + net::IpAddr::V6(v6) => Ok(Some(*v6)), + _ => unreachable!("invalid IP family"), + }, + + Err(e) => Err(e), + } } async fn cached_for(&self, ip: net::IpAddr) -> Option { From 849793e264ae66d9df46bdfbd69f3a3e0a453ab4 Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 00:57:17 +1000 Subject: [PATCH 3/8] note --- clash_lib/src/app/dispatcher/dispatcher_impl.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clash_lib/src/app/dispatcher/dispatcher_impl.rs b/clash_lib/src/app/dispatcher/dispatcher_impl.rs index c0bf618aa..2e6c2604c 100644 --- a/clash_lib/src/app/dispatcher/dispatcher_impl.rs +++ b/clash_lib/src/app/dispatcher/dispatcher_impl.rs @@ -308,6 +308,9 @@ impl Dispatcher { // mutate packet for fake ip let mut packet = packet; + // resolve is done in OutboundDatagramImpl so it's fine to have + // (Domain, port) here. ideally the OutboundDatagramImpl should only + // do Ip though? packet.dst_addr = dest; let mode = *mode.lock().unwrap(); From 1047eadfb3c38e183d02d659571273707e2d4770 Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 01:00:04 +1000 Subject: [PATCH 4/8] clippy --- clash_lib/src/app/dispatcher/dispatcher_impl.rs | 16 +++++++--------- clash_lib/src/app/dns/resolver/enhanced.rs | 2 +- clash_lib/src/app/router/mod.rs | 2 +- clash_lib/src/lib.rs | 1 + clash_lib/src/proxy/shadowsocks/datagram.rs | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/clash_lib/src/app/dispatcher/dispatcher_impl.rs b/clash_lib/src/app/dispatcher/dispatcher_impl.rs index 2e6c2604c..51583397a 100644 --- a/clash_lib/src/app/dispatcher/dispatcher_impl.rs +++ b/clash_lib/src/app/dispatcher/dispatcher_impl.rs @@ -286,16 +286,14 @@ impl Dispatcher { } else { (*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 { - 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() - } + (*socket_addr).into() } } crate::session::SocksAddr::Domain(host, port) => { diff --git a/clash_lib/src/app/dns/resolver/enhanced.rs b/clash_lib/src/app/dns/resolver/enhanced.rs index 4b8fbb422..9d4c215b5 100644 --- a/clash_lib/src/app/dns/resolver/enhanced.rs +++ b/clash_lib/src/app/dns/resolver/enhanced.rs @@ -289,7 +289,7 @@ impl EnhancedResolver { return Ok(cached.clone()); } } - self.exchange_no_cache(&message).await + self.exchange_no_cache(message).await } else { Err(anyhow!("invalid query")) } diff --git a/clash_lib/src/app/router/mod.rs b/clash_lib/src/app/router/mod.rs index ff9019aa5..a7ea8bdab 100644 --- a/clash_lib/src/app/router/mod.rs +++ b/clash_lib/src/app/router/mod.rs @@ -16,7 +16,7 @@ use crate::app::router::rules::final_::Final; use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use hyper::Uri; -use tracing::{debug, error, info}; +use tracing::{error, info}; use super::{ dns::ThreadSafeDNSResolver, diff --git a/clash_lib/src/lib.rs b/clash_lib/src/lib.rs index 0d660a892..bb5882635 100644 --- a/clash_lib/src/lib.rs +++ b/clash_lib/src/lib.rs @@ -517,6 +517,7 @@ mod tests { static INIT: Once = Once::new(); + #[allow(dead_code)] pub fn initialize() { INIT.call_once(|| { env_logger::init(); diff --git a/clash_lib/src/proxy/shadowsocks/datagram.rs b/clash_lib/src/proxy/shadowsocks/datagram.rs index c6acf251d..19b38140e 100644 --- a/clash_lib/src/proxy/shadowsocks/datagram.rs +++ b/clash_lib/src/proxy/shadowsocks/datagram.rs @@ -40,7 +40,7 @@ impl OutboundDatagramShadowsocks { inner, flushed: true, pkt: None, - remote_addr: remote_addr.try_into().expect("must into socks addr"), + remote_addr, buf: vec![0u8; 65535], } } From 1be5410c29e381674b9a73ff44b810297746ac9d Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 22:39:12 +1000 Subject: [PATCH 5/8] older toolchian --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9a85e862..1b6884bf1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -201,7 +201,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: ${{ matrix.toolchain || 'nightly' }} + toolchain: ${{ matrix.toolchain || 'nightly-2024-09-20' }} # until https://github.com/rust-lang/rust-clippy/issues/13457 target: ${{ matrix.target }} components: ${{ matrix.components || 'rustfmt, clippy' }} From e8db91a8810802917517d7c77cb0068f9d558a25 Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 23:14:03 +1000 Subject: [PATCH 6/8] c x --- clash_lib/src/config/def.rs | 44 +++++++++++++++--------------- clash_lib/src/proxy/urltest/mod.rs | 4 +-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/clash_lib/src/config/def.rs b/clash_lib/src/config/def.rs index 484451760..3afbd1975 100644 --- a/clash_lib/src/config/def.rs +++ b/clash_lib/src/config/def.rs @@ -80,11 +80,11 @@ impl Display for LogLevel { /// port: 8888 /// socks-port: 8889 /// mixed-port: 8899 - +/// /// tun: /// enable: false /// device-id: "dev://utun1989" - +/// /// dns: /// enable: true /// listen: 127.0.0.1:53553 @@ -92,9 +92,9 @@ impl Display for LogLevel { /// # tcp: 127.0.0.1:53553 /// # dot: 127.0.0.1:53554 /// # doh: 127.0.0.1:53555 - +/// /// # ipv6: false # when the false, response to AAAA questions will be empty - +/// /// # These nameservers are used to resolve the DNS nameserver hostnames /// below. # Specify IP addresses only /// default-nameserver: @@ -103,14 +103,14 @@ impl Display for LogLevel { /// enhanced-mode: fake-ip /// fake-ip-range: 198.18.0.2/16 # Fake IP addresses pool CIDR /// # use-hosts: true # lookup hosts and return IP record - +/// /// # Hostnames in this list will not be resolved with fake IPs /// # i.e. questions to these domain names will always be answered with their /// # real IP addresses /// # fake-ip-filter: /// # - '*.lan' /// # - localhost.ptlogin2.qq.com - +/// /// # Supports UDP, TCP, DoT, DoH. You can specify the port to connect to. /// # All DNS questions are sent directly to the nameserver, without proxies /// # involved. Clash answers the DNS question with the first result gathered. @@ -120,7 +120,7 @@ impl Display for LogLevel { /// - tls://1.1.1.1:853 # DNS over TLS /// - https://1.1.1.1/dns-query # DNS over HTTPS /// # - dhcp://en0 # dns from dhcp - +/// /// allow-lan: true /// mode: rule /// log-level: debug @@ -129,11 +129,11 @@ impl Display for LogLevel { /// # secret: "clash-rs" /// experimental: /// ignore-resolve-fail: true - +/// /// profile: /// store-selected: true /// store-fake-ip: false - +/// /// proxy-groups: /// - name: "relay" type: relay proxies: /// - "plain-vmess" @@ -143,24 +143,24 @@ impl Display for LogLevel { /// - "load-balance" /// - "select" /// - DIRECT - +/// /// - name: "relay-one" type: relay use: /// - "file-provider" - +/// /// - name: "auto" type: url-test use: /// - "file-provider" /// proxies: /// - DIRECT /// url: "http://www.gstatic.com/generate_204" /// interval: 300 - +/// /// - name: "fallback-auto" type: fallback use: /// - "file-provider" /// proxies: /// - DIRECT /// url: "http://www.gstatic.com/generate_204" /// interval: 300 - +/// /// - name: "load-balance" type: load-balance use: /// - "file-provider" /// proxies: @@ -168,15 +168,15 @@ impl Display for LogLevel { /// strategy: round-robin /// url: "http://www.gstatic.com/generate_204" /// interval: 300 - +/// /// - name: select type: select use: /// - "file-provider" - +/// /// - name: test 🌏 type: select use: /// - "file-provider" /// proxies: /// - DIRECT - +/// /// proxies: /// - name: plain-vmess type: vmess server: 10.0.0.13 port: 16823 uuid: /// b831381d-6324-4d53-ad4f-8cda48b30811 alterId: 0 cipher: auto udp: true @@ -185,15 +185,15 @@ impl Display for LogLevel { /// b831381d-6324-4d53-ad4f-8cda48b30811 alterId: 0 cipher: auto udp: true /// skip-cert-verify: true network: ws ws-opts: path: /// /api/v3/download.getFile headers: Host: www.amazon.com - +/// /// - name: tls-vmess type: vmess server: 10.0.0.13 port: 8443 uuid: /// 23ad6b10-8d1a-40f7-8ad0-e3e35cd38297 alterId: 0 cipher: auto udp: true /// skip-cert-verify: true tls: true - +/// /// - name: h2-vmess type: vmess server: 10.0.0.13 port: 8444 uuid: /// b831381d-6324-4d53-ad4f-8cda48b30811 alterId: 0 cipher: auto udp: true /// skip-cert-verify: true tls: true network: h2 h2-opts: path: /ray - +/// /// - name: vmess-altid type: vmess server: tw-1.ac.laowanxiang.com port: 153 /// uuid: 46dd0dd3-2cc0-3f55-907c-d94e54877687 alterId: 64 cipher: auto udp: /// true network: ws ws-opts: path: /api/v3/download.getFile headers: Host: @@ -205,7 +205,7 @@ impl Display for LogLevel { /// - h2 /// - http/1.1 /// skip-cert-verify: true - +/// /// proxy-providers: /// file-provider: /// type: file @@ -215,14 +215,14 @@ impl Display for LogLevel { /// enable: true /// url: http://www.gstatic.com/generate_204 /// interval: 300 - +/// /// rule-providers: /// file-provider: /// type: file /// path: ./rule-set.yaml /// interval: 300 /// behavior: domain - +/// /// rules: /// - DOMAIN,ipinfo.io,relay /// - RULE-SET,file-provider,trojan diff --git a/clash_lib/src/proxy/urltest/mod.rs b/clash_lib/src/proxy/urltest/mod.rs index 1cf0f602b..840397445 100644 --- a/clash_lib/src/proxy/urltest/mod.rs +++ b/clash_lib/src/proxy/urltest/mod.rs @@ -121,11 +121,11 @@ impl Handler { fastest_delay ); - return inner + inner .fastest_proxy .as_ref() .unwrap_or(proxies.first().unwrap()) - .clone(); + .clone() } } From 83199d39dae77cfad6037834a64ab9d9174cce7f Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 23:19:39 +1000 Subject: [PATCH 7/8] r --- clash_lib/src/app/dns/dummy_keys.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clash_lib/src/app/dns/dummy_keys.rs b/clash_lib/src/app/dns/dummy_keys.rs index ee755e290..863706429 100644 --- a/clash_lib/src/app/dns/dummy_keys.rs +++ b/clash_lib/src/app/dns/dummy_keys.rs @@ -1,6 +1,6 @@ -/// Test certificate and key -/// host: dns.example.com -/// TODO(#51): use real certificate and key +//! Test certificate and key +//! host: dns.example.com +//! TODO(#51): use real certificate and key pub static TEST_CERT: &str = include_str!("test/test.cert"); From f71d098869118e2f9a79c6fd3ef058bc0b187be3 Mon Sep 17 00:00:00 2001 From: dev0 Date: Fri, 27 Sep 2024 23:56:21 +1000 Subject: [PATCH 8/8] t --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5d56faf9a..426c5fe02 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly" +channel = "nightly-2024-09-20" # until https://github.com/rust-lang/rust-clippy/issues/13457