基于eBPF lpm map,icmp只有匹配上路由才能通。
最终目录结构
效果展示
启动应用前,可以ping通192.168.0.1和192.168.0.105。
启动应用后,无法ping通192.168.0.1,可以ping通192.168.0.105。
停止应用后,可以ping通192.168.0.1和192.168.0.105。
icmp/drop-icmp.c
#include "../headers/vmlinux.h"
#include "../headers/bpf_endian.h"
#include "../headers/bpf_helpers.h"
#define TC_ACT_SHOT 2
#define TC_ACT_OK 0
#define ETH_P_IP 0x0800
// 定义key
struct ipv4_lpm_key {
__u32 prefixlen;
__u32 data;
};
// 定义lpm map
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct ipv4_lpm_key);
__type(value, __u32);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, 255);
// 本地持久化map
__uint(pinning, LIBBPF_PIN_BY_NAME);
} ipv4_lpm_map SEC(".maps");
struct ipv4_lpm_key *unused_ipv4_lpm_key __attribute__((unused));
char __license[] SEC("license") = "Dual MIT/GPL";
SEC("tc")
int tc_deny_icmp(struct __sk_buff *skb) {
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;
struct ethhdr *eth_hdr = data;
if ((void *)eth_hdr + sizeof(*eth_hdr) > data_end || eth_hdr->h_proto != bpf_htons(ETH_P_IP)) {
return TC_ACT_OK;
}
struct iphdr *ip_hdr = (void *)eth_hdr + sizeof(*eth_hdr);
if ((void *)ip_hdr + sizeof(*ip_hdr) > data_end) {
return TC_ACT_OK;
}
if (ip_hdr->protocol == IPPROTO_ICMP) {
struct ipv4_lpm_key key = {
.prefixlen = 32,
.data = ip_hdr->daddr
};
if (!bpf_map_lookup_elem(&ipv4_lpm_map, &key)) {
return TC_ACT_SHOT;
}
}
return TC_ACT_OK;
}
cmd/main.go
#include "../headers/vmlinux.h"
#include "../headers/bpf_endian.h"
#include "../headers/bpf_helpers.h"
#define TC_ACT_SHOT 2
#define TC_ACT_OK 0
#define ETH_P_IP 0x0800
// 定义key
struct ipv4_lpm_key {
__u32 prefixlen;
__u32 data;
};
// 定义lpm map
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct ipv4_lpm_key);
__type(value, __u32);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, 255);
// 本地持久化map
__uint(pinning, LIBBPF_PIN_BY_NAME);
} ipv4_lpm_map SEC(".maps");
struct ipv4_lpm_key *unused_ipv4_lpm_key __attribute__((unused));
char __license[] SEC("license") = "Dual MIT/GPL";
SEC("tc")
int tc_deny_icmp(struct __sk_buff *skb) {
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;
struct ethhdr *eth_hdr = data;
if ((void *)eth_hdr + sizeof(*eth_hdr) > data_end || eth_hdr->h_proto != bpf_htons(ETH_P_IP)) {
return TC_ACT_OK;
}
struct iphdr *ip_hdr = (void *)eth_hdr + sizeof(*eth_hdr);
if ((void *)ip_hdr + sizeof(*ip_hdr) > data_end) {
return TC_ACT_OK;
}
if (ip_hdr->protocol == IPPROTO_ICMP) {
struct ipv4_lpm_key key = {
.prefixlen = 32,
.data = ip_hdr->daddr
};
if (!bpf_map_lookup_elem(&ipv4_lpm_map, &key)) {
return TC_ACT_SHOT;
}
}
return TC_ACT_OK;
}
编译运行
rm -f icmp/drop-icmp.o
clang -c icmp/drop-icmp.c -o icmp/drop-icmp.o -target bpf -O2 -g
go generate ./cmd
go build -o ./cmd/ebpf-test ./cmd/.
./cmd/ebpf-test
从C结构体到Go结构体
cilium/ebpf使用反射把eBPF map内的数据转为 go结构体,完成内核态与用户态之间传输。
用户态手动定义结构体的问题在于,除了首字母大写,go-binary库不是简单的memcpy,可能会让go结构体成员读到的值是错误的。
推荐自动生成Go结构体,bpf2go -type参数指定C结构体类型,C代码中加上struct key类型 *unused_xxx __attribute__((unused));
参考资料
https://docs.kernel.org/next/bpf/map_lpm_trie.html
标签:__,hdr,eBPF,lpm,LPM,key,data,路由,struct From: https://www.cnblogs.com/WJQ2017/p/18172130