add checkbox for setting capture filters

This commit is contained in:
GyulyVGC
2025-08-15 23:57:12 +02:00
parent c276536fa9
commit d58c27f36f
8 changed files with 171 additions and 43 deletions

View File

@@ -4,15 +4,6 @@
use std::fmt::Write;
use iced::Length::FillPortion;
use iced::widget::scrollable::Direction;
use iced::widget::tooltip::Position;
use iced::widget::{
Button, Checkbox, Column, Container, Row, Rule, Scrollable, Space, Text, TextInput, Tooltip,
button, center,
};
use iced::{Alignment, Font, Length, Padding, alignment};
use crate::gui::components::button::button_open_file;
use crate::gui::sniffer::Sniffer;
use crate::gui::styles::button::ButtonType;
@@ -20,9 +11,9 @@
use crate::gui::styles::scrollbar::ScrollbarType;
use crate::gui::styles::style_constants::{FONT_SIZE_SUBTITLE, FONT_SIZE_TITLE};
use crate::gui::styles::text::TextType;
use crate::gui::styles::text_input::TextInputType;
use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::export_pcap::ExportPcap;
use crate::gui::types::filters::Filters;
use crate::gui::types::message::Message;
use crate::networking::types::capture_context::CaptureSource;
use crate::translations::translations::{
@@ -33,10 +24,19 @@
directory_translation, export_capture_translation, file_name_translation,
};
use crate::translations::translations_4::import_capture_translation;
use crate::translations::translations_5::filter_traffic_translation;
use crate::utils::formatted_strings::get_path_termination_string;
use crate::utils::types::file_info::FileInfo;
use crate::utils::types::icon::Icon;
use crate::{ConfigSettings, Language, StyleType};
use iced::Length::FillPortion;
use iced::widget::scrollable::Direction;
use iced::widget::tooltip::Position;
use iced::widget::{
Button, Checkbox, Column, Container, Row, Rule, Scrollable, Space, Text, TextInput, Tooltip,
button, center,
};
use iced::{Alignment, Font, Length, Padding, alignment};
/// Computes the body of gui initial page
pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
@@ -57,8 +57,6 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
);
let col_capture_source = Column::new().push(col_adapter).push(col_import_pcap);
let col_bpf_filter = col_bpf_input(&sniffer.bpf_filter, font);
let filters_pane = Column::new()
.width(FillPortion(6))
.padding(10)
@@ -69,7 +67,7 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
.class(TextType::Title)
.size(FONT_SIZE_TITLE),
)
.push(col_bpf_filter)
.push(get_filters_group(&sniffer.filters, font, language))
.push(Rule::horizontal(40))
.push(get_export_pcap_group(
&sniffer.capture_source,
@@ -95,27 +93,6 @@ pub fn initial_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
Container::new(body).height(Length::Fill)
}
fn col_bpf_input(value: &str, font: Font) -> Column<'_, Message, StyleType> {
let input_row = Row::new().padding(Padding::ZERO.left(5)).push(
TextInput::new("Berkeley Packet Filter (BPF)", value)
.padding([3, 5])
.on_input(Message::BpfFilter)
.font(font)
.class(TextInputType::Standard),
);
Column::new()
.width(Length::Fill)
.spacing(7)
.push(
Text::new("Berkeley Packet Filter (BPF)")
.font(font)
.class(TextType::Subtitle)
.size(FONT_SIZE_SUBTITLE),
)
.push(input_row)
}
fn button_start<'a>(
font: Font,
language: Language,
@@ -298,6 +275,50 @@ fn get_col_import_pcap<'a>(
.push(button)
}
fn get_filters_group<'a>(
filters: &Filters,
font: Font,
language: Language,
) -> Container<'a, Message, StyleType> {
let expanded = filters.expanded();
let bpf = filters.bpf();
let caption = filter_traffic_translation(language);
let checkbox = Checkbox::new(caption, expanded)
.on_toggle(move |_| Message::ToggleFilters)
.size(18)
.font(font);
let mut ret_val = Column::new().spacing(10).push(checkbox);
if expanded {
let input = TextInput::new("", bpf)
.on_input(Message::BpfFilter)
.padding([2, 5])
.font(font);
let inner_col = Column::new()
.spacing(10)
.padding(Padding::ZERO.left(45))
.push(
Row::new()
.align_y(Alignment::Center)
.spacing(5)
.push(Text::new("BPF:").font(font))
.push(input),
);
ret_val = ret_val.push(inner_col);
}
Container::new(
Container::new(ret_val)
.padding(10)
.width(Length::Fill)
.class(ContainerType::BorderedRound),
)
.height(Length::Fill)
.align_y(Alignment::Start)
}
fn get_export_pcap_group<'a>(
cs: &CaptureSource,
export_pcap: &ExportPcap,

View File

@@ -38,6 +38,7 @@
use crate::gui::styles::types::custom_palette::{CustomPalette, ExtraStyles};
use crate::gui::styles::types::palette::Palette;
use crate::gui::types::export_pcap::ExportPcap;
use crate::gui::types::filters::Filters;
use crate::gui::types::message::Message;
use crate::gui::types::timing_events::TimingEvents;
use crate::mmdb::asn::ASN_MMDB;
@@ -92,7 +93,7 @@ pub struct Sniffer {
/// List of network devices
pub my_devices: Vec<MyDevice>,
/// BPF filter program to be applied to the capture
pub bpf_filter: String,
pub filters: Filters,
/// Signals if a pcap error occurred
pub pcap_error: Option<String>,
/// Messages status
@@ -155,7 +156,7 @@ pub fn new(configs: Configs) -> Self {
newer_release_available: None,
capture_source: CaptureSource::Device(device),
my_devices: Vec::new(),
bpf_filter: String::new(),
filters: Filters::default(),
pcap_error: None,
dots_pulse: (".".to_string(), 0),
traffic_chart: TrafficChart::new(style, language),
@@ -276,8 +277,11 @@ pub fn update(&mut self, message: Message) -> Task<Message> {
}
}
Message::DeviceSelection(name) => self.set_device(&name),
Message::ToggleFilters => {
self.filters.toggle();
}
Message::BpfFilter(value) => {
self.bpf_filter = value;
self.filters.set_bpf(value);
}
Message::DataReprSelection(unit) => self.traffic_chart.change_kind(unit),
Message::ReportSortSelection(sort) => {
@@ -716,7 +720,7 @@ fn start(&mut self) -> Task<Message> {
}
let pcap_path = self.export_pcap.full_path();
let capture_context =
CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.bpf_filter);
CaptureContext::new(&self.capture_source, pcap_path.as_ref(), &self.filters);
self.pcap_error = capture_context.error().map(ToString::to_string);
self.running_page = RunningPage::Overview;

84
src/gui/types/filters.rs Normal file
View File

@@ -0,0 +1,84 @@
#[derive(Default)]
pub struct Filters {
expanded: bool,
bpf: String,
}
impl Filters {
pub fn toggle(&mut self) {
self.expanded = !self.expanded;
}
pub fn set_bpf(&mut self, bpf: String) {
self.bpf = bpf;
}
pub fn expanded(&self) -> bool {
self.expanded
}
pub fn bpf(&self) -> &str {
&self.bpf
}
pub fn is_some_filter_active(&self) -> bool {
self.expanded && !self.bpf.trim().is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let filters = Filters::default();
assert_eq!(filters.expanded(), false);
assert_eq!(filters.bpf(), "");
}
#[test]
fn test_toggle() {
let mut filters = Filters::default();
assert_eq!(filters.expanded(), false);
filters.toggle();
assert_eq!(filters.expanded(), true);
filters.toggle();
assert_eq!(filters.expanded(), false);
}
#[test]
fn test_set_bpf() {
let mut filters = Filters::default();
assert_eq!(filters.bpf(), "");
filters.set_bpf("tcp port 80".to_string());
assert_eq!(filters.bpf(), "tcp port 80");
filters.set_bpf(" udp port 53 ".to_string());
assert_eq!(filters.bpf(), " udp port 53 ");
}
#[test]
fn test_is_some_filter_active() {
let mut filters = Filters::default();
assert_eq!(filters.is_some_filter_active(), false);
filters.toggle();
assert_eq!(filters.is_some_filter_active(), false);
filters.set_bpf("tcp port 80".to_string());
assert_eq!(filters.is_some_filter_active(), true);
filters.toggle();
assert_eq!(filters.is_some_filter_active(), false);
filters.toggle();
assert_eq!(filters.is_some_filter_active(), true);
filters.set_bpf(" \t \n ".to_string());
assert_eq!(filters.is_some_filter_active(), false);
}
}

View File

@@ -24,7 +24,9 @@ pub enum Message {
TickRun(usize, InfoTraffic, Vec<HostMessage>, bool),
/// Select network device
DeviceSelection(String),
/// Changed BPF filter
/// Toggle BPF filter checkbox
ToggleFilters,
/// Change BPF filter string
BpfFilter(String),
/// Select data representation to use
DataReprSelection(DataRepr),

View File

@@ -1,3 +1,4 @@
pub mod export_pcap;
pub mod filters;
pub mod message;
pub mod timing_events;

View File

@@ -1,10 +1,10 @@
use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat};
use crate::gui::types::filters::Filters;
use crate::networking::types::my_device::MyDevice;
use crate::networking::types::my_link_type::MyLinkType;
use crate::translations::translations::network_adapter_translation;
use crate::translations::translations_3::file_name_translation;
use crate::translations::types::language::Language;
use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat};
pub enum CaptureContext {
Live(Live),
@@ -14,13 +14,16 @@ pub enum CaptureContext {
}
impl CaptureContext {
pub fn new(source: &CaptureSource, pcap_out_path: Option<&String>, bpf: &str) -> Self {
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()),
};
if let Err(e) = cap_type.set_bpf(bpf) {
// only apply BPF filter if it is active, and return an error if it fails to apply
if filters.is_some_filter_active()
&& let Err(e) = cap_type.set_bpf(filters.bpf())
{
return Self::Error(e.to_string());
}

View File

@@ -3,4 +3,5 @@
pub mod translations_2;
pub mod translations_3;
pub mod translations_4;
pub mod translations_5;
pub mod types;

View File

@@ -0,0 +1,12 @@
#![allow(clippy::match_same_arms)]
use crate::translations::types::language::Language;
pub fn filter_traffic_translation(language: Language) -> String {
match language {
Language::EN => "Filter traffic",
Language::IT => "Filtra il traffico",
_ => "Filter traffic",
}
.to_string()
}