How Sniffnet Captures and Logs Network Events: A Technical Deep Dive
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 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:
// 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 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:
// 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 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:
// 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 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:
// 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.
Event Classification and Emission
The system checks three notification categories against the aggregated InfoTraffic data:
- Data-threshold notifications: Trigger when total transferred bytes or packets exceed user-defined limits.
- Favorites notifications: Fire for traffic involving marked hosts or services.
- 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:
// 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(¬ification);
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. This unified error-reporting mechanism captures file and line information via the location!() macro:
// 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
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
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
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:
CaptureSourceandCaptureContexthandle device selection and pcap initialization insrc/networking/types/capture_context.rs. - Acquisition: The
packet_streamthread insrc/networking/traffic_preview.rspulls raw packets from the pcap handle via bounded channels. - Processing:
parse_packetsinsrc/networking/parse_packets.rsdecodes protocols, updatesInfoTraffic, and coordinates reverse DNS lookups. - Logging:
notify_and_loginsrc/notifications/notify_and_log.rsevaluates thresholds and maintains the notification history inLoggedNotifications. - Reliability: The
ErrorLoggertrait insrc/utils/error_logger.rsprovides 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.
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 →