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

> Learn how to apply BPF-style filters to network traffic in Sniffnet. Discover the pipeline from the UI to filter execution and optimize your network analysis with precise filtering.

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

---

**Sniffnet applies Berkeley Packet Filter (BPF) expressions through a pipeline that moves from the UI layer in [`initial_page.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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:

```rust
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):

```rust
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:

```rust
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`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/pages/initial_page.rs) | Renders the filter checkbox and `TextInput`; emits `Message::BpfFilter` |
| [`src/gui/types/filters.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/types/filters.rs) | Defines `Filters` struct with `set_bpf()`, `bpf()`, and `is_some_filter_active()` |
| [`src/gui/sniffer.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs) | Handles `Message::BpfFilter` via `Sniffer::bpf_filter` (lines 512-514) |
| [`src/networking/types/capture_context.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/initial_page.rs)), configuration storage ([`filters.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/filters.rs)), and capture execution ([`capture_context.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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.