抓包、量化功能实现
在服务器上通过交换机流量镜像导出来数据到服务器,然后用服务器网卡进行抓包。同时将报文存到数据库中。
初期版本进行逐包抓取,后来发现存到数据库的报文信息与存取的pcap文件不匹配,后经分析,应该是数据量过大,逐包信息提交到数据库,无法全量存取(10G网口,如果按照64B的报文长度,每秒约有2000w条数据,即使1514的报文长度,也有每秒近200w条数据),即使采用多进程、分配存储,也无法实现全量存储。
考虑到实际需求,决定退而求其次,在抓包器上同时做量化,即按照给定的时间尺度,统计单位时间长度内的报文数量、报文长度和等,只存储量化后的数据即可。
适用场景:可以基于量化后的数据对业务流进行分析,可以牺牲逐包的细节信息。
以下是部分代码:
import time
from multiprocessing import Process, Queue
import logging
from src.persistence import mysql_opeation
from src.traffic_capture.traffic_trace_entity import TrafficTraceEntity, TrafficTraceQuantization
import src.persistence.mysql_opeation as mysql_helper
# 日志格式
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s ')
config = {
"net_card_name": "ens1f1",
"default_capture_time": 60, # 默认采集时间
"commit_pkt_num_once": 100000, # 一次提交数据库的数据包数量
"quantized_granularity": 5 * 1000, # 50 ms
# "quantization_type": 1, # 1代表byte
"quantization_time_unit": 'microsecond' # 1 表示微秒
}
# lock = threading.Lock()
# pkt_info = []
has_data = False # 数据库中已经存入数据的标志
def start_capture(duration, q: Queue):
"""
开始采集
:param duration: 欲采集的时间,默认1min
:param q: 信息传递
:return:
"""
global has_data
endFlag = False
has_data = False
capture_time = config['default_capture_time']
if duration is not None and duration > 0:
capture_time = duration
pc = pcap.pcap(config['net_card_name'], promisc=True, immediate=True, timeout_ms=50)
pcap_filepath = '/home/tsn/code/traffic_analyser_new/pkts_{}.pcap'.format(time.strftime("%Y%m%d-%H%M%S",
time.localtime()))
pcap_file = open(pcap_filepath, 'wb')
writer = dpkt.pcap.Writer(pcap_file)
# 起一个线程,用来数据持久化
start_time = time.time()
# tmp_thread = threading.Thread(target=commit_data)
# tmp_thread.start()
pkt_counter = 0
# 开始采集
for ptime, pdata in pc:
writer.writepkt(pdata, ptime)
pkt_counter += 1
# logging.info('cap: %d' %pkt_counter)
# ethernet_data = dpkt.ethernet.Ethernet(pdata)
if time.time() - start_time >= capture_time:
# 到达指定的采集时间后退出
endFlag = True
q.put({'ethernet_data': pdata,
'ptime': ptime,
'end_flag': endFlag})
if endFlag:
break
# tmp_thread.join()
return pkt_counter
def commit_data_and_quantized(q: Queue, result_Queue: Queue):
"""
数据处理及存储,但不进行量化,适用于低速端口
:param q:
:param result_Queue: 储存结果
:return:
"""
logging.info("ready to commit data...")
global has_data
end_flag = False
classed_traffic_ctr = {} # 按照IP对记录所有报文数
traffic_time_rec = {} # 按照IP对记录量化时,该量化区间的第一个报文的时戳
quantized_num = {} # 按照IP对记录量化区间的值
quantized_pkt_num = {} # 按照IP对记录量化区间的报文数的值
tmp_sum = {}
while not end_flag:
if not q.empty():
has_data = True
val = q.get(True)
origin_data = val['ethernet_data']
# frame_len = len(origin_data)
ethernet_data = dpkt.ethernet.Ethernet(origin_data)
ptime = val['ptime'] * 1000000
end_flag = val['end_flag']
if not isinstance(ethernet_data.data, dpkt.ip.IP):
# 非IP报文不采集
continue
# print(ptime, "%s -> %s" % (ip_addr(ethernet_data.data.src), ip_addr(ethernet_data.data.dst)))
if (ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst))) not in classed_traffic_ctr:
# 第一个报文
classed_traffic_ctr[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] = 1
traffic_time_rec[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] = ptime
quantized_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] = len(
ethernet_data)
quantized_pkt_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] = 1
tmp_sum[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] = 0
else:
classed_traffic_ctr[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] += 1
interval_start = traffic_time_rec[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))]
# 量化判断
if ptime - interval_start + 1 > config['quantized_granularity'] or end_flag:
""" 量化 [interval_start, boundary), boundary节点计入下一个区间。例如,区间长度设置为50,那么[0, 49), [50, 99)..
因此 判断条件+1"""
if end_flag:
# 最后一个包
quantized_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] += len(
ethernet_data)
quantized_pkt_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] += 1
quantized_trace = TrafficTraceQuantization(start_time=interval_start,
quantization_num=quantized_num[
(ip_addr(ethernet_data.data.src),
(ip_addr(ethernet_data.data.dst)))],
quantized_pkt_num=quantized_pkt_num[
(ip_addr(ethernet_data.data.src),
(ip_addr(ethernet_data.data.dst)))],
interval_len=min(ptime - interval_start,
config['quantized_granularity']),
time_unit=config['quantization_time_unit'],
src_IP=ip_addr(ethernet_data.data.src),
dst_IP=ip_addr(ethernet_data.data.dst),
src_Mac=mac_addr(ethernet_data.src),
dst_Mac=mac_addr(ethernet_data.dst),
src_port=0,
dst_port=0) # ToDo 量化时按照IP地址将不同的端口做了聚合
tmp_sum[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] += quantized_pkt_num[
(ip_addr(ethernet_data.data.src),
(ip_addr(ethernet_data.data.dst)))]
mysql_helper.add_quantized_data([quantized_trace])
# ptime - 1 是将量化区间定位给到该报文对应的ptime之前,否则会引起边界报文的重合
# ToDo 提交数据 单独进程
traffic_time_rec[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] = ptime
quantized_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] \
= len(ethernet_data)
quantized_pkt_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] = 1
else:
quantized_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] += len(
ethernet_data)
quantized_pkt_num[(ip_addr(ethernet_data.data.src), (ip_addr(ethernet_data.data.dst)))] += 1
logging.info('Exit thread of storing quantized data')
logging.info(classed_traffic_ctr)
result_Queue.put(sum([classed_traffic_ctr[i] for i in classed_traffic_ctr]))
def get_has_data_status():
global has_data
return has_data
def mac_addr(mac):
return '%02x:%02x:%02x:%02x:%02x:%02x' % tuple(mac)
def ip_addr(ip):
return '%d.%d.%d.%d' % tuple(ip)
def main_process(duration, reset_flag):
"""
开始采集
:param duration: 欲采集的时间,默认1min
:param reset_flag: 是否需要清空数据库
:return:
"""
# 主进程
global has_data
logging.info("Capture time is set to %d s" % duration)
logging.info("Start to capture...")
if reset_flag:
logging.info("Clarify database")
mysql_helper.createTrafficTraceTable()
mysql_helper.createTrafficTraceQuantizationTable()
q = Queue()
result_Queue = Queue()
p_com = Process(target=commit_data_and_quantized, args=(q, result_Queue))
p_cap = Process(target=start_capture, args=(duration, q))
p_com.start()
p_cap.start()
p_cap.join()
p_com.join()
logging.info("finish capture")
return result_Queue.get()
量化后的数据实体
class TrafficTraceQuantization:
start_time: int
'''量化间隔长度'''
interval_len: int
'''量化间隔内的数值'''
quantization_num: int
'''量化间隔内的包数量'''
quantized_pkt_num: int
'''时间单位'''
time_unit: str
src_IP: str
src_Mac: str
src_port: int
dst_IP: str
dst_Mac: str
dst_port: int
def __init__(self, start_time, interval_len, quantization_num, quantized_pkt_num, time_unit,
src_IP, src_Mac, src_port, dst_IP, dst_Mac, dst_port):
self.start_time = start_time
self.interval_len = interval_len
self.quantization_num = quantization_num
self.quantized_pkt_num = quantized_pkt_num
self.time_unit = time_unit
self.src_IP = src_IP
self.src_Mac = src_Mac
self.src_port = src_port
self.dst_IP = dst_IP
self.dst_Mac = dst_Mac
self.dst_port = dst_port
数据库的操作:
def dropTable(name):
"""
删除表
:param name: 新建表名
:return:
"""
stmt = "DROP TABLE IF EXISTS " + name
mycursor.execute(stmt)
def createTrafficTraceQuantizationTable():
dropTable(quantized_traffic_trace_table_name)
mycursor.execute("CREATE TABLE quantized_traffic_trace(id INT AUTO_INCREMENT PRIMARY KEY, start_time BIGINT, "
"interval_len INT, quantization_num INT, quantized_pkt_num INT, time_unit VARCHAR(48), "
"src_IP VARCHAR(48), src_Mac VARCHAR(48), src_port INT, dst_IP VARCHAR(48), "
"dst_Mac VARCHAR(48), dst_port INT)")
def add_quantized_data(quantized_info: List[TrafficTraceQuantization]):
# print(pkt_info)
# 分批储存
batch_size = 10000
repeat = int(math.ceil(len(quantized_info) / batch_size))
for i in range(repeat):
tmp_pkt_info = quantized_info[i * batch_size: (i + 1) * batch_size]
values = []
for quan in tmp_pkt_info:
values.append((quan.start_time, quan.interval_len, quan.quantization_num, quan.quantized_pkt_num,
quan.time_unit, quan.src_IP, quan.src_Mac, quan.src_port, quan.dst_IP, quan.dst_Mac,
quan.dst_port))
mycursor.executemany("INSERT INTO " + quantized_traffic_trace_table_name +
"(start_time, interval_len, quantization_num, quantized_pkt_num, time_unit, src_IP, "
"src_Mac, src_port, dst_IP, dst_Mac, dst_port) "
"VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", values)
con.commit()
return len(quantized_info)
### python dpkt解包和wireshark解包以太帧长度不一致的问题
后来发现,虽然pcap文件和数据库统计出来的报文数量一致,但是报文长度(字节数)依然不一致,经过深入分析,发现dpkt解析时,当报文payload为0时,其长度为54,而wireshark中,为60,具体的:
dpkt中,如下图
不对数据帧做补齐,因此以太帧头(6B目的地址+6B源地址+2B类型)14B+IP报文长40字节(payload长度为0,IP固定头部长度20B)=54B
而在wireshark中:
会自动进行补齐
可以参考:
抓包分析以太网帧和IP数据包,头部那么多东东用来干啥的,扫盲篇_wx5ea58764ce673的技术博客_51CTO博客
[Wireshark TS | Padding 和 Trailer 有啥关系 - 知乎 (zhihu.com)](