已知infohash与节点时BitTorrent握手空响应问题排查求助
BitTorrent搜索引擎构建问题排查
我正在构建BitTorrent搜索引擎,监听DHT网络的get_peers消息时成功抓取到infohash,接着向对应DHT节点请求peer列表。为验证infohash有效性,我尝试向peer发送BitTorrent握手并对比响应,但除了连接拒绝错误外,多数peer返回空响应,想排查操作是否有误,以下是相关代码:
握手函数
import socket def handshake(infohash, peer): peer_id = b"-TR2940-k8hj0wgej6ch" handshake = b'\x13' handshake += b'BitTorrent protocol' handshake += b'\x00\x00\x00\x00\x00\x10\x00\x00' handshake += infohash handshake += peer_id try: ClientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ClientSocket.settimeout(3) ClientSocket.connect(peer) print("Connected to peer.") ClientSocket.sendall(handshake) response = ClientSocket.recv(68) if not response: print("Empty response.") return print(f"Handshake completed, resp: {response}") ClientSocket.close() except Exception as e: print(e)
从指定infohash和DHT节点获取peer的工具函数
import random import uuid import bencode import socket from struct import unpack import handshake def newTID(tidlen): tid = "" for i in range(0, tidlen): tid += chr(random.randint(97, 122)) return tid def newID(): return uuid.uuid4().hex[0:20] def split_nodes(nodes): length = len(nodes) if (length % 26) != 0: return for i in range(0, length, 26): nid = nodes[i:i+20] ip = socket.inet_ntoa(nodes[i+20:i+24]) port = unpack("!H", nodes[i+24:i+26])[0] yield nid, ip, port UDPClientSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) UDPClientSocket.settimeout(4) def get_peers_from_infohash(infohash, node): get_peers_query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":newID(), "info_hash":infohash}} get_peers_query = bencode.encode(get_peers_query) UDPClientSocket.sendto(get_peers_query, node) received = UDPClientSocket.recvfrom(65536) msg = received[0] decoded = bencode.decode(msg) peers = split_nodes(decoded["r"]["nodes"]) for nid, ip, port in peers: print(infohash, infohash.hex(), ip, port) handshake.handshake(infohash, (ip, port))
DHT爬虫代码
import bencode import socket import uuid from struct import unpack import threading import random import dhtutils def newTID(tidlen): tid = "" for i in range(0, tidlen): tid += chr(random.randint(97, 122)) return tid def newID(): return uuid.uuid4().hex[0:20] def handle_message(msg, node): if msg.get("e"): # print(msg.get("e")) pass elif msg.get("y") == "r": handle_response(msg, node) elif msg.get("y") == "q": handle_query(msg, node) def handle_query(msg, node): try: if msg["q"] == "get_peers": infohash = msg["a"]["info_hash"] # print(infohash.hex(), msg, node) print(infohash.hex()) dhtutils.get_peers_from_infohash(infohash, node) except: pass def handle_response(msg, node): global all_nodes if msg.get("r").get("nodes"): # response from find_nodes nodes = msg.get("r").get("nodes") if nodes: nodes = split_nodes(nodes) for id, ip, port in nodes: find_nodes(id, (ip, port)) all_nodes.append((id, (ip, port))) elif msg.get("t") == "pg": # response from ping id = msg["r"]["id"] all_nodes.append((id, node)) def split_nodes(nodes): length = len(nodes) if (length % 26) != 0: return for i in range(0, length, 26): nid = nodes[i:i+20] ip = socket.inet_ntoa(nodes[i+20:i+24]) port = unpack("!H", nodes[i+24:i+26])[0] yield nid, ip, port def find_nodes(id, node): global UDPClientSocket find_node_query = {"t":newTID(2), "y":"q", "q":"find_node", "a": {"id":newID(), "target":id}} find_node_query = bencode.encode(find_node_query) UDPClientSocket.sendto(find_node_query, node) def ping(node): global UDPClientSocket ping_query = {"t":"pg", "y":"q", "q":"ping", "a":{"id":newID()}} ping_query = bencode.encode(ping_query) UDPClientSocket.sendto(ping_query, node) def listen(): while True: try: received = UDPClientSocket.recvfrom(65536) msg = received[0] src = received[1] decoded = bencode.decode(msg) handle_message(decoded, src) except Exception as e: pass UDPClientSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) T = threading.Thread(target=listen) T.start() nodes = [ ("router.bittorrent.com", 6881), ("dht.transmissionbt.com", 6881), ("router.utorrent.com", 6881) ] for node in nodes: ping(node) all_nodes = [] while True: if len(all_nodes) > 0: for node in all_nodes: find_nodes(node[0], node[1])
问题排查与修复建议
1. 错误处理get_peers响应的核心字段
当前代码只处理了get_peers响应中的nodes字段,但该字段返回的是DHT路由节点,并非实际拥有种子的下载peer。真正的下载peer列表在values字段中,每个peer以6字节格式存储(4字节IP + 2字节端口)。修改get_peers_from_infohash函数:
def get_peers_from_infohash(infohash, node): get_peers_query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":newID(), "info_hash":infohash}} get_peers_query = bencode.encode(get_peers_query) UDPClientSocket.sendto(get_peers_query, node) received = UDPClientSocket.recvfrom(65536) msg = received[0] decoded = bencode.decode(msg) # 优先处理实际下载peer的values字段 if "values" in decoded["r"]: for peer_bytes in decoded["r"]["values"]: if len(peer_bytes) != 6: continue ip = socket.inet_ntoa(peer_bytes[:4]) port = unpack("!H", peer_bytes[4:])[0] print(f"Found download peer: {ip}:{port}") handshake.handshake(infohash, (ip, port)) # 再处理路由节点(可选,用于扩展DHT网络) elif "nodes" in decoded["r"]: peers = split_nodes(decoded["r"]["nodes"]) for nid, ip, port in peers: print(f"Found DHT node: {ip}:{port}") # 路由节点不需要握手,除非要继续查询 # handshake.handshake(infohash, (ip, port))
2. 完善握手响应的接收逻辑
recv(68)可能因网络分片导致未一次性获取完整握手响应,改为循环接收直到拿到足够字节:
# 替换原handshake函数中的response接收部分 response = b"" while len(response) < 68: chunk = ClientSocket.recv(68 - len(response)) if not chunk: print("Connection closed before full handshake.") return response += chunk if len(response) != 68: print(f"Incomplete handshake response, length: {len(response)}") return print(f"Handshake completed, resp: {response}")
3. 其他潜在问题
- peer协议支持:部分peer仅支持uTP协议(基于UDP),不响应TCP握手,可尝试实现uTP握手逻辑。
- peer_id合法性:确保peer_id符合BitTorrent规范(如开头的客户端标识
-TR2940-是Transmission的标准标识,格式无误)。 - 连接频率限制:频繁向同一IP段发起连接可能被屏蔽,添加随机延迟或限制并发连接数。
内容的提问来源于stack exchange,提问作者Thanos Apostolidis
相关产品推荐
相关产品推荐

