diff --git a/scapy/layers/netflow.py b/scapy/layers/netflow.py index be130617e..bbfa06594 100644 --- a/scapy/layers/netflow.py +++ b/scapy/layers/netflow.py @@ -7,36 +7,6 @@ """ 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")]) """ @@ -50,6 +20,7 @@ class NetflowHeader(Packet): name = "Netflow Header" fields_desc = [ ShortField("version", 1) ] +bind_layers( UDP, NetflowHeader, dport=2055 ) ########################################### ### Netflow Version 1 @@ -298,7 +269,7 @@ class NetflowTemplateFieldV9(Packet): ShortField("fieldLength", 0) ] def __init__(self, *args, **kwargs): Packet.__init__(self, *args, **kwargs) - if self.fieldType != None: + if self.fieldType != None and not self.fieldLength and self.fieldType in NetflowV9TemplateFieldDefaultLengths: self.fieldLength = NetflowV9TemplateFieldDefaultLengths[self.fieldType] def default_payload_class(self, p): @@ -321,6 +292,18 @@ class NetflowFlowsetV9(Packet): PacketListField("templates", [], NetflowTemplateV9, length_from = lambda pkt: pkt.length-4) ] +class _CustomStrFixedLenField(StrFixedLenField): + def i2repr(self, pkt, v): + return repr(v) + +def _GenNetflowRecordV9(cls, lengths_list): + _fields_desc = [] + for j,k in lengths_list: + _fields_desc.append(_CustomStrFixedLenField(NetflowV9TemplateFieldTypes[k], b"", length=j)) + class NetflowRecordV9I(cls): + fields_desc = _fields_desc + return NetflowRecordV9I + class NetflowRecordV9(Packet): name = "Netflow DataFlowset Record V9" fields_desc = [ StrField("fieldValue", "") ] @@ -341,45 +324,90 @@ class NetflowDataflowsetV9(Packet): if _pkt: if _pkt[:2] == b"\x00\x01": return NetflowOptionsFlowsetV9 + if _pkt[:2] == b"\x00\x00": + return NetflowFlowsetV9 return cls - def post_dissection(self, pkt): + def plist_post_dissection(self, _packet_list): + pkt = self + pkt.payload.plist_post_dissection(_packet_list) + ## STEP 1 - NetflowFlowsetV9 # We need the whole packet to be dissected to access field def in NetflowFlowsetV9 + packet_list = _packet_list.filter(lambda x: x.haslayer(NetflowFlowsetV9)) root = pkt.firstlayer() - current = root # Get all linked NetflowFlowsetV9 - while current.payload.haslayer(NetflowFlowsetV9): - current = current.payload[NetflowFlowsetV9] + for p in packet_list: + current = p[NetflowFlowsetV9] for ntv9 in current.templates: current_ftl = root.getlayer(NetflowDataflowsetV9, templateID=ntv9.templateID) if current_ftl: # Matched - if len(current_ftl.records) > 1: + if len(current_ftl.records) < 0 or (not hasattr(current_ftl.records[0], "fieldValue")): # 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 + lengths_list = [] 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)) + lengths_list.append((template.fieldLength, template.fieldType)) + if lengths_list: + tot_len = sum(x for x,y in lengths_list) + cls = _GenNetflowRecordV9(NetflowRecordV9, lengths_list) + while len(data) >= tot_len: + res.append(cls(data[:tot_len])) + data = data[tot_len:] # Inject dissected data current_ftl.records = res + current_ftl.do_dissect_payload(data) + break 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 + ## STEP 2 - NetflowOptionsFlowsetV9 + # We need the whole packet to be dissected to access field def in NetflowOptionsFlowsetV9 + packet_list = _packet_list.filter(lambda x: x.haslayer(NetflowOptionsFlowsetV9)) + # Get all linked NetflowOptionsFlowsetV9 + for p in packet_list: + current = p[NetflowOptionsFlowsetV9] + current_ftl = root.getlayer(NetflowDataflowsetV9, templateID=current.templateID) + if current_ftl: + # Matched + if len(current_ftl.records) < 0 or (not hasattr(current_ftl.records[0], "fieldValue")): + # 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 NetflowOptionsFlowsetV9 data, re-dissect NetflowDataflowsetV9 + ## A - Decode scopes + lengths_list = [] + for scope in current.scopes: + lengths_list.append((scope.scopeFieldlength, scope.scopeFieldType)) + if lengths_list: + tot_len = sum(x for x,y in lengths_list) + cls = _GenNetflowRecordV9(NetflowOptionsRecordScopeV9, lengths_list) + while len(data) >= tot_len: + res.append(cls(data[:tot_len])) + data = data[tot_len:] + ## B - Decode options + lengths_list = [] + for option in current.options: + lengths_list.append((option.optionFieldlength, option.optionFieldType)) + if lengths_list: + tot_len = sum(x for x,y in lengths_list) + cls = _GenNetflowRecordV9(NetflowOptionsRecordOptionV9, lengths_list) + while len(data) >= tot_len: + res.append(cls(data[:tot_len])) + data = data[tot_len:] + if data: + res.append(Raw(data)) + # Inject dissected data + current_ftl.records = res + current_ftl.name = "Netflow DataFlowSet V9 - OPTIONS" + break + else: + warning("[NetflowFlowsetV9 templateID=%s]: No matching NetflowDataflowsetV9 !" % ntv9.templateID) class NetflowOptionsRecordScopeV9(NetflowRecordV9): name = "Netflow Options Template Record V9 - Scope" @@ -395,6 +423,14 @@ class NetflowOptionsFlowsetOptionV9(Packet): def default_payload_class(self, p): return conf.padding_layer +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 NetflowOptionsFlowsetV9(Packet): name = "Netflow Options Template FlowSet V9" fields_desc = [ ShortField("flowSetID", 1), @@ -408,41 +444,6 @@ class NetflowOptionsFlowsetV9(Packet): 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( NetflowHeaderV9, NetflowDataflowsetV9 ) bind_layers( NetflowDataflowsetV9, NetflowDataflowsetV9 ) - -bind_layers( NetflowOptionsFlowsetV9, NetflowOptionsDataRecordV9 ) diff --git a/scapy/packet.py b/scapy/packet.py index 3486d6d69..6492f362d 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -164,6 +164,11 @@ class Packet(six.with_metaclass(Packet_metaclass, BasePacket)): """DEV: is called after the dissection of the whole packet""" pass + def plist_post_dissection(self, packet_list): + """DEV: is called after the dissection of the packet list containing the packet""" + self.payload.plist_post_dissection(packet_list) + pass + def get_field(self, fld): """DEV: returns the field instance from the name of the field""" return self.fieldtype[fld] @@ -1256,6 +1261,8 @@ class NoPayload(Packet): pass def dissection_done(self,pkt): return + def plist_post_dissection(self,list): + pass def add_payload(self, payload): raise Scapy_Exception("Can't add payload to NoPayload instance") def remove_payload(self): diff --git a/scapy/plist.py b/scapy/plist.py index 61c2d5167..cd28da152 100644 --- a/scapy/plist.py +++ b/scapy/plist.py @@ -29,7 +29,7 @@ from scapy.modules.six.moves import range, zip class PacketList(BasePacketList): __slots__ = ["stats", "res", "listname"] - def __init__(self, res=None, name="PacketList", stats=None): + def __init__(self, res=None, name="PacketList", stats=None, plist_post_dissect=True): """create a packet list from a list of packets res: the list of packets stats: a list of classes that will appear in the stats (defaults to [TCP,UDP,ICMP])""" @@ -42,6 +42,11 @@ class PacketList(BasePacketList): res = res.res self.res = res self.listname = name + if plist_post_dissect: + self._call_plist_post_dissection() + def _call_plist_post_dissection(self): + for r in self.res: + (r[1] if isinstance(r, tuple) else r).plist_post_dissection(self) def __len__(self): return len(self.res) def _elt2pkt(self, elt): @@ -127,7 +132,8 @@ lfilter: truth function to apply to each packet to decide whether it will be dis def filter(self, func): """Returns a packet list filtered by a truth function""" return self.__class__([x for x in self.res if func(x)], - name="filtered %s"%self.listname) + name="filtered %s"%self.listname, + plist_post_dissect=False) def make_table(self, *args, **kargs): """Prints a table using a function that returns for each packet its head column value, head row value and displayed value ex: p.make_table(lambda x:(x[IP].dst, x[TCP].dport, x[TCP].sprintf("%flags%")) """ @@ -540,8 +546,8 @@ lfilter: truth function to apply to each packet to decide whether it will be dis class SndRcvList(PacketList): __slots__ = [] - def __init__(self, res=None, name="Results", stats=None): - PacketList.__init__(self, res, name, stats) + def __init__(self, res=None, name="Results", stats=None, plist_post_dissect=True): + PacketList.__init__(self, res, name, stats, plist_post_dissect) def _elt2pkt(self, elt): return elt[1] def _elt2sum(self, elt): diff --git a/test/pcaps/netflowv9.pcap b/test/pcaps/netflowv9.pcap new file mode 100755 index 000000000..c9c961af8 Binary files /dev/null and b/test/pcaps/netflowv9.pcap differ diff --git a/test/regression.uts b/test/regression.uts index 02a6482ee..c400bd5eb 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -5478,53 +5478,30 @@ nf5.version == 5 and nf5[NetflowHeaderV5].count == 2 and isinstance(nf5[NetflowR ############ + Netflow v9 -= NetflowHeaderV9 - advanced building += NetflowV9 - advanced dissection -import time +a = rdpcap("./test/pcaps/netflowv9.pcap" if WINDOWS else "./pcaps/netflowv9.pcap") -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")]) +nfv9_fl = a[4] +assert NetflowFlowsetV9 in nfv9_fl +assert len(nfv9_fl.templates[0].template_fields) == 21 -assert pkt[NetflowFlowsetV9].templates[0].template_fields[0].fieldLength == 4 -assert pkt[NetflowFlowsetV9].templates[0].template_fields[1].fieldLength == 16 +nfv9_ds = a[61] +assert NetflowDataflowsetV9 in nfv9_ds +assert len(nfv9_ds[NetflowDataflowsetV9].records) == 24 +assert nfv9_ds[NetflowDataflowsetV9].records[21].IP_PROTOCOL_VERSION == b'\x04' +assert inet_ntoa(nfv9_ds.records[21].IPV4_SRC_ADDR) == '20.0.0.248' +assert inet_ntoa(nfv9_ds.records[21].IPV4_DST_ADDR) == '30.0.0.248' -= NetflowHeaderV9 - advanced dissection +nfv9_options_fl = a[5] +assert NetflowOptionsFlowsetV9 in nfv9_options_fl +assert isinstance(nfv9_options_fl[NetflowOptionsFlowsetV9].scopes[0], NetflowOptionsFlowsetScopeV9) +assert isinstance(nfv9_options_fl[NetflowOptionsFlowsetV9].options[0], NetflowOptionsFlowsetOptionV9) +assert nfv9_options_fl[NetflowOptionsFlowsetV9].options[0].optionFieldType == 36 -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" +nfv9_options_ds = a[255] +assert NetflowDataflowsetV9 in nfv9_options_ds +assert isinstance(nfv9_options_ds.records[0], NetflowOptionsRecordScopeV9) ############ ############