Skip to content

Commit 83db3d9

Browse files
mikemiles-devMichael Mileusnich
and
Michael Mileusnich
authored
v9 + Ipfix cleanup (#41)
V9/IPFix parse cleanup --------- Co-authored-by: Michael Mileusnich <[email protected]>
1 parent 7038cc7 commit 83db3d9

10 files changed

+123
-132
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "netflow_parser"
33
description = "Parser for Netflow Cisco V5, V7, V9, IPFIX"
4-
version = "0.2.3"
4+
version = "0.2.4"
55
edition = "2021"
66
77
license = "MIT OR Apache-2.0"

RELEASES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.2.4
2+
* Fixes for V9 parsing. Now supports processing multiple templates.
3+
* General code cleanup/Removal of uneeded code.
4+
15
# 0.2.3
26
* Small performance improvement by not parsing netflow version twice each packet.
37
* General Code cleanup for field_types and DataNumbers.

SECURITY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
| Version | Supported |
66
| ------- | ------------------ |
7+
| 0.2.4 | :white_check_mark: |
78
| 0.2.3 | :white_check_mark: |
89
| 0.2.2 | :white_check_mark: |
910
| 0.2.1 | :white_check_mark: |

src/lib.rs

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ pub mod protocol;
7575
pub mod static_versions;
7676
pub mod variable_versions;
7777

78+
use nom_derive::Parse;
7879
use serde::Serialize;
7980

80-
use netflow_header::{NetflowHeader, NetflowVersion};
81+
use netflow_header::{NetflowHeader, NetflowHeaderResult};
8182
use static_versions::{v5::V5, v7::V7};
8283
use variable_versions::ipfix::{IPFix, IPFixParser};
8384
use variable_versions::v9::{V9Parser, V9};
@@ -121,24 +122,44 @@ impl NetflowPacketResult {
121122
}
122123
}
123124

125+
impl From<V5> for NetflowPacketResult {
126+
fn from(v5: V5) -> Self {
127+
Self::V5(v5)
128+
}
129+
}
130+
131+
impl From<V7> for NetflowPacketResult {
132+
fn from(v7: V7) -> Self {
133+
Self::V7(v7)
134+
}
135+
}
136+
137+
impl From<V9> for NetflowPacketResult {
138+
fn from(v9: V9) -> Self {
139+
Self::V9(v9)
140+
}
141+
}
142+
143+
impl From<IPFix> for NetflowPacketResult {
144+
fn from(ipfix: IPFix) -> Self {
145+
Self::IPFix(ipfix)
146+
}
147+
}
148+
124149
#[derive(Debug, Clone)]
125150
struct ParsedNetflow {
126151
remaining: Vec<u8>,
127152
/// Parsed Netflow Packet
128153
netflow_packet: NetflowPacketResult,
129154
}
130155

131-
/// Trait provided for all static parser versions
132-
trait NetflowByteParserStatic {
133-
fn parse_bytes(packet: &[u8]) -> Result<ParsedNetflow, Box<dyn std::error::Error>>;
134-
}
135-
136-
/// Trait provided for all variable parser versions. We need a mutable self reference to store things like tempalates.
137-
trait NetflowByteParserVariable {
138-
fn parse_bytes(
139-
&mut self,
140-
packet: &[u8],
141-
) -> Result<ParsedNetflow, Box<dyn std::error::Error>>;
156+
impl ParsedNetflow {
157+
fn new(remaining: &[u8], netflow_packet: NetflowPacketResult) -> Self {
158+
Self {
159+
remaining: remaining.to_vec(),
160+
netflow_packet,
161+
}
162+
}
142163
}
143164

144165
#[derive(Default, Debug)]
@@ -148,28 +169,50 @@ pub struct NetflowParser {
148169
}
149170

150171
impl NetflowParser {
151-
/// We match versions to parsers.
172+
/// Parses a Netflow by version packet and returns a Parsed Netflow.
152173
fn parse_by_version<'a>(
153174
&'a mut self,
154175
packet: &'a [u8],
155176
) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
156177
match NetflowHeader::parse_header(packet) {
157-
Ok((i, netflow_header)) if netflow_header.version == NetflowVersion::V5 => {
158-
V5::parse_bytes(i)
159-
}
160-
Ok((i, netflow_header)) if netflow_header.version == NetflowVersion::V7 => {
161-
V7::parse_bytes(i)
162-
}
163-
Ok((i, netflow_header)) if netflow_header.version == NetflowVersion::V9 => {
164-
self.v9_parser.parse_bytes(i)
165-
}
166-
Ok((i, netflow_header)) if netflow_header.version == NetflowVersion::IPFix => {
167-
self.ipfix_parser.parse_bytes(i)
168-
}
178+
Ok(NetflowHeaderResult::V5(v5_packet)) => Self::parse_v5(v5_packet),
179+
Ok(NetflowHeaderResult::V7(v7_packet)) => Self::parse_v7(v7_packet),
180+
Ok(NetflowHeaderResult::V9(v9_packet)) => self.parse_v9(v9_packet),
181+
Ok(NetflowHeaderResult::IPFix(ipfix_packet)) => self.parse_ipfix(ipfix_packet),
169182
_ => Err("Not Supported".to_string().into()),
170183
}
171184
}
172185

186+
fn parse_v5(v5_packet: &[u8]) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
187+
V5::parse(v5_packet)
188+
.map_err(|e| format!("Could not parse V5 packet: {e}").into())
189+
.map(|(remaining, v5_parsed)| ParsedNetflow::new(remaining, v5_parsed.into()))
190+
}
191+
192+
fn parse_v7(v7_packet: &[u8]) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
193+
V7::parse(v7_packet)
194+
.map_err(|e| format!("Could not parse V7 packet: {e}").into())
195+
.map(|(remaining, v5_parsed)| ParsedNetflow::new(remaining, v5_parsed.into()))
196+
}
197+
198+
fn parse_v9(
199+
&mut self,
200+
v9_packet: &[u8],
201+
) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
202+
V9::parse(v9_packet, &mut self.v9_parser)
203+
.map_err(|e| format!("Could not parse V9 packet: {e}").into())
204+
.map(|(remaining, v9_parsed)| ParsedNetflow::new(remaining, v9_parsed.into()))
205+
}
206+
207+
fn parse_ipfix(
208+
&mut self,
209+
ipfix_packet: &[u8],
210+
) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
211+
IPFix::parse(ipfix_packet, &mut self.ipfix_parser)
212+
.map_err(|e| format!("Could not parse v10_packet: {e}").into())
213+
.map(|(remaining, ipfix_parsed)| ParsedNetflow::new(remaining, ipfix_parsed.into()))
214+
}
215+
173216
/// Takes a Netflow packet slice and returns a vector of Parsed Netflows.
174217
/// If we reach some parse error we return what items be have.
175218
///

src/netflow_header.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use nom::number::complete::be_u16;
2-
use nom::IResult;
32
use nom_derive::{Nom, Parse};
43

54
/// Struct is used simply to match how to handle the result of the packet
@@ -10,9 +9,24 @@ pub struct NetflowHeader {
109
pub version: NetflowVersion,
1110
}
1211

12+
pub enum NetflowHeaderResult<'a> {
13+
V5(&'a [u8]),
14+
V7(&'a [u8]),
15+
V9(&'a [u8]),
16+
IPFix(&'a [u8]),
17+
}
18+
1319
impl NetflowHeader {
14-
pub fn parse_header(packet: &[u8]) -> IResult<&[u8], NetflowHeader> {
15-
NetflowHeader::parse_be(packet)
20+
pub fn parse_header(
21+
packet: &[u8],
22+
) -> Result<NetflowHeaderResult, Box<dyn std::error::Error>> {
23+
match NetflowHeader::parse_be(packet) {
24+
Ok((i, header)) if header.version.is_v5() => Ok(NetflowHeaderResult::V5(i)),
25+
Ok((i, header)) if header.version.is_v7() => Ok(NetflowHeaderResult::V7(i)),
26+
Ok((i, header)) if header.version.is_v9() => Ok(NetflowHeaderResult::V9(i)),
27+
Ok((i, header)) if header.version.is_ipfix() => Ok(NetflowHeaderResult::IPFix(i)),
28+
_ => Err(("Unsupported Version").into()),
29+
}
1630
}
1731
}
1832

@@ -25,6 +39,21 @@ pub enum NetflowVersion {
2539
Unsupported,
2640
}
2741

42+
impl NetflowVersion {
43+
pub fn is_v5(&self) -> bool {
44+
*self == NetflowVersion::V5
45+
}
46+
pub fn is_v7(&self) -> bool {
47+
*self == NetflowVersion::V7
48+
}
49+
pub fn is_v9(&self) -> bool {
50+
*self == NetflowVersion::V9
51+
}
52+
pub fn is_ipfix(&self) -> bool {
53+
*self == NetflowVersion::IPFix
54+
}
55+
}
56+
2857
impl From<u16> for NetflowVersion {
2958
fn from(version: u16) -> Self {
3059
match version {

src/snapshots/netflow_parser__tests__it_parses_ipfix_with_no_template_fields_raises_error.snap

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,19 @@
22
source: src/lib.rs
33
expression: parser.parse_bytes(&packet)
44
---
5+
- IPFix:
6+
header:
7+
version: 10
8+
length: 26
9+
export_time:
10+
secs: 1
11+
nanos: 0
12+
sequence_number: 1
13+
observation_domain_id: 0
14+
sets: []
515
- Error:
6-
error_message: "Could not parse v10_set: Parsing Error: Error { input: [0, 8, 0, 0, 1, 1], code: Fail }"
16+
error_message: Not Supported
717
bytes:
8-
- 0
9-
- 10
10-
- 0
11-
- 26
12-
- 0
13-
- 0
14-
- 0
15-
- 1
16-
- 0
17-
- 0
18-
- 0
19-
- 1
20-
- 0
21-
- 0
22-
- 0
23-
- 0
2418
- 1
2519
- 2
2620
- 0

src/static_versions/v5.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
//! - <https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html>
55
66
use crate::protocol::ProtocolTypes;
7-
use crate::{NetflowByteParserStatic, NetflowPacketResult, ParsedNetflow};
87

98
use nom::number::complete::be_u32;
109
#[cfg(feature = "unix_timestamp")]
@@ -16,25 +15,14 @@ use Nom;
1615
use std::net::Ipv4Addr;
1716
use std::time::Duration;
1817

19-
#[derive(Debug, Nom, Clone, Serialize)]
18+
#[derive(Nom, Debug, Clone, Serialize)]
2019
pub struct V5 {
2120
/// V5 Header
2221
pub header: Header,
2322
/// V5 Body
2423
pub body: Body,
2524
}
2625

27-
impl NetflowByteParserStatic for V5 {
28-
#[inline]
29-
fn parse_bytes(packet: &[u8]) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
30-
let parsed_packet = V5::parse_be(packet).map_err(|e| format!("{e}"))?;
31-
Ok(ParsedNetflow {
32-
remaining: parsed_packet.0.to_vec(),
33-
netflow_packet: NetflowPacketResult::V5(parsed_packet.1),
34-
})
35-
}
36-
}
37-
3826
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Nom)]
3927
pub struct Header {
4028
/// NetFlow export format version number

src/static_versions/v7.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
//! - <https://www.cisco.com/en/US/technologies/tk648/tk362/technologies_white_paper09186a00800a3db9.html>
55
66
use crate::protocol::ProtocolTypes;
7-
use crate::{NetflowByteParserStatic, NetflowPacketResult, ParsedNetflow};
87

98
use nom::number::complete::be_u32;
109
#[cfg(feature = "unix_timestamp")]
@@ -24,17 +23,6 @@ pub struct V7 {
2423
pub body: Body,
2524
}
2625

27-
impl NetflowByteParserStatic for V7 {
28-
#[inline]
29-
fn parse_bytes(packet: &[u8]) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
30-
let parsed_packet = V7::parse_be(packet).map_err(|e| format!("{e}"))?;
31-
Ok(ParsedNetflow {
32-
remaining: parsed_packet.0.to_vec(),
33-
netflow_packet: NetflowPacketResult::V7(parsed_packet.1),
34-
})
35-
}
36-
}
37-
3826
#[derive(Debug, PartialEq, Eq, Clone, Copy, Nom, Serialize)]
3927
pub struct Header {
4028
/// NetFlow export format version number

src/variable_versions/ipfix.rs

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
99
use super::common::*;
1010
use crate::variable_versions::ipfix_lookup::*;
11-
use crate::{NetflowByteParserVariable, NetflowPacketResult, ParsedNetflow};
1211

1312
use nom::bytes::complete::take;
13+
use nom::combinator::complete;
1414
use nom::error::{Error as NomError, ErrorKind};
15-
use nom::multi::count;
15+
use nom::multi::{count, many0};
1616
use nom::number::complete::be_u32;
1717
use nom::Err as NomErr;
1818
use nom::IResult;
@@ -35,11 +35,13 @@ pub struct IPFixParser {
3535
pub options_templates: BTreeMap<TemplateId, OptionsTemplate>,
3636
}
3737

38-
#[derive(Debug, PartialEq, Clone, Serialize)]
38+
#[derive(Nom, Debug, PartialEq, Clone, Serialize)]
39+
#[nom(ExtraArgs(parser: &mut IPFixParser))]
3940
pub struct IPFix {
4041
/// IPFix Header
4142
pub header: Header,
4243
/// Sets
44+
#[nom(Parse = "many0(complete(|i| Set::parse(i, parser)))")]
4345
pub sets: Vec<Set>,
4446
}
4547

@@ -239,43 +241,3 @@ fn parse_fields<'a, T: CommonTemplate>(
239241
}
240242
Ok((&[], fields))
241243
}
242-
243-
impl NetflowByteParserVariable for IPFixParser {
244-
/// Takes a byte stream, returns either a Parsed Netflow or a Boxed Error.
245-
#[inline]
246-
fn parse_bytes<'a>(
247-
&'a mut self,
248-
packet: &'a [u8],
249-
) -> Result<ParsedNetflow, Box<dyn std::error::Error>> {
250-
let mut sets = vec![];
251-
252-
let (mut remaining, v10_header) =
253-
Header::parse(packet).map_err(|_| "Could not parse v10_packet".to_string())?;
254-
255-
let mut total_left = v10_header.length as usize;
256-
257-
// dbg!("remaining: {}", remaining);
258-
259-
while total_left != 0 {
260-
let (left_remaining, v10_set) = Set::parse(remaining, self)
261-
.map_err(|e| format!("Could not parse v10_set: {e}"))?;
262-
// dbg!("left remaining: {}", left_remaining);
263-
remaining = left_remaining;
264-
let parsed = total_left
265-
.checked_sub(remaining.len())
266-
.unwrap_or(total_left);
267-
total_left -= parsed;
268-
sets.push(v10_set);
269-
}
270-
271-
let v10_parsed = IPFix {
272-
header: v10_header,
273-
sets,
274-
};
275-
276-
Ok(ParsedNetflow {
277-
remaining: remaining.to_vec(),
278-
netflow_packet: NetflowPacketResult::IPFix(v10_parsed),
279-
})
280-
}
281-
}

0 commit comments

Comments
 (0)