:::warning 此文档已经比较老了, 一些描述不符合当前代码情况(3.5.0), 所以这里根据代码分析做了更新. :::
wireshark 打开一个抓包文件, 会对应一个 capture_file
结构体, 见 cfile.h
. 目前同一时刻仅支持打开一个抓包文件.
capture_file 结构体早先有一个 plist 成员, 用来表示 frame_data
链表, 但现在已经没有了, 取而代之的应该是更复杂的 packet_provider_data
结构体. wireshark 读取到的每个链路层”帧(frame)”都对应一个 frame_data 结构体.
frame_data 结构体位于 epan/frame_data.h
. 它本来有两个指针分别指向前一个和后一个 frame, 但当前实现已不同. frame 序列由 packet_provider_data 结构体中的 frame_data_sequence
结构体来表示, 它实现为 radix tree. frame_data 携带了以下重要信息(不限于):
- 序号 (从1开始)
- 包长度
- 时间戳
- 在抓包文件中的偏移
查看 file.h
中的 cf_print_packets
或 cf_write_XXX_packets
函数可以了解 wireshark 是如何遍历一个抓包文件中的所有包的. 主要代码:
epan_dissect_init(&callback_args.edt, cf->epan, proto_tree_needed, proto_tree_needed);
/* Iterate through the list of packets, printing the packets we were
told to print. */
ret = process_specified_records(cf, &print_args->range, "Printing",
"selected packets", TRUE, print_packet,
&callback_args, show_progress_bar);
epan_dissect_cleanup(&callback_args.edt);
epan_dissect_init
初始化一次单包解析. 所以如果要解析多个包, 要么每次都初始化/创建 epan_dissect, 要么只初始化/创建一次, 然后在解析完每个单包后调用 epan_dissect_reset
.process_specified_records
是一个通用函数, 各种遍历包的函数都会调用它. 主要代码:
wtap_rec_init(&rec);
ws_buffer_init(&buf, 1514);
/* Iterate through all the packets, printing the packets that
were selected by the current display filter. */
for (framenum = 1; framenum <= cf->count; framenum++) {
fdata = frame_data_sequence_find(cf->provider.frames, framenum);
/* Get the packet */
if (!cf_read_record(cf, fdata, &rec, &buf)) {
/* Attempt to get the packet failed. */
ret = PSP_FAILED;
break;
}
/* Process the packet */
if (!callback(cf, fdata, &rec, &buf, callback_args)) {
/* Callback failed. We assume it reported the error appropriately. */
ret = PSP_FAILED;
break;
}
}
wtap_rec_cleanup(&rec);
ws_buffer_free(&buf);
frame_data_sequence_find
根据 frame 序号从 packet_provider_data 中得到 frame_datacf_read_record
从抓包文件中将信息读取到内存
对于 cf_print_packets, 这里的 callback 是 print_packet
, 其主要代码:
epan_dissect_run(&args->edt, cf->cd_t, rec,
frame_tvbuff_new_buffer(&cf->provider, fdata, buf),
fdata, NULL);
/* Print the information in that tree. */
if (!proto_tree_print(args->print_args->print_dissections,
args->print_args->print_hex, &args->edt, NULL,
args->print_args->stream))
goto fail;
epan_dissect_reset(&args->edt);
epan_dissect_run
调用解析器解析数据, 形成一个协议树(proto_tree
, 它是epan_dissect
的成员)proto_tree_print
打印协议树epan_dissect_reset
重置单包解析