bigywater.tech
网络协议 · 42

RST:哪些情况会收到连接重置

RST 到底在什么时候出现?它的序号是怎么填的,应用侧看到的是 Connection refused、reset by peer 还是 Broken pipe?这篇抓真实的 RST 包验证填法,并把四种常见错误码对应到具体的场景。

seq = 对方的 ack
对带 ACK 的段,RST 的序号取该段的确认号(实测相等);对 SYN 则序号 0、确认号 = SYN + 1
send 成功 1 次
对端 close 之后我方的第一次 send 仍会成功,第二次起 EPIPE:RST 要往返之后才回来
b'hello' 然后 ECONNRESET
服务端发 5 字节再 RST 中止时,客户端的两次 recv 的结果:数据没丢

对 SYN 的 RST

实测 · 本机执行输出
  connect 一个没人监听的端口:ConnectionRefusedError[Errno 61] Connection refused
  SYN:seq=2483746472;回来的包:标志 ACK+RST,seq=0,ack=2483746473 = SYN 的 seq + 1:True——规则:对一个没有连接的 SYN,RST 的序号取 0、确认号取 SYN 序号 + 1(因为 SYN 占一个序号)

连接没人监听的端口:ACK+RST,序号 0,确认号 = SYN 序号 + 1,应用得到 ECONNREFUSED。

对数据的 RST

实测 · 本机执行输出
  应用依次看到:send #1 成功;send #2 BrokenPipeError[Errno 32] Broken pipe;send #3 BrokenPipeError[Errno 32] Broken pipe;recv ConnectionResetError[Errno 54] Connection reset by peer
  线上:客户端:ACK seq=1 ack=1 → 服务端:ACK seq=1 ack=1 → 服务端:ACK+FIN seq=1 ack=1 → 客户端:ACK seq=1 ack=2 → 客户端:ACK+PSH seq=1 ack=2 载荷100 → 服务端:RST seq=2 ack=0
  RST 的序号 3977457150 = 触发它的数据包的确认号 3977457150:True——规则:对一个带 ACK 的段,RST 的序号取该段的确认号,这样发送方才会接受它(落在期望序号上)

连接已不存在时再发数据,对端回 RST,序号取该段的确认号;应用先成功一次 send,之后 EPIPE,recv 得 ECONNRESET。

RST 与数据

实测 · 本机执行输出
  服务端发了 hello 再中止;客户端(RST 到达之后)依次:recv #1 → b'hello';recv #2 ConnectionResetError[Errno 54] Connection reset by peer
  线上:客户端:ACK seq=1 ack=1 → 服务端:ACK seq=1 ack=1 → 服务端:ACK+PSH seq=1 ack=1 载荷5 → 客户端:ACK seq=1 ack=6 → 服务端:ACK+RST seq=6 ack=1
  对照:服务端发了 hello 再正常 close(FIN);客户端依次:recv #1 → b'hello';recv #2 → b''

RST 之前已经到达的数据在这台机器上仍可读,RST 之后才是错误;而 FIN 之后是正常的 EOF。

错误码对照

实测 · 本机执行输出
    连接没人监听的端口                   connect  → ConnectionRefusedError[Errno 61] Connection refused
    对端 RST 中止后                  recv     → ConnectionResetError[Errno 54] Connection reset by peer
    对端 RST 中止后                  send     → BrokenPipeError[Errno 32] Broken pipe
    对端 close 后我方第二次 send        send     → BrokenPipeError[Errno 32] Broken pipe
    SYN 无回应(设了 2 秒超时)           connect  → TimeoutError[Errno None] None
  ECONNREFUSED = 对 SYN 回了 RST;ECONNRESET = 连接已建立后收到 RST;EPIPE = 向已经收到 RST / FIN 的连接再写(默认还会给进程发 SIGPIPE,Python 屏蔽了它);ETIMEDOUT / 超时 = 对方没有任何回应

ECONNREFUSED、ECONNRESET、EPIPE 分别对应对 SYN 的 RST、连接中的 RST、向已关闭的连接写;超时则是对方毫无回应。

分步演示

TCP RST
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部在 lo0 上做,地址为 127.0.0.1;只有最后的“SYN 无回应”一项用了一个会丢弃 SYN 的公网地址(example.com 的 81 端口,2 秒超时);RST 的序号用抓包解析得到
    • 没有验证:RST 的窗口验证(收到序号落在窗口外的 RST 的行为需要注入包,没有权限);防火墙/NAT 主动发 RST 的情形;其它系统在收到 RST 时是否丢弃未读数据;SIGPIPE 的实际默认行为(Python 屏蔽了它)
    京ICP备2026067502号-1京公网安备11010502063487号