BitTorrent 的 UDP Tracker 协议
简介
为了在一个 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 requestt = 1: connect responset = 2: announce requestt = 3: announce response
连接超时:
t = 0: connect requestt = 15: connect requestt = 45: connect requestt = 105: connect requestetc
广播超时:
t = 0:t = 0: connect requestt = 1: connect responset = 2: announce requestt = 17: announce requestt = 47: announce requestt = 107: connect request (because connection ID expired)t = 227: connect requestetc
多重请求:
t = 0: connect requestt = 1: connect responset = 2: announce requestt = 3: announce responset = 4: announce requestt = 5: announce responset = 60: announce requestt = 61: announce responset = 62: connect requestt = 63: connect responset = 64: announce requestt = 64: scrape requestt = 64: scrape requestt = 64: announce requestt = 65: announce responset = 66: announce responset = 67: scrape responset = 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 Value0 64-bit integer protocol_id 0x41727101980 // magic constant8 32-bit integer action 0 // connect12 32-bit integer transaction_id16
接收数据包。
检查它的长度是否至少为 16 字节。
检查接收到的数据包中的 Transaction ID 是否与之前发送请求时选择的 ID 一致。
检查数据包中的操作是否为”connect” 。
将数据包中的 Connection ID 存储到本地,并用于后续操作。
连接响应:
Offset Size Name Value0 32-bit integer action 0 // connect4 32-bit integer transaction_id8 64-bit integer connection_id16
广播
从系统中随机选择一个 Transaction ID 。
填入一个广播请求结构。
发送数据包。
IPv4 广播请求:
Offset Size Name Value0 64-bit integer connection_id8 32-bit integer action 1 // announce12 32-bit integer transaction_id16 20-byte string info_hash36 20-byte string peer_id56 64-bit integer downloaded64 64-bit integer left72 64-bit integer uploaded80 32-bit integer event 0 // 0: none; 1: completed; 2: started; 3: stopped84 32-bit integer IP address 0 // default88 32-bit integer key92 32-bit integer num_want -1 // default96 16-bit integer port98
接收数据包。
检查数据包的长度是否至少为 20 字节。
检查数据包中的交易 ID 是否与之前选择的交易 ID 相等,以确保接收到的是之前发送的通告请求的响应。
检查数据包中的操作是否为 “announce” 。
进行时间间隔检查。如果与上次广播请求时间不足一定时间间隔(interval 秒),则不再进行广播请求;否则,可以继续进行广播请求或等待事件触发后再次请求。
大多数 Tracker 只在某些特定情况下才会考虑 IP 地址字段。
IPv4 广播请求:
Offset Size Name Value0 32-bit integer action 1 // announce4 32-bit integer transaction_id8 32-bit integer interval12 32-bit integer leechers16 32-bit integer seeders20 + 6 * n 32-bit integer IP address24 + 6 * n 16-bit integer TCP port20 + 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 Value0 64-bit integer connection_id8 32-bit integer action 2 // scrape12 32-bit integer transaction_id16 + 20 * n 20-byte string info_hash16 + 20 * N
接受数据包。
检查数据包的长度是否至少为 8 字节。
检查该数据包中的 Transaction ID 是否与之前你选择的相同。
检查数据包中的操作是否为 “scrape” 。
抓取响应:
Offset Size Name Value0 32-bit integer action 2 // scrape4 32-bit integer transaction_id8 + 12 * n 32-bit integer seeders12 + 12 * n 32-bit integer completed16 + 12 * n 32-bit integer leechers8 + 12 * N
如果 tracker 遇到错误,可能会发送一个错误数据包。
接受数据包。
检查数据包的长度是否至少为 8 字节。
检查该数据包中的 Transaction ID 是否与之前你选择的相同。
错误
错误响应:
Offset Size Name Value0 32-bit integer action 3 // error4 32-bit integer transaction_id8 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,同时使用两种不同的追踪器通信协议以提高可靠性。
参考链接
Like my work? Don't forget to support and clap, let me know that you are with me on the road of creation. Keep this enthusiasm together!