4_iptables_NAT与网络防火墙

1. 为什么需要 NAT?

NAT(Network Address Translation,网络地址转换)解决两个核心问题:

  • IPv4 地址只有约 43 亿个,远不够每台设备独占一个。NAT 允许多台内网设备共用一个公网 IP 上网——类似一家 100 人的公司只需一个总机号(公网 IP),员工用各自的分机号(内网 IP)打外线,对方看到的来电显示始终是总机号。
  • 除了地址复用,NAT 还提供天然的安全隔离:外部无法直接访问内网 IP。

理解了 NAT 的动机,接下来看它的两种核心工作模式:SNAT 和 DNAT。

2. SNAT 与 DNAT 的本质

NAT 只做一件事:改地址。改源地址就是 SNAT,改目标地址就是 DNAT。

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart LR
    subgraph net_in ["内网"]
        A["内网主机 10.0.0.5"]
    end
    subgraph gw ["网关"]
        B["SNAT/DNAT 公网IP: 2.2.2.2"]
    end
    subgraph net_out ["外网"]
        C["Web 服务器 8.8.8.8"]
    end

    A -- "① 源: 10.0.0.5 目标: 8.8.8.8" --> B
    B -- "② 源: 2.2.2.2 目标: 8.8.8.8" --> C
    C -- "③ 源: 8.8.8.8 目标: 2.2.2.2" --> B
    B -- "④ 源: 8.8.8.8 目标: 10.0.0.5" --> A

    classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef success fill:#10B981,stroke:#059669,color:#fff
    classDef warning fill:#F59E0B,stroke:#D97706,color:#fff

    class A primary
    class B warning
    class C success

整个过程是 SNAT(不是 DNAT),取决于前半段改的是什么。步骤 ② 改了源地址,所以是 SNAT。步骤 ④ 中目标地址被自动改回来,由 conntrack 完成,无需手动配规则。

SNATDNAT
改什么源地址目标地址
在哪改POSTROUTING(出去前)PREROUTING(进来后、路由前)
典型场景内网共享上网外部访问内网服务(端口映射)
反向自动响应包的目标地址自动改回响应包的源地址自动改回

下面分别展开这两种模式的配置方法。

3. SNAT——内网共享上网

3.1 基本配置

场景:内网网段 10.1.0.0/16 通过网关(公网 IP 2.2.2.2)上网。

1
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -j SNAT --to-source 2.2.2.2

各参数含义:

参数含义
-t nat操作 nat 表
-A POSTROUTING追加到 POSTROUTING 链
-s 10.1.0.0/16匹配源地址为内网网段
-j SNAT --to-source 2.2.2.2把源地址改为公网 IP

3.2 为什么 SNAT 必须在 POSTROUTING?

类比:寄信时先写好收件人地址(路由判断),在投入邮筒前最后一刻才把寄件人地址从 “ 家庭地址 “ 改成 “ 公司地址 “。如果先改了寄件人地址再做路由判断,万一出错就改不回来了。

技术原因:POSTROUTING 是报文离开系统前的最后一关,此时路由已完成、出口网卡已确定——这是修改源地址的最佳时机。

知道了固定 IP 的 SNAT 怎么配,那动态 IP 场景怎么办?

4. MASQUERADE——动态 SNAT

SNAT 的 --to-source 需要写死公网 IP。如果使用拨号上网或 DHCP 动态分配 IP,每次 IP 变化都要修改规则。MASQUERADE 自动使用出口网卡当前 IP 作为源地址:

1
2
3
4
5
# SNAT(写死 IP)
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -j SNAT --to-source 2.2.2.2

# MASQUERADE(自动获取出口网卡 IP)
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -o eth0 -j MASQUERADE

类比:SNAT 相当于在发件人地址写 “ 北京市 XX 路 XX 号 “(固定地址);MASQUERADE 相当于写 “ 发件人:当前所在地 “(到哪用哪的地址)。

公网 IP 固定时,优先用 SNAT。MASQUERADE 每次都要查询网卡 IP,性能略低。

前面讲的都是 “ 内网出去 “ 的场景,反过来——外网怎么访问内网服务?这就是 DNAT 的工作。

5. DNAT——外网访问内网服务

5.1 场景

公司只有一个公网 IP 2.2.2.2,内网有两个服务需要从外网访问:

  • Web 服务器 10.1.0.5:80
  • 远程桌面 10.1.0.6:3389

5.2 配置

1
2
3
4
5
# 公网 80 端口 → 内网 Web 服务器
iptables -t nat -I PREROUTING -d 2.2.2.2 -p tcp --dport 80 -j DNAT --to-destination 10.1.0.5:80

# 公网 3389 端口 → 内网远程桌面
iptables -t nat -I PREROUTING -d 2.2.2.2 -p tcp --dport 3389 -j DNAT --to-destination 10.1.0.6:3389

各参数含义:

参数含义
-t nat -I PREROUTING在 nat 表 PREROUTING 链插入规则
-d 2.2.2.2匹配目标地址为公网 IP
-p tcp --dport 80匹配 TCP 目标端口 80
-j DNAT --to-destination 10.1.0.5:80把目标地址改为内网 IP: 端口

5.3 为什么 DNAT 必须在 PREROUTING?

类比:收到一封寄给 “ 公司总机 “ 的信,必须在拆开之前就决定转给哪个部门。拆开后(路由判断完成),再想转发就来不及了。

技术原因:路由判断依据目标地址决定包的走向,DNAT 修改的正是目标地址。必须在路由前完成修改,否则路由会用错误的原始地址。

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TD
    A["数据包进入"] --> B["PREROUTING"]
    B --> C{"DNAT 规则匹配?"}
    C -->|"是"| D["修改目标地址"]
    C -->|"否"| E["保持原地址"]
    D --> F["路由判断"]
    E --> F
    F --> G{"目标是本机?"}
    G -->|"是"| H["INPUT 链"]
    G -->|"否"| I["FORWARD 链"]
    H --> L["本地进程"]
    I --> J["POSTROUTING"]
    J --> K["数据包离开"]

    classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef success fill:#10B981,stroke:#059669,color:#fff
    classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
    classDef danger fill:#EF4444,stroke:#DC2626,color:#fff

    class A,K primary
    class B,D warning
    class F,G success
    class H,I,L danger

5.4 DNAT 何时需要搭配 SNAT?

配置了 DNAT 规则但不生效,最常见的原因是回包没有走网关,导致 conntrack 无法还原地址。

5.4.1 正常情况:回包经过网关

如果内网服务器的默认网关就是 NAT 网关本身(大多数内网部署都是如此),DNAT 单独就能工作:

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3B82F6', 'actorTextColor': '#fff', 'actorBorder': '#2563EB', 'signalColor': '#1E3A5F', 'activationBkgColor': '#DBEAFE', 'activationBorderColor': '#3B82F6'}}}%%
sequenceDiagram
    autonumber
    participant Client as "客户端 1.1.1.1"
    participant GW as "网关 2.2.2.2 / 10.1.0.1"
    participant Web as "Web 10.1.0.5"

    Client->>GW: "src: 1.1.1.1 dst: 2.2.2.2:80"
    Note over GW: "DNAT: dst 改为 10.1.0.5:80"
    GW->>Web: "src: 1.1.1.1 dst: 10.1.0.5:80"
    Web->>GW: "src: 10.1.0.5 dst: 1.1.1.1"
    Note over GW: "conntrack 匹配,自动还原: src 改为 2.2.2.2"
    GW->>Client: "src: 2.2.2.2 dst: 1.1.1.1"

回包经过网关,conntrack 自动将源地址还原为 2.2.2.2,客户端正常收到响应。

5.4.2 异常情况:回包绕过网关

当内网服务器的默认网关不是 NAT 网关时(例如多网关环境、服务器有独立出口),回包会走其他路径,绕过 NAT 网关:

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3B82F6', 'actorTextColor': '#fff', 'actorBorder': '#2563EB', 'signalColor': '#1E3A5F', 'activationBkgColor': '#DBEAFE', 'activationBorderColor': '#3B82F6'}}}%%
sequenceDiagram
    autonumber
    participant Client as "客户端 1.1.1.1"
    participant GW as "网关 2.2.2.2"
    participant Web as "Web 10.1.0.5"

    Client->>GW: "src: 1.1.1.1 dst: 2.2.2.2:80"
    Note over GW: "DNAT: dst 改为 10.1.0.5:80"
    GW->>Web: "src: 1.1.1.1 dst: 10.1.0.5:80"
    Web-->>Client: "src: 10.1.0.5 dst: 1.1.1.1 (绕过网关)"
    Note over Client: "期望来自 2.2.2.2 的回包,收到 10.1.0.5,丢弃"

此时需要在网关上对 DNAT 流量做 SNAT,强制回包也经过网关:

1
2
3
4
5
6
# DNAT 规则
iptables -t nat -I PREROUTING -d 2.2.2.2 -p tcp --dport 80 -j DNAT --to-destination 10.1.0.5:80

# 配套 SNAT:将外网→内网 Web 的流量源地址改为网关内网 IP
# 这样 Web 服务器回包时,目标地址是网关 10.1.0.1,回包必经网关
iptables -t nat -A POSTROUTING -d 10.1.0.5 -p tcp --dport 80 -j SNAT --to-source 10.1.0.1

关键点:SNAT 的 --to-source 使用的是网关的内网 IP(10.1.0.1),而非公网 IP。这样 Web 服务器看到的源地址是 10.1.0.1,回包自然发回网关,conntrack 再完成地址还原。

代价:配套 SNAT 后,内网服务器的访问日志中看到的客户端 IP 全部变成网关 IP 10.1.0.1,丢失了真实客户端 IP 信息。如果需要保留真实 IP,应优先调整内网服务器的默认网关指向 NAT 网关。

6. REDIRECT——本机端口映射

将访问本机某个端口的流量重定向到另一个端口:

1
2
# 本机 80 端口 → 重定向到 8080 端口
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080

典型场景:应用运行在 8080 端口,但希望用户通过 80 端口访问(非 root 用户无法直接监听 1024 以下端口)。

REDIRECT 也支持端口范围,例如 --to-ports 8080-8090,内核会在这个范围内轮询分配端口。

REDIRECT 只能在 PREROUTING 或 OUTPUT 链中使用,且仅限本机端口映射,不能跨主机。

NAT 解决了地址转换问题,但经过网关转发的流量如何做安全管控?这就需要 FORWARD 链。

7. 网络防火墙:FORWARD 链

7.1 角色区分

iptables 的两种防火墙角色:

角色工作链场景
主机防火墙INPUT / OUTPUT保护本机
网络防火墙FORWARD保护本机背后的网络

Linux 主机充当路由器/网关时,转发流量不经过 INPUT/OUTPUT 链,只经过 FORWARD 链。

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#3B82F6', 'primaryTextColor': '#1E3A5F', 'primaryBorderColor': '#2563EB', 'lineColor': '#60A5FA', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart LR
    A["外网主机"] --> B["网关 FORWARD 链"]
    B --> C["内网服务器"]
    C --> B
    B --> A

    classDef primary fill:#3B82F6,stroke:#2563EB,color:#fff
    classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
    classDef success fill:#10B981,stroke:#059669,color:#fff

    class A primary
    class B warning
    class C success

7.2 配置网络防火墙

前提:开启 IP 转发。

1
2
3
4
5
6
# 临时开启
echo 1 > /proc/sys/net/ipv4/ip_forward

# 永久开启(使用独立配置文件,避免重复追加)
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-ip-forward.conf
sysctl -p /etc/sysctl.d/99-ip-forward.conf

白名单策略配置(推荐):

1
2
3
4
5
6
7
8
# 默认拒绝所有转发
iptables -A FORWARD -j REJECT

# 允许内网访问外网 HTTP
iptables -I FORWARD -s 10.1.0.0/16 -p tcp --dport 80 -j ACCEPT

# 允许已建立连接的回包
iptables -I FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

-m conntrack --ctstate-m state --state 的新版语法,功能相同但更推荐。旧版本内核若不支持 conntrack,可回退到 state。

7.3 State 模块简化双向规则

FORWARD 规则的常见困扰是 “ 双向性 “——放行请求后,还要放行响应:

1
2
3
4
5
6
7
8
9
10
11
# 不用 state 模块:每个服务都需要请求+响应两条规则
iptables -I FORWARD -s 10.1.0.0/16 -p tcp --dport 80 -j ACCEPT # 请求
iptables -I FORWARD -d 10.1.0.0/16 -p tcp --sport 80 -j ACCEPT # 响应
iptables -I FORWARD -s 10.1.0.0/16 -p tcp --dport 22 -j ACCEPT # 请求
iptables -I FORWARD -d 10.1.0.0/16 -p tcp --sport 22 -j ACCEPT # 响应

# 用 conntrack 模块:一条规则解决所有响应
iptables -I FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -I FORWARD -s 10.1.0.0/16 -p tcp --dport 80 -j ACCEPT
iptables -I FORWARD -s 10.1.0.0/16 -p tcp --dport 22 -j ACCEPT
# 无需单独写响应规则

理解了各个组件的原理,接下来把它们组合起来,看一个完整的企业网关配置。

8. 综合实战:企业级网关配置

8.1 网络拓扑

1
2
3
4
5
┌─────────────┐      ┌──────────────────────┐      ┌──────────────┐
│ 外网客户端 │ │ Linux 网关 │ │ 内网服务器 │
│ 1.1.1.1 │◄────►│ eth0: 2.2.2.2 (公网) │◄────►│ 10.1.0.5 │
│ │ │ eth1: 10.1.0.1 (内网) │ │ (Web: 80) │
└─────────────┘ └──────────────────────┘ └──────────────┘

目标:① 内网上网(SNAT) ② 外网访问内网 Web(DNAT) ③ FORWARD 链安全过滤

8.2 完整配置脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/bin/bash
# ============================================
# 企业网关 iptables 配置
# ============================================

# 0. 开启 IP 转发
echo 1 > /proc/sys/net/ipv4/ip_forward

# 1. 清空所有规则和自定义链
iptables -F
iptables -t nat -F
iptables -X
iptables -t nat -X

# 2. 设置默认策略(兜底安全保障)
# 即使后续规则写漏,未匹配的包也会被 DROP
# 生产环境推荐 DROP;调试阶段可临时改为 REJECT 以便排查
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# ========== NAT 配置 ==========

# 3. SNAT:内网通过网关上网
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -o eth0 -j SNAT --to-source 2.2.2.2

# 4. DNAT:外网 80 端口 → 内网 Web 服务器
iptables -t nat -A PREROUTING -d 2.2.2.2 -p tcp --dport 80 -j DNAT --to-destination 10.1.0.5:80

# ========== FORWARD 安全策略 ==========

# 5. 放行已建立连接的回包(必须在最前面)
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 6. 放行内网 → 外网的 HTTP/HTTPS
iptables -A FORWARD -s 10.1.0.0/16 -p tcp -m multiport --dports 80,443 -j ACCEPT

# 7. 放行内网 → 外网的 DNS(UDP + TCP)
iptables -A FORWARD -s 10.1.0.0/16 -p udp --dport 53 -j ACCEPT
iptables -A FORWARD -s 10.1.0.0/16 -p tcp --dport 53 -j ACCEPT

# 8. 放行外网 → 内网 Web(DNAT 后目标变成 10.1.0.5)
iptables -A FORWARD -d 10.1.0.5 -p tcp --dport 80 -j ACCEPT

# 默认策略已设为 DROP,无需兜底 REJECT 规则

# ========== INPUT 保护网关自身 ==========

# 9. 放行回环和已建立连接
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 10. 仅允许内网 SSH 管理
iptables -A INPUT -s 10.1.0.0/16 -p tcp --dport 22 -j ACCEPT

# 默认策略已设为 DROP,无需兜底 REJECT 规则

echo "网关配置完成"
iptables --line -nvL
iptables -t nat --line -nvL

8.3 规则持久化

iptables 规则存储在内存中,系统重启后会丢失。持久化方式:

1
2
3
4
5
# 导出当前规则到文件
iptables-save > /etc/iptables/rules.v4

# 从文件恢复规则(通常在 /etc/rc.local 或 systemd 服务中调用)
iptables-restore < /etc/iptables/rules.v4

在 Debian/Ubuntu 上可安装 iptables-persistent 包实现开机自动加载:

1
2
apt install iptables-persistent
# 安装时会提示保存当前规则,之后用 netfilter-persistent save 更新

在 CentOS/RHEL 上使用 iptables-services

1
2
3
yum install iptables-services
systemctl enable iptables
service iptables save

9. NAT 命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ========== SNAT ==========
# 静态 SNAT(固定公网 IP)
iptables -t nat -A POSTROUTING -s 内网网段 -j SNAT --to-source 公网IP

# 动态 SNAT(动态公网 IP)
iptables -t nat -A POSTROUTING -s 内网网段 -o 出口网卡 -j MASQUERADE

# ========== DNAT ==========
# 端口映射(外网:端口 → 内网:端口)
iptables -t nat -I PREROUTING -d 公网IP -p tcp --dport 公网端口 -j DNAT --to-destination 内网IP:端口

# ========== REDIRECT ==========
# 本机端口重定向
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080

# ========== 查看 ==========
iptables -t nat --line -nvL

下一篇:现代运维中的 iptables —— nftables 迁移指南、Docker/K8s 中的 iptables、生产最佳实践与常见故障排查。