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 @@ -5,6 +5,7 @@ All Sniffnet releases with the relative changes are documented in this file.
## [UNRELEASED]
- The size of text and widgets can now be customised by setting a proper zoom value (fixes [#202](https://github.com/GyulyVGC/sniffnet/issues/202) and [#344](https://github.com/GyulyVGC/sniffnet/issues/344))
- Added possibility to totally customize the app's theme via styles defined in TOML files ([#286](https://github.com/GyulyVGC/sniffnet/pull/286))
- IP addresses can now be copied to clipboard from the popup related to a given entry of the connections table, and a new search parameter has been introduced in Inspect page to allow users filter their connections based on IP address values ([#409](https://github.com/GyulyVGC/sniffnet/pull/409))
- Added Japanese translation 🇯🇵 ([#343](https://github.com/GyulyVGC/sniffnet/pull/343))
- Added Uzbek translation 🇺🇿 ([#385](https://github.com/GyulyVGC/sniffnet/pull/385))
- Window size and position are now remembered, so that Sniffnet can reopen with the same window properties
Expand Down
Binary file modified resources/fonts/subset/icons.ttf
Binary file not shown.
57 changes: 51 additions & 6 deletions src/gui/pages/connection_details_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::gui::styles::style_constants::{get_font, get_font_headers, FONT_SIZE_
use crate::gui::styles::text::TextType;
use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::message::Message;
use crate::gui::types::timing_events::TimingEvents;
use crate::networking::manage_packets::{get_address_to_lookup, get_traffic_type, is_my_address};
use crate::networking::types::address_port_pair::AddressPortPair;
use crate::networking::types::host::Host;
Expand All @@ -28,6 +29,7 @@ use crate::translations::translations_2::{
fqdn_translation, mac_address_translation, socket_address_translation, source_translation,
transmitted_data_translation,
};
use crate::translations::translations_3::copy_translation;
use crate::utils::formatted_strings::{get_formatted_bytes_string_with_b, get_socket_address};
use crate::utils::types::icon::Icon;
use crate::{Language, Sniffer, StyleType};
Expand All @@ -37,7 +39,11 @@ pub fn connection_details_page(
key: AddressPortPair,
) -> Container<Message, Renderer<StyleType>> {
Container::new(lazy(
sniffer.runtime_data.tot_sent_packets + sniffer.runtime_data.tot_received_packets,
(
sniffer.runtime_data.tot_sent_packets + sniffer.runtime_data.tot_received_packets,
sniffer.timing_events.was_just_copy_ip(&key.address1),
sniffer.timing_events.was_just_copy_ip(&key.address2),
),
move |_| page_content(sniffer, &key),
))
}
Expand Down Expand Up @@ -116,6 +122,7 @@ fn page_content(
&val.mac_address1,
font,
language,
&sniffer.timing_events,
);
let mut dest_col = get_src_or_dest_col(
dest_caption,
Expand All @@ -124,6 +131,7 @@ fn page_content(
&val.mac_address2,
font,
language,
&sniffer.timing_events,
);

if address_to_lookup.eq(&key.address1) {
Expand Down Expand Up @@ -308,6 +316,7 @@ fn get_src_or_dest_col(
mac: &str,
font: Font,
language: Language,
timing_events: &TimingEvents,
) -> Column<'static, Message, Renderer<StyleType>> {
Column::new()
.spacing(4)
Expand All @@ -317,11 +326,17 @@ fn get_src_or_dest_col(
.align_x(Horizontal::Center),
)
.push(Rule::horizontal(10.0))
.push(TextType::highlighted_subtitle_with_desc(
socket_address_translation(language),
&get_socket_address(ip, port),
font,
))
.push(
Row::new()
.spacing(10)
.align_items(Alignment::End)
.push(TextType::highlighted_subtitle_with_desc(
socket_address_translation(language),
&get_socket_address(ip, port),
font,
))
.push(get_button_copy(language, font, ip, timing_events)),
)
.push(TextType::highlighted_subtitle_with_desc(
mac_address_translation(language),
mac,
Expand Down Expand Up @@ -359,3 +374,33 @@ fn assemble_widgets(
.push(vertical_space(Length::FillPortion(1))),
)
}

fn get_button_copy(
language: Language,
font: Font,
string: &String,
timing_events: &TimingEvents,
) -> Tooltip<'static, Message, Renderer<StyleType>> {
let icon = if timing_events.was_just_copy_ip(string) {
Text::new("✔").font(font).size(14)
} else {
Icon::Copy.to_text().size(12)
};

let content = button(
icon.horizontal_alignment(Horizontal::Center)
.vertical_alignment(Vertical::Center),
)
.padding(0)
.height(Length::Fixed(25.0))
.width(Length::Fixed(25.0))
.on_press(Message::CopyIp(string.clone()));

Tooltip::new(
content,
format!("{} (IP)", copy_translation(language)),
Position::Right,
)
.font(font)
.style(ContainerType::Tooltip)
}
18 changes: 17 additions & 1 deletion src/gui/pages/inspect_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::gui::types::message::Message;
use crate::networking::types::search_parameters::{FilterInputType, SearchParameters};
use crate::networking::types::traffic_direction::TrafficDirection;
use crate::report::get_report_entries::get_searched_entries;
use crate::translations::translations::application_protocol_translation;
use crate::translations::translations::{address_translation, application_protocol_translation};
use crate::translations::translations_2::{
administrative_entity_translation, country_translation, domain_name_translation,
no_search_results_translation, only_show_favorites_translation, search_filters_translation,
Expand Down Expand Up @@ -244,6 +244,14 @@ fn filters_col(
Row::new()
.align_items(Alignment::Center)
.spacing(10)
.push(filter_input(
FilterInputType::Address,
&search_params.address,
address_translation(language),
120.0,
search_params.clone(),
font,
))
.push(filter_input(
FilterInputType::App,
&search_params.app,
Expand Down Expand Up @@ -312,6 +320,10 @@ fn filter_input(
as_name: String::new(),
..search_params.clone()
},
FilterInputType::Address => SearchParameters {
address: String::new(),
..search_params.clone()
},
},
font,
);
Expand All @@ -335,6 +347,10 @@ fn filter_input(
as_name: new_value.trim().to_string(),
..search_params.clone()
},
FilterInputType::Address => SearchParameters {
address: new_value.trim().to_string(),
..search_params.clone()
},
})
})
.padding([0, 5])
Expand Down
2 changes: 2 additions & 0 deletions src/gui/types/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,6 @@ pub enum Message {
// CustomReport(String),
/// Save the configurations of the app and quit
CloseRequested,
/// Copies the given string to clipboard
CopyIp(String),
}
1 change: 1 addition & 0 deletions src/gui/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod message;
pub mod runtime_data;
pub mod sniffer;
pub mod timing_events;
20 changes: 12 additions & 8 deletions src/gui/types/sniffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
use std::collections::{HashSet, VecDeque};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

use iced::{window, Command};
use pcap::Device;
Expand All @@ -16,6 +15,7 @@ use crate::gui::pages::types::running_page::RunningPage;
use crate::gui::pages::types::settings_page::SettingsPage;
use crate::gui::styles::types::custom_palette::{CustomPalette, ExtraStyles};
use crate::gui::types::message::Message;
use crate::gui::types::timing_events::TimingEvents;
use crate::mmdb::asn::ASN_MMDB;
use crate::mmdb::country::COUNTRY_MMDB;
use crate::mmdb::types::mmdb_reader::MmdbReader;
Expand Down Expand Up @@ -75,8 +75,6 @@ pub struct Sniffer {
pub search: SearchParameters,
/// Current page number of inspect search results
pub page_number: usize,
/// Record the timestamp of last window focus
pub last_focus_time: std::time::Instant,
/// Application settings
pub settings: ConfigSettings,
/// Position and size of the app window
Expand All @@ -85,6 +83,8 @@ pub struct Sniffer {
pub country_mmdb_reader: Arc<MmdbReader>,
/// MMDB reader for ASN
pub asn_mmdb_reader: Arc<MmdbReader>,
/// Time-related events
pub timing_events: TimingEvents,
}

impl Sniffer {
Expand All @@ -108,14 +108,14 @@ impl Sniffer {
unread_notifications: 0,
search: SearchParameters::default(),
page_number: 1,
last_focus_time: std::time::Instant::now(),
settings: configs.settings.clone(),
window: configs.window,
country_mmdb_reader: Arc::new(MmdbReader::from(
&configs.settings.mmdb_country,
COUNTRY_MMDB,
)),
asn_mmdb_reader: Arc::new(MmdbReader::from(&configs.settings.mmdb_asn, ASN_MMDB)),
timing_events: TimingEvents::default(),
}
}

Expand Down Expand Up @@ -196,7 +196,7 @@ impl Sniffer {
Message::SwitchPage(next) => {
// To prevent SwitchPage be triggered when using `Alt` + `Tab` to switch back,
// first check if user switch back just now, and ignore the request for a short time.
if self.last_focus_time.elapsed() > Duration::from_millis(200) {
if !self.timing_events.was_just_focus() {
self.switch_page(next);
}
}
Expand Down Expand Up @@ -232,7 +232,7 @@ impl Sniffer {
}
}
}
Message::WindowFocused => self.last_focus_time = std::time::Instant::now(),
Message::WindowFocused => self.timing_events.focus_now(),
Message::GradientsSelection(gradient_type) => {
self.settings.color_gradient = gradient_type;
}
Expand Down Expand Up @@ -263,6 +263,10 @@ impl Sniffer {
self.get_configs().store();
return iced::window::close();
}
Message::CopyIp(string) => {
self.timing_events.copy_ip_now(string.clone());
return iced::clipboard::write(string);
}
_ => {}
}
Command::none()
Expand Down Expand Up @@ -1119,9 +1123,9 @@ mod tests {
}

#[test]
fn test_correctly_switch_running_and_notification_pages() {
fn test_correctly_switch_running_and_settings_pages() {
let mut sniffer = Sniffer::new(&Configs::default(), Arc::new(Mutex::new(None)));
sniffer.last_focus_time = std::time::Instant::now().sub(Duration::from_millis(400));
sniffer.timing_events.focus = std::time::Instant::now().sub(Duration::from_millis(400));

// initial status
assert_eq!(sniffer.settings_page, None);
Expand Down
39 changes: 39 additions & 0 deletions src/gui/types/timing_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::time::Duration;

pub struct TimingEvents {
/// Timestamp of last window focus
pub focus: std::time::Instant,
/// Timestamp of the last press on Copy IP button, with the related IP address
pub copy_ip: (std::time::Instant, String),
}

impl TimingEvents {
const TIMEOUT_FOCUS: u64 = 200;
const TIMEOUT_COPY_IP: u64 = 1500;

pub fn focus_now(&mut self) {
self.focus = std::time::Instant::now();
}

pub fn was_just_focus(&self) -> bool {
self.focus.elapsed() < Duration::from_millis(TimingEvents::TIMEOUT_FOCUS)
}

pub fn copy_ip_now(&mut self, ip: String) {
self.copy_ip = (std::time::Instant::now(), ip);
}

pub fn was_just_copy_ip(&self, ip: &String) -> bool {
self.copy_ip.0.elapsed() < Duration::from_millis(TimingEvents::TIMEOUT_COPY_IP)
&& self.copy_ip.1.eq(ip)
}
}

impl Default for TimingEvents {
fn default() -> Self {
Self {
focus: std::time::Instant::now(),
copy_ip: (std::time::Instant::now(), String::new()),
}
}
}
4 changes: 4 additions & 0 deletions src/networking/types/search_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pub struct SearchParameters {
/// Application protocol
pub app: String,
/// IP address
pub address: String,
/// Domain
pub domain: String,
/// Country
Expand All @@ -20,6 +22,7 @@ impl SearchParameters {
|| !self.domain.is_empty()
|| !self.country.is_empty()
|| !self.as_name.is_empty()
|| !self.address.is_empty()
}
}

Expand All @@ -28,4 +31,5 @@ pub enum FilterInputType {
Domain,
Country,
AS,
Address,
}
9 changes: 9 additions & 0 deletions src/report/get_report_entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub fn get_searched_entries(sniffer: &Sniffer) -> (Vec<ReportEntry>, usize) {
let searched_domain = &*sniffer.search.domain.to_lowercase();
let searched_country = &*sniffer.search.country.to_lowercase();
let searched_as_name = &*sniffer.search.as_name.to_lowercase();
let searched_address = &*sniffer.search.address.to_lowercase();
let searched_only_fav = sniffer.search.only_favorites;
// if a host-related filter is active and this address has not been resolved yet => false
if r_dns_host.is_none()
Expand Down Expand Up @@ -67,6 +68,14 @@ pub fn get_searched_entries(sniffer: &Sniffer) -> (Vec<ReportEntry>, usize) {
return false;
}
}
// check IP address filter
if !searched_address.is_empty() {
let source = key.address1.to_lowercase();
let dest = key.address2.to_lowercase();
if !source.contains(searched_address) && !dest.contains(searched_address) {
return false;
}
}
// check favorites filter
if searched_only_fav
&& !info_traffic_lock
Expand Down
2 changes: 1 addition & 1 deletion src/secondary_threads/check_updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn is_newer_release_available(max_retries: u8, seconds_between_retries: u8) -> O
}
}

#[cfg(test)]
#[cfg(all(test, not(target_os = "macos")))]
mod tests {
use super::*;

Expand Down
8 changes: 8 additions & 0 deletions src/translations/translations_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,11 @@ pub fn custom_style_translation(language: Language) -> &'static str {
_ => "Custom style",
}
}

pub fn copy_translation(language: Language) -> &'static str {
match language {
Language::EN => "Copy",
Language::IT => "Copia",
_ => "Copy",
}
}
2 changes: 2 additions & 0 deletions src/utils/types/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub enum Icon {
Bin,
BytesThreshold,
Clock,
Copy,
Dots,
Error,
// File,
Expand Down Expand Up @@ -74,6 +75,7 @@ impl Icon {
Icon::Star => "g",
Icon::Warning => "T",
Icon::Waves => "y",
Icon::Copy => "u",
})
.font(ICONS)
}
Expand Down