一、iptables 是干什么的
iptables 就是 Linux 上的防火墙,落到日常运维手上主要两类事:封端口、封 IP,还有做 NAT。
NAT 又分两种用法:
- 共享上网
- 端口映射(也叫端口转发),还有 ip 映射
它管的其实就是 IP 和端口这一层,协议内容本身它不看 —— 七层的攻击得靠 WAF 去处理。

企业里的防火墙是一层套一层的。硬件那头有三层路由(H3C、华为、Cisco 思科),防火墙有深信服、Juniper;软件这头是开源方案,iptables、firewalld(C7)、nftables(C8)、ufw(Ubuntu firewall);公有云上还有阿里云的安全组(封 IP、封端口)、NAT 网关(共享上网、端口映射)、waf 应用防火墙。iptables 就是软件这一档,写进 Linux 内核里,大部分工作在第 4 层。

表、链、规则是套在一起的
一层套一层,表里放链,链里放规则:
+------------------------------------------------------+
| 容器 (Container) |
| 防火墙中最大的概念,用于存放链 |
| |
| +----------------------------------------------+ |
| | 表 (Table) | |
| | 存放链的容器,按功能分类 | |
| | | |
| | +------------------------------------+ | |
| | | 链 (Chain) | | |
| | | 存放规则的容器 | | |
| | | | | |
| | | +----------------------------+ | | |
| | | | 规则 (Policy/Rule) | | | |
| | | | 准许或拒绝数据包的条件 | | | |
| | | +----------------------------+ | | |
| | +------------------------------------+ | |
| +----------------------------------------------+ |
+------------------------------------------------------+
图里画了四层,实际上记住三层就够用了:表里放链,链里放规则,规则就是"准许或拒绝数据包的条件"。外面那个"容器"是这份笔记自己的叫法,别当术语背。
二、包是怎么过防火墙的
从上到下一条条匹配规则,匹配完了才能拿到主机的服务。

不管是拒绝还是接受,都算匹配成功。匹配不上就继续往下走,走到最底下都不匹配,才轮到默认规则。

所以有几条是定死的:
- 防火墙是层层过滤的,实际按配置规则的顺序从上到下、从前到后过滤
- 一旦匹配成功,也就是明确了是拒绝(DROP)还是接收(ACCEPT),这个包就不再往下匹配新规则了
- 如果规则里没明确说是拦还是放,继续向下匹配,直到匹配默认规则拿到明确结果
- 防火墙的默认规则是所有规则都匹配完之后才匹配的
表示拒绝的规则一般放在上层。
三、4 表 5 链处理流程
4 表:filter(过滤器)表、nat 表、raw 表、mangle 表。
5 链(按运维惯例,链名字全大写):
- PREROUTING — 路由前链(报文进入本机后、路由决策之前)
- INPUT — 输入链(报文目的地址为本机,进入本地进程之前)
- FORWARD — 转发链(报文目的地址非本机,经本机路由转发)
- OUTPUT — 输出链(本机进程发出的报文,在路由决策之后发出之前)
- POSTROUTING — 路由后链(报文即将离开本机,在发出前做最后处理)
平时打交道最多的还是 INPUT。另外不是每个表都有 5 个链,具体哪个表带哪些链,man iptables 的手册里写得很清楚:
man iptables
filter 表是默认表,管的是和主机自身相关的过滤,真正实现主机防火墙功能的就是它,定义了三个链,企业里主机防火墙基本都用它:

nat 表负责网络地址转换,也就是来源与目的 IP 地址和 port 的转换,跟主机本身关系不大,一般用在局域网共享上网或者特殊的端口转换服务上,也是三个链:

包的走向拉成一张图就是这样:

按运维惯例捋一遍:
- nat-prerouting*:处理到达服务器之前的请求
- filter-input*:处理进入进程服务的请求
- filter-forward:处理不使用进程服务的请求
- nat-output / filter-output:请求打出规则(推荐 filter-output)
- nat-postrouting*:数据包离开服务器时匹配
带星号这三个是核心,展开说一下:
nat-PREROUTING 处理的是刚进入本机网卡、还没经过路由决策的包,用来做 DNAT(目的地址转换),比如把公网 IP 的端口映射到内网服务器。这时候内核还没判断这个包是给自己的还是要转发出去。
filter-INPUT 处理的是目的地址为本机、即将交给本地进程的包。限制本机服务(比如 sshd、nginx)能被谁访问,就是在这一层做的。只有路由决策判定"目标就是本机"的包才会走到 INPUT。
filter-FORWARD 处理的是目的地址不是本机、需要本机转发到其他网络的包,本机当路由器或者网关的时候才用得上,一般默认配置为允许。要让它生效得开内核转发(net.ipv4.ip_forward=1)。
OUTPUT 链有两个,用途完全不一样:filter-OUTPUT 管本机进程主动发出去的包(curl 请求、本机服务对外连接),控制本机往外访问;nat-OUTPUT 是本机发出的包在路由之后、发出之前做 DNAT,很少用,一般本机访问自己的映射地址才会碰到。所以别以为"留一个 filter-OUTPUT 就够了",要做本机发出的源地址转换得用 nat-OUTPUT,只做访问限制才用 filter-OUTPUT。
nat-POSTROUTING 处理的是即将从本机网卡发出的包(路由决策已经做完),是离开本机之前的最后一个钩子,用来做 SNAT(源地址转换),共享上网(内网 IP 转公网 IP)就是它。本机自己发的包和转发的包,只要从本机出去都过这个链。
常用规则的优先级顺序也顺手记一下:

四、安装和配置
1. 装工具
装一个命令管理工具,让 iptables 变成能用 systemctl 控制的服务:
# 先关闭防火墙
# stop是临时关闭,disable是永久禁用
systemctl disable firewalld
yum install -y iptables-services
apt install -y iptables-persistent
# 可能报错:依赖冲突
# 修复破损的依赖
apt --fix-broken install -y
# 查看软件包的内容
rpm -ql iptables-services
dpkg -l | grep iptables
dpkg -L iptables-persistent
root@VM-4-16-ubuntu:~# dpkg -l | grep iptables
ii iptables 1.8.10-3ubuntu2 amd64 administration tools for packet filtering and NAT
ii iptables-persistent 1.0.20 all boot-time loader for netfilter rules, iptables plugin
# 其中的iptables软件包是系统的
root@VM-4-16-ubuntu:~# which iptables
/usr/sbin/iptables
root@VM-4-16-ubuntu:~# dpkg -L iptables-persistent
/.
/etc
# 配置文件
/etc/iptables
/lib
diverted by base-files to: /lib.usr-is-merged
/lib/systemd
/lib/systemd/system
/lib/systemd/system/netfilter-persistent.service.d
/lib/systemd/system/netfilter-persistent.service.d/iptables.conf
/usr
/usr/share
/usr/share/doc
/usr/share/doc/iptables-persistent
/usr/share/doc/iptables-persistent/copyright
/usr/share/netfilter-persistent
/usr/share/netfilter-persistent/plugins.d
/usr/share/netfilter-persistent/plugins.d/15-ip4tables
/usr/share/netfilter-persistent/plugins.d/25-ip6tables
/usr/share/doc/iptables-persistent/NEWS.Debian.gz
/usr/share/doc/iptables-persistent/README
/usr/share/doc/iptables-persistent/changelog.gz
# 配置文件有默认的规则
# 如果是云服务器会自动同步云服务器的规则
root@VM-4-16-ubuntu:/etc/iptables# ll
total 28
drwxr-xr-x 2 root root 4096 Aug 28 15:22 ./
drwxr-xr-x 117 root root 12288 Aug 28 15:22 ../
-rw-r--r-- 1 root root 5134 Aug 28 15:22 rules.v4
-rw-r--r-- 1 root root 1354 Aug 28 15:22 rules.v6
云服务器不方便演示,下面都用 CentOS Stream 10 来试。装之前先去官网下载页把镜像拿到手:

2. CentOS Stream 10 演示,以及和旧版的区别
# centos10会替换包名为 iptables-nft-services
[root@localhost ~]# rpm -ql iptables-nft-services
/etc/sysconfig/arptables
/etc/sysconfig/ebtables
/etc/sysconfig/ebtables-config
/etc/sysconfig/ip6tables
/etc/sysconfig/ip6tables-config
# 防火墙规则文件
/etc/sysconfig/iptables
/etc/sysconfig/iptables-config
/usr/lib/systemd/system/arptables.service
/usr/lib/systemd/system/ebtables.service
/usr/lib/systemd/system/ip6tables.service
/usr/lib/systemd/system/iptables.service
/usr/libexec/ebtables-helper
/usr/libexec/initscripts/legacy-actions/ip6tables
/usr/libexec/initscripts/legacy-actions/ip6tables/panic
/usr/libexec/initscripts/legacy-actions/ip6tables/save
/usr/libexec/initscripts/legacy-actions/iptables
/usr/libexec/initscripts/legacy-actions/iptables/panic
/usr/libexec/initscripts/legacy-actions/iptables/save
/usr/libexec/iptables
/usr/libexec/iptables/ip6tables.init
# 脚本
/usr/libexec/iptables/iptables.init
# 查看默认规则
[root@localhost ~]# cat /etc/sysconfig/iptables
# sample configuration for iptables service
# you can edit this manually or use system-config-firewall
# please do not ask us to add additional ports/services to this default configuration
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
# 防火墙默认嵌入linux内核,但是默认不激活
# lsmod看内核加载的模块
lsmod | grep nat
lsmod | grep iptables
lsmod | grep filter
# 启动防火墙查看
[root@localhost ~]# systemctl enable --now iptables
Created symlink '/etc/systemd/system/multi-user.target.wants/iptables.service' → '/usr/lib/systemd/system/iptables.service'.
[root@localhost ~]# lsmod | grep nat
lsmod | grep iptables
lsmod | grep filter
nft_chain_nat 12288 0
nf_nat 69632 1 nft_chain_nat
nf_conntrack 208896 3 xt_conntrack,nf_nat,nft_ct
nf_tables 397312 27 nft_ct,nft_compat,nft_reject_inet,nft_fib_ipv6,nft_fib_ipv4,nft_chain_nat,nft_reject,nft_fib,nft_fib_inet
[root@localhost ~]# lsmod | grep filter
[root@localhost ~]# lsmod | grep iptables
[root@localhost ~]#
# 只加载了一部分
开启 iptables 模块:
# 将防火墙相关模块加载到内核中,并写入开机自启动
# 加载 iptables 核心模块
modprobe ip_tables
modprobe iptable_filter
modprobe iptable_nat
modprobe ip_conntrack
# FTP 辅助模块(按需加载)
modprobe ip_conntrack_ftp
modprobe ip_nat_ftp
# state 模块(新内核可能已废弃,报错可忽略)
modprobe ipt_state
# 开机自启加载(永久)
cat >> /etc/modules-load.d/iptables.conf << EOF
ip_tables
iptable_filter
iptable_nat
ip_conntrack
ip_conntrack_ftp
ip_nat_ftp
ipt_state
EOF
# 查看是否加载(需要识别管道符,-E启动用正则或者egrep)
lsmod | grep -E 'nat|filter'
lsmod | egrep 'nat|filter'
[root@localhost ~]# lsmod | egrep 'nat|filter'
nft_chain_nat 12288 0
nf_nat 69632 1 nft_chain_nat
nf_conntrack 208896 3 xt_conntrack,nf_nat,nft_ct
nf_tables 397312 27 nft_ct,nft_compat,nft_reject_inet,nft_fib_ipv6,nft_fib_ipv4,nft_chain_nat,nft_reject,nft_fib,nft_fib_inet
# centos10默认使用nftables框架,不再加载传统模块
旧版(CentOS 7)是这么加载的,写进 /etc/rc.local:

两种环境对比下来就一句话:底层没变,变的只是表层。CentOS 6/7 是 xtables 传统 iptables,lsmod 里能看到 iptable_filter、iptable_nat、ip_tables,iptables 命令直接操作内核的 xtables 表;现在的 CentOS Stream 10 是 nftables 内核原生框架,lsmod 里看到的是 nf_tables、nft_chain_nat、nf_nat,iptables 命令是通过兼容层转成 nftables 规则的。
还有一点:iptables 是嵌在内核里的,所以 systemctl status 看到的运行状态不是 running,而是 active (exited):

3. 查看表-链
iptables -nL
# 不知道表查看的就是默认的 filter表
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22
REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT)
target prot opt source destination
REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
# 默认是filter表,看的是filter规则(--numeric 译为数字的 --list)
# 看指定表的链(--table)
iptables -t nat -nL
[root@localhost ~]# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
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
[root@localhost ~]# iptables -t filter -nL
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22
REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT)
target prot opt source destination
REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
每行开头的 Chain INPUT (policy ACCEPT) 就是链的默认规则,红框标出来的就是它:

最后一句得说在前头:配置防火墙必须谨慎,很容易把自己弹出去。出配置之前最好先挂个定时任务,比如过几分钟自动把防火墙关掉,真被踢出去了还能自己爬回来。