Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

1. VPP安装

  1. 安装Ubuntu:版本:Ubuntu 20.04.4
  2. 保证ubuntu可上网(虚拟机网络配置)
  3. 更新apt,sudo apt-get update -y
  4. 安装git ,sudo apt install git
  5. 安装vim, sudo apt install vim
  6. 下载vpp源码 :git clone http://gerrit.fd.io/r/vpp 很慢很慢(git到的路径为当前路径)
  7. 安装一些必要的软件:sudo apt install make , sudo apt install make-guile ,sudo apt install openssh-server
  8. 下载关系依赖:make install-dep , make dpdk-install-dev (下载时没成功,不影响),make install-ext-deps #时间较长
  9. 编译:make build (也可执行:./extras/vagrant/build.sh)、make pkg-deb
  10. 自动配置大页:dpkg -i build-root/*.deb
  11. 安装DPDK
    1
    2
    3
    4
    5
    sudo apt install meson
    sudo apt install -y python3-pyelftools python-pyelftools
    git clone https://gitee.com/yaozuluo/dpdk.git
    cd dpdk
    meson -Dexamples=all build
  12. 启动VPP,暂且不用配置其他文件,只是看到VPP运行
    1
    2
    3
    systemctl enable vpp
    systemctl start vpp
    systemctl status vpp.service
  13. 测试是否安装成功:sudo vppctl

2. 实例

2.1 运行多个VPP

1
2
3
4
5
6
7
sudo rm -rf /usr/lib/x86_64-linux-gnu/vpp_plugins/dpdk_plugin.so                # 删除dpdk插件
sudo vpp unix {cli-listen /run/vpp/cli-vpp1.sock} api-segment { prefix vpp1 } # 创建VPP
# api-segment {前缀 vpp1} 告诉 vpp 如何以不同于默认值的方式命名 vpp 实例的 /dev/shm/ 中的文件。
# Unix {cli-listen /run/vpp/cli-VPP1.sock}“告诉VPP在由VPPCTL寻址时使用非默认套接字文件。
sudo vpp unix {nodaemon cli-listen /run/vpp/cli-vpp1.sock} api-segment { prefix vpp1 }
# 激活 nodaemon 选项以更好地了解正在发生的事情
sudo vppctl -s /run/vpp/cli-vpp1.sock # 运行VPP

2.2 创建虚拟网口(主机与VPP之间)

让VPP简单地跑通,使用命令创建网口,开启主机到VPP的通道,一般采用veth技术。

创建虚拟网口连接到vpp内部建立通讯:

1
2
3
4
5
6
ip link add vhost type veth peer name vpp       # 创建成对的虚拟网口vhost及vpp
ip link set vhost up
ip link set vpp up # 开启它俩
ip addr add 10.0.0.1/24 dev vhost # 为主机侧指定ip地址

sudo ip link del dev vpphost # 删除veth接口

VPP指令:

1
2
3
4
5
6
create host-interface name vpp              # 创建主机接口,用vpp
set int state host-vpp up # host-vpp是vpp生成的,可以用show int确认名称
set int ip address host-vpp 10.0.0.2/24 # 指定ip地址
show int # 能看到接收发送的一些数据统计信息

delete host-interface name vpp # 删除主机接口vpp

在主机用ping 10.0.0.2应该能通了,并且vpp里面通过show int能看到统计数据在变化;
主机通过tcpdump应该能抓到包。
VPP中使用trace跟踪:

1
2
3
4
5
6
trace add af-packet-input 10   
trace add memif-input 10 # 添加跟踪
show trace # 查看跟踪信息
clear trace # 清除跟踪缓存区
show ip neighbors # 检查ARP表
show ip fib # 查看路由表

关闭vpp进程,删除虚拟接口

1
2
3
ps -ef | grep vpp | awk '{print $2}'| xargs sudo kill
sudo ip link del dev vpp1host
sudo ip link del dev vpp1vpp2

2.3 连接两个VPP实例

MEMIF 是一种性能非常高的直接内存接口类型,可以在 VPP 实例之间用于形成拓扑。它使用控制通道的文件套接字来设置该共享内存。

在两个 vpp 实例之间创建内存接口:
在vpp1和vpp2上各创建一个 memif 接口:

1
2
3
4
create interface memif id 0 master
create interface memif id 0 slave
set int state memif0/0 up
set int ip address host-vpp 10.0.0.2/24

2.4 路由

  1. 将路由添加到 Linux 主机路由表
  2. 将路由添加到 vpp 路由表

设置路由:

1
ip route add 10.10.1.0/24 via 10.10.2.1     # via下一跳IP地址

2.5 切换

  1. 将接口与桥接域关联
  2. 创建环回接口
  3. 为桥接域创建 BVI(网桥虚拟接口)
  4. 检查桥接域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 显示桥
show bridge-domain [bridge-domain-id [detail|int|arp]]. # 命令格式
show bridge-domain # 显示所有桥接域
show bridge-domain 1 detail # 显示单个桥接域

# 设置Int L2桥接器
set interface l2 bridge <interface> <bridge-domain-id> [bvi] [shg]. # 命令格式
set int l2 bridge host-vpp1out 1 # 设置host-vpp1out到桥接域1
set int l2 bridge loop0 1 bvi # loop0设置为桥接虚拟接口(bvi)到桥接域 1

# 创建环路接口
create loopback interface
set int state loop0 up
set int ip address loop0 10.10.1.2/24

# 显示L2FIB详细
show l2fib [verbose | bd_id <nn> | bd_index <nn> | raw]. # 命令格式
show l2fib verbose

# ping测试指定源端口
ping 10.0.0.1 source loop0

2.6 源NAT

  1. 滥用网络命名空间以获取乐趣和利润
  2. 配置抢断地址
  3. 配置 snat 内部和外部接口
1
2
3
4
5
6
7
8
9
10
11
# 创建一个inside网络空间
sudo ip netns add inside

# 将接口 vpp1inside 移动到“inside”命名空间中
sudo ip link set dev vpp1insidehost up netns inside

# 将 IP 地址分配给 vpp1insidehost
sudo ip netns exec inside ip addr add 10.10.2.1/24 dev vpp1insidehost

# 在网络内创建路由
sudo ip netns exec inside ip route add 10.10.1.0/24 via 10.10.2.2

3. 源码

3.1 源码目录

目录名称 描述
build-data 构建元数据
build-root 构建输出目录
doxygen 文档生成器配置
dpdk DPDK补丁与构建基础设施
extras/libmemif memif的客户端库
src/examples VPP示例代码
src/svm VPP的捆绑插件目录
src/tests 共享虚拟内存分配库(Shared virtual memory)
src/vat 独立测试(不属于测试套件)
src/vlib VPP应用程序库
src/vlibapi VPP API库
src/vlibsocket VPP Socket I/O
src/vnet VPP网络
src/vpp VPP应用程序
src/vpp-api VPP应用程序API绑定
src/vppinfra VPP核心库
src/vpp/api 尚未重新放置在新地方的API绑定
test 单元测试和Python测试工具

3.2 源码分类

vpp数据平面分为四个不同的层:

  • 基础架构层:包括vppinfra,vlib,svm和二进制api库。源码:/src/{vppinfra, vlib, svm, vlibapi, vlibmemory}
  • 通用网络协议栈层:vnet。源码:/src/vnet
  • 应用程序shell:vpp。源码:/src/vpp
  • 日益丰富的数据平面插件。源码:/src/plugins

3.2.1 Vppinfra:

Vppinfra是一个基本的c-library服务集合,可以构建直接在裸机上运行的独立程序。它提供高性能动态数组,hash表,位图,高精度实时时钟支持,精细的日志记录和数据结构序列化。

  1. Vectors

    Vppinfra向量是无处不在的动态调整大小的数组,具有用户定义的“头”。许多vpppinfra数据结构(例如hash,堆,池)是具有各种不同头的向量。

    1
    2
    3
    4
    5
    6
    7
    8
    内存布局如下所示:
    User header (optional, uword aligned)
    Alignment padding (if needed)
    Vector length in elements
    User's pointer -> Vector element 0
    Vector element 1
    ...
    Vector element N-1

    如上所示,向量API处理指向向量第0个元素的指针。 如果指为空针则说明向量的有效长度为0。

    为了避免内存分配器的分配失败,通常在保留内存分配的同时将向量的长度重置为零。通过vec_reset_length(v)宏将向量长度字段设置为零。

    通常情况下不存在用户标头。用户头允许在vppinfra向量上构建其他数据结构。用户可以通过vec * _aligned宏指定数据元素的对齐方式。

    向量元素可以是任何C语言数据结构类型,例如 (int,double,struct )。许多宏都用_a类型的变体支持向量数据的对齐,而_h类型的变体支持非零长度向量头。_ha类型的变体支持以上两者。

    头或对齐相关宏类型的变体使用不一致将导致延迟以及各种失败问题。

  2. Bitmaps

    Vppinfra位图是动态的,使用vppinfra向量API来构建。

  3. Pools

    Vppinfra池结合了Vectors和Bitmaps,可以快速分配和释放具有独立生命周期的固定大小的数据结构。池非常适合给每个会话分配结构。

  4. Hashes

    Vppinfra提供了几种不通类型的哈希。

    在涉及数据包分类/会话查找相关的数据平面问题是通常使用/src/vppinfra/bihash_templat.[ch] 有界索引可扩展哈希值。Bihashes是线程安全的。,不需要读取锁。只需要一个简单的自旋锁确保在同一时刻一次只有一个线程写入一个entry。

    在涉及需要字符串精确匹配的控制平面代码中通常使用/src/vppinfra/hash.[ch]中的原始vppinfra哈希。

  5. Format

    Vppinfra中的Format大致相当于c语言中的printf。

    Format的第一个参数是(u8 *)向量,它附加当前格式化操作的结果。

    1
    2
    3
    4
    5
    调用示例:
    u8 * result;

    result = format (0, "junk = %d, ", junk);
    result = format (result, "more junk = %d\n", more_junk);

    NULL指针是完全正确的0长度向量。格式返回(u8 *)向量,而不是C字符串。如果要打印(u8 *)向量,需要使用“%v”格式字符串。

    如果想要一个(u8 *)向量同时也是一个正确的C字符串,可以使用以下任一方案:

    1
    2
    3
    4
    5
    6
    7
    8
    u8 * format_junk (u8 * s, va_list *va)
    {
    junk = va_arg (va, u32);
    s = format (s, "%s", junk);
    return s;
    }

    result = format (0, "junk = %U, format_junk, "This is some junk");
  6. Unformat

    Vppinfra的unformat与scanf有点类似,但是更加的通用。

    从C-string或(u8 *)向量初始化unformat_input_t,然后通过unformat()进行解析,如下所示:

    1
    2
    3
    4
    5
    unformat_input_t input;

    unformat_init_string (&input, "<some-C-string>");
    /* or */
    unformat_init_vector (&input, <u8-vector>);

    然后循环解析各个元素:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT) 
    {
    if (unformat (&input, "value1 %d", &value1))
    ;/* unformat sets value1 */
    else if (unformat (&input, "value2 %d", &value2)
    ;/* unformat sets value2 */
    else
    return clib_error_return (0, "unknown input '%U'", format_unformat_error,
    input);
    }
  7. Vppinfra errors and warnings

    vpp数据平面中的许多函数都具有clib_error_t *类型的返回值。如果返回NULL则clib_error_t *表示“正确,没有错误”。

    Clib_warning()是一种添加调试的便捷方式;clib警告预置的功能:自动添加行信息以明确定位消息源。

  8. Serialization

    Vppinfra序列化功能支持允许程序员轻松地序列化和反序列化复杂的数据结构。底层的原始序列化/反序列化函数使用网络字节序,因此在小端主机上序列化并在大端主机上反序列化不会存在结构问题。

  9. Event-logger, graphical event log viewer

    vppinfra事件记录器提供轻量级的精确时间戳(低于100ns)事件记录服务。代码:/src/vppinfra/{elog.c, elog.h}

    序列化支持可以轻松保存并合并一组事件日志。

    典型的事件定义和日志记录调用如下所示:

    1
    2
    3
    4
    5
    6
    7
    ELOG_TYPE_DECLARE (e) = 
    {
    .format = "get_or_create: %s",
    .format_args = "t4",
    .n_enum_strings = 2,
    .enum_strings = { "old", "new", },
    };

    如下所示:“.format”字段可能包含以下一个或多个实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    i1 - 8-bit unsigned integer
    i2 - 16-bit unsigned integer
    i4 - 32-bit unsigned integer
    i8 - 64-bit unsigned integer
    f4 - float
    f8 - double
    s - 以NULL结尾的字符串
    sN - N字节字符数组
    t1,2,4 - 每一个事件枚举ID
    T4 - 事件日志字符串表的偏移量

    vpp引擎有几个事件日志的调试CLI:

    1
    2
    3
    4
    5
    vpp# event-logger clear
    vpp# event-logger save <filename> # for security, writes into /tmp/<filename>.
    # <filename> must not contain '.' or '/' characters
    vpp# show event-logger [all] [<nnn>] # display the event log
    # by default, the last 250 entries

    事件日志默认为128K条entry。 命令行参数“vlib {elog-events }”配置事件日志的entry大小。

3.2.2 VLIB:

Vlib提供向量处理支持,包括图形节点调度,可靠多播支持,超轻量级协作多任务线程,CLI,.DLL插件支持,物理内存和Linux epoll支持。

  1. Node Graph Initialization

    vlib数据包处理应用程序总是定义一组图节点来处理数据包。通过VLIB_REGISTER_NODE宏来构造vlib_node_registration_t数据结构。在运行时,框架将这样的注册集处理成有向图。在运行时向图添加节点很容易,该框架不支持删除节点。

    1
    2
    3
    4
    5
    6
    7
    vlib_node_registration_t的类型成员函数如下:

    VLIB_NODE_TYPE_PRE_INPUT:在所有其他节点类型之前运行
    VLIB_NODE_TYPE_INPUT:在pre_input节点之后尽可能的经常运行
    VLIB_NODE_TYPE_INTERNAL:仅当通过添加待处理帧进行处理而明确地使其可运行时
    VLIB_NODE_TYPE_PROCESS:只有在明确地设置为runnable时。“进程”节点实际上是协作的多任务线程。
    他们必须在相当短的时间后明确暂停。

    要准确理解图节点调度程序,请阅读:/src/vlib/main.c:vlib_main_loop

  2. Graph node dispatcher

    Vlib_main_loop()调度图节点。

4.B站

评论