Skip to content

Commit 7038cc7

Browse files
authored
Fix netflow v9 parsing (#42)
1 parent 4374d6a commit 7038cc7

7 files changed

+136
-106
lines changed

src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,6 @@ mod tests {
344344
},
345345
];
346346
let template = V9Template {
347-
length: 16,
348347
field_count: 2,
349348
template_id: 258,
350349
fields,
@@ -425,7 +424,6 @@ mod tests {
425424
0, 9, 0, 26, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2, 0, 10, 0, 8, 0, 0, 1, 1,
426425
];
427426
let template = V9Template {
428-
length: 10,
429427
field_count: 2,
430428
template_id: 258,
431429
fields: vec![],

src/snapshots/netflow_parser__tests__it_parses_v9.snap

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ expression: "NetflowParser::default().parse_bytes(&packet)"
1212
source_id: 1
1313
flowsets:
1414
- flow_set_id: 0
15-
template:
16-
length: 16
17-
template_id: 258
18-
field_count: 2
19-
fields:
20-
- field_type_number: 1
21-
field_type: InBytes
22-
field_length: 4
23-
- field_type_number: 8
24-
field_type: Ipv4SrcAddr
25-
field_length: 4
15+
length: 16
16+
templates:
17+
- template_id: 258
18+
field_count: 2
19+
fields:
20+
- field_type_number: 1
21+
field_type: InBytes
22+
field_length: 4
23+
- field_type_number: 8
24+
field_type: Ipv4SrcAddr
25+
field_length: 4
2626
- flow_set_id: 258
27+
length: 12
2728
data:
28-
length: 12
2929
data_fields:
3030
- InBytes:
3131
DataNumber: 151126788

src/snapshots/netflow_parser__tests__it_parses_v9_data_cached_template.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ expression: parser.parse_bytes(&packet)
1212
source_id: 1
1313
flowsets:
1414
- flow_set_id: 258
15+
length: 12
1516
data:
16-
length: 12
1717
data_fields:
1818
- InBytes:
1919
DataNumber: 151126788

src/snapshots/netflow_parser__tests__it_parses_v9_many_flows.snap

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ expression: "NetflowParser::default().parse_bytes(&packet)"
1212
source_id: 1
1313
flowsets:
1414
- flow_set_id: 0
15-
template:
16-
length: 16
17-
template_id: 258
18-
field_count: 2
19-
fields:
20-
- field_type_number: 1
21-
field_type: InBytes
22-
field_length: 4
23-
- field_type_number: 8
24-
field_type: Ipv4SrcAddr
25-
field_length: 4
15+
length: 16
16+
templates:
17+
- template_id: 258
18+
field_count: 2
19+
fields:
20+
- field_type_number: 1
21+
field_type: InBytes
22+
field_length: 4
23+
- field_type_number: 8
24+
field_type: Ipv4SrcAddr
25+
field_length: 4
2626
- flow_set_id: 258
27+
length: 12
2728
data:
28-
length: 12
2929
data_fields:
3030
- InBytes:
3131
DataNumber: 151126788
@@ -35,4 +35,3 @@ expression: "NetflowParser::default().parse_bytes(&packet)"
3535
DataNumber: 151126788
3636
Ipv4SrcAddr:
3737
Ip4Addr: 9.9.9.8
38-

src/snapshots/netflow_parser__tests__it_parses_v9_options_template.snap

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@ expression: "NetflowParser::default().parse_bytes(&packet)"
1212
source_id: 1
1313
flowsets:
1414
- flow_set_id: 1
15-
options_template:
16-
length: 22
17-
template_id: 275
18-
options_scope_length: 4
19-
options_length: 8
20-
scope_fields:
21-
- field_type_number: 2
22-
field_type: Interface
23-
field_length: 2
24-
option_fields:
25-
- field_type_number: 34
26-
field_type: SamplingInterval
27-
field_length: 2
28-
- field_type_number: 36
29-
field_type: FlowActiveTimeout
30-
field_length: 1
15+
length: 22
16+
options_templates:
17+
- template_id: 275
18+
options_scope_length: 4
19+
options_length: 8
20+
scope_fields:
21+
- field_type_number: 2
22+
field_type: Interface
23+
field_length: 2
24+
option_fields:
25+
- field_type_number: 34
26+
field_type: SamplingInterval
27+
field_length: 2
28+
- field_type_number: 36
29+
field_type: FlowActiveTimeout
30+
field_length: 1
3131
- flow_set_id: 275
32+
length: 9
3233
options_data:
33-
length: 9
3434
scope_fields:
3535
- interface:
3636
- 0

src/snapshots/netflow_parser__tests__it_parses_v9_with_no_template_fields_raises_error.snap

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,22 @@
22
source: src/lib.rs
33
expression: parser.parse_bytes(&packet)
44
---
5-
- Error:
6-
error_message: Could not parse v9_packet
7-
bytes:
8-
- 0
9-
- 9
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
24-
- 1
25-
- 2
26-
- 0
27-
- 10
28-
- 0
29-
- 8
30-
- 0
31-
- 0
32-
- 1
33-
- 1
5+
- V9:
6+
header:
7+
version: 9
8+
count: 26
9+
sys_up_time: 1
10+
unix_secs: 1
11+
sequence_number: 0
12+
source_id: 16908298
13+
flowsets:
14+
- flow_set_id: 8
15+
length: 0
16+
unparsed_data:
17+
- 0
18+
- 8
19+
- 0
20+
- 0
21+
- 1
22+
- 1
3423

src/variable_versions/v9.rs

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,35 @@ fn parse_flowsets<'a>(
4949
let mut remaining = i;
5050

5151
// Header.count represents total number of records in data + records in templates
52-
while count > 0 {
53-
let (i, flowset) = FlowSet::parse(remaining, parser)?;
54-
remaining = i;
52+
while count > 0 && !remaining.is_empty() {
53+
let (i, mut flowset) = FlowSet::parse(remaining, parser)?;
5554

56-
if flowset.template.is_some() || flowset.options_template.is_some() {
57-
count = count.saturating_sub(1);
58-
} else if let Some(data) = flowset.data.as_ref() {
55+
if let Some(data) = &flowset.templates {
56+
count = count.saturating_sub(data.len());
57+
}
58+
59+
if let Some(data) = &flowset.options_templates {
60+
count = count.saturating_sub(data.len());
61+
}
62+
63+
if let Some(data) = &flowset.data.as_ref() {
5964
count = count.saturating_sub(data.data_fields.len());
60-
} else if flowset.options_data.as_ref().is_some() {
65+
}
66+
67+
if flowset.options_data.as_ref().is_some() {
6168
count = count.saturating_sub(1);
6269
}
6370

71+
if flowset.is_empty() {
72+
flowset.unparsed_data = Some(remaining.to_vec());
73+
remaining = &[];
74+
} else if flowset.is_unparsed() {
75+
flowset.unparsed_data = Some(remaining[..flowset.length as usize].to_vec());
76+
remaining = &remaining[flowset.length as usize..];
77+
} else {
78+
remaining = i;
79+
}
80+
6481
flowsets.push(flowset)
6582
}
6683

@@ -104,26 +121,39 @@ pub struct FlowSet {
104121
/// the template record that describes option fields (described below) has a
105122
/// FlowSet ID of 1. A data record always has a nonzero FlowSet ID greater than 255.
106123
pub flow_set_id: u16,
124+
/// This field gives the length of the data FlowSet. Length is expressed in TLV format,
125+
/// meaning that the value includes the bytes used for the FlowSet ID and the length bytes
126+
/// themselves, as well as the combined lengths of any included data records.
127+
pub length: u16,
107128
/// Templates
108129
#[nom(
109130
Cond = "flow_set_id == TEMPLATE_ID",
110131
// Save our templates
111-
PostExec = "if let Some(template) = template.clone() { parser.templates.insert(template.template_id, template); }"
132+
PostExec = "if let Some(templates) = templates.clone() {
133+
for template in templates {
134+
parser.templates.insert(template.template_id, template);
135+
}
136+
}"
112137
)]
113138
#[serde(skip_serializing_if = "Option::is_none")]
114-
pub template: Option<Template>,
139+
pub templates: Option<Vec<Template>>,
115140
// Options template
116141
#[nom(
117142
Cond = "flow_set_id == OPTIONS_TEMPLATE_ID",
143+
Parse = "{ |i| parse_options_template_vec(i, length) }",
118144
// Save our options templates
119-
PostExec = "if let Some(options_template) = options_template.clone() { parser.options_templates.insert(options_template.template_id, options_template); }"
145+
PostExec = "if let Some(options_templates) = options_templates.clone() {
146+
for template in options_templates {
147+
parser.options_templates.insert(template.template_id, template);
148+
}
149+
}"
120150
)]
121151
#[serde(skip_serializing_if = "Option::is_none")]
122-
pub options_template: Option<OptionsTemplate>,
152+
pub options_templates: Option<Vec<OptionsTemplate>>,
123153
// Options Data
124154
#[nom(
125155
Cond = "flow_set_id > FLOW_SET_MIN_RANGE && parser.options_templates.get(&flow_set_id).is_some()",
126-
Parse = "{ |i| OptionsData::parse(i, parser, flow_set_id) }"
156+
Parse = "{ |i| OptionsData::parse(i, parser, flow_set_id, length) }"
127157
)]
128158
#[serde(skip_serializing_if = "Option::is_none")]
129159
pub options_data: Option<OptionsData>,
@@ -134,18 +164,27 @@ pub struct FlowSet {
134164
)]
135165
#[serde(skip_serializing_if = "Option::is_none")]
136166
pub data: Option<Data>,
167+
// Unparsed data
168+
#[nom(Ignore)]
169+
#[serde(skip_serializing_if = "Option::is_none")]
170+
pub unparsed_data: Option<Vec<u8>>,
171+
}
172+
173+
impl FlowSet {
174+
fn is_unparsed(&self) -> bool {
175+
self.templates.is_none()
176+
&& self.options_templates.is_none()
177+
&& self.data.is_none()
178+
&& self.options_data.is_none()
179+
}
180+
181+
fn is_empty(&self) -> bool {
182+
self.length == 0
183+
}
137184
}
138185

139186
#[derive(Debug, PartialEq, Clone, Serialize, Nom)]
140187
pub struct Template {
141-
/// Length refers to the total length of this FlowSet. Because an individual
142-
/// template FlowSet may contain multiple template IDs (as illustrated above),
143-
/// the length value should be used to determine the position of the next FlowSet
144-
/// record, which could be either a template or a data FlowSet.
145-
/// Length is expressed in Type/Length/Value (TLV) format, meaning that the value
146-
/// includes the bytes used for the FlowSet ID and the length bytes themselves, as
147-
/// well as the combined lengths of all template records included in this FlowSet.
148-
pub length: u16,
149188
/// As a router generates different template FlowSets to match the type of NetFlow
150189
/// data it will be exporting, each template is given a unique ID. This uniqueness
151190
/// is local to the router that generated the template ID.
@@ -162,10 +201,8 @@ pub struct Template {
162201
}
163202

164203
#[derive(Debug, PartialEq, Clone, Serialize, Nom)]
204+
#[nom(ExtraArgs(flowset_length: u16))]
165205
pub struct OptionsTemplate {
166-
/// This field gives the total length of this FlowSet. Because an individual template FlowSet might contain multiple template IDs, the length value must be used to determine the position of the next FlowSet record, which might be either a template or a data FlowSet.
167-
/// Length is expressed in TLV format, meaning that the value includes the bytes used for the FlowSet ID and the length bytes themselves, and the combined lengths of all template records included in this FlowSet.
168-
pub length: u16,
169206
/// As a router generates different template FlowSets to match the type of NetFlow data it is exporting, each template is given a unique ID. This uniqueness is local to the router that generated the template ID. The Template ID is greater than 255. Template IDs inferior to 255 are reserved.
170207
pub template_id: u16,
171208
/// This field gives the length in bytes of any scope fields that are contained in this options template.
@@ -181,12 +218,25 @@ pub struct OptionsTemplate {
181218
/// Padding
182219
#[nom(
183220
Map = "|i: &[u8]| i.to_vec()",
184-
Take = "(length.saturating_sub(options_scope_length).saturating_sub(options_length).saturating_sub(10)) as usize"
221+
Take = "(flowset_length.saturating_sub(options_scope_length).saturating_sub(options_length).saturating_sub(10)) as usize"
185222
)]
186223
#[serde(skip_serializing)]
187224
padding: Vec<u8>,
188225
}
189226

227+
fn parse_options_template_vec<'a>(
228+
i: &'a [u8],
229+
flowset_length: u16,
230+
) -> IResult<&'a [u8], Vec<OptionsTemplate>> {
231+
let mut fields = vec![];
232+
let mut remaining = i;
233+
while let Ok((rem, data)) = OptionsTemplate::parse(remaining, flowset_length) {
234+
fields.push(data);
235+
remaining = rem;
236+
}
237+
Ok((remaining, fields))
238+
}
239+
190240
/// Options Scope Fields
191241
#[derive(Debug, PartialEq, Clone, Serialize, Nom)]
192242
pub struct OptionsTemplateScopeField {
@@ -214,10 +264,8 @@ pub struct TemplateField {
214264
}
215265

216266
#[derive(Debug, PartialEq, Clone, Serialize, Nom)]
217-
#[nom(ExtraArgs(parser: &mut V9Parser, flow_set_id: u16))]
267+
#[nom(ExtraArgs(parser: &mut V9Parser, flow_set_id: u16, flowset_length: u16))]
218268
pub struct OptionsData {
219-
// Length
220-
pub length: u16,
221269
// Scope Data
222270
#[nom(
223271
Parse = "{ |i| parse_scope_data_fields(i, flow_set_id, &parser.options_templates) }"
@@ -230,7 +278,7 @@ pub struct OptionsData {
230278
pub options_fields: Vec<OptionDataField>,
231279
#[nom(
232280
Map = "|i: &[u8]| i.to_vec()",
233-
Take = "get_total_options_length(flow_set_id, length, parser)"
281+
Take = "get_total_options_length(flow_set_id, flowset_length, parser)"
234282
)]
235283
#[serde(skip_serializing)]
236284
padding: Vec<u8>,
@@ -319,10 +367,6 @@ pub struct ScopeDataField {
319367
#[derive(Debug, PartialEq, Clone, Serialize, Nom)]
320368
#[nom(ExtraArgs(parser: &mut V9Parser, flow_set_id: u16))]
321369
pub struct Data {
322-
/// This field gives the length of the data FlowSet. Length is expressed in TLV format,
323-
/// meaning that the value includes the bytes used for the FlowSet ID and the length bytes
324-
/// themselves, as well as the combined lengths of any included data records.
325-
pub length: u16,
326370
// Data Fields
327371
#[nom(Parse = "{ |i| parse_fields(i, parser.templates.get(&flow_set_id)) }")]
328372
pub data_fields: Vec<BTreeMap<V9Field, FieldValue>>,

0 commit comments

Comments
 (0)