首页  /  Linux 进阶  /  正文

iptables NAT 共享上网与端口映射

Linux 进阶 2026-10-02📖 14 分钟👁 —
🛡️ Linux 进阶 · iptables 防火墙第 4 / 4 页1234📖 完整导航 →

一、nat 表:共享上网

1. nat 表到底改什么

nat 表是 nat 网络地址转换的核心配置(这部分在计算机网络里讲过),负责把内网 IP 和公网 IP 互相转换。

iptables 的核心处理流程先捋清楚。访问服务器进程时走上面那条:

PREROUTING---INPUT--OUTPUT--POSTROUTING

只是路过就走下面那条:

PREROUTING--FORWARD--POSTROUTING

如果不开 FORWARD,包没法穿过这台机器,直接被丢掉。但是不管走哪条,PREROUTING 和 POSTROUTING 一定会走(包出去时处理)。

数据包完整的流向,黄框标出的是 PREROUTING 和 POSTROUTING

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

SNAT 共享上网的完整过程,公网 IP 不固定时改用 MASQUERADE

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 出去。用虚拟机做实验的话,得先确保虚拟机自己能访问外网。

防火墙这边要做两件事:

  1. 配置防火墙的 SNAT 功能(源 IP 地址转换)
  2. 开启内核转发 —— 网关服务器/防火墙服务器通常有两块网卡,一块负责内网之间,一块负责内网和外网之间

后端服务器这边:Web 服务器要把网关配置成网关服务器的 IP(Web 服务器找不到的地址会丢给网关)。

那为什么没有两块网卡、没开内核转发的反向代理,也能实现内外网 IP 转换?

因为两者根本不是一个层的东西:

WAF/Nginx 反向代理在应用层,iptables 网关转发在网络层

反向代理是应用层的,它自己建两段独立的 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 不是公网可路由的网段。所以整个流程是:

  1. web 发出去的包到防火墙
  2. 防火墙开着内核转发,准备把包送出去
  3. 同时执行 SNAT 规则,把包里的源地址从 192.168.73.129 改成防火墙的公网 IP
  4. 外网服务器看到源地址是公网 IP,正常回包给防火墙
  5. 防火墙根据连接跟踪表(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 就得再加一条放行规则。

这个实验需要防火墙有两块网卡,虚拟机上只有一块,所以得自己加一张内网口。网卡这么规划:

网卡 1 走 NAT 或桥接当外网口,网卡 2 用仅主机模式当内网口

先把虚拟机关机,添加一张网卡,默认不分配 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 地址:

ip a 里 ens33 是 192.168.73.128,ens36 只有 MAC 没有 IP

给它配上地址:

# 创建一个新连接,绑定 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 摸几遍就记住了:

nmcli 常用命令:看状态、改 IP、改网关、改 DNS、重启网卡

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:

用户访问 9000 落到管理机,再 DNAT 到内网服务器的 22 端口

后端节点的网关需要指向防火墙,请求进去才能回到客户端。

因为是在请求进入时修改的端口,所以用到的是 PREROUTING 链:

DNAT 发生在请求进入时的 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 网关。

nat 部分小结:共享上网 SNAT、端口转发 DNAT、云上用 NAT 网关

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

五道练习题:查看规则、封 IP 访问端口、规则永久生效、端口映射、共享上网

顺手把答案写在这: