Pcap print stats on ctrl-c

Hello,

I'm new to rust and looking for a way to print the stats from pcap::Capture when user presses ctrl-c. I found the ctrlc crate and have that working with printing a string. Stuck with how to pass the stats to the ctrlc::set_handler collection from:

ctrls::set_handler( move || {
   println!("ctrl-C pressed");
   exit(1);
}).expect("Error setting handler");
while let Ok(packet) = capture.next() {
....
}

Any suggestion or reference would be appreciated.

I would share the stats between both the ctrl-c handler and the main thread. Probably by creating a Arc<Mutex<Stats>> and giving one reference to the handler while the capture loop has the other reference.

let stats = Arc::new(Mutex::new(Stats::default()));

let stats_2 = Arc::clone(&stats);
ctrls::set_handler( move || {
   println!("ctrl-C pressed");
   println!("{:?}", stats_2.lock().unwrap());
   exit(1);
}).expect("Error setting handler");

while let Ok(packet) = capture.next() {
  *stats.lock().unwrap() = packet;
}

Okay I'm getting closer.

let stats = Arc::new(Mutex::new(Stats::default()));

give me a compiler error use of undeclared type Stats.

I've tried to figure out the correct namespace with no luck yet.

pcap::Capture::stats 

is a fn that returns a Result<Stat, Error>

so I'm not sure how to declare stats

Locking a mutex inside a signal handler may deadlock. The things you are allowed to do inside a signal handler are very limited as many things acquire a lock, even memory allocation is not allowed. Instead you will have to signal the main thread that it needs to stop some way or another.

Edit: looks like the ctrlc crate doesn't run your code inside a signal handler, but on a dedicated thread. Please ignore this post.

I was just using the generic Stats name as a placeholder because the original post didn't mention what type captures.next() returns. You'll need to change it to suit your application and the data you want to share with the ctrl-c handler.

Okay, I believe I do understand. So I currently have the following and I'm stuck with a compiler error:

*stats.lock().unwrap() = cap.stats().unwrap();
                                      ^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here

Sample code I'm testing/trying your suggestion(s)

use pcap::{Capture, Device};
use std::process::exit;
use std::sync::Arc;
use std::sync::Mutex;

fn main() {

    let val = 0;
    let main_device = "eth0";
    let mut cap = Capture::from_device(main_device)
    .unwrap()
    .promisc(true)
    .snaplen(1500)
    .timeout(1000)
    .open()
    .unwrap();

    let stats = Arc::new(Mutex::new(pcap::Stat {
        received: val,
        dropped: val,
        if_dropped: val,
    }));
    let stats_2 = Arc::clone(&stats);
    ctrlc::set_handler(move || {
        println!("{}", stats_2.lock().unwrap(),received);
        exit(1);
    })
    .expect("Error setting Ctrl-C handler");

    while let Ok(packet) = cap.next() {
        *stats.lock().unwrap() = cap.stats().unwrap();
        println!("received packet {:?}", packet);
    }
}

You didn't paste the full error message so I'm not 100% sure, but is it saying the first mutable borrow is at cap.next() and the second one is cap.stats()?

If so, that's because your packet borrows something from inside the cap and you can't access cap again until you stop referencing packet (the class of bugs we're trying to prevent is called "iterator invalidation"). You should be able to swap the stats.lock() line inside the while let with your println!().

    while let Ok(packet) = cap.next() {
        println!("received packet {:?}", packet);
        *stats.lock().unwrap() = cap.stats().unwrap();
    }

Thank you for your time and help. You were correct cap.next() was the first mutable borrow and cap.stats() was the second mutable borrow. I left that out by mistake.

For my own clarity and understanding.

The Ok(packet) creates a mutable borrow of cap.next(), the println! macro takes ownership of the mutable borrow packet had over cap.next()? This in turns releases any ownership it had within cap and I'm able to call cap.stats().unwrap()?

Thank you again!

I think this is pretty close, but it might be helpful if we walk through it using more precise terminology. In particular, "ownership" isn't really the correct word here because the stats and packet don't own caps.

Based on the original code...

while let Ok(packet) = cap.next() {
    *stats.lock().unwrap() = cap.stats().unwrap();
    println!("received packet {:?}", packet);
}

... we can create a stripped down version of pcap

struct Packet<'buffer> {
    payload: &'buffer [u8],
}

#[derive(Default)]
struct Capture {
    buffer: Vec<u8>,
}

impl Capture {
    fn next(&mut self) -> Option<Packet<'_>> {
        // pretend we filled the buffer and parsed a packet from it
        Some(Packet {
            payload: &self.buffer,
        })
    }

    fn stats(&self) -> String {
        String::new()
    }
}

(playground)

We can also get rid of the while let and mutex stuff and simplify it to the following 4 lines.

let mut caps = Capture::default(); 
let packet = caps.next().unwrap(); 
let stats = caps.stats(); 
println!("{:?}", packet); 

We can also make some notes:

  1. The packet variable is created on line 2 and borrows mutably from caps[1]
  2. The packet variable is last used on line 4
  3. Therefore packet is "alive" from line 2 to line 4
  4. The stats variable is created on line 3 and borrows immutably from caps
  5. From our knowledge of Rust, we know the borrow checker enforces that you can't borrow (immutably or mutably) a value while an existing mutable borrow is alive (references are mutable XOR shared)

When you look at things that way, you can see that packet being alive at the same time as stats is breaking the mutable XOR shared rule so the borrow checker rejects your code.

My fix was to make sure the "liveliness ranges" (not a real term - I just made it up) for packet and stats never cross because packet stops being used once stats is created.


  1. technically it's just a & reference, but the signature, fn next(&mut self) -> Option<Packet<'_>>, ties the lifetime in Packet to the &mut self mutable borrow so it doesn't matter whether Packet borrows Capture mutably or immutably. ↩ī¸Ž

1 Like

This was very helpful. Again Thank You! for taking the time to provide this great explanation. I appreciate it.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.