use std::net::IpAddr;
use crate::extractor::Orientation;
pub trait KeyFields {
fn src_ip(&self) -> Option<IpAddr> {
None
}
fn src_port(&self) -> Option<u16> {
None
}
fn dest_ip(&self) -> Option<IpAddr> {
None
}
fn dest_port(&self) -> Option<u16> {
None
}
fn proto_str(&self) -> Option<&'static str> {
None
}
fn protocol_identifier(&self) -> Option<u8> {
None
}
fn app_proto_str(&self) -> Option<&'static str> {
None
}
fn stable_hash(&self) -> Option<u64> {
let src_ip = self.src_ip()?;
let src_port = self.src_port()?;
let dest_ip = self.dest_ip()?;
let dest_port = self.dest_port()?;
let mut id_buf = [0u8; 1];
let proto: &[u8] = if let Some(s) = self.proto_str() {
s.as_bytes()
} else {
let id = self.protocol_identifier()?;
id_buf[0] = id;
&id_buf
};
Some(fnv1a_five_tuple(
proto, src_ip, src_port, dest_ip, dest_port,
))
}
fn shard_index(&self, n: usize) -> Option<usize> {
if n == 0 {
return None;
}
Some((self.stable_hash()? % n as u64) as usize)
}
fn community_id(&self) -> Option<String> {
self.community_id_seeded(0)
}
fn community_id_seeded(&self, seed: u16) -> Option<String> {
#[cfg(feature = "community-id")]
{
crate::community_id::community_id_for_key(self, seed)
}
#[cfg(not(feature = "community-id"))]
{
let _ = seed;
None
}
}
}
#[derive(Debug)]
pub struct Directed<'a, K: ?Sized> {
key: &'a K,
from: Orientation,
}
impl<K: ?Sized> Clone for Directed<'_, K> {
fn clone(&self) -> Self {
*self
}
}
impl<K: ?Sized> Copy for Directed<'_, K> {}
impl<'a, K: ?Sized> Directed<'a, K> {
#[inline]
pub fn new(key: &'a K, from: Orientation) -> Self {
Self { key, from }
}
#[cfg(feature = "tracker")]
#[inline]
pub fn initiator(key: &'a K, stats: &crate::FlowStats) -> Self {
Self::new(key, stats.initiator_orientation)
}
#[inline]
pub fn key(&self) -> &'a K {
self.key
}
#[inline]
pub fn from(&self) -> Orientation {
self.from
}
#[inline]
fn reversed(&self) -> bool {
self.from == Orientation::Reverse
}
}
impl<K: KeyFields + ?Sized> KeyFields for Directed<'_, K> {
fn src_ip(&self) -> Option<IpAddr> {
if self.reversed() {
self.key.dest_ip()
} else {
self.key.src_ip()
}
}
fn src_port(&self) -> Option<u16> {
if self.reversed() {
self.key.dest_port()
} else {
self.key.src_port()
}
}
fn dest_ip(&self) -> Option<IpAddr> {
if self.reversed() {
self.key.src_ip()
} else {
self.key.dest_ip()
}
}
fn dest_port(&self) -> Option<u16> {
if self.reversed() {
self.key.src_port()
} else {
self.key.dest_port()
}
}
fn proto_str(&self) -> Option<&'static str> {
self.key.proto_str()
}
fn protocol_identifier(&self) -> Option<u8> {
self.key.protocol_identifier()
}
fn app_proto_str(&self) -> Option<&'static str> {
self.key.app_proto_str()
}
fn stable_hash(&self) -> Option<u64> {
self.key.stable_hash()
}
fn shard_index(&self, n: usize) -> Option<usize> {
self.key.shard_index(n)
}
fn community_id(&self) -> Option<String> {
self.key.community_id()
}
fn community_id_seeded(&self, seed: u16) -> Option<String> {
self.key.community_id_seeded(seed)
}
}
pub(crate) fn fnv1a_five_tuple(
proto: &[u8],
src_ip: IpAddr,
src_port: u16,
dest_ip: IpAddr,
dest_port: u16,
) -> u64 {
let (lo_ip, lo_port, hi_ip, hi_port) = if (src_ip, src_port) <= (dest_ip, dest_port) {
(src_ip, src_port, dest_ip, dest_port)
} else {
(dest_ip, dest_port, src_ip, src_port)
};
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
let mut h = FNV_OFFSET;
fn feed(b: u8, h: &mut u64) {
*h ^= b as u64;
*h = h.wrapping_mul(FNV_PRIME);
}
fn feed_ip(ip: IpAddr, h: &mut u64) {
match ip {
IpAddr::V4(v4) => {
for &b in &v4.octets() {
feed(b, h);
}
}
IpAddr::V6(v6) => {
for &b in &v6.octets() {
feed(b, h);
}
}
}
}
fn feed_port(p: u16, h: &mut u64) {
feed((p >> 8) as u8, h);
feed((p & 0xff) as u8, h);
}
for &b in proto {
feed(b, &mut h);
}
feed_ip(lo_ip, &mut h);
feed_port(lo_port, &mut h);
feed_ip(hi_ip, &mut h);
feed_port(hi_port, &mut h);
h
}
pub trait AnomalyFields {
fn anomaly_type(&self) -> Option<&'static str> {
None
}
fn anomaly_event(&self) -> Option<&'static str> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
struct CustomKey {
src: IpAddr,
dst: IpAddr,
}
impl KeyFields for CustomKey {
fn src_ip(&self) -> Option<IpAddr> {
Some(self.src)
}
fn dest_ip(&self) -> Option<IpAddr> {
Some(self.dst)
}
}
#[test]
fn key_fields_default_impls_return_none() {
struct Empty;
impl KeyFields for Empty {}
let e = Empty;
assert!(e.src_ip().is_none());
assert!(e.src_port().is_none());
assert!(e.dest_ip().is_none());
assert!(e.dest_port().is_none());
assert!(e.proto_str().is_none());
assert!(e.app_proto_str().is_none());
}
#[test]
fn anomaly_fields_default_impls_return_none() {
struct Empty;
impl AnomalyFields for Empty {}
let e = Empty;
assert!(e.anomaly_type().is_none());
assert!(e.anomaly_event().is_none());
}
#[test]
fn directed_swaps_endpoints_and_keeps_identity() {
struct Full;
impl KeyFields for Full {
fn src_ip(&self) -> Option<IpAddr> {
Some("10.0.0.1".parse().unwrap())
}
fn src_port(&self) -> Option<u16> {
Some(80)
}
fn dest_ip(&self) -> Option<IpAddr> {
Some("10.0.0.9".parse().unwrap())
}
fn dest_port(&self) -> Option<u16> {
Some(40000)
}
fn proto_str(&self) -> Option<&'static str> {
Some("TCP")
}
}
let fwd = Directed::new(&Full, Orientation::Forward);
assert_eq!(fwd.src_ip(), Full.src_ip());
assert_eq!(fwd.dest_port(), Some(40000));
let rev = Directed::new(&Full, Orientation::Reverse);
assert_eq!(rev.src_ip(), Full.dest_ip());
assert_eq!(rev.src_port(), Some(40000));
assert_eq!(rev.dest_ip(), Full.src_ip());
assert_eq!(rev.dest_port(), Some(80));
assert_eq!(rev.proto_str(), Some("TCP"));
assert_eq!(rev.stable_hash(), Full.stable_hash());
assert_eq!(rev.from(), Orientation::Reverse);
let dynkey: &dyn KeyFields = &Full;
assert_eq!(
Directed::new(dynkey, Orientation::Reverse).src_port(),
Some(40000)
);
}
#[test]
fn custom_key_overrides_only_chosen_fields() {
let k = CustomKey {
src: "10.0.0.1".parse().unwrap(),
dst: "10.0.0.2".parse().unwrap(),
};
assert_eq!(k.src_ip(), Some("10.0.0.1".parse().unwrap()));
assert_eq!(k.dest_ip(), Some("10.0.0.2".parse().unwrap()));
assert!(k.src_port().is_none());
assert!(k.proto_str().is_none());
}
}