diff options
author | eta <tor@eta.st> | 2023-08-22 16:52:28 +0100 |
---|---|---|
committer | eta <tor@eta.st> | 2023-09-18 12:25:36 +0100 |
commit | a6fe895eb5c605e9c8de7ba871fc2114d65e098c (patch) | |
tree | 75d20c7d08d7258788975127719dcf9dc1372c5d | |
parent | 9367242f956b3bae242b0b2cb0a7420020a4ccc8 (diff) | |
download | arti-a6fe895eb5c605e9c8de7ba871fc2114d65e098c.tar.gz arti-a6fe895eb5c605e9c8de7ba871fc2114d65e098c.zip |
arti-client: add exit selection with GeoIP country codes
This threads the country codes work through the rest of the codebase:
- `tor-dirmgr` will now enable GeoIP with the embedded database when the
`geoip` future is enabled
- This can be extended later using the `DirMgrConfig` to allow
specifying a custom database; this is not done here, though
- `tor-circmgr`'s `SupportedCircUsage` and `TargetCircUsage` fields gain
new `country_code` members to allow filtering circuits by country
- These are `()` in builds where the `geoip` feature is not enabled --
doing it this way means we don't have to copy and paste huge swathes
of code, since we can't use `#[cfg]` in patterns
- `ExitPathBuilder` gains (hacked-in) support for choosing a relay with
the correct country code
- Due to the lack of conjuction, we just copy and paste a small bit,
pending further refactoring
- `StreamPrefs` now lets you specify a country code, letting embedders
make use of the feature
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | crates/arti-client/Cargo.toml | 3 | ||||
-rw-r--r-- | crates/arti-client/src/client.rs | 41 | ||||
-rw-r--r-- | crates/arti-client/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/tor-circmgr/Cargo.toml | 5 | ||||
-rw-r--r-- | crates/tor-circmgr/src/err.rs | 5 | ||||
-rw-r--r-- | crates/tor-circmgr/src/lib.rs | 13 | ||||
-rw-r--r-- | crates/tor-circmgr/src/mgr.rs | 4 | ||||
-rw-r--r-- | crates/tor-circmgr/src/path.rs | 13 | ||||
-rw-r--r-- | crates/tor-circmgr/src/path/exitpath.rs | 55 | ||||
-rw-r--r-- | crates/tor-circmgr/src/usage.rs | 95 | ||||
-rw-r--r-- | crates/tor-dirmgr/Cargo.toml | 4 | ||||
-rw-r--r-- | crates/tor-dirmgr/src/state.rs | 8 | ||||
-rw-r--r-- | crates/tor-netdir/src/lib.rs | 2 |
14 files changed, 244 insertions, 12 deletions
diff --git a/Cargo.lock b/Cargo.lock index 9b4535982..957441ee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,7 @@ dependencies = [ "tor-config", "tor-dirmgr", "tor-error", + "tor-geoip", "tor-guardmgr", "tor-hsclient", "tor-hscrypto", @@ -4608,6 +4609,7 @@ dependencies = [ "tor-chanmgr", "tor-config", "tor-error", + "tor-geoip", "tor-guardmgr", "tor-hscrypto", "tor-linkspec", @@ -4620,6 +4622,7 @@ dependencies = [ "tor-rtmock", "tracing", "visibility", + "void", "weak-table", ] @@ -4745,6 +4748,7 @@ dependencies = [ "tor-consdiff", "tor-dirclient", "tor-error", + "tor-geoip", "tor-guardmgr", "tor-linkspec", "tor-llcrypto", diff --git a/crates/arti-client/Cargo.toml b/crates/arti-client/Cargo.toml index 8113d2058..67ac2954a 100644 --- a/crates/arti-client/Cargo.toml +++ b/crates/arti-client/Cargo.toml @@ -78,6 +78,7 @@ experimental = [ "dirfilter", "experimental-api", "error_detail", + "geoip", "rpc", "tor-proto/experimental", "tor-cell/experimental", @@ -96,6 +97,7 @@ experimental = [ experimental-api = ["__is_experimental"] dirfilter = ["tor-dirmgr/dirfilter", "__is_experimental"] error_detail = ["__is_experimental"] +geoip = ["tor-circmgr/geoip", "tor-dirmgr/geoip", "tor-geoip", "__is_experimental"] onion-service-client = ["tor-hsclient", "tor-hscrypto"] rpc = ["tor-rpcbase", "__is_experimental"] @@ -127,6 +129,7 @@ tor-circmgr = { path = "../tor-circmgr", version = "0.10.0" } tor-config = { path = "../tor-config", version = "0.9.4" } tor-dirmgr = { path = "../tor-dirmgr", version = "0.11.1", default-features = false, features = ["mmap"] } tor-error = { path = "../tor-error", version = "0.5.4", features = ["tracing"] } +tor-geoip = { path = "../tor-geoip", version = "0.1.0", optional = true } tor-guardmgr = { path = "../tor-guardmgr", version = "0.10.1" } tor-hsclient = { path = "../tor-hsclient", version = "0.4.1", optional = true } tor-hscrypto = { path = "../tor-hscrypto", version = "0.3.2", optional = true } diff --git a/crates/arti-client/src/client.rs b/crates/arti-client/src/client.rs index e80374f2f..7188faf0e 100644 --- a/crates/arti-client/src/client.rs +++ b/crates/arti-client/src/client.rs @@ -55,6 +55,8 @@ use std::sync::{Arc, Mutex}; use crate::err::ErrorDetail; use crate::{status, util, TorClientBuilder}; +#[cfg(feature = "geoip")] +use tor_geoip::CountryCode; use tor_rtcompat::scheduler::TaskHandle; use tracing::{debug, info}; @@ -204,6 +206,14 @@ pub struct StreamPrefs { isolation: StreamIsolationPreference, /// Whether to return the stream optimistically. optimistic_stream: bool, + // TODO GEOIP Ideally this would be unconditional, with CountryCode maybe being Void + // This probably applies in many other places, so probably: git grep 'cfg.*geoip' + // and consider each one with a view to making it unconditional. Background: + // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1537#note_2935256 + // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1537#note_2942214 + #[cfg(feature = "geoip")] + /// A country to restrict the exit relay's location to. + country_code: Option<CountryCode>, /// Whether to try to make connections to onion services. /// /// `Auto` means to use the client configuration. @@ -284,6 +294,29 @@ impl StreamPrefs { self } + /// Indicate that a stream should appear to come from the given country. + /// + /// When this option is set, we will only pick exit relays that + /// have an IP address that matches the country in our GeoIP database. + #[cfg(feature = "geoip")] + #[cfg_attr(docsrs, doc(cfg(feature = "geoip")))] + pub fn exit_country(&mut self, country_code: CountryCode) -> &mut Self { + self.country_code = Some(country_code); + self + } + + /// Indicate that we don't care which country a stream appears to come from. + /// + /// This is available even in the case where GeoIP support is compiled out, + /// to make things easier. + pub fn any_exit_country(&mut self) -> &mut Self { + #[cfg(feature = "geoip")] + { + self.country_code = None; + } + self + } + /// Indicate that the stream should be opened "optimistically". /// /// By default, streams are not "optimistic". When you call @@ -1259,7 +1292,13 @@ impl<R: Runtime> TorClient<R> { let circ = self .circmgr - .get_or_launch_exit(dir.as_ref().into(), exit_ports, self.isolation(prefs)) + .get_or_launch_exit( + dir.as_ref().into(), + exit_ports, + self.isolation(prefs), + #[cfg(feature = "geoip")] + prefs.country_code, + ) .await .map_err(|cause| ErrorDetail::ObtainExitCircuit { cause, diff --git a/crates/arti-client/src/lib.rs b/crates/arti-client/src/lib.rs index 9814a42a4..c9646250a 100644 --- a/crates/arti-client/src/lib.rs +++ b/crates/arti-client/src/lib.rs @@ -69,3 +69,7 @@ pub type Result<T> = std::result::Result<T, Error>; #[cfg(feature = "experimental-api")] pub use builder::DirProviderBuilder; + +#[cfg(feature = "geoip")] +#[cfg_attr(docsrs, doc(cfg(feature = "geoip")))] +pub use tor_geoip::CountryCode; diff --git a/crates/tor-circmgr/Cargo.toml b/crates/tor-circmgr/Cargo.toml index 8cb7ee16a..dc1b6c29b 100644 --- a/crates/tor-circmgr/Cargo.toml +++ b/crates/tor-circmgr/Cargo.toml @@ -42,8 +42,9 @@ testing = ["tor-guardmgr/testing", "__is_experimental"] # # These APIs are not covered by semantic versioning. Using this # feature voids your "semver warrantee". -experimental = ["experimental-api", "hs-service", "testing"] +experimental = ["experimental-api", "hs-service", "testing", "geoip"] experimental-api = ["visibility", "__is_experimental"] +geoip = ["tor-geoip", "tor-netdir/geoip", "__is_experimental"] hs-client = ["hs-common"] hs-service = ["hs-common", "__is_experimental"] hs-common = ["tor-hscrypto"] @@ -73,6 +74,7 @@ tor-basic-utils = { path = "../tor-basic-utils", version = "0.7.3" } tor-chanmgr = { path = "../tor-chanmgr", version = "0.10.1" } tor-config = { path = "../tor-config", version = "0.9.4" } tor-error = { path = "../tor-error", version = "0.5.4", features = ["tracing"] } +tor-geoip = { path = "../tor-geoip", version = "0.1.0", optional = true } tor-guardmgr = { path = "../tor-guardmgr", version = "0.10.1" } tor-hscrypto = { path = "../tor-hscrypto", version = "0.3.2", optional = true } tor-linkspec = { path = "../tor-linkspec", version = "0.8.3" } @@ -83,6 +85,7 @@ tor-proto = { path = "../tor-proto", version = "0.12.1" } tor-rtcompat = { path = "../tor-rtcompat", version = "0.9.1" } tracing = "0.1.36" visibility = { version = "0.1.0", optional = true } +void = "1.0" weak-table = "0.3.0" [dev-dependencies] diff --git a/crates/tor-circmgr/src/err.rs b/crates/tor-circmgr/src/err.rs index 9967b09f5..90ec7e2c3 100644 --- a/crates/tor-circmgr/src/err.rs +++ b/crates/tor-circmgr/src/err.rs @@ -84,15 +84,18 @@ pub enum Error { /// No suitable exit relay for a request. #[error( "Can't find exit for circuit: \ - Rejected {} because of family restrictions and {} because of port requirements", + Rejected {} because of family restrictions, {} because of port requirements, and {} because of country requirements", can_share.display_frac_rejected(), correct_ports.display_frac_rejected(), + correct_country.display_frac_rejected() )] NoExit { /// Exit relays accepted and rejected based on relay family policies. can_share: FilterCount, /// Exit relays accepted and rejected base on the ports that we need. correct_ports: FilterCount, + /// Exit relays accepted and rejected based on exit country code. + correct_country: FilterCount, }, /// Problem creating or updating a guard manager. diff --git a/crates/tor-circmgr/src/lib.rs b/crates/tor-circmgr/src/lib.rs index 758f180c4..77de2c531 100644 --- a/crates/tor-circmgr/src/lib.rs +++ b/crates/tor-circmgr/src/lib.rs @@ -90,6 +90,8 @@ use crate::preemptive::PreemptiveCircuitPredictor; use usage::TargetCircUsage; use safelog::sensitive as sv; +#[cfg(feature = "geoip")] +use tor_geoip::CountryCode; pub use tor_guardmgr::{ExternalActivity, FirstHopId}; use tor_persist::{FsStateMgr, StateMgr}; use tor_rtcompat::scheduler::{TaskHandle, TaskSchedule}; @@ -390,6 +392,9 @@ impl<R: Runtime> CircMgr<R> { netdir: DirInfo<'_>, // TODO: This has to be a NetDir. ports: &[TargetPort], isolation: StreamIsolation, + // TODO GEOIP: this cannot be stabilised like this, since Cargo features need to be + // additive. The function should be refactored to be builder-like. + #[cfg(feature = "geoip")] country_code: Option<CountryCode>, ) -> Result<Arc<ClientCirc>> { self.expire_circuits(); let time = Instant::now(); @@ -404,7 +409,13 @@ impl<R: Runtime> CircMgr<R> { } } let ports = ports.iter().map(Clone::clone).collect(); - let usage = TargetCircUsage::Exit { ports, isolation }; + #[cfg(not(feature = "geoip"))] + let country_code = None; + let usage = TargetCircUsage::Exit { + ports, + isolation, + country_code, + }; self.mgr.get_or_launch(&usage, netdir).await.map(|(c, _)| c) } diff --git a/crates/tor-circmgr/src/mgr.rs b/crates/tor-circmgr/src/mgr.rs index accb44849..ca64acd82 100644 --- a/crates/tor-circmgr/src/mgr.rs +++ b/crates/tor-circmgr/src/mgr.rs @@ -2255,6 +2255,7 @@ mod test { SupportedCircUsage::Exit { policy: ep_none, isolation: None, + country_code: None, }, fake_circ.clone(), expiration.clone(), @@ -2264,6 +2265,7 @@ mod test { SupportedCircUsage::Exit { policy: ep_web, isolation: None, + country_code: None, }, fake_circ.clone(), expiration.clone(), @@ -2273,6 +2275,7 @@ mod test { SupportedCircUsage::Exit { policy: ep_full, isolation: None, + country_code: None, }, fake_circ, expiration, @@ -2282,6 +2285,7 @@ mod test { let usage_web = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(80)], isolation: StreamIsolation::no_isolation(), + country_code: None, }; let empty: Vec<&mut OpenEntry<SupportedCircUsage, FakeCirc>> = vec![]; diff --git a/crates/tor-circmgr/src/path.rs b/crates/tor-circmgr/src/path.rs index 155f65910..1341985a1 100644 --- a/crates/tor-circmgr/src/path.rs +++ b/crates/tor-circmgr/src/path.rs @@ -7,6 +7,8 @@ pub mod dirpath; pub mod exitpath; use tor_error::bad_api_usage; +#[cfg(feature = "geoip")] +use tor_geoip::{CountryCode, HasCountryCode}; use tor_guardmgr::fallback::FallbackDir; use tor_linkspec::{HasAddrs, HasRelayIds, OwnedChanTarget, OwnedCircTarget}; use tor_netdir::Relay; @@ -157,6 +159,17 @@ impl<'a> TorPath<'a> { }) } + /// Return the country code of the final relay in this path, if this is a + /// path for use with exit circuits with an exit taken from the network + /// directory. + #[cfg(feature = "geoip")] + pub(crate) fn country_code(&self) -> Option<CountryCode> { + self.exit_relay().and_then(|r| match r { + MaybeOwnedRelay::Relay(r) => r.country_code(), + MaybeOwnedRelay::Owned(_) => None, + }) + } + /// Return the number of relays in this path. #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { diff --git a/crates/tor-circmgr/src/path/exitpath.rs b/crates/tor-circmgr/src/path/exitpath.rs index 5c36308d5..546012fe3 100644 --- a/crates/tor-circmgr/src/path/exitpath.rs +++ b/crates/tor-circmgr/src/path/exitpath.rs @@ -6,6 +6,8 @@ use rand::Rng; use std::time::SystemTime; use tor_basic_utils::iter::FilterCount; use tor_error::{bad_api_usage, internal}; +#[cfg(feature = "geoip")] +use tor_geoip::{CountryCode, HasCountryCode}; use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable}; use tor_linkspec::{HasRelayIds, OwnedChanTarget, RelayIdSet}; use tor_netdir::{NetDir, Relay, SubnetConfig, WeightRole}; @@ -13,9 +15,23 @@ use tor_rtcompat::Runtime; /// Internal representation of PathBuilder. enum ExitPathBuilderInner<'a> { - /// Request a path that allows exit to the given `TargetPort]`s. + /// Request a path that allows exit to the given `TargetPort`s. WantsPorts(Vec<TargetPort>), + /// Request a path that allows exit with a relay in the given country. + // TODO GEOIP: refactor this builder to allow conjunction! + // See discussion here: + // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/1537#note_2942218 + #[cfg(feature = "geoip")] + ExitInCountry { + /// The country to exit in. + country: CountryCode, + /// Some target ports to use (works like `WantsPorts`). + /// + /// HACK(eta): This is a horrible hack to work around the lack of conjunction. + ports: Vec<TargetPort>, + }, + /// Request a path that allows exit to _any_ port. AnyExit { /// If false, then we fall back to non-exit nodes if we can't find an @@ -60,6 +76,24 @@ impl<'a> ExitPathBuilder<'a> { } } + #[cfg(feature = "geoip")] + #[cfg_attr(docsrs, doc(cfg(feature = "geoip")))] + /// Create a new builder that will try to get an exit relay in `country`, + /// containing all the ports in `ports`. + /// + /// If the list of ports is empty, it is disregarded. + // TODO GEOIP: this method is hacky, and should be refactored. + pub fn in_given_country( + country: CountryCode, + wantports: impl IntoIterator<Item = TargetPort>, + ) -> Self { + let ports: Vec<TargetPort> = wantports.into_iter().collect(); + Self { + inner: ExitPathBuilderInner::ExitInCountry { country, ports }, + compatible_with: None, + } + } + /// Create a new builder that will try to build a path with the given exit /// relay as the last hop. pub fn from_chosen_exit(exit_relay: Relay<'a>) -> Self { @@ -114,6 +148,8 @@ impl<'a> ExitPathBuilder<'a> { ) -> Result<Relay<'a>> { let mut can_share = FilterCount::default(); let mut correct_ports = FilterCount::default(); + #[allow(unused_mut)] + let mut correct_country = FilterCount::default(); match &self.inner { ExitPathBuilderInner::AnyExit { strict } => { let exit = netdir.pick_relay(rng, WeightRole::Exit, |r| { @@ -126,6 +162,7 @@ impl<'a> ExitPathBuilder<'a> { return Err(Error::NoExit { can_share, correct_ports, + correct_country, }) } (None, false) => {} @@ -147,8 +184,22 @@ impl<'a> ExitPathBuilder<'a> { .ok_or(Error::NoExit { can_share, correct_ports, + correct_country, }) } + #[cfg(feature = "geoip")] + ExitPathBuilderInner::ExitInCountry { country, ports } => Ok(netdir + .pick_relay(rng, WeightRole::Exit, |r| { + can_share.count(relays_can_share_circuit_opt(r, guard, config)) + && correct_ports.count(ports.iter().all(|p| p.is_supported_by(r))) + && correct_country + .count(r.country_code().map(|x| x == *country).unwrap_or(false)) + }) + .ok_or(Error::NoExit { + can_share, + correct_ports, + correct_country, + })?), #[cfg(feature = "hs-common")] ExitPathBuilderInner::AnyRelay => netdir @@ -158,6 +209,7 @@ impl<'a> ExitPathBuilder<'a> { .ok_or(Error::NoExit { can_share, correct_ports, + correct_country, }), ExitPathBuilderInner::WantsPorts(wantports) => Ok(netdir @@ -168,6 +220,7 @@ impl<'a> ExitPathBuilder<'a> { .ok_or(Error::NoExit { can_share, correct_ports, + correct_country, })?), ExitPathBuilderInner::ChosenExit(exit_relay) => { diff --git a/crates/tor-circmgr/src/usage.rs b/crates/tor-circmgr/src/usage.rs index 74639062c..3d09c6490 100644 --- a/crates/tor-circmgr/src/usage.rs +++ b/crates/tor-circmgr/src/usage.rs @@ -6,9 +6,13 @@ use std::fmt::{self, Display}; use std::sync::Arc; use std::time::SystemTime; use tracing::debug; +#[cfg(not(feature = "geoip"))] +use void::Void; use crate::path::{dirpath::DirPathBuilder, exitpath::ExitPathBuilder, TorPath}; use tor_chanmgr::ChannelUsage; +#[cfg(feature = "geoip")] +use tor_error::internal; use tor_guardmgr::{GuardMgr, GuardMonitor, GuardUsable}; use tor_netdir::Relay; use tor_netdoc::types::policy::PortPolicy; @@ -17,6 +21,19 @@ use tor_rtcompat::Runtime; #[cfg(feature = "specific-relay")] use tor_linkspec::{HasChanMethod, HasRelayIds}; +#[cfg(feature = "geoip")] +use tor_geoip::CountryCode; +/// A non-existent country code type, used as a placeholder for the real `tor_geoip::CountryCode` +/// when the `geoip` crate feature is not present. +/// +/// This type exists to simplify conditional compilation: without it, we'd have to duplicate a lot +/// of match patterns and things would suck a lot. +// TODO GEOIP: propagate this refactor down through the stack (i.e. all the way down to the +// `tor-geoip` crate) +// We can also get rid of a lot of #[cfg] then. +#[cfg(not(feature = "geoip"))] +pub(crate) type CountryCode = Void; + #[cfg(any(feature = "specific-relay", feature = "hs-common"))] use tor_linkspec::OwnedChanTarget; @@ -145,6 +162,8 @@ pub(crate) enum TargetCircUsage { ports: Vec<TargetPort>, /// Isolation group the circuit shall be part of isolation: StreamIsolation, + /// Restrict the circuit to only exits in the provided country code. + country_code: Option<CountryCode>, }, /// For a circuit is only used for the purpose of building it. TimeoutTesting, @@ -197,6 +216,8 @@ pub(crate) enum SupportedCircUsage { /// Isolation group the circuit is part of. None when the circuit is not yet assigned to an /// isolation group. isolation: Option<StreamIsolation>, + /// Country code the exit is in, or `None` if no country could be determined. + country_code: Option<CountryCode>, }, /// This circuit is not suitable for any usage. NoUsage, @@ -239,11 +260,17 @@ impl TargetCircUsage { let policy = path .exit_policy() .expect("ExitPathBuilder gave us a one-hop circuit?"); + #[cfg(feature = "geoip")] + let country_code = path.country_code(); + #[cfg(not(feature = "geoip"))] + let country_code = None; + Ok(( path, SupportedCircUsage::Exit { policy, isolation: None, + country_code, }, mon, usable, @@ -252,17 +279,41 @@ impl TargetCircUsage { TargetCircUsage::Exit { ports: p, isolation, + country_code, } => { - let (path, mon, usable) = ExitPathBuilder::from_target_ports(p.clone()) - .pick_path(rng, netdir, guards, config, now)?; + #[cfg(feature = "geoip")] + let builder = if let Some(cc) = country_code { + ExitPathBuilder::in_given_country(*cc, p.clone()) + } else { + ExitPathBuilder::from_target_ports(p.clone()) + }; + #[cfg(not(feature = "geoip"))] + let builder = ExitPathBuilder::from_target_ports(p.clone()); + + let (path, mon, usable) = builder.pick_path(rng, netdir, guards, config, now)?; let policy = path .exit_policy() .expect("ExitPathBuilder gave us a one-hop circuit?"); + + #[cfg(feature = "geoip")] + let resulting_cc = path.country_code(); + #[cfg(feature = "geoip")] + if resulting_cc != *country_code { + internal!( + "asked for a country code of {:?}, got {:?}", + country_code, + resulting_cc + ); + } + + #[cfg(not(feature = "geoip"))] + let resulting_cc = *country_code; // avoid unused var warning Ok(( path, SupportedCircUsage::Exit { policy, isolation: Some(isolation.clone()), + country_code: resulting_cc, }, mon, usable, @@ -272,10 +323,15 @@ impl TargetCircUsage { let (path, mon, usable) = ExitPathBuilder::for_timeout_testing() .pick_path(rng, netdir, guards, config, now)?; let policy = path.exit_policy(); + #[cfg(feature = "geoip")] + let country_code = path.country_code(); + #[cfg(not(feature = "geoip"))] + let country_code = None; let usage = match policy { Some(policy) if policy.allows_some_port() => SupportedCircUsage::Exit { policy, isolation: None, + country_code, }, _ => SupportedCircUsage::NoUsage, }; @@ -323,18 +379,26 @@ impl crate::mgr::AbstractSpec for SupportedCircUsage { Exit { policy: p1, isolation: i1, + country_code: cc1, }, TargetCircUsage::Exit { ports: p2, isolation: i2, + country_code: cc2, }, ) => { i1.as_ref() .map(|i1| i1.compatible_same_type(i2)) .unwrap_or(true) && p2.iter().all(|port| p1.allows_port(*port)) + && (cc2.is_none() || cc1 == cc2) } - (Exit { policy, isolation }, TargetCircUsage::Preemptive { port, .. }) => { + ( + Exit { + policy, isolation, .. + }, + TargetCircUsage::Preemptive { port, .. }, + ) => { if isolation.is_some() { // If the circuit has a stream isolation, we might not be able to use it // for new streams that don't share it. @@ -460,12 +524,14 @@ pub(crate) mod test { Exit { ports: p1, isolation: is1, + country_code: cc1, }, Exit { ports: p2, isolation: is2, + country_code: cc2, }, - ) => p1 == p2 && is1.isol_eq(is2), + ) => p1 == p2 && cc1 == cc2 && is1.isol_eq(is2), (TimeoutTesting, TimeoutTesting) => true, ( Preemptive { @@ -491,12 +557,14 @@ pub(crate) mod test { Exit { policy: p1, isolation: is1, + country_code: cc1, }, Exit { policy: p2, isolation: is2, + country_code: cc2, }, - ) => p1 == p2 && is1.isol_eq(is2), + ) => p1 == p2 && is1.isol_eq(is2) && cc1 == cc2, (NoUsage, NoUsage) => true, _ => false, } @@ -586,36 +654,44 @@ pub(crate) mod test { let supp_exit = SupportedCircUsage::Exit { policy: policy.clone(), isolation: Some(isolation.clone()), + country_code: None, }; let supp_exit_iso2 = SupportedCircUsage::Exit { policy: policy.clone(), isolation: Some(isolation2.clone()), + country_code: None, }; let supp_exit_no_iso = SupportedCircUsage::Exit { policy, isolation: None, + country_code: None, }; let supp_none = SupportedCircUsage::NoUsage; let targ_80_v4 = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(80)], isolation: isolation.clone(), + country_code: None, }; let targ_80_v4_iso2 = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(80)], isolation: isolation2, + country_code: None, }; let targ_80_23_v4 = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(80), TargetPort::ipv4(23)], isolation: isolation.clone(), + country_code: None, }; let targ_80_23_mixed = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(80), TargetPort::ipv6(23)], isolation: isolation.clone(), + country_code: None, }; let targ_999_v6 = TargetCircUsage::Exit { ports: vec![TargetPort::ipv6(999)], isolation, + country_code: None, }; let targ_testing = TargetCircUsage::TimeoutTesting; @@ -667,23 +743,28 @@ pub(crate) mod test { let supp_exit = SupportedCircUsage::Exit { policy: policy.clone(), isolation: Some(isolation.clone()), + country_code: None, }; let supp_exit_iso2 = SupportedCircUsage::Exit { policy: policy.clone(), isolation: Some(isolation2.clone()), + country_code: None, }; let supp_exit_no_iso = SupportedCircUsage::Exit { policy, isolation: None, + country_code: None, }; let supp_none = SupportedCircUsage::NoUsage; let targ_exit = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(80)], isolation, + country_code: None, }; let targ_exit_iso2 = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(80)], isolation: isolation2, + country_code: None, }; let targ_testing = TargetCircUsage::TimeoutTesting; @@ -771,6 +852,7 @@ pub(crate) mod test { let exit_usage = TargetCircUsage::Exit { ports: vec![TargetPort::ipv4(995)], isolation: isolation.clone(), + country_code: None, }; let (p_exit, u_exit, _, _) = exit_usage .build_path(&mut rng, di, guards, &config, now) @@ -806,7 +888,8 @@ pub(crate) mod test { usage, SupportedCircUsage::Exit { policy, - isolation: None + isolation: None, + country_code: None, } ); } diff --git a/crates/tor-dirmgr/Cargo.toml b/crates/tor-dirmgr/Cargo.toml index 57f404f89..44b696ab2 100644 --- a/crates/tor-dirmgr/Cargo.toml +++ b/crates/tor-dirmgr/Cargo.toml @@ -34,7 +34,7 @@ full = [ "tor-proto/full", "tor-rtcompat/full", ] -experimental = ["experimental-api", "dirfilter"] +experimental = ["experimental-api", "dirfilter", "geoip"] bridge-client = ["tor-circmgr/specific-relay", "tor-guardmgr/bridge-client", "routerdesc"] mmap = ["memmap2"] @@ -43,6 +43,7 @@ compression = ["tor-dirclient/xz", "tor-dirclient/zstd"] # (Incomplete) support for downloading and storing router descriptors routerdesc = ["tor-dirclient/routerdesc"] dirfilter = ["__is_experimental"] +geoip = ["tor-netdir/geoip", "tor-geoip", "__is_experimental"] # Enable experimental APIs that are not yet officially supported. # @@ -89,6 +90,7 @@ tor-config = { path = "../tor-config", version = "0.9.4" } tor-consdiff = { path = "../tor-consdiff", version = "0.5.3" } tor-dirclient = { path = "../tor-dirclient", version = "0.9.0", default-features = false } tor-error = { path = "../tor-error", version = "0.5.4", features = ["tracing"] } +tor-geoip = { path = "../tor-geoip", version = "0.1.0", optional = true } tor-guardmgr = { path = "../tor-guardmgr", version = "0.10.1" } tor-llcrypto = { path = "../tor-llcrypto", version = "0.5.4" } tor-netdir = { path = "../tor-netdir", version = "0.9.4" } diff --git a/crates/tor-dirmgr/src/state.rs b/crates/tor-dirmgr/src/state.rs index d8b9e16f1..dd6e2535e 100644 --- a/crates/tor-dirmgr/src/state.rs +++ b/crates/tor-dirmgr/src/state.rs @@ -34,6 +34,8 @@ use crate::{ }; use crate::{DocSource, SharedMutArc}; use tor_checkable::{ExternallySigned, SelfSigned, Timebound}; +#[cfg(feature = "geoip")] +use tor_geoip::GeoipDb; use tor_llcrypto::pk::rsa::RsaIdentity; use tor_netdoc::doc::{ microdesc::{MdDigest, Microdesc}, @@ -902,7 +904,13 @@ impl<R: Runtime> GetMicrodescsState<R> { let n_microdescs = consensus.relays().len(); let params = &config.override_net_params; + #[cfg(not(feature = "geoip"))] let mut partial_dir = PartialNetDir::new(consensus, Some(params)); + // TODO(eta): Make this embedded database configurable using the `DirMgrConfig`. + #[cfg(feature = "geoip")] + let mut partial_dir = + PartialNetDir::new_with_geoip(consensus, Some(params), &GeoipDb::new_embedded()); + if let Some(old_dir) = prev_netdir.as_ref().and_then(|x| x.get_netdir()) { partial_dir.fill_from_previous_netdir(old_dir); } diff --git a/crates/tor-netdir/src/lib.rs b/crates/tor-netdir/src/lib.rs index ad36aa0dc..73a3f9e81 100644 --- a/crates/tor-netdir/src/lib.rs +++ b/crates/tor-netdir/src/lib.rs @@ -670,6 +670,7 @@ impl PartialNetDir { /// This does the same thing as `new()`, except the provided GeoIP database is used to add /// country codes to relays. #[cfg(feature = "geoip")] + #[cfg_attr(docsrs, doc(cfg(feature = "geoip")))] pub fn new_with_geoip( consensus: MdConsensus, replacement_params: Option<&netstatus::NetParams<i32>>, @@ -1935,6 +1936,7 @@ impl<'a> HasAddrs for Relay<'a> { } |