use std::time::{Duration, SystemTime};
use crate::interface::MacAddr;
#[cfg(target_os = "linux")]
use crate::sys::linux::ring::{RxBlock, RxFrames};
#[cfg(all(target_os = "linux", feature = "xdp"))]
use crate::sys::linux::xdp::{XdpBlock, XdpFrames};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PacketType {
Host,
Broadcast,
Multicast,
OtherHost,
Outgoing,
Other(u8),
}
impl PacketType {
#[must_use]
pub fn from_raw(raw: u8) -> Self {
match raw {
libc::PACKET_HOST => PacketType::Host,
libc::PACKET_BROADCAST => PacketType::Broadcast,
libc::PACKET_MULTICAST => PacketType::Multicast,
libc::PACKET_OTHERHOST => PacketType::OtherHost,
libc::PACKET_OUTGOING => PacketType::Outgoing,
other => PacketType::Other(other),
}
}
#[must_use]
pub fn from_dest_mac(dest: MacAddr) -> Self {
if dest.is_broadcast() {
PacketType::Broadcast
} else if dest.is_multicast() {
PacketType::Multicast
} else {
PacketType::Host
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VlanTag {
pub tci: u16,
pub tpid: u16,
}
impl VlanTag {
#[must_use]
pub const fn vid(&self) -> u16 {
self.tci & 0x0fff
}
#[must_use]
pub const fn priority(&self) -> u8 {
(self.tci >> 13) as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct FrameMeta {
pub wire_len: usize,
pub timestamp: Option<SystemTime>,
pub vlan: Option<VlanTag>,
pub packet_type: PacketType,
}
impl FrameMeta {
pub(crate) fn timestamp_from_parts(sec: u32, nsec: u32) -> Option<SystemTime> {
if sec == 0 && nsec == 0 {
None
} else {
Some(SystemTime::UNIX_EPOCH + Duration::new(u64::from(sec), nsec))
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct RawRingMeta {
pub wire_len: u32,
pub ts_sec: u32,
pub ts_nsec: u32,
pub status: u32,
pub vlan_tci: u16,
pub vlan_tpid: u16,
}
#[derive(Debug, Clone, Copy)]
enum MetaSource {
Eager(FrameMeta),
Ring(RawRingMeta),
}
#[derive(Debug, Clone, Copy)]
pub struct Frame<'a> {
data: &'a [u8],
meta: MetaSource,
}
impl<'a> Frame<'a> {
pub(crate) fn new(data: &'a [u8], meta: FrameMeta) -> Self {
Frame { data, meta: MetaSource::Eager(meta) }
}
pub(crate) fn ring(data: &'a [u8], raw: RawRingMeta) -> Self {
Frame { data, meta: MetaSource::Ring(raw) }
}
#[must_use]
pub fn data(&self) -> &'a [u8] {
self.data
}
#[must_use]
pub fn wire_len(&self) -> usize {
match self.meta {
MetaSource::Eager(m) => m.wire_len,
MetaSource::Ring(r) => r.wire_len as usize,
}
}
#[must_use]
pub fn meta(&self) -> FrameMeta {
match self.meta {
MetaSource::Eager(m) => m,
MetaSource::Ring(_) => FrameMeta {
wire_len: self.wire_len(),
timestamp: self.timestamp(),
vlan: self.vlan(),
packet_type: self.packet_type(),
},
}
}
#[must_use]
pub fn packet_type(&self) -> PacketType {
match self.meta {
MetaSource::Eager(m) => m.packet_type,
MetaSource::Ring(_) => self
.data
.get(0..6)
.map(|b| PacketType::from_dest_mac(MacAddr([b[0], b[1], b[2], b[3], b[4], b[5]])))
.unwrap_or(PacketType::Other(0)),
}
}
#[must_use]
pub fn timestamp(&self) -> Option<SystemTime> {
match self.meta {
MetaSource::Eager(m) => m.timestamp,
MetaSource::Ring(r) => FrameMeta::timestamp_from_parts(r.ts_sec, r.ts_nsec),
}
}
#[must_use]
pub fn vlan(&self) -> Option<VlanTag> {
match self.meta {
MetaSource::Eager(m) => m.vlan,
MetaSource::Ring(r) => {
if r.status & libc::TP_STATUS_VLAN_VALID != 0 {
let tpid = if r.status & libc::TP_STATUS_VLAN_TPID_VALID != 0 {
r.vlan_tpid
} else {
0x8100
};
Some(VlanTag { tci: r.vlan_tci, tpid })
} else {
None
}
}
}
}
}
#[derive(Debug)]
pub struct Block<'a> {
inner: BlockInner<'a>,
}
#[derive(Debug)]
enum BlockInner<'a> {
#[cfg(target_os = "linux")]
Ring(RxBlock<'a>),
#[cfg(all(target_os = "linux", feature = "xdp"))]
Xdp(XdpBlock<'a>),
Single(Option<Frame<'a>>),
}
impl<'a> Block<'a> {
#[cfg(target_os = "linux")]
pub(crate) fn from_ring(block: RxBlock<'a>) -> Self {
Block { inner: BlockInner::Ring(block) }
}
#[cfg(all(target_os = "linux", feature = "xdp"))]
pub(crate) fn from_xdp(block: XdpBlock<'a>) -> Self {
Block { inner: BlockInner::Xdp(block) }
}
pub(crate) fn single(frame: Option<Frame<'a>>) -> Self {
Block { inner: BlockInner::Single(frame) }
}
#[must_use]
pub fn len(&self) -> usize {
match &self.inner {
#[cfg(target_os = "linux")]
BlockInner::Ring(b) => b.frame_count(),
#[cfg(all(target_os = "linux", feature = "xdp"))]
BlockInner::Xdp(b) => b.frame_count(),
BlockInner::Single(f) => usize::from(f.is_some()),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn is_losing(&self) -> bool {
match &self.inner {
#[cfg(target_os = "linux")]
BlockInner::Ring(b) => b.is_losing(),
#[cfg(all(target_os = "linux", feature = "xdp"))]
BlockInner::Xdp(_) => false,
BlockInner::Single(_) => false,
}
}
pub fn frames(&self) -> Frames<'_> {
match &self.inner {
#[cfg(target_os = "linux")]
BlockInner::Ring(b) => Frames { inner: FramesInner::Ring(b.frames()) },
#[cfg(all(target_os = "linux", feature = "xdp"))]
BlockInner::Xdp(b) => Frames { inner: FramesInner::Xdp(b.frames()) },
BlockInner::Single(f) => Frames { inner: FramesInner::Single(f.iter().copied()) },
}
}
}
#[derive(Debug)]
pub struct Frames<'a> {
inner: FramesInner<'a>,
}
#[derive(Debug)]
enum FramesInner<'a> {
#[cfg(target_os = "linux")]
Ring(RxFrames<'a>),
#[cfg(all(target_os = "linux", feature = "xdp"))]
Xdp(XdpFrames<'a>),
Single(std::iter::Copied<std::option::Iter<'a, Frame<'a>>>),
}
impl<'a> Iterator for Frames<'a> {
type Item = Frame<'a>;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.inner {
#[cfg(target_os = "linux")]
FramesInner::Ring(it) => it.next(),
#[cfg(all(target_os = "linux", feature = "xdp"))]
FramesInner::Xdp(it) => it.next(),
FramesInner::Single(it) => it.next(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packet_type_mapping() {
assert_eq!(PacketType::from_raw(libc::PACKET_HOST), PacketType::Host);
assert_eq!(PacketType::from_raw(libc::PACKET_BROADCAST), PacketType::Broadcast);
assert_eq!(PacketType::from_raw(99), PacketType::Other(99));
}
#[test]
fn packet_type_from_mac() {
assert_eq!(PacketType::from_dest_mac(MacAddr::BROADCAST), PacketType::Broadcast);
assert_eq!(
PacketType::from_dest_mac(MacAddr([0x01, 0, 0, 0, 0, 0])),
PacketType::Multicast
);
assert_eq!(
PacketType::from_dest_mac(MacAddr([0x02, 0, 0, 0, 0, 1])),
PacketType::Host
);
}
#[test]
fn vlan_decoding() {
let tag = VlanTag { tci: (5 << 13) | 100, tpid: 0x8100 };
assert_eq!(tag.vid(), 100);
assert_eq!(tag.priority(), 5);
}
proptest::proptest! {
#[test]
#[cfg_attr(miri, ignore = "proptest is slow under Miri and covers safe arithmetic")]
fn vlan_tci_decomposition(tci in proptest::num::u16::ANY) {
let tag = VlanTag { tci, tpid: 0x8100 };
proptest::prop_assert!(tag.vid() <= 0x0fff);
proptest::prop_assert!(tag.priority() <= 7);
proptest::prop_assert_eq!(tag.vid(), tci & 0x0fff);
proptest::prop_assert_eq!(tag.priority(), (tci >> 13) as u8);
}
}
#[test]
fn timestamp_zero_is_none() {
assert!(FrameMeta::timestamp_from_parts(0, 0).is_none());
assert!(FrameMeta::timestamp_from_parts(1, 0).is_some());
}
#[test]
fn ring_frame_decodes_metadata_lazily() {
let bytes = [0xff_u8; 14]; let raw = RawRingMeta {
wire_len: 1500,
ts_sec: 5,
ts_nsec: 6,
status: libc::TP_STATUS_VLAN_VALID | libc::TP_STATUS_VLAN_TPID_VALID,
vlan_tci: 100,
vlan_tpid: 0x8100,
};
let f = Frame::ring(&bytes, raw);
assert_eq!(f.wire_len(), 1500);
assert_eq!(f.packet_type(), PacketType::Broadcast);
assert!(f.timestamp().is_some());
assert_eq!(f.vlan().expect("vlan present").vid(), 100);
let m = f.meta();
assert_eq!(m.wire_len, 1500);
assert_eq!(m.packet_type, PacketType::Broadcast);
}
#[test]
fn ring_frame_without_vlan_or_timestamp() {
let bytes = [0x02, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]; let raw = RawRingMeta {
wire_len: 64,
ts_sec: 0,
ts_nsec: 0,
status: 0,
vlan_tci: 0,
vlan_tpid: 0,
};
let f = Frame::ring(&bytes, raw);
assert_eq!(f.wire_len(), 64);
assert!(f.timestamp().is_none());
assert!(f.vlan().is_none());
assert_eq!(f.packet_type(), PacketType::Host);
}
#[test]
fn single_block_iterates_once() {
let bytes = [0xaa_u8; 4];
let meta = FrameMeta {
wire_len: 4,
timestamp: None,
vlan: None,
packet_type: PacketType::Host,
};
let block = Block::single(Some(Frame::new(&bytes, meta)));
assert_eq!(block.len(), 1);
assert!(!block.is_empty());
let collected: Vec<_> = block.frames().map(|f| f.data().len()).collect();
assert_eq!(collected, vec![4]);
let empty = Block::single(None);
assert!(empty.is_empty());
assert_eq!(empty.frames().count(), 0);
}
}