# How Sniffnet Captures and Logs Network Events: A Technical Deep Dive

> Discover how Sniffnet captures and logs network events. Explore its pcap library integration, multi-threaded Rust pipeline, and structured logging for real-time insights.

- Repository: [Giuliano Bellini/sniffnet](https://github.com/GyulyVGC/sniffnet)
- Tags: deep-dive
- Published: 2026-04-28

---

**TLDR:** Sniffnet captures network traffic using the pcap library, processes packets through a multi-threaded Rust pipeline that aggregates statistics into an `InfoTraffic` structure, and logs significant events via the `notify_and_log` module which generates structured notifications for thresholds, favorites, and blacklisted IPs.

Sniffnet, developed by GyulyVGC/sniffnet, is a cross-platform network traffic monitor written in Rust. Understanding how network events are captured and logged by Sniffnet reveals a sophisticated architecture that bridges low-level packet capture with real-time event notifications. The implementation separates packet acquisition, protocol parsing, and event logging across distinct threads to maintain GUI responsiveness under high traffic loads.

## The Capture Configuration Pipeline

Every capture session begins with user configuration stored in the `Conf` struct. The system translates these settings into concrete capture resources through a type-safe enum hierarchy.

### Selecting the Capture Source

The `CaptureSource` enum in [`src/networking/types/capture_context.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/capture_context.rs) abstracts the choice between live network interfaces and offline pcap files. The `from_conf` method inspects the user's selection and constructs the appropriate variant:

```rust
// src/networking/types/capture_context.rs
impl CaptureSource {
    pub fn from_conf(conf: &Conf) -> Self {
        match conf.capture_source_picklist {
            CaptureSourcePicklist::Device => {
                let device = conf.device.to_my_device();
                Self::Device(device)
            }
            CaptureSourcePicklist::File => {
                let path = conf.import_pcap_path.clone();
                Self::File(MyPcapImport::new(path))
            }
        }
    }
}

```

This pattern ensures that both live device captures and file imports follow the same downstream processing path.

### Initializing the pcap Handle

The `CaptureContext::new` function in [`src/networking/types/capture_context.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/capture_context.rs) transforms the abstract `CaptureSource` into an active pcap handle. It creates a `CaptureType` (wrapping either live or offline captures), applies optional BPF filters, and configures save-file export if the user requested a live capture dump:

```rust
// src/networking/types/capture_context.rs
pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>, filters: &Filters) -> Self {
    let mut cap_type = match CaptureType::from_source(source, pcap_out_path) {
        Ok(c) => c,
        Err(e) => return Self::Error(e.to_string()),
    };
    // Apply BPF filter if requested
    if filters.is_some_filter_active() && let Err(e) = cap_type.set_bpf(filters.bpf()) {
        return Self::Error(e.to_string());
    }
    // Build the final context (Live, LiveWithSavefile, Offline, or Error)
    // ...
}

```

The `CaptureType` enum covers three operational modes: **Live**, **LiveWithSavefile**, and **Offline**, allowing the parser to handle packets uniformly regardless of origin.

## Multi-Threaded Packet Processing

Once the capture context is established, Sniffnet spawns dedicated threads to handle packet acquisition and parsing concurrently.

### The packet_stream Thread

The low-level `packet_stream` function in [`src/networking/traffic_preview.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/traffic_preview.rs) runs in a dedicated thread named `"thread_packet_stream"`. It continuously calls `next_packet()` on the pcap handle and forwards results through a bounded synchronous channel with a capacity of 10,000 packets:

```rust
// src/networking/traffic_preview.rs
fn packet_stream(
    mut cap: CaptureType,
    tx: &std::sync::mpsc::SyncSender<(Result<PacketOwned, pcap::Error>, Option<pcap::Stat>)>,
    dev_info: &DevInfo,
) {
    loop {
        let packet_res = cap.next_packet();
        let packet_owned = packet_res.map(|p| PacketOwned {
            data: p.data.into(),
            dev_info: dev_info.clone(),
        });
        if tx.send((packet_owned, cap.stats().ok())).is_err() {
            return;
        }
    }
}

```

This design decouples the blocking I/O of packet capture from the CPU-intensive parsing logic, preventing packet loss during traffic spikes.

### Parsing and Traffic Aggregation

The `parse_packets` function in [`src/networking/parse_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/parse_packets.rs) orchestrates the main processing loop. It receives raw packets from the `packet_stream` channel and spawns an additional `reverse_dns_lookups` thread for asynchronous hostname resolution.

For each packet, it extracts protocol headers via `get_sniffable_headers`, then updates the central traffic model using `modify_or_insert_in_map`:

```rust
// src/networking/parse_packets.rs (excerpt)
if let Some(headers) = get_sniffable_headers(&packet.data, my_link_type) {
    // ...
    let (traffic_direction, service) = modify_or_insert_in_map(
        &mut info_traffic_msg,
        &key,
        &cs,
        mac_addresses,
        icmp_type,
        arp_type,
        exchanged_bytes,
        ip_blacklist,
    );
    // ...
    // Write to pcap export if needed
    if let Some(file) = savefile.as_mut() {
        file.write(&Packet { header: &packet.header, data: &packet.data });
    }
}

```

The `InfoTraffic` structure maintains per-host and per-service counters, enabling real-time bandwidth statistics and protocol distribution analysis.

## Notification and Logging Logic

After processing intervals, Sniffnet evaluates which events merit user notification through the `notify_and_log` module in [`src/notifications/notify_and_log.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/notifications/notify_and_log.rs).

### Event Classification and Emission

The system checks three notification categories against the aggregated `InfoTraffic` data:

1. **Data-threshold notifications**: Trigger when total transferred bytes or packets exceed user-defined limits.
2. **Favorites notifications**: Fire for traffic involving marked hosts or services.
3. **IP-blacklist notifications**: Alert when traffic originates from or targets blacklisted addresses.

Each triggered event creates a `LoggedNotification` entry and optionally dispatches to remote webhooks:

```rust
// src/notifications/notify_and_log.rs (excerpt)
let data_info = info_traffic_msg.tot_data_info;

// Example: data-threshold check
if let Some(threshold) = notifications.data_notification.threshold {
    let data_repr = notifications.data_notification.data_repr;
    if data_info.tot_data(data_repr) > u128::from(threshold) {
        let notification = LoggedNotification::DataThresholdExceeded(DataThresholdExceeded {
            id: logged_notifications.tot(),
            data_repr,
            threshold: notifications.data_notification.previous_threshold,
            data_info,
            timestamp: get_formatted_timestamp(timestamp),
            is_expanded: false,
            hosts: threshold_hosts(info_traffic_msg, data_repr),
            services: threshold_services(info_traffic_msg, data_repr),
        });
        logged_notifications.push(&notification);
        send_remote_notification(notification, notifications.remote_notifications.clone());
        // ...
    }
}

```

For live captures, the system plays the configured notification sound; offline pcap imports suppress audio to avoid confusion.

## Error Handling and Observability

All fallible operations in the capture pipeline use the `ErrorLogger` trait defined in [`src/utils/error_logger.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/utils/error_logger.rs). This unified error-reporting mechanism captures file and line information via the `location!()` macro:

```rust
// src/utils/error_logger.rs (excerpt)
pub trait ErrorLogger<T, E> {
    fn log_err(self, loc: Location) -> Result<T, E>;
}

impl<T, E: Display> ErrorLogger<T, E> for Result<T, E> {
    fn log_err(self, location: Location) -> Result<T, E> {
        if let Err(e) = &self {
            eprintln!("Sniffnet error at [{file}:{line}]: {e}",
                      file = location.file, line = location.line);
            #[cfg(debug_assertions)]
            panic!();
        }
        self
    }
}

```

Thread spawning, pcap initialization, and channel operations all chain `.log_err(location!())` to ensure consistent error propagation and debugging output.

## End-to-End Implementation Examples

### Starting a Live Capture

```rust
use sniffnet::gui::types::conf::Conf;
use sniffnet::networking::types::capture_context::{CaptureContext, CaptureSource};

// Assume `conf` is already loaded from the user's config file.
let source = CaptureSource::from_conf(&conf);
let capture_context = CaptureContext::new(
    &source,
    None, // No export file
    &conf.filters,
);

```

### Configuring a BPF Filter

```rust
use sniffnet::gui::types::filters::Filters;

let mut filters = Filters::default();
filters.set_bpf("tcp port 443".to_string());
// Apply to conf.filters before creating CaptureContext

```

### Accessing Logged Notifications

```rust
use sniffnet::notifications::types::logged_notification::LoggedNotifications;

let total_events = logged_notifications.tot();
let recent_events = logged_notifications.recent(5);

```

## Summary

Sniffnet's event capture and logging architecture follows a clear pipeline:

- **Configuration**: `CaptureSource` and `CaptureContext` handle device selection and pcap initialization in [`src/networking/types/capture_context.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/capture_context.rs).
- **Acquisition**: The `packet_stream` thread in [`src/networking/traffic_preview.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/traffic_preview.rs) pulls raw packets from the pcap handle via bounded channels.
- **Processing**: `parse_packets` in [`src/networking/parse_packets.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/parse_packets.rs) decodes protocols, updates `InfoTraffic`, and coordinates reverse DNS lookups.
- **Logging**: `notify_and_log` in [`src/notifications/notify_and_log.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/notifications/notify_and_log.rs) evaluates thresholds and maintains the notification history in `LoggedNotifications`.
- **Reliability**: The `ErrorLogger` trait in [`src/utils/error_logger.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/utils/error_logger.rs) provides consistent error tracking across all capture operations.

## Frequently Asked Questions

### How does Sniffnet differentiate between live capture and pcap file import?

Sniffnet uses the `CaptureSource` enum to abstract the input type. When `CaptureSource::from_conf` detects a file selection, it wraps the path in `MyPcapImport` and `CaptureType::from_source` creates an offline pcap handle. The `parse_packets` function handles both modes identically, though it suppresses notification sounds for offline imports to prevent audio spam during file analysis.

### Where are captured network events physically stored?

Events reside in the `LoggedNotifications` structure in memory, which the GUI renders in real-time. If users enable the export feature during live capture, raw packets are written to a standard pcap file via the `Savefile` handle attached to `CaptureType`. Notification metadata (thresholds, timestamps) persists only for the application session unless forwarded to a remote webhook.

### Can notification thresholds be customized for specific protocols or hosts?

Yes. The `notify_and_log` function evaluates traffic against both global data thresholds and per-favorite configurations. The `InfoTraffic` structure maintains granular statistics by host and service, allowing the notification system to generate targeted alerts when specific IPs or protocols exceed configured limits.

### What happens if the packet processing thread encounters a malformed packet?

Malformed packets fail the `get_sniffable_headers` check and are silently skipped without updating the traffic maps. Critical errors (pcap disconnection, thread panics) are caught by the `ErrorLogger` trait, which prints the file and line number to stderr and optionally panics in debug builds, ensuring developers can trace issues to the specific `cap.next_packet()` or channel operation that failed.