Skip to content

Commit

Permalink
feat:(dns): dns over h3 server (#611)
Browse files Browse the repository at this point in the history
* dns server support user provided cert

* clippy

* read me

* doh3 server

---------

Signed-off-by: Yuwei Ba <dev@watfaq.com>
  • Loading branch information
ibigbug authored Sep 29, 2024
1 parent 2894f7c commit 44d299f
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 6 deletions.
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions clash/tests/data/config/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ dns:
tcp: 127.0.0.1:53553
dot:
addr: 127.0.0.1:53554
hostname: dns.example.com
ca-cert: dns.crt
ca-key: dns.key
doh:
addr: 127.0.0.1:53555
ca-cert: dns.crt
ca-key: dns.key

hostname: dns.example.com
doh3:
addr: 127.0.0.1:53555
ca-cert: dns.crt
ca-key: dns.key
hostname: dns.example.com

# ipv6: false # when the false, response to AAAA questions will be empty

# These nameservers are used to resolve the DNS nameserver hostnames below.
Expand Down
4 changes: 2 additions & 2 deletions clash_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ erased-serde = "0.4"
# DNS
hickory-client = "0.25.0-alpha.2"
hickory-resolver = "0.25.0-alpha.2"
hickory-server = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls"] }
hickory-proto = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls"]}
hickory-server = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls", "dns-over-h3"] }
hickory-proto = { version = "0.25.0-alpha.2", features = ["dns-over-rustls", "dns-over-https-rustls", "dns-over-h3"]}

dhcproto = "0.12"
ring-compat = { version = "0.8", features = ["aead"] }
Expand Down
31 changes: 30 additions & 1 deletion clash_lib/src/app/dns/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ pub struct DoHConfig {
pub hostname: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DoH3Config {
pub addr: SocketAddr,
pub ca_cert: DnsServerCert,
pub ca_key: DnsServerKey,
pub hostname: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DoTConfig {
Expand All @@ -71,6 +80,7 @@ pub struct DNSListenAddr {
pub tcp: Option<SocketAddr>,
pub doh: Option<DoHConfig>,
pub dot: Option<DoTConfig>,
pub doh3: Option<DoH3Config>,
}

#[derive(Default)]
Expand Down Expand Up @@ -280,6 +290,7 @@ impl TryFrom<&crate::config::def::Config> for Config {
let mut udp = None;
let mut tcp = None;
let mut doh = None;
let mut doh3 = None;
let mut dot = None;

for (k, v) in map {
Expand Down Expand Up @@ -341,6 +352,18 @@ impl TryFrom<&crate::config::def::Config> for Config {
})?;
dot = Some(c)
}
"doh3" => {
let c =
DoH3Config::deserialize(v).map_err(|x| {
Error::InvalidConfig(format!(
"invalid doh3 dns listen config: \
{:?}",
x
))
})?;

doh3 = Some(c)
}
_ => {
return Err(Error::InvalidConfig(format!(
"invalid dns listen address: {}",
Expand All @@ -350,7 +373,13 @@ impl TryFrom<&crate::config::def::Config> for Config {
}
}

Ok(DNSListenAddr { udp, tcp, doh, dot })
Ok(DNSListenAddr {
udp,
tcp,
doh,
dot,
doh3,
})
}
})
.transpose()?
Expand Down
61 changes: 60 additions & 1 deletion clash_lib/src/app/dns/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,37 @@ pub async fn get_dns_listener(
.ok()?;
}

if let Some(c) = listen.doh3 {
has_server = true;
UdpSocket::bind(c.addr)
.await
.and_then(|x| {
info!("DoT3 dns server listening on: {}", c.addr);
if let (Some(k), Some(c)) = (&c.ca_key, &c.ca_cert) {
debug!("using custom key and cert for dot: {}/{}", k, c);
}

let server_key = c
.ca_key
.map(|x| utils::load_priv_key(&cwd.join(x)))
.transpose()?
.unwrap_or(load_default_key());
let server_cert = c
.ca_cert
.map(|x| utils::load_cert_chain(&cwd.join(x)))
.transpose()?
.unwrap_or(load_default_cert());
s.register_h3_listener(
x,
DEFAULT_DNS_SERVER_TIMEOUT,
(server_cert, server_key),
c.hostname,
)?;
Ok(())
})
.ok()?;
}

if !has_server {
return None;
}
Expand All @@ -318,6 +349,7 @@ mod tests {
};
use hickory_proto::{
h2::HttpsClientStreamBuilder,
h3::H3ClientStreamBuilder,
rr::{rdata::A, DNSClass, Name, RData, RecordType},
rustls::tls_client_connect,
tcp::TcpClientStream,
Expand Down Expand Up @@ -395,6 +427,12 @@ mod tests {
ca_key: None,
ca_cert: None,
}),
doh3: Some(crate::app::dns::config::DoH3Config {
addr: "127.0.0.1:53556".parse().unwrap(),
hostname: Some("dns.example.com".to_string()),
ca_key: None,
ca_cert: None,
}),
};
let listener = super::get_dns_listener(
cfg,
Expand Down Expand Up @@ -477,6 +515,27 @@ mod tests {

send_query(&mut client).await;

tokio::time::sleep(Duration::from_secs(1)).await;
let mut tls_config = ClientConfig::builder()
.with_root_certificates(GLOBAL_ROOT_STORE.clone())
.with_no_client_auth();
tls_config.alpn_protocols = vec!["h3".into()];

tls_config
.dangerous()
.set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new()));

let stream = H3ClientStreamBuilder::default()
.crypto_config(tls_config)
.clone()
.build(
"127.0.0.1:53556".parse().unwrap(),
"dns.example.com".to_owned(),
);

let (mut client, handle) =
client::AsyncClient::connect(stream).await.unwrap();
tokio::spawn(handle);

send_query(&mut client).await;
}
}

0 comments on commit 44d299f

Please sign in to comment.