• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## This program is published under a GPLv2 license
5## Netflow V5 appended by spaceB0x and Guillaume Valadon
6## Netflow V9 appended ny Gabriel Potter
7
8"""
9Cisco NetFlow protocol v1, v5 and v9
10
11
12
13- NetflowV9 build example:
14
15pkt = NetflowHeader()/\
16    NetflowHeaderV9()/\
17    NetflowFlowsetV9(templates=[
18        NetflowTemplateV9(templateID=258, template_fields=[
19            NetflowTemplateFieldV9(fieldType=1),
20            NetflowTemplateFieldV9(fieldType=62),
21        ]),
22        NetflowTemplateV9(templateID=257, template_fields=[
23            NetflowTemplateFieldV9(fieldType=1),
24            NetflowTemplateFieldV9(fieldType=62),
25        ]),
26    ])/NetflowDataflowsetV9(templateID=258, records=[
27        NetflowRecordV9(fieldValue=b"\x01\x02\x03\x05"),
28        NetflowRecordV9(fieldValue=b"\x05\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01"),
29    ])/NetflowDataflowsetV9(templateID=257, records=[
30        NetflowRecordV9(fieldValue=b"\x01\x02\x03\x04"),
31        NetflowRecordV9(fieldValue=b"\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01"),
32    ])/NetflowOptionsFlowsetV9(templateID=256, scopes=[NetflowOptionsFlowsetScopeV9(scopeFieldType=1, scopeFieldlength=4),
33                                                       NetflowOptionsFlowsetScopeV9(scopeFieldType=1, scopeFieldlength=3)],
34                                               options=[NetflowOptionsFlowsetOptionV9(optionFieldType=1, optionFieldlength=2),
35                                                        NetflowOptionsFlowsetOptionV9(optionFieldType=1, optionFieldlength=1)])/\
36    NetflowOptionsDataRecordV9(templateID=256, records=[NetflowOptionsRecordScopeV9(fieldValue=b"\x01\x02\x03\x04"),
37                                                        NetflowOptionsRecordScopeV9(fieldValue=b"\x01\x02\x03"),
38                                                        NetflowOptionsRecordOptionV9(fieldValue=b"\x01\x02"),
39                                                        NetflowOptionsRecordOptionV9(fieldValue=b"\x01")])
40"""
41
42
43from scapy.fields import *
44from scapy.packet import *
45from scapy.data import IP_PROTOS
46from scapy.layers.inet import UDP
47
48
49class NetflowHeader(Packet):
50    name = "Netflow Header"
51    fields_desc = [ ShortField("version", 1) ]
52
53
54###########################################
55### Netflow Version 1
56###########################################
57
58
59class NetflowHeaderV1(Packet):
60    name = "Netflow Header v1"
61    fields_desc = [ ShortField("count", 0),
62                    IntField("sysUptime", 0),
63                    UTCTimeField("unixSecs", 0),
64                    UTCTimeField("unixNanoSeconds", 0, use_nano=True) ]
65
66
67class NetflowRecordV1(Packet):
68    name = "Netflow Record v1"
69    fields_desc = [ IPField("ipsrc", "0.0.0.0"),
70                    IPField("ipdst", "0.0.0.0"),
71                    IPField("nexthop", "0.0.0.0"),
72                    ShortField("inputIfIndex", 0),
73                    ShortField("outpuIfIndex", 0),
74                    IntField("dpkts", 0),
75                    IntField("dbytes", 0),
76                    IntField("starttime", 0),
77                    IntField("endtime", 0),
78                    ShortField("srcport", 0),
79                    ShortField("dstport", 0),
80                    ShortField("padding", 0),
81                    ByteField("proto", 0),
82                    ByteField("tos", 0),
83                    IntField("padding1", 0),
84                    IntField("padding2", 0) ]
85
86
87bind_layers( NetflowHeader,   NetflowHeaderV1, version=1)
88bind_layers( NetflowHeaderV1, NetflowRecordV1 )
89bind_layers( NetflowRecordV1, NetflowRecordV1 )
90
91
92#########################################
93### Netflow Version 5
94#########################################
95
96
97class NetflowHeaderV5(Packet):
98    name = "Netflow Header v5"
99    fields_desc = [ ShortField("count", 0),
100                    IntField("sysUptime", 0),
101                    UTCTimeField("unixSecs", 0),
102                    UTCTimeField("unixNanoSeconds", 0, use_nano=True),
103                    IntField("flowSequence",0),
104                    ByteField("engineType", 0),
105                    ByteField("engineID", 0),
106                    ShortField("samplingInterval", 0) ]
107
108
109class NetflowRecordV5(Packet):
110    name = "Netflow Record v5"
111    fields_desc = [ IPField("src", "127.0.0.1"),
112                    IPField("dst", "127.0.0.1"),
113                    IPField("nexthop", "0.0.0.0"),
114                    ShortField("input", 0),
115                    ShortField("output", 0),
116                    IntField("dpkts", 1),
117                    IntField("dOctets", 60),
118                    IntField("first", 0),
119                    IntField("last", 0),
120                    ShortField("srcport", 0),
121                    ShortField("dstport", 0),
122                    ByteField("pad1", 0),
123                    FlagsField("tcpFlags", 0x2, 8, "FSRPAUEC"),
124                    ByteEnumField("prot", IP_PROTOS["tcp"], IP_PROTOS),
125                    ByteField("tos",0),
126                    ShortField("src_as", 0),
127                    ShortField("dst_as", 0),
128                    ByteField("src_mask", 0),
129                    ByteField("dst_mask", 0),
130                    ShortField("pad2", 0)]
131
132
133bind_layers( NetflowHeader,   NetflowHeaderV5, version=5)
134bind_layers( NetflowHeaderV5, NetflowRecordV5 )
135bind_layers( NetflowRecordV5, NetflowRecordV5 )
136
137#########################################
138### Netflow Version 9
139#########################################
140
141# https://www.ietf.org/rfc/rfc3954.txt
142
143NetflowV9TemplateFieldTypes = {
144        1: "IN_BYTES",
145        2: "IN_PKTS",
146        3: "FLOWS",
147        4: "PROTOCOL",
148        5: "TOS",
149        6: "TCP_FLAGS",
150        7: "L4_SRC_PORT",
151        8: "IPV4_SRC_ADDR",
152        9: "SRC_MASK",
153        10: "INPUT_SNMP",
154        11: "L4_DST_PORT",
155        12: "IPV4_DST_ADDR",
156        13: "DST_MASK",
157        14: "OUTPUT_SNMP",
158        15: "IPV4_NEXT_HOP",
159        16: "SRC_AS",
160        17: "DST_AS",
161        18: "BGP_IPV4_NEXT_HOP",
162        19: "MUL_DST_PKTS",
163        20: "MUL_DST_BYTES",
164        21: "LAST_SWITCHED",
165        22: "FIRST_SWITCHED",
166        23: "OUT_BYTES",
167        24: "OUT_PKTS",
168        27: "IPV6_SRC_ADDR",
169        28: "IPV6_DST_ADDR",
170        29: "IPV6_SRC_MASK",
171        30: "IPV6_DST_MASK",
172        31: "IPV6_FLOW_LABEL",
173        32: "ICMP_TYPE",
174        33: "MUL_IGMP_TYPE",
175        34: "SAMPLING_INTERVAL",
176        35: "SAMPLING_ALGORITHM",
177        36: "FLOW_ACTIVE_TIMEOUT",
178        37: "FLOW_INACTIVE_TIMEOUT",
179        38: "ENGINE_TYPE",
180        39: "ENGINE_ID",
181        40: "TOTAL_BYTES_EXP",
182        41: "TOTAL_PKTS_EXP",
183        42: "TOTAL_FLOWS_EXP",
184        46: "MPLS_TOP_LABEL_TYPE",
185        47: "MPLS_TOP_LABEL_IP_ADDR",
186        48: "FLOW_SAMPLER_ID",
187        49: "FLOW_SAMPLER_MODE",
188        50: "FLOW_SAMPLER_RANDOM_INTERVAL",
189        55: "DST_TOS",
190        56: "SRC_MAC",
191        57: "DST_MAC",
192        58: "SRC_VLAN",
193        59: "DST_VLAN",
194        60: "IP_PROTOCOL_VERSION",
195        61: "DIRECTION",
196        62: "IPV6_NEXT_HOP",
197        63: "BGP_IPV6_NEXT_HOP",
198        64: "IPV6_OPTION_HEADERS",
199        70: "MPLS_LABEL_1",
200        71: "MPLS_LABEL_2",
201        72: "MPLS_LABEL_3",
202        73: "MPLS_LABEL_4",
203        74: "MPLS_LABEL_5",
204        75: "MPLS_LABEL_6",
205        76: "MPLS_LABEL_7",
206        77: "MPLS_LABEL_8",
207        78: "MPLS_LABEL_9",
208        79: "MPLS_LABEL_10",
209    }
210
211ScopeFieldTypes = {
212    1: "System",
213    2: "Interface",
214    3: "Line card",
215    4: "Cache",
216    5: "Template",
217    }
218
219NetflowV9TemplateFieldDefaultLengths = {
220        1: 4,
221        2: 4,
222        3: 4,
223        4: 1,
224        5: 1,
225        6: 1,
226        7: 2,
227        8: 4,
228        9: 1,
229        10: 2,
230        11: 2,
231        12: 4,
232        13: 1,
233        14: 2,
234        15: 4,
235        16: 2,
236        17: 2,
237        18: 4,
238        19: 4,
239        20: 4,
240        21: 4,
241        22: 4,
242        23: 4,
243        24: 4,
244        27: 16,
245        28: 16,
246        29: 1,
247        30: 1,
248        31: 3,
249        32: 2,
250        33: 1,
251        34: 4,
252        35: 1,
253        36: 2,
254        37: 2,
255        38: 1,
256        39: 1,
257        40: 4,
258        41: 4,
259        42: 4,
260        46: 1,
261        47: 4,
262        48: 1,
263        49: 1,
264        50: 4,
265        55: 1,
266        56: 6,
267        57: 6,
268        58: 2,
269        59: 2,
270        60: 1,
271        61: 1,
272        62: 16,
273        63: 16,
274        64: 4,
275        70: 3,
276        71: 3,
277        72: 3,
278        73: 3,
279        74: 3,
280        75: 3,
281        76: 3,
282        77: 3,
283        78: 3,
284        79: 3,
285    }
286
287class NetflowHeaderV9(Packet):
288    name = "Netflow Header V9"
289    fields_desc = [ ShortField("count", 0),
290                    IntField("sysUptime", 0),
291                    UTCTimeField("unixSecs", 0),
292                    IntField("packageSequence",0),
293                    IntField("SourceID", 0) ]
294
295class NetflowTemplateFieldV9(Packet):
296    name = "Netflow Flowset Template Field V9"
297    fields_desc = [ ShortEnumField("fieldType", None, NetflowV9TemplateFieldTypes),
298                    ShortField("fieldLength", 0) ]
299    def __init__(self, *args, **kwargs):
300        Packet.__init__(self, *args, **kwargs)
301        if self.fieldType != None:
302            self.fieldLength = NetflowV9TemplateFieldDefaultLengths[self.fieldType]
303
304    def default_payload_class(self, p):
305        return conf.padding_layer
306
307class NetflowTemplateV9(Packet):
308    name = "Netflow Flowset Template V9"
309    fields_desc = [ ShortField("templateID", 255),
310                    FieldLenField("fieldCount", None, count_of="template_fields"),
311                    PacketListField("template_fields", [], NetflowTemplateFieldV9,
312                                    count_from = lambda pkt: pkt.fieldCount) ]
313
314    def default_payload_class(self, p):
315        return conf.padding_layer
316
317class NetflowFlowsetV9(Packet):
318    name = "Netflow FlowSet V9"
319    fields_desc = [ ShortField("flowSetID", 0),
320                    FieldLenField("length", None, length_of="templates", adjust=lambda pkt,x:x+4),
321                    PacketListField("templates", [], NetflowTemplateV9,
322                                    length_from = lambda pkt: pkt.length-4) ]
323
324class NetflowRecordV9(Packet):
325    name = "Netflow DataFlowset Record V9"
326    fields_desc = [ StrField("fieldValue", "") ]
327
328    def default_payload_class(self, p):
329        return conf.padding_layer
330
331class NetflowDataflowsetV9(Packet):
332    name = "Netflow DataFlowSet V9"
333    fields_desc = [ ShortField("templateID", 255),
334                    FieldLenField("length", None, length_of="records", adjust = lambda pkt,x:x+4),
335                    PadField(PacketListField("records", [], NetflowRecordV9,
336                                    length_from = lambda pkt: pkt.length-4),
337                             4, padwith=b"\x00") ]
338
339    @classmethod
340    def dispatch_hook(cls, _pkt=None, *args, **kargs):
341        if _pkt:
342            if _pkt[:2] == b"\x00\x01":
343                return NetflowOptionsFlowsetV9
344        return cls
345
346    def post_dissection(self, pkt):
347        # We need the whole packet to be dissected to access field def in NetflowFlowsetV9
348        root = pkt.firstlayer()
349        current = root
350        # Get all linked NetflowFlowsetV9
351        while current.payload.haslayer(NetflowFlowsetV9):
352            current = current.payload[NetflowFlowsetV9]
353            for ntv9 in current.templates:
354                current_ftl = root.getlayer(NetflowDataflowsetV9, templateID=ntv9.templateID)
355                if current_ftl:
356                    # Matched
357                    if len(current_ftl.records) > 1:
358                        # post_dissection is not necessary
359                        return
360                    # All data is stored in one record, awaiting to be splitted
361                    data = current_ftl.records.pop(0).fieldValue
362                    res = []
363                    # Now, according to the NetflowFlowsetV9 data, re-dissect NetflowDataflowsetV9
364                    for template in ntv9.template_fields:
365                        _l = template.fieldLength
366                        if _l:
367                            res.append(NetflowRecordV9(data[:_l]))
368                            data = data[_l:]
369                    if data:
370                        res.append(Raw(data))
371                    # Inject dissected data
372                    current_ftl.records = res
373                else:
374                    warning("[NetflowFlowsetV9 templateID=%s]: No matching NetflowDataflowsetV9 !" % ntv9.templateID)
375
376class NetflowOptionsFlowsetScopeV9(Packet):
377    name = "Netflow Options Template FlowSet V9 - Scope"
378    fields_desc = [ ShortEnumField("scopeFieldType", None, ScopeFieldTypes),
379                    ShortField("scopeFieldlength", 0) ]
380
381    def default_payload_class(self, p):
382        return conf.padding_layer
383
384class NetflowOptionsRecordScopeV9(NetflowRecordV9):
385    name = "Netflow Options Template Record V9 - Scope"
386
387class NetflowOptionsRecordOptionV9(NetflowRecordV9):
388    name = "Netflow Options Template Record V9 - Option"
389
390class NetflowOptionsFlowsetOptionV9(Packet):
391    name = "Netflow Options Template FlowSet V9 - Option"
392    fields_desc = [ ShortEnumField("optionFieldType", None, NetflowV9TemplateFieldTypes),
393                    ShortField("optionFieldlength", 0) ]
394
395    def default_payload_class(self, p):
396        return conf.padding_layer
397
398class NetflowOptionsFlowsetV9(Packet):
399    name = "Netflow Options Template FlowSet V9"
400    fields_desc = [ ShortField("flowSetID", 1),
401                    LenField("length", None),
402                    ShortField("templateID", 255),
403                    FieldLenField("option_scope_length", None, length_of="scopes"),
404                    FieldLenField("option_field_length", None, length_of="options"),
405                    PacketListField("scopes", [], NetflowOptionsFlowsetScopeV9,
406                                    length_from = lambda pkt: pkt.option_scope_length),
407                    PadField(PacketListField("options", [], NetflowOptionsFlowsetOptionV9,
408                                    length_from = lambda pkt: pkt.option_field_length),
409                             4, padwith=b"\x00") ]
410
411class NetflowOptionsDataRecordV9(NetflowDataflowsetV9):
412    name = "Netflow Options Data Record V9"
413    fields_desc = [ ShortField("templateID", 255),
414                FieldLenField("length", None, length_of="records", adjust = lambda pkt,x:x+4),
415                PadField(PacketListField("records", [], NetflowRecordV9,
416                                length_from = lambda pkt: pkt.length-4),
417                         4, padwith=b"\x00") ]
418
419    def post_dissection(self, pkt):
420        options_data_record = pkt[NetflowOptionsDataRecordV9]
421        if pkt.haslayer(NetflowOptionsFlowsetV9):
422            options_flowset = pkt[NetflowOptionsFlowsetV9]
423            data = options_data_record.records.pop(0).fieldValue
424            res = []
425            # Now, according to the NetflowOptionsFlowsetV9 data, re-dissect NetflowOptionsDataRecordV9
426            for scope in options_flowset.scopes:
427                _l = scope.scopeFieldlength
428                if _l:
429                    res.append(NetflowOptionsRecordScopeV9(data[:_l]))
430                    data = data[_l:]
431
432            # Now, according to the NetflowOptionsFlowsetV9 data, re-dissect NetflowOptionsDataRecordV9
433            for option in options_flowset.options:
434                _l = option.optionFieldlength
435                if _l:
436                    res.append(NetflowOptionsRecordOptionV9(data[:_l]))
437                    data = data[_l:]
438            if data:
439                res.append(Raw(data))
440            # Inject dissected data
441            options_data_record.records = res
442
443bind_layers( NetflowHeader, NetflowHeaderV9, version=9 )
444bind_layers( NetflowHeaderV9, NetflowFlowsetV9 )
445bind_layers( NetflowFlowsetV9, NetflowDataflowsetV9 )
446bind_layers( NetflowDataflowsetV9, NetflowDataflowsetV9 )
447
448bind_layers( NetflowOptionsFlowsetV9, NetflowOptionsDataRecordV9 )
449