凑一块儿了...于是,便开工了。座椅爆炸!
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载荷长度的加和。 #!/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)
我们发现pcap的文件格式中,大部分的元描述结构都是固定数量且定长的,以LinkType为例,一次抓包我只能指定一个LinkType,它被记录在pcap文件开始的pcap_file_header中,这意味着,我无法同时在以太网卡和非以太的PPP网卡上抓包并同时得到详细的链路层信息!而pcapng解决了这个问题。
欲知pcapng如何,且看下篇文字。
如果说使用tcpdump -i any参数,我们不会看到标准的以太头信息,我们看到的是Cooked Capture,而不是Ethernet!关键的是,Cooked Capture描述的元信息长度是16字节而不是Ethernet的14字节。以下是Cooked Capture的头示例:
if pos > 900:
data = struct.pack("=LLLL", sec+40, microsec, iplensave, origlen)
file_out.write(data)
然后就重现了这个现象: