一、引言
在实际的工作场景中,会遇到需要将捕获的数据包按照文件大小进行保存,比如按照10M、20M、50M、100M乃至自定义大小的方式来存储文件,方便后期工作以及查看。目前开源工具如**tcpdump、tshark**
之类的工具均是支持按文件大小来捕获数据包,那如果基于libpcap库如何来手动实现呢?
二、概述
要手动实现上述需求,需要提前了解PCAP文件的格式,PCAP是一种常用的数据报文存储格式,其按照特定的规格存储,一般来说使用普通文本工具打开PCAP文件会显示乱码,查看该类型文件需要使用十六进制工具或者是指定工具来查看,十六进制工具:https://hexed.it/ 在线可以查看,其他工具如:wireshark、010editor(安装pcap插件即可)等等。
三、文件格式
PCAP文件格式,主要是由文件头 --> 数据包头1 + 数据包1 --> 数据包头2 + 数据包2 --> ...
这类格式组成。其中,文件头(Pcap Header)仅包含一个,而数据包头(Packet Header)+数据包(Packet Data)
可以包含多个,如下图所示:
同样,下图更详细展示了每个区域的字节含义与大小情况:
1.Pcap Header
Pcap Header的数据结构定义如下,总长度是24字节:
typedef struct pcap_hdr_s {
guint32 magic_number; /* magic number */
guint16 version_major; /* major version number */
guint16 version_minor; /* minor version number */
gint32 thiszone; /* GMT to local correction */
guint32 sigfigs; /* accuracy of timestamps */
guint32 snaplen; /* max length of captured packets, in octets */
guint32 network; /* data link type */
} pcap_hdr_t;
各个字段含义如下:
header field | size | explain |
---|---|---|
Magic | 4B | 标记文件开始,并用来识别文件和字节顺序 |
Major | 2B | 当前Pcap文件的主要版本号,一般为0x0200 |
Minor | 2B | 当前Pcap文件的次要版本号,一般为0x0400 |
ThisZone | 4B | 当地的标准事件,如果用的是GMT则全零,一般全零 |
SigFlags | 4B | 时间戳的精度,一般为全零 |
SnapLen | 4B | 所抓获的数据包的最大长度 |
LinkType | 4B | 数据链路类型 |
2.Packet Header
Packet Header的数据结构定义如下,总长度是16字节。
typedef struct pcaprec_hdr_s {
guint32 ts_sec; /* timestamp seconds */
guint32 ts_usec; /* timestamp microseconds */
guint32 incl_len; /* number of octets of packet saved in file */
guint32 orig_len; /* actual length of packet */
} pcaprec_hdr_t;
各个字段含义如下:
header field | size | explain |
---|---|---|
Timestamp | 4B | 时间戳高位,精确到seconds |
Timestamp | 4B | 时间戳低位,能够精确到microseconds |
Caplen | 4B | 即抓取到的数据帧长度,由此可以得到下一个数据帧的位置 |
Len | 4B | 实际的数据帧长度 |
3.Packet Data
Packet是链路层的数据帧,其长度是Packet Header中定义的**Caplen**
值,所以Packet Data的长度为Caplen,Packet Header
定义的结构:
struct pcap_pkthdr {
struct timeval ts; /* 时间戳 */
bpf_u_int32 caplen; /* 捕获的长度 */
bpf_u_int32 len; /* 数据包的实际长度 */
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
四、实现思路
- 开启循环捕获数据包,并设定每个文件的保存大小(
阈值
):X(字节); - 在保存数据包至文件的同时,计算文件的大小:Y(字节),即 Y = Pcap Header(24)+ (Packet Header(16)+ Packet Data(Packet Header->caplen)) * N , N表示数据包数量;
- 当Y大于X,即达到了设定阈值,应关闭当前文件,并新创建文件来接受数据包;
- 以此,循环采集即可。
参考示例代码:
- 使用
**pcap_loop()**
函数循环采集数据报文
int main(int argc, char *argv[]) {
char device[256];
char filter[2048];
char errbuf[PCAP_ERRBUF_SIZE];
*device = 0;
*filter = 0;
int option;
while((option = getopt(argc, argv, "hi:f:w:b:")) != -1) {
switch (option) {
case 'h':
print_help(argv[0]);
break;
case 'i':
strcpy(device, optarg);
break;
case 'f':
strcpy(filter, optarg);
break;
case 'w':
strcpy(filename, optarg);
break;
case 'b':
packet_cnt = atoi(optarg);
break;
default:
abort();
}
}
if (!*device){
print_help(argv[0]);
}
printf("[*]开始采集: %s, 过滤条件: %s , 文件大小(字节): %d\n",device, filter, packet_cnt);
handle = pcap_open_live(device, 65535, 1, 0, errbuf);
if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", "eth0", errbuf);
return 1;
}
dumpfile = pcap_dump_open(handle, filename);
if (dumpfile == NULL) {
fprintf(stderr, "Couldn't open dump file %s: %s\n", filename, pcap_geterr(handle));
return 2;
}
printf("[*]开始写入数据到文件: %s\n", filename);
if (pcap_loop(handle, -1, packet_handler, (u_char*)NULL) == PCAP_ERROR) {
fprintf(stderr, "[*]Pcap_loop failed: %s\n", pcap_geterr(handle));
return -1;
}
return 0;
}
- 定义
**packet_size_cnt **
变量存储当前采集数据包的大小,即是N个数据包【数据包头(16字节)+数据包大小(**packet_header->caplen**
)】,如果超过阈值就新建文件接收数据。
void packet_handler(u_char *user, const struct pcap_pkthdr *packet_header, const u_char *packet_data) {
// 累加每个数据报文的大小
packet_size_cnt += (packet_header->caplen + 16);
if ( packet_size_cnt < packet_cnt ) {
// 如果没有达到设定值就直接写
pcap_dump((char *)dumpfile, packet_header, packet_data);
} else {
// 反之,达到设定值,即关闭文件
pcap_dump_close(dumpfile);
// 重新改变计数的值
packet_size_cnt = 24;
// 文件名递增的方式
file_id += 1;
// 生成新的文件名
char tmp_fname[1024];
size_t buf_size = strlen(filename) + 20 + 1;
snprintf(tmp_fname, buf_size,"%s_%d", filename, file_id);
// 创建新文件
dumpfile = pcap_dump_open(handle, tmp_fname);
if (dumpfile == NULL) {
fprintf(stderr, "Couldn't open dump file %s: %s\n", tmp_fname, pcap_geterr(handle));
exit(EXIT_FAILURE);
}
// 将越界的数据报文写入新文件
printf("[*]开始写入数据到文件: %s\n", tmp_fname);
pcap_dump((char *)dumpfile, packet_header, packet_data);
packet_size_cnt += (packet_header->caplen + 16);
}
- 实现的效果如下,当然在实际应用的场景中还需要考虑更多的因素。