Linux网络编程:原始套接字--包过滤器BPF

目录

  • 参考文章
  • 一、BPF的介绍
  • 二、BPF的结构
  • 三、BPF Socket 的配置
  • 四、BPF Code 生成方法
  • 五、BPF Socket 实例


参考文章

  1. linux网络和BPF
  2. linux 下的 包过滤器 BPF
  3. 使用socket BPF
  4. Linux bpf 3.1、Berkeley Packet Filter (BPF) (Kernel Document)

一、BPF的介绍

BPFBerkeley 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 头文件包含以下的相关结构:

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

结果如下:
Linux网络编程:原始套接字--包过滤器BPF_第1张图片

tcpdump 提供了 -d 选项来阐述这段数字的意义:
Linux网络编程:原始套接字--包过滤器BPF_第2张图片

注意:
用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 
#include 
#include 
#include 
/* ... */

/* 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);

你可能感兴趣的:(网络编程,网络通信,socket,bpf)