主题
静态映射与端口转发
前两篇解决的都是"内网出去"。这一篇解决反方向:让外网能主动连进内网。
为什么需要它
动态会话是内网发起时才建立的。外网主机想主动连进来,NAT 表里查不到任何对应关系, 包只能被丢弃 —— 这正是上一篇里 no translation 的正常场景。
静态映射就是提前把这条路铺好:告诉 NAT "以后凡是发到这个公网地址端口的包, 就送到那台内网主机去"。
两种映射
text
端口转发(NAPT 静态映射)
┌──────────────────────┐ ┌──────────────────────┐
│ 203.0.113.1 : 8080 │ ───→ │ 10.10.0.12 : 9000 │
└──────────────────────┘ └──────────────────────┘
只占用公网地址的一个端口,同一地址可以转发给很多台内网主机
1:1 映射(address-only)
┌──────────────────────┐ ┌──────────────────────┐
│ 203.0.113.200 │ ───→ │ 10.10.0.11 │
│ 全部协议、全部端口 │ │ │
└──────────────────────┘ └──────────────────────┘
整个公网地址独占给一台内网主机,配置最简单,地址消耗大配置
应用配置
bash
./labctl reset base
./labctl apply base 03bash
vppctl nat44 plugin enable
vppctl set interface nat44 in host-eth1 out host-eth2
# 出方向仍然需要一个地址来源,否则内网主机出不去
vppctl nat44 add interface address host-eth2
# 端口转发:把公网 8080 转到 h2 的 9000
vppctl nat44 add static mapping tcp local 10.10.0.12 9000 external 203.0.113.1 8080
# 1:1 映射:203.0.113.200 就是 h1
vppctl nat44 add static mapping local 10.10.0.11 external 203.0.113.200别忘了出方向的地址来源
只配静态映射,内网主机是出不去的。静态映射管"进来",出方向仍然需要 nat44 add interface address 或者 nat44 add address 提供地址。
例外是 1:1 映射 —— 它是双向的,被映射的那台主机出去时也会用这个地址。 但其他没被映射的内网主机还是需要地址池。
命令的完整形态
bash
nat44 add static mapping tcp|udp|icmp \
local <内网地址> [<端口>] \
external <公网地址|接口> [<端口>] \
[vrf <表号>] \
[twice-nat|self-twice-nat] \
[out2in-only] \
[exact <池地址>] \
[del]几个关键选项:
| 选项 | 作用 |
|---|---|
| 省略协议和端口 | address-only 1:1 映射,覆盖所有协议(含 ICMP) |
external <接口名> | 用接口当前地址,地址变化时自动跟随(动态 IP 场景必备) |
out2in-only | 只对进方向生效,内网主机出去时不用这个映射 |
twice-nat | 源和目的都改写,第 2 章后续会讲 |
vrf <表号> | 绑定到指定 VRF,多租户场景 |
验证
跑自检
bash
./labctl verify base 03bash
./labctl vppctl base show nat44 static mappingstext
NAT44 static mappings:
TCP local 10.10.0.12:9000 external 203.0.113.1:8080 vrf 0
local 10.10.0.11 external 203.0.113.200 vrf 0注意第二条没有协议字段 —— 这就是 address-only 映射的样子。
端口转发:外网连进来了
bash
ssh chenzhong@192.168.49.182 \
'docker exec clab-natbase-s1 nc -w 3 203.0.113.1 8080'text
[h2 tcp/9000] 我看到你的地址是 203.0.113.11:42779这一篇最重要的一点:端口转发不改源地址
看清上面那行输出。h2 看到的是 203.0.113.11 —— s1 的真实地址。
text
s1 发出 203.0.113.11:x → 203.0.113.1:8080
VPP 改写后 203.0.113.11:x → 10.10.0.12:9000
───────────── ─────────────
源:原封不动 目的:改写了
h2 因此看到 203.0.113.11:x对比出方向 NAT:那里改的是源,所以外网服务器看不到内网真实地址。 端口转发改的是目的,所以内网服务器能拿到真实客户端 IP。
这个差别在实际工作中很重要
把服务发布到公网时,内网服务器的访问日志里记的是真实客户端 IP, 不是一堆相同的网关地址。风控、限流、地域分析、审计都依赖这一点。
如果哪天发现内网服务器日志里所有客户端 IP 都变成了同一个, 那说明中间有人配了 twice-NAT(源和目的都改)—— 通常是为了解决回程路由问题。 代价就是丢失真实客户端 IP,需要靠 X-Forwarded-For 之类的应用层手段补回来。
从会话表也能看出来:
text
i2o 10.10.0.12 proto TCP port 9000
o2i 203.0.113.1 proto TCP port 8080
external host 203.0.113.11:41789
i2o flow: rewrite: saddr 203.0.113.1 sport 8080
o2i flow: rewrite: daddr 10.10.0.12 dport 9000
static translation ← 注意这里o2i 的 rewrite 只有 daddr 和 dport,没有 saddr —— 源地址确实没被改。
另外注意最后一行是 static translation 而不是 dynamic translation。 静态映射产生的会话有这个标记,排障时可以据此区分"这条会话是配置来的还是用户流量建的"。
1:1 映射:连 ping 都通
bash
ssh chenzhong@192.168.49.182 \
'docker exec clab-natbase-s1 ping -c 3 -W 2 203.0.113.200'text
64 bytes from 203.0.113.200: seq=0 ttl=63 time=0.108 ms
3 packets transmitted, 3 packets received, 0% packet lossaddress-only 映射不带协议,所以 ICMP 也被覆盖了。这是它和端口转发的一个明显区别 —— 端口转发只转你指定的那个协议那个端口,ping 是不通的。
1:1 映射是双向的
text
s1 看到 h1 出方向的地址是 203.0.113.200:46639h1 主动出去时,用的也是 203.0.113.200,而不是地址池或接口地址。 1:1 映射把这台主机的外网身份完全固定了 —— 进出都是这个地址。
这个性质在部署对外服务时很有用:服务器的入向和出向地址一致, 对端做反向 DNS 校验、白名单、或者双向 TLS 时不会出问题。
如果你不想要这个双向性(只想让外网能进来,出去还是走地址池),加 out2in-only:
bash
vppctl nat44 add static mapping tcp local 10.10.0.11 8080 \
external 203.0.113.200 8080 out2in-only动态公网地址怎么办
家用宽带、云主机的公网地址可能会变。地址写死在静态映射里,地址一变映射就失效了。
解法是 external 后面填接口名而不是地址:
bash
vppctl nat44 add static mapping tcp local 10.10.0.12 9000 \
external host-eth2 8080试一下接口形式
bash
./labctl vppctl base nat44 add static mapping tcp local 10.10.0.12 9000 external 203.0.113.1 8080 del
./labctl vppctl base nat44 add static mapping tcp local 10.10.0.12 9000 external host-eth2 8080
./labctl vppctl base show nat44 static mappings然后再从 s1 连一次 203.0.113.1:8080,行为完全一样 —— 但如果接口地址变了, 映射会自动跟着变。
接口形式的映射会显示成两行,别以为 del 没生效
text
NAT44 static mappings:
TCP local 10.10.0.12:9000 external 203.0.113.1:8080 vrf 0 ← 解析后的地址条目
local 10.10.0.11 external 203.0.113.200 vrf 0
TCP local 10.10.0.12:9000 external host-eth2:8080 vrf -1 ← 你配的接口条目第一行和第三行是同一条映射:VPP 把接口解析成当前地址后,额外挂了一条已解析的条目 (vrf 0),你配的那条则以接口形式保留(vrf -1)。接口地址变化时, VPP 会重新解析并更新第一行。
第一次看到会以为"我刚才 del 掉的那条又回来了"。它不是残留,是接口形式映射的正常显示。
常见问题
外网连不上,怎么查?
bash
# 1. 映射配了吗,端口和协议对吗
./labctl vppctl base show nat44 static mappings
# 2. 有没有落到 no translation
./labctl vppctl base show errors | grep -i nat
# 3. 包到底走到哪一步了
./labctl vppctl base clear trace
./labctl vppctl base trace add af-packet-input 10
# ...从外网发起连接...
./labctl vppctl base show trace内网服务确实在监听吗?
一半的"端口转发不通"其实是内网服务没起来。先在内网主机本机上验证:
bash
ssh chenzhong@192.168.49.182 \
'docker exec clab-natbase-h2 nc -w 2 127.0.0.1 9000'协议写错了?
nat44 add static mapping tcp ... 只转 TCP。如果服务是 UDP 的(DNS、游戏、VoIP), 需要单独配一条 udp 的。这是一个很常见的疏漏。
什么时候用哪种
text
端口转发 1:1 映射
──────────────────────────── ────────────────────────────────
公网地址稀缺,要复用 公网地址够用
只发布少数几个服务端口 服务器要开很多端口,或端口不固定
同一地址服务多台内网主机 需要 ICMP 也能通(ping、traceroute)
想精确控制暴露面 需要进出地址一致(反向 DNS、白名单)
✗ 不适合的场景
────────────────────────────────────────────────────────────
端口范围很大 —— VPP 的静态映射是逐条配的,没有"端口段转发"语法,
一千个端口就要一千条命令。这种需求应该用 1:1 映射,或者考虑 pnat 插件。小结
| 端口转发 | 1:1 映射 | |
|---|---|---|
| 命令 | ... tcp local IP PORT external IP PORT | ... local IP external IP |
| 覆盖协议 | 只有指定的那个 | 全部,含 ICMP |
| 方向 | 默认双向,可用 out2in-only 限制 | 双向 |
| 源地址 | 不改,内网服务器能看到真实客户端 IP | 同样不改 |
| 地址消耗 | 一个端口 | 一整个地址 |
| 会话标记 | static translation | static translation |
第 2 章前三篇讲完了 NAT44-ED 的基础三件套:出方向伪装、地址池、静态映射。 后续篇目(identity mapping、负载均衡映射、twice-NAT、VRF 多租户、超时调优) 会在第 2 期补齐。
如果你想先把原理再过一遍,第 1 章值得重读 —— 配置命令会忘,那四篇里的模型不会。