Merge pull request #945 from GyulyVGC/linux-sll

Support `Linux SLL` link type
This commit is contained in:
Giuliano Bellini
2025-09-01 23:12:04 +02:00
committed by GitHub
6 changed files with 87 additions and 47 deletions

View File

@@ -4,6 +4,7 @@ # Changelog
## [UNRELEASED]
- Enhanced traffic filtering capabilities: Berkeley Packet Filter ([#937](https://github.com/GyulyVGC/sniffnet/pull/937) — fixes [#810](https://github.com/GyulyVGC/sniffnet/issues/810))
- Added support for `Linux SLL` link type, enabling to monitor the `any` interface on Linux ([#945](https://github.com/GyulyVGC/sniffnet/pull/945))
- Added _bits_ data representation ([#936](https://github.com/GyulyVGC/sniffnet/pull/936) — fixes [#506](https://github.com/GyulyVGC/sniffnet/issues/506))
- An AppImage of Sniffnet is now available ([#859](https://github.com/GyulyVGC/sniffnet/pull/859) — fixes [#900](https://github.com/GyulyVGC/sniffnet/issues/900))
- Added Dutch translation 🇳🇱 ([#854](https://github.com/GyulyVGC/sniffnet/pull/854))

View File

@@ -731,6 +731,7 @@ fn start(&mut self) -> Task<Message> {
let mmdb_readers = self.mmdb_readers.clone();
self.capture_source
.set_link_type(capture_context.my_link_type());
self.capture_source.set_addresses();
let capture_source = self.capture_source.clone();
self.traffic_chart
.change_capture_source(matches!(capture_source, CaptureSource::Device(_)));
@@ -799,13 +800,8 @@ fn set_device(&mut self, name: &str) {
fn fetch_devices(&mut self) {
self.my_devices.clear();
self.capture_source.set_addresses();
for dev in Device::list().log_err(location!()).unwrap_or_default() {
if matches!(&self.capture_source, CaptureSource::Device(_))
&& dev.name.eq(&self.capture_source.get_name())
{
// refresh active addresses
self.capture_source.set_addresses(dev.addresses.clone());
}
let my_dev = MyDevice::from_pcap_device(dev);
self.my_devices.push(my_dev);
}

View File

@@ -1,7 +1,9 @@
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use etherparse::{EtherType, LaxPacketHeaders, LinkHeader, NetHeaders, TransportHeader};
use etherparse::{
ArpHardwareId, EtherType, LaxPacketHeaders, LinkHeader, NetHeaders, TransportHeader,
};
use pcap::Address;
use crate::networking::types::address_port_pair::AddressPortPair;
@@ -81,13 +83,28 @@ fn analyze_link_header(
mac_address2: &mut Option<String>,
exchanged_bytes: &mut u128,
) {
if let Some(LinkHeader::Ethernet2(header)) = link_header {
*exchanged_bytes += 14;
*mac_address1 = Some(mac_from_dec_to_hex(header.source));
*mac_address2 = Some(mac_from_dec_to_hex(header.destination));
} else {
*mac_address1 = None;
*mac_address2 = None;
match link_header {
Some(LinkHeader::Ethernet2(header)) => {
*exchanged_bytes += 14;
*mac_address1 = Some(mac_from_dec_to_hex(header.source));
*mac_address2 = Some(mac_from_dec_to_hex(header.destination));
}
Some(LinkHeader::LinuxSll(header)) => {
*exchanged_bytes += 16;
*mac_address1 = if header.sender_address_valid_length == 6
&& header.arp_hrd_type == ArpHardwareId::ETHERNET
&& let Ok(sender) = header.sender_address[0..6].try_into()
{
Some(mac_from_dec_to_hex(sender))
} else {
None
};
*mac_address2 = None;
}
None => {
*mac_address1 = None;
*mac_address2 = None;
}
}
}
@@ -151,7 +168,7 @@ fn analyze_network_header(
*arp_type = ArpType::from_etherparse(arp_packet.operation);
true
}
_ => false,
None => false,
}
}
@@ -192,7 +209,7 @@ fn analyze_transport_header(
*icmp_type = IcmpTypeV6::from_etherparse(&icmpv6_header.icmp_type);
true
}
_ => false,
None => false,
}
}

View File

@@ -25,10 +25,8 @@
use crate::utils::types::timestamp::Timestamp;
use async_channel::Sender;
use dns_lookup::lookup_addr;
use etherparse::err::ip::{HeaderError, LaxHeaderSliceError};
use etherparse::err::{Layer, LenError};
use etherparse::{LaxPacketHeaders, LenSource};
use pcap::{Address, Device, Packet};
use etherparse::{EtherType, LaxPacketHeaders};
use pcap::{Address, Packet};
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::{Arc, Mutex};
@@ -95,7 +93,7 @@ pub fn parse_packets(
}
}
Ok(packet) => {
if let Ok(headers) = get_sniffable_headers(&packet, my_link_type) {
if let Some(headers) = get_sniffable_headers(&packet, my_link_type) {
#[allow(clippy::useless_conversion)]
let secs = i64::from(packet.header.ts.tv_sec);
#[allow(clippy::useless_conversion)]
@@ -281,27 +279,23 @@ pub fn parse_packets(
fn get_sniffable_headers<'a>(
packet: &'a Packet,
my_link_type: MyLinkType,
) -> Result<LaxPacketHeaders<'a>, LaxHeaderSliceError> {
) -> Option<LaxPacketHeaders<'a>> {
match my_link_type {
MyLinkType::Ethernet(_) | MyLinkType::Unsupported(_) | MyLinkType::NotYetAssigned => {
LaxPacketHeaders::from_ethernet(packet).map_err(LaxHeaderSliceError::Len)
LaxPacketHeaders::from_ethernet(packet).ok()
}
MyLinkType::RawIp(_) | MyLinkType::IPv4(_) | MyLinkType::IPv6(_) => {
LaxPacketHeaders::from_ip(packet)
LaxPacketHeaders::from_ip(packet).ok()
}
MyLinkType::LinuxSll(_) => from_linux_sll(packet, true),
MyLinkType::LinuxSll2(_) => from_linux_sll(packet, false),
MyLinkType::Null(_) | MyLinkType::Loop(_) => from_null(packet),
}
}
fn from_null(packet: &[u8]) -> Result<LaxPacketHeaders<'_>, LaxHeaderSliceError> {
fn from_null(packet: &[u8]) -> Option<LaxPacketHeaders<'_>> {
if packet.len() <= 4 {
return Err(LaxHeaderSliceError::Len(LenError {
required_len: 4,
len: packet.len(),
len_source: LenSource::Slice,
layer: Layer::Ethernet2Header,
layer_start_offset: 0,
}));
return None;
}
let is_valid_af_inet = {
@@ -322,14 +316,31 @@ fn matches(value: u32) -> bool {
};
if is_valid_af_inet {
LaxPacketHeaders::from_ip(&packet[4..])
LaxPacketHeaders::from_ip(&packet[4..]).ok()
} else {
Err(LaxHeaderSliceError::Content(
HeaderError::UnsupportedIpVersion { version_number: 0 },
))
None
}
}
fn from_linux_sll(packet: &[u8], is_v1: bool) -> Option<LaxPacketHeaders<'_>> {
let header_len = if is_v1 { 16 } else { 20 };
if packet.len() <= header_len {
return None;
}
let protocol_type = u16::from_be_bytes(if is_v1 {
[packet[14], packet[15]]
} else {
[packet[0], packet[1]]
});
let payload = &packet[header_len..];
Some(LaxPacketHeaders::from_ether_type(
EtherType(protocol_type),
payload,
))
}
fn reverse_dns_lookup(
resolutions_state: &Arc<Mutex<AddressesResolutionState>>,
new_hosts_to_send: &Arc<Mutex<Vec<HostMessage>>>,
@@ -431,12 +442,7 @@ fn maybe_send_tick_run_live(
new_hosts_to_send.lock().unwrap().drain(..).collect(),
false,
));
for dev in Device::list().log_err(location!()).unwrap_or_default() {
if dev.name.eq(&cs.get_name()) {
cs.set_addresses(dev.addresses);
break;
}
}
cs.set_addresses();
}
}

View File

@@ -1,11 +1,13 @@
use crate::gui::types::conf::Conf;
use crate::gui::types::filters::Filters;
use crate::location;
use crate::networking::types::my_device::MyDevice;
use crate::networking::types::my_link_type::MyLinkType;
use crate::translations::translations::network_adapter_translation;
use crate::translations::translations_4::capture_file_translation;
use crate::translations::types::language::Language;
use pcap::{Active, Address, Capture, Error, Packet, Savefile, Stat};
use crate::utils::error_logger::{ErrorLogger, Location};
use pcap::{Active, Address, Capture, Device, Error, Packet, Savefile, Stat};
use serde::{Deserialize, Serialize};
pub enum CaptureContext {
@@ -133,7 +135,7 @@ fn from_source(source: &CaptureSource, pcap_out_path: Option<&String>) -> Result
} else {
200 // limit stored packets slice dimension (to keep more in the buffer)
})
.immediate_mode(true) // parse packets ASAP
.immediate_mode(false)
.timeout(150) // ensure UI is updated even if no packets are captured
.open()?;
Ok(Self::Live(cap))
@@ -184,9 +186,21 @@ pub fn get_addresses(&self) -> &Vec<Address> {
}
}
pub fn set_addresses(&mut self, addresses: Vec<Address>) {
if let Self::Device(device) = self {
device.set_addresses(addresses);
pub fn set_addresses(&mut self) {
if let Self::Device(my_device) = self {
let mut addresses = Vec::new();
for dev in Device::list().log_err(location!()).unwrap_or_default() {
if matches!(
my_device.get_link_type(),
MyLinkType::LinuxSll(_) | MyLinkType::LinuxSll2(_)
) {
addresses.extend(dev.addresses);
} else if dev.name.eq(my_device.get_name()) {
addresses.extend(dev.addresses);
break;
}
}
my_device.set_addresses(addresses);
}
}

View File

@@ -12,6 +12,8 @@ pub enum MyLinkType {
Loop(Linktype),
IPv4(Linktype),
IPv6(Linktype),
LinuxSll(Linktype),
LinuxSll2(Linktype),
Unsupported(Linktype),
#[default]
NotYetAssigned,
@@ -30,6 +32,8 @@ pub fn from_pcap_link_type(link_type: Linktype) -> Self {
Linktype::LOOP => Self::Loop(link_type),
Linktype::IPV4 => Self::IPv4(link_type),
Linktype::IPV6 => Self::IPv6(link_type),
Linktype::LINUX_SLL => Self::LinuxSll(link_type),
Linktype::LINUX_SLL2 => Self::LinuxSll2(link_type),
_ => Self::Unsupported(link_type),
}
}
@@ -42,6 +46,8 @@ pub fn full_print_on_one_line(self, language: Language) -> String {
| Self::Loop(l)
| Self::IPv4(l)
| Self::IPv6(l)
| Self::LinuxSll(l)
| Self::LinuxSll2(l)
| Self::Unsupported(l) => {
format!(
"{}: {} ({})",