[TOC] | BEP: | 5 | | ------------- | ---------------------------------------- | | 标题: | DHT协议 | | 版: | 369b2bc90d29397f18f6a51ce517f65780cc0b13 | | 最后一次更改: | Mon May 1 15:42:08 2017 -0700 | | 作者: | Andrew Loewenstern <drue@bittorrent.com>, Arvid Norberg <arvid@bittorrent.com> | | 状态: | Accepted | | 类型: | Standards Track | | 创建: | 31-JAN-2008 | | Post-History: | 22-March-2013: Add "implied_port" to announce_peer message, to improve NAT support | BitTorrent使用“分布式散布哈希表”(DHT)来存储“trackerless”种子的同伴联系信息。实际上,每个对等成为跟踪者。该协议基于[Kademila](http://www.bittorrent.org/beps/bep_0005.html#kademlia)并通过UDP实现。 请注意本文档中使用的术语以避免混淆。“对等”是侦听实现BitTorrent协议的TCP端口的客户端/服务器。“节点”是监听实现分布式哈希表协议的UDP端口的客户端/服务器。DHT由节点组成并存储对等点的位置。BitTorrent客户端包括DHT节点,DHT节点用于联系DHT中的其他节点以获取使用BitTorrent协议下载的对等点的位置。 # 概观 每个节点都有一个全球唯一的标识符,称为“节点ID”。节点ID从与[BitTorrent infohashes](http://www.bittorrent.org/beps/bep_0005.html#entropy)相同的160位空间随机选择。“距离度量”用于比较两个节点ID或一个节点ID和一个“接近度”的infohash。节点必须维护包含少量其他节点的联系信息的路由表。随着ID更接近节点自己的ID,路由表变得更加详细。节点知道DHT中的许多其他节点的ID与自己“接近”,但只与少数与他们自己很远的ID进行联系。 在Kademlia中,距离度量是XOR,结果被解释为无符号整数。距离(A,B)= | A xor B | 较小的值更接近。 当一个节点想要找到一个torrent的对等点时,它使用距离度量来比较torrent的infohash和它自己的路由表中的节点的ID。然后,它会将它所知道的节点与最接近infohash的ID进行联系,并要求他们提供当前正在下载该torrent的对等方的联系信息。如果联系的节点知道关于该种子的对等点,则该对等联系信息与该响应一起返回。否则,被联系的节点必须响应其路由表中最靠近该torrent的infohash的节点的联系信息。原始节点迭代查询更接近目标infohash的节点,直到它找不到任何更近的节点。搜索结束后, 对等查询的返回值包含一个称为“标记”的不透明值。对于节点宣布其控制对等体正在下载洪流,它必须在近期查询同级中显示从同一查询节点接收到的令牌。当一个节点试图“宣布”一个种子时,被查询的节点检查该令牌与查询节点的IP地址。这是为了防止恶意主机注册其他主机。由于令牌只是由查询节点返回给它从其接收令牌的同一节点,因此未定义实现。令牌在分发后必须等待合理的时间。 # 路由表 每个节点维护已知好节点的路由表。DHT中的路由表中的节点用作查询的起点。响应来自其他节点的查询而返回来自路由表的节点。 并非我们所了解的所有节点都是平等的。有些是“好”,有些则不是。许多使用DHT的节点能够发送查询并接收响应,但无法响应来自其他节点的查询。每个节点的路由表必须只包含已知的好节点,这一点很重要。一个好的节点是一个节点在过去的15分钟内回应了我们的一个查询。如果一个节点对我们的某个查询做出了回应,并且在过去15分钟内向我们发送了查询,那么这个节点也很好。闲置15分钟后,节点会变得有问题。当它们无法响应连续的多个查询时,节点变得很糟糕。我们知道的节点优先于优先级高于具有未知状态的节点。 路由表覆盖从0到2 160的整个节点ID空间。路由表被细分为“桶”,每个桶都覆盖一部分空间。一个空表具有一个存储区,ID空间范围最小值= 0,最大值= 2 160。当具有ID“N”的节点被插入表中时,它被放置在具有min <= N < 最大。一张空表只有一个存储桶,因此任何节点都必须适合它。每个存储桶只能容纳K个节点,目前为8个,然后变为“满”。当一个桶充满已知的好节点时,除非我们自己的节点ID落入该桶的范围内,否则不会再添加更多的节点。在这种情况下,存储桶被两个新存储桶取代,每个存储桶都有旧存储区的一半范围,旧存储区中的节点分布在两个新存储区中。对于只有一个桶的新表,则满桶总是分成两个新的桶覆盖范围0..2 159和2 159 ..2 160。 当存储桶充满好节点时,新节点将被丢弃。如果存储桶中的任何节点已知变得不正常,则将其替换为新节点。如果在桶中有任何可疑的节点在过去的15分钟内没有被看到,则最近看到的最少节点被ping通。如果pingging节点响应,则ping最近看到的下一个最近可疑的节点,直到一个未能响应,或者桶中的所有节点都已知为好。如果存储桶中的某个节点无法响应ping,则建议在丢弃节点并将其替换为新的良好节点之前再次尝试。这样,表格就会填充稳定的长时间运行的节点。 每个桶应该保持“最后更改”属性以指示内容是多么“新鲜”。当一个存储桶中的一个节点被ping并响应,或者一个节点被添加到一个存储桶中,或者存储桶中的一个节点被另一个节点替换时,存储桶上次更改的属性应该被更新。在15分钟内没有改变的桶应该“刷新”。这是通过在存储区域中选择一个随机ID并在其上执行find_nodes搜索来完成的。能够从其他节点接收查询的节点通常不需要经常刷新桶。无法从其他节点接收查询的节点通常需要定期刷新所有存储桶,以确保在需要DHT时在其表中存在良好的节点。 在将第一个节点插入到其路由表中,并在此后启动时,节点应该尝试查找DHT中最靠近自己的节点。它通过发送find_node消息到更近和更近的节点来做到这一点,直到找不到更近的节点为止。路由表应在客户端软件的调用之间保存。 # BitTorrent协议扩展 BitTorrent协议已扩展到交换由跟踪器引入的对等节点之间的节点UDP端口号。通过这种方式,客户端可以通过常规种子的下载自动播种他们的路由表。试图在第一次尝试下载无跟踪器种子的新安装客户端的路由表中不会有任何节点,并且需要包含在该种子文件中的联系人。 支持DHT的对等设置在BitTorrent协议握手中交换的8字节保留标志的最后一位。对等方接收到指示远程对等方支持DHT的握手应发送PORT消息。它从字节0x09开始,并具有包含网络字节顺序的DHT节点的UDP端口的两个字节的有效载荷。接收此消息的对等方应尝试ping接收端口上的节点和远程对等体的IP地址。如果收到对ping的响应,则节点应根据常规规则尝试将新联系人信息插入其路由表中。 # Torrent文件扩展名 无跟踪器的洪流字典没有“通知”键。相反,一个trackerless torrent有一个“节点”键。此密钥应设置为洪流生成客户端路由表中K个最接近的节点。或者,可以将密钥设置为已知的良好节点,例如由生成洪流的人操作的节点。请不要自动将“router.bittorrent.com”添加到torrent文件,或自动将此节点添加到客户端路由表。 ``` 节点= [[“<主机>”,<端口>],[“<主机>”,<端口>],...] 节点= [[“127.0.0.1”,6881],[“your.router.node”,4804]] ``` # KRPC协议 KRPC协议是一种简单的RPC机制,由通过UDP发送的合码字典组成。发送单个查询分组并且发送单个分组作为响应。没有重试。有三种消息类型:查询,响应和错误。对于DHT协议,有四个查询:ping,find_node,get_peers和announce_peer。 KRPC消息是单个字典,其中每个消息都有三个通用键,而其他键则取决于消息的类型。每条消息都有一个键“t”,字符串值代表一个事务ID。该事务ID由查询节点生成并且在响应中被回显,因此响应可以与对同一节点的多个查询相关联。事务ID应该被编码为一串短的二进制数字,通常2个字符就足够了,因为它们覆盖了2 ^ 16个未完成的查询。每条消息还有一个关键字“y”,其中包含描述消息类型的单个字符值。“y”键的值是查询中的“q”,响应中的“r”或错误中的“e”中的一个。使用客户端版本字符串时,每个消息中应包含一个密钥“v”。[[\]](http://www.bittorrent.org/beps/bep_0005.html#bep-20)后面跟着一个双字符版本标识符。并非所有的实现都包含一个“v”键,所以客户端不应该假设它的存在。 ## 联系人编码 对等体的联系信息被编码为6字节的字符串。也称为“压缩IP地址/端口信息”,4字节IP地址按网络字节顺序排列,2字节端口以网络字节顺序连接到末尾。 节点的联系信息被编码为26个字节的字符串。也称为“压缩节点信息”,网络字节顺序中的20字节节点ID将压缩的IP地址/端口信息连接在一起。 ## 查询 查询或“y”值为“q”的KRPC消息字典包含两个附加键; “q”和“a”。键“q”具有包含查询的方法名称的字符串值。键“a”具有包含查询的命名参数的字典值。 ## 回应 “y”值为“r”的响应或KRPC消息字典包含一个附加密钥“r”。“r”的值是一个包含命名返回值的字典。响应消息在成功完成查询后发送。 ## 错误 “y”值为“e”的错误或KRPC消息字典包含一个附加键“e”。“e”的值是一个列表。第一个元素是表示错误代码的整数。第二个元素是包含错误消息的字符串。查询无法完成时发送错误。下表描述了可能的错误代码: | 码 | 描述 | | ---- | ------------------------- | | 201 | 一般错误 | | 202 | 服务器错误 | | 203 | 协议错误,例如格式错误的数据包,无效参数或错误标记 | | 204 | 方法未知 | 示例错误包: ``` 通用错误= {“t”:“aa”,“y”:“e”,“e”:[201,“发生一般错误”]} bencoded = d1:eli201e23:通用错误Ocurrede1:t2:aa1:y1:ee ``` # DHT查询 所有查询都有一个“id”键和包含查询节点的节点ID的值。所有响应都有一个“id”键和包含响应节点的节点ID的值。 ## ping 最基本的查询是ping。“q”=“ping”ping查询有一个参数,“id”是一个20字节的字符串,其中包含网络字节顺序中的发件人节点ID。对ping的适当响应具有包含响应节点的节点ID的单个密钥“id”。 ``` 参数:{“id”:“<查询节点id>”} 回应:{“id”:“<查询节点id>”} ``` 示例数据包 ``` ping查询= {“t”:“aa”,“y”:“q”,“q”:“ping”,“a”:{“id”:“abcdefghij0123456789”}} bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe ``` ``` Response = {“t”:“aa”,“y”:“r”,“r”:{“id”:“mnopqrstuvwxyz123456”}} bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re ``` ## find_node 查找节点用于查找给定ID的节点的联系信息。“q”==“find_node”find_node查询有两个参数,“id”包含查询节点的节点ID,“target”包含查询器查找的节点的ID。当一个节点收到一个find_node查询时,它应该用一个关键字“节点”和一个字符串值来响应,该字符串包含目标节点或者它自己的路由表中K(8)个最接近的好节点的压缩节点信息。 ``` 参数:{“id”:“<查询节点ID>”,“目标”:“<目标节点的ID>”} 响应:{“id”:“<查询节点id>”,“节点”:“<compact node info>”} ``` 示例数据包 ``` find_node Query = {“t”:“aa”,“y”:“q”,“q”:“find_node”,“a”:{“id”:“abcdefghij0123456789”,“target”:“mnopqrstuvwxyz123456”}} bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe ``` ``` Response = {“t”:“aa”,“y”:“r”,“r”:{“id”:“0123456789abcdefghij”,“nodes”:“def456 ...”}} bencoded = d 1:rd 2:id 20:0123456789 abcdefghij 5:节点9:def 456 ... e 1:t 2:aa 1:y 1:re ``` ## get_peers 获取与torrent信息冲突关联的同伴。“q”=“get_peers”get_peers查询有两个参数,“id”包含查询节点的节点ID,“info_hash”包含torrent的infohash。如果查询的节点具有infohash的对等项,则它们将作为字符串列表以键值的形式返回。每个包含单个对等体的“紧凑”格式对等体信息的字符串。如果查询节点没有用于infohash的对等项,则会返回一个关键字“nodes”,其中包含查询节点路由表中最靠近查询中提供的infohash的K个节点。在任何一种情况下,“标记”键也包含在返回值中。令牌值是未来announce_peer查询的必需参数。令牌值应该是一个短的二进制字符串。 ``` 参数:{“id”:“<查询节点id>”,“info_hash”:“<目标torrent的20字节infohash>”} 响应:{“id”:“<查询节点id>”,“令牌”:“<opaque write token>”,“values”:[“<peer 1 info string>”,“<peer 2 info string>”] } 或:{“id”:“<查询节点id>”,“令牌”:“<opaque write token>”,“节点”:“<compact node info>”} ``` 示例包: ``` get_peers Query = {“t”:“aa”,“y”:“q”,“q”:“get_peers”,“a”:{“id”:“abcdefghij0123456789”,“info_hash”:“mnopqrstuvwxyz123456”}} bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe ``` ``` 同侪反应= {“t”:“aa”,“y”:“r”,“r”:{“id”:“abcdefghij0123456789”,“token”:“aoeusnth”,“values”:[“axje。 u“,”idhtnm“]}} bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:values16:axje.u6:idhtnmee1:t2:aa1:y1:re ``` ``` 最近节点的响应= {“t”:“aa”,“y”:“r”,“r”:{“id”:“abcdefghij0123456789”,“token”:“aoeusnth”,“nodes” ..“}} bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456 ... 5:token8:aoeusnthe1:t2:aa1:y1:re ``` ## announce_peer 宣布控制查询节点的对等端正在下载端口上的洪流。announce_peer有四个参数:包含查询节点的节点ID的“id”,包含torrent的infohash的“info_hash”,包含端口的“port”作为整数,以及响应前一个get_peers查询而接收的“token” 。查询的节点必须验证令牌以前是否与查询节点发送到相同的IP地址。然后,查询的节点应该将其查询节点的IP地址和提供的端口号存储在其存储的对等联系信息中的infohash下。 有一个名为implied_port的可选参数,其值为0或1.如果它存在且不为零,则应忽略端口参数,而应将UDP数据包的源端口用作对等端口。这对于那些可能不知道它们的外部端口并支持uTP的NAT后面的对端来说很有用,它们接受与DHT端口相同的端口上的传入连接。 ``` 参数:{“id”:“<查询节点ID>”, “implied_port”:<0或1>, “info_hash”:“<目标torrent的20字节infohash>”, “端口”:<端口号>, “token”:“<opaque token>”} 回应:{“id”:“<查询节点id>”} ``` 示例包: ``` announce_peers Query = {“t”:“aa”,“y”:“q”,“q”:“announce_peer”,“a”:{“id”:“abcdefghij0123456789”,“implied_port”:1,“info_hash” :“mnopqrstuvwxyz123456”,“port”:6881,“token”:“aoeusnth”}} bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:<br /> mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:Q13:announce_peer1:T2:AA1:Y1:QE ``` ``` Response = {“t”:“aa”,“y”:“r”,“r”:{“id”:“mnopqrstuvwxyz123456”}} bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re ``` # 参考 [[1]](http://www.bittorrent.org/beps/bep_0005.html#id1) Peter Maymounkov, David Mazieres, "Kademlia: A Peer-to-peer Information System Based on the XOR Metric", IPTPS 2002. http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf [[2]](http://www.bittorrent.org/beps/bep_0005.html#id2) Use SHA1 and plenty of entropy to ensure a unique ID. [[3]](http://www.bittorrent.org/beps/bep_0005.html#id3) BEP_0020. Peer ID Conventions. (http://www.bittorrent.org/beps/bep_0020.html) # 版权 该文件已被置于公共领域。