Skip to main content
 首页 » 编程设计

python之BitTorrent 客户端 : Getting Peer List From Trackers [Python]

2024年12月31日6cloudgamer

我正在尝试使用 https://wiki.theory.org/BitTorrent_Tracker_Protocol 从跟踪器获取对等列表

但是大多数网络上可用的 torrent 文件都使用 udp跟踪器。上述网站用于 HTTP 跟踪器。带有基于 HTTP 的跟踪器的 torrent 文件会出错。( Passkey error , or hash error or user error or 404 not found response )

现在,我对所有 HTTP 跟踪器的请求代码如下所示:

payload = urllib.urlencode(payload) 
        for ann in self.torrent.announce_list: 
            if("udp" not in ann[0]): 
                url = ann[0] 
                print "Encoded Request URL :",(url + "?" + payload) 
                print "Requesting Tracker for Peer List" 
                try: 
                    response = urllib.urlopen(url + "?" + payload).read() 
                    print "RESPONSE :",response 
                except Exception, e: 
                    print e 

我已经根据 uTorrent 使用 wireshark 发出的请求对我的请求 URL 进行了交叉检查。他们都匹配。信息哈希正在正确生成。

如何从 UDP 和 HTTP 跟踪器检索对等列表?我试图不使用任何像 lib-torrent 这样的外部库。一些指导会非常有帮助。

请您参考如下方法:

以下代码对我有用。使用跟踪器 URL 和带有所有 URL 参数的有效负载字典调用announce_udp()。

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) 
 
    # Teporarly Change udp:// to http:// to get hostname and portnumbe 
    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) 
    #sock.bind((socket.gethostname(),s_port)) 
 
    #Get connection ID 
    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) 
 
    #Annoucing 
    s_port = sock.getsockname()[1] #get port number to which socket is connected 
    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 #action (1 = announce) 
    transaction_id = udp_get_transaction_id() 
    # print "2.Transaction ID :", transaction_id 
    buf = struct.pack("!q", connection_id)                                  #first 8 bytes is connection id 
    buf += struct.pack("!i", action)                                        #next 4 bytes is action  
    buf += struct.pack("!i", transaction_id)                                #followed by 4 byte transaction id 
    buf += struct.pack("!20s", urllib.unquote(payload['info_hash']))        #the info hash of the torrent we announce ourselves in 
    buf += struct.pack("!20s", urllib.unquote(payload['peer_id']))          #the peer_id we announce 
    buf += struct.pack("!q", int(urllib.unquote(payload['downloaded'])))    #number of bytes downloaded 
    buf += struct.pack("!q", int(urllib.unquote(payload['left'])))          #number of bytes left 
    buf += struct.pack("!q", int(urllib.unquote(payload['uploaded'])))      #number of bytes uploaded 
    buf += struct.pack("!i", 0x2)                                           #event 2 denotes start of downloading 
    buf += struct.pack("!i", 0x0)                                           #IP address set to 0. Response received to the sender of this packet 
    key = udp_get_transaction_id()                                          #Unique key randomized by client 
    buf += struct.pack("!i", key) 
    buf += struct.pack("!i", -1)                                            #Number of peers required. Set to -1 for default 
    buf += struct.pack("!i", s_port)                                        #port on which response will be sent 
    return (buf, transaction_id) 
 
def udp_parse_announce_response(buf, sent_transaction_id): 
    #print "Response is:"+str(buf)   
    if len(buf) < 20: 
        raise RuntimeError("Wrong response length while announcing: %s" % len(buf))  
    action = struct.unpack_from("!i", buf)[0] #first 4 bytes is action 
    res_transaction_id = struct.unpack_from("!i", buf, 4)[0] #next 4 bytes is transaction id     
    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; #next 4 bytes after action is transaction_id, so data doesnt start till byte 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: 
        #an error occured, try and extract the error string 
        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                   #default connection id 
    action = 0x0                                    #action (0 = give me a new connection id)    
    transaction_id = udp_get_transaction_id() 
    print "1.Transaction ID :", transaction_id 
    buf = struct.pack("!q", connection_id)          #first 8 bytes is connection id 
    buf += struct.pack("!i", action)                #next 4 bytes is action 
    buf += struct.pack("!i", transaction_id)        #next 4 bytes is 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] #first 4 bytes is action 
 
    res_transaction_id = struct.unpack_from("!i", buf, 4)[0] #next 4 bytes is transaction id 
    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] #unpack 8 bytes from byte 8, should be the connection_id 
        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))