scapy python_Python中使用Scapy小记

本帖最后由 灵·冥 于 2013-1-16 19:15 编辑

原文转自我自己的个人博客:

http://www.xsecure.cn/1/post/2012/12/python_scapy.html

如需转载,请注明出处

最近由于工作需要,要写一个脚本批量分析大量的pcap文件(使用tcpdump监控网络数据后存储的日志文件)。脚本这种东西当然还是要用比较熟悉的Python来写。虽然说pcap文件的格式并不算复杂,自己解析不是什么大问题。但毕竟没必要重复做轮子,Python有丰富的第三方模块,找一个比较成熟的模块import进去就可以了。

于是我找到了传说中的Scapy——我终于迈进了好大的一个坑里……

Scapy

当然,需要承认的是——就我的初步体验来讲,Scapy是个很牛X的工具。我这里只是需要在Python中调用Scapy模块,但并不是说Scapy就只是个Python的模块。实际上,Scapy是一个独立的工具,可以调用tcpdump用于网络通信数据的抓取,也可以发送任意用户指定的数据包。在网络通信的测试和分析方面非常实用。

不过当务之急是使用Scapy的Python模块去分析pcap文件。

如果你也是Linux系统,可以直接使用Python提供的easy_install工具来下载scapy模块,命令如下:

# easy_install scapy复制代码当然,要不要加sudo或者提前su root之类的这种事情看你的本地环境需求了。

装完之后,在python的环境下测一下,可以正常导入scapy即可了。

接下来,我要找到的自然是如何去读一个pcap包,发现很简单,直接如下两行,就可以获取到一个pcap包的完整信息了:

>>>> from scapy.all import *

>>>> pcap = rdpcap(r'test.pcap')复制代码之后的事情就是抽取pcap中的信息进行必要的数据分析,不在讨论范围之内。

tips: 因为scapy整个模块比较特殊,下面所有的模块之间并非独立,有一套依赖关系,初始化有先后要求。所以在不熟悉scapy的前提下,建议直接从scapy.all导入,而不要单独导入scapy下的其他模块。

坑……

本以为一切到此结束,我可以满心欢喜的把代码挂到服务器上跑着,然后就回家了~

到家之后收到通知——服务器被我那个脚本撑爆了……总共8G的内存空间,我那个脚本占了9个多G……这是多么痛的领悟……所以我的脚本被强行kill了。

于是第二天开始排查代码。发现了两处坑……好大的坑……

俩坑=.=~

因为脚本本身不复杂,所以很简单的就能排除掉我自己脚本内容的问题。那么问题一定出在这个我还不熟悉的scapy模块上。我直接调用的是rdpcap,当然要从rdpcap找原因。cd到python的scapy模块路径下。我的目录如下,仅供参考:

/usr/local/lib/python2.7/site-packages/scapy复制代码切换到目录后,搜索rdpcap这个函数到底是如何定义的:

$ grep "def rdpcap(" ./*

./utils.py:def rdpcap(filename, count=-1):

$复制代码找到了所在的utils.py文件,那就看源码吧,好简单粗暴的代码……

def rdpcap(filename, count=-1):

"""Read a pcap file and return a packet list

count: read only packets"""

return PcapReader(filename).read_all(count=count)复制代码好吧,那我们继续找PcapReader这个类。同样在这个文件中。继承自同在这个文件中定义的RawPcapReader类。但我此时更关心的是这个read_all是如何定义的……

def read_all(self,count=-1):

res = RawPcapReader.read_all(self, count)

import plist

return plist.PacketList(res,name = os.path.basename(self.filename))复制代码很显然,读取文件内容这部分的主体功能还是来自于他的父类RawPcapReader中的read_all函数,而父类函数定义如下:

def read_all(self,count=-1):

"""return a list of all packets in the pcap file

"""

res=[]

while count != 0:

count -= 1复制代码看了这代码我不禁高呼……这岂止是坑啊……简直就是坑啊!我在使用的时候的时候想当然的以为所谓的read_all函数只是打开了一个文件对象,而真正将数据加载到内存是逐个读物packet时才会做的事情。但现在看来是我图样图森破了……一个read_all就直接循环把整个的pcap文件全都加载到内存当中了。这能不大么……

但我也很清醒的明白,这一定不是根本原因:我处理的这些pcap文件中,单个文件小则几MB,多则几十MB上百MB,上GB的几本没见过。所以即便一次性读取所有数据全部加载入内存当中,内存占用无论如何不会上GB的。怎么可能耗尽所有内存却居高不下呢?一定有内存泄漏……

其实我想看到这,比较敏锐一些的人应该能看出问题在哪了——还是在rdpcap函数里!

rdpcap其实就是一行代码——return. 在return时,实例化了PcapReader类,并调用了其read_all函数。不难想到——既然rdpcap的作用是读取一个pcap文件的内容,那一定会有open操作。我们必然要问一句:既然open了,那么你是在哪close的呢??!!

刚才说了,PcapReader类是继承自RawPcapReader类。我们直接看下RawPcapReader类中的两个关键函数就好了——初始化函数和关闭函数:

def __init__(self, filename):

self.filename = filename

try:

self.f = gzip.open(filename,"rb")

magic = self.f.read(4)

except IOError:

self.f = open(filename,"rb")

magic = self.f.read(4)

if magic == "\xa1\xb2\xc3\xd4": #big endian

self.endian = ">"

elif magic == "\xd4\xc3\xb2\xa1": #little endian

self.endian = "<"

else:

raise Scapy_Exception("Not a pcap capture file (bad magic)")

hdr = self.f.read(20)

if len(hdr)<20:

raise Scapy_Exception("Invalid pcap file (too short)")

vermaj,vermin,tz,sig,snaplen,linktype = struct.unpack(self.endian+"HHIIII",hdr)

self.linktype = linktype

def close(self):

return self.f.close()复制代码可见,一旦实例化这个类,初始化函数就会自动的将用户指定的pcap文件打开,但用完之后,必须用户手动close掉才可以。而rdpcap这个函数只实例化了PcapReader这个类,却没有close掉,也没有将实例化的对象传递出来,用户也无法close掉……结果就是——谁都没有关闭这个对象。

内存泄漏啊……大坑啊……

结果显而易见……不断累积的内存泄漏造成了占满了内存……卡爆了服务器……

填坑!

解决方案当然是显而易见的,最坑的内存泄漏部分修改一下rdpcap这个函数即可:

def rdpcap(filename, count=-1):

"""Read a pcap file and return a packet list

count: read only packets"""

pcap = PcapReader(filename)

data = pcap.read_all(count=count)

pcap.close()

return data复制代码至于一次读取全部数据那个坑,绕过这个rdpcap就好,直接自己调用PcapReader类中的read_packet方法。这样就不会每次都读取全部数据了,而是逐个读取每个packet

经过以上修改,运行的脚本只会占用大约20MB以内的内存,而且不会不断累积增大了。

最后

最后就简单的说两点即可:

对于不熟悉的第三方模块,使用起来还是要多注意,有精力的话多看看源码。不知道哪里就有一个大坑……

不要像我这样天真的一位Python有自动垃圾回收机制就没有内存泄漏问题了……一样有的……只不过可能比C/C++一类的语言更少一些而已。

这里再留个扣子——其实也是给自己留个扣子,因为我也还没完全搞懂——就是到底是什么原因,导致此处的回收机制失效了,或者是回收机制在这里到底失效了多少?等我研究清楚了,我们再来分解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值