r_extcap/lib.rs
1//! Write [extcap](https://www.wireshark.org/docs/man-pages/extcap.html)
2//! programs in Rust.
3//!
4//! The extcap interface is a versatile plugin interface used by Wireshark to
5//! allow external binaries to act as capture interfaces. The extcap interface
6//! itself is generic and can be used by applications other than Wireshark, like
7//! Wireshark's command line sibling `tshark`. For the sake of brevity, in the
8//! documentation we will refer to the host application simply as Wireshark.
9//!
10//! ### Extcap overview
11//!
12//! 1. `--extcap-interfaces`: In this step, Wireshark asks the extcap for its
13//! list of supported interfaces, version metadata, and the list of toolbar
14//! controls.
15//! 2. `--extcap-dlts`: Invoked once for each interface, Wireshark asks the
16//! extcap program for the data link type associated with the interface.
17//! 3. `--extcap-config`: Invoked for each interface upon user request,
18//! Wireshark asks the extcap program for a list of configurations to
19//! populate a config dialog in the UI.
20//! 4. `--capture`: The main part of the extcap program – invoked once when the
21//! user selects an interface for capture, to tell the extcap to start
22//! capturing packets. Captured packets should be written to the `--fifo` in
23//! the PCAP format.
24//!
25//! ## Getting started
26//!
27//! To create an extcap using this library, these are the high level steps:
28//!
29//! 1. Create a struct with `#[derive(clap::Parser)]`, and add
30//! [`ExtcapArgs`](crate::ExtcapArgs) as one of the fields with the
31//! `#[command(flatten)]` attribute.
32//!
33//! ```
34//! #[derive(Debug, clap::Parser)]
35//! struct AppArgs {
36//! #[command(flatten)]
37//! extcap: r_extcap::ExtcapArgs,
38//!
39//! // Other args for extcap (see the `configs` module)
40//! }
41//! ```
42//!
43//! 2. In a `lazy_static`, define the necessary
44//! [interfaces](crate::interface::Interface), [toolbar
45//! controls](crate::controls::ToolbarControl), and
46//! [configs](crate::config::ConfigTrait). If you are unsure, you can simply
47//! start with the [`Interfaces`](crate::interface::Interface) you want to
48//! capture and add the others later as needed.
49//!
50//! 3. In the `main` function, parse the arguments and call
51//! [`ExtcapArgs::run`](crate::ExtcapArgs::run). Use the returned
52//! [`ExtcapStep`](crate::ExtcapStep) to perform the requested operation.
53//! There are 5 steps:
54//!
55//! 1. [`InterfacesStep`](crate::InterfacesStep): List the interfaces that
56//! can be captured by this program, as well as the metadata and
57//! toolbar controls associated.
58//! 2. [`DltsStep`](crate::DltsStep): Prints the DLTs for a given interface.
59//! 3. [`ConfigStep`](crate::ConfigStep): Optional, provide a list of UI
60//! configuration options that the user can change.
61//! 4. [`ReloadConfigStep`](crate::ReloadConfigStep): Optional, if
62//! [`SelectorConfig::reload`](crate::config::SelectorConfig)
63//! is configured in one of the configs, invoked to reload the list
64//! of options the user can choose from.
65//! 5. [`CaptureStep`](crate::CaptureStep): described below.
66//!
67//! 4. In the [`CaptureStep`](crate::CaptureStep), start capturing packets from
68//! the external interface, and write the packets to
69//! [`CaptureStep::fifo`](crate::CaptureStep) using the
70//! [`pcap_file`](https://docs.rs/pcap-file/latest/pcap_file/index.html)
71//! crate.
72//!
73//! # Example
74//!
75//! ```no_run
76//! # use lazy_static::lazy_static;
77//! use clap::Parser;
78//! use r_extcap::{cargo_metadata, ExtcapArgs, ExtcapStep, interface::*, controls::*, config::*};
79//!
80//! #[derive(Debug, Parser)]
81//! struct AppArgs {
82//! #[command(flatten)]
83//! extcap: ExtcapArgs,
84//! }
85//!
86//! lazy_static! {
87//! // static ref CONFIG_FOO: SelectorConfig = ...;
88//! // static ref CONFIG_BAR: StringConfig = ...;
89//!
90//! // static ref CONTROL_A: BooleanControl = ...;
91//!
92//! // static ref INTERFACE_1: Interface = ...;
93//! }
94//!
95//! fn main() -> anyhow::Result<()> {
96//! match AppArgs::parse().extcap.run()? {
97//! ExtcapStep::Interfaces(interfaces_step) => {
98//! interfaces_step.list_interfaces(
99//! &cargo_metadata!(),
100//! &[
101//! // &*INTERFACE_1,
102//! ],
103//! &[
104//! // &*CONTROL_A,
105//! // &*CONTROL_B,
106//! ],
107//! );
108//! }
109//! ExtcapStep::Dlts(dlts_step) => {
110//! dlts_step.print_from_interfaces(&[
111//! // &*INTERFACE_1,
112//! ])?;
113//! }
114//! ExtcapStep::Config(config_step) => config_step.list_configs(&[
115//! // &*CONFIG_FOO,
116//! // &*CONFIG_BAR,
117//! ]),
118//! ExtcapStep::ReloadConfig(reload_config_step) => {
119//! reload_config_step.reload_from_configs(&[
120//! // &*CONFIG_FOO,
121//! // &*CONFIG_BAR,
122//! ])?;
123//! }
124//! ExtcapStep::Capture(capture_step) => {
125//! // Run capture
126//! }
127//! }
128//! Ok(())
129//! }
130//! ```
131//!
132//! References:
133//! * <https://www.wireshark.org/docs/wsdg_html_chunked/ChCaptureExtcap.html>
134//! * <https://www.wireshark.org/docs/man-pages/extcap.html>
135//! * <https://gitlab.com/wireshark/wireshark/-/blob/master/doc/extcap_example.py>
136
137#![warn(missing_docs)]
138
139use clap::Args;
140use config::{ConfigTrait, SelectorConfig};
141use controls::ToolbarControl;
142use interface::{Interface, Metadata};
143use std::{
144 fmt::Display,
145 path::{Path, PathBuf},
146};
147use thiserror::Error;
148
149#[cfg(not(target_os = "windows"))]
150use std::fs::File;
151
152pub mod config;
153pub mod controls;
154pub mod interface;
155
156/// The arguments defined by extcap. These arguments are usable as a clap
157/// parser.
158///
159/// For example, if you use `clap` with the feature `derive`:
160/// ```
161/// # use clap::Parser;
162/// #[derive(Debug, Parser)]
163/// #[command(author, version, about)]
164/// pub struct ApplicationArgs {
165/// #[command(flatten)]
166/// extcap: r_extcap::ExtcapArgs,
167///
168/// // Other application args
169/// }
170/// ```
171///
172/// Wireshark will call extcap in 4 phases:
173///
174/// 1. [`--extcap-interfaces`][ExtcapArgs::extcap_interfaces]: Declare all
175/// supported interfaces and controls.
176/// 2. [`--extcap-config`][ExtcapArgs::extcap_config]: Called for each interface
177/// to declare configuration options that can be changed by the user in the
178/// UI. (This is used only in Wireshark, not available in tshark).
179/// 3. [`--extcap-dlts`][ExtcapArgs::extcap_dlts]: Called for each interface
180/// returned in `--extcap-interfaces` to specify which Data Link Type is
181/// being captured.
182/// 4. [`--capture`][ExtcapArgs::capture]: Called to initiate capture of the
183/// packets. See the documentation on the field for details.
184///
185/// When the capturing stops (i.e. the user presses the red Stop button),
186/// `SIGTERM` is sent by Wireshark.
187#[derive(Debug, Args)]
188pub struct ExtcapArgs {
189 /// First step in the extcap exchange: this program is queried for its
190 /// interfaces.
191 /// ```sh
192 /// $ extcapbin --extcap-interfaces
193 /// ```
194 /// This call must print the existing interfaces for this extcap and must
195 /// return 0. The output must conform to the grammar specified in the
196 /// [doc/extcap.4](https://www.wireshark.org/docs/man-pages/extcap.html)
197 /// man pages.
198 #[arg(long, verbatim_doc_comment)]
199 pub extcap_interfaces: bool,
200
201 /// The version of Wireshark (or tshark) calling into this extcap.
202 ///
203 /// Wireshark 2.9 and later pass `--extcap-version=x.x` when querying for
204 /// the list of interfaces, which provides the calling Wireshark's major and
205 /// minor version. This can be used to change behavior depending on the
206 /// Wireshark version in question.
207 ///
208 /// This argument is passed during the
209 /// [`--extcap-interfaces`][ExtcapArgs::extcap_interfaces] call.
210 #[arg(long)]
211 pub extcap_version: Option<String>,
212
213 /// Second step in the extcap exchange: this program is asked for the configuration of each
214 /// specific interface
215 /// ```sh
216 /// $ extcap_example.py --extcap-interface <iface> --extcap-config
217 /// ```
218 ///
219 /// Each interface can have custom options that are valid for this interface only. Those config
220 /// options are specified on the command line when running the actual capture. To allow an
221 /// end-user to specify certain options, such options may be provided using the extcap config
222 /// argument.
223 ///
224 /// To share which options are available for an interface, the extcap responds to the command
225 /// `--extcap-config`, which shows all the available options (aka additional command line
226 /// options).
227 ///
228 /// Those options are used to build a configuration dialog for the interface.
229 #[arg(long, verbatim_doc_comment)]
230 pub extcap_config: bool,
231
232 /// Third step in the extcap exchange: the extcap binary is queried for all valid DLTs for all
233 /// the interfaces returned during [`--extcap-interfaces`][Self::extcap_interfaces]).
234 ///
235 /// ```sh
236 /// $ extcap_example.py --extcap-dlts --extcap-interface <iface>
237 /// ```
238 ///
239 /// This call must print the valid DLTs for the interface specified. This call is made for all
240 /// the interfaces and must return exit code 0.
241 ///
242 /// Example for the DLT query.
243 /// ```sh
244 /// $ extcap_example.py --extcap-interface IFACE --extcap-dlts
245 /// dlt {number=147}{name=USER1}{display=Demo Implementation for Extcap}
246 /// ```
247 ///
248 /// A binary or script which neither provides an interface list or a DLT list will not show up
249 /// in the extcap interfaces list.
250 #[arg(long, requires = "extcap_interface", verbatim_doc_comment)]
251 pub extcap_dlts: bool,
252
253 /// Start the capturing phase.
254 ///
255 /// In addition to `--capture`, the
256 /// [`--extcap-capture-filter`][ExtcapArgs::extcap_capture_filter] and
257 /// [`--fifo`][ExtcapArgs::fifo] options are also required in this phase.
258 ///
259 /// Additionally, if `{config}` entries were returned during the
260 /// `--extcap-interfaces` phase, then
261 /// [`--extcap-control-in`][ExtcapArgs::extcap_control_in] and
262 /// [`--extcap-control-out`][ExtcapArgs::extcap_control_out] will be passed,
263 /// which are a pair of fifos in which [control
264 /// messages](https://www.wireshark.org/docs/wsdg_html_chunked/ChCaptureExtcap.html#_messages)
265 /// are sent.
266 #[arg(long, requires = "fifo", requires = "extcap_interface")]
267 pub capture: bool,
268
269 /// The extcap interface to perform the operation on.
270 ///
271 /// This should match one of the values returned earlier in
272 /// [`extcap_interfaces`][Self::extcap_interfaces], and is used in the
273 /// [`capture`][Self::capture], [`extcap_config`][Self::extcap_config], and
274 /// [`extcap_dlts`][Self::extcap_dlts] phases.
275 #[arg(long)]
276 pub extcap_interface: Option<String>,
277
278 /// Specifies the fifo for the packet captures. The extcap implementation
279 /// should write the captured packets to this fifo in pcap or pcapng format.
280 #[arg(long, requires = "capture")]
281 pub fifo: Option<PathBuf>,
282
283 /// The capture filter provided by wireshark. This extcap should avoid capturing packets that do
284 /// not match this filter. Used during the `--capture` phase.
285 #[arg(long, requires = "capture")]
286 pub extcap_capture_filter: Option<String>,
287
288 /// Used to get control messages from toolbar. Control messages are in the
289 /// format documented in [`ControlPacket`][controls::ControlPacket].
290 #[arg(long, requires = "capture")]
291 pub extcap_control_in: Option<PathBuf>,
292
293 /// Used to send control messages to toolbar. Control messages are in the
294 /// format documented in [`ControlPacket`][controls::ControlPacket].
295 #[arg(long, requires = "capture")]
296 pub extcap_control_out: Option<PathBuf>,
297
298 /// A [`SelectorConfig`] may be reloaded from the configuration dialog of
299 /// the extcap application within Wireshark. With [`SelectorConfig::reload`]
300 /// (defaults to `false`), the entry can be marked as reloadable.
301 ///
302 /// ```
303 /// use r_extcap::config::{ConfigOptionValue, SelectorConfig, Reload};
304 ///
305 /// SelectorConfig::builder()
306 /// .config_number(3)
307 /// .call("remote")
308 /// .display("Remote Channel")
309 /// .tooltip("Remote Channel Selector")
310 /// .reload(Reload {
311 /// label: String::from("Load interfaces..."),
312 /// reload_fn: || {
313 /// vec![
314 /// ConfigOptionValue::builder()
315 /// .value("if3")
316 /// .display("Remote Interface 3")
317 /// .default(true)
318 /// .build(),
319 /// ConfigOptionValue::builder()
320 /// .value("if4")
321 /// .display("Remote Interface 4")
322 /// .build(),
323 /// ]
324 /// }
325 /// })
326 /// .default_options([
327 /// ConfigOptionValue::builder()
328 /// .value("if1")
329 /// .display("Remote1")
330 /// .default(true)
331 /// .build(),
332 /// ConfigOptionValue::builder().value("if2").display("Remote2").build(),
333 /// ])
334 /// .build();
335 /// ```
336 ///
337 /// After this has been defined, the user will get a button displayed in the
338 /// configuration dialog for this extcap application, with the text "Load
339 /// interfaces...".
340 ///
341 /// The extcap utility is then called again with all filled out arguments
342 /// and the additional parameter `--extcap-reload-option <option_name>`. It
343 /// is expected to return a value section for this option, as it would
344 /// during normal configuration. The provided option list is then presented
345 /// as the selection, a previous selected option will be reselected if
346 /// applicable.
347 #[arg(long, requires = "extcap_interface")]
348 pub extcap_reload_option: Option<String>,
349}
350
351/// Error during the `--capture` phase of extcap.
352#[derive(Debug, Error)]
353pub enum CaptureError {
354 /// The `--extcap-interface` argument is required during the `--capture`
355 /// phase of extcap, but is not provided.
356 #[error("Missing `--extcap-interface` argument during `--capture` phase")]
357 MissingInterface,
358 /// Error caused by missing `--fifo` argument from the command line. This is
359 /// expected to be passed by Wireshark when invoking an extcap for
360 /// capturing, and is needed to write the output of the packet capture to.
361 #[error(
362 "--fifo argument is missing. This is expected to be included \
363when invoked by Wireshark during the capture stage."
364 )]
365 MissingFifo,
366 /// IO Error while trying to open the given fifo. Since the fifo is
367 /// necessary to send the captured packets to Wireshark, implementations are
368 /// recommended to clean up and terminate the execution. Additionally, the
369 /// error can be printed onto stderr. If Wireshark picks that up, it will
370 /// show that to the user in an error dialog.
371 #[error("IO error opening output FIFO for capture")]
372 Io(#[from] std::io::Error),
373}
374
375impl ExtcapArgs {
376 /// Runs the extcap program with the parsed arguments. This is the main
377 /// entry point for the extcap program. Implementations should call this
378 /// from their `main` functions.
379 ///
380 /// For detailed usage, see the [crate documentation][crate]
381 pub fn run(&self) -> Result<ExtcapStep, ExtcapError> {
382 if self.extcap_interfaces {
383 Ok(ExtcapStep::Interfaces(InterfacesStep))
384 } else if let Some(interface) = &self.extcap_interface {
385 if self.extcap_config {
386 if let Some(reload_config) = &self.extcap_reload_option {
387 Ok(ExtcapStep::ReloadConfig(ReloadConfigStep {
388 interface,
389 config: reload_config,
390 }))
391 } else {
392 Ok(ExtcapStep::Config(ConfigStep { interface }))
393 }
394 } else if self.extcap_dlts {
395 Ok(ExtcapStep::Dlts(DltsStep { interface }))
396 } else if self.capture {
397 let fifo_path = self.fifo.as_ref().ok_or(CaptureError::MissingFifo)?;
398
399 #[cfg(target_os = "windows")]
400 let fifo = {
401 use std::os::windows::prelude::OpenOptionsExt;
402 std::fs::OpenOptions::new()
403 .write(true)
404 .create(true)
405 // Sets the flag value to `SecurityIdentification`.
406 .security_qos_flags(0x10000)
407 .open(fifo_path)
408 .map_err(CaptureError::Io)?
409 };
410
411 #[cfg(not(target_os = "windows"))]
412 let fifo = File::create(fifo_path).map_err(CaptureError::Io)?;
413
414 let interface = self
415 .extcap_interface
416 .as_ref()
417 .ok_or(CaptureError::MissingInterface)?;
418 Ok(ExtcapStep::Capture(CaptureStep {
419 interface,
420 // Note: It is important to open this file, so the file gets
421 // closed even if the implementation doesn't use it.
422 // Otherwise Wireshark will hang there waiting for the FIFO.
423 fifo,
424 fifo_path,
425 extcap_control_in: &self.extcap_control_in,
426 extcap_control_out: &self.extcap_control_out,
427 }))
428 } else {
429 Err(ExtcapError::NotExtcapInput)
430 }
431 } else {
432 Err(ExtcapError::NotExtcapInput)
433 }
434 }
435}
436
437/// Error reported when running [`ExtcapArgs::run`].
438#[derive(Debug, Error)]
439pub enum ExtcapError {
440 /// The inputs given are not expected input from Wireshark. This can happen
441 /// for example, when the user tries to run the application directly from
442 /// command line. When this happens, you can print out the
443 /// [`installation_instructions`], to help the user install this in the
444 /// right location.
445 #[error("Missing input extcap command. {}", installation_instructions())]
446 NotExtcapInput,
447
448 /// Error when capturing packets. See [`CaptureError`].
449 #[error(transparent)]
450 CaptureError(#[from] CaptureError),
451}
452
453/// Get the installation instructions. This is useful to show if the program is
454/// used in unexpected ways (e.g. not as an extcap program), so users can easily
455/// install with a copy-pastable command.
456///
457/// ```
458/// # use indoc::formatdoc;
459/// # let exe = std::env::current_exe().unwrap();
460/// # let executable_path = exe.to_string_lossy();
461/// # let exe_name = exe.file_name().unwrap().to_string_lossy();
462/// assert_eq!(
463/// r_extcap::installation_instructions(),
464/// formatdoc!{"
465/// This is an extcap plugin meant to be used with Wireshark or tshark.
466/// To install this plugin for use with Wireshark, symlink or copy this executable \
467/// to your Wireshark extcap directory
468///
469/// For Wireshark 4.0 or before:
470/// mkdir -p \"$HOME/.config/wireshark/extcap/\" && ln -s \"{executable_path}\" \"$HOME/.config/wireshark/extcap/{exe_name}\"
471/// For Wireshark 4.1 or later:
472/// mkdir -p \"$HOME/.local/lib/wireshark/extcap/\" && ln -s \"{executable_path}\" \"$HOME/.local/lib/wireshark/extcap/{exe_name}\"\
473/// "}
474/// )
475/// ```
476pub fn installation_instructions() -> String {
477 let install_cmd = std::env::current_exe()
478 .ok()
479 .and_then(|exe| {
480 let path = exe.to_string_lossy();
481 let name = exe.file_name()?.to_string_lossy();
482 Some(format!(concat!(
483 "\n\nFor Wireshark 4.0 or before:\n",
484 " mkdir -p \"$HOME/.config/wireshark/extcap/\" && ln -s \"{path}\" \"$HOME/.config/wireshark/extcap/{name}\"\n",
485 "For Wireshark 4.1 or later:\n",
486 " mkdir -p \"$HOME/.local/lib/wireshark/extcap/\" && ln -s \"{path}\" \"$HOME/.local/lib/wireshark/extcap/{name}\"",
487 ), path = path, name = name))
488 })
489 .unwrap_or_default();
490 format!(
491 concat!(
492 "This is an extcap plugin meant to be used with Wireshark or tshark.\n",
493 "To install this plugin for use with Wireshark, symlink or copy this executable ",
494 "to your Wireshark extcap directory{}",
495 ),
496 install_cmd
497 )
498}
499
500/// Error printing DLTs to Wireshark.
501#[derive(Debug, Error)]
502pub enum PrintDltError {
503 /// The interface string value given from Wireshark is not found. Wireshark
504 /// invokes the extcap program multiple times, first to get the list of
505 /// interfaces, then multiple times to get the DLTs. Therefore,
506 /// implementations should make sure that the list of interfaces stay
507 /// consistent, or be prepared to gracefully handle this error.
508 #[error("Cannot list DLT for unknown interface \"{0}\".")]
509 UnknownInterface(String),
510}
511
512/// Error when reloading configs. Config reload happens when a config, like
513/// [`crate::config::SelectorConfig`] specifics the `reload` field and the user
514/// clicks on the created reload button.
515#[derive(Debug, Error)]
516pub enum ReloadConfigError {
517 /// The config `call` value given from Wireshark is not found in the configs
518 /// provided. Wireshark makes separate invocations to get the initial list
519 /// of interfaces, and when the user subsequently hits reload on a config.
520 /// Therefore, implementations should make sure that the configs used in
521 /// [`ConfigStep`] and [`ReloadConfigStep`] are consistent.
522 #[error("Cannot reload options for unknown config \"{0}\".")]
523 UnknownConfig(String),
524
525 /// The config given by Wireshark is found, but it is not a
526 /// [`SelectorConfig`]. This configuration is not expected to be invoked by
527 /// Wireshark, as the [`SelectorConfig::reload`] field only exists for the
528 /// appropriate types.
529 #[error("Cannot reload config options for \"{0}\", which is not of type \"selector\".")]
530 UnsupportedConfig(String),
531}
532
533/// Error listing configs.
534#[derive(Debug, Error)]
535pub enum ListConfigError {
536 /// The interface string value given from Wireshark is not found. Wireshark
537 /// makes separate invocations to get the initial list of interfaces, and
538 /// when the user subsequently opens the config dialog. Therefore,
539 /// implementations should make sure that the interfaces used in different
540 /// [`ExtcapSteps`][ExtcapStep] are deterministic and doesn't change across
541 /// invocations of the program.
542 #[error("Cannot reload config options for unknown interface \"{0}\".")]
543 UnknownInterface(String),
544}
545
546/// The step of extcap to execute, which is returned from [`ExtcapArgs::run`].
547/// Each step has its own type which contains the relevant methods for each
548/// step. See the docs for each individual step to for details on what
549/// operations should be performed.
550pub enum ExtcapStep<'a> {
551 /// List the interfaces and toolbar controls supported by this extcap
552 /// implementation in stdout for Wireshark's consumption. Corresponds to the
553 /// `--extcap-interfaces` argument in extcap.
554 ///
555 /// See the documentation on [`InterfacesStep`] for details.
556 Interfaces(InterfacesStep),
557 /// Prints the DLT to stdout for consumption by Wireshark. Corresponds to
558 /// the `--extcap-dlts` argument in extcap.
559 ///
560 /// See the documentation on [`DltsStep`] for details.
561 Dlts(DltsStep<'a>),
562 /// List the configs available for the given interface in stdout for
563 /// Wireshark's consumption. Corresponds to the `--extcap-config` argument
564 /// in extcap.
565 ///
566 /// See the documentation on [`ConfigStep`] for details.
567 Config(ConfigStep<'a>),
568 /// Reloads the available options for a given config and prints them out for
569 /// Wireshark's consumption. The default implementation looks up config returned from `configs` and calls its reload function. Corresponds to the `--extcap-reload-option`
570 /// argument in extcap.
571 ///
572 /// See the documentation on [`ReloadConfigStep`] for details.
573 ReloadConfig(ReloadConfigStep<'a>),
574 /// Corresponds to the `--capture` step in Wireshark. In this step, the
575 /// implementation should start capturing from the external interface and
576 /// write the output to the fifo given in [`CaptureStep::fifo`].
577 ///
578 /// See the documentation on [`CaptureStep`] for details.
579 Capture(CaptureStep<'a>),
580}
581
582/// List the interfaces and toolbar controls supported by this extcap
583/// implementation in stdout for Wireshark's consumption. Corresponds to the
584/// `--extcap-interfaces` argument in extcap. Implementations should call
585/// [`list_interfaces`][Self::list_interfaces] during this step.
586pub struct InterfacesStep;
587
588impl InterfacesStep {
589 /// List the interfaces and toolbar controls supported by this extcap
590 /// implementation in stdout for Wireshark's consumption. Wireshark calls
591 /// this when the application starts up to populate the list of available
592 /// interfaces.
593 ///
594 /// * metadata: metadata like version info and help URL for this program.
595 /// This is used by Wireshark to display in the UI.
596 ///
597 /// The [`cargo_metadata`] macro can be used to create this from data in
598 /// `Cargo.toml`.
599 /// * interfaces: List of interfaces to make available for external capture.
600 /// Since that interface list is cached and the interface names can be
601 /// used later when the user tries to start a capture session, the
602 /// interface list should stay as consistent as possible. If the list of
603 /// interfaces can change, the extcap program must be prepared to handle
604 /// stale values in [`ConfigStep::interface`] and
605 /// [`CaptureStep::interface`].
606 /// * controls: List the toolbar controls for this interface. In Wireshark,
607 /// this is presented to the user in View > Interface Toolbars. See the
608 /// documentation in [`controls`] for details.
609 pub fn list_interfaces(
610 &self,
611 metadata: &Metadata,
612 interfaces: &[&Interface],
613 controls: &[&dyn ToolbarControl],
614 ) {
615 metadata.print_sentence();
616 for interface in interfaces {
617 interface.print_sentence();
618 }
619 for control in controls {
620 control.print_sentence();
621 }
622 }
623}
624
625/// In the DLTs step, Wireshark asks the extcap program for the DLT for each
626/// interface. DLT stands for data link type, and is used to determine how
627/// Wireshark analyzes (dissects) the given packets.
628///
629/// Despite this step being named with plurals (DLTs) by extcap, only one DLT is
630/// expected for each interface. Corresponds to the `--extcap-dlts` argument in
631/// extcap.
632pub struct DltsStep<'a> {
633 /// The interface to print the DLT for.
634 pub interface: &'a str,
635}
636
637impl<'a> DltsStep<'a> {
638 /// Print the DLT for the given interface. If you have the list of
639 /// interfaces from [`InterfacesStep`], consider using
640 /// [`print_from_interfaces`][Self::print_from_interfaces] instead.
641 pub fn print_dlt(&self, interface: &Interface) {
642 interface.dlt.print_sentence();
643 }
644
645 /// Finds the interface within `interfaces` that matches the given request
646 /// and prints out its DLT. Typically `interfaces` will be the same list
647 /// given to [`InterfacesStep::list_interfaces`].
648 pub fn print_from_interfaces(&self, interfaces: &[&Interface]) -> Result<(), PrintDltError> {
649 interfaces
650 .iter()
651 .find(|i| i.value == self.interface)
652 .ok_or_else(|| PrintDltError::UnknownInterface(self.interface.to_owned()))?
653 .dlt
654 .print_sentence();
655 Ok(())
656 }
657}
658
659/// List the configurable UI elements for this interface. This is presented to
660/// the user when they click on the gear icon next to the capture interface
661/// name, or if they try to start a capture that is lacking a required config
662/// value.
663pub struct ConfigStep<'a> {
664 /// The interface that the configurations should be associated with.
665 pub interface: &'a str,
666}
667
668impl<'a> ConfigStep<'a> {
669 /// List the `configs` given, printing them out to stdout for consumption by
670 /// Wireshark. This list can vary by [`interface`].
671 pub fn list_configs(&self, configs: &[&dyn ConfigTrait]) {
672 for config in configs {
673 config.print_sentence();
674 }
675 }
676}
677
678/// Reload operation for a particular configuration. This is invoked when the
679/// user clicks on the reload button created by a [`SelectorConfig`] with the
680/// [`reload`][SelectorConfig::reload] field set. Corresponds to the
681/// `--extcap-reload-option` argument in extcap.
682pub struct ReloadConfigStep<'a> {
683 /// The [`Interface::value`] from the interface the reloaded config is
684 /// associated with.
685 pub interface: &'a str,
686 /// The [`ConfigTrait::call`] of the config being reloaded.
687 pub config: &'a str,
688}
689
690impl<'a> ReloadConfigStep<'a> {
691 /// Calls the [`reload`][SelectorConfig::reload] function in the given
692 /// `config`. Returns the error [`ReloadConfigError::UnsupportedConfig`] if
693 /// the given config does not have `reload` set.
694 ///
695 /// If you have the list of configs for the given interface, consider using
696 /// [`reload_from_configs`][Self::reload_from_configs] instead.
697 pub fn reload_options(&self, config: &SelectorConfig) -> Result<(), ReloadConfigError> {
698 let reload = config
699 .reload
700 .as_ref()
701 .ok_or_else(|| ReloadConfigError::UnsupportedConfig(config.call.clone()))?;
702 for value in (reload.reload_fn)() {
703 value.print_sentence(config.config_number);
704 }
705 Ok(())
706 }
707
708 /// Process config reload request using the list of `configs`. This list is
709 /// typically the same as the one given to [`ConfigStep::list_configs`].
710 pub fn reload_from_configs(
711 &self,
712 configs: &[&dyn ConfigTrait],
713 ) -> Result<(), ReloadConfigError> {
714 let config = configs
715 .iter()
716 .find(|c| c.call() == self.config)
717 .ok_or_else(|| ReloadConfigError::UnknownConfig(self.config.to_owned()))?;
718 let selector = config
719 .as_any()
720 .downcast_ref::<SelectorConfig>()
721 .ok_or_else(|| ReloadConfigError::UnsupportedConfig(self.config.to_owned()))?;
722 self.reload_options(selector)
723 }
724}
725
726/// When this value is returned in [`ExtcapArgs::run`], the implementation
727/// should use these returned values to start capturing packets from the
728/// external interface and write them to the [`fifo`][Self::fifo] in PCAP
729/// format.
730pub struct CaptureStep<'a> {
731 /// The interface to run this capture on. This is the string previously
732 /// defined in [`Interface::value`].
733 pub interface: &'a str,
734 /// The fifo to write the output packets to. The output packets should be
735 /// written in PCAP format. Implementations can use the
736 /// [`pcap-file`](https://docs.rs/pcap-file/latest/pcap_file/) crate to help
737 /// format the packets.
738 pub fifo: std::fs::File,
739 fifo_path: &'a Path,
740 /// The extcap control reader if the `--extcap-control-in` argument is
741 /// provided on the command line. This is used to receive arguments from the
742 /// toolbar controls and other control messages from Wireshark.
743 pub extcap_control_in: &'a Option<std::path::PathBuf>,
744 /// The extcap control sender if the `--extcap-control-out` argument is
745 /// provided on the command line. This is used to send control messages to
746 /// Wireshark to modify the toolbar controls and show status messages.
747 pub extcap_control_out: &'a Option<std::path::PathBuf>,
748}
749
750impl<'a> CaptureStep<'a> {
751 /// Create a new control sender for this capture, if `--extcap-control-out`
752 /// is specified in the command line. The control sender is used to send
753 /// control messages to Wireshark to modify
754 /// [`ToolbarControls`][controls::ToolbarControl] and communicate other
755 /// states.
756 #[cfg(feature = "sync")]
757 pub fn new_control_sender(&self) -> Option<controls::synchronous::ExtcapControlSender> {
758 self.extcap_control_out
759 .as_ref()
760 .map(|p| controls::synchronous::ExtcapControlSender::new(p))
761 }
762
763 /// Create a new control sender for this capture, if `--extcap-control-out`
764 /// is specified in the command line. The control sender is used to send
765 /// control messages to Wireshark to modify
766 /// [`ToolbarControls`][controls::ToolbarControl] and communicate other
767 /// states.
768 #[cfg(feature = "async")]
769 pub async fn new_control_sender_async(
770 &self,
771 ) -> Option<controls::asynchronous::ExtcapControlSender> {
772 if let Some(p) = &self.extcap_control_out {
773 Some(controls::asynchronous::ExtcapControlSender::new(p).await)
774 } else {
775 None
776 }
777 }
778
779 /// Spawn a new channel control reader, which also spawns a thread to
780 /// continuously forward control packets from the input fifo to the reader's
781 /// channel.
782 ///
783 /// See the documentations on
784 /// [`ChannelExtcapControlReader`][controls::synchronous::ChannelExtcapControlReader] for
785 /// more.
786 #[cfg(feature = "sync")]
787 pub fn spawn_channel_control_reader(
788 &self,
789 ) -> Option<controls::synchronous::ChannelExtcapControlReader> {
790 self.extcap_control_in
791 .as_ref()
792 .map(|p| controls::synchronous::ChannelExtcapControlReader::spawn(p.to_owned()))
793 }
794
795 /// Spawn a new channel control reader, which also spawns a thread to
796 /// continuously forward control packets from the input fifo to the reader's
797 /// channel.
798 ///
799 /// See the documentations on
800 /// [`ChannelExtcapControlReader`][controls::asynchronous::ChannelExtcapControlReader] for
801 /// more.
802 #[cfg(feature = "async")]
803 pub fn spawn_channel_control_reader_async(
804 &self,
805 ) -> Option<controls::asynchronous::ChannelExtcapControlReader> {
806 self.extcap_control_in
807 .as_ref()
808 .map(|p| controls::asynchronous::ChannelExtcapControlReader::spawn(p.to_owned()))
809 }
810
811 /// Create a new
812 /// [`ExtcapControlReader`][controls::synchronous::ExtcapControlReader] for
813 /// this capture context. `ExtcapControlReader` reads from the control
814 /// pipe given in this context synchronously, and blocks if there are no
815 /// incoming control packets waiting to be processed.
816 ///
817 /// For a higher level, easier to use API, see
818 /// [`spawn_channel_control_reader`][Self::spawn_channel_control_reader].
819 #[cfg(feature = "sync")]
820 pub fn new_control_reader(&self) -> Option<controls::synchronous::ExtcapControlReader> {
821 self.extcap_control_in
822 .as_ref()
823 .map(|p| controls::synchronous::ExtcapControlReader::new(p))
824 }
825
826 /// Create a new
827 /// [`ExtcapControlReader`][controls::asynchronous::ExtcapControlReader] for
828 /// this capture context. `ExtcapControlReader` reads from the control
829 /// pipe given in this context synchronously, and blocks if there are no
830 /// incoming control packets waiting to be processed.
831 ///
832 /// For a higher level, easier to use API, see
833 /// [`spawn_channel_control_reader`][Self::spawn_channel_control_reader].
834 #[cfg(feature = "async")]
835 pub async fn new_control_reader_async(
836 &self,
837 ) -> Option<controls::asynchronous::ExtcapControlReader> {
838 if let Some(p) = &self.extcap_control_in {
839 Some(controls::asynchronous::ExtcapControlReader::new(p).await)
840 } else {
841 None
842 }
843 }
844
845 /// Create an async version of the fifo that is used to write captured
846 /// packets to in the PCAP format.
847 #[cfg(feature = "async")]
848 pub async fn fifo_async(&self) -> tokio::io::Result<tokio::fs::File> {
849 tokio::fs::File::create(self.fifo_path).await
850 }
851}
852
853/// The extcap interface expects certain output "sentences" to stdout to
854/// communicate with Wireshark, like
855///
856/// ```text
857/// extcap {version=1.0}{help=Some help url}
858/// ```
859///
860/// This formatter serves as a wrapper to implement that format via the
861/// `Display` trait, and the Extcap output can be printed out like this:
862///
863/// ```
864/// use r_extcap::interface::Metadata;
865/// # use r_extcap::ExtcapFormatter;
866///
867/// print!("{}", ExtcapFormatter(&Metadata {
868/// version: "1.0".into(),
869/// help_url: "Some help url".into(),
870/// display_description: "Example extcap".into(),
871/// }));
872/// // Output: extcap {version=1.0}{help=Some help url}{display=Example extcap}
873/// ```
874pub struct ExtcapFormatter<'a, T: ?Sized>(pub &'a T)
875where
876 Self: Display;
877
878/// Elements that has a printable extcap sentence. See the documentation for
879/// [`ExtcapFormatter`] for details.
880pub trait PrintSentence {
881 /// The extcap interface expects certain output "sentences" to stdout to
882 /// communicate with Wireshark, like
883 ///
884 /// ```text
885 /// extcap {version=1.0}{help=Some help url}
886 /// ```
887 ///
888 /// This function writes to the formatter `f` in that format.
889 fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
890
891 /// Prints the extcap sentence to stdout.
892 fn print_sentence(&self) {
893 print!("{}", ExtcapFormatter(self));
894 }
895}
896
897impl<'a, T: PrintSentence + ?Sized> Display for ExtcapFormatter<'a, T> {
898 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
899 self.0.format_sentence(f)
900 }
901}
902
903/// Creates a [`Metadata`] from information in `Cargo.toml`, using the mapping
904/// as follows:
905///
906/// | Metadata field | Cargo.toml |
907/// |----------------------|---------------|
908/// |`version` | `version` |
909/// |`help_url` | `homepage` |
910/// |`display_description` | `description` |
911#[macro_export]
912macro_rules! cargo_metadata {
913 () => {
914 $crate::interface::Metadata {
915 version: env!("CARGO_PKG_VERSION").into(),
916 help_url: env!("CARGO_PKG_HOMEPAGE").into(),
917 display_description: env!("CARGO_PKG_DESCRIPTION").into(),
918 }
919 };
920}
921
922#[cfg(test)]
923mod test {
924 use clap::Args;
925
926 use super::ExtcapArgs;
927
928 #[test]
929 fn assert_args() {
930 let cmd = clap::Command::new("test");
931 let augmented_cmd = ExtcapArgs::augment_args(cmd);
932 augmented_cmd.debug_assert();
933 }
934}