Skip to content

搭建实验环境

我们要搭什么

一个五节点的拓扑:两台内网主机、一台 VPP 网关、两台外网服务器。

为什么两侧各放两台而不是各一台?因为很多 NAT 的关键行为,只有在"多"的时候才看得见

  • 两台内网主机同时出去,才能看到 NAPT 是怎么靠端口把它们区分开的
  • 同一台内网主机访问两台不同的外网服务器,才能看到端点相关(ED)和端点无关(EI)的区别 —— 这是第 3 章的重头戏
  • 地址池有多个地址时,才能观察 VPP 是按什么规则给内网主机挑地址的

三个组件的分工

netlab 是最上层。你只描述"有哪些节点、怎么连、各自什么地址", 它负责算出每个节点的接口名、IP、路由,并生成对应的配置脚本。 containerlab 负责真正把容器拉起来、用 veth 把它们连上。Docker 跑容器。

我们的 topology.yml 全文如下,二十几行:

yaml
name: natbase
provider: clab

addressing:
  mgmt:
    ipv4: 172.30.101.0/24
    start: 100

nodes:
  h1: { device: linux }
  h2: { device: linux }
  nat: { device: vpp }
  s1: { device: linux }
  s2: { device: linux }

links:
  # 内网段:一条链路上挂三个节点 → netlab 自动做成一个二层网段
  - nat: { ipv4: 10.10.0.1/24 }
    h1:  { ipv4: 10.10.0.11/24 }
    h2:  { ipv4: 10.10.0.12/24 }
    prefix: { ipv4: 10.10.0.0/24 }

  # 外网段
  - nat: { ipv4: 203.0.113.1/24 }
    s1:  { ipv4: 203.0.113.11/24 }
    s2:  { ipv4: 203.0.113.12/24 }
    prefix: { ipv4: 203.0.113.0/24 }

为什么用 203.0.113.0/24 当"公网"

这是 RFC 5737 专门留给文档和示例用的地址段(TEST-NET-3),不会和任何真实公网地址冲突。 看到这个段就知道"这里在扮演公网"。

起环境

启动底座

在你的开发机上(不是实验机):

bash
./labctl up base

第一次会花三十秒左右,之后重启大概十秒。

labctl 做的事很简单:把 labs/ 目录打包传到实验机,然后在那边执行对应的脚本。 它用 tar over ssh 而不是 rsync,因为实验机上不一定装了 rsync。

如果你更喜欢直接在实验机上操作,效果完全一样:

bash
ssh chenzhong@192.168.49.182
cd ~/vpp-study/labs/base-gateway
./up.sh

起来之后应该看到

最后一段输出是这样的:

text
── 就绪 ──────────────────────────────────────
拓扑已启动,VPP 目前只是一台普通路由器,尚未开启任何 NAT。

  h1 10.10.0.11 ─┐                                  ┌─ s1 203.0.113.11
                 ├─ .1 [ VPP natbase ] .1 ──────────┤
  h2 10.10.0.12 ─┘   eth1          eth2             └─ s2 203.0.113.12

三个不那么显然的设置

up.sh 里有三处操作,如果照着 netlab 的默认流程走是不会有的。它们都是为了让实验现象更干净, 值得解释一下 —— 顺便这也是理解 VPP 容器怎么启动的好机会。

一、抓住 VPP 启动前的那个窗口

netlab 的 VPP 镜像不是容器一起来就跑 VPP 的。它的入口进程是一个叫 dataplane-wait.sh 的脚本, 在那里死等一个文件出现:

所以我们用的是 netlab up --no-config:让 netlab 把容器和链路都建好,但不要执行配置步骤。 这样 VPP 容器会停在 dataplane-wait 里,我们得到一个可以修改配置的窗口, 改完再手动触发 01-initial.sh 放行。

二、去掉 poll-sleep-usec

netlab 的 VPP 镜像在 startup.conf 里默认带一行 poll-sleep-usec,意思是主循环每轮之间睡一小会儿。 这是为了在跑很多个 VPP 实例的实验里省 CPU,代价是稀疏流量的时延会抖到几十毫秒

做 NAT 实验时这个代价不能接受 —— 你 ping 一下看到 30ms,根本分不清是 NAT 的开销、 是首包建会话的开销,还是这个 sleep。所以 up.sh 在 VPP 启动前把这行删掉:

bash
sed '/^[[:space:]]*poll-sleep-usec[[:space:]]/d' /etc/vpp/startup.conf > /tmp/startup.conf

三、AF_PACKET 用 v2 而不是 v3,收包用 polling

我们的 VPP 是通过 Linux 的 AF_PACKET socket 收发包的(生产环境会用 DPDK 或 AF_XDP)。 AF_PACKET 有两代内存映射接口:

TPACKET v3(默认)TPACKET v2(我们用的)
交付粒度按 block,一整块攒满或超时才交给用户态逐帧交付
吞吐更高略低
稀疏流量时延差,要等 block 超时

我们的实验流量非常稀疏(几个 ping、几个短连接),v3 的 block 超时会主导时延。所以换成 v2:

bash
sed 's/^create host-interface name /create host-interface v2 name /' /etc/config/01-initial.sh

再把收包队列从中断模式改成轮询:

bash
vppctl set interface rx-mode host-eth1 polling
vppctl set interface rx-mode host-eth2 polling

polling 会吃满一个 CPU 核

这是轮询的本质:CPU 不停地问网卡"有包吗有包吗",没包也在问。 实验机是 4 核,跑一个 VPP 底座没问题,但不要同时跑多个。

up.sh 里做了保护:启动前会检查主机上有没有别的 netlab 实验在跑,有就先把它停掉。 被停掉的实验只是销毁了容器,目录和脚本都还在,随时可以回去重启。

验证这三处都生效了

bash
./labctl vppctl base show hardware-interfaces | grep 'PACKET socket'
./labctl vppctl base show interface rx-placement

应该看到 Linux PACKET socket interface v2(polling)

观察 NAT 的三件工具

「你是谁」服务

up.sh 会在 s1 和 s2 上启动一个几十行的 Python 服务,监听 TCP 9000 和 UDP 9001。 你连上去,它就把它看到的源地址回给你:

bash
docker exec clab-natbase-h1 nc -w 2 203.0.113.11 9000
text
[s1 tcp/9000] 我看到你的地址是 203.0.113.1:39637

这是判断 NAT 有没有生效最快的方式 —— 不用抓包,不用看会话表,客户端自己就知道了。 后面几乎每个实验的自检都会用到它。

会话表

bash
./labctl vppctl base show nat44 sessions

NAT 的全部状态都在这里。第 1 章会逐字段拆解它。

报文追踪

bash
./labctl vppctl base clear trace
./labctl vppctl base trace add af-packet-input 10
# ...制造一些流量...
./labctl vppctl base show trace

把接下来 10 个包经过的每个 graph node 逐行打印出来。这是排错时的终极武器,第 7 章会专门讲怎么读。

日常操作速查

想做的事命令
起底座./labctl up base
应用某一步的配置./labctl apply base 01
跑自检./labctl verify base 01
清掉 NAT 配置回到干净底座./labctl reset base
敲 vppctl 命令./labctl vppctl base show nat44 sessions
进某个节点的 shell./labctl sh base h1
看当前跑着什么./labctl status
销毁./labctl down base

换章节时先 reset

每个实验步骤的 apply.sh 开头都会先把所有 NAT 插件关掉再重新配置, 所以步骤之间是互相独立的,可以按任意顺序反复练。

但如果你自己手工敲了一些命令想清干净,./labctl reset base 是最快的方式 —— 它不销毁容器,两秒就回到干净底座。

环境版本

实验环境跑在一台 Debian 13 上,4 核 15G。关键组件版本:

组件版本
VPP26.06-release
netlab26.08
containerlab0.75.0
Docker29.7.2
主机镜像python:3.13-alpine(自带 python3、busybox nc/ping/wget)
VPP 镜像netlab/vpp:latest

这套环境不能用来测性能

AF_PACKET 是一条为了通用性而牺牲性能的收包路径,加上容器共享内核、 veth 链路本身也有开销 —— 在这里量出来的 pps 和时延,和真实部署(DPDK / AF_XDP、 物理网卡、多 worker)差得很远。

本教程用它来观察行为,不用它来衡量性能。性能该怎么测,第 7 章会讲。

下一步

环境好了,去跑通第一个 NAT —— 五分钟,三条命令。

实验环境基于 netlab + containerlab + VPP 26.06