:::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_packetscf_write_XXX_packets 函数可以了解 wireshark 是如何遍历一个抓包文件中的所有包的. 主要代码:

    1. epan_dissect_init(&callback_args.edt, cf->epan, proto_tree_needed, proto_tree_needed);
    2. /* Iterate through the list of packets, printing the packets we were
    3. told to print. */
    4. ret = process_specified_records(cf, &print_args->range, "Printing",
    5. "selected packets", TRUE, print_packet,
    6. &callback_args, show_progress_bar);
    7. epan_dissect_cleanup(&callback_args.edt);

    epan_dissect_init 初始化一次单包解析. 所以如果要解析多个包, 要么每次都初始化/创建 epan_dissect, 要么只初始化/创建一次, 然后在解析完每个单包后调用 epan_dissect_reset.
    process_specified_records 是一个通用函数, 各种遍历包的函数都会调用它. 主要代码:

    1. wtap_rec_init(&rec);
    2. ws_buffer_init(&buf, 1514);
    3. /* Iterate through all the packets, printing the packets that
    4. were selected by the current display filter. */
    5. for (framenum = 1; framenum <= cf->count; framenum++) {
    6. fdata = frame_data_sequence_find(cf->provider.frames, framenum);
    7. /* Get the packet */
    8. if (!cf_read_record(cf, fdata, &rec, &buf)) {
    9. /* Attempt to get the packet failed. */
    10. ret = PSP_FAILED;
    11. break;
    12. }
    13. /* Process the packet */
    14. if (!callback(cf, fdata, &rec, &buf, callback_args)) {
    15. /* Callback failed. We assume it reported the error appropriately. */
    16. ret = PSP_FAILED;
    17. break;
    18. }
    19. }
    20. wtap_rec_cleanup(&rec);
    21. ws_buffer_free(&buf);
    • frame_data_sequence_find 根据 frame 序号从 packet_provider_data 中得到 frame_data
    • cf_read_record 从抓包文件中将信息读取到内存

    对于 cf_print_packets, 这里的 callback 是 print_packet, 其主要代码:

    1. epan_dissect_run(&args->edt, cf->cd_t, rec,
    2. frame_tvbuff_new_buffer(&cf->provider, fdata, buf),
    3. fdata, NULL);
    4. /* Print the information in that tree. */
    5. if (!proto_tree_print(args->print_args->print_dissections,
    6. args->print_args->print_hex, &args->edt, NULL,
    7. args->print_args->stream))
    8. goto fail;
    9. epan_dissect_reset(&args->edt);
    • epan_dissect_run 调用解析器解析数据, 形成一个协议树(proto_tree, 它是 epan_dissect的成员)
    • proto_tree_print 打印协议树
    • epan_dissect_reset 重置单包解析