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::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
32impl<R: Runtime> RpcSuperuser<R> {
33 /// Construct a new RpcSuperuser object.
34 pub(super) fn new(
35 tor_client: Arc<TorClient<R>>,
36 launchable: Arc<LaunchableTorClient<R>>,
37 ) -> Self {
38 RpcSuperuser {
39 tor_client,
40 launchable,
41 }
42 }
43
44 /// Ensure that every RPC method is registered for this instantiation of TorClient.
45 ///
46 /// We can't use [`rpc::static_rpc_invoke_fn`] for these, since TorClient is
47 /// parameterized.
48 pub(super) fn rpc_methods() -> Vec<rpc::dispatch::InvokerEnt> {
49 rpc::invoker_ent_list![
50 enter_dormant_mode_on_rpcsuperuser::<R>,
51 bootstrap_client_on_rpcsuperuser::<R>,
52 ]
53 }
54}
55
56/// Enter "dormant mode".
57///
58/// Currently, the only available dormant mode is "soft dormant mode",
59/// which suspends most background operations until any client request
60/// is received.
61///
62/// Since this method affects all applications using the Arti process,
63/// it requires administrative permissions.
64///
65/// ## Limitations
66///
67/// As of 2026 March, this functionality is not perfectly implemented,
68/// and likely does not interact well with onion services.
69/// Additionally, there are likely background operations that
70/// this operation doesn't cover.
71///
72/// This method returns a reply immediately, but it may take a little
73/// while before all of the background tasks finish their work and stop.
74#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
75#[derive_deftly(rpc::DynMethod)]
76#[deftly(rpc(method_name = "arti:enter_dormant_mode"))]
77struct EnterDormantMode {}
78
79impl rpc::RpcMethod for EnterDormantMode {
80 type Output = rpc::Nil;
81 type Update = rpc::NoUpdates;
82}
83
84/// Implementation for [`EnterDormantMode`] on [`RpcSuperuser`].
85async fn enter_dormant_mode_on_rpcsuperuser<R: Runtime>(
86 session: Arc<RpcSuperuser<R>>,
87 _method: Box<EnterDormantMode>,
88 _ctx: Arc<dyn rpc::Context>,
89) -> Result<rpc::Nil, rpc::RpcError> {
90 use arti_client::DormantMode;
91 session.tor_client.set_dormant(DormantMode::Soft);
92 Ok(rpc::Nil::default())
93}
94
95/// Tell a client to connect to the network and bootstrap itself.
96///
97/// There is no need to invoke this method unless
98/// was started with the `application.defer_bootstrap` option set to true.
99/// By default, clients will automatically connect to the network and bootstrap
100/// themselves.
101///
102/// Since this method affects all applications using the Arti process,
103/// it requires administrative permissions. We may someday relax this
104/// property.
105#[derive(Debug, serde::Deserialize, serde::Serialize, Deftly)]
106#[derive_deftly(rpc::DynMethod)]
107#[deftly(rpc(method_name = "arti:bootstrap_client"))]
108struct BootstrapClient {}
109
110impl rpc::RpcMethod for BootstrapClient {
111 type Output = rpc::Nil;
112 type Update = ClientStatusInfo;
113}
114
115/// Implementation for [`BootstrapClient`] on [`RpcSuperuser`].
116async fn bootstrap_client_on_rpcsuperuser<R: Runtime>(
117 session: Arc<RpcSuperuser<R>>,
118 _method: Box<BootstrapClient>,
119 _ctx: Arc<dyn rpc::Context>,
120 mut updates: rpc::UpdateSink<ClientStatusInfo>,
121) -> Result<rpc::Nil, rpc::RpcError> {
122 let mut events = session.tor_client.bootstrap_events().fuse();
123 // Send the initial status unconditionally.
124 updates
125 .send(session.tor_client.bootstrap_status().into())
126 .await?;
127
128 let mut bootstrap = Box::pin(session.launchable.bootstrap()).fuse();
129
130 loop {
131 select_biased! {
132 outcome = bootstrap => {
133 let () = outcome?;
134 return Ok(rpc::Nil::default());
135 }
136 e = events.next() => {
137 // (If this returns None, then the `outcome` is about to fail.)
138 if let Some(e) = e {
139 let status = e.into();
140 let _ignore_failure = updates.send(status).await;
141 }
142 }
143 };
144 }
145}