BPF MAP本质上可以理解为以[key/value]方式存储在内核中的一个字典类型的数据结构(也可以理解为一个内存数据库)。通常它由运行于内核空间的eBPF程序创建并返回对应的文件描述符,在用户空间运行的用户应用程序可以通过这个文件描述符来访问并操作 BPF MAP。基于此,BPF MAP 通常被用于内核eBPF程序(内核空间)和用户应用程序(用户空间)之间进行双向数据交换与信息传递的桥梁,是BPF程序中的重要基础数据结构。
1、BPF MAP的创建
BPF的MAP的创建只能由用户态程序在加载eBPF程序时创建。创建时候很简单,如下所示,就当做一个普通的全局变量来创建就好了。注意__section(“maps”)这个不能改动,因为内核在加载eBPF程序的时候会描述section “maps”,如果找到就会按指定格式在加载时自动创建指定的BPF MAP。所以这段代码需要被写在eBPF程序的源代码中。
struct bpf_map_def __section("maps") mac_port_map = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(long long),
.value_size = sizeof(int),
.max_entries = 100,
};
2、BPF MAP元素新增与更新(修改)
本功能通过 bpf_map_update_elem(map, key, value, options)这个函数调用来实现,这个函数可以在用户空间的应用程序里调用,也可以在eBPF程序里调用,调用略有不同。这里要注意这个函数的最后一个参数不一样,会定义出不一样的函数行为。
- 用户空间要通过fd来访问,
通过/tools/lib/bpf/bpf.h里的函数声明来调用:
int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags);
- eBPF里直接通过指针来访问,
通过/tools/testing/selftests/bpf/bpf_helpers.h这个头文件里的函数声明来调用:
int (*bpf_map_update_elem)(void *map, const void *key, const void *value, unsigned long long flags)
- 最后一个参数可选如下三个参数
#define BPF_ANY 0 /* 如果key已经存在,则更新对应的value, 如果key不存在就创建新的key-value /
#define BPF_NOEXIST 1 / 只在key所指定的key在map里还不存在里,创建新的key-value,否则返回出错 /
#define BPF_EXIST 2 / 查找key指定的key-value, 并更新,否则返回出错 */
所以我们一般使用这一个选项BPF_ANY 0则可。
3、BPF MAP元素查找与读取
通过函数bpf_map_lookup_elem(map, key)调用来实现,同样可以在内核空间和用户空间里调用。通过key去查找对应的value。
- 用户空间要通过fd来访问,
通过/tools/lib/bpf/bpf.h里的函数声明来调用:
int bpf_map_lookup_elem(int fd, const void *key, void *value);
- eBPF里直接通过指针来访问,
通过/tools/testing/selftests/bpf/bpf_helpers.h这个头文件里的函数声明来调用:
static void *(*bpf_map_lookup_elem)(void *map, const void *key)
4、BPF MAP元素的删除
通过函数bpf_map_delete_elem(map, key)调用来实现,同样可以在内核空间和用户空间里调用。通过key去查找对应的value。
- 用户空间要通过fd来访问,
通过/tools/lib/bpf/bpf.h里的函数声明来调用:
int bpf_map_delete_elem(int fd, const void *key);
- eBPF里直接通过指针来访问,
通过/tools/testing/selftests/bpf/bpf_helpers.h这个头文件里的函数声明来调用:
static int (*bpf_map_delete_elem)(void *map, const void *key)
5、BPF MAP元素的遍历
通过函数bpf_map_get_next_key(map, lookup_key, next_key)函数来实现,只能在用户空间程序里调用。
- 用户空间要通过fd来访问,
通过/tools/lib/bpf/bpf.h里的函数声明来调用:
int bpf_map_get_next_key(int fd, const void *key, void *next_key);
6、调用的例子
在用户态程序,自动导入eBPF字节码到XDP,并将转发表写入BPF MAP。
在eBPF程序,查询BPF MAP根据收到报文来自于哪个网卡,决定前报文从另外一个网卡发送出去。
- 用户空间程序源代码
#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <net/if.h>
#include <bpf/bpf.h>
#include <linux/bpf.h>
#include <linux/rtnetlink.h>
#include "/usr/src/linux-5.15/tools/testing/selftests/bpf/bpf_util.h"
#include <net/ethernet.h>
#include <linux/if_vlan.h>
#include <netinet/in.h>
#include <linux/ip.h>
int flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static int *ifindex_list;
static int mac_port_map_fd;
static void uninstall_exit(int sig)
{
int i = 0;
for (i = 0; i < 2; i++) {
bpf_set_link_xdp_fd(ifindex_list[i], -1, 0);
}
exit(0);
}
int main(int argc, char *argv[])
{
printf ("sizeof int = %ld\r\n", sizeof(int));
printf ("sizeof short int = %ld\r\n", sizeof(short int));
printf ("sizeof char = %ld\r\n", sizeof(char));
printf ("sizeof long int = %ld\r\n", sizeof(long int));
unsigned int indexofeth0 = 0;
unsigned int indexofeth1 = 0;
indexofeth0 = if_nametoindex(argv[1]);
indexofeth1 = if_nametoindex(argv[2]);
printf("index of %s is %d, index of %s is %d\r\n", argv[1], indexofeth0, argv[2], indexofeth1);
int i;
char filename[64];
struct bpf_object *obj;
struct bpf_prog_load_attr prog_load_attr = {
.prog_type = BPF_PROG_TYPE_XDP,
};
int prog_fd;
snprintf(filename, sizeof(filename), "print.o");
prog_load_attr.file = filename;
if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd)) {
return 1;
}
mac_port_map_fd = bpf_object__find_map_fd_by_name(obj, "mac_port_map");
ifindex_list = (int *)calloc(2, sizeof(int *));
ifindex_list[0] = if_nametoindex(argv[1]);
ifindex_list[1] = if_nametoindex(argv[2]);
for (i = 0; i < 2; i++) {
if (bpf_set_link_xdp_fd(ifindex_list[i], prog_fd, flags) < 0) {
printf("install xdp fd failed\n");
return 1;
}
}
signal(SIGINT, uninstall_exit);
bpf_map_update_elem(mac_port_map_fd, (const void *)&ifindex_list[1], (const void *)&ifindex_list[0], 0);
printf("Update XDP item from [Port:Port] table-------------[%d]:[%d]\r\n", ifindex_list[1], ifindex_list[0]);
bpf_map_update_elem(mac_port_map_fd, (const void *)&ifindex_list[0], (const void *)&ifindex_list[1], 0);
printf("Update XDP item from [Port:Port] table-------------[%d]:[%d]\r\n", ifindex_list[0], ifindex_list[1]);
while(1){
i++;
sleep(1);
printf("working...%d\r\n", i);
}
}
- eBPF源代码例子
#include <stdio.h>
#include <linux/bpf.h>
#include <net/ethernet.h>
#include <linux/if_vlan.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>
#include <stdio.h>
#include <net/if.h>
#define i32bpf int
#ifndef __section
# define __section(NAME) \
__attribute__((section(NAME), used))
#endif
// mac_port_map save the target MAC/port mapping bable,target MAC is the key, ifindex is the value
struct bpf_map_def __section("maps") mac_port_map = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(int),
.value_size = sizeof(int),
.max_entries = 100,
};
__section("prog")
int xdp_ip_filter(struct xdp_md *ctx)
{
void *end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
int ip_src;
int ip_dst;
unsigned char ip_protocol;
long int offset;
short int eth_type;
int indexoftargetnic;
long dst_mac = 0x323232323200;
int in_index = ctx->ingress_ifindex, *out_index;
char info_fmt1[] ="Dst Addr: %pI4\r\n";
char info_fmt2[] ="Src Addr: %pI4\r\n";
char info_fmt3[] ="-----------------\r\n";
char info_fmt4[] ="return %d\r\n";
char info_fmt5[] ="eth_type is 0x%x\r\n";
char info_fmt6[] ="ip_protocal is 0x%x\r\n";
char info_fmt7[] ="dst_mac is 0x%x,in_if is %d, out_if is %d\r\n";
char info_fmt8[] ="do NOT find out index, XDP_PASS\r\n";
char info_fmt9[] ="in index = %d, find out index = %d, XDP_FORWARD\r\n";
struct ethhdr *eth = data;
offset = sizeof(*eth);
if (data + offset > end) {
bpf_trace_printk(info_fmt4, sizeof(info_fmt4),1);
return XDP_ABORTED;
}
eth_type = eth->h_proto;
if (eth_type != 0x8)
{
if (eth_type != (short int)0xF788)
{
bpf_trace_printk(info_fmt5, sizeof(info_fmt5), eth_type);
}
}
/* 查看转发表,直接转发1588V2协议报文*/
if (eth_type == (short int)0xF788)
{
/*bpf_trace_printk(info_fmt5, sizeof(info_fmt5), eth_type);*/
out_index = bpf_map_lookup_elem(&mac_port_map, &in_index);
if (out_index == 0)
{
// 如若找不到,则上传到内核TCP/IP协议栈处理
bpf_trace_printk(info_fmt8, sizeof(info_fmt8));
return XDP_PASS;
}
else
{
/*bpf_trace_printk(info_fmt9, sizeof(info_fmt9), in_index, *out_index);*/
return bpf_redirect(*out_index,0);
}
}
struct iphdr *iph = data + offset;
if (iph + 1 > end) {
bpf_trace_printk(info_fmt4, sizeof(info_fmt4),3);
return XDP_ABORTED;
}
return XDP_PASS;
}
char __license[] __section("license") = "GPL";
7、编译命令
gcc main.c -lbpf
clang -O2 -Wall -target bpf -c print.c -o print.o