Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All Sniffnet releases with the relative changes are documented in this file.

## [UNRELEASED]
- Send remote notifications via webhook ([#991](https://github.com/GyulyVGC/sniffnet/pull/991) — fixes [#841](https://github.com/GyulyVGC/sniffnet/issues/841))
- Pause and resume packet captures ([#992](https://github.com/GyulyVGC/sniffnet/pull/992) — fixes [#551](https://github.com/GyulyVGC/sniffnet/issues/551))
- Added Czech translation 🇨🇿 ([#960](https://github.com/GyulyVGC/sniffnet/pull/960))
- Improve update checks using `semver` ([#891](https://github.com/GyulyVGC/sniffnet/pull/891))
- Build the app also for Windows ARM64 (fixes [#988](https://github.com/GyulyVGC/sniffnet/issues/988))
Expand Down
Binary file modified resources/fonts/subset/icons.ttf
Binary file not shown.
66 changes: 61 additions & 5 deletions src/gui/components/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::gui::types::message::Message;
use crate::gui::types::settings::Settings;
use crate::translations::translations::{quit_analysis_translation, settings_translation};
use crate::translations::translations_3::thumbnail_mode_translation;
use crate::translations::translations_4::{pause_translation, resume_translation};
use crate::utils::types::icon::Icon;
use crate::{Language, SNIFFNET_TITLECASE, StyleType};

Expand All @@ -37,6 +38,7 @@ pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
language,
color_gradient,
unread_notifications,
sniffer.frozen,
);
}

Expand All @@ -60,10 +62,15 @@ pub fn header(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
Container::new(Space::with_width(60))
})
.push(horizontal_space())
.push(Container::new(Space::with_width(40)))
.push(Container::new(Space::with_width(80)))
.push(Space::with_width(20))
.push(logo)
.push(Space::with_width(20))
.push(if is_running {
Container::new(get_button_freeze(font, language, sniffer.frozen, false))
} else {
Container::new(Space::with_width(40))
})
.push(if is_running {
Container::new(get_button_minimize(font, language, false))
} else {
Expand Down Expand Up @@ -161,9 +168,56 @@ pub fn get_button_minimize<'a>(
.class(ButtonType::Thumbnail)
.on_press(Message::ToggleThumbnail(false));

Tooltip::new(content, Text::new(tooltip).font(font), Position::Right)
.gap(0)
.class(tooltip_style)
Tooltip::new(
content,
Text::new(tooltip).font(font),
Position::FollowCursor,
)
.gap(0)
.class(tooltip_style)
}

pub fn get_button_freeze<'a>(
font: Font,
language: Language,
frozen: bool,
thumbnail: bool,
) -> Tooltip<'a, Message, StyleType> {
let size = if thumbnail { 19 } else { 23 };
let button_size = if thumbnail { 30 } else { 40 };
let icon = if frozen { Icon::Resume } else { Icon::Pause };
let tooltip = if thumbnail {
""
} else if frozen {
resume_translation(language)
} else {
pause_translation(language)
};
let tooltip_style = if thumbnail {
ContainerType::Standard
} else {
ContainerType::Tooltip
};

let content = button(
icon.to_text()
.size(size)
.align_x(Alignment::Center)
.align_y(Alignment::Center),
)
.padding(0)
.height(button_size)
.width(button_size)
.class(ButtonType::Thumbnail)
.on_press(Message::Freeze);

Tooltip::new(
content,
Text::new(tooltip).font(font),
Position::FollowCursor,
)
.gap(0)
.class(tooltip_style)
}

fn thumbnail_header<'a>(
Expand All @@ -172,14 +226,16 @@ fn thumbnail_header<'a>(
language: Language,
color_gradient: GradientType,
unread_notifications: usize,
frozen: bool,
) -> Container<'a, Message, StyleType> {
Container::new(
Row::new()
.align_y(Alignment::Center)
.push(horizontal_space())
.push(Space::with_width(80))
.push(Space::with_width(110))
.push(Text::new(SNIFFNET_TITLECASE).font(font_headers))
.push(Space::with_width(10))
.push(get_button_freeze(font, language, frozen, true))
.push(get_button_minimize(font, language, true))
.push(horizontal_space())
.push(if unread_notifications > 0 {
Expand Down
30 changes: 30 additions & 0 deletions src/gui/sniffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ pub struct Sniffer {
pub id: Option<Id>,
/// Host data for filter dropdowns (comboboxes)
pub host_data_states: HostDataStates,
/// Flag reporting whether the packet capture is frozen
pub frozen: bool,
/// Sender to freeze the packet capture
freeze_tx: Option<tokio::sync::broadcast::Sender<()>>,
}

impl Sniffer {
Expand Down Expand Up @@ -160,6 +164,8 @@ impl Sniffer {
thumbnail: false,
id: None,
host_data_states: HostDataStates::default(),
frozen: false,
freeze_tx: None,
}
}

Expand All @@ -175,6 +181,7 @@ impl Sniffer {
}) => match key.as_ref() {
Key::Character("q") => Some(Message::QuitWrapper),
Key::Character("t") => Some(Message::CtrlTPressed),
Key::Named(Named::Space) => Some(Message::CtrlSpacePressed),
_ => None,
},
_ => None,
Expand All @@ -185,6 +192,7 @@ impl Sniffer {
Modifiers::COMMAND => match key.as_ref() {
Key::Character("q") => Some(Message::QuitWrapper),
Key::Character("t") => Some(Message::CtrlTPressed),
Key::Named(Named::Space) => Some(Message::CtrlSpacePressed),
Key::Character(",") => Some(Message::OpenLastSettings),
Key::Named(Named::Backspace) => Some(Message::ResetButtonPressed),
Key::Character("d") => Some(Message::CtrlDPressed),
Expand Down Expand Up @@ -500,6 +508,14 @@ impl Sniffer {
return Task::done(Message::ToggleThumbnail(false));
}
}
Message::CtrlSpacePressed => {
if self.running_page.is_some()
&& self.settings_page.is_none()
&& self.modal.is_none()
{
return Task::done(Message::Freeze);
}
}
Message::ScaleFactorShortcut(increase) => {
let scale_factor = self.conf.settings.scale_factor;
if !(scale_factor > 2.99 && increase || scale_factor < 0.31 && !increase) {
Expand Down Expand Up @@ -555,6 +571,12 @@ impl Sniffer {
.remote_notifications
.set_url(&url);
}
Message::Freeze => {
self.frozen = !self.frozen;
if let Some(tx) = &self.freeze_tx {
let _ = tx.send(());
}
}
}
Task::none()
}
Expand Down Expand Up @@ -750,6 +772,9 @@ impl Sniffer {
self.traffic_chart
.change_capture_source(matches!(capture_source, CaptureSource::Device(_)));
let (tx, rx) = async_channel::unbounded();
let (freeze_tx, freeze_rx) = tokio::sync::broadcast::channel(1_048_575);
let freeze_rx2 = freeze_tx.subscribe();
let filters = self.conf.filters.clone();
let _ = thread::Builder::new()
.name("thread_parse_packets".to_string())
.spawn(move || {
Expand All @@ -758,11 +783,14 @@ impl Sniffer {
capture_source,
&mmdb_readers,
capture_context,
filters,
&tx,
(freeze_rx, freeze_rx2),
);
})
.log_err(location!());
self.current_capture_rx.1 = Some(rx.clone());
self.freeze_tx = Some(freeze_tx);
return Task::run(rx, |backend_msg| match backend_msg {
BackendTrafficMessage::TickRun(cap_id, msg, host_msg, no_more_packets) => {
Message::TickRun(cap_id, msg, host_msg, no_more_packets)
Expand Down Expand Up @@ -800,6 +828,8 @@ impl Sniffer {
self.page_number = 1;
self.thumbnail = false;
self.host_data_states = HostDataStates::default();
self.frozen = false;
self.freeze_tx = None;
}

fn set_device(&mut self, name: &str) {
Expand Down
4 changes: 4 additions & 0 deletions src/gui/types/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ pub enum Message {
Drag,
/// Ctrl+T keys have been pressed
CtrlTPressed,
/// Ctrl+Space keys have been pressed
CtrlSpacePressed,
/// Edit scale factor via keyboard shortcut
ScaleFactorShortcut(bool),
/// Set new release status
Expand All @@ -139,4 +141,6 @@ pub enum Message {
ToggleRemoteNotifications,
/// The remote notifications URL has been updated
RemoteNotificationsUrl(String),
/// Pause or resume live capture
Freeze,
}
28 changes: 27 additions & 1 deletion src/networking/parse_packets.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Module containing functions executed by the thread in charge of parsing sniffed packets

use crate::gui::types::filters::Filters;
use crate::location;
use crate::mmdb::asn::get_asn;
use crate::mmdb::country::get_country;
Expand Down Expand Up @@ -32,15 +33,20 @@ use std::net::IpAddr;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use tokio::sync::broadcast::Receiver;

/// The calling thread enters a loop in which it waits for network packets
pub fn parse_packets(
cap_id: usize,
mut cs: CaptureSource,
mmdb_readers: &MmdbReaders,
capture_context: CaptureContext,
filters: Filters,
tx: &Sender<BackendTrafficMessage>,
freeze_rxs: (Receiver<()>, Receiver<()>),
) {
let (mut freeze_rx, mut freeze_rx_2) = freeze_rxs;

let my_link_type = capture_context.my_link_type();
let (cap, mut savefile) = capture_context.consume();

Expand All @@ -55,10 +61,18 @@ pub fn parse_packets(
let (pcap_tx, pcap_rx) = std::sync::mpsc::sync_channel(10_000);
let _ = thread::Builder::new()
.name("thread_packet_stream".to_string())
.spawn(move || packet_stream(cap, &pcap_tx))
.spawn(move || packet_stream(cap, &pcap_tx, &mut freeze_rx_2, &filters))
.log_err(location!());

loop {
// check if we need to freeze the parsing
if freeze_rx.try_recv().is_ok() {
// wait until unfreeze
let _ = freeze_rx.blocking_recv();
// reset the first packet ticks
first_packet_ticks = Some(Instant::now());
}

let (packet_res, cap_stats) = pcap_rx
.recv_timeout(Duration::from_millis(150))
.unwrap_or((Err(pcap::Error::TimeoutExpired), None));
Expand Down Expand Up @@ -486,8 +500,20 @@ fn maybe_send_tick_run_offline(
fn packet_stream(
mut cap: CaptureType,
tx: &std::sync::mpsc::SyncSender<(Result<PacketOwned, pcap::Error>, Option<pcap::Stat>)>,
freeze_rx: &mut Receiver<()>,
filters: &Filters,
) {
loop {
// check if we need to freeze the parsing
if freeze_rx.try_recv().is_ok() {
// pause the capture
cap.pause();
// wait until unfreeze
let _ = freeze_rx.blocking_recv();
// resume the capture
cap.resume(filters);
}

let packet_res = cap.next_packet();
let packet_owned = packet_res.map(|p| PacketOwned {
header: *p.header,
Expand Down
18 changes: 17 additions & 1 deletion src/networking/types/capture_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl CaptureType {
CaptureSource::Device(device) => {
let inactive = Capture::from_device(device.to_pcap_device())?;
let cap = inactive
.promisc(true)
.promisc(false)
.buffer_size(2_000_000) // 2MB buffer -> 10k packets of 200 bytes
.snaplen(if pcap_out_path.is_some() {
i32::from(u16::MAX)
Expand All @@ -150,6 +150,22 @@ impl CaptureType {
Self::Offline(cap) => cap.filter(bpf, true),
}
}

pub fn pause(&mut self) {
if let Self::Live(cap) = self {
let _ = cap.filter("less 2", true).log_err(location!());
}
}

pub fn resume(&mut self, filters: &Filters) {
if let Self::Live(cap) = self {
if filters.is_some_filter_active() {
let _ = cap.filter(filters.bpf(), true).log_err(location!());
} else if cap.filter("", true).log_err(location!()).is_err() {
let _ = cap.filter("greater 0", true).log_err(location!());
}
}
}
}

#[derive(Clone)]
Expand Down
4 changes: 4 additions & 0 deletions src/utils/types/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub enum Icon {
Update,
Warning,
Waves,
Pause,
Resume,
}

impl Icon {
Expand Down Expand Up @@ -105,6 +107,8 @@ impl Icon {
Icon::Update => '<',
// Icon::Expand => 'p',
// Icon::Collapse => 'q',
Icon::Pause => '-',
Icon::Resume => '+',
}
}

Expand Down