一次诡异的 TCP 连接重置排查
一次诡异的 TCP 连接重置排查:握手能成功,一发数据就被 RST
关键词:TCP RST、非对称路由、子网掩码不一致、状态防火墙、conntrack、ICMP Redirect、MikroTik RouterOS、Wireshark 两端抓包
引子:先回答一个问题
把环境抛开,先问一个纯技术问题:
两台主机,一台认为自己和对方在同一个子网、无需经过网关;另一台认为自己不和对方在同一个子网、需要经过网关。会发生什么?
先说明:这种"各算各的"完全合法。"是否直连"是由每台主机按自己的掩码单方面算出来的,两端不需要达成一致——所以谁都不觉得自己配错了,配置界面上一切正常。
多数人的直觉是:"顶多绕一下,路径长一点,能通就行。"
现实给出的答案远比这诡异:
ping 完全正常,TCP 三次握手也能成功建立,但只要发出第一个带业务数据的包,连接立刻被 RST,永远建不起来。
更让人困惑的是:两端主机的防火墙都是关的,服务本身也活得好好的——责任到底在谁?
这是我们前不久在生产环境里遇到的一次真实故障。下面按实际排查顺序还原:现象 → 定位 RST 的归属 → 两端抓包 → 根因 → 原理 → 解决。
一、故障现象
一台 PC(下称 PC-A)访问一台 Windows 服务器(下称 SRV-B)的两个服务:
SRV-B:3000上的 Web 服务SRV-B:3389的远程桌面
具体表现:
- 浏览器/客户端报"连接被重置",重试无限循环,全天不恢复;
- ICMP ping 完全正常;
- TCP 三次握手可以成功建立(抓包里 SYN / SYN-ACK / ACK 齐全);
- 但只要客户端发出带业务数据的包(HTTP
GET、RDP 的 X.224 协商请求),连接立刻被RST打断,反复重试永远失败。
两端主机的 Windows 防火墙都已关闭,服务器上的服务也确认正常启动。这就排除了最直觉的两类怀疑(ACL 拦截、应用故障)。"能 ping、能握手,就是发不了数据"——三个看似矛盾的结论同时成立,正是这类故障最迷惑人的地方。
二、第一步:先搞清"RST 到底是谁发的"
拿到一份抓包后,很多人的第一反应是"看 RST 的源 IP 是谁"——这是不够的,因为 RST 是可伪造的(IP 层源地址随便写)。要判断 RST 的真实来源,必须看下面四个维度:
| 判据 | 说明 |
|---|---|
| 以太网源 MAC | 最有价值的一列。RST 帧的 eth.src 是不是主机真实网卡?还是网关设备? |
| IP TTL | 对端直连应为默认值(Windows=128、Linux=64);少 1 跳说明包经过了路由 |
| TCP 标志位组合 | 内核发的 RST 通常符合规范;网络设备伪造的 RST 常带异常组合(如同时带 PSH) |
| Seq/Ack 是否合理 | 例如 RST 的 Seq 恰好"确认"了刚被丢弃的那个包的字节数 → 它是对那个包生成的 |
实操技巧:在 Wireshark 里加三列自定义列 ——
eth.src、ip.ttl、tcp.flags.str,肉眼扫一遍就能筛掉大部分误判。
本例的第一手线索:RST 的 TTL 与服务端 SYN-ACK 的 TTL 一模一样(都是 127),而同网段直连本应是 128。这说明两个方向都走了路由,路径上有一跳设备。
三、决定性证据:两端同时抓包
单端抓包只能看到"现象",两端同时抓包才能看到"差异"。在 PC-A 和 SRV-B 上同时抓包后(地址已匿名化):
| 角色 | 地址 | 抓到的关键包 |
|---|---|---|
| PC-A | 10.10.103.37/23 |
SYN、SYN-ACK、ACK、请求、重传、最后收到 RST |
| SRV-B | 10.10.102.200/24 |
SYN、SYN-ACK、请求、响应、被伪造的 RST、最终 RST |
| 网关 | 10.10.102.1 |
持续发出 ICMP Redirect |
先看清这组地址为什么是关键(本例的整个前提就在这两个掩码上):
- PC-A 是
/23,子网为10.10.102.0/23(覆盖10.10.102.1 ~ 10.10.103.254),它把10.10.102.200判定为同网段 → 直接 ARP、二层送达,不经过网关;- SRV-B 是
/24,子网为10.10.102.0/24,它认为10.10.103.37是异网段 → 所有回包交给默认网关10.10.102.1。两端掩码不一致,各自按自己的掩码做判断,于是同一条会话的两个方向走了不同的路——这就是非对称路由。
对比结论:
- PC-A 发出的所有包都是二层直连(以太网源 = PC-A 网卡,目的 = SRV-B 网卡);
- SRV-B 发出的所有回包都交给了网关(以太网目的 = 网关 MAC);
- SRV-B 明确发出了响应报文(RDP
Negotiate Response),但 PC-A 侧从未收到——被网关吞掉了; - 紧接着 SRV-B 收到一个 RST:
tcp.srcport = 客户端端口、tcp.dstport = 3389、Seq恰好确认了那个被吞掉的响应包; - 但 PC-A 侧抓包里,PC-A 从未发送过这个 RST;
- 该 RST 帧的以太网源 MAC 是网关的,而不是 PC-A 的网卡。
结论:这个 RST 是网关伪造的(源 IP/端口伪装成客户端)。这是 reject-with=tcp-reset 这类行为的标志性特征:被拒绝的包不回,反而把 RST 按"反向地址"发回给原发送方。
至此完整的死亡时序还原(以 RDP 为例):
客户端 SYN ──── 二层直连 ────► 服务端 (网关看不到)
客户端 ◄──── 经网关 ──── SYN,ACK (网关据此建了个半开表项)
客户端 ACK / Negotiate Request ── 二层直连 ────► (网关看不到,表项永远到不了 established)
服务端 Negotiate Response ─────► 网关:状态不符,丢弃 ✗
服务端 ◄──── 网关伪造的 RST(伪装成客户端) (服务端单方面销毁会话)
客户端 重传 Negotiate Request ──► 服务端 (撞上已销毁的会话)
客户端 ◄──── 服务端内核真实 RST(被网关正常转发) (两端全断)
四、根因:一个掩码写错,引发全链路连环坑
现在可以回答引子里的那个问题了。答案是:两端各算各的,谁都没算错,但同一条会话的两个方向走上了两条不同的路。
PC-A 掩码 /23 → 认为 SRV-B 同网段 → 直接二层发包(绕过网关)
SRV-B 掩码 /24 → 认为 PC-A 异网段 → 所有回包发给默认网关
↓
流量路径不对称(非对称路由)
↓
网关只能看到"服务端 → 客户端"的单向流量
↓
状态防火墙连接跟踪里的会话永远是"半开"状态
↓
① 丢弃服务端带业务数据的响应
② 用 tcp-reset 拒绝,并伪造 RST 打回服务端
↓
客户端收不到响应 → 无限重传;服务端收到伪 RST → 断链
是的,整条链路的所有异常,源头只是"两台机器的子网掩码不一致"。
五、原理追问:四个"为什么"
1. 为什么 ping 能通?
ICMP 是无连接协议,不参与 TCP 状态机。路由器只需查路由表转发,不检查会话状态。所以路径虽不对称,也没有任何设备会拦它。
2. 为什么三次握手能成功?
状态防火墙的会话表就是靠 SYN 建立的。SYN / SYN-ACK 在状态机里是"连接的合法开端",任何厂商的策略都必须放行,否则所有连接都建不起来。
本例的微妙之处:网关只看到服务端的 SYN-ACK(回程经它),于是建立了一个半开(SYN_RECV)表项——表项建起来了,所以 SYN-ACK 能过。
3. 为什么带业务数据的包被 RST?
因为客户端的 ACK 和请求全部走了二层直连,网关看不到,该表项永远无法迁移到 established。当服务端发出带 payload 的响应时,网关看到的是"一条还没建立的会话里突然出现了数据包" → 判定为 invalid / out-of-state → 命中拒绝规则。
此外还有两重叠加因素:
- 很多设备对"无 SYN 却携带数据"的包有专门防护逻辑(防伪造、防序列号攻击);
- 应用层识别(IPS/DPI)需要 payload 才能做判断。握手阶段没有数据,设备无从下手;一旦出现
GET / HTTP/1.1、X.224 协商这类特征,才命中策略。这正是"RST 总出现在请求发出之后"的原因。
4. 为什么客户端也能收到 RST?为什么这个 RST 反而被放行?
网关只看得见回程流量,它只能朝服务端方向伪造 RST。客户端收到的 RST 其实是服务端内核发出来的,遵循两条 TCP 铁律:
- 无监听端口收到 SYN → 回
RST, ACK(所以 3000 端口在服务未启动时会立刻被拒); - 收到不属于任何已存在连接的数据包 → 回
RST(RFC 793 / RFC 5961 的强制行为,目的是让对端立刻停止重传)。
之所以服务端发的这个 RST 能被网关正常转发、而前面的业务响应包被拦,原因有三:
- 状态防火墙普遍对 RST 特例放行——RST 是连接的"善后包",拦掉它反而会让半开会话堆积,比放行更危险;
- 前一步 reject 后该会话表项已被清理/标记终止,纯 RST 不再匹配"非法数据"规则;
- RST 无 payload、无 ACK 数据,天然不满足那些拦截规则的匹配条件。
这个细节本身就是"设备在做状态检测而非简单 ACL"的有力佐证:静态 ACL 是包级的,不会出现"同一五元组,有的拦有的放"。
5. 那个 ICMP Redirect 是什么信号?
抓包里网关在持续回 ICMP Type 5 / Code 1(Redirect for host)。路由器只在"主机把包发给我,但按我的路由表这个包应该从同一链路直接送达"时才发它。
所以它是症状物证,不是病因:它直接证明了"SRV-B 正在把本该二层直连的流量绕给网关",同时反证网关认为两台主机本就在同一网段(即规划就是 /23)。
有趣的是:Windows 默认忽略 ICMP Redirect(EnableICMPRedirect=0),所以 SRV-B 收到后照旧走网关,故障现象分毫未变。反过来说,如果 SRV-B 是 Linux(默认接受重定向),它很可能"听劝"改成直连发送,这个故障根本不会出现。这也解释了为什么这类配置错误在 Windows/Linux 混用环境里特别容易暴露。
六、解决方案
根治只有一条路:消除非对称路由。
- 统一两端子网掩码(首选)
- 若两个网段本属同一广播域(本例即如此),把配置错误的一端改回正确掩码;
- 若网络规划本就是两个独立子网,则必须保证双向流量都正常经网关三层转发。
- 网关侧整改(如果流量确实需要过网关)
建议把对/ip firewall filter print stats # 定位是哪些规则在拒绝(计数会持续增长) /ip firewall filter print detail # 找 action=reject reject-with=tcp-reset 及状态相关规则 /ip firewall connection print # 观察会话是否长期停留在半开状态 /ip settings set send-redirects=no # 关闭 ICMP Redirect(安全基线要求)invalid状态流量的处理从reject (tcp-reset)改为drop——伪造 RST 会同时干扰两端主机的连接状态,比静默丢弃更难排查。
改完掩码后验证(重新两端抓包):
- 双向报文的以太网源/目的都是两端网卡,不再出现网关 MAC;
- 不再出现
ICMP Redirect; - 无来源不明的 RST;
- HTTP 一次成型(
GET→200 OK),RDP 协商一次成型。
七、经验沉淀(可以直接抄进排查手册)
诊断方法
- ping 通 ≠ 端口通,握手成功 ≠ 会话可用。 三者的判定依据完全不同:路由表 / 状态机 / 会话状态。
- 判断 RST 归属必须看以太网源 MAC,不能只看 IP。伪造 RST 的 IP 源地址是随便写的。
- 两端同时抓包是终结争议的唯一手段:单端只能看到现象,两端对比才能发现"发送方发了、接收方没收到"。
- 在抓包里永远打开
eth.src/ip.ttl/tcp.flags.str这几列,它们比 IP 地址更能说明问题。 ICMP Redirect是路由配置错误的警报器:一旦出现,先怀疑掩码/路由配置。- TTL 是最便宜的路径探测器:同网段直连不该出现 TTL 减 1。
常见命令
# Windows:判断 IP 是 DHCP 下发还是手工配置(PrefixOrigin 才是权威字段)
Get-NetIPAddress -AddressFamily IPv4 |
Select-Object InterfaceAlias, IPAddress, PrefixLength, PrefixOrigin
route print # 看目标网段是 on-link 还是走网关
arp -a # 看 ARP/邻居表是否被网关代答
Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters -Name EnableICMPRedirect
# Wireshark 常用过滤
tcp.flags.reset == 1 # 所有 RST
tcp.port == 3389 && ip.addr == 10.10.102.200 # 锁定目标会话
icmp.type == 5 # 只看重定向
tcp.analysis.retransmission # 只看重传(判断是否"有去无回")
预防
- 变更 IP 配置时必须同时核对掩码与网关,不要只核 IP 本身;
- 批量配置优先走 DHCP,并核对作用域掩码(option 1)是否与规划一致;
- 网络规划变更(比如把两个 /24 合并成 /23)后,务必巡检存量主机的掩码是否全部更新到位——这类历史遗留配置不一致往往潜伏很久,直到某个应用恰好在"发数据"时才暴露。
八、延伸思考:能不能靠"关掉状态检测"绕过这个问题?
不能。两个理由。
1. 现实中绝大多数路由器都做状态检测
决定因素不是"它叫不叫路由器",而是它上面配了什么:
| 设备/配置形态 | 是否有状态 | 非对称路由下的表现 |
|---|---|---|
| 纯三层转发(未配 ACL/NAT/防火墙) | 无 | 正常转发,本故障不出现 |
| 无状态 ACL | 无(包级匹配) | 只有显式 deny + reset 才会拦 |
| NAT / PAT 设备 | 必须做(否则回程无法反向转换) | 会话不完整 → 丢包、连不上 |
| 状态防火墙(SPI / conntrack) | 有 | 本案例:判 invalid → 丢弃或拒绝 |
| uRPF / rp-filter 源地址校验 | 无状态,但有路径校验 | 静默丢包(比 RST 更隐蔽) |
| IPS / 应用识别 / 上网行为管理 | 有 + 深度检测 | 命中应用特征后重置 |
家用无线路由器、企业出口设备几乎都带 NAT 或防火墙,所以实际上几乎找不到"真的不做状态检测"的路由器。真正的纯转发只出现在核心交换机的三层接口、或未配置任何 ACL 的路由接口上。
2. 就算真的不检测,非对称路由照样坑你
只是症状换了一副面孔:
- uRPF(严格反向路径校验)直接静默丢包——没有任何 RST,只有"有去无回";
- NAT 只能转换一半——回程找不到映射表项,直接连不上;
- 有状态集群(防火墙/负载均衡双机 HA)会话表同步不上;
- 旁路审计、流量分析设备只能看到半条流——报表和日志失真(对安全审计要求高的场景,这本身就是问题)。
所以"关掉状态检测"不是解决方案,而是把干净利落的 RST 换成更隐蔽的丢包,让排查难度再上一个台阶。
3. 一个有意思的推论:同一种根因,四种完全不同的症状
这是因为设备对"异常状态流量"的处置动作是可配置的:
| 网关对 invalid 流量的处置 | 用户看到的现象 | 常见误诊方向 |
|---|---|---|
drop(静默丢弃) |
无响应、无限重传 | "服务卡死了""网络不通" |
reject(ICMP) |
报"目标主机不可达" | "路由配错了" |
reject(tcp-reset) |
连接被 RST | "服务端应用有问题"(本案例) |
| 不检查状态 | 通常能正常访问 | —— |
同一个根因(非对称路由),四种截然不同的"病例"。 这也是这类问题特别容易被误诊、并反复在应用侧"排查"的原因。
临时验证技巧(RouterOS):可以对怀疑的流量下发
notrack让它跳过连接跟踪:/ip firewall raw add chain=prerouting src-address=<客户端> dst-address=<服务端> \ protocol=tcp action=notrack如果加上后立刻恢复正常,就坐实了"是 conntrack 状态判定导致的"。仅用于验证,不要长期保留(该流量会同时失去状态防护;且若涉及 NAT 会直接失效)。
最终结论:不要试图通过降低中间设备的检测强度来治病。唯一正确的做法始终是消除非对称路由,让双向流量路径对称——症状会随配置变化,根因不会。
(发布前备注,发布时删除:文中 IP 已替换为 RFC 1918 私有地址(示例按 10.10.102.0/23 规划:客户端 /23、服务端 /24,两者掩码不一致的关系与原环境完全一致,读者可自行验算);MAC 仅保留厂商 OUI、主机位已遮蔽;设备类型与端口为真实场景,可保留。若需补充"实际处置动作"或现场截图,可在第六节后追加。)
浙公网安备 33010602011771号