以下代码对我有效。将announce_udp()与跟踪器URL和带有所有URL参数的负载字典一起调用。
import binascii, urllib, socket, random, struct
from bcode import bdecode
from urlparse import urlparse, urlunsplit
def announce_udp(tracker,payload):
tracker = tracker.lower()
parsed = urlparse(tracker)
url = parsed.geturl()[3:]
url = "http" + url
hostname = urlparse(url).hostname
port = urlparse(url).port
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(8)
conn = (socket.gethostbyname(hostname), port)
req, transaction_id = udp_create_connection_request()
sock.sendto(req, conn);
buf = sock.recvfrom(2048)[0]
connection_id = udp_parse_connection_response(buf, transaction_id)
s_port = sock.getsockname()[1]
req, transaction_id = udp_create_announce_request(connection_id, payload,s_port)
sock.sendto(req, conn)
print "Announce Request Sent"
buf = sock.recvfrom(2048)[0]
print "Response received"
return udp_parse_announce_response(buf, transaction_id)
def udp_create_announce_request(connection_id, payload, s_port):
action = 0x1
transaction_id = udp_get_transaction_id()
buf = struct.pack("!q", connection_id)
buf += struct.pack("!i", action)
buf += struct.pack("!i", transaction_id)
buf += struct.pack("!20s", urllib.unquote(payload['info_hash']))
buf += struct.pack("!20s", urllib.unquote(payload['peer_id']))
buf += struct.pack("!q", int(urllib.unquote(payload['downloaded'])))
buf += struct.pack("!q", int(urllib.unquote(payload['left'])))
buf += struct.pack("!q", int(urllib.unquote(payload['uploaded'])))
buf += struct.pack("!i", 0x2)
buf += struct.pack("!i", 0x0)
key = udp_get_transaction_id()
buf += struct.pack("!i", key)
buf += struct.pack("!i", -1)
buf += struct.pack("!i", s_port)
return (buf, transaction_id)
def udp_parse_announce_response(buf, sent_transaction_id):
if len(buf) < 20:
raise RuntimeError("Wrong response length while announcing: %s" % len(buf))
action = struct.unpack_from("!i", buf)[0]
res_transaction_id = struct.unpack_from("!i", buf, 4)[0]
if res_transaction_id != sent_transaction_id:
raise RuntimeError("Transaction ID doesnt match in announce response! Expected %s, got %s"
% (sent_transaction_id, res_transaction_id))
print "Reading Response"
if action == 0x1:
print "Action is 3"
ret = dict()
offset = 8;
ret['interval'] = struct.unpack_from("!i", buf, offset)[0]
print "Interval:"+str(ret['interval'])
offset += 4
ret['leeches'] = struct.unpack_from("!i", buf, offset)[0]
print "Leeches:"+str(ret['leeches'])
offset += 4
ret['seeds'] = struct.unpack_from("!i", buf, offset)[0]
print "Seeds:"+str(ret['seeds'])
offset += 4
peers = list()
x = 0
while offset != len(buf):
peers.append(dict())
peers[x]['IP'] = struct.unpack_from("!i",buf,offset)[0]
print "IP: "+socket.inet_ntoa(struct.pack("!i",peers[x]['IP']))
offset += 4
if offset >= len(buf):
raise RuntimeError("Error while reading peer port")
peers[x]['port'] = struct.unpack_from("!H",buf,offset)[0]
print "Port: "+str(peers[x]['port'])
offset += 2
x += 1
return ret,peers
else:
error = struct.unpack_from("!s", buf, 8)
print "Action="+str(action)
raise RuntimeError("Error while annoucing: %s" % error)
def udp_create_connection_request():
connection_id = 0x41727101980
action = 0x0
transaction_id = udp_get_transaction_id()
print "1.Transaction ID :", transaction_id
buf = struct.pack("!q", connection_id)
buf += struct.pack("!i", action)
buf += struct.pack("!i", transaction_id)
return (buf, transaction_id)
def udp_parse_connection_response(buf, sent_transaction_id):
if len(buf) < 16:
raise RuntimeError("Wrong response length getting connection id: %s" % len(buf))
action = struct.unpack_from("!i", buf)[0]
res_transaction_id = struct.unpack_from("!i", buf, 4)[0]
if res_transaction_id != sent_transaction_id:
raise RuntimeError("Transaction ID doesnt match in connection response! Expected %s, got %s"
% (sent_transaction_id, res_transaction_id))
if action == 0x0:
connection_id = struct.unpack_from("!q", buf, 8)[0]
return connection_id
elif action == 0x3:
error = struct.unpack_from("!s", buf, 8)
raise RuntimeError("Error while trying to get a connection response: %s" % error)
pass
def udp_get_transaction_id():
return int(random.randrange(0, 255))