一, 导论
BPF(Berkeley Packet Filter)伯克利包过滤器。 是在linux 平台下的一个包过滤器。使用此过滤器可以在socket编程时非常方便的实现各种过滤规则。
首先,要确保从socket中读取的是packet,也就是说是 MAC头+IP头+TCP/UDP头。
关于BPF的相关介绍可以查看英文文档:http://www.gsp.com/cgi-bin/man.cgi?section=4&topic=bpf#1
二, BPF的使用
首先来看一段实际应用中的代码:
int init_packet_capture(struct lib_cap *p)
{
int sock = -1;
struct sock_fprog Filter;
struct sockaddr_ll sll;
// tcpdump -dd ether proto 0x8033
struct sock_filter bpf_code [] = {
{0x20, 0, 0, 0x0000000c},
{0x15, 0, 1, 0x00000033},
{0x6, 0, 0, 0x00000200},
{0x6, 0, 0, 0x00000000}
};
if (NULL == p)
{
return -1;
}
// init filter settings
Filter.len =4;
Filter.filter = bpf_code;
//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);
....
}
以上代码首先定义并初始化了一个bpf过滤器 Filter, 然后将其 SO_ATTACH_FILTER 到了socket 上。
这里用到了两个结构体:sock_fprog和sock_filter:
sock_fprog
struct sock_fprog
{
unsigned short len;
struct sock_filter *filter;
}
sock_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 bpf_code [] = {
{0x20, 0, 0, 0x0000000c},
{0x15, 0, 1, 0x00000033},
{0x6, 0, 0, 0x00000200},
{0x6, 0, 0, 0x00000000}
};
下面将分析这一串数字是如何产生的,以及它的意义。
三, BPFcode 生成方法
在注释中有一段提示 tcpdump -dd ether proto 0x8033。
tcpdump 是linux 中调试网络的一个工具, 实际上tcpdump 就是用利用BPF原理编写的一个工具,所以tcpdump 提供了一个生成bpf code 的一个命令行:
这段数字的意义就是过滤以太网协议中类型是 0x8033的数据包(某某IT公司的产品自定义的一个数据包)。这段数字到底实现了什么功能呢? tcpdump 提供了 -d 选项来阐述这段数字的意义:
分析这段代码可知,
ldh 是高位加载, 即从帧的第12位开始加载进内存,第12位就是去除6位src mac 与 6位 dst mac 的数据报类型字段。
jeq: 如果类型字段是 0x8033 的话就返回 96个字节,如果类型字段不是 0x8033的话就返回 0个字节。
至此就已达到过滤类型为0x8033数据包的目的。
小结:
细心的朋友可能会发现,bpf_code 数组的第三行最后一列 0x00000060 与源代码中的 0x00000020 并不一样!这是因为tcpdump 的默认返回字节是96个字节, 而实际需要抓取512(0x00000200, 16*16*2)个字节的数据。关于这点给tcpdump 指定长度字段-s就可以了:
linux 下的 BPF 是包过滤的利器, 结合tcpdump 工具,能非常快速的实现各种个性化的包过滤需求!