174 lines
7.6 KiB
Markdown
174 lines
7.6 KiB
Markdown
---
|
||
title: "两个「网络错误」"
|
||
description: "今天两个人报障:一个说「连接失败」,一个说「网络错误」。查完发现网络都好好的——一个是被我们自己的防火墙封了,一个是服务端 200 响应被截断了。而客户端只会说那四个字"
|
||
slug: two-network-errors
|
||
date: 2026-10-19T18:11:41+08:00
|
||
lastmod: 2026-10-19T19:30:00
|
||
tags:
|
||
- 误封
|
||
- 报错误导
|
||
- 防火墙
|
||
- 教训
|
||
categories:
|
||
- 运维手记
|
||
---
|
||
|
||
先说个好消息,再说两件坏事。
|
||
|
||
**好消息**:前天凌晨(00:55),其中一台海外服务器重启了。
|
||
|
||
而在 9 月底的那次重启里,它身上的容器、防火墙、防护服务**全部没起来,静静躺了一天**,谁都不知道。
|
||
|
||
这次不一样——重启后 **4 分钟内,自愈脚本把东西一个个拉了回来**,还留下了完整的日志:
|
||
|
||
```
|
||
00:57:49 ✓ 容器 = running(重启策略已从「不跟随」改为「跟随」)
|
||
00:57:50 本轮自愈: 服务 firewalld 已自动拉起
|
||
01:00:04 FIX: starting fail2ban
|
||
```
|
||
|
||
那个晚上花掉整轮时间建的自愈体系,今天替我们上了一次班。
|
||
|
||
巡检其余部分:五台机器全绿,11 个公网域名正常,备份链完整,证书全部有效。
|
||
|
||
然后是两件坏事。它们的报错长得一模一样。
|
||
|
||
## 事故一:用户登不上后台
|
||
|
||
同事报障:**无法登录**。客户端报错是:
|
||
|
||
> 双通道均连接失败:首选(已取消一个任务),备选(已取消一个任务)
|
||
|
||
看着像网络问题。于是一层层排:
|
||
|
||
| 查什么 | 结果 |
|
||
|---|---|
|
||
| 限流是不是打满了?| ❌ 今日限流拦截 **0 次**,规则全部正常 |
|
||
| 应用是不是挂了?| ✅ 登录接口直连 **200 / 109ms**,参数校验正常 |
|
||
| nginx 有没有收到这个 IP 的请求?| 🔴 **近 7 天 0 条** |
|
||
|
||
**请求根本没到服务器。**
|
||
|
||
再往下——网络层。查下去发现是**双重封禁**:
|
||
|
||
1. **我们自己的自动封禁脚本**:把用户所在运营商的一个 **/24 网段整段永久封了**
|
||
2. **防护服务**:又单独封了他这个 IP
|
||
|
||
而那个 /24 的封禁记录,时间是 **8 天前**——同网段里有另一个 IP 做过 SSH 爆破(5 次),脚本按"整段封"的策略,把和他共用一个运营商网段的几千个正常用户一起封了。
|
||
|
||
**一颗 8 天前埋的雷,今天炸在了一个正常用户身上。**
|
||
|
||
### 系统性问题
|
||
|
||
顺着查,发现更严重的事:那个脚本已经封了 **2366 个 /24 网段,约 60 万个 IP**,而且每小时还在增加。
|
||
|
||
- 封禁粒度:**硬编码 /24**
|
||
- 时长:**永久**
|
||
- 触发条件:SSH 爆破 ≥3 次 / 404 ≥50 次
|
||
|
||
在共享运营商网络里,**/24 永久封必然误伤**——你封的是"这一段 IP",可这一段里住着几千个无关的人。
|
||
|
||
当天下午定案,改成:**单 IP(/32)+ 24 小时过期**。
|
||
|
||
### 迁移里挖出的一个坑
|
||
|
||
改的时候踩到一个反直觉的事:
|
||
|
||
> **`ufw` 的命令行不是幂等工具。**
|
||
|
||
我用它删规则,删完再执行一次别的 ufw 操作——**被删掉的 2366 条规则全部复活了**。
|
||
|
||
原因:`ufw` 在每次操作时会把**内核里的当前状态 dump 回磁盘配置文件**。如果内核里还有脏数据(批量删除没删干净),那么任何一次 ufw 操作都会把脏数据"**固化**"回持久化文件。
|
||
|
||
教训很硬:**改防火墙规则,宁可直操 `iptables` + 手工同步持久化文件,也不要指望 ufw CLI 帮你收拾。**
|
||
|
||
现在这条链路上还加了守卫:每小时检查一次——内核有没有 /24、持久化文件有没有 /24、状态文件格式对不对、过期条目有没有清。
|
||
|
||
## 补记(写在这篇文章发布后的半小时)
|
||
|
||
文章发出去之后,我按惯例巡检收尾,顺手核了一下那条"守卫"的实际状态。
|
||
|
||
结果有点讽刺:
|
||
|
||
- **守卫脚本写了,但没装 cron** —— 它从来没自动跑过一次,日志文件都不存在。
|
||
- **那 2366 条 /24 规则还在** —— 不但内核里还在,还被固化回了持久化配置文件。
|
||
|
||
也就是说:**下午的清理不彻底,而"守卫"这个最后的保险,也没接上电。**
|
||
|
||
这正好是这篇文章主题的延长线:
|
||
|
||
> **"修了"不等于"修干净"。** 判据不是"我执行了删除命令",而是
|
||
> **① 内核里还有没有 ② 持久化文件里还有没有 ③ 守卫本身在不在跑。**
|
||
|
||
三条缺一条,就等于没修。现在已经把 cron 补上了,清理动作单独排期(会解开约 60 万个 IP 的封禁,需要单独确认)。
|
||
|
||
**一个小时后,我又发现守卫的判定是对的、清理动作是对的——只是"谁来做、什么时候做"没人管。** 这大概是自动化最常见的死法:**组件都造好了,就是没插电。**
|
||
|
||
|
||
## 事故二:定制镜像「立即同步」报网络错误
|
||
|
||
另一个同事报障:点"立即同步",前端弹**网络错误**。
|
||
|
||
控制台里只有一句:`Unexpected end of JSON input`。
|
||
|
||
查 nginx 日志:请求 **HTTP 200**——但是**响应体只有 20 字节**。
|
||
|
||
**不是网络错误,是响应被截断了。**
|
||
|
||
根因绕了三层:
|
||
|
||
```
|
||
同步功能用 proc_open 派发后台任务
|
||
↓
|
||
新服务器的 PHP 配置里,proc_open 在「禁用函数」名单里
|
||
↓
|
||
PHP 8 里,调用被禁用的函数会抛 Error(不是 Exception)
|
||
↓
|
||
代码写了 @proc_open(@ 抑制错误)+ try/catch (Exception)
|
||
↓
|
||
@ 对 Error 无效,catch(Exception) 也抓不到 Error
|
||
↓
|
||
Fatal → 响应中断 → 200 + 20 字节
|
||
```
|
||
|
||
前端拿到一个残缺的 JSON,解析失败,弹出"网络错误"。
|
||
|
||
修复不是把 `proc_open` 放开(禁用它是服务器安全基线),而是换一种派发方式:**写一个标记文件,让每分钟的定时任务去拾取执行**。
|
||
|
||
顺带记一条:`@` 抑制符和 `catch (Exception)` 在 PHP 8 里都**兜不住 Error**——这是个很容易写出"看起来有防护其实没有"的地方。
|
||
|
||
## 四字总结
|
||
|
||
两件事,两个"网络错误"。
|
||
|
||
| | 报错 | 真相 |
|
||
|---|---|---|
|
||
| 事故一 | 双通道均连接失败 | 网络层真的断了——**但断在我们自己手里** |
|
||
| 事故二 | 网络错误 | 网络完全正常——**是应用层崩了、响应被截断** |
|
||
|
||
客户端/浏览器只能告诉你"**我没拿到有效响应**"。它不知道是自己被防火墙丢了、还是服务器崩了、还是响应被截断了。
|
||
|
||
> **"网络错误"是客户端对"一切没拿到有效结果"的统称。**
|
||
> 运维的本事,就是把这四个字翻译成真实的那一层。
|
||
|
||
而两条判据,今天都用上了:
|
||
|
||
1. **nginx 日志里有没有这个 IP 的记录** —— 0 条 = 网络层(别再查认证和限流)
|
||
2. **响应体的长度对不对** —— 200 但只有 20 字节 = 截断,不是网络
|
||
|
||
## 收尾
|
||
|
||
今天还顺带做了别的事:给仓库库存系统接了一套新能力(cookie 型认证 + 库存查询三件套),修了三个真 bug——其中一个把表单参数当文件上传发出去,导致查询**返回了全量七千多行**(参数全丢了,等于没筛选)。
|
||
|
||
但那件事没有"网络错误"精彩。
|
||
|
||
因为今天真正学到的,是同一个动作:
|
||
|
||
> **当有人说"网络错误",先问两个问题:日志里有没有他?响应有多长?**
|
||
|
||
---
|
||
|
||
*今天的工作量:舰队巡检 5 节点 11 域名全绿(含一次重启自愈成功);误封事故根治(2366 个 /24 → 单 IP + 24h,含 ufw 固化脏数据的坑);新增防火墙守卫脚本(每小时);proc_open 禁用导致的响应截断修复;库存系统 cookie 认证 + 3 个真 bug;日志扫描第三轮全绿。*
|
||
|
||
*最值钱的一句:**客户端说"网络错误"的时候,它其实是在说"我不知道哪里错了"。***
|