这两台机器从上周开始就有点不对劲,业务方反馈说跨网段调接口偶尔超时,严重的时候直接连接被拒绝。我从头到尾把SELinux和防火墙两层都翻了一遍,最后发现两边都有问题,而且都不是表面看上去那么简单。这篇就当一次完整的问题回顾,把排查链路、关键命令和容易踩的坑都写清楚,给后来人提个醒。
1. 问题现象与初步判断
1.1 故障现象描述
事情是这样的,我们内网有一套CRM系统,升级之后部分客户端开始报"连接超时",还有一部分直接报"Connection refused"。服务器本身跑得好好的,本机curl那个接口一点问题没有,日志里也没有任何服务异常。但客户端一跨网关过来就死活不通,甚至局域网内非同一网段的机器也连不上。
最开始我怀疑是不是程序升级改了什么监听地址,或者端口没起来,但查了一圈发现进程在跑,监听也正常,端口也开了。既然进程和端口都没问题,那就只剩下系统层面的网络过滤和访问控制,也就是SELinux和防火墙这两层。
这里有个经验要提前说:很多运维一看到连不上就习惯性地systemctl stop firewalld加setenforce 0,这种操作虽然能临时速通,但往往埋下隐患,尤其对于不能随便重启的生产服务器。所以我的原则是,先定位到具体原因,再考虑怎么改,不到万不得已不全局关防火墙。
1.2 初步排查思路:先看链路,再查监听,最后查过滤
遇到连接问题,我会按下面的顺序走一遍,不用上来就抓包,先排除最底层的网络可达性问题。
# 查看网卡和IP配置 ip addr # 查看路由表 ip route # 测试基本连通性 ping -c 3 目标IP # 测试端口是否通 telnet 目标IP 8080 # 查看本机监听端口和进程 ss -lntp | grep 8080ping和telnet的区别很重要:ping走的是ICMP协议,通与不通只能说明主机之间"有没有路";而telnet测的是TCP端口是否可达,端口能通才代表业务链路没问题。我们这里ping两边都通,但telnet只有从本机回环地址测才通,一旦用局域网IP去连就卡住,这基本可以断定路由和物理链路没问题,问题出在中间过滤层。
另外提醒一下,ss -lntp看到的监听地址很有讲究。如果看到的是127.0.0.1:8080而不是0.0.0.0:8080,那服务只监听了回环地址,外部怎么都连不上,这是程序配置问题,不是防火墙的事。我当时排查的服务监听在0.0.0.0,所以这种可能性直接排除了。
链路、监听、路由这几关都过了,那就轮到系统层的主角出场:SELinux和firewalld。
2. SELinux 到底拦截了什么
2.1 SELinux在连接问题里的真实角色
很多人觉得SELinux是个反人性的东西,设置复杂还看不懂日志。但实际上它是一款内核级强制访问控制机制,比普通的文件权限更严格。普通权限看的是"进程属主和文件属主是否匹配",SELinux关心的是"进程的安全上下文是否有权访问这个文件、这个端口、这个网络资源"。
拿我们这个场景来说,业务服务升级后,如果可执行文件从旧的路径搬到了新路径,SELinux的文件上下文标签会自动匹配吗?并不会。比如原本/usr/local/crm/server下是bin_t类型,新程序放到/opt/crm/bin下,上下文可能变成了usr_t。这时候服务进程如果还想通过特定端口对外监听,SELinux策略里没有对应的放行规则,就会直接在系统调用层面把这次操作拒掉,程序却不会主动崩溃,日志里也没有业务异常。
SELinux有三种模式:
| 模式 | 行为 |
|---|---|
| Enforcing | 强制执行策略,违反直接拒绝并记录AVC日志 |
| Permissive | 不实际拒绝,只记录警告日志 |
| Disabled | 完全禁用SELinux机制 |
生产环境我建议保持Enforcing,然后通过日志精准找问题,而不是一刀切关掉。你可以用下面命令快速了解当前状态:
getenforce sestatus如果输出Enforcing,说明SELinux正处于"执法"状态,连接问题完全有可能是它造成的。
2.2 用audit日志定位真实拦截原因
SELinux有没有拦,不是靠猜的,去/var/log/audit/audit.log里翻记录就行。最常用的命令是:
ausearch -m avc -ts recentavc是SELinux访问向量缓存的缩写,所有被拒绝的访问都会在这里留下AVC消息。-ts recent表示只看最近一段时间,避免日志太多刷屏。
我当时执行后看到类似这样的输出:
type=AVC msg=audit(1711345678.123:456): avc: denied { name_connect } for pid=1234 comm="crm-server" dest=8080 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:system_r:port_t:s0 tclass=tcp_socket这里的关键词是denied、name_connect、dest=8080,翻译成人话就是:crm-server进程被SELinux拦住了,因为它想连接TCP 8080端口,但策略里没有放行这个端口给当前进程类型。
如果你装了setroubleshoot,还能用sealert直接看可读性更强的解释:
sealert -a /var/log/audit/audit.log它会给出建议命令,比如:
semanage port -a -t http_port_t -p tcp 8080意思是把8080端口关联到http_port_t类型,这样运行在httpd上下文下的进程就能合法使用这个端口了。
2.3 怎么判断是不是SELinux在干扰
判断方法其实很简单,临时切换到Permissive模式测试一下就行,但千万注意顺序:
setenforce 0 # 现在试着从外部客户端连接一下 # 如果能通,说明SELinux确实在拦截 # 测试完马上恢复 setenforce 1这里有个安全细节必须强调:setenforce 0只是临时生效,重启机器后还会回到配置文件里的状态。如果改了/etc/selinux/config里的SELINUX=disabled,那是永久关闭,而且从Disabled切回Enforcing通常需要重启才能生效。我见过不少人在云服务器上调这个,结果重启后机器直接起不来了,原因就是SELinux上下文对系统文件的影响被忽略。
另一个判断办法是看dmesg或者/var/log/messages里有没有SELinux的拦截记录。还有一点值得注意:如果程序是运行在自己写的脚本里,脚本调用的子进程是否继承SELinux上下文也要考虑。有时候父进程是放行的,子进程换了域,照样会被拦。
3. 防火墙这层也别忽略
3.1 firewalld和iptables到底是什么关系
再来说防火墙。很多Linux服务器默认用的是firewalld,但它并不是独立于iptables的东西。简单理解,firewalld是前端管理工具,底层通过nftables或iptables来真正应用规则。所以如果你只查看了firewall-cmd --list-all,有可能遗漏了直接以iptables/nftables形式存在的规则。
这里给一个容易混淆的概念:即使你systemctl stop firewalld,如果服务器上还跑着独立的iptables服务,或者你用云平台自带的安全组,连接照样会被拦。所以排查连接问题时,一定要把"所有能过滤网络的地方"都过一遍。
我在实际排查时,是同时看三样东西:
systemctl status firewalld iptables -L -n -v --line-numbers nft list ruleset有些服务器换了nftables,但命令集合还是不完整的,iptables -L可能显示空,这时一定要用nft list ruleset看看真实规则。我们这次问题里,firewalld的规则倒还好,但有个zone配置把新端口漏掉了,导致外部访问总是被drop,这属于典型的"看起来放行了,实际上没进对zone"。
3.2 别把"运行时"和"永久"搞混
firewalld里面最坑的就是运行时配置和永久配置分离。你得记住一个铁律:不加--permanent的规则只对当前运行状态生效,防火墙一重启就没了;加了--permanent的规则只存在于配置文件里,必须执行firewall-cmd --reload才会加载到运行时。
# 临时添加端口 firewall-cmd --add-port=8080/tcp # 永久添加端口 firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --reload # 查看运行时规则 firewall-cmd --list-all # 查看永久规则 firewall-cmd --permanent --list-all我当时犯过一个很经典的错:临时加了端口,验证通了,然后直接关掉终端,结果第二天业务又连不上。后来才知道运行时规则在firewalld重启后就会消失,必须加--permanent再reload一次才行。
另外要特别注意,firewall-cmd --list-all默认显示的只是当前zone的规则。如果你的网卡被分到了另一个zone,比如public,你在默认zone里放行是没用的。
3.3 还有这些"隐形防火墙":nftables、iptables、云安全组
除了firewalld,还得注意云安全组。现在很多Linux服务器跑在云平台上,云控制台上的安全组规则是独立于操作系统之外的。如果你本机firewalld已经放行了8080,但云安全组没放行,外部照样连不上。我在排查时经常遇到客户说"我防火墙都关了怎么还是不通",一查,安全组压根没开端口。
再比如说ping不通的问题,很多云服务器默认安全组是不放行ICMP的,你在操作系统里把firewalld折腾烂了也没用。所以遇到问题时,排查顺序最好是这样:
- 先看云安全组(如果有)
- 再看操作系统firewalld
- 再看iptables/nftables残留规则
- 最后看SELinux
这样的顺序能少走不少弯路。我们这个问题在防火墙层面做了两件事:一是把业务端口加到正确的zone,二是确保--permanent永久生效,并且把iptables里残留的旧规则清理掉。
4. 完整解决流程与复盘
4.1 从SELinux到防火墙的完整操作步骤
为了便于大家直接参考,我把当时的解决流程完整列一下,前面几个步骤是诊断,后面是修改。
第一步,查看SELinux状态和AVC日志:
getenforce ausearch -m avc -ts recent发现denied消息后,查看具体上下文和端口:
semanage port -l | grep http_port_t这里可以看到当前SELinux放行的端口列表。如果8080不在列表里,就要添加:
semanage port -a -t http_port_t -p tcp 8080如果你的服务进程类型不是httpd_t,而是自定义的samba_t、postgresql_t之类,就按对应的类型加端口。一次性加多个端口也支持,用-p tcp后跟端口范围,比如8080-8089。
第二步,查看firewalld当前状态和zone:
systemctl status firewalld firewall-cmd --get-active-zones firewall-cmd --zone=public --list-all如果网卡在publiczone,就在public里放行端口:
firewall-cmd --zone=public --add-port=8080/tcp firewall-cmd --zone=public --permanent --add-port=8080/tcp firewall-cmd --reload注意第一条临时规则其实可以省掉,直接永久添加再reload就行。但为了快速测试,我先用临时规则验证,确认能连后再做永久配置,这样避免误写永久规则。
第三步,查看iptables和nftables里有没有残留规则:
iptables -L -n -v --line-numbers nft list ruleset如果有旧的DROP规则正好匹配业务端口,需要删除。比如iptables里有一条:
DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080对应规则编号如果是5,可以用:
iptables -D INPUT 5但请注意,直接用iptables -D删除的规则在重启后也可能因为服务恢复而回来,所以最好找到规则来源,比如/etc/sysconfig/iptables或firewalld的direct规则,从源头改掉。
到这里,SELinux和防火墙两层都放行后,外部客户端就能正常连接了。但别着急,真正的坑在后面。
4.2 有些坑必须拿出来说
第一个坑:SELinux的semanage port添加后,旧进程可能还要重启才生效。因为进程在启动时缓存了安全上下文,运行时再放行端口,部分场景不会立即生效。我当时改完端口后,业务依然连不上,后来重启了对应服务进程才恢复。你可以用systemctl restart 服务名来重启,不要盲目重启整台机器。
第二个坑:setenforce 0只是临时测试,千万别当解决方案。我们这次问题如果用setenforce 0确实也能暂时解决,但一旦重启,SELinux恢复Enforcing,问题又会出现。而且长期Permissive会让系统失去防护能力,对生产环境来说很不负责。
第三个坑:firewalld添加端口后,用telnet验证时要注意"通"和"被拒绝"的区别。如果端口没放行,通常表现是连接超时(数据包被drop);如果端口放行了但服务没监听,表现是立即拒绝。还有一种情况是端口放行了,但服务监听在127.0.0.1,外部连过去同样拒绝,但你在本机测却正常。这个我之前踩过一次,后面养成了看ss -lntp的习惯。
第四个坑:同一台服务器上可能存在多个防火墙管理工具。比如有些发行版预装了firewalld,同时还有一个独立的nftables服务,或者系统脚本里直接用iptables命令添加规则。你以为只查了一个,其实还有另一个在拦。所以iptables -L和nft list ruleset最好都跑一遍。
4.3 快速排查清单
这里整理成一个速查表,以后遇到类似问题可以直接对着看。
| 现象 | 可能原因 | 检查命令 | 解决方法 |
|---|---|---|---|
| 本机正常,跨网段连接超时 | firewalld未放行 | firewall-cmd --list-all | 添加端口或服务 |
| 连接被拒绝(拒绝比超时更快) | 服务未监听/监听地址不对 | ss -lntp | 修改监听地址到0.0.0.0 |
| 端口已放行但持续超时 | 云安全组或iptables拦截 | 云控制台/iptables -L -n | 安全组放行/删除规则 |
| 服务启动失败但日志无异常 | SELinux拒绝端口或文件访问 | ausearch -m avc -ts recent | semanage放行端口或上下文 |
| ping不通但端口能通 | ICMP被拦 | firewall-cmd --list-all检查icmp服务 | firewall-cmd添加icmp规则 |
| 重启防火墙后规则丢失 | 只加了运行时规则 | firewall-cmd --permanent --list-all | 永久规则再加reload |
这套清单其实覆盖了90%的Linux连接故障。所谓"连接问题",很大程度就是过滤规则和监听配置两层的事情,端口开没开、规则对不对、服务听没听,够用。
5.1 高频坑位提醒
整理了三个高频坑,尤其是新手特别容易踩。
第一,关于ping不通。ping走的是ICMP协议,firewalld默认情况下是不放行ICMP回显的。如果你需要外部能ping通服务器:
firewall-cmd --permanent --add-service=icmp firewall-cmd --reload注意这个操作要小心,放行ICMP意味着服务器可以被探测到,如果安全要求高,建议只在内网网络环境放行。
第二,关于firewall-cmd --runtime-to-permanent。有时候你已经加了一大堆临时规则,一条条重新加永久规则太累,可以用这条命令把当前运行时全部规则直接转成永久:
firewall-cmd --runtime-to-permanent但它也会把临时的不想要规则一起转永久,所以执行前最好先--list-all看一眼。
第三,关于SELinux上下文导致的服务无权限读取文件。连接问题除了端口,还有可能是服务进程启动时读不到配置文件或证书文件。这些文件如果被打包搬了位置,类型标签可能不对,这时候用ls -Z看看:
ls -Z /opt/crm/config/app.conf如果类型不对,可以用restorecon恢复默认上下文,或者用chcon手工改类型。最推荐restorecon -Rv /opt/crm,简单粗暴又不会改坏。
5.2 我的实际操作体会
这次问题从定位到解决大概花了两个小时,中间很大一部分时间浪费在反复验证SELinux和防火墙的交互上。说几个个人心得。
第一,日志永远比猜靠谱。不管是SELinux的AVC日志还是防火墙的日志,真实记录能告诉你具体被谁拦了。我甚至在/var/log/firewalld里看到过规则命中计数,配合iptables -L -v里的计数器,一眼就能看出某个规则是否真的有流量命中。
第二,验证一个修改,一定要从外部视角来测。本地curl通、本机telnet通,不代表客户端能通。最好从另一台机器telnet 目标IP 端口来验证,并且用nc -vz代替telnet效果更好,因为telnet在有些环境下没装,nc可以单独测试端口是否开放。
第三,不要害怕SELinux,它其实挺讲道理。你把服务该有的访问权限配好,它比防火墙还要稳定。我现在的习惯是,新装服务优先考虑把SELinux规则配合好,而不是直接setenforce 0。这样即使以后重启机器,服务也能自动恢复,不会出现早上到公司发现业务又挂了这种尴尬。
总的来说,这个问题回顾下来,核心其实就八个字:先看监听,再看过滤。SELinux和防火墙只是过滤的两层具体实现。只要戴好这个视角,遇到连接异常就不会慌了。后面后面如果再遇到类似问题,我会想想是不是还有第三层第四层过滤在拦截,别让经验变成了惯性。