BT协议
bt种子文件
编码 bencode
bencode 有 4 种数据类型: string, integer, list 和 dictionary。
-
string
e.g.: <字符串长度>:<字符串>
-
integer
e.g.: i<整数>e
-
list
e.g.: l数据1数据2数据3e
-
dictionary
e.g.:
d[key1][value1][key2][value2][…]e
,其中 key 必须是 string 而且按照字母顺序排序
种子文件结构
-
info
字典,描述种子内包含的文件列表信息,这里有两种类型,一是单文件的,二是多文件的,具体看后面的说明。 -
announce
字节串,描述 Tracker 的 URL。 -
announce-list
列表,可选,这是一个对官方规范的扩展项,提供后向兼容。 -
creation date
字节串,可选,这是一个UNIX时间戳,表示种子文件的创建时间。 -
comment
字节串,可选,自由描述字段,一般描述种子的制作者。 -
created by
字节串,可选,描述用于制作这个种子文件的程序。 -
encoding
字节串,可选,描述 info.pieces 字段的编码方式。
无论是单文件格式还是多文件格式的种子文件,其 info 字段下都可以包含以下 3 个字段:
-
piece length
整数,表示一个分片的长度(单位:字节),称之为 分片单位。 -
pieces
字节串,是由多个分片的 SHA-1校验码(20字节/个) 拼凑而成的字节串,因此其长度总是为20的倍数。 -
private
整数,可选,如果这个值被置为1,那么 BT客户端 必须通过向 种子文件 中指定的 Tracker 汇报自身的存在,从而获取其它 伙伴 的信息。反之如果置0,客户端 可以通过其它方法获取其它伙伴,例如 PEX,DHT。因此,private 字段也可以理解为“禁止通过其它途径获取伙伴信息”。
当种子内仅包含一个文件的信息时,其 info 字段 内必须包括如下字段:
-
name
字节串,种子内包含的唯一文件的名称,这不是强制性的命名,仅作参考。(下载时可以修改保存的文件名) -
length
整数,种子内包含的唯一文件的大小,单位是字节。 -
md5sum
字节串,可选,这是一个32位的16进制字符串,表示种子内包含的唯一文件的 MD5 校验值。尽管几乎所有BT客户端都未使用到这个字段,但是它仍被保留作为兼容性字段。
当种子内包含多个文件的信息时,其 info 字段 内必须包括如下字段:
name
字节串,种子内所有文件的总名称,BT客户端下载时默认使用它作为资源的目录名称,同理,这不是强制性的命名,仅作参考。(下载时可以修改保存的目录名)files列表,这是一个由多个字典组成的列表,每个字典对应一个文件的信息,这些字典的格式如下:
-
length
整数,文件的大小,单位是字节。 -
md5sum
字节串,可选,这是一个32位的16进制字符串,表示该文件的 MD5 校验值。尽管几乎所有BT客户端都未使用到这个字段,但是它仍被保留作为兼容性字段。 -
path
列表 这是一个特殊的文件路径表示方式。假设一个文件在种子内对应的相对路径是a/bb/ccc/hello.txt
,那么将其根据/
分割开,得到顺序列表[ 'a', 'bb', 'ccc', 'hello.txt' ]
,经过 BEncoding 编码后就是l1:a2:bb3:ccc9:hello.txte
。
Tracker服务器
一般有http和udp两种
入参
-
info_hash
:元信息文件中 20 字节的 SHA-1 散列值。注意此值会进入编码字典中,如上述的信息关键字的定义所述。与不需编码的 peer_id 相比,它总是被 URL 编码。
-
peer_id
:客户端 ID ,客户端用来唯一标识自己 ID 的 20 字节的串,它在客户端启动时生成。允许为任何值,包括二进制数据。目前没有特定的算法来生成客户端 ID。但是,人们会认为它至少对于自己的本地机器是唯一的,从而应该像进程 ID 一样合并数据,也可能在启动时由时标记录。见本区域下面的一般客户端编码的 peer_id。
-
port
:客户端监听的端口号。BitTorrent 所使用的典型端口是 6881-6889。如果此范围的端口都无效,可以选择其他的。
-
uploaded
:从客户端发送“已开始”事件到服务器算起的上传总量,数值采用 10 进制的 ASCII。对于没有在官方规范明确指出的,该值应为已上传的字节总数。
-
downloaded
:从客户端发送“已开始”事件到服务器算起的下载总量,数值采用 10 进制的 ASCII。对于没有在官方规范明确指出的,该值应为已下载的字节总数。
-
left
:客户端需要下载的字节数,以 10 进制 ASCII 编码。
-
no_peer_id
:客户端接受一个紧密的响应。客户端列表由客户端串代替,此串中每个客户端都编码成 6 字节。前 4 字节是主机名(以网络的字节顺序),后两个字节是端口号(同样以网络字节的顺序)。
-
event
:如果被指定,则是已开始,已完成,已停止中的一个,或者为空(表示未指定)。如果未指定,此请求为常规时间间隔中的一次运行。
-
started
:向服务器发送的第一个请求,必须包含开始值的事件关键字。
-
-
stopped
:如果客户端关机则须发送到服务器上。
-
-
completed
:完成下载时必须发送到服务器上。但是,当客户端启动时下载完成度为 100%(即:做种中)则不会发送。可能这是允许服务器增加“已完成下载”的方法。
-
-
ip
:可选。客户端的真实 IP 地址,以点分四元组格式或 RFC3513 中定义的 16 进制 IPv6 地址。注意:大体上此参数没有客户端地址重要,它能由 IP 地址决定,HTTP 请求也来自该处。仅在请求参与的 IP 地址不是客户端的 IP 地址的情况下才需要。这种情况发生在客户端通过代理服务器与服务器进行通信的情形。当客户端和服务器同时处在本地 NAT 网关时也需要。原因是服务器会发出客户端的内部地址(RFC1918),这是不可到达的。所以客户端必须清楚地把自己的外部可到达的 IP 地址发送到其他客户端中。不同的服务器对此参数的解释有所不同。某些只有当请求参与的 IP 地址属于 RFC1918 时才允许。有些无条件允许,但有些则完全忽略。如果使用 IPv6 地址(如:2001:db8:1:2::100),则表示客户端能通过 IPv6 进行通信。
-
numwant
:可选。客户端想从服务器接收的用户数目。允许此值为“0”。如果不用此项,则默认值为 50 个用户。
-
key
:可选。一个不与任何用户共享的另外的标识。当 IP 地址改变后,允许客户端证明它们的标识。
-
trackerid
:可选。如果先前发布包含服务器的 id,它应放在这里。
响应
-
failure reason
:如果当前使用此值,则其余关键字不会使用。该值是可读的错误消息,包括请求失败的原因。(字符串)
-
warning message
:(新)与失败原因相似,但响应仍然会被正常处理。警告消息看起来像错误。
-
interval
:以秒计算,是客户端发送规则请求到服务器之后等待的时间。(强制)
-
min interval
:最小发布时间间隔。当前客户重发间隔不能小于此值。
-
tracker id
:一个客户端应在下一个通告发回的字符串。如果没有该值,先前通告会发出一个服务器 id ,不要丢弃旧的值,一直使用它。
-
complete
:拥有完整文件的用户数,即做种者(整数)
-
incomplete
:非种子用户的数目,也叫“吸血者”(整数)
-
peers
:是字典的列表,每个值都有如下的关键字:
-
peer id
:用户的自选择 ID,如上述用来发送服务器请求的(字符串)
-
-
ip
:用户的 IP 地址(IPv4 或 IPv6 格式)或域名(字符串)
-
-
port
:用户的端口号(整数)
-
DHT
概述 Overview
每个节点有一个全局唯一的标识符,作为 "node ID"。节点 ID 是一个随机选择的 160bit 空间,BitTorrent infohash[2] 也使用这样的 160bit 空间。 "距离"用来比较两个节点 ID 之间或者节点 ID 和 infohash 之间的"远近"。节点必须维护一个路由表,路由表中含有一部分其它节点的联系信息。其它节点距离自己越近时,路由表信息越详细。因此每个节点都知道 DHT 中离自己很"近"的节点的联系信息,而离自己非常远的 ID 的联系信息却知道的很少。
在 Kademlia 网络中,距离是通过异或(XOR)计算的,结果为无符号整数。distance(A, B) = |A xor B|
,值越小表示越近。
当节点要为 torrent 寻找 peer 时,它将自己路由表中的节点 ID 和 torrent 的 infohash 进行"距离对比"。然后向路由表中离 infohash 最近的节点发送请求,问它们正在下载这个 torrent 的 peer 的联系信息。如果一个被联系的节点知道下载这个 torrent 的 peer 信息,那个 peer 的联系信息将被回复给当前节点。否则,那个被联系的节点则必须回复在它的路由表中离该 torrent 的 infohash 最近的节点的联系信息。最初的节点重复地请求比目标 infohash 更近的节点,直到不能再找到更近的节点为止。查询完了之后,客户端把自己作为一个 peer 插入到所有回复节点中离种子最近的那个节点中。
请求 peer 的返回值包含一个不透明的值,称之为"令牌(token)"。如果一个节点宣布它所控制的 peer 正在下载一个种子,它必须在回复请求节点的同时,附加上对方向我们发送的最近的"令牌(token)"。这样当一个节点试图"宣布"正在下载一个种子时,被请求的节点核对令牌和发出请求的节点的 IP 地址。这是为了防止恶意的主机登记其它主机的种子。由于令牌仅仅由请求节点返回给收到令牌的同一个节点,所以没有规定他的具体实现。但是令牌必须在一个规定的时间内被接受,超时后令牌则失效。在 BitTorrent 的实现中,token 是在 IP 地址后面连接一个 secret(通常是一个随机数),这个 secret 每五分钟改变一次,其中 token 在十分钟以内是可接受的。
路由表 Routing Table
每个节点维护一个路由表保存已知的好节点。路由表中的节点是用来作为在 DHT 中请求的起始点。路由表中的节点是在不断的向其他节点请求过程中,对方节点回复的。
并不是我们在请求过程中收到得节点都是平等的,有的节点是好的,而另一些则不是。许多使用 DHT 协议的节点都可以发送请求并接收回复,但是不能主动回复其他节点的请求。节点的路由表只包含已知的好节点,这很重要。好节点是指在过去的 15 分钟以内,曾经对我们的某一个请求给出过回复的节点,或者曾经对我们的请求给出过一个回复(不用在15分钟以内),并且在过去的 15 分钟给我们发送过请求。上述两种情况都可将节点视为好节点。在 15 分钟之后,对方没有上述 2 种情况发生,这个节点将变为可疑的。当节点不能给我们的一系列请求给出回复时,这个节点将变为坏的。相比那些未知状态的节点,已知的好节点会被给于更高的优先级。
路由表覆盖从 0 到 2^160 全部的节点 ID 空间。路由表又被划分为桶(bucket),每个桶包含一部分的 ID 空间。空的路由表只有一个桶,它的 ID 范围从 min=0 到 max=2^160。当 ID 为 N
的节点插入到表中时,它将被放到 ID 范围在 min <= N < max
的 桶 中。空的路由表只有一个桶,所以所有的节点都将被放到这个桶中。每个桶最多只能保存 K 个节点,当前 K=8。当一个桶放满了好节点之后,将不再允许新的节点加入,除非我们自身的节点 ID 在这个桶的范围内。在这样的情况下,这个桶将被分裂为 2 个新的桶,每个新桶的范围都是原来旧桶的一半。原来旧桶中的节点将被重新分配到这两个新的桶中。如果一个新表只有一个桶,这个包含整个范围的桶将总被分裂为 2 个新的桶,每个桶的覆盖范围从 0..2^159 和 2159..2160。
当桶装满了好节点,新的节点会被丢弃。一旦桶中的某个节点变为了坏的节点,那么我们就用新的节点来替换这个坏的节点。如果桶中有在 15 分钟内都没有活跃过的节点,我们将这样的节点视为可疑的节点,这时我们向最久没有联系的节点发送 ping。如果被 ping 的节点给出了回复,那么我们向下一个可疑的节点发送 ping,不断这样循环下去,直到有某一个节点没有给出 ping 的回复,或者当前桶中的所有节点都是好的(也就是所有节点都不是可疑节点,他们在过去 15 分钟内都有活动)。如果桶中的某个节点没有对我们的 ping 给出回复,我们最好再试一次(再发送一次 ping,因为这个节点也许仍然是活跃的,但由于网络拥塞,所以发生了丢包现象,注意 DHT 的包都是 UDP 的),而不是立即丢弃这个节点或者直接用新节点来替代它。这样,我们得路由表将充满稳定的长时间在线的节点。
每个桶都应该维持一个 lastchange
字段来表明桶中节点的"新鲜"度。当桶中的节点被 ping 并给出了回复,或者一个节点被加入到了桶,或者一个节点被新的节点所替代,桶的 lastchange
字段都应当被更新。如果一个桶的 lastchange
在过去的 15 分钟内都没有变化,那么我们将更新它。这个更新桶操作是这样完成的:从这个桶所覆盖的范围中随机选择一个 ID,并对这个 ID 执行 find_nodes
查找操作。常常收到请求的节点通常不需要常常更新自己的桶,反之,不常常收到请求的节点常常需要周期性的执行更新所有桶的操作,这样才能保证当我们用到 DHT 的时候,里面有足够多的好的节点。
在插入第一个节点到路由表并启动服务后,这个节点应试着查找 DHT 中离自己更近的节点,这个查找工作是通过不断的发出find_node
消息给越来越近的节点来完成的,当不能找到更近的节点时,这个扩散工作就结束了。路由表应当被启动工作和客户端软件保存(也就是启动的时候从客户端中读取路由表信息,结束的时候客户端软件记录到文件中)。
协议消息
Kademlia协议共有四种消息
1. PING消息: 用来测试节点是否仍然在线
2. STORE消息: 在某个节点中存储一个键值对
3. FIND_NODE消息: 消息请求的接收者将返回自己桶中离请求键值最近的K个节点: 将请求者请求的节点HASH和自己的HASH进行XOR计算,将计算结果
4. FIND_VALUE消息: 与FIND_NODE一样,不过当请求的接收者存有请求者所请求的键的时候,它将返回相应键的值
DHT嗅探器的原理
DHT这种对等分布式网络在带来抗DDOS的优点的同时,也带来了一些缺点
1. 伪造攻击: 有些不听话的用户可能会在DHT网络里捣乱,譬如说撒谎,明明自己不是奥巴马,却偏说自己是奥巴马,这样会误导其他人无法正常获取想要的资源
2. 嗅探攻击: 另外,用户在DHT网络里的隐私可能会被窃听,因为在DHT网络里跟其他用户交换资源的时候,难免会暴露自己的IP地址,所以别人就会知道你有什么资源,你在请求什么资源了。这也是目前DHT网络里一直存在的一个弱点
参考:
https://wiki.theory.org/index.php/BitTorrentSpecification#Notes
https://fenying.gitbooks.io/bittorrent-specification-chinese-edition/content/