How to Apply BPF-Style Filters to Network Traffic in Sniffnet

Sniffnet applies Berkeley Packet Filter (BPF) expressions through a pipeline that moves from the UI layer in initial_page.rs to the Filters configuration struct, ultimately executing via pcap::Capture::filter in the CaptureContext initialization code.

Sniffnet, an open-source network monitoring tool by GyulyVGC/sniffnet, supports standard BPF syntax to capture only specific traffic patterns like tcp port 443 or udp and src host 10.0.0.5. Understanding how these filters propagate through the codebase helps you configure captures programmatically or debug filtering issues.

Understanding Sniffnet's BPF Filter Architecture

The implementation spans three distinct layers, each handling a specific responsibility in the filter lifecycle.

UI Layer: Capturing User Input

The filter interface resides in src/gui/pages/initial_page.rs. When a user enables the "Filter traffic" checkbox, the UI expands to reveal a text input field. Typing a BPF expression triggers Message::BpfFilter(value), which carries the raw filter string into the application's message loop.

Key components in this layer include:

  • Filters::expanded() – tracks whether the filter UI is visible
  • TextInput::new("", bpf).on_input(Message::BpfFilter) – captures keystrokes and emits update messages

Configuration Layer: Storing Filter State

The Filters struct in src/gui/types/filters.rs serves as the authoritative storage for BPF rules. It maintains two critical fields: expanded (boolean UI state) and bpf (the raw filter string).

Important methods on this struct include:

  • set_bpf() – updates the internal filter string
  • bpf() – retrieves the current filter expression
  • is_some_filter_active() – validates that the UI is expanded and the filter string is non-empty after trimming

When the Sniffer component receives Message::BpfFilter, it calls self.conf.filters.set_bpf(value) to persist the expression for the next capture session.

Capture Layer: Executing Against Network Traffic

The actual filter application occurs in src/networking/types/capture_context.rs. During CaptureContext::new() initialization, the code checks filters.is_some_filter_active(). If true, it invokes cap_type.set_bpf(filters.bpf()), which forwards the expression to the underlying pcap library via pcap::Capture::filter.

The CaptureType::set_bpf method (lines 49-53) returns a Result<(), pcap::Error>, allowing Sniffnet to handle invalid syntax gracefully. When resuming a paused capture, the resume method (lines 62-68) reapplies the current filter or falls back to a permissive "greater 0" filter if none is configured.

Step-by-Step BPF Filter Implementation

Follow this execution flow to understand how a filter moves from user input to network interface:

  1. Enable the Filter Panel – The user ticks the checkbox, setting filters.expanded to true via the UI controls in get_filters_group.

  2. Enter the Expression – Typing in the TextInput emits Message::BpfFilter containing the BPF string (e.g., icmp or tcp port 80).

  3. Update Configuration – The Sniffer::bpf_filter method (lines 512-514 in src/gui/sniffer.rs) stores the value in the Filters struct.

  4. Initialize Capture – Upon starting a capture, CaptureContext::new checks is_some_filter_active() and calls set_bpf() on the CaptureType instance.

  5. Apply to Interface – The pcap crate compiles the BPF expression and attaches it to the live capture device, filtering packets at the kernel level before they reach Sniffnet's analysis engine.

Code Examples

Configuring a BPF Filter Programmatically

When building Sniffnet components directly, configure filters before initializing the capture context:

use sniffnet::gui::types::conf::Conf;
use sniffnet::gui::types::filters::Filters;

// Initialize default configuration
let mut conf = Conf::default();

// Enable filter UI visibility and set the BPF rule
conf.filters.toggle();  // Optional: expands the filter panel in UI
conf.filters.set_bpf("tcp port 443 and host 192.168.1.1".into());

// The filter automatically applies when creating CaptureContext
let capture = CaptureContext::new(
    &capture_source,
    None,  // Optional pcap output file
    &conf.filters,
);

Handling Dynamic Filter Updates

To modify filters on an existing capture context (useful for pause/resume workflows):

use sniffnet::networking::types::capture_context::{CaptureContext, CaptureType};

// Assuming capture is paused and you have access to the context
if let Some(mut cap_type) = capture.consume().0 {
    // Apply new filter expression
    match cap_type.set_bpf("udp port 53") {
        Ok(()) => println!("Filter applied successfully"),
        Err(e) => eprintln!("Invalid BPF syntax: {}", e),
    }
}

Validating Filter State

Check whether an active filter exists before processing capture results:

if conf.filters.is_some_filter_active() {
    println!("Current BPF filter: {}", conf.filters.bpf());
    // Proceed with filtered capture initialization
}

Key Source Files and Functions

File Purpose
src/gui/pages/initial_page.rs Renders the filter checkbox and TextInput; emits Message::BpfFilter
src/gui/types/filters.rs Defines Filters struct with set_bpf(), bpf(), and is_some_filter_active()
src/gui/sniffer.rs Handles Message::BpfFilter via Sniffer::bpf_filter (lines 512-514)
src/networking/types/capture_context.rs Executes filters via CaptureContext::new and CaptureType::set_bpf (lines 27-31, 49-53)

These files implement the complete path from user input to kernel-level packet filtering.

Summary

  • Sniffnet supports standard BPF syntax (e.g., tcp port 80, icmp) through its integration with the pcap library.
  • Three-layer architecture separates UI presentation (initial_page.rs), configuration storage (filters.rs), and capture execution (capture_context.rs).
  • Filters apply at capture initialization via CaptureContext::new, with automatic reapplication when resuming paused captures.
  • Validation occurs at the pcap level, with set_bpf() returning errors for malformed expressions.

Frequently Asked Questions

What BPF syntax does Sniffnet support?

Sniffnet supports standard Berkeley Packet Filter syntax as implemented by the underlying libpcap library. This includes protocol filters (tcp, udp, icmp), port specifications (port 443), directional filters (src host, dst net), and complex boolean expressions (and, or, not). The expression passes directly to pcap::Capture::filter without modification.

Can I change BPF filters during an active capture?

You cannot change filters while a capture is actively running. According to the implementation in capture_context.rs, Sniffnet applies filters during CaptureContext::new() and within the resume method. To apply a different filter, pause the capture, update the filter string via Message::BpfFilter or Filters::set_bpf, and resume the capture to trigger reapplication.

Where does Sniffnet store the BPF filter between sessions?

The Filters struct in src/gui/types/filters.rs stores the BPF string in its bpf field. When you enable the filter UI checkbox in src/gui/pages/initial_page.rs, the expanded boolean toggles, and is_some_filter_active() validates that both conditions are met before applying the filter during the next capture initialization.

What happens if I enter an invalid BPF expression?

Invalid syntax returns an error from the pcap crate when CaptureType::set_bpf attempts to compile the filter. In src/networking/types/capture_context.rs (lines 49-53), set_bpf returns Result<(), pcap::Error>, which propagates up to prevent the capture from starting with a malformed filter. The UI remains responsive, allowing you to correct the syntax and retry.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →