1、dpdk的bpf组件
dpdk提供了一个bpf的组件,编译成功后是librte_bpf.so/librte_bpf.a,以一个单独的库呈现。
特点:
1、可以不依赖于dpdk初始化,直接使用
2、用户自定义eBPF helper函数
3、支持arm和x86下的JIT
4、支持eBPF validate
5、支持rte mbuf
6、支持在rx/tx流程的处理
7、eBPF程序入口支持一个参数(void *)
不足之处:
1、不支持尾调用,即ebpf程序不能互相调用
2、API接口分析
2.1 结构体
//bpf主要的结构体信息
/**
* Possible types for function/BPF program arguments.
*/
enum rte_bpf_arg_type {
RTE_BPF_ARG_UNDEF, /**< undefined */
RTE_BPF_ARG_RAW, /**< scalar value */
RTE_BPF_ARG_PTR = 0x10, /**< pointer to data buffer */
RTE_BPF_ARG_PTR_MBUF, /**< pointer to rte_mbuf */
RTE_BPF_ARG_RESERVED /**< reserved for internal use */
};
/**
* function argument information
*/
struct rte_bpf_arg {
enum rte_bpf_arg_type type;
/**
* for ptr type - max size of data buffer it points to
* for raw type - the size (in bytes) of the value
*/
size_t size; /* type = RTE_BPF_ARG_PTR时,size = BUF大小 */
size_t buf_size;
/**< for mbuf ptr type, max size of rte_mbuf data buffer */
};
/**
* Possible types for external symbols.
*/
enum rte_bpf_xtype {
RTE_BPF_XTYPE_FUNC, /**< function */
RTE_BPF_XTYPE_VAR /**< variable */
};
/**
* Definition for external symbols available in the BPF program.
*/
/*
描述eBPF helper的结构体,分为两种:
1、全局变量:当eBPF程序使用全局变量时,需要定义,但是一般不建议使用全局变量
2、函数:elf文件的 rel section中描述的函数。
*/
struct rte_bpf_xsym {
const char *name; /**< name */ /* elf文件的 rel section中描述的函数的名称 */
enum rte_bpf_xtype type; /**< type */
union {
struct {
uint64_t (*val)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); /* eBPF helper 函数 */
uint32_t nb_args;
struct rte_bpf_arg args[EBPF_FUNC_MAX_ARGS];
/**< Function arguments descriptions. */
struct rte_bpf_arg ret; /**< function return value. */
} func;
struct {
void *val; /**< actual memory location */
struct rte_bpf_arg desc; /**< type, size, etc. */
} var; /**< external variable */
};
};
/**
* Input parameters for loading eBPF code.
*/
struct rte_bpf_prm {
const struct ebpf_insn *ins; /**< array of eBPF instructions */ //eBPF 字节码
uint32_t nb_ins; /**< number of instructions in ins */
const struct rte_bpf_xsym *xsym; //eBPF helper函数
/**< array of external symbols that eBPF code is allowed to reference */
uint32_t nb_xsym; /**< number of elements in xsym */
struct rte_bpf_arg prog_arg; /**< eBPF program input arg description */ //eBPF程序的入口参数的描述
};
/**
* Information about compiled into native ISA eBPF code.
*/
struct rte_bpf_jit {
uint64_t (*func)(void *); /**< JIT-ed native code */ //eBPF程序的入口
size_t sz; /**< size of JIT-ed code */
};
/*
rte bpf的主体
*/
struct rte_bpf {
struct rte_bpf_prm prm; //eBPF字节码等信息
struct rte_bpf_jit jit; //JIT
size_t sz; // sizeof(struct rte_bpf) + n_xsym * sizeof(struct rte_bpf_xsym) + n * sizeof(struct ebpf_insn)
uint32_t stack_sz; //
};
2.2 RX/TX收发包流程的接口( rte_bpf_ethdev.h )
前置:需使用dpdk进行收发包,才能使用。
(1)RX/TX加载eBPF的接口
int rte_bpf_eth_rx_elf_load(uint16_t port, uint16_t queue, const struct rte_bpf_prm *prm, const char *fname, const char *sname, uint32_t flags);
int rte_bpf_eth_tx_elf_load(uint16_t port, uint16_t queue, const struct rte_bpf_prm *prm, const char *fname, const char *sname, uint32_t flags);
参数:
port | 端口 |
queue | 队列 |
prm | eBPF的helper函数 |
fname | elf文件路径 |
sname | elf文件的可执行区域 |
flags | 预期执行eBPF的方法(JIT和非eBPF编码) |
流程:
rte_bpf_eth_rx/tx_elf_load()
->bpf_eth_elf_load()
->select_rx_callback() /select_tx_callback() //根据 flags 和 eBPF程序入口参数类型,选择rx和tx的回调函数
->rte_bpf_elf_load() // 解析elf文件,翻译为eBPF字节码,做validate和JIT,得到eBPF , 见下文
->rte_bpf_get_jit() //通过bpf获取JIT
->bpf_eth_cbh_add() //根据port和queue 使能callback
->rte_eth_add_rx_callback() / rte_eth_add_tx_callback() //根据port id和queue id将callback挂在对应的收发包回调上
(2)RX/TX卸载eBPF的接口
void rte_bpf_eth_rx_unload(uint16_t port, uint16_t queue);
void rte_bpf_eth_tx_unload(uint16_t port, uint16_t queue);
参数:
port | 端口号 |
queue | 队列号 |
流程:
rte_bpf_eth_rx/tx_unload()
->bpf_eth_cbh_find() // 根据port和queue找到挂载点
->rte_eth_remove_rx_callback() / rte_eth_remove_tx_callback() //从rx和tx上卸载callback
->bpf_eth_cbi_unload() //删除挂载点
(3)call流程
rte_eth_rx_burst() / rte_eth_tx_burst()
-> rte_eth_call_rx_callbacks()
->cb->fn.rx() / cb->fn.tx() // 执行select_tx/rx_callback()的函数
2.3、 generic API (rte_bpf.h)
(1)加载eBPF的接口
struct rte_bpf * rte_bpf_load(const struct rte_bpf_prm *prm); //通过prm得到bpf
备注:此接口需要填充好prm的所有信息(eBPF字节码,eBPF helper函数、eBPF程序入口参数的描述)
流程见下文:
struct rte_bpf * rte_bpf_elf_load(const struct rte_bpf_prm *prm, const char *fname, const char *sname);
参数:
prm | eBPF helper 函数、eBPF程序入口参数的描述 |
fname | elf文件路径 |
sname | elf文件的可执行区域 |
流程:
rte_bpf_elf_load()
->open() //根据 fname 打开elf文件
->bpf_load_elf()
->find_elf_code() //根据sname 找到elf文件的可执行区域
->elf_reloc_code() //do relocation 即将eBPF程序引用的外部函数(可以理解为库函数)替换为eBPF helper 函数
->rte_bpf_load() //此时已经将elf文件转换为了eBPF字节码
rte_bpf_load()
->bpf_check_xsym() //校验eBPF helper 函数是否合规
->bpf_load() //mmap 申请内存,得到struct rte bpf
->__rte_bpf_validate() //eBPF validate
->__rte_bpf_jit() // JIT
//可以修改rte_bpf_elf_load() 函数,只做将elf文件翻译为eBPF字节码。在不要求性能的情况下直接使用eBPF字节码
(2)执行eBPF的接口
uint64_t rte_bpf_exec(const struct rte_bpf *bpf, void *ctx); //执行指定的context
uint32_t rte_bpf_exec_burst(const struct rte_bpf *bpf, void *ctx[], uint64_t rc[], uint32_t num); //同一个eBPF程序,执行一组context
备注:这两个接口是直接执行eBPF字节码的接口,在不要求性能的情况下可以使用,要求性能时使用JIT
参数:
bpf | eBPF程序 |
ctx | eBPF程序入口的参数 |
流程:
rte_bpf_exec
rte_bpf_exec()
->rte_bpf_exec_burst()
->bpf_exec() //直接执行eBPF字节码
int rte_bpf_get_jit(const struct rte_bpf *bpf, struct rte_bpf_jit *jit); //执行JIT程序。
参数:
bpf | eBPF程序 |
jit | 出参,得到JIT |
rte_bpf_get_jit(const struct rte_bpf *bpf, struct rte_bpf_jit *jit)
{
if (bpf == NULL || jit == NULL)
return -EINVAL;
jit[0] = bpf->jit;
return 0;
}
拿到JIT后,运行 jit.func 即可
(3)销毁eBPF的接口
void rte_bpf_destroy(struct rte_bpf *bpf); // 接口会销毁bpf和jit
rte_bpf_destroy(struct rte_bpf *bpf)
{
if (bpf != NULL) {
if (bpf->jit.func != NULL)
munmap(bpf->jit.func, bpf->jit.sz);
munmap(bpf, bpf->sz);
}
}