ethereal源码分析和编译、使用步骤

 

ethereal源码分析和编译、使用步骤

ethereal是一个抓包软件,是著名的抓包软件wireshark的前身。
下载地址:http://www.ethereal.com/distribution/all-versions/

ethereal依赖GTK+、GLIB、libpcap
GTK+下载地址:ftp://ftp.gtk.org/pub/gtk/v1.2/
GLIB下载地址:ftp://ftp.gtk.org/pub/gtk/v1.2/
libpcap下载地址:http://www.tcpdump.org/

这里我使用的版本是
glib-1.2.9.tar.gz
gtk+-1.2.9.tar.gz
libpcap-1.2.0rc1.tar.gz
ethereal-0.8.10.tar.gz


源码包都放在 /opt 目录下。

【源代码编译】
编译 libpcap

tar -zxvf libpcap-1.2.0rc1.tar.gz
mkdir /usr/local/libpcap-1.2.0
cd libpcap-1.2.0
./configure --prefix=/usr/local/libpcap-1.2.0/
make
make install

//转到 /usr/local/libpcap-1.2.0/include/ 目录,将 pcap 子目录拷贝一份,然后重命名为 net
cd /usr/local/libpcap-1.2.0/include/
cp -R ./pcap ./net

编译GLIB

tar -zxvf glib-1.2.9.tar.gz
mkdir /usr/local/glib-1.2.9
cd glib-1.2.9
./configure --prefix=/usr/local/glib-1.2.9/
make

//编译的时候会报错误 gstrfuncs.c 870: error: expected ')' before string constant ,这时候需要修改源代码 gstrfuncs.c,
将 g_warning 函数调用注释掉,以下遇到相同的错误都进行相同的修改

make install

编译GTK+

tar -zxvf gtk+-1.2.9.tar.gz
mkdir /usr/local/gtk+-1.2.9
cd gtk+-1.2.9
./configure --prefix=/usr/local/gtk+-1.2.9/ --with-glib-prefix=/usr/local/glib-1.2.9/ --disable-glibtest
make
make install

编译ethereal

tar -zxvf ethereal-0.8.10.tar.gz
mkdir /usr/local/ethereal-0.8.10
cd ethereal-0.8.10

//修改一下 configure 文件
vim ./configure
定位到 #Evidently, some systems have pcap.h, etc. in */include/pcap 这一段代码,
将 for pcap_dir in /usr/include/pcap /usr/local/include/pcap 中的目录替换成我们自己的libpcap目录
for pcap_dir in /usr/local/libpcap-1.2.0/include

./configure --prefix=/usr/local/ethereal-0.8.10/ --with-glib-prefix=/usr/local/glib-1.2.9/ --with-gtk-prefix=/usr/local/gtk+-1.2.9/ --includedir=/usr/local/libpcap-1.2.0/include/ --libdir=/usr/local/libpcap-1.2.0/lib/ --disable-gtktest --disable-glibtest

make

//编译时遇到报错:
capture.c:112 错误:对'sync_pipe'的静态声明出现在非静态声明之后
captrue.h:35: 错误:'sync_pipe'的上一个声明在此
修改 capture.c 源文件,将 static int sync_pipe[2]; 的关键字 static 去掉,以下遇到相同的错误都进行相同的修改

make install


【ethereal使用】
使用前首先 export libpcap,glib,GTK+

export LD_LIBRARY_PATH=/usr/local/libpcap-1.2.0/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/glib-1.2.9/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/gtk+-1.2.9/lib:$LD_LIBRARY_PATH

ethereal-0.8.10 默认使用的字体 -*-lucidatypewriter-medium-r-normal-*-*-120-*-*-*-*-iso8859-1 在 gtk+-1.2.9 上面载入失败,
使用 xlsfonts 程序查看可用的字体,发现 fixed 字体可以成功载入

cd /usr/local/ethereal-0.8.10/bin/
./ethereal -m fixed -b fixed

 

 

 

 

 

 

【ethereal源码分析】


ethereal 流程
main 函数会根据参数选择调用 do_capture 还是 capture ,do_capture 在内部调用 capture 。

capture 使用libpcap捕获网卡数据包。
dfilter_compile显示过滤器编译函数。
dfilter_apply把编译好的显示过滤器函数应用到协议树。
follow_stream_cb把捕获到的数据包按照显示过滤器进行默认的 ASCII 格式的输出。

 


重要的数据结构

 

 

typedef struct _capture_file {
  FILE_T       fh;        /* 捕获到的数据包文件句柄 */
  int          filed;     /* 捕获到的数据包文件描述符 */
  gchar       *filename;  /* 捕获到的数据包文件名称 */
  gboolean     is_tempfile; /* 是一个捕获到的数据包文件还是一个临时文件? */
  gboolean     user_saved;/* 如果捕获到的数据包文件是一个临时的,它已经被用户保存了吗? */
  long         f_len;     /* 数据包文件的长度 */
  guint16      cd_t;      /* 数据包文件的文件类型 */
  int          lnk_t;     /* 捕获数据包的链路层类型 */
  guint32      vers;      /* 版本 */
  guint32      count;     /* 数据包的个数 */
  gfloat       unfiltered_count; /* 未过滤的个数,供显示过滤器进度条使用 */
  guint32      drops;     /* 丢失的数据包 */
  guint32      esec;      /* 经过的秒数 */
  guint32      eusec;     /* 经过的毫秒数 */
  guint32      snap;      /* 捕获的数据包的长度 */
  gboolean     update_progbar; /* 当我们应该更新进度条的时候设为真 */
  long         progbar_quantum; /* 每一次进度条更新的时候读取的字节数 */
  long         progbar_nextstep; /* 下一个应该更新进度条的时刻 */
  gchar       *iface;     /* 网络接口 */
  gchar       *save_file; /* 用户保存捕获到的数据包的文件名称 */
  int          save_file_fd; /* 保存捕获到的数据包文件的描述符 */
  wtap        *wth;       /* Wiretap session */
  dfilter     *rfcode;    /* 已经编译过的读取过滤器程序 */ 
  gchar       *dfilter;   /* 显示过滤器字符串 */
  colfilter   *colors;	  /* 着色数据包窗口的颜色 */
  dfilter     *dfcode;    /* 已经编译过的显示过滤器程序 */ 
#ifdef HAVE_LIBPCAP
  gchar       *cfilter;   /* libpcap使用的捕获数据包过滤器表达式 */
  bpf_prog     fcode;     /* 已经编译过的libpcap捕获过滤器程序 */
#endif
  gchar       *sfilter;   /* 查找过滤器字符串 */
  gboolean     sbackward;  /* 向后查找的时候为真,向前的时候为假 */
  guint8       pd[WTAP_MAX_PACKET_SIZE];  /* 数据包数据 */
  frame_data  *plist;     /* 数据包列表 */
  frame_data  *plist_end; /* 数据包链表中最后一个帧 */
  frame_data  *first_displayed; /* 第一个显示的帧 */
  frame_data  *last_displayed;  /* 最后一个显示的帧 */
  column_info  cinfo;    /* 列格式信息 */
  frame_data  *current_frame;  /* 当前帧的帧数据 */
  int          current_row;    /* 当前帧的行 */
  gboolean     current_frame_is_selected; /* 如果当前帧被选中,为真 */
  proto_tree  *protocol_tree; /* 当前选中的数据包的协议树 */
  FILE        *print_fh;  /* 我们将要打印的文件句柄 */
} capture_file;



typedef struct _frame_data {
  struct _frame_data *next; /* 链表的下一个结点 */
  struct _frame_data *prev; /* 链表的前一个结点 */
  guint32      num;       /* 帧序号 */
  guint32      pkt_len;   /* 包长度 */
  guint32      cap_len;   /* 实际捕获到的数据的长度 */
  guint32      rel_secs;  /* 相对的秒数 */
  guint32      rel_usecs; /* 相对的毫秒数 */
  guint32      abs_secs;  /* 绝对的秒数 */
  guint32      abs_usecs; /* 绝对的毫秒数 */
  guint32      del_secs;  /* 差量秒数 */
  guint32      del_usecs; /* 差量毫秒数 */
  long         file_off;  /* 文件偏移量 */
  column_info *cinfo;     /* 列格式信息 */
  gint         row;       /* 这个数据包的行序号 */
  int          lnk_t;     /* 每一个数据包的封装类型 */
  gboolean     passed_dfilter; /* 真 = 显示,假 = 不显示 */
  char_enc     encoding;  /* 字符编码(ASCII,EBCDIC ...) */
  union pseudo_header pseudo_header; /* "pseudo-header" from wiretap */
} frame_data;



typedef struct GNode proto_tree;



typedef struct _packet_info {
  int     len;
  int     captured_len;
  address dl_src;		/* MAC 源地址*/
  address dl_dst;		/* MAC 目的地址 */
  address net_src;		/* IP源地址 */
  address net_dst;		/* IP目的地址 */
  address src;			/* 源地址*/
  address dst;			/* 目的地址*/
  guint32 ipproto;
  port_type ptype;		/* 下面2个端口的类型 */
  guint32 srcport;		/* 源端口 */
  guint32 destport;		/* 目的端口 */
  guint32 match_port;
  int     iplen;
  int     iphdrlen;
} packet_info;



typedef struct _address {
  address_type  type;		/* 地址类型 */
  int           len;		/* 地址的长度(按字节算) */
  const guint8 *data;		/* 组成地址的数据 */
} address;


 

当打开菜单 “Capture” -> “Options”时,系统过程
capture_prep_cb(GtkWidget *w, gpointer d)
    |_ if_list = get_interface_list();    //获取可用的网卡
    |_    //显示各种配置控件
    |_ gtk_signal_connect(GTK_OBJECT(ok_bt), "clicked",
       GTK_SIGNAL_FUNC(capture_prep_ok_cb), GTK_OBJECT(cap_open_w));   //注册信号,当点击“OK”按钮时候,调用capture_prep_ok_cb函数




capture_prep_ok_cb(GtkWidget *ok_bt, gpointer parent_w)
    |_ if_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(if_cb)->entry)));    //获取指定的网卡
    |_ filter_text = gtk_entry_get_text(GTK_ENTRY(filter_te));     //获取libpcap过滤器表达式
    |_ cf.cfilter = g_strdup(filter_text);     //将当前libpcap过滤器表达式填充到 cf 中
    |_ save_file = gtk_entry_get_text(GTK_ENTRY(file_te));     //获取将要保存的文件路径
    |_ do_capture(save_file);    //开始捕获数据




do_capture(char *capfile_name)
   |_ if (capfile_name != NULL) {cf.save_file_fd = open(capfile_name, O_RDWR|O_TRUNC|O_CREAT, 0600);}    //如果capfile_name不为空,打开给出路径的文件,保存文件句柄
   |_ else {cf.save_file_fd = create_tempfile(tmpname, sizeof tmpname, "ether");}   //创建一个临时文件,然后保存文件句柄
   |_ close_cap_file(&cf, info_bar);    //重置 cf 结构体有关字段
   |_ if (sync_mode)    //如果是同步模式
      fork()    //创建子进程
      execlp(ethereal_path,      //以合适的参数启动 ethereal 
      i = read(sync_pipe[0], &c, 1);   //父进程读取子进程写入管道的内容
      i == 0 ?  //子进程异常退出,关闭管道,删除文件,给出提示,返回
      c == ;     break;   //跳出循环
      if (!isdigit(c))    //子进程处理失败,父进程关闭管道,删除文件,给出提示,返回
      byte_count = byte_count*10 + c - '0';   //计算 byte_count
   |_ if (byte_count == 0) {err = start_tail_cap_file(cf.save_file, is_tempfile, &cf);}    // 成功,打开记录好的文件
      else {i = read(sync_pipe[0], msg, byte_count);}   //失败了,接收子进程发送的错误消息,显示出来
   |_ else    //  不是同步模式
      capture_succeeded = capture();    //调用 capture 捕获数据包
   |_ if (capture_succeeded)   //捕获成功
      open_cap_file(cf.save_file, is_tempfile, &cf)      //打开捕获到的数据文件
      read_cap_file(&cf);     //读取文件
         |_ success = wtap_loop(cf->wth, 0, wtap_dispatch_cb, (u_char *) cf, &err);   //进入wtap_loop,每获得一个数据包,调用wtap_dispatch_cb 函数
         |_  cf->lnk_t = wtap_file_encap(cf->wth);  //设置wtap描述符封装类型
         |_  wtap_close(cf->wth); //关闭wtap描述符




wtap_dispatch_cb(u_char *user, const struct wtap_pkthdr *phdr, int offset,
  const u_char *buf) ;  //wtap回调函数,每当wtap获得一个数据包,调用这个函数
   |_ fdata = (frame_data *) g_malloc(sizeof(frame_data));    //申请内存用来保存一帧的数据
   |_ protocol_tree = proto_tree_create_root(); //创建协议树
   |_ dissect_packet(buf, fdata, protocol_tree); //分析一个数据包
          |_ proto_tree_add_item_format ()  //安照协议树格式添加树
          |_ fh_tree = proto_item_add_subtree(ti, ett_frame);    //添加子树
          |_ proto_tree_add_item(fh_tree, hf_frame_arrival_time,    //添加帧到达时间
          |_ proto_tree_add_item(fh_tree, hf_frame_time_delta,   //添加帧和上一个包到达时间的间隔
          |_ proto_tree_add_item(fh_tree, hf_frame_number,  //添加帧的序号
          |_ proto_tree_add_item_format(fh_tree, hf_frame_packet_len,   //添加帧的包长度
          |_ proto_tree_add_item_format(fh_tree, hf_frame_capture_len, //添加实际捕获到的包的长度
          |_ blank_packetinfo();  //置空数据包信息这个数据结构的有关字段
          |_ case WTAP_ENCAP_ETHERNET :dissect_eth(pd, 0, fd, tree);    //根据这个数据包的封装类型,进行解析
                                        |_ SET_ADDRESS(&pi.dl_src, AT_ETHER, 6, &pd[offset+6]);   //设置MAC源地址
                                        |_ SET_ADDRESS(&pi.dl_dst, AT_ETHER, 6, &pd[offset+0]);   //设置MAC目的地址
                                        |_ etype = pntohs(&pd[offset+12]);   //获得以太网数据类型
                                        |_ proto_tree_add_item_format  //安照协议树格式添加树
                                        |_ proto_item_add_subtree  //添加子树
                                        |_ proto_tree_add_item(fh_tree, hf_eth_dst, offset+0, 6, &pd[offset+0]); //添加目的MAC
                                        |_ proto_tree_add_item(fh_tree, hf_eth_src, offset+6, 6, &pd[offset+6]); //添加源MAC
                                        |_ ethertype(etype, offset, pd, fd, tree, fh_tree, hf_eth_type); //根据以太网类型分析数据封包
                                               |_ proto_tree_add_item(fh_tree, item_id, offset - 2, 2, etype); //添加类型
                                               |_ dissect_ip(pd, offset, fd, tree);  //分析这个IP数据封包
   |_ passed = dfilter_apply(cf->rfcode, protocol_tree, cf->pd); //应用显示过滤器
   |_ proto_tree_free(protocol_tree); //释放协议树资源
   |_ cf->plist = fdata;    //将 cf -> plist 指针指向分析后的数据 fdata
   |_ add_packet_to_packet_list(fdata, cf, buf); //将分析的数据添加到数据包列表




当在显示过滤器编辑框里面输入表达式,并按下回车键后,系统过程
filter_activate_cb(GtkWidget *w, gpointer data)
    |_ char *s = gtk_entry_get_text(GTK_ENTRY(w));    //获取编辑框字符串
    |_ filter_packets(&cf, g_strdup(s)   //使用这个过滤表达式过滤数据包,然后重新显示在列表控件上
         |_ dfilter_compile(dftext, &dfcode)    //编译过滤器,其中包含了分析语法的过程
         |_ g_free(cf->dfilter);    //释放 cf 结构体的dfilter字段
         |_ cf->dfilter = dftext;      //重新赋值 cf 结构体的dfilter字段
         |_ dfilter_destroy(cf->dfcode);       //释放 cf 结构体的dfcode字段
         |_ cf->dfcode = dfcode;     //重新赋值 cf 结构体的dfcode字段
         |_ colorize_packets(cf);    //遍历解析到的数据包的链表,将符合规则的数据包显示出来
               |_ conversation_init();    //初始化会话表
               |_ init_all_protocols();   //初始化特定的协议变量
               |_ gtk_clist_freeze(GTK_CLIST(packet_list));    //禁用数据包列表控件
               |_ gtk_clist_clear(GTK_CLIST(packet_list));    //清空数据包列表
               |_ for (fd = cf->plist; fd != NULL; fd = fd->next)     //遍历数据包链表
                  {
                   wtap_seek_read (cf->cd_t, cf->fh, fd->file_off, cf->pd, fd->cap_len);  //读取数据包内容
                   add_packet_to_packet_list(fd, cf, cf->pd);     //添加这个数据包到数据包列表控件
                  }
               |_ gtk_clist_moveto(GTK_CLIST(packet_list), cf->current_row, -1, 0.0, 0.0);   //移动到current_row指定的数据包这一行
               |_ gtk_clist_select_row(GTK_CLIST(packet_list), cf->current_row, -1);    //选中这一行
               |_ gtk_clist_thaw(GTK_CLIST(packet_list));    //启用数据包列表控件



当鼠标选中数据列中的一行时,调用
packet_list_select_cb(GtkWidget *w, gint row, gint col, gpointer evt)
    |_ blank_packetinfo(); //置空数据包信息这个数据结构的有关字段
    |_ select_packet(&cf, row); //选择这一行的数据包
         |_ wtap_seek_read();  //获取这一帧的数据
         |_ proto_tree_create_root(); //创建协议树
         |_ dissect_packet(); 分析这一帧的数据包




当对选中的TCP数据包进行“Follow Tcp Stream”操作时
follow_stream_cb (GtkWidget *w, gpointer data ) 
  |_ tmp_fd = create_tempfile( filename1, sizeof filename1, "follow");    //创建临时文件 
  |_ data_out_file = fdopen( tmp_fd, "w" );     //打开这个临时文件 
  |_ reset_tcp_reassembly();    //重置 TCP 组装 
  |_ follow_filter = build_follow_filter( &pi );     //生成 follow 过滤表达式 
  |_ filter_packets(&cf, follow_filter);     //过滤数据包
      |_ dfilter_compile(dftext, &dfcode);   //编译过滤器
          |_ df = dfilter_new();     //创建一个新的过滤器
          |_ dfilter_scanner_text(dfilter_text);     //告诉扫描器使用过滤表达式字符串作为输入
          |_ retval = dfilter_parse();     //分析语法
          |_ dfilter_scanner_cleanup();     //清理扫描器规则
      |_ colorize_packets(cf);     //着色过滤后的数据包
  |_ follow_load_text(text, filename1, TRUE);     //将分析后的数据显示到 text控件上面


 

你可能感兴趣的:(ethereal源码分析和编译、使用步骤)