Phase 0 的最后一章,也是一次总收口。还记得 0.1 里那个 curl 请求经过路由器时,"把源 IP 从你的私网地址改成公网 IP"那一步吗?那就是 NAT。这一章把它彻底讲清:家用路由器为什么非做不可、出站入站分别怎么改地址、它靠什么记住每条连接,以及你研究过的 Full Cone / Symmetric 到底在说什么。
NAT(Network Address Translation,网络地址转换):路由器在转发时改写数据包的 IP / 端口,让一群私网设备共用少量(通常是一个)公网地址。
1为什么必须做 NAT
接上 IPv6 加餐章:IPv4 地址枯竭,于是约定了一批私网地址段(RFC 1918),专供内网使用、永不在公网出现:
10.0.0.0/8172.16.0.0/12192.168.0.0/16(你家设备192.168.1.x就在这段)
问题来了:私网地址在公网上不可路由——你发一个源地址是 192.168.1.100 的包到公网,对方就算收到也无法回(全世界有无数个 192.168.1.100,回给谁?)。所以路由器必须在出门那一刻,把源地址换成它那个唯一的公网 IP。这就是 NAT 的根本动机。
对照加餐章:IPv6 地址管够,每台设备直接拿全局地址,根本不需要这套"换地址"的把戏。NAT 本质是 IPv4 地址短缺逼出来的妥协——理解了这点,你就明白为什么这一整章在 IPv6 世界几乎可以不存在。
2SNAT 与 conntrack:出门改地址,回包怎么找回来
SNAT(Source NAT,源地址转换)是你家路由器最核心的动作:数据包出门时,把源地址(和端口)改成路由器的公网地址,目的地不变。
源地址从私网 192.168.1.100 改成路由器公网 203.0.113.5,目的地不变——这正是 0.1 里 curl 经过路由器那一步。家用这种"一个公网 IP 给一堆设备共用"的动态 SNAT,有个专门名字叫 masquerade(伪装)。
但麻烦马上来了:回包带着 dst 203.0.113.5:51000 回到路由器,路由器怎么知道该把它转给内网哪台机器?靠 conntrack(connection tracking,连接跟踪)。改写出站包的同时,路由器在一张连接表里记一条:
内网 192.168.1.100:51000 ↔ 外网 93.184.216.34:443
出去时源被改成 203.0.113.5:51000
回包一到,路由器拿回包的四元组(0.4 那个四元组!)反查这张表,把目的地址改回 192.168.1.100:51000,再转进内网。
key 是连接四元组,value 是改写映射 + 连接状态。NAT 的"可逆"全靠它。回想 0.4——四元组唯一确定一条连接,在这里它就是 conntrack 的主键。没有 conntrack,SNAT 改出去的包就回不来了。
这张表,就是你在路由器上跑 conntrack -L 看到的东西。OpenWRT 的 firewall4 基于 nftables,NAT 规则最终落成内核 netfilter 的 conntrack + nat 表;那条 LAN→WAN 的 masquerade 规则,正是 SNAT。Phase 1 的 1.3(nftables)会让你亲手写这条规则,2.4 看 firewall4 怎么自动生成它。
看连接跟踪表(在做 NAT 的那台 Linux / OpenWRT 路由器上最有意义):
sudo conntrack -L | head # 需要 conntrack 工具
cat /proc/net/nf_conntrack | head # 直接看内核导出(无需额外工具)
每一行就是一条被跟踪的连接:协议、源 / 目的、状态,以及 NAT 改写信息。普通笔记本上能看到自己出站连接的跟踪;NAT 改写那部分,要在做 NAT 的路由器上才看得到。
3DNAT 与端口转发
DNAT(Destination NAT,目的地址转换)管入站,方向和 SNAT 相反。默认情况下,外网无法主动连进你的内网设备(它们只有私网地址,conntrack 里也没有对应的出站连接)。要让外网能访问内网某台机器(比如你在内网跑了个游戏服或自建服务),就配端口转发(port forwarding):
"凡是到达 公网IP:25565 的流量,目的地址改写成 192.168.1.50:25565,转进去。"这就是 DNAT——按目的端口改写目的地址。接上 0.4 那条 router-link:配 port forward 必须指定 TCP 还是 UDP,因为传输层是两个独立协议。
# 到本机 25565 的 tcp 流量,目的地改写为内网 192.168.1.50:25565
nft add rule ip nat prerouting tcp dport 25565 dnat to 192.168.1.50:25565
自建服务、NAS 远程访问要"端口映射",就是在配 DNAT。可如果你在 CGNAT 后面(加餐章那个),你的"公网 IP"其实是运营商的私网地址——你在自己路由器上配的 DNAT 根本没用,因为流量在运营商那层 NAT 就进不来。这是 CGNAT 最让人头疼的地方。
4NAT 类型:Full Cone 到 Symmetric
这是你之前研究过的部分。核心问题:当内网 192.168.1.100:51000 通过 NAT 出去、在公网侧被映射成 203.0.113.5:60000 后,这个公网端口 60000 谁能用、能用来连谁?不同 NAT 的答案不同,从宽到严:
| NAT 类型 | 公网端口映射 | 谁能借它连回内网 | P2P |
|---|---|---|---|
| Full Cone 全锥 | 只认内网源,固定 | 任何外部主机都行 | 最易 |
| Restricted Cone 地址限制锥 | 同上,固定 | 仅内网联系过的外部 IP | 较易 |
| Port-Restricted 端口限制锥 | 同上,固定 | 仅匹配的外部 IP + 端口 | 一般 |
| Symmetric 对称 | 对每个外部目标分不同端口 | 几乎无法被第三方借用 | 最难 |
关键区别在最后一行:Symmetric 下,内网同一个 IP:端口 去连不同外部目标,会被映射成不同的公网端口,外部想连回来必须用那个特定目标看到的端口——基本没法被第三方"猜"或借用,所以 P2P 最难打通。
这套分类是 NAT 穿透(NAT traversal)的基础——要让两个都在 NAT 后的设备直连(P2P、VoIP、联机游戏),得靠 STUN 探测各自的 NAT 类型、用打洞(hole punching)。两端都是 Symmetric 时往往打不通,只能走中转服务器(TURN)。你研究 Full Cone NAT,关心的正是"我家这个 NAT 够不够宽松,能不能直连"。
CGNAT 下你被套了两层 NAT(自己路由器一层 + 运营商一层),且运营商那层通常偏 Symmetric——你在自己路由器上怎么调都没用。这就是为什么很多人转向 IPv6(端到端可达,根本不用穿透)或向 ISP 申请公网 IP(撤掉那层 CGNAT)。一切又回到加餐章的结论:地址管够,这些麻烦从根上就不存在。
本章小结
- NAT 的根因:IPv4 公网地址不够 + 私网地址(RFC 1918)在公网不可路由,出门必须换成公网地址。
- SNAT(masquerade):出站把源地址改成路由器公网 IP——就是 0.1 curl 经过路由器那一步。
- conntrack:用连接四元组当主键记下每次改写,回包靠它反查改回——NAT 可逆的关键。
- DNAT / 端口转发:入站改目的地址,把公网端口流量转给内网指定机器;CGNAT 下你的 DNAT 会失效。
- NAT 类型从 Full Cone 到 Symmetric,决定 P2P 能否直连;穿透靠 STUN / 打洞,Symmetric + CGNAT 最难;根本出路是 IPv6。
到这里,0.1 那个 curl 请求的一生——从开机 DHCP 拿地址、DNS 查 IP、TCP/TLS 握手、经过路由器 NAT 出门,到对端层层解封装——每一步你都能拆开了。Phase 0 打通,你已经理解了"数据包从你的电脑到服务器经历了什么"。接下来 Phase 1 换挡:不再只是看懂,而是在一台 Linux 上,用 iproute2、nftables、namespace 亲手操控这一切。
动手练习
- 说出三个 RFC 1918 私网段,并确认你自己设备的 IP 落在哪一段。
- (Linux)跑
cat /proc/net/nf_conntrack | head(或sudo conntrack -L),挑一条连接,指出它的源 / 目的和状态;若能 SSH 进你的 OpenWRT 路由器,在那上面跑同样命令,看 NAT 改写信息。 - 思考题:你在内网跑了个服务想让朋友从外网访问,配了端口转发却连不上。结合本章,列出至少两个可能原因(提示:CGNAT?TCP/UDP 选错?防火墙没放行入站?)。
- 进阶:用在线 STUN 测试工具查一下你家网络的 NAT 类型。如果是 Symmetric 或 CGNAT,结合加餐章想想:为什么 IPv6 能从根上绕开这个问题?