DPDK ACL链表
本文简介DPDK中ACL链表的几个主要函数,以及数据结构。
ACL初始化
函数rte_acl_init如下,主要初始化工作是选择使用的分类算法。对于AVX2(Advanced Vector Extensions),仅当编译DPDK代码的编译器支持AVX2指令集,并且运行DPDK程序的处理器支持AVX2指令集时,才启用RTE_ACL_CLASSIFY_AVX2算法。否则,退而求其次,选用RTE_ACL_CLASSIFY_SSE算法。如果处理器不支持SSE4_1(Streaming SIMD Extensions)指令集,选用默认的算法为RTE_ACL_CLASSIFY_DEFAULT,
RTE_INIT(rte_acl_init)
{
enum rte_acl_classify_alg alg = RTE_ACL_CLASSIFY_DEFAULT;
#ifdef CC_AVX2_SUPPORT
if (rte_cpu_get_flag_enabled(RTE_CPUFLAG_AVX2))
alg = RTE_ACL_CLASSIFY_AVX2;
else if (rte_cpu_get_flag_enabled(RTE_CPUFLAG_SSE4_1))
#else
if (rte_cpu_get_flag_enabled(RTE_CPUFLAG_SSE4_1))
#endif
alg = RTE_ACL_CLASSIFY_SSE;
rte_acl_set_default_classify(alg);
}
以下指令检查当前的gcc编译器是否支持AVX2指令集。
$ gcc -march=core-avx2 -dM -E - </dev/null 2>&1
从输出结果可以看到(输出省略不相关部分),__AVX2__关键字,表明支持AVX2指令集。另外,可见gcc同时支持SSE4_1指令集。
#define __SSE4_1__ 1
#define __SSE4_2__ 1
#define __AVX__ 1
#define __AVX2__ 1
DPDK中使用如下的代码赋值宏CC_AVX2_SUPPORT,如果grep找到AVX2关键字,echo数字1即赋值给宏CC_AVX2_SUPPORT值1。
CC_AVX2_SUPPORT=$(shell $(CC) -march=core-avx2 -dM -E - </dev/null 2>&1 | grep -q AVX2 && echo 1)
由以下的ACL分类函数定义可见,默认的类型RTE_ACL_CLASSIFY_DEFAULT与RTE_ACL_CLASSIFY_SCALAR相同,都是使用rte_acl_classify_scalar函数作为分类处理函数。
static const rte_acl_classify_t classify_fns[] = {
[RTE_ACL_CLASSIFY_DEFAULT] = rte_acl_classify_scalar,
[RTE_ACL_CLASSIFY_SCALAR] = rte_acl_classify_scalar,
[RTE_ACL_CLASSIFY_SSE] = rte_acl_classify_sse,
[RTE_ACL_CLASSIFY_AVX2] = rte_acl_classify_avx2,
[RTE_ACL_CLASSIFY_NEON] = rte_acl_classify_neon,
[RTE_ACL_CLASSIFY_ALTIVEC] = rte_acl_classify_altivec,
};
全局ACL链表
宏TAILQ_HEAD定义tailq链表结构rte_acl_list,以及指定链表中元素的类型rte_tailq_entry。宏EAL_REGISTER_TAILQ定义了RTE初始化函数,用于注册名称为“RTE_ACL”类型为rte_tailq_elem的rte_acl_tailq变量。
TAILQ_HEAD(rte_acl_list, rte_tailq_entry);
static struct rte_tailq_elem rte_acl_tailq = {
.name = "RTE_ACL",
};
EAL_REGISTER_TAILQ(rte_acl_tailq)
#define EAL_REGISTER_TAILQ(t) \
RTE_INIT(tailqinitfn_ ##t) \
{ \
if (rte_eal_tailq_register(&t) < 0) \
rte_panic("Cannot initialize tailq: %s\n", t.name); \
}
如上所示,函数rte_eal_tailq_register将名为“RTE_ACL”的变量结构注册到全局rte_tailq_elem_head链表中。然而“RTE_ACL”结构自身亦包含一个tailq链表头成员head,由定义可见其类型与ACL链表rte_acl_list的类型一致,元素成员都为rte_tailq_entry结构,其正为ACL链表的头指针。
TAILQ_HEAD(rte_tailq_entry_head, rte_tailq_entry);
struct rte_tailq_head {
struct rte_tailq_entry_head tailq_head; /**< NOTE: must be first element */
char name[RTE_TAILQ_NAMESIZE];
};
struct rte_tailq_elem {
/* Reference to head in shared mem, updated at init time by rte_eal_tailqs_init() */
struct rte_tailq_head *head;
TAILQ_ENTRY(rte_tailq_elem) next;
const char name[RTE_TAILQ_NAMESIZE];
};
在函数rte_acl_create中可见,正是通过将rte_acl_tailq的head成员的tailq_head子成员转换为rte_acl_list,而得到全局的ACL链表头部指针的。
struct rte_acl_ctx * rte_acl_create(const struct rte_acl_param *param)
{
struct rte_acl_ctx *ctx;
struct rte_acl_list *acl_list;
struct rte_tailq_entry *te;
char name[sizeof(ctx->name)];
acl_list = RTE_TAILQ_CAST(rte_acl_tailq.head, rte_acl_list);
}
#define RTE_TAILQ_CAST(tailq_entry, struct_name) \
(struct struct_name *)&(tailq_entry)->tailq_head
ACL创建
主函数rte_acl_create,根据传入的rte_acl_param结构类型的参数,创建一个新的rte_acl_ctx结构,但是如果已经存在与参数中指定的名称相同的ACL上下文结构,直接返回其地址,不执行新建操作。以DPDK例程中的l3fwd-acl而言,其在调用ACL创建函数rte_acl_create时,传入的参数结构如下,参见文件/examples/l3fwd-acl/main.c中的函数setup_acl:
int dim = ipv6 ? RTE_DIM(ipv6_defs) : RTE_DIM(ipv4_defs);
/* Create ACL contexts */
snprintf(name, sizeof(name), "%s%d", ipv6 ? L3FWD_ACL_IPV6_NAME : L3FWD_ACL_IPV4_NAME, socketid);
acl_param.name = name;
acl_param.socket_id = socketid;
acl_param.rule_size = RTE_ACL_RULE_SZ(dim);
acl_param.max_rule_num = MAX_ACL_RULE_NUM;
对于IPv4而言,其ACL的名称为l3fwd-acl-ipv4(宏L3FWD_ACL_IPV4_NAME)与socketid拼接的字符串。对于IPv6,其名称为l3fwd-acl-ipv6+socketid的值。之后将会看到在函数rte_acl_create中,其将传入的ACL名称添加上ACL_的前缀。所以,实际的ACL名称为(以IPv4为例,假设socketid等于0):ACL_l3fwd-acl-ipv40。
在l3fwd-acl例程中,ACL单个规则项的大小(rule_size)设定为RTE_ACL_RULE_SZ(dim),其中dim的值为ACL规则的字段数量,对于IPv4来说,定义了5个字段,分别为协议proto、源IP、目的IP、源端口和目的端口。对于IPv6来说,同样是五元组,但是由于IPv6的地址长度为16个字节,所以,多出了6个字段,IPv6规则定义了11个字段。ACL规则大小等于rte_acl_rule结构本身的长度加上字段数量与字段结构体rte_acl_field的乘积。
#define RTE_ACL_RULE_SZ(fld_num) \
(sizeof(struct rte_acl_rule) + sizeof(struct rte_acl_field) * (fld_num))
最后一个参数最大规则数量(max_rule_num)设定为10万条(宏MAX_ACL_RULE_NUM)。
函数rte_acl_create实现如下,首先为传入参数结构中的ACL名称增加ACL_前缀。随后,在全局ACL链表中遍历查找,如果已经存在其名称与当前名称相同的ACL上下文结构,返回此结构即可。
struct rte_acl_ctx *rte_acl_create(const struct rte_acl_param *param)
{
struct rte_acl_list *acl_list;
struct rte_tailq_entry *te;
acl_list = RTE_TAILQ_CAST(rte_acl_tailq.head, rte_acl_list);
snprintf(name, sizeof(name), "ACL_%s", param->name);
/* if we already have one with that name */
TAILQ_FOREACH(te, acl_list, next) {
ctx = (struct rte_acl_ctx *) te->data;
if (strncmp(param->name, ctx->name, sizeof(ctx->name)) == 0)
break;
}
否则,创建新的ACL上下文结构(rte_acl_ctx),其占用空间大小(sz)为rte_acl_ctx上下文结构自身空间大小与所有的规则所占空间大小之和。另外,分配一个rte_tailq_entry链表项结构,以便向全局ACL链表上添加。在ACL上下文结构中规则链表开始的地址(rules成员)需要跳过rte_acl_ctx自身的长度。成员alg初始化为默认的rte_acl_default_classify。
/* calculate amount of memory required for pattern set. */
sz = sizeof(*ctx) + param->max_rule_num * param->rule_size;
/* if ACL with such name doesn't exist, then create a new one. */
if (te == NULL) {
ctx = NULL;
te = rte_zmalloc("ACL_TAILQ_ENTRY", sizeof(*te), 0);
ctx = rte_zmalloc_socket(name, sz, RTE_CACHE_LINE_SIZE, param->socket_id);
/* init new allocated context. */
ctx->rules = ctx + 1;
ctx->max_rules = param->max_rule_num;
ctx->rule_sz = param->rule_size;
ctx->socket_id = param->socket_id;
ctx->alg = rte_acl_default_classify;
snprintf(ctx->name, sizeof(ctx->name), "%s", param->name);
te->data = (void *) ctx;
TAILQ_INSERT_TAIL(acl_list, te, next);
}
添加ACL规则
函数acl_add_rules向指定的ACL上下文中一次添加多条规则(包括单条),首先偏移到ACL上下文中已有规则的末尾,之后将要添加的规则拷贝进去,最后增加ACL上下文结构中的规则计数。
static int acl_add_rules(struct rte_acl_ctx *ctx, const void *rules, uint32_t num)
{
uint8_t *pos;
if (num + ctx->num_rules > ctx->max_rules)
return -ENOMEM;
pos = ctx->rules;
pos += ctx->rule_sz * ctx->num_rules;
memcpy(pos, rules, num * ctx->rule_sz);
ctx->num_rules += num;
ACL规则树
以上创建的ACL上下文结构及其规则,如果要使用,需要对其进行build编译,生成运行时的trie树结构,以便进行高效的查询。由函数rte_acl_build完成,其参数为ACL上下文和rte_acl_config结构的编译控制参数。再次以示例程序l3fwd-acl来说明,初始化了ACL配置参数结构的三个成员,分别是类别数目num_categories,赋值为DEFAULT_MAX_CATEGORIES(1);字段数量num_fields,负责为dim,其值参见以上的说明;以及所有字段的定义成员defs,对于IPv4而言,将ipv4_defs结构的内容拷贝到其中。
struct rte_acl_config acl_build_param;
/* Perform builds */
memset(&acl_build_param, 0, sizeof(acl_build_param));
acl_build_param.num_categories = DEFAULT_MAX_CATEGORIES;
acl_build_param.num_fields = dim;
memcpy(&acl_build_param.defs, ipv6 ? ipv6_defs : ipv4_defs, ipv6 ? sizeof(ipv6_defs) : sizeof(ipv4_defs));
if (rte_acl_build(context, &acl_build_param) != 0)
rte_exit(EXIT_FAILURE, "Failed to build ACL trie\n");
最后,看一下IPv4的ACL字段定义ipv4_defs,如下列出了三种不同的ACL字段类型,由于目的IP地址、目的端口号分别与源IP地址和源端口号同类型,此处做了省略未列出。类型RTE_ACL_FIELD_TYPE_BITMASK对应于五元组的协议字段,长度为一个字节;类型RTE_ACL_FIELD_TYPE_MASK对应于源地址字段,长度为4个字节;类型RTE_ACL_FIELD_TYPE_RANGE对应于源端口字段,长度为2个字节。
struct rte_acl_field_def ipv4_defs[NUM_FIELDS_IPV4] = {
{
.type = RTE_ACL_FIELD_TYPE_BITMASK,
.size = sizeof(uint8_t),
.field_index = PROTO_FIELD_IPV4,
.input_index = RTE_ACL_IPV4VLAN_PROTO,
.offset = 0,
},
{
.type = RTE_ACL_FIELD_TYPE_MASK,
.size = sizeof(uint32_t),
.field_index = SRC_FIELD_IPV4,
.input_index = RTE_ACL_IPV4VLAN_SRC,
.offset = offsetof(struct ipv4_hdr, src_addr) -
offsetof(struct ipv4_hdr, next_proto_id),
},
...
{
.type = RTE_ACL_FIELD_TYPE_RANGE,
.size = sizeof(uint16_t),
.field_index = SRCP_FIELD_IPV4,
.input_index = RTE_ACL_IPV4VLAN_PORTS,
.offset = sizeof(struct ipv4_hdr) -
offsetof(struct ipv4_hdr, next_proto_id),
},
...
};
DPDK-19.02
上一篇: haproxy详解