Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "netflow_parser"
description = "Parser for Netflow Cisco V5, V7, V9, IPFIX"
version = "0.6.4"
version = "0.6.5"
edition = "2024"
authors = ["michael.mileusnich@gmail.com"]
license = "MIT OR Apache-2.0"
Expand Down
3 changes: 3 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 0.6.5
* Several memory and performance optimizations.

# 0.6.4
* Removed uneeded DataNumber Parsing for Durations.
* Renamed methods DurationMicros and DurationNanos into DurationMicrosNTP and DurationNanosNTP.
Expand Down
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,10 @@ pub struct NetflowParser {
}

#[derive(Debug, Clone)]
pub enum ParsedNetflow {
pub enum ParsedNetflow<'a> {
Success {
packet: NetflowPacket,
remaining: Vec<u8>,
remaining: &'a [u8],
},
Error {
error: NetflowParseError,
Expand Down Expand Up @@ -334,10 +334,10 @@ impl NetflowParser {
}

let mut packets = Vec::new();
let mut remaining = packet.to_vec();
let mut remaining = packet;

while !remaining.is_empty() {
match self.parse_packet_by_version(&remaining) {
match self.parse_packet_by_version(remaining) {
ParsedNetflow::Success {
packet,
remaining: new_remaining,
Expand All @@ -362,7 +362,7 @@ impl NetflowParser {
}

#[inline]
fn parse_packet_by_version(&mut self, packet: &[u8]) -> ParsedNetflow {
fn parse_packet_by_version<'a>(&mut self, packet: &'a [u8]) -> ParsedNetflow<'a> {
match GenericNetflowHeader::parse(packet) {
Ok((packet, header)) if self.allowed_versions.contains(&header.version) => {
match header.version {
Expand Down
173 changes: 100 additions & 73 deletions src/netflow_common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::net::IpAddr;

use crate::NetflowPacket;
Expand Down Expand Up @@ -116,6 +115,14 @@ impl From<&V7> for NetflowCommon {
}
}

/// Helper function to find a field value in a V9 flow record by field type
fn find_v9_field<'a>(
fields: &'a [(V9Field, FieldValue)],
field: V9Field,
) -> Option<&'a FieldValue> {
fields.iter().find(|(f, _)| *f == field).map(|(_, v)| v)
}

impl From<&V9> for NetflowCommon {
fn from(value: &V9) -> Self {
// Convert V9 to NetflowCommon
Expand All @@ -124,42 +131,33 @@ impl From<&V9> for NetflowCommon {
for flowset in &value.flowsets {
if let V9FlowSetBody::Data(data) = &flowset.body {
for data_field in &data.fields {
let value_map: HashMap<V9Field, FieldValue> =
data_field.clone().into_iter().collect();
flowsets.push(NetflowCommonFlowSet {
src_addr: value_map
.get(&V9Field::Ipv4SrcAddr)
.or_else(|| value_map.get(&V9Field::Ipv6SrcAddr))
src_addr: find_v9_field(data_field, V9Field::Ipv4SrcAddr)
.or_else(|| find_v9_field(data_field, V9Field::Ipv6SrcAddr))
.and_then(|v| v.try_into().ok()),
dst_addr: value_map
.get(&V9Field::Ipv4DstAddr)
.or_else(|| value_map.get(&V9Field::Ipv6DstAddr))
dst_addr: find_v9_field(data_field, V9Field::Ipv4DstAddr)
.or_else(|| find_v9_field(data_field, V9Field::Ipv6DstAddr))
.and_then(|v| v.try_into().ok()),
src_port: value_map
.get(&V9Field::L4SrcPort)
src_port: find_v9_field(data_field, V9Field::L4SrcPort)
.and_then(|v| v.try_into().ok()),
dst_port: value_map
.get(&V9Field::L4DstPort)
dst_port: find_v9_field(data_field, V9Field::L4DstPort)
.and_then(|v| v.try_into().ok()),
protocol_number: value_map
.get(&V9Field::Protocol)
protocol_number: find_v9_field(data_field, V9Field::Protocol)
.and_then(|v| v.try_into().ok()),
protocol_type: value_map.get(&V9Field::Protocol).and_then(|v| {
v.try_into()
.ok()
.map(|proto: u8| ProtocolTypes::from(proto))
}),
first_seen: value_map
.get(&V9Field::FirstSwitched)
protocol_type: find_v9_field(data_field, V9Field::Protocol).and_then(
|v| {
v.try_into()
.ok()
.map(|proto: u8| ProtocolTypes::from(proto))
},
),
first_seen: find_v9_field(data_field, V9Field::FirstSwitched)
.and_then(|v| v.try_into().ok()),
last_seen: value_map
.get(&V9Field::LastSwitched)
last_seen: find_v9_field(data_field, V9Field::LastSwitched)
.and_then(|v| v.try_into().ok()),
src_mac: value_map
.get(&V9Field::InSrcMac)
src_mac: find_v9_field(data_field, V9Field::InSrcMac)
.and_then(|v| v.try_into().ok()),
dst_mac: value_map
.get(&V9Field::InDstMac)
dst_mac: find_v9_field(data_field, V9Field::InDstMac)
.and_then(|v| v.try_into().ok()),
});
}
Expand All @@ -174,6 +172,14 @@ impl From<&V9> for NetflowCommon {
}
}

/// Helper function to find a field value in an IPFix flow record by field type
fn find_ipfix_field<'a>(
fields: &'a [(IPFixField, FieldValue)],
field: IPFixField,
) -> Option<&'a FieldValue> {
fields.iter().find(|(f, _)| *f == field).map(|(_, v)| v)
}

impl From<&IPFix> for NetflowCommon {
fn from(value: &IPFix) -> Self {
// Convert IPFix to NetflowCommon
Expand All @@ -183,52 +189,73 @@ impl From<&IPFix> for NetflowCommon {
for flowset in &value.flowsets {
if let IPFixFlowSetBody::Data(data) = &flowset.body {
for data_field in &data.fields {
let value_map: HashMap<IPFixField, FieldValue> =
data_field.clone().into_iter().collect();
flowsets.push(NetflowCommonFlowSet {
src_addr: value_map
.get(&IPFixField::IANA(IANAIPFixField::SourceIpv4address))
.or_else(|| {
value_map
.get(&IPFixField::IANA(IANAIPFixField::SourceIpv6address))
})
.and_then(|v| v.try_into().ok()),
dst_addr: value_map
.get(&IPFixField::IANA(IANAIPFixField::DestinationIpv4address))
.or_else(|| {
value_map.get(&IPFixField::IANA(
IANAIPFixField::DestinationIpv6address,
))
})
.and_then(|v| v.try_into().ok()),
src_port: value_map
.get(&IPFixField::IANA(IANAIPFixField::SourceTransportPort))
.and_then(|v| v.try_into().ok()),
dst_port: value_map
.get(&IPFixField::IANA(IANAIPFixField::DestinationTransportPort))
.and_then(|v| v.try_into().ok()),
protocol_number: value_map
.get(&IPFixField::IANA(IANAIPFixField::ProtocolIdentifier))
.and_then(|v| v.try_into().ok()),
protocol_type: value_map
.get(&IPFixField::IANA(IANAIPFixField::ProtocolIdentifier))
.and_then(|v| {
v.try_into()
.ok()
.map(|proto: u8| ProtocolTypes::from(proto))
}),
first_seen: value_map
.get(&IPFixField::IANA(IANAIPFixField::FlowStartSysUpTime))
.and_then(|v| v.try_into().ok()),
last_seen: value_map
.get(&IPFixField::IANA(IANAIPFixField::FlowEndSysUpTime))
.and_then(|v| v.try_into().ok()),
src_mac: value_map
.get(&IPFixField::IANA(IANAIPFixField::SourceMacaddress))
.and_then(|v| v.try_into().ok()),
dst_mac: value_map
.get(&IPFixField::IANA(IANAIPFixField::DestinationMacaddress))
.and_then(|v| v.try_into().ok()),
src_addr: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::SourceIpv4address),
)
.or_else(|| {
find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::SourceIpv6address),
)
})
.and_then(|v| v.try_into().ok()),
dst_addr: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::DestinationIpv4address),
)
.or_else(|| {
find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::DestinationIpv6address),
)
})
.and_then(|v| v.try_into().ok()),
src_port: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::SourceTransportPort),
)
.and_then(|v| v.try_into().ok()),
dst_port: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::DestinationTransportPort),
)
.and_then(|v| v.try_into().ok()),
protocol_number: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::ProtocolIdentifier),
)
.and_then(|v| v.try_into().ok()),
protocol_type: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::ProtocolIdentifier),
)
.and_then(|v| {
v.try_into()
.ok()
.map(|proto: u8| ProtocolTypes::from(proto))
}),
first_seen: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::FlowStartSysUpTime),
)
.and_then(|v| v.try_into().ok()),
last_seen: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::FlowEndSysUpTime),
)
.and_then(|v| v.try_into().ok()),
src_mac: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::SourceMacaddress),
)
.and_then(|v| v.try_into().ok()),
dst_mac: find_ipfix_field(
data_field,
IPFixField::IANA(IANAIPFixField::DestinationMacaddress),
)
.and_then(|v| v.try_into().ok()),
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/static_versions/v5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ use std::net::Ipv4Addr;
pub struct V5Parser;

impl V5Parser {
pub fn parse(packet: &[u8]) -> ParsedNetflow {
pub fn parse(packet: &[u8]) -> ParsedNetflow<'_> {
match V5::parse(packet) {
Ok((remaining, v5)) => ParsedNetflow::Success {
packet: NetflowPacket::V5(v5),
remaining: remaining.to_vec(),
remaining,
},
Err(e) => ParsedNetflow::Error {
error: NetflowParseError::Partial(PartialParse {
Expand Down
4 changes: 2 additions & 2 deletions src/static_versions/v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ use std::net::Ipv4Addr;
pub struct V7Parser;

impl V7Parser {
pub fn parse(packet: &[u8]) -> ParsedNetflow {
pub fn parse(packet: &[u8]) -> ParsedNetflow<'_> {
match V7::parse(packet) {
Ok((remaining, v7)) => ParsedNetflow::Success {
packet: NetflowPacket::V7(v7),
remaining: remaining.to_vec(),
remaining,
},
Err(e) => ParsedNetflow::Error {
error: NetflowParseError::Partial(PartialParse {
Expand Down
13 changes: 6 additions & 7 deletions src/variable_versions/data_number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,12 @@ impl FieldValue {
}
FieldDataType::String => {
let (i, taken) = take(field_length)(remaining)?;
let s = String::from_utf8_lossy(taken).to_string();
let s: String = s.chars().filter(|&c| !c.is_control()).collect();
let s = if s.starts_with("P4") {
s.trim_start_matches("P4").to_string()
} else {
s
};
// Single-pass string construction: filter control chars and strip "P4" prefix
let s: String = String::from_utf8_lossy(taken)
.chars()
.filter(|&c| !c.is_control())
.collect();
let s = s.strip_prefix("P4").unwrap_or(&s).to_string();
(i, FieldValue::String(s))
}
FieldDataType::Ip4Addr => {
Expand Down
Loading