aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreta <tor@eta.st>2023-08-22 16:52:28 +0100
committereta <tor@eta.st>2023-09-18 12:25:36 +0100
commita6fe895eb5c605e9c8de7ba871fc2114d65e098c (patch)
tree75d20c7d08d7258788975127719dcf9dc1372c5d
parent9367242f956b3bae242b0b2cb0a7420020a4ccc8 (diff)
downloadarti-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.lock4
-rw-r--r--crates/arti-client/Cargo.toml3
-rw-r--r--crates/arti-client/src/client.rs41
-rw-r--r--crates/arti-client/src/lib.rs4
-rw-r--r--crates/tor-circmgr/Cargo.toml5
-rw-r--r--crates/tor-circmgr/src/err.rs5
-rw-r--r--crates/tor-circmgr/src/lib.rs13
-rw-r--r--crates/tor-circmgr/src/mgr.rs4
-rw-r--r--crates/tor-circmgr/src/path.rs13
-rw-r--r--crates/tor-circmgr/src/path/exitpath.rs55
-rw-r--r--crates/tor-circmgr/src/usage.rs95
-rw-r--r--crates/tor-dirmgr/Cargo.toml4
-rw-r--r--crates/tor-dirmgr/src/state.rs8
-rw-r--r--crates/tor-netdir/src/lib.rs2
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> {
}