Skip to content

Commit 44d299f

Browse files
authored
feat:(dns): dns over h3 server (#611)
* dns server support user provided cert * clippy * read me * doh3 server --------- Signed-off-by: Yuwei Ba <dev@watfaq.com>
1 parent 2894f7c commit 44d299f

File tree

5 files changed

+135
-6
lines changed

5 files changed

+135
-6
lines changed

Cargo.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clash/tests/data/config/rules.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,19 @@ dns:
2323
tcp: 127.0.0.1:53553
2424
dot:
2525
addr: 127.0.0.1:53554
26-
hostname: dns.example.com
2726
ca-cert: dns.crt
2827
ca-key: dns.key
2928
doh:
3029
addr: 127.0.0.1:53555
3130
ca-cert: dns.crt
3231
ca-key: dns.key
33-
32+
hostname: dns.example.com
33+
doh3:
34+
addr: 127.0.0.1:53555
35+
ca-cert: dns.crt
36+
ca-key: dns.key
37+
hostname: dns.example.com
38+
3439
# ipv6: false # when the false, response to AAAA questions will be empty
3540

3641
# These nameservers are used to resolve the DNS nameserver hostnames below.

clash_lib/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ erased-serde = "0.4"
100100
# DNS
101101
hickory-client = "0.25.0-alpha.2"
102102
hickory-resolver = "0.25.0-alpha.2"
103-
hickory-server = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls"] }
104-
hickory-proto = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls"]}
103+
hickory-server = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls", "dns-over-h3"] }
104+
hickory-proto = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls", "dns-over-h3"]}
105105

106106
dhcproto = "0.12"
107107
ring-compat = { version = "0.8", features = ["aead"] }

clash_lib/src/app/dns/config.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ pub struct DoHConfig {
5454
pub hostname: Option<String>,
5555
}
5656

57+
#[derive(Debug, Deserialize)]
58+
#[serde(rename_all = "kebab-case")]
59+
pub struct DoH3Config {
60+
pub addr: SocketAddr,
61+
pub ca_cert: DnsServerCert,
62+
pub ca_key: DnsServerKey,
63+
pub hostname: Option<String>,
64+
}
65+
5766
#[derive(Debug, Deserialize)]
5867
#[serde(rename_all = "kebab-case")]
5968
pub struct DoTConfig {
@@ -71,6 +80,7 @@ pub struct DNSListenAddr {
7180
pub tcp: Option<SocketAddr>,
7281
pub doh: Option<DoHConfig>,
7382
pub dot: Option<DoTConfig>,
83+
pub doh3: Option<DoH3Config>,
7484
}
7585

7686
#[derive(Default)]
@@ -280,6 +290,7 @@ impl TryFrom<&crate::config::def::Config> for Config {
280290
let mut udp = None;
281291
let mut tcp = None;
282292
let mut doh = None;
293+
let mut doh3 = None;
283294
let mut dot = None;
284295

285296
for (k, v) in map {
@@ -341,6 +352,18 @@ impl TryFrom<&crate::config::def::Config> for Config {
341352
})?;
342353
dot = Some(c)
343354
}
355+
"doh3" => {
356+
let c =
357+
DoH3Config::deserialize(v).map_err(|x| {
358+
Error::InvalidConfig(format!(
359+
"invalid doh3 dns listen config: \
360+
{:?}",
361+
x
362+
))
363+
})?;
364+
365+
doh3 = Some(c)
366+
}
344367
_ => {
345368
return Err(Error::InvalidConfig(format!(
346369
"invalid dns listen address: {}",
@@ -350,7 +373,13 @@ impl TryFrom<&crate::config::def::Config> for Config {
350373
}
351374
}
352375

353-
Ok(DNSListenAddr { udp, tcp, doh, dot })
376+
Ok(DNSListenAddr {
377+
udp,
378+
tcp,
379+
doh,
380+
dot,
381+
doh3,
382+
})
354383
}
355384
})
356385
.transpose()?

clash_lib/src/app/dns/server/mod.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,37 @@ pub async fn get_dns_listener(
294294
.ok()?;
295295
}
296296

297+
if let Some(c) = listen.doh3 {
298+
has_server = true;
299+
UdpSocket::bind(c.addr)
300+
.await
301+
.and_then(|x| {
302+
info!("DoT3 dns server listening on: {}", c.addr);
303+
if let (Some(k), Some(c)) = (&c.ca_key, &c.ca_cert) {
304+
debug!("using custom key and cert for dot: {}/{}", k, c);
305+
}
306+
307+
let server_key = c
308+
.ca_key
309+
.map(|x| utils::load_priv_key(&cwd.join(x)))
310+
.transpose()?
311+
.unwrap_or(load_default_key());
312+
let server_cert = c
313+
.ca_cert
314+
.map(|x| utils::load_cert_chain(&cwd.join(x)))
315+
.transpose()?
316+
.unwrap_or(load_default_cert());
317+
s.register_h3_listener(
318+
x,
319+
DEFAULT_DNS_SERVER_TIMEOUT,
320+
(server_cert, server_key),
321+
c.hostname,
322+
)?;
323+
Ok(())
324+
})
325+
.ok()?;
326+
}
327+
297328
if !has_server {
298329
return None;
299330
}
@@ -318,6 +349,7 @@ mod tests {
318349
};
319350
use hickory_proto::{
320351
h2::HttpsClientStreamBuilder,
352+
h3::H3ClientStreamBuilder,
321353
rr::{rdata::A, DNSClass, Name, RData, RecordType},
322354
rustls::tls_client_connect,
323355
tcp::TcpClientStream,
@@ -395,6 +427,12 @@ mod tests {
395427
ca_key: None,
396428
ca_cert: None,
397429
}),
430+
doh3: Some(crate::app::dns::config::DoH3Config {
431+
addr: "127.0.0.1:53556".parse().unwrap(),
432+
hostname: Some("dns.example.com".to_string()),
433+
ca_key: None,
434+
ca_cert: None,
435+
}),
398436
};
399437
let listener = super::get_dns_listener(
400438
cfg,
@@ -477,6 +515,27 @@ mod tests {
477515

478516
send_query(&mut client).await;
479517

480-
tokio::time::sleep(Duration::from_secs(1)).await;
518+
let mut tls_config = ClientConfig::builder()
519+
.with_root_certificates(GLOBAL_ROOT_STORE.clone())
520+
.with_no_client_auth();
521+
tls_config.alpn_protocols = vec!["h3".into()];
522+
523+
tls_config
524+
.dangerous()
525+
.set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new()));
526+
527+
let stream = H3ClientStreamBuilder::default()
528+
.crypto_config(tls_config)
529+
.clone()
530+
.build(
531+
"127.0.0.1:53556".parse().unwrap(),
532+
"dns.example.com".to_owned(),
533+
);
534+
535+
let (mut client, handle) =
536+
client::AsyncClient::connect(stream).await.unwrap();
537+
tokio::spawn(handle);
538+
539+
send_query(&mut client).await;
481540
}
482541
}

0 commit comments

Comments
 (0)