1#![deny(missing_docs)]
40#![deny(warnings)]
41
42use std::collections::HashSet;
43use std::fmt::Display;
44use std::fs::File;
45use std::io::{self, Stdout, Write};
46
47use clap::{Arg, ArgGroup, ArgMatches, Command};
48#[cfg(feature = "ctrl-pipe")]
49use futures::future;
50#[cfg(feature = "async-api")]
51use futures::{channel::mpsc::Receiver, stream::StreamExt};
52use log::{debug, warn};
53#[cfg(feature = "async-api")]
54use pcap_file::pcap::Packet;
55use pcap_file::pcap::{PcapHeader, PcapWriter};
56
57mod error;
58pub use crate::error::ExtcapError;
59
60mod iface;
61pub use crate::iface::IFace;
62
63mod arg;
64pub use crate::arg::{IfArg, IfArgType, IfArgVal};
65
66mod control;
67pub use crate::control::{ButtonRole, Control, ControlType, ControlVal};
68
69#[cfg(feature = "ctrl-pipe")]
70mod control_pipe;
71#[cfg(feature = "ctrl-pipe")]
72use crate::control_pipe::ControlPipe;
73#[cfg(feature = "ctrl-pipe")]
74pub use crate::control_pipe::{ControlCmd, ControlMsg, CtrlPipes};
75
76#[cfg(feature = "ctrl-pipe")]
77mod control_pipe_runtime;
78
79const OPT_EXTCAP_VERSION: &str = "extcap-version";
80const OPT_EXTCAP_INTERFACES: &str = "extcap-interfaces";
81const OPT_EXTCAP_INTERFACE: &str = "extcap-interface";
82const OPT_EXTCAP_DTLS: &str = "extcap-dlts";
83const OPT_EXTCAP_CONFIG: &str = "extcap-config";
84const OPT_EXTCAP_RELOAD_OPTION: &str = "extcap-reload-option";
85const OPT_CAPTURE: &str = "capture";
86const OPT_EXTCAP_CAPTURE_FILTER: &str = "extcap-capture-filter";
87const OPT_FIFO: &str = "fifo";
88const OPT_EXTCAP_CONTROL_IN: &str = "extcap-control-in";
89const OPT_EXTCAP_CONTROL_OUT: &str = "extcap-control-out";
90const OPT_DEBUG: &str = "debug";
91const OPT_DEBUG_FILE: &str = "debug-file";
92
93fn print_opt_value<T: Display>(name: &str, value: &Option<T>) {
94 if let Some(val) = value {
95 print!("{{{}={}}}", name, val);
96 }
97}
98
99pub enum ExtcapWriter {
101 EWStdout(Stdout),
103 EWFile(File),
105}
106
107impl Write for ExtcapWriter {
108 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
109 match self {
110 ExtcapWriter::EWStdout(sout) => sout.write(buf),
111 ExtcapWriter::EWFile(file) => file.write(buf),
112 }
113 }
114
115 fn flush(&mut self) -> io::Result<()> {
116 match self {
117 ExtcapWriter::EWStdout(sout) => sout.flush(),
118 ExtcapWriter::EWFile(file) => file.flush(),
119 }
120 }
121}
122
123#[cfg(feature = "ctrl-pipe")]
124fn create_control_pipe(ctrl_in: &str, ctrl_out: &str) -> io::Result<ControlPipe> {
125 Ok(ControlPipe::new(
126 File::open(ctrl_in)?,
127 File::create(ctrl_out)?,
128 ))
129}
130
131fn create_pcap_writer(fifo: &str, pcap_header: PcapHeader) -> io::Result<PcapWriter<ExtcapWriter>> {
132 let writer = if fifo == "-" {
133 ExtcapWriter::EWStdout(io::stdout())
134 } else {
135 ExtcapWriter::EWFile(File::create(fifo)?)
136 };
137 PcapWriter::with_header(pcap_header, writer)
138 .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e)))
139}
140
141pub type ExtcapResult<T> = Result<T, ExtcapError>;
143
144#[cfg(feature = "async-api")]
146pub type ExtcapReceiver = Receiver<Packet<'static>>;
147
148pub trait ExtcapListener {
150 fn init_log(&mut self, _extcap: &Extcap, _debug: bool, _debug_file: Option<&str>) {}
152
153 fn update_interfaces(&mut self, _extcap: &mut Extcap) {}
155
156 fn reload_option(
158 &mut self,
159 _extcap: &Extcap,
160 _ifc: &IFace,
161 _arg: &IfArg,
162 ) -> Option<Vec<IfArgVal>> {
163 None
164 }
165
166 fn capture_header(&mut self, extcap: &Extcap, ifc: &IFace) -> PcapHeader;
168
169 fn capture(
171 &mut self,
172 _extcap: &Extcap,
173 _ifc: &IFace,
174 _pcap_writer: PcapWriter<ExtcapWriter>,
175 ) -> ExtcapResult<()> {
176 unimplemented!()
177 }
178
179 #[cfg(feature = "async-api")]
181 fn capture_async(&mut self, _extcap: &Extcap, _ifc: &IFace) -> ExtcapResult<ExtcapReceiver> {
182 unimplemented!()
183 }
184
185 #[cfg(feature = "ctrl-pipe")]
187 fn capture_async_with_ctrl(
188 &mut self,
189 extcap: &Extcap,
190 ifc: &IFace,
191 _ctrl_pipes: Option<CtrlPipes>,
192 ) -> ExtcapResult<ExtcapReceiver> {
193 self.capture_async(extcap, ifc)
194 }
195}
196
197#[derive(Debug)]
199pub enum ExtcapStep {
200 None,
202 QueryIfaces,
204 QueryDlts,
206 ConfigIface {
208 reload: bool,
210 },
211 Capture {
213 ctrl_pipe: bool,
215 },
216}
217
218impl Default for ExtcapStep {
219 fn default() -> Self {
220 ExtcapStep::None
221 }
222}
223
224enum TillCaptureOutcome<T> {
225 Finish(T),
226 Capture { ifidx: usize },
227}
228
229type TillCaptureResult<T> = Result<TillCaptureOutcome<T>, ExtcapError>;
230
231#[derive(Default)]
233pub struct Extcap<'a> {
234 step: ExtcapStep,
235 app: Option<Command<'a>>,
236 app_args: HashSet<String>, matches: Option<ArgMatches>,
238 version: Option<String>,
239 helppage: Option<String>,
240 ws_version: Option<String>,
241 interfaces: Vec<IFace<'a>>,
242 reload_opt: bool,
243 ifc_debug: bool,
244 control: bool,
245 controls: Vec<Control>,
246}
247
248impl<'a> Extcap<'a> {
249 pub fn new(name: &'a str) -> Self {
251 let app = Command::new(name)
252 .allow_negative_numbers(true)
253 .arg(
255 Arg::new(OPT_EXTCAP_VERSION)
256 .long(OPT_EXTCAP_VERSION)
257 .help("Wireshark version")
258 .takes_value(true)
259 .value_name("ver"),
260 )
261 .arg(
262 Arg::new(OPT_EXTCAP_INTERFACES)
263 .long(OPT_EXTCAP_INTERFACES)
264 .help("List the extcap Interfaces"),
265 )
266 .arg(
267 Arg::new(OPT_EXTCAP_INTERFACE)
268 .long(OPT_EXTCAP_INTERFACE)
269 .help("Specify the extcap interface")
270 .takes_value(true)
271 .value_name("iface")
272 .conflicts_with(OPT_EXTCAP_INTERFACES),
273 )
274 .arg(
275 Arg::new(OPT_EXTCAP_DTLS)
276 .long(OPT_EXTCAP_DTLS)
277 .help("List the DLTs"),
278 )
279 .arg(
280 Arg::new(OPT_EXTCAP_CONFIG)
281 .long(OPT_EXTCAP_CONFIG)
282 .help("List the additional configuration for an interface"),
283 )
284 .arg(
285 Arg::new(OPT_CAPTURE)
286 .long(OPT_CAPTURE)
287 .help("Run the capture")
288 .requires(OPT_FIFO),
289 )
290 .group(
291 ArgGroup::new("if_action")
292 .args(&[OPT_EXTCAP_DTLS, OPT_EXTCAP_CONFIG, OPT_CAPTURE])
293 .multiple(false)
294 .requires(OPT_EXTCAP_INTERFACE),
295 )
296 .arg(
297 Arg::new(OPT_EXTCAP_CAPTURE_FILTER)
298 .long(OPT_EXTCAP_CAPTURE_FILTER)
299 .help("The capture filter")
300 .takes_value(true)
301 .value_name("filter")
302 .requires(OPT_CAPTURE),
303 )
304 .arg(
305 Arg::new(OPT_FIFO)
306 .long(OPT_FIFO)
307 .help("Dump data to file or fifo")
308 .takes_value(true)
309 .value_name("file")
310 .requires(OPT_CAPTURE),
311 );
312
313 Self {
314 app: Some(app),
315 ..Default::default()
316 }
317 }
318
319 pub fn get_step(&self) -> &ExtcapStep {
321 &self.step
322 }
323
324 fn take_app(&mut self) -> Command<'a> {
325 self.app.take().expect("Extcap invalid state: already run")
326 }
327
328 fn update_app<F>(&mut self, f: F)
329 where
330 F: FnOnce(Command<'a>) -> Command<'a>,
331 {
332 self.app = Some(f(self.take_app()));
333 }
334
335 fn app_arg(&mut self, arg: Arg<'a>) {
336 self.app = Some(self.take_app().arg(arg));
337 }
338
339 pub fn get_matches(&self) -> &ArgMatches {
341 self.matches
342 .as_ref()
343 .expect("Extcap invalid state: not run yet")
344 }
345
346 pub fn version(&mut self, ver: &'a str) {
348 self.version = Some(ver.to_owned());
349 self.update_app(|a| a.version(ver));
350 }
351
352 pub fn help(&mut self, helppage: &'a str) {
354 self.helppage = Some(String::from(helppage));
355 }
356
357 pub fn author(&mut self, author: &'a str) {
359 self.update_app(|a| a.author(author));
360 }
361
362 pub fn about(&mut self, about: &'a str) {
364 self.update_app(|a| a.about(about));
365 }
366
367 pub fn usage(&mut self, usage: &'a str) {
369 self.update_app(|a| a.override_usage(usage));
370 }
371
372 pub fn after_help(&mut self, help: &'a str) {
374 self.update_app(|a| a.after_help(help));
375 }
376
377 pub fn add_interface(&mut self, ifc: IFace<'a>) {
379 if ifc.has_reloadable_arg() && !self.reload_opt {
380 self.config_reload_opt();
381 }
382 if ifc.has_debug() && !self.ifc_debug {
383 self.config_debug();
384 }
385 for ifa in ifc.get_args() {
386 self.config_arg(ifa)
387 }
388 self.interfaces.push(ifc);
389 }
390
391 fn get_if_idx(&self, ifc: &str) -> Option<usize> {
392 self.interfaces
393 .iter()
394 .position(|x| x.get_interface() == ifc)
395 }
396
397 fn get_if(&self, ifidx: usize) -> &IFace {
398 &self.interfaces[ifidx]
399 }
400
401 fn get_if_mut(&mut self, ifidx: usize) -> &mut IFace<'a> {
402 &mut self.interfaces[ifidx]
403 }
404
405 pub(crate) fn config_arg(&mut self, ifa: &IfArg<'a>) {
406 if self.app_args.contains(ifa.get_name()) {
407 return;
408 }
409
410 let mut arg = Arg::new(ifa.get_name()).long(ifa.get_name());
411 if let Some(hlp) = ifa.get_display() {
412 arg = arg.help(hlp);
413 }
414 arg = arg.takes_value(!matches!(ifa.get_type(), IfArgType::Boolflag));
415 self.app_arg(arg);
416
417 self.app_args.insert(ifa.get_name().to_owned());
418 }
419
420 pub(crate) fn config_reload_opt(&mut self) {
421 if self.reload_opt {
422 return;
423 }
424 self.reload_opt = true;
425 self.app_arg(
426 Arg::new(OPT_EXTCAP_RELOAD_OPTION)
427 .long(OPT_EXTCAP_RELOAD_OPTION)
428 .help("Reload values for the given argument")
429 .takes_value(true)
430 .value_name("option")
431 .requires(OPT_EXTCAP_INTERFACE)
432 .requires(OPT_EXTCAP_CONFIG),
433 );
434 self.app_args.insert(OPT_EXTCAP_RELOAD_OPTION.to_owned());
435 }
436
437 pub(crate) fn config_control(&mut self) {
438 if self.control {
439 return;
440 }
441 self.control = true;
442 self.app_arg(
443 Arg::new(OPT_EXTCAP_CONTROL_IN)
444 .long(OPT_EXTCAP_CONTROL_IN)
445 .help("The pipe for control messages from toolbar")
446 .takes_value(true)
447 .value_name("in-pipe")
448 .requires(OPT_CAPTURE),
449 );
450 self.app_arg(
451 Arg::new(OPT_EXTCAP_CONTROL_OUT)
452 .long(OPT_EXTCAP_CONTROL_OUT)
453 .help("The pipe for control messages to toolbar")
454 .takes_value(true)
455 .value_name("out-pipe")
456 .requires(OPT_CAPTURE),
457 );
458 self.app_args.insert(OPT_EXTCAP_CONTROL_IN.to_owned());
459 self.app_args.insert(OPT_EXTCAP_CONTROL_OUT.to_owned());
460 }
461
462 fn config_debug(&mut self) {
463 if self.ifc_debug {
464 return;
465 }
466 self.ifc_debug = true;
467 self.app_arg(
468 Arg::new(OPT_DEBUG)
469 .long(OPT_DEBUG)
470 .help("Print additional messages"),
471 );
472 self.app_arg(
473 Arg::new(OPT_DEBUG_FILE)
474 .long(OPT_DEBUG_FILE)
475 .help("Print debug messages to file")
476 .takes_value(true)
477 .value_name("file"),
478 );
479 self.app_args.insert(OPT_DEBUG.to_owned());
480 self.app_args.insert(OPT_DEBUG_FILE.to_owned());
481 }
482
483 pub fn add_control(&mut self, mut control: Control) {
485 if !self.control {
486 self.config_control();
487 }
488 control.set_number(self.controls.len());
489 self.controls.push(control);
490 }
491
492 fn print_version(&self) {
493 print!(
494 "extcap {{version={}}}",
495 self.version.as_ref().map_or("unknown", String::as_ref)
496 );
497 print_opt_value("help", &self.helppage);
498 println!();
499 }
500
501 fn print_iface_list(&self) {
502 self.interfaces.iter().for_each(IFace::print_iface);
503 }
504
505 fn print_control_list(&self) {
506 self.controls.iter().for_each(Control::print_control);
507 }
508
509 pub fn run<T: ExtcapListener>(mut self, mut listener: T) -> ExtcapResult<()> {
511 match self.run_till_capture(&mut listener)? {
512 TillCaptureOutcome::Finish(_) => Ok(()),
513 TillCaptureOutcome::Capture { ifidx } => {
514 self.capture(&mut listener, self.get_if(ifidx))
515 }
516 }
517 }
518
519 #[cfg(feature = "async-api")]
521 pub async fn run_async<T: ExtcapListener>(mut self, mut listener: T) -> ExtcapResult<()> {
522 match self.run_till_capture(&mut listener)? {
523 TillCaptureOutcome::Finish(_) => Ok(()),
524 TillCaptureOutcome::Capture { ifidx } => {
525 self.capture_async(&mut listener, self.get_if(ifidx)).await
526 }
527 }
528 }
529
530 fn run_till_capture<T: ExtcapListener>(&mut self, listener: &mut T) -> TillCaptureResult<()> {
531 self.matches = match self.take_app().try_get_matches() {
533 Ok(m) => Some(m),
534 Err(cerr) => match cerr.kind() {
535 clap::ErrorKind::DisplayHelp | clap::ErrorKind::DisplayVersion => {
536 print!("{}", cerr);
537 return Ok(TillCaptureOutcome::Finish(()));
538 }
539 _ => return Err(cerr.into()),
540 },
541 };
542
543 self.step = if self.get_matches().is_present(OPT_EXTCAP_INTERFACES) {
545 ExtcapStep::QueryIfaces
546 } else if self.get_matches().is_present(OPT_EXTCAP_DTLS) {
547 ExtcapStep::QueryDlts
548 } else if self.get_matches().is_present(OPT_EXTCAP_CONFIG) {
549 let reload = self.get_matches().is_present(OPT_EXTCAP_RELOAD_OPTION);
550 ExtcapStep::ConfigIface { reload }
551 } else if self.get_matches().is_present(OPT_CAPTURE) {
552 let ctrl_pipe = self.get_matches().is_present(OPT_EXTCAP_CONTROL_IN)
553 && self.get_matches().is_present(OPT_EXTCAP_CONTROL_OUT);
554 ExtcapStep::Capture { ctrl_pipe }
555 } else {
556 ExtcapStep::None
557 };
558
559 let debug = self.get_matches().is_present(OPT_DEBUG);
561 let debug_file = self.get_matches().value_of(OPT_DEBUG_FILE).and_then(|s| {
562 if s.trim().is_empty() {
563 None
564 } else {
565 Some(s)
566 }
567 });
568 listener.init_log(self, debug, debug_file);
569 debug!("=======================");
570 debug!(
571 "Log initialized debug={} debug_file={}",
572 debug,
573 debug_file.unwrap_or_default()
574 );
575 debug!("step = {:?}", self.step);
576 debug!("env::args = {:?}", std::env::args());
577
578 self.ws_version = self
580 .get_matches()
581 .value_of(OPT_EXTCAP_VERSION)
582 .map(String::from);
583 debug!(
584 "Wireshark version {}",
585 self.ws_version
586 .as_ref()
587 .map_or("-not provided-", String::as_str)
588 );
589
590 listener.update_interfaces(self);
592
593 if let ExtcapStep::QueryIfaces = self.get_step() {
594 debug!("list of interfaces required");
595 self.print_version();
596 self.print_iface_list();
597 self.print_control_list();
598 return Ok(TillCaptureOutcome::Finish(()));
599 }
600
601 let ifidx = self
602 .get_matches()
603 .value_of(OPT_EXTCAP_INTERFACE)
604 .map_or_else(
605 || Err(ExtcapError::missing_interface()),
606 |ifnm| {
607 self.get_if_idx(ifnm)
608 .ok_or_else(|| ExtcapError::invalid_interface(ifnm))
609 },
610 )?;
611
612 debug!("interface = {}", self.get_if(ifidx).get_interface());
613 match self.get_step() {
614 ExtcapStep::QueryDlts => {
615 debug!("interface DLTs required");
616 self.get_if(ifidx).print_dlt_list();
617 Ok(TillCaptureOutcome::Finish(()))
618 }
619 ExtcapStep::ConfigIface { .. } => {
620 if let Some(arg) = self.get_matches().value_of(OPT_EXTCAP_RELOAD_OPTION) {
621 let arg = arg.to_owned(); debug!("interface config reload required for '{}' argument", arg);
623 self.reload_option(listener, ifidx, &arg);
624 } else {
625 debug!("interface config required");
626 self.get_if(ifidx).print_arg_list();
627 }
628 Ok(TillCaptureOutcome::Finish(()))
629 }
630 ExtcapStep::Capture { .. } => Ok(TillCaptureOutcome::Capture { ifidx }),
631 _ => Err(ExtcapError::unknown_step()),
632 }
633 }
634
635 fn reload_option<T: ExtcapListener>(&mut self, listener: &mut T, ifidx: usize, arg: &str) {
636 let ifc = self.get_if(ifidx);
637 let aidx = if let Some(aidx) = ifc.get_arg_idx(arg) {
638 aidx
639 } else {
640 warn!(
641 "reload_option() arg '{}' not available for interface '{}'",
642 arg,
643 ifc.get_interface()
644 );
645 return;
646 };
647
648 if let Some(nargs) = listener.reload_option(self, ifc, ifc.get_arg(aidx)) {
649 debug!(
650 "reload_option() arg '{}' for interface '{}' has got {} values",
651 arg,
652 ifc.get_interface(),
653 nargs.len()
654 );
655 let arg = self.get_if_mut(ifidx).get_arg_mut(aidx);
656 arg.reload_option(nargs);
657 } else {
658 debug!(
659 "reload_option() arg '{}' for interface '{}' nothing has changed",
660 arg,
661 ifc.get_interface()
662 );
663 };
664
665 self.get_if(ifidx).get_arg(aidx).print_arg();
666 }
667
668 fn capture<T: ExtcapListener>(&self, listener: &mut T, ifc: &IFace) -> ExtcapResult<()> {
669 let fifo = self.get_matches().value_of(OPT_FIFO).unwrap();
670 let capture_filter = self.get_matches().value_of(OPT_EXTCAP_CAPTURE_FILTER);
671 debug!(
672 "capture required fifo={} capture_filter={}",
673 fifo,
674 capture_filter.unwrap_or_default()
675 );
676
677 let ph = listener.capture_header(self, ifc);
678 debug!("capture pcap header: {:?}", ph);
679 let pw = create_pcap_writer(fifo, ph)?;
680
681 let res = {
682 debug!("capture starting");
683 listener.capture(self, ifc, pw)
684 };
685 debug!("capture finished: {:?}", res);
686
687 res
688 }
689
690 #[cfg(feature = "async-api")]
691 async fn capture_async<T: ExtcapListener>(
692 &self,
693 listener: &mut T,
694 ifc: &IFace<'_>,
695 ) -> ExtcapResult<()> {
696 let fifo = self.get_matches().value_of(OPT_FIFO).unwrap();
697 let capture_filter = self.get_matches().value_of(OPT_EXTCAP_CAPTURE_FILTER);
698 debug!(
699 "async capture required fifo={} capture_filter={}",
700 fifo,
701 capture_filter.unwrap_or_default()
702 );
703 #[cfg(feature = "ctrl-pipe")]
704 let mut control_pipe = {
705 let control_in = self.get_matches().value_of(OPT_EXTCAP_CONTROL_IN);
706 let control_out = self.get_matches().value_of(OPT_EXTCAP_CONTROL_OUT);
707 if let (Some(ctrl_in), Some(ctrl_out)) = (control_in, control_out) {
708 debug!("async capture with control in={} out={}", ctrl_in, ctrl_out);
709 match create_control_pipe(ctrl_in, ctrl_out) {
710 Ok(ctrl_pipe) => Some(ctrl_pipe),
711 Err(e) => {
712 warn!(
713 "create_control_pipe(ctrl_in={}, ctrl_out={}), failed with error {}",
714 ctrl_in, ctrl_out, e
715 );
716 None
717 }
718 }
719 } else {
720 None
721 }
722 };
723
724 let ph = listener.capture_header(self, ifc);
725 debug!("async capture pcap header: {:?}", ph);
726 let pw = create_pcap_writer(fifo, ph)?;
727
728 #[cfg(feature = "ctrl-pipe")]
729 let res = {
730 let ctrl_pipe = control_pipe.as_mut().map(control_pipe::ControlPipe::start);
731 debug!(
732 "async capture starting {} ctrl pipes",
733 if ctrl_pipe.is_some() {
734 "with"
735 } else {
736 "without"
737 }
738 );
739 let receiver = listener.capture_async_with_ctrl(self, ifc, ctrl_pipe)?;
740 let tsk_ctrl_opt = control_pipe
741 .as_mut()
742 .map(control_pipe::ControlPipe::run_task);
743 let tsk_capture = async {
744 let res = capture_async_loop(receiver, pw).await;
745 if let Some(cp) = control_pipe {
746 cp.stop();
747 }
748 res
749 };
750 if let Some(tsk_ctrl) = tsk_ctrl_opt {
751 future::join(tsk_ctrl, tsk_capture).await.1
752 } else {
753 tsk_capture.await
754 }
755 };
756
757 #[cfg(not(feature = "ctrl-pipe"))]
758 let res = {
759 debug!("async capture starting");
760 let receiver = listener.capture_async(self, ifc)?;
761 capture_async_loop(receiver, pw).await
762 };
763
764 debug!("async capture finished: {:?}", res);
765
766 res
767 }
768}
769
770#[cfg(feature = "async-api")]
771async fn capture_async_loop(
772 mut receiver: ExtcapReceiver,
773 mut pw: PcapWriter<ExtcapWriter>,
774) -> ExtcapResult<()> {
775 debug!("async capture started");
776 while let Some(pkt) = receiver.next().await {
777 debug!("async packet received {:?}", pkt);
778 pw.write_packet(&pkt)?;
779 }
780 Ok(())
781}