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 visibleTextInput::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 stringbpf()– retrieves the current filter expressionis_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:
-
Enable the Filter Panel – The user ticks the checkbox, setting
filters.expandedto true via the UI controls inget_filters_group. -
Enter the Expression – Typing in the
TextInputemitsMessage::BpfFiltercontaining the BPF string (e.g.,icmp or tcp port 80). -
Update Configuration – The
Sniffer::bpf_filtermethod (lines 512-514 insrc/gui/sniffer.rs) stores the value in theFiltersstruct. -
Initialize Capture – Upon starting a capture,
CaptureContext::newchecksis_some_filter_active()and callsset_bpf()on theCaptureTypeinstance. -
Apply to Interface – The
pcapcrate 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 thepcaplibrary. - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →