1use crate::config::{ManagedTransportOptions, TransportOptions};
4use crate::err;
5use crate::err::PtError;
6use crate::ipc::{
7 PluggableClientTransport, PluggableTransport, PtClientParameters, PtCommonParameters,
8 sealed::PluggableTransportPrivate,
9};
10use crate::{PtClientMethod, PtSharedState};
11use futures::channel::mpsc::UnboundedReceiver;
12use futures::stream::FuturesUnordered;
13use futures::{FutureExt, StreamExt, select};
14use oneshot_fused_workaround as oneshot;
15use std::collections::{HashMap, HashSet};
16use std::future::Future;
17use std::path::{Path, PathBuf};
18use std::pin::Pin;
19use std::sync::{Arc, RwLock};
20use tor_chanmgr::ProxyProtocol;
21use tor_config_path::CfgPathResolver;
22use tor_error::internal;
23use tor_linkspec::PtTransportName;
24use tor_rtcompat::Runtime;
25use tracing::{debug, warn};
26
27pub(crate) enum PtReactorMessage {
29 Reconfigured,
31 Spawn {
33 pt: PtTransportName,
35 result: oneshot::Sender<err::Result<PtClientMethod>>,
37 },
38}
39
40type SpawnResult = (Vec<PtTransportName>, err::Result<PluggableClientTransport>);
42
43pub(crate) struct PtReactor<R> {
45 rt: R,
47 running: Vec<PluggableClientTransport>,
49 requests: HashMap<PtTransportName, Vec<oneshot::Sender<err::Result<PtClientMethod>>>>,
54 spawning: FuturesUnordered<Pin<Box<dyn Future<Output = SpawnResult> + Send>>>,
58 state: Arc<RwLock<PtSharedState>>,
60 rx: UnboundedReceiver<PtReactorMessage>,
64 state_dir: PathBuf,
66 path_resolver: Arc<CfgPathResolver>,
68}
69
70impl<R: Runtime> PtReactor<R> {
71 pub(crate) fn new(
73 rt: R,
74 state: Arc<RwLock<PtSharedState>>,
75 rx: UnboundedReceiver<PtReactorMessage>,
76 state_dir: PathBuf,
77 path_resolver: Arc<CfgPathResolver>,
78 ) -> Self {
79 let spawning = FuturesUnordered::new();
80 spawning.push(Box::pin(futures::future::pending::<SpawnResult>())
81 as Pin<Box<dyn Future<Output = _> + Send>>);
82 Self {
83 rt,
84 running: vec![],
85 requests: Default::default(),
86 spawning,
87 state,
88 rx,
89 state_dir,
90 path_resolver,
91 }
92 }
93
94 #[allow(clippy::needless_pass_by_value)]
96 fn handle_spawned(
97 &mut self,
98 covers: Vec<PtTransportName>,
99 result: err::Result<PluggableClientTransport>,
100 ) {
101 match result {
102 Err(e) => {
103 warn!("Spawning PT for {:?} failed: {}", covers, e);
104 let senders = covers
106 .iter()
107 .flat_map(|x| self.requests.remove(x))
108 .flatten();
109 for sender in senders {
110 let _ = sender.send(Err(e.clone()));
112 }
113 }
114 Ok(pt) => {
115 let mut state = self.state.write().expect("ptmgr state poisoned");
116 for (transport, method) in pt.transport_methods() {
117 state
118 .managed_cmethods
119 .insert(transport.clone(), method.clone());
120 for sender in self.requests.remove(transport).into_iter().flatten() {
121 let _ = sender.send(Ok(method.clone()));
122 }
123 }
124
125 let requested: HashSet<_> = covers.iter().collect();
126 let found: HashSet<_> = pt.transport_methods().keys().collect();
127 if requested != found {
128 warn!(
129 "Bug: PT {} succeeded, but did not give the same transports we asked for. ({:?} vs {:?})",
130 pt.identifier(),
131 found,
132 requested
133 );
134 }
135 self.running.push(pt);
136 }
137 }
138 }
139
140 fn remove_pt(&self, pt: PluggableClientTransport) {
142 let mut state = self.state.write().expect("ptmgr state poisoned");
143 for transport in pt.transport_methods().keys() {
144 state.managed_cmethods.remove(transport);
145 }
146 drop(pt);
149 }
150
151 pub(crate) async fn run_one_step(&mut self) -> err::Result<bool> {
153 use futures::future::Either;
154
155 let mut all_next_messages = self
156 .running
157 .iter_mut()
158 .map(|pt| pt.next_message())
159 .collect::<Vec<_>>();
160
161 let mut next_message = if all_next_messages.is_empty() {
163 Either::Left(futures::future::pending())
164 } else {
165 Either::Right(futures::future::select_all(all_next_messages.iter_mut()).fuse())
166 };
167
168 select! {
169 (result, idx, _) = next_message => {
170 drop(all_next_messages); match result {
173 Ok(m) => {
174 debug!("PT {} message: {:?}", self.running[idx].identifier(), m);
176 },
177 Err(e) => {
178 warn!("PT {} quit: {:?}", self.running[idx].identifier(), e);
179 let pt = self.running.remove(idx);
180 self.remove_pt(pt);
181 }
182 }
183 },
184 spawn_result = self.spawning.next() => {
185 drop(all_next_messages);
186 let (covers, result) = spawn_result.expect("self.spawning should never dry up");
188 self.handle_spawned(covers, result);
189 }
190 internal = self.rx.next() => {
191 drop(all_next_messages);
192
193 match internal {
194 Some(PtReactorMessage::Reconfigured) => {},
195 Some(PtReactorMessage::Spawn { pt, result }) => {
196 if let Some(requests) = self.requests.get_mut(&pt) {
198 requests.push(result);
199 return Ok(false);
200 }
201 for rpt in self.running.iter() {
203 if let Some(cmethod) = rpt.transport_methods().get(&pt) {
204 let _ = result.send(Ok(cmethod.clone()));
205 return Ok(false);
206 }
207 }
208 let (config, outbound_proxy) = {
210 let state = self.state.read().expect("ptmgr state poisoned");
211 (state.configured.get(&pt).cloned(), state.outbound_proxy.clone())
212 };
213
214 let Some(config) = config else {
215 let _ = result.send(Err(PtError::UnconfiguredTransportDueToConcurrentReconfiguration));
216 return Ok(false);
217 };
218
219 let TransportOptions::Managed(config) = config else {
220 let _ = result.send(Err(internal!("Tried to spawn an unmanaged transport").into()));
221 return Ok(false);
222 };
223
224 self.requests.entry(pt).or_default().push(result);
227 for proto in config.protocols.iter() {
228 self.requests.entry(proto.clone()).or_default();
229 }
230
231 let spawn_fut = Box::pin(
233 spawn_from_config(
234 self.rt.clone(),
235 self.state_dir.clone(),
236 config.clone(),
237 Arc::clone(&self.path_resolver),
238 outbound_proxy,
239 )
240 .map(|result| (config.protocols, result))
241 );
242 self.spawning.push(spawn_fut);
243 },
244 None => return Ok(true)
245 }
246 }
247 }
248 Ok(false)
249 }
250}
251
252async fn spawn_from_config<R: Runtime>(
254 rt: R,
255 state_dir: PathBuf,
256 cfg: ManagedTransportOptions,
257 path_resolver: Arc<CfgPathResolver>,
258 outbound_proxy: Option<ProxyProtocol>,
259) -> Result<PluggableClientTransport, PtError> {
260 let cfg_path = cfg.path;
263
264 let binary_path = cfg_path
265 .path(&path_resolver)
266 .map_err(|e| PtError::PathExpansionFailed {
267 path: cfg_path.clone(),
268 error: e,
269 })?;
270
271 let filename = pt_identifier_as_path(&binary_path)?;
272
273 let new_state_dir = state_dir.join(filename);
276 std::fs::create_dir_all(&new_state_dir).map_err(|e| PtError::StatedirCreateFailed {
277 path: new_state_dir.clone(),
278 error: Arc::new(e),
279 })?;
280
281 let pt_common_params = PtCommonParameters::builder()
283 .state_location(new_state_dir)
284 .build()
285 .expect("PtCommonParameters constructed incorrectly");
286
287 let pt_client_params = PtClientParameters::builder()
290 .transports(cfg.protocols)
291 .proxy_uri(outbound_proxy.as_ref().map(ToString::to_string))
292 .build()
293 .expect("PtClientParameters constructed incorrectly");
294
295 let mut pt = PluggableClientTransport::new(
296 binary_path,
297 cfg.arguments,
298 pt_common_params,
299 pt_client_params,
300 );
301 pt.launch(rt).await?;
302 Ok(pt)
303}
304
305fn pt_identifier_as_path(binary_path: impl AsRef<Path>) -> Result<PathBuf, PtError> {
308 let mut filename =
310 PathBuf::from(
311 binary_path
312 .as_ref()
313 .file_name()
314 .ok_or_else(|| PtError::NotAFile {
315 path: binary_path.as_ref().to_path_buf(),
316 })?,
317 );
318
319 if let Some(ext) = filename.extension() {
321 if ext.eq_ignore_ascii_case(std::env::consts::EXE_EXTENSION) {
322 filename.set_extension("");
323 }
324 }
325
326 Ok(filename)
327}
328
329pub(crate) fn pt_identifier(binary_path: impl AsRef<Path>) -> Result<String, PtError> {
332 Ok(pt_identifier_as_path(binary_path)?
333 .to_string_lossy()
334 .to_string())
335}