一、nat 表:共享上网
1. nat 表到底改什么
nat 表是 nat 网络地址转换的核心配置(这部分在计算机网络里讲过),负责把内网 IP 和公网 IP 互相转换。
iptables 的核心处理流程先捋清楚。访问服务器进程时走上面那条:
PREROUTING---INPUT--OUTPUT--POSTROUTING
只是路过就走下面那条:
PREROUTING--FORWARD--POSTROUTING
如果不开 FORWARD,包没法穿过这台机器,直接被丢掉。但是不管走哪条,PREROUTING 和 POSTROUTING 一定会走(包出去时处理)。

nat 表的 POSTROUTING 链就是在转发出去的时候,防火墙把内网的源 IP(sip)改成自己的公网 IP:

2. 网卡配置文件(CentOS 10 换地方了)
vim /etc/sysconfig/network-scripts/ifcfg-ens33
# centos 10 采用了新管理目录
/etc/NetworkManager/system-connections/
[root@localhost ~]# ll /etc/NetworkManager/system-connections/
总计 8
-rw-------. 1 root root 250 8月28日 21:52 '配置 1.nmconnection'
-rw-------. 1 root root 302 8月28日 21:51 ens33.nmconnection
cat /etc/NetworkManager/system-connections/ens33.nmconnection
[connection]
id=ens33
uuid=41016ec6-765c-30eb-82a7-bd4c363951bc
type=ethernet
autoconnect-priority=-999
interface-name=ens33
timestamp=1787941829
[ethernet]
[ipv4]
address1=192.168.100.100/24
dns=223.5.5.5;8.8.8.8;
gateway=192.168.100.2
method=manual
[ipv6]
addr-gen-mode=eui64
method=auto
[proxy]
vim /etc/NetworkManager/system-connections/ens33.nmconnection
3. 准备两张网卡
目标是把 192.168.73.0/24 网段的 IP 转换成公网 IP 出去。用虚拟机做实验的话,得先确保虚拟机自己能访问外网。
防火墙这边要做两件事:
- 配置防火墙的 SNAT 功能(源 IP 地址转换)
- 开启内核转发 —— 网关服务器/防火墙服务器通常有两块网卡,一块负责内网之间,一块负责内网和外网之间
后端服务器这边:Web 服务器要把网关配置成网关服务器的 IP(Web 服务器找不到的地址会丢给网关)。
那为什么没有两块网卡、没开内核转发的反向代理,也能实现内外网 IP 转换?
因为两者根本不是一个层的东西:

反向代理是应用层的,它自己建两段独立的 TCP 连接,把应用层数据复制转发一遍,所以一块网卡就够了;iptables 是网络层的,直接路由和转发 IP 数据包,不关心包里的内容,要区分内外网口所以通常需要两块网卡。数据包的变化也不一样:反向代理会让源/目的 IP 变成它自己的(代理模式),iptables 是改了 IP 但包本身没有被"拆开"处理。
内核转发是共享上网的核心概念。所谓内核转发(IP forwarding),就是让 Linux 内核把不是发给自己的数据包从一块网卡收进来,再按路由表从另一块网卡送出去。
Linux 默认是关闭这个功能的:一台机器收到包,发现目标 IP 不是自己,内核就认为"这不是给我的",直接丢掉。拿这个实验说,web 服务器要访问外网,包先到达防火墙服务器,防火墙两块网卡一块接内网(192.168.73.x)、一块接外网。不开转发,包到这就被丢了,web 永远上不了外网。
开了转发之后,内核看到"这个包目标不是我,但路由表告诉我该走另一块网卡",就从另一块网卡送出去。
为什么还必须配合 SNAT? web 的 IP 是 192.168.73.129,私网地址,外网服务器收到这种源地址的包,回包时根本不知道往哪送,因为 192.168.73.0/24 不是公网可路由的网段。所以整个流程是:
- web 发出去的包到防火墙
- 防火墙开着内核转发,准备把包送出去
- 同时执行 SNAT 规则,把包里的源地址从 192.168.73.129 改成防火墙的公网 IP
- 外网服务器看到源地址是公网 IP,正常回包给防火墙
- 防火墙根据连接跟踪表(conntrack)里的记录,把回包的目标地址改回 192.168.73.129,再送回内网
一句话:内核转发负责"包能穿过这台机器",SNAT 负责"穿过去的时候换个身份",两个配合才能完成共享上网。
怎么开启:
临时开启(重启失效):
echo 1 > /proc/sys/net/ipv4/ip_forward
永久开启:
vi /etc/sysctl.conf
# 找到或添加这一行:
net.ipv4.ip_forward = 1
# 然后执行生效:
sysctl -p
查看当前状态:
cat /proc/sys/net/ipv4/ip_forward
# 输出 1 表示已开启
还有一个坑: 开了转发之后还要检查 filter 表的 FORWARD 链,因为转发的流量走的是 FORWARD 链,不是 INPUT 链。FORWARD 默认是 DROP 的话,包照样过不去:
iptables -nL FORWARD
默认 ACCEPT 就省事,是 DROP 就得再加一条放行规则。
这个实验需要防火墙有两块网卡,虚拟机上只有一块,所以得自己加一张内网口。网卡这么规划:

先把虚拟机关机,添加一张网卡,默认不分配 ipv4:
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:9a:d9:63 brd ff:ff:ff:ff:ff:ff
altname enp2s1
altname enx000c299ad963
inet 192.168.73.128/24 brd 192.168.73.255 scope global dynamic noprefixroute ens33
valid_lft 1773sec preferred_lft 1773sec
inet6 fe80::177e:42f8:b365:5911/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: ens36: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:9a:d9:6d brd ff:ff:ff:ff:ff:ff
altname enp2s4
altname enx000c299ad96d
新加的 ens36 这会儿还没有 ipv4 地址:

给它配上地址:
# 创建一个新连接,绑定 ens36
nmcli con add con-name ens36 ifname ens36 type ethernet
# 配置静态 IP(作为内网口)
nmcli con mod ens36 ipv4.method manual ipv4.addresses 10.0.0.1/24
# 激活连接
nmcli con up ens36
# 验证
ip addr show ens36
[root@localhost ~]# ip addr show ens36
3: ens36: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:9a:d9:6d brd ff:ff:ff:ff:ff:ff
altname enp2s4
altname enx000c299ad96d
inet 10.0.0.1/24 brd 10.0.0.255 scope global noprefixroute ens36
valid_lft forever preferred_lft forever
inet6 fe80::f7a5:b5b0:f4fb:504f/64 scope link noprefixroute
valid_lft forever preferred_lft forever
手里这台机器叫 ens33 还是 ens36、走的是不是仅主机模式,用 nmcli 摸几遍就记住了:

4. 命令操作
# 修改网关服务器
# 1. 恢复默认
iptables -P INPUT ACCEPT
iptables -F
iptables -t nat -F
sysctl net.ipv4.ip_forward
# 要输出 1
# 2. 配置 SNAT(内网访问外网时,源地址改成 10.0.0.1)
# POST链是进入时的操作
iptables -t nat -A POSTROUTING -s 192.168.73.0/24 -o ens36 -j SNAT --to-source 10.0.0.1
# 3. 配置内核转发
vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
sysctl -p # 加载
[root@localhost ~]# sysctl -p
net.ipv4.icmp_echo_ignore_all = 0
net.ipv4.ip_forward = 1
# 4. 放行 FORWARD 链(不然转发包会被挡)
iptables -P FORWARD ACCEPT
# 或者更精确的:
iptables -A FORWARD -i ens36 -o ens33 -p tcp --dport 22 -j ACCEPT
iptables -A FORWARD -i ens33 -o ens36 -m state --state ESTABLISHED,RELATED -j ACCEPT
# 修改web服务器
# 把web服务器(129)的网关改为防火墙服务器(128)
vim /etc/NetworkManager/system-connections/ens33.nmconnection
[ipv4]
address1=192.168.73.129/24
dns=223.5.5.5;8.8.8.8; # 建议配置DNS
gateway=192.168.73.128 # 写网关服务器的内网网卡IP
method=manual
systemctl restart network
# stream 10不再使用network
# 重启 NetworkManager 服务
systemctl restart NetworkManager
没事干多玩玩 nmcli。
最后在 web 服务器上测,全通:
# 在Web服务器(129)上
ping 192.168.73.128 # 通(网关)
ping 10.0.0.1 # 通(防火墙外网口)
ping 8.8.8.8 # 通(外网)
ping www.baidu.com
# 保存规则(没有目录就创建)
iptables-save > /etc/sysconfig/iptables
# 恢复
iptables-restore < /etc/sysconfig/iptables
补一点:如果代理的公网 IP 变了,POSTROUTING 规则里配的地址也得跟着改。为了少折腾,可以换个写法:
iptables -t nat -A POSTROUTING -s 192.168.73.0/24 -j MASQUERADE
# masquerade 译为伪装,公网IP不固定
云服务器的 nat 网关功能,其实就是把这一堆加规则的过程做成了 Web 页面。
二、端口映射(端口转发)
1. 思路
场景是:用户要访问一台内网服务器,通过管理机做跳板进到内网机器的某个端口。
假设用户请求内网机器的 9000 端口打到管理机,管理机再转发和映射到内网 IP:22:

后端节点的网关需要指向防火墙,请求进去才能回到客户端。
因为是在请求进入时修改的端口,所以用到的是 PREROUTING 链:

# 防火墙配置
iptables -t nat -A PREROUTING -d [目标IP] -p tcp --dport 9000 -j DNAT --to-destination 192.168.73.129:22
# 后端节点的网关需要指向防火墙,请求进去才能回到客户端
这里的目标 IP 是用户先访问到的那个网关服务器 IP。
2. 测试
可以用 win 做客户端测试:
# 网关配置
iptables -t nat -A PREROUTING -d 10.0.0.1 -p tcp --dport 9000 -j DNAT --to-destination 192.168.73.129:22
# 网关机器:ens33 = 192.168.73.128(内网),ens36 = 10.0.0.1(外网)
# Web后端机器:ens33 = 192.168.73.129(内网)
iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 10.0.0.1 tcp dpt:9000 to:192.168.73.129:22
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
SNAT all -- 192.168.73.0/24 0.0.0.0/0 to:10.0.0.1
iptables-save >/etc/iptables/rules.v4
# 更好的做法,不指定目标IP(转发服务器),直接指定网卡
iptables -t nat -A PREROUTING -i ens36 -p tcp --dport 9000 -j DNAT --to-destination 192.168.73.129:22
ssh -p 9000 root@10.0.0.1
这条规则里藏着一个坑。
iptables -t nat -A PREROUTING -d 10.0.0.1 -p tcp --dport 9000 -j DNAT --to-destination 192.168.73.128:22
如果目标地址写成 192.168.73.128,那是网关自己的内网 IP,不是后端服务器的 —— 转发过去等于让网关连自己,映射就白配了。要写后端那台机器的地址(这个实验里是 192.168.73.129)。
完整的路径是这样的:外部设备访问 10.0.0.1:9000,包先到达网关的外网网卡 ens36;PREROUTING 链匹配上 DNAT 规则,目的地址和端口被改写成内网后端;路由一看目标在内网,走 FORWARD 把包转出去;后端收到包处理完回包,而这个回包必须先回到网关,再由网关交还给外部客户端。所以后端的默认网关一定要指向防火墙,不然回包直接扔给别的网关,就再也回不到客户端了。
3. 踩到的坑:回包回不来
# 查看web129的路由
ip route
default via 192.168.73.2 dev ens33 proto dhcp src 192.168.73.129 metric 100
192.168.73.0/24 dev ens33 proto kernel scope link src 192.168.73.129 metric 100
# 发现路由不是网关的路由128,是192.168.73.2不认识的路由,无法处理数据包
# 修改默认网关
# 临时
sudo ip route replace default via 192.168.73.128 dev ens33
# 永久
sudo nmcli con mod ens33 ipv4.gateway 192.168.73.128
sudo nmcli con down ens33 && sudo nmcli con up ens33
# 确认
[root@localhost ~]# ip route
default via 192.168.73.128 dev ens33
default via 192.168.73.2 dev ens33 proto dhcp src 192.168.73.129 metric 100
192.168.73.0/24 dev ens33 proto kernel scope link src 192.168.73.129 metric 100
果然,web129 的默认路由是 DHCP 发下来的 192.168.73.2,不是网关 192.168.73.128。改成网关之后映射才通。
三、总结
这一页两个东西最重要:共享上网靠 SNAT,端口转发靠 DNAT。另外 nat 功能在云服务器上是没法直接用的,替代品叫 NAT 网关。

最后是几道练习题,正好把前面的东西串一遍:

顺手把答案写在这:
- 查看 iptables 当前所有规则:
iptables -nL,要看指定表就加-t 表名,例如iptables -t nat -nL - 禁止来自
10.0.0.188访问 80 端口:iptables -I INPUT -s 10.0.0.188 -p tcp --dport 80 -j DROP - 让命令行执行的规则永久生效:
iptables-save >/etc/sysconfig/iptables(Ubuntu 上用 iptables-persistent 的话是netfilter-persistent save) - 把访问
10.0.0.3:80的请求转到172.16.1.17:80:iptables -t nat -A PREROUTING -d 10.0.0.3 -p tcp --dport 80 -j DNAT --to-destination 172.16.1.17:80 172.16.1.0/24段所有主机通过124.32.54.26这个外网 IP 共享上网:iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -j SNAT --to-source 124.32.54.26,别忘了开net.ipv4.ip_forward=1