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 .iter()
161 .any(|addr| patterns.iter().any(|pat| pat.matches_sockaddr(addr)))
162}
163
164#[derive(Clone, Debug)]
176pub struct RelayExclusion<'a> {
177 exclude_ids: RelayIdSet,
181 exclude_subnets: Vec<IpAddr>,
185 exclude_relay_families: RelayList<'a>,
187 subnet_config: SubnetConfig,
190 family_rules: FamilyRules,
192}
193
194#[derive(Clone)]
196struct RelayList<'a>(Vec<Relay<'a>>);
197impl<'a> fmt::Debug for RelayList<'a> {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "[ ")?;
200 for r in &self.0 {
201 write!(f, "{}, ", r.display_relay_ids())?;
202 }
203 write!(f, "]")
204 }
205}
206
207impl<'a> RelayExclusion<'a> {
208 pub fn no_relays_excluded() -> Self {
216 RelayExclusion {
217 exclude_ids: RelayIdSet::new(),
218 exclude_subnets: Vec::new(),
219 exclude_relay_families: RelayList(Vec::new()),
220 subnet_config: SubnetConfig::no_addresses_match(),
221 family_rules: FamilyRules::ignore_declared_families(),
222 }
223 }
224
225 pub fn exclude_identities(ids: RelayIdSet) -> Self {
227 RelayExclusion {
228 exclude_ids: ids,
229 ..RelayExclusion::no_relays_excluded()
230 }
231 }
232
233 pub fn exclude_specific_relays(relays: &[Relay<'a>]) -> Self {
235 let ids: RelayIdSet = relays
236 .iter()
237 .flat_map(Relay::identities)
238 .map(|id_ref| id_ref.to_owned())
239 .collect();
240
241 Self::exclude_identities(ids)
242 }
243
244 pub fn exclude_channel_target_family<CT: ChanTarget>(
255 cfg: &RelaySelectionConfig,
256 ct: &CT,
257 netdir: &'a NetDir,
258 ) -> Self {
259 if let Some(r) = netdir.by_ids(ct) {
260 return Self::exclude_relays_in_same_family(
261 cfg,
262 vec![r],
263 FamilyRules::from(netdir.params()),
264 );
265 }
266
267 let exclude_ids = ct.identities().map(|id_ref| id_ref.to_owned()).collect();
268 let exclude_addr_families = ct.addrs().iter().map(|a| a.ip()).collect();
269
270 Self {
271 exclude_ids,
272 exclude_subnets: exclude_addr_families,
273 subnet_config: cfg.subnet_config,
274 ..Self::no_relays_excluded()
275 }
276 }
277
278 pub fn exclude_relays_in_same_family(
288 cfg: &RelaySelectionConfig,
289 relays: Vec<Relay<'a>>,
290 family_rules: FamilyRules,
291 ) -> Self {
292 RelayExclusion {
293 exclude_relay_families: RelayList(relays),
294 subnet_config: cfg.subnet_config,
295 family_rules,
296 ..RelayExclusion::no_relays_excluded()
297 }
298 }
299
300 pub fn extend(&mut self, other: &RelayExclusion<'a>) {
305 let RelayExclusion {
306 exclude_ids,
307 exclude_subnets: exclude_addr_families,
308 exclude_relay_families,
309 subnet_config,
310 family_rules,
311 } = other;
312 self.exclude_ids
313 .extend(exclude_ids.iter().map(|id_ref| id_ref.to_owned()));
314 self.exclude_subnets
315 .extend_from_slice(&exclude_addr_families[..]);
316 self.exclude_relay_families
317 .0
318 .extend_from_slice(&exclude_relay_families.0[..]);
319 self.subnet_config = self.subnet_config.union(subnet_config);
320 self.family_rules = self.family_rules.union(family_rules);
321 }
322
323 pub(crate) fn rejection_description(&self) -> Option<&'static str> {
326 if self.exclude_relay_families.0.is_empty() && self.exclude_subnets.is_empty() {
327 if self.exclude_ids.is_empty() {
328 None
329 } else {
330 Some("already selected")
331 }
332 } else {
333 Some("in same family as already selected")
334 }
335 }
336}
337
338impl<'a> LowLevelRelayPredicate for RelayExclusion<'a> {
339 fn low_level_predicate_permits_relay(&self, relay: &Relay<'_>) -> bool {
340 if relay.identities().any(|id| self.exclude_ids.contains(id)) {
341 return false;
342 }
343
344 if relay.addrs().iter().any(|addr| {
345 self.exclude_subnets
346 .iter()
347 .any(|fam| self.subnet_config.addrs_in_same_subnet(&addr.ip(), fam))
348 }) {
349 return false;
350 }
351
352 if self.exclude_relay_families.0.iter().any(|r| {
353 relays_in_same_extended_family(&self.subnet_config, relay, r, self.family_rules)
354 }) {
355 return false;
356 }
357
358 true
359 }
360}
361
362fn relays_in_same_extended_family(
366 subnet_config: &SubnetConfig,
367 r1: &Relay<'_>,
368 r2: &Relay<'_>,
369 family_rules: FamilyRules,
370) -> bool {
371 r1.low_level_details().in_same_family(r2, family_rules)
372 || subnet_config.any_addrs_in_same_subnet(r1, r2)
373}
374
375#[cfg(test)]
376mod test {
377 #![allow(clippy::bool_assert_comparison)]
379 #![allow(clippy::clone_on_copy)]
380 #![allow(clippy::dbg_macro)]
381 #![allow(clippy::mixed_attributes_style)]
382 #![allow(clippy::print_stderr)]
383 #![allow(clippy::print_stdout)]
384 #![allow(clippy::single_char_pattern)]
385 #![allow(clippy::unwrap_used)]
386 #![allow(clippy::unchecked_duration_subtraction)]
387 #![allow(clippy::useless_vec)]
388 #![allow(clippy::needless_pass_by_value)]
389 use tor_linkspec::RelayId;
392 use tor_netdir::testnet::construct_custom_netdir;
393
394 use super::*;
395 use crate::testing::{cfg, split_netdir, testnet};
396
397 #[test]
398 fn exclude_nothing() {
399 let nd = testnet();
400 let usage = RelayExclusion::no_relays_excluded();
401 assert!(
402 nd.relays()
403 .all(|r| usage.low_level_predicate_permits_relay(&r))
404 );
405 }
406
407 #[test]
408 fn exclude_ids() {
409 let nd = testnet();
410 let id_0 = "$0000000000000000000000000000000000000000".parse().unwrap();
411 let id_5 = "ed25519:BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU"
412 .parse()
413 .unwrap();
414 let ids: RelayIdSet = [id_0, id_5].into_iter().collect();
415 let (yes, no) = split_netdir(&nd, &RelayExclusion::exclude_identities(ids));
416
417 let p = |r: &Relay<'_>| !(r.has_identity(id_0.as_ref()) || r.has_identity(id_5.as_ref()));
418 assert_eq!(yes.len(), 38);
419 assert_eq!(no.len(), 2);
420 assert!(yes.iter().all(p));
421 assert!(no.iter().all(|r| !p(r)));
422 }
423
424 #[test]
425 fn exclude_relays() {
426 let nd = testnet();
427 let id_0: RelayId = "$0000000000000000000000000000000000000000".parse().unwrap();
428 let id_5: RelayId = "ed25519:BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU"
429 .parse()
430 .unwrap();
431 let relay_0 = nd.by_id(&id_0).unwrap();
432 let relay_5 = nd.by_id(&id_5).unwrap();
433
434 let (yes, no) = split_netdir(
435 &nd,
436 &RelayExclusion::exclude_specific_relays(&[relay_0.clone(), relay_5.clone()]),
437 );
438 let p = |r: &Relay<'_>| !(r.same_relay_ids(&relay_0) || r.same_relay_ids(&relay_5));
439 assert_eq!(yes.len(), 38);
440 assert_eq!(no.len(), 2);
441 assert!(yes.iter().all(p));
442 assert!(no.iter().all(|r| !p(r)));
443 }
444
445 fn exclude_families_impl(nd: &NetDir, family_rules: FamilyRules) {
448 let id_0: RelayId = "$0000000000000000000000000000000000000000".parse().unwrap();
449 let id_5: RelayId = "ed25519:BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU"
450 .parse()
451 .unwrap();
452 let relay_0 = nd.by_id(&id_0).unwrap();
453 let relay_5 = nd.by_id(&id_5).unwrap();
454 let excluding_relays = vec![relay_0, relay_5];
455
456 let id_1 = "$0101010101010101010101010101010101010101".parse().unwrap();
458 let id_4 = "$0404040404040404040404040404040404040404".parse().unwrap();
459 let expect_excluded_ids: RelayIdSet = [id_0, id_1, id_4, id_5].into_iter().collect();
460
461 let cfg_no_subnet = RelaySelectionConfig {
464 long_lived_ports: cfg().long_lived_ports,
465 subnet_config: SubnetConfig::new(255, 255),
466 };
467
468 let (yes, no) = split_netdir(
469 nd,
470 &RelayExclusion::exclude_relays_in_same_family(
471 &cfg_no_subnet,
472 excluding_relays.clone(),
473 family_rules,
474 ),
475 );
476 let p = |r: &Relay<'_>| !r.identities().any(|id| expect_excluded_ids.contains(id));
477 assert_eq!(yes.len(), 36);
478 assert_eq!(no.len(), 4);
479 assert!(yes.iter().all(p));
480 assert!(no.iter().all(|r| !p(r)));
481
482 let expect_excluded_ids: RelayIdSet = nd
489 .relays()
490 .filter_map(|r| {
491 let rsa = r.rsa_identity().unwrap();
492 let b = rsa.as_bytes()[0];
493 if [0, 1, 4, 5].contains(&b) || [0, 5].contains(&(b % 5)) {
494 Some(RelayId::from(*rsa))
495 } else {
496 None
497 }
498 })
499 .collect();
500
501 let (yes, no) = split_netdir(
502 nd,
503 &RelayExclusion::exclude_relays_in_same_family(&cfg(), excluding_relays, family_rules),
504 );
505 for r in &no {
506 dbg!(r.rsa_identity().unwrap());
507 }
508 dbg!(&expect_excluded_ids);
509 dbg!(expect_excluded_ids.len());
510 let p = |r: &Relay<'_>| !r.identities().any(|id| expect_excluded_ids.contains(id));
511 assert_eq!(yes.len(), 30);
512 assert_eq!(no.len(), 10);
513 assert!(yes.iter().all(p));
514
515 assert!(no.iter().all(|r| { !p(r) }));
516 }
517
518 #[test]
519 fn exclude_families_by_list() {
520 exclude_families_impl(
521 &testnet(),
522 *FamilyRules::ignore_declared_families().use_family_lists(true),
523 );
524 }
525
526 #[test]
527 fn exclude_families_by_id() {
528 let netdir = construct_custom_netdir(|pos, nb, _| {
532 nb.md.family("".parse().unwrap());
534 let fam_id = format!("pos:{}", pos / 2);
537 nb.md.add_family_id(fam_id.parse().unwrap());
538 })
539 .unwrap()
540 .unwrap_if_sufficient()
541 .unwrap();
542
543 exclude_families_impl(
544 &netdir,
545 *FamilyRules::ignore_declared_families().use_family_ids(true),
546 );
547 }
548
549 #[test]
550 fn filter_addresses() {
551 let nd = testnet();
552 let reachable = vec![
553 "1.0.0.0/8:*".parse().unwrap(),
554 "2.0.0.0/8:*".parse().unwrap(),
555 ];
556 let reachable = RelayRestriction::require_address(reachable);
557
558 let (yes, no) = split_netdir(&nd, &reachable);
559 assert_eq!(yes.len(), 16);
560 assert_eq!(no.len(), 24);
561
562 let expected = ["1.0.0.3".parse().unwrap(), "2.0.0.3".parse().unwrap()];
563 let p = |r: &Relay<'_>| expected.contains(&r.addrs()[0].ip());
564 assert!(yes.iter().all(p));
565 assert!(no.iter().all(|r| !p(r)));
566 }
567
568 }