Rust in Everything
基于 rustls 0.23.46

记录层

TLS 连接上流动的是记录(record):5 字节的头(内容类型、版本、长度)加一段载荷,载荷在密钥就位后是 AEAD 密文。握手消息、告警和应用数据都装在记录里,一条握手消息可以拆进几条记录,几条握手消息也可以挤进一条记录。这一部分讲 rustls 怎么把字节变成记录、把记录变成消息,以及反方向怎么走,也就是记录主线在记录这一层的全部细节:writer().write_all(b"...") 的明文怎么分片、加密、排队,write_tls 交出去的字节在对端怎么经 read_tls 进缓冲、被 process_new_packets 切开、原地解密,再从 reader() 读出来。连接对象和 sans-IO 的调用循环见 sans-IO,同一次握手逐条记录的抓取见一次握手。

代码分布(tag v/0.23.46,.rs 行数,含文件内的单元测试):msgs/codec.rs 404 行、msgs/macros.rs 329 行、msgs/base.rs 258 行、msgs/handshake.rs 3266 行(各种握手消息和扩展);去帧在 msgs/deframer/ 三个文件共 1059 行,消息类型在 msgs/message/ 三个文件共 854 行;record_layer.rs 358 行、crypto/cipher.rs 364 行;发送侧的 msgs/fragmenter.rs 232 行、vecbuf.rs 377 行;把它们串起来的是 conn.rs(1335 行)和 common_state.rs(1136 行)。

  1. 编解码:Reader 加 Codec<'a> 是所有线上结构的读写接口,读出来的值可以借用输入;长度前缀先写 0xff 占位、Drop 时回填;enum_builder! 给每个协议枚举加 Unknown,extension_struct! 把扩展列表变成一个全是 Option 字段的结构,未知扩展在 ClientHello 里丢弃、在 HelloRetryRequest 里报错,重复扩展在解码时就拒绝。
  2. 去帧与缓冲:read_tls 是密文进入 rustls 的唯一一次拷贝,之后切记录、解密、拼接握手消息都在同一块 DeframerVecBuffer 里原地进行;跨记录的握手消息靠 HandshakeDeframer 记下区间、用 copy_within 挪到一起,超过 64 KiB 直接拒绝;应用数据解密后拷进 received_plaintext,后者超过 16 KiB 时 read_tls 报错。
  3. 加密与序列号:RecordLayer 两个方向各一个加解密器和序列号,nonce 是 IV 异或序列号;TLS 1.3 的真实内容类型藏在密文最后一个字节,rustls 发送时不填充、接收时剥掉填充;AES-GCM 一把密钥最多加密 2^24 条记录:TLS 1.3 用最后一个可用的序列号发 KeyUpdate 换密钥,TLS 1.2 到上限时发 close_notify。
  4. 发送路径:MessageFragmenter 按 16 KiB(或 max_fragment_size 减 5)切明文;握手完成前的写入整块缓存在 sendable_plaintext,之后每条记录一个 Vec 进 sendable_tls,write_tls 一次最多交出 64 条;set_buffer_limit 默认 64 KiB,满了 write 返回 Ok(0),write_all 报 WriteZero。
flowchart TB
  w["writer write"] --> frag["MessageFragmenter"]
  frag --> enc["encrypt_outgoing"]
  enc --> tls["sendable_tls"]
  tls --> wt["write_tls"]
  wt --> rt["对端 read_tls"]
  rt --> buf["DeframerVecBuffer"]
  buf --> it["DeframerIter"]
  it --> dec["decrypt_incoming"]
  dec --> hs["HandshakeDeframer"]
  dec --> plain["received_plaintext"]
  hs --> codec["Codec 解析"]
  plain --> rd["reader read"]

四页的实验都在 exp-rustls/rec-lab(M2 Max、macOS 27、rustc 1.99.0,release),连接在内存里握手,不开套接字。各页的「实测」用 ring provider,吞吐对比在发送路径的末尾同时测 ring 与 aws-lc-rs。

💬 关联讨论

发起讨论
加载中...