From dc3757d9319f04aa2c6ff8350e5ba2409a1fc210 Mon Sep 17 00:00:00 2001 From: GyulyVGC Date: Sun, 24 May 2026 18:50:33 +0200 Subject: [PATCH] add support for flow timestamps in IPFIX --- src/networking/ipfix/collect.rs | 2 ++ src/networking/ipfix/wire.rs | 21 +++++++++++++++++++++ src/networking/manage_packets.rs | 16 ++++++++++++---- src/networking/parse_packets.rs | 1 + 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/networking/ipfix/collect.rs b/src/networking/ipfix/collect.rs index a5a33438..9d3d191a 100644 --- a/src/networking/ipfix/collect.rs +++ b/src/networking/ipfix/collect.rs @@ -252,6 +252,7 @@ fn ingest_flow_record( record.dst_mac.map(format_mac), ); + let timestamps_hint = record.flow_start.zip(record.flow_end); let (traffic_direction, service) = modify_or_insert_in_map( info_traffic_msg, &key, @@ -263,6 +264,7 @@ fn ingest_flow_record( exchanged_packets, ip_blacklist, record.direction, + timestamps_hint, ); info_traffic_msg.tot_data_info.add_packets( diff --git a/src/networking/ipfix/wire.rs b/src/networking/ipfix/wire.rs index e1266315..8f17c577 100644 --- a/src/networking/ipfix/wire.rs +++ b/src/networking/ipfix/wire.rs @@ -17,6 +17,7 @@ use nom::number::complete::{be_u8, be_u16, be_u32}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::networking::types::traffic_direction::TrafficDirection; +use crate::utils::types::timestamp::Timestamp; pub const IPFIX_VERSION: u16 = 0x000A; pub const SET_ID_TEMPLATE: u16 = 2; @@ -107,6 +108,8 @@ pub struct FlowRecord { pub src_mac: Option<[u8; 6]>, pub dst_mac: Option<[u8; 6]>, pub direction: Option, + pub flow_start: Option, + pub flow_end: Option, } /// Parse a complete IPFIX message (header + sets). @@ -316,10 +319,28 @@ fn apply_ie(ie_id: u16, raw: &[u8], record: &mut FlowRecord) { _ => None, }; } + ie::FLOW_START_MILLISECONDS => { + record.flow_start = read_timestamp_ms(raw); + } + ie::FLOW_END_MILLISECONDS => { + record.flow_end = read_timestamp_ms(raw); + } _ => {} } } +/// IPFIX `dateTimeMilliseconds` is 8 bytes big-endian, ms since UNIX epoch. +/// Converted to Sniffnet's `Timestamp(secs, usecs)` representation. +fn read_timestamp_ms(raw: &[u8]) -> Option { + if raw.len() != 8 { + return None; + } + let ms = u64::from_be_bytes(raw.try_into().ok()?); + let secs = (ms / 1_000) as i64; + let usecs = ((ms % 1_000) * 1_000) as i64; + Some(Timestamp::new(secs, usecs)) +} + /// Read a big-endian unsigned integer of 1..=8 bytes into a `u128`. fn read_unsigned(raw: &[u8]) -> Option { if raw.is_empty() || raw.len() > 8 { diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs index 8226c47c..f0c9b4d8 100644 --- a/src/networking/manage_packets.rs +++ b/src/networking/manage_packets.rs @@ -19,6 +19,7 @@ use crate::networking::types::service::Service; use crate::networking::types::service_query::ServiceQuery; use crate::networking::types::traffic_direction::TrafficDirection; use crate::networking::types::traffic_type::TrafficType; +use crate::utils::types::timestamp::Timestamp; use std::fmt::Write; use std::time::Instant; @@ -267,6 +268,7 @@ pub fn modify_or_insert_in_map( exchanged_packets: u128, ip_blacklist: &IpBlacklist, direction_hint: Option, + timestamps_hint: Option<(Timestamp, Timestamp)>, ) -> (TrafficDirection, Service) { let mut traffic_direction = TrafficDirection::default(); let mut service = Service::Unknown; @@ -294,14 +296,20 @@ pub fn modify_or_insert_in_map( is_blacklisted = ip_blacklist.contains(&address_to_lookup); } - let timestamp = info_traffic_msg.last_packet_timestamp; + let receipt_ts = info_traffic_msg.last_packet_timestamp; + let (initial_ts, final_ts) = timestamps_hint.unwrap_or((receipt_ts, receipt_ts)); let new_info = info_traffic_msg .map .entry(*key) .and_modify(|info| { info.transmitted_bytes += exchanged_bytes; info.transmitted_packets += exchanged_packets; - info.final_timestamp = timestamp; + if initial_ts < info.initial_timestamp { + info.initial_timestamp = initial_ts; + } + if final_ts > info.final_timestamp { + info.final_timestamp = final_ts; + } info.final_instant = Instant::now(); if key.protocol.eq(&Protocol::ICMP) { info.icmp_types @@ -321,8 +329,8 @@ pub fn modify_or_insert_in_map( mac_address2: mac_addresses.1, transmitted_bytes: exchanged_bytes, transmitted_packets: exchanged_packets, - initial_timestamp: timestamp, - final_timestamp: timestamp, + initial_timestamp: initial_ts, + final_timestamp: final_ts, final_instant: Instant::now(), service, traffic_direction, diff --git a/src/networking/parse_packets.rs b/src/networking/parse_packets.rs index f1adfad8..cf272c44 100644 --- a/src/networking/parse_packets.rs +++ b/src/networking/parse_packets.rs @@ -186,6 +186,7 @@ pub fn parse_packets( 1, ip_blacklist, None, + None, ); info_traffic_msg