0 背景介绍
对于做网络的工程师,pcap文件是经常接触的,通常抓包之后保存的报文即使pcap文件,用来分析定位问题是非常方便的。这篇文章介绍下pcap文件格式、报文格式以及如何将一个报文写入到pcap文件的c语言实现。
Pcap文件是最常用的数据报文存储格式,可以理解为一种文件格式。Pcap文件用记事本打开会显示乱码,对于pcap文件有特定的打开工具,例如最常用的wireshark软件,可以用wireshark打开来分析一个pcap文件的内容。
对于一个做网络相关开发的工程师,会分析pcap文件是非常有用的,对我们开发及定位问题非常有用。通常我们可以使用wireshark、tcpdump等工具抓包,抓取到的报文可以存储为pcap文件,分析报文用于确定问题原因。
Pcap的文件格式也比较简单,大致为:pcap文件头-报文1头-报文1数据……报文n头-报文n数据,如下图:
文件头,每一个pcap文件只有一个文件头,总共占24(B)字节,以下是总共7个字段的含义,格式如下图:
每个字段的含义如下:
Magic(4B):标记文件开始,并用来识别文件和字节顺序。值可以为0xa1b2c3d4或者0xd4c3b2a1,如果是0xa1b2c3d4表示是大端模式,按照原来的顺序一个字节一个字节的读,如果是0xd4c3b2a1表示小端模式,下面的字节都要交换顺序。现在的电脑大部分是小端模式;
Major(2B):当前文件的主要版本号,一般为0x0200;
Minor(2B):当前文件的次要版本号,一般为0x0400;
ThisZone(4B):当地的标准事件,如果用的是GMT则全零,一般全零;
SigFigs(4B):时间戳的精度,一般为全零;
SnapLen(4B):最大的存储长度,设置所抓获的数据包的最大长度,如果所有数据包都要抓获,将值设置为65535;
LinkType(4B):链路类型。解析数据包首先要判断它的LinkType,所以这个值很重要。一般的值为1,即以太网。常用的LinkType(链路类型),如下:
值 类型描述
0 BSD loopback devices, except for later OpenBSD
1 Ethernet, and Linux loopback devices
6 802.5 Token Ring
7 ARCnet
8 SLIP
9 PPP
10 FDDI
100 LLC/SNAP-encapsulated ATM
101 “raw IP”, with no link
102 BSD/OS SLIP
103 BSD/OS PPP
104 Cisco HDLC
105 802.11
108 later OpenBSD loopback devices (with the AF_value in network byte order)
113 special Linux “cooked” capture
114 LocalTalk
数据包头可以有多个,每个数据包头后面都跟着真正的数据包。以下是Packet Heade的格式及4个字段含义:
Timestamp(4B):时间戳高位,精确到seconds,这是Unix时间戳。捕获数据包的时间一般是根据这个值;
Timestamp(4B):时间戳低位,能够精确到microseconds;
Caplen(4B):当前数据区的长度,即抓取到的数据帧长度,由此可以得到下一个数据帧的位置;
Len(4B):离线数据长度,网路中实际数据帧的长度,一般不大于Caplen,多数情况下和Caplen值一样。
Packet是链路层的数据帧,长度就是Packet Header中定义的Caplen值,所以每个Packet Header后面都跟着Caplen长度的Packet Data。也就是说pcap文件并没有规定捕获的数据帧之间有什么间隔字符串。Packet数据帧部分的格式就是标准的网络协议格式了。
最简单的tcp报文,不考虑ip option字段、分片等各种复杂的情况,最简单的报文封装如下图:
最终收发的报文都是一个封装好的以太帧,我们写入pcap文件的data即是将一个以太帧写入pcap文件。
libpcap(Packet Capture Library)即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。著名的软件TCPDUMP就是在libpcap的基础上开发而成的。
libpcap可以实现以下功能:
- 数据包捕获:捕获流经网卡的原始数据包
- 自定义数据包发送:任何构造格式的原始数据包
- 流量采集与统计:网络采集的中流量信息
- 规则过滤:提供自带规则过滤功能,按需要选择过滤规则
Libpcap库带有写pcap文件的功能,可以使用libpcap库的相关接口实现pcap文件的写入,相关接口如下:
libpcap库提供的功能比较多,如果只写pcap文件可以直接写,参考libpcap文件的源码及pcap文件格式,很容易将一个报文写入pcap文件。
使用c语言直接写pacao文件,代码量也不大,当我们只需要写pcap文件的功能的时候,可以直接将报文写入pcap文件(跟libpcap实现其实是相同的),以一个最简单的tcp报文为例,源码如下:
#include
#include
#include
#define FILE_NAME_LEN 64
char g_pcap_filename[FILE_NAME_LEN] = {0};
FILE *g_pcap_file = NULL;
/*
* Standard libpcap format.
*/
#define TCPDUMP_MAGIC 0xa1b2c3d4
#define PCAP_VERSION_MAJOR 2
#define PCAP_VERSION_MINOR 4
struct pcap_file_header {
unsigned int magic;
unsigned short version_major;
unsigned short version_minor;
unsigned int thiszone; /* gmt to local correction */
unsigned int sigfigs; /* accuracy of timestamps */
unsigned int snaplen; /* max length saved portion of each pkt */
unsigned int linktype; /* data link type (LINKTYPE_*) */
};
struct pcap_timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
struct pcap_sf_pkthdr {
struct pcap_timeval ts; /* time stamp */
unsigned int caplen; /* length of portion present */
unsigned int len; /* length of this packet (off wire) */
};
int pcap_init(const char *file)
{
size_t size = 0;
struct pcap_file_header pcap_filehdr;
memset(&pcap_filehdr, 0, sizeof(pcap_filehdr));
pcap_filehdr.magic = TCPDUMP_MAGIC;
pcap_filehdr.version_major = PCAP_VERSION_MAJOR;
pcap_filehdr.version_minor = PCAP_VERSION_MINOR;
pcap_filehdr.thiszone = 0;
pcap_filehdr.sigfigs = 0;
pcap_filehdr.snaplen = 65535;
pcap_filehdr.linktype = 1;
if ('\0' == file[0]) {
return 0;
}
g_pcap_file = fopen(file, "wb");
if (!g_pcap_file) {
printf("Open pcap file failed\n");
return -1;
}
size = sizeof(pcap_filehdr);
if (fwrite(&pcap_filehdr, size, 1, g_pcap_file) != 1) {
printf("Write pcapfile header failed\n");
fclose(g_pcap_file);
return -1;
}
return 0;
}
int pcap_write(const char *buf, const unsigned int len)
{
struct pcap_sf_pkthdr pkthdr;
clock_t time;
time = clock();
memset(&pkthdr, 0, sizeof(pkthdr));
pkthdr.ts.tv_sec = time / CLOCKS_PER_SEC;
pkthdr.ts.tv_usec = time % CLOCKS_PER_SEC;
pkthdr.caplen = len;
pkthdr.len = pkthdr.caplen;
if (g_pcap_file) {
if (fwrite(&pkthdr, sizeof(pkthdr), 1, g_pcap_file) != 1) {
printf("Fwrite packet header failed.\n");
return -1;
}
if (fwrite(buf, (size_t)len, 1, g_pcap_file) != 1) {
printf("Fwrite data failed.\n");
return -1;
}
}
return 0;
}
void pcap_clean(void)
{
if (g_pcap_file) {
fclose(g_pcap_file);
g_pcap_file = NULL;
}
return;
}
int main()
{
char file[FILE_NAME_LEN] = "test.pcap";
char buf[2048] = {0x00, 0x50, 0x56, 0xe4, 0xa4, 0x69, 0x00, 0x0c, 0x29, 0xde, 0x31, 0xa6, 0x08, 0x00, 0x45, 0x00, 0x00, 0x3c, 0xa1, 0x78, 0x40, 0x00, 0x40, 0x06, 0xe7, 0x37, 0xc0, 0xa8, 0x3a, 0x80, 0x5b, 0xbd, 0x5b, 0x26, 0xe8, 0xfa, 0x00, 0x50, 0x5b, 0xcb, 0xb3, 0x62, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, 0xfa, 0xf0, 0xb2, 0x3a, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0x7c, 0x2b,
0xa0, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07};
if(0 != pcap_init(file)) {
printf("Pcap init failed.\n");
return -1;
}
if(0 != pcap_write(buf, 74)) {
printf("Pcap write failed.\n");
return -1;
}
pcap_clean();
return 0;
}
此代码中,直接将一个buf[2048]写入了pcap文件,通常会将一个skb写入pcap文件(关于skb的详细解释后续将写文字介绍)。其实都是大同小异的。
编译运行上面的代码,这个报文便写入了test.pcap的pcap的文件,现在我们分别用wireshark和vim(16进制)打开分析一下此文件。
从wireshark的解析,可以获取到非常详细的报文信息,可以看到最下面的16进制报文显示,既是代码中的buf值。
接下来将test.pcap以16进制打开,分析如下:
可以清楚的看到,蓝色框既是pcap header;黄色框是packet header;红色框是data。
至此,pcap文件格式以及如何将一个报文写入pcap文件分析完毕,pcap文件分析报文对于网络工程师是非常有用的,希望此文章对大家有一定的帮助。