Skip to content

Commit ff776e3

Browse files
authored
Merge branch 'master' into fix-clippy
2 parents 05a9f2c + fc9ebc5 commit ff776e3

File tree

7 files changed

+280
-7
lines changed

7 files changed

+280
-7
lines changed

clash-lib/src/app/dns/config.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use crate::{
33
Error,
44
app::net::{OutboundInterface, get_interface_by_name, get_outbound_interface},
55
common::trie,
6-
config::def::{DNSListen, DNSMode},
6+
config::def::{DNSListen, DNSMode, EdnsClientSubnet as DefEdnsClientSubnet},
77
};
8-
use ipnet::AddrParseError;
8+
use ipnet::{AddrParseError, Ipv4Net, Ipv6Net};
99
use regex::Regex;
1010
use serde::Deserialize;
1111
use std::{
@@ -38,6 +38,12 @@ pub struct FallbackFilter {
3838
pub domain: Vec<String>,
3939
}
4040

41+
#[derive(Clone, Debug, Default, PartialEq)]
42+
pub struct EdnsClientSubnet {
43+
pub ipv4: Option<Ipv4Net>,
44+
pub ipv6: Option<Ipv6Net>,
45+
}
46+
4147
#[derive(Default)]
4248
pub struct Config {
4349
pub enable: bool,
@@ -54,6 +60,7 @@ pub struct Config {
5460
pub store_smart_stats: bool,
5561
pub hosts: Option<trie::StringTrie<IpAddr>>,
5662
pub nameserver_policy: HashMap<String, NameServer>,
63+
pub edns_client_subnet: Option<EdnsClientSubnet>,
5764
}
5865

5966
impl Config {
@@ -265,6 +272,12 @@ impl TryFrom<&crate::config::def::Config> for Config {
265272
})?;
266273
}
267274

275+
let edns_client_subnet = dc
276+
.edns_client_subnet
277+
.as_ref()
278+
.map(parse_edns_client_subnet)
279+
.transpose()?;
280+
268281
Ok(Self {
269282
enable: dc.enable,
270283
ipv6: c.ipv6 && dc.ipv6,
@@ -395,10 +408,47 @@ impl TryFrom<&crate::config::def::Config> for Config {
395408
Some(tree)
396409
},
397410
nameserver_policy,
411+
edns_client_subnet,
398412
})
399413
}
400414
}
401415

416+
fn parse_edns_client_subnet(
417+
ecs: &DefEdnsClientSubnet,
418+
) -> Result<EdnsClientSubnet, Error> {
419+
let ipv4 = ecs
420+
.ipv4
421+
.as_ref()
422+
.map(|value| {
423+
value.parse::<Ipv4Net>().map_err(|_| {
424+
Error::InvalidConfig(format!(
425+
"invalid edns-client-subnet ipv4 network: {value}"
426+
))
427+
})
428+
})
429+
.transpose()?;
430+
431+
let ipv6 = ecs
432+
.ipv6
433+
.as_ref()
434+
.map(|value| {
435+
value.parse::<Ipv6Net>().map_err(|_| {
436+
Error::InvalidConfig(format!(
437+
"invalid edns-client-subnet ipv6 network: {value}"
438+
))
439+
})
440+
})
441+
.transpose()?;
442+
443+
if ipv4.is_none() && ipv6.is_none() {
444+
return Err(Error::InvalidConfig(
445+
"edns-client-subnet requires at least one of ipv4/ipv6".into(),
446+
));
447+
}
448+
449+
Ok(EdnsClientSubnet { ipv4, ipv6 })
450+
}
451+
402452
impl From<crate::config::def::FallbackFilter> for FallbackFilter {
403453
fn from(c: crate::config::def::FallbackFilter) -> Self {
404454
let ipcidr = Config::parse_fallback_ip_cidr(&c.ip_cidr);

clash-lib/src/app/dns/dhcp.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl DhcpClient {
103103
.collect(),
104104
None,
105105
HashMap::new(),
106+
None,
106107
)
107108
.await;
108109
}

clash-lib/src/app/dns/dns_client.rs

Lines changed: 198 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{ClashResolver, Client, runtime::DnsRuntimeProvider};
1+
use super::{ClashResolver, Client, EdnsClientSubnet, runtime::DnsRuntimeProvider};
22
use std::{
33
fmt::{Debug, Display, Formatter},
44
net,
@@ -11,7 +11,13 @@ use async_trait::async_trait;
1111

1212
use hickory_client::client;
1313
use hickory_proto::{
14-
ProtoError, rustls::tls_client_connect, tcp::TcpClientStream,
14+
ProtoError,
15+
rr::{
16+
RecordType,
17+
rdata::opt::{ClientSubnet, EdnsCode, EdnsOption},
18+
},
19+
rustls::tls_client_connect,
20+
tcp::TcpClientStream,
1521
udp::UdpClientStream,
1622
};
1723
use rustls::ClientConfig;
@@ -57,6 +63,133 @@ impl Display for DNSNetMode {
5763
}
5864
}
5965

66+
#[cfg(test)]
67+
mod tests {
68+
use super::*;
69+
use crate::proxy;
70+
use hickory_proto::{
71+
op,
72+
rr::{Name, rdata::opt::EdnsOption},
73+
};
74+
use std::str::FromStr;
75+
76+
fn client_with_ecs(ecs: Option<EdnsClientSubnet>) -> DnsClient {
77+
let proxy = Arc::new(proxy::direct::Handler::new("test-proxy"));
78+
let addr = net::SocketAddr::new(net::IpAddr::from([127, 0, 0, 1]), 53);
79+
DnsClient {
80+
inner: Arc::new(RwLock::new(Inner {
81+
c: None,
82+
bg_handle: None,
83+
})),
84+
cfg: DnsConfig::Udp(addr, None, proxy.clone()),
85+
proxy,
86+
host: "example.org".to_string(),
87+
port: 53,
88+
net: DNSNetMode::Udp,
89+
iface: None,
90+
ecs,
91+
}
92+
}
93+
94+
fn build_message(record_type: RecordType) -> Message {
95+
let mut msg = Message::new();
96+
let mut query = op::Query::new();
97+
query.set_name(Name::from_ascii("example.org").expect("valid name"));
98+
query.set_query_type(record_type);
99+
msg.add_query(query);
100+
msg
101+
}
102+
103+
#[test]
104+
fn apply_edns_client_subnet_adds_ipv4_option() {
105+
let ecs = EdnsClientSubnet {
106+
ipv4: Some("1.2.3.4/24".parse().unwrap()),
107+
ipv6: None,
108+
};
109+
let client = client_with_ecs(Some(ecs));
110+
let mut msg = build_message(RecordType::A);
111+
112+
client.apply_edns_client_subnet(&mut msg);
113+
114+
let edns = msg.extensions().as_ref().expect("edns should exist");
115+
let option = edns
116+
.option(EdnsCode::Subnet)
117+
.expect("subnet option missing");
118+
match option {
119+
EdnsOption::Subnet(subnet) => {
120+
assert_eq!(subnet.addr(), net::IpAddr::from([1, 2, 3, 0]));
121+
assert_eq!(subnet.source_prefix(), 24);
122+
assert_eq!(subnet.scope_prefix(), 24);
123+
}
124+
_ => panic!("unexpected edns option"),
125+
}
126+
}
127+
128+
#[test]
129+
fn apply_edns_client_subnet_prefers_ipv6_for_aaaa() {
130+
let ecs = EdnsClientSubnet {
131+
ipv4: Some("1.2.3.4/24".parse().unwrap()),
132+
ipv6: Some("2001:db8::/48".parse().unwrap()),
133+
};
134+
let client = client_with_ecs(Some(ecs));
135+
let mut msg = build_message(RecordType::AAAA);
136+
137+
client.apply_edns_client_subnet(&mut msg);
138+
139+
let edns = msg.extensions().as_ref().expect("edns should exist");
140+
let option = edns
141+
.option(EdnsCode::Subnet)
142+
.expect("subnet option missing");
143+
match option {
144+
EdnsOption::Subnet(subnet) => {
145+
assert_eq!(
146+
subnet.addr(),
147+
net::IpAddr::from_str("2001:db8::").unwrap()
148+
);
149+
assert_eq!(subnet.source_prefix(), 48);
150+
assert_eq!(subnet.scope_prefix(), 48);
151+
}
152+
_ => panic!("unexpected edns option"),
153+
}
154+
}
155+
156+
#[test]
157+
fn apply_edns_client_subnet_respects_existing_option() {
158+
let ecs = EdnsClientSubnet {
159+
ipv4: Some("1.2.3.4/24".parse().unwrap()),
160+
ipv6: None,
161+
};
162+
let client = client_with_ecs(Some(ecs));
163+
let mut msg = build_message(RecordType::A);
164+
165+
let mut edns = hickory_proto::op::Edns::new();
166+
{
167+
let opts = edns.options_mut();
168+
opts.insert(EdnsOption::Subnet(ClientSubnet::new(
169+
net::IpAddr::from([9, 8, 7, 0]),
170+
24,
171+
24,
172+
)));
173+
}
174+
msg.set_edns(edns);
175+
176+
client.apply_edns_client_subnet(&mut msg);
177+
178+
let edns = msg.extensions().as_ref().expect("edns should remain");
179+
let option = edns
180+
.option(EdnsCode::Subnet)
181+
.expect("subnet option missing");
182+
match option {
183+
EdnsOption::Subnet(subnet) => {
184+
assert_eq!(subnet.addr(), net::IpAddr::from([9, 8, 7, 0]));
185+
assert_eq!(subnet.source_prefix(), 24);
186+
assert_eq!(subnet.scope_prefix(), 24);
187+
}
188+
_ => panic!("unexpected edns option"),
189+
}
190+
}
191+
}
192+
60193
impl FromStr for DNSNetMode {
61194
type Err = Error;
62195

@@ -80,6 +213,7 @@ pub struct Opts {
80213
pub net: DNSNetMode,
81214
pub iface: Option<OutboundInterface>,
82215
pub proxy: Arc<dyn OutboundHandler>,
216+
pub ecs: Option<EdnsClientSubnet>,
83217
}
84218

85219
enum DnsConfig {
@@ -163,6 +297,7 @@ pub struct DnsClient {
163297
port: u16,
164298
net: DNSNetMode,
165299
iface: Option<OutboundInterface>,
300+
ecs: Option<EdnsClientSubnet>,
166301
}
167302

168303
impl DnsClient {
@@ -216,6 +351,7 @@ impl DnsClient {
216351
port: opts.port,
217352
net: opts.net,
218353
iface: opts.iface,
354+
ecs: opts.ecs.clone(),
219355
}))
220356
}
221357
DNSNetMode::Tcp => {
@@ -237,6 +373,7 @@ impl DnsClient {
237373
port: opts.port,
238374
net: opts.net,
239375
iface: opts.iface,
376+
ecs: opts.ecs.clone(),
240377
}))
241378
}
242379
DNSNetMode::DoT => {
@@ -259,6 +396,7 @@ impl DnsClient {
259396
port: opts.port,
260397
net: opts.net,
261398
iface: opts.iface,
399+
ecs: opts.ecs.clone(),
262400
}))
263401
}
264402
DNSNetMode::DoH => {
@@ -281,13 +419,67 @@ impl DnsClient {
281419
port: opts.port,
282420
net: opts.net,
283421
iface: opts.iface,
422+
ecs: opts.ecs.clone(),
284423
}))
285424
}
286425
_ => unreachable!("."),
287426
}
288427
}
289428
}
290429
}
430+
431+
fn apply_edns_client_subnet(&self, message: &mut Message) {
432+
let Some(ecs) = &self.ecs else {
433+
return;
434+
};
435+
436+
if ecs.ipv4.is_none() && ecs.ipv6.is_none() {
437+
return;
438+
}
439+
440+
if message
441+
.extensions()
442+
.as_ref()
443+
.is_some_and(|edns| edns.option(EdnsCode::Subnet).is_some())
444+
{
445+
return;
446+
}
447+
448+
let prefer_ipv6 = matches!(
449+
message.query().map(|q| q.query_type()),
450+
Some(RecordType::AAAA)
451+
);
452+
453+
let candidate = if prefer_ipv6 {
454+
ecs.ipv6
455+
.map(|ipv6| (net::IpAddr::from(ipv6.network()), ipv6.prefix_len()))
456+
.or_else(|| {
457+
ecs.ipv4.map(|ipv4| {
458+
(net::IpAddr::from(ipv4.network()), ipv4.prefix_len())
459+
})
460+
})
461+
} else {
462+
ecs.ipv4
463+
.map(|ipv4| (net::IpAddr::from(ipv4.network()), ipv4.prefix_len()))
464+
.or_else(|| {
465+
ecs.ipv6.map(|ipv6| {
466+
(net::IpAddr::from(ipv6.network()), ipv6.prefix_len())
467+
})
468+
})
469+
};
470+
471+
let Some((addr, prefix)) = candidate else {
472+
return;
473+
};
474+
475+
let edns = message
476+
.extensions_mut()
477+
.get_or_insert_with(hickory_proto::op::Edns::new);
478+
479+
let options = edns.options_mut();
480+
options.remove(EdnsCode::Subnet);
481+
options.insert(EdnsOption::Subnet(ClientSubnet::new(addr, prefix, prefix)));
482+
}
291483
}
292484

293485
impl Debug for DnsClient {
@@ -345,7 +537,10 @@ impl Client for DnsClient {
345537
}
346538
}
347539

348-
let mut req = DnsRequest::new(msg.clone(), DnsRequestOptions::default());
540+
let mut outbound = msg.clone();
541+
self.apply_edns_client_subnet(&mut outbound);
542+
543+
let mut req = DnsRequest::new(outbound, DnsRequestOptions::default());
349544
if req.id() == 0 {
350545
req.set_id(rand::random::<u16>());
351546
}

0 commit comments

Comments
 (0)