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 #[cfg_attr(not(feature = "tor-channel-factory"), allow(dead_code))]
33 Spawn {
34 pt: PtTransportName,
36 result: oneshot::Sender<err::Result<PtClientMethod>>,
38 },
39}
40
41type SpawnResult = (Vec<PtTransportName>, err::Result<PluggableClientTransport>);
43
44pub(crate) struct PtReactor<R> {
46 rt: R,
48 running: Vec<PluggableClientTransport>,
50 requests: HashMap<PtTransportName, Vec<oneshot::Sender<err::Result<PtClientMethod>>>>,
55 spawning: FuturesUnordered<Pin<Box<dyn Future<Output = SpawnResult> + Send>>>,
59 state: Arc<RwLock<PtSharedState>>,
61 rx: UnboundedReceiver<PtReactorMessage>,
65 state_dir: PathBuf,
67 path_resolver: Arc<CfgPathResolver>,
69}
70
71impl<R: Runtime> PtReactor<R> {
72 pub(crate) fn new(
74 rt: R,
75 state: Arc<RwLock<PtSharedState>>,
76 rx: UnboundedReceiver<PtReactorMessage>,
77 state_dir: PathBuf,
78 path_resolver: Arc<CfgPathResolver>,
79 ) -> Self {
80 let spawning = FuturesUnordered::new();
81 spawning.push(Box::pin(futures::future::pending::<SpawnResult>())
82 as Pin<Box<dyn Future<Output = _> + Send>>);
83 Self {
84 rt,
85 running: vec![],
86 requests: Default::default(),
87 spawning,
88 state,
89 rx,
90 state_dir,
91 path_resolver,
92 }
93 }
94
95 #[allow(clippy::needless_pass_by_value)]
97 fn handle_spawned(
98 &mut self,
99 covers: Vec<PtTransportName>,
100 result: err::Result<PluggableClientTransport>,
101 ) {
102 match result {
103 Err(e) => {
104 warn!("Spawning PT for {:?} failed: {}", covers, e);
105 let senders = covers
107 .iter()
108 .flat_map(|x| self.requests.remove(x))
109 .flatten();
110 for sender in senders {
111 let _ = sender.send(Err(e.clone()));
113 }
114 }
115 Ok(pt) => {
116 let mut state = self.state.write().expect("ptmgr state poisoned");
117 for (transport, method) in pt.transport_methods() {
118 state
119 .managed_cmethods
120 .insert(transport.clone(), method.clone());
121 for sender in self.requests.remove(transport).into_iter().flatten() {
122 let _ = sender.send(Ok(method.clone()));
123 }
124 }
125
126 let requested: HashSet<_> = covers.iter().collect();
127 let found: HashSet<_> = pt.transport_methods().keys().collect();
128 if requested != found {
129 warn!(
130 "Bug: PT {} succeeded, but did not give the same transports we asked for. ({:?} vs {:?})",
131 pt.identifier(),
132 found,
133 requested
134 );
135 }
136 self.running.push(pt);
137 }
138 }
139 }
140
141 fn remove_pt(&self, pt: PluggableClientTransport) {
143 let mut state = self.state.write().expect("ptmgr state poisoned");
144 for transport in pt.transport_methods().keys() {
145 state.managed_cmethods.remove(transport);
146 }
147 drop(pt);
150 }
151
152 pub(crate) async fn run_one_step(&mut self) -> err::Result<bool> {
154 use futures::future::Either;
155
156 let mut all_next_messages = self
157 .running
158 .iter_mut()
159 .map(|pt| pt.next_message())
160 .collect::<Vec<_>>();
161
162 let mut next_message = if all_next_messages.is_empty() {
164 Either::Left(futures::future::pending())
165 } else {
166 Either::Right(futures::future::select_all(all_next_messages.iter_mut()).fuse())
167 };
168
169 select! {
170 (result, idx, _) = next_message => {
171 drop(all_next_messages); match result {
174 Ok(m) => {
175 debug!("PT {} message: {:?}", self.running[idx].identifier(), m);
177 },
178 Err(e) => {
179 warn!("PT {} quit: {:?}", self.running[idx].identifier(), e);
180 let pt = self.running.remove(idx);
181 self.remove_pt(pt);
182 }
183 }
184 },
185 spawn_result = self.spawning.next() => {
186 drop(all_next_messages);
187 let (covers, result) = spawn_result.expect("self.spawning should never dry up");
189 self.handle_spawned(covers, result);
190 }
191 internal = self.rx.next() => {
192 drop(all_next_messages);
193
194 match internal {
195 Some(PtReactorMessage::Reconfigured) => {},
196 Some(PtReactorMessage::Spawn { pt, result }) => {
197 if let Some(requests) = self.requests.get_mut(&pt) {
199 requests.push(result);
200 return Ok(false);
201 }
202 for rpt in self.running.iter() {
204 if let Some(cmethod) = rpt.transport_methods().get(&pt) {
205 let _ = result.send(Ok(cmethod.clone()));
206 return Ok(false);
207 }
208 }
209 let (config, outbound_proxy) = {
211 let state = self.state.read().expect("ptmgr state poisoned");
212 (state.configured.get(&pt).cloned(), state.outbound_proxy.clone())
213 };
214
215 let Some(config) = config else {
216 let _ = result.send(Err(PtError::UnconfiguredTransportDueToConcurrentReconfiguration));
217 return Ok(false);
218 };
219
220 let TransportOptions::Managed(config) = config else {
221 let _ = result.send(Err(internal!("Tried to spawn an unmanaged transport").into()));
222 return Ok(false);
223 };
224
225 self.requests.entry(pt).or_default().push(result);
228 for proto in config.protocols.iter() {
229 self.requests.entry(proto.clone()).or_default();
230 }
231
232 let spawn_fut = Box::pin(
234 spawn_from_config(
235 self.rt.clone(),
236 self.state_dir.clone(),
237 config.clone(),
238 Arc::clone(&self.path_resolver),
239 outbound_proxy,
240 )
241 .map(|result| (config.protocols, result))
242 );
243 self.spawning.push(spawn_fut);
244 },
245 None => return Ok(true)
246 }
247 }
248 }
249 Ok(false)
250 }
251}
252
253async fn spawn_from_config<R: Runtime>(
255 rt: R,
256 state_dir: PathBuf,
257 cfg: ManagedTransportOptions,
258 path_resolver: Arc<CfgPathResolver>,
259 outbound_proxy: Option<ProxyProtocol>,
260) -> Result<PluggableClientTransport, PtError> {
261 let cfg_path = cfg.path;
264
265 let binary_path = cfg_path
266 .path(&path_resolver)
267 .map_err(|e| PtError::PathExpansionFailed {
268 path: cfg_path.clone(),
269 error: e,
270 })?;
271
272 let filename = pt_identifier_as_path(&binary_path)?;
273
274 let new_state_dir = state_dir.join(filename);
277 std::fs::create_dir_all(&new_state_dir).map_err(|e| PtError::StatedirCreateFailed {
278 path: new_state_dir.clone(),
279 error: Arc::new(e),
280 })?;
281
282 let pt_common_params = PtCommonParameters::builder()
284 .state_location(new_state_dir)
285 .build()
286 .expect("PtCommonParameters constructed incorrectly");
287
288 let pt_client_params = PtClientParameters::builder()
291 .transports(cfg.protocols)
292 .proxy_uri(outbound_proxy.as_ref().map(ToString::to_string))
293 .build()
294 .expect("PtClientParameters constructed incorrectly");
295
296 let mut pt = PluggableClientTransport::new(
297 binary_path,
298 cfg.arguments,
299 pt_common_params,
300 pt_client_params,
301 );
302 pt.launch(rt).await?;
303 Ok(pt)
304}
305
306fn pt_identifier_as_path(binary_path: impl AsRef<Path>) -> Result<PathBuf, PtError> {
309 let mut filename =
311 PathBuf::from(
312 binary_path
313 .as_ref()
314 .file_name()
315 .ok_or_else(|| PtError::NotAFile {
316 path: binary_path.as_ref().to_path_buf(),
317 })?,
318 );
319
320 if let Some(ext) = filename.extension() {
322 if ext.eq_ignore_ascii_case(std::env::consts::EXE_EXTENSION) {
323 filename.set_extension("");
324 }
325 }
326
327 Ok(filename)
328}
329
330pub(crate) fn pt_identifier(binary_path: impl AsRef<Path>) -> Result<String, PtError> {
333 Ok(pt_identifier_as_path(binary_path)?
334 .to_string_lossy()
335 .to_string())
336}