From e4cfe276b55ddbf414ab2ef6565791f0933971b5 Mon Sep 17 00:00:00 2001 From: dev0 Date: Sun, 29 Sep 2024 04:08:42 +1000 Subject: [PATCH] dns server support user provided cert --- .../tests/data/config/dns.cert | 0 .../tests/data/config/dns.key | 0 clash/tests/data/config/rules.yaml | 19 +- clash_lib/src/app/dns/config.rs | 118 +++++---- clash_lib/src/app/dns/dns_client.rs | 2 +- clash_lib/src/app/dns/helper.rs | 2 +- clash_lib/src/app/dns/mod.rs | 1 - .../src/app/dns/{ => server}/dummy_keys.rs | 1 - clash_lib/src/app/dns/server/mod.rs | 238 ++++++++++++++++-- clash_lib/src/app/dns/server/test/test.cert | 19 ++ clash_lib/src/app/dns/server/test/test.key | 28 +++ clash_lib/src/app/dns/server/utils.rs | 39 +++ clash_lib/src/common/utils.rs | 31 --- clash_lib/src/config/def.rs | 17 +- clash_lib/src/lib.rs | 4 +- 15 files changed, 410 insertions(+), 109 deletions(-) rename clash_lib/src/app/dns/test/test.cert => clash/tests/data/config/dns.cert (100%) rename clash_lib/src/app/dns/test/test.key => clash/tests/data/config/dns.key (100%) rename clash_lib/src/app/dns/{ => server}/dummy_keys.rs (80%) create mode 100644 clash_lib/src/app/dns/server/test/test.cert create mode 100644 clash_lib/src/app/dns/server/test/test.key create mode 100644 clash_lib/src/app/dns/server/utils.rs diff --git a/clash_lib/src/app/dns/test/test.cert b/clash/tests/data/config/dns.cert similarity index 100% rename from clash_lib/src/app/dns/test/test.cert rename to clash/tests/data/config/dns.cert diff --git a/clash_lib/src/app/dns/test/test.key b/clash/tests/data/config/dns.key similarity index 100% rename from clash_lib/src/app/dns/test/test.key rename to clash/tests/data/config/dns.key diff --git a/clash/tests/data/config/rules.yaml b/clash/tests/data/config/rules.yaml index 874b2139f..ebd0dafe9 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" route-all: false gateway: "198.19.0.1/32" @@ -18,11 +18,18 @@ ipv6: true dns: enable: true ipv6: true - listen: 127.0.0.1:53553 - # udp: 127.0.0.1:53553 - # tcp: 127.0.0.1:53553 - # dot: 127.0.0.1:53554 - # doh: 127.0.0.1:53555 + listen: + udp: 127.0.0.1:53553 + 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 # ipv6: false # when the false, response to AAAA questions will be empty diff --git a/clash_lib/src/app/dns/config.rs b/clash_lib/src/app/dns/config.rs index 7e7a53827..ef1785cf8 100644 --- a/clash_lib/src/app/dns/config.rs +++ b/clash_lib/src/app/dns/config.rs @@ -7,19 +7,17 @@ use std::{ use ipnet::AddrParseError; use regex::Regex; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; + +use serde::Deserialize; use url::Url; use crate::{ - common::{trie, utils}, + common::trie, config::def::{DNSListen, DNSMode}, Error, }; -use super::{ - dns_client::DNSNetMode, - dummy_keys::{TEST_CERT, TEST_KEY}, -}; +use super::dns_client::DNSNetMode; #[derive(Clone, Debug)] pub struct NameServer { @@ -47,23 +45,32 @@ pub struct FallbackFilter { pub domain: Vec, } -#[derive(Debug)] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] pub struct DoHConfig { - pub certificate_and_key: (Vec>, PrivateKeyDer<'static>), - pub dns_hostname: Option, + pub addr: SocketAddr, + pub ca_cert: DnsServerCert, + pub ca_key: DnsServerKey, + pub hostname: Option, } -#[derive(Debug)] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] pub struct DoTConfig { - pub certificate_and_key: (Vec>, PrivateKeyDer<'static>), + pub addr: SocketAddr, + pub ca_cert: DnsServerCert, + pub ca_key: DnsServerKey, } +pub type DnsServerKey = Option; +pub type DnsServerCert = Option; + #[derive(Debug, Default)] pub struct DNSListenAddr { pub udp: Option, pub tcp: Option, - pub doh: Option<(SocketAddr, DoHConfig)>, - pub dot: Option<(SocketAddr, DoTConfig)>, + pub doh: Option, + pub dot: Option, } #[derive(Default)] @@ -270,48 +277,69 @@ impl TryFrom<&crate::config::def::Config> for Config { }) } DNSListen::Multiple(map) => { - use std::path::Path; let mut udp = None; let mut tcp = None; let mut doh = None; let mut dot = None; for (k, v) in map { - let addr = v.parse::().map_err(|_| { - Error::InvalidConfig(format!( - "invalid DNS listen address: {} -> {}", - k, v - )) - })?; match k.as_str() { - "udp" => udp = Some(addr), - "tcp" => tcp = Some(addr), + "udp" => { + let addr = v + .as_str() + .ok_or(Error::InvalidConfig(format!( + "invalid udp dns listen address - must \ + be string: {:?}", + v + )))? + .parse::() + .map_err(|_| { + Error::InvalidConfig(format!( + "invalid dns listen address: {:?}", + v + )) + })?; + udp = Some(addr) + } + "tcp" => { + let addr = v + .as_str() + .ok_or(Error::InvalidConfig(format!( + "invalid tcp dns listen address - must \ + be string: {:?}", + v + )))? + .parse::() + .map_err(|_| { + Error::InvalidConfig(format!( + "invalid dns listen address: {:?}", + v + )) + })?; + tcp = Some(addr) + } "doh" => { - let certs = - utils::load_cert_chain(Path::new(TEST_CERT)) - .unwrap(); - let priv_key = - utils::load_priv_key(Path::new(TEST_KEY)) - .unwrap(); - let c = DoHConfig { - certificate_and_key: (certs, priv_key), - dns_hostname: Some( - "dns.example.com".to_owned(), - ), - }; - doh = Some((addr, c)) + let c = + DoHConfig::deserialize(v).map_err(|x| { + Error::InvalidConfig(format!( + "invalid doh dns listen config: \ + {:?}", + x + )) + })?; + + doh = Some(c) } "dot" => { - let certs = - utils::load_cert_chain(Path::new(TEST_CERT)) - .unwrap(); - let priv_key = - utils::load_priv_key(Path::new(TEST_KEY)) - .unwrap(); - let c = DoTConfig { - certificate_and_key: (certs, priv_key), - }; - dot = Some((addr, c)) + let c = + DoTConfig::deserialize(v).map_err(|x| { + Error::InvalidConfig(format!( + "invalid dot dns listen config: \ + {:?}", + x + )) + })?; + dot = Some(c) } _ => { return Err(Error::InvalidConfig(format!( diff --git a/clash_lib/src/app/dns/dns_client.rs b/clash_lib/src/app/dns/dns_client.rs index 3fbd300aa..f4864307e 100644 --- a/clash_lib/src/app/dns/dns_client.rs +++ b/clash_lib/src/app/dns/dns_client.rs @@ -372,7 +372,7 @@ async fn dns_stream_builder( let mut tls_config = ClientConfig::builder() .with_root_certificates(GLOBAL_ROOT_STORE.clone()) .with_no_client_auth(); - tls_config.alpn_protocols = vec!["dot".into()]; + tls_config.alpn_protocols = vec!["dot".into(), "h2".into()]; let fut = new_tcp_stream( *addr, diff --git a/clash_lib/src/app/dns/helper.rs b/clash_lib/src/app/dns/helper.rs index 098fc404e..8b6db7326 100644 --- a/clash_lib/src/app/dns/helper.rs +++ b/clash_lib/src/app/dns/helper.rs @@ -17,7 +17,7 @@ pub async fn make_clients( let mut rv = Vec::new(); for s in servers { - debug!("building nameserver: {:?}", s); + debug!("building nameserver: {}", s); let (host, port) = if s.net == DNSNetMode::Dhcp { (s.address.as_str(), "0") diff --git a/clash_lib/src/app/dns/mod.rs b/clash_lib/src/app/dns/mod.rs index ff1309046..5b62a8e99 100644 --- a/clash_lib/src/app/dns/mod.rs +++ b/clash_lib/src/app/dns/mod.rs @@ -11,7 +11,6 @@ use mockall::automock; mod config; mod dhcp; mod dns_client; -mod dummy_keys; mod fakeip; mod filters; mod helper; diff --git a/clash_lib/src/app/dns/dummy_keys.rs b/clash_lib/src/app/dns/server/dummy_keys.rs similarity index 80% rename from clash_lib/src/app/dns/dummy_keys.rs rename to clash_lib/src/app/dns/server/dummy_keys.rs index 863706429..80fdf85ce 100644 --- a/clash_lib/src/app/dns/dummy_keys.rs +++ b/clash_lib/src/app/dns/server/dummy_keys.rs @@ -1,6 +1,5 @@ //! 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"); diff --git a/clash_lib/src/app/dns/server/mod.rs b/clash_lib/src/app/dns/server/mod.rs index 3075b4ccf..991423106 100644 --- a/clash_lib/src/app/dns/server/mod.rs +++ b/clash_lib/src/app/dns/server/mod.rs @@ -1,3 +1,6 @@ +mod dummy_keys; +mod utils; + use std::{net::IpAddr, time::Duration}; use async_trait::async_trait; @@ -16,11 +19,12 @@ use hickory_server::{ }; use thiserror::Error; use tokio::net::{TcpListener, UdpSocket}; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; +use utils::{load_default_cert, load_default_key}; use crate::Runner; -use super::{Config, ThreadSafeDNSResolver}; +use super::{config::DNSListenAddr, ThreadSafeDNSResolver}; static DEFAULT_DNS_SERVER_TTL: u32 = 60; @@ -201,60 +205,89 @@ impl RequestHandler for DnsHandler { static DEFAULT_DNS_SERVER_TIMEOUT: Duration = Duration::from_secs(5); pub async fn get_dns_listener( - cfg: Config, + listen: DNSListenAddr, resolver: ThreadSafeDNSResolver, + cwd: &std::path::Path, ) -> Option { let h = DnsHandler { resolver }; let mut s = ServerFuture::new(h); let mut has_server = false; - if let Some(addr) = cfg.listen.udp { + if let Some(addr) = listen.udp { has_server = true; UdpSocket::bind(addr) .await .map(|x| { - info!("dns server listening on udp: {}", addr); + info!("UDP dns server listening on: {}", addr); s.register_socket(x); }) .ok()?; } - if let Some(addr) = cfg.listen.tcp { + if let Some(addr) = listen.tcp { has_server = true; TcpListener::bind(addr) .await .map(|x| { - info!("dns server listening on tcp: {}", addr); + info!("TCP dns server listening on: {}", addr); s.register_listener(x, DEFAULT_DNS_SERVER_TIMEOUT); }) .ok()?; } - if let Some(c) = cfg.listen.doh { + if let Some(c) = listen.doh { has_server = true; - TcpListener::bind(c.0) + TcpListener::bind(c.addr) .await .and_then(|x| { - info!("dns server listening on doh: {}", c.0); + info!("DoH server listening on: {}", c.addr); + if let (Some(k), Some(c)) = (&c.ca_key, &c.ca_cert) { + debug!("using custom key and cert for doh: {}/{}", 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_https_listener( x, DEFAULT_DNS_SERVER_TIMEOUT, - c.1.certificate_and_key, - c.1.dns_hostname, + (server_cert, server_key), + c.hostname, )?; Ok(()) }) .ok()?; } - if let Some(c) = cfg.listen.dot { + if let Some(c) = listen.dot { has_server = true; - TcpListener::bind(c.0) + TcpListener::bind(c.addr) .await .and_then(|x| { - info!("dns server listening on dot: {}", c.0); + info!("DoT 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_tls_listener( x, DEFAULT_DNS_SERVER_TIMEOUT, - c.1.certificate_and_key, + (server_cert, server_key), )?; Ok(()) }) @@ -274,3 +307,176 @@ pub async fn get_dns_listener( }) })) } + +#[cfg(test)] +mod tests { + use std::{sync::Arc, time::Duration}; + + use hickory_client::{ + client::{self, AsyncClient, ClientHandle}, + proto::iocompat::AsyncIoTokioAsStd, + }; + use hickory_proto::{ + h2::HttpsClientStreamBuilder, + rr::{rdata::A, DNSClass, Name, RData, RecordType}, + rustls::tls_client_connect, + tcp::TcpClientStream, + udp::UdpClientStream, + }; + use rustls::ClientConfig; + use tokio::net::{TcpStream as TokioTcpStream, UdpSocket as TokioUdpSocket}; + + use crate::{ + app::dns::MockClashResolver, + common::tls::{self, GLOBAL_ROOT_STORE}, + tests::initialize, + }; + + async fn send_query(client: &mut AsyncClient) { + // Specify the name, note the final '.' which specifies it's an FQDN + let name = Name::from_ascii("www.example.com.").unwrap(); + + // NOTE: see 'Setup a connection' example above + // Send the query and get a message response, see RecordType for all + // supported options + let response = client + .query(name, DNSClass::IN, RecordType::A) + .await + .unwrap(); + + // Messages are the packets sent between client and server in DNS. + // there are many fields to a Message, DnsResponse can be dereferenced into + // a Message. It's beyond the scope of these examples + // to explain all the details of a Message. See + // hickory_client::op::message::Message for more details. generally + // we will be interested in the Message::answers + let answers = response.answers(); + + // Records are generic objects which can contain any data. + // In order to access it we need to first check what type of record it is + // In this case we are interested in A, IPv4 address + if let RData::A(ref ip) = answers[0].data() { + assert_eq!(*ip, A::new(93, 184, 215, 14)) + } else { + assert!(false, "unexpected result") + } + } + + #[tokio::test] + async fn test_multiple_dns_server() { + initialize(); + + let mut resolver = MockClashResolver::new(); + resolver.expect_fake_ip_enabled().returning(|| false); + resolver.expect_exchange().returning(|_| { + let mut m = hickory_proto::op::Message::new(); + m.set_response_code(hickory_proto::op::ResponseCode::NoError); + m.add_answer(hickory_proto::rr::Record::from_rdata( + "www.example.com".parse().unwrap(), + 60, + hickory_proto::rr::RData::A(hickory_proto::rr::rdata::A( + std::net::Ipv4Addr::new(93, 184, 215, 14), + )), + )); + Ok(m) + }); + + let cfg = crate::app::dns::config::DNSListenAddr { + udp: Some("127.0.0.1:53553".parse().unwrap()), + tcp: Some("127.0.0.1:53554".parse().unwrap()), + dot: Some(crate::app::dns::config::DoTConfig { + addr: "127.0.0.1:53555".parse().unwrap(), + ca_key: None, + ca_cert: None, + }), + doh: Some(crate::app::dns::config::DoHConfig { + 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, + Arc::new(resolver), + &std::path::Path::new("."), + ) + .await; + + assert!(listener.is_some()); + tokio::spawn(async move { + listener.unwrap().await.unwrap(); + }); + + let stream = UdpClientStream::::new( + "127.0.0.1:53553".parse().unwrap(), + ); + let (mut client, handle) = + client::AsyncClient::connect(stream).await.unwrap(); + tokio::spawn(handle); + + send_query(&mut client).await; + + let (stream, sender) = + TcpClientStream::>::new( + "127.0.0.1:53554".parse().unwrap(), + ); + + let (mut client, handle) = client::AsyncClient::new(stream, sender, None) + .await + .unwrap(); + tokio::spawn(handle); + + send_query(&mut client).await; + + let mut tls_config = ClientConfig::builder() + .with_root_certificates(GLOBAL_ROOT_STORE.clone()) + .with_no_client_auth(); + tls_config.alpn_protocols = vec!["h2".into()]; + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new())); + + let (stream, sender) = tls_client_connect::>( + "127.0.0.1:53555".parse().unwrap(), + "dns.example.com".to_owned(), + Arc::new(tls_config), + ); + + let (mut client, handle) = client::AsyncClient::with_timeout( + stream, + sender, + Duration::from_secs(5), + None, + ) + .await + .unwrap(); + tokio::spawn(handle); + + send_query(&mut client).await; + + let mut tls_config = ClientConfig::builder() + .with_root_certificates(GLOBAL_ROOT_STORE.clone()) + .with_no_client_auth(); + tls_config.alpn_protocols = vec!["h2".into()]; + + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(tls::DummyTlsVerifier::new())); + + let stream = + HttpsClientStreamBuilder::with_client_config(Arc::new(tls_config)) + .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; + + tokio::time::sleep(Duration::from_secs(1)).await; + } +} diff --git a/clash_lib/src/app/dns/server/test/test.cert b/clash_lib/src/app/dns/server/test/test.cert new file mode 100644 index 000000000..4d9a87294 --- /dev/null +++ b/clash_lib/src/app/dns/server/test/test.cert @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDITCCAgmgAwIBAgIQOp++4lLpVGLWkaGuRrrP2zANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTIzMDkwMjA4MzIyM1oXDTI0MDkwMTA4MzIy +M1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAPS+pfYexKHq50myjnY6kAZ1uLDmgR/Vy9y7rPcrkqci25ClCnuUiUgY +ABhrdq16oW2wOymc9A56YH9X99UNX6E+VmckCP8v9xcPV4/Zoc+KIeuJHRUBHFMK +ieiWe560XcRCTP7OudUZ+iSHbjhaxZJCEfFaE8aSZXmiLKUETZ7z2lAm75fRCBaR +Vy+jewy04F1BKg4VlnrZnVHzQCcBHZEpsAz4YpbNHMYf9sYRrh+T2j9Zya5tUklI +LzEobWXUzjhrkcBXSVC5dLa9sxz7eSeg0dY0SkP7O5pelr8LaXnBudAk5B0ByqFD +s83Z1AZMkv7vL2L8AGA/X1PP2UV3BrECAwEAAaNzMHEwDgYDVR0PAQH/BAQDAgKk +MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FIiHKw+Z9rUjV6pbqcDl+teSTLxoMBoGA1UdEQQTMBGCD2Rucy5leGFtcGxlLmNv +bTANBgkqhkiG9w0BAQsFAAOCAQEACTYIij1QqPhke3aI6HVLuXy75YHjxic0cIcX +7u41WSnZWOrnsBYAFPfjwH7sW6dX+5twUludSCSQ9Xyhl/tdQ0AKWJJNZ7irArS9 +kz2rU4u7YjtR4tuuRB2t+8UEcGA/m0EPhQfFbZedAy/Y2oc+RodwlqibVB/WCMOQ +BL4HS1wFaYZw9WhXk3nzb+wjBhvyEQkI9oeMqVYZLN/9kIY9QuGtDg5onFrVSgjZ +qiR7EfdICe8ogM6IiemQJfZ5SeWkoLpuRlaeVhqFFaFFeJ6cMTJ+Jluh6a3DGP91 +aRaPVO7r8gPq4mACua0HQcBfmH4VKS3hsQHdWDivRUT7xkZ/6g== +-----END CERTIFICATE----- diff --git a/clash_lib/src/app/dns/server/test/test.key b/clash_lib/src/app/dns/server/test/test.key new file mode 100644 index 000000000..eb2e6342e --- /dev/null +++ b/clash_lib/src/app/dns/server/test/test.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0vqX2HsSh6udJ +so52OpAGdbiw5oEf1cvcu6z3K5KnItuQpQp7lIlIGAAYa3ateqFtsDspnPQOemB/ +V/fVDV+hPlZnJAj/L/cXD1eP2aHPiiHriR0VARxTConolnuetF3EQkz+zrnVGfok +h244WsWSQhHxWhPGkmV5oiylBE2e89pQJu+X0QgWkVcvo3sMtOBdQSoOFZZ62Z1R +80AnAR2RKbAM+GKWzRzGH/bGEa4fk9o/WcmubVJJSC8xKG1l1M44a5HAV0lQuXS2 +vbMc+3knoNHWNEpD+zuaXpa/C2l5wbnQJOQdAcqhQ7PN2dQGTJL+7y9i/ABgP19T +z9lFdwaxAgMBAAECggEAaMFNYcoLmc5kjsvJZFtumAU9NyKCNDEbX/BIeUcCL12h +IwkxMnICTIRRTiJ5Gom5nKxotkgCwkupD/iEEIH345k9/EmVPDy4gvtDHEQnmSBj +ol/+vaXLDNQe8Rmv8d77n2xNbmbnbYn/4jDBgYeAtzhmW6qVelHg8y3x8/OikZyy +EB+ORiH/p4XN+3xBuEnFwBjQSHlayYVL8E68rp2wcKIeB2wo27Noh+VNqc2jks/l +h9V+cydCOHY2OQ0iepZwPmsVVjEObtckksBTFWgpc5ZH6Ctq1YjtTpcOey9FRzhZ +fuK9cj7bO5uW1duocgMPGsgiYMdvGyOCmM4tgp4lwQKBgQD+SXkzutxu0577g3Td +8BWYvksM6O3Lq8ZHKQtcxcNpp9S/fQ134IMErlykS1FJ5290KkHwQ4tojiOhtYQo +GOlmbS7USR2puhNzEWxmhi0CIyyWyRDECrVZVnLfcVxZrxpoh4LjZFA6nVRhZipN +oMig1zDhVmhvUTrTxVygFp9/aQKBgQD2ZLgGTK0kZ4npsUEL/ZantlZwEpY8S3TK +muLlcGx3EFwPrX558xhYSwkh/afNTcRekJkoBA9SXaioZNKlOD/XyIFq0OeurygB +gC8RdBs3xAjNHyj9qxajhuBqxxxTa/HmUnWqH2zDv20DcyvLTXjpRuFBxBj77ka9 +eVyGBxmsCQKBgQCC2tJpIWagDXyJl2tDbnHeqUY7vX3pSlr9cYysUASwUTJ02+hb +YQhrF0MLNMr/Cf7bu4c1Gb0ar9J8O8lnTPKGx/bKPVnrZprtovCyjaeJqwoeChf7 +mjsaXxc8DrzkVex0EA/17kAu+ZlbidSJIA0+X56CxxF0/0sTgUOaCipHyQKBgQDl +Bie7y0fhH9CkhRtWPufrimP8FnrJHsY3kRK4e/CGF5HLDNQUHK8TWuPpUXLJNbEC +yVtjQ6rOP7qGk/jslEVbmMca94Vy7OK9yl111rt58WDQ8VbTu1T2uWceOWeN7zdR +hHJUqJMbvHJjE4mwlpl+FGFLFTC38/qTIhyrhCwLqQKBgQDz67mt3XyD5HUT/GYn +A91ZGjWfh3YPXYkjOOjw1MWqeO7LQi6A8uWkfRFPWn/h3gECqItrWFogJixRyL+G +TWQ7SWmJctHD8YjU3E6tlPSWQhD2U75pZWtjpGAIYSdvS5V9Q+95jaKKhsYneThB +SNEnosgWOR5eg9c0FznaDn6nRw== +-----END PRIVATE KEY----- diff --git a/clash_lib/src/app/dns/server/utils.rs b/clash_lib/src/app/dns/server/utils.rs new file mode 100644 index 000000000..93bbf0cd1 --- /dev/null +++ b/clash_lib/src/app/dns/server/utils.rs @@ -0,0 +1,39 @@ +use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; +use std::{fs, path::Path}; + +use crate::common::errors::new_io_error; + +use super::dummy_keys::{TEST_CERT, TEST_KEY}; + +pub fn load_cert_chain( + cert_path: &Path, +) -> std::io::Result>> { + let cert_chain = fs::read(cert_path)?; + if cert_path.extension().map_or(false, |x| x == "der") { + Ok(vec![CertificateDer::from(cert_chain)]) + } else { + rustls_pemfile::certs(&mut &*cert_chain).collect::>() + } +} + +pub fn load_priv_key(key_path: &Path) -> std::io::Result> { + let key = fs::read(key_path)?; + if key_path.extension().map_or(false, |x| x == "der") { + Ok(PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key))) + } else { + rustls_pemfile::private_key(&mut &*key)? + .ok_or(new_io_error("no private key found")) + } +} + +pub fn load_default_cert() -> Vec> { + rustls_pemfile::certs(&mut TEST_CERT.as_bytes()) + .collect::>() + .unwrap() +} + +pub fn load_default_key() -> PrivateKeyDer<'static> { + rustls_pemfile::private_key(&mut TEST_KEY.as_bytes()) + .unwrap() + .unwrap() +} diff --git a/clash_lib/src/common/utils.rs b/clash_lib/src/common/utils.rs index f8bf9d961..f121a1917 100644 --- a/clash_lib/src/common/utils.rs +++ b/clash_lib/src/common/utils.rs @@ -111,35 +111,4 @@ where Ok(()) } -use anyhow::Context; -use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; -use std::fs; - use super::http::HttpClient; - -pub fn load_cert_chain( - cert_path: &Path, -) -> anyhow::Result>> { - let cert_chain = - fs::read(cert_path).context("failed to read certificate chain")?; - let cert_chain = if cert_path.extension().map_or(false, |x| x == "der") { - vec![CertificateDer::from(cert_chain)] - } else { - rustls_pemfile::certs(&mut &*cert_chain) - .collect::>() - .context("invalid PEM-encoded certificate")? - }; - Ok(cert_chain) -} - -pub fn load_priv_key(key_path: &Path) -> anyhow::Result> { - let key = fs::read(key_path).context("failed to read private key")?; - let key = if key_path.extension().map_or(false, |x| x == "der") { - PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key)) - } else { - rustls_pemfile::private_key(&mut &*key) - .context("malformed PKCS #1 private key")? - .ok_or_else(|| anyhow::Error::msg("no private keys found"))? - }; - Ok(key) -} diff --git a/clash_lib/src/config/def.rs b/clash_lib/src/config/def.rs index 3afbd1975..641f78c33 100644 --- a/clash_lib/src/config/def.rs +++ b/clash_lib/src/config/def.rs @@ -408,7 +408,7 @@ impl Default for Config { #[serde(untagged)] pub enum DNSListen { Udp(String), - Multiple(HashMap), + Multiple(HashMap), } /// DNS client/server settings @@ -419,10 +419,17 @@ pub enum DNSListen { /// enable: true /// ipv6: false # when the false, response to AAAA questions will be empty /// listen: -/// udp: 127.0.0.1:5353 -/// tcp: 127.0.0.1:5353 -/// doh: 127.0.0.1:5354 -/// dot: 127.0.0.1:5355 +/// udp: 127.0.0.1:53553 +/// tcp: 127.0.0.1:53553 +/// dot: +/// addr: 127.0.0.1:53554 +/// hostname: dns.clash +/// ca-cert: dns.crt +/// ca-key: dns.key +/// doh: +/// addr: 127.0.0.1:53555 +/// ca-cert: dns.crt +/// ca-key: dns.key /// ``` #[derive(Serialize, Deserialize)] diff --git a/clash_lib/src/lib.rs b/clash_lib/src/lib.rs index bb5882635..ef2aeae07 100644 --- a/clash_lib/src/lib.rs +++ b/clash_lib/src/lib.rs @@ -281,7 +281,7 @@ async fn start_async(opts: Options) -> Result<(), Error> { debug!("initializing dns listener"); let dns_listener_handle = - dns::get_dns_listener(config.dns, dns_resolver.clone()) + dns::get_dns_listener(config.dns.listen, dns_resolver.clone(), &cwd) .await .map(tokio::spawn); @@ -476,7 +476,7 @@ async fn start_async(opts: Options) -> Result<(), Error> { debug!("reloading dns listener"); let dns_listener_handle = - dns::get_dns_listener(config.dns, dns_resolver.clone()) + dns::get_dns_listener(config.dns.listen, dns_resolver.clone(), &cwd) .await .map(tokio::spawn);