Commit 7d95db3b authored by Nick Mathewson's avatar Nick Mathewson 🥔
Browse files

Merge branch 'defer_bootstrap_v1' into 'main'

arti: Configuration and RPC support for deferred bootstrapping

Closes #2547

See merge request !4056
parents 4e20d4fe a1344fc1
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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.
+4 −0
Original line number Diff line number Diff line
@@ -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
+10 −0
Original line number Diff line number Diff line
@@ -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.
@@ -559,6 +568,7 @@ mod test {
                "storage.port_info_file",
                "proxy.socket_send_buf_size",
                "proxy.socket_recv_buf_size",
                "application.defer_bootstrap",
            ],
        );

+17 −0
Original line number Diff line number Diff line
@@ -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>>,
@@ -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(())
    }
+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;
@@ -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)
@@ -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.
@@ -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