pcap Rust Crate

repository·main·Indexed 20 days ago

https://github.com/rust-pcap/pcap

A Rust packet capture API providing access to packet sniffing capabilities via libpcap on Linux/macOS or Npcap on Windows. Version 2.4.0 supports listing network devices, opening capture handles, BPF filtering, and writing packets to savefiles. It features a state-based Capture<T> abstraction to manage handle lifecycles and provides options for streamed captures via tokio, zero-copy access through lending iterators, and integration with event loops via SelectableCapture.

Tokens
9.9K
Snippets
33
Records
41
Agent score
73%

What's inside pcap

  1. pcap core capabilities overview

    main

    The pcap crate provides the following core functionalities:

    • List network devices
    • Open capture handles on devices or savefiles
    • Retrieve packets from capture handles
    • Filter packets using BPF (Berkeley Packet Filter) programs
    • List, set, and get datalink link types
    • Configure parameters like promiscuity and buffer length
    • Write packets to savefiles
    • Inject packets into an interface
  2. Install pcap dependencies on Linux

    main

    Install the libpcap library and its header files using your package manager:

    • Debian-based: sudo apt install libpcap-dev (or equivalent)
    • Fedora: sudo dnf install libpcap-devel (or equivalent)

    Permissions Note: If you are not running your application as root, you must grant the binary the necessary network capabilities to capture packets:

    sudo setcap cap_net_raw,cap_net_admin=eip path/to/bin
    sudo setcap cap_net_raw,cap_net_admin=eip path/to/bin
  3. Install pcap dependencies on macOS

    main

    libpcap is typically installed by default on macOS.

    Important Usage Note: Setting a timeout of zero may cause pcap::Capture::next to hang indefinitely because it waits for the timeout to expire before returning. To avoid this, use a non-zero timeout and call pcap::Capture::next within a loop.

  4. Use lending iterators for zero-copy packet access

    main

    When the lending-iter feature is enabled, you can use Capture::into_iter() to obtain a PacketLendingIter.

    Unlike the standard PacketIter which uses a codec to transform packets, the PacketLendingIter yields Result<Packet<'a>, Error>. This allows for more efficient, zero-copy access to the raw packet data by yielding references (Packet<'a>) that are tied to the lifetime of the capture session.

    // Requires 'lending-iter' feature
    // This yields references to packets rather than decoded objects
    let mut packet_iter = capture.into_iter();
    
    for packet_result in packet_iter {
        match packet_result {
            Ok(packet) => {
                // 'packet' is a Packet<'a> containing references to the data
                println!("Packet length: {}", packet.data.len());
            }
            Err(e) => eprintln!("Error: {:?}", e),
        }
    }
  5. Abstract over live and offline captures using Activated

    main

    You can write generic functions that work with both live network captures (Capture<Active>) and file-based captures (Capture<Offline>) by using the Activated trait as a generic bound.

    use pcap::{Activated, Capture};
    
    fn read_packets<T: Activated>(mut capture: Capture<T>) {
        while let Ok(packet) = capture.next_packet() {
            println!("received packet! {:?}", packet);
        }
    }
  6. Use the Linktype struct to manage data link types

    main

    The Linktype struct represents a data link type (e.g., Ethernet, PPP, etc.). It is a wrapper around an i32 value. You can use predefined constants to specify common link types or convert between names and Linktype instances.

    Key methods:

    • get_name() -> Result<String, Error>: Returns the name of the link type (e.g., "EN10MB").
    • get_description() -> Result<String, Error>: Returns a description of the link type.
    • from_name(name: &str) -> Result<Linktype, Error>: Creates a Linktype from a string name. Returns Error::InvalidLinktype if the name is not recognized.
    use pcap::Linktype;
    
    let lt = Linktype::ETHERNET;
    let name = lt.get_name().unwrap();
    assert_eq!(name, "EN10MB");
    
    let from_str = Linktype::from_name("ETHERNET").unwrap();
    assert_eq!(from_str, Linktype::ETHERNET);
  7. Enable the lending-iter unstable feature

    main
    The lending-iter feature enables the lending packet iterator. This is currently an unstable feature and should be used at your own risk as it is not considered part of the public API.
  8. Inspect network device flags and status

    main

    The Device struct contains a flags field of type DeviceFlags, which provides information about the interface's state and capabilities.

    IfFlags

    Use DeviceFlags::contains() or helper methods to check for:

    • IfFlags::LOOPBACK: The device is a loopback interface.
    • IfFlags::UP: The device is up.
    • IfFlags::RUNNING: The device is running.
    • IfFlags::WIRELESS: The device is a wireless interface (includes Wi-Fi, IEEE 802.15.4, etc.).

    ConnectionStatus

    Describes the connectivity of the adapter:

    • ConnectionStatus::Connected: The adapter is connected (or associated with a network for wireless).
    • ConnectionStatus::Disconnected: The adapter is disconnected.
    • ConnectionStatus::Unknown: Connectivity status is unknown.
    • ConnectionStatus::NotApplicable: Connectivity status does not apply (e.g., loopback).
  9. Enable the capture-stream feature

    main

    To enable support for streamed packet captures, add the capture-stream feature to your Cargo.toml. Note that this feature adds a dependency on tokio = "1.0".

    [dependencies]
    pcap = { version = "2", features = ["capture-stream"] }
  10. How Capture states and phantom types work

    main

    The Capture<T> struct uses phantom types to represent the lifecycle and capabilities of a pcap handle at compile time. This prevents runtime errors by ensuring you only call methods valid for the current state of the capture.

    Capture States

    • Capture<Inactive>: Created via Capture::from_device(). You can configure settings (buffer size, snaplen, timeout, promiscuity) but cannot yet capture packets.
    • Capture<Active>: Created by calling .open() on an Inactive handle. This state allows packet retrieval via .next_packet() and applying filters via .filter().
    • Capture<Offline>: Created via Capture::from_file(). Used for reading pcap dump files as if they were live interfaces.
    • Capture<Dead>: Created via Capture::dead(). Used to create new pcap dump files without an active capture.

    State Traits

    • State: Implemented by all states (Inactive, Active, Offline, Dead).
    • Activated: Implemented by Active, Offline, and Dead. These states allow packet-related operations.
    // Example of transitioning from Inactive to Active
    let mut cap = Capture::from_device(Device::lookup().unwrap().unwrap())
                  .unwrap() // Capture<Inactive>
                  .open()    // Capture<Active>
                  .unwrap();
    
    while let Ok(packet) = cap.next_packet() {
        println!("received packet! {:?}", packet);
    }
  11. Configure a capture handle before opening

    main

    If you need to customize parameters like timeout, snaplen, or promiscuous mode, use Capture::from_device() instead of the direct .open() method on a device. This returns a Capture<Inactive> handle. After applying your configurations, call .open() to transition the handle to a Capture<Active> state ready for sniffing.

    use pcap::{Device, Capture};
    
    let main_device = Device::lookup().unwrap().unwrap();
    let mut cap = Capture::from_device(main_device).unwrap()
                      .promisc(true)
                      .snaplen(5000)
                      .open().unwrap();
    
    while let Ok(packet) = cap.next_packet() {
        println!("received packet! {:?}", packet);
    }