一台只有内网 IP 的服务器想访问外网,又不能直接配网关。我手头正好有一台双网口的虚拟机,决定拿它当“路由器”做 NAT 转发。本以为 iptables 配两条规则就完事,结果折腾了一下午——开了转发没开 NAT,配了 SNAT 忘了配路由……这篇文章把整个过程和踩的坑全记下来,方便以后自己查。
一、场景描述
- 一台 server 虚拟机:双网卡
eth0: 172.25.254.224(可以访问外网/同网段其他机器)eth1: 1.1.1.224(连接 client 的“内网侧”)- 一台 client 虚拟机:单网卡
eth0: 172.25.254.124(原本只能和同网段通信,无法访问其他网段)
目标:让 client(172.25.254.124)能通过 server 访问外部网络,并且外部也能通过 server 访问 client 的特定端口(比如 SSH)。
说白了就是:server 做网关 + NAT + 端口转发。
二、准备工作:关闭 firewalld,启用 iptables
CentOS 7 以后默认用 firewalld,但用 iptables 更直观(至少我觉得)。
在 server 上执行:
systemctl stop firewalld
systemctl mask firewalld
systemctl start iptables.service
systemctl enable iptables.service
踩坑:如果不执行 mask,firewalld 可能被其他服务拉起来,和 iptables 冲突。我遇到过明明 iptables 加了规则,但就是不生效,最后发现 firewalld 又活了。
三、iptables 常用参数速查(我记不全,列个表)
参数 含义 小抄
-t 指定表(默认 filter) -t nat 常用
-nL 列出规则,不解析 IP iptables -nL
-A 追加规则(放最后) -A INPUT
-I 插入规则(可指定位置) -I INPUT 2
-D 删除规则 -D INPUT 3
-R 替换规则 -R INPUT 2 -s x.x -j ACCEPT
-F 清空规则 iptables -F
-P 修改默认策略 -P INPUT DROP
-s 源地址 -s 172.25.254.10
-p 协议 -p tcp
--dport 目的端口 --dport 22
-j 动作 ACCEPT / REJECT / DROP / SNAT / DNAT
-o 出口网卡 -o eth0
-i 入口网卡 -i eth0
四、基础操作:filter 表练手(先热热身)
在 server 上先玩玩 filter 表,熟悉增删改查:
4.1 允许本地回环
iptables -t filter -A INPUT -i lo -j ACCEPT
iptables -nL # 查看
4.2 允许特定 IP 访问所有端口
iptables -A INPUT -s 172.25.254.10 -j ACCEPT
4.3 拒绝特定 IP
iptables -A INPUT -s 172.25.254.10 -j REJECT
踩坑:如果上面那条 ACCEPT 在前面,REJECT 永远不会执行。iptables 是按顺序匹配的,匹配到就停止。所以要把精细规则放前面,默认策略放最后。
4.4 删除、替换、插入规则
# 删除第 3 条规则
iptables -D INPUT 3
# 修改第 2 条规则:把 ACCEPT 改成 REJECT
iptables -R INPUT 2 -s 172.25.254.10 -j REJECT
# 在第 2 条位置插入一个规则(允许特定 IP 的 SSH)
iptables -I INPUT 2 -s 172.25.254.10 -p tcp --dport 22 -j ACCEPT
4.5 修改默认策略
iptables -P INPUT DROP # 丢掉所有没匹配到的包
# 然后只有上面明确 ACCEPT 的 IP 能连
iptables -P INPUT ACCEPT # 改回来
踩坑:千万别在远程连接时直接 -P INPUT DROP 然后忘记加允许 SSH 的规则。我这么干过,只能让同事去机房重启。
五、核心:地址伪装(SNAT)——让 client 能上网
现在开始正经配置。
第一步:开启路由转发
echo 1 > /proc/sys/net/ipv4/ip_forward
# 永久生效的话改 /etc/sysctl.conf: net.ipv4.ip_forward=1
第二步:在 server 上添加 SNAT 规则
把 client 的流量“伪装”成 server 的 eth0 地址出去:
iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 172.25.254.224
解释:
1、POSTROUTING:路由之后,离开网卡之前
2、-o eth0:从 eth0 出去的包
3、SNAT --to-source:把源地址改成 172.25.254.224
这样,client(172.25.254.124)往外发包时,源地址会被替换成 server 的 eth0 IP。外部服务器回包会回到 server,server 再转发给 client。
测试:在 client 上 ping 8.8.8.8。如果通了,并且在 server 上用 w -i 看到 client 的访问源 IP 显示为 172.25.254.224,说明伪装成功。
踩坑:如果只开了 ip_forward 而忘了配 SNAT,client 能发出包,但回包到 server 后,server 不知道发给谁(因为源地址是 client 的真实 IP,不在同一子网),直接丢包。
六、端口转发(DNAT)——让外部能访问 client 的 SSH
需求:外网用户 SSH 到 server 的 172.25.254.224:22,实际被转发到 client 的 1.1.1.124:22。
注意:这里 client 的 IP 是 1.1.1.124(server 的 eth1 同网段),外网不能直接访问它。
在 server 上添加 DNAT 规则:
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 22 -j DNAT --to-dest 1.1.1.124:22
解释:
1、PREROUTING:路由之前,刚进网卡
2、-i eth0:从 eth0 进来的包
3、DNAT --to-dest:把目的地址改成 1.1.1.124:22
测试:从外部机器(比如你的笔记本)执行 ssh 172.25.254.224,应该会登录到 client 而不是 server。
踩坑:如果 client 的默认网关不是 server 的 eth1(1.1.1.224),那么 client 的回包会走自己的默认网关,导致连接失败。解决方法:在 client 上添加一条路由,或者把 client 的默认网关设为 server 的 eth1 IP。
七、管理 NAT 规则
查看 NAT 表:
iptables -t nat -nL
删除第一条 PREROUTING 规则:
iptables -t nat -D PREROUTING 1
删除第一条 POSTROUTING 规则:
iptables -t nat -D POSTROUTING 1
问题汇总(全都是真实踩过的)
-
开启 ip_forward 后 client 依然不能上网
现象:client 能 ping 通 server 的 eth1(1.1.1.224),但 ping 不通 8.8.8.8。
原因:很可能忘了配 SNAT,或者 SNAT 规则配错了网卡名/源地址。
检查:在 server 上 tcpdump -i eth0 icmp,看有没有发出 client 的 ping 包。如果发出去了但源地址是 client 原始 IP(172.25.254.124),说明 SNAT 没生效。 -
端口转发后 SSH 卡在 “connection refused”
现象:ssh 172.25.254.224 能连,但输入密码后卡住或直接拒绝。
原因:client 的回包路由不对。client 收到请求后,回复的源 IP 是它的 eth0(1.1.1.124),但目标 IP 是外部机器的 IP,如果没有路由,回包会走默认网关(可能不是 server)。
解决:在 client 上把默认网关设为 server 的 eth1:
ip route add default via 1.1.1.224
或者更精细地:只给外部 IP 段加路由。
-
iptables -P INPUT DROP 后把自己踢出去了
现象:远程 SSH 断开,再也连不上。
原因:没有提前加 -A INPUT -p tcp --dport 22 -j ACCEPT。
修复:只能去物理机/控制台执行 iptables -P INPUT ACCEPT。
教训:修改默认策略前,先确认有一条允许当前连接的规则。 -
使用 --dport 时没有指定协议 -p
现象:规则加进去了,但就是不匹配。
原因:--dport 必须配合 -p tcp 或 -p udp 使用,不然 iptables 不知道是哪个协议的端口。
正确写法:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
- 清空 NAT 表后用 service iptables save 保存不上
现象:iptables -F 清空后,重启 iptables 服务规则又回来了。
原因:iptables 规则保存在 /etc/sysconfig/iptables,-F 只清内存,不写文件。
解决:
service iptables save
或者手动 iptables-save > /etc/sysconfig/iptables。
九、一点小建议
做任何路由转发前,先用 tcpdump -i eth0 icmp 抓包看流向。比瞎猜快多了。
SNAT 和 DNAT 一定要搞清楚方向:
SNAT:改源地址(让出去的数据包像是我发的)
DNAT:改目的地址(让进来的数据包被转给别人)
如果只是临时测试,可以在 iptables 规则前加 -t 30 定时自动清空,防止把自己锁外面。
保存好你的 iptables 脚本,我习惯写成 iptables-restore < rules.txt 的形式,比一条条敲省事。
写完这篇博客,我对 iptables 的恐惧少了一分。其实双网口转发就那么几个关键点:ip_forward + SNAT + DNAT。剩下的都是细节和坑。
顺便说一句,别在生产环境直接用 -P INPUT DROP。