ACL
数据包过滤,通常根据规则过滤,例如:IP/端口/协议等
下面从API使用角度分析ACL创建和过滤。
下面以5元组为例,说明DPDK acl工作模式
定义规则
在开始匹配前,首先要定义规则,并且传入实际规矩数据
下面先来看看dpdk开始构建规则前,规则和实际数据的定义格式
数据类型
dpdk用field来表示规则里的每一个组成
例如:5元组,就有5个field
field的数据类型是:
| C |
|---|
| //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 |
|---|
| enum {
RTE_ACL_FIELD_TYPE_MASK = 0,
RTE_ACL_FIELD_TYPE_RANGE,
RTE_ACL_FIELD_TYPE_BITMASK
};
|
filed属性
对于任意一元(field),我们需要知道它的属性:类型,匹配模式,在规则里的位置,实际数据的位置等
| C |
|---|
| 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 |
|---|
| 一组实际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 |
|---|
| //对于每一个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 |
|---|
| struct rte_acl_rule_data {
uint32_t category_mask; /**< 每一bit代表一种规则,最多16种规则;
* 每个规则可以申请使用若干种,例如申请0,1 则掩码为3;申请1 则掩码为2
* 如果多条规则,则在匹配时,会从同种里选取优先级最高的规则作为匹配结果
*/
int32_t priority; /**< 规则优先级*/
uint32_t userdata; /**< 用户自定义数据,规则匹配时回填该值*/
};
|
下面就是完整就的rule结构体
| C |
|---|
| //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 |
|---|
| //创建函数
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 |
|---|
| //前面已经分析了struct rte_acl_rule的定义,这个添加就不过多分析了
int rte_acl_add_rules(struct rte_acl_ctx *ctx, const struct rte_acl_rule *rules, uint32_t num);
|
构建查询树
| C |
|---|
| 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 |
|---|
| 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都介绍了。
但是规则种类,查询使用的总类,还不是很清楚,后续再分析。