歷史
早期的BT下載主要是通過開放的網站,進行種子資源的公布。在利用tracker中心服務器完成下載peer的交換,最終實現從下載用戶的電腦中獲取資源。
在這個過程中,存在兩個風險比較大的點:
第一個公布種子的網站,種子文件包含了所需要下載資源的全部信息,很容易被檢測出種子內容是否合規,從而關閉種子公布資源站點。
第二個提供tracker的中心服務節點,這個也是很容易從種子中查詢到的,很容易被封殺。導致P2P自由的分享環境被打破。
在這個基礎上,就出現替代tracker服務器和種子一些新方法,我們簡單的介紹下比較主流的去中心化tracker和種子分享網站的P2P分享網絡。
torrent and magnet
首先從第一步獲取種子開始,我們一般想找一部電影/游戲或者一些其他資源,一般都是網盤搜索,或者去BT站,PT去找種子,或者論壇上去找鏈接。先介紹下種子和磁鏈的關系,種子一般以.torrent結尾的索引文件。
torrentd8:announce62:https://xxxxx.im.xxx/88696dc48cbdad7518e4b111b83ee77c7:comment14:TorrenTGui.ORG10:created by13:uTorrent/221013:creation datei1523099215e8:encoding5:UTF-84:infod6:lengthi23715250176e4:name14:SKY HUNTER.iso12:piece lengthi4194304e6:pieces113100:xxx
這種被序列化的信息就是種子了,里面使用的是一種明文的序列化方法。bencode 第二節簡單介紹。
反序列化里面主要包含以下信息:
announce:Tracker的主服務器announce-list:Tracker服務器列表comment:種子文件的注釋comment.utf-8:種子文件注釋的utf-8編碼creation date:種子文件建立的時間,是從1970年1月1日00:00:00到現在的秒數。encoding:種子文件的默認編碼,比如GB2312,Big5,utf-8等info:所有關于下載的文件的信息都在這個字段里,根據下載的是單個文件還是多個文件,子字段的項目會不同。files:表示文件的名字,大小,該字段包含如下三個子字段:length:文件的大小,用byte計算path:文件的名字,在下載時不可更改path.utf-8:文件名的UTF-8編碼,多文件Torrent的結構的樹形圖為
Multi-file Torrent├─announce├─announce-list├─comment├─comment.utf-8├─creation date├─encoding├─info│ ├─files│ │ ├─length│ │ ├─path│ │ └─path.utf-8│ ├─name│ ├─name.utf-8│ ├─piece length│ ├─pieces│ ├─publisher│ ├─publisher-url│ ├─publisher-url.utf-8│ └─publisher.utf-8└─nodes
單文件Torrent的結構的樹形圖為
Single-File Torrent├─announce├─announce-list├─comment├─comment.utf-8├─creation date├─encoding├─info│ ├─length│ ├─name│ ├─name.utf-8│ ├─piece length│ ├─pieces│ ├─publisher│ ├─publisher-url│ ├─publisher-url.utf-8│ └─publisher.utf-8└─nodesmagnet
為了解決第一種子文件包含的內容信息太多,容易被檢測中其中的關鍵信息。第二種子文件過大,不太容易擴散分享。出現了一種替代種子文件的信息字符串就是磁力鏈接。形如:magnet:?xt=urn:btih:b7d9b9d9df8d7678af1f2542677e195fdbdb1674
其中主要字段是 btih,其實這里的值就是bt種子文件中info字段sha1值的ba32編碼后的字符串。
bencode
種子文件的bencode 包含四種類型的編碼:
string類型string類型的編碼格式為[length]:[string]。以字符串的長度開頭,加一個冒號,并以字符串內容結束。
示例:"abc" => 3:abc
int類型int類型的編碼格式為i[int]e。以i開頭,加上數字,以e結尾。
示例:123 => i123e
List<object>類型List<object>類型的編碼格式為l[object]e。以l開頭,加上列表中各個元素的編碼(元素的類型同樣為BEncoding支持的類型),以e結尾。
示例:List<"abc", 123> => l3:abci123ee
Dictionary<string, object>類型Dictionary<string, object>類型的編碼格式為d[Key-Value Pair]e。以d開頭,加上字典中每個鍵值對的編碼,以e結尾。
示例:Dictionary<{"name":"create chen"},{"age":23}> => d4:name11:create chen3:agei23ee
以上編碼list和dictionary支持嵌套。bencode編碼方式也被用在后續的BT查詢消息的構建上。
去中心化的peer交換網絡 DHT
在去中心化的交換網絡上,每個用戶(node)都會存儲一部分種子信息和種子索引信息。這些索引信息里面包含自己正在下載的資源(peer)、自己周邊正在下載的資源信息(peer)、以及一些可能擁有某種子資源的信息(node)。當我們獲取到某個種子或者磁鏈時,就會到這個網絡中查詢哪些用戶在進行這個種子的資源下載,獲取這些用戶的peer信息(包含ip port token),然后和這些peer進行連接,獲取資源。在查詢的過程中,主要利用krpc進行調用,krpc是一種基于udp的rpc調用服務。
krpc
基本結構如下:
{ "t":"aa", --transcationID 2^16 two byte "y":"r", --message type query->q respon->r error->e "q":" ". --ping find_node get_peers "r":{} "e" "a": --query params}
一條 KRPC 消息由一個獨立的字典組成,其中有 2 個關鍵字是所有的消息都包含的,其余的附加關鍵字取決于消息類型。每一個消息都包含“t”關鍵字,它是一個代表了 transaction ID 的字符串類型。transaction ID由請求節點產生,并且回復中要包含回顯該字段,所以回復可能對應一個節點的多個請求。transaction ID應當被編碼為一個短的二進制字符串,比如 2 個字節,這樣就可以對應 2^16 個請求。另一個每個 KRPC 消息都包含的關鍵字是“y”,它由一個字節組成,表明這個消息的類型。y 對應的值有三種情況:q 表示請求,r 表示回復,e 表示錯誤。
主要包含以下四個操作:
ping檢測節點是否存活。
最基礎的請求是一個ping。"q"="ping",一個ping請求有一個參數,"id",它的值是一個20字節的字符串,包含網絡字節排序的發送者的節點ID。一個ping的適當回復有一個關鍵字"id",包含發出回復的節點的節點ID。
arguments: {"id" : "<querying nodes id>"}respon: {"id" : "<queried nodes id>"}Example Packetsping Query = {"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}}bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qeRespon = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:refind_node
查找節點。
find_node 被用來查找給定 ID 的節點的聯系信息。這時 KPRC 協議中的 "q" == "find_node"。find_node請求包含 2 個參數,第一個參數是 id,包含了請求節點的ID。第二個參數是 target,包含了請求者正在查找的節點的ID。當一個節點接收到了 find_node 的請求,他應該給出對應的回復,回復中包含 2 個關鍵字 id 和 nodes,nodes 是一個字符串類型,包含了被請求節點的路由表中最接近目標節點的 K(n) 個最接近的節點的聯系信息。在規范的DHT網絡中,K(n)建議值是8。
arguments: {"id" : "<querying nodes id>", "target" : "<id of target node>"}respon: {"id" : "<queried nodes id>", "nodes" : "<compact node info>"}Example Packetsfind_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:qeRespon = {"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}}bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:reget_peers
查找文件peer。
get_peers 與 torrent 文件的 infohash 有關。這時 KPRC 協議中的 "q"="get_peers"。get_peers 請求包含 2 個參數。第一個參數是 id,包含了請求節點的 ID。第二個參數是info_hash,它代表 torrent 文件的 infohash。如果被請求的節點有對應 info_hash 的peers,他將返回一個關鍵字 values,這是一個列表類型的字符串。每一個字符串包含了 "CompactIP-address/portinfo" 格式的 peers 信息。如果被請求的節點沒有這個 infohash 的peers,那么他將返回關鍵字 nodes,這個關鍵字包含了被請求節點的路由表中離 info_hash 最近的 K 個節點,
arguments: {"id" : "<querying nodes id>", "info_hash" : "<20-byte infohash of target torrent>"}respon: {"id" : "<queried nodes id>", "token" :"<opaque write token>", "values" : ["<peer 1 info string>", "<peer 2 info string>"]} or: {"id" : "<queried nodes id>", "token" :"<opaque write token>", "nodes" : "<compact node info>"}Example Packetsget_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:qeRespon with peers = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "values": ["axje.u", "idhtnm"]}}bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:reRespon with clost nodes = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "nodes": "def456..."}}bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:t2:aa1:y1:reannounce_peer
發起請求節點。
這個請求用來表明發出 announce_peer 請求的節點,正在某個端口下載 torrent 文件。announce_peer 包含 4個參數。第一個參數是 id,包含了請求節點的 ID;第二個參數是 info_hash,包含了 torrent 文件的infohash;第三個參數是 port 包含了整型的端口號,表明 peer 在哪個端口下載;第四個參數數是 token,這是在之前的get_peers 請求中收到的回復中包含的。收到 announce_peer 請求的節點必須檢查這個 token 與之前我們回復給這個節點get_peers 的 token 是否相同。如果相同,那么被請求的節點將記錄發送 announce_peer 節點的 IP 和請求中包含的port 端口號在 peer 聯系信息中對應的 infohash 下。
arguments: {"id" : "<querying nodes id>", "implied_port": <0 or 1>, "info_hash" : "<20-byte infohash of target torrent>", "port" : <port number>, "token" : "<opaque token>"}respon: {"id" : "<queried nodes id>"}Example Packetsannounce_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:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qeRespon = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}}bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re
DHT and Kademlia
DHT (Distributed Hash Table)分布式哈希表,Kad網絡是其的一種實現方式。Kademlia協議(以下簡稱Kad)是美國紐約大學的PetarP. Maymounkov和David Mazieres.在2002年發布的一項研究結果《Kademlia: A peerto -peer information system bad onthe XOR metric》。
nodeid2^160位(bit)的標志id,可以是本機mac的sha1值。在DHT分享網絡中,資源的info-hash正好也是160位(bit)。在利用kad網絡,將info-hash存儲于與其相近的多個node中,方便在查詢過程中利用get_peer進行快速逼近。在如何判定是否與其相近,kad網絡利用的是xor運算。
xor distanceKademlia 根據兩個標示符之間的距離來把 <key , value> 對分配給特定的節點。對于兩個 160 位標示符 x 和 y , Kademlia 把它們之間的距離定義為二者按位異或( XOR )結果的整數值, d(x,y)=x ⊕y 。
首先,XOR 是一種有效的度量方法(雖然不是歐幾里得幾何意義上的)。很顯然,d(x,x)=0 ;當x 不等于y 時,d(x,y)>0 ,并且對于任意的x,y 來說,d(x,y)=d(y,x) 。XOR 還具有三角特性:d(x,y)+d(y,z) 大于等于d (x,z )。該三角特性可以從如下事實得出:d(x,y) ⊕d(y,z)=d(x,z) ,并且對于任意大于等于0 的a 和b :a+b 都大于等于a ⊕b 。
同時,XOR 也刻畫出了隱含在我們基于二叉樹描繪的系統中距離的概念。在160 位ID 的滿二叉樹中,兩個IDs 間的距離大小就是包含它們的最小子樹的高度。當樹不是滿的時,距離ID x 最近的葉子就是其ID 和x 具有最長的公共前綴的那個葉子。如果樹中有空的分支,那么具有最長公共前綴的葉子就會有多個。此時,我們基于該樹的空分支,把x 對應的位取反得到ID x’ ,那么距離x’ 最近的葉子即為距離x 最近的葉子。利用xor的距離測算,最壞O(n)即可獲得到查詢結果。
routing table路由表是一顆二叉樹,其葉子是 k-buckets 。每個 k-bucket 存放著具有某些公共 ID 前綴的節點。前綴就是該 k-bucket 在二叉樹中的位置。了因此,每個 k-bucket 覆蓋了 ID 空間的某個范圍,所有 k-bucket 合起來完整覆蓋了整個 160 位 ID 空間。
節點是根據需要動態分配到路由樹中的。一開始,節點A的路由樹只有一個節點——覆蓋整個 ID 空間的單個 k-bucket 。當A通過find_node或者get_peer獲取一個新節點的聯系信息時,就會試圖把其插入到相應的 k-bucket 中。如果該 bucket 不滿,簡單將其插入即可。否則,如果該 k-bucket 的區間范圍包含了A自己的節點 ID ,那么該 bucket 會分裂為兩個新的 buckets ,原有的內容會被劃分到這兩個 buckets 中,接著重復插入過程。如果 k-bucket 已滿且不含有A的節點 ID ,那么就直接丟棄這個新的聯系信息。需要注意的是P2P節點在線時間是不穩定的,需要定期去ping檢測每個k-bucket捅中的節點,如果死掉則剔除掉。按照beps005規范沒有更新的節點15分鐘需要檢測一次。每次krpc操作中可以正常響應的則認為是活躍節點。
在Kad網絡中還存在一種高度不平衡的二叉樹均衡的過程,在BT的DHT網絡中,目前沒有看到使用。
BT 爬蟲實現要點
1.先偽裝自己向公共節點進行find_node 查詢,將自己加入網絡。
"router.bittorrent.com:6881","router.utorrent.com:6881","dht.transmissionbt.com:6881",
2.在返回的find_node中,替換返回nodeid的某些n位,造成與返回node節點id不在區間內,繼續探索整個網絡節點分布。
func CreateRandomFindNode(target string) (res []byte, transid string) {msg := make(map[string]interface{}) msg["t"] = TCIDMNGR.GetTranscationID() msg["y"] = "q" msg["q"] = "find_node" msg["a"] = map[string]interface{}{"id": target[:15] + OwnNodeID.ToString()[15:], "target": target} return []byte(PackageMessage(msg)), msg["t"].(string)}
3.一般BT爬蟲被動接收announce_peer 從peer的信息中使用peer wire protocol 獲取 metadata 即種子信息。然后分析torrent中的信息確認文件內容。
4.注意進行ip黑名單過濾,DHT網絡中,存在很多這樣爬蟲,在實現過程中,發現北美有個ip爬蟲瘋狂變更nodeid進行find_node 請求,把內存塞滿的情況(1G),注意控制本地DHT表的總節點數。
5.加速搜索過程,在獲取get_peer請求時進行遞歸get_peer請求,注意控制深度。可以擴大K-bucket桶的大小,降低二叉樹的深度。
6.在使用peer wire protocol過程中,因為運營商對P2P封鎖,很多情況下下載種子失敗,可以使用知名種子庫進行info-hash下載。
7.注意控制udp發包速度,以免被主機商防火墻誤以為中毒。
PT
額外提一點,關于現在的PT,PT網絡是禁止開啟DHT,防止資源外泄。在PT站發布的種子都會包含PT站的私有tracker服務地址,每個會員下載的種子也會包含自己的私有token,當種子泄露到BT上時,PT管理員很容易封禁掉該會員的信息。PT致力于打造高端高活躍的P2P分享網絡。
參考資料
http://www.cs.rice.edu/Conferences/IPTPS02/109.pdf
http://www.bittorrent.org/beps/bep_0005.html
http://www.bittorrent.org/beps/bep_0003.html
http://www.bittorrent.org/beps/bep_0000.html
http://www.bittorrent.org/beps/bep_0009.html
https://wiki.theory.org/index.php/
作者:戚華威
來源:微信公眾號:訊飛技術沙龍
出處:https://mp.weixin.qq.com/s/Y1hMesi54glenNt7n_UCiw
本文發布于:2023-02-28 21:33:00,感謝您對本站的認可!
本文鏈接:http://m.newhan.cn/zhishi/a/1677770944117091.html
版權聲明:本站內容均來自互聯網,僅供演示用,請勿用于商業和其他非法用途。如果侵犯了您的權益請與我們聯系,我們將在24小時內刪除。
本文word下載地址:bt種子是什么意思.doc
本文 PDF 下載地址:bt種子是什么意思.pdf
| 留言與評論(共有 0 條評論) |