use bitflags::bitflags;
use crate::view::PacketView;
pub trait FlowExtractor: Send + Sync + 'static {
type Key: Eq + std::hash::Hash + Clone + Send + Sync + 'static;
fn extract(&self, view: PacketView<'_>) -> Option<Extracted<Self::Key>>;
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Extracted<K> {
pub key: K,
pub orientation: Orientation,
pub l4: Option<L4Proto>,
pub tcp: Option<TcpInfo>,
pub l4_meta: Option<L4Meta>,
}
impl<K> Extracted<K> {
pub fn new(
key: K,
orientation: Orientation,
l4: Option<L4Proto>,
tcp: Option<TcpInfo>,
) -> Self {
Self {
key,
orientation,
l4,
tcp,
l4_meta: None,
}
}
pub fn with_l4_meta(mut self, meta: L4Meta) -> Self {
self.l4_meta = Some(meta);
self
}
pub fn rebased(mut self, delta: isize) -> Self {
if delta == 0 {
return self;
}
if let Some(t) = self.tcp.as_mut() {
match t.payload_offset.checked_add_signed(delta) {
Some(o) => t.payload_offset = o,
None => t.payload_len = 0,
}
}
if let Some(m) = self.l4_meta.as_mut() {
match m.payload_offset.checked_add_signed(delta) {
Some(o) => m.payload_offset = o,
None => self.l4_meta = None,
}
}
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct L4Meta {
pub ports: Option<(u16, u16)>,
pub payload_offset: usize,
pub payload_len: usize,
}
impl L4Meta {
pub fn new(ports: Option<(u16, u16)>, payload_offset: usize, payload_len: usize) -> Self {
Self {
ports,
payload_offset,
payload_len,
}
}
pub fn payload<'a>(&self, frame: &'a [u8]) -> &'a [u8] {
self.payload_offset
.checked_add(self.payload_len)
.and_then(|end| frame.get(self.payload_offset..end))
.unwrap_or(&[])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Orientation {
#[default]
Forward,
Reverse,
}
impl Orientation {
#[inline]
#[must_use]
pub fn flipped(self) -> Self {
match self {
Orientation::Forward => Orientation::Reverse,
Orientation::Reverse => Orientation::Forward,
}
}
#[inline]
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Orientation::Forward => "forward",
Orientation::Reverse => "reverse",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(tag = "kind", content = "value", rename_all = "snake_case")
)]
#[non_exhaustive]
pub enum L4Proto {
Tcp,
Udp,
Icmp,
IcmpV6,
Sctp,
Other(u8),
}
#[cfg(feature = "tracker")]
impl std::fmt::Display for L4Proto {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(crate::obs::l4_label(Some(*self)))
}
}
impl crate::KeyFields for L4Proto {
fn proto_str(&self) -> Option<&'static str> {
Some(match self {
L4Proto::Tcp => "TCP",
L4Proto::Udp => "UDP",
L4Proto::Icmp => "ICMP",
L4Proto::IcmpV6 => "ICMPv6",
L4Proto::Sctp => "SCTP",
L4Proto::Other(_) => return None,
})
}
fn protocol_identifier(&self) -> Option<u8> {
Some(self.as_u8())
}
}
impl L4Proto {
pub fn canonical_name(&self) -> &'static str {
match self {
L4Proto::Tcp => "tcp",
L4Proto::Udp => "udp",
L4Proto::Icmp => "icmp",
L4Proto::IcmpV6 => "icmp6",
L4Proto::Sctp => "sctp",
L4Proto::Other(_) => "other",
}
}
pub fn as_u8(&self) -> u8 {
match self {
L4Proto::Tcp => 6,
L4Proto::Udp => 17,
L4Proto::Icmp => 1,
L4Proto::IcmpV6 => 58,
L4Proto::Sctp => 132,
L4Proto::Other(n) => *n,
}
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct TcpInfo {
pub flags: TcpFlags,
pub seq: u32,
pub ack: u32,
pub payload_offset: usize,
pub payload_len: usize,
pub window: u16,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TcpFlags: u8 {
const FIN = 0b0000_0001;
const SYN = 0b0000_0010;
const RST = 0b0000_0100;
const PSH = 0b0000_1000;
const ACK = 0b0001_0000;
const URG = 0b0010_0000;
const ECE = 0b0100_0000;
const CWR = 0b1000_0000;
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for TcpFlags {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.bits().serialize(s)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for TcpFlags {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let raw = u8::deserialize(d)?;
Ok(TcpFlags::from_bits_retain(raw))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tcp_flags_basic() {
let f = TcpFlags::SYN | TcpFlags::ACK;
assert!(f.contains(TcpFlags::SYN));
assert!(f.contains(TcpFlags::ACK));
assert!(!f.contains(TcpFlags::FIN));
}
#[test]
fn extracted_clone() {
let e: Extracted<u32> = Extracted {
key: 42,
orientation: Orientation::Forward,
l4: Some(L4Proto::Tcp),
tcp: Some(TcpInfo {
flags: TcpFlags::SYN,
seq: 1,
ack: 0,
payload_offset: 54,
payload_len: 0,
window: 8192,
}),
l4_meta: None,
};
let cloned = e.clone();
assert_eq!(cloned.key, 42);
assert_eq!(cloned.orientation, Orientation::Forward);
}
#[test]
fn l4_proto_eq() {
assert_eq!(L4Proto::Tcp, L4Proto::Tcp);
assert_ne!(L4Proto::Tcp, L4Proto::Udp);
assert_eq!(L4Proto::Other(1), L4Proto::Other(1));
assert_ne!(L4Proto::Other(1), L4Proto::Other(2));
}
}