:::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 weretold 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 thatwere 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重置单包解析
