diff --git a/crates/tor-netdir/Cargo.toml b/crates/tor-netdir/Cargo.toml index 9b4ce654a..f6541064d 100644 --- a/crates/tor-netdir/Cargo.toml +++ b/crates/tor-netdir/Cargo.toml @@ -14,14 +14,16 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/" [features] default = [] -experimental = ["experimental-api"] +experimental = ["experimental-api", "onion-service", "onion-client"] # Enable experimental APIs that are not yet officially supported. # # These APIs are not covered by semantic versioning. Using this # feature voids your "semver warrantee". experimental-api = [] -hs-common = ["tor-hscrypto"] +onion-client = ["onion-common"] +onion-service = ["onion-common"] +onion-common = ["tor-hscrypto"] # Enable testing-only APIs. APIs under this feature are not # covered by semver. diff --git a/crates/tor-netdir/src/err.rs b/crates/tor-netdir/src/err.rs index 831b1625f..4a745fd78 100644 --- a/crates/tor-netdir/src/err.rs +++ b/crates/tor-netdir/src/err.rs @@ -32,3 +32,26 @@ impl HasKind for Error { } } } + +/// An error returned when looking up onion service directories. +#[derive(Error, Clone, Debug)] +#[cfg(feature = "onion-common")] +#[cfg_attr(docsrs, doc(cfg(feature = "onion-common")))] +#[non_exhaustive] +pub enum OnionDirLookupError { + /// We tried to look up an onion service directory for a time period that + /// did not correspond to one of our hash rings. + #[error("Tried to look up an onion service directory for an invalid time period.")] + WrongTimePeriod, +} + +#[cfg(feature = "onion-common")] +impl HasKind for OnionDirLookupError { + fn kind(&self) -> tor_error::ErrorKind { + use tor_error::ErrorKind as EK; + use OnionDirLookupError as E; + match self { + E::WrongTimePeriod => EK::BadApiUsage, + } + } +} diff --git a/crates/tor-netdir/src/hsdir_ring.rs b/crates/tor-netdir/src/hsdir_ring.rs index f3a7f474a..f7143b302 100644 --- a/crates/tor-netdir/src/hsdir_ring.rs +++ b/crates/tor-netdir/src/hsdir_ring.rs @@ -45,7 +45,7 @@ pub(crate) struct HsDirRing { /// The time period for which the ring is valid. period: TimePeriod, - /// The shared random value for which the ring is valid. + /// The shared random value that applies to the ring. shared_rand: SharedRandVal, /// The ring itself. diff --git a/crates/tor-netdir/src/lib.rs b/crates/tor-netdir/src/lib.rs index 2e011998a..6dfca9812 100644 --- a/crates/tor-netdir/src/lib.rs +++ b/crates/tor-netdir/src/lib.rs @@ -38,7 +38,7 @@ //! mod err; -#[cfg(feature = "hs-common")] +#[cfg(feature = "onion-common")] mod hsdir_ring; pub mod params; mod weight; @@ -48,7 +48,7 @@ pub mod testnet; #[cfg(feature = "testing")] pub mod testprovider; -#[cfg(feature = "hs-common")] +#[cfg(feature = "onion-common")] use hsdir_ring::HsDirRing; use static_assertions::const_assert; use tor_linkspec::{ @@ -70,14 +70,17 @@ use std::sync::Arc; use strum::{EnumCount, EnumIter}; use tracing::warn; -#[cfg(feature = "hs-common")] -use {std::time::SystemTime, tor_hscrypto::pk::BlindedOnionId}; +#[cfg(feature = "onion-common")] +use tor_hscrypto::{pk::BlindedOnionId, time::TimePeriod}; pub use err::Error; pub use weight::WeightRole; /// A Result using the Error type from the tor-netdir crate pub type Result = std::result::Result; +#[cfg(feature = "onion-common")] +pub use err::OnionDirLookupError; + use params::NetParameters; /// Configuration for determining when two relays have addresses "too close" in @@ -214,10 +217,8 @@ impl From for RelayWeight { #[derive(Copy, Clone, Debug)] #[non_exhaustive] pub enum OnionServiceDirOp { - /// Uploading an onion service descriptor to the current ring. + /// Uploading an onion service descriptor. Upload, - /// Uploading an onion service descriptor to the previous ring. - UploadPrevious, /// Downloading an onion service descriptor. Download, } @@ -282,33 +283,49 @@ pub struct NetDir { /// /// This is empty in a PartialNetDir, and is filled in before the NetDir is /// built. + /// + /// It corresponds to the time period containing the `valid-after` time in + /// the consensus. Its SRV is whatever SRV was most current at the time when + /// that time period began. + /// + /// This is the hash ring that we should use whenever we are fetching an + /// onion service descriptor. // // TODO hs: It is ugly to have this be Option. - #[cfg(feature = "hs-common")] + #[cfg(feature = "onion-common")] #[allow(dead_code)] hsdir_ring: Option, /// A hash ring describing the onion service directory based on the - /// parameters for the previous (or next) time period. + /// parameters for the previous and next time periods. /// - /// Onion services upload to positions in this ring as well, based on how + /// Onion services upload to positions on these ring as well, based on how /// far into the current time period this directory is, so that /// not-synchronized clients can still find their descriptor. /// - /// This is None in a PartialNetDir, and None if this ring should not be - /// used. + /// Each of these rings is None in a PartialNetDir, and None if this ring + /// should not be used. + /// + /// Note that with the current (2023) network parameters, with + /// `hsdir_interval = SRV lifetime = 24 hours` at most one of these + /// secondary rings will be active at a time. We have two here in order + /// to conform with a more flexible regime in proposal 342. // - // TODO hs: It is ugly to have this be Option. + // TODO hs: It is sort of ugly to have these be Option. // - // TODO hs: hs clients never need this; maybe it could be optional? Or does - // that risk too much? + // TODO hs: hs clients never need this; so I've made it not-present for thm. + // But does that risk too much with respect to side channels? // - // TODO hs spec: Despite the confidence in the above comment, I am not sure - // that I really get how this secondary ring works. I need to re-read that - // part of the spec carefully; we might find a better way to implement this. - #[cfg(feature = "hs-common")] + // TODO hs: Perhaps we should refactor this so that there is just one + // Vec (or SmallVec<>); or so that there are `current`, `next`, + // and `previous`. + // + // TODO hs: Perhaps we should refactor this so that it is clear that these + // are immutable? On the other hand, the documentation for this type + // declares that it is immutable, so we are likely okay. + #[cfg(feature = "onion-service")] #[allow(dead_code)] - hsdir_secondary_ring: Option, + hsdir_secondary_rings: (Option, Option), /// Weight values to apply to a given relay when deciding how frequently /// to choose it for a given role. @@ -545,10 +562,10 @@ impl PartialNetDir { rs_idx_by_missing, rs_idx_by_rsa: Arc::new(rs_idx_by_rsa), rs_idx_by_ed: HashMap::with_capacity(n_relays), - #[cfg(feature = "hs-common")] + #[cfg(feature = "onion-common")] hsdir_ring: None, - #[cfg(feature = "hs-common")] - hsdir_secondary_ring: None, + #[cfg(feature = "onion-service")] + hsdir_secondary_rings: (None, None), weights, }; @@ -582,7 +599,7 @@ impl PartialNetDir { loaded } - /// Compute the hash ring for this NetDir, if one is not already computed. + /// Compute the hash ring(s) for this NetDir, if one is not already computed. #[cfg(feature = "hs-common")] #[allow(clippy::missing_panics_doc)] pub fn compute_ring(&mut self) { @@ -1080,25 +1097,44 @@ impl NetDir { }) } + /// Return the current onion service directory "time period". + /// + /// Specifically, this returns the time period that contains the beginning + /// of the validity period of this `NetDir`'s consensus. That time period + /// is the one we use when acting as an onion service client. + #[cfg(feature = "onion-common")] + #[allow(unused, clippy::missing_panics_doc)] // TODO hs: remove. + pub fn onion_service_time_period(&self) -> TimePeriod { + todo!() // TODO hs + } + + /// Return the secondary onion service directory "time periods". + /// + /// These are additional time periods that we publish descriptors for when we are + /// acting as an onion service. + #[cfg(feature = "onion-service")] + #[allow(unused, clippy::missing_panics_doc)] // TODO hs: remove. + pub fn onion_service_secondary_time_periods(&self) -> Vec { + todo!() + } + /// Return the relays in this network directory that will be used to store a - /// given onion service's descriptor at a given time. - #[cfg(feature = "hs-common")] + /// given onion service's descriptor at a given time period. + /// + /// Return an error if the time period is not one returned by + /// `onion_service_time_period` or `onion_service_secondary_time_periods`. + #[cfg(feature = "onion-common")] #[allow(unused, clippy::missing_panics_doc)] // TODO hs: remove. pub fn onion_service_dirs( &self, id: BlindedOnionId, op: OnionServiceDirOp, - when: SystemTime, - ) -> Vec> { + when: TimePeriod, + ) -> std::result::Result>, OnionDirLookupError> { // Algorithm: // - // 1. Use the time period length from our parameters to determine what - // TimePeriod contains `when`. - // 1b. If fetching, use the current ring. - // If uploading, determine whether to use the current or secondary ring - // based on the time period. - // 2. Use `when` to determine whether to use current or previous shared - // random value. + // 1. Determine which HsDirRing to use, based on the time period. + // 2. Find the shared random value that's associated with that HsDirRing. // 3. Choose spread = the parameter `hsdir_spread_store` or // `hsdir_spread_fetch` based on `op`. // 4. Let n_replicas = the parameter `hsdir_n_replicas`.