栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

capture

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

capture

wireshark中capture_session结构代表了一个捕捉过程。默认情况下,当用户选择网卡启动wireshark的捕获数据帧功能后会启动一个dumpcap子进程,这个子进程在管道上透过一个自定义的协议传递消息给wireshark主程序,这些消息包括了告知主进程捕获文件所在的路径的通知、捕获工作状态变化的通知(例如暂停、停止捕获、发生错误)、新捕获到数据包的通知等等。

(一)初始化捕获会话

在程序启动时调用capture_session_init()函数以初始化其内部基本的成员函数,调用栈如下:

capture_session_init(...)

capture_input_init(capture_session *cap_session, capture_file *cf)

MainWindow::MainWindow(...)

这里传递给capture_input_init()函数的cap_session是一个MainWindow的私有成员,cf是一个全局对象。在capture_session_init()中主要做以下事情:

  • 将cap_session->cf置为capture_input_init参数2传入的全局变量。
  • 初始化捕获子进程的pid为-1
  • 初始化和Pipe有关的fd为-1
  • 当前sesseion状态置为CAPTURE_STOPPED
  • 捕获封包计数设置为0
  • session即将重启的标记设置为FALSE
  • 设置几个回调函数,当对应事件发生的时候调用对应的函数。这几个函数都定义在capture.c中。
    • capture_session.new_file=capture_input_new_file
    • capture_session.new_packets=capture_input_new_packets
    • capture_session.drops=capture_input_drops
    • capture_session.error=capture_input_error
    • capture_session.cfilter_error=capture_input_cfilter_error
    • capture_session.closed=capture_input_closed

(二) 启动捕获(capture_start())

用户选择网卡接口启动捕获时的调用栈如下:

Wireshark.exe!capture_start(...)

Wireshark.exe!MainWindow::startCapture()

在capture_start函数中主要做以下事情:

  • 设置会话状态为“捕获准备中”capture_session.state = CAPTURE_PREPARING;
  • 重置封包计数capture_session.count = 0;
  • 根据capture_options得到临时文件名,并赋予capture_session.cf.source
  • 调用sync_pipe_start,在这个函数中:
    • 以capture_options对象为选项设置dumpcap子进程的命令行参数。
    • 创建和dumpcap子进程通信的pipe
    • 在sync_pipe_start中创建dumpcap子进程。
    • 保存子进程句柄到capture_session.fork_child
    • 保存pipe写fd到capture_session.signal_pipe_write_fd
    • 重置子进程exitcode。capture_session.fork_child_status = 0;
    • 保存capture_options对象到capture_session.capture_opts
    • 初始化capture_session.cap_data_info让它指向MainWindow::info_data_,这个数据结构应该是用于统计数据包相关功能的。
    • 调用pipe_input_set_handler,设置管道读取事件的处理函数为sync_pipe_input_cb,以后当有消息从管道获取到的时候将调用它处理。
  • 使用wtap_rec_init()函数初始化capture_session.rec,这个缓冲区会在读例程capture_session.new_packets中用到。(用户读取数据包的元数据,后续内容详细说明wtap_rec结构)
  • 使用ws_buffer_init()初始化capture_session.buf,并为它申请1514个字节(数据链路帧MTU1500字节+14字节?),这个缓冲区用于存放数据包,会在读例程capture_session.new_packets中用到。

(三)打开数据捕获文件

在执行启动捕获后(在dumpcap子进程启动后),子进程将通过管道发送一条SP_FILE消息(处理过程详情见sync_pipe_input_cb()函数),这条消息携带一个数据捕获文件名,dumpcap子进程会按照约定的格式往这个文件里写入数据包。在处理这个消息时会调用capture_session.new_file方法初始化capture_file结构。

在capture_session.new_file中(通过capture_input_new_file()实现)做以下这些事情:

  • 检查session状态,确保为CAPTURE_PREPARING或CAPTURE_RUNNING
  • 判断capture_opts确认是否为临时文件模式,wireshark程序如果没有设置保存到文件这里就是临时文件模式(通常为临时文件模式)。并根据它的值设置capture_session.cf.is_tempfile。
  • 根据管道发来的文件名,设置capture_opts.save_file的值。
  • 执行cf_open,打开临时文件并设置session从中读取。
    • 执行wtap_open_offline()函数,打开文件,alloc和初始化一个 wtap 结构。在这个过程中wiretap库将确认文件的具体类型,并在wtap结构中设置对应的处理函数。(例如对于默认的pacpng捕获程序而言,这些值分别是pcapng_read、pcapng_seek_read、pcapng_close,见下方"关于wtap_open_offline"一段)
    • cf_close()关闭capture_file,重置一堆文件有关的状态。
    • 使用wtap_rec_init()初始化记录元数据(capture_file.rec, wtap_rec类型)。
    • 使用ws_buffer_init()初始化capture_file.buf
    • 将capture_file设置为FILE_READ_IN_PROGRESS状态,表示我们即将开始读取文件
    • wtap_open_offline()返回的wtap结构指针赋予capture_file.provider.wth
    • 初始化capture_file的f_datalen、filename、is_tempfile、unsaved_changes、computed_elapsed、cd_t、open_type等等成员
    • 初始化capture_file.provider.frames,在这里会存放文件读取出来的所有数据帧(这是一棵四级radix tree,每隔节点都有1024个子节点,所以树的第一层、第二层、第三层分别可以放1024、1024*1024、1024*1024*1024个节点)
    • 创建新的epan(数据包解析模块句柄),用capture_file作为参数。
    • 通知UI的数据包列表重绘。
    • 设置wtap_set_cb_new_ipv4、wtap_set_cb_new_ipv6、wtap_set_cb_new_secrets等名字解析的额外回调(仅pcapng适用)。
  • 设置session状态为CAPTURE_RUNNING
  • 调用capture_callback_invoke()执行UI设置的callback,以更新对应的UI。

关于wtap_open_offline

这个函数打开一个文件,alloc和准备一个 wtap 结构。如果“do_random”为TRUE,则打开文件两次; 第二个 open 允许应用程序执行随机访问 I/O而无需移动顺序 I/O 的查找偏移量,在选中数据包时用随机 I/O 可以显示数据包的协议树。Wireshark 使用该偏移量以便对作为新数据包写入的捕获文件执行顺序 I/O。

  • alloc一个wtap
  • 执行file_open,将结果赋予wtap.fh
  • 如果允许随机读取(do_random=TRUE),再执行一次file_open,将结果赋予wtap.random_fh
  • 初始化wtap的部分成员
    • 设置时间戳精度file_tsprec=WTAP_TSPREC_USEC
    • 初始化shb_hdrs链表,为它添加第一个wtap_block。
    • 设置pathname为dumpcap创建的数据捕获文件路径。
    • 初始化ispipe、file_encap、subtype_sequential_close、subtype_close、priv、wslua_data等的值为默认值。
    • 初始化interface_data,它是包含接口列表的数组。pcapng_open 和 erf_open 需要这个(和 libpcap_open 用于ERF封装类型)。 总是在这里初始化它可以节省以后检查 NULL ptr 的时间。
    • 初始化next_interface_data,它是wtap_get_next_interface_description() 将返回的下一个接口数据。
    • 如果允许随机读取,则调用file_set_random_access()以设置文件随机读取的功能。
  • 执行循环尝试遍历open_routines链表中所有的open_routine看看哪个函数可以打开这个文件,用这种方式确定捕获文件的类型。这里open_routines是从open_info_base[]数组初始化而来(见init_open_routines),在open_info_base定义了各种类型数据捕获文件的特征(典型如幻数、拓展名)。同时按照libwiretap库的约定,在open_routine中还将设置wtap.subtype_read(顺序读)、wtap.subtype_seek_read(随机读)、wtap.subtype_close等特定文件格式有关的处理函数(例如对于wireshark默认使用的pacpng捕获文件格式而言,这些值分别是pcapng_read()pcapng_seek_read()pcapng_close(),这样当执行wtap.subtype_read时实际执行的是pcapng文件格式对应的pcapng_read()例程)。

(四)从文件中读取数据包

wireshark数据的收集默认是通过pcapng(pcap next gen)库,通过一个管道,当子进程dumpcap有新捕获带数据(并保存到临时文件)后,将通知wireshark主模块。最终主进程调用capture.c文件中的capture_input_new_packets()函数去读取和解析这些数据包,定义如下:


static void capture_input_new_packets(capture_session *cap_session, int to_read);

第一个参数是捕包会话,第二个参数是有多少个新的数据包到了需要读取。在整个函数中,主要起到读取和解析作用的是cf_continue_tail()函数:

if(capture_opts->real_time_mode) 
{
     
    switch (cf_continue_tail(
                (capture_file *)cap_session->cf, 
                to_read,
                &cap_session->rec, 
                &cap_session->buf, &err)) 
    {
        ...                                 
    }
}

其声明如下:

cf_read_status_t cf_continue_tail(capture_file *cf, volatile int to_read,
                                  wtap_rec *rec, Buffer *buf, int *err);

这个函数定义在file.c文件中。

这个函数关键的几个地方:

 // 省略部分代码......
  epan_dissect_t    edt;
  // 省略部分代码......
  
  epan_dissect_init(&edt, cf->epan, create_proto_tree, FALSE);

  TRY {
    gint64 data_offset = 0;
    column_info *cinfo;

    
    cinfo = (tap_flags & TL_REQUIRES_COLUMNS) ? &cf->cinfo : NULL;
    // 在下面这里循环读取to_read个数据包
    while (to_read != 0) {
      wtap_cleareof(cf->provider.wth);
      // 从临时读取这个数据包到rec和buf
      if (!wtap_read(cf->provider.wth, rec, buf, err, &err_info,
                     &data_offset)) {
        break;
      }
      if (cf->state == FILE_READ_ABORTED) {
        
        break;
      }
      // 读取封包内容,并使用epan_dissect_t解析(并通过dfcode过滤)
      if (read_record(cf, rec, buf, dfcode, &edt, cinfo, data_offset)) {
        newly_displayed_packets++;
      }
      to_read--;
    }
  }
  CATCH(OutOfMemoryError) {
      ...... //错误处理,如严重就退出程序。
  }
  ENDTRY;
  // ......省略部分代码
  // 解析完成后
  epan_dissect_cleanup(&edt);

这里有两个关键的调用wtap_read()read_record()

wtap_read()的作用如下:

  • 初始化一个wtap_rec结构,然后从数据捕获文件中读取数据包的元数据到wtap_rec结构中,元数据指的是记录数据包在文件中的偏移,数据包的长度等信息。
  • 将数据包从文件中读取到Buffer结构中。Buffer.data指向了实际的数据。

read_record()的作用:

  • 如果程序设置了过滤器,则尝试过滤这个数据包,如果满足过滤条件直接pass并返回FALSE。
  • 根据wtap_rec结构构建frame_data,然后添加到capture_file.provider.frames这颗基数树中(基数为1024),这里是唯一保存帧的地方。注意frame_data中不包含数据包,很明显如果每个包都保存下来很快内存就会耗尽,所以capture_file.provider.frames这颗树仅起到一个索引的作用。
  • 将数据包Buffer和wtap_rec等传递给epan_dissect_run_with_taps执行,解析这个帧。
  • 调用packet_list_append将这个帧添加到数据包列表中(添加到PacketListModel对象中,这样UI就可以将他绘制出来,见PacketListModel::appendPacket())。

关于元数据wtap_rec,定义如下:

typedef struct {
    guint     rec_type;          
    guint32   presence_flags;    
    nstime_t  ts;                
    int       tsprec;            
    union {
        wtap_packet_header packet_header;  // 对于REC_TYPE_PACKET有效
        wtap_ft_specific_header ft_specific_header; //暂不用管
        wtap_syscall_header syscall_header; //暂不用管
        wtap_systemd_journal_export_header systemd_journal_export_header; //暂不用管
        wtap_custom_block_header custom_block_header; //暂不用管
    } rec_header;

    wtap_block_t block ;         
    gboolean block_was_modified; 

    
    Buffer    options_buf;       
} wtap_rec

typedef struct {
    guint32   caplen;           
    guint32   len;              
    int       pkt_encap;        
    guint32   interface_id;     

    union wtap_pseudo_header  pseudo_header; 
} wtap_packet_header;


struct wtap_block
{
    wtap_blocktype_t* info;  // 见下面的描述
    void* mandatory_data;
    GArray* options;
    gint ref_count;
#ifdef DEBUG_COUNT_REFS
    guint id;
#endif
};


typedef struct {
    wtap_block_type_t block_type;    
    const char *name;                
    const char *description;         
    wtap_block_create_func create;
    wtap_mand_free_func free_mand;
    wtap_mand_copy_func copy_mand;
    GHashTable *options;             
} wtap_blocktype_t;

关于frame_data定义如下:

typedef struct _frame_data {
  guint32      num;          
  guint32      pkt_len;      
  guint32      cap_len;      
  guint32      cum_bytes;    
  gint64       file_off;     
  
  GSList      *pfd;          
  const struct _color_filter *color_filter;  
  guint16      subnum;       
  
  unsigned int passed_dfilter   : 1; 
  unsigned int dependent_of_displayed : 1; 
  
  unsigned int encoding         : 1; 
  unsigned int visited          : 1; 
  unsigned int marked           : 1; 
  unsigned int ref_time         : 1; 
  unsigned int ignored          : 1; 
  unsigned int has_ts           : 1; 
  unsigned int has_phdr_block   : 1; 
  unsigned int has_modified_block : 1; 
  unsigned int need_colorize    : 1; 
  unsigned int tsprec           : 4; 
  nstime_t     abs_ts;       
  nstime_t     shift_offset; 
  guint32      frame_ref_num; 
  guint32      prev_dis_num; 
} frame_data;

当一个frame_data需要展示到界面的时候,程序会先调用cf_read_record_no_alert()或者cf_read_record()这两个函数,它们将根据该frame_data取得元数据wtap_rec和数据包Buffer,然后执行epan_dissect_run()函数解析数据包协议树,这部分的逻辑在PacketListRecord::dissect()这个成员函数中。

默认情况下主界面列表展示【序号(No.)、时间(Time)、源地址(Source)、目标地址(Destination)、协议(Protocol)、长度(Length)、简要信息(Info)】。这几项中序号、时间、长度已经在frame_data中,其余几个项都需要执行epan_dissect_run()函数后才可以取到。

总结一下:

  • wireshark通过子进程抓取数据包,并通过一个管道接收子进程的通知。
  • 当收取到有新的数据包时,读取子进程写入数据包的那个临时文件。
  • wireshark通过wiretap库读取数据包,wiretap库可以读取各种格式的数据捕获文件,wireshark默认使用的是pcapng格式。
  • wiretap库通过wtap_read()读取一个数据包,同时返回元数据(wtap_rec)和数据包数据(Buffer)。
  • 展示在界面数据包列表上的数据基础类型为frame_data结构。通过read_record()将一个wtap_rec重新封装成frame_data,过滤器在这里就已经生效,如果数据包为需要过滤的类型则frame_data在这里就被直接丢弃,否则将添加到capture_file.provider.frames这颗基数树中,并且插入到树控件的模型中。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/307450.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号