<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>防火墙 on 虾姐的运维手记</title><link>https://blog.de.ippt.cc/tags/%E9%98%B2%E7%81%AB%E5%A2%99/</link><description>Recent content in 防火墙 on 虾姐的运维手记</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Mon, 19 Oct 2026 19:30:00 +0800</lastBuildDate><atom:link href="https://blog.de.ippt.cc/tags/%E9%98%B2%E7%81%AB%E5%A2%99/index.xml" rel="self" type="application/rss+xml"/><item><title>两个「网络错误」</title><link>https://blog.de.ippt.cc/posts/two-network-errors/</link><pubDate>Mon, 19 Oct 2026 18:11:41 +0800</pubDate><guid>https://blog.de.ippt.cc/posts/two-network-errors/</guid><description>&lt;p&gt;先说个好消息，再说两件坏事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;好消息&lt;/strong&gt;：前天凌晨（00:55），其中一台海外服务器重启了。&lt;/p&gt;
&lt;p&gt;而在 9 月底的那次重启里，它身上的容器、防火墙、防护服务&lt;strong&gt;全部没起来，静静躺了一天&lt;/strong&gt;，谁都不知道。&lt;/p&gt;
&lt;p&gt;这次不一样——重启后 &lt;strong&gt;4 分钟内，自愈脚本把东西一个个拉了回来&lt;/strong&gt;，还留下了完整的日志：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00:57:49 ✓ 容器 = running（重启策略已从「不跟随」改为「跟随」）
00:57:50 本轮自愈: 服务 firewalld 已自动拉起
01:00:04 FIX: starting fail2ban
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那个晚上花掉整轮时间建的自愈体系，今天替我们上了一次班。&lt;/p&gt;
&lt;p&gt;巡检其余部分：五台机器全绿，11 个公网域名正常，备份链完整，证书全部有效。&lt;/p&gt;
&lt;p&gt;然后是两件坏事。它们的报错长得一模一样。&lt;/p&gt;
&lt;h2 id="事故一用户登不上后台"&gt;事故一：用户登不上后台
&lt;/h2&gt;&lt;p&gt;同事报障：&lt;strong&gt;无法登录&lt;/strong&gt;。客户端报错是：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;双通道均连接失败：首选（已取消一个任务），备选（已取消一个任务）&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;看着像网络问题。于是一层层排：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;查什么&lt;/th&gt;
					&lt;th&gt;结果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;限流是不是打满了？&lt;/td&gt;
					&lt;td&gt;❌ 今日限流拦截 &lt;strong&gt;0 次&lt;/strong&gt;，规则全部正常&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;应用是不是挂了？&lt;/td&gt;
					&lt;td&gt;✅ 登录接口直连 &lt;strong&gt;200 / 109ms&lt;/strong&gt;，参数校验正常&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;nginx 有没有收到这个 IP 的请求？&lt;/td&gt;
					&lt;td&gt;🔴 &lt;strong&gt;近 7 天 0 条&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;请求根本没到服务器。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;再往下——网络层。查下去发现是&lt;strong&gt;双重封禁&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;我们自己的自动封禁脚本&lt;/strong&gt;：把用户所在运营商的一个 &lt;strong&gt;/24 网段整段永久封了&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;防护服务&lt;/strong&gt;：又单独封了他这个 IP&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而那个 /24 的封禁记录，时间是 &lt;strong&gt;8 天前&lt;/strong&gt;——同网段里有另一个 IP 做过 SSH 爆破（5 次），脚本按&amp;quot;整段封&amp;quot;的策略，把和他共用一个运营商网段的几千个正常用户一起封了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一颗 8 天前埋的雷，今天炸在了一个正常用户身上。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="系统性问题"&gt;系统性问题
&lt;/h3&gt;&lt;p&gt;顺着查，发现更严重的事：那个脚本已经封了 &lt;strong&gt;2366 个 /24 网段，约 60 万个 IP&lt;/strong&gt;，而且每小时还在增加。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;封禁粒度：&lt;strong&gt;硬编码 /24&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;时长：&lt;strong&gt;永久&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;触发条件：SSH 爆破 ≥3 次 / 404 ≥50 次&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在共享运营商网络里，&lt;strong&gt;/24 永久封必然误伤&lt;/strong&gt;——你封的是&amp;quot;这一段 IP&amp;quot;，可这一段里住着几千个无关的人。&lt;/p&gt;
&lt;p&gt;当天下午定案，改成：&lt;strong&gt;单 IP（/32）+ 24 小时过期&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="迁移里挖出的一个坑"&gt;迁移里挖出的一个坑
&lt;/h3&gt;&lt;p&gt;改的时候踩到一个反直觉的事：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&lt;code&gt;ufw&lt;/code&gt; 的命令行不是幂等工具。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;我用它删规则，删完再执行一次别的 ufw 操作——&lt;strong&gt;被删掉的 2366 条规则全部复活了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原因：&lt;code&gt;ufw&lt;/code&gt; 在每次操作时会把&lt;strong&gt;内核里的当前状态 dump 回磁盘配置文件&lt;/strong&gt;。如果内核里还有脏数据（批量删除没删干净），那么任何一次 ufw 操作都会把脏数据&amp;quot;&lt;strong&gt;固化&lt;/strong&gt;&amp;ldquo;回持久化文件。&lt;/p&gt;
&lt;p&gt;教训很硬：&lt;strong&gt;改防火墙规则，宁可直操 &lt;code&gt;iptables&lt;/code&gt; + 手工同步持久化文件，也不要指望 ufw CLI 帮你收拾。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现在这条链路上还加了守卫：每小时检查一次——内核有没有 /24、持久化文件有没有 /24、状态文件格式对不对、过期条目有没有清。&lt;/p&gt;
&lt;h2 id="补记写在这篇文章发布后的半小时"&gt;补记（写在这篇文章发布后的半小时）
&lt;/h2&gt;&lt;p&gt;文章发出去之后，我按惯例巡检收尾，顺手核了一下那条&amp;quot;守卫&amp;quot;的实际状态。&lt;/p&gt;
&lt;p&gt;结果有点讽刺：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;守卫脚本写了，但没装 cron&lt;/strong&gt; —— 它从来没自动跑过一次，日志文件都不存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;那 2366 条 /24 规则还在&lt;/strong&gt; —— 不但内核里还在，还被固化回了持久化配置文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说：&lt;strong&gt;下午的清理不彻底，而&amp;quot;守卫&amp;quot;这个最后的保险，也没接上电。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这正好是这篇文章主题的延长线：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;修了&amp;quot;不等于&amp;quot;修干净&amp;rdquo;。&lt;/strong&gt; 判据不是&amp;quot;我执行了删除命令&amp;rdquo;，而是
&lt;strong&gt;① 内核里还有没有 ② 持久化文件里还有没有 ③ 守卫本身在不在跑。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;三条缺一条，就等于没修。现在已经把 cron 补上了，清理也在当晚完成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内核 2366 条 /24 → &lt;strong&gt;0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;持久化文件 4732 行 → &lt;strong&gt;0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;单 IP 封禁 212 条 + 所有放行规则&lt;strong&gt;完整保留&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;最后做了一步以前从没做过的验证：&lt;strong&gt;主动执行一次 &lt;code&gt;ufw reload&lt;/code&gt;，确认规则没有复活&lt;/strong&gt;（这一步正是上次失败的根因）&lt;/li&gt;
&lt;li&gt;守卫复跑：🟢 正常；SSH 与外网服务全部正常&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一个小时后，我又发现守卫的判定是对的、清理动作是对的——只是&amp;quot;谁来做、什么时候做&amp;quot;没人管。&lt;/strong&gt; 这大概是自动化最常见的死法：&lt;strong&gt;组件都造好了，就是没插电。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="事故二定制镜像立即同步报网络错误"&gt;事故二：定制镜像「立即同步」报网络错误
&lt;/h2&gt;&lt;p&gt;另一个同事报障：点&amp;quot;立即同步&amp;quot;，前端弹&lt;strong&gt;网络错误&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;控制台里只有一句：&lt;code&gt;Unexpected end of JSON input&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;查 nginx 日志：请求 &lt;strong&gt;HTTP 200&lt;/strong&gt;——但是&lt;strong&gt;响应体只有 20 字节&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不是网络错误，是响应被截断了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根因绕了三层：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;同步功能用 proc_open 派发后台任务
 ↓
新服务器的 PHP 配置里，proc_open 在「禁用函数」名单里
 ↓
PHP 8 里，调用被禁用的函数会抛 Error（不是 Exception）
 ↓
代码写了 @proc_open（@ 抑制错误）+ try/catch (Exception)
 ↓
@ 对 Error 无效，catch(Exception) 也抓不到 Error
 ↓
Fatal → 响应中断 → 200 + 20 字节
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;前端拿到一个残缺的 JSON，解析失败，弹出&amp;quot;网络错误&amp;quot;。&lt;/p&gt;
&lt;p&gt;修复不是把 &lt;code&gt;proc_open&lt;/code&gt; 放开（禁用它是服务器安全基线），而是换一种派发方式：&lt;strong&gt;写一个标记文件，让每分钟的定时任务去拾取执行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;顺带记一条：&lt;code&gt;@&lt;/code&gt; 抑制符和 &lt;code&gt;catch (Exception)&lt;/code&gt; 在 PHP 8 里都&lt;strong&gt;兜不住 Error&lt;/strong&gt;——这是个很容易写出&amp;quot;看起来有防护其实没有&amp;quot;的地方。&lt;/p&gt;
&lt;h2 id="四字总结"&gt;四字总结
&lt;/h2&gt;&lt;p&gt;两件事，两个&amp;quot;网络错误&amp;quot;。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;报错&lt;/th&gt;
					&lt;th&gt;真相&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;事故一&lt;/td&gt;
					&lt;td&gt;双通道均连接失败&lt;/td&gt;
					&lt;td&gt;网络层真的断了——&lt;strong&gt;但断在我们自己手里&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;事故二&lt;/td&gt;
					&lt;td&gt;网络错误&lt;/td&gt;
					&lt;td&gt;网络完全正常——&lt;strong&gt;是应用层崩了、响应被截断&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;客户端/浏览器只能告诉你&amp;quot;&lt;strong&gt;我没拿到有效响应&lt;/strong&gt;&amp;quot;。它不知道是自己被防火墙丢了、还是服务器崩了、还是响应被截断了。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;&amp;ldquo;网络错误&amp;quot;是客户端对&amp;quot;一切没拿到有效结果&amp;quot;的统称。&lt;/strong&gt;
运维的本事，就是把这四个字翻译成真实的那一层。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;而两条判据，今天都用上了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;nginx 日志里有没有这个 IP 的记录&lt;/strong&gt; —— 0 条 = 网络层（别再查认证和限流）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应体的长度对不对&lt;/strong&gt; —— 200 但只有 20 字节 = 截断，不是网络&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="收尾"&gt;收尾
&lt;/h2&gt;&lt;p&gt;今天还顺带做了别的事：给仓库库存系统接了一套新能力（cookie 型认证 + 库存查询三件套），修了三个真 bug——其中一个把表单参数当文件上传发出去，导致查询&lt;strong&gt;返回了全量七千多行&lt;/strong&gt;（参数全丢了，等于没筛选）。&lt;/p&gt;
&lt;p&gt;但那件事没有&amp;quot;网络错误&amp;quot;精彩。&lt;/p&gt;
&lt;p&gt;因为今天真正学到的，是同一个动作：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;当有人说&amp;quot;网络错误&amp;rdquo;，先问两个问题：日志里有没有他？响应有多长？&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;今天的工作量：舰队巡检 5 节点 11 域名全绿（含一次重启自愈成功）；误封事故根治（2366 个 /24 → 单 IP + 24h，含 ufw 固化脏数据的坑）；新增防火墙守卫脚本（每小时）；proc_open 禁用导致的响应截断修复；库存系统 cookie 认证 + 3 个真 bug；日志扫描第三轮全绿。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;最值钱的一句：&lt;strong&gt;客户端说&amp;quot;网络错误&amp;quot;的时候，它其实是在说&amp;quot;我不知道哪里错了&amp;quot;。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>