Loading crates/arti-client/src/rpc.rs +1 −1 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ impl rpc::RpcMethod for WatchClientStatus { /// Note that all `TorClient`s on a session share the same underlying bootstrap status: /// if you check the status for one, you don't need to check the others. #[derive(Serialize, Deserialize)] struct ClientStatusInfo { pub struct ClientStatusInfo { /// True if the client is ready for traffic. ready: bool, /// Approximate estimate of how close the client is to being ready for traffic. Loading crates/arti/src/arti-example-config.toml +4 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,10 @@ # mistake.) #allow_running_as_root = false # If true, then we don't connect to the external network until told to do so, # either by setting this option to `true`, or via the RPC interface. #defer_bootstrap = false # Set up the Arti program to run as a proxy. [proxy] # Default port to use when listening to SOCKS connections Loading crates/arti/src/cfg.rs +10 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,15 @@ pub(crate) struct ApplicationConfig { /// This has no effect on Windows. #[deftly(tor_config(default))] pub(crate) allow_running_as_root: bool, /// If true, then we do not bootstrap a [`TorClient`](arti_client::TorClient) on startup. /// Instead, we defer bootstrapping until _either_ this option is false, /// or until an RPC-using application tells us to bootstrap. /// /// We will still bind to proxy ports at startup, but we won't make any connections /// to the network until after we are bootstrapping. #[deftly(tor_config(default))] pub(crate) defer_bootstrap: bool, } /// Configuration for one or more proxy listeners. Loading Loading @@ -559,6 +568,7 @@ mod test { "storage.port_info_file", "proxy.socket_send_buf_size", "proxy.socket_recv_buf_size", "application.defer_bootstrap", ], ); Loading crates/arti/src/onion_proxy.rs +17 −0 Original line number Diff line number Diff line Loading @@ -277,6 +277,17 @@ pub(crate) struct ProxySet<R: Runtime> { } impl<R: Runtime> ProxySet<R> { /// Create a new empty onion service proxy set. /// /// We do this when we are running with deferred bootstrapping, /// since we can't launch an onion service on an unbootstrapped client. pub(crate) fn new_deferred(client: Arc<arti_client::TorClient<R>>) -> Self { Self { client, proxies: Mutex::new(BTreeMap::new()), } } /// Create and launch a set of onion service proxies. pub(crate) fn launch_new( client: Arc<arti_client::TorClient<R>>, Loading Loading @@ -372,6 +383,12 @@ impl<R: Runtime> ProxySet<R> { impl<R: Runtime> crate::reload_cfg::ReconfigurableModule for ProxySet<R> { fn reconfigure(&self, new: &crate::ArtiCombinedConfig) -> anyhow::Result<()> { if new.0.application().defer_bootstrap { // Do not actually launch any onion services unless we are trying // to bootstrap the client. return Ok(()); } ProxySet::reconfigure(self, new.0.onion_services.clone())?; Ok(()) } Loading crates/arti/src/reload_cfg.rs +68 −4 Original line number Diff line number Diff line //! Code to watch configuration files for any changes. use std::sync::Weak; use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; use anyhow::Context; Loading Loading @@ -164,6 +164,7 @@ async fn reload_configuration<R: Runtime>( modules: &[Weak<dyn ReconfigurableModule>], tx: FileEventSender, ) -> anyhow::Result<Option<FileWatcher>> { // TODO RPC: Take 'how' as an argument. let found_files = if watcher.is_some() { let mut new_watcher = FileWatcher::builder(runtime.clone()); let found_files = prepare(&mut new_watcher, sources) Loading Loading @@ -203,14 +204,78 @@ async fn reload_configuration<R: Runtime>( Ok(watcher) } impl<R: Runtime> ReconfigurableModule for TorClient<R> { /// A TorClient that we may or may not have told to start bootstrapping. pub(crate) struct LaunchableTorClient<R: Runtime> { /// Original value of defer_bootstrap. orig_defer_bootstrap: bool, /// True if we have launched bootstrapping on the the client. have_launched: Mutex<bool>, /// The client itself. client: Arc<TorClient<R>>, } impl<R: Runtime> ReconfigurableModule for LaunchableTorClient<R> { #[instrument(level = "trace", skip_all)] fn reconfigure(&self, new: &ArtiCombinedConfig) -> anyhow::Result<()> { TorClient::reconfigure(self, &new.1, Reconfigure::WarnOnFailures)?; // TODO RPC: Take 'how' as an argument. if new.0.application().defer_bootstrap && !self.orig_defer_bootstrap { warn!("Cannot enable defer_bootstrap while arti is running."); } if !new.0.application().defer_bootstrap { self.ensure_bootstrap_launched()?; } TorClient::reconfigure(&self.client, &new.1, Reconfigure::WarnOnFailures)?; Ok(()) } } impl<R: Runtime> LaunchableTorClient<R> { /// Create a new LaunchableTorClient. /// /// We assume that it has (or has not) been told to bootstrap itself based on `cfg`. pub(crate) fn new(client: Arc<TorClient<R>>, cfg: &crate::ApplicationConfig) -> Self { Self { orig_defer_bootstrap: cfg.defer_bootstrap, have_launched: Mutex::new(!cfg.defer_bootstrap), client, } } /// If we have not already told this LaunchableTorClient to bootstrap itself, do so. fn ensure_bootstrap_launched(&self) -> anyhow::Result<()> { let mut have_launched = self.have_launched.lock().expect("lock poisoned"); if *have_launched { return Ok(()); } let client = Arc::clone(&self.client); // We spawn this as a new task since `bootstrap` is very much async, // but this needs to be called from `reconfigure`, which is not. self.client .runtime() .spawn(async move { let _outcome = client.bootstrap().await; }) .context("Launching bootstrap")?; *have_launched = true; Ok(()) } /// As [`TorClient::bootstrap`], but performs necessary bookkeeping to remember /// that we have launched a bootstrap attempt. pub(crate) async fn bootstrap(&self) -> arti_client::Result<()> { *self.have_launched.lock().expect("lock poisoned") = true; self.client.bootstrap().await } } /// Internal type to represent the Arti application as a `ReconfigurableModule`. pub(crate) struct Application { /// The configuration that Arti had at startup. Loading Loading @@ -252,7 +317,6 @@ impl ReconfigurableModule for Application { if config.application().permit_debugging && !original.application().permit_debugging { warn!("Cannot disable application hardening when it has already been enabled."); } // Note that this is the only config transition we actually perform so far. if !config.application().permit_debugging { #[cfg(feature = "harden")] Loading Loading
crates/arti-client/src/rpc.rs +1 −1 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ impl rpc::RpcMethod for WatchClientStatus { /// Note that all `TorClient`s on a session share the same underlying bootstrap status: /// if you check the status for one, you don't need to check the others. #[derive(Serialize, Deserialize)] struct ClientStatusInfo { pub struct ClientStatusInfo { /// True if the client is ready for traffic. ready: bool, /// Approximate estimate of how close the client is to being ready for traffic. Loading
crates/arti/src/arti-example-config.toml +4 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,10 @@ # mistake.) #allow_running_as_root = false # If true, then we don't connect to the external network until told to do so, # either by setting this option to `true`, or via the RPC interface. #defer_bootstrap = false # Set up the Arti program to run as a proxy. [proxy] # Default port to use when listening to SOCKS connections Loading
crates/arti/src/cfg.rs +10 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,15 @@ pub(crate) struct ApplicationConfig { /// This has no effect on Windows. #[deftly(tor_config(default))] pub(crate) allow_running_as_root: bool, /// If true, then we do not bootstrap a [`TorClient`](arti_client::TorClient) on startup. /// Instead, we defer bootstrapping until _either_ this option is false, /// or until an RPC-using application tells us to bootstrap. /// /// We will still bind to proxy ports at startup, but we won't make any connections /// to the network until after we are bootstrapping. #[deftly(tor_config(default))] pub(crate) defer_bootstrap: bool, } /// Configuration for one or more proxy listeners. Loading Loading @@ -559,6 +568,7 @@ mod test { "storage.port_info_file", "proxy.socket_send_buf_size", "proxy.socket_recv_buf_size", "application.defer_bootstrap", ], ); Loading
crates/arti/src/onion_proxy.rs +17 −0 Original line number Diff line number Diff line Loading @@ -277,6 +277,17 @@ pub(crate) struct ProxySet<R: Runtime> { } impl<R: Runtime> ProxySet<R> { /// Create a new empty onion service proxy set. /// /// We do this when we are running with deferred bootstrapping, /// since we can't launch an onion service on an unbootstrapped client. pub(crate) fn new_deferred(client: Arc<arti_client::TorClient<R>>) -> Self { Self { client, proxies: Mutex::new(BTreeMap::new()), } } /// Create and launch a set of onion service proxies. pub(crate) fn launch_new( client: Arc<arti_client::TorClient<R>>, Loading Loading @@ -372,6 +383,12 @@ impl<R: Runtime> ProxySet<R> { impl<R: Runtime> crate::reload_cfg::ReconfigurableModule for ProxySet<R> { fn reconfigure(&self, new: &crate::ArtiCombinedConfig) -> anyhow::Result<()> { if new.0.application().defer_bootstrap { // Do not actually launch any onion services unless we are trying // to bootstrap the client. return Ok(()); } ProxySet::reconfigure(self, new.0.onion_services.clone())?; Ok(()) } Loading
crates/arti/src/reload_cfg.rs +68 −4 Original line number Diff line number Diff line //! Code to watch configuration files for any changes. use std::sync::Weak; use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; use anyhow::Context; Loading Loading @@ -164,6 +164,7 @@ async fn reload_configuration<R: Runtime>( modules: &[Weak<dyn ReconfigurableModule>], tx: FileEventSender, ) -> anyhow::Result<Option<FileWatcher>> { // TODO RPC: Take 'how' as an argument. let found_files = if watcher.is_some() { let mut new_watcher = FileWatcher::builder(runtime.clone()); let found_files = prepare(&mut new_watcher, sources) Loading Loading @@ -203,14 +204,78 @@ async fn reload_configuration<R: Runtime>( Ok(watcher) } impl<R: Runtime> ReconfigurableModule for TorClient<R> { /// A TorClient that we may or may not have told to start bootstrapping. pub(crate) struct LaunchableTorClient<R: Runtime> { /// Original value of defer_bootstrap. orig_defer_bootstrap: bool, /// True if we have launched bootstrapping on the the client. have_launched: Mutex<bool>, /// The client itself. client: Arc<TorClient<R>>, } impl<R: Runtime> ReconfigurableModule for LaunchableTorClient<R> { #[instrument(level = "trace", skip_all)] fn reconfigure(&self, new: &ArtiCombinedConfig) -> anyhow::Result<()> { TorClient::reconfigure(self, &new.1, Reconfigure::WarnOnFailures)?; // TODO RPC: Take 'how' as an argument. if new.0.application().defer_bootstrap && !self.orig_defer_bootstrap { warn!("Cannot enable defer_bootstrap while arti is running."); } if !new.0.application().defer_bootstrap { self.ensure_bootstrap_launched()?; } TorClient::reconfigure(&self.client, &new.1, Reconfigure::WarnOnFailures)?; Ok(()) } } impl<R: Runtime> LaunchableTorClient<R> { /// Create a new LaunchableTorClient. /// /// We assume that it has (or has not) been told to bootstrap itself based on `cfg`. pub(crate) fn new(client: Arc<TorClient<R>>, cfg: &crate::ApplicationConfig) -> Self { Self { orig_defer_bootstrap: cfg.defer_bootstrap, have_launched: Mutex::new(!cfg.defer_bootstrap), client, } } /// If we have not already told this LaunchableTorClient to bootstrap itself, do so. fn ensure_bootstrap_launched(&self) -> anyhow::Result<()> { let mut have_launched = self.have_launched.lock().expect("lock poisoned"); if *have_launched { return Ok(()); } let client = Arc::clone(&self.client); // We spawn this as a new task since `bootstrap` is very much async, // but this needs to be called from `reconfigure`, which is not. self.client .runtime() .spawn(async move { let _outcome = client.bootstrap().await; }) .context("Launching bootstrap")?; *have_launched = true; Ok(()) } /// As [`TorClient::bootstrap`], but performs necessary bookkeeping to remember /// that we have launched a bootstrap attempt. pub(crate) async fn bootstrap(&self) -> arti_client::Result<()> { *self.have_launched.lock().expect("lock poisoned") = true; self.client.bootstrap().await } } /// Internal type to represent the Arti application as a `ReconfigurableModule`. pub(crate) struct Application { /// The configuration that Arti had at startup. Loading Loading @@ -252,7 +317,6 @@ impl ReconfigurableModule for Application { if config.application().permit_debugging && !original.application().permit_debugging { warn!("Cannot disable application hardening when it has already been enabled."); } // Note that this is the only config transition we actually perform so far. if !config.application().permit_debugging { #[cfg(feature = "harden")] Loading