3. VPP基础架构:VPP插件框架、Node图处理模型、VPP CLI与API
VPP(Vector Packet Processing)是FD.io项目下的核心数据平面技术,其设计哲学围绕高性能、可扩展性与模块化展开。理解VPP的基础架构是掌握DPDK与VPP集成的关键前提。本章将深入剖析VPP的三大支柱:插件框架、Node图处理模型以及CLI/API体系。
3.1 VPP插件框架
VPP的插件框架是其生态系统的核心。它允许开发者在不修改核心代码的情况下,动态添加新的协议、功能或硬件适配层。插件本质上是一个动态链接库(.so文件),在VPP启动时或运行时被加载。
3.1.1 插件生命周期与注册机制
每个VPP插件都遵循一套标准的生命周期管理流程,包括初始化、配置、启用和销毁。插件通过宏定义向VPP主框架注册自身。
VLIB_PLUGIN_REGISTER():声明插件的基本信息(名称、版本、描述)。VLIB_INIT_FUNCTION():注册插件的初始化函数,在VPP启动时调用。VLIB_CONFIG_FUNCTION():注册配置处理函数,用于解析启动配置。
以下是一个典型的插件注册代码片段,展示了如何将自定义节点集成到VPP的Node图中:
// 插件描述
VLIB_PLUGIN_REGISTER() = {
.version = "1.0",
.description = "My Custom DPDK Integration Plugin",
};
// 初始化函数
static clib_error_t *
my_plugin_init (vlib_main_t * vm)
{
// 注册自定义节点
vlib_node_register (vm, &my_custom_node);
return 0;
}
VLIB_INIT_FUNCTION (my_plugin_init);
// 配置函数
static clib_error_t *
my_plugin_config (vlib_main_t * vm, unformat_input_t * input)
{
// 解析配置参数
return 0;
}
VLIB_CONFIG_FUNCTION (my_plugin_config, "my-plugin");
3.1.2 插件与核心的交互模型
插件通过VPP提供的API与核心数据平面交互。这种交互模型确保了核心的稳定性,同时赋予了插件极大的灵活性。
| 交互方式 | 描述 | 典型场景 |
|---|---|---|
| Node注册 | 插件向VLIB注册新的处理节点,这些节点成为数据包处理图的一部分。 | 添加新的封装/解封装协议。 |
| API消息处理 | 插件注册二进制API消息处理函数,接收来自控制平面的指令。 | 动态配置路由表或ACL规则。 |
| 共享内存 | 插件可以直接访问VPP的共享内存堆(heap)或DPDK的mempool。 | 零拷贝数据交换。 |
| CLI注册 | 插件可以添加自定义的CLI命令,方便调试和运维。 | 查看插件内部统计信息。 |
vlib_buffer_t和vlib_node_t)来操作数据包,而不是直接操作DPDK的rte_mbuf。这可以保证插件在不同VPP版本间的兼容性。
3.2 Node图处理模型
VPP的核心处理模型是一个有向无环图(DAG),称为Node图。数据包不是通过传统的函数调用栈处理,而是在图中从一个节点(Node)游走到另一个节点。这种模型是VPP实现高性能的关键。
3.2.1 Node图架构
每个Node代表一个特定的处理步骤(如输入、查找、转发、输出)。Node之间通过Arc(弧)连接,定义了数据包的流动路径。VPP的调度器(Scheduler)负责将数据包向量(Vector)分发到各个Node。
3.2.2 向量化处理与调度
VPP的“V”代表Vector。与传统的逐包处理不同,VPP将多个数据包聚合成一个向量(Vector),然后一次性提交给Node处理。这种设计带来了显著的性能优势:
- 指令缓存友好:同一段处理代码被连续执行多次,减少了指令缓存的抖动。
- 数据缓存友好:连续的数据包描述符(
vlib_buffer_t)在内存中通常是连续的,提高了预取效率。 - 分摊调度开销:调度器每次调度的开销被向量中的多个数据包分摊。
- Dispatch:调度器从运行队列中取出一个Node。
- Vector Load:将待处理的包向量加载到Node的输入帧中。
- Process:Node的处理函数遍历向量中的每个包,执行逻辑。
- Enqueue:处理完成后,Node将包分发到下一个Node的输入队列。
3.3 VPP CLI与API
VPP提供了丰富的控制平面接口,包括交互式CLI和二进制API。这些接口是运维人员和管理系统与VPP数据平面交互的桥梁。
3.3.1 VPP CLI
VPP CLI是一个运行在VPP进程内部的交互式shell。它通过vppctl工具或直接连接到VPP的Unix socket进行访问。CLI命令用于查看状态、配置接口、调试问题等。
常用CLI命令示例:
# 查看VPP版本和运行状态
vpp# show version
vpp# show runtime
# 查看接口信息
vpp# show interface
vpp# show hardware
# 配置IP地址
vpp# set interface ip address GigabitEthernet0/8/0 192.168.1.1/24
# 查看节点图统计
vpp# show node counters
vpp# show graph
3.3.2 二进制API
二进制API是VPP与外部控制器(如ODL、ONAP或自定义管理应用)通信的主要方式。它基于共享内存(SHM)实现,具有低延迟和高吞吐量的特点。
| 特性 | CLI | 二进制API |
|---|---|---|
| 通信方式 | Unix Socket / Telnet | 共享内存(SHM) |
| 数据格式 | 人类可读文本 | 二进制消息(TLV) |
| 性能 | 中等(适合交互) | 高(适合自动化) |
| 典型用途 | 调试、手动配置 | 控制器集成、策略下发 |
以下是一个使用Python调用VPP二进制API的示例,展示了如何通过共享内存通道添加一条路由:
from vpp_papi import VPPApiClient
# 连接到VPP
vpp = VPPApiClient(apifiles=['/usr/share/vpp/api/core/ip.api'])
vpp.connect('my-app')
# 构造并发送API消息
req = {
'dst_address': '10.0.0.0',
'dst_address_length': 24,
'next_hop_address': '192.168.1.254',
'sw_if_index': 0xffffffff, # 表示通过路由表查找
}
reply = vpp.api.ip_add_del_route(is_add=1, **req)
# 检查返回状态
if reply.retval == 0:
print("Route added successfully.")
else:
print(f"Failed to add route, error code: {reply.retval}")
vpp.disconnect()