Skip to main content

arti/rpc/
superuser.rs

1//! Administrative RPC functionality.
2//!
3//! In general, RPC function is "administrative", and requires superuser access,
4//! whenever it can affect other applications.
5//!
6//! This is not a perfect sandbox: applications can _always_ interfere with one another's traffic by
7//! consuming resources (like bandwidth or CPU) in a way that introduces side channels.
8
9use arti_client::{TorClient, rpc::ClientStatusInfo};
10use derive_deftly::Deftly;
11use futures::{FutureExt as _, SinkExt as _, StreamExt as _, select_biased};
12use std::sync::Arc;
13use tor_rpcbase::{self as rpc};
14use tor_rtcompat::Runtime;
15
16use crate::reload_cfg::{CfgMgr, LaunchableTorClient};
17
18/// An object representing superuser access to Arti over an RPC session.
19///
20/// In general, RPC function is "administrative", and requires superuser access,
21/// whenever it can affect other applications.
22#[derive(Deftly)]
23#[derive_deftly(rpc::Object)]
24pub(super) struct RpcSuperuser<R: Runtime> {
25    /// A view of the underlying TorClient managed by this RpcSuperuser object.
26    tor_client: Arc<TorClient<R>>,
27
28    /// A wrapper around `tor_client` with the ability to launch a deferred-bootstrap client.
29    launchable: Arc<LaunchableTorClient<R>>,
30
31    /// A handle to the manager for configuration information.
32    pub(super) cfg_mgr: Arc<CfgMgr<R>>,
33}
34
35impl<R: Runtime> RpcSuperuser<R> {
36    /// Construct a new RpcSuperuser object.
37    pub(super) fn new(
38        tor_client: Arc<TorClient<R>>,
39        launchable: Arc<LaunchableTorClient<R>>,
40        cfg_mgr: Arc<CfgMgr<R>>,
41    ) -> Self {
42        RpcSuperuser {
43            tor_client,
44            launchable,
45            cfg_mgr,
46        }
47    }
48
49    /// Ensure that every RPC method is registered for this instantiation of TorClient.
50    ///
51    /// We can't use [`rpc::static_rpc_invoke_fn`] for these, since TorClient is
52    /// parameterized.
53    pub(super) fn rpc_methods() -> Vec<rpc::dispatch::InvokerEnt> {
54        rpc::invoker_ent_list![
55            enter_dormant_mode_on_rpcsuperuser::<R>,
56            bootstrap_client_on_rpcsuperuser::<R>,
57            crate::rpc::configuration::set_config_on_rpcsuperuser::<R>,
58            crate::rpc::configuration::get_config_on_rpcsuperuser::<R>,
59        ]
60    }
61}
62
63/// Enter "dormant mode".
64///
65/// Currently, the only available dormant mode is "soft dormant mode",
66/// which suspends most background operations until any client request
67/// is received.
68///
69/// Since this method affects all applications using the Arti process,
70/// it requires administrative permissions.
71///
72/// ## Limitations
73///
74/// As of 2026 March, this functionality is not perfectly implemented,
75/// and likely does not interact well with onion services.
76/// Additionally, there are likely background operations that
77/// this operation doesn't cover.
78///
79/// This method returns a reply immediately, but it may take a little
80/// while before all of the background tasks finish their work and stop.
81#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
82#[derive_deftly(rpc::DynMethod)]
83#[deftly(rpc(method_name = "arti:enter_dormant_mode"))]
84struct EnterDormantMode {}
85
86impl rpc::RpcMethod for EnterDormantMode {
87    type Output = rpc::Nil;
88    type Update = rpc::NoUpdates;
89}
90
91/// Implementation for [`EnterDormantMode`] on [`RpcSuperuser`].
92async fn enter_dormant_mode_on_rpcsuperuser<R: Runtime>(
93    session: Arc<RpcSuperuser<R>>,
94    _method: Box<EnterDormantMode>,
95    _ctx: Arc<dyn rpc::Context>,
96) -> Result<rpc::Nil, rpc::RpcError> {
97    use arti_client::DormantMode;
98    session.tor_client.set_dormant(DormantMode::Soft);
99    Ok(rpc::Nil::default())
100}
101
102/// Tell a client to connect to the network and bootstrap itself.
103///
104/// There is no need to invoke this method unless
105/// was started with the `application.defer_bootstrap` option set to true.
106/// By default, clients will automatically connect to the network and bootstrap
107/// themselves.
108///
109/// Since this method affects all applications using the Arti process,
110/// it requires administrative permissions.  We may someday relax this
111/// property.
112#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
113#[derive_deftly(rpc::DynMethod)]
114#[deftly(rpc(method_name = "arti:bootstrap_client"))]
115struct BootstrapClient {}
116
117impl rpc::RpcMethod for BootstrapClient {
118    type Output = rpc::Nil;
119    type Update = ClientStatusInfo;
120}
121
122/// Implementation for [`BootstrapClient`] on [`RpcSuperuser`].
123async fn bootstrap_client_on_rpcsuperuser<R: Runtime>(
124    session: Arc<RpcSuperuser<R>>,
125    _method: Box<BootstrapClient>,
126    _ctx: Arc<dyn rpc::Context>,
127    mut updates: rpc::UpdateSink<ClientStatusInfo>,
128) -> Result<rpc::Nil, rpc::RpcError> {
129    let mut events = session.tor_client.bootstrap_events().fuse();
130    // Send the initial status unconditionally.
131    updates
132        .send(session.tor_client.bootstrap_status().into())
133        .await?;
134
135    let mut bootstrap = Box::pin(session.launchable.bootstrap()).fuse();
136
137    loop {
138        select_biased! {
139            outcome = bootstrap => {
140                 let () = outcome?;
141                 return Ok(rpc::Nil::default());
142            }
143            e = events.next() => {
144                // (If this returns None, then the `outcome` is about to fail.)
145                if let Some(e) = e {
146                    let status = e.into();
147                    let _ignore_failure = updates.send(status).await;
148                }
149            }
150        };
151    }
152}