BitTorrent协议(六)之种子嗅探器

Sniffer(嗅探器)

实现一个简单的BT种子嗅探器才算是有点实际价值的吧。这样就可以把种子的metadata信息 缓存下来,提供按照文件名进行检索。

不过这里只是实现一个demo,有兴趣的话可以看看github上的dht项目。

基本原理

简单的说,就是把嗅探节点加入到其他节点的路由表中,等待其他节点发来的announce_peer请求,然后获取种子的metadata信息。

我们根据以下的代码具体说明。

  1. 89行代码,向路由节点发送find_node请求。这些路由节点。就是25行代码的3个地址。 这样做的意义有两个,一方面,路由节点会把我们的节点加入到他们的路由表中。另一方面,路由节点会返回一个好节点列表。

  2. 252行代码,启动了6个线程。

线程 说明
listener 代码93行,监听所有接收到的udp数据,并把这些放入到队列recv_q
t_dispatch 代码106行,从队列recv_q中取出数据,并进行bdecoding,根据消息类别分别放入对应的队列。一共有三种:应答,请求,错误。
t_hand_reply 代码132行,从reply_q中取出数据,解码节点列表。每一个节点都插入到本地的路由表(这里不太好,最好先进行ping_node,确定是好节点再加入),并向其发送find_node消息
t_hand_query 代码165行,从query_q中取出请求,分别对四种请求进行应答处理,分别是ping,find_node, get_peers, announce_peer。
t_hand_error 错误消息的处理。
t_handle_metadata 代码234行,从metadata_q中取出已经获取的种子的metadata,打印并把种子保存再字典表中。
  1. 代码165行,t_hand_query线程执行对请求进行处理。
  • ping: 返回本地节点id
  • find_node: 从本地路由表中查出与请求节点id最近的8个节点,并返回。(据说把本地id进行返回,有利于本地节点加入其他路由表)
  • get_peers: 这里进行了简单处理,仅仅返回空节点list。
  • announce_peer: 这个请求是有节点告诉我们有新的种子文件发布了,并告诉我们info_hash。然后我们拿着info_hash取进行获取metadata的操作。 代码207行,开启一个独立的获取metadata的线程。如果获取到metadata,则放入metadata_q中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__author__ = 'ym'
"""
Date : '2019/2/3'
Description :

"""

from ym.bt.routing_table import RoutingTable, BTNode
from ym.bt.util import id_generator, ip_me, decode_compact_node, encode_compact_node, format_size
from ym.bt.bencoding_bin import BEncode
from ym.bt.bdecoding import BDecode
from ym.bt.request_metadata import request_metadata_top
from queue import Queue
import socket
import threading
import logging

log = logging.getLogger()

NODES = [("router.bittorrent.com", 6881),
("router.utorrent.com", 6881),
("dht.transmissionbt.com", 6881)]

ID = id_generator()
SOURCE_IP = ip_me()
SOURCE_PORT = 6881
TIME_OUT = 1

INFO_HASH_METADATA_DIC = {}
RLOCK = threading.RLock()


def boot_step(sock: socket.socket, local_node: BTNode):
# send find_node query to NODES
try:
dic = {"t": "aa", "y": "q", "q": "find_node",
"a": {"id": local_node.id.hex(), "target": local_node.id.hex()}}
data = BEncode(dic).encode_bin()

for NODE in NODES:
dst_ip, dst_port = NODE
sock.sendto(data, (dst_ip, dst_port))
print("boot_step|send|dst_ip={}|dst_port={}".format(dst_ip, dst_port))
except Exception as e:
log.exception(e)
raise Exception("boot_step error")


def request_metadata_thread(peer_id: bytes, info_hash, ip, port, metadata_q: Queue):
try:
print("request_metadata_thread|info_hash={}|ip={}|port={}".format(info_hash, ip, port))
metadata_dic = request_metadata_top(peer_id, info_hash, ip, port)
if metadata_dic is not None:
metadata_q.put((metadata_dic, info_hash, ip, port))
except Exception as e:
log.exception("request_metadata_thread error")
raise Exception("request metadata error")


def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setblocking(True)
sock.bind((SOURCE_IP, SOURCE_PORT))

# recv queue
recv_q = Queue(maxsize=0)
# reply queue
reply_q = Queue(maxsize=0)
# query queue
query_q = Queue(maxsize=0)
# error queue
error_q = Queue(maxsize=0)
# request metadata
metadata_q = Queue(maxsize=0)

# declare routing table
rt = RoutingTable()

local_node = BTNode(id=ID, ip=SOURCE_IP, port=SOURCE_PORT)
print("local_node={}".format(local_node))
rt.insert(local_node)
# 1. boot step
print("boot start.")
boot_step(sock, local_node)
print("boot end.")

# 2. listener
def recv_listener(sock: socket.socket, recv_q: Queue):
while True:
try:
data, addr = sock.recvfrom(2048)
# print("recv_listener|recv|addr={}".format(addr))
recv_q.put((data, addr))
except Exception as e:
print("recv_listener error")

listener = threading.Thread(target=recv_listener, args=(sock, recv_q,))
listener.daemon = True

# 3. handle recv msg
def dispatch_recv(recv_q: Queue, reply_q: Queue, query_q: Queue, error_q: Queue):
while True:
data, addr = recv_q.get()
try:
dic, i, n = BDecode(data).parse()
# print("dispatch_recv|dic={}".format(dic))
if dic.get("y") is not None:
v = dic['y']
if v == 'r':
# reply msg
reply_q.put((dic, addr))
elif v == 'q':
# query msg
query_q.put((dic, addr))
elif v == 'e':
# error msg
error_q.put((dic, addr))
else:
print("unknown type msg.")
except Exception as e:
log.exception('dispatch_recv error|data={}'.format(data))

t_dispatch = threading.Thread(target=dispatch_recv, args=(recv_q, reply_q, query_q, error_q,))
t_dispatch.daemon = True

# 4. handle reply
def handle_reply(sock: socket.socket, reply_q: Queue, rt: RoutingTable):
while True:
dic, addr = reply_q.get()
try:
r = dic['r']
src_id = r['id']
src_ip, src_port = addr
src_node = BTNode(id=src_id, ip=src_ip, port=src_port)
rt.insert(src_node)
print("rt.size={}".format(rt.size()))
# print("handle_reply|src_ip={}|src_port={}".format(src_ip, src_port))
if r.get('nodes') is not None:
# ping every node and put good in rt.
nodes = decode_compact_node(r['nodes'])
dic = {"t": "aa", "y": "q", "q": "find_node",
"a": {"id": local_node.id.hex(), "target": local_node.id.hex()}}
data = BEncode(dic).encode_bin()
for node in nodes:
try:
dst_ip, dst_port, id = node
sock.sendto(data, (dst_ip, dst_port))
except Exception as e:
log.exception("send node error|node={}".format(node))
elif r.get('peers') is not None:
# ping every peer and put good in rt.
print("handle_reply|peers")
except Exception as e:
log.exception("handle_reply error|dic={}".format(dic), e)

t_hand_reply = threading.Thread(target=handle_reply, args=(sock, reply_q, rt,))
t_hand_reply.daemon = True

# 5. handle query
def handle_query(sock: socket.socket, query_q: Queue):
while True:
dic, addr = query_q.get()
dst_ip, dst_port = addr
try:
print("handle_query|addr={}|dic={}".format(addr, dic))
if dic['q'] == 'ping':
# handle ping query
reply = {"t": dic['t'], "y": "r", "r": {"id": local_node.id.hex()}}
data = BEncode(reply).encode_bin()
sock.sendto(data, (dst_ip, dst_port))
# print("handle_query|ping|send_reply|dst_ip={}|dst_port={}|data={}".format(dst_ip, dst_port, data))
elif dic['q'] == 'find_node':
# handle find_node query
nodes = rt.find_closer(bytes.fromhex(dic['a']['id']))
print("local_node.id={}".format(local_node.id.hex()))
print(nodes)
compact_node = encode_compact_node(nodes)
reply = {"t": dic['t'], "y": "r", "r": {"id": local_node.id.hex(), "nodes": compact_node}}
data = BEncode(reply).encode_bin()
sock.sendto(data, (dst_ip, dst_port))
print("handle_query|find_node|send_reply|dst_ip={}|dst_port={}|data={}".format(dst_ip, dst_port,
data))
elif dic['q'] == 'get_peers':
# handle get_peers query
reply = {"t": dic['t'], "y": "r",
"r": {"id": local_node.id.hex(), "token": "alenym".encode("utf8"), "nodes": b""}}
data = BEncode(reply).encode_bin()
sock.sendto(data, (dst_ip, dst_port))
print("handle_query|get_peers|send_reply|dst_ip={}|dst_port={}|data={}".format(dst_ip, dst_port,
data))
elif dic['q'] == 'announce_peer':
# handle announce_peer
print("handle_query|announce_peer|dic={}".format(dic))
info_hash = dic['a'].get('info_hash')
with RLOCK:
if info_hash is None or info_hash in INFO_HASH_METADATA_DIC:
continue

target_ip = dst_ip
target_port = dst_port

t_request_meta = threading.Thread(target=request_metadata_thread,
args=(
local_node.id, info_hash, target_ip, target_port, metadata_q))
t_request_meta.start()


else:
print("handle_query|other|dic={}".format(dic))
except Exception as e:
log.exception("handle_query error|dic={}".format(dic))

t_hand_query = threading.Thread(target=handle_query, args=(sock, query_q,))
t_hand_query.daemon = True

# 6. handle error
def handle_error(error_q: Queue):
while True:
dic, addr = error_q.get()
try:
print("handle_error|addr={}|dic={}".format(addr, dic))
except Exception as e:
log.exception('handle_error error', e)

t_hand_error = threading.Thread(target=handle_error, args=(error_q,))
t_hand_error.daemon = True

# 7. handle requested metadata
def handle_metadata(metadata_q: Queue):
while True:
try:
metadata_dic, info_hash, ip, port = metadata_q.get()
format_s = "handle_metadata|info_hash={}|ip={}|port={}|name={}|size={}"
file_name = metadata_dic['name']
size = format_size(metadata_dic.get('length'))
print(format_s.format(info_hash, ip, port, file_name, size))
with RLOCK:
if info_hash not in INFO_HASH_METADATA_DIC:
INFO_HASH_METADATA_DIC[info_hash] = metadata_dic
except Exception as e:
log.exception("handle_metadata error")


t_handle_metadata = threading.Thread(target=handle_metadata, args=(metadata_q,))
t_handle_metadata.daemon = True

listener.start()
t_dispatch.start()
t_hand_reply.start()
t_hand_query.start()
t_hand_error.start()
t_handle_metadata.start()

listener.join()
t_dispatch.join()
t_hand_reply.join()
t_hand_query.join()
t_hand_error.join()
t_handle_metadata.join()


if __name__ == '__main__':
main()

运行日志

我们在一台有公网ip的电脑上运行十几分钟,过滤日志,查看接收到的种子信息如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
root@ubuntu:~# cat log.txt | grep "handle_metadata"
handle_metadata|info_hash=5553330daa12bde6a2f71ab26f7b26688219f276|ip=69.80.12.126|port=11715|name=War for the Planet of the Apes 2017 1080p BluRay x264 DTS 5.1 MSubS-Hon3y|size=None
handle_metadata|info_hash=5556f3a9c605dd009f92fddb91848f565439e4f4|ip=59.169.228.207|port=62215|name=(C94) [くろすこスイッチ (くろすこ)] 冷泉さんといちゃいちゃする本 (ガールズ&パンツァー).zip|size=7.80M
handle_metadata|info_hash=555333edb2519c3aa93db4a150f6e021f5a138ff|ip=96.40.42.32|port=32796|name=Princess Go Round|size=None
handle_metadata|info_hash=572d4df79a7151d9466d371b960cfece91289bf4|ip=1.175.76.59|port=13283|name=(同人ゲーム) [181102][RJ237312][ピンポイント/キングピン] 妻が隠していたビデオ…~元カレ寝取らせ観察記~ DL版 (files).rar|size=1.08G
handle_metadata|info_hash=572edd8670066945538e4fe823294b45f2c2e3a3|ip=14.199.224.142|port=22979|name=0407raw021|size=None
handle_metadata|info_hash=574b27358fc1604e65babf32abe839267d36ba4b|ip=14.199.224.142|port=22979|name=o0Akrios0o@www.sexinsex.com@假裝租屋 事實上在陽台勾引住宅區人妻|size=None
handle_metadata|info_hash=54730eeeb5d74a58f49c6da72eb90be922556f0a|ip=219.98.7.166|port=18586|name=DSAM-29-DVD|size=None
handle_metadata|info_hash=57039c3ef3343c5b288c5ff219c66f837a88e808|ip=14.199.224.142|port=22979|name=tmem|size=None
handle_metadata|info_hash=5687578db92c7df4ae3ab7e33c895f04b1e27d37|ip=14.199.224.142|port=22979|name=GNE-148|size=None
handle_metadata|info_hash=57b832066b2103e29f3a4af256e25175da6dbdc2|ip=14.199.224.142|port=22979|name=[Thz.la]supa-154|size=None
handle_metadata|info_hash=56936a12cdb3a1a837c8faa206a2dea189dadf66|ip=14.199.224.142|port=22979|name=club-022_1.wmv|size=2.96G
handle_metadata|info_hash=570e89058161385b1d7dfadfcdc2d9f276ab829b|ip=14.199.224.142|port=22979|name=avidol.us-PTBI-026.wmv|size=2.71G
handle_metadata|info_hash=57c30a1ddecac71142208425c610837b961f4de6|ip=14.199.224.142|port=22979|name=[HD]sad-039.wmv|size=3.52G
handle_metadata|info_hash=56794273cf092e0b4f671885a16cf2dbe484f559|ip=14.199.224.142|port=22979|name=KRE-002.wmv|size=1.20G
handle_metadata|info_hash=5710c35d85a9a0ed8cadc10bdcd09db5bb0c46fd|ip=14.199.224.142|port=22979|name=0111-xv1088|size=None
handle_metadata|info_hash=561b55ec9a956f208f58b798d781bd5577a47b9e|ip=14.199.224.142|port=22979|name=52.R18-099|size=None
handle_metadata|info_hash=57c262437e7cf0ca5f24bff947757026f9cef9a8|ip=14.199.224.142|port=22979|name=DF-35976|size=None
handle_metadata|info_hash=565bedb7fc17d20f017a668e2931cb1d30b53556|ip=14.199.224.142|port=22979|name=judexkwok@片瀬まこ合集06|size=None
handle_metadata|info_hash=57e93ca1527cd35045f0ca75d0c96579dcf96d2a|ip=14.199.224.142|port=22979|name=SMDV-10-DVD|size=None
handle_metadata|info_hash=56747025a196cb78db9f94d8ca933677f57e54b0|ip=14.199.224.142|port=22979|name=HUNT-759.mp4|size=2.01G
handle_metadata|info_hash=56e45f76194dadb2c799c7f2c9b34bc2fff07cee|ip=14.199.224.142|port=22979|name=0510-sama538|size=None
handle_metadata|info_hash=57fae1a68ee9241a593c1623f64feb7927b40469|ip=14.199.224.142|port=22979|name=[thz.la]chunta-219|size=None
handle_metadata|info_hash=56b89964da08b923b20722b89ccfc6ae4928aca3|ip=14.199.224.142|port=22979|name=HUNT-665_2.mp4|size=1.60G
handle_metadata|info_hash=57528d8633ee8b823aea17e47def172082072dd5|ip=14.199.224.142|port=22979|name=HUNT|size=None
handle_metadata|info_hash=57b14ca4cf260763e170e515b1f235ae5dca487f|ip=14.199.224.142|port=22979|name=SAMA-477|size=None
handle_metadata|info_hash=57bc38a6f182dbd6be5b42371d645cc43cfa12e1|ip=14.199.224.142|port=22979|name=HUNT-710.mp4|size=1.82G
handle_metadata|info_hash=57f2b65672462ff81834a928cf8e9863687dc220|ip=14.199.224.142|port=22979|name=3208|size=None
...

有些种子文件为什么没有文件大小呢?例如size=None,

1
handle_metadata|info_hash=54730eeeb5d74a58f49c6da72eb90be922556f0a|ip=219.98.7.166|port=18586|name=DSAM-29-DVD|size=None

用info_hash过滤日志,可以看到metadata包含了一个files项,该项包含了多个文件。

1
2
root@ubuntu:~# cat log.txt | grep "54730eeeb5d74a58f49c6da72eb90be922556f0a"| grep "metadata_dic" 
request_metadata|info_hash=54730eeeb5d74a58f49c6da72eb90be922556f0a|metadata_dic={'files': [{'ed2k': b'\xc4"%lT\xa14s\xd86\xd7a\xb1:\x8bC', 'filehash': '788c531731917e92334c03423df5433b4d3d941d', 'length': 228076, 'path': ['C9~Fang Ping Bi Cheng Xu防屏蔽程序.rar'], 'path.utf-8': ['C9~Fang Ping Bi Cheng Xu防屏蔽程序.rar']}, {'ed2k': b'\xff,5\xb4W\x1b\xf1\xb0\x16\x13=\x0c\xc1\xc5\xb56', 'filehash': 'b760813c2b6690123eb174b5561ce6b4a0b6e000', 'length': 1966849, 'path': ['HOTAVXXX,Free Adult Movie, Fastest & Newest Porn Movie Site.jpg'], 'path.utf-8': ['HOTAVXXX,Free Adult Movie, Fastest & Newest Porn Movie Site.jpg']}, {'ed2k': b'\xfaggnH\xf8e}O\x97\x04\xbb\x86\x9e\x92\xf5', 'filehash': '9a8b43c663ecc46d3a29196cf527d8431fef842b', 'length': 180, 'path': ['HOTAVXXX~最新最快的AV影片每日更新.url'], 'path.utf-8': ['HOTAVXXX~最新最快的AV影片每日更新.url']}, {'ed2k': b'\xff,5\xb4W\x1b\xf1\xb0\x16\x13=\x0c\xc1\xc5\xb56', 'filehash': 'b760813c2b6690123eb174b5561ce6b4a0b6e000', 'length': 1966849, 'path': ['HOTAVXXX、自由な成人映画、最も速く&最新ポルノ映画サイト.jpg'], 'path.utf-8': ['HOTAVXXX、自由な成人映画、最も速く&最新ポルノ映画サイト.jpg']}, {'ed2k': b'$\x13\xfd\\\x8b\xa5\xee=\xc0\xfb\x88\xff~\x03/\x18', 'filehash': 'dddbfe3e917739e1da7afcdd8a78e1a6677b6648', 'length': 235, 'path': ['QQ愛真人視頻交友聊天室.url'], 'path.utf-8': ['QQ愛真人視頻交友聊天室.url']}, {'ed2k': b'/\xe2\xcf\x1b\xc06\xea[\xda\x93\xb9\xd0\\\xc1\x14\x98', 'filehash': '715c4e653db4bb74bb7e40073d55113f512299e9', 'length': 628211, 'path': ['SIS001全面封殺.jpg'], 'path.utf-8': ['SIS001全面封殺.jpg']}, {'ed2k': b'\xf3\x19\x893\x8b\r\xc4\r\x99|W)\xf4\xce\x00J', 'filehash': 'c4eadc74cdbf36df8a66710f62932a60b1a0949a', 'length': 267, 'path': ['[城風 - C9]~成人精品長篇區 最新http一手資源.url'], 'path.utf-8': ['[城風 - C9]~成人精品長篇區 最新http一手資源.url']}, {'ed2k': b'r\xbab\x10i\xc4\x10<f$\x10+p\x84\xd2Z', 'filehash': 'c6a1cb245511e4013c7510220ce3a6d912f8290f', 'length': 217, 'path': ['dioguitar23(第六天魔王)@草榴社區.url'], 'path.utf-8': ['dioguitar23(第六天魔王)@草榴社區.url']}, {'ed2k': b'\xe4z.\xa4\xfa\x88h\xfe\x07\xedy\xc3\x13D\xe8\x7f', 'filehash': 'dddd22e1c4027a8e5c6cec98a4ef3dd29b951682', 'length': 229, 'path': ['dioguitar23@ HD1080.org.url'], 'path.utf-8': ['dioguitar23@ HD1080.org.url']}, {'ed2k': b'\xf9\xee!X\xb3\x9d\xd9\x104\xc52\xc4\x872&a', 'filehash': '7191792635319be376c5ff3edd251e7bda81d069', 'length': 267, 'path': ['dioguitar23@AV 天空.url'], 'path.utf-8': ['dioguitar23@AV 天空.url']}, {'ed2k': b'\xba\x18f&*y&\xf5H\xe8\x1f;8x\x1eA', 'filehash': 'd774c64c18ec44e607ba6825675e841332a0d4f4', 'length': 188, 'path': ['dioguitar23@D.C.資訊交流網.url'], 'path.utf-8': ['dioguitar23@D.C.資訊交流網.url']}, {'ed2k': b'\x91\x97\x15O\rxh\x7fa\xbf^\xbf \x187K', 'filehash': 'f06835576489512e2c4a3ff58a5c4a2fed3131a3', 'length': 174, 'path': ['dioguitar23@KTzone.url'], 'path.utf-8': ['dioguitar23@KTzone.url']}, {'ed2k': b"\xf7\xf1\x9c\xa0+\x15\xc5_\xdb2u'\x81\xcb\xefU", 'filehash': '25a54a9fdab494a60fdbd206089ee75c08913f24', 'length': 235, 'path': ['dioguitar23@SexInSex! Board.url'], 'path.utf-8': ['dioguitar23@SexInSex! Board.url']}, {'ed2k': b'\x10k\x08\xafU\xcb\x05\x8b\x0f\xf9\xaf\xcb\r\xdd\xf3\xc4', 'filehash': 'a9bb602e06bb33483a1960db95bc65b535831c5d', 'length': 1086, 'path': ['dioguitar23@公仔箱論壇.url'], 'path.utf-8': ['dioguitar23@公仔箱論壇.url']}, {'ed2k': b'6-*X\x12\xf3f\xbe\xd8q\x15\xbe\x91[\xee4', 'filehash': 'd19dd08e932c7db940491e5ac259c6ad0ff225c3', 'length': 188, 'path': ['dioguitar23@痴漢俱樂部.url'], 'path.utf-8': ['dioguitar23@痴漢俱樂部.url']}, {'ed2k': b'\x89\xe1CN\xef\xd1\x83\xc2\x8c?,|;\xdb\t\x10', 'filehash': '47255a6ce6a43dc9af1de780fcffd210de793bfd', 'length': 190, 'path': ['dioguitar23@香港廣場.url'], 'path.utf-8': ['dioguitar23@香港廣場.url']}, {'ed2k': b'\xaaJh&\x1f\x88\x93\xda\x9bL\xca\xd9\x9c+V\xa9', 'filehash': '168c3faa1f01a31b31c8d5a1bb7e699333e0bfe4', 'length': 226, 'path': ['dioguitar23_Plus28 討論區.url'], 'path.utf-8': ['dioguitar23_Plus28 討論區.url']}, {'ed2k': b"\x04k`{\x0b'\xee\x80\xb4\x8e\x91=\xa92\xaf\x95", 'filehash': 'c7181e934379905222bf363317d8ae8ddfb12eb4', 'length': 220, 'path': ['dioguitar23_Touch99.url'], 'path.utf-8': ['dioguitar23_Touch99.url']}, {'ed2k': b'\xaf\xbe+\xd5\xde\x96\xdc\xe0\xa8a\x18\x87b\x8d\xf6\x0c', 'filehash': 'a92caa360c647d14ae05655c6e43e9f28d7edcf5', 'length': 208, 'path': ['dioguitar23_WK綜合論壇.url'], 'path.utf-8': ['dioguitar23_WK綜合論壇.url']}, {'ed2k': b'\x05>\x9b.\nS\xb2\x9c&L\xbc5H\x82\xcc\xcb', 'filehash': '6fa079db3cafb196163e9a6b126f60a5d4aa85b1', 'length': 156, 'path': ['dioguitar23_mimip2p.url'], 'path.utf-8': ['dioguitar23_mimip2p.url']}, {'ed2k': b'\xc3|\x1fKA\xb7\xdd\xb9\x87\xd3}\x04\x8f}\xb3m', 'filehash': '1a709fb06ff75ced80995ab8fcdefa69744d7aa0', 'length': 229, 'path': ['dioguitar23_※=色界论坛=※ (开放注册) 色界论坛.url'], 'path.utf-8': ['dioguitar23_※=色界论坛=※ (开放注册) 色界论坛.url']}, {'ed2k': b'>\x13\x98U3q\xf6u\xe6\x06\xc4\xa4\x91\xb24\xc5', 'filehash': '350cdfa69ce763f8c2fad85e1f0f4323c3fb5dfa', 'length': 208, 'path': ['dioguitar23_九九情色帝国.url'], 'path.utf-8': ['dioguitar23_九九情色帝国.url']}, {'ed2k': b'E\x0c\x8a\xed5{\x00\xe6\xdb\xfc\x9b\xb1|U%\xf5', 'filehash': '384d925cb1da6605d49014622ed9f0b455efe343', 'length': 261, 'path': ['dioguitar23_性吧春暖花开,春暖花开性吧有你.url'], 'path.utf-8': ['dioguitar23_性吧春暖花开,春暖花开性吧有你.url']}, {'ed2k': b'\xefE\xbe\xc2\xcf(\xf5\xe3\x18\x04\xdan\x8f\x08j\xcf', 'filehash': '2509953b0831793667a904ee08229da3114835cf', 'length': 214, 'path': ['dioguitar23_找樂子論壇.url'], 'path.utf-8': ['dioguitar23_找樂子論壇.url']}, {'ed2k': b"QuDc'zc]\xcb\xeb\xc0\x86-\xfe\xa5\xf4", 'filehash': '2e92574d6c46add45048b08aa15b4eae132c2cc1', 'length': 235, 'path': ['dioguitar23_無限討論區.url'], 'path.utf-8': ['dioguitar23_無限討論區.url']}, {'ed2k': b'tf\xe0\xda\x8c&\x96A\x9d\x9e~P\xe9\xd6\x83\xdb', 'filehash': '6b2a83a72df99bd7f2a197cb6fd423a41e061b70', 'length': 138, 'path': ['hav.tv-新幹線ONLINE~慶祝開站包月對折優惠~免費影片天看到爽!!.url'], 'path.utf-8': ['hav.tv-新幹線ONLINE~慶祝開站包月對折優惠~免費影片天看到爽!!.url']}, {'ed2k': b'\'\xf0\x08\xf1 {\xa9"\x13//(\xc7o\x9d`', 'filehash': '226f0d140af4689833613f0f21a95608fff324ef', 'length': 4324384768, 'path': ['hotavxxx.com_DSAM-29.ISO'], 'path.utf-8': ['hotavxxx.com_DSAM-29.ISO']}, {'ed2k': b'\x92]\xef\x0b\xa5\xee\xff\xd9\xb9\xd2\xd2\xc2J\x9e\x1a\xdc', 'filehash': 'eb8316fc354049e3bc1fb251bf6928dc55ed157a', 'length': 131523, 'path': ['hotavxxx.com_DSAM-29.jpg'], 'path.utf-8': ['hotavxxx.com_DSAM-29.jpg']}, {'ed2k': b'0\xaf\xc8Q\xc9\xa3\xfb\xacn\xf4\xde\xc1\r\x9c\xda\xbd', 'filehash': 'aa6975d6341d2a079661c066eb5244ee0fd1aaff', 'length': 2254263, 'path': ['hotavxxx.com_DSAM-29A.jpg'], 'path.utf-8': ['hotavxxx.com_DSAM-29A.jpg']}, {'ed2k': b'\xa1\xaf\x1b\xd7\x84\xd2g\x87\x99>\x82\xec\x94j\xb1U', 'filehash': '7254c5e02a0119f38e9c0e7576a1d8ac04b59f67', 'length': 214, 'path': ['❤dioguitar23❤18P2P-2013年04月2日 18p2p開放註冊3天.url'], 'path.utf-8': ['❤dioguitar23❤18P2P-2013年04月2日 18p2p開放註冊3天.url']}, {'ed2k': b'\xe0\x82]\xe7\xe2\xc3\xe0\x9bn\x81\x1d\xa4\x0fr\x9f+', 'filehash': 'cad3021392437e48d35faa57861e8ce0a89a6a0d', 'length': 233, 'path': ['アジア表動画公開板 [城風 - C9].url'], 'path.utf-8': ['アジア表動画公開板 [城風 - C9].url']}, {'ed2k': b'\x9a\xdeh5D\x1e}5`FeM\xf3\xcf\x1dt', 'filehash': '37402d4e9e9fedb26ff5c833fbc399fb5462a4af', 'length': 168, 'path': ['京色屋~即日起大降價.一片只要30元起.藍光片只要150元.url'], 'path.utf-8': ['京色屋~即日起大降價.一片只要30元起.藍光片只要150元.url']}, {'ed2k': b'W\x9a\xf4E\x1d\xb0b(\x05_v\x00\xef\xe5\xe4\xe6', 'filehash': '6d6df82810c4aa00152c3912d28bc13b6589a780', 'length': 223, 'path': ['堂本屋 TW DMM~即日起大降價.一片只要30元起.藍光片只要150元.url'], 'path.utf-8': ['堂本屋 TW DMM~即日起大降價.一片只要30元起.藍光片只要150元.url']}, {'ed2k': b'\x83\x05t=\xda\x85\x84\xbc\x1c\xb3J\xac\x04\x94\xfea', 'filehash': '52dcc77f3f2aad3ffd1cf4af73157deb1aeb8f4c', 'length': 2994, 'path': ['更多精彩.rar'], 'path.utf-8': ['更多精彩.rar']}, {'ed2k': b'<\xf8Y\xfd\x08\x99.\xc5\xec\x0e\x03V\r\xb4%\x06', 'filehash': 'ab91ce91df44509574450c875435f3c2a7bf18cf', 'length': 226, 'path': ['金花娱乐城-澳门网上真人赌博,90后性感荷官24小时在线存提款(5分钟到账).url'], 'path.utf-8': ['金花娱乐城-澳门网上真人赌博,90后性感荷官24小时在线存提款(5分钟到账).url']}, {'ed2k': b'\xfc*"\xad\xdbva\xdf\xbf\x92\x0e\x83.\x08/\x84', 'filehash': 'b60f8d54f1756a04753905be9a9dc7982f33ad14', 'length': 235, 'path': ['魔王の家-情慾視界~最新最快的成人資訊平台 魔王の家,http--bbs.hotavxxx.com.url'], 'path.utf-8': ['魔王の家-情慾視界~最新最快的成人資訊平台 魔王の家,http--bbs.hotavxxx.com.url']}], 'name': 'DSAM-29-DVD', 'name.utf-8': 'DSAM-29-DVD', 'piece length': 1048576, 'pieces': ['557c428f62f38fe8ac8d11411bc9796abb28254d', '53effb4976edd7d32c9be7a0315bfd40ff93875e', 'c38ea041059367193bc3e9cc6fac40f67cfb377f', '63d640ba1942f834e4ec0340f84d8c0703a1f992', '8b783f6a2dc8ec649ad78acc8c49f892a8bf7663', '961a8f0b0cdfe010c979061082982c3548173948', '57dd2532983cf4d30bd442281d007ce0189b6eac', 'd858de50e2a5d5dd60945ac687baa159697d88cd', 'f7a262965c87d1c4d3cc10660176c4d063d16807', '43f7e4f982c4a47d94757071e8b280798d5fbfa9', ...