mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
5601 字
15 分钟
NAT与中间盒:互联网的"不完美"现实
2022-03-26

教科书告诉你互联网是端到端的——发送方和接收方直接通信,中间的路由器只负责转发 IP 包。但现实是:你的手机发出去的包,源地址 192.168.1.105 在离开家用的路由器时就被改成了运营商分配的公网 IP;你想在家搭建一个 Web 服务,外网根本连不进来;你的 P2P 下载永远卡在”寻找节点”——这一切的幕后推手,就是 NAT 和各种中间盒。

NAT(Network Address Translation,网络地址翻译)是互联网对 IPv4 地址耗尽问题的工程妥协。它打破了端到端原则,让数十亿设备躲在少量公网 IP 后面上网——代价是接收方无法主动连接你,P2P 通信变得困难,协议设计者不得不发明 STUN、TURN、ICE 等穿透机制来绕过 NAT。

而 NAT 只是中间盒(Middlebox)的一种。防火墙过滤流量、DPI 检查应用层内容、负载均衡器分发请求、CDN 缓存内容——这些设备都不在”路由器只转发 IP 包”的教科书模型里,但它们构成了互联网的”隐形基础设施”。

本章从 NAT 为什么存在开始,深入 NAPT 的工作原理和连接追踪,分析 NAT 如何破坏端到端,然后讲解 STUN/TURN/ICE 穿透机制,最后扩展到防火墙、DPI、负载均衡器等中间盒,理解为什么”干净分层”是理想而非现实。

一、NAT 为什么存在#

1.1 IPv4 地址不够了#

IP地址与子网 中看到,IPv4 只有 32 位地址空间,理论上限约 43 亿个地址。但实际可用地址远少于此——IANA 在 2011 年就分配完了最后的 /8 地址块,APNIC 在 2011 年、RIPE NCC 在 2012 年、LACNIC 在 2014 年、AFRINIC 在 2020 年也相继耗尽了各自的地址池。

到 2026 年,全球联网设备数量已超过 300 亿——手机、电脑、IoT 设备、智能家电——远超 IPv4 地址空间。但互联网并没有因为地址耗尽而崩溃,原因就是 NAT。

1.2 NAT 如何节省地址#

NAT 的核心思想很简单:多台设备共享一个(或少数几个)公网 IP。内部网络使用私有地址(10.0.0.0/8172.16.0.0/12192.168.0.0/16),这些地址在公网上不可路由。当内部设备访问公网时,NAT 设备将私有地址替换为公网地址,并在返回时反向替换。

# 查看本机的私有 IP 和公网 IP
ip addr show | grep "inet " # 私有 IP(通常是 192.168.x.x 或 10.x.x.x)
# 查看公网 IP
curl -s ifconfig.me # 通过外部服务获取公网 IP
# 典型场景:
# 内网 IP: 192.168.1.105
# 公网 IP: 203.0.113.42(NAT 设备的外网地址)
# 比例:整个家庭网络(可能 20+ 设备)共享 1 个公网 IP

一个典型的家庭网络:路由器 WAN 口获得运营商分配的 1 个公网 IP,LAN 口使用 192.168.1.0/24 私有网段。家里 20 台设备共享这 1 个公网 IP 上网——地址节省了 20 倍。

1.3 四种 NAT 类型#

RFC 2663 定义了四种 NAT 类型,它们的区别在于翻译的粒度:

NAT 类型翻译方式地址节省端到端影响典型场景
静态 NAT(1:1)一个私有 IP 固定映射到一个公网 IP不节省几乎无服务器发布、云主机
动态 NAT(池)�私有 IP 从公网 IP 池中动态选取有限中等企业出口(地址池)
NAPT(PAT)私有 IP + 端口映射到公网 IP + 端口大幅节省严重家用路由器、企业出口
双向 NAT同时改写源和目的地址视场景严重网络合并、IPv4-IPv6 翻译
Note

日常说的”NAT”几乎都是 NAPT(Network Address Port Translation),也叫 PAT(Port Address Translation)。静态 NAT 和动态 NAT 不改写端口号,一个公网 IP 只能映射一台内部设备,实际上并不节省地址。NAPT 通过同时改写 IP 和端口,让数千个内部连接复用同一个公网 IP——这才是 NAT 能解决地址危机的根本原因。

1.4 为什么 NAPT 是主流#

NAPT 成为事实标准有三个原因:

  1. 地址节省效果最好:一个公网 IP 可以同时支持约 65000 个并发连接(端口号 16 位),对家庭网络绰绰有余
  2. 隐式安全:外部无法主动连接内部设备——NAPT 转换表里没有的映射,包会被丢弃。这虽然不是真正的安全机制,但客观上挡掉了大量扫描和攻击
  3. 对应用透明:大部分客户端-服务器应用(浏览网页、发邮件)不需要任何修改就能工作——NAPT 自动处理出站连接的地址翻译
# 查看家用路由器的 NAT 配置(OpenWrt 示例)
uci show firewall | grep nat
# 典型输出:
# firewall.@nat[0]=nat
# firewall.@nat[0].name='masquerade'
# firewall.@nat[0].src='lan'
# firewall.@nat[0].dest='wan'
# firewall.@nat[0].target='MASQUERADE'
# MASQUERADE = 动态 NAPT,公网 IP 由 DHCP 分配时使用

二、NAPT 工作原理#

2.1 转换表结构#

NAPT 的核心数据结构是转换表(Translation Table),每条记录映射一个”内部 IP:端口”到”外部 IP:端口”:

# NAPT 转换表示例
# 格式:内部地址:端口 ←→ 外部地址:端口 ←→ 目标地址:端口
#
# 内部地址 外部地址 目标地址 协议
# 192.168.1.105:54321 203.0.113.42:54321 93.184.216.34:80 TCP
# 192.168.1.105:54322 203.0.113.42:54322 140.82.121.3:443 TCP
# 192.168.1.106:54321 203.0.113.42:54323 93.184.216.34:443 TCP
# 192.168.1.107:54321 203.0.113.42:54324 151.101.1.69:443 TCP

注意第三条:内部两台设备使用了相同的内部端口号 54321,但 NAPT 为它们分配了不同的外部端口号 5432154323——外部端口号是区分不同内部连接的关键。

2.2 完整连接转换追踪#

以一个 HTTP 请求为例,追踪数据包从内网到外网的完整转换过程:

sequenceDiagram participant 内部主机 as 内部主机<br/>192.168.1.105 participant NAT设备 as NAT设备<br/>内:192.168.1.1 / 外:203.0.113.42 participant 服务器 as Web服务器<br/>93.184.216.34 Note over 内部主机,服务器: 出站:内部主机 → 服务器 内部主机->>NAT设备: TCP SYN<br/>Src: 192.168.1.105:54321<br/>Dst: 93.184.216.34:80 Note over NAT设备: 查转换表:无匹配<br/>创建新映射<br/>192.168.1.105:54321<br/>↔ 203.0.113.42:54321 NAT设备->>服务器: TCP SYN(改写后)<br/>Src: 203.0.113.42:54321<br/>Dst: 93.184.216.34:80 Note over 内部主机,服务器: 入站:服务器 → 内部主机 服务器->>NAT设备: TCP SYN+ACK<br/>Src: 93.184.216.34:80<br/>Dst: 203.0.113.42:54321 Note over NAT设备: 查转换表:<br/>203.0.113.42:54321<br/>↔ 192.168.1.105:54321 NAT设备->>内部主机: TCP SYN+ACK(改写后)<br/>Src: 93.184.216.34:80<br/>Dst: 192.168.1.105:54321 Note over 内部主机,服务器: 后续数据包同理 内部主机->>NAT设备: TCP ACK + HTTP GET NAT设备->>服务器: TCP ACK + HTTP GET(改写源地址) 服务器->>NAT设备: HTTP Response NAT设备->>内部主机: HTTP Response(改写目的地址)

出站时,NAPT 改写源 IP 和源端口;入站时,改写目的 IP 和目的端口。服务器只知道它在和 203.0.113.42:54321 通信,完全不知道后面还有一台 192.168.1.105:54321 的主机。

2.3 源 NAT vs 目的 NAT#

NAT 不只改写源地址。根据改写的对象不同,NAT 分为两大类:

类型改写内容方向典型场景iptables 链
源 NAT(SNAT)源 IP + 源端口出站内网上网(MASQUERADE)POSTROUTING
目的 NAT(DNAT)目的 IP + 目的端口入站端口转发、服务器发布PREROUTING
# Linux iptables 配置源 NAT(MASQUERADE)
# 出站时改写源地址为外网接口地址
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE
# Linux iptables 配置目的 NAT(端口转发)
# 将外网 203.0.113.42:8080 转发到内网 192.168.1.100:80
iptables -t nat -A PREROUTING -d 203.0.113.42 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80
# 查看 NAT 规则
iptables -t nat -L -n -v
# 典型输出:
# Chain POSTROUTING (policy ACCEPT)
# target prot opt source destination
# MASQUERADE all -- 192.168.1.0/24 0.0.0.0/0
# Chain PREROUTING (policy ACCEPT)
# target prot opt source destination
# DNAT tcp -- 0.0.0.0/0 203.0.113.42 tcp dpt:8080 to:192.168.1.100:80

源 NAT 让内网设备能访问外网;目的 NAT 让外网能访问内网服务。两者经常配合使用——家用路由器做源 NAT 让全家上网,同时做目的 NAT 把特定端口转发到内网的 NAS 或游戏主机。

2.4 Conntrack:连接追踪#

NAPT 要正确改写返回包,必须记住每个连接的映射关系——这就是连接追踪(Connection Tracking)。Linux 内核的 nf_conntrack 模块负责这项工作:

# 查看当前连接追踪表
cat /proc/net/nf_conntrack
# 典型输出:
# ipv4 2 tcp 6 299 ESTABLISHED src=192.168.1.105 dst=93.184.216.34 sport=54321 dport=80 src=93.184.216.34 dst=203.0.113.42 sport=80 dport=54321 [ASSURED] mark=0 zone=0 use=2
# ipv4 2 udp 17 29 src=192.168.1.105 dst=8.8.8.8 sport=54322 dport=53 src=8.8.8.8 dst=203.0.113.42 sport=53 dport=54322 [ASSURED] mark=0 zone=0 use=2
# 用 conntrack 工具查看(更可读)
conntrack -L
# 查看连接追踪统计
conntrack -S
# 查看最大追踪连接数
cat /proc/sys/net/netfilter/nf_conntrack_max
# 典型值:65536(家用路由器)或 262144+(服务器)
# 查看当前追踪连接数
cat /proc/sys/net/netfilter/nf_conntrack_count
# 当 conntrack_count 接近 conntrack_max 时,新连接会被丢弃
# 症状:网络间歇性断开,dmesg 出现 "nf_conntrack: table full, dropping packet"
Warning

Conntrack 表满是一个常见但隐蔽的故障。当并发连接数超过 nf_conntrack_max 时,内核直接丢弃新连接的包——不是拒绝,而是静默丢弃。症状是网络间歇性不可用,但已有连接正常。在高并发服务器上,务必调大 nf_conntrack_max 或使用无状态防火墙规则绕过 conntrack。

Conntrack 的超时机制也很重要——TCP 连接在 ESTABLISHED 状态默认 432000 秒(5 天)才过期,UDP 只需 30 秒。这意味着:

  • 长连接(SSH、数据库连接池)会长期占据 conntrack 表项
  • UDP 应用(DNS、游戏)需要频繁发送心跳包维持映射
  • NAT 设备重启后,所有连接映射丢失,TCP 连接会卡住直到超时
# 调整 conntrack 超时参数
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=7200 # TCP 已建立:2小时
sysctl -w net.netfilter.nf_conntrack_udp_timeout=60 # UDP:60秒
sysctl -w net.netfilter.nf_conntrack_max=262144 # 最大连接数
# 持久化配置
echo "net.netfilter.nf_conntrack_tcp_timeout_established = 7200" >> /etc/sysctl.conf
echo "net.netfilter.nf_conntrack_max = 262144" >> /etc/sysctl.conf
sysctl -p

三、NAT 如何破坏端到端#

3.1 接收方无法主动连接#

端到端原则的核心假设是:任何一方都能主动向对方发起连接。NAT 打破了这个假设。

# 场景:外部主机想连接内部服务器
#
# 外部主机发送:Dst: 192.168.1.100:80
# → 192.168.1.100 是私有地址,在公网上不可路由,包被丢弃
#
# 外部主机发送:Dst: 203.0.113.42:80
# → NAT 设备收到,查转换表:没有 203.0.113.42:80 的映射
# → 包被丢弃(NAT 不知道该转发给哪台内部设备)

除非你在 NAT 设备上配置了端口转发(DNAT),否则外部无法主动连接内部任何设备。这就是为什么你在家搭了 Web 服务器,外网却访问不了。

3.2 IP 地址不可达#

NAT 后面的设备没有公网 IP,这意味着:

  • 服务器无法识别客户端身份:服务器看到的源 IP 是 NAT 设备的公网 IP,不是客户端的真实 IP。同一 NAT 后面的 100 台设备,在服务器看来都是同一个 IP
  • 地理位置判断失准:IP 地理定位服务只能定位到 NAT 设备的位置,而非客户端的实际位置
  • IP 黑名单失效:封禁一个 NAT 公网 IP 可能影响该 NAT 后面的所有用户
# 用 tcpdump 观察 NAT 改写前后的包
# 在 NAT 设备的内网接口抓包
tcpdump -i eth1 -nn host 192.168.1.105 and port 80
# 在 NAT 设备的外网接口抓包
tcpdump -i eth0 -nn host 93.184.216.34 and port 80
# 对比:内网接口看到 Src: 192.168.1.105:54321
# 外网接口看到 Src: 203.0.113.42:54321
# 服务器永远看不到 192.168.1.105 这个地址

3.3 P2P 通信受影响#

P2P 应用(BitTorrent、视频通话、在线游戏)需要双方都能主动向对方发送数据。但 NAT 后面的设备无法被外部主动连接,导致:

  • BitTorrent:无法接收入站连接,只能主动连接他人,下载速度受限
  • VoIP/视频通话:双方都在 NAT 后面时,无法直接建立媒体流
  • 在线游戏:玩家之间的直接通信被 NAT 阻断,必须通过中继服务器

3.4 为什么是”必要的恶”#

NAT 破坏了端到端原则,但它解决了 IPv4 地址耗尽这个更紧迫的问题。在 IPv6 全面普及之前(这可能还需要很多年),NAT 是互联网能继续运转的关键补丁。

NAT 还带来了一些”意外的”好处:

  • 隐式防火墙:未请求的入站流量被 NAT 丢弃,客观上提供了安全防护
  • 网络重编号:更换 ISP 时只需改 NAT 外网地址,内网无需任何变动
  • 地址空间隔离:内网地址可以随意规划,不与公网地址冲突

但不要把 NAT 的隐式安全当作真正的安全——NAT 不是防火墙,它不做任何安全策略检查,只是碰巧丢弃了没有映射的包。专业的防火墙在 第五节 讨论。

四、NAT 穿透:STUN / TURN / ICE#

既然 NAT 阻断了入站连接,应用如何绕过 NAT 建立直接通信?这就是 NAT 穿透(NAT Traversal)要解决的问题。

4.1 STUN:发现公网地址#

STUN(Session Traversal Utilities for NAT,RFC 8489)的工作原理很简单:让客户端发现自己的公网 IP 和端口

sequenceDiagram participant 客户端 as NAT后客户端<br/>192.168.1.105 participant NAT as NAT设备<br/>203.0.113.42 participant STUN as STUN服务器<br/>203.0.113.100 客户端->>NAT: STUN Binding Request<br/>Src: 192.168.1.105:54321<br/>Dst: 203.0.113.100:3478 NAT->>STUN: STUN Binding Request(改写后)<br/>Src: 203.0.113.42:54321<br/>Dst: 203.0.113.100:3478 Note over STUN: 记录源地址:<br/>203.0.113.42:54321 STUN->>NAT: STUN Binding Response<br/>包含 MAPPED-ADDRESS:<br/>203.0.113.42:54321 NAT->>客户端: STUN Binding Response(改写后)<br/>客户端得知:<br/>自己的公网映射是 203.0.113.42:54321

客户端向 STUN 服务器发送请求,STUN 服务器看到的是 NAT 改写后的公网 IP:端口,将其返回给客户端。客户端现在知道了自己的”公网身份”,可以将这个地址告诉对端,让对端尝试直接连接。

# 使用 stunclient 测试 NAT 类型
apt install stuntman-client
stunclient stun.l.google.com:19302
# 典型输出:
# Binding test: success
# Local address: 192.168.1.105:54321
# Mapped address: 203.0.113.42:54321
# 用 nmap 检测 NAT 类型
nmap -sU -p 3478 --script stun-info stun.l.google.com
# Python 实现 STUN 客户端
pip install pystun3
pystun3
# 输出:
# NAT Type: Full Cone NAT
# External IP: 203.0.113.42
# External Port: 54321

STUN 的局限在于:它只能发现映射地址,不能保证对端能通过这个地址连进来。NAT 的行为决定了穿透是否可行——RFC 3489 定义了四种 NAT 行为:

NAT 行为类型入站过滤规则STUN 穿透P2P 可行性典型设备
完全锥形(Full Cone)任何源都可连接映射端口容易少见,部分企业 NAT
受限锥形(Restricted Cone)仅限客户端发过包的 IP需配合部分家用路由器
端口受限锥形(Port Restricted)仅限客户端发过包的 IP:端口需配合大部分家用路由器
对称型(Symmetric)同一内部端口对不同目标分配不同外部端口不可行极低运营商级 NAT(CGNAT)

对称型 NAT 是穿透的噩梦——STUN 发现的映射地址只对 STUN 服务器有效,对其他目标会分配不同的外部端口。双方都是对称型 NAT 时,STUN 穿透彻底失败。

4.2 TURN:中继兜底#

当 STUN 穿透失败时,TURN(Traversal Using Relays around NAT,RFC 8656)提供兜底方案——通过中继服务器转发所有流量

# TURN 工作流程
#
# 客户端A ←→ TURN服务器 ←→ 客户端B
#
# 1. 客户端A 向 TURN 服务器申请一个中继地址
# 2. TURN 服务器分配 relayed-address: 203.0.113.200:50000
# 3. 客户端A 将这个地址告诉客户端B(通过信令通道)
# 4. 客户端B 向 203.0.113.200:50000 发送数据
# 5. TURN 服务器将数据转发给客户端A
#
# 所有流量都经过 TURN 服务器,延迟增加,带宽成本高
# 但保证在任何 NAT 类型下都能工作

TURN 的代价是明显的:所有流量都经过中继,延迟增加一跳,带宽成本翻倍。因此 TURN 只作为最后的兜底手段——ICE 框架会优先尝试直接连接(STUN),只有全部失败才回退到 TURN。

4.3 ICE:综合框架#

ICE(Interactive Connectivity Establishment,RFC 8445)不是一种新的穿透技术,而是一个综合框架——它系统地收集所有可能的连接方式(候选地址),按优先级逐一尝试,选择第一个成功的。

flowchart TB subgraph 候选收集["候选地址收集"] HOST["Host 候选<br/>本地网卡地址<br/>192.168.1.105:54321"] SRFLX["Server Reflexive 候选<br/>STUN 发现的公网地址<br/>203.0.113.42:54321"] RELAY["Relay 候选<br/>TURN 中继地址<br/>203.0.113.200:50000"] end subgraph 连接检查["连接检查(按优先级)"] C1["1. Host ↔ Host<br/>同局域网直连"] C2["2. Host ↔ Srflx<br/>一方直连一方穿透"] C3["3. Srflx ↔ Srflx<br/>双方 STUN 穿透"] C4["4. 任意 ↔ Relay<br/>TURN 中继兜底"] end HOST --> C1 SRFLX --> C2 SRFLX --> C3 RELAY --> C4 C1 -->|成功| OK["建立连接"] C1 -->|失败| C2 C2 -->|成功| OK C2 -->|失败| C3 C3 -->|成功| OK C3 -->|失败| C4 C4 -->|成功| OK style HOST fill:#e3f2fd,stroke:#1565c0 style SRFLX fill:#fff3e0,stroke:#e65100 style RELAY fill:#fce4ec,stroke:#c62828 style OK fill:#e8f5e9,stroke:#2e7d32

ICE 的候选地址有三类:

候选类型来源优先级说明
Host 候选本地网卡最高直接使用本地 IP,仅同局域网有效
Server Reflexive 候选STUNNAT 映射的公网 IP:端口
Peer Reflexive 候选连接检查中发现ICE 检查过程中新发现的映射
Relay 候选TURN最低TURN 中继地址,保证可达但延迟高

4.4 WebRTC 穿透流程#

WebRTC 是 ICE 框架最典型的应用。一个完整的 WebRTC 连接建立过程:

# WebRTC 连接建立的关键步骤
#
# 1. 信令交换(通过 WebSocket/HTTP,非 WebRTC 本身)
# - 交换 SDP(Session Description Protocol)
# - SDP 包含媒体格式、ICE 候选等信息
#
# 2. ICE 候选收集
# - Host 候选:枚举本地网卡
# - Srflx 候选:查询 STUN 服务器
# - Relay 候选:申请 TURN 中继
#
# 3. ICE 连接检查
# - 对每个候选对发送 STUN Binding Request
# - 成功的候选对标记为 valid
# - 选择优先级最高的 valid 候选对
#
# 4. DTLS 握手
# - 在 ICE 选定的连接上建立加密通道
#
# 5. 媒体流传输
# - SRTP 加密的音视频数据通过选定的连接传输
# 使用 ss 查看套接字统计
ss -s
# 典型输出:
# Total: 234 (kernel 0)
# TCP: 56 (estab 30, closed 12, orphaned 0, timewait 12)
# Transport Total IP IPv6
# RAW 1 0 1
# UDP 12 8 4
# TCP 44 36 8
# INET 57 44 13
# FRAG 0 0 0
# 查看 UDP 套接字(WebRTC 使用 UDP)
ss -ulnp
# 查看特定连接的详细信息
ss -ti dst 203.0.113.42
# Cisco IOS 配置 NAT(企业级设备)
# ip nat inside source static tcp 192.168.1.100 80 203.0.113.42 8080
# ip nat inside source list 1 interface GigabitEthernet0/0 overload
# access-list 1 permit 192.168.1.0 0.0.0.255
Note

WebRTC 的信令交换不属于 ICE 本身——ICE 只负责连接检查和候选选择。信令通常通过 WebSocket、HTTP 等方式实现,交换 SDP offer/answer 和 ICE 候选。这种分离设计让 ICE 可以适配任何信令机制。

五、中间盒:互联网的”隐形基础设施”#

NAT 只是中间盒的一种。互联网中还有大量设备不在”路由器只转发 IP 包”的教科书模型里——防火墙、DPI、负载均衡器、CDN——它们构成了互联网的”隐形基础设施”。

5.1 中间盒的位置#

graph LR subgraph 内网["内网"] CLIENT["客户端"] end subgraph 边界["网络边界"] NAT["NAT<br/>地址翻译"] FW["防火墙<br/>包过滤"] end subgraph 运营商["运营商网络"] DPI["DPI<br/>深度包检测"] CGNAT["CGNAT<br/>运营商级NAT"] end subgraph 数据中心["数据中心"] LB["负载均衡器<br/>流量分发"] WAF["WAF<br/>应用层防火墙"] SERVER["服务器"] end subgraph 边缘["边缘节点"] CDN["CDN<br/>内容缓存"] end CLIENT --> NAT --> FW --> DPI --> CGNAT --> LB --> WAF --> SERVER CDN -.->|缓存命中| CLIENT style 内网 fill:#e3f2fd,stroke:#1565c0 style 边界 fill:#fff3e0,stroke:#e65100 style 运营商 fill:#fce4ec,stroke:#c62828 style 数据中心 fill:#e0f2f1,stroke:#00695c style 边缘 fill:#f3e5f5,stroke:#6a1b9a

数据包从客户端到服务器,可能经过 5-6 种不同的中间盒。每个中间盒都在做”路由器不该做的事”——改写地址、过滤包、检查内容、分发流量、缓存数据。

5.2 防火墙#

防火墙是最常见的中间盒,它根据规则决定哪些包可以通过、哪些包被丢弃。

包过滤防火墙(无状态)只看单个包的头部信息:

# Linux iptables 包过滤规则
# 允许已建立的连接和相关连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 允许 SSH 入站
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 允许 HTTP/HTTPS 入站
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 丢弃其他所有入站流量
iptables -A INPUT -j DROP
# 查看规则
iptables -L -n -v --line-numbers

状态检测防火墙(有状态)跟踪连接状态,能识别属于同一连接的包:

# 状态检测依赖 conntrack
# 查看连接状态
conntrack -L -p tcp --state ESTABLISHED
# 典型输出:
# tcp 6 431999 ESTABLISHED src=192.168.1.105 dst=93.184.216.34 sport=54321 dport=80
# 只允许内网主动发起的连接(类似 NAT 的隐式安全,但更灵活)
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
iptables -A FORWARD -j DROP

应用层网关(ALG)能理解应用层协议,为特定协议做特殊处理:

  • FTP ALG:FTP 使用控制通道和数据通道分离,NAT 需要改写 FTP PORT 命令中的 IP 地址
  • SIP ALG:VoIP 的 SIP 协议在消息体中嵌入 IP 地址,NAT 需要改写
  • DNS ALG:某些 NAT 会缓存 DNS 响应,对后续查询返回缓存结果

ALG 是 NAT 和应用层协议冲突的产物——应用层协议把 IP 地址嵌在数据载荷里,NAT 只改写 IP 头部,导致载荷中的地址与实际不符。ALG 的存在本身就说明 NAT 破坏了协议的层次分离。

5.3 DPI:深度包检测#

DPI(Deep Packet Inspection)不只看包头,还检查包的载荷内容。它的用途包括:

  • 流量分类:识别应用类型(视频、文件下载、P2P),用于 QoS 或限速
  • 入侵检测:匹配已知攻击特征(Snort、Suricata 规则)
  • 内容过滤:根据关键词或 URL 过滤特定内容
  • 协议识别:识别加密流量的应用层协议(如通过 TLS SNI 识别访问的域名)
# nmap NAT 检测脚本(检测 NAT 类型和外部地址)
nmap -sU -p 3478 --script stun-version stun.l.google.com
# 用 tcpdump 抓取经过 NAT 的包,观察地址改写
tcpdump -i any -nn -vv host 93.184.216.34
# 典型输出(在 NAT 设备上同时抓内外接口):
# 内网接口:IP 192.168.1.105.54321 > 93.184.216.34.80: Flags [S]
# 外网接口:IP 203.0.113.42.54321 > 93.184.216.34.80: Flags [S]
# 注意源地址从 192.168.1.105 变为 203.0.113.42

DPI 的技术挑战在于加密——TLS 1.3 加密了大部分握手信息,ESNI/ECH 加密了 SNI,使得 DPI 越来越难以识别加密流量的具体内容。这是隐私保护和流量管理的持续博弈。

5.4 负载均衡器#

负载均衡器是数据中心中最关键的中间盒之一,它将流量分发到多台后端服务器:

层级工作方式改写内容典型产品适用场景
L4(传输层)基于 IP:端口分发目的 IP + 端口(DNAT)LVS、IPVS高吞吐、协议无关
L7(应用层)基于 HTTP 头/URL/Cookie 分发全部(反向代理)Nginx、HAProxy、Envoy智能路由、会话保持

L4 负载均衡器本质上就是一个高性能的 DNAT——它不改写应用层内容,只改写 IP 和端口,因此速度极快。L7 负载均衡器是反向代理——它终止客户端连接,建立到后端的新连接,可以检查和改写 HTTP 头部。

# IPVS(L4 负载均衡器)配置示例
ipvsadm -A -t 203.0.113.42:80 -s rr # 创建虚拟服务,轮询调度
ipvsadm -a -t 203.0.113.42:80 -r 10.0.0.1:80 -m # 添加后端服务器(NAT 模式)
ipvsadm -a -t 203.0.113.42:80 -r 10.0.0.2:80 -m
ipvsadm -a -t 203.0.113.42:80 -r 10.0.0.3:80 -m
# 查看连接统计
ipvsadm -L -n --stats
# Nginx(L7 负载均衡器)配置示例
# upstream backend {
# server 10.0.0.1:80;
# server 10.0.0.2:80;
# server 10.0.0.3:80;
# }
# server {
# listen 80;
# location / {
# proxy_pass http://backend;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# }
# }

5.5 CDN 作为应用层中间盒#

CDN(Content Delivery Network)从网络架构的角度看,也是一种中间盒——它在客户端和源站之间插入了一层缓存节点。当用户请求 https://example.com/image.jpg 时,DNS 将域名解析到最近的 CDN 边缘节点,CDN 节点如果有缓存就直接返回,没有则回源获取。

CDN 与传统中间盒的区别在于:它工作在应用层,通过 DNS 任播或 HTTP 重定向将流量引导到边缘节点,而不是在网络层拦截流量。更多细节在 CDN与内容分发 中展开。

六、现实互联网架构:分层 + 中间盒#

6.1 干净分层是理想#

教科书里的互联网是干净分层的——每一层只关心自己的职责,路由器只转发 IP 包,交换机只转发以太网帧。这个模型优雅、简洁、可推理。

但现实不是这样。NAT 打破了网络层的端到端,防火墙打破了”路由器只转发”的假设,DPI 打破了层次隔离,负载均衡器同时操作 L4 和 L7,CDN 改变了 DNS 解析和 HTTP 路由。每一层都有”越界”的设备在做不该做的事。

6.2 分层 + 中间盒是现实#

Pamela Zave 和 Jennifer Rexford 在《The Misfortunes of Network Architecture》中精辟地总结了这种张力:互联网的架构不是教科书描述的干净分层,而是分层 + 中间盒的混合体。中间盒的存在不是偶然的,而是因为纯粹的分层模型无法满足现实需求:

  • NAT:分层模型说地址应该全局唯一,但 IPv4 地址不够用
  • 防火墙:分层模型说路由器只转发,但安全需要过滤
  • DPI:分层模型说层次隔离,但运营商需要管理流量
  • 负载均衡器:分层模型说端到端,但服务器需要横向扩展
  • CDN:分层模型说路由器选路,但延迟需要边缘缓存

这些需求是真实的,中间盒是对真实需求的工程回应。问题不在于中间盒存在,而在于中间盒的实现方式——它们通常是专有设备,配置复杂,难以编程,与路由器的交互不透明。

6.3 可编程化趋势#

中间盒的未来不是消灭它们,而是让它们可编程

  • eBPF/XDP:在 Linux 内核中运行自定义包处理程序,替代专有防火墙和负载均衡器
  • P4:可编程数据平面语言,让交换机/网卡执行自定义包处理逻辑
  • SDN:集中控制网络设备,通过控制器编程转发规则
  • Service Mesh:将中间盒功能(负载均衡、加密、观测)从网络设备移到应用层的 Sidecar 代理

这些趋势的共同点是:将中间盒的功能从专有硬件中解放出来,变成可编程的软件。这不改变”互联网是分层 + 中间盒”的现实,但让中间盒更透明、更可控、更易演进。

七、动手实践:观察 NAT 和中间盒#

7.1 观察本机 NAT 配置#

# 查看本机 IP 地址(判断是否在 NAT 后面)
ip addr show
# 如果你的 IP 是以下范围,你在 NAT 后面:
# 10.0.0.0 - 10.255.255.255 (10.0.0.0/8)
# 172.16.0.0 - 172.31.255.255 (172.16.0.0/12)
# 192.168.0.0 - 192.168.255.255 (192.168.0.0/16)
# 查看公网 IP
curl -s ifconfig.me
echo
# 如果公网 IP ≠ 本机 IP,你在 NAT 后面
# 如果公网 IP = 本机 IP,你有公网地址(罕见)

7.2 检测 NAT 类型#

# 安装 STUN 客户端
pip3 install pystun3
# 检测 NAT 类型
pystun3
# 可能的输出:
# NAT Type: Full Cone NAT → 穿透容易
# NAT Type: Restricted Cone NAT → 穿透需要配合
# NAT Type: Port Restricted Cone NAT → 穿透困难
# NAT Type: Symmetric NAT → 穿透极难,需要 TURN
# NAT Type: No NAT (Public IP) → 无 NAT,直连
# 用 nmap 检测
nmap -sU -p 3478 --script stun-info stun.l.google.com

7.3 观察 conntrack 表#

# 查看 conntrack 表
cat /proc/net/nf_conntrack | head -20
# 用 conntrack 工具查看
conntrack -L | head -20
# 统计连接数
conntrack -C
# 按协议统计
conntrack -L -p tcp | wc -l
conntrack -L -p udp | wc -l
# 查看特定目标的连接
conntrack -L -d 93.184.216.34
# 删除特定连接(调试用)
conntrack -D -d 93.184.216.34

7.4 抓包观察 NAT 改写#

# 在 NAT 设备上同时抓内外接口
# 内网接口
tcpdump -i eth1 -nn not port 22 -w /tmp/nat_inside.pcap &
# 外网接口
tcpdump -i eth0 -nn not port 22 -w /tmp/nat_outside.pcap &
# 产生一些流量
curl -s http://example.com > /dev/null
# 停止抓包
kill %1 %2
# 对比两个 pcap 文件
# 内网:Src: 192.168.1.105:54321 → Dst: 93.184.216.34:80
# 外网:Src: 203.0.113.42:54321 → Dst: 93.184.216.34:80
# 源地址被 NAT 改写了

八、本章小结#

概念要点
NAT 存在原因IPv4 地址耗尽,NAPT 让多台设备共享一个公网 IP
四种 NAT 类型静态 NAT(1:1)、动态 NAT(池)、NAPT(端口复用)、双向 NAT
NAPT 工作原理转换表映射 内部IP:端口 ↔ 外部IP:端口,出站改写源地址,入站改写目的地址
Conntrack连接追踪表记录每个 NAT 映射,表满会导致新连接被丢弃
NAT 破坏端到端接收方无法主动连接、IP 不可达、P2P 受影响
STUN发现 NAT 映射的公网 IP:端口,但对称型 NAT 无法穿透
TURN通过中继服务器转发流量,保证可达但延迟高、带宽成本大
ICE综合框架,按优先级尝试 Host/Srflx/Relay 候选,选择最优连接
防火墙包过滤(无状态)、状态检测(有状态)、ALG(应用层网关)
DPI深度包检测,检查载荷内容,受加密流量挑战
负载均衡器L4(DNAT 分发)和 L7(反向代理),数据中心核心中间盒
CDN应用层中间盒,通过边缘缓存降低延迟
现实架构干净分层是理想,分层 + 中间盒是现实,可编程化是趋势

NAT 和中间盒是互联网对现实需求的工程妥协。下一章进入 域内路由,看看组织内部的路由器如何通过 OSPF 和 IS-IS 计算最短路径——这次,路由器终于只做路由器该做的事了。


参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

NAT与中间盒:互联网的"不完美"现实
https://blog.souloss.com/posts/internet-architecture/nat-and-middleboxes/
作者
Souloss
发布于
2022-03-26
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时