1. 先搞清楚iptables管的是哪一段网络路径
先说一个很多人问过我的问题:iptables到底是个防火墙,还是一个命令?严格来说,iptables是用户态的管理工具,真正干活的是Linux内核里的Netfilter框架。你输入的每一条iptables规则,本质上是往Netfilter挂载的钩子点上注册了一段处理逻辑。把这两个概念区分开,后面学什么都顺。
很多初学者拿iptables跟Windows防火墙做类比,以为它就是一个"放行/拦截某些程序"的开关。这个类比害了不少人。Windows防火墙默认是基于应用程序的维度做管控,而iptables是基于数据包特征做的管控。它根本不关心这个流量是哪个程序发的,它只看源IP、目的IP、源端口、目的端口、协议类型、进出网卡接口这些网络层和传输层的信息。这意味着什么?意味着同一个进程产生的流量,如果走了不同的端口,iptables对它们的处理方式可以完全不同;反过来,不同进程如果复用了同一个端口,iptables也分不清谁是谁。
在动手配置iptables之前,必须先建立一个核心概念:数据包在Linux内核里的流转路径是有固定顺序的。一个数据包从网卡进来之后,不是直接交给应用程序,它要依次经过内核协议栈中的几个检查点。iptables的所有规则,都挂在这些检查点上。
以一个最简单的场景为例:你本机的一个进程要访问外网,数据包从发送到回应,经历的过程是这样的:
- 请求数据包由本机进程产生,走的是OUTPUT链;
- 数据包从网卡发出去,在发送之前经过POSTROUTING链;
- 对端的回应数据包从网卡进来,首先经过PREROUTING链;
- 然后根据目的IP判断是不是本机地址,如果是就走INPUT链交给本机进程。
流量如果要转发呢?比如你的Linux机器充当路由器,内网机器发来的包要转发到外网,数据包进来后走PREROUTING,然后判断目的IP不是本机,于是走FORWARD链,最后经过POSTROUTING发出去。
这三个方向——发给本机的、本机发出的、经过本机转发的——对应了INPUT、OUTPUT、FORWARD这三条链。搞不清FORWARD和INPUT的区别,是后面所有配置混乱的根源。比如你的Linux服务器开了IP转发,内网其他机器通过它上网,结果你只在INPUT链上放行了某些端口,FORWARD链却是默认DROP,那么所有经过这台机器的转发流量都会掉包。这类问题我在各种技术群里见过不下十次。
数据包经过的检查点,在Netfilter里这些点有标准的名称:NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING。iptables把用户配置的规则挂在这些点上,每经过一个点就检查一遍规则列表,匹配到规则就执行对应的动作(ACCEPT、DROP、REJECT等),没匹配到就继续看下一条。
我一直强调一个比喻:你可以把iptables想象成小区门口的一排保安,每个保安负责一道工序,有的保安只看进小区的人,有的保安只看出门的人,有的保安负责在两道门之间的通道上巡逻。数据包这条路,必须依次接受所有保安的检查。你配置的每一条规则,就相当于给某个保安下了一条具体的指令。弄明白了这条路径,再看四表五链就不会晕。
2. 四表五链的职责划分:iptables的骨架怎么记
netfilter提供点位,iptables提供规则,但规则不是一股脑堆在一起的,它按功能用途分了四张表。这就是所谓的"四表五链"。
四张表分别是:filter表、nat表、mangle表、raw表。如果你看过一些老教程,可能还听说过五张表的说法,那是指早期版本的实现,现代内核里主要就是这四张。
每张表能挂到的链不完全一样。这是一个很多人记混的点,我直接列个表格看清楚:
| 表 | 能挂载的链 | 核心作用 | 典型动作 |
|---|---|---|---|
| filter | INPUT、FORWARD、OUTPUT | 进出本机和转发流量的过滤 | ACCEPT、DROP、REJECT |
| nat | PREROUTING、INPUT、OUTPUT、POSTROUTING | 地址转换,改写数据包源/目的地址 | SNAT、DNAT、MASQUERADE、REDIRECT |
| mangle | 全部五条链 | 修改数据包头部字段(如TOS、TTL) | MARK、TOS、TTL |
| raw | PREROUTING、OUTPUT | 决定数据包是否进入连接跟踪机制 | NOTRACK |
刚接触的人最容易犯的一个错误是:给filter表写规则时忘了指定-t参数,给nat表写规则时也忘了指定-t参数。iptables命令如果不加-t,默认操作的是filter表。所以当你执行iptables -A INPUT -p tcp --dport 22 -j ACCEPT时,这条规则进的是filter表的INPUT链,这个好理解。但当你执行iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE时,就必须要带-t nat,否则这条规则会落到filter表里,而filter表压根没有POSTROUTING链,命令直接报错。
2.1 filter表与INPUT/OUTPUT/FORWARD的配合逻辑
filter表是最常用的一张表。它负责的决策非常纯粹——这个数据包是放行还是丢弃。在实际配置中,我建议你把这个表的默认策略想清楚:默认ACCEPT还是默认DROP?这个决策决定了写规则的思维方式。
如果你用的是默认ACCEPT策略,那你的filter表现相当于一个"黑名单"模式,只需要写"禁止哪些流量"的规则。这在测试环境里问题不大,但在生产环境里存在一个隐患:某天你忘了加某条封禁规则,原本应该被拦截的流量就溜进来了。
如果你用的是默认DROP策略,那filter表就是一个"白名单"模式,只放行明确需要的流量。这种策略安全等级更高,但对运维的要求也更高——你漏掉一条放行规则,服务就直接不可用了。生产环境里,我更推荐默认DROP + 显式放行的组合,因为"不可用"比"被入侵"好处理得多。
FORWARD链值得单独说一说。很多只管理单机的人,根本没碰过FORWARD链。但只要你开始做NAT网关、做Docker端口映射、做KVM虚拟化,FORWARD链就绕不开。举个最常见的场景:Docker启动一个容器并映射了端口,你以为Docker会把流量转发进容器,实际上Docker只是往iptables的DOCKER链里动态加了几条规则,而这些规则最终都挂在FORWARD链上。如果你手动清空了FORWARD链的规则,或者把FORWARD默认策略改成了DROP,你可能会发现容器服务突然从外部访问不了了。
2.2 连接跟踪:五表之外的隐藏机制
在聊raw表和状态匹配之前,必须先讲连接跟踪(conntrack),它是理解iptables状态规则的核心。
连接跟踪机制会记录每一个经过系统的网络连接的状态,Linux把连接状态分为四种:NEW、ESTABLISHED、RELATED、INVALID。
- NEW:新发起的连接请求,只有第一个包是NEW状态;
- ESTABLISHED:连接已经建立,后续双向的包都是这个状态;
- RELATED:与已有连接相关的新连接,典型例子是FTP的数据连接;
- INVALID:无法识别的包,通常是无效的、伪造的或损坏的包。
我见过的最典型的iptables配置错误,是只放行了NEW状态的包,没放行ESTABLISHED和RELATED的包。你从服务器上主动访问外网,服务器发出的响应包回到本地时,它是ESTABLISHED状态。如果你在INPUT链上只写了-m state --state NEW -j ACCEPT,那么所有回来的响应包都会被丢,表现就是"服务器能发起请求但收不到任何响应"。正确的写法通常是:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m conntrack --ctstate NEW -p tcp --dport 80 -j ACCEPT iptables -A INPUT -j DROP顺序也很重要。放行ESTABLISHED状态的规则一定要放在前面,这样已经建立的连接直接被放行,不会走到后面的DROP规则那里。
conntrack机制虽然强大,但它也有代价。每个连接都需要在内存里记录,如果连接数特别大,conntrack表满了之后,新连接会被直接丢弃,服务器日志里会出现nf_conntrack: table full, dropping packet的报错。遇到这种情况,要么调大net.netfilter.nf_conntrack_max参数,要么用raw表的NOTRACK跳过某些高并发流量的连接跟踪。
3. 规则增删改查与匹配条件:从ping通到限速的完整实践
基础操作层面,我把最常用的iptables命令按功能分类整理一遍。这些命令看起来多,但核心就几个动作:增(-A)、删(-D)、插(-I)、替换(-R)、查(-L)、清空(-F)。你可以把每条规则想象成链表里的一个节点,iptables的规则是有顺序的,从上到下逐条匹配。
3.1 放行、拒绝与丢弃:ACCEPT、DROP、REJECT怎么选
查询当前规则,用iptables -L -n -v。-n的作用是不做DNS反向解析,直接显示IP地址,否则域名解析会拖慢查询速度;-v显示每个链上的数据包计数和字节计数,这对后续排查问题很有用。看计数器你就能知道某条规则有没有被命中——如果计数一直为零,要么规则没匹配到流量,要么流量在前面就被别的规则截胡了。
添加放行规则的基本姿势:
iptables -A INPUT -s 192.168.1.100 -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -i lo -j ACCEPT第二条命令的含义是允许TCP协议、目标端口为22的流量进入本机,也就是放行SSH端口。第三条命令放行回环接口(lo)的所有流量,这条规则必须要有。很多诡异的问题——比如本机访问本机的Web服务不通——都是因为回环流量被DROP规则拦了。
ACCEPT、DROP、REJECT三者的区别一定要清楚。ACCEPT表示放行,数据包进入后续流程;DROP表示丢弃,直接把包扔掉,不回复任何信息;REJECT表示拒绝,丢弃包的同时给发送方回一个错误信息(默认是ICMP port unreachable)。从发送方的角度看,DROP的表现是"超时",REJECT的表现是"立即被拒绝"。对外服务推荐用DROP,理由很简单:不暴露端口的存在,减少被扫描的风险;对内网的特殊场景,有时用REJECT更方便排错——至少你知道对方有没有收到拒绝通知。
3.2 匹配条件全拆解:IP、端口、协议、网卡、状态组合使用
一条iptables规则的"条件部分"由很多匹配项组成。我来把这几个最常用的匹配项讲透。
IP匹配:
iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -d 192.168.1.10 -j DROPs指定源地址,d指定目的地址。两者可以同时写,也可以只写其中一个。地址支持单个IP、CIDR网段,也支持非连续匹配,比如用!取反:-s ! 192.168.1.0/24表示"源地址不是这个网段"。
端口匹配:
iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p udp --sport 53 -j ACCEPT iptables -A INPUT -p tcp --dport 1024:65535 -j ACCEPT--dport后面跟目的端口,--sport后面跟源端口。端口匹配必须配合-p tcp或-p udp使用,不指定协议直接写--dport会报错。支持连续端口范围,中间用冒号分隔。
协议匹配:
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT不写-p默认匹配所有协议,也可以写-p all。ICMP协议就是ping命令用的协议,控制ping的放行和拒绝多是用--icmp-type配合。生产环境建议在INPUT链上放行ICMP的echo-request,禁止的话会影响网络连通性排查。
网卡接口匹配:
iptables -A INPUT -i eth0 -p tcp --dport 8080 -j ACCEPT iptables -A FORWARD -o eth1 -d 10.0.0.0/8 -j ACCEPT-i(input interface)匹配数据包进入的网卡,-o(output interface)匹配数据包出去的网卡。多网卡服务器上这个匹配项极其有用。比如你有内网网卡eth0和外网网卡eth1,内网服务只允许内网访问,就直接在规则里限定-i eth0,外网网卡来的包即使目的端口对上了也进不来。
状态匹配,前面提过,用-m conntrack --ctstate。注意新旧内核版本的区别:老版本用--state,新版本推荐用--ctstate,功能等价。
3.3 规则的插入、删除、替换与顺序陷阱
iptables的规则是从上到下逐条匹配的,匹配到第一条规则就执行动作,不再往下看。这个特性决定了"顺序"本身就是一种策略。
举例说明:你写了一条iptables -A INPUT -j DROP想默认拒绝所有流量,但在这之前已经有一条iptables -A INPUT -p tcp --dport 22 -j ACCEPT,那么SSH访问会被放行,因为SSH流量走到DROP之前就匹配了ACCEPT规则。反过来,如果你先执行了DROP,再执行ACCEPT,那么ACCEPT永远不会被命中,SSH服务直接断开。这就是"先拒绝后放行"和"先放行后拒绝"的经典差异。
所以需要用到-I(插入)命令:
iptables -I INPUT -p tcp --dport 3306 -j ACCEPT-I默认把新规则插到链的最前面。也可以指定插入位置:iptables -I INPUT 3 ...表示插入到第三条位置上。
删除规则用-D。删除时可以精确指定同样的匹配条件和动作,也可以用规则的编号删除:
iptables -D INPUT -p tcp --dport 3306 -j ACCEPT iptables -D INPUT 3用编号删除前必须先用iptables -L --line-numbers查看规则编号,这个--line-numbers在规则多了以后是排查神器。
这里提醒一个很多人踩过的坑:宁可用新的规则链来实现精细化管控,也不要试图通过频繁调整filter表规则顺序来满足需求。规则一多,手工维护顺序的复杂度是指数上升的。我习惯的玩法是:把一类规则放到一个自定义链里,然后主链上留一条跳转规则。比如所有进入的HTTP流量统一走HTTP_CHAIN,后续调整只需要改自定义链,不会影响主链的其他规则。
4. NAT与端口转发:让内网服务对外可见的关键配置
刚做运维那阵子,我对NAT的理解仅限于"上网要开NAT"。后来被业务方逼着给内网某个服务做端口映射,才真正把NAT的用法研究明白。这里直接给出企业环境里最常见的三类需求以及对应的iptables配置。
4.1 SNAT与MASQUERADE:内网机器共享公网出口
内网有几十台机器,但只有一台机器有公网IP,想让所有内网机器都能上外网。解决方案是在这台双网卡机器上开启IP转发,并配置SNAT规则。
首先确认内核参数:
echo 1 > /proc/sys/net/ipv4/ip_forward # 或者 sysctl -w net.ipv4.ip_forward=1然后配置SNAT:
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j SNAT --to-source 203.0.113.5这条规则的意思是:从192.168.10.0/24网段来的、要从eth0出去的包,把源地址改写成203.0.113.5。内网机器访问外网时,外网服务器看到的是公网IP;外网服务器回应时,数据包回到这台网关,网关再把目的地址改回内网IP。整个过程对内网机器来说是透明的。
如果你的公网IP是动态获取的(比如PPPoE拨号),用SNAT就麻烦了——每次IP变了都要重写规则。这种情况推荐用MASQUERADE:
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o pppoe0 -j MASQUERADEMASQUERADE会动态获取出口网卡当前的IP地址,不需要手动指定。它的代价是每次处理新连接时都要额外查询一次网卡IP,性能略低于SNAT。但在家庭宽带、动态IP场景里,这个性能损耗可以忽略不计。
4.2 DNAT端口映射:把内网服务发布到公网
内网有一台Web服务器192.168.10.20,监听8080端口,想通过公网网关的80端口访问它。需要两步配置:先配置DNAT(改目的地址),再配合filter表的FORWARD放行。
DNAT规则:
iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.10.20:8080这条规则的意思是:访问公网IP 203.0.113.5的80端口的数据包,把目的地址改成192.168.10.20,目的端口改成8080。
注意,仅仅改目的地址还不够。数据包到了网关后会继续走FORWARD链,如果FORWARD链默认DROP,数据包会被丢。于是还需要放行对应的流量:
iptables -A FORWARD -d 192.168.10.20 -p tcp --dport 8080 -j ACCEPT iptables -A FORWARD -s 192.168.10.20 -p tcp --sport 8080 -j ACCEPT第二条放行Web服务器回给客户端的数据包,方向相反,但同样经过FORWARD链。很多人在配置完DNAT之后发现外网访问不通,多半是漏了FORWARD链的放行规则。
如果只有一个公网IP,想把不同端口映射到内网不同机器上,就在规则里区分目的端口:
iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 8081 -j DNAT --to-destination 192.168.10.21:80 iptables -t nat -A PREROUTING -d 203.0.113.5 -p tcp --dport 8082 -j DNAT --to-destination 192.168.10.22:80这是用一个公网IP做多服务发布的常见方案,我建议在PREROUTING链里给每个映射写好注释,规则多了以后能省很多事。iptables 1.4.20以上版本支持-m comment --comment "描述",可以给每条规则附加说明。
4.3 REDIRECT:在本机内部做流量转向
有一种需求是在本机做端口重定向,不动源和目的地址,只把某个端口的流量转给本机另一个端口。经典场景:透明代理。
比如你有一个HTTP代理跑在8123端口,想让它拦截所有发往80端口的流量:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8123这条规则把进入本机的80端口流量重定向到8123端口,源和目的IP地址不变。代理程序可以读取原始的CONNTRACK信息还原出真实的目的地址,然后完成请求转发。还有的软件(squid透明代理模式)就会用到这个机制。
注意,REDIRECT的流量也会经过INPUT链。如果INPUT链有严格限制,你得放行8123端口的本地访问,否则流量被重定向之后直接被filter表拦掉了。
5. 防火墙规则的持久化与动态更新:重启不丢配置
如果你在终端里敲了一堆iptables规则,测试也通过了,然后重启了服务器——恭喜你,所有规则全部消失。这是iptables默认行为:规则存在内存里,不落盘。
5.1 iptables-save与iptables-restore的组合
保存规则最经典的方式是用iptables-save导出:
iptables-save > /etc/iptables.rules恢复规则:
iptables-restore < /etc/iptables.rules这两条命令在不同发行版上的默认路径略有差异。CentOS 6时代习惯保存到/etc/sysconfig/iptables,Ubuntu上早期是/etc/iptables.rules,后来系统引入了netfilter-persistent服务(底层其实就是iptables-save/restore的封装),配置文件路径变为/etc/iptables/rules.v4和/etc/iptables/rules.v6。
要让规则开机自动恢复,CentOS系直接执行:
service iptables save systemctl enable iptablesDebian/Ubuntu系执行:
apt install iptables-persistent netfilter-persistent save netfilter-persistent reload5.2 同时维护IPv4和IPv6:容易被忽略的Ip6tables
很多人在服务器上把IPv4的防火墙配得滴水不漏,却完全忘了IPv6。如果你的服务器开启了IPv6,攻击者完全可以用IPv6地址绕过你的IPv4防火墙。用ip6tables命令配置IPv6版本的规则,语法几乎一样,只是把iptables换成ip6tables。
ip6tables -P INPUT DROP ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -A INPUT -p ipv6-icmp -j ACCEPT ip6tables -A INPUT -i lo -j ACCEPT ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT注意IPv6下ping用的协议是ipv6-icmp,用来区分IPv4的icmp。如果服务器不需要IPv6,干脆在系统层面禁用IPv6,省心很多;如果需要,建议把IPv6和IPv4规则一起纳管,不要只配一半。
5.3 动态更新规则时的"会话保持"问题
升级防火墙规则最怕的一件事:新规则一上,现有连接全部断开。比如你更新SSH端口的放行规则,如果不小心把当前SSH的ESTABLISHED连接给DROP了,那你可能直接断开了自己的远程管理通道。所以在生产环境修改规则前,我强烈建议先把两条保底规则加上:
iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -I INPUT -p tcp --dport 22 -j ACCEPT先确保当前会话不会中断,再去做修改。如果你所在的环境不允许临时测试,那就准备一个"逃生通道"——比如带外管理卡、IPMI、或者另一个机房机器上的跳板。网上流传的操作是把规则脚本写成定时任务,几分钟后自动恢复,防止自己把自己锁在外面。这个思路在没法物理接触服务器时非常实用。
5.4 firewalld和iptables到底什么关系
这两年问这个问题的同行越来越多了。简单说,firewalld是RHEL/CentOS 7之后默认的防火墙服务,它底层仍然调用iptables命令,只是提供了一套更上层的封装,支持动态修改规则而无需刷新整个规则集。也就是说,firewalld是"管理器",iptables是"执行器"。
使用firewalld时,你不需要直接写iptables命令,而是用firewall-cmd或firewall-config操作富规则,典型的例子:
firewall-cmd --zone=public --add-port=80/tcp --permanent firewall-cmd --reload但如果你是老运维,习惯直接写iptables规则,也可以在系统层面禁用firewalld,装回iptables-services,用传统的方式配置。我个人的观点是:新项目直接学firewalld更省力,但搞懂iptables底层会让你在遇到复杂问题(比如Docker端口映射、kube-proxy的iptables模式)时不至于抓瞎。很多K8s网络插件至今用的还是iptables,这也是为什么现在运维对iptables知识的需求并没有因为firewalld的出现而减少。
6. 我踩过的iptables坑与排查方法
最后这部分,我把这些年线上环境里真实踩过的坑掰开揉碎讲一讲。每个坑背后都是一段"排查两小时,解决一分钟"的惨痛经历。
6.1 规则顺序引发的"灵异事件"
有一次线上环境反馈:服务器上某个API偶发性超时。我登录上去一看,iptables规则里有一条iptables -A INPUT -s 某个内网网段 -j DROP,位置在放行规则之前。奇怪的是,之前一直好好的,怎么突然就出问题了?
后来排查发现,那台服务器的内网IP变了,原来"被放行"的网段恰好落到了新加上的一条DROP规则的匹配范围内。问题不在规则本身对不对,而在规则顺序。那件事之后,我给自己定了一个纪律:每加一条DROP规则,先确认它不会影响已有的ESTABLISHED连接,再确认它不会拦掉更宽泛网段的合法流量。iptables的规则顺序是自顶向下的,前面一旦DROP,后面再写什么都是徒劳。
排查方法也很简单:iptables -L INPUT -n --line-numbers查看顺序,iptables -I INPUT <编号>插入到正确位置。定位问题时多用-v看计数,哪条规则计数在暴涨,问题大概率就在那条规则附近。
6.2 默认策略是DROP,结果把自己锁死在外面
这是新手翻车率最高的一件事。场景通常是这样的:你为了安全,先把INPUT链默认策略改成了DROP:
iptables -P INPUT DROP然后你意识到自己当前这条SSH连接的响应包还没被放行——因为你只改了默认策略,还没有添加任何放行规则。于是啪,断了。
我自己的做法是:任何时候修改默认策略之前,先确认当前会话的连续性。具体就是先把对应的放行规则写好,再改默认策略。比如你要把INPUT默认策略改成DROP,那就先把这几条写进去:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT然后再执行iptables -P INPUT DROP。如果发现自己已经锁死了,而服务器是云主机,大多数云平台控制台提供VNC/管理终端,可以从控制台直接登录进去把规则清掉。物理机就只能依赖带外管理了。这也是我前面建议准备"逃生通道"的原因。
6.3 开启IP转发后FORWARD链不放行,转发流量全丢
某次帮朋友排查公司内网的问题:所有机器能ping通网关,但是上不了外网,网关机器本身能上。第一反应是NAT没配置好,结果一查SNAT规则在。再一查FORWARD链,默认策略是DROP,而且完全没有放行规则。问题一下就清楚了:内网到外网的包走到网关后,在FORWARD链被丢了。加了放行规则后问题解决:
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -s 192.168.10.0/24 -j ACCEPT为什么FORWARD链会变成默认DROP?不用怀疑,就是之前某个"安全加固"脚本干的。这类加固脚本通常把三条链的默认策略全改成DROP,但没考虑这台机器是否需要转发流量。所以我一直建议:安全基线的配置要结合机器实际的业务角色,不能拿同一套模板生搬硬套。
6.4 Docker环境下iptables规则被"动过手脚"
用Docker的人大概都遇到过这样的情况:明明我把宿主机的安全组/防火墙配置好了,但容器端口一映射,规则就"乱"了。这是因为Docker在创建容器时,会自动往iptables的nat表和filter表里插入自己的规则。如果你手动清空iptables规则,Docker容器可能就失去了端口映射能力。
两个高频问题的解法:
问题一:清空规则后容器端口映射失效。
原因在于Docker通过DOCKER链和iptables规则完成了端口转发。手动清空规则把这些规则删了。解决办法是重启Docker服务,让Docker重建自己的链和规则:
systemctl restart docker问题二:手动启动Docker时,外部访问不了映射端口。多数情况下是你把FORWARD链默认策略改成了DROP,而Docker的转发流量会被DROP。解决方式有两种:一是把FORWARD默认策略改回ACCEPT;二是在FORWARD链里显式放行Docker相关的流量。
这里我并不建议简单粗暴地把FORWARD默认策略改回ACCEPT,因为那样整个防火墙策略等于开了个大口子。更稳妥的做法是了解Docker实际依赖哪些链,按需放行。如果你在宿主机上装了firewalld,那么firewall-cmd --zone=docker --add-masquerade这类操作也许能解决部分问题,但底层还是iptables。
6.5 端口通了但业务不通:别忘了状态规则和并发限制
最后一个常被忽略的坑:你放行了端口,外部也能连上TCP连接,但业务就是响应异常。这种情况我倾向于用conntrack的状态规则来排查。比如你的规则只放行NEW状态的包,忘记放行ESTABLISHED和RELATED,那么双向通信就会表现得很诡异——建立连接的包能过,后面的数据全被丢。
排查命令可以直接查conntrack表:
conntrack -L如果没有安装conntrack命令行工具,直接看/proc/net/nf_conntrack也可以。如果发现连接状态大量是INVALID,那多半是规则配置有问题,或者有伪造IP的流量攻击。
另外,iptables -L -v能看到每一条规则的包计数和字节计数。如果某条规则计数不断增长,说明它一直在被命中,对定位问题帮助极大。记住一点:iptables的问题,先看计数,再看顺序,最后看默认策略,这个顺序能帮你省掉80%的排查时间。
7. 再聊几个生产环境常见的iptables优化细节
如果只满足于"能配规则能放通端口",那你和iptables之间还差一层。生产环境里性能和安全同样重要,这里分享几个我一直在用的优化习惯。
7.1 把"匹配频率高"的规则放在前面
iptables是逐条遍历的,规则越多,匹配时间越长。虽然现代内核的iptables性能已经很好,但高并发场景下,规则顺序仍然会影响转发性能。
一个实用原则:流量命中率高的规则往前放。比如你有一台Web服务器,90%的流量都是访问80端口,那就把80端口的ACCEPT规则放在最前面。这样大部分数据包在第一二条规则就命中了,不用一条一条往下刷。iptables -I INPUT 1 -p tcp --dport 80 -j ACCEPT这样的插入方式就能实现。
7.2 用自定义链做规则分组
规则一多,直接在系统链(INPUT、OUTPUT、FORWARD)上堆规则会变得难以维护。自定义链能帮你把规则结构化。
举个例子,假设INPUT链上需要管理SSH、HTTP、HTTPS、监控采集等好几组规则。我可以建三个自定义链:SSH_CHAIN、WEB_CHAIN、MONITOR_CHAIN。主链上只留几条跳转:
iptables -N SSH_CHAIN iptables -A INPUT -p tcp --dport 22 -j SSH_CHAIN iptables -A SSH_CHAIN -s 192.168.0.0/16 -j ACCEPT iptables -A SSH_CHAIN -j DROP这样规则语义极其清晰:SSH流量先走SSH_CHAIN,内网放行,其余丢弃。后面要调整SSH策略,只需要操作SSH_CHAIN,不影响其他规则。用-N创建自定义链,用-F清空自定义链,用-X删除自定义链。
7.3 日志审计:如何把丢包行为记录下来
防火墙最怕"静默丢包"——包丢了,但你没日志可查。生产环境里我建议对关键动作开启日志记录,尤其是DROP操作。
iptables提供了LOG动作,但LOG动作有一个特点:它只负责记录日志,不改变数据包的走向。如果你想让数据包既被记录又被丢弃,需要写两条规则:
iptables -A INPUT -p tcp --dport 23 -j LOG --log-prefix "IPTables-DROP-Telnet: " --log-level 4 iptables -A INPUT -p tcp --dport 23 -j DROPLOG产生的日志默认写入内核日志,可通过dmesg查看,或者通过rsyslog转发到/var/log/messages。日志量大的时候,一定要做好日志轮转,否则磁盘会被日志灌满。如果只想统计某些规则的命中次数,又不愿意看日志,用-j ACCEPT加计数器(-v参数看计数)就行。
7.4 秒级启停:用脚本管理整个规则集
手工一条一条敲命令,在生产环境太脆弱了。我习惯把一套完整的规则集合写成一个脚本,脚本开头先清空当前所有规则,然后按顺序重新加载。这样有两个好处:一是规则的全貌一目了然;二是出问题后可以快速回滚到"空规则"状态,恢复网络。
一个简单的脚本结构大致是这样的:
#!/bin/bash IPT=/usr/sbin/iptables # flush all rules $IPT -F $IPT -t nat -F $IPT -t mangle -F $IPT -X # set default policy $IPT -P INPUT DROP $IPT -P FORWARD DROP $IPT -P OUTPUT ACCEPT # loopback $IPT -A INPUT -i lo -j ACCEPT # established connections $IPT -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # services $IPT -A INPUT -p tcp --dport 22 -j ACCEPT $IPT -A INPUT -p tcp --dport 80 -j ACCEPT $IPT -A INPUT -p tcp --dport 443 -j ACCEPT # ping $IPT -A INPUT -p icmp --icmp-type echo-request -j ACCEPT # save iptables-save > /etc/iptables.rules脚本执行完后,再验证一下关键端口是否正常。如果脚本有问题,马上执行iptables -F清空规则恢复默认状态,远程连接不会断(前提是OUTPUT默认策略是ACCEPT)。脚本上线前先在测试机跑一遍,这个习惯能救你一命。
8. 写在最后:iptables学习路径的几点个人体会
很多人问我,现在有firewalld、有安全组、有云防火墙了,还有必要花时间学iptables吗?我的回答一直是:有必要,而且非常有必要。
firewalld再方便,遇到复杂网络场景时,最终仍然会暴露成一条条iptables规则。云平台的安全组再强大,到了自建机房、裸金属服务器、容器网络环境里,最底层能精确控制数据包的仍然只有Netfilter/iptables。哪怕你打算往K8s方向发展,kube-proxy在iptables模式下维护的那一大堆转发规则,不懂iptables的话根本看不明白。
学习路径上,我建议按这个顺序推进:先搞清楚数据包流向和四表五链的框架,再用filter表练手做端口放行与封锁,接着掌握NAT和端口转发,然后是状态匹配与连接跟踪,最后才是性能优化和自定义链这样的进阶内容。每一步都最好在虚拟机或者云主机上亲手敲命令验证,光看不练记不牢。
测试环境里随便玩没关系,但生产环境有一条底线:所有操作前先备份当前规则。iptables-save > backup.rules这一条命令,成本几乎为零,收益却可能是一次原本要通宵处理的事故被缩成十分钟恢复。
最后分享一个小技巧:写完一套规则后,不要急着收工,花两分钟逐条检查一遍计数器和规则顺序。iptables -L -n -v --line-numbers这条命令值得你养成肌肉记忆。很多问题在刚配完的时候就会暴露出来,当场发现比半小时后业务告警再排查要舒服得多。