
1. 为什么还在聊netfilter/iptables如果你管过Linux服务器早晚会撞上这样一幕某个服务明明起来了端口也在监听但远程就是连不上。查了半天最后发现是防火墙规则把流量挡了。这时候如果你对netfilter/iptables没有一个清晰的理解排查起来就是无头苍蝇只能一条条试运气好能试出来运气不好直接把自己锁在服务器外面。netfilter是Linux内核里的数据包处理框架iptables是用户空间里用来操作这个框架的命令行工具。简单说netfilter是内核里真正干活的那套机制iptables是给管理员用来下发规则、配置策略的那个控制台。两者配合构成了绝大多数Linux发行版默认的防火墙方案。虽然现在nftables已经出现好几年新一代工具也在逐步普及但了解iptables仍然很有必要存量服务器上有大量生产环境用的是iptables你不可能要求所有运维立刻迁移到新方案而且netfilter的框架思想、hook点设计、表链结构理解透彻了对学习nftables和其他防火墙产品都有直接帮助。这篇文章不打算给你堆一长串手册式的命令列表而是把我实际排查问题、配置策略时的经验拆开讲。从数据包在内核里怎么流转到表、链、规则这些概念到底是什么意思再到真实场景下的配置套路和排错思路尽量一次讲透。适合刚接触Linux网络的新人也适合那些已经会敲几条iptables命令、但遇到复杂规则就犯迷糊的运维同行。2. 先理解netfilter的数据包流向很多教程一上来就给你画一堆表和链的关系图说实话我当年就是这么被绕晕的。真正搞明白iptables第一步不是背表和链而是先理解数据包进出一台Linux主机时会经过哪几个关键位置。netfilter在内核协议栈里预设了若干个钩子hook点。所谓钩子就是数据包在特定路径上会被内核暂停一下交给注册在这些点上的规则去检查放行或丢弃。Linux内核里主要有五个钩子点PREROUTING数据包刚进入协议栈还没做路由判断时触发。INPUT数据包经过路由判断确认是发给本机时触发。FORWARD数据包经过路由判断确认不是发给本机、需要转发出去时触发。OUTPUT本机进程发出的数据包在真正发出前触发。POSTROUTING数据包即将离开协议栈、发往网卡之前触发。这五个点基本上覆盖了所有网络路径。比如你访问一台Web服务器数据包到达网卡后先过PREROUTING再判断目标是本机IP于是走INPUT被应用程序接收如果你在这台机器上配置了NAT转发从内网进来的数据包经过PREROUTING后会走FORWARD最后经过POSTROUTING做源地址转换发出去。我对这个路径的理解有一个很直观的类比数据包像一份快递PREROUTING是门卫第一次看到快递时检查发件人路由判断是决定这份快递该送给你还是转交到别处INPUT是送到你手里之前最后再验一次OUTPUT是你寄出快递前自己检查一遍POSTROUTING是快递被收走时贴上新面单。iptables的规则就贴在这几个检查点上。理解了这个路径很多问题就迎刃而解。比如有一类典型的坑你在一台开启了IP转发的主机上用iptables配置了FORWARD链放行规则但流量还是不通。排查方向如果只盯着INPUT链那永远找不到问题。FORWARD链和INPUT链是两条完全不同的路径前者处理的是转发流量后者处理的是发到本机的流量。搞混了这两个链基本上就是被墙半天还一脸懵。3. 表与链iptables的四张表和五个链netfilter提供了五个钩子点iptables则在钩子点之上组织了表和链的抽象层。你配置规则时不是直接往钩子点塞命令而是先确定规则要放进哪张表、挂在哪条链上。3.1 四张核心表iptables内置了filter、nat、mangle、raw四张表。每张表的职责不同优先级也不同。数据包经过一个钩子点时会按照表优先级依次检查raw最先然后是mangle、nat最后是filter。filter表是最常用的表专门用于过滤数据包决定放行还是丢弃。nat表用于地址转换包括源地址转换SNAT和目的地址转换DNAT。mangle表用于修改数据包的特定字段比如修改TTL、TOS或者打标记。raw表优先级最高主要作用是在数据包连接跟踪之前做处理一般用于跳过连接跟踪比如对某些不需要追踪的流量设置NOTRACK。你可能会问为什么要分这么多表直接在一个地方写规则不行吗原因是性能和组织上的双重考虑。filter表只做允许/拒绝判断规则匹配命中后就不会再去nat表绕一圈如果所有规则都堆在一起每次数据包经过都要把无关规则全刷一遍效率太低。把不同职责的规则拆到不同表内核可以把“允许通行”和“转换地址”两种操作并行处理逻辑上也清晰很多。3.2 内置链与自定义链前面提到的五个钩子点映射到iptables里就是五条内置链PREROUTING链、INPUT链、FORWARD链、OUTPUT链、POSTROUTING链。不是每张表都有全部五条链比如nat表有PREROUTING、INPUT、OUTPUT、POSTROUTING没有FORWARDfilter表有INPUT、FORWARD、OUTPUT没有PREROUTING和POSTROUTING。这是因为地址转换和过滤的职责范围不一样不是每个钩子点放每种操作都有意义。理解内置链还不够真正复杂的生产环境里几乎一定要用自定义链。自定义链有两个好处一是把同类型的规则集中管理二是在规则数量较多时可以跳过整条链的检查。举个例子你有一台服务器需要同时对外开放Web、SSH、数据库三个服务每个服务都有自己的IP白名单和限速策略。如果所有规则都平铺在INPUT链里规则一多阅读和维护都很痛苦。我的做法是创建三张自定义链分别叫WEB_SERVICES、SSH_ACCESS、DB_ACCESS然后在INPUT链里用跳转动作把流量导向对应的自定义链。这样后续要加一条数据库的规则只需要改动DB_ACCESS链不需要碰其他规则。自定义链还有个容易踩的坑自定义链本身不会主动被检查必须通过内置链上的-j跳转动作进入。而且跳转进去后如果没有匹配到任何规则数据包会自动返回到调用它的链继续检查而不是默认放行或丢弃。这一点希望大家牢记我在刚开始用自定义链时就在这儿吃过亏以为进了自定义链没有匹配就会丢弃结果流量直接绕过了我的限制规则。4. 规则的五元组匹配与动作理解了表和链再来看规则本身。iptables的一条规则主要由两部分构成匹配条件和动作。匹配条件就是描述“什么样的数据包”动作就是“匹配到了怎么办”。4.1 匹配条件最基础的匹配方式是五元组源IP、目的IP、源端口、目的端口、协议类型。比如一条规则写成iptables -A INPUT -s 192.168.1.0/24 -d 10.0.0.5 -p tcp --dport 22 -j ACCEPT意思是放行来自192.168.1.0/24网段、发往10.0.0.5的22端口即SSH的TCP数据包。这里的s是源地址d是目的地址p是协议--dport是目的端口如果同时指定源端口就用--sport。除了五元组iptables还支持很多扩展匹配。比如用-m state匹配连接状态用-m limit做流量速率限制用-m mac匹配MAC地址用-m time匹配时间段。这些扩展匹配大大增强了规则的表达能力但也增加了配置复杂度。我的建议是先从五元组入手把基础规则写到熟练再逐步学习扩展匹配。4.2 动作和跳转匹配条件确定后动作决定了数据包的最终去向。最常用的是ACCEPT放行、DROP丢弃、REJECT拒绝并回送错误信息。三者区别我用一句话概括DROP是假装什么都没发生直接把包扔掉REJECT是礼貌地说“我不收”回一个拒收通知给发送方ACCEPT是直接放行。这个区别在实际使用中影响很大。对外提供服务时如果用DROP客户端的表现是连接超时可能要等很久才能感知到失败如果用REJECT客户端会立刻收到拒绝信号比如SSH直接提示Connection refused。安全加固时有些场景故意用DROP来隐藏服务的存在让扫描工具以为这个IP根本不可达。具体怎么选要看业务需求和暴露面控制策略。还有一类动作是跳转包括前面说的跳转到自定义链以及SNAT、DNAT、MASQUERADE、LOG等。以LOG为例它把匹配到的数据包记录到内核日志方便调试规则。这里有个实际经验LOG动作只记录日志不会中断规则链的继续执行。所以你可以在一条DROP规则前面加一条LOG规则这样既能记录被丢弃的流量又不会影响最终丢弃的结果。5. 真实场景下的配置实操概念讲多了容易飘我直接拿出几个自己踩过坑、反复验证过的配置场景把命令和应用背景一起讲透。5.1 基础安全策略只开放必要端口新装一台Linux服务器第一件事就是把默认的INPUT链策略改成DROP然后只放行需要的端口。默认策略就是一整条链的“兜底动作”如果没有规则匹配就执行这个默认策略。iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT这样设置的意思是所有发往本机的数据包除非有明确规则放行否则一律丢弃。输出方向默认放行保证本机发起的连接没问题。接着放行必要的服务iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT第一条规则特别重要上一节提到的state匹配就在这里发挥作用。它的意思是所有已经建立连接ESTABLISHED或者由已建立连接衍生的新连接RELATED都放行。没有这条规则即使你开放了22端口SSH建立了连接后后续的数据包也可能因为不属于“新连接”而被默认策略丢弃导致连接断断续续甚至直接掉线。我在实际配置中遇到过一个问题只开放了端口却没加状态匹配规则结果服务器所有外部连接都异常。排查了半天最后发现是ESTABLISHED规则缺失。这个经验值得写在最前面新建INPUT规则时第一行先加状态放行规则再开放具体端口顺序别搞反。5.2 NAT转发让内网机器上网假设你有一台双网卡Linux服务器一块网卡连外网另一块连内网内网机器要通过这台服务器上网。这里就要用到nat表和FORWARD链。先开启内核的IP转发功能sysctl -w net.ipv4.ip_forward1这个参数要永久生效的话需要写入/etc/sysctl.conf配置文件否则重启后就会失效。我建议配置完立即用sysctl -p刷新确认生效再继续。然后配置iptables规则iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE iptables -A FORWARD -s 192.168.10.0/24 -j ACCEPT iptables -A FORWARD -d 192.168.10.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPTPOSTROUTING链上的MASQUERADE动作是SNAT的一种特殊形式实现了动态源地址转换。它会自动读取出口网卡的IP地址把来自内网的流量源地址改成服务器的外网地址。这里的-o eth0限定了从eth0出去的流量才做转换防止流向其他网段的流量被错误改写。FORWARD链上的两条规则第一条放行内网发往外部的转发流量第二条放行外部返回的应答流量。这里有一个非常关键的细节FORWARD规则默认策略通常是DROP如果只写第一条而遗漏第二条那么内网机器能发出请求但响应包会被丢弃表现依然是无法上网。这种“有去无回”的故障在处理转发场景时非常常见。5.3 端口映射把外部流量转到内网服务另一个高频场景是端口映射一般也叫DNAT。比如你有一个内网Web服务器IP是192.168.10.10想通过公网服务器的8080端口访问它配置方式是iptables -t nat -A PREROUTING -d 公网IP -p tcp --dport 8080 -j DNAT --to-destination 192.168.10.10:80这条规则的作用是当数据包到达服务器、还没做路由判断时在PREROUTING链上直接把目的地址和端口改写为内网服务器的实际地址和端口。后续内核才会根据改写后的目的地址做路由将数据包转发到FORWARD链。端口映射有个容易忽略的点需要确认FORWARD链里放行了对应流量。很多人在nat表配了DNAT但FORWARD链默认DROP流量到了服务器后直接被丢弃内网Web服务器永远收不到请求。我当时排查这类问题习惯性先看nat表后来才养成一个习惯做完DNAT后立刻检查FORWARD链、加上对应放行规则。推荐用下面的命令验证iptables -L -v -n --line-numbers这个命令用-v参数显示每个链的包计数用-n不做反向解析用--line-numbers显示规则序号。看计数就能知道规则有没有被命中如果计数一直是0说明规则本身没匹配到流量如果计数在增长但流量还是不通说明规则命中了但后面还有规则把它拦了或者返回流量有问题。5.4 保存与恢复规则iptables规则默认只存在于当前内核内存中重启后就会丢失。生产环境必须把规则持久化保存。不同的Linux发行版方式不一样。以常见的发行版为例可以用iptables-save /etc/iptables/rules.v4恢复时iptables-restore /etc/iptables/rules.v4如果你用的是启用了iptables服务的老版本系统一般默认会在网络服务启动时自动加载规则文件。新版本系统里强烈建议配置一个systemd服务单元或者干脆把规则的保存和加载挂在网络接口启动的脚本里。这里我踩过一个大坑有一次改完规则后没有立即保存结果跑了一段时间后服务器重启所有规则消失服务直接暴露出来了。从那以后我养成了习惯每次确认规则生效后立即执行保存并且把保存结果再执行一次恢复验证确保规则语法没有在保存/恢复的过程中出错。6. 连接跟踪机制如果说netfilter框架是iptables的骨架那连接跟踪机制就是让iptables真正智能起来的神经网络。state匹配之所以能用就是因为内核会为每个网络连接建立一条跟踪记录记住这个连接处于什么状态。6.1 四种连接状态常用的连接状态是NEW、ESTABLISHED、RELATED、INVALID。NEW表示数据包所属的连接是新建的通常是连接的第一个数据包ESTABLISHED表示这个连接已经双向通信过后续的数据包都算建立状态RELATED表示这个连接是由已有连接衍生的最典型的是FTP的数据连接INVALID表示数据包无法被识别为合法的连接状态通常意味着数据包损坏或者不完整。实际配置中最常用的组合是放行ESTABLISHED和RELATED只对NEW状态的连接做严格管理。前面写的SSH场景里就有一行iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT有了这行所有已经建立的连接自动放行而新连接的建立则只允许通过指定端口。这种策略既保证了安全性又避免了维护大量针对回复数据包的繁琐规则。6.2 连接跟踪的代价与优化连接跟踪是有代价的。系统需要维护一张连接跟踪表每条连接在表里占用一块内存。如果服务器承载的连接数非常大连接跟踪表可能被填满导致新连接被丢弃表现为正常访问突然失败。遇到这种情况有几个排查思路。先用下面的命令查看连接跟踪表的使用率cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max如果count接近max说明连接跟踪表快满了。临时处理可以调大上限sysctl -w net.netfilter.nf_conntrack_max65536还要注意UDP和TCP的跟踪超时时间不同。UDP的超时默认比较短TCP的较长对于大量保持长连接的TCP业务可能需要适当调整超时参数。这个调整要结合业务实际来测试不宜一次性调得太大否则跟踪表里会堆积大量无效连接白白占用内存。我的实操经验是对于高并发短连接场景比如频繁请求HTTP接口的服务可以适当调低TCP跟踪超时时间让失效连接尽快被清理对于WebSocket这类需要保持长时间连接的业务则要保证超时时间大于心跳间隔否则连接会被误判失效。7. 规则匹配顺序与性能说一个新手容易忽略但影响很大的点iptables的规则是按顺序匹配的一旦某条规则命中就执行该规则的动作后续规则不再检查。这个特点决定了规则顺序本身就是一种策略。7.1 规则顺序的实战影响举个例子。你有两条规则iptables -A INPUT -s 192.168.1.100 -j DROP iptables -A INPUT -s 192.168.1.100 -p tcp --dport 22 -j ACCEPT第一条已经把来自192.168.1.100的所有数据包都丢弃了第二条永远不可能匹配到。如果你想让这个IP只能访问SSH就不能这么写。正确的写法是把范围小的规则放在前面iptables -A INPUT -s 192.168.1.100 -p tcp --dport 22 -j ACCEPT iptables -A INPUT -s 192.168.1.100 -j DROP这样处理的效果是来自该IP的SSH请求被放行其他所有请求被丢弃。先精确放行再兜底拒绝这个原则适用于大多数配置场景。7.2 高流量环境下的规则优化在流量比较大的服务器上规则数量越多每经过一个数据包要做的匹配判断就越多性能开销越明显。优化思路主要有两个方向。一是把命中率高的规则尽量往前放。比如你的服务器99%的流量都是HTTPS那HTTPS的放行规则就应该放在SSH规则之前减少大多数数据包在规则链表里的匹配深度。二是多用自定义链。每次跳转到自定义链相当于给一类流量划定专线其他流量不会逐一经过这些规则。假设你有20条针对数据库服务的规则如果不使用自定义链这20条规则对每一个进入INPUT链的数据包都要被顺序匹配一遍。用自定义链则可以在入口处判断如果目的端口不是5432直接跳过整条DB_ACCESS链。7.3 查看命中计数来调优性能优化不能拍脑袋要看实际数据。用下面的命令查看每条规则的匹配计数iptables -L -v -n每一行规则的最后会显示该规则匹配了多少个数据包、多少字节。如果某条规则长期以来一条都没有匹配就要反思它是否真的有必要存在或者是不是放错了链、写错了匹配条件。这套逻辑和数据库索引失效的分析很像本质上都是让最有效的判断尽量前置。我还习惯定期导出线上规则和计数保存下来做对比。规则改动前记录一次改动后再记录一次方便回滚和确认效果。8. 常见问题与排查技巧实录这些年积累了很多iptables相关的排查经验挑几个典型场景分享出来。8.1 配置完所有规则后服务还是不通这个问题出现频率最高。按顺序排查的思路如下第一步确认服务本身在监听。用ss命令看端口ss -tlnp | grep 8080如果服务没监听和防火墙无关先解决服务本身的问题。第二步确认规则确实生效了。执行iptables -L -n --line-numbers看INPUT链中是否包含了你想添加的规则注意规则顺序是否合理。第三步看计数。用前面提到的iptables -L -v -n --line-numbers如果目标是端口8080的规则匹配计数没有任何增长说明数据包根本没有走到这条规则。这时需要回头检查数据包路径是不是PREROUTING链转走了或者流量走的是FORWARD路径而不是INPUT路径。第四步如果计数在增长但不通用tcpdump抓包看数据包在哪里终止。抓包命令结合端口过滤比如tcpdump -i eth0 port 8080 -nn这能看到数据包是否到达网卡、到达了哪一张网卡以及是否有响应包发出。看到现象以后再回推是哪条链的动作影响了这个包。8.2 修改规则导致自己断连这个问题基本上是每个用iptables的人都经历过的。核心原因很简单你把自己当前所在的连接给DROP了。比如你在远程SSH到服务器上执行了一条iptables -A INPUT -s 你的IP -j DROP或者不小心把INPUT默认策略改成DROP然后忘了加放行SSH的规则。结果就是手一抖连接瞬间断开再也没法远程访问。如果人就在机房现场问题不大把控制台接上把规则清掉就行。但大多数时候你不一定在服务器旁边这时候有几种应急手段。一种是在配置高危规则前用at命令定时清理规则。比如echo iptables -F | at now 1 minute这个命令会在一分钟后清空所有规则。如果配置规则后连接没断那么需要手动把这条定时任务取消如果连接已经断了定时清理会在之后自动恢复规则。这个技巧在远程环境配置防火墙时非常实用。另一种做法是配置一个“管理网段白名单”规则只允许来自某个已知安全网段的SSH连接其他IP一律不开放SSH。这样即使其他规则写错了管理网段仍然可以登录。8.3 SNAT和MASQUERADE选哪个这两个动作都做源地址转换区别在于是否动态获取出口地址。SNAT的写法是显式指定转换后的源地址iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j SNAT --to-source 203.0.113.10MASQUERADE则是自动读取出口网卡的当前IP进行转换iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o eth0 -j MASQUERADE如果出口IP固定用SNAT更高效因为不需要每次动态获取地址如果出口IP不固定比如拨号上网、DHCP动态获取用MASQUERADE更省心。实践中很多人的服务器是固定公网IP的我一般更倾向SNAT开销小一点规则也更明确。8.4 RELATED状态什么时候用前面提到RELATED状态用于衍生连接最典型的场景是FTP。FTP有两种模式主动模式和被动模式无论哪种模式除了控制连接外还可能建立一条数据传输连接。如果iptables规则只放行了控制连接的21端口数据连接可能经过随机端口被默认策略丢弃。RELATED状态就是解决这个问题的iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT配合加载相应模块比如nf_conntrack_ftp内核就能识别出FTP数据连接和控制连接之间的关联关系自动为数据连接放行。这个经验不光适用于FTP也适用于SIP、H.323等协议。9. 说给新手的两句实在话最后补充一点自己的看法。netfilter/iptables的官方文档和资料很多但真正让你掌握它的不是把man手册背下来而是多折腾、多踩坑、多观察现象和数据。我在实际使用中有一个习惯每次配置一组新规则时都会先写清楚“我要解决什么问题”然后用最小化的规则集去验证再逐步扩展。比如要先放开80端口就先加一条80端口的放行规则用浏览器验证通了以后再加其他规则。不要一上来写个十好几条的规则组出了问题根本不知道是第几条导致的。还有一个建议是熟悉iptables的日志输出。在关键规则前面加上LOG动作把被拦截的流量记录下来配合系统日志查看。在这个基础上结合抓包绝大多数网络排查问题都能找到线索。netfilter/iptables的功能远不止这篇文章里讲的这些但把数据包流向、表和链的关系、规则的匹配与顺序、连接跟踪机制这几个核心点理解透了日常工作中百分之八十的防火墙场景都能从容应对。后面有机会我再单独聊聊nftables的迁移思路以及借助云平台安全组和iptables做分层防御的一些实践心得。