diff --git a/CHANGELOG.md b/CHANGELOG.md index 1475a6302..679edcd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All Sniffnet releases with the relative changes are documented in this file. ## [UNRELEASED] +- Added support for ARP protocol ([#759](https://github.com/GyulyVGC/sniffnet/pull/759) — fixes [#680](https://github.com/GyulyVGC/sniffnet/issues/680)) - Identify and tag unassigned/reserved "bogon" IP addresses ([#678](https://github.com/GyulyVGC/sniffnet/pull/678) — fixes [#209](https://github.com/GyulyVGC/sniffnet/issues/209)) - Show data agglomerates in _Inspect_ page table ([#684](https://github.com/GyulyVGC/sniffnet/pull/684) — fixes [#601](https://github.com/GyulyVGC/sniffnet/issues/601)) - Updated some of the existing translations to v1.3: diff --git a/src/gui/pages/connection_details_page.rs b/src/gui/pages/connection_details_page.rs index 840011dda..834e7ace8 100644 --- a/src/gui/pages/connection_details_page.rs +++ b/src/gui/pages/connection_details_page.rs @@ -19,6 +19,7 @@ use crate::networking::manage_packets::{ get_address_to_lookup, get_traffic_type, is_local_connection, is_my_address, }; use crate::networking::types::address_port_pair::AddressPortPair; +use crate::networking::types::arp_type::ArpType; use crate::networking::types::bogon::is_bogon; use crate::networking::types::host::Host; use crate::networking::types::icmp_type::IcmpType; @@ -185,6 +186,7 @@ fn col_info<'a>( language: Language, ) -> Column<'a, Message, StyleType> { let is_icmp = key.protocol.eq(&Protocol::ICMP); + let is_arp = key.protocol.eq(&Protocol::ARP); let mut ret_val = Column::new() .spacing(10) @@ -207,7 +209,7 @@ fn col_info<'a>( font, )); - if !is_icmp { + if !is_icmp && !is_arp { ret_val = ret_val.push(TextType::highlighted_subtitle_with_desc( service_translation(language), &val.service.to_string(), @@ -234,7 +236,7 @@ fn col_info<'a>( font, )); - if is_icmp { + if is_icmp || is_arp { ret_val = ret_val.push( Column::new() .push( @@ -245,7 +247,14 @@ fn col_info<'a>( .push(Scrollable::with_direction( Column::new() .padding(Padding::ZERO.right(10).bottom(10)) - .push(Text::new(IcmpType::pretty_print_types(&val.icmp_types)).font(font)), + .push( + Text::new(if is_icmp { + IcmpType::pretty_print_types(&val.icmp_types) + } else { + ArpType::pretty_print_types(&val.arp_types) + }) + .font(font), + ), Direction::Both { vertical: ScrollbarType::properties(), horizontal: ScrollbarType::properties(), diff --git a/src/gui/pages/initial_page.rs b/src/gui/pages/initial_page.rs index 205156978..6ab574985 100644 --- a/src/gui/pages/initial_page.rs +++ b/src/gui/pages/initial_page.rs @@ -131,7 +131,7 @@ fn col_ip_buttons( .align_y(Alignment::Center) .font(font), ) - .width(90) + .width(80) .height(35) .class(if is_active { ButtonType::BorderedRoundSelected @@ -170,7 +170,7 @@ fn col_protocol_buttons( .align_y(Alignment::Center) .font(font), ) - .width(90) + .width(80) .height(35) .class(if is_active { ButtonType::BorderedRoundSelected diff --git a/src/gui/sniffer.rs b/src/gui/sniffer.rs index 62d41b273..3b0a9dafa 100644 --- a/src/gui/sniffer.rs +++ b/src/gui/sniffer.rs @@ -1056,11 +1056,16 @@ mod tests { sniffer.update(Message::ProtocolSelection(Protocol::UDP, false)); assert_eq!( sniffer.filters.protocols, - HashSet::from([Protocol::TCP, Protocol::ICMP]) + HashSet::from([Protocol::TCP, Protocol::ICMP, Protocol::ARP]) ); sniffer.update(Message::ProtocolSelection(Protocol::TCP, false)); - assert_eq!(sniffer.filters.protocols, HashSet::from([Protocol::ICMP])); + assert_eq!( + sniffer.filters.protocols, + HashSet::from([Protocol::ICMP, Protocol::ARP]) + ); sniffer.update(Message::ProtocolSelection(Protocol::ICMP, false)); + assert_eq!(sniffer.filters.protocols, HashSet::from([Protocol::ARP])); + sniffer.update(Message::ProtocolSelection(Protocol::ARP, false)); assert_eq!(sniffer.filters.protocols, HashSet::new()); sniffer.update(Message::ProtocolSelection(Protocol::UDP, true)); assert_eq!(sniffer.filters.protocols, HashSet::from([Protocol::UDP])); diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 7f2a35f45..ab3399809 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -5,13 +5,14 @@ use std::sync::Mutex; use chrono::Local; use dns_lookup::lookup_addr; -use etherparse::{LaxPacketHeaders, LinkHeader, NetHeaders, TransportHeader}; +use etherparse::{EtherType, LaxPacketHeaders, LinkHeader, NetHeaders, TransportHeader}; use pcap::{Address, Device}; use crate::mmdb::asn::get_asn; use crate::mmdb::country::get_country; use crate::mmdb::types::mmdb_reader::MmdbReaders; use crate::networking::types::address_port_pair::AddressPortPair; +use crate::networking::types::arp_type::ArpType; use crate::networking::types::bogon::is_bogon; use crate::networking::types::data_info_host::DataInfoHost; use crate::networking::types::host::Host; @@ -37,6 +38,7 @@ pub fn analyze_headers( mac_addresses: &mut (Option, Option), exchanged_bytes: &mut u128, icmp_type: &mut IcmpType, + arp_type: &mut ArpType, packet_filters_fields: &mut PacketFiltersFields, ) -> Option { analyze_link_header( @@ -46,23 +48,28 @@ pub fn analyze_headers( exchanged_bytes, ); + let is_arp = matches!(&headers.net, Some(NetHeaders::Arp(_))); + if !analyze_network_header( headers.net, exchanged_bytes, &mut packet_filters_fields.ip_version, &mut packet_filters_fields.source, &mut packet_filters_fields.dest, + arp_type, ) { return None; } - if !analyze_transport_header( - headers.transport, - &mut packet_filters_fields.sport, - &mut packet_filters_fields.dport, - &mut packet_filters_fields.protocol, - icmp_type, - ) { + if !is_arp + && !analyze_transport_header( + headers.transport, + &mut packet_filters_fields.sport, + &mut packet_filters_fields.dport, + &mut packet_filters_fields.protocol, + icmp_type, + ) + { return None; } @@ -103,6 +110,7 @@ fn analyze_network_header( network_protocol: &mut IpVersion, address1: &mut IpAddr, address2: &mut IpAddr, + arp_type: &mut ArpType, ) -> bool { match network_header { Some(NetHeaders::Ipv4(ipv4header, _)) => { @@ -119,6 +127,40 @@ fn analyze_network_header( *exchanged_bytes += u128::from(40 + ipv6header.payload_length); true } + Some(NetHeaders::Arp(arp_packet)) => { + match arp_packet.proto_addr_type { + EtherType::IPV4 => { + *network_protocol = IpVersion::IPv4; + *address1 = + match TryInto::<[u8; 4]>::try_into(arp_packet.sender_protocol_addr()) { + Ok(source) => IpAddr::from(source), + Err(_) => return false, + }; + *address2 = + match TryInto::<[u8; 4]>::try_into(arp_packet.target_protocol_addr()) { + Ok(destination) => IpAddr::from(destination), + Err(_) => return false, + }; + } + EtherType::IPV6 => { + *network_protocol = IpVersion::IPv6; + *address1 = + match TryInto::<[u8; 16]>::try_into(arp_packet.sender_protocol_addr()) { + Ok(source) => IpAddr::from(source), + Err(_) => return false, + }; + *address2 = + match TryInto::<[u8; 16]>::try_into(arp_packet.target_protocol_addr()) { + Ok(destination) => IpAddr::from(destination), + Err(_) => return false, + }; + } + _ => return false, + } + *exchanged_bytes += arp_packet.packet_len() as u128; + *arp_type = ArpType::from_etherparse(arp_packet.operation); + true + } _ => false, } } @@ -165,7 +207,11 @@ fn analyze_transport_header( } pub fn get_service(key: &AddressPortPair, traffic_direction: TrafficDirection) -> Service { - if key.port1.is_none() || key.port2.is_none() || key.protocol == Protocol::ICMP { + if key.port1.is_none() + || key.port2.is_none() + || key.protocol == Protocol::ICMP + || key.protocol == Protocol::ARP + { return Service::NotApplicable; } @@ -217,6 +263,7 @@ pub fn modify_or_insert_in_map( my_device: &MyDevice, mac_addresses: (Option, Option), icmp_type: IcmpType, + arp_type: ArpType, exchanged_bytes: u128, ) -> InfoAddressPortPair { let now = Local::now(); @@ -268,6 +315,12 @@ pub fn modify_or_insert_in_map( .and_modify(|n| *n += 1) .or_insert(1); } + if key.protocol.eq(&Protocol::ARP) { + info.arp_types + .entry(arp_type) + .and_modify(|n| *n += 1) + .or_insert(1); + } }) .or_insert_with(|| InfoAddressPortPair { mac_address1: mac_addresses.0, @@ -283,6 +336,11 @@ pub fn modify_or_insert_in_map( } else { HashMap::new() }, + arp_types: if key.protocol.eq(&Protocol::ARP) { + HashMap::from([(arp_type, 1)]) + } else { + HashMap::new() + }, }) .clone(); @@ -1239,7 +1297,7 @@ mod tests { Service::Name(match p { Protocol::TCP => "mdns", Protocol::UDP => "zeroconf", - Protocol::ICMP => panic!(), + _ => panic!(), }) ); @@ -1249,7 +1307,7 @@ mod tests { match p { Protocol::TCP => Service::Name("netstat"), Protocol::UDP => Service::Unknown, - Protocol::ICMP => panic!(), + _ => panic!(), } ); @@ -1260,7 +1318,7 @@ mod tests { match p { Protocol::TCP => Service::Unknown, Protocol::UDP => Service::Name("murmur"), - Protocol::ICMP => panic!(), + _ => panic!(), } ); diff --git a/src/networking/types/arp_type.rs b/src/networking/types/arp_type.rs new file mode 100644 index 000000000..cb8a158c1 --- /dev/null +++ b/src/networking/types/arp_type.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; +use std::fmt::{Display, Formatter}; + +use etherparse::ArpOperation; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)] +pub enum ArpType { + Request, + Reply, + #[default] + Unknown, +} + +impl ArpType { + pub fn from_etherparse(arp_operation: ArpOperation) -> ArpType { + match arp_operation { + ArpOperation(1) => Self::Request, + ArpOperation(2) => Self::Reply, + ArpOperation(_) => Self::Unknown, + } + } + + pub fn pretty_print_types(map: &HashMap) -> String { + let mut ret_val = String::new(); + + let mut vec: Vec<(&ArpType, &usize)> = map.iter().collect(); + vec.sort_by(|(_, a), (_, b)| b.cmp(a)); + + for (arp_type, n) in vec { + ret_val.push_str(&format!(" {arp_type} ({n})\n")); + } + ret_val + } +} + +impl Display for ArpType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + ArpType::Request => "Request", + ArpType::Reply => "Reply", + ArpType::Unknown => "?", + } + ) + } +} diff --git a/src/networking/types/info_address_port_pair.rs b/src/networking/types/info_address_port_pair.rs index 44e8febfd..52b39ce80 100644 --- a/src/networking/types/info_address_port_pair.rs +++ b/src/networking/types/info_address_port_pair.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use chrono::{DateTime, Local}; +use crate::networking::types::arp_type::ArpType; use crate::networking::types::icmp_type::IcmpType; use crate::networking::types::traffic_direction::TrafficDirection; use crate::Service; @@ -32,4 +33,6 @@ pub struct InfoAddressPortPair { pub traffic_direction: TrafficDirection, /// Types of the ICMP messages exchanged, with the relative count (this is empty if not ICMP) pub icmp_types: HashMap, + /// Types of the ARP operations, with the relative count (this is empty if not ARP) + pub arp_types: HashMap, } diff --git a/src/networking/types/mod.rs b/src/networking/types/mod.rs index 93c87318a..8553faafb 100644 --- a/src/networking/types/mod.rs +++ b/src/networking/types/mod.rs @@ -1,4 +1,5 @@ pub mod address_port_pair; +pub mod arp_type; pub mod asn; pub mod bogon; pub mod byte_multiple; diff --git a/src/networking/types/packet_filters_fields.rs b/src/networking/types/packet_filters_fields.rs index 268989f9e..2767160c8 100644 --- a/src/networking/types/packet_filters_fields.rs +++ b/src/networking/types/packet_filters_fields.rs @@ -24,7 +24,7 @@ impl Default for PacketFiltersFields { fn default() -> Self { Self { ip_version: IpVersion::IPv4, - protocol: Protocol::TCP, + protocol: Protocol::ARP, source: IpAddr::from_str("::").unwrap(), dest: IpAddr::from_str("::").unwrap(), sport: None, diff --git a/src/networking/types/protocol.rs b/src/networking/types/protocol.rs index 40ac658e8..31f20e625 100644 --- a/src/networking/types/protocol.rs +++ b/src/networking/types/protocol.rs @@ -10,6 +10,8 @@ pub enum Protocol { UDP, /// Internet Control Message Protocol ICMP, + /// Address Resolution Protocol + ARP, } impl std::fmt::Display for Protocol { @@ -19,7 +21,7 @@ impl std::fmt::Display for Protocol { } impl Protocol { - pub const ALL: [Protocol; 3] = [Protocol::TCP, Protocol::UDP, Protocol::ICMP]; + pub const ALL: [Protocol; 4] = [Protocol::TCP, Protocol::UDP, Protocol::ICMP, Protocol::ARP]; } #[cfg(test)] @@ -33,15 +35,17 @@ mod tests { Protocol::TCP => assert_eq!(protocol.to_string(), "TCP"), Protocol::UDP => assert_eq!(protocol.to_string(), "UDP"), Protocol::ICMP => assert_eq!(protocol.to_string(), "ICMP"), + Protocol::ARP => assert_eq!(protocol.to_string(), "ARP"), } } } #[test] fn test_all_protocols_collection() { - assert_eq!(Protocol::ALL.len(), 3); + assert_eq!(Protocol::ALL.len(), 4); assert_eq!(Protocol::ALL.get(0).unwrap(), &Protocol::TCP); assert_eq!(Protocol::ALL.get(1).unwrap(), &Protocol::UDP); assert_eq!(Protocol::ALL.get(2).unwrap(), &Protocol::ICMP); + assert_eq!(Protocol::ALL.get(3).unwrap(), &Protocol::ARP); } } diff --git a/src/secondary_threads/parse_packets.rs b/src/secondary_threads/parse_packets.rs index 41a215ca7..e695e39df 100644 --- a/src/secondary_threads/parse_packets.rs +++ b/src/secondary_threads/parse_packets.rs @@ -13,6 +13,7 @@ use crate::mmdb::types::mmdb_reader::MmdbReaders; use crate::networking::manage_packets::{ analyze_headers, get_address_to_lookup, modify_or_insert_in_map, reverse_dns_lookup, }; +use crate::networking::types::arp_type::ArpType; use crate::networking::types::capture_context::CaptureContext; use crate::networking::types::data_info::DataInfo; use crate::networking::types::filters::Filters; @@ -56,6 +57,7 @@ pub fn parse_packets( let mut exchanged_bytes = 0; let mut mac_addresses = (None, None); let mut icmp_type = IcmpType::default(); + let mut arp_type = ArpType::default(); let mut packet_filters_fields = PacketFiltersFields::default(); let key_option = analyze_headers( @@ -63,8 +65,10 @@ pub fn parse_packets( &mut mac_addresses, &mut exchanged_bytes, &mut icmp_type, + &mut arp_type, &mut packet_filters_fields, ); + if key_option.is_none() { continue; } @@ -85,6 +89,7 @@ pub fn parse_packets( device, mac_addresses, icmp_type, + arp_type, exchanged_bytes, ); }