From 48b48986f5929ce6d09baa73a79e91c2396a4ccd Mon Sep 17 00:00:00 2001 From: Yuwei B Date: Mon, 11 May 2026 12:45:10 -0700 Subject: [PATCH 1/5] refactor: migrate hickory-client to hickory-net The hickory-client crate has been subsumed into hickory-net per the hickory-dns release notes. No future releases of hickory-client are expected. - Replace hickory-client 0.25 dep in clash-dns with hickory-net 0.26 - Update feature flags (https-aws-lc-rs, h3-aws-lc-rs, etc.) to point to hickory-net instead of hickory-client - Add missing DnsUdpSocket and AsyncIoTokioAsStd imports in runtime.rs - Update dns_client.rs to use Client (now generic) - Remove proto 0.25/0.26 encode/decode roundtrip workaround - Rewrite dns_stream_builder for new sync API: TcpClientStream::new and tls_client_connect now return (Future, BufDnsStreamHandle); await the future before passing to Client::new/with_timeout - tls_client_connect now takes ServerName<'static> instead of String - HttpsClientStream and H3ClientStream use from_sender pattern - Update handler.rs tests to use hickory_net/hickory_proto imports and new 0.26 connection API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Cargo.lock | 102 ++------------------- clash-dns/Cargo.toml | 10 +- clash-dns/src/handler.rs | 88 +++++++++--------- clash-lib/Cargo.toml | 6 +- clash-lib/src/app/dns/dns_client.rs | 94 ++++++++----------- clash-lib/src/app/dns/resolver/enhanced.rs | 18 ++-- clash-lib/src/app/dns/runtime.rs | 9 +- 7 files changed, 106 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42069683d..7f106c850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1347,10 +1347,10 @@ dependencies = [ "futures", "generic-array 1.4.1", "h2", - "h3 0.0.8", - "h3-quinn 0.0.10", + "h3", + "h3-quinn", "hex", - "hickory-client", + "hickory-net", "hickory-proto 0.26.1", "hickory-resolver", "hmac 0.13.0", @@ -2652,12 +2652,6 @@ dependencies = [ "encoding_rs", ] -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - [[package]] name = "enum-as-inner" version = "0.6.1" @@ -3356,20 +3350,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "h3" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfb059a4f28a66f186ed16ad912d142f490676acba59353831d7cb45a96b0d3" -dependencies = [ - "bytes", - "fastrand", - "futures-util", - "http", - "pin-project-lite", - "tokio", -] - [[package]] name = "h3" version = "0.0.8" @@ -3384,20 +3364,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "h3-quinn" -version = "0.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d482318ae94198fc8e3cbb0b7ba3099c865d744e6ec7c62039ca7b6b6c66fbf" -dependencies = [ - "bytes", - "futures", - "h3 0.0.7", - "quinn 0.11.9", - "tokio", - "tokio-util", -] - [[package]] name = "h3-quinn" version = "0.0.10" @@ -3406,7 +3372,7 @@ checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" dependencies = [ "bytes", "futures", - "h3 0.0.8", + "h3", "quinn 0.11.9", "tokio", "tokio-util", @@ -3554,25 +3520,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" -[[package]] -name = "hickory-client" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c466cd63a4217d5b2b8e32f23f58312741ce96e3c84bf7438677d2baff0fc555" -dependencies = [ - "cfg-if", - "data-encoding", - "futures-channel", - "futures-util", - "hickory-proto 0.25.2", - "once_cell", - "radix_trie", - "rand 0.9.4", - "thiserror 2.0.18", - "tokio", - "tracing", -] - [[package]] name = "hickory-net" version = "0.26.1" @@ -3587,8 +3534,8 @@ dependencies = [ "futures-io", "futures-util", "h2", - "h3 0.0.8", - "h3-quinn 0.0.10", + "h3", + "h3-quinn", "hickory-proto 0.26.1", "http", "idna", @@ -3613,29 +3560,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ "async-trait", - "bytes", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", - "futures-io", "futures-util", - "h2", - "h3 0.0.7", - "h3-quinn 0.0.9", - "http", "idna", "ipnet", "once_cell", - "pin-project-lite", - "quinn 0.11.9", "rand 0.9.4", - "ring", - "rustls", "thiserror 2.0.18", "tinyvec", - "tokio", - "tokio-rustls", "tracing", "url", ] @@ -3707,8 +3642,8 @@ dependencies = [ "data-encoding", "futures-util", "h2", - "h3 0.0.8", - "h3-quinn 0.0.10", + "h3", + "h3-quinn", "hickory-net", "hickory-proto 0.26.1", "hickory-resolver", @@ -5193,15 +5128,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - [[package]] name = "nix" version = "0.27.1" @@ -6824,16 +6750,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rand" version = "0.8.6" @@ -11618,7 +11534,7 @@ dependencies = [ "async-trait", "env_logger", "futures", - "hickory-client", + "hickory-net", "hickory-proto 0.26.1", "hickory-server", "mockall", diff --git a/clash-dns/Cargo.toml b/clash-dns/Cargo.toml index adc1641cd..a19547d32 100644 --- a/clash-dns/Cargo.toml +++ b/clash-dns/Cargo.toml @@ -13,16 +13,16 @@ aws-lc-rs = [ "hickory-server/https-aws-lc-rs", "hickory-server/h3-aws-lc-rs", "hickory-proto/dnssec-aws-lc-rs", - "hickory-client/https-aws-lc-rs", - "hickory-client/h3-aws-lc-rs", + "hickory-net/https-aws-lc-rs", + "hickory-net/h3-aws-lc-rs", ] ring = [ "rustls/ring", "hickory-server/https-ring", "hickory-server/h3-ring", "hickory-proto/dnssec-ring", - "hickory-client/https-ring", - "hickory-client/h3-ring", + "hickory-net/https-ring", + "hickory-net/h3-ring", ] [dependencies] @@ -38,7 +38,7 @@ rustls = { version = "0.23", default-features = false } rustls-pemfile = "2" webpki-roots = "1.0" -hickory-client = { version = "0.25", default-features = false } +hickory-net = { version = "0.26", default-features = false } hickory-server = { version = "0.26", default-features = false } hickory-proto = { version = "0.26", default-features = false } diff --git a/clash-dns/src/handler.rs b/clash-dns/src/handler.rs index 1cd86f191..b0c04397e 100644 --- a/clash-dns/src/handler.rs +++ b/clash-dns/src/handler.rs @@ -379,26 +379,28 @@ mod tests { tls::{self, global_root_store}, }; use futures::FutureExt; - use hickory_client::{ + use hickory_net::{ client::{Client, ClientHandle}, - proto::{ - h2::HttpsClientStreamBuilder, - h3::H3ClientStreamBuilder, - rr::{DNSClass, Name, RData, RecordType, rdata::A}, - runtime::TokioRuntimeProvider, - rustls::tls_client_connect, - tcp::TcpClientStream, - udp::UdpClientStream, - }, + h2::{HttpsClientStream, HttpsClientStreamBuilder}, + h3::H3ClientStreamBuilder, + tcp::TcpClientStream, + tls::tls_client_connect, + udp::UdpClientStream, }; - use rustls::ClientConfig; + use hickory_proto::{ + rr::{DNSClass, Name, RData, RecordType, rdata::A}, + runtime::TokioRuntimeProvider, + }; + use rustls::{ClientConfig, pki_types::ServerName}; use std::{sync::Arc, time::Duration}; use tokio::{ net::{TcpListener, UdpSocket}, task::JoinHandle, }; - async fn send_query(client: &mut Client) -> anyhow::Result<()> { + async fn send_query( + client: &mut Client, + ) -> anyhow::Result<()> { let name = Name::from_ascii("www.example.com.").unwrap(); let mut retries = 3; @@ -523,16 +525,16 @@ mod tests { let stream = UdpClientStream::builder(udp_addr, TokioRuntimeProvider::new()).build(); - let (mut client, handle) = Client::connect(stream).await?; - tokio::spawn(handle); + let (mut client, bg) = Client::::from_sender(stream); + tokio::spawn(bg); send_query(&mut client).await?; - let (stream, sender) = + let (stream_future, sender) = TcpClientStream::new(tcp_addr, None, None, TokioRuntimeProvider::new()); - - let (mut client, handle) = Client::new(stream, sender, None).await?; - tokio::spawn(handle); + let stream = stream_future.await?; + let (mut client, bg) = Client::::new(stream, sender); + tokio::spawn(bg); send_query(&mut client).await?; @@ -544,20 +546,23 @@ mod tests { .dangerous() .set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new())); - let (stream, sender) = tls_client_connect( + let server_name = ServerName::try_from("dns.example.com").unwrap(); + let (stream_future, sender) = tls_client_connect( dot_addr, - "dns.example.com".to_owned(), + server_name, Arc::new(tls_config), TokioRuntimeProvider::new(), ); - let (mut client, handle) = - Client::with_timeout(stream, sender, Duration::from_secs(5), None) - .await - .inspect_err(|e| { - assert!(false, "Failed to connect to DoT server: {}", e); - })?; - tokio::spawn(handle); + let stream = stream_future.await.inspect_err(|e| { + assert!(false, "Failed to connect to DoT server: {}", e); + })?; + let (mut client, bg) = Client::::with_timeout( + stream, + sender, + Duration::from_secs(5), + ); + tokio::spawn(bg); send_query(&mut client).await?; @@ -570,18 +575,15 @@ mod tests { .dangerous() .set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new())); - let stream = HttpsClientStreamBuilder::with_client_config( + let stream = HttpsClientStream::builder( Arc::new(tls_config), TokioRuntimeProvider::new(), ) - .build( - doh_addr, - "dns.example.com".to_owned(), - "/dns-query".to_owned(), - ); + .build(doh_addr, "dns.example.com".into(), "/dns-query".into()) + .await?; - let (mut client, handle) = Client::connect(stream).await?; - tokio::spawn(handle); + let (mut client, bg) = Client::::from_sender(stream); + tokio::spawn(bg); send_query(&mut client).await?; @@ -594,17 +596,13 @@ mod tests { .dangerous() .set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new())); - let stream = H3ClientStreamBuilder::default() + let stream = H3ClientStreamBuilder::builder() .crypto_config(tls_config) - .clone() - .build( - doh3_addr, - "dns.example.com".to_owned(), - "/dns-query".to_owned(), - ); - - let (mut client, handle) = Client::connect(stream).await?; - tokio::spawn(handle); + .build(doh3_addr, "dns.example.com".into(), "/dns-query".into()) + .await?; + + let (mut client, bg) = Client::::from_sender(stream); + tokio::spawn(bg); send_query(&mut client).await?; Ok(()) diff --git a/clash-lib/Cargo.toml b/clash-lib/Cargo.toml index adad0c1c4..a8a798c1f 100644 --- a/clash-lib/Cargo.toml +++ b/clash-lib/Cargo.toml @@ -13,7 +13,7 @@ aws-lc-rs = [ "quinn-proto/rustls-aws-lc-rs", "russh/aws-lc-rs", "boringtun/aws-lc-rs", - "hickory-client/https-aws-lc-rs", + "hickory-net/https-aws-lc-rs", "tuic-quinn?/rustls-aws-lc-rs", "watfaq-dns/aws-lc-rs", ] @@ -22,7 +22,7 @@ ring = [ "quinn-proto/ring", "russh/ring", "boringtun/ring", - "hickory-client/https-ring", + "hickory-net/https-ring", "tuic-quinn?/rustls-ring", "watfaq-dns/ring", ] @@ -158,7 +158,7 @@ educe = "0.6" # DNS watfaq-dns = { path = "../clash-dns", default-features = false } -hickory-client = { version = "0.25", default-features = false } +hickory-net = { version = "0.26", default-features = false } hickory-resolver = { version = "0.26", default-features = false, features = ["tokio", "system-config"] } hickory-proto = { version = "0.26", default-features = false } diff --git a/clash-lib/src/app/dns/dns_client.rs b/clash-lib/src/app/dns/dns_client.rs index b2277b0ca..7a74f8311 100644 --- a/clash-lib/src/app/dns/dns_client.rs +++ b/clash-lib/src/app/dns/dns_client.rs @@ -9,25 +9,18 @@ use std::{ use async_trait::async_trait; -use hickory_client::{ - client, - proto::{ - DnsHandle, ProtoError, - h2::HttpsClientStreamBuilder, - rustls::tls_client_connect, - tcp::TcpClientStream, - udp::UdpClientStream, - xfer::{DnsRequest, DnsRequestOptions, FirstAnswer}, - }, +use hickory_net::{ + DnsHandle, client, h2::HttpsClientStream, tcp::TcpClientStream, + tls::tls_client_connect, udp::UdpClientStream, xfer::FirstAnswer, }; use hickory_proto::{ - op::Message, + op::{self, DnsRequest, DnsRequestOptions, Message}, rr::{ RecordType, rdata::opt::{ClientSubnet, EdnsCode, EdnsOption}, }, }; -use rustls::ClientConfig; +use rustls::{ClientConfig, pki_types::ServerName}; use tokio::{sync::RwLock, task::JoinHandle}; use tracing::{info, instrument, trace, warn}; @@ -273,8 +266,8 @@ impl Display for DnsConfig { } struct Inner { - c: Option, - bg_handle: Option>>, + c: Option>, + bg_handle: Option>, } /// DnsClient @@ -549,20 +542,9 @@ impl Client for DnsClient { let mut outbound = msg.clone(); self.apply_edns_client_subnet(&mut outbound); - // TODO: remove this encode/decode roundtrip once hickory-client 0.26.0 - // stable is published. Currently hickory-client is pinned to 0.25.x - // which internally uses hickory-proto 0.25.x, while the rest of the - // stack uses hickory-proto 0.26.x. The two Message types are - // incompatible at the Rust type level, so we serialize to wire bytes - // and reparse to cross the version boundary. - let bytes = outbound - .to_vec() - .map_err(|e| Error::DNSError(e.to_string()))?; - let msg_025 = hickory_client::proto::op::Message::from_vec(&bytes) - .map_err(|e| Error::DNSError(e.to_string()))?; - let mut req = DnsRequest::new(msg_025, DnsRequestOptions::default()); - if req.id() == 0 { - req.set_id(rand::random::()); + let mut req = DnsRequest::new(outbound, DnsRequestOptions::default()); + if req.metadata.id == 0 { + req.metadata.id = rand::random::(); } self.inner .read() @@ -574,19 +556,13 @@ impl Client for DnsClient { .first_answer() .await .map_err(|x| Error::DNSError(x.to_string()).into()) - .and_then(|x: hickory_client::proto::xfer::DnsResponse| { - // TODO: same version-boundary workaround as above — remove - // once hickory-client 0.26.0 stable ships. - let bytes = x.into_buffer(); - hickory_proto::op::Message::from_vec(&bytes) - .map_err(|e| Error::DNSError(e.to_string()).into()) - }) + .map(|x: op::DnsResponse| x.into_message()) } } async fn dns_stream_builder( cfg: &DnsConfig, -) -> Result<(client::Client, JoinHandle>), Error> { +) -> Result<(client::Client, JoinHandle<()>), Error> { let dns_resolver = Arc::new(dns::SystemResolver::new(false)?); match cfg { DnsConfig::Udp(addr, iface, proxy, fw_mark) => { @@ -602,13 +578,11 @@ async fn dns_stream_builder( .with_timeout(Some(Duration::from_secs(5))) .build(); - client::Client::connect(stream) - .await - .map(|(x, y)| (x, tokio::spawn(y))) - .map_err(|x| Error::DNSError(x.to_string())) + let (x, y) = client::Client::::from_sender(stream); + Ok((x, tokio::spawn(y))) } DnsConfig::Tcp(addr, iface, proxy, fw_mark) => { - let (stream, sender) = TcpClientStream::new( + let (stream_future, sender) = TcpClientStream::new( *addr, None, Some(Duration::from_secs(5)), @@ -620,10 +594,11 @@ async fn dns_stream_builder( ), ); - client::Client::new(stream, sender, None) + let stream = stream_future .await - .map(|(x, y)| (x, tokio::spawn(y))) - .map_err(|x| Error::DNSError(x.to_string())) + .map_err(|x| Error::DNSError(x.to_string()))?; + let (x, y) = client::Client::::new(stream, sender); + Ok((x, tokio::spawn(y))) } DnsConfig::Tls(addr, host, iface, proxy, fw_mark) => { let mut tls_config = ClientConfig::builder() @@ -634,9 +609,12 @@ async fn dns_stream_builder( let addr = *addr; let host = host.clone(); let iface = iface.clone(); - let (stream, sender) = tls_client_connect( + + let server_name = ServerName::try_from(host.to_string()) + .map_err(|e| Error::DNSError(e.to_string()))?; + let (stream_future, sender) = tls_client_connect( addr, - host.to_string(), + server_name, Arc::new(tls_config), DnsRuntimeProvider::new( proxy.clone(), @@ -646,15 +624,15 @@ async fn dns_stream_builder( ), ); - client::Client::with_timeout( + let stream = stream_future + .await + .map_err(|x| Error::DNSError(x.to_string()))?; + let (x, y) = client::Client::::with_timeout( stream, sender, Duration::from_secs(5), - None, - ) - .await - .map(|(x, y)| (x, tokio::spawn(y))) - .map_err(|x| Error::DNSError(x.to_string())) + ); + Ok((x, tokio::spawn(y))) } DnsConfig::Https(addr, host, iface, proxy, fw_mark) => { let mut tls_config = ClientConfig::builder() @@ -672,7 +650,7 @@ async fn dns_stream_builder( tls::NoHostnameTlsVerifier::new(), )); } - let stream = HttpsClientStreamBuilder::with_client_config( + let stream = HttpsClientStream::builder( Arc::new(tls_config), DnsRuntimeProvider::new( proxy.clone(), @@ -681,12 +659,12 @@ async fn dns_stream_builder( *fw_mark, ), ) - .build(*addr, host.to_string(), "/dns-query".to_string()); + .build(*addr, host.to_string().into(), "/dns-query".into()) + .await + .map_err(|x| Error::DNSError(x.to_string()))?; - client::Client::connect(stream) - .await - .map(|(x, y)| (x, tokio::spawn(y))) - .map_err(|x| Error::DNSError(x.to_string())) + let (x, y) = client::Client::::from_sender(stream); + Ok((x, tokio::spawn(y))) } } } diff --git a/clash-lib/src/app/dns/resolver/enhanced.rs b/clash-lib/src/app/dns/resolver/enhanced.rs index 546a2a0f4..b6cf14c0f 100644 --- a/clash-lib/src/app/dns/resolver/enhanced.rs +++ b/clash-lib/src/app/dns/resolver/enhanced.rs @@ -736,14 +736,11 @@ impl ClashResolver for EnhancedResolver { #[cfg(test)] mod tests { - use hickory_client::{ - client, - proto::{ - udp::UdpClientStream, - xfer::{DnsHandle, DnsRequest, DnsRequestOptions, FirstAnswer}, - }, + use hickory_net::{client, udp::UdpClientStream}; + use hickory_proto::{ + op::{self, DnsRequest, DnsRequestOptions}, + rr, }; - use hickory_proto::{op, rr}; use std::{net::Ipv4Addr, sync::Arc, time::Instant}; use crate::{ @@ -860,10 +857,7 @@ mod tests { #[tokio::test] async fn test_bad_labels_with_custom_resolver() { - // Use hickory_client::proto (0.25) types throughout so DnsRequest::new - // gets a compatible Message. TODO: remove shadowing once hickory-client - // 0.26.0 stable ships and aligns with hickory-proto 0.26. - use hickory_client::proto::{op, rr}; + use hickory_net::proto::{op, rr}; let name = rr::Name::from_str_relaxed("some_domain.understore") .unwrap() @@ -871,7 +865,7 @@ mod tests { .unwrap(); assert_eq!(name.to_string(), "some_domain.understore."); - let mut m = op::Message::new(); + let mut m = op::Message::query(); let mut q = op::Query::new(); q.set_name(name); diff --git a/clash-lib/src/app/dns/runtime.rs b/clash-lib/src/app/dns/runtime.rs index 5ecad20e3..df6de14f7 100644 --- a/clash-lib/src/app/dns/runtime.rs +++ b/clash-lib/src/app/dns/runtime.rs @@ -16,11 +16,10 @@ use crate::{ session::{Network, Session, Type}, }; use futures::{SinkExt, StreamExt}; -use hickory_client::proto::{ - runtime::{ - RuntimeProvider, TokioHandle, TokioTime, iocompat::AsyncIoTokioAsStd, - }, - udp::DnsUdpSocket, + +use hickory_net::runtime::{ + DnsUdpSocket, RuntimeProvider, TokioHandle, TokioTime, + iocompat::AsyncIoTokioAsStd, }; use tokio::sync::Mutex; From 2be01fc14c4eca845f82e8bdf3420322672268b2 Mon Sep 17 00:00:00 2001 From: Yuwei B Date: Mon, 11 May 2026 12:48:11 -0700 Subject: [PATCH 2/5] build(deps): use dhcproto git ref to pull hickory-proto 0.26 Switch dhcproto from crates.io 0.14 to latest git ref (eece41c) which has been updated to use hickory-proto 0.26.1, eliminating the last duplicate hickory-proto 0.25.x in the dependency tree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Cargo.lock | 40 ++++++++-------------------------------- clash-lib/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f106c850..4f95d9bf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1351,7 +1351,7 @@ dependencies = [ "h3-quinn", "hex", "hickory-net", - "hickory-proto 0.26.1", + "hickory-proto", "hickory-resolver", "hmac 0.13.0", "http", @@ -2341,11 +2341,10 @@ dependencies = [ [[package]] name = "dhcproto" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "425ab19f6a915beac79cac8ec2810c1311b502ae14d7f294682081cf5ae4c5bb" +source = "git+https://github.com/bluecatengineering/dhcproto?rev=eece41c9a13b0e4912fb9a8f08401ab01b4123d4#eece41c9a13b0e4912fb9a8f08401ab01b4123d4" dependencies = [ "dhcproto-macros", - "hickory-proto 0.25.2", + "hickory-proto", "ipnet", "rand 0.9.4", "thiserror 2.0.18", @@ -2354,8 +2353,7 @@ dependencies = [ [[package]] name = "dhcproto-macros" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944d18b1889ce23702b0ea0230e87033cf5b4659e233419b5c00da3983a387b" +source = "git+https://github.com/bluecatengineering/dhcproto?rev=eece41c9a13b0e4912fb9a8f08401ab01b4123d4#eece41c9a13b0e4912fb9a8f08401ab01b4123d4" dependencies = [ "proc-macro2", "quote", @@ -3536,7 +3534,7 @@ dependencies = [ "h2", "h3", "h3-quinn", - "hickory-proto 0.26.1", + "hickory-proto", "http", "idna", "ipnet", @@ -3553,28 +3551,6 @@ dependencies = [ "url", ] -[[package]] -name = "hickory-proto" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-util", - "idna", - "ipnet", - "once_cell", - "rand 0.9.4", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "url", -] - [[package]] name = "hickory-proto" version = "0.26.1" @@ -3609,7 +3585,7 @@ dependencies = [ "cfg-if", "futures-util", "hickory-net", - "hickory-proto 0.26.1", + "hickory-proto", "ipconfig", "ipnet", "jni", @@ -3645,7 +3621,7 @@ dependencies = [ "h3", "h3-quinn", "hickory-net", - "hickory-proto 0.26.1", + "hickory-proto", "hickory-resolver", "http", "ipnet", @@ -11535,7 +11511,7 @@ dependencies = [ "env_logger", "futures", "hickory-net", - "hickory-proto 0.26.1", + "hickory-proto", "hickory-server", "mockall", "rustls", diff --git a/clash-lib/Cargo.toml b/clash-lib/Cargo.toml index a8a798c1f..19ec45aa9 100644 --- a/clash-lib/Cargo.toml +++ b/clash-lib/Cargo.toml @@ -162,7 +162,7 @@ hickory-net = { version = "0.26", default-features = false } hickory-resolver = { version = "0.26", default-features = false, features = ["tokio", "system-config"] } hickory-proto = { version = "0.26", default-features = false } -dhcproto = "0.14" +dhcproto = { git = "https://github.com/bluecatengineering/dhcproto", rev = "eece41c9a13b0e4912fb9a8f08401ab01b4123d4" } ssh-key = { version = "0.6", features = ["std"] } rand = "0.10" From 9f55e98b64f1cf2124f73c764164942dc984935b Mon Sep 17 00:00:00 2001 From: Yuwei B Date: Mon, 11 May 2026 12:52:08 -0700 Subject: [PATCH 3/5] refactor: replace feature-gated crypto calls with CryptoProvider::get_default() Instead of repeating #[cfg(feature = "aws-lc-rs")] / #[cfg(feature = "ring")] at every call site, use the runtime-installed default provider: - clash-dns/src/handler.rs: remove the two cfg-gated any_supported_type imports; use CryptoProvider::get_default().key_provider.load_private_key() - clash-lib/src/proxy/anytls/inbound/handler.rs: replace feature-gated supported_verify_schemes() body with CryptoProvider::get_default() - anytls/inbound/{handler,tls}.rs tests: replace local install_crypto_provider() helpers that duplicated the feature-gated logic with a call to crate::setup_default_crypto_provider() The single source of truth for feature selection remains the centralized setup_default_crypto_provider() functions in clash-lib/src/lib.rs and clash-dns/src/lib.rs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- clash-dns/src/handler.rs | 13 +++++++----- clash-lib/src/proxy/anytls/inbound/handler.rs | 21 +++++-------------- clash-lib/src/proxy/anytls/inbound/tls.rs | 5 +---- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/clash-dns/src/handler.rs b/clash-dns/src/handler.rs index b0c04397e..67247ef68 100644 --- a/clash-dns/src/handler.rs +++ b/clash-dns/src/handler.rs @@ -18,10 +18,6 @@ use hickory_server::{ server::{Request, RequestHandler, ResponseHandler, ResponseInfo}, zone_handler::MessageResponseBuilder, }; -#[cfg(feature = "aws-lc-rs")] -use rustls::crypto::aws_lc_rs::sign::any_supported_type; -#[cfg(all(not(feature = "aws-lc-rs"), feature = "ring"))] -use rustls::crypto::ring::sign::any_supported_type; use rustls::{server::AlwaysResolvesServerRawPublicKeys, sign::CertifiedKey}; use std::{sync::Arc, time::Duration}; use thiserror::Error; @@ -36,7 +32,14 @@ struct CertificateKeyPair { impl From for Arc { fn from(pair: CertificateKeyPair) -> Self { Arc::new(AlwaysResolvesServerRawPublicKeys::new(Arc::new( - CertifiedKey::new(pair.certs, any_supported_type(&pair.key).unwrap()), + CertifiedKey::new( + pair.certs, + rustls::crypto::CryptoProvider::get_default() + .expect("no default crypto provider installed") + .key_provider + .load_private_key(pair.key) + .expect("unsupported private key type"), + ), ))) } } diff --git a/clash-lib/src/proxy/anytls/inbound/handler.rs b/clash-lib/src/proxy/anytls/inbound/handler.rs index dd6fff30d..63fc13805 100644 --- a/clash-lib/src/proxy/anytls/inbound/handler.rs +++ b/clash-lib/src/proxy/anytls/inbound/handler.rs @@ -580,10 +580,7 @@ mod tests { use tokio_rustls::TlsConnector; fn install_crypto_provider() { - #[cfg(feature = "aws-lc-rs")] - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); - #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] - let _ = rustls::crypto::ring::default_provider().install_default(); + crate::setup_default_crypto_provider(); } /// Tests the complete AnyTLS server-side handshake parsing over a real TLS @@ -847,18 +844,10 @@ mod tests { } fn supported_verify_schemes(&self) -> Vec { - #[cfg(feature = "aws-lc-rs")] - { - rustls::crypto::aws_lc_rs::default_provider() - .signature_verification_algorithms - .supported_schemes() - } - #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] - { - rustls::crypto::ring::default_provider() - .signature_verification_algorithms - .supported_schemes() - } + rustls::crypto::CryptoProvider::get_default() + .expect("no default crypto provider installed") + .signature_verification_algorithms + .supported_schemes() } } diff --git a/clash-lib/src/proxy/anytls/inbound/tls.rs b/clash-lib/src/proxy/anytls/inbound/tls.rs index b02e5bb84..a714a72a0 100644 --- a/clash-lib/src/proxy/anytls/inbound/tls.rs +++ b/clash-lib/src/proxy/anytls/inbound/tls.rs @@ -115,10 +115,7 @@ mod tests { use super::*; fn install_crypto_provider() { - #[cfg(feature = "aws-lc-rs")] - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); - #[cfg(all(feature = "ring", not(feature = "aws-lc-rs")))] - let _ = rustls::crypto::ring::default_provider().install_default(); + crate::setup_default_crypto_provider(); } #[test] From 80043abb9ac1a4f69e61c97a6987e5308d8aec5f Mon Sep 17 00:00:00 2001 From: Yuwei B Date: Mon, 11 May 2026 13:05:47 -0700 Subject: [PATCH 4/5] fix: address test build failures from hickory-net 0.26 API changes - clash-dns/src/handler.rs: move TokioRuntimeProvider import from hickory_proto::runtime to hickory_net::runtime (it was never in proto); fix A record comparison: A has no ::new() in 0.26, use ip.0 == Ipv4Addr - clash-lib/src/app/dns/resolver/enhanced.rs: replace removed Message::set_recursion_desired() with direct field m.metadata.recursion_desired; replace Client::connect().await (removed) with Client::from_sender() (sync); replace DnsRequest::set_id() (removed) with req.metadata.id = ... Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- clash-dns/src/handler.rs | 8 +++----- clash-lib/src/app/dns/resolver/enhanced.rs | 7 +++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/clash-dns/src/handler.rs b/clash-dns/src/handler.rs index 67247ef68..42f695b84 100644 --- a/clash-dns/src/handler.rs +++ b/clash-dns/src/handler.rs @@ -386,14 +386,12 @@ mod tests { client::{Client, ClientHandle}, h2::{HttpsClientStream, HttpsClientStreamBuilder}, h3::H3ClientStreamBuilder, + runtime::TokioRuntimeProvider, tcp::TcpClientStream, tls::tls_client_connect, udp::UdpClientStream, }; - use hickory_proto::{ - rr::{DNSClass, Name, RData, RecordType, rdata::A}, - runtime::TokioRuntimeProvider, - }; + use hickory_proto::rr::{DNSClass, Name, RData, RecordType, rdata::A}; use rustls::{ClientConfig, pki_types::ServerName}; use std::{sync::Arc, time::Duration}; use tokio::{ @@ -428,7 +426,7 @@ mod tests { let answers = response.answers(); if let RData::A(ip) = answers[0].data() { - assert_eq!(*ip, A::new(93, 184, 215, 14)) + assert_eq!(ip.0, std::net::Ipv4Addr::new(93, 184, 215, 14)) } else { unreachable!("unexpected result") } diff --git a/clash-lib/src/app/dns/resolver/enhanced.rs b/clash-lib/src/app/dns/resolver/enhanced.rs index b6cf14c0f..ffcfc4e43 100644 --- a/clash-lib/src/app/dns/resolver/enhanced.rs +++ b/clash-lib/src/app/dns/resolver/enhanced.rs @@ -871,19 +871,18 @@ mod tests { q.set_name(name); q.set_query_type(rr::RecordType::A); m.add_query(q); - m.set_recursion_desired(true); + m.metadata.recursion_desired = true; let stream = UdpClientStream::builder( "1.1.1.1:53".parse().unwrap(), DnsRuntimeProvider::new_direct(None, None), ) .build(); - let (client, bg) = client::Client::connect(stream).await.unwrap(); - + let (client, bg) = client::Client::::from_sender(stream); tokio::spawn(bg); let mut req = DnsRequest::new(m, DnsRequestOptions::default()); - req.set_id(rand::random::()); + req.metadata.id = rand::random::(); let res = client.send(req).first_answer().await; assert!(res.is_ok()); } From 4a72eee8a97523b666bd763d96b6c61aae0f70a8 Mon Sep 17 00:00:00 2001 From: Yuwei B Date: Mon, 11 May 2026 13:12:40 -0700 Subject: [PATCH 5/5] fix: more hickory-net 0.26 API fixes in test code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - handler.rs: H3ClientStreamBuilder::builder() → H3ClientStream::builder() (builder() is a static method on H3ClientStream, not H3ClientStreamBuilder); import H3ClientStream alongside H3ClientStreamBuilder - handler.rs: response.answers() → &response.answers (now a pub field in 0.26.1) - handler.rs: answers[0].data() → &answers[0].data (pub field; method requires RecordData trait in scope, field access is simpler) - enhanced.rs: bring DnsHandle and FirstAnswer traits into test scope so client.send() and .first_answer() resolve correctly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- clash-dns/src/handler.rs | 8 ++++---- clash-lib/src/app/dns/resolver/enhanced.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clash-dns/src/handler.rs b/clash-dns/src/handler.rs index 42f695b84..639e43438 100644 --- a/clash-dns/src/handler.rs +++ b/clash-dns/src/handler.rs @@ -385,7 +385,7 @@ mod tests { use hickory_net::{ client::{Client, ClientHandle}, h2::{HttpsClientStream, HttpsClientStreamBuilder}, - h3::H3ClientStreamBuilder, + h3::{H3ClientStream, H3ClientStreamBuilder}, runtime::TokioRuntimeProvider, tcp::TcpClientStream, tls::tls_client_connect, @@ -423,9 +423,9 @@ mod tests { } }; - let answers = response.answers(); + let answers = &response.answers; - if let RData::A(ip) = answers[0].data() { + if let RData::A(ip) = &answers[0].data { assert_eq!(ip.0, std::net::Ipv4Addr::new(93, 184, 215, 14)) } else { unreachable!("unexpected result") @@ -597,7 +597,7 @@ mod tests { .dangerous() .set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new())); - let stream = H3ClientStreamBuilder::builder() + let stream = H3ClientStream::builder() .crypto_config(tls_config) .build(doh3_addr, "dns.example.com".into(), "/dns-query".into()) .await?; diff --git a/clash-lib/src/app/dns/resolver/enhanced.rs b/clash-lib/src/app/dns/resolver/enhanced.rs index ffcfc4e43..8981ad515 100644 --- a/clash-lib/src/app/dns/resolver/enhanced.rs +++ b/clash-lib/src/app/dns/resolver/enhanced.rs @@ -736,7 +736,7 @@ impl ClashResolver for EnhancedResolver { #[cfg(test)] mod tests { - use hickory_net::{client, udp::UdpClientStream}; + use hickory_net::{DnsHandle, client, udp::UdpClientStream, xfer::FirstAnswer}; use hickory_proto::{ op::{self, DnsRequest, DnsRequestOptions}, rr,