Esempio n. 1
0
 def request_send(self, event_dispatcher, result_callback, error_callback):
    """Initiate announce sequence"""
    if not (self.state is None):
       raise TrackerRequestError("Request {0!a} is still pending.".format(self.sock))
    
    self.result_callback = result_callback
    self.error_callback = error_callback
    
    self.ed = event_dispatcher
    
    try:
       tracker_addrinfo = random.choice(socket.getaddrinfo(*self.address_get()))
    except socket.error as exc:
       self.log(35, "Connection to tracker {0} failed; found no valid records. Faking timeout.".format(self.address_get()))
       self._timeout_fake()
       return
    
    tracker_AF = tracker_addrinfo[0]
    rsock = socket.socket(tracker_AF, socket.SOCK_DGRAM)
    
    self.sock = AsyncPacketSock(event_dispatcher, rsock)
    self.sock.process_input = self.frame_process
    self.sock.process_close = (lambda *args: None)
     
    self.transaction_id = self.tid_generate()
    self.state = 0
    self.tracker_address = tracker_addrinfo[4][:2]
    
    try:
       self.frame_send(self.frame_build_init())
    except socket.error as exc:
       # Ugly hack, but modeling explicitly reported send failure different
       # from a timeout isn't worth the hassle.
       self.log(30, 'Unable to contact tracker {0}: UDP sendto() failed with \'{1}\'. Faking timeout.'.format(self.address_get(),exc))
       self._timeout_fake()
       return
    self.timeout_timer = event_dispatcher.set_timer(self.TIMEOUT, self.timeout_handle)
Esempio n. 2
0
class UDPTrackerRequest(TrackerRequest):
   proto = b'udp'
   pm_in_init = '>llq'
   pm_in_announce = '>lllll'
   CONNECTION_ID_DEFAULT = 0x41727101980
   ACTION_CONNECT = 0
   ACTION_ANNOUNCE = 1
   ACTION_SCRAPE = 2
   ACTION_ERROR = 3
   EXT_AUTH = 1
   
   TIMEOUT = 50
   
   EVENT_MAP = {
      None:0,
      b'completed':1,
      b'started':2,
      b'stopped':3
   }
   
   def __init__(self, *args, **kwargs):
      TrackerRequest.__init__(self, *args, **kwargs)
      self.sock = None
      self.timeout_timer = None
      self.close()
   
   @staticmethod
   def tid_generate():
      """Return a random 32bit signed integer"""
      return random.randint(-1*2**31,2**31-1)
   
   def frame_build_init(self, tid=None):
      """Build and return frame for initiating session to tracker"""
      if (tid is None):
         tid = self.transaction_id
      return struct.pack('>qll', self.CONNECTION_ID_DEFAULT,
         self.ACTION_CONNECT, tid)
   
   def numwant_get(self):
      if (self.numwant is None):
         return -1
      return self.numwant
   
   def frame_build_announce(self, tid=None, ip_address=0, extensions=0):
      """Build and return announce frame"""
      if (tid is None):
         tid = self.transaction_id
      
      key = self.key
      if (key is None):
         key = ''
      key = key[-4:]
      return struct.pack('>qll20s20sqqqlL4slHH', self.connection_id,
         self.ACTION_ANNOUNCE, tid, self.info_hash, self.peer_id,
         self.downloaded, self.left, self.uploaded, self.EVENT_MAP[self.event],
         ip_address, key, self.numwant_get(), self.port, extensions)
    
   @staticmethod
   def frame_parsebody_init(data):
      """Parse init response fragment (packet minus initial 8 bytes) and return contents"""
      if (len(data) != 8):
         raise ValueError('Data {0!a} invalid; expected exactly 8 bytes.'.format(data,))
      return struct.unpack('>q', data)[0]
   
   def frame_parsebody_announce(self, data):
      """Parse announce response body (packet minus initial 8 bytes) and return contents"""
      if (len(data) < 12):
         raise ValueError('Data {0!a} invalid; expected at least 12 bytes.'.format(data,))
      if (((len(data) - 12) % 6) != 0):
         raise ValueError('Data {0!a} invalid; length {1} does not satisfy (((l - 12) % 6) == 0) condition.'.format(data, len(data)))
      
      (interval, seeders, leechers) = struct.unpack('>lll', data[:12])
      peers = BTPeer.seq_build(data[12:])
      
      peers_filtered = []
      for peer in peers:
         if ((int(peer.ip) == 0) or (peer.port == 0)):
            self.log(30, '{0} for invalid BTPeer data {1!a}. Discarding.'.format(self, peer))
            continue
         peers_filtered.append(peer)
      
      peers = peers_filtered
      
      # Build response data manually, since it doesn't exist at protocol level
      response_data = {
         b'peers': peers,
         b'interval':interval,
         b'complete':seeders,
         b'incomplete':leechers
         }
      return response_data
   
   def timeout_handle(self):
      """Handle session timeout."""
      self.log(30, '{0!a} (tracker {1!a}) timeouted in state {2!a}.'.format(self, self.announce_url, self.state))
      self.timeout_timer = None
      self.error_callback(TrackerResponseError('Session to tracker {0!a} timeouted in state {1!a}.'.format(self.announce_url, self.state)))
      self.close()
       
   def frame_process_init(self, data):
      """Process init response"""
      if (self.state != 0):
         raise TrackerResponseError('{0!a}.frame_process_init({1!a}) got called while state == {2!a}.'.format(self, data, self.state))
      self.connection_id = self.frame_parsebody_init(data)
      self.transaction_id = self.tid_generate()
      self.state = 1
      try:
         self.frame_send(self.frame_build_announce())
      except socket.error as exc:
         self.log(30, 'Unable to contact tracker {0}: UDP sendto() failed with \'{1}\'. Faking timeout.'.format(self.address_get(),exc))
         self._timeout_fake()
         return
      
   def frame_process_announce(self, data):
      """Process announce response"""
      if (self.state != 1):
         raise TrackerResponseError('{0!a}.frame_process_announce({1!a}) got called while state == {2!a}.'.format(self, data, self.state))
       
      response_data = self.frame_parsebody_announce(data)
      self.result_callback(self, response_data)
      self.close()
    
   def frame_process_error(self, data):
      """Process error response"""
      self.log(30, '{0!a} got error {1!a} from tracker.'.format(self, data))
      self.error_callback(TrackerResponseError('Tracker {0!a} returned failure reason {1!a}.'.format(self.announce_url, data)))
      self.close()
    
   FRAME_HANDLERS = {
      ACTION_CONNECT:frame_process_init,
      ACTION_ANNOUNCE:frame_process_announce,
      ACTION_ERROR:frame_process_error
   }
    
   def frame_process(self, data, source):
      """Process UDP frame received from tracker"""
      if (source != self.tracker_address):
         # Not what we're expecting.
         self.log(30, '{0!a} got unexpected udp frame {1!a} from {2!a}. Discarding.'.format(self, data, source))
         return
       
      if (len(data) < 8):
         raise ValueError('data {0!a} invalid; expected at least 8 bytes.'.format(data,))
      
      (action, tid) = struct.unpack('>ll', data[:8])
       
      if (tid != self.transaction_id):
         # Not what we're expecting.
         self.log(30, '{0!a} got unexpected tid {1!a}; expected {2!a}. Discarding frame.'.format(self, tid, self.transaction_id))
         return

      if (action in self.FRAME_HANDLERS):
         try:
            self.FRAME_HANDLERS[action](self, data[8:])
         except (TrackerResponseError, ValueError) as exc:
            self.log(30, '{0!a} failed to parse udp frame. Discarding frame. Traceback:'.format(self), exc_info=True)
      else:
         self.log(30, '{0!a} got frame with unknown action {1!a}. Discarding frame.'.format(self, action))
    
   def frame_send(self, data):
      """Send frame to tracker"""
      self.sock.send_bytes((data,), self.tracker_address)
   
   def _timeout_fake(self):
      if not (self.timeout_timer is None):
         self.timeout_timer.cancel()
      self.timeout_timer = self.ed.set_timer(0, self.timeout_handle)
   
   def request_send(self, event_dispatcher, result_callback, error_callback):
      """Initiate announce sequence"""
      if not (self.state is None):
         raise TrackerRequestError("Request {0!a} is still pending.".format(self.sock))
      
      self.result_callback = result_callback
      self.error_callback = error_callback
      
      self.ed = event_dispatcher
      
      try:
         tracker_addrinfo = random.choice(socket.getaddrinfo(*self.address_get()))
      except socket.error as exc:
         self.log(35, "Connection to tracker {0} failed; found no valid records. Faking timeout.".format(self.address_get()))
         self._timeout_fake()
         return
      
      tracker_AF = tracker_addrinfo[0]
      rsock = socket.socket(tracker_AF, socket.SOCK_DGRAM)
      
      self.sock = AsyncPacketSock(event_dispatcher, rsock)
      self.sock.process_input = self.frame_process
      self.sock.process_close = (lambda *args: None)
       
      self.transaction_id = self.tid_generate()
      self.state = 0
      self.tracker_address = tracker_addrinfo[4][:2]
      
      try:
         self.frame_send(self.frame_build_init())
      except socket.error as exc:
         # Ugly hack, but modeling explicitly reported send failure different
         # from a timeout isn't worth the hassle.
         self.log(30, 'Unable to contact tracker {0}: UDP sendto() failed with \'{1}\'. Faking timeout.'.format(self.address_get(),exc))
         self._timeout_fake()
         return
      self.timeout_timer = event_dispatcher.set_timer(self.TIMEOUT, self.timeout_handle)
    
   def close(self):
      """Close socket, if opened, and reset state variables"""
      self.ed = None
      if (self.sock):
         self.sock.close()
         self.sock = None
      if not (self.timeout_timer is None):
         self.timeout_timer.cancel()
         self.timeout_timer = None
      self.connection_id = None
      self.state = None
      self.transaction_id = None
      self.tracker_address = None
      self.result_callback = None
      self.error_callback = None