最近突然有了時間,想研究一下 p2p 網絡,先做一個較容易實現的 bt 下載實現,盡量少的使用第3方庫。
先寫第一篇 種子的解析:
種子文件,不是全是 asc2 可見字符,有些是二進制編碼,不能直接看所以需要解析。
Bencode 編碼
https://zh.wikipedia.org/wiki/Bencode
Bencode(發音為Bee-Encode)是BitTorrent用在傳輸數據結構的編碼方式。這種編碼方式支持四種資料類型:
字符串
整數
串列
字典表
Bencode最常被用在.torrent檔中,文件里的元數據都是Bencode過的字典表。其也被用在tracker返回響應時使用。
雖然比用純二進制編碼效率低,但由於結構簡單而且不受字節存儲順序影響(所有數字以十進制編碼)——這對於跨平台性非常重要。而且具有較好的靈活性,即使存在故障的字典鍵,只要將其忽略並更換新的就能兼容補充。
編碼方法
Bencode使用ASCII字符進行編碼。
一個整型數會以十進制數編碼並括在i和e之間,不允許前導零(但0依然為整數0),負數如十進制表示一樣使用前導負號,不允許負零。如整型數“42”編碼為“i42e”,數字“0”編碼為“i0e”,“-42”編碼為“i-42e”。
一個以字節為單位表示的字符串(字符串的字為一個字節,不一定是一個字符)會以(長度):(內容)編碼,長度的值和數字編碼方法一樣,只是不允許負數;內容就是字符串的內容,如字符串“spam”就會編碼為“4:spam”,本規則不能處理ASCII以外的字符串,為了解決這個問題,一些BitTorrent程序會以非標准的方式將ASCII以外的字符以UTF-8編碼轉化后再編碼。
線性表會以l和e括住來編碼,其中的內容為Bencode四種編碼格式所組成的編碼字符串,如包含和字符串“spam”數字“42”的線性表會被編碼為“l4:spami42ee”,注意分隔符要對應配對。
字典表會以d和e括住來編碼,字典元素的鍵和值必須緊跟在一起,而且所有鍵為字符串類型並按字典順序排好。如鍵為“bar”值為字符串“spam”和鍵為“foo”值為整數“42”的字典表會被編碼為“d3:bar4:spam3:fooi42ee”。
對於線性表和字典的取值范圍並沒有限制,他們通常會包含其他元素,這樣就允許對很復雜的數據結構進行編碼。
多文件Torrent的結構的樹形圖為:
Multi-file Torrent
├─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編碼,同上
│ ├─name:推薦的文件夾名,此項可於下載時更改
│ ├─name.utf-8:推薦的文件夾名的utf-8編碼,同上
│ ├─piece length:每個文件塊的大小(Byte)
│ ├─pieces:文件的特征信息(將所有文件按照piece length的字節大小分成塊,每塊計算一個SHA1值,然后將這些值連接起來所組成)
│ ├─publisher:文件發布者的名字
│ ├─publisher-url:文件發布者的網址
│ ├─publisher-url.utf-8:文件發布者網址的utf-8編碼
│ └─publisher.utf-8:文件發布者的名字的utf-8編碼
└─nodes:這個字段包含一系列ip和相應端口的列表,是用於連接DHT初始node
單文件Torrent的結構的樹形圖為:
(各個字段含義同多文件Torrent一樣,只是單文件Torrent沒有files字段)
Single-File Torrent
├─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:所有關於下載的文件的信息
│ ├─length:文件的大小(Byte)
│ ├─name:推薦的文件夾名,此項可於下載時更改
│ ├─name.utf-8:推薦的文件夾名的utf-8編碼,同上
│ ├─piece length:每個文件塊的大小(Byte)
│ ├─pieces:文件的特征信息(將所有文件按照piece length的字節大小分成塊,每塊計算一個SHA1值,然后將這些值連接起來所組成)
│ ├─publisher:文件發布者的名字
│ ├─publisher-url:文件發布者的網址
│ ├─publisher-url.utf-8:文件發布者網址的utf-8編碼
│ └─publisher.utf-8:文件發布者的名字的utf-8編碼
└─nodes:這個字段包含一系列ip和相應端口的列表,是用於連接DHT初始node
比較關鍵的數據:
Tracker服務器列表 文件名 文件大小 文件塊sha1值 都可以直接解析出來
特征碼:SHA1 雖然是20字節 但是顯示的時候,是按16進制顯示就變成了40個字符
特征值 :872FDFA84B97B5EC7BA668CAB5FF2840FE4B220F
下面看 Wireshark 抓包的特征值
是原始20字節的二進制,經過 urlencode 編碼后:%87%2F%DF%A8K%97%B5%EC%7B%A6h%CA%B5%FF%28%40%FEK%22%0F
下面試着 url 解碼看看,結果是亂碼。
關鍵的問題 info_hash 怎么計算出來?
通過上面的介紹,應該明白了,先計算出來 info 字典的 SHA1 的值,然后在進行 urlencode 編碼就能得到這個值,所以要用到2個庫 openssl 中含加密庫 curl 中含 url 功能。
其實,我很輕松的就解碼出來了僅有一個文件的 bt 種子: [NC-Raws] 本田小狼與我 _ Super Cub - 07 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4].torrent
但是又找了另外2個種子,含多個文件的種子,也含有 DTH 網絡的信息的種子解析失敗,原因后面會說。
DTH 網絡信息,放到種子文件的 nodes 里面,重點來了。
專業的事必須使用專業的工具
SHA1 的計算方式,就是從 info 開始,到 nodes 這里,含 0x65 0x35 對應字符 e5 結束的 SHA1 的值,在轉 url 編碼就對了。
找了好幾個種子每次看位置都不一樣,感覺很奇怪,原來是因為 notepad++ hex 這個插件 十六進制和 顯示的 asc2 字符根本對不上,后來換了專業的 winhex 終於確定了是這個位置。
代碼已開源到 github 可以到置頂的貼里面去找。
解析文件列表並計算大小:
訊雷顯示:
1 void Bencode::showFileEntity() 2 { 3 int file_length = 0; 4 std::string file_path = ""; 5 6 for ( auto &file : file_entity_list ) 7 { 8 file_length = file.getLength(); 9 file_path = file.getPath(); 10 11 //filter _____padding_file_0_如果您看到此文件,請升級到BitComet(比特彗星)0.85或以上版本____ 12 13 if ( file_path.find("_____padding_file_" ) ) 14 { 15 LOG_INFO("file length:%d %d.%dMB path:%s", file_length, file_length/1024/1024, 16 (file_length - (file_length/1024/1024 * 1024 * 1024))*1000/1024/1024/10, 17 file_path.c_str()); 18 } 19 } 20 }