使用 libpcap 分析网络报文 (2)

这里打算写写报文分析接口的设计。 一般来说,以太网的报文格式为:

+------------+-------------------+-----+
| eth header | ip/arp/... header | ... |
+------------+-------------------+-----+

例如手机上的一次网页请求的报文格式可能像下面这样:

+-----+----+-----+-----+----+-----+--------------+
| eth | ip | udp | gtp | ip | tcp | http request |
+-----+----+-----+-----+----+-----+--------------+

不同的应用对于不同协议的内容可能会有不同的需求。比方说,一个应用希望抓取 gtp 报头中的 pdp context,以此建立起某个手机号在某段时间内使用的是哪个 ip 的映射,它不关心上层应用的内容;另一个应用希望抓取内层 ip 和该 ip 的 http 的请求地址,它并不关心外层 ip 和 gtp 的内容;还有一个应用根据第一层 ip(或之上的 tcp/udp)指定的协议对数据进行分发,让不同的机器处理不同协议的数据:

           app1      app2     app3
            |         |        |
+------+    |         |        |
| http | <------------+        |
+------+    |         |        |
| tcp  |    |         |        |
+------+    |         |        |
| ip   | <------------+        |
+------+    |                  |
| gtp  | <--+                  |
+------+                       |
| udp  | <---------------------+
+------+                       |
| ip   | <---------------------+
+------+
| eth  |
+------+

程序中有一个全局静态链表 filter_list,保存着所有通过 register_filter() 函数注册的 filter:

struct filter_node {
   struct list_node sibling;
   struct filter_operations* fops;
};

struct filter_operations {
   int (*eth)(struct filter_node*, const struct ether_header*,
         unsigned int level);
   int (*ip)(struct filter_node*, const struct iphdr*,
         unsigned int level);
   int (*tcp)(struct filter_node*, const struct tcphdr*,
         unsigned int level);

   int (*http)(struct filter_node*, const struct httphdr*,
         unsigned int level);

   ......
};

static DEF_LIST_NODE(filter_list);

int register_filter(struct filter_node* node)
{
   list_add_before(&node->sibling, &filter_list);

   return 0;
}

每个 filter 实现对各自需要的协议的操作,然后调用 register_filter() 把自己添加到全局链表中。各个 filter 实现自己的功能并挂到全局链表中,不需要改动主程序和已有 filter 的代码。例如一个简单的打印每层报文内容的 filter:

static struct filter_operations print_ops = {
   .ip      =  print_ip,
   .udp     =  print_udp,
   .gtp     =  print_gtp,
   .tcp     =  print_tcp,
   .http    =  print_http,

   ......
};

static struct filter_node print_filter = {
   .fops    =  &print_ops,
};

static __attribute__((constructor)) void print_filter_register(void)
{
   register_filter(&print_filter);
}

每分析出来一种协议就会遍历 filter_list 中的每个 filter,把解析出来的协议内容传给对应的回调函数。例如捕获到一个 ip 报文:

static void ip_frame_handler(const struct iphdr* ih, int fr_len,
      unsigned int level)
{
   unsigned int len;
   struct list_node *p, *next;
   char *buf = NULL, *inner_pkt;

   ......

   list_for_each_safe (p, next, &filter_list) {
      struct filter_node* node;
      node = list_entry(p, struct filter_node, sibling);
      if (node->fops->ip)
         if (node->fops->ip(node, ih, level) != 0)
            list_move_head(&node->sibling, &abandoned);
   }

   ......

   if (len > 0) {
      switch (ih->protocol) {
         case IPPROTO_UDP:
            udp_frame_handler((struct udphdr*)inner_pkt, len, level + 1);
            break;

         case IPPROTO_TCP:
            tcp_frame_handler((struct tcphdr*)inner_pkt, len, level + 1);
            break;

         default:
            break;
      }
   }

   ......
}

如果某个 filter 在处理完某一层的数据后发现不需要继续往下走,只要返回一个非 0 值,主程序就会把这个 filter 暂时放到一个不被使用的链表 abandoned 中,等到下一个数据包到达时再把 abandoned 中的 filter 放回 filter_list。

主要的结构大概就是这样。这里还有些不足,就是只能针对现有的协议指定不同的操作,如果想增加新的协议要改动主函数的内容。接下来打算研究一下 wireshark 的代码结构,希望既能动态增加新协议的分析,也能针对新增的协议指定不同的操作。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注