当控制面更新一条 ACL 规则时,如何更新给数据面

我们以 DPDK 提供的 ACL 库(libacl 为背景,结合典型的负载均衡或 NFV 架构(如 DPVS、VPP 等),详细解析整个流程。


一、前置知识:DPDK ACL 是什么?

1. DPDK ACL 库(rte_acl

  • DPDK 提供了一个高性能的 多维字段匹配库rte_acl
  • 它支持基于多个字段(如源IP、目的IP、源端口、目的端口、协议、VLAN等)进行 规则匹配
  • 使用 Trie + 搜索优化算法(如 SIMD) ,实现微秒级规则查找。
  • 适用于:防火墙、ACL、策略路由、服务链(Service Chaining)等场景。

2. 典型架构:控制面 + 数据面分离

lua 复制代码
text
深色版本
+------------------+       +------------------+
|   Control Plane    |     |   Data Plane       |
| (Single thread or |<--->| (Multiple lcores)  |
|  management proc)  | IPC |                  |
+------------------+       +------------------+
     ↑                             ↑
  配置输入                    报文处理
  策略决策                    ACL 匹配
  规则编译                    快速转发
  • 控制面:负责接收配置、解析规则、构建 ACL 分类树(Trie)、下发到数据面。
  • 数据面 :加载 ACL 规则库,对每个报文执行 rte_acl_classify() 进行匹配,决定动作(permit/deny/redirect)。

二、场景设定

假设我们有一个基于 DPDK 的负载均衡器(如 DPVS 增强版),支持 ACL 功能。

现有 ACL 规则:

arduino 复制代码
text
深色版本
ACL 100:
  rule 1: permit tcp 192.168.1.0/24 → 10.1.1.100 80
  rule 2: deny   ip  any         → any

现在,控制面要新增一条规则

arduino 复制代码
text
深色版本
rule 3: permit udp 10.0.0.50 → 10.1.1.100 53

我们要看:从配置变更到数据面生效的全过程


三、控制面做了什么?(Control Plane Actions)

步骤 1:接收配置变更

  • 用户通过 CLI、API 或配置文件提交新规则。
  • 控制面模块(如 acl_manager)接收到更新请求。

步骤 2:解析与验证规则

  • 调用 ACL 解析器,将文本规则转换为内部结构体
arduino 复制代码
c
深色版本
struct acl_rule {
    uint32_t priority;     // 优先级(越小越高)
    uint8_t  proto;        // 协议:TCP/UDP/ICMP
    uint32_t src_ip;       // 源IP
    uint32_t src_mask;     // 源掩码
    uint32_t dst_ip;
    uint32_t dst_mask;
    uint16_t src_port_low, src_port_high;
    uint16_t dst_port_low, dst_port_high;
    uint32_t action;       // permit/deny/log
};
  • 验证 IP 格式、端口范围、避免冲突等。

步骤 3:更新控制面规则数据库

  • 将新规则插入控制面的 ACL 规则列表(通常是一个有序链表或数组,按优先级排序)。
  • 此时规则尚未生效,仅存在于控制面内存中。

步骤 4:重建 ACL 搜索结构(最关键一步)

DPDK 的 rte_acl 要求将规则集整体编译 成一个 Trie 结构(或称为"分类树"),不能"增量更新"。

所以控制面必须:

  1. 收集当前所有有效规则(包括新添加的 rule 3);
  2. 按优先级排序;
  3. 调用 rte_acl_build() 重建整个 ACL 上下文(struct rte_acl_ctx);
ini 复制代码
c
深色版本
struct rte_acl_ctx *acl_ctx;
struct rte_acl_config acl_cfg = {
    .num_categories = 1,  // 通常 1 表示单个动作维度
    .num_fields = 5,      // 5元组字段
};

// 定义字段类型(Field Types)
static const struct rte_acl_field_def field_defs[5] = {
    {.type = RTE_ACL_FIELD_TYPE_MASK, .size = 4, .field_index = 0}, // SIP
    {.type = RTE_ACL_FIELD_TYPE_MASK, .size = 4, .field_index = 1}, // DIP
    {.type = RTE_ACL_FIELD_TYPE_RANGE, .size = 2, .field_index = 2}, // Sport
    {.type = RTE_ACL_FIELD_TYPE_RANGE, .size = 2, .field_index = 3}, // Dport
    {.type = RTE_ACL_FIELD_TYPE_BITMASK, .size = 1, .field_index = 4}, // Proto
};

// 1. 创建 ACL 上下文
acl_ctx = rte_acl_create(&acl_param);

// 2. 添加所有规则(调用 rte_acl_add_rules())
rte_acl_add_rules(acl_ctx, rules_array, num_rules);

// 3. 构建 Trie 结构
rte_acl_build(acl_ctx, &config);

⚠️ 注意:rte_acl 不支持热更新 ,必须重建整个 acl_ctx


四、控制面推送什么给数据面?

控制面不能直接把 rte_acl_ctx 对象通过 IPC 发送(因为涉及指针、内存布局不同),所以采用以下方式:

方式 1:序列化规则 + 重建(推荐)

  • 控制面将所有规则 (包括新增的)序列化为二进制或 JSON 格式

  • 通过 共享内存RPC/消息队列 (如 DPDK 的 rte_mp_msg)发送给每个数据面 lcore;

  • 数据面收到后:

    • 释放旧的 rte_acl_ctx
    • 调用 rte_acl_create()rte_acl_add_rules()rte_acl_build() 重建新的 ACL 上下文;
    • 原子替换旧的指针(使用 rte_atomic 或内存屏障);

方式 2:共享内存 + 版本号(高性能场景)

  • 控制面将新的 rte_acl_ctx 构建在共享内存区域
  • 更新一个全局版本号指针原子更新
  • 数据面轮询版本号,发现变化后切换到新的 acl_ctx
  • 旧的 acl_ctx 由控制面延迟释放(等待所有数据面不再引用);

这种方式避免了规则传输开销,但实现复杂,需考虑内存一致性。


五、数据面如何更新转发规则?

步骤 1:接收更新通知

  • 数据面通过 IPC、共享内存轮询或中断方式得知"ACL 规则已更新"。

步骤 2:加载新 ACL 上下文

scss 复制代码
c
深色版本
// 假设新上下文通过共享内存映射
extern struct rte_acl_ctx *g_new_acl_ctx;

// 原子替换(伪代码)
struct rte_acl_ctx *old_ctx = rte_atomic64_cmpset(&g_current_acl_ctx, g_new_acl_ctx);

// 可选:延迟释放旧上下文(防止正在使用)
schedule_free_acl_ctx(old_ctx);

步骤 3:报文处理使用新规则

在数据面报文处理循环中:

ini 复制代码
c
深色版本
void lcore_main_loop() {
    while (1) {
        // 收包
        nb_rx = rte_eth_rx_burst(port, queue, pkts, BURST_SIZE);

        for (i = 0; i < nb_rx; i++) {
            struct rte_mbuf *pkt = pkts[i];
            const struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod_offset(pkt, ...);

            uint32_t results[1];
            int ret;

            // 使用当前最新的 ACL 上下文进行分类
            ret = rte_acl_classify(g_current_acl_ctx,     // volatile 指针
                                   (const uint8_t **)pkts,
                                   results, 1, 0);  // 1 packet, 0 context

            if (ret == 0) {
                uint32_t action = results[0];  // 低8位通常是动作
                switch (action) {
                case ACL_ACTION_PERMIT:
                    forward_packet(pkt);
                    break;
                case ACL_ACTION_DENY:
                    drop_packet(pkt);
                    break;
                }
            }
        }
    }
}

从下一条报文开始,新规则生效


六、关键挑战与优化

挑战 解决方案
规则重建耗时 控制面异步构建,避免阻塞;使用多线程加速 rte_acl_build
数据面中断 双缓冲(Double Buffering):新旧 acl_ctx 同时存在,原子切换
内存占用 规则压缩、字段合并、使用更小的 Trie
一致性 版本号 + 引用计数,确保旧规则不再使用后再释放
增量更新缺失 使用支持增量的 ACL 库(如 FD.io VPP 的 classify),或自研

七、总结:完整流程图

diff 复制代码
text
深色版本
+---------------------+
|  用户添加 ACL 规则    |
+----------+----------+
           |
           v
+---------------------+
| 控制面:解析并验证规则 |
+----------+----------+
           |
           v
+-----------------------------+
| 更新控制面规则数据库         |
+--------------+--------------+
               |
               v
+----------------------------------+
| 重建 ACL 上下文(rte_acl_build)  |
+--------------+-------------------+
               |
               v
+----------------------------------+
| 推送新规则或新上下文给数据面       |
|  (共享内存 / IPC / 消息队列)       |
+--------------+-------------------+
               |
               v
+----------------------------------+
| 数据面:接收更新,原子替换 acl_ctx |
+--------------+-------------------+
               |
               v
+----------------------------------+
| 数据面:后续报文使用新 ACL 规则匹配 |
+----------------------------------+

八、结论

  • 控制面负责 :规则解析、验证、排序、调用 rte_acl_build() 重建整个 ACL 上下文。
  • 推送内容不是单条规则 ,而是整个规则集编译后的上下文 ,或序列化的规则列表
  • 数据面更新 :接收新上下文,原子替换指针,后续报文即使用新规则。
  • ⚠️ 限制 :DPDK rte_acl 不支持增量更新,每次变更需重建整个结构,大规则集时开销大。
  • 🔄 趋势:现代系统(如 VPP、DPDK 增强版)正在引入支持增量更新的分类器,以降低更新延迟。

这种机制保证了高性能匹配规则一致性 ,是 NFV、云网络、智能网卡中 ACL 实现的标准范式

相关推荐
Victor356几秒前
Redis(10)如何连接到Redis服务器?
后端
他日若遂凌云志2 小时前
深入剖析 Fantasy 框架的消息设计与序列化机制:协同架构下的高效转换与场景适配
后端
快手技术2 小时前
快手Klear-Reasoner登顶8B模型榜首,GPPO算法双效强化稳定性与探索能力!
后端
二闹2 小时前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
林太白2 小时前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈2 小时前
VS Code 终端完全指南
后端
该用户已不存在3 小时前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃3 小时前
内存监控对应解决方案
后端
码事漫谈3 小时前
VS Code Copilot 内联聊天与提示词技巧指南
后端