diff --git a/clash/tests/data/config/rules.yaml b/clash/tests/data/config/rules.yaml index 3dfef846a..b71679e26 100644 --- a/clash/tests/data/config/rules.yaml +++ b/clash/tests/data/config/rules.yaml @@ -4,7 +4,7 @@ socks-port: 8889 mixed-port: 8899 tun: - enable: true + enable: false device-id: "dev://utun1989" ipv6: true @@ -40,9 +40,9 @@ dns: # All DNS questions are sent directly to the nameserver, without proxies # involved. Clash answers the DNS question with the first result gathered. nameserver: - - 114.114.114.114 # default value - - 1.1.1.1 # default value - - tls://1.1.1.1:853 # DNS over TLS + # - 114.114.114.114 # default value + # - 1.1.1.1#auto # default value + - tls://1.1.1.1:853#en0 # DNS over TLS # - dhcp://en0 # dns from dhcp allow-lan: true @@ -280,7 +280,7 @@ rule-providers: rules: - DOMAIN,google.com,ws-vmess - DOMAIN-KEYWORD,httpbin,trojan-grpc - - DOMAIN,ipinfo.io,trojan-grpc + - DOMAIN,ipinfo.io,DIRECT # - RULE-SET,file-provider,trojan - GEOIP,CN,relay - DOMAIN-SUFFIX,facebook.com,REJECT diff --git a/clash_lib/src/app/dns/config.rs b/clash_lib/src/app/dns/config.rs index 2ebcefa63..8e9183c78 100644 --- a/clash_lib/src/app/dns/config.rs +++ b/clash_lib/src/app/dns/config.rs @@ -137,9 +137,10 @@ impl Config { } } + let net = net.parse()?; nameservers.push(NameServer { address: addr, - net: net.parse()?, + net, interface: iface.map(String::from), }); } diff --git a/clash_lib/src/app/dns/dhcp.rs b/clash_lib/src/app/dns/dhcp.rs index c5794e60d..6dcea690c 100644 --- a/clash_lib/src/app/dns/dhcp.rs +++ b/clash_lib/src/app/dns/dhcp.rs @@ -186,8 +186,8 @@ async fn listen_dhcp_client(iface: &str) -> io::Result { }; new_udp_socket( - Some(&listen_addr.parse().expect("must parse")), - Some(&Interface::Name(iface.to_string())), + Some(listen_addr.parse().expect("must parse")), + Some(Interface::Name(iface.to_string())), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) diff --git a/clash_lib/src/app/dns/dns_client.rs b/clash_lib/src/app/dns/dns_client.rs index f635d0a0d..1efdc7d66 100644 --- a/clash_lib/src/app/dns/dns_client.rs +++ b/clash_lib/src/app/dns/dns_client.rs @@ -9,11 +9,14 @@ use std::{ use async_trait::async_trait; +use futures::{future::BoxFuture, TryFutureExt}; use hickory_client::{ client, client::AsyncClient, proto::iocompat::AsyncIoTokioAsStd, tcp::TcpClientStream, udp::UdpClientStream, }; -use hickory_proto::error::ProtoError; +use hickory_proto::{ + error::ProtoError, rustls::tls_client_stream::tls_client_connect_with_future, +}; use rustls::ClientConfig; use tokio::{sync::RwLock, task::JoinHandle}; use tracing::{info, warn}; @@ -21,11 +24,11 @@ use tracing::{info, warn}; use crate::{ common::tls::{self, GLOBAL_ROOT_STORE}, dns::{dhcp::DhcpClient, ThreadSafeDNSClient}, + proxy::utils::{new_tcp_stream, new_udp_socket}, }; use hickory_proto::{ h2::HttpsClientStreamBuilder, op::Message, - rustls::tls_client_connect_with_bind_addr, xfer::{DnsRequest, DnsRequestOptions, FirstAnswer}, DnsHandle, }; @@ -93,28 +96,28 @@ impl Display for DnsConfig { DnsConfig::Udp(addr, iface) => { write!(f, "UDP: {}:{} ", addr.ip(), addr.port())?; if let Some(iface) = iface { - write!(f, "bind: {}", iface)?; + write!(f, "bind: {} ", iface)?; } Ok(()) } DnsConfig::Tcp(addr, iface) => { write!(f, "TCP: {}:{} ", addr.ip(), addr.port())?; if let Some(iface) = iface { - write!(f, "bind: {}", iface)?; + write!(f, "bind: {} ", iface)?; } Ok(()) } DnsConfig::Tls(addr, host, iface) => { write!(f, "TLS: {}:{} ", addr.ip(), addr.port())?; if let Some(iface) = iface { - write!(f, "bind: {}", iface)?; + write!(f, "bind: {} ", iface)?; } write!(f, "host: {}", host) } DnsConfig::Https(addr, host, iface) => { write!(f, "HTTPS: {}:{} ", addr.ip(), addr.port())?; if let Some(iface) = iface { - write!(f, "bind: {}", iface)?; + write!(f, "bind: {} ", iface)?; } write!(f, "host: {}", host) } @@ -317,32 +320,48 @@ async fn dns_stream_builder( ) -> Result<(AsyncClient, JoinHandle>), Error> { match cfg { DnsConfig::Udp(addr, iface) => { - let stream = - UdpClientStream::::with_bind_addr_and_timeout( - net::SocketAddr::new(addr.ip(), addr.port()), - // TODO: simplify this match - match iface { - Some(Interface::IpAddr(ip)) => Some(SocketAddr::new(*ip, 0)), - _ => None, - }, - Duration::from_secs(5), - ); + let iface = iface.clone(); + + let closure = move |_: SocketAddr, + _: SocketAddr| + -> BoxFuture< + 'static, + std::io::Result, + > { + Box::pin(new_udp_socket( + None, + iface.clone(), + #[cfg(any(target_os = "linux", target_os = "android"))] + None, + )) + }; + let stream = UdpClientStream::::with_creator( + net::SocketAddr::new(addr.ip(), addr.port()), + None, + Duration::from_secs(5), + Arc::new(closure), + ); + client::AsyncClient::connect(stream) .await .map(|(x, y)| (x, tokio::spawn(y))) .map_err(|x| Error::DNSError(x.to_string())) } DnsConfig::Tcp(addr, iface) => { - let (stream, sender) = TcpClientStream::< - AsyncIoTokioAsStd, - >::with_bind_addr_and_timeout( - net::SocketAddr::new(addr.ip(), addr.port()), - match iface { - Some(Interface::IpAddr(ip)) => Some(SocketAddr::new(*ip, 0)), - _ => None, - }, - Duration::from_secs(5), - ); + let fut = new_tcp_stream( + *addr, + iface.clone(), + #[cfg(any(target_os = "linux", target_os = "android"))] + None, + ) + .map_ok(AsyncIoTokioAsStd); + + let (stream, sender) = + TcpClientStream::>::with_future( + fut, + net::SocketAddr::new(addr.ip(), addr.port()), + Duration::from_secs(5), + ); client::AsyncClient::new(stream, sender, None) .await @@ -356,14 +375,23 @@ async fn dns_stream_builder( .with_no_client_auth(); tls_config.alpn_protocols = vec!["dot".into()]; - let (stream, sender) = tls_client_connect_with_bind_addr::< + let fut = new_tcp_stream( + *addr, + iface.clone(), + #[cfg(any(target_os = "linux", target_os = "android"))] + None, + ) + .map_ok(AsyncIoTokioAsStd); + + let (stream, sender) = tls_client_connect_with_future::< AsyncIoTokioAsStd, + BoxFuture< + 'static, + std::io::Result>, + >, >( + Box::pin(fut), net::SocketAddr::new(addr.ip(), addr.port()), - match iface { - Some(Interface::IpAddr(ip)) => Some(SocketAddr::new(*ip, 0)), - _ => None, - }, host.clone(), Arc::new(tls_config), ); @@ -391,13 +419,18 @@ async fn dns_stream_builder( .set_certificate_verifier(Arc::new(tls::NoHostnameTlsVerifier)); } - let mut stream_builder = - HttpsClientStreamBuilder::with_client_config(Arc::new(tls_config)); - if let Some(Interface::IpAddr(ip)) = iface { - stream_builder.bind_addr(net::SocketAddr::new(*ip, 0)); - } - let stream = stream_builder.build::>( - net::SocketAddr::new(addr.ip(), addr.port()), + let fut = new_tcp_stream( + *addr, + iface.clone(), + #[cfg(any(target_os = "linux", target_os = "android"))] + None, + ) + .map_ok(AsyncIoTokioAsStd); + + let stream = HttpsClientStreamBuilder::build_with_future( + Box::pin(fut), + Arc::new(tls_config), + *addr, host.clone(), ); diff --git a/clash_lib/src/app/dns/helper.rs b/clash_lib/src/app/dns/helper.rs index 802dfd5c4..098fc404e 100644 --- a/clash_lib/src/app/dns/helper.rs +++ b/clash_lib/src/app/dns/helper.rs @@ -3,7 +3,7 @@ use crate::{ dns_client::{DNSNetMode, DnsClient, Opts}, ClashResolver, ThreadSafeDNSClient, }, - proxy::utils::Interface, + proxy::utils::{get_outbound_interface, Interface}, }; use std::sync::Arc; use tracing::{debug, warn}; @@ -37,7 +37,16 @@ pub async fn make_clients( .parse::() .unwrap_or_else(|_| panic!("no port for DNS server: {}", s.address)), net: s.net.to_owned(), - iface: s.interface.as_ref().map(|x| Interface::Name(x.to_owned())), + iface: s + .interface + .as_ref() + .and_then(|x| match x.as_str() { + "auto" => { + get_outbound_interface().map(|x| Interface::Name(x.name)) + } + _ => Some(Interface::Name(x.to_owned())), + }) + .inspect(|x| debug!("DNS client interface: {:?}", x)), }) .await { diff --git a/clash_lib/src/app/dns/resolver/enhanced.rs b/clash_lib/src/app/dns/resolver/enhanced.rs index 58d54ced0..a79c97c07 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, instrument, warn}; +use tracing::{debug, error, instrument, warn}; use hickory_proto::{op, rr}; @@ -212,7 +212,7 @@ impl EnhancedResolver { async move { c.exchange(message) .inspect_err(|x| { - debug!( + error!( "DNS client {} resolve error: {}", c.id(), x.to_string() diff --git a/clash_lib/src/app/logging.rs b/clash_lib/src/app/logging.rs index 409ad2e1b..6a40564b3 100644 --- a/clash_lib/src/app/logging.rs +++ b/clash_lib/src/app/logging.rs @@ -168,7 +168,7 @@ pub fn setup_logging( tracing_subscriber::fmt::Layer::new() .with_ansi(std::io::stdout().is_terminal()) .compact() - .with_target(true) + .with_target(false) .with_file(true) .with_line_number(true) .with_level(true) diff --git a/clash_lib/src/common/errors.rs b/clash_lib/src/common/errors.rs index fbc23e75a..69dd88732 100644 --- a/clash_lib/src/common/errors.rs +++ b/clash_lib/src/common/errors.rs @@ -1,7 +1,10 @@ use std::io; -pub fn new_io_error(msg: &str) -> io::Error { - io::Error::new(io::ErrorKind::Other, msg) +pub fn new_io_error(msg: T) -> io::Error +where + T: Into>, +{ + io::Error::new(io::ErrorKind::Other, msg.into()) } pub fn map_io_error(err: T) -> io::Error diff --git a/clash_lib/src/common/http.rs b/clash_lib/src/common/http.rs index 692ed4d8a..4c22e3ce4 100644 --- a/clash_lib/src/common/http.rs +++ b/clash_lib/src/common/http.rs @@ -39,9 +39,15 @@ impl Service for LocalConnector { let dns = self.0.clone(); Box::pin(async move { - new_tcp_stream( - dns, - host.as_str(), + let remote_ip = dns + .resolve(host.as_str(), false) + .await + .map_err(|v| std::io::Error::new(std::io::ErrorKind::Other, v))? + .ok_or(std::io::Error::new( + std::io::ErrorKind::Other, + "no dns result", + ))?; + let remote_port = remote.port_u16().unwrap_or(match remote.scheme_str() { None => 80, Some(s) => match s { @@ -49,12 +55,15 @@ impl Service for LocalConnector { "https" => 443, _ => panic!("invalid url: {}", remote), }, - }), + }); + new_tcp_stream( + (remote_ip, remote_port).into(), None, #[cfg(any(target_os = "linux", target_os = "android"))] None, ) .await + .map(|x| Box::new(x) as _) }) } } diff --git a/clash_lib/src/proxy/direct/mod.rs b/clash_lib/src/proxy/direct/mod.rs index 7ec11a78a..f146cf1cb 100644 --- a/clash_lib/src/proxy/direct/mod.rs +++ b/clash_lib/src/proxy/direct/mod.rs @@ -8,6 +8,7 @@ use crate::{ }, dns::ThreadSafeDNSResolver, }, + common::errors::map_io_error, config::internal::proxy::PROXY_DIRECT, proxy::{ datagram::OutboundDatagramImpl, @@ -18,6 +19,7 @@ use crate::{ }; use async_trait::async_trait; +use futures::TryFutureExt; use serde::Serialize; use super::{ @@ -60,11 +62,17 @@ impl OutboundHandler for Handler { sess: &Session, resolver: ThreadSafeDNSResolver, ) -> std::io::Result { + let remote_ip = resolver + .resolve(sess.destination.host().as_str(), false) + .map_err(map_io_error) + .await? + .ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::Other, "no dns result") + })?; + let s = new_tcp_stream( - resolver, - sess.destination.host().as_str(), - sess.destination.port(), - sess.iface.as_ref(), + (remote_ip, sess.destination.port()).into(), + sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) @@ -82,7 +90,7 @@ impl OutboundHandler for Handler { ) -> std::io::Result { let d = new_udp_socket( None, - sess.iface.as_ref(), + sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) @@ -129,8 +137,8 @@ impl OutboundHandler for Handler { .connect_datagram( resolver, None, - &sess.destination, - sess.iface.as_ref(), + sess.destination.clone(), + sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 86bf1eb4e..244c3d473 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -224,7 +224,7 @@ impl OutboundHandler for Handler { ); let socket = new_udp_socket( None, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + self.opts.common_opts.iface.clone().or(sess.iface.clone()), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) diff --git a/clash_lib/src/proxy/socks/inbound/stream.rs b/clash_lib/src/proxy/socks/inbound/stream.rs index d6034986e..4a7de423a 100644 --- a/clash_lib/src/proxy/socks/inbound/stream.rs +++ b/clash_lib/src/proxy/socks/inbound/stream.rs @@ -151,7 +151,7 @@ pub async fn handle_tcp<'a>( socks_command::UDP_ASSOCIATE => { let udp_addr = SocketAddr::new(s.local_addr()?.ip(), 0); let udp_inbound = new_udp_socket( - Some(&udp_addr), + Some(udp_addr), None, #[cfg(any(target_os = "linux", target_os = "android"))] None, diff --git a/clash_lib/src/proxy/socks/outbound/mod.rs b/clash_lib/src/proxy/socks/outbound/mod.rs index 4de870e69..087a94791 100644 --- a/clash_lib/src/proxy/socks/outbound/mod.rs +++ b/clash_lib/src/proxy/socks/outbound/mod.rs @@ -149,7 +149,7 @@ impl Handler { let udp_socket = new_udp_socket( None, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + self.opts.common_opts.iface.clone().or(sess.iface.clone()), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) diff --git a/clash_lib/src/proxy/tor/mod.rs b/clash_lib/src/proxy/tor/mod.rs index a4c45783e..b273cfd36 100644 --- a/clash_lib/src/proxy/tor/mod.rs +++ b/clash_lib/src/proxy/tor/mod.rs @@ -83,7 +83,7 @@ impl OutboundHandler for Handler { StreamPrefs::new().any_exit_country(), ) .await - .map_err(|x| new_io_error(&x.to_string()))?; + .map_err(|x| new_io_error(x.to_string()))?; let s = ChainedStreamWrapper::new(StreamWrapper::new(s)); s.append_to_chain(self.name()).await; Ok(Box::new(s)) diff --git a/clash_lib/src/proxy/tuic/compat.rs b/clash_lib/src/proxy/tuic/compat.rs index 25d97c063..e5a7ca28d 100644 --- a/clash_lib/src/proxy/tuic/compat.rs +++ b/clash_lib/src/proxy/tuic/compat.rs @@ -18,7 +18,7 @@ impl Sink for TuicDatagramOutbound { ) -> Poll> { self.send_tx .poll_ready_unpin(cx) - .map_err(|v| new_io_error(&format!("{v:?}"))) + .map_err(|v| new_io_error(format!("{v:?}"))) } fn start_send( @@ -27,7 +27,7 @@ impl Sink for TuicDatagramOutbound { ) -> Result<(), Self::Error> { self.send_tx .start_send_unpin(item) - .map_err(|v| new_io_error(&format!("{v:?}"))) + .map_err(|v| new_io_error(format!("{v:?}"))) } fn poll_flush( @@ -36,7 +36,7 @@ impl Sink for TuicDatagramOutbound { ) -> Poll> { self.send_tx .poll_flush_unpin(cx) - .map_err(|v| new_io_error(&format!("{v:?}"))) + .map_err(|v| new_io_error(format!("{v:?}"))) } fn poll_close( @@ -45,7 +45,7 @@ impl Sink for TuicDatagramOutbound { ) -> Poll> { self.send_tx .poll_close_unpin(cx) - .map_err(|v| new_io_error(&format!("{v:?}"))) + .map_err(|v| new_io_error(format!("{v:?}"))) } } diff --git a/clash_lib/src/proxy/tuic/mod.rs b/clash_lib/src/proxy/tuic/mod.rs index 6d49bc336..ba2965cae 100644 --- a/clash_lib/src/proxy/tuic/mod.rs +++ b/clash_lib/src/proxy/tuic/mod.rs @@ -202,16 +202,16 @@ impl Handler { if resolver.ipv6() { new_udp_socket( - Some((Ipv6Addr::UNSPECIFIED, 0).into()).as_ref(), - iface.map(|x| Interface::Name(x.name.clone())).as_ref(), + Some((Ipv6Addr::UNSPECIFIED, 0).into()), + iface.map(|x| Interface::Name(x.name.clone())), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) .await? } else { new_udp_socket( - Some((Ipv4Addr::UNSPECIFIED, 0).into()).as_ref(), - iface.map(|x| Interface::Name(x.name.clone())).as_ref(), + Some((Ipv4Addr::UNSPECIFIED, 0).into()), + iface.map(|x| Interface::Name(x.name.clone())), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) diff --git a/clash_lib/src/proxy/tuic/types.rs b/clash_lib/src/proxy/tuic/types.rs index 3306be2b0..1e9e32f2e 100644 --- a/clash_lib/src/proxy/tuic/types.rs +++ b/clash_lib/src/proxy/tuic/types.rs @@ -50,7 +50,7 @@ impl TuicEndpoint { let iface = get_outbound_interface(); new_udp_socket( None, - iface.map(|x| Interface::Name(x.name)).as_ref(), + iface.map(|x| Interface::Name(x.name)), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) diff --git a/clash_lib/src/proxy/tun/inbound.rs b/clash_lib/src/proxy/tun/inbound.rs index 7ba1edf68..a2b27b6be 100644 --- a/clash_lib/src/proxy/tun/inbound.rs +++ b/clash_lib/src/proxy/tun/inbound.rs @@ -180,7 +180,7 @@ pub fn get_runner( tun_cfg.up(); let tun = tun::create_as_async(&tun_cfg) - .map_err(|x| new_io_error(&format!("failed to create tun device: {}", x)))?; + .map_err(|x| new_io_error(format!("failed to create tun device: {}", x)))?; let tun_name = tun.get_ref().name().map_err(map_io_error)?; info!("tun started at {}", tun_name); diff --git a/clash_lib/src/proxy/utils/platform/apple.rs b/clash_lib/src/proxy/utils/platform/apple.rs index e48c16a83..24b790070 100644 --- a/clash_lib/src/proxy/utils/platform/apple.rs +++ b/clash_lib/src/proxy/utils/platform/apple.rs @@ -1,6 +1,8 @@ use std::{io, net::SocketAddr}; -use crate::proxy::utils::Interface; +use tracing::warn; + +use crate::{common::errors::new_io_error, proxy::utils::Interface}; pub(crate) fn must_bind_socket_on_interface( socket: &socket2::Socket, @@ -9,21 +11,28 @@ pub(crate) fn must_bind_socket_on_interface( ) -> io::Result<()> { match iface { Interface::IpAddr(ip) => socket.bind(&SocketAddr::new(*ip, 0).into()), - Interface::Name(name) => match family { - socket2::Domain::IPV6 => { - socket.bind_device_by_index_v4(std::num::NonZeroU32::new(unsafe { - libc::if_nametoindex(name.as_str().as_ptr() as *const _) - })) + Interface::Name(name) => { + let index = + unsafe { libc::if_nametoindex(name.as_str().as_ptr() as *const _) }; + if index == 0 { + warn!("failed to get interface index: {}", name); + return Err(new_io_error(format!( + "failed to get interface index: {}", + name + ))); } - socket2::Domain::IPV4 => { - socket.bind_device_by_index_v6(std::num::NonZeroU32::new(unsafe { - libc::if_nametoindex(name.as_str().as_ptr() as *const _) - })) + match family { + socket2::Domain::IPV4 => { + socket.bind_device_by_index_v4(std::num::NonZeroU32::new(index)) + } + socket2::Domain::IPV6 => { + socket.bind_device_by_index_v6(std::num::NonZeroU32::new(index)) + } + _ => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "unsupported socket family", + )), } - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "unsupported socket family", - )), - }, + } } } diff --git a/clash_lib/src/proxy/utils/platform/win.rs b/clash_lib/src/proxy/utils/platform/win.rs index b13dad8b2..78f7057dc 100644 --- a/clash_lib/src/proxy/utils/platform/win.rs +++ b/clash_lib/src/proxy/utils/platform/win.rs @@ -68,7 +68,7 @@ pub(crate) fn must_bind_socket_on_interface( if errno != 0 { let err = unsafe { GetLastError().to_hresult().message() }; error!("bind socket to interface failed: {}", err); - return Err(new_io_error(&err)); + return Err(new_io_error(err)); } Ok(()) } diff --git a/clash_lib/src/proxy/utils/proxy_connector.rs b/clash_lib/src/proxy/utils/proxy_connector.rs index 445ff6017..648379741 100644 --- a/clash_lib/src/proxy/utils/proxy_connector.rs +++ b/clash_lib/src/proxy/utils/proxy_connector.rs @@ -12,6 +12,7 @@ use crate::{ }, dns::ThreadSafeDNSResolver, }, + common::errors::new_io_error, proxy::{ datagram::OutboundDatagramImpl, AnyOutboundDatagram, AnyOutboundHandler, AnyStream, @@ -38,9 +39,9 @@ pub trait RemoteConnector: Send + Sync + Debug { async fn connect_datagram( &self, resolver: ThreadSafeDNSResolver, - src: Option<&SocketAddr>, - destination: &SocksAddr, - iface: Option<&Interface>, + src: Option, + destination: SocksAddr, + iface: Option, #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option< u32, >, @@ -75,23 +76,28 @@ impl RemoteConnector for DirectConnector { u32, >, ) -> std::io::Result { + let dial_addr = resolver + .resolve(address, false) + .await + .map_err(|v| new_io_error(format!("can't resolve dns: {}", v)))? + .ok_or(new_io_error("no dns result"))?; + new_tcp_stream( - resolver, - address, - port, - iface, + (dial_addr, port).into(), + iface.cloned(), #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark, ) .await + .map(|x| Box::new(x) as _) } async fn connect_datagram( &self, resolver: ThreadSafeDNSResolver, - src: Option<&SocketAddr>, - _destination: &SocksAddr, - iface: Option<&Interface>, + src: Option, + _destination: SocksAddr, + iface: Option, #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option< u32, >, @@ -175,9 +181,9 @@ impl RemoteConnector for ProxyConnector { async fn connect_datagram( &self, resolver: ThreadSafeDNSResolver, - _src: Option<&SocketAddr>, - destination: &SocksAddr, - iface: Option<&Interface>, + _src: Option, + destination: SocksAddr, + iface: Option, #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option< u32, >, @@ -185,7 +191,7 @@ impl RemoteConnector for ProxyConnector { let sess = Session { network: Network::Udp, typ: Type::Ignore, - iface: iface.cloned(), + iface, destination: destination.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark, diff --git a/clash_lib/src/proxy/utils/socket_helpers.rs b/clash_lib/src/proxy/utils/socket_helpers.rs index 2e99838d7..c6b9ed97b 100644 --- a/clash_lib/src/proxy/utils/socket_helpers.rs +++ b/clash_lib/src/proxy/utils/socket_helpers.rs @@ -1,8 +1,4 @@ -use std::{ - io, - net::{IpAddr, SocketAddr}, - time::Duration, -}; +use std::{io, net::SocketAddr, time::Duration}; use socket2::TcpKeepalive; use tokio::{ @@ -13,7 +9,6 @@ use tokio::{ use tracing::{debug, error}; use super::{platform::must_bind_socket_on_interface, Interface}; -use crate::{app::dns::ThreadSafeDNSResolver, proxy::AnyStream}; pub fn apply_tcp_options(s: TcpStream) -> std::io::Result { #[cfg(not(target_os = "windows"))] @@ -39,31 +34,13 @@ pub fn apply_tcp_options(s: TcpStream) -> std::io::Result { } } -pub async fn new_tcp_stream<'a>( - resolver: ThreadSafeDNSResolver, - address: &'a str, - port: u16, - iface: Option<&'a Interface>, +pub async fn new_tcp_stream( + endpoint: SocketAddr, + iface: Option, #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option, -) -> io::Result { - let dial_addr = resolver - .resolve(address, false) - .await - .map_err(|v| { - io::Error::new(io::ErrorKind::Other, format!("dns failure: {}", v)) - })? - .ok_or(io::Error::new( - io::ErrorKind::Other, - format!("can't resolve dns: {}", address), - ))?; - - debug!( - "dialing {}[{}]:{} via iface {:?}", - address, dial_addr, port, iface - ); - - let (socket, family) = match (dial_addr, resolver.ipv6()) { - (IpAddr::V4(_), _) => ( +) -> io::Result { + let (socket, family) = match endpoint { + SocketAddr::V4(_) => ( socket2::Socket::new( socket2::Domain::IPV4, socket2::Type::STREAM, @@ -71,7 +48,7 @@ pub async fn new_tcp_stream<'a>( )?, socket2::Domain::IPV4, ), - (IpAddr::V6(_), true) => ( + SocketAddr::V6(_) => ( socket2::Socket::new( socket2::Domain::IPV6, socket2::Type::STREAM, @@ -79,17 +56,11 @@ pub async fn new_tcp_stream<'a>( )?, socket2::Domain::IPV6, ), - (IpAddr::V6(_), false) => { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("ipv6 is disabled, can't dial {}", address), - )) - } }; if let Some(iface) = iface { debug!("binding tcp socket to interface: {:?}", iface); - must_bind_socket_on_interface(&socket, iface, family)?; + must_bind_socket_on_interface(&socket, &iface, family)?; } #[cfg(any(target_os = "linux", target_os = "android"))] @@ -101,19 +72,16 @@ pub async fn new_tcp_stream<'a>( socket.set_nodelay(true)?; socket.set_nonblocking(true)?; - let stream = timeout( + timeout( Duration::from_secs(10), - TcpSocket::from_std_stream(socket.into()).connect((dial_addr, port).into()), + TcpSocket::from_std_stream(socket.into()).connect(endpoint), ) - .await??; - - debug!("connected to {}[{}]:{}", address, dial_addr, port); - Ok(Box::new(stream)) + .await? } pub async fn new_udp_socket( - src: Option<&SocketAddr>, - iface: Option<&Interface>, + src: Option, + iface: Option, #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option, ) -> io::Result { let (socket, family) = match src { @@ -147,7 +115,7 @@ pub async fn new_udp_socket( match (src, iface) { (Some(_), Some(iface)) => { debug!("both src and iface are set, iface will be used: {:?}", src); - must_bind_socket_on_interface(&socket, iface, family).inspect_err( + must_bind_socket_on_interface(&socket, &iface, family).inspect_err( |x| { error!("failed to bind socket to interface: {}", x); }, @@ -155,11 +123,11 @@ pub async fn new_udp_socket( } (Some(src), None) => { debug!("binding socket to: {:?}", src); - socket.bind(&(*src).into())?; + socket.bind(&src.into())?; } (None, Some(iface)) => { debug!("binding udp socket to interface: {:?}", iface); - must_bind_socket_on_interface(&socket, iface, family).inspect_err( + must_bind_socket_on_interface(&socket, &iface, family).inspect_err( |x| { error!("failed to bind socket to interface: {}", x); }, diff --git a/clash_lib/src/proxy/wg/wireguard.rs b/clash_lib/src/proxy/wg/wireguard.rs index 96f040d4b..45d166d7f 100644 --- a/clash_lib/src/proxy/wg/wireguard.rs +++ b/clash_lib/src/proxy/wg/wireguard.rs @@ -100,7 +100,7 @@ impl WireguardTunnel { .connect_datagram( resolver, None, - &remote_endpoint.into(), + remote_endpoint.into(), None, #[cfg(any(target_os = "linux", target_os = "android"))] None,