Adjust onion service ring APIs in light of prop#342

* It is the NetDir's responsibility to tell the caller what the time
  period is.
* There can be up to two secondary time periods.
* Each time period has a single SRV.
* Secondary time periods only apply for onion services, when they
  publish.
* When publishing, the correct input is a time period.
This commit is contained in:
Nick Mathewson 2023-01-10 09:23:42 -05:00
parent f24f8d295c
commit 5b74ef7af9
4 changed files with 99 additions and 38 deletions

View File

@ -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.

View File

@ -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,
}
}
}

View File

@ -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.

View File

@ -38,7 +38,7 @@
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
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<T> = std::result::Result<T, Error>;
#[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<u64> 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<HsDirRing>,
/// 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<HsDirRing> (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<HsDirRing>,
hsdir_secondary_rings: (Option<HsDirRing>, Option<HsDirRing>),
/// 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<TimePeriod> {
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<Relay<'_>> {
when: TimePeriod,
) -> std::result::Result<Vec<Relay<'_>>, 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`.