python scapy填充公共pcap数据集负载实现流量重放

前言

 

在计算机网络领域的实验中,为了测试所提出的网络模型的性能,需要在模拟的环境中(如NS-2,SDN-Mininet)构建网络的背景流量。

然而,如果自己编写脚本发包(套接字,多线程等)或者使用发包工具(iperf、D-ITG等)作为背景流量生成器会存在两个缺陷,一是这些脚本发包速率特别稳定,不容易有波动,仿真的效果不够理想(比如,攻击的检测率太高);二是自己编写的脚本往往参数过于理想化,而发包工具通常也依赖于特定的发包参数的输入(每秒包数、字节数、带宽等),容易被审稿人怼不符合实际的网络情况。

因此很多论文都用到了pcap格式的公共数据集作为背景流量,比如CAIDA、WIDE、UNB系列(具体能用的可以看:https://blog.csdn.net/answer3lin/article/details/86480662

然而,下载下来这些数据集,用tcpreplay重放会出现问题,当修改数据包的源和目的IP地址的时候会报错,提示包过短:

Fatal Error: Error rewriting packets: From tcpedit.c:tcpedit_packet() line 172:
Packet length 60 is to short to contain a layer 52 byte IP header for DLT 0x0001

如下图:

出现这种现象的原因是公共数据集的隐私性,出于保护用户隐私,这些机构在创建数据集时仅保留了头部信息(否则,一些不加密的协议,比如HTTP,数据就会泄露)

看一下数据包,发现pcap数据集里面没有负载,如下图:(数据来自IMC-10,univ1_pt1.pcap)

本文来解决上述问题,为匿名数据集的pcap中填入负载,让这些pcap能够被tcpreplay修改并重放。

 

数据集推荐

 

WIDE系列:http://mawi.wide.ad.jp/mawi/

IMC系列:http://pages.cs.wisc.edu/~tbenson/IMC10_Data.html

本文使用WIDE数据集作为示例,具体是2020年10月12日下午2点的数据:http://mawi.wide.ad.jp/mawi/samplepoint-F/2020/202010121400.html

 

数据集预处理

 

这个数据集非常大,大概解压了接近7G,所以我对数据集进行了分片,每2000个包一片。实验时只用了第一个2000包的片。

具体操作采用Wirshark自带的editcap.exe -c实现。

pcap数据集拆分参考百度经验:https://jingyan.baidu.com/article/63f2362854be2e0208ab3d20.html

另外,我忽略了所有的IPv6包,过滤了其他的协议,只保留IP层的协议,即TCP/UDP/ICMP,所用的过滤器是:

tcp || udp || icmp

并且输入过滤器:

ipv6

Ctrl-A选中全部,Ctrl-D忽略。

最终得到的这个2000包的pcap文件我上传了网盘。

链接:https://pan.baidu.com/s/1GmrZg35ogbJHPp0vkx5bOA

提取码:r12i

 

分析

1、分层信息获取

编写python代码,查看数据集的第一个包:

from scapy.all import *
from scapy.utils import PcapReader
import scapy.all as scapy

pkt=rdpcap("test_spilt_00000_20201012130000.pcap")

pkt[0].show()

我们得到:

 

对比Wireshark的数据,我们可以发现,Wireshark显示的是1444字节,而IP层显示的是1430字节,多出了14字节:

我们得到结论,这多出来的14字节来自于IP层以下的Ethernet层,其中包含源和目的MAC地址,以及协议类型,也就是14字节。

所以,Wireshark里面的len是数据包的真实大小,而scapy show里面的len,则是数据包在IP层(含)以上的所有负载,不包括IP层以下的层次。

其它层的信息也可以通过show()函数进行打印,如果直接print会出现乱码。

 

2、payload,payload.name的使用,确定填充的数据大小

 

2-1:计算L2层大小:证明

如上所述,数据包的len字段,仅表示数据包的IP层以及IP层以上层数的总大小,并不包含L2层的头部大小信息,且scapy并不能从Wireshark里面直接获取Length,因此我们需要编程计算L2层的大小(即MAC那一层)。

现在我们证明,为什么需要计算L2层大小:

1、对于一个未知总长度且可能不完整的数据包,假设它是pkt,我们可以调用pkt.len,获取该数据包在L3(IP层)及以上的总大小,其中就包括负载。

2、同时我们可以调用len(pkt),得到当前不完整的数据包的大小。

3、那么我们计算len(pkt)-pkt.len,即用当前未填充的数据包大小,减去L3及以上应当有(但是实际上很可能没有这个长度,因为数据包未填充data)的长度,就得到了一个差值,记为cur_L2。

4、如果cur_L2等于我们计算出的数据包实际L2大小,那么说明这个数据包是完整的,无需填充任何负载数据,该数据包无需任何操作。(对于无需携带负载的ICMP数据,这一点必须先判断)

5、如果cur_L2不等于(小于)我们计算出的数据包实际L2大小,那么说明,该数据包除了L2、L3的基本信息以外,还应当包含额外的数据。原本携带的数据越大,L3以上的长度,pkt.len也越大,计算得到的cur_L2的数值越小,可能是负数。

6、最后判断:如果cur_L2不等于实际计算得到的L2,那么就需要填充数据。

其实,不同的数据集L2的大小也不固定,比如WIDE里面Ethernet层就是常规的14字节,而IMC-10数据集中L2包含了802.1Q,它的Ethernet是18字节。

因此,实际计算时,L2的大小不能被固定声明,应当依据2-2节的方法进行计算。

 

2-2:计算L2与L3层大小:实现(payload)

使用数据集,执行代码:

from scapy.all import *
from scapy.utils import PcapReader
import scapy.all as scapy

pkt=rdpcap("test_spilt_00000_20201012130000.pcap")

print(pkt[0].name)
print(len(pkt[0]))

print(pkt[0].payload.name)
print(len(pkt[0].payload))

print(pkt[0].payload.payload.name)
print(len(pkt[0].payload.payload))

得到输出:

Ethernet
54
IP
40
TCP
20

我们发现,payload函数就是现实数据包的当前层次信息,且多次调用payload时,会显示更高一层的信息。

使用payload.name打印当前层的名称。

(参考:https://blog.csdn.net/meanong/article/details/53942671

 

因此,我们代码的实现思路是:

1、首先记录数据包当前的总大小,调用len()函数即可;

2、从整个数据包开始,不断调用其payload.name,判断如果payload.name不是‘IP’,那么循环调用其payload的name。

(这么做是因为,L2的大小不定,不一定仅包含Ethernet,还可能有802.1之类的玩意,到时候填充内容不准,会出现safe_pcap_next ERROR: Invalid packet length in tcprewrite.c:rewrite_packets() line 260: packet length=1444 capture length=1445之类的错误,这个错误就是由于填充后,数据包的实际长度和原始(预期的)长度不符导致的)

3、如果某次循环中,payload的name是'IP',那么此时调用len()函数,获取该数据包当前IP层及之上的大小;

4、用第1步的结果减去第3步的结果,就是整个L2的大小,其中第3步获取的是数据包当前的L3及之上的大小。

 

代码的函数实现是:

def get_l2_l3_length(pkt):
    pkt_layer = pkt
    total_length = len(pkt)
    while pkt_layer.name != 'IP':
        pkt_layer = pkt_layer.payload
    l3_length = len(pkt_layer)
    l2_length = total_length - l3_length
    return l2_length, l3_length

 

2-3:计算所需填充的数据大小

在2-2节的代码第3步,我们获得了不完整的数据包L3及以上的当前长度,记为l3_length

那么我们现在可以直接调用数据包的len字段,它代表数据包L3及以上层次应有的总长度,记为len

此时,计算:len-l3_length,就是所需填充的数据量大小。

返回一个这么大的字符串即可,使用scapy中的分层:“/”,就可以增加负载了。

 

代码实现

from scapy.all import *
from scapy.utils import PcapReader
import scapy.all as scapy

def generate_payload(length):
    payload = length * '0'
    return payload

def get_l2_l3_length(pkt):
    pkt_layer = pkt
    total_length = len(pkt)
    while pkt_layer.name != 'IP':
        pkt_layer = pkt_layer.payload
    l3_length = len(pkt_layer)
    l2_length = total_length - l3_length
    return l2_length, l3_length
   
pkt=rdpcap("test_spilt_00000_20201012130000.pcap")

pkt_list = []
for i in range (0, len(pkt)):

    tmp_pkt = pkt[i]
    l2_length, l3_length = get_l2_l3_length(tmp_pkt)
    
    if tmp_pkt.proto != 1:
        if (len(tmp_pkt) - tmp_pkt.len) != l2_length:
            tmp_pkt = tmp_pkt / generate_payload(tmp_pkt.len-l3_length)
                
    pkt_list.append(tmp_pkt)

scapy.wrpcap('ip_load.pcap', pkt_list)

 

后续步骤

 

此时,匿名的pcap数据集已经填充完毕,经过tcpreplay的修改IP和MAC地址,计算校验和就可以重放,构建网络测试的背景流量了。

具体步骤请阅读我的另一篇博客:https://blog.csdn.net/yyd19981117/article/details/114301857?spm=1001.2014.3001.5501

 

 

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值