一、编译安装
1. 克隆源码
git clone -b stable/1801 https://github.com/FDio/vpp.git
cd vpp
2. 执行编译脚本
./extras/vagrant/build.sh && make
这个脚本会自动处理依赖、配置编译环境,然后开始编译。编译完成后会在 build-root 目录生成 .deb 安装包。
3. 进入编译输出目录
cd build-root
4. 安装生成的 .deb 包
按以下顺序执行安装命令:
dpkg -i vpp-lib_18.01.2-1~g9b554f3_amd64.deb
dpkg -i vpp_18.01.2-1~g9b554f3_amd64.deb
dpkg -i vpp-dev_18.01.2-1~g9b554f3_amd64.deb
dpkg -i vpp-plugins_18.01.2-1~g9b554f3_amd64.deb
5. 验证与配置
安装完成后,VPP 的主配置文件会生成在 /etc/vpp/startup.conf,你可以根据需要修改这个文件来配置接口、IP 等参数。
二、启动与命令
1. VPP 服务启动 / 停止 / 状态管理(系统层面)
这些命令用于控制 VPP 服务的启停,需要 root 权限执行:
| 功能 | 命令 | 说明 |
|---|---|---|
| 启动 VPP 服务 | systemctl start vpp |
常规启动(推荐) |
| 设置开机自启 | systemctl enable vpp |
服务器重启后自动启动 VPP |
| 停止 VPP 服务 | systemctl stop vpp |
优雅停止服务 |
| 重启 VPP 服务 | systemctl restart vpp |
配置修改后生效常用 |
| 查看 VPP 运行状态 | systemctl status vpp |
查看是否运行、报错信息等 |
| 手动前台启动(调试) | vpp -c /etc/vpp/startup.conf |
直接启动,日志输出到终端,适合调试 |
| 手动后台启动 | vpp -c /etc/vpp/startup.conf -d |
后台启动,进程脱离终端 |
示例:查看 VPP 状态
systemctl status vpp
# 正常输出会显示 "active (running)"
2. VPP 交互命令行(CLI)核心操作
VPP 提供专属的交互命令行,是日常操作的核心入口:
2.1 进入 VPP 交互终端
vppctl # 最简方式(推荐)
# 或
vpp -c /etc/vpp/startup.conf # 启动并直接进入CLI
进入后会看到提示符 vpp#,所有操作都在这个提示符下执行。
2.2 基础信息查看(高频,基于 help 输出)
| 功能 | VPP CLI 命令 | 说明 |
|---|---|---|
| 查看所有接口 | show interface |
列出所有网口、状态、MAC、MTU 等 |
| 查看接口详细信息 | show interface <接口名> |
例:show interface GigabitEthernet0/0/0 |
| 查看 IP 地址配置 | show ip address |
列出所有接口的 IP 配置(IPv4/IPv6 通用) |
| 查看路由表 | show ip route |
查看静态 / 动态路由 |
| 查看 VPP 版本 | show version |
确认安装的 VPP 版本 |
| 查看运行状态 / 统计 | show run |
查看 VPP 运行时配置和状态 |
| 查看数据包统计 | show interface counters |
查看各接口收发包、丢包、错误数 |
| 查看内存使用 | show memory |
查看 VPP 内存分配情况(含 DPDK 相关) |
| 查看 DPDK 端口 | show dpdk ports |
查看 DPDK 管理的网卡(核心关联) |
| 查看错误日志 | show errors |
查看 VPP 运行中的错误信息 |
| 查看命令历史 | history |
显示当前 CLI 会话的命令执行历史 |
2.3 接口配置(核心操作,基于 help 输出)
| 功能 | VPP CLI 命令 | 说明 |
|---|---|---|
| 启用接口 | set interface state <接口名> up |
例:set interface state GigabitEthernet0/0/0 up |
| 禁用接口 | set interface state <接口名> down |
停用指定接口 |
| 配置 IP 地址 | set ip address <接口名> <IP / 掩码> |
例:set ip address GigabitEthernet0/0/0 192.168.1.1/24 |
| 清除接口 IP | clear ip address <接口名> <IP / 掩码> |
移除指定接口的 IP |
| 配置 MTU | set interface mtu <接口名> <数值> |
例:set interface mtu GigabitEthernet0/0/0 1500 |
| 开启 IP4 转发 | set ip forwarding enable |
通用语法(替代旧版本专属语法) |
| 关闭 IP4 转发 | set ip forwarding disable |
关闭 IPv4 转发功能 |
2.4 路由配置(基于 help 输出)
| 功能 | VPP CLI 命令 | 说明 |
|---|---|---|
| 添加静态路由 | ip route add <目标网段> via <下一跳> <出接口> |
例:ip route add 10.0.0.0/8 via 192.168.1.254 GigabitEthernet0/0/0 |
| 删除静态路由 | ip route del <目标网段> |
例:ip route del 10.0.0.0/8 |
| 添加默认路由 | ip route add 0.0.0.0/0 via <下一跳> <出接口> |
例:ip route add 0.0.0.0/0 via 192.168.1.254 GigabitEthernet0/0/0 |
2.5 调试 / 排障命令(基于 help 输出)
| 功能 | VPP CLI 命令 | 说明 |
|---|---|---|
| 开启接口抓包 | pcap trace add <接口名> rx/tx both |
例:pcap trace add GigabitEthernet0/0/0 both(捕获收发包) |
| 查看抓包内容 | show pcap trace |
查看捕获的数据包 |
| 清除抓包缓存 | clear pcap trace |
清空抓包数据 |
| 数据包追踪 | trace <相关参数> |
启动 / 查看数据包追踪(替代旧版抓包) |
| 测试接口连通性 | ping <ip-addr> [repeat <cnt>] |
例:ping 192.168.1.254 repeat 5(repeat 指定包数) |
| 进阶 ping 测试 | ping ipv4 192.168.1.254 size 1500 interval 1 repeat 10 verbose |
指定包大小、间隔、次数、详细输出 |
2.6 退出 CLI
quit # 或 exit / Ctrl+D(均支持)
3. 常用运维快捷操作(Linux 终端,适配最新版本)
除了 CLI,日常还会用到这些终端命令:
# 查看 VPP 进程
ps -ef | grep vpp
# 查看 VPP 日志(核心排障)
tail -f /var/log/vpp/vpp.log
# 重载 VPP 配置(无需重启)
vppctl reload
# 批量执行 CLI 命令(脚本化)
# 将命令写入文件,比如 cmds.txt
echo -e "show interface\nshow ip route" > cmds.txt
# 批量执行
vppctl -f cmds.txt
# 重启 VPP 进程(CLI 内操作)
vpp# restart process
三、代码案例
在vpp/src/plugin目录下创建一个hdrcap目录,里面创建三个文件,这个案例的任务就是打印从dpdk网卡接收的数据的头部:

1.hdrcap.h
cpp
#ifndef __HDRCAP_H__
#define __HDRCAP_H__
// 仅引入VPP 18.01核心头文件(按原生插件顺序)
#include <vlib/vlib.h>
#include <vnet/vnet.h>
#include <vnet/ip/ip4_packet.h>
#include <vnet/ethernet/ethernet.h>
#include <vnet/feature/feature.h>
#include <vlib/cli.h>
// 极简全局结构(仅保留vnet_main)
typedef struct {
vnet_main_t *vnet_main;
} hdrcap_main_t;
// 全局实例声明
extern hdrcap_main_t hdrcap_main;
#endif /* __HDRCAP_H__ */
2.hdrcap.c
cpp
#include <vlib/vlib.h>
#include <vnet/plugin/plugin.h>
#include <vnet/interface.h> // ← 关键:提供 unformat_vnet_sw_interface
#include "hdrcap.h"
hdrcap_main_t hdrcap_main;
static clib_error_t *
hdrcap_enable_command_fn(struct vlib_main_t *vm,
unformat_input_t *input,
vlib_cli_command_t *cmd)
{
vnet_main_t *vnm = vnet_get_main();
u32 sw_if_index = ~0;
int disable = 0;
while (unformat_check_input(input) != UNFORMAT_END_OF_INPUT)
{
if (unformat(input, "disable"))
{
disable = 1;
}
else if (unformat(input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
{
// OK
}
else
{
return clib_error_return(0, "参数错误!用法: hdr cap <interface> [disable]");
}
}
if (sw_if_index == ~0)
{
return clib_error_return(0, "缺少接口名或索引!");
}
vnet_feature_enable_disable("ip4-unicast", "hdrcap", sw_if_index, !disable, 0, 0);
vnet_feature_enable_disable("ip4-local", "hdrcap", sw_if_index, !disable, 0, 0);
vlib_cli_output(vm, "hdrcap插件%s成功(接口索引:%d)", disable ? "禁用" : "启用", sw_if_index);
return NULL;
}
VLIB_CLI_COMMAND(hdrcap_command, static) = {
.path = "hdr cap",
.short_help = "hdr cap <interface> [disable] - 启用/禁用IP4报文捕获",
.function = hdrcap_enable_command_fn,
};
static clib_error_t *
hdrcap_init(struct vlib_main_t *vm)
{
hdrcap_main.vnet_main = vnet_get_main();
vlib_cli_output(vm, "hdrcap插件初始化完成!");
return NULL;
}
VLIB_INIT_FUNCTION(hdrcap_init);
VNET_FEATURE_INIT(hdrcap, static) = {
.arc_name = "ip4-unicast",
.node_name = "hdrcap",
.runs_before = VNET_FEATURES("ip4-lookup"),
};
VNET_FEATURE_INIT(hdrcap_local, static) = {
.arc_name = "ip4-local",
.node_name = "hdrcap",
};
/* *INDENT-OFF* */
VLIB_PLUGIN_REGISTER () = {
.version = "1.0",
.description = "Header Capture Plugin",
};
/* *INDENT-ON* */
/*
DBGvpp# set interface state GigabitEthernetb/0/0 up
DBGvpp# set interface ip address GigabitEthernetb/0/0 192.168.3.100/24
DBGvpp# hdr cap GigabitEthernetb/0/0
*/
3.hdrcap_node.c
cpp
#include "hdrcap.h"
// Trace格式化函数(极简)
static u8 *
format_hdrcap_trace(u8 *s, va_list *args)
{
return format(s, "HDRCAP: IP4报文捕获节点");
}
// 节点处理函数(VPP 18.01原生参数类型)
static uword
hdrcap_node_fn(struct vlib_main_t *vm,
struct vlib_node_runtime_t *node,
struct vlib_frame_t *frame)
{
// 帧数据指针
u32 n_left_from = frame->n_vectors;
u32 *from = vlib_frame_vector_args(frame);
u32 *to_next;
u32 n_left_to_next;
// 获取下一跳节点(IP4_LOOKUP)
vlib_get_next_frame(vm, node, node->cached_next_index, to_next, n_left_to_next);
// 遍历所有报文
while (n_left_from > 0) {
// 取单个报文缓冲区
u32 bi0 = from[0];
vlib_buffer_t *b0 = vlib_get_buffer(vm, bi0);
u8 *data = vlib_buffer_get_current(b0);
// 传递报文到下一跳(不修改报文,仅捕获)
to_next[0] = bi0;
from++;
to_next++;
n_left_from--;
n_left_to_next--;
// 解析以太网头和IP4头(极简格式化,避免复杂函数)
ethernet_header_t *eth = (ethernet_header_t *)data;
ip4_header_t *ip4 = (ip4_header_t *)(data + sizeof(ethernet_header_t));
// 打印核心报文信息(VPP 18.01兼容写法)
vlib_cli_output(vm, "\n=== IP4报文捕获 ===");
vlib_cli_output(vm, "源MAC: %02x:%02x:%02x:%02x:%02x:%02x",
eth->src_address[0], eth->src_address[1], eth->src_address[2],
eth->src_address[3], eth->src_address[4], eth->src_address[5]);
vlib_cli_output(vm, "目的MAC: %02x:%02x:%02x:%02x:%02x:%02x",
eth->dst_address[0], eth->dst_address[1], eth->dst_address[2],
eth->dst_address[3], eth->dst_address[4], eth->dst_address[5]);
vlib_cli_output(vm, "源IP: %d.%d.%d.%d",
ip4->src_address.as_u8[0], ip4->src_address.as_u8[1],
ip4->src_address.as_u8[2], ip4->src_address.as_u8[3]);
vlib_cli_output(vm, "目的IP: %d.%d.%d.%d",
ip4->dst_address.as_u8[0], ip4->dst_address.as_u8[1],
ip4->dst_address.as_u8[2], ip4->dst_address.as_u8[3]);
}
// 释放下一跳节点
vlib_put_next_frame(vm, node, node->cached_next_index, n_left_to_next);
// 返回处理的报文数
return frame->n_vectors;
}
// 注册节点(VPP 18.01极简写法,无多余字段)
VLIB_REGISTER_NODE(hdrcap_node) = {
.name = "hdrcap", // 节点名(必需)
.function = hdrcap_node_fn, // 处理函数(必需)
.vector_size = sizeof(u32), // 向量大小(固定为u32)
.format_trace = format_hdrcap_trace, // Trace函数(可选)
.type = VLIB_NODE_TYPE_INTERNAL, // 节点类型(必需)
.next_nodes = { // 下一跳节点(必需)
[0] = "ip4-lookup",
},
};
4.编译
编译的过程需要改三个文件:
先把plugin目录下的sixrd.am复制一份为hdrcap.am:

再编辑,包含上面的三个文件:

再在src目录下修改configure.am文件增加:

再修改src/plugin目录下的Makefile.am:

最后编译:
# 1. 清理旧的构建文件(可选,确保干净)
make wipe # 对应你提到的 make wipe
# 2. 重新生成自动构建文件(让 Makefile 修改生效)
autoreconf -if
# 3. 生成最终的 Makefile
./configure
# 4. 编译构建
make build # 或直接 make
# 5. 运行
make run
5.运行
在make run之前需要 启动大页:
# 1. 创建大页内存挂载目录(如果不存在)
mkdir -p /run/vpp/hugepages
# 2. 挂载 2MB 大页内存(临时生效,重启后失效)
mount -t hugetlbfs nodev /run/vpp/hugepages -o pagesize=2M
# 3. 预留 512 个 2MB 大页(匹配 DPDK 要求)
echo 512 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
启动make run之后,执行下面两个命令设置网卡:

执行命令:

最后ping就可以看到现象:

当然因为上面实例代码没有处理回显,所以ping的终端是100%丢包的。