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插件的其他高级功能和扩展。


相关推荐
hanyi_qwe2 小时前
自动化运维工具 Ansible 集中化管理服务器
运维·自动化·ansible
wayuncn2 小时前
预算49800,99800,299800能买到什么样的算力服务器
运维·服务器·人工智能·算力一体机·ai算力服务器
CHrisFC2 小时前
疾控实验室信息系统选型指南:需求分析与实践路径
网络·安全·需求分析
qq_150841992 小时前
CVI基于TCP的C/S例程学习心得
服务器·网络·tcp/ip
zbtlink2 小时前
专业路由器 vs 电脑软件WiFi,差异在哪?
网络·智能路由器
wanhengidc2 小时前
云端虚拟 巨椰 云手机
运维·服务器·安全·智能手机·云计算
wanhengidc2 小时前
云手机 互联网 云端科技
运维·服务器·科技·智能手机·云计算
XXYBMOOO2 小时前
基于 HTML5 Canvas 的终端日志流可视化实现(支持多 Pane / 运维模式)
运维·前端·html5
元气满满-樱2 小时前
负载均衡-动静分离实验
运维·firefox·负载均衡