参考文章
- linux网络和BPF
- linux 下的 包过滤器 BPF
- 使用socket BPF
- Linux bpf 3.1、Berkeley Packet Filter (BPF) (Kernel Document)
一、BPF的介绍
BPF(Berkeley Packet Filter)伯克利包过滤器。 BPF允许用户空间程序将一个过滤(filter)附加到任何的套接字(socket)上面用来允许或不允许某些类型的数据通过socket。
通常,大多数对包 socket 上 socket filter 的使用已经被 libpcap 高层次的语法所覆盖,作为应用开发人员应当坚持使用。libpcap wraps是它的封装层。
除非:
- i) 使用/链接libpcap不是选项;
- ii) 需要的BPF filter使用了linux扩展,libpcap的编译器不支持;
- iii) filter可能更复杂,不能由libpcap编译器清晰的实现;
- iv) 特定的filter代码需要被优化成和libpcap内部编译器不同,在这种情况下“手工”编写filter可能是一种选择。例如,xt_bpf和cls_bpf用户有可能产生的需求需要更复杂的filter代码或者不能使用libpcap表达(例如不同代码路径对应不同返回码)。此外,BPF JIT实现者希望手工写测试用例,因此需要低层次的访问BPF代码。
二、BPF的结构
用户空间的应用程序 include <linux/filter.h>
头文件包含以下的相关结构:
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
这样的结构被组装成一个4元数组,包含:code、jt、jf和K值。jt和jf是跳转偏移量,k是一个通用值提供给code使用。
struct sock_fprog { /* Required for SO_ATTACH_FILTER. */
unsigned short len; /* Number of filter blocks */
struct sock_filter __user *filter;
};
其中 *filter 指向结构为 struct sock_filter 的 BPF过滤代码(code)。
三、BPF Socket 的配置
在Linux上,BPF比在BSD上简单的多。只需要创建你的 filter 代码,通过 setsockopt 调用来完成:
- SO_ATTTACH_FILTER选项发送到内核,并且你的filter代码能通过内核的检查,这样你就可以立即过滤socket上面的数据了。
- SO_DETACH_FILTER选项把filter从socket上移除。这可能不会被经常使用,因为当你关闭socket的时候如果有filter会被自动移除。另外一个不太常见的情况是在同一个socket上添加不同的filter,当你还有另一个filter正在运行:如果你的新filter代码能够通过内核检查,内核小心的把旧的filter移除把新的filter换上,如果检查失败旧的filter将继续保留在socket上。
- SO_LOCK_FILTER 选项运行锁定附加到socket上的filter。一旦设置,filter不能被移除或者改变。这种允许一个进程设置一个socket、附加一个filter、锁定它们并放弃特权,确保这个filter保持到socket的关闭。
对socket过滤,将一个指向 struct sock_fprog 结构的指针 &Filter 通过 setsockopt 系统调用传递给内核,具体格式如下:
setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter));
setsockopt(sockfd, SOL_SOCKET, SO_DETACH_FILTER, &Filter, sizeof(Filter));
setsockopt(sockfd, SOL_SOCKET, SO_LOCK_FILTER, &Filter, sizeof(Filter));
setsockopt 调用SO_ATTACH_FILTER,将 tcpdump -dd
的结果内容添加到 struct sock_filter code[] = {} 结构体中,将 Filter.filter 指向这个code数组,Filter.len 设置长度,就可以用setsockopt设置过滤器了。
setsockopt 调用SO_DETACH_FILTER不需要任何参数,调用SO_LOCK_FILTER来预防filter被解绑附带一个整数参数0或1.
注意: socket filter没有限制仅仅用在PF_PACKET socket上,也可以用于其他socket家族。
四、BPF Code 生成方法
tcpdump 是linux 中调试网络的一个工具, 实际上 tcpdump 就是用利用BPF原理编写的一个工具,所以tcpdump 提供了一个生成 bpf code 的选项 -dd 。
使用 tcpdump 命令加上 -dd 选项来生成 bpf code,命令格式如下:
tcpdump -dd -i eth0 port 22
结果如下:
tcpdump 提供了 -d 选项来阐述这段数字的意义:
注意:
用tcpdump生成的BPF代码只能用于SOCK_RAW的socket,这类socket是可以直接操作数据链路层的。如果你打算将BPF用于ip层等较高层次的socket,那么你需要手工修改部分行的code.k,也就是修改如ldh [12]当中的[12]这个数值,因为这个数值的偏移量是按照从链路层开始计算得到的,在没有链路层之后,这个值就发生了变化,这个是需要注意的。
五、BPF Socket 实例
附加一个socket filter到一个PF_PACKET socket上,为了让所有IPv4/IPv6 port 22的包通过,这个socket上所有其他的包将会被丢弃。
代码如下:
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
/* ... */
/* From the example above: tcpdump -dd -i eth0 port 22 */
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 8, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 17, 0x00000011 },
{ 0x28, 0, 0, 0x00000036 },
{ 0x15, 14, 0, 0x00000016 },
{ 0x28, 0, 0, 0x00000038 },
{ 0x15, 12, 13, 0x00000016 },
{ 0x15, 0, 12, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 8, 0x00000011 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x45, 6, 0, 0x00001fff },
{ 0xb1, 0, 0, 0x0000000e },
{ 0x48, 0, 0, 0x0000000e },
{ 0x15, 2, 0, 0x00000016 },
{ 0x48, 0, 0, 0x00000010 },
{ 0x15, 0, 1, 0x00000016 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0)
/* ... bail out ... */
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0)
/* ... bail out ... */
/* ... */
close(sock);