这里打算写写报文分析接口的设计。 一般来说,以太网的报文格式为:
+------------+-------------------+-----+
| 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 的代码结构,希望既能动态增加新协议的分析,也能针对新增的协议指定不同的操作。