1use anyhow::Result;
4use arti_rpcserver::RpcMgr;
5use derive_deftly::Deftly;
6use fs_mistrust::Mistrust;
7use futures::{AsyncReadExt, stream::StreamExt};
8use session::ArtiRpcSession;
9use std::collections::BTreeMap;
10use std::{io::Result as IoResult, sync::Arc};
11use tor_config::derive::prelude::*;
12use tor_config_path::CfgPathResolver;
13use tracing::{debug, info};
14
15use arti_client::TorClient;
16use tor_rtcompat::{NetStreamListener as _, Runtime, SpawnExt, general};
17
18pub(crate) mod conntarget;
19pub(crate) mod listener;
20mod proxyinfo;
21mod session;
22mod superuser;
23
24use listener::RpcListenerSetConfig;
25pub(crate) use session::{RpcStateSender, RpcVisibleArtiState};
26
27use crate::reload_cfg::LaunchableTorClient;
28use crate::rpc::superuser::RpcSuperuser;
29
30#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
34#[derive_deftly(TorConfig)]
35#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
36#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))]
37pub(crate) struct RpcConfig {
38 #[deftly(tor_config(default = "false"))] enable: bool,
41
42 #[deftly(tor_config(map, default = "listener::listener_map_defaults()"))]
44 listen: BTreeMap<String, RpcListenerSetConfig>,
45
46 #[deftly(tor_config(list(element(clone)), default = "listen_defaults_defaults()"))]
49 listen_default: Vec<String>,
50}
51
52fn listen_defaults_defaults() -> Vec<String> {
54 vec![tor_rpc_connect::USER_DEFAULT_CONNECT_POINT.to_string()]
55}
56
57type IncomingConn = (
61 general::Stream,
62 general::SocketAddr,
63 Arc<listener::RpcConnInfo>,
64);
65
66#[allow(clippy::cognitive_complexity)] async fn launch_all_listeners<R: Runtime>(
71 runtime: &R,
72 cfg: &RpcConfig,
73 resolver: &CfgPathResolver,
74 mistrust: &Mistrust,
75) -> anyhow::Result<(
76 impl futures::Stream<Item = IoResult<IncomingConn>> + Unpin + use<R>,
77 Vec<tor_rpc_connect::server::ListenerGuard>,
78)> {
79 let mut listeners = Vec::new();
80 let mut guards = Vec::new();
81 for (name, listener_cfg) in cfg.listen.iter() {
82 for (lis, info, guard) in listener_cfg
83 .bind(runtime, name.as_str(), resolver, mistrust)
84 .await?
85 {
86 debug!(
88 "Listening at {} for {}",
89 lis.local_addr()
90 .expect("general::listener without address?")
91 .display_lossy(),
92 info.name,
93 );
94 listeners.push((lis, info));
95 guards.push(guard);
96 }
97 }
98 if listeners.is_empty() {
99 for (idx, connpt) in cfg.listen_default.iter().enumerate() {
100 let display_index = idx + 1; let (lis, info, guard) =
102 listener::bind_string(connpt, display_index, runtime, resolver, mistrust).await?;
103 debug!(
104 "Listening at {} for {}",
105 lis.local_addr()
106 .expect("general::listener without address?")
107 .display_lossy(),
108 info.name,
109 );
110 listeners.push((lis, info));
111 guards.push(guard);
112 }
113 }
114 if listeners.is_empty() {
115 info!("No RPC listeners configured.");
116 }
117
118 let streams = listeners.into_iter().map(|(listener, info)| {
119 listener
120 .incoming()
121 .map(move |accept_result| match accept_result {
122 Ok((netstream, addr)) => Ok((netstream, addr, Arc::clone(&info))),
123 Err(e) => Err(e),
124 })
125 });
126
127 Ok((futures::stream::select_all(streams), guards))
128}
129
130pub(crate) async fn launch_rpc_mgr<R: Runtime>(
133 runtime: &R,
134 cfg: &RpcConfig,
135 resolver: &CfgPathResolver,
136 mistrust: &Mistrust,
137 client: Arc<TorClient<R>>,
138 launchable: Arc<LaunchableTorClient<R>>,
139) -> Result<Option<RpcProxySupport>> {
140 if !cfg.enable {
141 return Ok(None);
142 }
143 let (rpc_state, rpc_state_sender) = RpcVisibleArtiState::new();
144
145 let rpc_mgr = RpcMgr::new()?;
146 rpc_mgr.register_rpc_methods(TorClient::<R>::rpc_methods());
150 rpc_mgr.register_rpc_methods(arti_rpcserver::rpc_methods::<R>());
151 rpc_mgr.register_rpc_methods(RpcSuperuser::<R>::rpc_methods());
152
153 let rt_clone = runtime.clone();
154 let rpc_mgr_clone = rpc_mgr.clone();
155
156 let (incoming, guards) = launch_all_listeners(runtime, cfg, resolver, mistrust).await?;
157
158 runtime.spawn(async move {
162 let result = run_rpc_listener(
163 rt_clone,
164 incoming,
165 rpc_mgr_clone,
166 client,
167 launchable,
168 rpc_state,
169 )
170 .await;
171 if let Err(e) = result {
172 tracing::warn!("RPC manager quit with an error: {}", e);
173 }
174 drop(guards);
175 })?;
176 Ok(Some(RpcProxySupport {
177 rpc_mgr,
178 rpc_state_sender,
179 }))
180}
181
182async fn run_rpc_listener<R: Runtime>(
184 runtime: R,
185 mut incoming: impl futures::Stream<Item = IoResult<IncomingConn>> + Unpin,
186 rpc_mgr: Arc<RpcMgr>,
187 client: Arc<TorClient<R>>,
188 launchable: Arc<LaunchableTorClient<R>>,
189 rpc_state: Arc<RpcVisibleArtiState>,
190) -> Result<()> {
191 while let Some((stream, _addr, info)) = incoming.next().await.transpose()? {
192 debug!("Received incoming RPC connection from {}", &info.name);
193
194 let client_clone = client.clone();
195 let rpc_state_clone = rpc_state.clone();
196 let launchable = launchable.clone();
197 let connection = rpc_mgr.new_connection(info.auth.clone(), move |auth| {
198 ArtiRpcSession::new(auth, &client_clone, &launchable, &rpc_state_clone, &info) as _
199 });
200 let (input, output) = stream.split();
201
202 runtime.spawn(async {
203 let result = connection.run(input, output).await;
204 if let Err(e) = result {
205 tracing::warn!("RPC session ended with an error: {}", e);
206 }
207 })?;
208 }
209 Ok(())
210}
211
212pub(crate) struct RpcProxySupport {
214 pub(crate) rpc_mgr: Arc<arti_rpcserver::RpcMgr>,
216 pub(crate) rpc_state_sender: RpcStateSender,
218}
219
220#[cfg(test)]
221mod test {
222 #![allow(clippy::bool_assert_comparison)]
224 #![allow(clippy::clone_on_copy)]
225 #![allow(clippy::dbg_macro)]
226 #![allow(clippy::mixed_attributes_style)]
227 #![allow(clippy::print_stderr)]
228 #![allow(clippy::print_stdout)]
229 #![allow(clippy::single_char_pattern)]
230 #![allow(clippy::unwrap_used)]
231 #![allow(clippy::unchecked_time_subtraction)]
232 #![allow(clippy::useless_vec)]
233 #![allow(clippy::needless_pass_by_value)]
234 #![allow(clippy::string_slice)] use listener::{ConnectPointOptionsBuilder, RpcListenerSetConfigBuilder};
238 use tor_config_path::CfgPath;
239 use tor_rpc_connect::ParsedConnectPoint;
240
241 use super::*;
242
243 #[test]
244 fn rpc_method_names() {
245 let problems = tor_rpcbase::check_method_names([]);
248
249 for (m, err) in &problems {
250 eprintln!("Bad method name {m:?}: {err}");
251 }
252 assert!(problems.is_empty());
253 }
254
255 #[test]
256 fn parse_listener_defaults() {
257 for string in listen_defaults_defaults() {
258 let _parsed: ParsedConnectPoint = string.parse().unwrap();
259 }
260 }
261
262 #[test]
263 fn parsing_and_building() {
264 fn build(s: &str) -> Result<RpcConfig, anyhow::Error> {
265 let b: RpcConfigBuilder = toml::from_str(s)?;
266 Ok(b.build()?)
267 }
268
269 let mut user_defaults_builder = RpcListenerSetConfigBuilder::default();
270 user_defaults_builder.listener_options().enable(true);
271 user_defaults_builder.dir(CfgPath::new("${ARTI_LOCAL_DATA}/rpc/connect.d".to_string()));
272 let mut system_defaults_builder = RpcListenerSetConfigBuilder::default();
273 system_defaults_builder.listener_options().enable(false);
274 system_defaults_builder.dir(CfgPath::new("/etc/arti-rpc/connect.d".to_string()));
275
276 let defaults = build("").unwrap();
278 assert_eq!(
279 defaults,
280 RpcConfig {
281 enable: false,
282 listen: vec![
283 (
284 "user-default".to_string(),
285 user_defaults_builder.build().unwrap()
286 ),
287 (
288 "system-default".to_string(),
289 system_defaults_builder.build().unwrap()
290 ),
291 ]
292 .into_iter()
293 .collect(),
294 listen_default: listen_defaults_defaults()
295 }
296 );
297
298 let altered = build(
300 r#"
301[listen."user-default"]
302enable = false
303[listen."system-default"]
304dir = "/usr/local/etc/arti-rpc/connect.d"
305file_options = { "tmp.toml" = { "enable" = false } }
306[listen."my-connpt"]
307file = "/home/dante/.paradiso/connpt.toml"
308"#,
309 )
310 .unwrap();
311 let mut altered_user_defaults = user_defaults_builder.clone();
312 altered_user_defaults.listener_options().enable(false);
313 let mut altered_system_defaults = system_defaults_builder.clone();
314 altered_system_defaults.dir(CfgPath::new(
315 "/usr/local/etc/arti-rpc/connect.d".to_string(),
316 ));
317 let mut opt = ConnectPointOptionsBuilder::default();
318 opt.enable(false);
319 altered_system_defaults
320 .file_options()
321 .insert("tmp.toml".to_string(), opt);
322 let mut my_connpt = RpcListenerSetConfigBuilder::default();
323 my_connpt.file(CfgPath::new(
324 "/home/dante/.paradiso/connpt.toml".to_string(),
325 ));
326
327 assert_eq!(
328 altered,
329 RpcConfig {
330 enable: false,
331 listen: vec![
332 (
333 "user-default".to_string(),
334 altered_user_defaults.build().unwrap()
335 ),
336 (
337 "system-default".to_string(),
338 altered_system_defaults.build().unwrap()
339 ),
340 ("my-connpt".to_string(), my_connpt.build().unwrap()),
341 ]
342 .into_iter()
343 .collect(),
344 listen_default: listen_defaults_defaults()
345 }
346 );
347 }
348}