sock_filter之bpf

本文最后更新于:2025年11月19日 下午

在linux上,我们创建socket收包时,默认会把指定协议的所有数据包收上来。
如指定的ETH_P_ALL,那么所有包都会收上来,如果指定IPPROTO_ICMPV6,那么只会收icmpv6的报文。
有时我们想过滤部分数据包怎么办呢?
使用setsockopt的SO_ATTACH_FILTER选项设置过滤器到内核。

SO_ATTACH_FILTER使用

通过SO_ATTACH_FILTER选项,可以把我们设置的filter发送到内核,内核运行filter之后再决定是否把数据包收到应用层。
一般性的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <linux/filter.h>
/* ... */

struct sock_filter code[] = {
....
};

struct sock_fprog bpf = {
.len = sizeof(code) / sizeof(code[0]),
.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);

重点在于sock_filter这个结构怎么填充,怎么写,语法如何。

使用tcpdump生成过滤器代码

使用tcpdump的-dd参数命令,可以直接生成sock_filter的代码,例如过滤icmpv6的NS和NA报文。

1
2
3
4
5
6
7
8
9
10
# tcpdump icmp6[0]==135 or icmp6[0]==136 -dd
{ 0x28, 0, 0, 0x0000000e },
{ 0x15, 0, 6, 0x000086dd },
{ 0x30, 0, 0, 0x00000016 },
{ 0x15, 0, 4, 0x0000003a },
{ 0x30, 0, 0, 0x00000038 },
{ 0x15, 1, 0, 0x00000087 },
{ 0x15, 0, 1, 0x00000088 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },

这种方法最简单,但是代码不易读,需要写注释用什么产生的。

手写过滤器代码

实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
// Filter ICMPv6 messages of type neighbor soliciation
static struct sock_filter bpf[] = {
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)), // 获取ip6_hdr的ip6_nxt偏移的一个字节
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 5), //如果不是icmpv6协议,跳5行,否则继续执行下一行
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
offsetof(struct icmp6_hdr, icmp6_type)), //提取icmp6 type偏移的一个字节
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1), // 如果不是NS,跳1行
BPF_STMT(BPF_RET | BPF_K, 0xffffffff), //返回接收
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 0, 1), // 如果不是NA,跳1行
BPF_STMT(BPF_RET | BPF_K, 0xffffffff), //返回接收
BPF_STMT(BPF_RET | BPF_K, 0), //返回拒绝
};

这个规则比较复杂。

其他过滤api

针对icmpv6有一些api可以调用,如

1
2
3
4
5
// Filter ICMPv6 package types
struct icmp6_filter filt;
ICMP6_FILTER_SETBLOCKALL(&filt);
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt));

参考

[# Linux bpf 3.1、Berkeley Packet Filter (BPF) (Kernel Document)


sock_filter之bpf
https://leon0625.github.io/2024/03/15/098666119136/
作者
leon.liu
发布于
2024年3月15日
许可协议