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/