在linux下使用tcpdump进行报文抓取是理解和分析网络信息交互过程的重要步骤。相信有不少同学在设计网络程序时有过这样的冲动——能否在代码中也像tcpdump一样过滤获得我想要的报文呢?答案当然是“没问题”。这里介绍的BPF就是编程中使用的工具,当然似乎tcpdump也是利用该工具来实现的。
BPF(Berkeley Packet Filter)伯克利包过滤器。其最初构想提出于 1992 年,其目的是为了提供一种过滤包的方法,并且要避免从内核空间到用户空间的无用的数据包复制行为。它最初是由从用户空间注入到内核的一个简单的字节码构成,它在那个位置利用一个校验器进行检查 —— 以避免内核崩溃或者安全问题 —— 并附着到一个套接字上,接着在每个接收到的包上运行。几年后它被移植到 Linux 上,并且应用于一小部分应用程序上(例如,tcpdump)。其简化的语言以及存在于内核中的即时编译器(JIT),使 BPF 成为一个性能卓越的工具。
1. 预置条件:创建socket,并确保从socket中读取的是packet,也就是说是 MAC头+IP头+TCP/UDP头。
2. 参考说明:http://www.gsp.com/cgi-bin/man.cgi?section=4&topic=bpf#1
3. 使用步骤:
a. 创建socket
b. 初始化BPF过滤器
c. 使用setsockopt将过滤器SO_ATTACH_FILTER 到了socket 上
注:a和b无先后顺序。
4. 代码分析:
//dump arp 0x0806 struct sock_filter CODE_BPF [] = { {0x20, 0, 0, 0x0000000c}, {0x15, 0, 1, 0x00000806}, {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000} }; int packet_handle_init(struct lib_cap *p) { int sock = -1; struct sock_fprog Filter; struct sockaddr_ll sll; if (NULL == p) { return -1; } // init filter settings Filter.len =4; Filter.filter = CODE_BPF; //set default value p->ifindex = -1; p->fd = -1; p->buffer = NULL; p->buf_len = 0; if ( (sock = socket(PF_PACKET, SOCK_RAW, htons(ETHERTYPE_SADP))) < 0) { return -1; } if ( setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter)) < 0); }其中sock_fprog和sock_filter为BPF库特固定的结构体,其定义如下:
struct sock_fprog { unsigned short len; struct sock_filter *filter; } struct sock_filter { __u16 code; /* actual filter code */ __8 jt; /* jump true */ __8 jf; /* jump false */ __u32 k; /* Generic multiuse field */ }什么是字节码?即上文代码中定义的结构体struct sock_filter CODE_BPF初始化编码,
//dump arp 0x0806 struct sock_filter CODE_BPF [] = { {0x20, 0, 0, 0x0000000c}, {0x15, 0, 1, 0x00000806}, {0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000} };其具体的含义,需要借助于tcpdump来说明:
上述代码的生成,使用命令tcpdump -dd ether proto 0x0806
{ 0x20, 0, 0, 0x0000000c }, { 0x15, 0, 1, 0x00000806 }, { 0x6, 0, 0, 0x00040000 }, { 0x6, 0, 0, 0x00000000 }其意义即过滤以太网协议中类型是0x0806(arp)的数据包。这段数字到底实现了什么功能呢?-d 选项来可获得其意义:
tcpdump -d ether proto 0x0806
(000) ldh [12] (001) jeq #0x806 jt 2 jf 3 (002) ret #262144 (003) ret #0查阅相关资料可知:
ldh:高字节加载, 即从帧第12字节起载入内存,即IP报文中12字节src与dst mac后的两字节,如下图所示
jeq: 如果类型字段是 0x0806 的话就返回 262144个字节,不是的话就返回 0字节。
至此就已达到过滤类型为0x0806数据包的目的。
有过一些tcpdump使用经验的同学可能会发现,CODE_BPF数组的有时候非常复杂,其实这与其过滤条件有关,如:
tcpdump ip -dd -s 2048 host 192.168.1.2
tcpdump ip -d -s 2048 host 192.168.1.2
不同的过滤条件会有不同的过滤BPF code。
通过以上分析和实验过程,可以清楚的看到,tcpdump的强大远远不止抓包分析,其本身也有很多值得去研究的价值。