跳转至

ACL


数据包过滤,通常根据规则过滤,例如:IP/端口/协议等

下面从API使用角度分析ACL创建和过滤。

下面以5元组为例,说明DPDK acl工作模式

定义规则

在开始匹配前,首先要定义规则,并且传入实际规矩数据
下面先来看看dpdk开始构建规则前,规则和实际数据的定义格式

数据类型

dpdk用field来表示规则里的每一个组成
例如:5元组,就有5个field
field的数据类型是:

C
1
2
3
4
5
6
7
//dpdk定义了4种类型
union rte_acl_field_types {
    uint8_t  u8;
    uint16_t u16;
    uint32_t u32;
    uint64_t u64;
};

匹配模式

对于IP,要根据掩码匹配
对于端口,通常要匹配范围或固定值
对于协议(tcp/udp),或者头部里若干bit,可以用字节掩码匹配
dpdk定义了3种匹配模式:

C
1
2
3
4
5
enum {
    RTE_ACL_FIELD_TYPE_MASK = 0,
    RTE_ACL_FIELD_TYPE_RANGE,
    RTE_ACL_FIELD_TYPE_BITMASK
};

filed属性

对于任意一元(field),我们需要知道它的属性:类型,匹配模式,在规则里的位置,实际数据的位置等

C
1
2
3
4
5
6
7
struct rte_acl_field_def {
    uint8_t  type;        /**  数据类型,RTE_ACL_FIELD_TYPE_*  */
    uint8_t  size;        /**  数据大小,1,2,4,7字节*/
    uint8_t  field_index; /**<  规则表里的索引*/
    uint8_t  input_index; /**<  */
    uint32_t offset;      /** 匹配字段距离数据起始位置的偏移*/
};
规则里的第一个域必须是1字节,后续的按4字节分组
ps:经过测试,第一个域必须是1字节。后续分析下构建过程,再来填坑。
C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//ipv4 5元组属性定义
enum {
    PROTO_FIELD_IPV4, //协议类型,tcp/udp
    SRC_FIELD_IPV4,   //源ip
    DST_FIELD_IPV4,   //目的ip
    SRCP_FIELD_IPV4,  //源端口
    DSTP_FIELD_IPV4,  //目的端口
    NUM_FIELDS_IPV4
};
//看看5元素的定义,offset可以先忽略
static struct rte_acl_field_def ipv4_defs[NUM_FIELDS_IPV4] = {
    {
        .type = RTE_ACL_FIELD_TYPE_BITMASK,
        .size = sizeof(uint8_t),              //第一个域必须是1字节
        .field_index = PROTO_FIELD_IPV4,
        .input_index = PROTO_INPUT_IPV4,
        .offset = sizeof(struct rte_ether_hdr) +
            offsetof(struct rte_ipv4_hdr, next_proto_id),
    },
    {
        .type = RTE_ACL_FIELD_TYPE_BITMASK,
        .size = sizeof(uint32_t),
        .field_index = SRC_FIELD_IPV4,
        .input_index = SRC_INPUT_IPV4,
        .offset = sizeof(struct rte_ether_hdr) +
            offsetof(struct rte_ipv4_hdr, src_addr),
    },
    {
        .type = RTE_ACL_FIELD_TYPE_BITMASK,
        .size = sizeof(uint32_t),
        .field_index = DST_FIELD_IPV4,
        .input_index = DST_INPUT_IPV4,
        .offset = sizeof(struct rte_ether_hdr) +
            offsetof(struct rte_ipv4_hdr, dst_addr),
    },
    {
        .type = RTE_ACL_FIELD_TYPE_BITMASK,
        .size = sizeof(uint16_t),
        .field_index = SRCP_FIELD_IPV4,
        .input_index = SRCP_DESTP_INPUT_IPV4,
        .offset = sizeof(struct rte_ether_hdr) +
            sizeof(struct rte_ipv4_hdr) +
            offsetof(struct rte_tcp_hdr, src_port),
    },
    {
        .type = RTE_ACL_FIELD_TYPE_BITMASK,
        .size = sizeof(uint16_t),
        .field_index = DSTP_FIELD_IPV4,
        .input_index = SRCP_DESTP_INPUT_IPV4, //注意这里,由于源跟目的端口加起来才是4字节对齐,所以他们index_filed值一样
        .offset = sizeof(struct rte_ether_hdr) +
            sizeof(struct rte_ipv4_hdr) +
            offsetof(struct rte_tcp_hdr, dst_port),
    },
};

用户规则

规则属性(5元组)已经定义好了,现在看看实际规则。

Text Only
1
2
3
4
5
6
7
一组实际5元组规则
src_ip/masklen dst_ip/masklen src_port : mask dst_port : mask   proto/mask priority
   2.2.2.3/24    2.2.2.7/24      32 : 0xffff        33 : 0xffff    17/0xff   0
   9.9.9.3/24    9.9.9.7/24      32 : 0xffff        33 : 0xffff    17/0xff   1
   9.9.9.3/24    9.9.9.7/24      32 : 0xffff        33 : 0xffff     6/0xff   2
   9.9.8.3/24    9.9.8.7/24      32 : 0xffff        33 : 0xffff     6/0xff   3
   6.7.8.9/24    2.3.4.5/24      32 : 0x0000        33 : 0x0000   132/0xff   4
实际field的存储
C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//对于每一个filed,需要保存数值与mask的值
struct rte_acl_field {
    union rte_acl_field_types value; /**< a 1,2,4, or 8 字节值 */
    union rte_acl_field_types mask_range; //掩码或范围
    /**<
     * 根据field的类型决定,如果是bitmask,mask_range就是掩码,如果是range,mask_range就是范围
     * mask -> 1.2.3.4/32 value=0x1020304, mask_range=32,
     * range -> 0:65535 value=0, mask_range=65535, 即value-mask_range这个范围
     * bitmask -> 0x06/0xff value=6, mask_range=0xff.
     */
};
再加上下面这个rule数据结构体
C
1
2
3
4
5
6
7
8
struct rte_acl_rule_data {
    uint32_t category_mask; /**< 每一bit代表一种规则,最多16种规则; 
                              *  每个规则可以申请使用若干种,例如申请0,1 则掩码为3;申请1 则掩码为2
                              *  如果多条规则,则在匹配时,会从同种里选取优先级最高的规则作为匹配结果
                              */
    int32_t  priority;      /**< 规则优先级*/
    uint32_t userdata;      /**< 用户自定义数据,规则匹配时回填该值*/
};
下面就是完整就的rule结构体
C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//rte_acl_rule实际是用下面这个宏定义的
#define RTE_ACL_RULE_DEF(name, fld_num) struct name {\
    struct rte_acl_rule_data data;               \
    struct rte_acl_field field[fld_num];         \
}
//RTE_ACL_RULE_DEF(rte_acl_rule,) 展开如下:
struct rte_acl_rule{
    struct rte_acl_rule_data data; //规则其他信息
    struct rte_acl_field field[]; //实际规则地址 
};

构建

由于acl上下文结构体包含内容比较多,暂时先从API使用角度分析

创建ACL上下文

C
1
2
3
4
5
6
7
8
9
//创建函数
struct rte_acl_ctx *rte_acl_create(const struct rte_acl_param *param);
//其中有acl参数
struct rte_acl_param {
    const char *name;         /** 名字*/
    int         socket_id;    /**<从哪个socket分配内存*/
    uint32_t    rule_size;    /**< 一条规则的大小,即 sizeof(struct rte_acl_rule_data)+sizeof(rte_acl_field)*field_num */
    uint32_t    max_rule_num; /**< 规则最大个数*/
};

添加ACL规则

C
1
2
//前面已经分析了struct rte_acl_rule的定义,这个添加就不过多分析了
int rte_acl_add_rules(struct rte_acl_ctx *ctx, const struct rte_acl_rule *rules, uint32_t num);

构建查询树

C
1
2
3
4
5
6
7
8
int rte_acl_build(struct rte_acl_ctx *ctx, const struct rte_acl_config *cfg);
//构建配置参数
struct rte_acl_config {
    uint32_t num_categories; /**< 使用多少种类规则构建查询树,后续深入阅读源码再来分析这个参数*/
    uint32_t num_fields;     /**< 一条规则里有几个field,例如5元组就是5个*/
    struct rte_acl_field_def defs[RTE_ACL_MAX_FIELDS]; //field的定义,参见上面IPv4 5元组的定义
    size_t max_size;         /**< 内部构建最大内存,这个稍后分析 */
};

查找

C
1
2
3
4
int rte_acl_classify(const struct rte_acl_ctx *ctx,
         const uint8_t **data,
         uint32_t *results, uint32_t num,
         uint32_t categories);

总结

至此,就简单把dpdk-acl的api都介绍了。
但是规则种类,查询使用的总类,还不是很清楚,后续再分析。