Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 12 additions & 3 deletions src/gui/pages/connection_details_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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(),
Expand All @@ -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(
Expand All @@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions src/gui/pages/initial_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions src/gui/sniffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down
82 changes: 70 additions & 12 deletions src/networking/manage_packets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +38,7 @@ pub fn analyze_headers(
mac_addresses: &mut (Option<String>, Option<String>),
exchanged_bytes: &mut u128,
icmp_type: &mut IcmpType,
arp_type: &mut ArpType,
packet_filters_fields: &mut PacketFiltersFields,
) -> Option<AddressPortPair> {
analyze_link_header(
Expand All @@ -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;
}

Expand Down Expand Up @@ -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, _)) => {
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -217,6 +263,7 @@ pub fn modify_or_insert_in_map(
my_device: &MyDevice,
mac_addresses: (Option<String>, Option<String>),
icmp_type: IcmpType,
arp_type: ArpType,
exchanged_bytes: u128,
) -> InfoAddressPortPair {
let now = Local::now();
Expand Down Expand Up @@ -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,
Expand All @@ -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();

Expand Down Expand Up @@ -1239,7 +1297,7 @@ mod tests {
Service::Name(match p {
Protocol::TCP => "mdns",
Protocol::UDP => "zeroconf",
Protocol::ICMP => panic!(),
_ => panic!(),
})
);

Expand All @@ -1249,7 +1307,7 @@ mod tests {
match p {
Protocol::TCP => Service::Name("netstat"),
Protocol::UDP => Service::Unknown,
Protocol::ICMP => panic!(),
_ => panic!(),
}
);

Expand All @@ -1260,7 +1318,7 @@ mod tests {
match p {
Protocol::TCP => Service::Unknown,
Protocol::UDP => Service::Name("murmur"),
Protocol::ICMP => panic!(),
_ => panic!(),
}
);

Expand Down
48 changes: 48 additions & 0 deletions src/networking/types/arp_type.rs
Original file line number Diff line number Diff line change
@@ -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<ArpType, usize>) -> 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 => "?",
}
)
}
}
3 changes: 3 additions & 0 deletions src/networking/types/info_address_port_pair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<IcmpType, usize>,
/// Types of the ARP operations, with the relative count (this is empty if not ARP)
pub arp_types: HashMap<ArpType, usize>,
}
1 change: 1 addition & 0 deletions src/networking/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod address_port_pair;
pub mod arp_type;
pub mod asn;
pub mod bogon;
pub mod byte_multiple;
Expand Down
2 changes: 1 addition & 1 deletion src/networking/types/packet_filters_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 6 additions & 2 deletions src/networking/types/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum Protocol {
UDP,
/// Internet Control Message Protocol
ICMP,
/// Address Resolution Protocol
ARP,
}

impl std::fmt::Display for Protocol {
Expand All @@ -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)]
Expand All @@ -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);
}
}
Loading