一、源代码结构

在wireshark源代码根目录下,可以看到以下子目录:

1)物理结构


    其中,epan文件夹负责所有网络协议识别工作,plugins里面存放了wireshark所有插件,gtk文件夹里面是wireshark的界面部分代码,其余文件夹没有单独研究。

2)逻辑结构

下图给出了Ethereal功能模块:

a) GTK1/2

处理用户的输入输出,源码在gtk目录

b) Core

将其他模块连接在一起,源码在根目录

c) Epan

Ethereal Packetage Analyzing,包分析引擎,源码在epan目录

l Protocol-Tree:保存数据包的协议信息

l Dissectors:在epan/dissector目录下,各种协议解码器

l Plugins:一些协议解码器以插件形式实现,源码在plugins目录

l Display-Filters:显示过滤引擎,源码在epan/dfilter目录

d) Capture

捕包引擎

e) Wiretap

从文件中读取数据包,支持多种文件格式,源码在wiretap目录

二、Tshark协议解析模块

主要处理流程如下所示:

第一步: cf_status_t cf_open(capture_file *cf, const char *fname, gboolean is_tempfile, int *err):该函数首先调用wtap* wtap_open_offline (const char *filename, int *err, char **err_info,gboolean do_random) 函数,获得一个wtap struct。(Wtap结构暂时没有找到。)

a) wtap_open_offline() 函数的处理流程:

  • 调用int libpcap_open(wtap *wth, int *err, gchar **err_info)函数读取给定pcap文件的文件头信息,总共28个字节,如下所示:

文件头结构体
 sturct pcap_file_header
 {
      DWORD           magic; //4个字节
      WORD           version_major; //主版本号,通常为2
      WORD           version_minor; //副版本号,通常为4
      DWORD           thiszone;
      DWORD           sigfigs;
      DWORD           snaplen;
      DWORD           linktype;
 }

libpcap_open() 函数首先读取magic信息,如果文件没有被修改,则继续读取文件头的后续信息。读取整个文件头信息后,判断该信息是否合法,如果合法,再尝试读取给定pcap文件的前2个数据包的包头记录,这个工作通过调用static libpcap_try_t libpcap_try(wtap *wth, int *err)函数完成。

libpcap_try()函数首先调用libpcap_read_header()函数读取第一个数据包的数据包头,该数据包头的结构如下所示:

struct pcap_pkthdr
{
struct tim         ts;
      DWORD              caplen; //所捕获数据包保存在pcap文件中的实际长度,以字节为单位。
      DWORD              len; //所捕获的数据包的真实长度。
}
 
struct tim
{
DWORD       GMTtime; //捕获数据包的时间,从格林尼治时间的1970年1月1日00:00:00到抓包时经过的秒数。
DWORD       microTime; //抓取数据包时的微秒值。
}

该数据包头总共16个字节。然后再调用file_seek()(该函数主要功能就是实现文件定位)函数定位到第一个数据包之后,即24字节的文件头+16字节的数据包头+caplen字节的第一个数据包的数据部分长度。如果file_seek()操作正确,即不返回-1,则libpcap_try()函数再次调用libpcap_read_header()函数,读取第二个数据包的包头信息。如果读取正确,则libpcap_try()函数返回THIS_FORMAT,表示pcap文件格式正确。

这时libpcap_open()调用file_seek()定位到给定pcap文件的文件头之后,也就是24字节处,并返回1,表示操作成功。

b) 字节,然后返回给定pcap文件的wtap信息。

c) 当wtap_open_offline()函数正确返回wtap信息后,cf_open()函数调用cleanup_dissection()函数清空接下来在协议解析工作中需要用到的数据结构。

  • cleanup_dissection()的清空工作包括:

1) conversation(epan_conversation_cleanup());

2) circuits(epan_circuit_cleanup());

3) protocol-specific variables(g_slist_foreach(init_routines, &call_init_routine, NULL));

4) stream-handling tables(stream_cleanup());

5) expert infos(expert_cleanup())。

  • cleanup_dissection()返回后,cf_open()函数调用init_dissection()函数初始化后续解析工作中要用到的所有数据。init_dissection()的初始化工作基本上与cleanup_dissection()清空的内容一致。
  • 这些工作完成后,设置capture_file信息并返回到main()函数。

2. 设置时间精度

3. static int load_cap_file(capture_file *cf, char *save_file, int out_file_type, gboolean out_file_name_res, int max_packet_count, gint64 max_byte_count)函数加载给定的pcap文件。

load_cap_file()函数完成的具体工作如下:

a) 如果save_file(保存捕获的数据包信息)参数不为空,则调用wtap_dump_open()函数获取wtap_dump结构体。

b) 调用gboolean wtap_read(wtap *wth, int *err, gchar **err_info, gint64 *data_offset)函数读取给定pcap文件数据。

c) 调用wtap结构中的subtype_read指向的函数,即个字节的包头部分,具体读取16字节包头的工作由libpcap_read_header()函数调用file_read()完成。

d) 如果libpcap_read_header()操作正确,成功返回读取的数据包字节数,则libpcap_read()函数调整文件指针偏移量,然后调用pcap_process_pseudo_header()函数处理该数据包的pseudo_header信息,主要是确定FCS的长度。(pseudo翻译成"虚假的,假冒的",可能是有些数据包有些假冒包头吧,不是很了解!)然后返回pseudo_header的长度。

e) Libpcap_reader()函数根据返回的pseudo_header长度,调整数据包的实际长度,以及文件的偏移量,具体操作是:

orig_size -= phdr_len; //phdr_len is the length of pseudo_header.

packet_size -= phdr_len;

wth->data_offset += phdr_len;

f) Libpcap_read()函数调用buffer_assure_space()函数确保wtap结构中的frame_buffer中分配的空间足够容纳正在读取的数据包的大小,如果不够,则需要把frame_buffer中已经用掉的空间中存放的内容移到frame_buffer的前面,然后在分配1024个字节给frame_buffer,并返回。

g) Libpcap_read()函数调用libpcap_read_rec_data()函数读取改数据包的数据部分,具体的读取工作同样由file_read()函数完成。

h) 调整文件偏移量,更新timestamp,调用pcap_read_post_process()函数,根据wtap_encap值,确定pseudo_header的eth.fcs_len的大小。

i) Libpcap_read()工作完成,返回到wtap_read(),如果libpcap_read()返回true,wtap_read()也返回true。

j) 调用static gboolean process_packet(capture_file *cf, gint64 offset, const struct wtap_pkthdr *whdr, union wtap_pseudo_header *pseudo_header, const guchar *pd, gboolean filtering_tap_listeners, guint tap_flags)函数处理前面读取的数据包内容。具体操作如下:

i. Process_packet()函数调用frame_data_init()函数初始化该数据包的帧数据结构,包括记录数据包头信息等。如果需要解析数据包,在判断是否需要创建协议树(打印协议详细信息),接着调用epan_dissect_t* epan_dissect_init(epan_dissect_t *edt, const gboolean create_proto_tree, const gboolean proto_tree_visible)函数完成解析前的初始化工作。在epan_dissect_init()函数中,如果create_proto_tree为真,就调用protp_tree_create_root()函数创建协议树的根节点,并赋值为edt->tree,并且,调用proto_tree_set_visible()设置该协议树的可见性为proto_tree_visible参数的值。如果create_proto_tree为假,则edt->tree=NULL;返回edt。至此,epan_dissect_init()的工作完成

ii. 接着,判断是否设置有读过滤器,即capture_file结构体的rfnode是否为空。如果不为空,就调用epan_dissect_prime_dfilter(&edt, cf->rfnode)进行处理(具体处理过程还没跟踪调试!)。

iii. 调用void col_custom_prime_edt(epan_dissect_t *edt, column_info *cinfo)函数(这个函数具体任务不确定)。如果没有用户自定义的打印内容,则直接返回;否则要根据用户设定,进行一些操作。

iv. 调用void tap_queue_init(epan_dissect_t *edt)函数初始化tap queue。

v. 调用frame_data_set_before_dissect()函数进行解析前的帧数据设置操作。具体内容包括:1)设置第一个包和前一个包的时间戳。如果first_ts未设置,则表示当前的包是第一个数据包,则把first_ts设为第一个数据包的时间;如果prev_cap_ts未设置,也表明当前的数据包是第一个数据包,则也把它设为第一个包的时间,即frame_data结构体的abs_ts属性。2)计算当前数据包接收时间与第一个数据包接收时间之差。3)计算前一个数据包已经被打印的数据包的接收时间与当前数据包的接收时间之差。如果前一个被打印的数据包的接收时间没有设置,则表示在当前数据包之前,还没有数据包被打印。这个时候,就把前一个被打印数据包的接收时间设为0;否则计算这个差值。4)计算前一个数据包的捕捉时间与当前数据包的捕捉时间之差,并保存到frame_date的del_cap_ts属性中。5)以上4步完成之后,把前一个数据包的捕捉时间设为当前数据包的捕捉时间, 返回到process_packet()。

vi. 调用void epan_dissect_run(epan_dissect_t *edt, void* pseudo_header, const guint8* data, frame_data *fd, column_info *cinfo)函数开始真正的解析工作。该函数的处理过程大致如下所示:

a) 调用ep_free_all()函数释放前一个数据包中的解析工作中分配的所有内存。

b) 调用void dissect_packet(epan_dissect_t *edt, union wtap_pseudo_header *pseudo_header, const guchar *pd, frame_data *fd, column_info *cinfo)函数进行解析。具体操作为:

1)如果column_info不为空,则调用col_init(column_info *) 函数初始化该结构体。

2)设置edt->pi的值。如下所示:

edt->pi.current_proto = "<Missing Protocol Name>";

edt->pi.cinfo = cinfo;

edt->pi.fd = fd;

edt->pi.pseudo_header = pseudo_header;

edt->pi.dl_src.type = AT_NONE;

edt->pi.dl_dst.type = AT_NONE;

edt->pi.net_src.type = AT_NONE;

edt->pi.net_dst.type = AT_NONE;

edt->pi.src.type = AT_NONE;

edt->pi.dst.type = AT_NONE;

edt->pi.ctype = CT_NONE;

edt->pi.noreassembly_reason = "";

edt->pi.ptype = PT_NONE;

edt->pi.p2p_dir = P2P_DIR_UNKNOWN;

edt->pi.dcetransporttype = -1;

edt->pi.annex_a_used = MTP2_ANNEX_A_USED_UNKNOWN;

edt->pi.dcerpc_procedure_name="";

edt->pi.link_dir = LINK_DIR_UNKNOWN;

3)调用tvbuff_t* tvb_new_real_data(const guint8* data, const guint  length, const gint reported_length),根据数据包长度,分配一个tvbuff_t的数据结构空间,并调用tvb_set_real_data_no_exceptions(tvb, data, length, reported_length)函数,为刚申请的tvbuff_t空间赋值,然后返回其指针。

4)调用void add_new_data_source(packet_info *pinfo, tvbuff_t *tvb, const char *name)函数,申请一片data_source结构的空间,并把步骤3)中得到的tvbuff_t指针赋给data_source的tvb属性,最后把这个data_source空间的指针添加到data_source结构体的列表中。

5)调用int call_dissector(dissector_handle_t handle, tvbuff_t *tvb,packet_info *pinfo, proto_tree *tree)函数解析当前数据包的数据。该函数首先根据给定的handle(frame_handle)进行解析,如果失败,再利用data_handle进行解析。不管是用frame_handle还是用data_handle,都是通过调用static int call_dissector_work (dissector_handle_t handle, tvbuff_t *tvb, packet_info *pinfo_arg, proto_tree *tree, gboolean add_proto_name)函数完成(该函数又调用call_dissector_through_handle()函数完成)。

vii. 调用tap_push_tapped_queue(&edt)函数(该函数具体作用不清楚!)。

viii. 如果cf->rfcode不为空,则调用dfilter_applu_edt(cf->rfcode, &edt)读过滤函数(具体操作还未跟踪调试!)。

ix. 调用void frame_data_set_after_dissect(frame_data *fdata, guint32 *cum_bytes, nstime_t *prev_dis_ts)函数为解析后的帧数据进行必要的设置,包括记录总的数据包字节数,以及设置上一个包的显示时间prev_dis_ts为当前解析包的时间。

x. 调用static gboolean print_packet(capture_file *cf, epan_dissect_t *edt)函数打印刚才被解析的数据包信息。Print_packet()函数的处理过程大致为:

a) 判断是否需要打印协议树中的信息:如果verbose为真,则表示需要打印协议树种的内容(这部分的处理流程还未具体调试!);否则调用epan_dissect_fill_in_columns()函数填充column信息(主要由col_fill_in_frame_data()函数完成具体的填充任务)。

b) 根据output_action的值,打印刚才解析的数据包信息。调用print_columns(capture_file* cf)函数打印列信息。如果需要打印十六进制信息,则再调用print_hex_data(print_stream,edt)打印十六进制信息。(打印列信息,就只需要capture_file,打印十六进制信息则需要edt,考虑下次调试时,让其打印十六进制信息,看在不创建协议树的情况下,这两种打印有什么不同,为什么需要的参数不一样。到目前为止,个人感觉capture_file中记录的信息要比edt中多很多,而且capture_file结构体中有一个edt属性,这样的定义感觉有些冗余!)

xi. 如果do_dissection为真,表示已经完成解析工作,这时调用epan_dissect_cleanup(&edt)完成下列清空工作:

a) 调用free_data_sources(&edt->pi) 清空data source列表;

b) 调用tvb_free_chain(edt->tvb) 清空tvb创建的所有内存空间;

c) 如果edt->tree不为空,再调用proto_tree_free(edt->tree)释放解析过程中创建的协议树空间。

xii. Epan_dissect_cleanup()函数返回后,再调用frame_data_cleanup(frame_data*)函数完成下列清空工作:

a) 如果fdata->pfd不为空,则释放该列表;

b) 把fdata->pfd置为NULL,并返回。

k) process_packet()函数返回到load_cap_file()函数后,如果wtap_dumper* pdp不为空,则调用wtap_dump()函数。Wtap_dump()函数则调用pcapng_dump()(该函数的主要处理过程不清楚)。

l) 刚才读取的数据包已经解析并打印完成,这时跳转到步骤b),读取下一个数据包数据,直到到达给定pcap文件的文件尾。

wireshark源码分析二的更多相关文章

  1. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  2. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  3. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  4. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  5. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  6. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  7. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  8. spring源码分析(二)Aop

    创建日期:2016.08.19 修改日期:2016.08.20-2016.08.21 交流QQ:992591601 参考资料:<spring源码深度解析>.<spring技术内幕&g ...

  9. ConcurrenHashMap源码分析(二)

    本篇博客的目录: 一:put方法源码 二:get方法源码 三:rehash的过程 四:总结 一:put方法的源码 首先,我们来看一下segment内部类中put方法的源码,这个方法它是segment片 ...

随机推荐

  1. js实现多级复选框的交互

    功能介绍   整个复选框是包含多级,可能有父级,可能有子级,在勾选复选框时,要做两种判断: 1要判断它下面有没有子级,有子级将子级的选中状态checked变得和自己一样. 2要判断它是否有父级,有父级 ...

  2. 搭建harbor仓库、LDAP认证

    ldap: 192.168.199.177 c5game.com 宿主机:192.168.199.224 测试客户机:192.168.199.223 安装docker.docker-compose 访 ...

  3. python爬虫----scrapy框架简介和基础应用

    Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中.其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以 ...

  4. 179. Largest Number(INT, String)

    Given a list of non negative integers, arrange them such that they form the largest number. For exam ...

  5. java深拷贝与浅拷贝

    1.调用Object类的clone方法必须实现Cloneable接口,clone属于浅拷贝. 2.可以通过java的反序列化机制进行深拷贝. 3.可以直接用apache提供的一些包进行深拷贝和浅拷贝, ...

  6. 吉哥系列故事——恨7不成妻(数位DP)

    吉哥系列故事——恨7不成妻 http://acm.hdu.edu.cn/showproblem.php?pid=4507 Time Limit: 1000/500 MS (Java/Others)   ...

  7. PHP简单工厂模式、工厂方法模式和抽象工厂模式比较

    PHP工厂模式概念:工厂模式是一种类,它具有为您创建对象的某些方法.您可以使用工厂类创建对象,而不直接使用 new.这样,如果您想要更改所创建的对象类型,只需更改该工厂即可.使用该工厂的所有代码会自动 ...

  8. 速卖通API开发步骤

    http://gw.api.alibaba.com/dev/doc/intl/sys_auth.htm?ns=aliexpress.open#concept 关键字段说明 1.appKey和appSe ...

  9. [LeetCode_96] Unique Binary Search Trees

    题目链接 https://leetcode.com/problems/unique-binary-search-trees/ 题意 计算给定节点数的BST有多少种 思路 递归 相关知识 二叉搜索树(B ...

  10. FortiGate基本信息

    1.介绍 FortiGate是全新的下一代防火墙,在整个硬件架构和系统上面都有新的设计,在性能和功能上面都有了很大提升,具有性能高.接口丰富.功能齐全.安全路由交换一体化.性价比高等优势. Forti ...