Skip to content

第 6 章:连接、握手与加密 —— 从 socket 到“属于某个 Torrent 的 Peer”

一个 TCP/µTP 连接刚建立时,只是一条匿名字节流。握手层必须识别 torrent、验证对端、协商扩展与加密,再把连接的所有权交给 Peer 消息层。

一、四层连接栈

层次职责非常清楚:socket 负责传输,Peer I/O 负责可控字节流,Handshake 负责连接身份,Peer Messages 负责长期协议。

二、tr_peer_socket 抹平 TCP 与 µTP

TCP 使用内核 stream socket;µTP 在 UDP 之上提供可靠有序传输。上层不应到处判断协议,因此两者实现共同的 socket 抽象:读、写、关闭、地址、是否 µTP。

Peer Manager 选择 transport 后创建 outgoing socket;Session 收到入站 TCP/µTP 时创建 incoming socket。两者随后都进入 tr_peerIo

三、tr_peerIo 是连接的“字节阀门”

它持有:

  • 输入/输出 buffer;
  • tr_peer_socket
  • info hash 与远端地址;
  • 上下行 tr_bandwidth 节点;
  • MSE 加解密 filter;
  • LTEP/Fast/DHT 支持位;
  • CanRead / DidWrite / GotError 三个回调。

握手期与消息期复用同一个 I/O 对象,只替换 callbacks。这个设计避免在交接时迁移 socket buffer:对端可能把握手和第一条 BitTorrent 消息一次写进网络包,剩余字节仍留在 peerIo 输入 buffer,切换回调后可继续解析。

读回调返回 ReadState

状态含义
Nowbuffer 里可能还有足够数据,立刻继续解析
Later数据不足,等下次可读事件
Break本轮停止循环,通常用于回调所有权刚切换
Err协议或连接错误,关闭路径

四、标准 BitTorrent 握手恰好 68 字节

text
1 byte   pstrlen = 19
19 bytes "BitTorrent protocol"
8 bytes  reserved feature bits
20 bytes info_hash
20 bytes peer_id

保留位中 Transmission 关注 LTEP、Fast Extension 和 DHT。入站连接在读到前 48 字节(协议名 + flags + info hash)时就能找到 torrent 并回复握手,不必等远端 peer id 全部到达。

握手验证的关键问题包括:

  • 协议名是否正确;
  • info hash 是否对应本 Session 中运行的 torrent;
  • 远端 peer id 是否与本机相同;
  • 扩展位是否与后续行为一致;
  • incoming 连接是否允许绑定到该 torrent。

五、为什么握手是状态机而不是一次 read(68)

网络 read 可能只返回任意前缀;MSE 又引入多轮 Diffie-Hellman 与 padding。tr_handshake::State 因此区分:

  • 双向共有:AwaitingHandshakeAwaitingPeerId
  • incoming MSE:AwaitingYa/PadA/CryptoProvide/PadC/Ia
  • outgoing MSE:AwaitingYb/Vc/CryptoSelect/PadD

can_read() 循环调用当前状态对应的 reader。每个 reader 只在数据足够时消费 buffer,并返回 Now/Later/Break/Err

六、MSE 协商在保护什么

Message Stream Encryption 通过 Diffie-Hellman 共享秘密与 torrent key 协商 plaintext 或 RC4 流。它主要用于协议流量混淆和避免简单识别,不应等同于现代端到端身份认证协议。

Session 的 encryption mode 决定策略:要求加密、偏好加密、偏好明文。Outgoing 在 MSE 失败且策略允许时可以 reconnect 后尝试明文;要求加密时则直接失败。

生成 DH key 成本较高。tr_handshake 有一个最大 32 项的静态 DH pool:只有在完全没从对端读到数据时才回收 key,避免把已经参与交互的密钥材料随意复用。这是性能优化与安全边界的折中。

七、30 秒超时与失败记忆

Handshake 创建 timer,约 30 秒未完成就失败。结果对象包含:

  • 共享 peerIo
  • 可选 peer id;
  • 是否从对端读到任何内容;
  • 是否连接成功。

µTP outgoing 失败时 Mediator 可以记录该地址的 µTP 失败状态,使后续候选选择转向 TCP。Peer 记录也会积累 fruitless connection 次数,避免持续重试无效节点。

八、完成交接:短生命周期对象退场

握手完成回调回到 Peer Manager。Manager 会重新确认:

  1. torrent 仍存在且运行;
  2. 地址没有被 ban/block;
  3. 没有重复连接;
  4. peer id 不与本机或现有连接冲突;
  5. swarm 仍有容量。

通过后创建 tr_peerMsgsImpl,把 peerIo callbacks 从 Handshake 切换到消息解析器,并把 Peer 挂到 swarm。tr_handshake 随后销毁。

这里体现了清晰的对象生命周期:Handshake 只活在身份协商阶段,不把临时状态永久塞进 Peer。

九、入站与出站为何共享同一状态机

二者差异主要是:谁先知道 info hash、谁先发送哪段 MSE 数据、连接失败能否重拨。共享一个状态机保证协议校验、feature bit 解析、超时与完成回调一致,减少两套实现产生安全差异。

Mediator 提供 torrent(info_hash)torrent_from_obfuscated(hash),让 incoming MSE 在不知道明文 info hash 时也能映射到任务,同时不让 Handshake 直接遍历 Session 内部容器。

十、常见误解

  • 握手成功不代表可以下载:对端之后仍可能 choke、没有我们需要的 piece,或请求窗口为零。
  • 连接数不等于活跃 Peer 数:Handshake、connected、interested、unchoked、transferring 是不同阶段。
  • 加密不是匿名:Tracker、DHT、IP 地址和流量行为仍可暴露参与关系。
  • µTP 不是“UDP 不可靠模式”:可靠性与拥塞控制由 libutp 实现,上层仍看到有序字节流。

源码锚点

文档采用 CC BY-SA 4.0;源码片段保留 Transmission 上游许可。