主题
五分钟跑通第一个 NAT
这一篇不解释原理,只做一件事:让你亲眼看到 NAT 生效前后的差别。原理从第 1 章开始讲。
先看没有 NAT 时是什么样
底座刚起来时,VPP 只是一台普通的三层转发设备,没有任何 NAT 配置。
让 h1 试着访问外网
bash
./labctl sh base h1
# 在 h1 里:
ping -c 3 -W 2 203.0.113.11或者不进 shell 直接执行:
bash
ssh chenzhong@192.168.49.182 \
'docker exec clab-natbase-h1 ping -c 3 -W 2 203.0.113.11'text
PING 203.0.113.11 (203.0.113.11): 56 data bytes
--- 203.0.113.11 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss全丢。但奇怪的是,网关是通的:
bash
docker exec clab-natbase-h1 ping -c 2 -W 1 10.10.0.1text
64 bytes from 10.10.0.1: seq=0 ttl=64 time=0.379 ms
64 bytes from 10.10.0.1: seq=1 ttl=64 time=0.084 ms为什么不通:去程没问题,回程无路可走
问题不在去的方向。h1 的默认路由指向 10.10.0.1,VPP 收到包、查路由、 知道 203.0.113.11 在 eth2 那边,老老实实转发出去了。s1 也确实收到了。
卡住的是回程。看一眼 s1 的路由表:
bash
docker exec clab-natbase-s1 ip routetext
172.30.101.0/24 dev eth0 scope link src 172.30.101.104
203.0.113.0/24 dev eth1 scope link src 203.0.113.11s1 只知道两个直连网段,完全不知道 10.10.0.0/24 在哪里。 它想回包给 10.10.0.11,查不到路由,包就地丢弃。
这不是实验环境的缺陷,而是故意做成这样的 —— 它就是真实互联网的样子。 10.0.0.0/8 是 RFC 1918 私有地址,公网上的任何路由器都不会为它转发流量。 你家里的电脑之所以能上网,唯一的原因就是路由器帮你做了 NAT。
NAT 存在的根本理由
不是"IPv4 地址不够用"这么笼统。具体到每一个包上,NAT 解决的是: 让回程流量有一个公网上可路由的目的地址。
把源地址换成一个公网地址,服务器就知道往哪回;NAT 设备再根据自己记下的对应关系, 把回包还原成内网地址。地址复用是这个机制带来的副产品,虽然后来变成了它最主要的用途。
开启 NAT:三条命令
应用第一步配置
bash
./labctl apply base 01脚本干的事就是这三条 vppctl 命令:
bash
# 1. 启用 nat44 插件(端点相关模式,VPP 的主力 NAT 实现)
vppctl nat44 plugin enable
# 2. 把出接口自己的地址当作转换后要用的地址
vppctl nat44 add interface address host-eth2
# 3. 告诉 VPP 哪个接口在"内"、哪个在"外"
vppctl set interface nat44 in host-eth1 out host-eth2第三条是 VPP NAT 模型里最需要转过弯的地方。VPP 不像 iptables 那样靠规则匹配来判断 "这个包该不该做 NAT",而是先给接口贴标签。包从贴了 in 的接口进来,走 in2out 分支(改源地址); 从贴了 out 的接口进来,走 out2in 分支(查会话表还原目的地址)。
再试一次
跑自检
bash
./labctl verify base 01text
── 2) h1 现在能到外网了吗 ────────────────────────────
PING 203.0.113.11 (203.0.113.11): 56 data bytes
64 bytes from 203.0.113.11: seq=0 ttl=63 time=0.217 ms
64 bytes from 203.0.113.11: seq=1 ttl=63 time=0.168 ms
64 bytes from 203.0.113.11: seq=2 ttl=63 time=0.098 ms
--- 203.0.113.11 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
✓ h1 → s1 全通通了。s1 的路由表一个字都没改 —— 变的是它收到的包长什么样。
让服务器自己告诉你它看到了什么
这是本教程最常用的观察手段。s1 上跑着一个「你是谁」服务,你连上去,它把它看到的源地址回给你:
问一下 s1
bash
docker exec clab-natbase-h1 nc -w 2 203.0.113.11 9000text
[s1 tcp/9000] 我看到你的地址是 203.0.113.1:39637h1 的真实地址是 10.10.0.11,但 s1 看到的是 203.0.113.1 —— VPP 出接口的地址。 这就是 NAT。
再让 h2 也连一次:
text
[s1 tcp/9000] 我看到你的地址是 203.0.113.1:45381两台内网主机在外网看来是同一个地址,只有端口不同:
text
内网真实来源 外网看到的来源
───────────────────── ─────────────────────
10.10.0.11 (h1) 203.0.113.1:39637
10.10.0.12 (h2) 203.0.113.1:45381这就是 NAPT(Network Address Port Translation,网络地址端口转换)。 严格来说我们平时说的 "NAT" 绝大多数时候指的都是 NAPT —— 靠端口这个维度, 一个公网地址可以撑起成千上万个内网主机。
看一眼 VPP 记了什么
会话表
bash
./labctl vppctl base show nat44 sessionstext
NAT44 ED sessions:
-------- thread 0 vpp_main: 1 sessions --------
i2o 10.10.0.11 proto TCP port 39637 fib 0
o2i 203.0.113.1 proto TCP port 39637 fib 0
external host 203.0.113.11:9000
i2o flow: match: saddr 10.10.0.11 sport 39637 daddr 203.0.113.11 dport 9000 proto TCP
rewrite: saddr 203.0.113.1 sport 39637 daddr 203.0.113.11 dport 9000
o2i flow: match: saddr 203.0.113.11 sport 9000 daddr 203.0.113.1 dport 39637 proto TCP
rewrite: saddr 203.0.113.11 daddr 10.10.0.11 dport 39637
index 0
last heard 484.35
timeout in 235.88
total pkts 9, total bytes 541
dynamic translation现在不用全看懂,只要注意三点:
- 一条会话记了两个方向:
i2o(inside → outside)和o2i(outside → inside), 每个方向都写明了"匹配什么"和"改成什么"。NAT 不是一个改写动作,是一对。 - 有一行
external host 203.0.113.11:9000:这条会话不只记了内网端,还记了外网端是谁。 这个细节就是 "ED"(端点相关)的全部含义,第 1 章会讲它为什么重要到值得单开一篇。 - 有超时:
timeout in 235.88。会话不是永久的,闲置久了会被回收。
顺手看一个 ICMP 的例子
刚才 ping 的时候也产生了会话。ICMP 没有端口,NAT 改什么?
text
i2o 10.10.0.11 proto ICMP port 32 fib 0
o2i 203.0.113.1 proto ICMP port 63327 fib 0
i2o flow: rewrite: saddr 203.0.113.1 daddr 203.0.113.11 icmp-id 63327
o2i flow: rewrite: saddr 203.0.113.11 daddr 10.10.0.11 icmp-id 32icmp-id 32 被改成了 icmp-id 63327。ICMP Echo 报文头里有个 Identifier 字段, 本来是给主机自己配对请求和应答用的,NAT 就借用它来充当"端口"的角色。
注意这里和 TCP 的一个区别:TCP 那条会话里源端口 39637 被原样保留了, 而 ICMP 的 id 被换掉了。为什么?下一章讲端口分配策略时会回答。
清场
回到干净底座
bash
./labctl reset base两秒。容器不销毁,只是把 NAT 配置清空。之后 h1 又会 ping 不通 203.0.113.11 —— 你可以随时用这个来确认自己在哪个状态。
这五分钟里发生了什么
你看到了 NAT 最基本的形态,并且拿到了三个观察手段:
- 「你是谁」服务:外部视角 —— 别人看到我是谁
show nat44 sessions:设备视角 —— NAT 记了什么状态show trace:包的视角 —— 一个包经历了什么(还没用,第 1 章开始用)
也留下了几个问题,正好是第 1 章的目录:
- 一个包具体被改了哪几个字节?校验和怎么办?→ NAT 到底改了什么
- 会话表里每个字段是什么意思?→ 五元组与会话表
external host那一行为什么重要?→ 端点无关 vs 端点相关- 端口是怎么挑的?会话什么时候被回收?→ 端口分配与会话老化