简介#
为了在一个 BitTorrent 下载群体中找到其他的节点,一个客户端会向一个追踪器发出宣告自己的请求。这个请求使用 HTTP 协议,并包含一些参数,比如 info_hash 、 key 、 peer_id 、 port 、 downloaded 、 left 、 uploaded 和 compact 等。追踪器会返回一组节点(主机和端口)以及其他一些信息给客户端。这个请求和响应都非常简短。由于使用的是 TCP 协议,需要在发送请求前打开连接,在完成请求后关闭连接,这会引入额外的开销。
系统开销#
使用 HTTP 协议会带来很大的系统开销,因为在以太网层、 IP 层、 TCP 层和 HTTP 层都会有开销,并且一个请求加上响应包含 50 个节点需要使用大约 10 个数据包,总共使用的字节数约为 1206 。通过使用基于 UDP 的协议可以显著减少这种开销,本文提出的协议只需 4 个数据包和约 618 个字节,将流量减少了 50% 。虽然对于客户端来说每小时节省 1KB 并不重要,但对于为百万节点提供服务的 Tracker 来说,减少流量的 50% 非常重要。此外,基于 UDP 的二进制协议不需要复杂的解析器和连接处理,降低了 Tracker 代码的复杂性并提高了其性能。
UDP 连接 / 欺骗攻击#
在理想情况下,只需要两个数据包就可以完成通信。在使用 UDP 协议时,由于 UDP 协议是无连接的,因此可能会存在伪造源地址的情况。因此,tracker 必须采取措施来确保不会出现欺骗行为。为了避免这种情况发生,Tracker(一个网络中用来协调客户端与其他 peer 之间数据共享的服务器程序)采取一些措施。
当客户端向 tracker 发送一个请求时,tracker 会生成一个随机数(connection_id),并将其发送给客户端。客户端再次向 tracker 发送请求时,需要带上这个 connection_id,以便 tracker 可以验证请求的来源是否合法。如果客户端伪造了源地址,则不可能收到 tracker 发回的 connection_id,从而请求会被 tracker 拒绝。
为了确保 connection_id 不被客户端猜测到,tracker 可以采用类似于 TCP 握手和 syn-cookie 的方法,在服务器端存储 connection_id,并仅在特定情况下才返回给客户端使用。一个 connection_id 可以用于多个请求,并且客户端可以在接收到 connection_id 后的一分钟内使用它。 tracker 应该在发出 connection_id 后的两分钟内接受它,并验证来自客户端的请求是否匹配这个 connection_id 。
超时#
UDP 是一种不保证数据包可靠传输的协议,这意味着如果在传输过程中丢失了数据包,UDP 不会自动重传。相反,使用 UDP 的应用程序需要负责处理丢失的数据包,并在必要时进行重传。
为了处理丢失的数据包,客户端应该在发送请求后等待服务器的响应。如果在 15 * 2 ^ n 秒后没有收到响应,则客户端应该重新发送请求。重新发送请求的时间间隔从 15 秒开始,每次尝试都会加倍,最多尝试 8 次,即 3840 秒。
需要注意的是,如果连接 ID 已经过期,在重新发送请求之前需要重新请求新的连接 ID 。这确保了请求将被正确地认证并由服务器进行处理。
样例#
常规广播:
t = 0: connect request
t = 1: connect response
t = 2: announce request
t = 3: announce response
连接超时:
t = 0: connect request
t = 15: connect request
t = 45: connect request
t = 105: connect request
etc
广播超时:
t = 0:
t = 0: connect request
t = 1: connect response
t = 2: announce request
t = 17: announce request
t = 47: announce request
t = 107: connect request (because connection ID expired)
t = 227: connect request
etc
多重请求:
t = 0: connect request
t = 1: connect response
t = 2: announce request
t = 3: announce response
t = 4: announce request
t = 5: announce response
t = 60: announce request
t = 61: announce response
t = 62: connect request
t = 63: connect response
t = 64: announce request
t = 64: scrape request
t = 64: scrape request
t = 64: announce request
t = 65: announce response
t = 66: announce response
t = 67: scrape response
t = 68: scrape response
UDP Tracker 协议#
所有数值(例如整数或浮点数)在发送时都应该按照网络字节顺序(大端序)进行编码。此外,不应该预期每个数据包都具有确定的大小。因为将来可能会通过添加新功能来增加数据包的大小。
连接#
Before announcing or scraping, you have to obtain a connection ID.
- Choose a random transaction ID.
- Fill the connect request structure.
- Send the packet.
对于进行”announcing” 或 “scraping” 操作之前,需要先获取一个连接 ID 的过程。步骤如下:
- 随机生成一个 Transaction ID 作为唯一标识符。
- 填充连接请求结构体。
- 发送请求数据包。
连接请求:
Offset Size Name Value
0 64-bit integer protocol_id 0x41727101980 // magic constant
8 32-bit integer action 0 // connect
12 32-bit integer transaction_id
16
- 接收数据包。
- 检查它的长度是否至少为 16 字节。
- 检查接收到的数据包中的 Transaction ID 是否与之前发送请求时选择的 ID 一致。
- 检查数据包中的操作是否为”connect” 。
- 将数据包中的 Connection ID 存储到本地,并用于后续操作。
连接响应:
Offset Size Name Value
0 32-bit integer action 0 // connect
4 32-bit integer transaction_id
8 64-bit integer connection_id
16
广播#
- 从系统中随机选择一个 Transaction ID 。
- 填入一个广播请求结构。
- 发送数据包。
IPv4 广播请求:
Offset Size Name Value
0 64-bit integer connection_id
8 32-bit integer action 1 // announce
12 32-bit integer transaction_id
16 20-byte string info_hash
36 20-byte string peer_id
56 64-bit integer downloaded
64 64-bit integer left
72 64-bit integer uploaded
80 32-bit integer event 0 // 0: none; 1: completed; 2: started; 3: stopped
84 32-bit integer IP address 0 // default
88 32-bit integer key
92 32-bit integer num_want -1 // default
96 16-bit integer port
98
- 接收数据包。
- 检查数据包的长度是否至少为 20 字节。
- 检查数据包中的交易 ID 是否与之前选择的交易 ID 相等,以确保接收到的是之前发送的通告请求的响应。
- 检查数据包中的操作是否为 “announce” 。
- 进行时间间隔检查。如果与上次广播请求时间不足一定时间间隔(interval 秒),则 不再进行广播请求;否则,可以继续进行广播请求或等待事件触发后再次请求。
大多数 Tracker 只在某些特定情况下才会考虑 IP 地址字段。
IPv4 广播请求:
Offset Size Name Value
0 32-bit integer action 1 // announce
4 32-bit integer transaction_id
8 32-bit integer interval
12 32-bit integer leechers
16 32-bit integer seeders
20 + 6 * n 32-bit integer IP address
24 + 6 * n 16-bit integer TCP port
20 + 6 * N
IPv6#
IPv6 和 IPv4 在协议结构上的区别以及如何在使用中进行适配。其中,IPv6 和 IPv4 的消息格式基本相同,但是在回应消息中,<IP 地址、TCP 端口> 对的步进大小从 6 字节变成了 18 字节。此外,在请求消息中,IP 地址字段仍然是 32 位宽度,不能用于 IPv6,并且始终应该设置为 0 。
同时,该段落还提到了适配的方式:根据 UDP 包的地址族来确定所使用的格式,即来自 IPv4 地址的数据包使用 IPv4 格式,来自 IPv6 地址的数据包使用 IPv6 格式。最后,对于将主机名解析为 IPv4 和 IPv6 并向两个传输使用相同密钥的客户端,需要确保跟踪器能够准确地匹配两个通告,以实现准确统计。
抓取#
最多可以同时获取约 74 个种子文件的信息。无法使用此协议完成完整的数据抓取。
- 随机选择一个 Transaction ID 。
- 填充抓取请求结构。
- 发送数据包。
抓取请求:
Offset Size Name Value
0 64-bit integer connection_id
8 32-bit integer action 2 // scrape
12 32-bit integer transaction_id
16 + 20 * n 20-byte string info_hash
16 + 20 * N
- 接受数据包。
- 检查数据包的长度是否至少为 8 字节。
- 检查该数据包中的 Transaction ID 是否与之前你选择的相同。
- 检查数据包中的操作是否为 “scrape” 。
抓取响应:
Offset Size Name Value
0 32-bit integer action 2 // scrape
4 32-bit integer transaction_id
8 + 12 * n 32-bit integer seeders
12 + 12 * n 32-bit integer completed
16 + 12 * n 32-bit integer leechers
8 + 12 * N
如果 tracker 遇到错误,可能会发送一个错误数据包。
- 接受数据包。
- 检查数据包的长度是否至少为 8 字节。
- 检查该数据包中的 Transaction ID 是否与之前你选择的相同。
错误#
错误响应:
Offset Size Name Value
0 32-bit integer action 3 // error
4 32-bit integer transaction_id
8 string message
现有实例#
IMFile,Azureus,libtorrent,opentracker,XBT Client 和 XBT Tracker 支持该协议。
插件#
为了保持协议的兼容性,一般不会在协议中包含扩展位或版本字段。客户端和 Tracker 也不应该假定数据包的大小。这样做可以在不破坏兼容性的情况下添加额外的字段。换句话说,通过避免固定数据包长度和格式,协议可以更容易地进行扩展和更新,并且可以保持向后兼容性。
总结#
UDP Tracker Protocol for BitTorrent 是一种用于点对点文件共享协议 BitTorrent 中的追踪器通信协议。它是基于 UDP 协议的,相较于 HTTP 协议,可以更快地传输数据,同时也具有更好的扩展性和高效性。
通过 UDP Tracker Protocol,BitTorrent 客户端可以向追踪器发送请求,获取连接到种子的其他用户列表。这些用户可以帮助下载者提供文件块,提高下载速度。此外,追踪器还可以提供有关特定种子的统计信息,如上传和下载速度、剩余时间等。
尽管 UDP Tracker Protocol 具有许多优点,但由于其本质上是无状态的,因此在某些情况下可能会遇到一些问题,例如有时会丢失请求或响应。为了解决这些问题,很多 BitTorrent 客户端还支持 HTTP Tracker Protocol,同时使用两种不同的追踪器通信协议以提高可靠性。