Skip to content

Commit a7f81dd

Browse files
mikemiles-devMichael Mileusnich
and
Michael Mileusnich
authored
fix: Reworked how padding is calculated + vec export. (#112)
Co-authored-by: Michael Mileusnich <[email protected]>
1 parent ff0015e commit a7f81dd

7 files changed

+89
-78
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.5.3"
4+
version = "0.5.4"
55
edition = "2024"
66
authors = ["[email protected]"]
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.5.4
2+
* Reworked how padding is calculated for IPFIx.
3+
* Fixed Vecs not being exported for DataNumber.
4+
15
# 0.5.3
26
* Fixed bug when calcualting the enteperise field.
37
* Now properly parses variable length fields.

src/netflow_common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ mod common_tests {
513513
length: 0,
514514
},
515515
body: IPFixFlowSetBody {
516+
padding: vec![],
516517
template: None,
517518
options_template: None,
518519
options_data: None,

src/snapshots/netflow_parser__tests__base_tests__it_parses_ipfix_with_no_template_fields_raises_error.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ expression: parser.parse_bytes(&packet)
3131
- 0
3232
- 1
3333
- 1
34-
error: "Parsing Error: Error { input: [0, 8, 0, 0, 1, 1], code: Fail }"
34+
error: "Parsing Error: Error { input: [0, 8, 0, 0, 1, 1], code: MapRes }"
3535
remaining:
3636
- 0
3737
- 10

src/snapshots/netflow_parser__tests__base_tests__it_parses_multiple_packets.snap

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,37 @@ expression: "NetflowParser::default().parse_bytes(&all)"
186186
- 2:
187187
- PacketDeltaCount
188188
- DataNumber: 67438087
189+
- V5:
190+
header:
191+
version: 5
192+
count: 1
193+
sys_up_time: 50332672
194+
unix_secs: 83887623
195+
unix_nsecs: 134807553
196+
flow_sequence: 33752069
197+
engine_type: 6
198+
engine_id: 7
199+
sampling_interval: 2057
200+
flowsets:
201+
- src_addr: 0.1.2.3
202+
dst_addr: 4.5.6.7
203+
next_hop: 8.9.0.1
204+
input: 515
205+
output: 1029
206+
d_pkts: 101124105
207+
d_octets: 66051
208+
first: 67438087
209+
last: 134807553
210+
src_port: 515
211+
dst_port: 1029
212+
pad1: 6
213+
tcp_flags: 7
214+
protocol_number: 8
215+
protocol_type: Egp
216+
tos: 9
217+
src_as: 1
218+
dst_as: 515
219+
src_mask: 4
220+
dst_mask: 5
221+
pad2: 1543
222+

src/variable_versions/data_number.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ impl FieldValue {
196196
FieldValue::Float64(f) => f.to_be_bytes().to_vec(),
197197
FieldValue::Duration(d) => (d.as_secs() as u32).to_be_bytes().to_vec(),
198198
FieldValue::Ip4Addr(ip) => ip.octets().to_vec(),
199+
FieldValue::Vec(v) => v.clone(),
199200
_ => vec![],
200201
}
201202
}

src/variable_versions/ipfix.rs

Lines changed: 47 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use Nom;
1414
use nom::Err as NomErr;
1515
use nom::IResult;
1616
use nom::bytes::complete::take;
17+
use nom::combinator::map_res;
1718
use nom::error::{Error as NomError, ErrorKind};
1819
use nom::multi::count;
1920
use nom::number::complete::{be_u8, be_u16};
@@ -49,6 +50,26 @@ pub struct IPFixParser {
4950
pub options_templates: BTreeMap<TemplateId, OptionsTemplate>,
5051
}
5152

53+
// Custom parse set function to take only length provided by header.
54+
fn parse_sets<'a>(
55+
i: &'a [u8],
56+
parser: &mut IPFixParser,
57+
length: u16,
58+
) -> IResult<&'a [u8], Vec<FlowSet>> {
59+
let length = length.checked_sub(16).unwrap_or(length);
60+
let (i, taken) = take(length)(i)?;
61+
let mut sets = vec![];
62+
let mut remaining = taken;
63+
64+
while !remaining.is_empty() {
65+
let (i, set) = FlowSet::parse(remaining, parser)?;
66+
sets.push(set);
67+
remaining = i;
68+
}
69+
70+
Ok((i, sets))
71+
}
72+
5273
#[derive(Nom, Debug, PartialEq, Clone, Serialize)]
5374
#[nom(ExtraArgs(parser: &mut IPFixParser))]
5475
pub struct IPFix {
@@ -94,7 +115,12 @@ pub struct Header {
94115
#[nom(ExtraArgs(parser: &mut IPFixParser))]
95116
pub struct FlowSet {
96117
pub header: FlowSetHeader,
97-
#[nom(Parse = "{ |i| parse_set_body(i, parser, header.length, header.header_id) }")]
118+
#[nom(
119+
PreExec = "let length = header.length.saturating_sub(4);",
120+
Parse = "map_res(take(length),
121+
|i| FlowSetBody::parse(i, parser, header.header_id)
122+
.map(|(_, flow_set)| flow_set))"
123+
)]
98124
pub body: FlowSetBody,
99125
}
100126

@@ -112,7 +138,7 @@ pub struct FlowSetHeader {
112138
}
113139

114140
#[derive(Debug, PartialEq, Clone, Serialize, Nom)]
115-
#[nom(ExtraArgs(parser: &mut IPFixParser, id: u16, length: u16))]
141+
#[nom(ExtraArgs(parser: &mut IPFixParser, id: u16))]
116142
pub struct FlowSetBody {
117143
#[nom(
118144
Cond = "id < SET_MIN_RANGE && id != OPTIONS_TEMPLATE_ID",
@@ -126,8 +152,7 @@ pub struct FlowSetBody {
126152
pub template: Option<Template>,
127153
#[nom(
128154
Cond = "id == OPTIONS_TEMPLATE_ID",
129-
PreExec = "let set_length = length.checked_sub(4).unwrap_or(length);",
130-
Parse = "{ |i| OptionsTemplate::parse(i, set_length) }",
155+
Parse = "{ |i| OptionsTemplate::parse(i) }",
131156
Verify = "usize::from(options_template.field_count) == options_template.fields.len()",
132157
Verify = "options_template.get_fields().any(|f| f.field_length > 0)",
133158
// Save our templates
@@ -151,6 +176,8 @@ pub struct FlowSetBody {
151176
)]
152177
#[serde(skip_serializing_if = "Option::is_none")]
153178
pub options_data: Option<OptionsData>,
179+
#[serde(skip_serializing)]
180+
pub padding: Vec<u8>,
154181
}
155182

156183
#[derive(Debug, PartialEq, Clone, Serialize, Nom)]
@@ -176,46 +203,25 @@ pub struct OptionsData {
176203
}
177204

178205
#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Nom)]
179-
#[nom(ExtraArgs(set_length: u16))]
180206
pub struct OptionsTemplate {
181207
pub template_id: u16,
182208
pub field_count: u16,
183209
pub scope_field_count: u16,
184210
#[nom(
185211
PreExec = "let combined_count = usize::from(scope_field_count.saturating_add(
186212
field_count.checked_sub(scope_field_count).unwrap_or(field_count)));",
187-
Parse = "count(TemplateField::parse, combined_count)",
188-
PostExec = "let options_remaining
189-
= set_length.checked_sub(field_count.saturating_mul(4)).unwrap_or(set_length) > 0;"
213+
Parse = "count(TemplateField::parse, combined_count)"
190214
)]
191215
pub fields: Vec<TemplateField>,
192-
#[nom(Cond = "options_remaining && !i.is_empty()")]
193-
#[serde(skip_serializing)]
194-
padding: Option<u16>,
195216
}
196217

197218
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Nom, Default)]
198219
pub struct Template {
199220
pub template_id: u16,
200221
pub field_count: u16,
201-
#[nom(Parse = "{ |i| parse_template_fields(i, field_count) } ")]
202222
pub fields: Vec<TemplateField>,
203223
}
204224

205-
fn parse_template_fields(i: &[u8], count: u16) -> IResult<&[u8], Vec<TemplateField>> {
206-
let mut result = vec![];
207-
208-
let mut remaining = i;
209-
210-
for _ in 0..count {
211-
let (i, field) = TemplateField::parse(remaining)?;
212-
result.push(field);
213-
remaining = i;
214-
}
215-
216-
Ok((remaining, result))
217-
}
218-
219225
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Nom)]
220226
pub struct TemplateField {
221227
pub field_type_number: u16,
@@ -252,40 +258,6 @@ impl CommonTemplate for OptionsTemplate {
252258
}
253259
}
254260

255-
// Custom parse set function to take only length provided by header.
256-
fn parse_sets<'a>(
257-
i: &'a [u8],
258-
parser: &mut IPFixParser,
259-
length: u16,
260-
) -> IResult<&'a [u8], Vec<FlowSet>> {
261-
let length = length.checked_sub(16).unwrap_or(length);
262-
let (_, taken) = take(length)(i)?;
263-
let mut sets = vec![];
264-
let mut remaining = taken;
265-
266-
while !remaining.is_empty() {
267-
let (i, set) = FlowSet::parse(remaining, parser)?;
268-
sets.push(set);
269-
remaining = i;
270-
}
271-
272-
Ok((remaining, sets))
273-
}
274-
275-
// Custom parse set body function to take only length provided by set header.
276-
fn parse_set_body<'a>(
277-
i: &'a [u8],
278-
parser: &mut IPFixParser,
279-
length: u16,
280-
id: u16,
281-
) -> IResult<&'a [u8], FlowSetBody> {
282-
// length - 4 to account for the set header
283-
let length = length.checked_sub(4).unwrap_or(length);
284-
let (remaining, taken) = take(length)(i)?;
285-
let (_, set_body) = FlowSetBody::parse(taken, parser, id, length)?;
286-
Ok((remaining, set_body))
287-
}
288-
289261
/// Takes a byte stream and a cached template.
290262
/// Fields get matched to static types.
291263
/// Returns BTree of IPFix Types & Fields or IResult Error.
@@ -297,38 +269,38 @@ fn parse_fields<T: CommonTemplate + std::fmt::Debug>(
297269
return Err(NomErr::Error(NomError::new(i, ErrorKind::Fail)));
298270
}
299271

272+
let mut total_taken = 0;
273+
300274
// If no fields there are no fields to parse, return an error.
301275
let mut fields = vec![];
302276
let mut remaining = i;
303277
for (c, field) in template.get_fields().iter().enumerate() {
304278
// Iter through template fields and push them to a vec. If we encouter any zero length fields we return an error.
305279
let mut data_field = BTreeMap::new();
306280
let (i, field_value) = parse_field(remaining, field)?;
281+
let taken = remaining.len().saturating_sub(i.len());
282+
total_taken += taken;
307283
remaining = i;
308284
data_field.insert(c, (field.field_type, field_value));
309285
fields.push(data_field);
310286
}
311287

312-
if !remaining.is_empty()
313-
&& i.len()
314-
>= template
315-
.get_fields()
316-
.iter()
317-
.map(|m| m.field_length as usize)
318-
.sum::<usize>()
319-
{
320-
let (_, more) = parse_fields(remaining, template)?;
288+
let remaining = if !remaining.is_empty() && remaining.len() >= total_taken {
289+
let (remaining, more) = parse_fields(remaining, template)?;
321290
fields.extend(more);
322-
}
291+
remaining
292+
} else {
293+
remaining
294+
};
323295

324-
Ok((&[], fields))
296+
Ok((remaining, fields))
325297
}
326298

327299
// If 65335, read 1 byte.
328300
// If that byte is < 255 that is the length.
329301
// If that byte is == 255 then read 2 bytes. That is the length.
330302
// Otherwise, return the field length.
331-
fn calculate_variable_field_length<'a>(
303+
fn calculate_field_length<'a>(
332304
i: &'a [u8],
333305
template_field: &TemplateField,
334306
) -> IResult<&'a [u8], u16> {
@@ -349,7 +321,7 @@ fn parse_field<'a>(
349321
i: &'a [u8],
350322
template_field: &TemplateField,
351323
) -> IResult<&'a [u8], FieldValue> {
352-
let (i, length) = calculate_variable_field_length(i, template_field)?;
324+
let (i, length) = calculate_field_length(i, template_field)?;
353325
if template_field.enterprise_number.is_some() {
354326
let (i, data) = take(length)(i)?;
355327
Ok((i, FieldValue::Vec(data.to_vec())))
@@ -401,9 +373,6 @@ impl IPFix {
401373
result_flowset.extend_from_slice(&enterprise.to_be_bytes());
402374
}
403375
}
404-
if let Some(padding) = &options_template.padding {
405-
result_flowset.extend_from_slice(&padding.to_be_bytes());
406-
}
407376
}
408377

409378
if let Some(data) = &flow.body.data {
@@ -412,6 +381,8 @@ impl IPFix {
412381
result_flowset.extend_from_slice(&v.to_be_bytes());
413382
}
414383
}
384+
385+
result_flowset.extend_from_slice(&flow.body.padding);
415386
}
416387

417388
if let Some(data) = &flow.body.options_data {

0 commit comments

Comments
 (0)