2019-10-17 05:23:51 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
"""
|
|
|
|
Netflow V5 collector and parser implementation in Python 3.
|
2020-03-30 12:29:50 +02:00
|
|
|
This file belongs to https://github.com/bitkeks/python-netflow-v9-softflowd.
|
2019-10-17 05:23:51 +02:00
|
|
|
Created purely for fun. Not battled tested nor will it be.
|
|
|
|
|
|
|
|
Reference: https://www.cisco.com/c/en/us/td/docs/net_mgmt/netflow_collection_engine/3-6/user/guide/format.html
|
2020-03-29 19:49:57 +02:00
|
|
|
This script is specifically implemented in combination with softflowd. See https://github.com/djmdjm/softflowd
|
2019-10-17 05:23:51 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
import struct
|
|
|
|
|
2020-03-29 22:14:45 +02:00
|
|
|
__all__ = ["V5DataFlow", "V5ExportPacket", "V5Header"]
|
|
|
|
|
2019-10-17 05:23:51 +02:00
|
|
|
|
2020-03-29 19:49:57 +02:00
|
|
|
class V5DataFlow:
|
|
|
|
"""Holds one v5 DataRecord
|
|
|
|
"""
|
2019-10-17 05:23:51 +02:00
|
|
|
length = 48
|
|
|
|
|
|
|
|
def __init__(self, data):
|
2020-03-30 12:29:50 +02:00
|
|
|
pack = struct.unpack("!IIIHHIIIIHHxBBBHHBBxx", data)
|
|
|
|
fields = [
|
|
|
|
'IPV4_SRC_ADDR',
|
|
|
|
'IPV4_DST_ADDR',
|
|
|
|
'NEXT_HOP',
|
|
|
|
'INPUT',
|
|
|
|
'OUTPUT',
|
|
|
|
'IN_PACKETS',
|
|
|
|
'IN_OCTETS',
|
|
|
|
'FIRST_SWITCHED',
|
|
|
|
'LAST_SWITCHED',
|
|
|
|
'SRC_PORT',
|
|
|
|
'DST_PORT',
|
|
|
|
# Byte 36 is used for padding
|
|
|
|
'TCP_FLAGS',
|
|
|
|
'PROTO',
|
|
|
|
'TOS',
|
|
|
|
'SRC_AS',
|
|
|
|
'DST_AS',
|
|
|
|
'SRC_MASK',
|
|
|
|
'DST_MASK',
|
|
|
|
# Word 46 is used for padding
|
|
|
|
]
|
|
|
|
|
2019-10-17 05:23:51 +02:00
|
|
|
self.data = {}
|
2020-03-30 12:29:50 +02:00
|
|
|
for idx, field in enumerate(fields):
|
|
|
|
self.data[field] = pack[idx]
|
|
|
|
self.__dict__.update(self.data) # Make data dict entries accessible as object attributes
|
2019-10-17 05:23:51 +02:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "<DataRecord with data {}>".format(self.data)
|
|
|
|
|
|
|
|
|
2020-03-29 19:49:57 +02:00
|
|
|
class V5Header:
|
|
|
|
"""The header of the V5ExportPacket
|
|
|
|
"""
|
2019-10-17 05:23:51 +02:00
|
|
|
length = 24
|
|
|
|
|
|
|
|
def __init__(self, data):
|
2020-03-30 12:29:50 +02:00
|
|
|
pack = struct.unpack('!HHIIIIBBH', data[:self.length])
|
|
|
|
self.version = pack[0]
|
|
|
|
self.count = pack[1]
|
|
|
|
self.uptime = pack[2]
|
|
|
|
self.timestamp = pack[3]
|
|
|
|
self.timestamp_nano = pack[4]
|
|
|
|
self.sequence = pack[5]
|
|
|
|
self.engine_type = pack[6]
|
|
|
|
self.engine_id = pack[7]
|
|
|
|
self.sampling_interval = pack[8]
|
2019-10-17 05:23:51 +02:00
|
|
|
|
2020-03-29 23:13:22 +02:00
|
|
|
def to_dict(self):
|
|
|
|
return self.__dict__
|
|
|
|
|
2019-10-17 05:23:51 +02:00
|
|
|
|
|
|
|
class V5ExportPacket:
|
2020-03-29 19:49:57 +02:00
|
|
|
"""The flow record holds the header and data flowsets.
|
|
|
|
"""
|
2020-04-24 16:34:37 +02:00
|
|
|
|
2019-10-17 05:23:51 +02:00
|
|
|
def __init__(self, data):
|
|
|
|
self.flows = []
|
2020-03-29 19:49:57 +02:00
|
|
|
self.header = V5Header(data)
|
2019-10-17 05:23:51 +02:00
|
|
|
|
|
|
|
offset = self.header.length
|
|
|
|
for flow_count in range(0, self.header.count):
|
2020-03-30 12:29:50 +02:00
|
|
|
end = offset + V5DataFlow.length
|
|
|
|
flow = V5DataFlow(data[offset:end])
|
2019-10-17 05:23:51 +02:00
|
|
|
self.flows.append(flow)
|
|
|
|
offset += flow.length
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "<ExportPacket v{} with {} records>".format(
|
2020-04-24 16:34:37 +02:00
|
|
|
self.header.version, self.header.count)
|