sniffer/
sniffer.rs

1use std::path::PathBuf;
2
3use clap::Parser;
4use pcap::{Capture, ConnectionStatus, Device, IfFlags};
5
6// cargo run --release --bin sniffer -- --interface auto --path target/test.pcap
7
8#[derive(Parser)]
9struct Cli {
10    #[arg(
11        long,
12        help = "name of the interface, use `auto` to determine automatically"
13    )]
14    interface: Option<String>,
15
16    #[arg(
17        long,
18        help = "if `interface` is set, the packets will be written to the `pcap` file, \
19                otherwise the file will be a source of packets"
20    )]
21    path: PathBuf,
22
23    #[arg(long, help = "bpf filter, example: \"udp and not port 443\"")]
24    filter: Option<String>,
25
26    /// rng seed
27    #[arg(long, short)]
28    rng_seed: String,
29}
30
31fn init_logger_std() -> Box<dyn log::Log> {
32    use env_logger::{Builder, Env};
33
34    let env = Env::new().filter_or("RUST_LOG", "debug");
35    let logger = Builder::default().parse_env(env).build();
36    Box::new(logger) as Box<dyn log::Log>
37}
38
39fn main() {
40    log::set_boxed_logger(init_logger_std()).unwrap_or_default();
41    log::set_max_level(log::LevelFilter::max());
42
43    let Cli {
44        interface,
45        path,
46        filter,
47        rng_seed,
48    } = Cli::parse();
49
50    let rng_seed = <[u8; 32]>::try_from(hex::decode(rng_seed).unwrap().as_slice()).unwrap();
51
52    if let Some(name) = interface {
53        sudo::escalate_if_needed().unwrap();
54
55        log::info!("try to choose device");
56        let mut selected = None;
57        match Device::list() {
58            Ok(list) => {
59                for device in list {
60                    if name != "auto" {
61                        if device.name.eq(&name) {
62                            selected = Some(device);
63                            break;
64                        }
65                    } else {
66                        log::debug!("candidate: {device:?}");
67                        if !device.addresses.is_empty()
68                            && device.flags.contains(IfFlags::UP | IfFlags::RUNNING)
69                            && matches!(device.flags.connection_status, ConnectionStatus::Connected)
70                        {
71                            selected = Some(device);
72                        }
73                    }
74                }
75            }
76            Err(err) => log::error!("{err}"),
77        }
78
79        if let Some(device) = selected {
80            log::info!("will use: {device:?}");
81            let res = Ok(()).and_then(|()| {
82                let mut capture = Capture::from_device(device)?.immediate_mode(true).open()?;
83                capture
84                    .filter(&filter.unwrap_or_default(), true)
85                    .expect("Failed to apply filter");
86                let savefile = capture.savefile(&path)?;
87
88                webrtc_sniffer::run(capture, Some(savefile), rng_seed)
89            });
90            if let Err(err) = res {
91                log::error!("{err}");
92            }
93        } else {
94            log::error!("cannot find a device: {name}");
95        }
96    } else {
97        log::info!("use file");
98        let res = Ok(()).and_then(|()| {
99            let mut capture = Capture::from_file(&path)?;
100            capture
101                .filter(&filter.unwrap_or_default(), true)
102                .expect("Failed to apply filter");
103            webrtc_sniffer::run(capture, None, rng_seed)
104        });
105        if let Err(err) = res {
106            log::error!("{err}");
107        }
108    }
109}