pcap文件的python解析实例

最近一直在分析数据包。
同时也一直想学python。

凑一块儿了...于是,便开工了。座椅爆炸!

正文

首先要说的是,我知道python有很多解析pcap文件的库,这里不使用它们的原因是为了理解pcap文件的格式细节。使用tcpdump你可以很容易抓取到一系列的数据包,然而tcpdump并没有分析数据包的功能,如果想从这个抓包文件中分析出一些端倪,比如重传情况,你必须使用wireshark之类的软件,用wireshark打开tcpdump抓取的pcap文件,如果你看到了一堆堆的深红色(类似静脉血管里流出的猪血的颜色)的数据包,那么这些包一定是“在协议层看来”异常的数据包,包括但不限于重传,乱序等等,欲知详情,请在wireshark的过滤器中敲进去“tcp.analysis.”然后就会自动补全,这一切简直方便到极点。如果你还想看一些全局的统计数据,那么请点击”统计“菜单的第一个”捕获文件属性“,你会看到更多的信息。虽然数据包早就已经过去,但是雁过留声,我们通过抓取的数据包,还是可以得到更多的信息,多谢有wireshark/tshark(一个字符界面的pcap文件分析工具,类似wireshark,但更适合玩机械键盘的命令行粉们使用)/shookshark(...)这些工具,使得我们真实能够分析pcap文件以获取信息。
        然而,这些我觉得还不够。
        有一个简单的需求,我想得到在一个TCP连接中,一个端节点一共发送了多少字节的TCP载荷数据,包括正常发送以及重传。我没有在wireshark中找到得到这个数据的功能,于是我迫不及待自己写一个。厨师还怕没肉吃吗?
        但有个前提,那就是我必须搞明白pcap文件的格式,因为我想裸分析pcap文件,试图找出每一个感兴趣数据包的TCP载荷(不包括TCP头和IP头)长度,然后将其累加。这样我必须知道pcap文件的格式细节才行。
        幸运的是,pcap文件非常简单,就像我几乎10年前分析Windows PE文件一样,如今依然循着老路做着同样的事情。
        如果你不善于查文档,那么作为一个编程者,看libpcap的源码也是个不错的选择,几乎和任何文件格式一样,pcap也是一个自描述的格式(这个自描述设计的不够优雅,以至于后来出现了pcapng文件格式,后面我会写一篇文章单独论述之),整体包括文件头和数据载荷,这里所谓的数据载荷就是网络数据包。在libpcap的pcap.h文件中,结构体pcap_file_header描述了文件头:
struct pcap_file_header {
    bpf_u_int32 magic;
    u_short version_major;
    u_short version_minor;
    bpf_int32 thiszone;    /* gmt to local correction */
    bpf_u_int32 sigfigs;    /* accuracy of timestamps */
    bpf_u_int32 snaplen;    /* max length saved portion of each pkt */
    bpf_u_int32 linktype;    /* data link type (LINKTYPE_*) */
};
具体我就不解释了,待会儿我会用一个实例来解析。紧接着这个文件头,后面就是一个个数据包了,为了描述每一个数据包的元信息,每一个数据包都会有一个描述头:
struct pcap_pkthdr {
    struct timeval ts;    /* time stamp */
    bpf_u_int32 caplen;    /* length of portion present 由于tcpdump可以设置-s参数指定抓取的长度,这个字段表示实际抓取的数据包长度 */
    bpf_u_int32 len;    /* length this packet (off wire) 这个字段表示数据包的自然长度 */
};

这个结构体描述了数据包抓取的时间信息以及长度信息,在这个结构之后才会是数据包,因此一个典型的pcap文件应该是如下所示:




这简直清晰至极啊,再次看我的那个需求,我想统计的两个量怎么得到呢?

一个TCP连接实际发送的字节数:每一个数据包的TCP载荷长度的加和。
一个TCP理论上应该发送的字节数:结束的TCP序列号与初始序列号之差。
有了上面的论述,我觉得这个需求超级简单就实现了,为了展示一下学习python的出血效果,给出以下的代码:
	#!/usr/bin/python

	import sys
	import socket
	import struct

	filename = sys.argv[0]
	filename = sys.argv[1]
	ipaddr = sys.argv[2]
	direction = sys.argv[3]

	packed = socket.inet_aton(ipaddr)
	ip32 = struct.unpack("!L", packed)[0]

	file = open(filename, "rb") 

	pcaphdrlen = 24
	pkthdrlen=16
	pkthdrlen1=14
	iphdrlen=20
	tcphdrlen=20
	stdtcp = 20
	total = 0
	pos = 0

	start_seq = 0
	end_seq = 0
	cnt = 0

	# Read 24-bytes pcap header
	data = file.read(pcaphdrlen)
	(tag, maj, min, tzone, ts, ppsize, lt) = struct.unpack("=L2p2pLLLL", data)

	# 具体的LinkType细节,请看:
	# http://www.winpcap.org/ntar/draft/PCAP-DumpFileFormat.html#appendixBlockCodes
	if lt == 0x71:
		pkthdrlen1 = 16
	else:
		pkthdrlen1 = 14

	ipcmp = 0

	# Read 16-bytes packet header
	data = file.read(pkthdrlen)

	while data:
		(sec, microsec, iplensave, origlen) = struct.unpack("=LLLL", data)

		# read link
		link = file.read(pkthdrlen1)
		
		# read IP header
		data = file.read(iphdrlen)
		(vl, tos, tot_len, id, frag_off, ttl, protocol, check, saddr, daddr) = struct.unpack(">ssHHHssHLL", data)
		iphdrlen = ord(vl) & 0x0F 
		iphdrlen *= 4

		# read TCP standard header
		tcpdata = file.read(stdtcp)	
		(sport, dport, seq, ack_seq, pad1, win, check, urgp) = struct.unpack(">HHLLHHHH", tcpdata)
		tcphdrlen = pad1 & 0xF000
		tcphdrlen = tcphdrlen >> 12
		tcphdrlen = tcphdrlen*4
		
		if direction == 'out':
			ipcmp = saddr
		else:
			ipcmp = daddr

		if ipcmp == ip32:
			cnt += 1
			total += tot_len
			total -= iphdrlen + tcphdrlen
			if start_seq == 0:  # BUG?
				start_seq = seq
			end_seq = seq

		# skip data
		skip = file.read(iplensave-pkthdrlen1-iphdrlen-stdtcp)

		# read next packet
		pos += 1
		data = file.read(pkthdrlen)

	# 打印出实际传输的字节数,以及本应该传输的字节数
	print pos, cnt, 'Actual:'+str(total),  'ideal:'+str(end_seq-start_seq)

很简单吧!懂python的人都会嘲笑我!
        其实,在我看pcap文件格式之前,我曾经一直以为pcap文件是由类似ASN.1组织的,但是看了以后却发现不是,也是挺失望的。我之所以失望是因为,看起来以上描述的这种pcap不能描述除了数据包之外的更多东西,它事实上并不是自描述的,它是一种固定长度格式的文件结构,虽然处理起来很快,但是却十分不灵活不易扩展!彻底的自描述结构就是ASN.1!
......
我们来看一个例子。随便抓一个TCP包获得test.pcap文件,用UE打开这个pcap,请自行脑补!如果你真的理解了pcap的文件组织形式,那么请认真分析,如果不,请理解透彻而不要脑补!


执行python脚本pcap-parser.py,我们一无所获,因为这只是包含一个纯ACK包的pcap,没有携带任何数据,而python脚本旨在得到TCP数据流实际传输的数据量,因此我们不得不抓取一个携带TCP流量的pcap文件,而这非常简单。
        两台虚拟机A,B互联,A启动httpd,B上执行wget下载一个文件,同时设置丢包率以获得额外的重传数据量。执行:
pcap-parser.py ./testTCP.pcap 192.168.44.129 out
我们得到了以下结果:
...请自行执行获取
肉眼计算后发现结果是一致的,我认为这个脚本可用了。然而...
        然而当我用我写的python脚本去分析一个tshark抓取的数据包的时候,发现解析错了,这个时候魔术字就起作用了,我用UE残忍地打开了这个pcap文件,结果呢?

魔术字都是错的!于是上wireshark网站,知道了这是一个pcapng这个文件格式,同时,也知道了pcapng不能向下兼容。这是令人悲伤的一件事,但是幸运的是,pcapng文件格式要比pcap简单的多,而且,它基本就是类似ASN.1的组织办法。

       我们发现pcap的文件格式中,大部分的元描述结构都是固定数量且定长的,以LinkType为例,一次抓包我只能指定一个LinkType,它被记录在pcap文件开始的pcap_file_header中,这意味着,我无法同时在以太网卡和非以太的PPP网卡上抓包并同时得到详细的链路层信息!而pcapng解决了这个问题。

        欲知pcapng如何,且看下篇文字。


附录

细节1:Cooked Capture与Ethernet

如果说使用tcpdump -i any参数,我们不会看到标准的以太头信息,我们看到的是Cooked Capture,而不是Ethernet!关键的是,Cooked Capture描述的元信息长度是16字节而不是Ethernet的14字节。以下是Cooked Capture的头示例:



这个信息可以通过LinkType来获取。为什么会有这种Cooked Capture类型的数据包?因为抓包工具在-i any的情况下,无法用一种统一的方式来处理链路层的长度,比如很多协议,内部又区分了很多的子协议,其协议头的长度根据应用层而定,这是内核在抓包层面所处理不了的。pcap文件中,只能在一处指定LinkType,那就是文件头之后的pcap_pkthdr,如果说我指定-i eth0 -i lo -i ppp0 -i tun0,那就彻底没辙了!幸运的是,如果使用pcapng格式来存储抓包文件,那就可以针对这些网卡区别对待了,每一个网卡抓到的包都会关到一个LinkType,你会更轻松处理链路层,不过,大多数人不在乎链路层,也不在乎IP,更多人在乎的是TCP。

细节2:时钟跳变

作为一个pcap文件写的练习,这里有一个关于时钟跳变现象重现的例子。
        我们抓包发现了一个奇怪的现象,那就是从客户端抓包上看,中间间隔了几十秒没有收到任何数据,在服务端抓包看来一切正常,总共的数据传输时间也就十几秒,这是怎么回事呢?
        当即判断是客户端抓包时发生了时钟的跳变,比如时钟突然后跳了40秒,为了重现这个现象,我拿一个真实的普通正常的TCP下载为例,在收发第900个数据包的时候以及以后,数据包描述头里的时间戳字段统一加上40秒,看看是什么效果...代码非常简单:
       if pos > 900:
               data = struct.pack("=LLLL", sec+40, microsec, iplensave, origlen)
        file_out.write(data)
然后就重现了这个现象:



本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

CPU和内存监测 - 2016-07-24 19:07:07

CPU和内存监测 vmstat命令的VM模式 vmstat可以监测给定时间间隔的服务器的状态值,包括CPU的使用率,内存的使用,虚拟内存的交换情况,IO读写情况。 主要从/proc/meminfo,/proc/stat和/proc/*/stat中获取数据 常用手段vmstat [采样的时间间隔秒数] [采样的次数],举例如下: [root@dtbase-master- 2 /root] #vmstat 5 10 procs -----------memory---------- ---swap-- ---
正文 为了弥补pcap文件的缺陷,让抓包文件可以容纳更多的信息,pcapng格式应运而生。关于它的介绍详见《 PCAP Next Generation Dump File Format 》         当前的wireshark/tshark抓取的包默认都被保存为pcapng格式。         形而上的论述就不多谈了,直接给出一个pcapng数据包文件的例子: 然后我强烈建议,对着《 PCAP Next Generation Dump File Format 》来把一个实际抓取的pcapng文件里面
PS:历史原因作者账号名为:ymh198816,但事实上作者的生日并不是1988年1月6日 今天作者要在这里通过一个简单的电商网站订单实时分析系统和大家一起梳理一下大数据环境下的实时分析系统的架构模型。当然这个架构模型只是实时分析技术的一 个简单的入门级架构,实际生产环境中的大数据实时分析技术还涉及到很多细节的处理, 比如使用Storm的ACK机制保证数据都能被正确处理, 集群的高可用架构, 消费数据时如何处理重复数据或者丢失数据等问题,根据不同的业务场景,对数据的可靠性要求以及系统的复杂度的要求也会不同
一 协议端口 如果把IP地址比作一间房子 ,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536(即:2^16)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)。 在Internet上,各主机间通过TCP/IP协议发送和接收数据包,各个数据包根据其目的主机的ip地址来进行互联网络中的路由选择,把数据包顺利的传送到目的主机。大多数操作系统都支持多程序(进程)同时运行,那么目的主机应该把接收到的数据包传送给众多同时运行的进程中的哪一个
MyBatis真正的强大,在于其映射语句的魔力。 SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序): (1)cache  给定命名空间的配置缓存。 (2)cache-ref  其他命名空间缓存配置的引用。 (3)resultMap  是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象 (4)sql 可被其他语句引用的可重用语句块。 (5)insert 映射插入语句 (6)update  映射更新语句 (7)delete   映射删除语句 (8)select   映射查询语句
1、Maven构建Spring Boot 创建Maven Web工程,引入spring-boot-starter-parent依赖 project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/m

平衡搜索树之AVLTree - 2016-07-24 14:07:56

今天我想要在这里写下个人觉得比较难的数据结构---AVL树的一些些心得。 一。了解一种新的数据结构,首先要搞懂它的定义 : AVL树又称为高度平衡的二叉搜索树,是1962年有俄罗斯的数学家G.M.Adel'son-Vel'skii和E.M.Landis提出来的。它能保持二叉树的高度 平衡,尽量降低二叉树的高度,减少树的平均搜索长度。所以严格点来说,对于一棵搜索二叉树,能达到O(logn)的只是AVL树,因为他对于二叉树的深度控制的最为严格 ,那么这是为什么呢?让我们来看看 AVL树的性质 : 左子树和右子

linux基础知识 - 2016-07-24 14:07:48

1:基本知识 微内核:是一种提供必要服务的操作系统内核,大部分内核都作为单独的进程在特权模式先运行,他们通过消息传递进行通讯 单内核:单内核是个很大的进程,他的内部又悲愤为若干个模块,是个单独的二进制但印象,其模块间的通讯是通过直接调用其他模块中的函数实现的,而不是消息传递。 linux分几种应用程序级别 Ring 0 特权模式 一般是系统底层运行级别 Ring3 应用程序级别 一般的级别 有时候应用进程为了调用系统底层的模块,可能会在用户空间和内核空间之间进行来回的切换,这是很耗时间的,平时工作中应注意
1. 概述 嵌入式系统由硬件环境、嵌入式操作系统和应用程序组成,硬件环境是操作系统和应用程序运行的硬件平台,它随应用的不同而有不同的要求。硬件平台的多样性是嵌入式系统的主要特点,如何使嵌入式操作系统在不同的硬件平台上有效地运行,是嵌入式系统开发中需要解决的关键问题。解决的方法是在硬件平台和操作系统之间提供硬件相关层来屏蔽这些硬件的差异,给操作系统提供统一的运行环境,这种硬件相关层就是嵌入式系统中的板级支持包BSP(Board Support Package,简称BSP)。 2. BSP及其作用 BSP是嵌
一.什么是装箱?什么是拆箱? 在前面的文章中提到,Java为每种基本数据类型都提供了对应的包装器类型,至于为什么会为每种基本数据类型提供包装器类型在此不进行阐述,有兴趣的朋友可以查阅相关资料。在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:   1 Integer i = new   Integer( 10 ); 而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:   1 Integer i = 10 ;