HRR 是什么
HelloRetryRequest(HRR)是 TLS 1.3 的协商重试机制。服务端读第一个 ClientHello 时,如果其中的 key_share 列表没有自己偏好的密钥交换组(例如客户端只带 x25519,而服务端策略要求 P-256),就回一条 HRR 指定要用的组,客户端据此再发一次 ClientHello。第二次 ClientHello 必须携带服务端指定的组,并复用 HRR 中的随机数置换值与 cookie,否则握手失败。代价是多一次往返,跨地域链路上会直接体现为首字节时间增加。
基本信息
| 触发条件 | 首个 ClientHello 的 key_share 与服务端偏好组不匹配 |
|---|---|
| 代价 | 握手多一个往返,1-RTT 变 2-RTT |
| 协议要求 | 第二次 ClientHello 使用 HRR 的随机数置换,并回带 cookie |
| 观察方式 | 抓包看同一连接是否出现两个 ClientHello,或用 s_client -msg |
| 性质 | 正常协商路径,不是错误 |
| 版本 | 仅 TLS 1.3 存在该机制 |
典型使用场景
- 建连延迟优化:发现大量连接触发 HRR 时,统一双方的密钥交换组配置。
- 跨地域接入排查:海外用户首包慢,先确认是否被 HRR 多花了一个往返。
- 客户端库评估:不同版本浏览器与 SDK 默认发送的 key_share 数量差别很大。
- 中间设备兼容排查:设备改写 ClientHello 可能导致 HRR 后协商失败。
- 服务端配置审计:只启用单一曲线的策略会放大 HRR 比例。
观察 HRR 是否发生
观察 HRR 是否发生
openssl s_client -connect example.com:443 -servername example.com -msg </dev/null 2>&1 | grep -ci 'hello retry'
# 抓包统计每个连接的 ClientHello 条数,大于 1 说明发生了 HRR
# tshark -r tls.pcap -Y 'tls.handshake.type == 1' -T fields -e tcp.stream | sort -n | uniq -c
# 服务端侧可用 SSLKEYLOGFILE + Wireshark 解密查看 key_share 协商细节现象与处理
| 现象 | 说明 | 处理 |
|---|---|---|
| 同一连接出现两个 ClientHello | 首个未带服务端偏好的组 | 客户端预置更多组,或服务端接受 x25519 |
| 握手成功但建连耗时增加 | 多一次往返,RTT 越大越明显 | 跨地域访问统一到 x25519 等通用组 |
| HRR 之后握手失败 | cookie 未回带,或第二次仍未匹配 | 核对客户端库版本,排查中间设备改写 |
| HRR 比例接近 100% | 双方组配置互不兼容 | 按服务端支持的组清单收敛客户端配置 |
少量 HRR 是正常现象,不需要处理;只有在高延迟链路上比例明显偏高、用户可感知时才值得统一密钥交换组配置。