使用python对自定义数据报文TAG标签的剥离,便于后期分析,快速排查问题


背景

在一些特殊的地方网络设备会在特定数据报文头部打上私有标签,且标签不定长,当遇到问题需要抓取数据报文通过wireshark解析时,无法解析出上层协议造成排查困难。

图-1

准备通过先对数据包进行处理后,删除头部标签后再进行分析。主要使用了python 的dpkt库。

准备工作

安装dpkt包

使用pip安装

pip install dpkt

标签格式

该标签位于数据链路层之后,标志字段为0xFFFE,紧接着的一个字节指示了标签长度

图-2

如图所示0x39 就表示从该标签为57字节。之后则为正常网络层数据包

使用dpkt读取数据包

读取链路层信息

dpkt的使用十分简单,打开文件使用dpkt内置的pcap包Reader函数,和Ethernet函数解析就可以读取书数据包的基本信息。

import dpkt
with open('t.pcap','rb') as f:
  pcap = dpkt.pcap.Reader(f)
  for ts, buf in pcap:
    eth = dpkt.ethernet.Ethernet(buf)
    print(repr(eth))

以下是打印的内容,可以观察到如果是dpkt可以识别的协议会解析出最高层,而特殊标签无法识别则只显示出为eth的data数据部分。

Ethernet(dst=b'\x00\x00\x01\x00\x01A', src=b'\xdc\x99\x14\x94I0', type=65534, data=b"90\x01\x08d\x00a\t\x08A%\xf2\x02\x08h1\x80\x96\x00#\xf2\xff\r\x01\x06\x03\x08h\x93H@\x01Q\x13\xf5\x0b\x07d\xf0\x10\x00\xe0N\xeb\n\x05d\xf0\x00\x85\x04\x06\x053gnet\x08\x00Eh\x00L\x1a\xad\x00\x00\xfa\x11\x07Q\neiE\nd -\x08h\x08h\x008\x00\x000\xff\x00(*i\x05\x96E\x00\x00(\x8bL@\x00@\x06\x1c\xc1\n'\xeb\xeb\x0e\xcc\x8d\xe4\xc9\x18\x01\xbb\xd3!\xd3.b\xb4\x84\x01P\x10\x01m\xc3\xca\x00\x00")
Ethernet(dst=b'\x00\x0b\x00e\x02\\', src=b'\x00\x00\x00\x03\x05\x02', data=IP(len=78, id=49313, ttl=62, p=17, sum=2693, src=b'tO\xe3\xe9', dst=b'tO\xe4\xf0', opts=b'', data=UDP(sport=30057, dport=2123, ulen=58, sum=60037, data=b'H"\x00.w\xa0{J\x1c\xb5y\x00R\x00\x01\x00\x06M\x00\x02\x00\x00\x10\\\x00\x01\x00\x00]\x00\x12\x00I\x00\x01\x00\x05W\x00\t\x00\x80\xa9.\x91\xaf\ne\xa7\x05')))

读取next_type

dpkt.ethernet.Ethernet类可以返回这几个变量

​ dst :目的mac地址

​ src: 源mac地址

​ type: 后面的协议类型

​ data: 数据载荷

import dpkt
with open('t.pcap','rb') as f:
    pcap = dpkt.pcap.Reader(f)
    for ts, buf in pcap:
        eth = dpkt.ethernet.Ethernet(buf)
        print(eth.type)

使用eth.type即可获取next_type协议类型值

删除标签字段

删除标签字段的思路为先找到标签标志位,识别标签长度,通过偏移直接读取data数据字段,再将以太网的头部字段与以太网数据字段拼接。

读取以太网头部信息

需要通过dpkt.ethernet.Ethernet类的一个私有变量__hdr_len__来读取以太网头部长度,然后通过偏移字段找到以太网头部,因为__hdr_len__的描述的长度包含了next_type的两个字节所以需要将该值减2才是正真的以太网的头部信息。

import dpkt
with open('t.pcap','rb') as f:
    pcap = dpkt.pcap.Reader(f)
    for ts, buf in pcap:
        eth = dpkt.ethernet.Ethernet(buf)
        print(eth.__hdr_len__)
        eth.len = eth.__hdr_len__ - 2
        print(buf[:eth_len].hex())

读取标签字段长度

通过读取dpkt.ethernet.Ethernet类的data变量的第一个字节即可以得到标签长度

import dpkt
with open('t.pcap','rb') as f:
    pcap = dpkt.pcap.Reader(f)
    for ts, buf in pcap:
        eth = dpkt.ethernet.Ethernet(buf)
        tag_len = int(tlv_buf[:1].hex(),16)

拼接以太网头部信息与去标签数据

只需要通过对eth.data 偏移标签长度加1即可读取到去tag的原始数据。再将以太网头部与标签的数据部分重新拼接即可组成原始真实网络数据。因为并不是每个数据包都是打上了标签信息,所以需要对next_type类型进行判断,只有为tag的字段特征的才进行相关操作。

import dpkt
with open('t.pcap','rb') as f:
    pcap = dpkt.pcap.Reader(f)
    for ts, buf in pcap:
        eth = dpkt.ethernet.Ethernet(buf)
        if eth.type == 0xfffe:
            eth.len = eth.__hdr_len__ - 2
            tag_len = int(eth.data[:1].hex(),16) + 1  
            eth_data = buf[:eth_len] + eth.data[tag_len:]

保存数据包

创建pcap文件

dpkt库中有Write类,可以创建单个pcap数据包

def creat_pcap(pkt, snaplen, ts):
    with open('t_u.tag', 'wb') as wf:
        test_write = dpkt.pcap.Writer(wf, snaplen,linktype)
        test_write.writepkt(pkt, ts)

追加写入pcap文件

因为使用默认dpkt.pcap.Writer时类在实例化就会写入pcap文件头部信息,如果持续一边读取,一边写入需要判断是否为第一的报文,如果第一个报文就创建文件,如果非第一个就是追加写入文件不需要写入pcap文件头,只需要追加packet数据包就行了。

def write_pcap(pkt, ts):
    with open('t_u.pcap', 'ab') as wf:
        ph, s = writepkt(pkt, ts)
        wf.write(bytes(ph))
        wf.write(s)

基本代码示例

import sys
import time

import dpkt
from dpkt.compat import compat_ord
from dpkt.pcap import LEPktHdr, PktHdr

ETH_TYPE_TAG = 0xfffe


def writepkt(pkt, ts=None, nano=False):
    precision = 9 if nano else 6
    if ts is None:
        ts = time.time()
    s = bytes(pkt)
    n = len(s)
    sec = int(ts)
    usec = int(round(ts % 1 * 10 ** precision))
    if sys.byteorder == 'little':
        ph = LEPktHdr(tv_sec=sec,
                      tv_usec=usec,
                      caplen=n, len=n)
    else:
        ph = PktHdr(tv_sec=sec,
                    tv_usec=usec,
                    caplen=n, len=n)
    return ph, s


def creat_pcap(pkt, snaplen, ts):
    with open('t_u.tag', 'wb') as wf:
        test_write = dpkt.pcap.Writer(wf, snaplen)
        test_write.writepkt(pkt, ts)

def write_pcap(pkt, ts):
    with open('t_u.pcap', 'ab') as wf:
        ph, s = writepkt(pkt, ts)
        wf.write(bytes(ph))
        wf.write(s)

def save_pcap(pkt, snaplen, ts, i):
    if i == 0:
        creat_pcap(pkt, snaplen, ts)
    else:
        write_pcap(pkt, ts)
        
with open('t.pcap','rb') as f
    i = 0 
    pcap = dpkt.pcap.Reader(f)
    snaplen = pcap.snaplen
    for ts, buf in pcap:
        eth = dpkt.ethernet.Ethernet(buf)
        if eth.type == ETH_TYPE_TAG:
            eth.len = eth.__hdr_len__ - 2
            tag_len = int(eth.data[:1].hex(),16) + 1  
            eth_data = buf[:eth_len] + eth.data[tag_len:]
            save_pcap(eth_data, snaplen, ts, i)
        else:
            save_pcap(buf, snaplen, ts, i)
        i += 1