欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DPDK ACL链表

程序员文章站 2024-02-28 17:32:28
...

 

本文简介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

 

相关标签: DPDK ACL