1use std::collections::HashMap;
8use std::io::{self, Read, Write};
9use std::os::fd::AsRawFd;
10use std::process::{Command, Stdio};
11use std::sync::{Arc, Mutex};
12use std::thread;
13use std::time::Duration;
14
15use rns_core::transport::types::{InterfaceId, InterfaceInfo};
16
17use super::{InterfaceConfigData, InterfaceFactory, StartContext, StartResult};
18use crate::event::{Event, EventSender, LoopContext, LoopSource, SourceStatus};
19use crate::hdlc;
20use crate::interface::{lock_or_recover, Writer};
21
22#[derive(Debug, Clone)]
24pub struct PipeConfig {
25 pub name: String,
26 pub command: String,
27 pub respawn_delay: Duration,
28 pub interface_id: InterfaceId,
29 pub runtime: Arc<Mutex<PipeRuntime>>,
30}
31
32#[derive(Debug, Clone)]
33pub struct PipeRuntime {
34 pub respawn_delay: Duration,
35}
36
37impl PipeRuntime {
38 pub fn from_config(config: &PipeConfig) -> Self {
39 Self {
40 respawn_delay: config.respawn_delay,
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
46pub struct PipeRuntimeConfigHandle {
47 pub interface_name: String,
48 pub runtime: Arc<Mutex<PipeRuntime>>,
49 pub startup: PipeRuntime,
50}
51
52impl Default for PipeConfig {
53 fn default() -> Self {
54 let mut config = PipeConfig {
55 name: String::new(),
56 command: String::new(),
57 respawn_delay: Duration::from_secs(5),
58 interface_id: InterfaceId(0),
59 runtime: Arc::new(Mutex::new(PipeRuntime {
60 respawn_delay: Duration::from_secs(5),
61 })),
62 };
63 let startup = PipeRuntime::from_config(&config);
64 config.runtime = Arc::new(Mutex::new(startup));
65 config
66 }
67}
68
69struct PipeWriter {
71 stdin: std::process::ChildStdin,
72}
73
74impl Writer for PipeWriter {
75 fn send_frame(&mut self, data: &[u8]) -> io::Result<()> {
76 self.stdin.write_all(&hdlc::frame(data))
77 }
78
79 fn loop_socket(&self) -> Option<std::os::fd::RawFd> {
80 crate::engine::FOLD_IO.then(|| self.stdin.as_raw_fd())
82 }
83}
84
85pub fn start(config: PipeConfig, tx: EventSender) -> io::Result<Box<dyn Writer>> {
87 let id = config.interface_id;
88 {
89 let startup = PipeRuntime::from_config(&config);
90 *lock_or_recover(&config.runtime, "pipe runtime") = startup;
91 }
92
93 let mut child = spawn_child(&config.command)?;
94
95 let stdout = child
96 .stdout
97 .take()
98 .ok_or_else(|| io::Error::other("no stdout from child"))?;
99 let stdin = child
100 .stdin
101 .take()
102 .ok_or_else(|| io::Error::other("no stdin from child"))?;
103
104 log::info!(
105 "[{}] pipe interface started: {}",
106 config.name,
107 config.command
108 );
109
110 let _ = tx.send(Event::InterfaceUp(id, None, None));
112
113 if crate::engine::FOLD_IO {
114 Arc::new(PipeLink { config, tx }).serve(stdout, child)?;
115 return Ok(Box::new(PipeWriter { stdin }));
116 }
117
118 thread::Builder::new()
120 .name(format!("pipe-reader-{}", id.0))
121 .spawn(move || {
122 reader_loop(stdout, child, id, config, tx);
123 })?;
124
125 Ok(Box::new(PipeWriter { stdin }))
126}
127
128fn spawn_child(command: &str) -> io::Result<std::process::Child> {
129 Command::new("sh")
130 .args(["-c", command])
131 .stdin(Stdio::piped())
132 .stdout(Stdio::piped())
133 .stderr(Stdio::null())
134 .spawn()
135}
136
137fn reader_loop(
140 mut stdout: std::process::ChildStdout,
141 mut child: std::process::Child,
142 id: InterfaceId,
143 config: PipeConfig,
144 tx: EventSender,
145) {
146 let mut decoder = hdlc::Decoder::new();
147 let mut buf = [0u8; 4096];
148
149 loop {
150 match stdout.read(&mut buf) {
151 Ok(0) => {
152 let _ = child.wait();
154 log::warn!("[{}] subprocess terminated", config.name);
155 let _ = tx.send(Event::InterfaceDown(id));
156 match respawn(&config, &tx) {
157 Some((new_stdout, new_child)) => {
158 stdout = new_stdout;
159 child = new_child;
160 decoder = hdlc::Decoder::new();
161 continue;
162 }
163 None => return,
164 }
165 }
166 Ok(n) => {
167 for frame in decoder.feed(&buf[..n]) {
168 if tx
169 .send(Event::Frame {
170 interface_id: id,
171 data: frame.into(),
172 rssi: None,
173 snr: None,
174 })
175 .is_err()
176 {
177 let _ = child.kill();
179 return;
180 }
181 }
182 }
183 Err(e) => {
184 log::warn!("[{}] pipe read error: {}", config.name, e);
185 let _ = child.kill();
186 let _ = child.wait();
187 let _ = tx.send(Event::InterfaceDown(id));
188 match respawn(&config, &tx) {
189 Some((new_stdout, new_child)) => {
190 stdout = new_stdout;
191 child = new_child;
192 decoder = hdlc::Decoder::new();
193 continue;
194 }
195 None => return,
196 }
197 }
198 }
199 }
200}
201
202fn respawn(
204 config: &PipeConfig,
205 tx: &EventSender,
206) -> Option<(std::process::ChildStdout, std::process::Child)> {
207 loop {
208 let respawn_delay = lock_or_recover(&config.runtime, "pipe runtime").respawn_delay;
209 thread::sleep(respawn_delay);
210 log::info!(
211 "[{}] attempting to respawn subprocess: {}",
212 config.name,
213 config.command
214 );
215
216 match spawn_child(&config.command) {
217 Ok(mut child) => {
218 let stdout = match child.stdout.take() {
219 Some(s) => s,
220 None => {
221 let _ = child.kill();
222 let _ = child.wait();
223 continue;
224 }
225 };
226 let stdin = match child.stdin.take() {
227 Some(s) => s,
228 None => {
229 let _ = child.kill();
230 let _ = child.wait();
231 continue;
232 }
233 };
234
235 let new_writer: Box<dyn Writer> = Box::new(PipeWriter { stdin });
236 if tx
237 .send(Event::InterfaceUp(
238 config.interface_id,
239 Some(new_writer),
240 None,
241 ))
242 .is_err()
243 {
244 return None; }
246 log::info!("[{}] subprocess respawned", config.name);
247 return Some((stdout, child));
248 }
249 Err(e) => {
250 log::warn!("[{}] respawn failed: {}", config.name, e);
251 }
252 }
253 }
254}
255
256struct PipeLink {
259 config: PipeConfig,
260 tx: EventSender,
261}
262
263impl PipeLink {
264 fn serve(
266 self: &Arc<Self>,
267 stdout: std::process::ChildStdout,
268 child: std::process::Child,
269 ) -> io::Result<()> {
270 crate::interface::stream_read::set_nonblocking(stdout.as_raw_fd())?;
271 self.tx.register_source(
272 crate::interface::loop_writer::read_key(self.config.interface_id),
273 Box::new(PipeSource {
274 stdout,
275 child: Some(child),
276 decoder: hdlc::Decoder::new(),
277 link: Arc::clone(self),
278 }),
279 )
280 }
281
282 fn subprocess_lost(self: &Arc<Self>, mut child: std::process::Child, kill: bool) {
285 let id = self.config.interface_id;
286 let _ = self.tx.push_from_loop([Event::InterfaceDown(id)]);
287 let link = Arc::clone(self);
288 let spawned = thread::Builder::new()
289 .name(format!("pipe-respawn-{}", id.0))
290 .spawn(move || {
291 if kill {
292 let _ = child.kill();
293 }
294 let _ = child.wait();
295 while let Some((stdout, child)) = respawn(&link.config, &link.tx) {
296 match link.serve(stdout, child) {
297 Ok(()) => return,
298 Err(e) => {
299 log::warn!("[{}] cannot read the subprocess: {}", link.config.name, e);
300 let _ = link.tx.send(Event::InterfaceDown(id));
301 }
302 }
303 }
304 });
305 if let Err(e) = spawned {
306 log::error!(
307 "[{}] cannot start respawning, giving up: {}",
308 self.config.name,
309 e
310 );
311 }
312 }
313}
314
315struct PipeSource {
317 stdout: std::process::ChildStdout,
318 child: Option<std::process::Child>,
321 decoder: hdlc::Decoder,
322 link: Arc<PipeLink>,
323}
324
325impl LoopSource for PipeSource {
326 fn fd(&self) -> std::os::fd::RawFd {
327 self.stdout.as_raw_fd()
328 }
329
330 fn on_ready(&mut self, cx: &mut LoopContext<'_>) -> SourceStatus {
331 use crate::interface::stream_read::{read_device_frames, StreamRead};
332 let link = &self.link;
333 let name = &link.config.name;
334 let kill = match read_device_frames(
335 self.stdout.as_raw_fd(),
336 &mut self.decoder,
337 cx,
338 &link.tx,
339 link.config.interface_id,
340 name,
341 |_, _| {},
342 ) {
343 StreamRead::Rearm => return SourceStatus::Rearm,
344 StreamRead::Suspend => return SourceStatus::Suspend,
345 StreamRead::DriverGone => return SourceStatus::Remove,
346 StreamRead::Closed => {
347 log::warn!("[{}] subprocess terminated", name);
348 false
349 }
350 StreamRead::Failed(e) => {
351 log::warn!("[{}] pipe read error: {}", name, e);
352 true
353 }
354 };
355 if let Some(child) = self.child.take() {
356 link.subprocess_lost(child, kill);
357 }
358 SourceStatus::Remove
359 }
360
361 fn on_removed(&mut self) {
362 if let Some(mut child) = self.child.take() {
365 let _ = child.kill();
366 let _ = child.wait();
367 }
368 }
369}
370
371pub struct PipeFactory;
373
374impl InterfaceFactory for PipeFactory {
375 fn type_name(&self) -> &str {
376 "PipeInterface"
377 }
378
379 fn parse_config(
380 &self,
381 name: &str,
382 id: InterfaceId,
383 params: &HashMap<String, String>,
384 ) -> Result<Box<dyn InterfaceConfigData>, String> {
385 let command = params
386 .get("command")
387 .ok_or_else(|| "PipeInterface requires 'command'".to_string())?
388 .clone();
389
390 let respawn_delay = match params.get("respawn_delay") {
391 Some(v) => {
392 let ms: u64 = v
393 .parse()
394 .map_err(|_| format!("invalid respawn_delay: {}", v))?;
395 Duration::from_millis(ms)
396 }
397 None => Duration::from_secs(5),
398 };
399
400 Ok(Box::new(PipeConfig {
401 name: name.to_string(),
402 command,
403 respawn_delay,
404 interface_id: id,
405 runtime: Arc::new(Mutex::new(PipeRuntime { respawn_delay })),
406 }))
407 }
408
409 fn start(
410 &self,
411 config: Box<dyn InterfaceConfigData>,
412 ctx: StartContext,
413 ) -> io::Result<StartResult> {
414 let pipe_config = *config
415 .into_any()
416 .downcast::<PipeConfig>()
417 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "wrong config type"))?;
418
419 let id = pipe_config.interface_id;
420 let info = InterfaceInfo {
421 stamp_costs: Default::default(),
422 id,
423 name: pipe_config.name.clone(),
424 mode: ctx.mode,
425 gravity: ctx.gravity,
426 recursive_prs: ctx.recursive_prs,
427 announces_from_internal: ctx.announces_from_internal,
428 announces_to_internal: ctx.announces_to_internal,
429 out_capable: true,
430 in_capable: true,
431 bitrate: Some(1_000_000),
432 airtime_profile: None,
433 announce_rate_target: None,
434 announce_rate_grace: 0,
435 announce_rate_penalty: 0.0,
436 announce_cap: rns_core::constants::ANNOUNCE_CAP,
437 is_local_client: false,
438 wants_tunnel: false,
439 tunnel_id: None,
440 mtu: rns_core::constants::MTU as u32,
441 ingress_control: rns_core::transport::types::IngressControlConfig::disabled(),
442 ia_freq: 0.0,
443 ip_freq: 0.0,
444 op_freq: 0.0,
445 op_samples: 0,
446 started: crate::time::now(),
447 };
448
449 let writer = start(pipe_config, ctx.tx)?;
450
451 Ok(StartResult::Simple {
452 control: None,
453 id,
454 info,
455 writer,
456 interface_type_name: "PipeInterface".to_string(),
457 })
458 }
459}
460
461pub(crate) fn pipe_runtime_handle_from_config(config: &PipeConfig) -> PipeRuntimeConfigHandle {
462 PipeRuntimeConfigHandle {
463 interface_name: config.name.clone(),
464 runtime: Arc::clone(&config.runtime),
465 startup: PipeRuntime::from_config(config),
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 #[test]
474 fn pipe_start_and_receive() {
475 let (tx, rx) = crate::event::channel();
477 let config = PipeConfig {
478 name: "test-pipe".into(),
479 command: "cat".into(),
480 respawn_delay: Duration::from_secs(1),
481 interface_id: InterfaceId(100),
482 runtime: Arc::new(Mutex::new(PipeRuntime {
483 respawn_delay: Duration::from_secs(1),
484 })),
485 };
486
487 let mut writer = start(config, tx).unwrap();
488
489 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
491 assert!(matches!(
492 event,
493 Event::InterfaceUp(InterfaceId(100), None, None)
494 ));
495
496 let payload: Vec<u8> = (0..32).collect();
498 writer.send_frame(&payload).unwrap();
499
500 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
502 match event {
503 Event::Frame {
504 interface_id,
505 data,
506 rssi: _,
507 snr: _,
508 } => {
509 assert_eq!(interface_id, InterfaceId(100));
510 assert_eq!(data, payload.into());
511 }
512 other => panic!("expected Frame, got {:?}", other),
513 }
514 }
515
516 #[test]
517 fn pipe_writer_sends() {
518 let (tx, rx) = crate::event::channel();
520 let config = PipeConfig {
521 name: "test-pipe-writer".into(),
522 command: "cat".into(),
523 respawn_delay: Duration::from_secs(1),
524 interface_id: InterfaceId(101),
525 runtime: Arc::new(Mutex::new(PipeRuntime {
526 respawn_delay: Duration::from_secs(1),
527 })),
528 };
529
530 let mut writer = start(config, tx).unwrap();
531 let _ = rx.recv_timeout(Duration::from_secs(2)).unwrap(); let payload: Vec<u8> = (10..42).collect();
535 writer.send_frame(&payload).unwrap();
536
537 let event = rx.recv_timeout(Duration::from_secs(2)).unwrap();
538 match event {
539 Event::Frame { data, .. } => {
540 assert_eq!(data, payload.into());
541 }
542 other => panic!("expected Frame, got {:?}", other),
543 }
544 }
545
546 #[test]
547 fn pipe_subprocess_exit() {
548 let (tx, rx) = crate::event::channel();
550 let config = PipeConfig {
551 name: "test-pipe-exit".into(),
552 command: "true".into(), respawn_delay: Duration::from_secs(60), interface_id: InterfaceId(102),
555 runtime: Arc::new(Mutex::new(PipeRuntime {
556 respawn_delay: Duration::from_secs(60),
557 })),
558 };
559
560 let _writer = start(config, tx).unwrap();
561
562 let mut got_down = false;
564 for _ in 0..5 {
565 match rx.recv_timeout(Duration::from_secs(2)) {
566 Ok(Event::InterfaceDown(InterfaceId(102))) => {
567 got_down = true;
568 break;
569 }
570 Ok(_) => continue,
571 Err(_) => break,
572 }
573 }
574 assert!(
575 got_down,
576 "should receive InterfaceDown after subprocess exits"
577 );
578 }
579
580 #[test]
581 fn folded_pipe_runs_on_the_driver_loop_and_respawns() {
582 if !crate::engine::FOLD_IO {
583 return;
584 }
585 let (tx, rx) = crate::event::channel();
586 let config = PipeConfig {
587 name: "test-pipe-folded".into(),
588 command: "head -c 34".into(),
590 respawn_delay: Duration::from_millis(100),
591 interface_id: InterfaceId(104),
592 runtime: Arc::new(Mutex::new(PipeRuntime {
593 respawn_delay: Duration::from_millis(100),
594 })),
595 };
596 let writer = start(config, tx.clone()).unwrap();
597 assert!(writer.loop_socket().is_some());
598 let (mut writer, _) =
599 crate::interface::wrap_async_writer(writer, InterfaceId(104), "pipe", tx.clone(), 16);
600 assert!(matches!(
601 rx.recv_timeout(Duration::from_secs(2)).unwrap(),
602 Event::InterfaceUp(InterfaceId(104), None, None)
603 ));
604 let threads = || {
605 std::fs::read_dir("/proc/self/task")
606 .unwrap()
607 .filter_map(|task| std::fs::read_to_string(task.unwrap().path().join("comm")).ok())
608 .collect::<Vec<_>>()
609 };
610 assert!(!threads().iter().any(|t| t.starts_with("pipe-reader-104")));
611
612 let payload: Vec<u8> = (0..32).collect();
614 writer.send_frame(&payload).unwrap();
615 writer.flush_pending();
616 match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
617 Event::Frame { data, .. } => assert_eq!(data, payload.clone().into()),
618 other => panic!("expected Frame, got {other:?}"),
619 }
620
621 let mut down = false;
624 let mut new_writer = None;
625 let deadline = std::time::Instant::now() + Duration::from_secs(5);
626 while new_writer.is_none() && std::time::Instant::now() < deadline {
627 match rx.recv_timeout(Duration::from_millis(500)) {
628 Ok(Event::InterfaceDown(InterfaceId(104))) => down = true,
629 Ok(Event::InterfaceUp(InterfaceId(104), Some(writer), None)) => {
630 new_writer = Some(writer)
631 }
632 _ => {}
633 }
634 }
635 assert!(down);
636 let (mut writer, _) = crate::interface::wrap_async_writer(
637 new_writer.expect("respawned"),
638 InterfaceId(104),
639 "pipe",
640 tx,
641 16,
642 );
643 writer.send_frame(&payload).unwrap();
644 writer.flush_pending();
645 match rx.recv_timeout(Duration::from_secs(2)).unwrap() {
646 Event::Frame { data, .. } => assert_eq!(data, payload.into()),
647 other => panic!("expected Frame, got {other:?}"),
648 }
649 }
650
651 #[test]
652 fn pipe_config_defaults() {
653 let config = PipeConfig::default();
654 assert_eq!(config.respawn_delay, Duration::from_secs(5));
655 assert_eq!(config.interface_id, InterfaceId(0));
656 assert!(config.command.is_empty());
657 }
658
659 #[test]
660 fn pipe_invalid_command() {
661 let (tx, _rx) = crate::event::channel();
662 let config = PipeConfig {
663 name: "test-pipe-bad".into(),
664 command: "/nonexistent_rns_test_binary_that_does_not_exist_xyz".into(),
665 respawn_delay: Duration::from_secs(60),
666 interface_id: InterfaceId(103),
667 runtime: Arc::new(Mutex::new(PipeRuntime {
668 respawn_delay: Duration::from_secs(60),
669 })),
670 };
671
672 let result = start(config, tx);
677 assert!(result.is_ok());
680 }
681}