From 211ea028b4cd4910da27bb20df1ff36e4ab6a014 Mon Sep 17 00:00:00 2001 From: Giuliano Bellini s294739 Date: Mon, 1 May 2023 19:00:08 +0200 Subject: [PATCH] feat: local addresses identification and directed broadcast support --- .../4x3/question-mark-svgrepo-com.svg | 7 - .../countries_flags/4x3/zz-broadcast.svg | 13 + resources/countries_flags/4x3/zz-home.svg | 16 + .../countries_flags/4x3/zz-multicast.svg | 11 + resources/countries_flags/4x3/zz-unknown.svg | 17 ++ src/gui/pages/inspect_page.rs | 48 ++- src/gui/pages/notifications_page.rs | 21 +- src/gui/pages/overview_page.rs | 33 ++- src/gui/types/sniffer.rs | 2 +- src/networking/manage_packets.rs | 279 +++++++++++++++--- src/networking/types/data_info_host.rs | 17 ++ .../types/info_address_port_pair.rs | 15 +- src/networking/types/info_traffic.rs | 3 +- src/networking/types/mod.rs | 2 + src/networking/types/traffic_direction.rs | 14 + src/networking/types/traffic_type.rs | 20 +- src/notifications/notify_and_log.rs | 1 + .../types/logged_notification.rs | 2 + src/report/get_report_entries.rs | 12 +- src/secondary_threads/parse_packets.rs | 104 ++++--- src/translations/translations_2.rs | 20 +- src/utils/asn.rs | 8 +- src/utils/countries.rs | 78 ++++- src/utils/formatted_strings.rs | 6 +- 24 files changed, 562 insertions(+), 187 deletions(-) delete mode 100644 resources/countries_flags/4x3/question-mark-svgrepo-com.svg create mode 100644 resources/countries_flags/4x3/zz-broadcast.svg create mode 100644 resources/countries_flags/4x3/zz-home.svg create mode 100644 resources/countries_flags/4x3/zz-multicast.svg create mode 100644 resources/countries_flags/4x3/zz-unknown.svg create mode 100644 src/networking/types/data_info_host.rs create mode 100644 src/networking/types/traffic_direction.rs diff --git a/resources/countries_flags/4x3/question-mark-svgrepo-com.svg b/resources/countries_flags/4x3/question-mark-svgrepo-com.svg deleted file mode 100644 index c4b3abd1..00000000 --- a/resources/countries_flags/4x3/question-mark-svgrepo-com.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/resources/countries_flags/4x3/zz-broadcast.svg b/resources/countries_flags/4x3/zz-broadcast.svg new file mode 100644 index 00000000..22a3fba1 --- /dev/null +++ b/resources/countries_flags/4x3/zz-broadcast.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/countries_flags/4x3/zz-home.svg b/resources/countries_flags/4x3/zz-home.svg new file mode 100644 index 00000000..bf2147d6 --- /dev/null +++ b/resources/countries_flags/4x3/zz-home.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/countries_flags/4x3/zz-multicast.svg b/resources/countries_flags/4x3/zz-multicast.svg new file mode 100644 index 00000000..f11e6473 --- /dev/null +++ b/resources/countries_flags/4x3/zz-multicast.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/resources/countries_flags/4x3/zz-unknown.svg b/resources/countries_flags/4x3/zz-unknown.svg new file mode 100644 index 00000000..efb9c0a0 --- /dev/null +++ b/resources/countries_flags/4x3/zz-unknown.svg @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs index 85067292..8ebde945 100644 --- a/src/gui/pages/inspect_page.rs +++ b/src/gui/pages/inspect_page.rs @@ -5,7 +5,7 @@ use crate::gui::styles::types::element_type::ElementType; use crate::gui::styles::types::style_tuple::StyleTuple; use crate::gui::types::message::Message; use crate::report::get_report_entries::get_searched_entries; -use crate::utils::countries::{get_flag_from_country_code, FLAGS_WIDTH_SMALL}; +use crate::utils::countries::{get_flag_tooltip, FLAGS_WIDTH_SMALL}; use crate::utils::formatted_strings::{get_connection_color, get_open_report_tooltip}; use crate::{Language, RunningPage, Sniffer, StyleType}; use iced::widget::{Button, Column, Container, Row, Scrollable, Text, Tooltip}; @@ -66,33 +66,27 @@ pub fn inspect_page(sniffer: &Sniffer) -> Container { for index in &search_results { let info_traffic_lock = sniffer.info_traffic.lock().unwrap(); let key_val = info_traffic_lock.map.get_index(*index).unwrap(); - let entry_color = get_connection_color(key_val.1.traffic_type, sniffer.style); - let mut entry_row = Row::new().align_items(Alignment::Center).push( - Text::new(format!( - " {}{}", - key_val.0.print_gui(), - key_val.1.print_gui() - )) - .style(iced::theme::Text::Color(entry_color)) - .font(SARASA_MONO_SC_BOLD), - ); - if key_val.1.country.is_empty() { - entry_row = entry_row - .push( - Text::new("?") - .width(Length::Fixed(FLAGS_WIDTH_SMALL)) - .style(iced::theme::Text::Color(entry_color)) - .font(SARASA_MONO_SC_BOLD), - ) - .push(Text::new(" ")); - } else { - entry_row = entry_row - .push(get_flag_from_country_code( - &key_val.1.country, - FLAGS_WIDTH_SMALL, + let entry_color = get_connection_color(key_val.1.traffic_direction, sniffer.style); + let entry_row = Row::new() + .align_items(Alignment::Center) + .push( + Text::new(format!( + " {}{}", + key_val.0.print_gui(), + key_val.1.print_gui() )) - .push(Text::new(" ")); - } + .style(iced::theme::Text::Color(entry_color)) + .font(SARASA_MONO_SC_BOLD), + ) + .push(get_flag_tooltip( + &key_val.1.country, + FLAGS_WIDTH_SMALL, + key_val.1.is_local, + key_val.1.traffic_type, + sniffer.language, + sniffer.style, + )) + .push(Text::new(" ")); scroll_report = scroll_report.push( button(entry_row) diff --git a/src/gui/pages/notifications_page.rs b/src/gui/pages/notifications_page.rs index 852a6437..3b34a57e 100644 --- a/src/gui/pages/notifications_page.rs +++ b/src/gui/pages/notifications_page.rs @@ -24,7 +24,7 @@ use crate::translations::translations::{ packets_exceeded_translation, packets_exceeded_value_translation, per_second_translation, threshold_translation, }; -use crate::utils::countries::{get_flag_from_country_code, FLAGS_WIDTH_BIG}; +use crate::utils::countries::{get_flag_tooltip, FLAGS_WIDTH_BIG}; use crate::utils::formatted_strings::get_formatted_bytes_string; use crate::{Language, RunningPage, Sniffer, StyleType}; @@ -187,9 +187,8 @@ fn packets_notification_log( Tooltip::new( Text::new("e").font(ICONS).size(80), packets_exceeded_translation(language), - Position::Left, + Position::FollowCursor, ) - .gap(5) .font(font) .style(>::into( StyleTuple(style, ElementType::Tooltip), @@ -266,9 +265,8 @@ fn bytes_notification_log( Tooltip::new( Text::new("f").font(ICONS).size(80), bytes_exceeded_translation(language), - Position::Left, + Position::FollowCursor, ) - .gap(5) .font(font) .style(>::into( StyleTuple(style, ElementType::Tooltip), @@ -323,8 +321,14 @@ fn favorite_notification_log( let details_str = format!("{domain} - {}", asn.name); let mut row_flag_details = Row::new().align_items(Alignment::Center).spacing(5); if !country.is_empty() { - row_flag_details = - row_flag_details.push(get_flag_from_country_code(&country, FLAGS_WIDTH_BIG)); + row_flag_details = row_flag_details.push(get_flag_tooltip( + &country, + FLAGS_WIDTH_BIG, + logged_notification.data_info_host.is_local, + logged_notification.data_info_host.traffic_type, + language, + style, + )); } row_flag_details = row_flag_details.push(Text::new(details_str).font(font)); let content = Row::new() @@ -335,9 +339,8 @@ fn favorite_notification_log( Tooltip::new( Text::new("g").font(ICONS).size(80), favorite_transmitted_translation(language), - Position::Left, + Position::FollowCursor, ) - .gap(5) .font(font) .style(>::into( StyleTuple(style, ElementType::Tooltip), diff --git a/src/gui/pages/overview_page.rs b/src/gui/pages/overview_page.rs index 9f6f38a7..3ed8425f 100644 --- a/src/gui/pages/overview_page.rs +++ b/src/gui/pages/overview_page.rs @@ -32,9 +32,9 @@ use crate::translations::translations::{ }; use crate::translations::translations_2::{ data_representation_translation, dropped_packets_translation, host_translation, - of_total_translation, only_top_30_hosts, + of_total_translation, only_top_30_hosts_translation, }; -use crate::utils::countries::{get_flag_from_country_code, FLAGS_WIDTH_BIG}; +use crate::utils::countries::{get_flag_tooltip, FLAGS_WIDTH_BIG}; use crate::utils::formatted_strings::{ get_active_filters_string, get_formatted_bytes_string, get_percentage_string, }; @@ -402,12 +402,12 @@ fn col_host(width: f32, sniffer: &Sniffer) -> Column<'static, Message> { .align_items(Alignment::Center); let entries = get_host_entries(&sniffer.info_traffic, chart_type); - for (host, (data_info, is_favorite)) in &entries { + for (host, data_info_host) in &entries { let (mut incoming_bar_len, mut outgoing_bar_len) = get_bars_length( width * 0.86, chart_type, - entries.get(0).unwrap().1 .0.clone(), - data_info, + entries.get(0).unwrap().1.data_info.clone(), + &data_info_host.data_info, ); let star_button = button( @@ -423,7 +423,7 @@ fn col_host(width: f32, sniffer: &Sniffer) -> Column<'static, Message> { .style( StyleTuple( sniffer.style, - if *is_favorite { + if data_info_host.is_favorite { ElementType::Starred } else { ElementType::NotStarred @@ -431,7 +431,10 @@ fn col_host(width: f32, sniffer: &Sniffer) -> Column<'static, Message> { ) .into(), ) - .on_press(Message::AddOrRemoveFavorite(host.clone(), !*is_favorite)); + .on_press(Message::AddOrRemoveFavorite( + host.clone(), + !data_info_host.is_favorite, + )); // normalize smaller values if incoming_bar_len > 0.0 && incoming_bar_len < 3.0 { @@ -454,10 +457,11 @@ fn col_host(width: f32, sniffer: &Sniffer) -> Column<'static, Message> { })) .push(horizontal_space(Length::FillPortion(1))) .push(Text::new(if chart_type.eq(&ChartType::Packets) { - data_info.tot_packets().to_string() + data_info_host.data_info.tot_packets().to_string() } else { let mut bytes_string = - get_formatted_bytes_string(data_info.tot_bytes()).replace(" ", " "); + get_formatted_bytes_string(data_info_host.data_info.tot_bytes()) + .replace(" ", " "); bytes_string.push('B'); bytes_string })), @@ -490,7 +494,14 @@ fn col_host(width: f32, sniffer: &Sniffer) -> Column<'static, Message> { .align_items(Alignment::Center) .spacing(5) .push(star_button) - .push(get_flag_from_country_code(&host.country, FLAGS_WIDTH_BIG)) + .push(get_flag_tooltip( + &host.country, + FLAGS_WIDTH_BIG, + data_info_host.is_local, + data_info_host.traffic_type, + sniffer.language, + sniffer.style, + )) .push(host_bar); scroll_host = scroll_host.push( @@ -508,7 +519,7 @@ fn col_host(width: f32, sniffer: &Sniffer) -> Column<'static, Message> { if entries.len() == 30 { scroll_host = scroll_host.push(vertical_space(Length::Fixed(25.0))).push( - Text::new(only_top_30_hosts(sniffer.language)) + Text::new(only_top_30_hosts_translation(sniffer.language)) .horizontal_alignment(Horizontal::Center) .font(font), ); diff --git a/src/gui/types/sniffer.rs b/src/gui/types/sniffer.rs index 7a26e011..79a1a78e 100644 --- a/src/gui/types/sniffer.rs +++ b/src/gui/types/sniffer.rs @@ -379,7 +379,7 @@ impl Sniffer { info_traffic.favorite_hosts.remove(&host); } if let Some(host_info) = info_traffic.hosts.get_mut(&host) { - host_info.1 = add; + host_info.is_favorite = add; } drop(info_traffic); } diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 58cfacf1..a97262dc 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -1,19 +1,23 @@ +use std::net::{IpAddr, Ipv4Addr}; use std::sync::{Arc, Mutex}; use chrono::Local; use dns_lookup::lookup_addr; use etherparse::{Ethernet2Header, IpHeader, TransportHeader}; use maxminddb::Reader; -use pcap::{Active, Capture, Device}; +use pcap::{Active, Address, Capture, Device}; use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::app_protocol::from_port_to_application_protocol; use crate::networking::types::asn::Asn; use crate::networking::types::data_info::DataInfo; +use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; +use crate::networking::types::traffic_direction::TrafficDirection; use crate::networking::types::traffic_type::TrafficType; use crate::utils::asn::get_asn; use crate::utils::countries::get_country_code; +use crate::IpVersion::{IPv4, IPv6}; use crate::{AppProtocol, InfoTraffic, IpVersion, TransProtocol}; /// This function analyzes the data link layer header passed as parameter and updates variables @@ -111,26 +115,40 @@ pub fn analyze_transport_header( pub fn modify_or_insert_in_map( info_traffic_mutex: &Arc>, key: AddressPortPair, + my_interface_addresses: &Vec
, mac_address1: String, mac_address2: String, exchanged_bytes: u128, - traffic_type: TrafficType, application_protocol: AppProtocol, country_db_reader: &Reader<&[u8]>, asn_db_reader: &Reader<&[u8]>, ) -> InfoAddressPortPair { let now = Local::now(); - let very_long_address = key.address1.len() > 25 || key.address2.len() > 25; + let mut traffic_direction = TrafficDirection::default(); + let source_ip = &key.address1.clone(); + let destination_ip = &key.address2.clone(); + let very_long_address = source_ip.len() > 25 || destination_ip.len() > 25; + let mut is_local = false; + let mut traffic_type = TrafficType::default(); + let mut info_traffic = info_traffic_mutex .lock() .expect("Error acquiring mutex\n\r"); let len = info_traffic.map.len(); let index = info_traffic.map.get_index_of(&key).unwrap_or(len); let (country, asn) = if index == len { - // first occurrence of key => retrieve country code and asn + // first occurrence of key => retrieve traffic type, country code and asn + traffic_type = get_traffic_type(destination_ip, my_interface_addresses); + traffic_direction = get_traffic_direction(source_ip, my_interface_addresses); + is_local = is_local_connection( + source_ip, + destination_ip, + traffic_direction, + my_interface_addresses, + ); ( - get_country_code(traffic_type, &key, country_db_reader), - get_asn(traffic_type, &key, asn_db_reader), + get_country_code(traffic_direction, &key, country_db_reader), + get_asn(traffic_direction, &key, asn_db_reader), ) } else { // this key already occurred @@ -154,11 +172,13 @@ pub fn modify_or_insert_in_map( final_timestamp: now, app_protocol: application_protocol, very_long_address, + traffic_direction, traffic_type, country, asn, r_dns: None, index, + is_local, }) .clone(); @@ -175,10 +195,10 @@ pub fn modify_or_insert_in_map( pub fn reverse_dns_lookup( info_traffic: Arc>, key: AddressPortPair, - traffic_type: TrafficType, + traffic_direction: TrafficDirection, ) { - let address_to_lookup = match traffic_type { - TrafficType::Outgoing => key.address2.clone(), + let address_to_lookup = match traffic_direction { + TrafficDirection::Outgoing => key.address2.clone(), _ => key.address1.clone(), }; @@ -213,40 +233,74 @@ pub fn reverse_dns_lookup( info_traffic_lock .hosts .entry(new_info.get_host()) - .and_modify(|(data_info, _)| { - if new_info.traffic_type == TrafficType::Outgoing { - data_info.outgoing_packets += new_info.transmitted_packets; - data_info.outgoing_bytes += new_info.transmitted_bytes; + .and_modify(|data_info_host| { + if new_info.traffic_direction == TrafficDirection::Outgoing { + data_info_host.data_info.outgoing_packets += new_info.transmitted_packets; + data_info_host.data_info.outgoing_bytes += new_info.transmitted_bytes; } else { - data_info.incoming_packets += new_info.transmitted_packets; - data_info.incoming_bytes += new_info.transmitted_bytes; + data_info_host.data_info.incoming_packets += new_info.transmitted_packets; + data_info_host.data_info.incoming_bytes += new_info.transmitted_bytes; } }) - .or_insert(if new_info.traffic_type == TrafficType::Outgoing { - ( - DataInfo { - incoming_packets: 0, - outgoing_packets: new_info.transmitted_packets, - incoming_bytes: 0, - outgoing_bytes: new_info.transmitted_bytes, - }, - false, - ) - } else { - ( - DataInfo { - incoming_packets: new_info.transmitted_packets, - outgoing_packets: 0, - incoming_bytes: new_info.transmitted_bytes, - outgoing_bytes: 0, - }, - false, - ) - }); + .or_insert( + if new_info.traffic_direction == TrafficDirection::Outgoing { + DataInfoHost { + data_info: DataInfo { + incoming_packets: 0, + outgoing_packets: new_info.transmitted_packets, + incoming_bytes: 0, + outgoing_bytes: new_info.transmitted_bytes, + }, + is_favorite: false, + is_local: new_info.is_local, + traffic_type: new_info.traffic_type, + } + } else { + DataInfoHost { + data_info: DataInfo { + incoming_packets: new_info.transmitted_packets, + outgoing_packets: 0, + incoming_bytes: new_info.transmitted_bytes, + outgoing_bytes: 0, + }, + is_favorite: false, + is_local: new_info.is_local, + traffic_type: new_info.traffic_type, + } + }, + ); drop(info_traffic_lock); } +/// Returns the traffic direction observed (incoming or outgoing) +fn get_traffic_direction( + source_ip: &String, + my_interface_addresses: &Vec
, +) -> TrafficDirection { + let my_interface_addresses_string: Vec = my_interface_addresses + .iter() + .map(|address| address.addr.to_string()) + .collect(); + + if my_interface_addresses_string.contains(source_ip) { + TrafficDirection::Outgoing + } else { + TrafficDirection::Incoming + } +} + +/// Returns the traffic type observed (unicast, multicast or broadcast) +fn get_traffic_type(destination_ip: &String, my_interface_addresses: &Vec
) -> TrafficType { + if is_multicast_address(destination_ip) { + TrafficType::Multicast + } else if is_broadcast_address(destination_ip, my_interface_addresses) { + TrafficType::Broadcast + } else { + TrafficType::Unicast + } +} + /// Determines if the input address is a multicast address or not. /// /// # Arguments @@ -280,8 +334,23 @@ pub fn is_multicast_address(address: &str) -> bool { /// # Arguments /// /// * `address` - string representing an IPv4 or IPv6 network address. -pub fn is_broadcast_address(address: &str) -> bool { +pub fn is_broadcast_address(address: &str, my_interface_addresses: &Vec
) -> bool { let mut ret_val = false; + + // check if directed broadcast + let my_broadcast_addresses: Vec = my_interface_addresses + .iter() + .map(|address| { + address + .broadcast_addr + .unwrap_or("255.255.255.255".parse().unwrap()) + .to_string() + }) + .collect(); + if my_broadcast_addresses.contains(&address.to_string()) { + return true; + } + if !address.contains(':') { //IPv4 address let groups: Vec = address @@ -295,11 +364,82 @@ pub fn is_broadcast_address(address: &str) -> bool { { ret_val = true; } - // still missing a check for directed broadcast! } ret_val } +/// Determines if the connection is local +fn is_local_connection( + source_ip: &String, + destination_ip: &String, + traffic_direction: TrafficDirection, + my_interface_addresses: &Vec
, +) -> bool { + let mut ret_val = false; + + let address_to_lookup = match traffic_direction { + TrafficDirection::Outgoing => destination_ip, + _ => source_ip, + }; + let address_to_lookup_type = if address_to_lookup.contains(":") { + IPv6 + } else { + IPv4 + }; + + for address in my_interface_addresses { + match address.addr { + IpAddr::V4(local_addr) if address_to_lookup_type.eq(&IPv4) => { + // check if the two IPv4 addresses are in the same subnet + let address_to_lookup_parsed: Ipv4Addr = + address_to_lookup.parse().unwrap_or(Ipv4Addr::from(0)); + // remote is link local? + if address_to_lookup_parsed.is_link_local() { + ret_val = true; + } + // is the same subnet? + else if let Some(IpAddr::V4(netmask)) = address.netmask { + let mut local_subnet = Vec::new(); + let mut remote_subnet = Vec::new(); + let netmask_digits = netmask.octets(); + let local_addr_digits = local_addr.octets(); + let remote_addr_digits = address_to_lookup_parsed.octets(); + for (i, netmask_digit) in netmask_digits.iter().enumerate() { + local_subnet.push(netmask_digit & local_addr_digits[i]); + remote_subnet.push(netmask_digit & remote_addr_digits[i]); + } + if local_subnet == remote_subnet { + ret_val = true; + } + } + } + IpAddr::V6(local_addr) if address_to_lookup_type.eq(&IPv6) => { + // check if the two IPv6 addresses are in the same subnet + // remote is link local? + if address_to_lookup.starts_with("fe80") { + ret_val = true; + } + // is the same subnet? + else if let Some(IpAddr::V6(netmask)) = address.netmask { + let netmask_len = netmask.to_string().len(); + let local_address_string = local_addr.to_string(); + let local_subnet = local_address_string.get(0..netmask_len).unwrap_or(""); + let remote_subnet = address_to_lookup.get(0..netmask_len).unwrap_or(""); + if !local_subnet.is_empty() + && !remote_subnet.is_empty() + && local_subnet == remote_subnet + { + ret_val = true; + } + } + } + _ => {} + } + } + + ret_val +} + /// Determines if the capture opening resolves into an Error pub fn get_capture_result(device: &Device) -> (Option, Option>) { let cap_result = Capture::from_device(&*device.name) @@ -420,7 +560,12 @@ fn ipv6_from_long_dec_to_short_hex(ipv6_long: [u8; 16]) -> String { #[cfg(test)] mod tests { - use crate::networking::manage_packets::{ipv6_from_long_dec_to_short_hex, mac_from_dec_to_hex}; + use crate::networking::manage_packets::{ + ipv6_from_long_dec_to_short_hex, is_local_connection, mac_from_dec_to_hex, + }; + use crate::networking::types::traffic_direction::TrafficDirection; + use pcap::Address; + use std::net::{IpAddr, Ipv4Addr}; #[test] fn mac_simple_test() { @@ -533,4 +678,60 @@ mod tests { ipv6_from_long_dec_to_short_hex([0, 16, 16, 0, 0, 1, 7, 0, 0, 2, 216, 0, 1, 0, 0, 1]); assert_eq!(result, "10:1000:1:700:2:d800:100:1".to_string()); } + + #[test] + fn is_local_connection_ipv4_test() { + let mut address_vec: Vec
= Vec::new(); + let my_address = Address { + addr: IpAddr::V4("172.20.10.9".parse().unwrap()), + netmask: Some(IpAddr::V4("255.255.255.240".parse().unwrap())), + broadcast_addr: None, + dst_addr: None, + }; + address_vec.push(my_address); + + let result1 = is_local_connection( + &"172.20.10.9".to_string(), + &"104.18.43.158".to_string(), + TrafficDirection::Outgoing, + &address_vec, + ); + assert_eq!(result1, false); + + let result2 = is_local_connection( + &"172.20.10.9".to_string(), + &"172.20.10.15".to_string(), + TrafficDirection::Outgoing, + &address_vec, + ); + assert_eq!(result2, true); + + let result3 = is_local_connection( + &"172.20.10.9".to_string(), + &"172.20.10.16".to_string(), + TrafficDirection::Outgoing, + &address_vec, + ); + assert_eq!(result3, false); + } + + #[test] + fn is_local_connection_ipv4_multicast_test() { + let mut address_vec: Vec
= Vec::new(); + let my_address = Address { + addr: IpAddr::V4("172.20.10.9".parse().unwrap()), + netmask: Some(IpAddr::V4("255.255.255.240".parse().unwrap())), + broadcast_addr: None, + dst_addr: None, + }; + address_vec.push(my_address); + + let result1 = is_local_connection( + &"172.20.10.9".to_string(), + &"224.0.0.251".to_string(), + TrafficDirection::Outgoing, + &address_vec, + ); + assert_eq!(result1, false); + } } diff --git a/src/networking/types/data_info_host.rs b/src/networking/types/data_info_host.rs new file mode 100644 index 00000000..c31845b2 --- /dev/null +++ b/src/networking/types/data_info_host.rs @@ -0,0 +1,17 @@ +//! Module defining the `DataInfoHost` struct related to hosts. + +use crate::networking::types::data_info::DataInfo; +use crate::networking::types::traffic_type::TrafficType; + +/// Host-related information. +#[derive(Clone, Default)] +pub struct DataInfoHost { + /// Incoming and outgoing packets and bytes + pub data_info: DataInfo, + /// Determine if this host is one of the favorites + pub is_favorite: bool, + /// Determine if the connection with this host is local + pub is_local: bool, + /// Determine if the connection with this host is multicast or broadcast + pub traffic_type: TrafficType, +} diff --git a/src/networking/types/info_address_port_pair.rs b/src/networking/types/info_address_port_pair.rs index 8c827d2d..84da827d 100644 --- a/src/networking/types/info_address_port_pair.rs +++ b/src/networking/types/info_address_port_pair.rs @@ -3,12 +3,12 @@ use std::fmt; use std::net::IpAddr; -use std::ops::Add; use chrono::{DateTime, Local}; use crate::networking::types::asn::Asn; use crate::networking::types::host::Host; +use crate::networking::types::traffic_direction::TrafficDirection; use crate::networking::types::traffic_type::TrafficType; use crate::utils::formatted_strings::get_formatted_bytes_string; use crate::AppProtocol; @@ -34,8 +34,6 @@ pub struct InfoAddressPortPair { pub app_protocol: AppProtocol, /// Check if source or destination is an IPv6 address longer than 25 bytes (used for Display pub very_long_address: bool, - /// Flag to determine which of the address is that of the sniffed adapter or remote - pub traffic_type: TrafficType, /// Country of the remote IP address pub country: String, /// Autonomous System of the remote IP address @@ -44,6 +42,12 @@ pub struct InfoAddressPortPair { pub r_dns: Option, /// Integer corresponding to the index inside the connections map pub index: usize, + /// Determines if the connection is incoming or outgoing + pub traffic_direction: TrafficDirection, + /// Determines if the connection is unicast, multicast or broadcast + pub traffic_type: TrafficType, + /// Determines if the connection is local + pub is_local: bool, } impl Default for InfoAddressPortPair { @@ -57,11 +61,13 @@ impl Default for InfoAddressPortPair { final_timestamp: Default::default(), app_protocol: AppProtocol::Other, very_long_address: false, - traffic_type: TrafficType::Other, + traffic_direction: TrafficDirection::default(), + traffic_type: TrafficType::default(), country: String::new(), asn: Default::default(), r_dns: None, index: 0, + is_local: false, } } } @@ -73,7 +79,6 @@ impl InfoAddressPortPair { .unwrap() .to_string() .replace('|', "") - .add(&*format!(" {} ", &self.country)) } pub fn get_host(&self) -> Host { diff --git a/src/networking/types/info_traffic.rs b/src/networking/types/info_traffic.rs index 05a79113..ad4933ab 100644 --- a/src/networking/types/info_traffic.rs +++ b/src/networking/types/info_traffic.rs @@ -7,6 +7,7 @@ use indexmap::IndexMap; use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::data_info::DataInfo; +use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; use crate::AppProtocol; @@ -38,7 +39,7 @@ pub struct InfoTraffic { /// Map of the application layer protocols with their data info pub app_protocols: HashMap, /// Map of the hosts with their data info - pub hosts: HashMap, + pub hosts: HashMap, } impl InfoTraffic { diff --git a/src/networking/types/mod.rs b/src/networking/types/mod.rs index cbc4b183..6df89a79 100644 --- a/src/networking/types/mod.rs +++ b/src/networking/types/mod.rs @@ -3,11 +3,13 @@ pub mod app_protocol; pub mod asn; pub mod byte_multiple; pub mod data_info; +pub mod data_info_host; pub mod filters; pub mod host; pub mod info_address_port_pair; pub mod info_traffic; pub mod ip_version; pub mod search_parameters; +pub mod traffic_direction; pub mod traffic_type; pub mod trans_protocol; diff --git a/src/networking/types/traffic_direction.rs b/src/networking/types/traffic_direction.rs new file mode 100644 index 00000000..6cf2d8b8 --- /dev/null +++ b/src/networking/types/traffic_direction.rs @@ -0,0 +1,14 @@ +/// Enum representing the possible traffic direction (incoming or outgoing). +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum TrafficDirection { + /// Incoming traffic (from remote address to local interface) + Incoming, + /// Outgoing traffic (from local interface to remote address) + Outgoing, +} + +impl Default for TrafficDirection { + fn default() -> Self { + Self::Incoming + } +} diff --git a/src/networking/types/traffic_type.rs b/src/networking/types/traffic_type.rs index 3cb5fdb6..9d55c18a 100644 --- a/src/networking/types/traffic_type.rs +++ b/src/networking/types/traffic_type.rs @@ -1,14 +1,16 @@ -/// Enum representing the possible traffic type (incoming, outgoing or multicast). +/// Enum representing the possible traffic type (unicast, multicast or broadcast). #[derive(Clone, Copy, PartialEq, Eq)] pub enum TrafficType { - /// Incoming traffic (from remote address to local interface) - Incoming, - /// Outgoing traffic (from local interface to remote address) - Outgoing, - /// Multicast traffic (from remote address to multicast address) + /// Unicast traffic + Unicast, + /// Multicast traffic (destination is a multicast address) Multicast, - /// Multicast traffic (from remote address to broadcast address) + /// Broadcast traffic (destination is a broadcast address) Broadcast, - /// Not identified - Other, +} + +impl Default for TrafficType { + fn default() -> Self { + Self::Unicast + } } diff --git a/src/notifications/notify_and_log.rs b/src/notifications/notify_and_log.rs index d033a01f..5c341e8f 100644 --- a/src/notifications/notify_and_log.rs +++ b/src/notifications/notify_and_log.rs @@ -100,6 +100,7 @@ pub fn notify_and_log( .push_front(LoggedNotification::FavoriteTransmitted( FavoriteTransmitted { host: host.clone(), + data_info_host: info_traffic_lock.hosts.get(host).unwrap().clone(), timestamp: Local::now().to_string().get(11..19).unwrap().to_string(), }, )); diff --git a/src/notifications/types/logged_notification.rs b/src/notifications/types/logged_notification.rs index fb1f892c..d723a576 100644 --- a/src/notifications/types/logged_notification.rs +++ b/src/notifications/types/logged_notification.rs @@ -1,3 +1,4 @@ +use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; use crate::ByteMultiple; @@ -31,5 +32,6 @@ pub struct BytesThresholdExceeded { #[derive(Clone)] pub struct FavoriteTransmitted { pub(crate) host: Host, + pub(crate) data_info_host: DataInfoHost, pub(crate) timestamp: String, } diff --git a/src/report/get_report_entries.rs b/src/report/get_report_entries.rs index a1c2ff19..98789caf 100644 --- a/src/report/get_report_entries.rs +++ b/src/report/get_report_entries.rs @@ -1,8 +1,8 @@ use std::cmp::{min, Ordering}; use std::sync::{Arc, Mutex}; -use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::data_info::DataInfo; +use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; use crate::networking::types::search_parameters::SearchParameters; use crate::{AppProtocol, ChartType, InfoTraffic}; @@ -102,13 +102,13 @@ pub fn get_searched_entries( pub fn get_host_entries( info_traffic: &Arc>, chart_type: ChartType, -) -> Vec<(Host, (DataInfo, bool))> { +) -> Vec<(Host, DataInfoHost)> { let info_traffic_lock = info_traffic.lock().unwrap(); - let mut sorted_vec: Vec<(&Host, &(DataInfo, bool))> = info_traffic_lock.hosts.iter().collect(); + let mut sorted_vec: Vec<(&Host, &DataInfoHost)> = info_traffic_lock.hosts.iter().collect(); - sorted_vec.sort_by(|&(_, (a, _)), &(_, (b, _))| match chart_type { - ChartType::Packets => b.tot_packets().cmp(&a.tot_packets()), - ChartType::Bytes => b.tot_bytes().cmp(&a.tot_bytes()), + sorted_vec.sort_by(|&(_, a), &(_, b)| match chart_type { + ChartType::Packets => b.data_info.tot_packets().cmp(&a.data_info.tot_packets()), + ChartType::Bytes => b.data_info.tot_bytes().cmp(&a.data_info.tot_bytes()), }); let n_entry = min(sorted_vec.len(), 30); diff --git a/src/secondary_threads/parse_packets.rs b/src/secondary_threads/parse_packets.rs index e93c8942..6977e40c 100644 --- a/src/secondary_threads/parse_packets.rs +++ b/src/secondary_threads/parse_packets.rs @@ -8,14 +8,15 @@ use etherparse::PacketHeaders; use pcap::{Active, Capture, Device}; use crate::networking::manage_packets::{ - analyze_link_header, analyze_network_header, analyze_transport_header, is_broadcast_address, - is_multicast_address, modify_or_insert_in_map, reverse_dns_lookup, + analyze_link_header, analyze_network_header, analyze_transport_header, modify_or_insert_in_map, + reverse_dns_lookup, }; use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::data_info::DataInfo; +use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::filters::Filters; use crate::networking::types::info_address_port_pair::InfoAddressPortPair; -use crate::networking::types::traffic_type::TrafficType; +use crate::networking::types::traffic_direction::TrafficDirection; use crate::utils::countries::COUNTRY_MMDB; use crate::{AppProtocol, InfoTraffic, IpVersion, TransProtocol}; @@ -32,11 +33,6 @@ pub fn parse_packets( ) { let capture_id = *current_capture_id.lock().unwrap(); - let mut my_interface_addresses = Vec::new(); - for address in device.addresses { - my_interface_addresses.push(address.addr.to_string()); - } - let network_layer_filter = filters.ip; let transport_layer_filter = filters.transport; let app_layer_filter = filters.application; @@ -47,7 +43,6 @@ pub fn parse_packets( let mut network_protocol; let mut transport_protocol; let mut application_protocol; - let mut traffic_type; let mut skip_packet; let mut reported_packet; @@ -78,7 +73,6 @@ pub fn parse_packets( network_protocol = IpVersion::Other; transport_protocol = TransProtocol::Other; application_protocol = AppProtocol::Other; - traffic_type = TrafficType::Other; skip_packet = false; reported_packet = false; @@ -116,16 +110,6 @@ pub fn parse_packets( continue; } - if my_interface_addresses.contains(&address1) { - traffic_type = TrafficType::Outgoing; - } else if my_interface_addresses.contains(&address2) { - traffic_type = TrafficType::Incoming; - } else if is_multicast_address(&address2) { - traffic_type = TrafficType::Multicast; - } else if is_broadcast_address(&address2) { - traffic_type = TrafficType::Broadcast; - } - let key: AddressPortPair = AddressPortPair::new( address1.clone(), port1, @@ -145,10 +129,10 @@ pub fn parse_packets( new_info = modify_or_insert_in_map( info_traffic_mutex, key.clone(), + &device.addresses, mac_address1, mac_address2, exchanged_bytes, - traffic_type, application_protocol, &country_db_reader, &asn_db_reader, @@ -168,7 +152,7 @@ pub fn parse_packets( } if reported_packet { - if traffic_type == TrafficType::Outgoing { + if new_info.traffic_direction == TrafficDirection::Outgoing { //increment number of sent packets and bytes info_traffic.tot_sent_packets += 1; info_traffic.tot_sent_bytes += exchanged_bytes; @@ -201,7 +185,7 @@ pub fn parse_packets( reverse_dns_lookup( info_traffic2, key2, - new_info.traffic_type, + new_info.traffic_direction, ); }) .unwrap(); @@ -216,38 +200,48 @@ pub fn parse_packets( info_traffic .hosts .entry(new_info.get_host()) - .and_modify(|(data_info, _)| { - if traffic_type == TrafficType::Outgoing { - data_info.outgoing_packets += 1; - data_info.outgoing_bytes += exchanged_bytes; + .and_modify(|data_info_host| { + if new_info.traffic_direction + == TrafficDirection::Outgoing + { + data_info_host.data_info.outgoing_packets += 1; + data_info_host.data_info.outgoing_bytes += + exchanged_bytes; } else { - data_info.incoming_packets += 1; - data_info.incoming_bytes += exchanged_bytes; + data_info_host.data_info.incoming_packets += 1; + data_info_host.data_info.incoming_bytes += + exchanged_bytes; } }) .or_insert( - if new_info.traffic_type == TrafficType::Outgoing { - ( - DataInfo { + if new_info.traffic_direction + == TrafficDirection::Outgoing + { + DataInfoHost { + data_info: DataInfo { incoming_packets: 0, outgoing_packets: new_info .transmitted_packets, incoming_bytes: 0, outgoing_bytes: new_info.transmitted_bytes, }, - false, - ) + is_favorite: false, + is_local: new_info.is_local, + traffic_type: new_info.traffic_type, + } } else { - ( - DataInfo { + DataInfoHost { + data_info: DataInfo { incoming_packets: new_info .transmitted_packets, outgoing_packets: 0, incoming_bytes: new_info.transmitted_bytes, outgoing_bytes: 0, }, - false, - ) + is_favorite: false, + is_local: new_info.is_local, + traffic_type: new_info.traffic_type, + } }, ); } @@ -258,7 +252,7 @@ pub fn parse_packets( .app_protocols .entry(application_protocol) .and_modify(|data_info| { - if traffic_type == TrafficType::Outgoing { + if new_info.traffic_direction == TrafficDirection::Outgoing { data_info.outgoing_packets += 1; data_info.outgoing_bytes += exchanged_bytes; } else { @@ -266,21 +260,23 @@ pub fn parse_packets( data_info.incoming_bytes += exchanged_bytes; } }) - .or_insert(if traffic_type == TrafficType::Outgoing { - DataInfo { - incoming_packets: 0, - outgoing_packets: 1, - incoming_bytes: 0, - outgoing_bytes: exchanged_bytes, - } - } else { - DataInfo { - incoming_packets: 1, - outgoing_packets: 0, - incoming_bytes: exchanged_bytes, - outgoing_bytes: 0, - } - }); + .or_insert( + if new_info.traffic_direction == TrafficDirection::Outgoing { + DataInfo { + incoming_packets: 0, + outgoing_packets: 1, + incoming_bytes: 0, + outgoing_bytes: exchanged_bytes, + } + } else { + DataInfo { + incoming_packets: 1, + outgoing_packets: 0, + incoming_bytes: exchanged_bytes, + outgoing_bytes: 0, + } + }, + ); } } } diff --git a/src/translations/translations_2.rs b/src/translations/translations_2.rs index 9c1ba171..42311351 100644 --- a/src/translations/translations_2.rs +++ b/src/translations/translations_2.rs @@ -67,10 +67,28 @@ pub fn host_translation(language: Language) -> &'static str { } } -pub fn only_top_30_hosts(language: Language) -> &'static str { +pub fn only_top_30_hosts_translation(language: Language) -> &'static str { match language { Language::EN => "Only the top 30 hosts are displayed here", Language::IT => "Solo i maggiori 30 host sono mostrati qui", _ => "Only the top 30 hosts are displayed here", } } + +pub fn local_translation(language: Language) -> String { + match language { + Language::EN => "Local", + Language::IT => "Locale", + _ => "Local", + } + .to_string() +} + +pub fn unknown_translation(language: Language) -> String { + match language { + Language::EN => "Unknown", + Language::IT => "Sconosciuto", + _ => "Unknown", + } + .to_string() +} diff --git a/src/utils/asn.rs b/src/utils/asn.rs index 2b90da08..74068353 100644 --- a/src/utils/asn.rs +++ b/src/utils/asn.rs @@ -1,17 +1,17 @@ use crate::networking::types::address_port_pair::AddressPortPair; use crate::networking::types::asn::Asn; -use crate::networking::types::traffic_type::TrafficType; +use crate::networking::types::traffic_direction::TrafficDirection; use maxminddb::{geoip2, MaxMindDBError, Reader}; pub const ASN_MMDB: &[u8] = include_bytes!("../../resources/DB/GeoLite2-ASN.mmdb"); pub fn get_asn( - traffic_type: TrafficType, + traffic_direction: TrafficDirection, key: &AddressPortPair, asn_db_reader: &Reader<&[u8]>, ) -> Asn { - let address_to_lookup = match traffic_type { - TrafficType::Outgoing => &key.address2, + let address_to_lookup = match traffic_direction { + TrafficDirection::Outgoing => &key.address2, _ => &key.address1, }; diff --git a/src/utils/countries.rs b/src/utils/countries.rs index df329b8f..4b83ad1f 100644 --- a/src/utils/countries.rs +++ b/src/utils/countries.rs @@ -1,19 +1,26 @@ -use iced::widget::{svg::Handle, Svg}; +use crate::gui::styles::types::element_type::ElementType; +use crate::gui::styles::types::style_tuple::StyleTuple; +use crate::gui::types::message::Message; +use crate::{Language, StyleType}; +use iced::widget::{svg::Handle, Svg, Tooltip}; use iced::{Length, Renderer}; +use iced_native::widget::tooltip::Position; use maxminddb::{geoip2, MaxMindDBError, Reader}; use crate::networking::types::address_port_pair::AddressPortPair; +use crate::networking::types::traffic_direction::TrafficDirection; use crate::networking::types::traffic_type::TrafficType; +use crate::translations::translations_2::{local_translation, unknown_translation}; pub const COUNTRY_MMDB: &[u8] = include_bytes!("../../resources/DB/GeoLite2-Country.mmdb"); pub fn get_country_code( - traffic_type: TrafficType, + traffic_direction: TrafficDirection, key: &AddressPortPair, country_db_reader: &Reader<&[u8]>, ) -> String { - let address_to_lookup = match traffic_type { - TrafficType::Outgoing => &key.address2, + let address_to_lookup = match traffic_direction { + TrafficDirection::Outgoing => &key.address2, _ => &key.address1, }; @@ -29,7 +36,7 @@ pub fn get_country_code( String::new() } -pub const FLAGS_WIDTH_SMALL: f32 = 15.0; +pub const FLAGS_WIDTH_SMALL: f32 = 20.0; pub const FLAGS_WIDTH_BIG: f32 = 37.5; pub const AD: &[u8] = include_bytes!("../../resources/countries_flags/4x3/ad.svg"); @@ -281,8 +288,10 @@ pub const YT: &[u8] = include_bytes!("../../resources/countries_flags/4x3/yt.svg pub const ZA: &[u8] = include_bytes!("../../resources/countries_flags/4x3/za.svg"); pub const ZM: &[u8] = include_bytes!("../../resources/countries_flags/4x3/zm.svg"); pub const ZW: &[u8] = include_bytes!("../../resources/countries_flags/4x3/zw.svg"); -pub const UNKNOWN: &[u8] = - include_bytes!("../../resources/countries_flags/4x3/question-mark-svgrepo-com.svg"); +pub const HOME: &[u8] = include_bytes!("../../resources/countries_flags/4x3/zz-home.svg"); +pub const MULTICAST: &[u8] = include_bytes!("../../resources/countries_flags/4x3/zz-multicast.svg"); +pub const BROADCAST: &[u8] = include_bytes!("../../resources/countries_flags/4x3/zz-broadcast.svg"); +pub const UNKNOWN: &[u8] = include_bytes!("../../resources/countries_flags/4x3/zz-unknown.svg"); pub fn get_flag_from_language_code(language: &str) -> Svg { Svg::new(Handle::from_memory(Vec::from(match language { @@ -306,9 +315,16 @@ pub fn get_flag_from_language_code(language: &str) -> Svg { .width(Length::Fixed(FLAGS_WIDTH_SMALL)) } -pub fn get_flag_from_country_code(country: &str, width: f32) -> Svg { +fn get_flag_from_country_code( + country: &str, + width: f32, + is_local: bool, + traffic_type: TrafficType, + language: Language, +) -> (Svg, String) { #![allow(clippy::too_many_lines)] - Svg::new(Handle::from_memory(Vec::from(match country { + let mut tooltip = country.to_string(); + let svg = Svg::new(Handle::from_memory(Vec::from(match country { "AD" => AD, "AE" => AE, "AF" => AF, @@ -558,7 +574,49 @@ pub fn get_flag_from_country_code(country: &str, width: f32) -> Svg { "ZA" => ZA, "ZM" => ZM, "ZW" => ZW, - _ => UNKNOWN, + _ => { + if is_local { + tooltip = local_translation(language); + HOME + } else if traffic_type.eq(&TrafficType::Multicast) { + tooltip = "Multicast".to_string(); + MULTICAST + } else if traffic_type.eq(&TrafficType::Broadcast) { + tooltip = "Broadcast".to_string(); + BROADCAST + } else { + tooltip = unknown_translation(language); + UNKNOWN + } + } }))) .width(Length::Fixed(width)) + .height(Length::Fixed(width * 0.75)); + + (svg, tooltip) +} + +pub fn get_flag_tooltip( + country: &str, + width: f32, + is_local: bool, + traffic_type: TrafficType, + language: Language, + style: StyleType, +) -> Tooltip<'static, Message> { + let (content, tooltip) = + get_flag_from_country_code(country, width, is_local, traffic_type, language); + let mut position = Position::FollowCursor; + let mut snap = true; + + if width == FLAGS_WIDTH_SMALL { + position = Position::Right; + snap = false; + } + + Tooltip::new(content, tooltip, position) + .snap_within_viewport(snap) + .style(>::into( + StyleTuple(style, ElementType::Tooltip), + )) } diff --git a/src/utils/formatted_strings.rs b/src/utils/formatted_strings.rs index d3a3a157..b5bf9f54 100644 --- a/src/utils/formatted_strings.rs +++ b/src/utils/formatted_strings.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use iced::Color; use crate::networking::types::filters::Filters; -use crate::networking::types::traffic_type::TrafficType; +use crate::networking::types::traffic_direction::TrafficDirection; use crate::translations::translations::{ active_filters_translation, none_translation, open_report_translation, }; @@ -55,8 +55,8 @@ pub fn get_active_filters_string(filters: &Filters, language: Language) -> Strin } /// Returns the color to be used for a specific connection of the relevant connections table in gui run page -pub fn get_connection_color(traffic_type: TrafficType, style: StyleType) -> Color { - if traffic_type == TrafficType::Outgoing { +pub fn get_connection_color(traffic_direction: TrafficDirection, style: StyleType) -> Color { + if traffic_direction == TrafficDirection::Outgoing { get_colors(style).outgoing } else { get_colors(style).incoming