diff --git a/Cargo.lock b/Cargo.lock index 865fcdbbf..aab044a91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3673,6 +3673,7 @@ dependencies = [ "tor-config", "tor-error", "tor-guardmgr", + "tor-hscrypto", "tor-linkspec", "tor-llcrypto", "tor-netdir", @@ -3883,6 +3884,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "tor-hsclient" +version = "0.1.0" +dependencies = [ + "async-trait", + "rand_core 0.6.4", + "thiserror", + "tor-circmgr", + "tor-hscrypto", + "tor-llcrypto", + "tor-netdir", + "tor-proto", + "tor-rtcompat", +] + [[package]] name = "tor-hscrypto" version = "0.1.0" @@ -3892,6 +3908,21 @@ dependencies = [ "tor-llcrypto", ] +[[package]] +name = "tor-hsservice" +version = "0.1.0" +dependencies = [ + "async-trait", + "rand_core 0.6.4", + "thiserror", + "tor-circmgr", + "tor-hscrypto", + "tor-llcrypto", + "tor-netdir", + "tor-proto", + "tor-rtcompat", +] + [[package]] name = "tor-linkspec" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 2bb83d047..4c29a00c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ members = [ "crates/tor-circmgr", "crates/tor-dirclient", "crates/tor-dirmgr", + "crates/tor-hsclient", + "crates/tor-hsservice", "crates/arti-client", "crates/arti-config", "crates/arti-hyper", diff --git a/crates/tor-circmgr/Cargo.toml b/crates/tor-circmgr/Cargo.toml index 2ce9f0b4b..801f1cac5 100644 --- a/crates/tor-circmgr/Cargo.toml +++ b/crates/tor-circmgr/Cargo.toml @@ -21,8 +21,11 @@ specific-relay = [] # # These APIs are not covered by semantic versioning. Using this # feature voids your "semver warrantee". -experimental = ["experimental-api"] +experimental = ["experimental-api", "onion-client", "onion-service"] experimental-api = [] +onion-client = ["onion-common"] +onion-service = ["onion-common"] +onion-common = ["tor-hscrypto"] [dependencies] async-trait = "0.1.2" @@ -47,6 +50,7 @@ tor-chanmgr = { path = "../tor-chanmgr", version = "0.8.0" } tor-config = { path = "../tor-config", version = "0.7.0" } tor-error = { path = "../tor-error", version = "0.4.0" } tor-guardmgr = { path = "../tor-guardmgr", version = "0.8.0" } +tor-hscrypto = { path = "../tor-hscrypto", version = "0.1.0", optional = true } tor-linkspec = { path = "../tor-linkspec", version = "0.6.0" } tor-netdir = { path = "../tor-netdir", version = "0.7.0" } tor-netdoc = { path = "../tor-netdoc", version = "0.6.0" } diff --git a/crates/tor-circmgr/src/lib.rs b/crates/tor-circmgr/src/lib.rs index 44dfbeba8..8a9aa1f74 100644 --- a/crates/tor-circmgr/src/lib.rs +++ b/crates/tor-circmgr/src/lib.rs @@ -60,6 +60,8 @@ mod err; mod impls; pub mod isolation; mod mgr; +#[cfg(feature = "onion-client")] +mod onion_connector; pub mod path; mod preemptive; mod timeouts; @@ -67,6 +69,9 @@ mod usage; pub use err::Error; pub use isolation::IsolationToken; +#[cfg(feature = "onion-client")] +#[cfg_attr(docsrs, doc(cfg(feature = "onion-client")))] +pub use onion_connector::{OnionConnectError, OnionServiceConnector}; use tor_guardmgr::fallback::FallbackList; pub use tor_guardmgr::{ClockSkewEvents, GuardMgrConfig, SkewEstimate}; pub use usage::{TargetPort, TargetPorts}; @@ -206,6 +211,20 @@ impl CircMgr { Ok(circmgr) } + /// Install a given [`OnionServiceConnector`] object to be used when making + /// connections to an onion service. + /// + /// (This cannot be done at construction time, since the + /// OnionServiceConnector will have to keep a reference to this `CircMgr`.) + #[cfg(feature = "onion-client")] + #[allow(unused_variables, clippy::missing_panics_doc)] + pub fn install_onion_service_connector( + &self, + connector: &Arc, + ) -> Result<()> { + todo!() // TODO hs + } + /// Launch the periodic daemon tasks required by the manager to function properly. /// /// Returns a set of [`TaskHandle`]s that can be used to manage the daemon tasks. @@ -410,7 +429,35 @@ impl CircMgr { self.mgr.get_or_launch(&usage, netdir).await.map(|(c, _)| c) } - /// Return a circuit to a specific relay, suitable for using for directory downloads. + /// Try to connect to an onion service via this circuit manager. + /// + /// If `using_keys` is provided, then we will use those keys, in addition to + /// any configured in our `OnionServiceConnector`, to connect to the + /// service. + /// + /// If we already have an existing circuit with the appropriate isolation, + /// we will return that circuit regardless of the content of `using_keys`. + /// + /// Requires that an `OnionServiceConnector` has been installed. If it + /// hasn't, then we return an error. + #[cfg(feature = "onion-client")] + #[allow(clippy::missing_panics_doc, unused_variables)] + pub async fn get_or_launch_onion_client( + &self, + service_id: tor_hscrypto::pk::OnionId, + using_keys: Option, + isolation: StreamIsolation, + ) -> Result { + todo!() // TODO hs + + // The implementation should look up whether we have an appropriate + // connected rendezvous circuit built or in progress in our CircMgr. If + // we do, we should return it or wait for it. Otherwise we should + // delegate to our OnionServiceConnector to build it. + } + + /// Return a circuit to a specific relay, suitable for using for direct + /// (one-hop) directory downloads. /// /// This could be used, for example, to download a descriptor for a bridge. #[cfg_attr(docsrs, doc(cfg(feature = "specific-relay")))] @@ -427,6 +474,28 @@ impl CircMgr { .map(|(c, _)| c) } + /// Create and return a new (typically anonymous) circuit whose last hop is + /// `target`. + /// + /// This circuit is guaranteed not to have been used for any traffic + /// previously, and it will not be given out for any other requests in the + /// future unless explicitly re-registered with a circuit manager. + /// + /// Used to implement onion service clients and services. + #[cfg(feature = "onion-common")] + #[allow(unused_variables, clippy::missing_panics_doc)] + pub async fn launch_specific_isolated( + &self, + target: tor_linkspec::OwnedCircTarget, + // TODO hs: this should at least be an enum to define what kind of + // circuit we want, in case we have different rules for different types. + // It might also need to include a "anonymous?" flag for supporting + // single onion services. + preferences: (), + ) -> Result { + todo!() // TODO hs implement. + } + /// Launch circuits preemptively, using the preemptive circuit predictor's /// predictions. /// diff --git a/crates/tor-circmgr/src/onion_connector.rs b/crates/tor-circmgr/src/onion_connector.rs new file mode 100644 index 000000000..eb175ed74 --- /dev/null +++ b/crates/tor-circmgr/src/onion_connector.rs @@ -0,0 +1,28 @@ +//! Declare the `OnionServiceConnector` trait. + +use async_trait::async_trait; +use thiserror::Error; +use tor_proto::circuit::ClientCirc; + +/// A trait representing the ability to make a connection to an onion service. +/// +/// This is defined in `tor-circmgr`, since `tor-circmgr` uses an instance of +/// this object to connect to onion services. +#[async_trait] +pub trait OnionServiceConnector { + /// Try to launch a connection to a given onion service. + async fn create_connection( + &self, + service_id: tor_hscrypto::pk::OnionId, + using_keys: Option, + // TODO hs: If we want to support cache isolation, we may need to pass + // an additional argument here. + ) -> Result; +} + +/// An error returned when constructing an onion service. +#[derive(Debug, Clone, Error)] +#[non_exhaustive] +pub enum OnionConnectError { + // TODO hs add variants. +} diff --git a/crates/tor-hsclient/Cargo.toml b/crates/tor-hsclient/Cargo.toml new file mode 100644 index 000000000..34bb88175 --- /dev/null +++ b/crates/tor-hsclient/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tor-hsclient" +version = "0.1.0" +authors = ["The Tor Project, Inc.", "Nick Mathewson "] +edition = "2021" +rust-version = "1.60" +license = "MIT OR Apache-2.0" +homepage = "https://gitlab.torproject.org/tpo/core/arti/-/wikis/home" +description = "Arti's implementation of an onion service client" +keywords = ["tor", "arti", "cryptography"] +categories = ["cryptography"] +repository = "https://gitlab.torproject.org/tpo/core/arti.git/" + +publish = false + +[features] +default = [] + +[dependencies] +async-trait = "0.1.2" +rand_core = "0.6.2" +thiserror = "1" +tor-circmgr = { version = "0.7.0", path = "../tor-circmgr", features = ["onion-client"] } +tor-hscrypto = { version = "0.1.0", path = "../tor-hscrypto" } +tor-llcrypto = { version = "0.4.0", path = "../tor-llcrypto" } +tor-netdir = { version = "0.7.0", path = "../tor-netdir" } +tor-proto = { version = "0.8.0", path = "../tor-proto" } +tor-rtcompat = { version = "0.8.0", path = "../tor-rtcompat" } + +[dev-dependencies] diff --git a/crates/tor-hsclient/README.md b/crates/tor-hsclient/README.md new file mode 100644 index 000000000..620cc4352 --- /dev/null +++ b/crates/tor-hsclient/README.md @@ -0,0 +1,17 @@ +# tor-hsclient + +Core implementation for onion services client. + +## EXPERIMENTAL DRAFT + +This crate is a work in progress; it is not the least bit complete. + +Right now, it does not even work: it's only here so that we can prototype +our APIs. + +## ARCHITECTURAL NOTE + +This crate creates circuits to onion circuits, but does not remember them: it is +the circmgr's job to remember circuits. The tor-circmgr crate uses this module +indirectly, via a trait that it defines. + diff --git a/crates/tor-hsclient/src/keys.rs b/crates/tor-hsclient/src/keys.rs new file mode 100644 index 000000000..f189b3f6e --- /dev/null +++ b/crates/tor-hsclient/src/keys.rs @@ -0,0 +1,11 @@ +//! Manage a set of private keys for a client to authenticate to one or more +//! onion services. + +use std::{collections::HashMap, sync::Mutex}; + +use tor_hscrypto::pk::{ClientSecretKeys, OnionId}; + +pub(crate) struct Keys { + /// The + keys: Mutex>, +} diff --git a/crates/tor-hsclient/src/lib.rs b/crates/tor-hsclient/src/lib.rs new file mode 100644 index 000000000..792b41a2f --- /dev/null +++ b/crates/tor-hsclient/src/lib.rs @@ -0,0 +1,80 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] +#![doc = include_str!("../README.md")] +// TODO hs: Add complete suite of warnings here. +#![allow(dead_code, unused_variables)] // TODO hs remove. + +mod keys; +mod state; + +use async_trait::async_trait; +use std::sync::Arc; +use tor_hscrypto::pk::{ClientSecretKeys, OnionId}; +use tor_proto::circuit::ClientCirc; + +use tor_circmgr::{CircMgr, OnionConnectError, OnionServiceConnector}; +use tor_netdir::NetDirProvider; +use tor_rtcompat::Runtime; + +/// An object that negotiates connections with onion services +pub struct HsClientConnector { + /// A [`CircMgr`] that we use to build circuits to HsDirs, introduction + /// points, and rendezvous points. + // + // TODO hs: currently this is a circular set of Arc, since the CircMgr will + // have to hold an Arc. We should make one Weak. + // + // TODO hs: Maybe we can make a trait that only gives a minimal "build a + // circuit" API from CircMgr, so that we can have this be a dyn reference + // too? + circmgr: Arc>, + /// A [`NetDirProvider`] that we use to pick rendezvous points. + // + // TODO hs: Should this be weak too? + netdir_provider: Arc, + /// Information we are remembering about different onion services. + // + // TODO hs: if we implement cache isolation or state isolation, we might + // need multiple instances of this. + state: state::StateMap, + /// A collection of private keys to be used with various onion services. + // + // TODO hs: we might even want multiple instances of this, depending on how + // we decide to do isolation. + keys: keys::Keys, +} + +impl HsClientConnector { + // TODO hs: Need a way to manage the set of keys. + + // TODO hs: need a constructor here. + + // TODO hs: need a function to clear our StateMap, or to create a new + // isolated StateMap. + // + // TODO hs: Also, we need to expose that function from `TorClient`, possibly + // in the existing isolation API, possibly in something new. +} + +#[async_trait] +impl OnionServiceConnector for HsClientConnector { + async fn create_connection( + &self, + service_id: OnionId, + using_keys: Option, + ) -> Result { + todo!() // TODO hs + + // This function must do the following, retrying as appropriate. + // - Look up the onion descriptor in the state. + // - Download the onion descriptor if one isn't there. + // - In parallel: + // - Pick a rendezvous point from the netdirprovider and launch a + // rendezvous circuit to it. Then send ESTABLISH_INTRO. + // - Pick a number of introduction points (1 or more) and try to + // launch circuits to them. + // - On a circuit to an introduction point, send an INTRODUCE1 cell. + // - Wait for a RENDEZVOUS2 cell on the rendezvous circuit + // - Add a virtual hop to the rendezvous circuit. + // - Return the rendezvous circuit. + } +} diff --git a/crates/tor-hsclient/src/state.rs b/crates/tor-hsclient/src/state.rs new file mode 100644 index 000000000..f0a9bcc71 --- /dev/null +++ b/crates/tor-hsclient/src/state.rs @@ -0,0 +1,39 @@ +//! Implement a cache for onion descriptors and the facility to remember a bit +//! about onion service history. + +use std::collections::HashMap; +use std::sync::Mutex; +use std::time::SystemTime; + +use tor_hscrypto::pk::BlindedOnionId; + +/// Information about onion services and our history of connecting to them. +pub(crate) struct StateMap { + /// A map from blinded onion identity to information about an onion service. + /// + /// If the map is to `None`, then a download is in progress for that state's + /// descriptor. + members: Mutex>>, +} + +/// Information about our history of connecting to an onion service. +// +// TODO hs: We might need this to be an enum, if we want to represent "fetch +// pending" as something with a RetryDelay. We might even want a RetryDelay +// associated with each HsDir for the service as well! +pub(crate) struct State { + /// A time when we should check whether this descriptor is still the latest. + desc_fresh_until: SystemTime, + /// A time when we should expire this entry completely. + expires: SystemTime, + /// The latest known onion service descriptor for this service. + desc: (), // TODO hs: use actual onion service descriptor type. + /// Information about the latest status of trying to connect to this service + /// through each of its introduction points. + /// + ipts: (), // TODO hs: make this type real, use `RetryDelay`, etc. +} + +impl StateMap { + // TODO hs: we need a way to make the entries here expire over time. +} diff --git a/crates/tor-hscrypto/src/pk.rs b/crates/tor-hscrypto/src/pk.rs index 23333733b..cfa18fa34 100644 --- a/crates/tor-hscrypto/src/pk.rs +++ b/crates/tor-hscrypto/src/pk.rs @@ -127,7 +127,16 @@ pub struct ClientDescAuthKey(curve25519::PublicKey); // The names should be something like these: pub struct OnionIdSecretKey(ed25519::SecretKey); pub struct ClientDescAuthSecretKey(curve25519::StaticSecret); +pub struct ClientIntroAuthSecretKey(ed25519::SecretKey); // ... and so on. // // NOTE: We'll have to use ExpandedSecretKey as the secret key // for BlindedOnionIdSecretKey. + +/// A set of keys to tell the client to use when connecting to an onion service. +// +// TODO hs +pub struct ClientSecretKeys { + desc_auth: Option, + intro_auth: Option, +} diff --git a/crates/tor-hsservice/Cargo.toml b/crates/tor-hsservice/Cargo.toml new file mode 100644 index 000000000..9a02c5905 --- /dev/null +++ b/crates/tor-hsservice/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "tor-hsservice" +version = "0.1.0" +authors = ["The Tor Project, Inc.", "Nick Mathewson "] +edition = "2021" +rust-version = "1.60" +license = "MIT OR Apache-2.0" +homepage = "https://gitlab.torproject.org/tpo/core/arti/-/wikis/home" +description = "Arti's implementation of an onion service provider" +keywords = ["tor", "arti", "cryptography"] +categories = ["cryptography"] +repository = "https://gitlab.torproject.org/tpo/core/arti.git/" + +publish = false + +[features] +default = [] + +[dependencies] +async-trait = "0.1.2" +rand_core = "0.6.2" +thiserror = "1" +tor-circmgr = { version = "0.7.0", path = "../tor-circmgr", features = ["onion-client"] } +tor-hscrypto = { version = "0.1.0", path = "../tor-hscrypto" } +tor-llcrypto = { version = "0.4.0", path = "../tor-llcrypto" } +tor-netdir = { version = "0.7.0", path = "../tor-netdir" } +tor-proto = { version = "0.8.0", path = "../tor-proto" } +tor-rtcompat = { version = "0.8.0", path = "../tor-rtcompat" } + +[dev-dependencies] diff --git a/crates/tor-hsservice/README.md b/crates/tor-hsservice/README.md new file mode 100644 index 000000000..fc20ed3f2 --- /dev/null +++ b/crates/tor-hsservice/README.md @@ -0,0 +1,11 @@ +# tor-hsclient + +Core implementation for onion services in arti. + +## EXPERIMENTAL DRAFT + +This crate is a work in progress; it is not the least bit complete. + +Right now, it does not even work: it's only here so that we can prototype +our APIs. + diff --git a/crates/tor-hsservice/src/err.rs b/crates/tor-hsservice/src/err.rs new file mode 100644 index 000000000..06b7d349b --- /dev/null +++ b/crates/tor-hsservice/src/err.rs @@ -0,0 +1,8 @@ +//! Declare an error type for the `tor-hsservice` crate. + +use thiserror::Error; + +/// An error affecting the operation of an onion service. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum Error {} diff --git a/crates/tor-hsservice/src/lib.rs b/crates/tor-hsservice/src/lib.rs new file mode 100644 index 000000000..54b0488da --- /dev/null +++ b/crates/tor-hsservice/src/lib.rs @@ -0,0 +1,45 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] +#![doc = include_str!("../README.md")] +// TODO hs: Add complete suite of warnings here. +#![allow(dead_code, unused_variables)] // TODO hs remove. + +mod err; +mod status; +mod streamproxy; +mod svc; + +use async_trait::async_trait; + +pub use err::Error; +pub use status::OnionServiceStatus; +pub use svc::OnionService; + +/// A Result type describing an onion service operation. +pub type Result = std::result::Result; + +/// An object that knows how to handle stream requests. +#[async_trait] +pub trait StreamHandler { + /// Handle an incoming stream request on a given onion service. + // + // TODO hs: the `circ_info` argument should have data about the circuit on + // which the request arrived. If the client authenticated, it might tell us + // who they are. Or it might have information about how many requests + // (and/or failed requests) we've gotten on the circuit. + // + // TODO hs: The `circ_info` argument should at a minimum include the + // circuit; ideally in a form that we can get a weak reference to it, and + // use it in the key of a `PtrWeakKeyHashMap`. (Or we could stick the info + // in the circuit itself somehow, and access it as a Box, but + // that's a bit sketchy type-wise.) + // + // TODO hs: the `stream` argument should be an IncomingStream from + // tor-proto, but that branch is not yet merged as of this writing. + async fn handle_request(&self, circ_info: &(), stream: ()); +} + +mod mgr { + // TODO hs: Do we want to have the notion of a collection of onion services, + // running in tandem? Or is that a higher-level crate, possibly a part of + // TorClient? +} diff --git a/crates/tor-hsservice/src/status.rs b/crates/tor-hsservice/src/status.rs new file mode 100644 index 000000000..ed7abf2bd --- /dev/null +++ b/crates/tor-hsservice/src/status.rs @@ -0,0 +1,4 @@ +pub struct OnionServiceStatus { + // TODO hs Should say how many intro points are active, how many descriptors + // are updated, whether we're "healthy", etc. +} diff --git a/crates/tor-hsservice/src/streamproxy.rs b/crates/tor-hsservice/src/streamproxy.rs new file mode 100644 index 000000000..83e0aac57 --- /dev/null +++ b/crates/tor-hsservice/src/streamproxy.rs @@ -0,0 +1,35 @@ +//! Implement a StreamHandler that proxies connections to ports, typically on +//! localhost. + +use std::{collections::HashMap, net::SocketAddr}; + +use async_trait::async_trait; + +use crate::StreamHandler; + +pub(crate) struct StreamProxy { + /// Map from virtual port on the onion service to an address we connect to + /// in order to implement that port. + ports: HashMap, +} + +impl StreamProxy { + // TODO hs need a new() function. It should reject non-localhost addresses + // by default, and have a way to override. (Alternatively, that should be + // done in the configuration code?) +} + +#[async_trait] +impl StreamHandler for StreamProxy { + async fn handle_request(&self, circinfo: &(), stream: ()) { + todo!() // TODO hs: implement + + // - Look up the port for the incoming stream request. + // - If no port is found, reject the request, and possibly increment a + // counter in circinfo. + // - Otherwise, open a TCP connection to the target address. + // - On success, accept the stream, and launch tasks to relay traffic + // from the stream to the TCP connection. + // - On failure, reject the stream with an error. + } +} diff --git a/crates/tor-hsservice/src/svc.rs b/crates/tor-hsservice/src/svc.rs new file mode 100644 index 000000000..ea4349e27 --- /dev/null +++ b/crates/tor-hsservice/src/svc.rs @@ -0,0 +1,110 @@ +use std::sync::Arc; + +use tor_circmgr::CircMgr; +use tor_netdir::NetDirProvider; +use tor_rtcompat::Runtime; + +use crate::{OnionServiceStatus, Result}; + +/// A handle to an instance of an onion service. +// +// TODO hs: We might want to wrap this in an Arc>, and have an inner +// structure that contains these elements. Or we might want to refactor this in +// some other way. +pub struct OnionService { + /// Needs some kind of configuration about: what is our identity (if we know + /// it), is this anonymous, do we store persistent info and if so where and + /// how, etc. + /// + /// Authorized client public keys might be here, or they might be in a + /// separate structure. + config: (), + /// A netdir provider to use in finding our directories and choosing our + /// introduction points. + netdir_provider: Arc, + /// A circuit manager to use in making circuits to our introduction points, + /// HsDirs, and rendezvous points. + // TODO hs: Maybe we can make a trait that only gives a minimal "build a + // circuit" API from CircMgr, so that we can have this be a dyn reference + // too? + circmgr: Arc>, + /// Private keys in actual use for this onion service. + /// + /// TODO hs: This will need heavy refactoring. + /// + /// TODO hs: There's a separate blinded ID, certificate, and signing key + /// for each active time period. + keys: (), + /// Status for each active introduction point for this onion service. + intro_points: Vec<()>, + /// Status for our onion service descriptor + descriptor_status: (), + + /// Object that handles incoming streams from the client. + stream_handler: Arc, +} + +impl OnionService { + /// Create (but do not launch) a new onion service. + pub fn new(config: (), netdir_provider: (), circmgr: ()) -> Self { + todo!(); // TODO hs + } + + /// Change the configuration of this onion service. + /// + /// (Not everything can be changed here. At the very least we'll need to say + /// that the identity of a service is fixed. We might want to make the + /// storage backing this, and the anonymity status, unchangeable.) + pub fn reconfigure(&self, new_config: ()) -> Result<()> { + todo!() // TODO hs + } + + /// Tell this onion service about some new short-term keys it can use. + pub fn add_keys(&self, keys: ()) -> Result<()> { + todo!() // TODO hs + } + + /// Return the current status of this onion service. + pub fn status(&self) -> OnionServiceStatus { + todo!() // TODO hs + } + // TODO hs let's also have a function that gives you a stream of Status + // changes? Or use a publish-based watcher? + + /// Tell this onion service to begin running. + pub fn launch(&self) -> Result<()> { + todo!() // TODO hs + + // This needs to launch at least the following tasks: + // + // - If we decide to use separate disk-based key provisioning, a task to + // monitor our keys directory. + // - If we own our identity key, a task to generate per-period sub-keys as + // needed. + // - A task to make sure that we have enough introduction point circuits + // at all times, and launch new ones as needed. + // - A task to see whether we have an up-to-date descriptor uploaded for + // each supported time period to every HsDir listed for us in the + // current directory, and if not, regenerate and upload our descriptor + // as needed. + // - A task to receive introduction requests from our introduction + // points, decide whether to answer them, and if so launch a new + // rendezvous task to: + // - finish the cryptographic handshake + // - build a circuit to the rendezvous point + // - Send the RENDEZVOUS1 reply + // - Add a virtual hop to the rendezvous circuit + // - Launch a new task to handle BEGIN requests on the rendezvous + // circuit, using our StreamHandler. + } + + /// Tell this onion service to stop running. + /// + /// It can be restarted with launch(). + /// + /// You can also shut down an onion service completely by dropping the last + /// Clone of it. + pub fn stop(&self) -> Result<()> { + todo!() // TODO hs + } +}