据了解(未查证),从 clang12
开始,eBPF 代码中的变量声明不再要求写在函数体的最前方,而是可以按需声明并初始化。
写法一:一次性声明全部的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
|
static __always_inline void
set_output_headers(struct __sk_buff *skb, struct event *ev)
{
struct ethhdr *eth;
struct vlan_hdr *vh;
struct iphdr *iph;
struct udphdr *udph;
struct tcphdr *tcph;
struct icmphdr *icmph;
int l3_off = 0, l4_off, var_off = 0, cpy_off = 0;
// ...
}
|
如上代码片段,将所有需要处理的协议头对应的 struct
变量放在一起声明。如果后续需要处理其它协议,则需要在此处添加对应的 struct
变量的声明。
先声明后使用
我不喜欢这种写法,不过这种写法能在编码的时候有一个好处:使用 typeof(hdr)
获取类型、使用 sizeof(*hdr)
获取协议头大小,而无需啰嗦地再写一遍 struct xxxhdr
。
写法二:按需声明并初始化
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
27
28
29
30
31
32
|
static __always_inline __u16
calc_l3_off(struct sk_buff *skb)
{
__u16 skb_network_header = BPF_CORE_READ(skb, network_header);
if (skb_network_header != (__u16)~0ul)
return skb_network_header;
// calculate l3_off from eth layer
__u16 skb_mac_header = BPF_CORE_READ(skb, mac_header);
__u16 l2_off = 0;
if (skb_mac_header != (__u16)~0ul)
l2_off = skb_mac_header;
__u16 l3_off = 0;
unsigned char *skb_head = BPF_CORE_READ(skb, head);
struct ethhdr *eth = (struct ethhdr *)(skb_head + l2_off);
l3_off += sizeof(*eth);
__be16 l3_proto = BPF_CORE_READ(eth, h_proto);
if (is_vlan_proto(l3_proto)) {
struct vlan_hdr *vh = (struct vlan_hdr *)(eth + 1);
l3_off += sizeof(*vh);
l3_proto = BPF_CORE_READ(vh, h_vlan_encapsulated_proto);
}
if (!is_ipv4_proto(l3_proto))
return 0;
return l3_off;
}
|
如上代码片段,将需要用到的变量在需要的地方声明并初始化即可。
对比写法一,此种写法对变量的使用更加灵活,对程序员更加友好。
写法三:混合写法一和写法二
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
27
28
29
30
31
32
33
34
35
36
37
|
static volatile const struct config __cfg = {};
static __always_inline bool
filter_skb(struct sk_buff *skb)
{
struct config cfg = __cfg;
if (!cfg.target_ip && !cfg.target_port && !cfg.l4_proto)
return false;
__u16 l3_off = calc_l3_off(skb);
if (l3_off == 0)
return true;
unsigned char *skb_head = BPF_CORE_READ(skb, head);
struct iphdr *iph;
iph = (typeof(iph))(skb_head + l3_off);
__u8 l4_proto = BPF_CORE_READ(iph, protocol);
if (filter_proto(l4_proto))
return true;
if (cfg.l4_proto && cfg.l4_proto != l4_proto)
return true;
__be32 dip = BPF_CORE_READ(iph, daddr);
if (cfg.target_ip && cfg.target_ip != dip)
return true;
struct udphdr *udph;
udph = (typeof(udph))(skb_head + l3_off + calc_ipv4_hdr_size(iph));
__be16 dport = BPF_CORE_READ(udph, dest);
if (cfg.target_port && cfg.target_port != dport)
return true;
return false;
}
|
如上代码片段,对于协议头的变量,先声明而后使用 typeof()
初始化,其它变量则可以直接声明并初始化。
小结
编写的 eBPF 代码量逐渐增大后,逐渐地形成了自己的代码风格。
而且,有了自己的代码风格后,可以一次性地编写 eBPF 代码并编译、加载通过,而无需反
复地来回调试(为了通过编译器、eBPF verifier/校验器的检查)。