1#[cfg(feature = "geoip")]
4use tor_geoip::HasCountryCode;
5use tor_linkspec::{ChanTarget, HasAddrs, HasRelayIds, RelayIdSet};
6use tor_netdir::{FamilyRules, NetDir, Relay, SubnetConfig};
7use tor_netdoc::types::policy::AddrPortPattern;
8
9use crate::{LowLevelRelayPredicate, RelaySelectionConfig, RelayUsage};
10use std::{fmt, net::IpAddr};
11
12#[derive(Clone, Debug)]
19pub struct RelayRestriction<'a> {
20 inner: RestrictionInner<'a>,
22}
23
24#[derive(Clone, Debug)]
37enum RestrictionInner<'a> {
38 NoRestriction,
43 SupportsUsage(crate::RelayUsage),
45 Exclude(RelayExclusion<'a>),
47 HasAddrInSet(Vec<AddrPortPattern>),
50 #[cfg(feature = "geoip")]
52 RequireCountry(tor_geoip::CountryCode),
53}
54
55impl<'a> RelayRestriction<'a> {
56 pub(crate) fn no_restriction() -> Self {
58 RelayRestriction {
59 inner: RestrictionInner::NoRestriction,
60 }
61 }
62
63 pub(crate) fn for_usage(usage: crate::RelayUsage) -> Self {
68 RelayRestriction {
69 inner: RestrictionInner::SupportsUsage(usage),
70 }
71 }
72
73 #[cfg(feature = "geoip")]
76 pub fn require_country_code(cc: tor_geoip::CountryCode) -> Self {
77 RelayRestriction {
78 inner: RestrictionInner::RequireCountry(cc),
79 }
80 }
81
82 pub fn require_address(addr_patterns: Vec<AddrPortPattern>) -> Self {
85 RelayRestriction {
88 inner: RestrictionInner::HasAddrInSet(addr_patterns),
89 }
90 }
91
92 pub(crate) fn relax(&self) -> Self {
96 use RestrictionInner::*;
97 match &self.inner {
98 SupportsUsage(usage) => Self::for_usage(RelayUsage::middle_relay(Some(usage))),
101 _ => Self::no_restriction(),
103 }
104 }
105
106 pub(crate) fn as_usage(&self) -> Option<&RelayUsage> {
108 use RestrictionInner::*;
109 match &self.inner {
110 SupportsUsage(usage) => Some(usage),
111 _ => None,
112 }
113 }
114
115 pub(crate) fn rejection_description(&self) -> Option<&'static str> {
118 use RestrictionInner::*;
119 match &self.inner {
120 NoRestriction => None,
121 SupportsUsage(u) => Some(u.rejection_description()),
122 Exclude(e) => e.rejection_description(),
123 HasAddrInSet(_) => Some("not reachable (according to address filter)"),
124 #[cfg(feature = "geoip")]
125 RequireCountry(_) => Some("not in correct country"),
126 }
127 }
128}
129
130impl<'a> LowLevelRelayPredicate for RelayRestriction<'a> {
131 fn low_level_predicate_permits_relay(&self, relay: &tor_netdir::Relay<'_>) -> bool {
132 use RestrictionInner::*;
133 match &self.inner {
134 NoRestriction => true,
135 SupportsUsage(usage) => usage.low_level_predicate_permits_relay(relay),
136 Exclude(exclusion) => exclusion.low_level_predicate_permits_relay(relay),
137 HasAddrInSet(patterns) => relay_has_addr_in_set(relay, patterns),
138 #[cfg(feature = "geoip")]
139 RequireCountry(cc) => relay.country_code() == Some(*cc),
140 }
141 }
142}
143
144impl<'a> From<RelayExclusion<'a>> for RelayRestriction<'a> {
145 fn from(value: RelayExclusion<'a>) -> Self {
146 RelayRestriction {
147 inner: RestrictionInner::Exclude(value),
148 }
149 }
150}
151
152fn relay_has_addr_in_set(relay: &Relay<'_>, patterns: &[AddrPortPattern]) -> bool {
155 relay
159 .addrs()
160 .any(|addr| patterns.iter().any(|pat| pat.matches_sockaddr(&addr)))
161}
162
163#[derive(Clone, Debug)]
175pub struct RelayExclusion<'a> {
176 exclude_ids: RelayIdSet,
180 exclude_subnets: Vec<IpAddr>,
184 exclude_relay_families: RelayList<'a>,
186 subnet_config: SubnetConfig,
189 family_rules: FamilyRules,
191}
192
193#[derive(Clone)]
195struct RelayList<'a>(Vec<Relay<'a>>);
196impl<'a> fmt::Debug for RelayList<'a> {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 write!(f, "[ ")?;
199 for r in &self.0 {
200 write!(f, "{}, ", r.display_relay_ids())?;
201 }
202 write!(f, "]")
203 }
204}
205
206impl<'a> RelayExclusion<'a> {
207 pub fn no_relays_excluded() -> Self {
215 RelayExclusion {
216 exclude_ids: RelayIdSet::new(),
217 exclude_subnets: Vec::new(),
218 exclude_relay_families: RelayList(Vec::new()),
219 subnet_config: SubnetConfig::no_addresses_match(),
220 family_rules: FamilyRules::ignore_declared_families(),
221 }
222 }
223
224 pub fn exclude_identities(ids: RelayIdSet) -> Self {
226 RelayExclusion {
227 exclude_ids: ids,
228 ..RelayExclusion::no_relays_excluded()
229 }
230 }
231
232 pub fn exclude_specific_relays(relays: &[Relay<'a>]) -> Self {
234 let ids: RelayIdSet = relays
235 .iter()
236 .flat_map(Relay::identities)
237 .map(|id_ref| id_ref.to_owned())
238 .collect();
239
240 Self::exclude_identities(ids)
241 }
242
243 pub fn exclude_channel_target_family<CT: ChanTarget>(
254 cfg: &RelaySelectionConfig,
255 ct: &CT,
256 netdir: &'a NetDir,
257 ) -> Self {
258 if let Some(r) = netdir.by_ids(ct) {
259 return Self::exclude_relays_in_same_family(
260 cfg,
261 vec![r],
262 FamilyRules::from(netdir.params()),
263 );
264 }
265
266 let exclude_ids = ct.identities().map(|id_ref| id_ref.to_owned()).collect();
267 let exclude_addr_families = ct.addrs().map(|a| a.ip()).collect();
268
269 Self {
270 exclude_ids,
271 exclude_subnets: exclude_addr_families,
272 subnet_config: cfg.subnet_config,
273 ..Self::no_relays_excluded()
274 }
275 }
276
277 pub fn exclude_relays_in_same_family(
287 cfg: &RelaySelectionConfig,
288 relays: Vec<Relay<'a>>,
289 family_rules: FamilyRules,
290 ) -> Self {
291 RelayExclusion {
292 exclude_relay_families: RelayList(relays),
293 subnet_config: cfg.subnet_config,
294 family_rules,
295 ..RelayExclusion::no_relays_excluded()
296 }
297 }
298
299 pub fn extend(&mut self, other: &RelayExclusion<'a>) {
304 let RelayExclusion {
305 exclude_ids,
306 exclude_subnets: exclude_addr_families,
307 exclude_relay_families,
308 subnet_config,
309 family_rules,
310 } = other;
311 self.exclude_ids
312 .extend(exclude_ids.iter().map(|id_ref| id_ref.to_owned()));
313 self.exclude_subnets
314 .extend_from_slice(&exclude_addr_families[..]);
315 self.exclude_relay_families
316 .0
317 .extend_from_slice(&exclude_relay_families.0[..]);
318 self.subnet_config = self.subnet_config.union(subnet_config);
319 self.family_rules = self.family_rules.union(family_rules);
320 }
321
322 pub(crate) fn rejection_description(&self) -> Option<&'static str> {
325 if self.exclude_relay_families.0.is_empty() && self.exclude_subnets.is_empty() {
326 if self.exclude_ids.is_empty() {
327 None
328 } else {
329 Some("already selected")
330 }
331 } else {
332 Some("in same family as already selected")
333 }
334 }
335}
336
337impl<'a> LowLevelRelayPredicate for RelayExclusion<'a> {
338 fn low_level_predicate_permits_relay(&self, relay: &Relay<'_>) -> bool {
339 if relay.identities().any(|id| self.exclude_ids.contains(id)) {
340 return false;
341 }
342
343 if relay.addrs().any(|addr| {
344 self.exclude_subnets
345 .iter()
346 .any(|fam| self.subnet_config.addrs_in_same_subnet(&addr.ip(), fam))
347 }) {
348 return false;
349 }
350
351 if self.exclude_relay_families.0.iter().any(|r| {
352 relays_in_same_extended_family(&self.subnet_config, relay, r, self.family_rules)
353 }) {
354 return false;
355 }
356
357 true
358 }
359}
360
361fn relays_in_same_extended_family(
365 subnet_config: &SubnetConfig,
366 r1: &Relay<'_>,
367 r2: &Relay<'_>,
368 family_rules: FamilyRules,
369) -> bool {
370 r1.low_level_details().in_same_family(r2, family_rules)
371 || subnet_config.any_addrs_in_same_subnet(r1, r2)
372}
373
374#[cfg(test)]
375mod test {
376 #![allow(clippy::bool_assert_comparison)]
378 #![allow(clippy::clone_on_copy)]
379 #![allow(clippy::dbg_macro)]
380 #![allow(clippy::mixed_attributes_style)]
381 #![allow(clippy::print_stderr)]
382 #![allow(clippy::print_stdout)]
383 #![allow(clippy::single_char_pattern)]
384 #![allow(clippy::unwrap_used)]
385 #![allow(clippy::unchecked_duration_subtraction)]
386 #![allow(clippy::useless_vec)]
387 #![allow(clippy::needless_pass_by_value)]
388 use tor_linkspec::RelayId;
391 use tor_netdir::testnet::construct_custom_netdir;
392
393 use super::*;
394 use crate::testing::{cfg, split_netdir, testnet};
395
396 #[test]
397 fn exclude_nothing() {
398 let nd = testnet();
399 let usage = RelayExclusion::no_relays_excluded();
400 assert!(
401 nd.relays()
402 .all(|r| usage.low_level_predicate_permits_relay(&r))
403 );
404 }
405
406 #[test]
407 fn exclude_ids() {
408 let nd = testnet();
409 let id_0 = "$0000000000000000000000000000000000000000".parse().unwrap();
410 let id_5 = "ed25519:BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU"
411 .parse()
412 .unwrap();
413 let ids: RelayIdSet = [id_0, id_5].into_iter().collect();
414 let (yes, no) = split_netdir(&nd, &RelayExclusion::exclude_identities(ids));
415
416 let p = |r: &Relay<'_>| !(r.has_identity(id_0.as_ref()) || r.has_identity(id_5.as_ref()));
417 assert_eq!(yes.len(), 38);
418 assert_eq!(no.len(), 2);
419 assert!(yes.iter().all(p));
420 assert!(no.iter().all(|r| !p(r)));
421 }
422
423 #[test]
424 fn exclude_relays() {
425 let nd = testnet();
426 let id_0: RelayId = "$0000000000000000000000000000000000000000".parse().unwrap();
427 let id_5: RelayId = "ed25519:BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU"
428 .parse()
429 .unwrap();
430 let relay_0 = nd.by_id(&id_0).unwrap();
431 let relay_5 = nd.by_id(&id_5).unwrap();
432
433 let (yes, no) = split_netdir(
434 &nd,
435 &RelayExclusion::exclude_specific_relays(&[relay_0.clone(), relay_5.clone()]),
436 );
437 let p = |r: &Relay<'_>| !(r.same_relay_ids(&relay_0) || r.same_relay_ids(&relay_5));
438 assert_eq!(yes.len(), 38);
439 assert_eq!(no.len(), 2);
440 assert!(yes.iter().all(p));
441 assert!(no.iter().all(|r| !p(r)));
442 }
443
444 fn exclude_families_impl(nd: &NetDir, family_rules: FamilyRules) {
447 let id_0: RelayId = "$0000000000000000000000000000000000000000".parse().unwrap();
448 let id_5: RelayId = "ed25519:BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU"
449 .parse()
450 .unwrap();
451 let relay_0 = nd.by_id(&id_0).unwrap();
452 let relay_5 = nd.by_id(&id_5).unwrap();
453 let excluding_relays = vec![relay_0, relay_5];
454
455 let id_1 = "$0101010101010101010101010101010101010101".parse().unwrap();
457 let id_4 = "$0404040404040404040404040404040404040404".parse().unwrap();
458 let expect_excluded_ids: RelayIdSet = [id_0, id_1, id_4, id_5].into_iter().collect();
459
460 let cfg_no_subnet = RelaySelectionConfig {
463 long_lived_ports: cfg().long_lived_ports,
464 subnet_config: SubnetConfig::new(255, 255),
465 };
466
467 let (yes, no) = split_netdir(
468 nd,
469 &RelayExclusion::exclude_relays_in_same_family(
470 &cfg_no_subnet,
471 excluding_relays.clone(),
472 family_rules,
473 ),
474 );
475 let p = |r: &Relay<'_>| !r.identities().any(|id| expect_excluded_ids.contains(id));
476 assert_eq!(yes.len(), 36);
477 assert_eq!(no.len(), 4);
478 assert!(yes.iter().all(p));
479 assert!(no.iter().all(|r| !p(r)));
480
481 let expect_excluded_ids: RelayIdSet = nd
488 .relays()
489 .filter_map(|r| {
490 let rsa = r.rsa_identity().unwrap();
491 let b = rsa.as_bytes()[0];
492 if [0, 1, 4, 5].contains(&b) || [0, 5].contains(&(b % 5)) {
493 Some(RelayId::from(*rsa))
494 } else {
495 None
496 }
497 })
498 .collect();
499
500 let (yes, no) = split_netdir(
501 nd,
502 &RelayExclusion::exclude_relays_in_same_family(&cfg(), excluding_relays, family_rules),
503 );
504 for r in &no {
505 dbg!(r.rsa_identity().unwrap());
506 }
507 dbg!(&expect_excluded_ids);
508 dbg!(expect_excluded_ids.len());
509 let p = |r: &Relay<'_>| !r.identities().any(|id| expect_excluded_ids.contains(id));
510 assert_eq!(yes.len(), 30);
511 assert_eq!(no.len(), 10);
512 assert!(yes.iter().all(p));
513
514 assert!(no.iter().all(|r| { !p(r) }));
515 }
516
517 #[test]
518 fn exclude_families_by_list() {
519 exclude_families_impl(
520 &testnet(),
521 *FamilyRules::ignore_declared_families().use_family_lists(true),
522 );
523 }
524
525 #[test]
526 fn exclude_families_by_id() {
527 let netdir = construct_custom_netdir(|pos, nb, _| {
531 nb.md.family("".parse().unwrap());
533 let fam_id = format!("pos:{}", pos / 2);
536 nb.md.add_family_id(fam_id.parse().unwrap());
537 })
538 .unwrap()
539 .unwrap_if_sufficient()
540 .unwrap();
541
542 exclude_families_impl(
543 &netdir,
544 *FamilyRules::ignore_declared_families().use_family_ids(true),
545 );
546 }
547
548 #[test]
549 fn filter_addresses() {
550 let nd = testnet();
551 let reachable = vec![
552 "1.0.0.0/8:*".parse().unwrap(),
553 "2.0.0.0/8:*".parse().unwrap(),
554 ];
555 let reachable = RelayRestriction::require_address(reachable);
556
557 let (yes, no) = split_netdir(&nd, &reachable);
558 assert_eq!(yes.len(), 16);
559 assert_eq!(no.len(), 24);
560
561 let expected = ["1.0.0.3".parse().unwrap(), "2.0.0.3".parse().unwrap()];
562 let p = |r: &Relay<'_>| expected.contains(&r.addrs().next().unwrap().ip());
563 assert!(yes.iter().all(p));
564 assert!(no.iter().all(|r| !p(r)));
565 }
566
567 }