Python基于pypcap、dpkt的抓包、量化工具及抓包长度与wireshark不匹配的问题

抓包、量化功能实现

在服务器上通过交换机流量镜像导出来数据到服务器,然后用服务器网卡进行抓包。同时将报文存到数据库中。
初期版本进行逐包抓取,后来发现存到数据库的报文信息与存取的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)](

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值