VPP中ACL源码详解第三篇:ACL是如何以统一的接口来为其他插件服务(上)

本篇文章主要讲解第五部分(ACL作为服务与内部机制扩展)的前4小节,其他部分请阅读专栏其他篇幅

目录

第一部分:ACL插件的作用和意义

  • [第1章:从生活中的"门禁系统"说起------ACL 插件整体认知](#第1章:从生活中的"门禁系统"说起——ACL 插件整体认知)
    • 1.1 先不管 VPP:现代 ACL 系统应该会做哪些事?
    • 1.2 再看 VPP:ACL 插件具体提供了哪些能力?
    • 1.3 ACL 插件在 VPP 里的大致位置:插在哪些弧上?
    • 1.4 ACL 与其他模块的关系:谁先谁后、谁配合谁?
    • 1.5 常见场景:ACL 插件在工程中的几种典型用法

第二部分:ACL插件的整体架构

  • 第2章:模块架构和文件组织

    • 2.1 ACL插件的文件组织结构
    • 2.2 各文件的功能和职责
    • 2.3 模块间的依赖关系
    • 2.4 模块与外部系统的关系
    • 2.5 文件组织的设计原则
    • 2.6 总结:文件组织的"设计哲学"
  • 第3章:核心数据结构

    • 3.1 acl_rule_t:一条 IP ACL 规则长什么样?
    • 3.2 acl_list_t:一张 IP ACL 表(很多规则 + 一个标签)
    • 3.3 macip_acl_rule_t:一条 MAC+IP 绑定规则
    • 3.4 macip_acl_list_t:一张 MACIP ACL 表
    • 3.5 acl_main_t:ACL 插件的全局大脑
    • 3.6 会话相关结构体:Flow-aware ACL 的骨架
    • 3.7 Hash 查找相关结构体:高性能匹配的"索引卡片"
    • 3.8 Lookup Context 与对外导出方法:ACL 作为服务的"接口面"
    • 3.9 数据结构之间的关系小结

第三部分:ACL插件的初始化和控制平面

  • 第4章:模块初始化------ACL插件是如何"开机启动"的?

    • 4.1 插件注册:告诉VPP"我是谁"
    • 4.2 初始化函数注册:告诉VPP"什么时候叫我"
    • 4.3 初始化主函数:acl_init 的完整流程
    • 4.4 初始化流程总结:从"冷启动"到"就绪"
    • 4.5 关键概念深入理解
    • 4.6 初始化完成后的状态
    • 4.7 本章小结
  • 第5章:ACL规则管理------如何"登记员工名单"?

    • 5.1 从API消息到规则存储:完整的流程概览
    • 5.2 API消息处理:接收"入职申请"
    • 5.3 核心函数:acl_add_list 的完整实现
    • 5.4 ACL规则删除:如何"办理离职手续"
    • 5.5 规则替换机制:add_replace 的语义
    • 5.6 规则验证详解:为什么需要这么多检查?
    • 5.7 规则转换详解:API格式 vs 内部格式
    • 5.8 通知机制:为什么需要通知相关系统?
    • 5.9 完整流程总结:从API到存储
    • 5.10 本章小结
  • 第6章:接口ACL绑定------如何"给门装上锁"?

    • 6.1 接口ACL绑定的概念:什么是"绑定"?
    • 6.2 从API消息到绑定完成:完整的流程概览
    • 6.3 API消息处理:接收"安装门禁申请"
    • 6.4 核心函数:acl_interface_add_del_inout_acl
    • 6.5 核心函数:acl_interface_set_inout_acl_list(完整实现)
    • 6.6 Policy Epoch机制:什么是"策略纪元"?
    • 6.7 Feature Arc启用/禁用:如何"打开/关闭门禁"?
    • 6.8 多ACL串联处理:如何"安装多个门禁系统"?
    • 6.9 反向索引的维护:为什么需要"反向查找"?
    • 6.10 完整流程总结:从API到Feature Arc启用
    • 6.11 关键概念深入理解
    • 6.12 本章小结
  • [第7章:MACIP ACL管理------如何"绑定IP和MAC地址"?](#第7章:MACIP ACL管理——如何"绑定IP和MAC地址"?)

    • 7.1 MACIP ACL的概念:什么是"MAC+IP绑定"?
    • 7.2 MACIP ACL的用途:为什么需要"MAC+IP绑定"?
    • 7.3 从API消息到MACIP ACL创建:完整的流程概览
    • 7.4 API消息处理:接收"建立身份验证系统申请"
    • 7.5 核心函数:macip_acl_add_list 的完整实现
    • 7.6 L2 Classify表构建:macip_create_classify_tables 的核心逻辑
    • 7.7 MACIP ACL接口绑定:如何"安装身份验证系统"?
    • 7.8 MACIP ACL删除:如何"销毁身份验证系统"?
    • 7.9 API消息处理:macip_acl_interface_add_del
    • 7.10 MACIP ACL vs IP ACL:关键区别总结
    • 7.11 完整流程总结:从API到Classify表应用
    • 7.12 本章小结

第四部分:数据平面ACL处理

  • 第8章:数据平面ACL匹配流程------数据包如何"过安检"?

    • 8.1 数据平面ACL匹配的概念:什么是"数据平面"?
    • 8.2 数据平面节点:ACL插件如何"插入"到数据包处理流程?
    • 8.3 数据包处理流程:从节点调用到ACL匹配
    • 8.4 5-tuple提取:如何"读取旅客信息"?
    • 8.5 ACL匹配逻辑:如何"检查旅客是否符合规则"?
    • 8.6 多ACL匹配:如何"检查多个安检规则表"?
    • 8.7 会话管理:如何"记录常客信息"?
    • 8.8 完整流程总结:从数据包到达到ACL匹配完成
    • 8.9 性能优化技术
    • 8.10 本章小结
  • [第9章:Flow-aware ACL详细实现------如何"记录和管理常客信息"?](#第9章:Flow-aware ACL详细实现——如何"记录和管理常客信息"?)

    • 9.1 Flow-aware ACL的概念:什么是"有状态ACL"?
    • 9.2 会话结构体:如何"存储常客信息"?
    • 9.3 会话创建:如何"登记新常客"?
    • 9.4 会话查找:如何"查找常客信息"?
    • 9.5 会话跟踪:如何"更新常客访问记录"?
    • 9.6 会话超时类型:如何"判断常客类型"?
    • 9.7 会话超时计算:如何"计算常客过期时间"?
    • 9.8 会话链表管理:如何"管理待检查列表"?
    • 9.9 反向会话:如何"匹配返回流量"?
    • 9.10 会话处理:如何"处理已有会话的数据包"?
    • 9.11 会话删除:如何"注销常客信息"?
    • 9.12 会话清理:如何"清理过期常客"?
    • 9.13 会话清理进程:如何"定期清理过期常客"?
    • 9.14 Policy Epoch机制:如何"检测过期会话"?
    • 9.15 会话链表删除:如何"从待检查列表中删除常客"?
    • 9.16 会话创建条件:如何"判断是否可以创建新会话"?
    • 9.17 完整流程总结:从数据包到会话管理完成
    • 9.18 本章小结
  • 第10章:Hash匹配引擎------如何"用字典快速查找规则"?

    • 10.1 Hash匹配引擎的概念:什么是"字典查找"?
    • 10.2 数据结构:如何"组织规则信息"?
    • 10.3 Hash表构建:如何"建立字典索引"?
    • 10.4 掩码类型分配:如何"给规则分配分类"?
    • 10.5 Hash表条目激活:如何"将规则添加到字典"?
    • 10.6 TupleMerge算法:如何"合并相似分类"?
    • 10.7 Hash匹配流程:如何"使用字典查找规则"?
    • 10.8 本章小结

第五部分:ACL作为服务与内部机制扩展(ACL-as-a-service)

  • 第11章:ACL-as-a-service------如何"让其他插件使用ACL引擎"?

    • 11.1 ACL-as-a-service的概念:什么是"ACL作为服务"?
    • 11.2 数据结构:如何"组织用户和上下文信息"?
    • 11.3 用户模块注册:如何"注册为ACL服务用户"?
    • 11.4 Lookup Context创建:如何"创建安检规则集合"?
    • 11.5 ACL列表设置:如何"为规则集合添加规则"?
    • 11.6 Lookup Context释放:如何"销毁安检规则集合"?
    • 11.7 ACL锁定和解锁:如何"追踪ACL的使用情况"?
    • 11.8 ACL应用和取消应用:如何"将规则添加到Hash表"?
    • 11.9 ACL变更通知:如何"通知上下文ACL规则变化"?
    • 11.10 方法导出和初始化:如何"让外部插件调用ACL方法"?
    • 11.11 5-tuple填充和匹配:如何"使用ACL匹配引擎"?
    • 11.12 完整使用流程:从注册到匹配
    • 11.13 本章小结
  • 第12章:会话表管理------如何"建立和管理常客数据库"?

    • 12.1 会话表管理的概念:什么是"会话表管理"?
    • 12.2 会话表初始化:如何"建立常客数据库"?
    • 12.3 Per-worker数据结构:如何"为每个安检通道建立档案系统"?
    • 12.4 会话表容量管理:如何"管理数据库容量"?
    • 12.5 会话表性能优化:如何"优化数据库性能"?
    • 12.6 会话表监控和调试:如何"监控和调试数据库"?
    • 12.7 会话表清理机制:如何"定期清理过期记录"?
    • 12.8 会话表容量管理:如何"管理数据库容量"?
    • 12.9 会话表性能优化:如何"优化数据库性能"?
    • 12.10 会话表监控和调试:如何"监控和调试数据库"?
    • 12.11 会话表CLI命令:如何"使用命令行管理数据库"?
    • 12.12 会话表初始化时机:何时"建立数据库"?
    • 12.13 会话表容量限制:如何"防止数据库溢出"?
    • 12.14 会话表性能调优:如何"优化数据库性能"?
    • 12.15 会话表监控指标:如何"监控数据库健康状态"?
    • 12.16 本章小结
  • 第13章:ACL插件性能优化------如何"让安检更快更高效"?

    • 13.1 性能优化的概念:什么是"性能优化"?
    • 13.2 批量处理优化:如何"一次处理多个数据包"?
    • 13.3 预取优化:如何"提前准备好数据"?
    • 13.4 流水线处理:如何"同时处理多个数据包"?
    • 13.5 缓存优化:如何"优化内存访问"?
    • 13.6 分支预测优化:如何"帮助CPU预测分支"?
    • 13.7 性能调优建议:如何"优化ACL插件性能"?
    • 13.8 性能监控:如何"监控ACL插件性能"?
    • 13.9 本章小结
  • 第14章:ACL插件调试工具------如何"诊断和排查问题"?

    • 14.1 调试工具的概念:什么是"调试工具"?
    • 14.2 Trace功能:如何"跟踪数据包处理过程"?
    • 14.3 ELOG功能:如何"记录系统事件"?
    • 14.4 Show命令:如何"显示系统状态"?
    • 14.5 调试宏:如何"输出调试信息"?
    • 14.6 故障排查方法:如何"诊断和解决问题"?
    • 14.7 本章小结
  • 第15章:ACL插件高级功能------如何"处理非IP数据包"?

    • 15.1 高级功能的概念:什么是"非IP数据包处理"?
    • 15.2 Ethertype白名单:如何"管理特殊旅客白名单"?
    • 15.3 非IP数据包处理节点:如何"检查特殊旅客"?
    • 15.4 节点注册和Feature Arc集成:如何"注册特殊旅客检查点"?
    • 15.5 Trace格式化:如何"格式化检查记录"?
    • 15.6 本章小结
  • [第16章:MACIP ACL数据面处理------如何"检查MAC+IP绑定"?](#第16章:MACIP ACL数据面处理——如何"检查MAC+IP绑定"?)

    • 16.1 MACIP ACL数据面处理的概念:什么是"MAC+IP绑定检查"?
    • 16.2 MACIP ACL数据结构:如何"存储MAC+IP绑定规则"?
    • 16.3 匹配类型管理:如何"组织MAC+IP绑定规则"?
    • 16.4 L2 Classify表构建:如何"建立快速查找表"?
    • 16.5 接口应用:如何"将MACIP ACL应用到接口"?
    • 16.6 MACIP ACL在L2输入弧中的位置:如何"集成到数据平面"?
    • 16.7 本章小结
  • 第17章:Hash查找引擎------如何"用字典快速查找规则"?

    • 17.1 Hash查找的必要性:为什么需要"字典查找"?
    • 17.2 掩码类型(Mask Type)概念:什么是"字典分类"?
    • 17.3 掩码类型池管理:如何"管理字典分类"?
    • 17.4 ACL规则到Hash条目的转换:如何"将规则转换为字典条目"?
    • 17.5 Hash表构建:如何"建立字典"?
    • 17.6 Hash查找流程:如何"使用字典查找规则"?
    • 17.7 本章小结
  • [第19章:Lookup Context机制------ACL作为服务的核心设计](#第19章:Lookup Context机制——ACL作为服务的核心设计)

    • 19.1 ACL-as-a-Service的概念:为什么需要Lookup Context?
    • 19.2 用户模块注册:acl_lookup_context_user_t
    • 19.3 Lookup Context创建:acl_lookup_context_t
    • 19.4 Context与ACL的绑定:set_acl_vec_for_context
    • 19.5 多插件复用ACL引擎:exports.h和exported_types.h
    • 19.6 Lookup Context的释放:put_lookup_context_index
    • 19.7 ACL变更通知机制:notify_acl_change
    • 19.8 调试和展示:show_lookup_context和show_lookup_user
    • 19.9 本章小结
  • 第20章:ACL插件方法导出------让其他插件也能"用上这套门禁系统"

    • 20.1 acl_plugin_methods_t:ACL服务"菜单"的结构
    • 20.2 方法注册和导出:ACL插件如何填充方法表?
    • 20.3 外部插件如何使用这些方法?(调用流程示意)
    • 20.4 内联匹配函数:acl_plugin_fill_5tuple_inlineacl_plugin_match_5tuple_inline
    • 20.5 本章小结

第六部分:多核和性能优化

  • 第21章:多核会话管理------如何让多个"安检员"协同工作?

    • 21.1 Per-worker 数据结构:为每个"安检员"准备独立的办公桌
    • 21.2 会话表分布策略:如何决定旅客档案放在哪个安检通道?
    • 21.3 线程间同步机制:如何让多个"安检员"协同工作而不冲突?
    • 21.4 本章小结
  • 第22章:性能优化技术------如何让"安检系统"快如闪电?

    • 22.1 批量处理优化:如何"批量安检"提高效率?
    • 22.2 预取(Prefetch)优化:如何"提前准备材料"减少等待时间?
    • 22.3 缓存行对齐:如何避免"伪共享"导致的性能问题?
    • 22.4 分支预测优化:如何帮助CPU"猜对"程序执行路径?
    • 22.5 向量化处理:如何一次处理多个数据包?
    • 22.6 本章小结

第七部分:可观测性和调试

  • 第23章:错误处理、日志记录与调试追踪机制------如何"记录安检日志和排查问题"?

    • 23.1 错误处理机制:如何"标记和处理安检异常"?
    • 23.2 日志记录机制:如何"记录安检工作日志"?
    • 23.3 数据包追踪机制:如何"回放特定旅客的安检过程"?
    • 23.4 计数器统计机制:如何"统计安检工作数据"?
    • 23.5 事件日志追踪(ELOG):如何"记录详细的操作日志"?
    • 23.6 本章小结
  • [第24章:日志与 Trace 实战------如何"从外部视角"排查 ACL 问题?](#第24章:日志与 Trace 实战——如何"从外部视角"排查 ACL 问题?)

    • 24.1 总体排障思路:从"外症状"到"内原因"
    • 24.2 ACL 插件 CLI 命令总览:有什么"观察窗口"?
    • 24.3 show acl-plugin sessions:看清"常客"会话情况
    • 24.4 set acl-plugin ...:开启事件 Trace 和调试开关
    • 24.5 VPP 通用 Packet Trace:抓一条"问题包"的全链路
    • 24.6 本章小结
  • 第25章:CLI和API接口------如何"指挥"ACL插件做事?

    • 25.1 ACL相关CLI命令
    • 25.2 MACIP ACL相关CLI命令
    • 25.3 ACL API消息处理
    • 25.4 API消息格式和编码
    • 25.5 本章小结

第八部分:综合案例和最佳实践

  • 第26章:综合配置案例

    • 26.1 边界防火墙配置
    • 26.2 内部安全区隔离
    • 26.3 机房接入控制
    • 26.4 多ACL串联配置
    • 26.5 有状态ACL配置
    • 26.6 本章总结
  • 第27章:性能调优实践

    • 27.1 Hash匹配启用建议
    • 27.2 TupleMerge参数调优
    • 27.3 会话超时配置建议
    • 27.4 多核配置优化
    • 27.5 规则组织最佳实践
    • 27.6 本章总结
  • 第28章:故障排查指南

    • 28.1 常见问题诊断
    • 28.2 规则匹配问题排查
    • 28.3 会话表问题排查
    • 28.4 性能问题排查
    • 28.5 调试工具使用
    • 28.6 本章总结
  • 第29章:ACL插件总结

    • 29.1 ACL插件的关键特点
    • 29.2 在VPP数据包转发中的作用
    • 29.3 性能优化要点
    • 29.4 与其他模块的关系
    • 29.5 最佳实践和注意事项
    • 29.6 知识体系总结
    • 29.7 本章总结

第11章:ACL-as-a-service------如何"让其他插件使用ACL引擎"?

生活类比:想象一下,你是一个"安检服务提供商",不仅为自己的机场提供安检服务,还向其他机场(其他插件)提供安检服务。

  • 传统方式:每个机场都要自己建立安检系统(每个插件都要自己实现ACL匹配)
  • ACL-as-a-service:其他机场可以"租用"你的安检服务(其他插件可以使用ACL插件的匹配引擎)

这一章,我们就跟着ACL插件的代码,看看它是如何"让其他插件使用ACL引擎"的。每一步我都会详细解释,确保你完全理解。


11.1 ACL-as-a-service的概念:什么是"ACL作为服务"?

11.1.1 什么是ACL-as-a-service?

ACL-as-a-service(ACL作为服务):一种机制,允许其他VPP插件使用ACL插件的匹配引擎,而无需自己实现ACL匹配逻辑。

关键概念详解

  1. 传统方式的问题

    • 问题:每个需要ACL匹配的插件都要自己实现匹配逻辑
    • 缺点:代码重复、维护困难、性能不一致
    • 类比:就像"每个机场都要自己建立安检系统"(重复建设,浪费资源)
  2. ACL-as-a-service的优势

    • 代码复用:多个插件共享同一个ACL匹配引擎
    • 统一优化:ACL插件的性能优化对所有用户都有效
    • 易于维护:只需要维护一个ACL匹配引擎
    • 类比:就像"多个机场共享一个安检服务提供商"(统一标准,易于维护)
  3. Lookup Context(查找上下文):一个抽象的ACL匹配上下文,不绑定到特定接口

    • 作用:将多个ACL规则组织在一起,用于first-match查找
    • 优势:可以用于接口ACL、ACL-based forwarding等多种场景
    • 类比:就像"一个安检规则集合"(可以用于不同的机场)
  4. 用户模块(User Module):使用ACL插件的其他插件

    • 注册:在使用ACL插件之前,需要先注册为用户模块
    • 标识:每个用户模块有一个唯一的ID
    • 类比:就像"租用安检服务的机场"(需要先注册,获得唯一标识)
11.1.2 为什么需要ACL-as-a-service?

使用场景

  1. ACL-based Forwarding:基于ACL规则的转发

    • 场景:根据ACL规则匹配结果决定数据包的转发路径
    • 需求:需要ACL匹配,但不绑定到接口
    • 类比:就像"根据安检结果决定旅客的登机口"(不绑定到特定接口)
  2. Policy-based Routing:基于策略的路由

    • 场景:根据ACL规则匹配结果选择不同的路由表
    • 需求:需要ACL匹配,但用于路由决策
    • 类比:就像"根据安检结果选择不同的航班"(用于路由决策)
  3. 多插件复用:多个插件都需要ACL匹配

    • 场景:多个插件都需要使用ACL匹配功能
    • 需求:避免重复实现,统一使用ACL插件的匹配引擎
    • 类比:就像"多个机场共享一个安检服务提供商"(避免重复建设)

优势总结

特性 传统方式 ACL-as-a-service
代码复用 每个插件都要实现 共享ACL插件的匹配引擎
性能优化 各自优化,不一致 统一优化,所有用户受益
维护成本 高(多个实现) 低(一个实现)
灵活性 低(绑定到接口) 高(不绑定到接口)

类比

  • 传统方式:就像"每个机场都要自己建立安检系统"(重复建设,浪费资源)
  • ACL-as-a-service:就像"多个机场共享一个安检服务提供商"(统一标准,易于维护)

11.2 数据结构:如何"组织用户和上下文信息"?

11.2.1 用户模块结构体:acl_lookup_context_user_t

acl_lookup_context_user_t 用于存储使用ACL插件的用户模块信息:

c 复制代码
//19:28:src/plugins/acl/lookup_context.h
typedef struct {
  /* A name of the portion of the code using the ACL infra */
  char *user_module_name;                         // 用户模块名称
                                         // user_module_name:使用ACL基础设施的代码部分的名称(如"acl-based-forwarding")
                                         // 为什么需要?用于标识用户模块,便于调试和追踪
                                         // 类比:就像"机场名称"(用于标识租用安检服务的机场)
  
  /* text label for the first u32 user value assigned to context */
  char *val1_label;                               // 第一个用户值的标签
                                         // val1_label:分配给上下文的第一个u32用户值的文本标签(用于调试)
                                         // 为什么需要?用于描述用户值的含义,便于调试
                                         // 类比:就像"第一个用户值的标签"(如"转发表ID")
  
  /* text label for the second u32 user value assigned to context */
  char *val2_label;                               // 第二个用户值的标签
                                         // val2_label:分配给上下文的第二个u32用户值的文本标签(用于调试)
                                         // 为什么需要?用于描述用户值的含义,便于调试
                                         // 类比:就像"第二个用户值的标签"(如"策略ID")
  
  /* vector of lookup contexts of this user */
  u32 *lookup_contexts;                           // 此用户的Lookup Context索引列表
                                         // lookup_contexts:此用户创建的所有Lookup Context的索引列表
                                         // 为什么需要?用于追踪用户创建的所有上下文,便于管理和清理
                                         // 类比:就像"此机场的所有安检规则集合列表"(用于追踪和管理)
} acl_lookup_context_user_t;

结构体字段总结

  1. 用户模块名称:用于标识用户模块(如"acl-based-forwarding")
  2. 用户值标签:用于描述用户值的含义(便于调试)
  3. Lookup Context列表:此用户创建的所有上下文的索引列表

类比:就像"机场的注册信息":

  • 机场名称:用于标识机场(用户模块名称)
  • 标签:用于描述用户值的含义(如"转发表ID"、"策略ID")
  • 安检规则集合列表:此机场创建的所有安检规则集合(Lookup Context列表)

11.2.2 Lookup Context结构体:acl_lookup_context_t

acl_lookup_context_t 用于存储一个Lookup Context的信息:

c 复制代码
//30:39:src/plugins/acl/lookup_context.h
typedef struct {
  /* vector of acl #s within this context */
  u32 *acl_indices;                               // ACL索引列表
                                         // acl_indices:此上下文中要查找的ACL索引列表(按优先级排序)
                                         // 为什么需要?用于存储要在此上下文中查找的ACL列表
                                         // 类比:就像"安检规则集合"(包含多个安检规则,按优先级排序)
  
  /* index of corresponding acl_lookup_context_user_t */
  u32 context_user_id;                            // 对应的用户模块ID
                                         // context_user_id:创建此上下文的用户模块ID
                                         // 为什么需要?用于追踪上下文的创建者,便于管理和调试
                                         // 类比:就像"创建此安检规则集合的机场ID"(用于追踪创建者)
  
  /* per-instance user value 1 */
  u32 user_val1;                                  // 用户值1
                                         // user_val1:用户指定的第一个u32值(用于区分同一用户的不同上下文实例)
                                         // 为什么需要?用于区分同一用户模块的不同上下文实例
                                         // 类比:就像"第一个用户值"(如"转发表ID",用于区分不同的转发表)
  
  /* per-instance user value 2 */
  u32 user_val2;                                  // 用户值2
                                         // user_val2:用户指定的第二个u32值(用于区分同一用户的不同上下文实例)
                                         // 为什么需要?用于区分同一用户模块的不同上下文实例
                                         // 类比:就像"第二个用户值"(如"策略ID",用于区分不同的策略)
} acl_lookup_context_t;

结构体字段总结

  1. ACL索引列表:此上下文中要查找的ACL列表(按优先级排序)
  2. 用户模块ID:创建此上下文的用户模块ID
  3. 用户值:用户指定的两个u32值(用于区分不同的上下文实例)

类比:就像"一个安检规则集合":

  • 规则列表:包含多个安检规则,按优先级排序(ACL索引列表)
  • 创建者:创建此规则集合的机场(用户模块ID)
  • 标识信息:用于区分不同实例的信息(如"转发表ID"、"策略ID")

11.2.3 ACL插件方法结构体:acl_plugin_methods_t

acl_plugin_methods_t 用于存储ACL插件导出的方法(函数指针):

c 复制代码
//87:91:src/plugins/acl/exported_types.h
#define _(name) acl_plugin_ ## name ## _fn_t name;
typedef struct {
  void *p_acl_main; /* a local copy of a pointer to acl_main */  // ACL主结构体指针
                                         // p_acl_main:指向acl_main的指针(用于访问ACL插件的全局状态)
                                         // 为什么需要?用于在外部插件中访问ACL插件的全局状态
                                         // 类比:就像"安检服务提供商的'总部地址'"(用于访问全局状态)
  
  foreach_acl_plugin_exported_method_name  // 导出的方法列表(通过宏展开)
                                         // 包括:acl_exists、register_user_module、get_lookup_context_index等
                                         // 为什么需要?用于外部插件调用ACL插件的方法
                                         // 类比:就像"安检服务提供商的'服务接口列表'"(用于调用服务)
} acl_plugin_methods_t;
#undef _

导出的方法列表 (通过foreach_acl_plugin_exported_method_name宏展开):

c 复制代码
//78:85:src/plugins/acl/exported_types.h
#define foreach_acl_plugin_exported_method_name \
_(acl_exists)                          // 检查ACL是否存在
_(register_user_module)                // 注册用户模块
_(get_lookup_context_index)            // 获取Lookup Context索引
_(put_lookup_context_index)            // 释放Lookup Context索引
_(set_acl_vec_for_context)             // 为上下文设置ACL列表
_(fill_5tuple)                         // 填充5-tuple
_(match_5tuple)                        // 匹配5-tuple

结构体字段总结

  1. ACL主结构体指针:指向acl_main的指针(用于访问全局状态)
  2. 导出的方法:ACL插件导出的所有方法(函数指针)

类比:就像"安检服务提供商的'服务接口'":

  • 总部地址:用于访问全局状态(ACL主结构体指针)
  • 服务接口列表:提供的所有服务(导出的方法)

11.3 用户模块注册:如何"注册为ACL服务用户"?

11.3.1 用户模块注册函数:acl_plugin_register_user_module

acl_plugin_register_user_module 用于注册用户模块,获取用户模块ID:

c 复制代码
//77:88:src/plugins/acl/lookup_context.c
static u32 acl_plugin_register_user_module (char *user_module_name, char *val1_label, char *val2_label)
{
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  /*
   * Because folks like to call this early on,
   * use the global heap, so as to avoid
   * initializing the main ACL heap before
   * they start using ACLs.
   */
                                         // 注释:因为人们喜欢在早期调用此函数,所以使用全局堆,以避免在开始使用ACL之前初始化主ACL堆。
                                         // 为什么需要?允许在ACL插件完全初始化之前注册用户模块
                                         // 类比:就像"允许在安检服务提供商完全启动之前注册机场"(提前注册)
  
  u32 user_id = get_acl_user_id(am, user_module_name, val1_label, val2_label);  // 获取或创建用户模块ID
                                         // get_acl_user_id:获取或创建用户模块ID(我们下面详细讲解)
                                         // 类比:就像"获取或创建'机场ID'"
  
  return user_id;                                // 返回用户模块ID
                                         // 类比:就像"返回'机场ID'"
}

函数总结

  1. 获取ACL主结构体:获取ACL插件的全局状态
  2. 获取或创建用户模块ID :调用get_acl_user_id获取或创建用户模块ID
  3. 返回用户模块ID:返回用户模块ID(用于后续创建Lookup Context)

类比:就像"注册机场":

  1. 联系总部:联系"安检服务提供商"的"总部"
  2. 获取或创建ID:获取或创建"机场ID"
  3. 返回ID:返回"机场ID"(用于后续创建安检规则集合)

11.3.2 获取用户模块ID:get_acl_user_id

get_acl_user_id 用于获取或创建用户模块ID:

c 复制代码
//36:52:src/plugins/acl/lookup_context.c
static u32 get_acl_user_id(acl_main_t *am, char *user_module_name, char *val1_label, char *val2_label)
{
    acl_lookup_context_user_t *auser;            // 用户模块指针(临时变量)
                                         // 类比:就像"机场信息指针"

    // ========== 第一部分:查找现有用户模块 ==========
    
    pool_foreach (auser, am->acl_users)           // 遍历所有用户模块
     {
      if (0 == strcmp(auser->user_module_name, user_module_name))  // 如果用户模块名称匹配
        {
          return (auser - am->acl_users);         // 返回现有用户模块ID
                                         // auser - am->acl_users:计算指针差值,得到用户模块ID
                                         // 为什么需要?如果用户模块已存在,直接返回其ID(避免重复创建)
                                         // 类比:就像"如果机场已注册,直接返回'机场ID'"
        }
     }

    // ========== 第二部分:创建新用户模块 ==========
    
    pool_get(am->acl_users, auser);               // 从pool中分配用户模块内存
                                         // pool_get:从pool中分配内存
                                         // acl_users:用户模块pool(存储所有用户模块)
                                         // 类比:就像"从'机场信息池'中分配新的'机场信息'"
    
    auser->user_module_name = user_module_name;   // 设置用户模块名称
                                         // 类比:就像"设置'机场名称'"
    
    auser->val1_label = val1_label;               // 设置第一个用户值标签
                                         // 类比:就像"设置'第一个用户值标签'"
    
    auser->val2_label = val2_label;               // 设置第二个用户值标签
                                         // 类比:就像"设置'第二个用户值标签'"
    
    return (auser - am->acl_users);               // 返回新用户模块ID
                                         // 类比:就像"返回新'机场ID'"
}

函数总结

  1. 查找现有用户模块:遍历所有用户模块,如果名称匹配,返回现有ID
  2. 创建新用户模块:如果未找到,创建新的用户模块,设置名称和标签
  3. 返回用户模块ID:返回用户模块ID(新创建或已存在的)

类比:就像"获取或创建机场ID":

  1. 查找现有机场:遍历所有已注册的机场,如果名称匹配,返回现有ID
  2. 创建新机场:如果未找到,创建新的机场信息,设置名称和标签
  3. 返回机场ID:返回机场ID(新创建或已存在的)

11.4 Lookup Context创建:如何"创建安检规则集合"?

11.4.1 Lookup Context创建函数:acl_plugin_get_lookup_context_index

acl_plugin_get_lookup_context_index 用于创建新的Lookup Context,返回上下文索引:

c 复制代码
//98:122:src/plugins/acl/lookup_context.c
static int acl_plugin_get_lookup_context_index (u32 acl_user_id, u32 val1, u32 val2)
{
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  acl_lookup_context_t *acontext;                 // Lookup Context指针(临时变量)
                                         // 类比:就像"安检规则集合指针"

  // ========== 第一部分:验证用户模块ID ==========
  
  if (!acl_user_id_valid(am, acl_user_id))       // 如果用户模块ID无效
    {
      return VNET_API_ERROR_INVALID_REGISTRATION;  // 返回错误码(无效注册)
                                         // acl_user_id_valid:检查用户模块ID是否有效(我们下面详细讲解)
                                         // VNET_API_ERROR_INVALID_REGISTRATION:无效注册错误码
                                         // 为什么需要?确保用户模块已注册
                                         // 类比:就像"如果'机场ID'无效,返回错误"(确保机场已注册)
    }

  /*
   * The lookup context index allocation is
   * an operation done within the global heap,
   * so no heap switching necessary.
   */
                                         // 注释:Lookup Context索引分配是在全局堆中进行的操作,所以不需要切换堆。
                                         // 为什么需要?允许在ACL插件完全初始化之前创建上下文
                                         // 类比:就像"允许在安检服务提供商完全启动之前创建规则集合"(提前创建)

  // ========== 第二部分:分配Lookup Context内存 ==========
  
  pool_get(am->acl_lookup_contexts, acontext);    // 从pool中分配Lookup Context内存
                                         // pool_get:从pool中分配内存
                                         // acl_lookup_contexts:Lookup Context pool(存储所有上下文)
                                         // 类比:就像"从'安检规则集合池'中分配新的'安检规则集合'"
  
  // ========== 第三部分:初始化Lookup Context ==========
  
  acontext->acl_indices = 0;                      // 初始化ACL索引列表为空
                                         // 类比:就像"初始化'规则列表'为空"
  
  acontext->context_user_id = acl_user_id;        // 设置用户模块ID
                                         // 类比:就像"设置'创建此规则集合的机场ID'"
  
  acontext->user_val1 = val1;                     // 设置用户值1
                                         // 类比:就像"设置'第一个用户值'"(如"转发表ID")
  
  acontext->user_val2 = val2;                     // 设置用户值2
                                         // 类比:就像"设置'第二个用户值'"(如"策略ID")

  // ========== 第四部分:将上下文添加到用户模块的上下文列表 ==========
  
  u32 new_context_id = acontext - am->acl_lookup_contexts;  // 计算新上下文ID
                                         // acontext - am->acl_lookup_contexts:计算指针差值,得到上下文ID
                                         // 类比:就像"计算新'规则集合ID'"
  
  vec_add1(am->acl_users[acl_user_id].lookup_contexts, new_context_id);  // 将上下文ID添加到用户模块的上下文列表
                                         // vec_add1:向向量添加一个元素
                                         // lookup_contexts:用户模块的上下文列表
                                         // 为什么需要?用于追踪用户创建的所有上下文,便于管理和清理
                                         // 类比:就像"将'规则集合ID'添加到'机场的规则集合列表'"

  return new_context_id;                          // 返回新上下文ID
                                         // 类比:就像"返回新'规则集合ID'"
}

函数总结

  1. 验证用户模块ID:确保用户模块已注册
  2. 分配内存:从pool中分配Lookup Context内存
  3. 初始化上下文:设置ACL索引列表、用户模块ID、用户值
  4. 添加到用户列表:将上下文ID添加到用户模块的上下文列表
  5. 返回上下文ID:返回新创建的上下文ID

类比:就像"创建安检规则集合":

  1. 验证机场:确保机场已注册
  2. 分配空间:从"规则集合池"中分配新的"规则集合"
  3. 初始化集合:设置规则列表、创建者、标识信息
  4. 添加到列表:将"规则集合ID"添加到"机场的规则集合列表"
  5. 返回ID:返回新创建的"规则集合ID"

11.4.2 用户模块ID验证:acl_user_id_valid

acl_user_id_valid 用于验证用户模块ID是否有效:

c 复制代码
//54:61:src/plugins/acl/lookup_context.c
static int acl_user_id_valid(acl_main_t *am, u32 acl_user_id)
{

  if (pool_is_free_index (am->acl_users, acl_user_id))  // 如果用户模块索引在pool中是空闲的
    {
      return 0;                                 // 返回0(无效)
                                         // pool_is_free_index:检查pool中的索引是否空闲
                                         // 为什么需要?确保用户模块存在
                                         // 类比:就像"如果'机场ID'对应的'机场信息'不存在,返回无效"
    }

  return 1;                                      // 返回1(有效)
                                         // 类比:就像"如果'机场信息'存在,返回有效"
}

函数总结

  1. 检查pool索引:检查用户模块索引在pool中是否空闲
  2. 返回结果:如果空闲,返回0(无效);否则返回1(有效)

类比:就像"验证机场ID":

  1. 检查信息:检查"机场ID"对应的"机场信息"是否存在
  2. 返回结果:如果不存在,返回无效;否则返回有效

11.5 ACL列表设置:如何"为规则集合添加规则"?

11.5.1 ACL列表设置函数:acl_plugin_set_acl_vec_for_context

acl_plugin_set_acl_vec_for_context 用于为Lookup Context设置ACL列表:

c 复制代码
//216:267:src/plugins/acl/lookup_context.c
static int acl_plugin_set_acl_vec_for_context (u32 lc_index, u32 *acl_list)
{
  int rv = 0;                                     // 返回值(初始化为0,表示成功)
                                         // 类比:就像"初始化'操作结果'为成功"
  
  uword *seen_acl_bitmap = 0;                    // 已见ACL位图(临时变量,用于检测重复ACL)
                                         // seen_acl_bitmap:用于记录已见过的ACL索引(检测重复)
                                         // 为什么需要?确保ACL列表中不包含重复的ACL
                                         // 类比:就像"已见规则位图"(用于检测重复规则)
  
  u32 *pacln = 0;                                // ACL索引指针(临时变量,用于遍历)
                                         // 类比:就像"规则索引指针"
  
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  acl_lookup_context_t *acontext;                 // Lookup Context指针(临时变量)
                                         // 类比:就像"安检规则集合指针"

  // ========== 第一部分:打印调试信息 ==========
  
  if (am->trace_acl)                              // 如果启用ACL Trace
    {
      u32 i;
      elog_acl_cond_trace_X1(am, (1), "LOOKUP-CONTEXT: set-acl-list lc_index %d", "i4", lc_index);  // 打印Trace信息
                                         // elog_acl_cond_trace_X1:打印Trace信息(1个整数参数)
                                         // 类比:就像"记录'设置规则列表'的Trace信息"
      
      for(i=0; i<vec_len(acl_list); i++)          // 遍历ACL列表
        {
          elog_acl_cond_trace_X2(am, (1), "   acl-list[%d]: %d", "i4i4", i, acl_list[i]);  // 打印每个ACL索引
                                         // 类比:就像"记录每个规则的Trace信息"
        }
    }

  // ========== 第二部分:验证Lookup Context索引 ==========
  
  if (!acl_lc_index_valid(am, lc_index))          // 如果Lookup Context索引无效
    {
      clib_warning("BUG: lc_index %d is not valid", lc_index);  // 打印警告
                                         // acl_lc_index_valid:检查Lookup Context索引是否有效(我们下面详细讲解)
                                         // 类比:就像"如果'规则集合ID'无效,报错"
      
      return -1;                                   // 返回错误(-1)
                                         // 类比:就像"返回错误"
    }

  // ========== 第三部分:验证ACL列表 ==========
  
  vec_foreach (pacln, acl_list)                   // 遍历ACL列表
    {
      if (pool_is_free_index (am->acls, *pacln))   // 如果ACL索引在pool中是空闲的(ACL不存在)
        {
          /* ACL is not defined. Can not apply */
                                         // 注释:ACL未定义。无法应用。
          
          clib_warning ("ERROR: ACL %d not defined", *pacln);  // 打印警告
                                         // 类比:就像"如果规则不存在,报错"
          
          rv = VNET_API_ERROR_NO_SUCH_ENTRY;       // 设置错误码(条目不存在)
                                         // VNET_API_ERROR_NO_SUCH_ENTRY:条目不存在错误码
                                         // 类比:就像"设置错误码为'条目不存在'"
          
          goto done;                                // 跳转到done标签(退出函数)
                                         // 类比:就像"跳转到'完成'标签"
        }
      
      if (clib_bitmap_get (seen_acl_bitmap, *pacln))  // 如果ACL已在位图中(重复ACL)
        {
          /* ACL being applied twice within the list. error. */
                                         // 注释:ACL在列表中应用了两次。错误。
          
          clib_warning ("ERROR: ACL %d being applied twice", *pacln);  // 打印警告
                                         // 类比:就像"如果规则重复,报错"
          
          rv = VNET_API_ERROR_ENTRY_ALREADY_EXISTS;  // 设置错误码(条目已存在)
                                         // VNET_API_ERROR_ENTRY_ALREADY_EXISTS:条目已存在错误码
                                         // 类比:就像"设置错误码为'条目已存在'"
          
          goto done;                                // 跳转到done标签(退出函数)
                                         // 类比:就像"跳转到'完成'标签"
        }
      
      seen_acl_bitmap = clib_bitmap_set (seen_acl_bitmap, *pacln, 1);  // 将ACL索引设置到位图中
                                         // clib_bitmap_set:设置位图中的位
                                         // 为什么需要?用于检测重复ACL
                                         // 类比:就像"将规则索引设置到'已见规则位图'中"
    }

  // ========== 第四部分:获取Lookup Context并保存旧ACL列表 ==========
  
  acontext = pool_elt_at_index(am->acl_lookup_contexts, lc_index);  // 获取Lookup Context指针
                                         // pool_elt_at_index:从pool中获取指定索引的元素
                                         // 类比:就像"获取'规则集合'指针"
  
  u32 *old_acl_vector = acontext->acl_indices;    // 保存旧ACL列表指针
                                         // 类比:就像"保存'旧规则列表'指针"
  
  acontext->acl_indices = vec_dup(acl_list);      // 复制新ACL列表到上下文
                                         // vec_dup:复制向量
                                         // 为什么需要?创建新ACL列表的副本,避免修改原始列表
                                         // 类比:就像"复制新规则列表到'规则集合'"

  // ========== 第五部分:取消应用旧ACL列表 ==========
  
  unapply_acl_vec(lc_index, old_acl_vector);       // 取消应用旧ACL列表
                                         // unapply_acl_vec:取消应用ACL列表(从Hash表中移除)
                                         // 作用:从Hash表中移除旧ACL列表的规则
                                         // 类比:就像"从'字典索引系统'中移除'旧规则'"
  
  unlock_acl_vec(lc_index, old_acl_vector);       // 解锁旧ACL列表
                                         // unlock_acl_vec:解锁ACL列表(从反向索引中移除)
                                         // 作用:从反向索引中移除旧ACL列表的引用
                                         // 类比:就像"从'反向索引'中移除'旧规则'的引用"

  // ========== 第六部分:锁定和应用新ACL列表 ==========
  
  lock_acl_vec(lc_index, acontext->acl_indices);  // 锁定新ACL列表
                                         // lock_acl_vec:锁定ACL列表(添加到反向索引)
                                         // 作用:将新ACL列表添加到反向索引(用于追踪ACL的使用情况)
                                         // 类比:就像"将'新规则'添加到'反向索引'"
  
  apply_acl_vec(lc_index, acontext->acl_indices);  // 应用新ACL列表
                                         // apply_acl_vec:应用ACL列表(添加到Hash表)
                                         // 作用:将新ACL列表添加到Hash表(用于快速查找)
                                         // 类比:就像"将'新规则'添加到'字典索引系统'"

  // ========== 第七部分:释放旧ACL列表内存 ==========
  
  vec_free(old_acl_vector);                       // 释放旧ACL列表内存
                                         // vec_free:释放向量内存
                                         // 类比:就像"释放'旧规则列表'内存"

done:                                             // done标签(用于错误处理)
  clib_bitmap_free (seen_acl_bitmap);             // 释放已见ACL位图内存
                                         // clib_bitmap_free:释放位图内存
                                         // 类比:就像"释放'已见规则位图'内存"
  
  return rv;                                      // 返回返回值
                                         // 类比:就像"返回'操作结果'"
}

函数总结

这个函数完成了以下工作:

  1. 打印调试信息:如果启用Trace,打印设置ACL列表的信息
  2. 验证Lookup Context索引:确保上下文索引有效
  3. 验证ACL列表:检查ACL是否存在、是否重复
  4. 保存旧ACL列表:保存旧的ACL列表指针
  5. 取消应用旧ACL列表:从Hash表和反向索引中移除旧ACL列表
  6. 锁定和应用新ACL列表:将新ACL列表添加到反向索引和Hash表
  7. 释放内存:释放旧ACL列表和位图的内存

类比:就像完整的"为规则集合添加规则"流程:

  1. 记录信息:记录"设置规则列表"的信息
  2. 验证集合:确保"规则集合"存在
  3. 验证规则:检查规则是否存在、是否重复
  4. 保存旧列表:保存"旧规则列表"
  5. 移除旧规则:从"字典索引系统"和"反向索引"中移除"旧规则"
  6. 添加新规则:将"新规则"添加到"反向索引"和"字典索引系统"
  7. 释放内存:释放"旧规则列表"和"已见规则位图"的内存


11.6 Lookup Context释放:如何"销毁安检规则集合"?

11.6.1 Lookup Context释放函数:acl_plugin_put_lookup_context_index

acl_plugin_put_lookup_context_index 用于释放Lookup Context,销毁相关的数据结构:

c 复制代码
//190:210:src/plugins/acl/lookup_context.c
static void acl_plugin_put_lookup_context_index (u32 lc_index)
{
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"

  elog_acl_cond_trace_X1(am, (am->trace_acl), "LOOKUP-CONTEXT: put-context lc_index %d", "i4", lc_index);  // 打印Trace信息
                                         // elog_acl_cond_trace_X1:打印Trace信息(1个整数参数)
                                         // 类比:就像"记录'释放规则集合'的Trace信息"
  
  // ========== 第一部分:验证Lookup Context索引 ==========
  
  if (!acl_lc_index_valid(am, lc_index))          // 如果Lookup Context索引无效
    {
      clib_warning("BUG: lc_index %d is not valid", lc_index);  // 打印警告
                                         // acl_lc_index_valid:检查Lookup Context索引是否有效(我们上面已经讲解过)
                                         // 类比:就像"如果'规则集合ID'无效,报错"
      
      return;                                      // 返回(不继续执行)
                                         // 类比:就像"返回,不继续销毁"
    }

  // ========== 第二部分:获取Lookup Context ==========
  
  acl_lookup_context_t *acontext = pool_elt_at_index(am->acl_lookup_contexts, lc_index);  // 获取Lookup Context指针
                                         // pool_elt_at_index:从pool中获取指定索引的元素
                                         // 类比:就像"获取'规则集合'指针"

  // ========== 第三部分:从用户模块的上下文列表中移除 ==========
  
  u32 index = vec_search(am->acl_users[acontext->context_user_id].lookup_contexts, lc_index);  // 在用户模块的上下文列表中搜索
                                         // vec_search:在向量中搜索元素(返回索引,如果未找到返回~0)
                                         // 类比:就像"在'机场的规则集合列表'中搜索'规则集合ID'"
  
  ASSERT(index != ~0);                            // 断言索引不为无效值(应该找到)
                                         // 为什么需要?确保上下文在用户模块的列表中(数据一致性检查)
                                         // 类比:就像"确保'规则集合'在'机场的规则集合列表'中"

  vec_del1(am->acl_users[acontext->context_user_id].lookup_contexts, index);  // 从用户模块的上下文列表中删除
                                         // vec_del1:从向量中删除一个元素
                                         // 类比:就像"从'机场的规则集合列表'中删除'规则集合ID'"

  // ========== 第四部分:取消应用ACL列表 ==========
  
  unapply_acl_vec(lc_index, acontext->acl_indices);  // 取消应用ACL列表
                                         // unapply_acl_vec:取消应用ACL列表(从Hash表中移除)
                                         // 作用:从Hash表中移除ACL列表的规则
                                         // 类比:就像"从'字典索引系统'中移除'规则'"

  // ========== 第五部分:解锁ACL列表 ==========
  
  unlock_acl_vec(lc_index, acontext->acl_indices);  // 解锁ACL列表
                                         // unlock_acl_vec:解锁ACL列表(从反向索引中移除)
                                         // 作用:从反向索引中移除ACL列表的引用
                                         // 类比:就像"从'反向索引'中移除'规则'的引用"

  // ========== 第六部分:释放ACL列表内存 ==========
  
  vec_free(acontext->acl_indices);                 // 释放ACL列表内存
                                         // vec_free:释放向量内存
                                         // 类比:就像"释放'规则列表'内存"

  // ========== 第七部分:释放Lookup Context内存 ==========
  
  pool_put(am->acl_lookup_contexts, acontext);     // 将Lookup Context归还给pool
                                         // pool_put:将元素归还给pool(标记为可用)
                                         // 作用:释放Lookup Context占用的内存,标记为可重用
                                         // 类比:就像"将'规则集合'归还给'规则集合池',标记为可用"
}

函数总结

这个函数完成了以下工作:

  1. 打印调试信息:如果启用Trace,打印释放上下文的信息
  2. 验证Lookup Context索引:确保上下文索引有效
  3. 获取Lookup Context:从pool中获取上下文指针
  4. 从用户列表移除:从用户模块的上下文列表中移除上下文ID
  5. 取消应用ACL列表:从Hash表中移除ACL列表的规则
  6. 解锁ACL列表:从反向索引中移除ACL列表的引用
  7. 释放内存:释放ACL列表和上下文的内存

类比:就像完整的"销毁安检规则集合"流程:

  1. 记录信息:记录"释放规则集合"的信息
  2. 验证集合:确保"规则集合"存在
  3. 获取集合:获取"规则集合"指针
  4. 从列表移除:从"机场的规则集合列表"中移除"规则集合ID"
  5. 移除规则:从"字典索引系统"中移除"规则"
  6. 移除引用:从"反向索引"中移除"规则"的引用
  7. 释放内存:释放"规则列表"和"规则集合"的内存

11.7 ACL锁定和解锁:如何"追踪ACL的使用情况"?

11.7.1 ACL锁定函数:lock_acl

lock_acl 用于锁定ACL(将ACL添加到反向索引):

c 复制代码
//125:130:src/plugins/acl/lookup_context.c
static void
lock_acl(acl_main_t *am, u32 acl, u32 lc_index)
{
  vec_validate(am->lc_index_vec_by_acl, acl);     // 确保反向索引向量有足够的空间
                                         // lc_index_vec_by_acl:按ACL索引的Lookup Context索引向量数组(反向索引)
                                         // vec_validate:确保向量有足够的空间(如果不够,自动扩展)
                                         // 为什么需要?用于追踪每个ACL被哪些Lookup Context使用
                                         // 类比:就像"确保'规则使用列表数组'有足够的空间"
  
  elog_acl_cond_trace_X2(am, (am->trace_acl), "lock acl %d in lc_index %d", "i4i4", acl, lc_index);  // 打印Trace信息
                                         // elog_acl_cond_trace_X2:打印Trace信息(2个整数参数)
                                         // 类比:就像"记录'锁定规则'的Trace信息"
  
  vec_add1(am->lc_index_vec_by_acl[acl], lc_index);  // 将Lookup Context索引添加到反向索引
                                         // vec_add1:向向量添加一个元素
                                         // 作用:记录此ACL被此Lookup Context使用
                                         // 类比:就像"将'规则集合ID'添加到'规则使用列表'"
}

函数总结

  1. 确保空间:确保反向索引向量有足够的空间
  2. 打印Trace:如果启用Trace,打印锁定ACL的信息
  3. 添加到反向索引:将Lookup Context索引添加到反向索引

为什么需要反向索引?

  • 问题:当ACL规则变化时,需要通知所有使用此ACL的Lookup Context
  • 解决方案 :使用反向索引(lc_index_vec_by_acl)记录每个ACL被哪些Lookup Context使用
  • 类比:就像"规则使用列表"(记录每个规则被哪些规则集合使用)

11.7.2 ACL解锁函数:unlock_acl

unlock_acl 用于解锁ACL(从反向索引中移除ACL):

c 复制代码
//143:152:src/plugins/acl/lookup_context.c
static void
unlock_acl(acl_main_t *am, u32 acl, u32 lc_index)
{
  vec_validate(am->lc_index_vec_by_acl, acl);     // 确保反向索引向量有足够的空间
                                         // 类比:就像"确保'规则使用列表数组'有足够的空间"
  
  elog_acl_cond_trace_X2(am, (am->trace_acl), "unlock acl %d in lc_index %d", "i4i4", acl, lc_index);  // 打印Trace信息
                                         // 类比:就像"记录'解锁规则'的Trace信息"
  
  u32 index = vec_search(am->lc_index_vec_by_acl[acl], lc_index);  // 在反向索引中搜索Lookup Context索引
                                         // vec_search:在向量中搜索元素(返回索引,如果未找到返回~0)
                                         // 类比:就像"在'规则使用列表'中搜索'规则集合ID'"
  
  if (index != ~0)                                // 如果找到
    {
      vec_del1(am->lc_index_vec_by_acl[acl], index);  // 从反向索引中删除
                                         // vec_del1:从向量中删除一个元素
                                         // 类比:就像"从'规则使用列表'中删除'规则集合ID'"
    }
  else                                          // 如果未找到
    {
      clib_warning("BUG: can not unlock acl %d lc_index %d", acl, lc_index);  // 打印警告
                                         // 为什么需要?数据一致性检查(应该找到但未找到,说明数据不一致)
                                         // 类比:就像"如果未找到,报错"(数据不一致)
    }
}

函数总结

  1. 确保空间:确保反向索引向量有足够的空间
  2. 打印Trace:如果启用Trace,打印解锁ACL的信息
  3. 搜索并删除:在反向索引中搜索Lookup Context索引,如果找到则删除
  4. 错误检查:如果未找到,打印警告(数据一致性检查)

类比:就像"从规则使用列表中移除规则集合ID":

  1. 确保空间:确保"规则使用列表数组"有足够的空间
  2. 记录信息:记录"解锁规则"的信息
  3. 搜索并删除:在"规则使用列表"中搜索"规则集合ID",如果找到则删除
  4. 错误检查:如果未找到,报错(数据不一致)

11.8 ACL应用和取消应用:如何"将规则添加到Hash表"?

11.8.1 ACL应用函数:apply_acl_vec

apply_acl_vec 用于应用ACL列表(将ACL列表添加到Hash表):

c 复制代码
//164:172:src/plugins/acl/lookup_context.c
static void
apply_acl_vec(u32 lc_index, u32 *acls)
{
  int i;                                          // 循环变量(临时变量)
                                         // 类比:就像"循环变量"
  
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"

  for(i=0; i<vec_len(acls); i++)                  // 遍历ACL列表
    {
      hash_acl_apply(am, lc_index, acls[i], i);   // 应用单个ACL
                                         // hash_acl_apply:应用单个ACL(添加到Hash表)
                                         // 参数:am(ACL插件主结构体)、lc_index(Lookup Context索引)、acls[i](ACL索引)、i(ACL位置)
                                         // 作用:将ACL添加到Hash表,构建Hash匹配结构
                                         // 类比:就像"将单个规则添加到'字典索引系统'"
    }
}

函数总结

  1. 遍历ACL列表:逐个遍历ACL列表中的每个ACL
  2. 应用单个ACL :调用hash_acl_apply将每个ACL添加到Hash表

类比:就像"将规则列表添加到字典索引系统":

  1. 遍历规则:逐个遍历规则列表中的每个规则
  2. 添加规则:将每个规则添加到"字典索引系统"

11.8.2 ACL取消应用函数:unapply_acl_vec

unapply_acl_vec 用于取消应用ACL列表(从Hash表中移除ACL列表):

c 复制代码
//175:184:src/plugins/acl/lookup_context.c
static void
unapply_acl_vec(u32 lc_index, u32 *acls)
{
  int i;                                          // 循环变量(临时变量)
                                         // 类比:就像"循环变量"
  
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  if (vec_len(acls) == 0)                         // 如果ACL列表为空
    {
      return;                                     // 返回(无需处理)
                                         // 类比:就像"如果规则列表为空,返回"
    }
  
  for(i=vec_len(acls); i > 0; i--)                // 从后往前遍历ACL列表(逆序)
    {
      hash_acl_unapply(am, lc_index, acls[i-1]);   // 取消应用单个ACL
                                         // hash_acl_unapply:取消应用单个ACL(从Hash表中移除)
                                         // 参数:am(ACL插件主结构体)、lc_index(Lookup Context索引)、acls[i-1](ACL索引)
                                         // 为什么从后往前?因为Hash表构建时是从前往后,取消应用时需要逆序
                                         // 类比:就像"从后往前移除规则"(逆序移除)
    }
}

函数总结

  1. 检查空列表:如果ACL列表为空,直接返回
  2. 逆序遍历:从后往前遍历ACL列表(逆序)
  3. 取消应用单个ACL :调用hash_acl_unapply从Hash表中移除每个ACL

为什么需要逆序?

  • 原因:Hash表构建时是从前往后,取消应用时需要逆序,以保持数据一致性
  • 类比:就像"拆房子时要从上往下拆"(逆序操作)

11.9 ACL变更通知:如何"通知上下文ACL规则变化"?

11.9.1 ACL变更通知函数:acl_plugin_lookup_context_notify_acl_change

acl_plugin_lookup_context_notify_acl_change 用于通知Lookup Context系统ACL规则发生了变化:

c 复制代码
//270:283:src/plugins/acl/lookup_context.c
void acl_plugin_lookup_context_notify_acl_change(u32 acl_num)
{
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  if (acl_plugin_acl_exists(acl_num))            // 如果ACL存在
    {
      if (hash_acl_exists(am, acl_num))           // 如果Hash ACL存在
        {
          /* this is a modification, clean up the older entries */
                                         // 注释:这是修改,清理旧条目
          
          hash_acl_delete(am, acl_num);           // 删除Hash ACL
                                         // hash_acl_delete:删除Hash ACL(从Hash表中移除旧规则)
                                         // 作用:清理旧的Hash表条目
                                         // 类比:就像"从'字典索引系统'中删除'旧规则'"
        }
      
      hash_acl_add(am, acl_num);                  // 添加Hash ACL
                                         // hash_acl_add:添加Hash ACL(重新构建Hash表)
                                         // 作用:重新构建Hash表,包含新的规则
                                         // 类比:就像"将'新规则'添加到'字典索引系统'"
    }
  else                                            // 如果ACL不存在(已删除)
    {
      /* this is a deletion notification */
                                         // 注释:这是删除通知
      
      hash_acl_delete(am, acl_num);               // 删除Hash ACL
                                         // hash_acl_delete:删除Hash ACL(从Hash表中移除规则)
                                         // 作用:从Hash表中移除已删除的ACL
                                         // 类比:就像"从'字典索引系统'中删除'已删除的规则'"
    }
}

函数总结

  1. 检查ACL是否存在:检查ACL是否存在
  2. 处理修改:如果ACL存在且Hash ACL存在,删除旧Hash ACL,添加新Hash ACL
  3. 处理删除:如果ACL不存在,删除Hash ACL

为什么需要通知机制?

  • 问题:当ACL规则变化时,使用此ACL的Lookup Context需要更新Hash表
  • 解决方案 :通过反向索引(lc_index_vec_by_acl)找到所有使用此ACL的Lookup Context,然后重新构建Hash表
  • 类比:就像"当规则变化时,通知所有使用此规则的规则集合更新"

11.10 方法导出和初始化:如何"让外部插件调用ACL方法"?

11.10.1 方法表初始化函数:acl_plugin_methods_vtable_init

acl_plugin_methods_vtable_init 用于初始化ACL插件的方法表(函数指针表):

c 复制代码
//362:370:src/plugins/acl/lookup_context.c
__clib_export clib_error_t *
acl_plugin_methods_vtable_init(acl_plugin_methods_t *m)
{
  m->p_acl_main = &acl_main;                      // 设置ACL主结构体指针
                                         // p_acl_main:指向acl_main的指针(用于访问ACL插件的全局状态)
                                         // 类比:就像"设置'总部地址'"
  
#define _(name) m->name = acl_plugin_ ## name;   // 宏定义:设置方法指针
                                         // 作用:将ACL插件的内部函数指针赋值给方法表
                                         // 类比:就像"设置'服务接口'指针"
  
  foreach_acl_plugin_exported_method_name         // 遍历所有导出的方法名称(通过宏展开)
                                         // 包括:acl_exists、register_user_module、get_lookup_context_index等
                                         // 类比:就像"遍历所有'服务接口'名称"
#undef _
  
  return 0;                                       // 返回0(成功)
                                         // 类比:就像"返回'成功'"
}

函数总结

  1. 设置ACL主结构体指针 :将&acl_main赋值给方法表的p_acl_main字段
  2. 设置方法指针:通过宏展开,将所有导出的方法指针赋值给方法表
  3. 返回成功:返回0表示成功

宏展开过程

cpp 复制代码
// foreach_acl_plugin_exported_method_name 展开为:
_(acl_exists)
_(register_user_module)
_(get_lookup_context_index)
_(put_lookup_context_index)
_(set_acl_vec_for_context)
_(fill_5tuple)
_(match_5tuple)

// 然后 _(name) 展开为:
m->acl_exists = acl_plugin_acl_exists;
m->register_user_module = acl_plugin_register_user_module;
m->get_lookup_context_index = acl_plugin_get_lookup_context_index;
// ... 等等

类比:就像"初始化服务接口表":

  1. 设置总部地址:设置"总部地址"到"服务接口表"
  2. 设置接口指针:将所有"服务接口"的指针设置到"服务接口表"
  3. 返回成功:返回"成功"

11.10.2 外部插件初始化函数:acl_plugin_exports_init

acl_plugin_exports_init 用于外部插件初始化ACL插件的方法表:

c 复制代码
//39:45:src/plugins/acl/public_inlines.h
static inline clib_error_t * acl_plugin_exports_init (acl_plugin_methods_t *m)
{
    acl_plugin_methods_vtable_init_fn_t mvi;      // 方法表初始化函数指针(临时变量)
                                         // acl_plugin_methods_vtable_init_fn_t:方法表初始化函数指针类型
                                         // 类比:就像"服务接口表初始化函数指针"

    LOAD_SYMBOL_FROM_PLUGIN_TO("acl_plugin.so", acl_plugin_methods_vtable_init, mvi);  // 从插件中加载符号
                                         // LOAD_SYMBOL_FROM_PLUGIN_TO:从插件中加载符号(函数指针)
                                         // 参数:"acl_plugin.so"(插件文件名)、acl_plugin_methods_vtable_init(符号名)、mvi(输出函数指针)
                                         // 作用:从ACL插件中加载方法表初始化函数
                                         // 类比:就像"从'安检服务提供商'加载'服务接口表初始化函数'"
    
    return (mvi(m));                               // 调用方法表初始化函数
                                         // mvi(m):调用方法表初始化函数,传入方法表指针
                                         // 作用:初始化方法表,填充所有函数指针
                                         // 类比:就像"调用'服务接口表初始化函数',初始化'服务接口表'"
}

函数总结

  1. 加载符号:从ACL插件中加载方法表初始化函数
  2. 调用初始化函数:调用方法表初始化函数,填充所有函数指针

为什么需要动态加载?

  • 原因:外部插件和ACL插件是独立的动态库,需要通过动态加载来获取函数指针
  • 类比:就像"从'安检服务提供商'动态加载'服务接口'"

11.11 5-tuple填充和匹配:如何"使用ACL匹配引擎"?

11.11.1 5-tuple填充函数:acl_plugin_fill_5tuple

acl_plugin_fill_5tuple 用于从数据包中提取5-tuple:

c 复制代码
//288:292:src/plugins/acl/lookup_context.c
static void acl_plugin_fill_5tuple (u32 lc_index, vlib_buffer_t * b0, int is_ip6, int is_input,
                                int is_l2_path, fa_5tuple_opaque_t * p5tuple_pkt)
{
  acl_plugin_fill_5tuple_inline(&acl_main, lc_index, b0, is_ip6, is_input, is_l2_path, p5tuple_pkt);  // 调用内联版本
                                         // acl_plugin_fill_5tuple_inline:内联版本的5-tuple填充函数
                                         // 作用:从数据包中提取5-tuple(源IP、目的IP、协议、源端口、目的端口)
                                         // 类比:就像"从'数据包'中提取'旅客信息'"
}

函数总结

  1. 调用内联版本:调用内联版本的5-tuple填充函数

为什么有内联和非内联版本?

  • 内联版本:编译到调用者代码中,性能更好,但需要包含ACL插件的头文件
  • 非内联版本:通过函数指针调用,灵活性更好,但性能稍差
  • 类比:就像"直接调用"和"通过接口调用"的区别

11.11.2 5-tuple匹配函数:acl_plugin_match_5tuple

acl_plugin_match_5tuple 用于匹配5-tuple:

c 复制代码
//294:303:src/plugins/acl/lookup_context.c
static int acl_plugin_match_5tuple (u32 lc_index,
                                           fa_5tuple_opaque_t * pkt_5tuple,
                                           int is_ip6, u8 * r_action,
                                           u32 * r_acl_pos_p,
                                           u32 * r_acl_match_p,
                                           u32 * r_rule_match_p,
                                           u32 * trace_bitmap)
{
  return acl_plugin_match_5tuple_inline (&acl_main, lc_index, pkt_5tuple, is_ip6, r_action, r_acl_pos_p, r_acl_match_p, r_rule_match_p, trace_bitmap);  // 调用内联版本
                                         // acl_plugin_match_5tuple_inline:内联版本的5-tuple匹配函数
                                         // 作用:在Lookup Context中匹配5-tuple,返回匹配结果
                                         // 类比:就像"在'规则集合'中匹配'旅客信息',返回匹配结果"
}

函数总结

  1. 调用内联版本:调用内联版本的5-tuple匹配函数

参数说明

  • lc_index:Lookup Context索引
  • pkt_5tuple:数据包的5-tuple(不透明类型)
  • is_ip6:是否IPv6(0=IPv4,1=IPv6)
  • r_action:输出参数,匹配的动作(permit/deny)
  • r_acl_pos_p:输出参数,匹配的ACL位置
  • r_acl_match_p:输出参数,匹配的ACL索引
  • r_rule_match_p:输出参数,匹配的规则索引
  • trace_bitmap:输出参数,Trace位图

返回值:1表示匹配成功,0表示未匹配


11.12 完整使用流程:从注册到匹配

让我们用一个完整的例子来总结整个流程:

场景:一个外部插件(如ACL-based forwarding)需要使用ACL插件的匹配引擎

步骤1:初始化方法表

cpp 复制代码
acl_plugin_methods_t acl_plugin;
acl_plugin_exports_init(&acl_plugin);  // 初始化方法表

步骤2:注册用户模块

cpp 复制代码
u32 user_id = acl_plugin.register_user_module(
    "acl-based-forwarding",  // 用户模块名称
    "table_id",              // 第一个用户值标签
    "policy_id"              // 第二个用户值标签
);

步骤3:创建Lookup Context

cpp 复制代码
u32 lc_index = acl_plugin.get_lookup_context_index(
    user_id,    // 用户模块ID
    10,         // 第一个用户值(转发表ID)
    1           // 第二个用户值(策略ID)
);

步骤4:设置ACL列表

cpp 复制代码
u32 acl_list[] = {0, 1, 2};  // ACL索引列表
acl_plugin.set_acl_vec_for_context(lc_index, acl_list);

步骤5:匹配数据包

cpp 复制代码
fa_5tuple_opaque_t pkt_5tuple;
acl_plugin.fill_5tuple(lc_index, buffer, is_ip6, is_input, is_l2_path, &pkt_5tuple);

u8 action;
u32 acl_pos, acl_match, rule_match;
if (acl_plugin.match_5tuple(lc_index, &pkt_5tuple, is_ip6, &action, 
                            &acl_pos, &acl_match, &rule_match, NULL)) {
    // 匹配成功,处理动作
}

步骤6:释放Lookup Context

cpp 复制代码
acl_plugin.put_lookup_context_index(lc_index);

类比:就像完整的"租用安检服务"流程:

  1. 初始化服务接口:初始化"服务接口表"
  2. 注册机场:注册为"机场",获得"机场ID"
  3. 创建规则集合:创建"安检规则集合",获得"规则集合ID"
  4. 添加规则:为"规则集合"添加规则
  5. 使用服务:使用"安检服务"匹配"旅客信息"
  6. 释放资源:释放"规则集合"资源

11.13 本章小结

通过这一章的详细讲解,我们了解了:

  1. ACL-as-a-service的概念:什么是ACL作为服务,为什么需要它
  2. 数据结构:用户模块结构体、Lookup Context结构体、方法表结构体
  3. 用户模块注册:如何注册为用户模块,获取用户模块ID
  4. Lookup Context创建:如何创建Lookup Context,获取上下文索引
  5. ACL列表设置:如何为Lookup Context设置ACL列表
  6. Lookup Context释放:如何释放Lookup Context,销毁相关数据结构
  7. ACL锁定和解锁:如何追踪ACL的使用情况(反向索引)
  8. ACL应用和取消应用:如何将ACL添加到Hash表或从Hash表中移除
  9. ACL变更通知:如何通知Lookup Context系统ACL规则变化
  10. 方法导出和初始化:如何让外部插件调用ACL方法
  11. 5-tuple填充和匹配:如何使用ACL匹配引擎

核心要点

  • ACL-as-a-service允许其他插件使用ACL插件的匹配引擎,避免重复实现
  • Lookup Context是一个抽象的ACL匹配上下文,不绑定到特定接口
  • 反向索引用于追踪ACL的使用情况,当ACL变化时通知所有使用此ACL的Lookup Context
  • 方法表通过函数指针实现,允许外部插件动态调用ACL插件的方法
  • 完整流程:注册用户模块 → 创建Lookup Context → 设置ACL列表 → 匹配数据包 → 释放资源

下一步:在下一章,我们会看到会话表管理的详细实现,包括会话表初始化、Per-worker数据结构、容量管理等。


第12章:会话表管理------如何"建立和管理常客数据库"?

生活类比:想象一下,你是一个"常客数据库管理员",需要建立一个"常客数据库系统"。

  • 数据库设计:需要设计数据库结构(会话表结构)
  • 索引系统:需要建立索引系统(Hash表)
  • 容量管理:需要管理数据库容量(最大会话数)
  • 性能优化:需要优化数据库性能(Per-worker数据)

这一章,我们就跟着ACL插件的代码,看看它是如何"建立和管理常客数据库"的。每一步我都会详细解释,确保你完全理解。


12.1 会话表管理的概念:什么是"会话表管理"?

12.1.1 什么是会话表管理?

会话表管理(Session Table Management):Flow-aware ACL的核心机制,用于管理所有网络连接的会话状态信息。

关键概念详解

  1. 会话表(Session Table):存储所有会话的Hash表

    • IPv4会话表 :使用 clib_bihash_16_8_t(16字节key,8字节value)
    • IPv6会话表 :使用 clib_bihash_40_8_t(40字节key,8字节value)
    • 类比:就像"常客数据库"(存储所有常客信息,通过身份证号快速查找)
  2. Per-worker数据结构:每个Worker线程独立的数据结构

    • 会话Pool:每个Worker线程有独立的会话内存池
    • 超时链表:每个Worker线程有独立的超时链表
    • 为什么需要?:避免线程间竞争,提高性能
    • 类比:就像"每个安检通道有独立的'档案柜'和'待检查列表'"
  3. 容量管理:管理会话表的最大容量

    • 最大会话数fa_conn_table_max_entries(默认500,000)
    • Hash表桶数fa_conn_table_hash_num_buckets(默认64K)
    • Hash表内存fa_conn_table_hash_memory_size(默认1GB)
    • 类比:就像"数据库的最大容量"(最大记录数、索引大小等)
  4. 性能优化:通过各种技术优化会话表性能

    • 缓存行对齐:会话结构体对齐到缓存行(128字节)
    • 预取优化:预取Hash表bucket和数据
    • 批量处理:批量处理数据包
    • 类比:就像"数据库性能优化"(索引优化、查询优化等)
12.1.2 会话表管理的架构

架构层次

  1. 全局层 :ACL插件主结构体(acl_main_t

    • IPv4会话Hash表fa_ip4_sessions_hash
    • IPv6会话Hash表fa_ip6_sessions_hash
    • Per-worker数据数组per_worker_data[]
    • 类比:就像"数据库系统的'总部'"(全局Hash表、Worker数据数组)
  2. Worker层 :每个Worker线程的数据结构(acl_fa_per_worker_data_t

    • 会话Poolfa_sessions_pool
    • 超时链表fa_conn_list_head[]fa_conn_list_tail[]
    • 统计信息fa_session_adds_by_sw_if_index[]fa_session_dels_by_sw_if_index[]
    • 类比:就像"每个安检通道的'档案柜'和'待检查列表'"
  3. 会话层 :单个会话结构体(fa_session_t

    • 5-tuple信息info(用于Hash表key)
    • 状态信息last_active_timetcp_flags_seen
    • 链表信息link_prev_idxlink_next_idx
    • 类比:就像"单个常客的'档案夹'"

数据流

复制代码
数据包 → 提取5-tuple → Hash表查找 → 找到会话 → 更新会话状态
                                    ↓
                                未找到 → ACL匹配 → 创建新会话 → 添加到Hash表

类比:就像"常客数据库系统"的数据流:

复制代码
旅客 → 读取身份证号 → 数据库查找 → 找到常客 → 更新访问记录
                                    ↓
                                未找到 → 安检检查 → 登记新常客 → 添加到数据库

12.2 会话表初始化:如何"建立常客数据库"?

12.2.1 会话表初始化函数:acl_fa_verify_init_sessions

acl_fa_verify_init_sessions 用于初始化会话表系统(包括Per-worker会话Pool和Hash表):

c 复制代码
//84:121:src/plugins/acl/sess_mgmt_node.c
static void
acl_fa_verify_init_sessions (acl_main_t * am)
{
  if (!am->fa_sessions_hash_is_initialized)      // 如果会话Hash表未初始化
    {
      u16 wk;                                     // Worker线程索引(临时变量)
                                         // 类比:就像"安检通道编号"

      // ========== 第一部分:初始化Per-worker会话Pool ==========
      
      /* Allocate the per-worker sessions pools */
                                         // 注释:分配Per-worker会话Pool
      
      for (wk = 0; wk < vec_len (am->per_worker_data); wk++)  // 遍历所有Worker线程
	{
	  acl_fa_per_worker_data_t *pw = &am->per_worker_data[wk];  // 获取Worker线程的Per-worker数据
                                         // per_worker_data:Per-worker数据数组(每个Worker线程一个)
                                         // 类比:就像"获取'安检通道'的'数据'"

	  /*
	   * // In lieu of trying to preallocate the pool and its free bitmap, rather use pool_init_fixed
	   * pool_alloc_aligned(pw->fa_sessions_pool, am->fa_conn_table_max_entries, CLIB_CACHE_LINE_BYTES);
	   * clib_bitmap_validate(pool_header(pw->fa_sessions_pool)->free_bitmap, am->fa_conn_table_max_entries);
	   */
                                         // 注释:与其尝试预分配pool和其free bitmap,不如使用pool_init_fixed
                                         // 为什么使用pool_init_fixed?简化初始化,自动管理free bitmap
                                         // 类比:就像"使用'固定大小档案柜',自动管理'空闲位图'"
	  
	  pool_init_fixed (pw->fa_sessions_pool,      // 初始化固定大小的会话Pool
			   am->fa_conn_table_max_entries);  // 参数:Pool指针、最大条目数
                                         // pool_init_fixed:初始化固定大小的pool(预分配内存)
                                         // fa_sessions_pool:会话Pool(每个Worker线程一个)
                                         // fa_conn_table_max_entries:最大会话数(默认500,000)
                                         // 为什么需要固定大小?避免动态分配,提高性能
                                         // 类比:就像"初始化固定大小的'档案柜'"(预分配空间,避免动态分配)
	}

      // ========== 第二部分:初始化IPv6会话Hash表 ==========
      
      /* ... and the interface session hash table */
                                         // 注释:...以及接口会话Hash表
      
      clib_bihash_init_40_8 (&am->fa_ip6_sessions_hash,  // 初始化IPv6会话Hash表
			     "ACL plugin FA IPv6 session bihash",  // Hash表名称(用于调试)
			     am->fa_conn_table_hash_num_buckets,  // Hash表桶数量(默认64K)
			     am->fa_conn_table_hash_memory_size);  // Hash表内存大小(默认1GB)
                                         // clib_bihash_init_40_8:初始化40_8双向Hash表
                                         // fa_ip6_sessions_hash:IPv6会话Hash表(40字节key,8字节value)
                                         // fa_conn_table_hash_num_buckets:Hash表桶数量(在初始化时配置)
                                         // fa_conn_table_hash_memory_size:Hash表内存大小(在初始化时配置)
                                         // 为什么需要40字节key?IPv6地址128位(16字节)+ 其他字段
                                         // 类比:就像"初始化'IPv6常客数据库'"(40字节key用于IPv6地址)
      
      clib_bihash_set_kvp_format_fn_40_8 (&am->fa_ip6_sessions_hash,  // 设置Hash表key-value格式化函数
					  format_ip6_session_bihash_kv);  // 格式化函数(用于调试输出)
                                         // clib_bihash_set_kvp_format_fn_40_8:设置40_8 Hash表的key-value格式化函数
                                         // format_ip6_session_bihash_kv:IPv6会话Hash表key-value格式化函数
                                         // 为什么需要?用于调试输出(show命令)
                                         // 类比:就像"设置'数据库记录格式化函数'"(用于调试输出)

      // ========== 第三部分:初始化IPv4会话Hash表 ==========
      
      clib_bihash_init_16_8 (&am->fa_ip4_sessions_hash,  // 初始化IPv4会话Hash表
			     "ACL plugin FA IPv4 session bihash",  // Hash表名称(用于调试)
			     am->fa_conn_table_hash_num_buckets,  // Hash表桶数量(默认64K)
			     am->fa_conn_table_hash_memory_size);  // Hash表内存大小(默认1GB)
                                         // clib_bihash_init_16_8:初始化16_8双向Hash表
                                         // fa_ip4_sessions_hash:IPv4会话Hash表(16字节key,8字节value)
                                         // 为什么需要16字节key?IPv4地址32位(4字节)+ 其他字段
                                         // 类比:就像"初始化'IPv4常客数据库'"(16字节key用于IPv4地址)
      
      clib_bihash_set_kvp_format_fn_16_8 (&am->fa_ip4_sessions_hash,  // 设置Hash表key-value格式化函数
					  format_ip4_session_bihash_kv);  // 格式化函数(用于调试输出)
                                         // 类比:就像"设置'数据库记录格式化函数'"(用于调试输出)

      // ========== 第四部分:设置初始化标志 ==========
      
      am->fa_sessions_hash_is_initialized = 1;    // 设置初始化标志为1
                                         // fa_sessions_hash_is_initialized:会话Hash表初始化标志
                                         // 为什么需要?避免重复初始化
                                         // 类比:就像"标记'数据库系统'已初始化"
    }
}

函数总结

这个函数完成了以下工作:

  1. 初始化Per-worker会话Pool:为每个Worker线程初始化固定大小的会话Pool
  2. 初始化IPv6会话Hash表:初始化IPv6会话Hash表(40字节key,8字节value)
  3. 初始化IPv4会话Hash表:初始化IPv4会话Hash表(16字节key,8字节value)
  4. 设置初始化标志:设置初始化标志,避免重复初始化

初始化顺序

  1. Per-worker Pool:先初始化Per-worker会话Pool(因为Hash表需要引用Pool中的会话)
  2. IPv6 Hash表:然后初始化IPv6会话Hash表
  3. IPv4 Hash表:最后初始化IPv4会话Hash表

类比:就像完整的"建立常客数据库系统"流程:

  1. 初始化档案柜:为每个"安检通道"初始化固定大小的"档案柜"
  2. 初始化IPv6数据库:初始化"IPv6常客数据库"(40字节key)
  3. 初始化IPv4数据库:初始化"IPv4常客数据库"(16字节key)
  4. 标记完成:标记"数据库系统"已初始化

12.3 Per-worker数据结构:如何"为每个安检通道建立档案系统"?

12.3.1 Per-worker数据结构:acl_fa_per_worker_data_t

acl_fa_per_worker_data_t 用于存储每个Worker线程的会话管理数据:

c 复制代码
//168:235:src/plugins/acl/fa_node.h
typedef struct {
  /* The pool of sessions managed by this worker */
  fa_session_t *fa_sessions_pool;                 // 会话Pool(此Worker管理的会话)
                                         // fa_sessions_pool:会话Pool(存储此Worker线程的所有会话)
                                         // 为什么需要?每个Worker线程有独立的会话Pool,避免线程间竞争
                                         // 类比:就像"此安检通道的'档案柜'"(存储此通道的所有常客档案)
  
  /* incoming session change requests from other workers */
  clib_spinlock_t pending_session_change_request_lock;  // 待处理会话变更请求锁
                                         // pending_session_change_request_lock:自旋锁(用于保护待处理会话变更请求)
                                         // 为什么需要?多个线程可能同时访问待处理请求列表
                                         // 类比:就像"待处理请求列表的'锁'"
  
  u64 *pending_session_change_requests;           // 待处理会话变更请求列表
                                         // pending_session_change_requests:待处理的会话变更请求列表(从其他Worker线程接收)
                                         // 为什么需要?允许其他Worker线程请求此Worker线程修改会话
                                         // 类比:就像"待处理请求列表"(从其他通道接收的请求)
  
  u64 *wip_session_change_requests;               // 处理中的会话变更请求列表
                                         // wip_session_change_requests:处理中的会话变更请求列表(Work In Progress)
                                         // 为什么需要?在处理请求时,允许其他线程继续入队新请求(无锁设计)
                                         // 类比:就像"处理中请求列表"(正在处理的请求)
  
  u64 rcvd_session_change_requests;               // 接收的会话变更请求计数
                                         // rcvd_session_change_requests:接收的会话变更请求总数(统计信息)
                                         // 类比:就像"接收的请求总数"
  
  u64 sent_session_change_requests;               // 发送的会话变更请求计数
                                         // sent_session_change_requests:发送的会话变更请求总数(统计信息)
                                         // 类比:就像"发送的请求总数"
  
  /* per-worker ACL_N_TIMEOUTS of conn lists */
  u32 *fa_conn_list_head;                         // 超时链表头节点索引数组
                                         // fa_conn_list_head:超时链表头节点索引数组(每个超时类型一个)
                                         // ACL_N_TIMEOUTS:超时类型数量(TCP Established、TCP Transient、UDP Idle等)
                                         // 为什么需要数组?不同类型的会话有不同的超时时间,需要分别管理
                                         // 类比:就像"待检查列表头数组"(每个类型一个列表头)
  
  u32 *fa_conn_list_tail;                         // 超时链表尾节点索引数组
                                         // fa_conn_list_tail:超时链表尾节点索引数组(每个超时类型一个)
                                         // 类比:就像"待检查列表尾数组"(每个类型一个列表尾)
  
  /* expiry time set whenever an element is enqueued */
  u64 *fa_conn_list_head_expiry_time;             // 超时链表头节点过期时间数组
                                         // fa_conn_list_head_expiry_time:超时链表头节点过期时间数组(每个超时类型一个)
                                         // 为什么需要?用于优化清理性能(如果头节点未到过期时间,不继续检查)
                                         // 类比:就像"待检查列表头过期时间数组"(用于优化检查性能)
  
  /* adds and deletes per-worker-per-interface */
  u64 *fa_session_dels_by_sw_if_index;            // 按接口索引的会话删除计数数组
                                         // fa_session_dels_by_sw_if_index:按接口索引统计会话删除次数
                                         // 为什么需要?用于统计和监控(每个接口的会话删除情况)
                                         // 类比:就像"按通道统计的'常客删除计数'"
  
  u64 *fa_session_adds_by_sw_if_index;            // 按接口索引的会话添加计数数组
                                         // fa_session_adds_by_sw_if_index:按接口索引统计会话添加次数
                                         // 类比:就像"按通道统计的'常客添加计数'"
  
  /* sessions deleted due to epoch change */
  u64 *fa_session_epoch_change_by_sw_if_index;    // 按接口索引的Policy Epoch变化计数数组
                                         // fa_session_epoch_change_by_sw_if_index:按接口索引统计Policy Epoch变化次数
                                         // 为什么需要?用于统计和监控(Policy Epoch变化导致的会话删除)
                                         // 类比:就像"按通道统计的'规则版本号变化计数'"
  
  /* Vector of expired connections retrieved from lists */
  u32 *expired;                                    // 过期会话索引向量
                                         // expired:过期会话索引向量(从超时链表中检索到的过期会话)
                                         // 为什么需要?用于批量处理过期会话(先收集,后处理)
                                         // 类比:就像"过期常客列表"(从待检查列表中检索到的过期常客)
  
  /* the earliest next expiry time */
  u64 next_expiry_time;                            // 最早的下次过期时间
                                         // next_expiry_time:最早的下次过期时间(用于优化清理调度)
                                         // 为什么需要?用于优化清理进程的唤醒时间(避免频繁唤醒)
                                         // 类比:就像"最早的下次检查时间"(用于优化检查调度)
  
  /* if not zero, look at all the elements until their enqueue timestamp is after below one */
  u64 requeue_until_time;                          // 重新入队截止时间
                                         // requeue_until_time:重新入队截止时间(用于批量重新入队)
                                         // 为什么需要?用于批量处理会话重新入队(提高性能)
                                         // 类比:就像"重新入队截止时间"(用于批量重新入队)
  
  /* Current time between the checks */
  u64 current_time_wait_interval;                  // 当前检查间隔时间
                                         // current_time_wait_interval:当前检查间隔时间(用于自适应调度)
                                         // 为什么需要?用于自适应调整清理频率(根据负载动态调整)
                                         // 类比:就像"当前检查间隔时间"(用于自适应调整)
  
  /* Counter of how many sessions we did delete */
  u64 cnt_deleted_sessions;                        // 删除的会话计数
                                         // cnt_deleted_sessions:删除的会话计数(用于统计和监控)
                                         // 类比:就像"删除的常客计数"
  
  /* Counter of already deleted sessions being deleted - should not increment unless a bug */
  u64 cnt_already_deleted_sessions;                // 已删除会话的重复删除计数
                                         // cnt_already_deleted_sessions:已删除会话的重复删除计数(用于检测bug)
                                         // 为什么需要?用于检测bug(正常情况下应该为0)
                                         // 类比:就像"已删除常客的重复删除计数"(用于检测bug)
  
  /* Number of times we requeued a session to a head of the list */
  u64 cnt_session_timer_restarted;                // 会话定时器重启计数
                                         // cnt_session_timer_restarted:会话定时器重启计数(用于统计和监控)
                                         // 为什么需要?用于统计和监控(会话定时器重启的频率)
                                         // 类比:就像"常客定时器重启计数"
  
  /* swipe up to this enqueue time, rather than following the timeouts */
  u64 swipe_end_time;                              // 清理截止时间
                                         // swipe_end_time:清理截止时间(用于批量清理)
                                         // 为什么需要?用于批量清理会话(提高性能)
                                         // 类比:就像"清理截止时间"(用于批量清理)
  
  /* bitmap of sw_if_index serviced by this worker */
  uword *serviced_sw_if_index_bitmap;               // 此Worker服务的接口索引位图
                                         // serviced_sw_if_index_bitmap:此Worker线程服务的接口索引位图
                                         // 为什么需要?用于追踪此Worker线程负责哪些接口
                                         // 类比:就像"此安检通道服务的通道位图"(用于追踪负责哪些通道)
  
  /* bitmap of sw_if_indices to clear. set by main thread, cleared by worker */
  uword *pending_clear_sw_if_index_bitmap;         // 待清理接口索引位图
                                         // pending_clear_sw_if_index_bitmap:待清理接口索引位图(由主线程设置,Worker线程清除)
                                         // 为什么需要?用于通知Worker线程清理指定接口的所有会话
                                         // 类比:就像"待清理通道位图"(由主管理员设置,安检通道清除)
  
  /* atomic, indicates that the swipe-deletion of connections is in progress */
  u32 clear_in_process;                             // 清理进行中标志
                                         // clear_in_process:清理进行中标志(原子变量,用于同步)
                                         // 为什么需要?用于防止并发清理(确保同一时间只有一个清理操作)
                                         // 类比:就像"清理进行中标志"(用于防止并发清理)
  
  /* Interrupt is pending from main thread */
  int interrupt_is_pending;                         // 中断挂起标志
                                         // interrupt_is_pending:中断挂起标志(主线程发送的中断)
                                         // 为什么需要?用于通知Worker线程有清理工作要做
                                         // 类比:就像"中断挂起标志"(主管理员发送的中断)
  
  /*
   * Interrupt node on the worker thread sets this if it knows there is
   * more work to do, but it has to finish to avoid hogging the
   * core for too long.
   */
                                         // 注释:Worker线程上的中断节点设置此标志,如果它知道还有工作要做,但必须完成以避免长时间占用核心。
  
  int interrupt_is_needed;                          // 需要中断标志
                                         // interrupt_is_needed:需要中断标志(Worker线程设置,表示需要更多中断)
                                         // 为什么需要?用于自适应调度(如果还有工作,请求更多中断)
                                         // 类比:就像"需要中断标志"(如果还有工作,请求更多中断)
  
  /*
   * Set to indicate that the interrupt node wants to get less interrupts
   * because there is not enough work for the current rate.
   */
                                         // 注释:设置此标志表示中断节点希望减少中断,因为当前速率下没有足够的工作。
  
  int interrupt_is_unwanted;                        // 不需要中断标志
                                         // interrupt_is_unwanted:不需要中断标志(Worker线程设置,表示不需要更多中断)
                                         // 为什么需要?用于自适应调度(如果工作不足,减少中断频率)
                                         // 类比:就像"不需要中断标志"(如果工作不足,减少中断频率)
  
  /*
   * Set to copy of a "generation" counter in main thread so we can sync the interrupts.
   */
                                         // 注释:设置为主线程中"generation"计数器的副本,以便我们可以同步中断。
  
  int interrupt_generation;                         // 中断生成计数
                                         // interrupt_generation:中断生成计数(用于同步中断)
                                         // 为什么需要?用于同步中断(确保中断的顺序和一致性)
                                         // 类比:就像"中断生成计数"(用于同步中断)
  
   /*
    * work in progress data for the pipelined node operation
    */
                                         // 注释:流水线节点操作的处理中数据
  
  vlib_buffer_t *bufs[VLIB_FRAME_SIZE];             // 数据包缓冲区数组
                                         // bufs:数据包缓冲区数组(用于批量处理)
                                         // VLIB_FRAME_SIZE:帧大小(通常256)
                                         // 为什么需要?用于批量处理数据包(提高性能)
                                         // 类比:就像"数据包缓冲区数组"(用于批量处理)
  
  u32 sw_if_indices[VLIB_FRAME_SIZE];               // 接口索引数组
                                         // sw_if_indices:接口索引数组(用于批量处理)
                                         // 类比:就像"通道索引数组"(用于批量处理)
  
  fa_5tuple_t fa_5tuples[VLIB_FRAME_SIZE];         // 5-tuple数组
                                         // fa_5tuples:5-tuple数组(用于批量处理)
                                         // 类比:就像"旅客信息数组"(用于批量处理)
  
  u64 hashes[VLIB_FRAME_SIZE];                      // Hash值数组
                                         // hashes:Hash值数组(用于批量处理)
                                         // 类比:就像"Hash值数组"(用于批量处理)
  
  u16 nexts[VLIB_FRAME_SIZE];                      // 下一个节点索引数组
                                         // nexts:下一个节点索引数组(用于批量处理)
                                         // 类比:就像"下一个节点索引数组"(用于批量处理)

} acl_fa_per_worker_data_t;

结构体字段总结

  1. 会话Pool:存储此Worker线程的所有会话
  2. 会话变更请求:处理来自其他Worker线程的会话变更请求
  3. 超时链表:管理不同类型的超时链表(TCP Established、TCP Transient、UDP Idle等)
  4. 统计信息:按接口索引统计会话添加、删除、Policy Epoch变化等
  5. 过期会话处理:收集和处理过期会话
  6. 清理调度:管理清理进程的调度(自适应调整)
  7. 接口管理:追踪此Worker线程负责的接口
  8. 中断管理:管理主线程和Worker线程之间的中断同步
  9. 批量处理数据:用于批量处理数据包(提高性能)

类比:就像"每个安检通道的完整档案系统":

  • 档案柜:存储此通道的所有常客档案(会话Pool)
  • 请求处理:处理来自其他通道的请求(会话变更请求)
  • 待检查列表:管理不同类型的待检查列表(超时链表)
  • 统计信息:按通道统计常客添加、删除等(统计信息)
  • 过期处理:收集和处理过期常客(过期会话处理)
  • 检查调度:管理检查进程的调度(清理调度)
  • 通道管理:追踪此通道负责的通道(接口管理)
  • 中断管理:管理主管理员和通道之间的中断同步(中断管理)
  • 批量处理:用于批量处理旅客(批量处理数据)

12.4 会话表容量管理:如何"管理数据库容量"?

12.4.1 会话表容量参数

会话表的容量由以下参数控制:

c 复制代码
//241:244:src/plugins/acl/acl.h
  /* conn table per-interface conn table parameters */
  u32 fa_conn_table_hash_num_buckets;              // Hash表桶数量
                                         // fa_conn_table_hash_num_buckets:Hash表桶数量(默认64K)
                                         // 为什么需要?Hash表的桶数量影响Hash冲突率(桶越多,冲突越少)
                                         // 类比:就像"数据库索引的'桶数量'"(影响查找性能)
  
  uword fa_conn_table_hash_memory_size;            // Hash表内存大小
                                         // fa_conn_table_hash_memory_size:Hash表内存大小(默认1GB)
                                         // 为什么需要?Hash表需要预先分配内存(避免动态分配)
                                         // 类比:就像"数据库索引的'内存大小'"(影响容量)
  
  u64 fa_conn_table_max_entries;                   // 最大会话数
                                         // fa_conn_table_max_entries:最大会话数(默认500,000)
                                         // 为什么需要?限制会话表的最大容量(避免内存耗尽)
                                         // 类比:就像"数据库的'最大记录数'"(影响容量)

默认值

c 复制代码
//23:25:src/plugins/acl/fa_node.h
#define ACL_FA_CONN_TABLE_DEFAULT_HASH_NUM_BUCKETS (64 * 1024)  // 默认Hash表桶数量:64K
#define ACL_FA_CONN_TABLE_DEFAULT_HASH_MEMORY_SIZE (1ULL<<30)   // 默认Hash表内存大小:1GB
#define ACL_FA_CONN_TABLE_DEFAULT_MAX_ENTRIES 500000            // 默认最大会话数:500,000

容量计算

  • 每个会话大小 :128字节(fa_session_t
  • 最大会话数:500,000
  • 会话Pool总内存:500,000 × 128字节 = 64MB(每个Worker线程)
  • Hash表内存:1GB(IPv4和IPv6共享)

类比:就像"数据库容量配置":

  • 索引桶数量:64K(影响查找性能)
  • 索引内存大小:1GB(影响容量)
  • 最大记录数:500,000(影响容量)

12.4.2 容量参数配置

容量参数可以通过CLI命令配置:

c 复制代码
//2613:2657:src/plugins/acl/acl.c
      if (unformat (input, "table"))
	{
	  /* The commands here are for tuning/testing. No user-serviceable parts inside */
                                         // 注释:这里的命令用于调优/测试。内部没有用户可维修的部分。
	  
	  if (unformat (input, "max-entries"))
	    {
	      if (!unformat (input, "%u", &val))  // 解析最大会话数参数
		{
		  error = clib_error_return (0,
					     "expecting maximum number of entries, got `%U`",
					     format_unformat_error, input);
		  goto done;
		}
	      else
		{
		  acl_set_session_max_entries (val);  // 设置最大会话数
                                         // acl_set_session_max_entries:设置最大会话数
                                         // 作用:更新最大会话数配置
                                         // 类比:就像"设置数据库的'最大记录数'"
		  goto done;
		}
	    }
	  
	  if (unformat (input, "hash-table-buckets"))
	    {
	      if (!unformat (input, "%u", &val))  // 解析Hash表桶数量参数
		{
		  error = clib_error_return (0,
					     "expecting maximum number of hash table buckets, got `%U`",
					     format_unformat_error, input);
		  goto done;
		}
	      else
		{
		  am->fa_conn_table_hash_num_buckets = val;  // 设置Hash表桶数量
                                         // 类比:就像"设置数据库索引的'桶数量'"
		  goto done;
		}
	    }
	  
	  if (unformat (input, "hash-table-memory"))
	    {
	      if (!unformat (input, "%U", unformat_memory_size, &memory_size))  // 解析Hash表内存大小参数
		{
		  error = clib_error_return (0,
					     "expecting maximum amount of hash table memory, got `%U`",
					     format_unformat_error, input);
		  goto done;
		}
	      else
		{
		  am->fa_conn_table_hash_memory_size = memory_size;  // 设置Hash表内存大小
                                         // 类比:就像"设置数据库索引的'内存大小'"
		  goto done;
		}
	    }

CLI命令示例

bash 复制代码
# 设置最大会话数为1,000,000
set acl plugin session table max-entries 1000000

# 设置Hash表桶数量为128K
set acl plugin session table hash-table-buckets 131072

# 设置Hash表内存大小为2GB
set acl plugin session table hash-table-memory 2G

类比:就像"配置数据库容量":

  • 设置最大记录数set database max-entries 1000000
  • 设置索引桶数量set database index-buckets 131072
  • 设置索引内存大小set database index-memory 2G


12.5 会话表性能优化:如何"优化数据库性能"?

12.5.1 缓存行对齐优化

会话结构体(fa_session_t)设计为128字节,正好是2个缓存行(64字节 × 2):

c 复制代码
//157:158:src/plugins/acl/fa_node.h
/* Let's try to fit within two cachelines */
CT_ASSERT_EQUAL(fa_session_t_size_is_128, sizeof(fa_session_t), 128);  // 编译时断言:会话结构体大小为128字节
                                         // CT_ASSERT_EQUAL:编译时断言(如果条件不满足,编译失败)
                                         // 为什么需要128字节?正好是2个缓存行(现代CPU的缓存行大小通常是64字节)
                                         // 为什么需要缓存行对齐?减少缓存未命中,提高访问速度
                                         // 类比:就像"档案夹大小正好是2个'存储单元'"(减少存储访问次数)

缓存行对齐的优势

  1. 减少缓存未命中:结构体对齐到缓存行,减少跨缓存行访问
  2. 提高访问速度:CPU可以一次性加载整个缓存行
  3. 减少伪共享:不同线程的会话不会共享同一个缓存行

类比:就像"档案夹大小优化":

  • 对齐到存储单元:档案夹大小正好是2个"存储单元"
  • 减少访问次数:一次可以加载整个"存储单元"
  • 避免冲突:不同通道的档案不会共享同一个"存储单元"

12.5.2 预取优化

在查找会话之前,预取Hash表的bucket和数据:

c 复制代码
//615:631:src/plugins/acl/session_inlines.h
always_inline void
acl_fa_prefetch_session_bucket_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
  if (is_ip6)                                    // 如果是IPv6
    {
      clib_bihash_prefetch_bucket_40_8 (&am->fa_ip6_sessions_hash, hash);  // 预取IPv6会话Hash表的bucket
                                         // clib_bihash_prefetch_bucket_40_8:预取40_8 Hash表的bucket
                                         // 作用:提前加载Hash表的bucket到CPU缓存(减少延迟)
                                         // 类比:就像"提前加载'数据库索引页'到'内存缓存'"
    }
  else                                          // 如果是IPv4
    {
      clib_bihash_prefetch_bucket_16_8 (&am->fa_ip4_sessions_hash, hash);  // 预取IPv4会话Hash表的bucket
                                         // 类比:就像"提前加载'数据库索引页'到'内存缓存'"
    }
}

always_inline void
acl_fa_prefetch_session_data_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
  if (is_ip6)                                    // 如果是IPv6
    {
      clib_bihash_prefetch_data_40_8 (&am->fa_ip6_sessions_hash, hash);  // 预取IPv6会话Hash表的数据
                                         // clib_bihash_prefetch_data_40_8:预取40_8 Hash表的数据
                                         // 作用:提前加载Hash表的数据到CPU缓存(减少延迟)
                                         // 类比:就像"提前加载'数据库记录'到'内存缓存'"
    }
  else                                          // 如果是IPv4
    {
      clib_bihash_prefetch_data_16_8 (&am->fa_ip4_sessions_hash, hash);  // 预取IPv4会话Hash表的数据
                                         // 类比:就像"提前加载'数据库记录'到'内存缓存'"
    }
}

预取优化策略

  1. Bucket预取:在计算Hash值后,立即预取Hash表的bucket
  2. 数据预取:在查找bucket后,立即预取Hash表的数据
  3. 流水线处理:在处理当前数据包时,预取下一个数据包的Hash表数据

类比:就像"数据库查询优化":

  • 索引页预取:在计算索引后,立即预取索引页
  • 数据页预取:在查找索引后,立即预取数据页
  • 流水线查询:在处理当前查询时,预取下一个查询的数据

12.5.3 批量处理优化

数据平面节点使用批量处理来提高性能:

c 复制代码
//229:234:src/plugins/acl/fa_node.h
   /*
    * work in progress data for the pipelined node operation
    */
                                         // 注释:流水线节点操作的处理中数据
  
  vlib_buffer_t *bufs[VLIB_FRAME_SIZE];             // 数据包缓冲区数组
                                         // bufs:数据包缓冲区数组(用于批量处理)
                                         // VLIB_FRAME_SIZE:帧大小(通常256)
                                         // 为什么需要?用于批量处理数据包(提高性能)
                                         // 类比:就像"数据包缓冲区数组"(用于批量处理)
  
  u32 sw_if_indices[VLIB_FRAME_SIZE];               // 接口索引数组
                                         // sw_if_indices:接口索引数组(用于批量处理)
                                         // 类比:就像"通道索引数组"(用于批量处理)
  
  fa_5tuple_t fa_5tuples[VLIB_FRAME_SIZE];         // 5-tuple数组
                                         // fa_5tuples:5-tuple数组(用于批量处理)
                                         // 类比:就像"旅客信息数组"(用于批量处理)
  
  u64 hashes[VLIB_FRAME_SIZE];                      // Hash值数组
                                         // hashes:Hash值数组(用于批量处理)
                                         // 类比:就像"Hash值数组"(用于批量处理)
  
  u16 nexts[VLIB_FRAME_SIZE];                      // 下一个节点索引数组
                                         // nexts:下一个节点索引数组(用于批量处理)
                                         // 类比:就像"下一个节点索引数组"(用于批量处理)

批量处理流程

  1. 批量提取:批量提取多个数据包的5-tuple
  2. 批量Hash:批量计算多个数据包的Hash值
  3. 批量预取:批量预取多个数据包的Hash表数据
  4. 批量查找:批量查找多个数据包的会话

性能提升

  • 减少函数调用开销:批量处理减少函数调用次数
  • 提高缓存利用率:批量处理提高CPU缓存利用率
  • 向量化优化:批量处理可以使用SIMD指令优化

类比:就像"批量处理旅客":

  • 批量读取:批量读取多个旅客的身份证号
  • 批量查找:批量在数据库中查找多个旅客
  • 批量处理:批量处理多个旅客的安检

12.6 会话表监控和调试:如何"监控和调试数据库"?

12.6.1 会话表显示函数:acl_plugin_show_sessions

acl_plugin_show_sessions 用于显示会话表的详细状态信息:

c 复制代码
//3443:3586:src/plugins/acl/acl.c
static void
acl_plugin_show_sessions (acl_main_t * am,
			  u32 show_session_thread_id,
			  u32 show_session_session_index)
{
  vlib_main_t *vm = am->vlib_main;                // 获取VPP主结构体
                                         // 类比:就像"获取'系统主结构体'"
  
  u16 wk;                                         // Worker线程索引(临时变量)
                                         // 类比:就像"安检通道编号"
  
  vnet_interface_main_t *im = &am->vnet_main->interface_main;  // 获取接口主结构体
                                         // 类比:就像"获取'通道主结构体'"
  
  vnet_sw_interface_t *swif;                     // 接口指针(临时变量)
                                         // 类比:就像"通道指针"
  
  u64 now = clib_cpu_time_now ();                 // 获取当前时间戳
                                         // 类比:就像"获取当前时间"
  
  u64 clocks_per_second = am->vlib_main->clib_time.clocks_per_second;  // 获取每秒时钟周期数
                                         // 类比:就像"获取每秒时间单位数"

  // ========== 第一部分:显示全局统计信息 ==========
  
  {
    u64 n_adds = am->fa_session_total_adds;       // 获取全局会话添加计数
                                         // fa_session_total_adds:全局会话添加计数
                                         // 类比:就像"获取全局'常客添加计数'"
    
    u64 n_dels = am->fa_session_total_dels;       // 获取全局会话删除计数
                                         // fa_session_total_dels:全局会话删除计数
                                         // 类比:就像"获取全局'常客删除计数'"
    
    u64 n_deact = am->fa_session_total_deactivations;  // 获取全局会话停用计数
                                         // fa_session_total_deactivations:全局会话停用计数
                                         // 类比:就像"获取全局'常客停用计数'"
    
    vlib_cli_output (vm, "Sessions total: add %lu - del %lu = %lu", n_adds,
		     n_dels, n_adds - n_dels);  // 显示会话总数(添加 - 删除)
                                         // 类比:就像"显示'常客总数'(添加 - 删除)"
    
    vlib_cli_output (vm, "Sessions active: add %lu - deact %lu = %lu", n_adds,
		     n_deact, n_adds - n_deact);  // 显示活跃会话数(添加 - 停用)
                                         // 类比:就像"显示'活跃常客数'(添加 - 停用)"
    
    vlib_cli_output (vm, "Sessions being purged: deact %lu - del %lu = %lu",
		     n_deact, n_dels, n_deact - n_dels);  // 显示正在清理的会话数(停用 - 删除)
                                         // 类比:就像"显示'正在清理的常客数'(停用 - 删除)"
  }
  
  vlib_cli_output (vm, "now: %lu clocks per second: %lu", now,
		   clocks_per_second);  // 显示当前时间和时钟周期
                                         // 类比:就像"显示当前时间和时间单位"

  // ========== 第二部分:显示Per-worker数据 ==========
  
  vlib_cli_output (vm, "\n\nPer-thread data:");  // 显示Per-worker数据标题
                                         // 类比:就像"显示'每个安检通道的数据'"
  
  for (wk = 0; wk < vec_len (am->per_worker_data); wk++)  // 遍历所有Worker线程
    {
      acl_fa_per_worker_data_t *pw = &am->per_worker_data[wk];  // 获取Worker线程的Per-worker数据
                                         // 类比:就像"获取'安检通道'的'数据'"
      
      vlib_cli_output (vm, "Thread #%d:", wk);  // 显示Worker线程编号
                                         // 类比:就像"显示'安检通道编号'"

      // ========== 显示指定会话的详细信息 ==========
      
      if (show_session_thread_id == wk
	  && show_session_session_index < pool_len (pw->fa_sessions_pool))  // 如果指定了会话索引
	{
	  vlib_cli_output (vm, "  session index %u:",
			   show_session_session_index);  // 显示会话索引
                                         // 类比:就像"显示'常客索引'"
	  
	  fa_session_t *sess =
	    pw->fa_sessions_pool + show_session_session_index;  // 获取会话指针
                                         // 类比:就像"获取'常客档案夹'指针"
	  
	  u64 *m = (u64 *) & sess->info;    // 获取5-tuple信息指针(转换为u64指针)
                                         // 类比:就像"获取'常客基本信息'指针"
	  
	  vlib_cli_output (vm,
			   "    info: %016llx %016llx %016llx %016llx %016llx %016llx",
			   m[0], m[1], m[2], m[3], m[4], m[5]);  // 显示5-tuple信息(6个u64)
                                         // 类比:就像"显示'常客基本信息'(6个u64)"
	  
	  vlib_cli_output (vm, "    sw_if_index: %u", sess->sw_if_index);  // 显示接口索引
                                         // 类比:就像"显示'通道编号'"
	  
	  vlib_cli_output (vm, "    tcp_flags_seen: %x",
			   sess->tcp_flags_seen.as_u16);  // 显示TCP标志位
                                         // 类比:就像"显示'访问记录'"
	  
	  vlib_cli_output (vm, "    last active time: %lu",
			   sess->last_active_time);  // 显示最后活跃时间
                                         // 类比:就像"显示'最后访问时间'"
	  
	  vlib_cli_output (vm, "    thread index: %u", sess->thread_index);  // 显示线程索引
                                         // 类比:就像"显示'安检通道编号'"
	  
	  vlib_cli_output (vm, "    link enqueue time: %lu",
			   sess->link_enqueue_time);  // 显示链表入队时间
                                         // 类比:就像"显示'加入列表时间'"
	  
	  vlib_cli_output (vm, "    link next index: %u",
			   sess->link_next_idx);  // 显示链表下一个节点索引
                                         // 类比:就像"显示'下一个常客索引'"
	  
	  vlib_cli_output (vm, "    link prev index: %u",
			   sess->link_prev_idx);  // 显示链表前一个节点索引
                                         // 类比:就像"显示'前一个常客索引'"
	  
	  vlib_cli_output (vm, "    link list id: %u", sess->link_list_id);  // 显示链表ID
                                         // 类比:就像"显示'列表类型'"
	}

      // ========== 显示按接口的统计信息 ==========
      
      vlib_cli_output (vm, "  connection add/del stats:", wk);  // 显示连接添加/删除统计标题
                                         // 类比:就像"显示'常客添加/删除统计'"
      
      pool_foreach (swif, im->sw_interfaces)       // 遍历所有接口
         {
          u32 sw_if_index = swif->sw_if_index;     // 获取接口索引
                                         // 类比:就像"获取'通道编号'"
          
          u64 n_adds =
            (sw_if_index < vec_len (pw->fa_session_adds_by_sw_if_index) ?
             pw->fa_session_adds_by_sw_if_index[sw_if_index] :
             0);                                    // 获取接口的会话添加计数
                                         // 类比:就像"获取'通道'的'常客添加计数'"
          
          u64 n_dels =
            (sw_if_index < vec_len (pw->fa_session_dels_by_sw_if_index) ?
             pw->fa_session_dels_by_sw_if_index[sw_if_index] :
             0);                                    // 获取接口的会话删除计数
                                         // 类比:就像"获取'通道'的'常客删除计数'"
          
          u64 n_epoch_changes =
            (sw_if_index < vec_len (pw->fa_session_epoch_change_by_sw_if_index) ?
             pw->fa_session_epoch_change_by_sw_if_index[sw_if_index] :
             0);                                    // 获取接口的Policy Epoch变化计数
                                         // 类比:就像"获取'通道'的'规则版本号变化计数'"
          
          vlib_cli_output (vm,
                           "    sw_if_index %d: add %lu - del %lu = %lu; epoch chg: %lu",
                           sw_if_index,
                           n_adds,
                           n_dels,
                           n_adds - n_dels,         // 当前会话数(添加 - 删除)
                           n_epoch_changes);        // Policy Epoch变化次数
                                         // 类比:就像"显示'通道'的统计信息"
        }

      // ========== 显示超时链表信息 ==========
      
      vlib_cli_output (vm, "  connection timeout type lists:", wk);  // 显示连接超时类型列表标题
                                         // 类比:就像"显示'待检查列表'"
      
      u8 tt = 0;                                  // 超时类型索引(临时变量)
                                         // 类比:就像"列表类型编号"
      
      for (tt = 0; tt < ACL_N_TIMEOUTS; tt++)     // 遍历所有超时类型
	{
	  u32 head_session_index = pw->fa_conn_list_head[tt];  // 获取链表头节点索引
                                         // 类比:就像"获取'列表头'索引"
	  
	  vlib_cli_output (vm, "  fa_conn_list_head[%d]: %d", tt,
			   head_session_index);  // 显示链表头节点索引
                                         // 类比:就像"显示'列表头'索引"
	  
	  if (~0 != head_session_index)            // 如果链表不为空
	    {
	      fa_session_t *sess = pw->fa_sessions_pool + head_session_index;  // 获取链表头节点会话指针
                                         // 类比:就像"获取'列表头'的'常客档案夹'指针"
	      
	      vlib_cli_output (vm, "    last active time: %lu",
			       sess->last_active_time);  // 显示最后活跃时间
                                         // 类比:就像"显示'最后访问时间'"
	      
	      vlib_cli_output (vm, "    link enqueue time: %lu",
			       sess->link_enqueue_time);  // 显示链表入队时间
                                         // 类比:就像"显示'加入列表时间'"
	    }
	}

      // ========== 显示清理调度信息 ==========
      
      vlib_cli_output (vm, "  Next expiry time: %lu", pw->next_expiry_time);  // 显示下次过期时间
                                         // 类比:就像"显示'下次检查时间'"
      
      vlib_cli_output (vm, "  Requeue until time: %lu",
		       pw->requeue_until_time);  // 显示重新入队截止时间
                                         // 类比:就像"显示'重新入队截止时间'"
      
      vlib_cli_output (vm, "  Current time wait interval: %lu",
		       pw->current_time_wait_interval);  // 显示当前等待间隔时间
                                         // 类比:就像"显示'当前等待间隔时间'"

      // ========== 显示统计计数器 ==========
      
      vlib_cli_output (vm, "  Count of deleted sessions: %lu",
		       pw->cnt_deleted_sessions);  // 显示删除的会话计数
                                         // 类比:就像"显示'删除的常客计数'"
      
      vlib_cli_output (vm, "  Delete already deleted: %lu",
		       pw->cnt_already_deleted_sessions);  // 显示已删除会话的重复删除计数
                                         // 类比:就像"显示'已删除常客的重复删除计数'"
      
      vlib_cli_output (vm, "  Session timers restarted: %lu",
		       pw->cnt_session_timer_restarted);  // 显示会话定时器重启计数
                                         // 类比:就像"显示'常客定时器重启计数'"
      
      vlib_cli_output (vm, "  Swipe until this time: %lu",
		       pw->swipe_end_time);  // 显示清理截止时间
                                         // 类比:就像"显示'清理截止时间'"

      // ========== 显示接口位图 ==========
      
      vlib_cli_output (vm, "  sw_if_index serviced bitmap: %U",
		       format_bitmap_hex, pw->serviced_sw_if_index_bitmap);  // 显示服务的接口位图
                                         // format_bitmap_hex:格式化位图为十六进制字符串
                                         // 类比:就像"显示'服务的通道位图'"
      
      vlib_cli_output (vm, "  pending clear intfc bitmap : %U",
		       format_bitmap_hex,
		       pw->pending_clear_sw_if_index_bitmap);  // 显示待清理接口位图
                                         // 类比:就像"显示'待清理通道位图'"

      // ========== 显示清理状态 ==========
      
      vlib_cli_output (vm, "  clear in progress: %u", pw->clear_in_process);  // 显示清理进行中标志
                                         // 类比:就像"显示'清理进行中标志'"

      // ========== 显示中断状态 ==========
      
      vlib_cli_output (vm, "  interrupt is pending: %d",
		       pw->interrupt_is_pending);  // 显示中断挂起标志
                                         // 类比:就像"显示'中断挂起标志'"
      
      vlib_cli_output (vm, "  interrupt is needed: %d",
		       pw->interrupt_is_needed);  // 显示需要中断标志
                                         // 类比:就像"显示'需要中断标志'"
      
      vlib_cli_output (vm, "  interrupt is unwanted: %d",
		       pw->interrupt_is_unwanted);  // 显示不需要中断标志
                                         // 类比:就像"显示'不需要中断标志'"
      
      vlib_cli_output (vm, "  interrupt generation: %d",
		       pw->interrupt_generation);  // 显示中断生成计数
                                         // 类比:就像"显示'中断生成计数'"

      // ========== 显示会话变更请求统计 ==========
      
      vlib_cli_output (vm, "  received session change requests: %d",
		       pw->rcvd_session_change_requests);  // 显示接收的会话变更请求计数
                                         // 类比:就像"显示'接收的请求总数'"
      
      vlib_cli_output (vm, "  sent session change requests: %d",
		       pw->sent_session_change_requests);  // 显示发送的会话变更请求计数
                                         // 类比:就像"显示'发送的请求总数'"
    }

  // ========== 第三部分:显示清理进程计数器 ==========
  
  vlib_cli_output (vm, "\n\nConn cleaner thread counters:");  // 显示清理进程计数器标题
                                         // 类比:就像"显示'清理进程计数器'"
  
#define _(cnt, desc) vlib_cli_output(vm, "             %20lu: %s", am->cnt, desc);  // 宏定义:显示计数器
                                         // 类比:就像"显示计数器"
  
  foreach_fa_cleaner_counter;                     // 遍历所有清理进程计数器(通过宏展开)
                                         // 包括:fa_cleaner_cnt_delete_by_sw_index、fa_cleaner_cnt_unknown_event等
                                         // 类比:就像"遍历所有'清理进程计数器'"
#undef _
  
  vlib_cli_output (vm, "Interrupt generation: %d",
		   am->fa_interrupt_generation);  // 显示中断生成计数
                                         // 类比:就像"显示'中断生成计数'"
  
  vlib_cli_output (vm,
		   "Sessions per interval: min %lu max %lu increment: %f ms current: %f ms",
		   am->fa_min_deleted_sessions_per_interval,  // 最小删除会话数
		   am->fa_max_deleted_sessions_per_interval,  // 最大删除会话数
		   am->fa_cleaner_wait_time_increment * 1000.0,  // 等待时间增量(转换为毫秒)
		   ((f64) am->fa_current_cleaner_timer_wait_interval) *
		   1000.0 / (f64) vm->clib_time.clocks_per_second);  // 当前等待间隔(转换为毫秒)
                                         // 类比:就像"显示'清理调度参数'"
  
  vlib_cli_output (vm, "Reclassify sessions: %d", am->reclassify_sessions);  // 显示会话重分类标志
                                         // reclassify_sessions:会话重分类标志(是否启用Policy Epoch检查)
                                         // 类比:就像"显示'会话重分类标志'"
}

函数总结

这个函数显示了会话表的完整状态信息:

  1. 全局统计信息:会话总数、活跃会话数、正在清理的会话数
  2. Per-worker数据
    • 指定会话的详细信息(如果指定了会话索引)
    • 按接口的统计信息(添加、删除、Policy Epoch变化)
    • 超时链表信息(每个超时类型的链表头)
    • 清理调度信息(下次过期时间、等待间隔等)
    • 统计计数器(删除计数、定时器重启计数等)
    • 接口位图(服务的接口、待清理的接口)
    • 清理状态(清理进行中标志)
    • 中断状态(中断挂起、需要、不需要标志)
    • 会话变更请求统计(接收、发送计数)
  3. 清理进程计数器:清理进程的各种计数器
  4. 清理调度参数:最小/最大删除会话数、等待时间增量、当前等待间隔
  5. 会话重分类标志:是否启用Policy Epoch检查

类比:就像完整的"数据库状态报告":

  • 全局统计:总记录数、活跃记录数、正在清理的记录数
  • 每个通道的数据
    • 指定记录的详细信息
    • 按通道的统计信息
    • 待检查列表信息
    • 检查调度信息
    • 统计计数器
    • 通道位图
    • 检查状态
    • 中断状态
    • 请求统计
  • 清理进程计数器:清理进程的各种计数器
  • 清理调度参数:清理调度参数
  • 记录重分类标志:是否启用记录重分类

12.6.2 Hash表显示函数:show_fa_sessions_hash

show_fa_sessions_hash 用于显示会话Hash表的详细信息:

c 复制代码
//908:920:src/plugins/acl/sess_mgmt_node.c
void
show_fa_sessions_hash (vlib_main_t * vm, u32 verbose)
{
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  vlib_cli_output (vm, "\n\nIPv6 sessions hash table:");  // 显示IPv6会话Hash表标题
                                         // 类比:就像"显示'IPv6常客数据库'标题"
  
  vlib_cli_output (vm, "%U",
		   format_bihash_40_8, &am->fa_ip6_sessions_hash,
		   verbose);                            // 显示IPv6会话Hash表详细信息
                                         // format_bihash_40_8:格式化40_8双向Hash表(用于调试输出)
                                         // verbose:详细程度(0=简要,非0=详细)
                                         // 类比:就像"显示'IPv6常客数据库'详细信息"
  
  vlib_cli_output (vm, "\n\nIPv4 sessions hash table:");  // 显示IPv4会话Hash表标题
                                         // 类比:就像"显示'IPv4常客数据库'标题"
  
  vlib_cli_output (vm, "%U",
		   format_bihash_16_8, &am->fa_ip4_sessions_hash,
		   verbose);                            // 显示IPv4会话Hash表详细信息
                                         // format_bihash_16_8:格式化16_8双向Hash表(用于调试输出)
                                         // 类比:就像"显示'IPv4常客数据库'详细信息"
}

函数总结

  1. 显示IPv6会话Hash表:显示IPv6会话Hash表的详细信息(bucket数量、条目数量、冲突情况等)
  2. 显示IPv4会话Hash表:显示IPv4会话Hash表的详细信息

输出信息

  • Bucket数量:Hash表的bucket数量
  • 条目数量:Hash表中的条目数量
  • 冲突情况:Hash冲突的统计信息
  • 内存使用:Hash表的内存使用情况

类比:就像"显示数据库索引信息":

  • 索引桶数量:索引的桶数量
  • 索引条目数量:索引中的条目数量
  • 冲突情况:索引冲突的统计信息
  • 内存使用:索引的内存使用情况

12.7 会话表清理机制:如何"定期清理过期记录"?

12.7.1 清理进程参数

清理进程使用自适应调度机制,根据负载动态调整清理频率:

c 复制代码
//249:268:src/plugins/acl/acl.h
  /*
   * If the cleaner has to delete more than this number
   * of connections, it halves the sleep time.
   */
                                         // 注释:如果清理进程需要删除超过此数量的连接,它将睡眠时间减半。
  
#define ACL_FA_DEFAULT_MAX_DELETED_SESSIONS_PER_INTERVAL 100  // 默认最大删除会话数:100
  u64 fa_max_deleted_sessions_per_interval;        // 最大删除会话数
                                         // fa_max_deleted_sessions_per_interval:每次清理的最大删除会话数
                                         // 为什么需要?限制每次清理的会话数,避免一次性清理太多(影响性能)
                                         // 类比:就像"每次检查的最大删除常客数"(避免一次性删除太多)
  
  /*
   * If the cleaner deletes less than these connections,
   * it increases the wait time by the "increment"
   */
                                         // 注释:如果清理进程删除的连接少于这些,它将等待时间增加"增量"。
  
#define ACL_FA_DEFAULT_MIN_DELETED_SESSIONS_PER_INTERVAL 1  // 默认最小删除会话数:1
  u64 fa_min_deleted_sessions_per_interval;        // 最小删除会话数
                                         // fa_min_deleted_sessions_per_interval:每次清理的最小删除会话数
                                         // 为什么需要?如果删除的会话数少于最小值,增加等待时间(减少清理频率)
                                         // 类比:就像"每次检查的最小删除常客数"(如果删除太少,减少检查频率)
  
#define ACL_FA_DEFAULT_CLEANER_WAIT_TIME_INCREMENT 0.1  // 默认等待时间增量:0.1秒
  f64 fa_cleaner_wait_time_increment;                // 等待时间增量
                                         // fa_cleaner_wait_time_increment:等待时间增量(秒)
                                         // 为什么需要?用于自适应调整清理频率(根据负载动态调整)
                                         // 类比:就像"等待时间增量"(用于自适应调整检查频率)
  
  u64 fa_current_cleaner_timer_wait_interval;      // 当前清理定时器等待间隔
                                         // fa_current_cleaner_timer_wait_interval:当前清理定时器等待间隔(时钟周期)
                                         // 为什么需要?用于自适应调整清理频率(根据负载动态调整)
                                         // 类比:就像"当前检查定时器等待间隔"(用于自适应调整检查频率)

自适应调度机制

  1. 如果删除的会话数 > 最大删除数:将等待时间减半(增加清理频率)
  2. 如果删除的会话数 < 最小删除数:将等待时间增加增量(减少清理频率)

类比:就像"自适应检查机制":

  • 如果删除的常客数 > 最大删除数:将检查间隔减半(增加检查频率)
  • 如果删除的常客数 < 最小删除数:将检查间隔增加增量(减少检查频率)

12.7.2 清理进程计数器

清理进程使用多个计数器来统计清理操作:

c 复制代码
//281:292:src/plugins/acl/acl.h
#define foreach_fa_cleaner_counter                                         \
  _(fa_cleaner_cnt_delete_by_sw_index, "delete_by_sw_index events")        // 按接口删除事件计数
                                         // fa_cleaner_cnt_delete_by_sw_index:按接口删除事件计数
                                         // 为什么需要?统计按接口删除会话的事件数
                                         // 类比:就像"按通道删除事件计数"
  
  _(fa_cleaner_cnt_delete_by_sw_index_ok, "delete_by_sw_index handled ok")  // 按接口删除成功计数
                                         // fa_cleaner_cnt_delete_by_sw_index_ok:按接口删除成功计数
                                         // 类比:就像"按通道删除成功计数"
  
  _(fa_cleaner_cnt_unknown_event, "unknown events received")               // 未知事件计数
                                         // fa_cleaner_cnt_unknown_event:未知事件计数
                                         // 为什么需要?统计未知事件数(用于检测bug)
                                         // 类比:就像"未知事件计数"(用于检测bug)
  
  _(fa_cleaner_cnt_timer_restarted, "session idle timers restarted")       // 会话空闲定时器重启计数
                                         // fa_cleaner_cnt_timer_restarted:会话空闲定时器重启计数
                                         // 为什么需要?统计会话定时器重启次数(用于性能分析)
                                         // 类比:就像"常客空闲定时器重启计数"
  
  _(fa_cleaner_cnt_wait_with_timeout, "event wait with timeout called")    // 带超时的等待计数
                                         // fa_cleaner_cnt_wait_with_timeout:带超时的等待计数
                                         // 为什么需要?统计带超时的等待次数(用于性能分析)
                                         // 类比:就像"带超时的等待计数"
  
  _(fa_cleaner_cnt_wait_without_timeout, "event wait w/o timeout called")  // 不带超时的等待计数
                                         // fa_cleaner_cnt_wait_without_timeout:不带超时的等待计数
                                         // 为什么需要?统计不带超时的等待次数(用于性能分析)
                                         // 类比:就像"不带超时的等待计数"
  
  _(fa_cleaner_cnt_event_cycles, "total event cycles")                     // 总事件周期计数
                                         // fa_cleaner_cnt_event_cycles:总事件周期计数
                                         // 为什么需要?统计总事件周期数(用于性能分析)
                                         // 类比:就像"总事件周期计数"
/* end of counters */
#define _(id, desc) u32 id;               // 宏定义:定义计数器变量
  foreach_fa_cleaner_counter              // 遍历所有计数器(通过宏展开)
#undef _

计数器用途

  1. 性能分析:统计清理操作的频率和性能
  2. Bug检测:检测未知事件和异常情况
  3. 监控:监控清理进程的健康状态

类比:就像"清理进程的统计信息":

  • 性能分析:统计清理操作的频率和性能
  • Bug检测:检测未知事件和异常情况
  • 监控:监控清理进程的健康状态

12.8 会话表容量管理:如何"管理数据库容量"?

12.8.1 最大会话数设置函数:acl_set_session_max_entries

acl_set_session_max_entries 用于设置最大会话数:

c 复制代码
//2494:2499:src/plugins/acl/acl.c
static void
acl_set_session_max_entries (u32 value)
{
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  am->fa_conn_table_max_entries = value;          // 设置最大会话数
                                         // fa_conn_table_max_entries:最大会话数(每个Worker线程)
                                         // 为什么需要?限制会话表的最大容量(避免内存耗尽)
                                         // 类比:就像"设置数据库的'最大记录数'"(限制容量)
}

函数总结

  1. 获取ACL主结构体:获取ACL插件的全局状态
  2. 设置最大会话数:更新最大会话数配置

注意事项

  • 每个Worker线程:最大会话数是每个Worker线程的限制,不是全局限制
  • 总容量 :如果有N个Worker线程,总容量 = N × fa_conn_table_max_entries
  • 内存计算 :每个会话128字节,总内存 = N × fa_conn_table_max_entries × 128字节

类比:就像"设置数据库的最大记录数":

  • 每个通道:最大记录数是每个"安检通道"的限制
  • 总容量:如果有N个"安检通道",总容量 = N × "最大记录数"
  • 内存计算:每个记录128字节,总内存 = N × "最大记录数" × 128字节

12.8.2 容量参数配置

容量参数可以通过CLI命令配置:

c 复制代码
//2613:2657:src/plugins/acl/acl.c
      if (unformat (input, "table"))
	{
	  /* The commands here are for tuning/testing. No user-serviceable parts inside */
                                         // 注释:这里的命令用于调优/测试。内部没有用户可维修的部分。
	  
	  if (unformat (input, "max-entries"))
	    {
	      if (!unformat (input, "%u", &val))  // 解析最大会话数参数
		{
		  error = clib_error_return (0,
					     "expecting maximum number of entries, got `%U`",
					     format_unformat_error, input);
		  goto done;
		}
	      else
		{
		  acl_set_session_max_entries (val);  // 设置最大会话数
                                         // acl_set_session_max_entries:设置最大会话数(我们上面已经详细讲解过)
                                         // 类比:就像"设置数据库的'最大记录数'"
		  goto done;
		}
	    }
	  
	  if (unformat (input, "hash-table-buckets"))
	    {
	      if (!unformat (input, "%u", &val))  // 解析Hash表桶数量参数
		{
		  error = clib_error_return (0,
					     "expecting maximum number of hash table buckets, got `%U`",
					     format_unformat_error, input);
		  goto done;
		}
	      else
		{
		  am->fa_conn_table_hash_num_buckets = val;  // 设置Hash表桶数量
                                         // fa_conn_table_hash_num_buckets:Hash表桶数量
                                         // 为什么需要?Hash表的桶数量影响Hash冲突率(桶越多,冲突越少)
                                         // 类比:就像"设置数据库索引的'桶数量'"
		  goto done;
		}
	    }
	  
	  if (unformat (input, "hash-table-memory"))
	    {
	      if (!unformat (input, "%U", unformat_memory_size, &memory_size))  // 解析Hash表内存大小参数
		{
		  error = clib_error_return (0,
					     "expecting maximum amount of hash table memory, got `%U`",
					     format_unformat_error, input);
		  goto done;
		}
	      else
		{
		  am->fa_conn_table_hash_memory_size = memory_size;  // 设置Hash表内存大小
                                         // fa_conn_table_hash_memory_size:Hash表内存大小
                                         // 为什么需要?Hash表需要预先分配内存(避免动态分配)
                                         // 类比:就像"设置数据库索引的'内存大小'"
		  goto done;
		}
	    }

CLI命令示例

bash 复制代码
# 设置最大会话数为1,000,000
set acl plugin session table max-entries 1000000

# 设置Hash表桶数量为128K
set acl plugin session table hash-table-buckets 131072

# 设置Hash表内存大小为2GB
set acl plugin session table hash-table-memory 2G

类比:就像"配置数据库容量":

  • 设置最大记录数set database max-entries 1000000
  • 设置索引桶数量set database index-buckets 131072
  • 设置索引内存大小set database index-memory 2G

12.9 会话表性能优化:如何"优化数据库性能"?

12.9.1 缓存行对齐优化

会话结构体(fa_session_t)设计为128字节,正好是2个缓存行(64字节 × 2):

c 复制代码
//157:158:src/plugins/acl/fa_node.h
/* Let's try to fit within two cachelines */
CT_ASSERT_EQUAL(fa_session_t_size_is_128, sizeof(fa_session_t), 128);  // 编译时断言:会话结构体大小为128字节
                                         // CT_ASSERT_EQUAL:编译时断言(如果条件不满足,编译失败)
                                         // 为什么需要128字节?正好是2个缓存行(现代CPU的缓存行大小通常是64字节)
                                         // 为什么需要缓存行对齐?减少缓存未命中,提高访问速度
                                         // 类比:就像"档案夹大小正好是2个'存储单元'"(减少存储访问次数)

缓存行对齐的优势

  1. 减少缓存未命中:结构体对齐到缓存行,减少跨缓存行访问
  2. 提高访问速度:CPU可以一次性加载整个缓存行
  3. 减少伪共享:不同线程的会话不会共享同一个缓存行

类比:就像"档案夹大小优化":

  • 对齐到存储单元:档案夹大小正好是2个"存储单元"
  • 减少访问次数:一次可以加载整个"存储单元"
  • 避免冲突:不同通道的档案不会共享同一个"存储单元"

12.9.2 预取优化

在查找会话之前,预取Hash表的bucket和数据:

c 复制代码
//615:631:src/plugins/acl/session_inlines.h
always_inline void
acl_fa_prefetch_session_bucket_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
  if (is_ip6)                                    // 如果是IPv6
    {
      clib_bihash_prefetch_bucket_40_8 (&am->fa_ip6_sessions_hash, hash);  // 预取IPv6会话Hash表的bucket
                                         // clib_bihash_prefetch_bucket_40_8:预取40_8 Hash表的bucket
                                         // 作用:提前加载Hash表的bucket到CPU缓存(减少延迟)
                                         // 类比:就像"提前加载'数据库索引页'到'内存缓存'"
    }
  else                                          // 如果是IPv4
    {
      clib_bihash_prefetch_bucket_16_8 (&am->fa_ip4_sessions_hash, hash);  // 预取IPv4会话Hash表的bucket
                                         // 类比:就像"提前加载'数据库索引页'到'内存缓存'"
    }
}

always_inline void
acl_fa_prefetch_session_data_for_hash (acl_main_t * am, int is_ip6, u64 hash)
{
  if (is_ip6)                                    // 如果是IPv6
    {
      clib_bihash_prefetch_data_40_8 (&am->fa_ip6_sessions_hash, hash);  // 预取IPv6会话Hash表的数据
                                         // clib_bihash_prefetch_data_40_8:预取40_8 Hash表的数据
                                         // 作用:提前加载Hash表的数据到CPU缓存(减少延迟)
                                         // 类比:就像"提前加载'数据库记录'到'内存缓存'"
    }
  else                                          // 如果是IPv4
    {
      clib_bihash_prefetch_data_16_8 (&am->fa_ip4_sessions_hash, hash);  // 预取IPv4会话Hash表的数据
                                         // 类比:就像"提前加载'数据库记录'到'内存缓存'"
    }
}

预取优化策略

  1. Bucket预取:在计算Hash值后,立即预取Hash表的bucket
  2. 数据预取:在查找bucket后,立即预取Hash表的数据
  3. 流水线处理:在处理当前数据包时,预取下一个数据包的Hash表数据

类比:就像"数据库查询优化":

  • 索引页预取:在计算索引后,立即预取索引页
  • 数据页预取:在查找索引后,立即预取数据页
  • 流水线查询:在处理当前查询时,预取下一个查询的数据

12.9.3 批量处理优化

数据平面节点使用批量处理来提高性能:

c 复制代码
//229:234:src/plugins/acl/fa_node.h
   /*
    * work in progress data for the pipelined node operation
    */
                                         // 注释:流水线节点操作的处理中数据
  
  vlib_buffer_t *bufs[VLIB_FRAME_SIZE];             // 数据包缓冲区数组
                                         // bufs:数据包缓冲区数组(用于批量处理)
                                         // VLIB_FRAME_SIZE:帧大小(通常256)
                                         // 为什么需要?用于批量处理数据包(提高性能)
                                         // 类比:就像"数据包缓冲区数组"(用于批量处理)
  
  u32 sw_if_indices[VLIB_FRAME_SIZE];               // 接口索引数组
                                         // sw_if_indices:接口索引数组(用于批量处理)
                                         // 类比:就像"通道索引数组"(用于批量处理)
  
  fa_5tuple_t fa_5tuples[VLIB_FRAME_SIZE];         // 5-tuple数组
                                         // fa_5tuples:5-tuple数组(用于批量处理)
                                         // 类比:就像"旅客信息数组"(用于批量处理)
  
  u64 hashes[VLIB_FRAME_SIZE];                      // Hash值数组
                                         // hashes:Hash值数组(用于批量处理)
                                         // 类比:就像"Hash值数组"(用于批量处理)
  
  u16 nexts[VLIB_FRAME_SIZE];                      // 下一个节点索引数组
                                         // nexts:下一个节点索引数组(用于批量处理)
                                         // 类比:就像"下一个节点索引数组"(用于批量处理)

批量处理流程

  1. 批量提取:批量提取多个数据包的5-tuple
  2. 批量Hash:批量计算多个数据包的Hash值
  3. 批量预取:批量预取多个数据包的Hash表数据
  4. 批量查找:批量查找多个数据包的会话

性能提升

  • 减少函数调用开销:批量处理减少函数调用次数
  • 提高缓存利用率:批量处理提高CPU缓存利用率
  • 向量化优化:批量处理可以使用SIMD指令优化

类比:就像"批量处理旅客":

  • 批量读取:批量读取多个旅客的身份证号
  • 批量查找:批量在数据库中查找多个旅客
  • 批量处理:批量处理多个旅客的安检

12.10 会话表监控和调试:如何"监控和调试数据库"?

12.10.1 会话表显示函数:acl_plugin_show_sessions

acl_plugin_show_sessions 用于显示会话表的详细状态信息(我们已经在12.6.1节详细讲解过,这里不再重复)。

12.10.2 Hash表显示函数:show_fa_sessions_hash

show_fa_sessions_hash 用于显示会话Hash表的详细信息(我们已经在12.6.2节详细讲解过,这里不再重复)。


12.11 会话表CLI命令:如何"使用命令行管理数据库"?

12.11.1 会话表显示命令:show acl-plugin sessions

show acl-plugin sessions 命令用于显示会话表的详细状态(我们已经在12.9.1节详细讲解过,这里不再重复)。

12.11.2 会话表配置命令:set acl plugin session

set acl plugin session 命令用于配置会话表参数(我们已经在12.9.2节详细讲解过,这里不再重复)。


12.12 会话表初始化时机:何时"建立数据库"?

12.12.1 延迟初始化机制

会话表采用延迟初始化机制,只有在需要时才初始化:

c 复制代码
//84:121:src/plugins/acl/sess_mgmt_node.c
static void
acl_fa_verify_init_sessions (acl_main_t * am)
{
  if (!am->fa_sessions_hash_is_initialized)      // 如果会话Hash表未初始化
    {
      // ... 初始化代码(我们已经在12.2节详细讲解过)...
    }
}

延迟初始化的原因

  1. 节省内存:如果不需要Flow-aware ACL,不初始化会话表(节省内存)
  2. 按需分配:只有在第一次使用Flow-aware ACL时才初始化
  3. 性能优化:避免不必要的初始化开销

初始化时机

  • 第一次启用Flow-aware ACL:当第一次在接口上启用Flow-aware ACL时
  • 第一次创建会话:当第一次创建会话时(如果Hash表未初始化)

类比:就像"按需建立数据库":

  • 节省资源:如果不需要"常客系统",不建立"数据库"(节省资源)
  • 按需分配:只有在第一次使用"常客系统"时才建立"数据库"
  • 性能优化:避免不必要的建立开销

12.13 会话表容量限制:如何"防止数据库溢出"?

12.13.1 会话创建容量检查:acl_fa_can_add_session

在创建新会话之前,需要检查是否可以创建(是否超过最大会话数):

c 复制代码
//465:475:src/plugins/acl/session_inlines.h
always_inline int
acl_fa_can_add_session (acl_main_t * am, int is_input, u32 sw_if_index)
{
  u64 curr_sess_count;                            // 当前会话数量(临时变量)
                                         // 类比:就像"当前常客数量"
  
  curr_sess_count = am->fa_session_total_adds - am->fa_session_total_dels;  // 计算当前会话数量
                                         // fa_session_total_adds:全局会话添加计数
                                         // fa_session_total_dels:全局会话删除计数
                                         // 当前会话数量 = 添加计数 - 删除计数
                                         // 为什么使用全局计数?简化计算,避免遍历所有Worker线程
                                         // 类比:就像"计算'当前常客数量' = '添加计数' - '删除计数'"
  
  return (curr_sess_count < am->fa_conn_table_max_entries);  // 检查是否小于最大会话数
                                         // fa_conn_table_max_entries:最大会话数(每个Worker线程)
                                         // 为什么使用全局计数与每个Worker的限制比较?简化实现,实际限制是每个Worker的限制
                                         // 类比:就像"检查'当前常客数量'是否小于'最大常客数'"
}

函数总结

  1. 计算当前会话数:当前会话数 = 添加计数 - 删除计数
  2. 检查限制:检查当前会话数是否小于最大会话数

容量限制说明

  • 全局计数 vs 每个Worker限制:使用全局计数与每个Worker的限制比较,简化实现
  • 实际限制:实际限制是每个Worker线程的限制,不是全局限制
  • 总容量 :如果有N个Worker线程,总容量 = N × fa_conn_table_max_entries

类比:就像"检查数据库容量":

  • 计算当前记录数:当前记录数 = 添加计数 - 删除计数
  • 检查限制:检查当前记录数是否小于最大记录数

12.13.2 会话回收机制:acl_fa_try_recycle_session

如果当前会话数已达到上限,需要尝试回收一些会话(删除最老的会话):

c 复制代码
//475:511:src/plugins/acl/session_inlines.h
always_inline int
acl_fa_try_recycle_session (acl_main_t * am,
			    int is_input, u16 thread_index,
			    u32 sw_if_index, u64 now)
{
  acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];  // 获取线程的Per-worker数据
                                         // 类比:就像"获取'安检通道'的'数据'"
  
  fa_full_session_id_t sess_id;                 // 会话ID(临时变量)
  sess_id.thread_index = thread_index;         // 设置会话ID的线程索引

  // ========== 第一部分:尝试从Purgatory链表中回收 ==========
  
  sess_id.session_index = pw->fa_conn_list_head[ACL_TIMEOUT_PURGATORY];  // 获取Purgatory链表头节点索引
                                         // ACL_TIMEOUT_PURGATORY:Purgatory链表ID(待删除会话)
                                         // 类比:就像"获取'待删除列表'的'列表头'"
  
  while ((FA_SESSION_BOGUS_INDEX != sess_id.session_index)  // 如果链表不为空
	 && (pw->fa_conn_list_head[ACL_TIMEOUT_PURGATORY] != FA_SESSION_BOGUS_INDEX))  // 且链表头节点有效
    {
      fa_session_t *sess =                      // 获取会话结构体指针
	get_session_ptr (am, thread_index, sess_id.session_index);  // 从pool中获取会话指针
                                         // 类比:就像"获取'待删除常客'的'档案夹'"
      
      if (sess->link_enqueue_time + fa_session_get_timeout (am, sess) < now)  // 如果会话已过期
	{
	  acl_fa_conn_list_delete_session (am, sess_id, now);  // 从链表中删除会话
	  acl_fa_put_session (am, sw_if_index, sess_id);  // 释放会话内存
                                         // acl_fa_put_session:释放会话内存(我们第9章已经详细讲解过)
                                         // 类比:就像"如果'待删除常客'已过期,删除并释放'档案夹'"
	}
      
      sess_id.session_index = pw->fa_conn_list_head[ACL_TIMEOUT_PURGATORY];  // 获取下一个节点索引
                                         // 类比:就像"获取'下一个待删除常客'"
    }

  // ========== 第二部分:尝试从TCP Transient链表中回收 ==========
  
  sess_id.session_index = pw->fa_conn_list_head[ACL_TIMEOUT_TCP_TRANSIENT];  // 获取TCP Transient链表头节点索引
                                         // ACL_TIMEOUT_TCP_TRANSIENT:TCP Transient链表ID(未建立连接的TCP会话)
                                         // 类比:就像"获取'TCP未建立连接列表'的'列表头'"
  
  if (FA_SESSION_BOGUS_INDEX != sess_id.session_index)  // 如果链表不为空
    {
      acl_fa_conn_list_delete_session (am, sess_id, now);  // 从链表中删除会话
      acl_fa_deactivate_session (am, sw_if_index, sess_id);  // 停用会话(从Hash表移除)
      acl_fa_conn_list_add_session (am, sess_id, now);  // 添加到Purgatory链表
                                         // 为什么需要?TCP Transient会话优先级较低,可以回收
                                         // 类比:就像"如果'TCP未建立连接常客'存在,删除并添加到'待删除列表'"
    }
  
  return 1;                                      // 返回成功
                                         // 类比:就像"返回'成功'"
}

函数总结

  1. 回收Purgatory会话:尝试从Purgatory链表中回收已过期的会话
  2. 回收TCP Transient会话:如果Purgatory链表为空,尝试回收TCP Transient会话(优先级较低)

回收策略

  • 优先级1:Purgatory会话(已标记为删除,可以立即回收)
  • 优先级2:TCP Transient会话(未建立连接的TCP会话,优先级较低)

类比:就像"回收常客档案":

  • 优先级1:待删除常客(已标记为删除,可以立即回收)
  • 优先级2:未建立连接常客(优先级较低,可以回收)

12.14 会话表性能调优:如何"优化数据库性能"?

12.14.1 Hash表参数调优

Hash表的性能主要受以下参数影响:

  1. Hash表桶数量fa_conn_table_hash_num_buckets):

    • 默认值:64K
    • 影响:桶越多,Hash冲突越少,查找性能越好,但内存占用越大
    • 调优建议
      • 低冲突率:如果Hash冲突少,可以减少桶数量(节省内存)
      • 高冲突率:如果Hash冲突多,可以增加桶数量(提高性能)
  2. Hash表内存大小fa_conn_table_hash_memory_size):

    • 默认值:1GB
    • 影响:内存越大,可以存储更多会话,但内存占用越大
    • 调优建议
      • 高流量场景:如果流量大,可以增加内存大小(支持更多会话)
      • 低流量场景:如果流量小,可以减少内存大小(节省内存)
  3. 最大会话数fa_conn_table_max_entries):

    • 默认值:500,000(每个Worker线程)
    • 影响:限制会话表的最大容量
    • 调优建议
      • 高连接数场景:如果连接数多,可以增加最大会话数
      • 低连接数场景:如果连接数少,可以减少最大会话数(节省内存)

调优示例

bash 复制代码
# 高流量场景:增加Hash表桶数量和内存大小
set acl plugin session table hash-table-buckets 131072  # 128K桶
set acl plugin session table hash-table-memory 2G       # 2GB内存
set acl plugin session table max-entries 1000000        # 1M会话(每个Worker)

# 低流量场景:减少Hash表桶数量和内存大小
set acl plugin session table hash-table-buckets 32768   # 32K桶
set acl plugin session table hash-table-memory 512M     # 512MB内存
set acl plugin session table max-entries 100000        # 100K会话(每个Worker)

类比:就像"数据库性能调优":

  • 高流量场景:增加索引桶数量和内存大小,增加最大记录数
  • 低流量场景:减少索引桶数量和内存大小,减少最大记录数

12.14.2 清理进程参数调优

清理进程的性能主要受以下参数影响:

  1. 最大删除会话数fa_max_deleted_sessions_per_interval):

    • 默认值:100
    • 影响:限制每次清理的会话数,避免一次性清理太多(影响性能)
    • 调优建议
      • 高流量场景:如果流量大,可以增加最大删除数(加快清理速度)
      • 低流量场景:如果流量小,可以减少最大删除数(减少CPU占用)
  2. 最小删除会话数fa_min_deleted_sessions_per_interval):

    • 默认值:1
    • 影响:如果删除的会话数少于最小值,增加等待时间(减少清理频率)
    • 调优建议
      • 高流量场景:可以增加最小值(保持较高的清理频率)
      • 低流量场景:可以减少最小值(降低清理频率)
  3. 等待时间增量fa_cleaner_wait_time_increment):

    • 默认值:0.1秒
    • 影响:用于自适应调整清理频率
    • 调优建议
      • 高流量场景:可以减少增量(更快响应负载变化)
      • 低流量场景:可以增加增量(更慢响应负载变化)

调优示例

bash 复制代码
# 高流量场景:增加最大删除数,减少等待时间增量
set acl plugin session table max-deleted-per-interval 200
set acl plugin session table wait-time-increment 0.05

# 低流量场景:减少最大删除数,增加等待时间增量
set acl plugin session table max-deleted-per-interval 50
set acl plugin session table wait-time-increment 0.2

类比:就像"清理进程性能调优":

  • 高流量场景:增加最大删除数,减少等待时间增量
  • 低流量场景:减少最大删除数,增加等待时间增量

12.15 会话表监控指标:如何"监控数据库健康状态"?

12.15.1 关键监控指标

会话表的关键监控指标包括:

  1. 会话总数fa_session_total_adds - fa_session_total_dels

    • 含义:当前活跃的会话总数
    • 监控:如果接近最大会话数,需要关注容量问题
    • 类比:就像"当前活跃的常客总数"(如果接近最大常客数,需要关注容量问题)
  2. 会话添加速率fa_session_total_adds 的变化速率

    • 含义:每秒添加的会话数
    • 监控:如果添加速率过高,可能需要增加容量
    • 类比:就像"每秒添加的常客数"(如果添加速率过高,可能需要增加容量)
  3. 会话删除速率fa_session_total_dels 的变化速率

    • 含义:每秒删除的会话数
    • 监控:如果删除速率过低,可能需要检查清理进程
    • 类比:就像"每秒删除的常客数"(如果删除速率过低,可能需要检查清理进程)
  4. Hash冲突率 :通过 show acl-plugin sessions 查看

    • 含义:Hash冲突的比率
    • 监控:如果冲突率过高,可能需要增加Hash表桶数量
    • 类比:就像"索引冲突率"(如果冲突率过高,可能需要增加索引桶数量)
  5. 清理进程性能 :通过 show acl-plugin sessions 查看清理进程计数器

    • 含义:清理进程的各种计数器
    • 监控:如果清理进程性能不佳,可能需要调整清理参数
    • 类比:就像"清理进程性能"(如果清理进程性能不佳,可能需要调整清理参数)

监控命令

bash 复制代码
# 显示会话表状态(包括所有监控指标)
show acl-plugin sessions

# 显示Hash表详细信息(包括冲突率)
show acl-plugin sessions verbose

类比:就像"数据库监控指标":

  • 记录总数:当前活跃的记录总数
  • 添加速率:每秒添加的记录数
  • 删除速率:每秒删除的记录数
  • 索引冲突率:索引冲突的比率
  • 清理进程性能:清理进程的性能指标

12.16 本章小结

通过这一章的详细讲解,我们了解了:

  1. 会话表管理的概念:什么是会话表管理,为什么需要它
  2. 会话表初始化:如何初始化会话表系统(Per-worker Pool和Hash表)
  3. Per-worker数据结构:每个Worker线程的完整数据结构(逐字段注释)
  4. 会话表容量管理:如何管理会话表的容量(最大会话数、Hash表参数等)
  5. 会话表性能优化:如何优化会话表性能(缓存行对齐、预取、批量处理等)
  6. 会话表监控和调试:如何监控和调试会话表(显示函数、CLI命令等)
  7. 会话表清理机制:如何定期清理过期会话(自适应调度、清理参数等)
  8. 会话表容量限制:如何防止会话表溢出(容量检查、会话回收等)
  9. 会话表性能调优:如何调优会话表性能(Hash表参数、清理进程参数等)
  10. 会话表监控指标:如何监控会话表健康状态(关键指标、监控命令等)

核心要点

  • 会话表采用Per-worker架构,每个Worker线程有独立的会话Pool和超时链表
  • 延迟初始化机制,只有在需要时才初始化会话表(节省内存)
  • 自适应清理调度,根据负载动态调整清理频率
  • 容量管理通过最大会话数、Hash表参数等控制
  • 性能优化通过缓存行对齐、预取、批量处理等技术实现
  • 监控和调试通过CLI命令和显示函数实现

下一步:在下一章,我们会看到ACL插件的性能优化技术,包括批量处理、预取优化、流水线处理等。


第13章:ACL插件性能优化------如何"让安检更快更高效"?

生活类比:想象一下,你是一个"安检系统优化工程师",需要让安检系统处理更多旅客,同时保持高效。

  • 批量处理:一次处理多个旅客,而不是一个一个处理(提高吞吐量)
  • 预取优化:提前准备好下一个旅客的信息(减少等待时间)
  • 流水线处理:在处理当前旅客时,同时准备下一个旅客(提高效率)

这一章,我们就跟着ACL插件的代码,看看它是如何"让安检更快更高效"的。每一步我都会详细解释,确保你完全理解。


13.1 性能优化的概念:什么是"性能优化"?

13.1.1 什么是性能优化?

性能优化(Performance Optimization):通过各种技术手段,提高系统的处理速度和效率。

关键概念详解

  1. 批量处理(Vector Processing):一次处理多个数据包,而不是一个一个处理

    • 优势:减少函数调用开销,提高CPU缓存利用率
    • 类比:就像"一次检查多个旅客"(而不是一个一个检查)
  2. 预取优化(Prefetch Optimization):提前加载数据到CPU缓存

    • 优势:减少内存访问延迟,提高处理速度
    • 类比:就像"提前准备好下一个旅客的信息"(减少等待时间)
  3. 流水线处理(Pipeline Processing):在处理当前数据包时,同时准备下一个数据包

    • 优势:隐藏内存访问延迟,提高CPU利用率
    • 类比:就像"在处理当前旅客时,同时准备下一个旅客"(提高效率)
  4. 缓存优化(Cache Optimization):优化数据结构布局,减少缓存未命中

    • 优势:减少内存访问次数,提高访问速度
    • 类比:就像"优化档案柜布局"(减少查找时间)
  5. 分支预测优化(Branch Prediction Optimization) :使用PREDICT_FALSEPREDICT_TRUE提示编译器

    • 优势:帮助CPU更好地预测分支,减少流水线停顿
    • 类比:就像"提前告诉CPU哪个分支更可能发生"(减少预测错误)
13.1.2 性能优化的目标

主要目标

  1. 提高吞吐量:每秒处理更多的数据包
  2. 降低延迟:减少单个数据包的处理时间
  3. 提高CPU利用率:减少CPU空闲时间
  4. 减少内存访问:优化内存访问模式

性能指标

  • 吞吐量(Throughput):每秒处理的数据包数(PPS - Packets Per Second)
  • 延迟(Latency):单个数据包的处理时间(微秒)
  • CPU利用率(CPU Utilization):CPU使用率(百分比)

类比:就像"安检系统性能指标":

  • 吞吐量:每秒检查的旅客数(人/秒)
  • 延迟:单个旅客的检查时间(秒)
  • CPU利用率:安检通道的使用率(百分比)

13.2 批量处理优化:如何"一次处理多个数据包"?

13.2.1 批量处理的概念

批量处理(Vector Processing):一次处理多个数据包,而不是一个一个处理。

为什么需要批量处理?

  1. 减少函数调用开销:批量处理减少函数调用次数
  2. 提高缓存利用率:批量处理提高CPU缓存利用率
  3. 向量化优化:批量处理可以使用SIMD指令优化

类比:就像"批量检查旅客":

  • 减少调用开销:一次检查多个旅客,减少"检查函数"调用次数
  • 提高效率:批量处理提高"安检通道"的利用率
  • 向量化优化:可以使用"并行检查"技术

13.2.2 批量处理参数定义

ACL插件定义了批量处理的参数:

c 复制代码
//228:229:src/plugins/acl/dataplane_node.c
#define ACL_PLUGIN_VECTOR_SIZE 4                 // 批量处理大小:4个数据包
                                         // ACL_PLUGIN_VECTOR_SIZE:批量处理的大小(一次处理4个数据包)
                                         // 为什么是4?平衡性能和代码复杂度(太小效果不明显,太大代码复杂)
                                         // 类比:就像"一次检查4个旅客"(平衡效率和复杂度)
#define ACL_PLUGIN_PREFETCH_GAP 3                // 预取间隔:3个批量大小
                                         // ACL_PLUGIN_PREFETCH_GAP:预取间隔(提前3个批量大小预取)
                                         // 为什么是3?平衡预取效果和内存占用(太小预取效果不明显,太大占用内存)
                                         // 类比:就像"提前3个批量准备旅客信息"(平衡预取效果和内存占用)

参数说明

  • ACL_PLUGIN_VECTOR_SIZE:批量处理大小(4个数据包)
  • ACL_PLUGIN_PREFETCH_GAP:预取间隔(3个批量大小 = 12个数据包)

类比:就像"批量检查参数":

  • 批量大小:一次检查4个旅客
  • 预取间隔:提前12个旅客准备信息

13.2.3 批量处理准备函数:acl_fa_node_common_prepare_fn

acl_fa_node_common_prepare_fn 用于批量提取数据包信息(5-tuple、Hash值等):

c 复制代码
//231:317:src/plugins/acl/dataplane_node.c
always_inline void
acl_fa_node_common_prepare_fn (vlib_main_t * vm,
			       vlib_node_runtime_t * node,
			       vlib_frame_t * frame, int is_ip6, int is_input,
			       int is_l2_path, int with_stateful_datapath)
	/* , int node_trace_on,
	   int reclassify_sessions) */
{
  u32 n_left, *from;                              // 剩余数据包数、数据包索引数组指针(临时变量)
                                         // n_left:剩余需要处理的数据包数
                                         // from:数据包索引数组指针(从frame中获取)
                                         // 类比:就像"剩余旅客数"和"旅客索引数组"
  
  acl_main_t *am = &acl_main;                     // 获取ACL插件主结构体
                                         // 类比:就像"获取'安检服务提供商'的'总部'"
  
  uword thread_index = os_get_thread_index ();    // 获取当前线程索引
                                         // os_get_thread_index:获取当前线程索引
                                         // 为什么需要?每个线程有独立的Per-worker数据
                                         // 类比:就像"获取'安检通道编号'"
  
  acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];  // 获取线程的Per-worker数据
                                         // per_worker_data:Per-worker数据数组(每个线程一个)
                                         // 类比:就像"获取'安检通道'的'数据'"

  vlib_buffer_t **b;                               // 数据包缓冲区数组指针(临时变量)
                                         // 类比:就像"旅客信息数组指针"
  
  u32 *sw_if_index;                               // 接口索引数组指针(临时变量)
                                         // 类比:就像"通道索引数组指针"
  
  fa_5tuple_t *fa_5tuple;                         // 5-tuple数组指针(临时变量)
                                         // 类比:就像"旅客信息数组指针"
  
  u64 *hash;                                      // Hash值数组指针(临时变量)
                                         // 类比:就像"Hash值数组指针"

  // ========== 第一部分:获取数据包缓冲区 ==========
  
  from = vlib_frame_vector_args (frame);          // 获取数据包索引数组
                                         // vlib_frame_vector_args:获取frame中的数据包索引数组
                                         // frame:VPP的数据包帧(包含多个数据包的索引)
                                         // 类比:就像"获取'旅客索引数组'"
  
  vlib_get_buffers (vm, from, pw->bufs, frame->n_vectors);  // 获取数据包缓冲区数组
                                         // vlib_get_buffers:根据索引数组获取数据包缓冲区数组
                                         // pw->bufs:Per-worker数据包缓冲区数组(用于批量处理)
                                         // frame->n_vectors:frame中的数据包数量
                                         // 为什么需要?将数据包索引转换为数据包缓冲区指针
                                         // 类比:就像"根据'旅客索引数组'获取'旅客信息数组'"

  // ========== 第二部分:设置数组指针 ==========
  
  /* set the initial values for the current buffer the next pointers */
                                         // 注释:设置当前缓冲区的初始值和下一个指针
  
  b = pw->bufs;                                    // 设置数据包缓冲区数组指针
                                         // 类比:就像"设置'旅客信息数组'指针"
  
  sw_if_index = pw->sw_if_indices;                 // 设置接口索引数组指针
                                         // 类比:就像"设置'通道索引数组'指针"
  
  fa_5tuple = pw->fa_5tuples;                      // 设置5-tuple数组指针
                                         // 类比:就像"设置'旅客信息数组'指针"
  
  hash = pw->hashes;                               // 设置Hash值数组指针
                                         // 类比:就像"设置'Hash值数组'指针"

  // ========== 第三部分:批量处理循环(带预取) ==========
  
  /*
   * fill the sw_if_index, 5tuple and session hash,
   * First in strides of size ACL_PLUGIN_VECTOR_SIZE,
   * with buffer prefetch being
   * ACL_PLUGIN_PREFETCH_GAP * ACL_PLUGIN_VECTOR_SIZE entries
   * in front. Then with a simple single loop.
   */
                                         // 注释:填充接口索引、5-tuple和会话Hash值
                                         // 首先以ACL_PLUGIN_VECTOR_SIZE为步长批量处理
                                         // 预取间隔为ACL_PLUGIN_PREFETCH_GAP * ACL_PLUGIN_VECTOR_SIZE个条目
                                         // 然后使用简单的单循环处理剩余数据包

  n_left = frame->n_vectors;                       // 初始化剩余数据包数
                                         // 类比:就像"初始化剩余旅客数"
  
  while (n_left >= (ACL_PLUGIN_PREFETCH_GAP + 1) * ACL_PLUGIN_VECTOR_SIZE)  // 如果剩余数据包数足够进行批量处理(带预取)
    {
      const int vec_sz = ACL_PLUGIN_VECTOR_SIZE;   // 批量处理大小(4个数据包)
                                         // 类比:就像"批量处理大小(4个旅客)"
      
      // ========== 预取数据包缓冲区 ==========
      
      {
	int ii;
	for (ii = ACL_PLUGIN_PREFETCH_GAP * vec_sz;  // 从预取位置开始
	     ii < (ACL_PLUGIN_PREFETCH_GAP + 1) * vec_sz; ii++)  // 到下一个批量位置
	  {
	    clib_prefetch_load (b[ii]);              // 预取数据包缓冲区指针
                                         // clib_prefetch_load:预取数据到CPU缓存(用于指针)
                                         // 作用:提前加载数据包缓冲区指针到CPU缓存
                                         // 类比:就像"提前加载'旅客信息'指针到'内存缓存'"
	    
	    CLIB_PREFETCH (b[ii]->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD);  // 预取数据包数据
                                         // CLIB_PREFETCH:预取数据到CPU缓存(用于数据)
                                         // 参数:b[ii]->data(数据包数据指针)、2 * CLIB_CACHE_LINE_BYTES(预取大小,2个缓存行)、LOAD(加载操作)
                                         // 作用:提前加载数据包数据到CPU缓存(2个缓存行 = 128字节)
                                         // 为什么预取2个缓存行?数据包头部通常需要多个缓存行
                                         // 类比:就像"提前加载'旅客信息'数据到'内存缓存'(2个存储单元)"
	  }
      }

      // ========== 批量提取接口索引、5-tuple和Hash值 ==========
      
      get_sw_if_index_xN (vec_sz, is_input, b, sw_if_index);  // 批量提取接口索引
                                         // get_sw_if_index_xN:批量提取接口索引(N个数据包)
                                         // 参数:vec_sz(批量大小)、is_input(是否输入方向)、b(数据包缓冲区数组)、sw_if_index(输出接口索引数组)
                                         // 作用:批量提取N个数据包的接口索引
                                         // 类比:就像"批量提取N个旅客的'通道编号'"
      
      fill_5tuple_xN (vec_sz, am, is_ip6, is_input, is_l2_path, &b[0],
		      &sw_if_index[0], &fa_5tuple[0]);  // 批量提取5-tuple
                                         // fill_5tuple_xN:批量提取5-tuple(N个数据包)
                                         // 参数:vec_sz(批量大小)、am(ACL主结构体)、is_ip6(是否IPv6)、is_input(是否输入方向)、is_l2_path(是否L2路径)、b(数据包缓冲区数组)、sw_if_index(接口索引数组)、fa_5tuple(输出5-tuple数组)
                                         // 作用:批量提取N个数据包的5-tuple(源IP、目的IP、协议、源端口、目的端口)
                                         // 类比:就像"批量提取N个旅客的'基本信息'"
      
      if (with_stateful_datapath)                  // 如果启用有状态数据平面
	{
	  make_session_hash_xN (vec_sz, am, is_ip6, &sw_if_index[0],
			      &fa_5tuple[0], &hash[0]);  // 批量计算会话Hash值
                                         // make_session_hash_xN:批量计算会话Hash值(N个数据包)
                                         // 参数:vec_sz(批量大小)、am(ACL主结构体)、is_ip6(是否IPv6)、sw_if_index(接口索引数组)、fa_5tuple(5-tuple数组)、hash(输出Hash值数组)
                                         // 作用:批量计算N个数据包的会话Hash值(用于Hash表查找)
                                         // 类比:就像"批量计算N个旅客的'Hash值'(用于数据库查找)"
	}

      // ========== 更新指针和剩余数量 ==========
      
      n_left -= vec_sz;                            // 减少剩余数据包数
                                         // 类比:就像"减少剩余旅客数"
      
      fa_5tuple += vec_sz;                         // 移动5-tuple数组指针
                                         // 类比:就像"移动'旅客信息数组'指针"
      
      b += vec_sz;                                 // 移动数据包缓冲区数组指针
                                         // 类比:就像"移动'旅客信息数组'指针"
      
      sw_if_index += vec_sz;                       // 移动接口索引数组指针
                                         // 类比:就像"移动'通道索引数组'指针"
      
      hash += vec_sz;                              // 移动Hash值数组指针
                                         // 类比:就像"移动'Hash值数组'指针"
    }

  // ========== 第四部分:单循环处理剩余数据包 ==========
  
  while (n_left > 0)                               // 如果还有剩余数据包
    {
      const int vec_sz = 1;                        // 单数据包处理
                                         // 类比:就像"单旅客处理"

      get_sw_if_index_xN (vec_sz, is_input, b, sw_if_index);  // 提取接口索引
                                         // 类比:就像"提取'通道编号'"
      
      fill_5tuple_xN (vec_sz, am, is_ip6, is_input, is_l2_path, &b[0],
		      &sw_if_index[0], &fa_5tuple[0]);  // 提取5-tuple
                                         // 类比:就像"提取'旅客基本信息'"
      
      if (with_stateful_datapath)                  // 如果启用有状态数据平面
	{
	  make_session_hash_xN (vec_sz, am, is_ip6, &sw_if_index[0],
			      &fa_5tuple[0], &hash[0]);  // 计算会话Hash值
                                         // 类比:就像"计算'Hash值'"
	}

      n_left -= vec_sz;                            // 减少剩余数据包数
                                         // 类比:就像"减少剩余旅客数"

      fa_5tuple += vec_sz;                         // 移动5-tuple数组指针
                                         // 类比:就像"移动'旅客信息数组'指针"
      
      b += vec_sz;                                 // 移动数据包缓冲区数组指针
                                         // 类比:就像"移动'旅客信息数组'指针"
      
      sw_if_index += vec_sz;                       // 移动接口索引数组指针
                                         // 类比:就像"移动'通道索引数组'指针"
      
      hash += vec_sz;                              // 移动Hash值数组指针
                                         // 类比:就像"移动'Hash值数组'指针"
    }
}

函数总结

这个函数完成了批量提取数据包信息的工作:

  1. 获取数据包缓冲区:从frame中获取数据包缓冲区数组
  2. 设置数组指针:设置各种数组指针(数据包、接口索引、5-tuple、Hash值)
  3. 批量处理循环
    • 预取数据包:提前预取下一个批量的数据包缓冲区
    • 批量提取:批量提取接口索引、5-tuple和Hash值
    • 更新指针:移动数组指针,准备处理下一批
  4. 单循环处理剩余:处理剩余的数据包(不足一个批量)

批量处理流程

复制代码
数据包1-4: 提取信息 → 预取数据包13-16 → 处理数据包1-4
数据包5-8: 提取信息 → 预取数据包17-20 → 处理数据包5-8
...
剩余数据包: 逐个处理

类比:就像完整的"批量检查旅客"流程:

  1. 获取旅客信息:从"旅客队列"中获取"旅客信息数组"
  2. 设置数组指针:设置各种数组指针(旅客、通道、信息、Hash值)
  3. 批量检查循环
    • 预取旅客:提前预取下一个批量的旅客信息
    • 批量提取:批量提取通道编号、旅客信息和Hash值
    • 更新指针:移动数组指针,准备检查下一批
  4. 单循环处理剩余:处理剩余的旅客(不足一个批量)


13.3 预取优化:如何"提前准备好数据"?

13.3.1 预取优化的概念

预取优化(Prefetch Optimization):提前将数据加载到CPU缓存,减少内存访问延迟。

为什么需要预取?

  1. 内存访问延迟:内存访问比CPU缓存访问慢得多(通常慢100倍以上)
  2. 隐藏延迟:在处理当前数据时,提前加载下一个数据到缓存
  3. 提高性能:减少CPU等待内存访问的时间

类比:就像"提前准备旅客信息":

  • 内存访问延迟:从"档案柜"读取信息比从"桌面"读取慢得多
  • 隐藏延迟:在处理当前旅客时,提前从"档案柜"读取下一个旅客的信息
  • 提高性能:减少"安检员"等待"档案柜"的时间

13.3.2 数据包预取

在批量处理时,提前预取下一个批量的数据包:

c 复制代码
//274:281:src/plugins/acl/dataplane_node.c
      {
	int ii;
	for (ii = ACL_PLUGIN_PREFETCH_GAP * vec_sz;  // 从预取位置开始(12个数据包后)
	     ii < (ACL_PLUGIN_PREFETCH_GAP + 1) * vec_sz; ii++)  // 到下一个批量位置(16个数据包)
	  {
	    clib_prefetch_load (b[ii]);              // 预取数据包缓冲区指针
                                         // clib_prefetch_load:预取数据到CPU缓存(用于指针)
                                         // 作用:提前加载数据包缓冲区指针到CPU缓存
                                         // 为什么需要?数据包缓冲区指针可能不在缓存中,提前加载减少延迟
                                         // 类比:就像"提前加载'旅客信息'指针到'内存缓存'"
	    
	    CLIB_PREFETCH (b[ii]->data, 2 * CLIB_CACHE_LINE_BYTES, LOAD);  // 预取数据包数据
                                         // CLIB_PREFETCH:预取数据到CPU缓存(用于数据)
                                         // 参数:b[ii]->data(数据包数据指针)、2 * CLIB_CACHE_LINE_BYTES(预取大小,2个缓存行 = 128字节)、LOAD(加载操作)
                                         // 作用:提前加载数据包数据到CPU缓存(2个缓存行)
                                         // 为什么预取2个缓存行?数据包头部通常需要多个缓存行(IP头 + L4头)
                                         // 类比:就像"提前加载'旅客信息'数据到'内存缓存'(2个存储单元)"
	  }
      }

预取策略

  1. 预取位置:提前3个批量大小(12个数据包)预取
  2. 预取大小:2个缓存行(128字节)
  3. 预取内容:数据包缓冲区指针和数据包数据

预取时机

复制代码
处理数据包1-4: 预取数据包13-16
处理数据包5-8: 预取数据包17-20
处理数据包9-12: 预取数据包21-24
...

类比:就像"提前准备旅客信息":

  • 预取位置:提前12个旅客准备信息
  • 预取大小:2个存储单元(128字节)
  • 预取内容:旅客信息指针和旅客信息数据

13.3.3 Hash表预取

在查找会话时,提前预取Hash表的bucket和数据:

c 复制代码
//396:410:src/plugins/acl/dataplane_node.c
	    default:
	      acl_fa_prefetch_session_bucket_for_hash (am, is_ip6, hash[5]);  // 预取Hash表bucket(提前5个数据包)
                                         // acl_fa_prefetch_session_bucket_for_hash:预取会话Hash表的bucket
                                         // 参数:am(ACL主结构体)、is_ip6(是否IPv6)、hash[5](提前5个数据包的Hash值)
                                         // 作用:提前加载Hash表的bucket到CPU缓存
                                         // 为什么提前5个数据包?平衡预取效果和内存占用
                                         // 类比:就像"提前加载'数据库索引页'到'内存缓存'"
	      /* fallthrough */
	    case 5:
	    case 4:
	      acl_fa_prefetch_session_data_for_hash (am, is_ip6, hash[3]);  // 预取Hash表数据(提前3个数据包)
                                         // acl_fa_prefetch_session_data_for_hash:预取会话Hash表的数据
                                         // 参数:am(ACL主结构体)、is_ip6(是否IPv6)、hash[3](提前3个数据包的Hash值)
                                         // 作用:提前加载Hash表的数据到CPU缓存
                                         // 为什么提前3个数据包?在bucket预取之后,预取数据
                                         // 类比:就像"提前加载'数据库记录'到'内存缓存'"
	      /* fallthrough */
	    case 3:
	    case 2:
	      acl_fa_find_session_with_hash (am, is_ip6, sw_if_index[1],
					     hash[1], &fa_5tuple[1],
					     &f_sess_id_next.as_u64);  // 查找下一个会话(提前1个数据包)
                                         // acl_fa_find_session_with_hash:使用Hash值查找会话
                                         // 参数:am(ACL主结构体)、is_ip6(是否IPv6)、sw_if_index[1](下一个数据包的接口索引)、hash[1](下一个数据包的Hash值)、fa_5tuple[1](下一个数据包的5-tuple)、f_sess_id_next(输出会话ID)
                                         // 作用:查找下一个数据包的会话(用于流水线处理)
                                         // 为什么提前1个数据包?在处理当前数据包时,查找下一个数据包的会话
                                         // 类比:就像"查找下一个旅客的'常客信息'"
	      if (f_sess_id_next.as_u64 != ~0ULL)
		{
		  prefetch_session_entry (am, f_sess_id_next);  // 预取会话条目
                                         // prefetch_session_entry:预取会话条目
                                         // 参数:am(ACL主结构体)、f_sess_id_next(会话ID)
                                         // 作用:提前加载会话条目到CPU缓存
                                         // 为什么需要?会话条目可能不在缓存中,提前加载减少延迟
                                         // 类比:就像"提前加载'常客档案'到'内存缓存'"
		}

三级预取流水线

  1. 第一级:预取Hash表bucket(提前5个数据包)
  2. 第二级:预取Hash表数据(提前3个数据包)
  3. 第三级:查找会话并预取会话条目(提前1个数据包)

流水线示意图

复制代码
数据包0: 查找会话 → 处理会话
数据包1: 预取bucket[5] → 预取data[3] → 查找会话[1] → 预取会话[1] → 等待处理
数据包2: 预取bucket[6] → 预取data[4] → 查找会话[2] → 预取会话[2] → 等待处理
...

类比:就像"三级预取流水线":

  • 第一级:预取"数据库索引页"(提前5个旅客)
  • 第二级:预取"数据库记录"(提前3个旅客)
  • 第三级:查找"常客信息"并预取"常客档案"(提前1个旅客)

13.3.4 会话条目预取

在找到会话后,提前预取会话条目:

c 复制代码
//172:178:src/plugins/acl/dataplane_node.c
always_inline void
prefetch_session_entry (acl_main_t * am, fa_full_session_id_t f_sess_id)
{
  fa_session_t *sess = get_session_ptr_no_check (am, f_sess_id.thread_index,
						 f_sess_id.session_index);  // 获取会话指针
                                         // get_session_ptr_no_check:获取会话指针(不检查边界)
                                         // 参数:am(ACL主结构体)、f_sess_id.thread_index(线程索引)、f_sess_id.session_index(会话索引)
                                         // 作用:从Per-worker Pool中获取会话指针
                                         // 类比:就像"从'档案柜'中获取'常客档案'指针"
  
  CLIB_PREFETCH (sess, sizeof (*sess), STORE);  // 预取会话条目
                                         // CLIB_PREFETCH:预取数据到CPU缓存
                                         // 参数:sess(会话指针)、sizeof(*sess)(会话大小,128字节)、STORE(存储操作)
                                         // 作用:提前加载会话条目到CPU缓存(128字节 = 2个缓存行)
                                         // 为什么使用STORE?因为后续会修改会话(更新last_active_time等)
                                         // 类比:就像"提前加载'常客档案'到'内存缓存'(2个存储单元)"
}

预取策略

  1. 预取大小:会话结构体大小(128字节 = 2个缓存行)
  2. 预取类型:STORE(因为后续会修改会话)
  3. 预取时机:在找到会话后立即预取

类比:就像"预取常客档案":

  • 预取大小:常客档案大小(2个存储单元)
  • 预取类型:STORE(因为后续会更新访问记录)
  • 预取时机:在找到常客后立即预取

13.4 流水线处理:如何"同时处理多个数据包"?

13.4.1 流水线处理的概念

流水线处理(Pipeline Processing):在处理当前数据包时,同时准备下一个数据包。

为什么需要流水线?

  1. 隐藏延迟:在处理当前数据包时,提前准备下一个数据包(隐藏内存访问延迟)
  2. 提高CPU利用率:减少CPU空闲时间
  3. 提高吞吐量:提高每秒处理的数据包数

类比:就像"流水线安检":

  • 隐藏延迟:在处理当前旅客时,提前准备下一个旅客(隐藏查找档案的时间)
  • 提高效率:减少"安检员"空闲时间
  • 提高吞吐量:提高每秒检查的旅客数

13.4.2 三级预取流水线

ACL插件使用三级预取流水线来处理会话查找:

c 复制代码
//360:410:src/plugins/acl/dataplane_node.c
  /*
   * Now the "hard" work of session lookups and ACL lookups for new sessions.
   * Due to the complexity, do it for the time being in single loop with
   * a pipeline of three prefetches:
   *    1) bucket for the session bihash
   *    2) data for the session bihash
   *    3) worker session record
   */
                                         // 注释:现在是会话查找和ACL查找的"硬"工作
                                         // 由于复杂性,暂时使用单循环,带有三级预取流水线:
                                         // 1) 会话双向Hash表的bucket
                                         // 2) 会话双向Hash表的数据
                                         // 3) Worker会话记录

  fa_full_session_id_t f_sess_id_next = {.as_u64 = ~0ULL };  // 下一个会话ID(初始化为无效值)
                                         // f_sess_id_next:下一个数据包的会话ID(用于流水线处理)
                                         // 为什么需要?在处理当前数据包时,提前查找下一个数据包的会话
                                         // 类比:就像"下一个旅客的'常客ID'"

  /* find the "next" session so we can kickstart the pipeline */
                                         // 注释:查找"下一个"会话,以便启动流水线
  
  if (with_stateful_datapath)                      // 如果启用有状态数据平面
    {
      acl_fa_find_session_with_hash (am, is_ip6, sw_if_index[0], hash[0],
				   &fa_5tuple[0], &f_sess_id_next.as_u64);  // 查找第一个数据包的会话(启动流水线)
                                         // acl_fa_find_session_with_hash:使用Hash值查找会话
                                         // 作用:查找第一个数据包的会话,启动流水线
                                         // 类比:就像"查找第一个旅客的'常客信息',启动流水线"
    }

  n_left = frame->n_vectors;                         // 初始化剩余数据包数
                                         // 类比:就像"初始化剩余旅客数"
  
  while (n_left > 0)                                // 如果还有剩余数据包
    {
      u8 action = 0;                                // 动作(permit/deny)
                                         // 类比:就像"检查结果"(允许/拒绝)
      
      u32 lc_index0 = ~0;                           // Lookup Context索引(初始化为无效值)
                                         // 类比:就像"规则集合索引"
      
      int acl_check_needed = 1;                     // 是否需要ACL检查(默认需要)
                                         // acl_check_needed:是否需要ACL检查(如果找到会话,可能不需要)
                                         // 类比:就像"是否需要安检检查"
      
      u32 match_acl_in_index = ~0;                  // 匹配的ACL索引(初始化为无效值)
                                         // 类比:就像"匹配的规则表索引"
      
      u32 match_acl_pos = ~0;                       // 匹配的ACL位置(初始化为无效值)
                                         // 类比:就像"匹配的规则表位置"
      
      u32 match_rule_index = ~0;                    // 匹配的规则索引(初始化为无效值)
                                         // 类比:就像"匹配的规则索引"

      next[0] = 0;		                        /* drop by default */  // 默认丢弃
                                         // next[0]:下一个节点索引(0表示丢弃)
                                         // 类比:就像"默认拒绝"

      /* Try to match an existing session first */
                                         // 注释:首先尝试匹配现有会话

      if (with_stateful_datapath)                  // 如果启用有状态数据平面
	{
	  fa_full_session_id_t f_sess_id = f_sess_id_next;  // 获取当前数据包的会话ID(从上一个循环的"下一个"获取)
                                         // f_sess_id:当前数据包的会话ID(从上一个循环的f_sess_id_next获取)
                                         // 为什么需要?流水线处理,当前数据包的会话ID是上一个循环预取的
                                         // 类比:就像"当前旅客的'常客ID'(从上一个循环的'下一个'获取)"
	  
	  switch (n_left)                              // 根据剩余数据包数选择预取策略
	    {
	    default:                                    // 如果剩余数据包数 >= 6
	      acl_fa_prefetch_session_bucket_for_hash (am, is_ip6, hash[5]);  // 预取Hash表bucket(提前5个数据包)
                                         // 类比:就像"预取'数据库索引页'(提前5个旅客)"
	      /* fallthrough */
	    case 5:
	    case 4:                                     // 如果剩余数据包数 >= 4
	      acl_fa_prefetch_session_data_for_hash (am, is_ip6, hash[3]);  // 预取Hash表数据(提前3个数据包)
                                         // 类比:就像"预取'数据库记录'(提前3个旅客)"
	      /* fallthrough */
	    case 3:
	    case 2:                                     // 如果剩余数据包数 >= 2
	      acl_fa_find_session_with_hash (am, is_ip6, sw_if_index[1],
					     hash[1], &fa_5tuple[1],
					     &f_sess_id_next.as_u64);  // 查找下一个数据包的会话(提前1个数据包)
                                         // 类比:就像"查找下一个旅客的'常客信息'(提前1个旅客)"
	      if (f_sess_id_next.as_u64 != ~0ULL)
		{
		  prefetch_session_entry (am, f_sess_id_next);  // 预取会话条目
                                         // 类比:就像"预取'常客档案'"
		}
	      /* fallthrough */
	    case 1:                                     // 如果剩余数据包数 >= 1
	      if (f_sess_id.as_u64 != ~0ULL)            // 如果找到会话
		{
		  // ... 处理现有会话的代码 ...
		}
	    }
	}

流水线处理流程

复制代码
循环1: 处理数据包0 → 预取bucket[5] → 预取data[3] → 查找会话[1] → 预取会话[1]
循环2: 处理数据包1(使用预取的会话[1]) → 预取bucket[6] → 预取data[4] → 查找会话[2] → 预取会话[2]
循环3: 处理数据包2(使用预取的会话[2]) → 预取bucket[7] → 预取data[5] → 查找会话[3] → 预取会话[3]
...

类比:就像"流水线安检":

  • 循环1:检查旅客0 → 预取索引页5 → 预取记录3 → 查找常客1 → 预取档案1
  • 循环2:检查旅客1(使用预取的档案1) → 预取索引页6 → 预取记录4 → 查找常客2 → 预取档案2
  • 循环3:检查旅客2(使用预取的档案2) → 预取索引页7 → 预取记录5 → 查找常客3 → 预取档案3

13.5 缓存优化:如何"优化内存访问"?

13.5.1 缓存行对齐优化

会话结构体设计为128字节,正好是2个缓存行:

c 复制代码
//157:158:src/plugins/acl/fa_node.h
/* Let's try to fit within two cachelines */
CT_ASSERT_EQUAL(fa_session_t_size_is_128, sizeof(fa_session_t), 128);  // 编译时断言:会话结构体大小为128字节
                                         // CT_ASSERT_EQUAL:编译时断言(如果条件不满足,编译失败)
                                         // 为什么需要128字节?正好是2个缓存行(现代CPU的缓存行大小通常是64字节)
                                         // 为什么需要缓存行对齐?减少缓存未命中,提高访问速度
                                         // 类比:就像"档案夹大小正好是2个'存储单元'"(减少存储访问次数)

缓存行对齐的优势

  1. 减少缓存未命中:结构体对齐到缓存行,减少跨缓存行访问
  2. 提高访问速度:CPU可以一次性加载整个缓存行
  3. 减少伪共享:不同线程的会话不会共享同一个缓存行

类比:就像"档案夹大小优化":

  • 对齐到存储单元:档案夹大小正好是2个"存储单元"
  • 减少访问次数:一次可以加载整个"存储单元"
  • 避免冲突:不同通道的档案不会共享同一个"存储单元"

13.5.2 内存访问模式优化

ACL插件优化了内存访问模式,减少缓存未命中:

  1. 顺序访问:按顺序访问数据包,提高缓存命中率
  2. 批量访问:批量访问数据,提高缓存利用率
  3. 预取访问:提前预取数据,减少缓存未命中

类比:就像"优化档案访问模式":

  • 顺序访问:按顺序访问档案,提高"存储单元"命中率
  • 批量访问:批量访问档案,提高"存储单元"利用率
  • 预取访问:提前预取档案,减少"存储单元"未命中

13.6 分支预测优化:如何"帮助CPU预测分支"?

13.6.1 分支预测的概念

分支预测(Branch Prediction):CPU预测分支指令的执行路径,提前执行。

为什么需要分支预测?

  1. 流水线停顿:如果分支预测错误,需要清空流水线,造成性能损失
  2. 提高性能:正确的分支预测可以隐藏分支延迟
  3. 编译器提示 :使用PREDICT_FALSEPREDICT_TRUE提示编译器

类比:就像"提前预测检查结果":

  • 流水线停顿:如果预测错误,需要重新检查,造成时间损失
  • 提高性能:正确的预测可以隐藏检查延迟
  • 编译器提示:使用提示告诉CPU哪个分支更可能发生

13.6.2 分支预测宏的使用

ACL插件使用PREDICT_FALSEPREDICT_TRUE提示编译器:

c 复制代码
//198:198:src/plugins/acl/dataplane_node.c
  if (PREDICT_FALSE (old_timeout_type != new_timeout_type))  // 如果超时类型变化(不太可能发生)
                                         // PREDICT_FALSE:提示编译器这个分支不太可能发生
                                         // 作用:帮助CPU更好地预测分支,减少流水线停顿
                                         // 为什么不太可能?会话超时类型通常不会变化
                                         // 类比:就像"提示CPU'超时类型变化'不太可能发生"
    {
      acl_fa_restart_timer_for_session (am, now, f_sess_id);  // 重启会话定时器
                                         // 类比:就像"重启'常客定时器'"
    }

PREDICT_FALSE的使用场景

  1. 错误处理:错误情况通常不太可能发生
  2. 异常情况:异常情况通常不太可能发生
  3. 调试代码:调试代码通常不太可能执行

PREDICT_TRUE的使用场景

  1. 正常路径:正常处理路径通常会发生
  2. 热点代码:热点代码通常会执行

类比:就像"提示CPU分支概率":

  • PREDICT_FALSE:提示CPU"这个分支不太可能发生"(如错误处理)
  • PREDICT_TRUE:提示CPU"这个分支很可能发生"(如正常处理)

13.7 性能调优建议:如何"优化ACL插件性能"?

13.7.1 批量处理调优

调优建议

  1. 批量大小ACL_PLUGIN_VECTOR_SIZE(默认4)

    • 增加批量大小:可以提高吞吐量,但增加代码复杂度
    • 减少批量大小:可以降低延迟,但降低吞吐量
  2. 预取间隔ACL_PLUGIN_PREFETCH_GAP(默认3)

    • 增加预取间隔:可以预取更远的数据,但占用更多内存
    • 减少预取间隔:可以减少内存占用,但预取效果不明显

类比:就像"优化批量检查参数":

  • 批量大小:一次检查的旅客数(默认4)
  • 预取间隔:提前准备的旅客数(默认12)

13.7.2 缓存优化调优

调优建议

  1. 数据结构对齐:确保关键数据结构对齐到缓存行
  2. 减少伪共享:避免不同线程访问同一个缓存行
  3. 提高缓存命中率:优化内存访问模式,提高缓存命中率

类比:就像"优化档案存储":

  • 对齐到存储单元:确保关键档案对齐到"存储单元"
  • 减少冲突:避免不同通道访问同一个"存储单元"
  • 提高命中率:优化档案访问模式,提高"存储单元"命中率

13.7.3 分支预测调优

调优建议

  1. 使用PREDICT宏 :在关键分支使用PREDICT_FALSEPREDICT_TRUE
  2. 优化分支顺序:将更可能发生的分支放在前面
  3. 减少分支数量:减少不必要的分支

类比:就像"优化检查流程":

  • 使用提示:在关键检查点使用"不太可能"和"很可能"提示
  • 优化顺序:将更可能发生的情况放在前面
  • 减少检查点:减少不必要的检查点

13.8 性能监控:如何"监控ACL插件性能"?

13.8.1 性能计数器

ACL插件提供了多个性能计数器:

  1. 数据包计数:处理的数据包总数
  2. 会话计数:创建的会话总数
  3. ACL匹配计数:ACL匹配的总数

监控命令

bash 复制代码
# 显示ACL插件统计信息
show acl-plugin sessions

# 显示接口ACL统计信息
show acl-plugin interface

类比:就像"监控安检系统性能":

  • 旅客计数:检查的旅客总数
  • 常客计数:登记的常客总数
  • 规则匹配计数:规则匹配的总数

13.9 本章小结

通过这一章的详细讲解,我们了解了:

  1. 性能优化的概念:什么是性能优化,为什么需要它
  2. 批量处理优化 :如何一次处理多个数据包(acl_fa_node_common_prepare_fn
  3. 预取优化:如何提前准备好数据(数据包预取、Hash表预取、会话条目预取)
  4. 流水线处理:如何同时处理多个数据包(三级预取流水线)
  5. 缓存优化:如何优化内存访问(缓存行对齐、内存访问模式优化)
  6. 分支预测优化 :如何帮助CPU预测分支(PREDICT_FALSEPREDICT_TRUE
  7. 性能调优建议:如何优化ACL插件性能
  8. 性能监控:如何监控ACL插件性能

核心要点

  • 批量处理:一次处理4个数据包,减少函数调用开销
  • 三级预取流水线:bucket预取(提前5个) → data预取(提前3个) → 会话查找和预取(提前1个)
  • 缓存优化:会话结构体对齐到2个缓存行(128字节),减少缓存未命中
  • 分支预测 :使用PREDICT_FALSEPREDICT_TRUE提示编译器,帮助CPU预测分支
  • 性能调优:根据实际场景调整批量大小、预取间隔等参数

下一步:在下一章,我们会看到ACL插件的调试工具和故障排查方法。


第14章:ACL插件调试工具------如何"诊断和排查问题"?

生活类比:想象一下,你是一个"安检系统维护工程师",需要诊断和排查安检系统的问题。

  • Trace功能:记录每个旅客的检查过程(就像"检查记录")
  • ELOG功能:记录系统事件(就像"系统日志")
  • Show命令:显示系统状态(就像"查看系统状态")

这一章,我们就跟着ACL插件的代码,看看它是如何"诊断和排查问题"的。每一步我都会详细解释,确保你完全理解。


14.1 调试工具的概念:什么是"调试工具"?

14.1.1 什么是调试工具?

调试工具(Debugging Tools):用于诊断和排查系统问题的工具集合。

关键概念详解

  1. Trace功能(数据包跟踪):记录数据包的处理过程

    • 作用:跟踪数据包经过ACL插件的完整路径
    • 类比:就像"检查记录"(记录每个旅客的检查过程)
  2. ELOG功能(事件日志):记录系统事件

    • 作用:记录ACL插件的关键事件(如会话创建、删除等)
    • 类比:就像"系统日志"(记录系统的重要事件)
  3. Show命令(显示状态):显示系统状态信息

    • 作用:显示ACL插件的当前状态(如会话数、ACL规则等)
    • 类比:就像"查看系统状态"(查看安检系统的当前状态)
  4. 调试宏(Debug Macros):用于调试的宏定义

    • 作用:在调试模式下输出调试信息
    • 类比:就像"调试输出"(在调试模式下输出详细信息)
14.1.2 调试工具的分类

按功能分类

  1. 数据包跟踪:Trace功能,跟踪单个数据包的处理过程
  2. 事件日志:ELOG功能,记录系统事件
  3. 状态显示:Show命令,显示系统状态
  4. 调试输出:调试宏,输出调试信息

按使用场景分类

  1. 开发调试:开发时使用,输出详细的调试信息
  2. 生产排查:生产环境使用,输出关键信息
  3. 性能分析:性能分析时使用,输出性能相关数据

类比:就像"安检系统调试工具":

  • 检查记录:记录每个旅客的检查过程(Trace功能)
  • 系统日志:记录系统的重要事件(ELOG功能)
  • 状态查看:查看系统的当前状态(Show命令)
  • 调试输出:在调试模式下输出详细信息(调试宏)

14.2 Trace功能:如何"跟踪数据包处理过程"?

14.2.1 Trace功能的概念

Trace功能(数据包跟踪):记录数据包经过ACL插件的完整处理过程。

为什么需要Trace?

  1. 问题诊断:当数据包被错误处理时,可以查看Trace找出问题
  2. 流程验证:验证数据包是否按照预期流程处理
  3. 性能分析:分析数据包处理的性能瓶颈

类比:就像"检查记录":

  • 问题诊断:当旅客被错误处理时,可以查看"检查记录"找出问题
  • 流程验证:验证旅客是否按照预期流程检查
  • 性能分析:分析检查流程的性能瓶颈

14.2.2 Trace数据结构:acl_fa_trace_t

Trace数据结构用于存储数据包的跟踪信息:

c 复制代码
//35:45:src/plugins/acl/dataplane_node.c
typedef struct
{
  u32 next_index;                                 // 下一个节点索引
                                         // next_index:数据包的下一个节点索引(用于转发)
                                         // 为什么需要?记录数据包的转发路径
                                         // 类比:就像"下一个检查点索引"
  
  u32 sw_if_index;                                // 接口索引
                                         // sw_if_index:数据包进入的接口索引
                                         // 为什么需要?记录数据包的入口接口
                                         // 类比:就像"入口通道编号"
  
  u32 lc_index;                                   // Lookup Context索引
                                         // lc_index:用于匹配的Lookup Context索引
                                         // 为什么需要?记录使用的Lookup Context
                                         // 类比:就像"使用的规则集合索引"
  
  u32 match_acl_in_index;                         // 匹配的ACL索引
                                         // match_acl_in_index:匹配的ACL索引(在ACL列表中的位置)
                                         // 为什么需要?记录匹配的ACL
                                         // 类比:就像"匹配的规则表索引"
  
  u32 match_rule_index;                           // 匹配的规则索引
                                         // match_rule_index:匹配的规则索引(在ACL中的位置)
                                         // 为什么需要?记录匹配的规则
                                         // 类比:就像"匹配的规则索引"
  
  u64 packet_info[6];                             // 数据包信息(5-tuple,6个u64)
                                         // packet_info:数据包的5-tuple信息(用于Hash表查找)
                                         // 为什么需要?记录数据包的5-tuple信息
                                         // 类比:就像"旅客的基本信息"
  
  u32 trace_bitmap;                               // Trace位图
                                         // trace_bitmap:Trace位图(记录Trace的各种标志位)
                                         // 为什么需要?记录Trace的各种标志(如是否找到会话、是否匹配ACL等)
                                         // 类比:就像"检查标志位图"
  
  u8 action;                                      // 动作(permit/deny)
                                         // action:ACL匹配的动作(permit=允许,deny=拒绝)
                                         // 为什么需要?记录ACL匹配的结果
                                         // 类比:就像"检查结果"(允许/拒绝)
} acl_fa_trace_t;

结构体字段总结

  1. next_index:下一个节点索引(用于转发)
  2. sw_if_index:接口索引(入口接口)
  3. lc_index:Lookup Context索引(使用的规则集合)
  4. match_acl_in_index:匹配的ACL索引(在ACL列表中的位置)
  5. match_rule_index:匹配的规则索引(在ACL中的位置)
  6. packet_info:数据包信息(5-tuple)
  7. trace_bitmap:Trace位图(各种标志位)
  8. action:动作(permit/deny)

类比:就像"检查记录结构":

  • 下一个检查点:下一个检查点的索引
  • 入口通道:入口通道编号
  • 使用的规则集合:使用的规则集合索引
  • 匹配的规则表:匹配的规则表索引
  • 匹配的规则:匹配的规则索引
  • 旅客信息:旅客的基本信息
  • 检查标志:检查的各种标志
  • 检查结果:检查结果(允许/拒绝)

14.2.3 Trace记录函数:maybe_trace_buffer

maybe_trace_buffer 用于记录数据包的Trace信息:

c 复制代码
//79:102:src/plugins/acl/dataplane_node.c
always_inline void
maybe_trace_buffer (vlib_main_t * vm, vlib_node_runtime_t * node,
		    vlib_buffer_t * b, u32 sw_if_index0, u32 lc_index0,
		    u16 next0, int match_acl_in_index, int match_rule_index,
		    fa_5tuple_t * fa_5tuple, u8 action, u32 trace_bitmap)
{
  if (PREDICT_FALSE (b->flags & VLIB_BUFFER_IS_TRACED))  // 如果数据包需要Trace
    {
                                         // PREDICT_FALSE:提示编译器这个分支不太可能发生
                                         // VLIB_BUFFER_IS_TRACED:数据包需要Trace的标志位
                                         // 为什么不太可能?大多数数据包不需要Trace(只有标记的数据包才需要)
                                         // 类比:就像"如果旅客需要'检查记录'"
      
      acl_fa_trace_t *t = vlib_add_trace (vm, node, b, sizeof (*t));  // 添加Trace记录
                                         // vlib_add_trace:添加Trace记录到数据包
                                         // 参数:vm(VPP主结构体)、node(节点)、b(数据包缓冲区)、sizeof(*t)(Trace结构体大小)
                                         // 作用:为数据包添加Trace记录,返回Trace结构体指针
                                         // 类比:就像"为旅客添加'检查记录'"
      
      t->sw_if_index = sw_if_index0;              // 设置接口索引
                                         // 类比:就像"设置'入口通道编号'"
      
      t->lc_index = lc_index0;                     // 设置Lookup Context索引
                                         // 类比:就像"设置'使用的规则集合索引'"
      
      t->next_index = next0;                       // 设置下一个节点索引
                                         // 类比:就像"设置'下一个检查点索引'"
      
      t->match_acl_in_index = match_acl_in_index;  // 设置匹配的ACL索引
                                         // 类比:就像"设置'匹配的规则表索引'"
      
      t->match_rule_index = match_rule_index;      // 设置匹配的规则索引
                                         // 类比:就像"设置'匹配的规则索引'"
      
      t->packet_info[0] = fa_5tuple->kv_40_8.key[0];  // 设置数据包信息(第1个u64)
                                         // kv_40_8.key[0]:IPv6会话Hash表的key[0](包含源IP地址的一部分)
                                         // 类比:就像"设置'旅客信息'(第1部分)"
      
      t->packet_info[1] = fa_5tuple->kv_40_8.key[1];  // 设置数据包信息(第2个u64)
                                         // kv_40_8.key[1]:IPv6会话Hash表的key[1](包含源IP地址的另一部分)
                                         // 类比:就像"设置'旅客信息'(第2部分)"
      
      t->packet_info[2] = fa_5tuple->kv_40_8.key[2];  // 设置数据包信息(第3个u64)
                                         // kv_40_8.key[2]:IPv6会话Hash表的key[2](包含目的IP地址的一部分)
                                         // 类比:就像"设置'旅客信息'(第3部分)"
      
      t->packet_info[3] = fa_5tuple->kv_40_8.key[3];  // 设置数据包信息(第4个u64)
                                         // kv_40_8.key[3]:IPv6会话Hash表的key[3](包含目的IP地址的另一部分)
                                         // 类比:就像"设置'旅客信息'(第4部分)"
      
      t->packet_info[4] = fa_5tuple->kv_40_8.key[4];  // 设置数据包信息(第5个u64)
                                         // kv_40_8.key[4]:IPv6会话Hash表的key[4](包含L4信息)
                                         // 类比:就像"设置'旅客信息'(第5部分)"
      
      t->packet_info[5] = fa_5tuple->kv_40_8.value;   // 设置数据包信息(第6个u64)
                                         // kv_40_8.value:IPv6会话Hash表的value(包含会话ID等)
                                         // 类比:就像"设置'旅客信息'(第6部分)"
      
      t->action = action;                          // 设置动作
                                         // 类比:就像"设置'检查结果'"
      
      t->trace_bitmap = trace_bitmap;              // 设置Trace位图
                                         // 类比:就像"设置'检查标志位图'"
    }
}

函数总结

  1. 检查Trace标志:检查数据包是否需要Trace
  2. 添加Trace记录:为数据包添加Trace记录
  3. 填充Trace信息:填充Trace结构体的所有字段

Trace标志位说明

  • VLIB_BUFFER_IS_TRACED:数据包需要Trace的标志位(由用户通过CLI命令设置)

类比:就像完整的"记录检查过程"流程:

  1. 检查标志:检查旅客是否需要"检查记录"
  2. 添加记录:为旅客添加"检查记录"
  3. 填充信息:填充"检查记录"的所有字段(通道、规则集合、规则、旅客信息、结果等)

14.2.4 Trace格式化函数:format_acl_plugin_trace

format_acl_plugin_trace 用于格式化Trace信息,用于显示:

c 复制代码
//707:727:src/plugins/acl/dataplane_node.c
/* packet trace format function */
                                         // 注释:数据包Trace格式化函数
static u8 *
format_acl_plugin_trace (u8 * s, va_list * args)
{
  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);  // 获取VPP主结构体(未使用)
                                         // CLIB_UNUSED:标记变量为未使用(避免编译器警告)
                                         // va_arg:从可变参数列表中获取参数
                                         // 类比:就像"获取'系统主结构体'(未使用)"
  
  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);  // 获取节点(未使用)
                                         // 类比:就像"获取'检查点'(未使用)"
  
  acl_fa_trace_t *t = va_arg (*args, acl_fa_trace_t *);  // 获取Trace结构体指针
                                         // 类比:就像"获取'检查记录'指针"

  // ========== 第一部分:格式化基本信息 ==========
  
  s =
    format (s,
	    "acl-plugin: lc_index: %d, sw_if_index %d, next index %d, action: %d, match: acl %d rule %d trace_bits %08x\n"
	    "  pkt info %016llx %016llx %016llx %016llx %016llx %016llx",
	    t->lc_index,                                    // Lookup Context索引
                                         // 类比:就像"规则集合索引"
	    
	    t->sw_if_index,                                 // 接口索引
                                         // 类比:就像"通道编号"
	    
	    t->next_index,                                  // 下一个节点索引
                                         // 类比:就像"下一个检查点索引"
	    
	    t->action,                                      // 动作(permit/deny)
                                         // 类比:就像"检查结果"
	    
	    t->match_acl_in_index,                          // 匹配的ACL索引
                                         // 类比:就像"匹配的规则表索引"
	    
	    t->match_rule_index,                            // 匹配的规则索引
                                         // 类比:就像"匹配的规则索引"
	    
	    t->trace_bitmap,                                // Trace位图
                                         // 类比:就像"检查标志位图"
	    
	    t->packet_info[0],                              // 数据包信息(第1个u64)
                                         // 类比:就像"旅客信息(第1部分)"
	    
	    t->packet_info[1],                              // 数据包信息(第2个u64)
                                         // 类比:就像"旅客信息(第2部分)"
	    
	    t->packet_info[2],                              // 数据包信息(第3个u64)
                                         // 类比:就像"旅客信息(第3部分)"
	    
	    t->packet_info[3],                              // 数据包信息(第4个u64)
                                         // 类比:就像"旅客信息(第4部分)"
	    
	    t->packet_info[4],                              // 数据包信息(第5个u64)
                                         // 类比:就像"旅客信息(第5部分)"
	    
	    t->packet_info[5]);                             // 数据包信息(第6个u64)
                                         // 类比:就像"旅客信息(第6部分)"
  
  // ========== 第二部分:格式化5-tuple信息(人类可读格式) ==========
  
  /* Now also print out the packet_info in a form usable by humans */
                                         // 注释:现在也以人类可读的格式打印packet_info
  
  s = format (s, "\n   %U", format_fa_5tuple, t->packet_info);  // 格式化5-tuple信息
                                         // format_fa_5tuple:格式化5-tuple信息(人类可读格式)
                                         // 作用:将5-tuple信息转换为人类可读的格式(如IP地址、端口等)
                                         // 类比:就像"将'旅客信息'转换为人类可读的格式"
  
  return s;                                      // 返回格式化后的字符串
                                         // 类比:就像"返回格式化后的'检查记录'"
}

函数总结

  1. 获取Trace结构体:从可变参数列表中获取Trace结构体指针
  2. 格式化基本信息:格式化Trace的基本信息(Lookup Context索引、接口索引、动作等)
  3. 格式化5-tuple信息:将5-tuple信息转换为人类可读的格式

输出格式

复制代码
acl-plugin: lc_index: 0, sw_if_index 1, next index 2, action: 1, match: acl 0 rule 5 trace_bits 80000000
  pkt info 20010db800000001 0000000000000000 20010db800000002 0000000000000000 0000000000000000 0000000000000000
   [5-tuple in human readable format]

类比:就像"格式化检查记录":

  • 基本信息:规则集合索引、通道编号、下一个检查点、检查结果、匹配的规则表、匹配的规则、检查标志
  • 旅客信息:旅客的基本信息(人类可读格式)

14.3 ELOG功能:如何"记录系统事件"?

14.3.1 ELOG功能的概念

ELOG功能(事件日志):记录ACL插件的关键事件(如会话创建、删除等)。

为什么需要ELOG?

  1. 事件追踪:追踪系统事件的发生顺序
  2. 问题诊断:当出现问题时,可以查看ELOG找出原因
  3. 性能分析:分析系统事件的频率和模式

类比:就像"系统日志":

  • 事件追踪:追踪系统事件的发生顺序
  • 问题诊断:当出现问题时,可以查看"系统日志"找出原因
  • 性能分析:分析系统事件的频率和模式

14.3.2 ELOG宏定义:elog_acl_cond_trace_X1

elog_acl_cond_trace_X1 用于记录带1个参数的事件:

c 复制代码
//22:42:src/plugins/acl/elog_acl_trace.h
#define elog_acl_cond_trace_X1(am, trace_cond, acl_elog_trace_format_label, acl_elog_trace_format_args, acl_elog_val1)              \
do {                                                                                                                     \
  if (trace_cond) {                                                                                              // 如果Trace条件满足
                                         // trace_cond:Trace条件(如果为真,则记录事件)
                                         // 类比:就像"如果'记录条件'满足"
    
    CLIB_UNUSED(struct { u8 available_space[18 - sizeof(acl_elog_val1)]; } *static_check);                               // 静态检查可用空间
                                         // CLIB_UNUSED:标记变量为未使用(避免编译器警告)
                                         // available_space:可用空间(18字节 - 参数大小)
                                         // 为什么需要?确保ELOG记录不超过18字节(VPP的ELOG限制)
                                         // 类比:就像"检查'日志记录'的可用空间"
    
    u16 thread_index = os_get_thread_index ();                                                                           // 获取线程索引
                                         // os_get_thread_index:获取当前线程索引
                                         // 为什么需要?记录事件发生的线程(用于多线程调试)
                                         // 类比:就像"获取'安检通道编号'"
    
    vlib_worker_thread_t * w = vlib_worker_threads + thread_index;                                                       // 获取Worker线程指针
                                         // vlib_worker_threads:Worker线程数组
                                         // 为什么需要?获取Worker线程的ELOG track(用于记录事件)
                                         // 类比:就像"获取'安检通道'的'日志记录器'"
    
    ELOG_TYPE_DECLARE (e) =                                                                                              // 声明ELOG事件类型
      {                                                                                                                  
        .format = "(%02d) " acl_elog_trace_format_label,                                                                 // 格式化字符串
                                         // format:ELOG事件的格式化字符串
                                         // "(%02d) ":线程索引(2位数字)
                                         // acl_elog_trace_format_label:用户提供的格式化标签
                                         // 类比:就像"声明'日志事件类型'(包含格式化字符串)"
        
        .format_args = "i2" acl_elog_trace_format_args,                                                                  // 格式化参数
                                         // format_args:ELOG事件的格式化参数
                                         // "i2":线程索引(2字节整数)
                                         // acl_elog_trace_format_args:用户提供的格式化参数
                                         // 类比:就像"声明'日志事件类型'(包含格式化参数)"
      };                                                                                                                 
    
    CLIB_PACKED(struct                                                                                                   // 声明打包结构体
      {                                                                                                                  
        u16 thread;                                                                                                      // 线程索引
                                         // thread:线程索引(用于记录事件发生的线程)
                                         // 类比:就像"安检通道编号"
        
        typeof(acl_elog_val1) val1;                                                                                      // 参数值1
                                         // val1:用户提供的参数值1(类型由typeof自动推导)
                                         // 类比:就像"参数值1"
      }) *ed;                                                                                                            // 结构体指针
    
    ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, e, w->elog_track);                                                // 记录ELOG事件
                                         // ELOG_TRACK_DATA:记录ELOG事件到ELOG track
                                         // 参数:&vlib_global_main.elog_main(ELOG主结构体)、e(事件类型)、w->elog_track(Worker线程的ELOG track)
                                         // 作用:记录事件到ELOG track,返回事件数据指针
                                         // 类比:就像"记录事件到'日志记录器'"
    
    ed->thread = thread_index;                                                                                           // 设置线程索引
                                         // 类比:就像"设置'安检通道编号'"
    
    ed->val1 = acl_elog_val1;                                                                                            // 设置参数值1
                                         // 类比:就像"设置'参数值1'"
  }                                                                                                                      
} while (0)

宏定义总结

  1. 检查Trace条件:如果Trace条件满足,则记录事件
  2. 静态检查空间:确保ELOG记录不超过18字节
  3. 获取线程信息:获取当前线程索引和Worker线程指针
  4. 声明事件类型:声明ELOG事件类型(格式化字符串和参数)
  5. 记录事件:记录事件到ELOG track
  6. 填充事件数据:填充事件数据(线程索引、参数值)

使用示例

c 复制代码
elog_acl_cond_trace_X1(am, (am->trace_acl), "LOOKUP-CONTEXT: set-acl-list lc_index %d", "i4", lc_index);

类比:就像"记录系统事件":

  • 检查条件:如果"记录条件"满足,则记录事件
  • 检查空间:确保"日志记录"不超过限制
  • 获取通道:获取当前"安检通道编号"和"日志记录器"
  • 声明类型:声明"日志事件类型"(格式化字符串和参数)
  • 记录事件:记录事件到"日志记录器"
  • 填充数据:填充事件数据(通道编号、参数值)

14.3.3 ELOG宏定义:elog_acl_cond_trace_X2

elog_acl_cond_trace_X2 用于记录带2个参数的事件:

c 复制代码
//45:70:src/plugins/acl/elog_acl_trace.h
#define elog_acl_cond_trace_X2(am, trace_cond, acl_elog_trace_format_label, acl_elog_trace_format_args,                             \
                                                                                           acl_elog_val1, acl_elog_val2) \
do {                                                                                                                     \
  if (trace_cond) {                                                                                              // 如果Trace条件满足
                                         // 类比:就像"如果'记录条件'满足"
    
    CLIB_UNUSED(struct { u8 available_space[18 - sizeof(acl_elog_val1) - sizeof(acl_elog_val2)]; } *static_check);       // 静态检查可用空间
                                         // available_space:可用空间(18字节 - 参数1大小 - 参数2大小)
                                         // 类比:就像"检查'日志记录'的可用空间"
    
    u16 thread_index = os_get_thread_index ();                                                                           // 获取线程索引
                                         // 类比:就像"获取'安检通道编号'"
    
    vlib_worker_thread_t * w = vlib_worker_threads + thread_index;                                                       // 获取Worker线程指针
                                         // 类比:就像"获取'安检通道'的'日志记录器'"
    
    ELOG_TYPE_DECLARE (e) =                                                                                              // 声明ELOG事件类型
      {                                                                                                                  
        .format = "(%02d) " acl_elog_trace_format_label,                                                                 // 格式化字符串
                                         // 类比:就像"声明'日志事件类型'(包含格式化字符串)"
        
        .format_args = "i2" acl_elog_trace_format_args,                                                                  // 格式化参数
                                         // 类比:就像"声明'日志事件类型'(包含格式化参数)"
      };                                                                                                                 
    
    CLIB_PACKED(struct                                                                                                   // 声明打包结构体
      {                                                                                                                  
        u16 thread;                                                                                                      // 线程索引
                                         // 类比:就像"安检通道编号"
        
        typeof(acl_elog_val1) val1;                                                                                      // 参数值1
                                         // 类比:就像"参数值1"
        
        typeof(acl_elog_val2) val2;                                                                                      // 参数值2
                                         // 类比:就像"参数值2"
      }) *ed;                                                                                                            // 结构体指针
    
    ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, e, w->elog_track);                                                // 记录ELOG事件
                                         // 类比:就像"记录事件到'日志记录器'"
    
    ed->thread = thread_index;                                                                                           // 设置线程索引
                                         // 类比:就像"设置'安检通道编号'"
    
    ed->val1 = acl_elog_val1;                                                                                            // 设置参数值1
                                         // 类比:就像"设置'参数值1'"
    
    ed->val2 = acl_elog_val2;                                                                                            // 设置参数值2
                                         // 类比:就像"设置'参数值2'"
  }                                                                                                                      
} while (0)

宏定义总结

elog_acl_cond_trace_X1类似,但支持2个参数。

使用示例

c 复制代码
elog_acl_cond_trace_X2(am, (am->trace_acl), "lock acl %d in lc_index %d", "i4i4", acl, lc_index);

类比:就像"记录带2个参数的系统事件":

  • 检查条件:如果"记录条件"满足,则记录事件
  • 检查空间:确保"日志记录"不超过限制(考虑2个参数)
  • 获取通道:获取当前"安检通道编号"和"日志记录器"
  • 声明类型:声明"日志事件类型"(格式化字符串和参数)
  • 记录事件:记录事件到"日志记录器"
  • 填充数据:填充事件数据(通道编号、参数值1、参数值2)


14.4 Show命令:如何"显示系统状态"?

14.4.1 Show命令的概念

Show命令(显示状态):通过CLI命令显示ACL插件的当前状态。

为什么需要Show命令?

  1. 状态查看:查看ACL插件的当前状态(如会话数、ACL规则等)
  2. 问题诊断:当出现问题时,可以查看状态找出原因
  3. 性能监控:监控ACL插件的性能指标

类比:就像"查看系统状态":

  • 状态查看:查看安检系统的当前状态(如常客数、规则数等)
  • 问题诊断:当出现问题时,可以查看状态找出原因
  • 性能监控:监控安检系统的性能指标

14.4.2 Show ACL命令:show acl-plugin acl

show acl-plugin acl 命令用于显示ACL规则:

c 复制代码
//3231:3267:src/plugins/acl/acl.c
acl_plugin_show_acl (acl_main_t * am, u32 acl_index)
{
  u32 i;
  vlib_main_t *vm = am->vlib_main;                    // 获取VPP主结构体
                                         // 类比:就像"获取'系统主结构体'"

  for (i = 0; i < vec_len (am->acls); i++)            // 遍历所有ACL
    {
                                         // 类比:就像"遍历所有'规则表'"
      
      if (acl_is_not_defined (am, i))                  // 如果ACL未定义,跳过
	{
	  /* don't attempt to show the ACLs that do not exist */
                                         // 注释:不要尝试显示不存在的ACL
	  continue;
                                         // 类比:就像"如果'规则表'不存在,跳过"
	}
      
      if ((acl_index != ~0) && (acl_index != i))      // 如果指定了ACL索引,且当前索引不匹配,跳过
	{
	  continue;
                                         // 类比:就像"如果指定了'规则表索引',且当前索引不匹配,跳过"
	}
      
      acl_print_acl (vm, am, i);                       // 打印ACL信息
                                         // acl_print_acl:打印ACL信息(包括规则列表)
                                         // 类比:就像"打印'规则表'信息"

      if (i < vec_len (am->input_sw_if_index_vec_by_acl))  // 如果ACL应用于输入接口
	{
	  vlib_cli_output (vm, "  applied inbound on sw_if_index: %U\n",
			   format_vec32, am->input_sw_if_index_vec_by_acl[i],
			   "%d");                          // 显示应用的输入接口列表
                                         // input_sw_if_index_vec_by_acl:ACL应用的输入接口列表
                                         // 类比:就像"显示'规则表'应用的'入口通道'列表"
	}
      
      if (i < vec_len (am->output_sw_if_index_vec_by_acl))  // 如果ACL应用于输出接口
	{
	  vlib_cli_output (vm, "  applied outbound on sw_if_index: %U\n",
			   format_vec32, am->output_sw_if_index_vec_by_acl[i],
			   "%d");                          // 显示应用的输出接口列表
                                         // output_sw_if_index_vec_by_acl:ACL应用的输出接口列表
                                         // 类比:就像"显示'规则表'应用的'出口通道'列表"
	}
      
      if (i < vec_len (am->lc_index_vec_by_acl))       // 如果ACL用于Lookup Context
	{
	  vlib_cli_output (vm, "  used in lookup context index: %U\n",
			   format_vec32, am->lc_index_vec_by_acl[i], "%d");  // 显示使用的Lookup Context列表
                                         // lc_index_vec_by_acl:使用ACL的Lookup Context列表
                                         // 类比:就像"显示使用'规则表'的'规则集合'列表"
	}
    }
}

函数总结

  1. 遍历所有ACL:遍历所有已定义的ACL
  2. 过滤ACL:如果指定了ACL索引,只显示匹配的ACL
  3. 打印ACL信息:打印ACL的规则列表
  4. 显示应用信息:显示ACL应用的接口和Lookup Context

CLI命令处理函数

c 复制代码
//3269:3281:src/plugins/acl/acl.c
static clib_error_t *
acl_show_aclplugin_acl_fn (vlib_main_t * vm,
			   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  clib_error_t *error = 0;
  acl_main_t *am = &acl_main;                         // 获取ACL主结构体
                                         // 类比:就像"获取'安检系统主结构体'"

  u32 acl_index = ~0;                                 // 初始化ACL索引为无效值
                                         // 类比:就像"初始化'规则表索引'为无效值"
  
  (void) unformat (input, "index %u", &acl_index);   // 解析命令行参数(可选)
                                         // unformat:解析命令行参数
                                         // 如果命令行包含"index N",则设置acl_index为N
                                         // 类比:就像"解析命令行参数(可选)"

  acl_plugin_show_acl (am, acl_index);               // 显示ACL信息
                                         // 类比:就像"显示'规则表'信息"
  
  return error;                                        // 返回错误(如果有)
                                         // 类比:就像"返回错误(如果有)"
}

使用示例

bash 复制代码
# 显示所有ACL
show acl-plugin acl

# 显示指定ACL
show acl-plugin acl index 0

类比:就像"查看规则表":

  • 显示所有规则表:显示所有已定义的规则表
  • 显示指定规则表:显示指定索引的规则表
  • 显示应用信息:显示规则表应用的通道和规则集合

14.4.3 Show Interface命令:show acl-plugin interface

show acl-plugin interface 命令用于显示接口的ACL配置:

c 复制代码
//3313:3393:src/plugins/acl/acl.c
acl_plugin_show_interface (acl_main_t * am, u32 sw_if_index, int show_acl,
			   int detail)
{
  vlib_main_t *vm = am->vlib_main;                    // 获取VPP主结构体
                                         // 类比:就像"获取'系统主结构体'"
  
  u32 swi;                                             // 接口索引
                                         // 类比:就像"通道索引"
  
  u32 *pj;                                             // ACL索引指针
                                         // 类比:就像"规则表索引指针"
  
  for (swi = 0; (swi < vec_len (am->input_acl_vec_by_sw_if_index)) ||
       (swi < vec_len (am->output_acl_vec_by_sw_if_index)); swi++)  // 遍历所有接口
    {
                                         // 类比:就像"遍历所有'通道'"
      
      /* if we need a particular interface, skip all the others */
                                         // 注释:如果我们需要特定的接口,跳过所有其他接口
      
      if ((sw_if_index != ~0) && (sw_if_index != swi))  // 如果指定了接口索引,且当前索引不匹配,跳过
	continue;
                                         // 类比:就像"如果指定了'通道索引',且当前索引不匹配,跳过"

      vlib_cli_output (vm, "sw_if_index %d:\n", swi);  // 显示接口索引
                                         // 类比:就像"显示'通道编号'"

      if (swi < vec_len (am->input_policy_epoch_by_sw_if_index))  // 如果接口有输入Policy Epoch
	vlib_cli_output (vm, "   input policy epoch: %x\n",
			 vec_elt (am->input_policy_epoch_by_sw_if_index,
				  swi));                  // 显示输入Policy Epoch
                                         // input_policy_epoch_by_sw_if_index:接口的输入Policy Epoch
                                         // Policy Epoch:策略版本号(用于检测ACL规则变更)
                                         // 类比:就像"显示'入口通道'的'策略版本号'"

      if (swi < vec_len (am->output_policy_epoch_by_sw_if_index))  // 如果接口有输出Policy Epoch
	vlib_cli_output (vm, "   output policy epoch: %x\n",
			 vec_elt (am->output_policy_epoch_by_sw_if_index,
				  swi));                  // 显示输出Policy Epoch
                                         // 类比:就像"显示'出口通道'的'策略版本号'"

      if (intf_has_etype_whitelist (am, swi, 1))      // 如果接口有输入Ethertype白名单
	{
	  vlib_cli_output (vm, "  input etype whitelist: %U", format_vec16,
			   am->input_etype_whitelist_by_sw_if_index[swi],
			   "%04x");                      // 显示输入Ethertype白名单
                                         // etype whitelist:Ethertype白名单(允许的以太网类型)
                                         // 类比:就像"显示'入口通道'的'以太网类型白名单'"
	}
      
      if (intf_has_etype_whitelist (am, swi, 0))       // 如果接口有输出Ethertype白名单
	{
	  vlib_cli_output (vm, " output etype whitelist: %U", format_vec16,
			   am->output_etype_whitelist_by_sw_if_index[swi],
			   "%04x");                      // 显示输出Ethertype白名单
                                         // 类比:就像"显示'出口通道'的'以太网类型白名单'"
	}

      if ((swi < vec_len (am->input_acl_vec_by_sw_if_index)) &&
	  (vec_len (am->input_acl_vec_by_sw_if_index[swi]) > 0))  // 如果接口有输入ACL
	{
	  vlib_cli_output (vm, "  input acl(s): %U", format_vec32,
			   am->input_acl_vec_by_sw_if_index[swi], "%d");  // 显示输入ACL列表
                                         // input_acl_vec_by_sw_if_index:接口的输入ACL列表
                                         // 类比:就像"显示'入口通道'的'规则表'列表"
	  
	  if (show_acl)                                    // 如果要求显示ACL详情
	    {
	      vlib_cli_output (vm, "\n");
	      vec_foreach (pj, am->input_acl_vec_by_sw_if_index[swi])  // 遍历输入ACL列表
	      {
		acl_print_acl (vm, am, *pj);               // 打印每个ACL的详情
                                         // 类比:就像"打印每个'规则表'的详情"
	      }
	      vlib_cli_output (vm, "\n");
	    }
	}

      if ((swi < vec_len (am->output_acl_vec_by_sw_if_index)) &&
	  (vec_len (am->output_acl_vec_by_sw_if_index[swi]) > 0))  // 如果接口有输出ACL
	{
	  vlib_cli_output (vm, "  output acl(s): %U", format_vec32,
			   am->output_acl_vec_by_sw_if_index[swi], "%d");  // 显示输出ACL列表
                                         // output_acl_vec_by_sw_if_index:接口的输出ACL列表
                                         // 类比:就像"显示'出口通道'的'规则表'列表"
	  
	  if (show_acl)                                    // 如果要求显示ACL详情
	    {
	      vlib_cli_output (vm, "\n");
	      vec_foreach (pj, am->output_acl_vec_by_sw_if_index[swi])  // 遍历输出ACL列表
	      {
		acl_print_acl (vm, am, *pj);               // 打印每个ACL的详情
                                         // 类比:就像"打印每个'规则表'的详情"
	      }
	      vlib_cli_output (vm, "\n");
	    }
	}
      
      if (detail && (swi < vec_len (am->input_lc_index_by_sw_if_index)))  // 如果要求显示详情,且接口有输入Lookup Context
	{
	  vlib_cli_output (vm, "   input lookup context index: %d",
			   am->input_lc_index_by_sw_if_index[swi]);  // 显示输入Lookup Context索引
                                         // input_lc_index_by_sw_if_index:接口的输入Lookup Context索引
                                         // 类比:就像"显示'入口通道'的'规则集合索引'"
	}
      
      if (detail && (swi < vec_len (am->output_lc_index_by_sw_if_index)))  // 如果要求显示详情,且接口有输出Lookup Context
	{
	  vlib_cli_output (vm, "  output lookup context index: %d",
			   am->output_lc_index_by_sw_if_index[swi]);  // 显示输出Lookup Context索引
                                         // output_lc_index_by_sw_if_index:接口的输出Lookup Context索引
                                         // 类比:就像"显示'出口通道'的'规则集合索引'"
	}
    }

}

函数总结

  1. 遍历所有接口:遍历所有配置了ACL的接口
  2. 过滤接口:如果指定了接口索引,只显示匹配的接口
  3. 显示Policy Epoch:显示接口的输入/输出Policy Epoch
  4. 显示Ethertype白名单:显示接口的输入/输出Ethertype白名单
  5. 显示ACL列表:显示接口的输入/输出ACL列表
  6. 显示ACL详情:如果要求,显示每个ACL的详情
  7. 显示Lookup Context:如果要求显示详情,显示接口的Lookup Context索引

CLI命令处理函数

c 复制代码
//3415:3430:src/plugins/acl/acl.c
static clib_error_t *
acl_show_aclplugin_interface_fn (vlib_main_t * vm,
				 unformat_input_t *
				 input, vlib_cli_command_t * cmd)
{
  clib_error_t *error = 0;
  acl_main_t *am = &acl_main;                         // 获取ACL主结构体
                                         // 类比:就像"获取'安检系统主结构体'"

  u32 sw_if_index = ~0;                               // 初始化接口索引为无效值
                                         // 类比:就像"初始化'通道索引'为无效值"
  
  (void) unformat (input, "sw_if_index %u", &sw_if_index);  // 解析命令行参数(可选)
                                         // 如果命令行包含"sw_if_index N",则设置sw_if_index为N
                                         // 类比:就像"解析命令行参数(可选)"
  
  int show_acl = unformat (input, "acl");            // 检查是否要求显示ACL详情
                                         // 如果命令行包含"acl",则show_acl为真
                                         // 类比:就像"检查是否要求显示'规则表'详情"
  
  int detail = unformat (input, "detail");           // 检查是否要求显示详情
                                         // 如果命令行包含"detail",则detail为真
                                         // 类比:就像"检查是否要求显示详情"

  acl_plugin_show_interface (am, sw_if_index, show_acl, detail);  // 显示接口ACL配置
                                         // 类比:就像"显示'通道'的'规则表'配置"
  
  return error;                                        // 返回错误(如果有)
                                         // 类比:就像"返回错误(如果有)"
}

使用示例

bash 复制代码
# 显示所有接口的ACL配置
show acl-plugin interface

# 显示指定接口的ACL配置
show acl-plugin interface sw_if_index 0

# 显示指定接口的ACL配置(包括ACL详情)
show acl-plugin interface sw_if_index 0 acl

# 显示指定接口的ACL配置(包括所有详情)
show acl-plugin interface sw_if_index 0 acl detail

类比:就像"查看通道配置":

  • 显示所有通道:显示所有已配置的通道
  • 显示指定通道:显示指定编号的通道
  • 显示规则表详情:显示通道的规则表详情
  • 显示所有详情:显示通道的所有详情(包括规则集合索引)

14.4.4 Show Sessions命令:show acl-plugin sessions

show acl-plugin sessions 命令用于显示Flow-aware会话信息:

c 复制代码
//3442:3586:src/plugins/acl/acl.c
acl_plugin_show_sessions (acl_main_t * am,
			  u32 show_session_thread_id,
			  u32 show_session_session_index)
{
  vlib_main_t *vm = am->vlib_main;                    // 获取VPP主结构体
                                         // 类比:就像"获取'系统主结构体'"
  
  u16 wk;                                              // Worker线程索引
                                         // 类比:就像"安检通道索引"
  
  vnet_interface_main_t *im = &am->vnet_main->interface_main;  // 获取接口主结构体
                                         // 类比:就像"获取'通道主结构体'"
  
  vnet_sw_interface_t *swif;                          // 接口指针
                                         // 类比:就像"通道指针"
  
  u64 now = clib_cpu_time_now ();                     // 获取当前时间(CPU时钟)
                                         // clib_cpu_time_now:获取当前CPU时钟时间
                                         // 类比:就像"获取当前时间"
  
  u64 clocks_per_second = am->vlib_main->clib_time.clocks_per_second;  // 获取每秒时钟数
                                         // clocks_per_second:每秒CPU时钟数(用于时间转换)
                                         // 类比:就像"获取每秒时钟数"

  // ========== 第一部分:显示会话统计信息 ==========
  
  {
    u64 n_adds = am->fa_session_total_adds;           // 会话总添加数
                                         // fa_session_total_adds:Flow-aware会话总添加数
                                         // 类比:就像"常客总登记数"
    
    u64 n_dels = am->fa_session_total_dels;           // 会话总删除数
                                         // fa_session_total_dels:Flow-aware会话总删除数
                                         // 类比:就像"常客总注销数"
    
    u64 n_deact = am->fa_session_total_deactivations;  // 会话总停用数
                                         // fa_session_total_deactivations:Flow-aware会话总停用数
                                         // 类比:就像"常客总停用数"
    
    vlib_cli_output (vm, "Sessions total: add %lu - del %lu = %lu", n_adds,
		     n_dels, n_adds - n_dels);          // 显示会话总数(添加数 - 删除数)
                                         // 类比:就像"显示常客总数(登记数 - 注销数)"
    
    vlib_cli_output (vm, "Sessions active: add %lu - deact %lu = %lu", n_adds,
		     n_deact, n_adds - n_deact);         // 显示活跃会话数(添加数 - 停用数)
                                         // 类比:就像"显示活跃常客数(登记数 - 停用数)"
    
    vlib_cli_output (vm, "Sessions being purged: deact %lu - del %lu = %lu",
		     n_deact, n_dels, n_deact - n_dels);  // 显示正在清理的会话数(停用数 - 删除数)
                                         // 类比:就像"显示正在清理的常客数(停用数 - 注销数)"
  }
  
  vlib_cli_output (vm, "now: %lu clocks per second: %lu", now,
		   clocks_per_second);                  // 显示当前时间和每秒时钟数
                                         // 类比:就像"显示当前时间和每秒时钟数"

  // ========== 第二部分:显示每个Worker线程的会话信息 ==========
  
  vlib_cli_output (vm, "\n\nPer-thread data:");
                                         // 类比:就像"显示每个'安检通道'的数据"
  
  for (wk = 0; wk < vec_len (am->per_worker_data); wk++)  // 遍历所有Worker线程
    {
                                         // 类比:就像"遍历所有'安检通道'"
      
      acl_fa_per_worker_data_t *pw = &am->per_worker_data[wk];  // 获取Worker线程数据
                                         // per_worker_data:每个Worker线程的数据(包括会话池、超时列表等)
                                         // 类比:就像"获取'安检通道'的数据(包括'常客池'、'超时列表'等)"
      
      vlib_cli_output (vm, "Thread #%d:", wk);       // 显示线程索引
                                         // 类比:就像"显示'安检通道编号'"
      
      if (show_session_thread_id == wk
	  && show_session_session_index < pool_len (pw->fa_sessions_pool))  // 如果要求显示特定会话
	{
	  vlib_cli_output (vm, "  session index %u:",
			   show_session_session_index);  // 显示会话索引
                                         // 类比:就像"显示'常客索引'"
	  
	  fa_session_t *sess =
	    pw->fa_sessions_pool + show_session_session_index;  // 获取会话指针
                                         // fa_sessions_pool:Worker线程的会话池
                                         // 类比:就像"获取'常客档案'指针"
	  
	  u64 *m = (u64 *) & sess->info;    // 获取会话信息的指针(转换为u64数组)
                                         // sess->info:会话信息(5-tuple等)
                                         // 类比:就像"获取'常客信息'指针"
	  
	  vlib_cli_output (vm,
			   "    info: %016llx %016llx %016llx %016llx %016llx %016llx",
			   m[0], m[1], m[2], m[3], m[4], m[5]);  // 显示会话信息(6个u64)
                                         // 类比:就像"显示'常客信息'(6个u64)"
	  
	  vlib_cli_output (vm, "    sw_if_index: %u", sess->sw_if_index);  // 显示接口索引
                                         // 类比:就像"显示'通道编号'"
	  
	  vlib_cli_output (vm, "    tcp_flags_seen: %x",
			   sess->tcp_flags_seen.as_u16);  // 显示已见的TCP标志
                                         // tcp_flags_seen:已见的TCP标志(用于TCP会话跟踪)
                                         // 类比:就像"显示已见的'TCP标志'"
	  
	  vlib_cli_output (vm, "    last active time: %lu",
			   sess->last_active_time);        // 显示最后活跃时间
                                         // last_active_time:会话最后活跃时间(CPU时钟)
                                         // 类比:就像"显示最后活跃时间"
	  
	  vlib_cli_output (vm, "    thread index: %u", sess->thread_index);  // 显示线程索引
                                         // 类比:就像"显示'安检通道编号'"
	  
	  vlib_cli_output (vm, "    link enqueue time: %lu",
			   sess->link_enqueue_time);       // 显示链表入队时间
                                         // link_enqueue_time:会话加入超时链表的时间
                                         // 类比:就像"显示'超时列表'入队时间"
	  
	  vlib_cli_output (vm, "    link next index: %u",
			   sess->link_next_idx);            // 显示链表下一个索引
                                         // link_next_idx:超时链表下一个会话的索引
                                         // 类比:就像"显示'超时列表'下一个'常客索引'"
	  
	  vlib_cli_output (vm, "    link prev index: %u",
			   sess->link_prev_idx);            // 显示链表上一个索引
                                         // link_prev_idx:超时链表上一个会话的索引
                                         // 类比:就像"显示'超时列表'上一个'常客索引'"
	  
	  vlib_cli_output (vm, "    link list id: %u", sess->link_list_id);  // 显示链表ID
                                         // link_list_id:超时链表ID(超时类型)
                                         // 类比:就像"显示'超时列表ID'(超时类型)"
	}
      
      // ========== 第三部分:显示每个接口的会话统计 ==========
      
      vlib_cli_output (vm, "  connection add/del stats:", wk);  // 显示连接添加/删除统计
                                         // 类比:就像"显示'常客登记/注销'统计"
      
      pool_foreach (swif, im->sw_interfaces)         // 遍历所有接口
         {
                                         // 类比:就像"遍历所有'通道'"
          
          u32 sw_if_index = swif->sw_if_index;        // 获取接口索引
                                         // 类比:就像"获取'通道编号'"
          
          u64 n_adds =
            (sw_if_index < vec_len (pw->fa_session_adds_by_sw_if_index) ?
             pw->fa_session_adds_by_sw_if_index[sw_if_index] :
             0);                                       // 获取该接口的会话添加数
                                         // fa_session_adds_by_sw_if_index:每个接口的会话添加数
                                         // 类比:就像"获取该'通道'的'常客登记数'"
          
          u64 n_dels =
            (sw_if_index < vec_len (pw->fa_session_dels_by_sw_if_index) ?
             pw->fa_session_dels_by_sw_if_index[sw_if_index] :
             0);                                       // 获取该接口的会话删除数
                                         // fa_session_dels_by_sw_if_index:每个接口的会话删除数
                                         // 类比:就像"获取该'通道'的'常客注销数'"
          
          u64 n_epoch_changes =
            (sw_if_index < vec_len (pw->fa_session_epoch_change_by_sw_if_index) ?
             pw->fa_session_epoch_change_by_sw_if_index[sw_if_index] :
             0);                                       // 获取该接口的Policy Epoch变更数
                                         // fa_session_epoch_change_by_sw_if_index:每个接口的Policy Epoch变更数
                                         // Policy Epoch变更:当ACL规则变更时,会话需要重新分类
                                         // 类比:就像"获取该'通道'的'策略版本变更数'"
          
          vlib_cli_output (vm,
                           "    sw_if_index %d: add %lu - del %lu = %lu; epoch chg: %lu",
                           sw_if_index,
                           n_adds,
                           n_dels,
                           n_adds -
                           n_dels,
                           n_epoch_changes);           // 显示该接口的会话统计
                                         // 类比:就像"显示该'通道'的'常客统计'"
        }

      // ========== 第四部分:显示超时链表信息 ==========
      
      vlib_cli_output (vm, "  connection timeout type lists:", wk);  // 显示连接超时类型列表
                                         // 类比:就像"显示'常客超时类型列表'"
      
      u8 tt = 0;
      for (tt = 0; tt < ACL_N_TIMEOUTS; tt++)         // 遍历所有超时类型
	{
                                         // ACL_N_TIMEOUTS:超时类型数量(TCP、UDP、Transient等)
                                         // 类比:就像"遍历所有'超时类型'"
	  
	  u32 head_session_index = pw->fa_conn_list_head[tt];  // 获取超时链表的头索引
                                         // fa_conn_list_head:每个超时类型的链表头索引
                                         // 类比:就像"获取'超时列表'的头索引"
	  
	  vlib_cli_output (vm, "  fa_conn_list_head[%d]: %d", tt,
			   head_session_index);              // 显示超时链表的头索引
                                         // 类比:就像"显示'超时列表'的头索引"
	  
	  if (~0 != head_session_index)                     // 如果链表不为空
	    {
	      fa_session_t *sess = pw->fa_sessions_pool + head_session_index;  // 获取头会话指针
                                         // 类比:就像"获取头'常客档案'指针"
	      
	      vlib_cli_output (vm, "    last active time: %lu",
			       sess->last_active_time);      // 显示头会话的最后活跃时间
                                         // 类比:就像"显示头'常客'的最后活跃时间"
	      
	      vlib_cli_output (vm, "    link enqueue time: %lu",
			       sess->link_enqueue_time);     // 显示头会话的链表入队时间
                                         // 类比:就像"显示头'常客'的'超时列表'入队时间"
	    }
	}

      // ========== 第五部分:显示清理进程信息 ==========
      
      vlib_cli_output (vm, "  Next expiry time: %lu", pw->next_expiry_time);  // 显示下一个过期时间
                                         // next_expiry_time:下一个会话过期时间(CPU时钟)
                                         // 类比:就像"显示下一个'常客过期时间'"
      
      vlib_cli_output (vm, "  Requeue until time: %lu",
		       pw->requeue_until_time);            // 显示重新入队截止时间
                                         // requeue_until_time:重新入队截止时间(用于批量处理)
                                         // 类比:就像"显示重新入队截止时间"
      
      vlib_cli_output (vm, "  Current time wait interval: %lu",
		       pw->current_time_wait_interval);   // 显示当前TIME_WAIT间隔
                                         // current_time_wait_interval:当前TIME_WAIT间隔(用于TCP会话)
                                         // 类比:就像"显示当前'TIME_WAIT间隔'"
      
      vlib_cli_output (vm, "  Count of deleted sessions: %lu",
		       pw->cnt_deleted_sessions);          // 显示已删除会话数
                                         // cnt_deleted_sessions:已删除会话数(统计信息)
                                         // 类比:就像"显示已删除'常客数'"
      
      vlib_cli_output (vm, "  Delete already deleted: %lu",
		       pw->cnt_already_deleted_sessions);  // 显示重复删除会话数
                                         // cnt_already_deleted_sessions:重复删除会话数(统计信息)
                                         // 类比:就像"显示重复删除'常客数'"
      
      vlib_cli_output (vm, "  Session timers restarted: %lu",
		       pw->cnt_session_timer_restarted);   // 显示会话定时器重启数
                                         // cnt_session_timer_restarted:会话定时器重启数(统计信息)
                                         // 类比:就像"显示'常客定时器'重启数"
      
      vlib_cli_output (vm, "  Swipe until this time: %lu",
		       pw->swipe_end_time);                // 显示清理截止时间
                                         // swipe_end_time:清理截止时间(用于批量清理)
                                         // 类比:就像"显示清理截止时间"
      
      vlib_cli_output (vm, "  sw_if_index serviced bitmap: %U",
		       format_bitmap_hex, pw->serviced_sw_if_index_bitmap);  // 显示已服务接口位图
                                         // serviced_sw_if_index_bitmap:已服务接口位图(用于清理进程)
                                         // 类比:就像"显示已服务'通道位图'"
      
      vlib_cli_output (vm, "  pending clear intfc bitmap : %U",
		       format_bitmap_hex,
		       pw->pending_clear_sw_if_index_bitmap);  // 显示待清理接口位图
                                         // pending_clear_sw_if_index_bitmap:待清理接口位图(用于清理进程)
                                         // 类比:就像"显示待清理'通道位图'"
      
      vlib_cli_output (vm, "  clear in progress: %u", pw->clear_in_process);  // 显示清理进行中标志
                                         // clear_in_process:清理进行中标志(用于防止重复清理)
                                         // 类比:就像"显示清理进行中标志"
      
      vlib_cli_output (vm, "  interrupt is pending: %d",
		       pw->interrupt_is_pending);           // 显示中断挂起标志
                                         // interrupt_is_pending:中断挂起标志(用于清理进程)
                                         // 类比:就像"显示中断挂起标志"
      
      vlib_cli_output (vm, "  interrupt is needed: %d",
		       pw->interrupt_is_needed);            // 显示中断需要标志
                                         // interrupt_is_needed:中断需要标志(用于清理进程)
                                         // 类比:就像"显示中断需要标志"
      
      vlib_cli_output (vm, "  interrupt is unwanted: %d",
		       pw->interrupt_is_unwanted);          // 显示中断不需要标志
                                         // interrupt_is_unwanted:中断不需要标志(用于清理进程)
                                         // 类比:就像"显示中断不需要标志"
      
      vlib_cli_output (vm, "  interrupt generation: %d",
		       pw->interrupt_generation);           // 显示中断生成数
                                         // interrupt_generation:中断生成数(用于清理进程)
                                         // 类比:就像"显示中断生成数"
      
      vlib_cli_output (vm, "  received session change requests: %d",
		       pw->rcvd_session_change_requests);  // 显示接收的会话变更请求数
                                         // rcvd_session_change_requests:接收的会话变更请求数(用于多线程同步)
                                         // 类比:就像"显示接收的'常客变更请求数'"
      
      vlib_cli_output (vm, "  sent session change requests: %d",
		       pw->sent_session_change_requests);   // 显示发送的会话变更请求数
                                         // sent_session_change_requests:发送的会话变更请求数(用于多线程同步)
                                         // 类比:就像"显示发送的'常客变更请求数'"
    }
  
  // ========== 第六部分:显示清理线程计数器 ==========
  
  vlib_cli_output (vm, "\n\nConn cleaner thread counters:");
                                         // 类比:就像"显示'清理线程'计数器"
  
#define _(cnt, desc) vlib_cli_output(vm, "             %20lu: %s", am->cnt, desc);  // 定义宏用于显示计数器
                                         // 类比:就像"定义宏用于显示计数器"
  
  foreach_fa_cleaner_counter;                        // 遍历所有清理线程计数器
                                         // foreach_fa_cleaner_counter:遍历所有清理线程计数器的宏
                                         // 类比:就像"遍历所有'清理线程'计数器"
  
#undef _
  
  vlib_cli_output (vm, "Interrupt generation: %d",
		   am->fa_interrupt_generation);          // 显示中断生成数
                                         // fa_interrupt_generation:全局中断生成数
                                         // 类比:就像"显示全局中断生成数"
  
  vlib_cli_output (vm,
		   "Sessions per interval: min %lu max %lu increment: %f ms current: %f ms",
		   am->fa_min_deleted_sessions_per_interval,  // 最小每间隔删除会话数
                                         // fa_min_deleted_sessions_per_interval:最小每间隔删除会话数(用于自适应调度)
                                         // 类比:就像"最小每间隔删除'常客数'"
		   
		   am->fa_max_deleted_sessions_per_interval,  // 最大每间隔删除会话数
                                         // fa_max_deleted_sessions_per_interval:最大每间隔删除会话数(用于自适应调度)
                                         // 类比:就像"最大每间隔删除'常客数'"
		   
		   am->fa_cleaner_wait_time_increment * 1000.0,  // 等待时间增量(毫秒)
                                         // fa_cleaner_wait_time_increment:清理进程等待时间增量(用于自适应调度)
                                         // 类比:就像"等待时间增量(毫秒)"
		   
		   ((f64) am->fa_current_cleaner_timer_wait_interval) *
		   1000.0 / (f64) vm->clib_time.clocks_per_second);  // 当前等待间隔(毫秒)
                                         // fa_current_cleaner_timer_wait_interval:当前清理进程定时器等待间隔(CPU时钟)
                                         // 类比:就像"当前等待间隔(毫秒)"
  
  vlib_cli_output (vm, "Reclassify sessions: %d", am->reclassify_sessions);  // 显示重新分类会话标志
                                         // reclassify_sessions:重新分类会话标志(当ACL规则变更时,是否重新分类会话)
                                         // 类比:就像"显示重新分类'常客'标志"
}

函数总结

  1. 显示会话统计信息:显示会话总数、活跃会话数、正在清理的会话数
  2. 显示每个Worker线程的会话信息:显示每个Worker线程的会话详情
  3. 显示每个接口的会话统计:显示每个接口的会话添加/删除统计
  4. 显示超时链表信息:显示每个超时类型的链表头信息
  5. 显示清理进程信息:显示清理进程的各种状态和统计信息
  6. 显示清理线程计数器:显示清理线程的各种计数器

使用示例

bash 复制代码
# 显示所有会话信息
show acl-plugin sessions

# 显示指定线程和会话的详情
show acl-plugin sessions thread 0 index 100

类比:就像"查看常客信息":

  • 显示常客统计:显示常客总数、活跃常客数、正在清理的常客数
  • 显示每个通道的常客信息:显示每个安检通道的常客详情
  • 显示每个通道的常客统计:显示每个通道的常客登记/注销统计
  • 显示超时列表信息:显示每个超时类型的列表头信息
  • 显示清理进程信息:显示清理进程的各种状态和统计信息

14.5 调试宏:如何"输出调试信息"?

14.5.1 调试宏的概念

调试宏(Debug Macros):用于在调试模式下输出调试信息的宏定义。

为什么需要调试宏?

  1. 开发调试:开发时使用,输出详细的调试信息
  2. 问题诊断:当出现问题时,可以启用调试宏找出原因
  3. 性能分析:性能分析时使用,输出性能相关数据

类比:就像"调试输出":

  • 开发调试:开发时使用,输出详细的调试信息
  • 问题诊断:当出现问题时,可以启用调试输出找出原因
  • 性能分析:性能分析时使用,输出性能相关数据

14.5.2 Hash查找调试宏:DBG0、DBG、DBG_UNIX_LOG

Hash查找模块使用调试宏来输出调试信息:

c 复制代码
//18:32:src/plugins/acl/hash_lookup_private.h
#define ACL_HASH_LOOKUP_DEBUG 0                        // Hash查找调试级别(0=关闭,1=警告,2=详细)
                                         // ACL_HASH_LOOKUP_DEBUG:Hash查找调试级别
                                         // 0:关闭调试(不输出任何信息)
                                         // 1:警告级别(只输出警告信息)
                                         // 2:详细级别(输出所有调试信息)
                                         // 类比:就像"'Hash查找调试级别'(0=关闭,1=警告,2=详细)"

#if ACL_HASH_LOOKUP_DEBUG == 1                         // 如果调试级别为1(警告级别)
#define DBG0(...) clib_warning(__VA_ARGS__)           // DBG0:输出警告信息
                                         // clib_warning:VPP的警告输出函数
                                         // __VA_ARGS__:可变参数(传递给clib_warning)
                                         // 类比:就像"输出警告信息"
#define DBG(...)                                        // DBG:不输出(空宏)
                                         // 类比:就像"不输出(空宏)"
#define DBG_UNIX_LOG(...)                               // DBG_UNIX_LOG:不输出(空宏)
                                         // 类比:就像"不输出(空宏)"
#elif ACL_HASH_LOOKUP_DEBUG == 2                       // 如果调试级别为2(详细级别)
#define DBG0(...) clib_warning(__VA_ARGS__)           // DBG0:输出警告信息
                                         // 类比:就像"输出警告信息"
#define DBG(...) do { void *prevheap = clib_mem_set_heap (vlib_global_main.heap_base); vlib_cli_output(&vlib_global_main, __VA_ARGS__); clib_mem_set_heap (prevheap); } while (0)
                                         // DBG:输出CLI信息
                                         // clib_mem_set_heap:设置内存堆(用于CLI输出)
                                         // vlib_cli_output:VPP的CLI输出函数
                                         // 作用:输出调试信息到CLI(需要设置正确的内存堆)
                                         // 类比:就像"输出CLI信息"
#define DBG_UNIX_LOG(...) clib_unix_warning(__VA_ARGS__)  // DBG_UNIX_LOG:输出Unix警告信息
                                         // clib_unix_warning:VPP的Unix警告输出函数
                                         // 作用:输出调试信息到Unix日志
                                         // 类比:就像"输出Unix警告信息"
#else                                                   // 如果调试级别为0(关闭)
#define DBG0(...)                                       // DBG0:不输出(空宏)
                                         // 类比:就像"不输出(空宏)"
#define DBG(...)                                        // DBG:不输出(空宏)
                                         // 类比:就像"不输出(空宏)"
#define DBG_UNIX_LOG(...)                               // DBG_UNIX_LOG:不输出(空宏)
                                         // 类比:就像"不输出(空宏)"
#endif

调试宏总结

  1. DBG0:输出警告信息(调试级别1和2)
  2. DBG:输出CLI信息(仅调试级别2)
  3. DBG_UNIX_LOG:输出Unix警告信息(仅调试级别2)

使用示例

c 复制代码
// 在Hash查找代码中使用
DBG0("Hash lookup failed for ACL %d", acl_index);
DBG("Hash table entry: key=%llx value=%llx", key, value);
DBG_UNIX_LOG("Hash collision detected");

类比:就像"调试输出级别":

  • 警告级别:只输出警告信息(DBG0)
  • 详细级别:输出所有调试信息(DBG0、DBG、DBG_UNIX_LOG)
  • 关闭:不输出任何信息(所有宏为空)

14.6 故障排查方法:如何"诊断和解决问题"?

14.6.1 故障排查的一般流程

故障排查流程

  1. 收集信息:使用Show命令收集系统状态信息
  2. 启用Trace:启用Trace功能跟踪数据包处理过程
  3. 启用ELOG:启用ELOG功能记录系统事件
  4. 启用调试宏:启用调试宏输出详细调试信息
  5. 分析日志:分析Trace、ELOG和调试输出找出问题
  6. 修复问题:根据分析结果修复问题

类比:就像"故障排查流程":

  1. 收集信息:使用"查看系统状态"收集系统状态信息
  2. 启用检查记录:启用"检查记录"跟踪旅客检查过程
  3. 启用系统日志:启用"系统日志"记录系统事件
  4. 启用调试输出:启用"调试输出"输出详细调试信息
  5. 分析日志:分析"检查记录"、"系统日志"和"调试输出"找出问题
  6. 修复问题:根据分析结果修复问题

14.6.2 常见问题和解决方案

问题1:数据包被错误处理

排查步骤

  1. 启用Trace :使用trace add命令启用数据包Trace
  2. 查看Trace :使用show trace命令查看Trace信息
  3. 分析Trace:分析Trace信息找出问题(如匹配的ACL、规则等)

类比:就像"排查旅客被错误处理":

  1. 启用检查记录:启用"检查记录"跟踪旅客检查过程
  2. 查看检查记录:查看"检查记录"信息
  3. 分析检查记录:分析"检查记录"找出问题(如匹配的规则表、规则等)

问题2:会话未正确创建

排查步骤

  1. 查看会话统计 :使用show acl-plugin sessions查看会话统计
  2. 启用ELOG:启用ELOG功能记录会话创建事件
  3. 分析ELOG:分析ELOG信息找出问题(如会话创建失败的原因等)

类比:就像"排查常客未正确登记":

  1. 查看常客统计:查看"常客统计"信息
  2. 启用系统日志:启用"系统日志"记录常客登记事件
  3. 分析系统日志:分析"系统日志"找出问题(如常客登记失败的原因等)

问题3:性能问题

排查步骤

  1. 查看性能计数器 :使用show acl-plugin sessions查看性能计数器
  2. 启用性能分析:启用性能分析功能(如ELOG、Trace等)
  3. 分析性能数据:分析性能数据找出瓶颈(如会话查找时间、ACL匹配时间等)

类比:就像"排查性能问题":

  1. 查看性能计数器:查看"性能计数器"信息
  2. 启用性能分析:启用性能分析功能(如系统日志、检查记录等)
  3. 分析性能数据:分析性能数据找出瓶颈(如常客查找时间、规则匹配时间等)

14.7 本章小结

通过这一章的详细讲解,我们了解了:

  1. 调试工具的概念:什么是调试工具,为什么需要它
  2. Trace功能 :如何跟踪数据包处理过程(maybe_trace_bufferformat_acl_plugin_trace
  3. ELOG功能 :如何记录系统事件(elog_acl_cond_trace_X1elog_acl_cond_trace_X2
  4. Show命令 :如何显示系统状态(show acl-plugin aclshow acl-plugin interfaceshow acl-plugin sessions
  5. 调试宏 :如何输出调试信息(DBG0DBGDBG_UNIX_LOG
  6. 故障排查方法:如何诊断和解决问题

核心要点

  • Trace功能:记录数据包的处理过程,用于问题诊断和流程验证
  • ELOG功能:记录系统事件,用于事件追踪和问题诊断
  • Show命令:显示系统状态,用于状态查看和问题诊断
  • 调试宏:在调试模式下输出调试信息,用于开发调试和问题诊断
  • 故障排查:使用Trace、ELOG、Show命令和调试宏诊断和解决问题

下一步:在下一章,我们会看到ACL插件的其他高级功能和扩展。


相关推荐
zzzzzz3101 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode1 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220702 天前
如何搭建本地yum源(上)
运维
大树885 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠5 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质5 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz5 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工5 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
网络研究院5 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展
酣大智5 天前
ARP代理--工作原理
运维·网络·arp·arp代理