diff --git a/scapy/fields.py b/scapy/fields.py index b5a3270c1..002b74e0b 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -1434,8 +1434,8 @@ class IP6PrefixField(_IPPrefixFieldBase): _IPPrefixFieldBase.__init__(self, name, default, wordbytes, 16, lambda a: inet_pton(socket.AF_INET6, a), lambda n: inet_ntop(socket.AF_INET6, n), length_from) class UTCTimeField(IntField): - __slots__ = ["epoch", "delta", "strf"] - def __init__(self, name, default, epoch=None, strf="%a, %d %b %Y %H:%M:%S +0000"): + __slots__ = ["epoch", "delta", "strf", "use_nano"] + def __init__(self, name, default, epoch=None, use_nano=False, strf="%a, %d %b %Y %H:%M:%S +0000"): IntField.__init__(self, name, default) if epoch is None: mk_epoch = EPOCH @@ -1444,9 +1444,12 @@ class UTCTimeField(IntField): self.epoch = mk_epoch self.delta = mk_epoch - EPOCH self.strf = strf + self.use_nano = use_nano def i2repr(self, pkt, x): if x is None: x = 0 + elif self.use_nano: + x = x/1e9 x = int(x) + self.delta t = time.strftime(self.strf, time.gmtime(x)) return "%s (%d)" % (t, x) diff --git a/scapy/layers/netflow.py b/scapy/layers/netflow.py index e659a5613..be130617e 100644 --- a/scapy/layers/netflow.py +++ b/scapy/layers/netflow.py @@ -3,15 +3,47 @@ ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license ## Netflow V5 appended by spaceB0x and Guillaume Valadon +## Netflow V9 appended ny Gabriel Potter """ -Cisco NetFlow protocol v1 and v5 +Cisco NetFlow protocol v1, v5 and v9 + + + +- NetflowV9 build example: + +pkt = NetflowHeader()/\ + NetflowHeaderV9()/\ + NetflowFlowsetV9(templates=[ + NetflowTemplateV9(templateID=258, template_fields=[ + NetflowTemplateFieldV9(fieldType=1), + NetflowTemplateFieldV9(fieldType=62), + ]), + NetflowTemplateV9(templateID=257, template_fields=[ + NetflowTemplateFieldV9(fieldType=1), + NetflowTemplateFieldV9(fieldType=62), + ]), + ])/NetflowDataflowsetV9(templateID=258, records=[ + NetflowRecordV9(fieldValue=b"\x01\x02\x03\x05"), + NetflowRecordV9(fieldValue=b"\x05\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01"), + ])/NetflowDataflowsetV9(templateID=257, records=[ + NetflowRecordV9(fieldValue=b"\x01\x02\x03\x04"), + NetflowRecordV9(fieldValue=b"\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01"), + ])/NetflowOptionsFlowsetV9(templateID=256, scopes=[NetflowOptionsFlowsetScopeV9(scopeFieldType=1, scopeFieldlength=4), + NetflowOptionsFlowsetScopeV9(scopeFieldType=1, scopeFieldlength=3)], + options=[NetflowOptionsFlowsetOptionV9(optionFieldType=1, optionFieldlength=2), + NetflowOptionsFlowsetOptionV9(optionFieldType=1, optionFieldlength=1)])/\ + NetflowOptionsDataRecordV9(templateID=256, records=[NetflowOptionsRecordScopeV9(fieldValue=b"\x01\x02\x03\x04"), + NetflowOptionsRecordScopeV9(fieldValue=b"\x01\x02\x03"), + NetflowOptionsRecordOptionV9(fieldValue=b"\x01\x02"), + NetflowOptionsRecordOptionV9(fieldValue=b"\x01")]) """ from scapy.fields import * from scapy.packet import * from scapy.data import IP_PROTOS +from scapy.layers.inet import UDP class NetflowHeader(Packet): @@ -28,8 +60,8 @@ class NetflowHeaderV1(Packet): name = "Netflow Header v1" fields_desc = [ ShortField("count", 0), IntField("sysUptime", 0), - IntField("unixSecs", 0), - IntField("unixNanoSeconds", 0) ] + UTCTimeField("unixSecs", 0), + UTCTimeField("unixNanoSeconds", 0, use_nano=True) ] class NetflowRecordV1(Packet): @@ -66,8 +98,8 @@ class NetflowHeaderV5(Packet): name = "Netflow Header v5" fields_desc = [ ShortField("count", 0), IntField("sysUptime", 0), - IntField("unixSecs", 0), - IntField("unixNanoSeconds", 0), + UTCTimeField("unixSecs", 0), + UTCTimeField("unixNanoSeconds", 0, use_nano=True), IntField("flowSequence",0), ByteField("engineType", 0), ByteField("engineID", 0), @@ -101,3 +133,316 @@ class NetflowRecordV5(Packet): bind_layers( NetflowHeader, NetflowHeaderV5, version=5) bind_layers( NetflowHeaderV5, NetflowRecordV5 ) bind_layers( NetflowRecordV5, NetflowRecordV5 ) + +######################################### +### Netflow Version 9 +######################################### + +# https://www.ietf.org/rfc/rfc3954.txt + +NetflowV9TemplateFieldTypes = { + 1: "IN_BYTES", + 2: "IN_PKTS", + 3: "FLOWS", + 4: "PROTOCOL", + 5: "TOS", + 6: "TCP_FLAGS", + 7: "L4_SRC_PORT", + 8: "IPV4_SRC_ADDR", + 9: "SRC_MASK", + 10: "INPUT_SNMP", + 11: "L4_DST_PORT", + 12: "IPV4_DST_ADDR", + 13: "DST_MASK", + 14: "OUTPUT_SNMP", + 15: "IPV4_NEXT_HOP", + 16: "SRC_AS", + 17: "DST_AS", + 18: "BGP_IPV4_NEXT_HOP", + 19: "MUL_DST_PKTS", + 20: "MUL_DST_BYTES", + 21: "LAST_SWITCHED", + 22: "FIRST_SWITCHED", + 23: "OUT_BYTES", + 24: "OUT_PKTS", + 27: "IPV6_SRC_ADDR", + 28: "IPV6_DST_ADDR", + 29: "IPV6_SRC_MASK", + 30: "IPV6_DST_MASK", + 31: "IPV6_FLOW_LABEL", + 32: "ICMP_TYPE", + 33: "MUL_IGMP_TYPE", + 34: "SAMPLING_INTERVAL", + 35: "SAMPLING_ALGORITHM", + 36: "FLOW_ACTIVE_TIMEOUT", + 37: "FLOW_INACTIVE_TIMEOUT", + 38: "ENGINE_TYPE", + 39: "ENGINE_ID", + 40: "TOTAL_BYTES_EXP", + 41: "TOTAL_PKTS_EXP", + 42: "TOTAL_FLOWS_EXP", + 46: "MPLS_TOP_LABEL_TYPE", + 47: "MPLS_TOP_LABEL_IP_ADDR", + 48: "FLOW_SAMPLER_ID", + 49: "FLOW_SAMPLER_MODE", + 50: "FLOW_SAMPLER_RANDOM_INTERVAL", + 55: "DST_TOS", + 56: "SRC_MAC", + 57: "DST_MAC", + 58: "SRC_VLAN", + 59: "DST_VLAN", + 60: "IP_PROTOCOL_VERSION", + 61: "DIRECTION", + 62: "IPV6_NEXT_HOP", + 63: "BGP_IPV6_NEXT_HOP", + 64: "IPV6_OPTION_HEADERS", + 70: "MPLS_LABEL_1", + 71: "MPLS_LABEL_2", + 72: "MPLS_LABEL_3", + 73: "MPLS_LABEL_4", + 74: "MPLS_LABEL_5", + 75: "MPLS_LABEL_6", + 76: "MPLS_LABEL_7", + 77: "MPLS_LABEL_8", + 78: "MPLS_LABEL_9", + 79: "MPLS_LABEL_10", + } + +ScopeFieldTypes = { + 1: "System", + 2: "Interface", + 3: "Line card", + 4: "Cache", + 5: "Template", + } + +NetflowV9TemplateFieldDefaultLengths = { + 1: 4, + 2: 4, + 3: 4, + 4: 1, + 5: 1, + 6: 1, + 7: 2, + 8: 4, + 9: 1, + 10: 2, + 11: 2, + 12: 4, + 13: 1, + 14: 2, + 15: 4, + 16: 2, + 17: 2, + 18: 4, + 19: 4, + 20: 4, + 21: 4, + 22: 4, + 23: 4, + 24: 4, + 27: 16, + 28: 16, + 29: 1, + 30: 1, + 31: 3, + 32: 2, + 33: 1, + 34: 4, + 35: 1, + 36: 2, + 37: 2, + 38: 1, + 39: 1, + 40: 4, + 41: 4, + 42: 4, + 46: 1, + 47: 4, + 48: 1, + 49: 1, + 50: 4, + 55: 1, + 56: 6, + 57: 6, + 58: 2, + 59: 2, + 60: 1, + 61: 1, + 62: 16, + 63: 16, + 64: 4, + 70: 3, + 71: 3, + 72: 3, + 73: 3, + 74: 3, + 75: 3, + 76: 3, + 77: 3, + 78: 3, + 79: 3, + } + +class NetflowHeaderV9(Packet): + name = "Netflow Header V9" + fields_desc = [ ShortField("count", 0), + IntField("sysUptime", 0), + UTCTimeField("unixSecs", 0), + IntField("packageSequence",0), + IntField("SourceID", 0) ] + +class NetflowTemplateFieldV9(Packet): + name = "Netflow Flowset Template Field V9" + fields_desc = [ ShortEnumField("fieldType", None, NetflowV9TemplateFieldTypes), + ShortField("fieldLength", 0) ] + def __init__(self, *args, **kwargs): + Packet.__init__(self, *args, **kwargs) + if self.fieldType != None: + self.fieldLength = NetflowV9TemplateFieldDefaultLengths[self.fieldType] + + def default_payload_class(self, p): + return conf.padding_layer + +class NetflowTemplateV9(Packet): + name = "Netflow Flowset Template V9" + fields_desc = [ ShortField("templateID", 255), + FieldLenField("fieldCount", None, count_of="template_fields"), + PacketListField("template_fields", [], NetflowTemplateFieldV9, + count_from = lambda pkt: pkt.fieldCount) ] + + def default_payload_class(self, p): + return conf.padding_layer + +class NetflowFlowsetV9(Packet): + name = "Netflow FlowSet V9" + fields_desc = [ ShortField("flowSetID", 0), + FieldLenField("length", None, length_of="templates", adjust=lambda pkt,x:x+4), + PacketListField("templates", [], NetflowTemplateV9, + length_from = lambda pkt: pkt.length-4) ] + +class NetflowRecordV9(Packet): + name = "Netflow DataFlowset Record V9" + fields_desc = [ StrField("fieldValue", "") ] + + def default_payload_class(self, p): + return conf.padding_layer + +class NetflowDataflowsetV9(Packet): + name = "Netflow DataFlowSet V9" + fields_desc = [ ShortField("templateID", 255), + FieldLenField("length", None, length_of="records", adjust = lambda pkt,x:x+4), + PadField(PacketListField("records", [], NetflowRecordV9, + length_from = lambda pkt: pkt.length-4), + 4, padwith=b"\x00") ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + if _pkt[:2] == b"\x00\x01": + return NetflowOptionsFlowsetV9 + return cls + + def post_dissection(self, pkt): + # We need the whole packet to be dissected to access field def in NetflowFlowsetV9 + root = pkt.firstlayer() + current = root + # Get all linked NetflowFlowsetV9 + while current.payload.haslayer(NetflowFlowsetV9): + current = current.payload[NetflowFlowsetV9] + for ntv9 in current.templates: + current_ftl = root.getlayer(NetflowDataflowsetV9, templateID=ntv9.templateID) + if current_ftl: + # Matched + if len(current_ftl.records) > 1: + # post_dissection is not necessary + return + # All data is stored in one record, awaiting to be splitted + data = current_ftl.records.pop(0).fieldValue + res = [] + # Now, according to the NetflowFlowsetV9 data, re-dissect NetflowDataflowsetV9 + for template in ntv9.template_fields: + _l = template.fieldLength + if _l: + res.append(NetflowRecordV9(data[:_l])) + data = data[_l:] + if data: + res.append(Raw(data)) + # Inject dissected data + current_ftl.records = res + else: + warning("[NetflowFlowsetV9 templateID=%s]: No matching NetflowDataflowsetV9 !" % ntv9.templateID) + +class NetflowOptionsFlowsetScopeV9(Packet): + name = "Netflow Options Template FlowSet V9 - Scope" + fields_desc = [ ShortEnumField("scopeFieldType", None, ScopeFieldTypes), + ShortField("scopeFieldlength", 0) ] + + def default_payload_class(self, p): + return conf.padding_layer + +class NetflowOptionsRecordScopeV9(NetflowRecordV9): + name = "Netflow Options Template Record V9 - Scope" + +class NetflowOptionsRecordOptionV9(NetflowRecordV9): + name = "Netflow Options Template Record V9 - Option" + +class NetflowOptionsFlowsetOptionV9(Packet): + name = "Netflow Options Template FlowSet V9 - Option" + fields_desc = [ ShortEnumField("optionFieldType", None, NetflowV9TemplateFieldTypes), + ShortField("optionFieldlength", 0) ] + + def default_payload_class(self, p): + return conf.padding_layer + +class NetflowOptionsFlowsetV9(Packet): + name = "Netflow Options Template FlowSet V9" + fields_desc = [ ShortField("flowSetID", 1), + LenField("length", None), + ShortField("templateID", 255), + FieldLenField("option_scope_length", None, length_of="scopes"), + FieldLenField("option_field_length", None, length_of="options"), + PacketListField("scopes", [], NetflowOptionsFlowsetScopeV9, + length_from = lambda pkt: pkt.option_scope_length), + PadField(PacketListField("options", [], NetflowOptionsFlowsetOptionV9, + length_from = lambda pkt: pkt.option_field_length), + 4, padwith=b"\x00") ] + +class NetflowOptionsDataRecordV9(NetflowDataflowsetV9): + name = "Netflow Options Data Record V9" + fields_desc = [ ShortField("templateID", 255), + FieldLenField("length", None, length_of="records", adjust = lambda pkt,x:x+4), + PadField(PacketListField("records", [], NetflowRecordV9, + length_from = lambda pkt: pkt.length-4), + 4, padwith=b"\x00") ] + + def post_dissection(self, pkt): + options_data_record = pkt[NetflowOptionsDataRecordV9] + if pkt.haslayer(NetflowOptionsFlowsetV9): + options_flowset = pkt[NetflowOptionsFlowsetV9] + data = options_data_record.records.pop(0).fieldValue + res = [] + # Now, according to the NetflowOptionsFlowsetV9 data, re-dissect NetflowOptionsDataRecordV9 + for scope in options_flowset.scopes: + _l = scope.scopeFieldlength + if _l: + res.append(NetflowOptionsRecordScopeV9(data[:_l])) + data = data[_l:] + + # Now, according to the NetflowOptionsFlowsetV9 data, re-dissect NetflowOptionsDataRecordV9 + for option in options_flowset.options: + _l = option.optionFieldlength + if _l: + res.append(NetflowOptionsRecordOptionV9(data[:_l])) + data = data[_l:] + if data: + res.append(Raw(data)) + # Inject dissected data + options_data_record.records = res + +bind_layers( NetflowHeader, NetflowHeaderV9, version=9 ) +bind_layers( NetflowHeaderV9, NetflowFlowsetV9 ) +bind_layers( NetflowFlowsetV9, NetflowDataflowsetV9 ) +bind_layers( NetflowDataflowsetV9, NetflowDataflowsetV9 ) + +bind_layers( NetflowOptionsFlowsetV9, NetflowOptionsDataRecordV9 ) diff --git a/test/regression.uts b/test/regression.uts index 8e9c8bc2f..f4f2a53dd 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -5254,6 +5254,57 @@ raw(NetflowHeader()/NetflowHeaderV5(count=1)/NetflowRecordV5(dst="192.168.0.1")) nf5 = NetflowHeader(b'\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00') nf5.version == 5 and nf5[NetflowHeaderV5].count == 2 and isinstance(nf5[NetflowRecordV5].payload, NetflowRecordV5) +############ +############ ++ Netflow v9 + += NetflowHeaderV9 - advanced building + +import time + +pkt = NetflowHeader()/\ + NetflowHeaderV9(unixSecs=int(time.time()))/\ + NetflowFlowsetV9(templates=[ + NetflowTemplateV9(templateID=258, template_fields=[ + NetflowTemplateFieldV9(fieldType=1), + NetflowTemplateFieldV9(fieldType=62), + ]), + NetflowTemplateV9(templateID=257, template_fields=[ + NetflowTemplateFieldV9(fieldType=1), + NetflowTemplateFieldV9(fieldType=62), + ]), + ])/NetflowDataflowsetV9(templateID=258, records=[ + NetflowRecordV9(fieldValue=b"\x01\x02\x03\x05"), + NetflowRecordV9(fieldValue=b"\x05\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01"), + ])/NetflowDataflowsetV9(templateID=257, records=[ + NetflowRecordV9(fieldValue=b"\x01\x02\x03\x04"), + NetflowRecordV9(fieldValue=b"\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01"), + ])/NetflowOptionsFlowsetV9(templateID=256, scopes=[NetflowOptionsFlowsetScopeV9(scopeFieldType=1, scopeFieldlength=4), + NetflowOptionsFlowsetScopeV9(scopeFieldType=1, scopeFieldlength=3)], + options=[NetflowOptionsFlowsetOptionV9(optionFieldType=1, optionFieldlength=2), + NetflowOptionsFlowsetOptionV9(optionFieldType=1, optionFieldlength=1)])/\ + NetflowOptionsDataRecordV9(templateID=256, records=[NetflowOptionsRecordScopeV9(fieldValue=b"\x01\x02\x03\x04"), + NetflowOptionsRecordScopeV9(fieldValue=b"\x01\x02\x03"), + NetflowOptionsRecordOptionV9(fieldValue=b"\x01\x02"), + NetflowOptionsRecordOptionV9(fieldValue=b"\x01")]) + +assert pkt[NetflowFlowsetV9].templates[0].template_fields[0].fieldLength == 4 +assert pkt[NetflowFlowsetV9].templates[0].template_fields[1].fieldLength == 16 + += NetflowHeaderV9 - advanced dissection + +d = NetflowHeader(raw(pkt)) +d.show() +assert len(d[NetflowDataflowsetV9].records) == 2 +assert d.getlayer(NetflowDataflowsetV9, templateID=257).records[0].fieldValue == b"\x01\x02\x03\x04" +assert d.getlayer(NetflowDataflowsetV9, templateID=257).records[1].fieldValue == b"\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01" + +assert d.getlayer(NetflowDataflowsetV9, templateID=258).records[0].fieldValue == b"\x01\x02\x03\x05" +assert d.getlayer(NetflowDataflowsetV9, templateID=258).records[1].fieldValue == b"\x05\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01\x04\x03\x02\x01" + +assert d[NetflowOptionsFlowsetV9].scopes[0].scopeFieldType == 1 +assert d[NetflowOptionsDataRecordV9].records[1].fieldValue == b"\x01\x02\x03" +assert d[NetflowOptionsDataRecordV9].records[3].fieldValue == b"\x01" ############ ############