8.8 NAT 与端口映射
一句话
NAT 做的是 “改写地址与端口,并记住这层映射”:SNAT 让私网出公网,DNAT 让公网进私网;代价是三样——连接状态(conntrack)成为单点、源 IP 丢失、端口数量成为上限。
两种 NAT,两个方向
| 类型 | 方向 | 改写什么 | 典型实现 |
|---|---|---|---|
| SNAT(源地址转换) | 内 → 外 | 源 IP:端口 → NAT 的 IP:端口 | 云 NAT 网关、iptables -t nat -j MASQUERADE、家用路由器 |
| DNAT(目的地址转换) | 外 → 内 | 目的 IP:端口 → 内网 IP:端口 | 端口映射/端口转发、云负载均衡的转发 |
| 双向 NAT | 双向 | 两端都改写 | 复杂互调场景,排障最难 |
SNAT: 10.0.1.5:53124 → 203.0.113.7:40001 → 目标:443
DNAT: 203.0.113.7:8443 → 10.0.1.5:5432
关键点:NAT 是有状态的。设备必须记住 “哪个内部连接对应哪个外部端口”,这条记录存在连接跟踪表(conntrack)里。
端口映射的三种形态
| 形态 | 例子 | 说明 |
|---|---|---|
| 一对一(EIP 绑定) | 公网 IP 直接绑到实例 | 没有端口改写,源 IP 保留;最贵(占一个公网 IP) |
| 端口转发 | 公网:8443 → 内网:5432 | 一个公网 IP 映射多个服务,靠端口区分 |
| 端口段 | 公网:30000-30100 → 内网:30000-30100 | K8s NodePort 的典型形态,端口范围有限 |
生产上更推荐 用负载均衡(L4/L7)代替端口映射:LB 能做健康检查、连接限速、证书终结,而裸 NAT 只能转发。选型见 8.9 四层与七层负载均衡。
三笔必须算的账
| 账 | 上限 | 打满后的症状 | 缓解 |
|---|---|---|---|
| conntrack 表容量 | 取决于内核参数与内存 | 新连接随机失败、日志极少(nf_conntrack: table full) | 调大 nf_conntrack_max、减少长连接、分流到多 NAT |
| 端口数量 | 单源 IP 到单目标 IP:port 约 6.4 万 | 连接排队/超时(尤其高频调用同一目标) | 增加源 IP(多 NAT 网关)、连接复用 |
| NAT 带宽/连接数规格 | 云产品按规格限量 | 突发流量被限速或丢弃 | 提前压测、按峰值选规格 |
# 看当前连接跟踪量(Linux)
conntrack -C 2>/dev/null || echo "conntrack 工具未装"
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max 2>/dev/null
ss -tan state established | wc -l # 本机已建立连接数
源 IP 去哪了:XFF 与真实客户端
经过 SNAT 后,服务端看到的源 IP 是 NAT 的地址。恢复真实来源有三种手段:
| 手段 | 在哪生效 | 限制 |
|---|---|---|
X-Forwarded-For / Forwarded | 应用层(七层代理追加) | 可被伪造,只能信任最靠近的一跳 |
PROXY protocol | 四层(LB 与后端都支持) | 需要后端显式解析,不是所有软件都支持 |
externalTrafficPolicy: Local(K8s) | Service 层 | 保留源 IP,但流量分布不均(见 7.3 Service 与 Ingress) |
安全规则:只信任你自己那一跳追加的 XFF。直接取 X-Forwarded-For 的第一段,等于让攻击者随便写来源 IP。
云上的 NAT 网关
| 项 | 说明 |
|---|---|
| 作用 | 私有子网出公网的默认出口(SNAT);子网规划见 8.7 VPC、子网、安全组 |
| 计费 | 按小时 + 按流量(见 5.6 云成本结构) |
| 可用区 | 按 AZ 部署,跨 AZ 的私有子网要各自有 NAT,否则单 AZ 故障断网 |
| 入向 | 不支持;要入向用 LB/EIP,不要用 NAT “反向打洞” |
反模式
| 反模式 | 后果 |
|---|---|
| 用端口映射把数据库暴露公网 | 被扫描后爆破,数据泄露的头号路径 |
| 只用一台 NAT 网关 | 该 AZ 故障时所有私有子网断网 |
| 忽略 conntrack 上限 | 高并发下新连接随机失败,排查耗时以天计 |
直接信任 X-Forwarded-For 第一段 | 攻击者可伪造源 IP,绕过风控与限流 |
| 单 IP 高频调同一目标 | 源端口耗尽,出现 “连接超时但服务正常” |
| 期望 NAT 支持入向 | 方向搞反,方案从前提就是错的 |
| 排障只看应用日志 | NAT 层的丢包/表满在应用侧没有痕迹 |
三条铁律
🔑 NAT 有状态:conntrack 是容量与故障点,必须监控。
🔑 入向用 LB,出向用 NAT;端口映射只用于过渡与调试。
🔑 真实源 IP 只能信任最近一跳;XFF 拿来即用等于没有来源校验。
自检
Q1 私有子网机器能拉镜像,但外部无法访问它的服务,为什么?
▸ 答案:因为 NAT 只为出向建立映射:出向连接由内部发起,NAT 记住映射并把回包转回;外部主动发起时没有任何映射记录,包被直接丢弃。要提供入向服务,必须用负载均衡、EIP 或端口映射,而不是靠 NAT。
Q2 服务端日志里全是 10.0.0.5(NAT 地址),怎么拿到用户真实 IP?
▸ 答案:靠代理层传递:七层代理追加 X-Forwarded-For(或 Forwarded),四层用 PROXY protocol。应用侧只信任 “自己最近一跳” 写入的值,并把它之后的段丢弃(否则可伪造)。若用 K8s,可配 externalTrafficPolicy: Local 直接保留源 IP,代价是流量分布不均。
Q3 一台 NAT 网关扛着整个 VPC 的出网,出故障会怎样?怎么设计?
▸ 答案:全部私有子网同时失去出网能力(拉不到镜像、调不了外部 API、连不上监控),但入向服务(经 LB)不受影响。设计上要按可用区各部署一台 NAT,路由表按子网指向本 AZ 的 NAT;同时监控 NAT 的连接数与带宽,避免 “单台打满”。
Q4 什么情况下会出现 “服务正常但连接超时”?给一个 NAT 相关的原因。
▸ 答案:源端口耗尽或 conntrack 表满。前者发生在一个源 IP 高频连接同一目标 IP:port 时(可用端口约 6.4 万),新连接没有可用四元组;后者是 NAT 设备记不住更多映射,直接丢新连接。两者在应用日志里都只表现为 “超时”,必须到 NAT/conntrack 层看计数。
Q5 为什么不建议用端口映射把内网 Redis 暴露给办公网?
▸ 答案:因为 Redis 的认证与访问控制很弱(很多部署根本没有密码),一旦映射到公网或办公网,等于把数据库交给扫描器;同时端口映射没有健康检查、限流与审计。正确路径是:跳板机/VPN + 安全组限制来源,或用带鉴权的代理网关,而不是裸 NAT。