Fix netflow v9

This commit is contained in:
gpotter2 2017-10-25 22:00:37 +02:00
parent 6c8e2bb5b8
commit 22da5cf12f
5 changed files with 123 additions and 132 deletions

View File

@ -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 )

View File

@ -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):

View File

@ -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):

BIN
test/pcaps/netflowv9.pcap Executable file

Binary file not shown.

View File

@ -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)
############
############