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 +60 −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,10 +204,66 @@ 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(()) } } Loading Loading @@ -252,7 +309,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 crates/arti/src/subcommands/proxy.rs +36 −13 Original line number Diff line number Diff line Loading @@ -125,32 +125,51 @@ async fn run_proxy<R: ToplevelRuntime>( ) -> Result<()> { // Using OnDemand arranges that, while we are bootstrapping, incoming connections wait // for bootstrap to complete, rather than getting errors. use arti_client::BootstrapBehavior::OnDemand; use arti_client::BootstrapBehavior; use futures::FutureExt; // TODO: We may instead want to provide a way to get these items out of TorClient. let fs_mistrust = client_config.fs_mistrust().clone(); let path_resolver: CfgPathResolver = AsRef::<CfgPathResolver>::as_ref(&client_config).clone(); let defer_bootstrap = arti_config.application().defer_bootstrap; let bootstrap_behavior = match defer_bootstrap { true => BootstrapBehavior::Manual, false => BootstrapBehavior::OnDemand, }; let client_builder = TorClient::with_runtime(runtime.clone()) .config(client_config) .bootstrap_behavior(OnDemand); .bootstrap_behavior(bootstrap_behavior); let client = client_builder.create_unbootstrapped_async().await?; let launchable_client = Arc::new(reload_cfg::LaunchableTorClient::new( Arc::clone(&client), arti_config.application(), )); #[allow(unused_mut)] let mut reconfigurable_modules: Vec<Arc<dyn reload_cfg::ReconfigurableModule>> = vec![ Arc::clone(&client) as _, Arc::clone(&launchable_client) as _, Arc::new(reload_cfg::Application::new(arti_config.clone())), ]; cfg_if::cfg_if! { if #[cfg(feature = "onion-service-service")] { let have_onion_svc = if defer_bootstrap { let onion_services = onion_proxy::ProxySet::new_deferred(Arc::clone(&client)); reconfigurable_modules.push(Arc::new(onion_services)); arti_config.onion_services.values().any(|c| *c.svc_cfg.enabled()) } else { let onion_services = onion_proxy::ProxySet::launch_new(Arc::clone(&client), arti_config.onion_services.clone())?; let launched_onion_svc = !onion_services.is_empty(); let have_onion_svc = !onion_services.is_empty(); reconfigurable_modules.push(Arc::new(onion_services)); have_onion_svc }; } else { let launched_onion_svc = false; let have_onion_svc = false; } }; Loading Loading @@ -245,7 +264,7 @@ async fn run_proxy<R: ToplevelRuntime>( } if proxy.is_empty() { if !launched_onion_svc { if !have_onion_svc { // TODO: rename "socks_listen" to "proxy_listen", preserving compat, once http-connect is stable. warn!( "No proxy address set; \ Loading Loading @@ -288,12 +307,16 @@ async fn run_proxy<R: ToplevelRuntime>( r = proxy.fuse() => r, r = async { if defer_bootstrap { info!("Bootstrapping deferred."); } else { client.bootstrap().await?; if !socks_listen.is_empty() { info!("Sufficiently bootstrapped; proxy now functional."); } else { info!("Sufficiently bootstrapped."); } } futures::future::pending::<Result<()>>().await }.fuse() => r.context("bootstrap"), Loading 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 +60 −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,10 +204,66 @@ 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(()) } } Loading Loading @@ -252,7 +309,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
crates/arti/src/subcommands/proxy.rs +36 −13 Original line number Diff line number Diff line Loading @@ -125,32 +125,51 @@ async fn run_proxy<R: ToplevelRuntime>( ) -> Result<()> { // Using OnDemand arranges that, while we are bootstrapping, incoming connections wait // for bootstrap to complete, rather than getting errors. use arti_client::BootstrapBehavior::OnDemand; use arti_client::BootstrapBehavior; use futures::FutureExt; // TODO: We may instead want to provide a way to get these items out of TorClient. let fs_mistrust = client_config.fs_mistrust().clone(); let path_resolver: CfgPathResolver = AsRef::<CfgPathResolver>::as_ref(&client_config).clone(); let defer_bootstrap = arti_config.application().defer_bootstrap; let bootstrap_behavior = match defer_bootstrap { true => BootstrapBehavior::Manual, false => BootstrapBehavior::OnDemand, }; let client_builder = TorClient::with_runtime(runtime.clone()) .config(client_config) .bootstrap_behavior(OnDemand); .bootstrap_behavior(bootstrap_behavior); let client = client_builder.create_unbootstrapped_async().await?; let launchable_client = Arc::new(reload_cfg::LaunchableTorClient::new( Arc::clone(&client), arti_config.application(), )); #[allow(unused_mut)] let mut reconfigurable_modules: Vec<Arc<dyn reload_cfg::ReconfigurableModule>> = vec![ Arc::clone(&client) as _, Arc::clone(&launchable_client) as _, Arc::new(reload_cfg::Application::new(arti_config.clone())), ]; cfg_if::cfg_if! { if #[cfg(feature = "onion-service-service")] { let have_onion_svc = if defer_bootstrap { let onion_services = onion_proxy::ProxySet::new_deferred(Arc::clone(&client)); reconfigurable_modules.push(Arc::new(onion_services)); arti_config.onion_services.values().any(|c| *c.svc_cfg.enabled()) } else { let onion_services = onion_proxy::ProxySet::launch_new(Arc::clone(&client), arti_config.onion_services.clone())?; let launched_onion_svc = !onion_services.is_empty(); let have_onion_svc = !onion_services.is_empty(); reconfigurable_modules.push(Arc::new(onion_services)); have_onion_svc }; } else { let launched_onion_svc = false; let have_onion_svc = false; } }; Loading Loading @@ -245,7 +264,7 @@ async fn run_proxy<R: ToplevelRuntime>( } if proxy.is_empty() { if !launched_onion_svc { if !have_onion_svc { // TODO: rename "socks_listen" to "proxy_listen", preserving compat, once http-connect is stable. warn!( "No proxy address set; \ Loading Loading @@ -288,12 +307,16 @@ async fn run_proxy<R: ToplevelRuntime>( r = proxy.fuse() => r, r = async { if defer_bootstrap { info!("Bootstrapping deferred."); } else { client.bootstrap().await?; if !socks_listen.is_empty() { info!("Sufficiently bootstrapped; proxy now functional."); } else { info!("Sufficiently bootstrapped."); } } futures::future::pending::<Result<()>>().await }.fuse() => r.context("bootstrap"), Loading