def _msgTimeoutInProgress(self, messageID, remoteContactID, df, method, args): # See if any progress has been made; if not, kill the message if self._hasProgressBeenMade(messageID): # Reset the RPC timeout timer timeoutCall, _ = self._node.reactor_callLater(constants.rpcTimeout, self._msgTimeout, messageID) self._sentMessages[messageID] = (remoteContactID, df, timeoutCall, method, args) else: # No progress has been made if messageID in self._partialMessagesProgress: del self._partialMessagesProgress[messageID] if messageID in self._partialMessages: del self._partialMessages[messageID] df.errback(TimeoutError(remoteContactID))
def _msgTimeout(self, messageID): """ Called when an RPC request message times out """ # Find the message that timed out if messageID not in self._sentMessages: # This should never be reached log.error("deferred timed out, but is not present in sent messages list!") return remoteContactID, df, timeout_call, method, args = self._sentMessages[messageID] if self._partialMessages.has_key(messageID): # We are still receiving this message self._msgTimeoutInProgress(messageID, remoteContactID, df, method, args) return del self._sentMessages[messageID] # The message's destination node is now considered to be dead; # raise an (asynchronous) TimeoutError exception and update the host node self._node.removeContact(remoteContactID) df.errback(TimeoutError(remoteContactID))
def datagramReceived(self, datagram, address): """ Handles and parses incoming RPC messages (and responses) @note: This is automatically called by Twisted when the protocol receives a UDP datagram """ if datagram[0] == '\x00' and datagram[25] == '\x00': totalPackets = (ord(datagram[1]) << 8) | ord(datagram[2]) msgID = datagram[5:25] seqNumber = (ord(datagram[3]) << 8) | ord(datagram[4]) if msgID not in self._partialMessages: self._partialMessages[msgID] = {} self._partialMessages[msgID][seqNumber] = datagram[26:] if len(self._partialMessages[msgID]) == totalPackets: keys = self._partialMessages[msgID].keys() keys.sort() data = '' for key in keys: data += self._partialMessages[msgID][key] datagram = data del self._partialMessages[msgID] else: return try: msgPrimitive = self._encoder.decode(datagram) message = self._translator.fromPrimitive(msgPrimitive) except (encoding.DecodeError, ValueError) as err: # We received some rubbish here log.warning("Error decoding datagram %s from %s:%i - %s", datagram.encode('hex'), address[0], address[1], err) return except (IndexError, KeyError): log.warning("Couldn't decode dht datagram from %s", address) return if isinstance(message, msgtypes.RequestMessage): # This is an RPC method request remoteContact = self._node.contact_manager.make_contact( message.nodeID, address[0], address[1], self) remoteContact.update_last_requested() # only add a requesting contact to the routing table if it has replied to one of our requests if remoteContact.contact_is_good is True: df = self._node.addContact(remoteContact) else: df = defer.succeed(None) df.addCallback(lambda _: self._handleRPC( remoteContact, message.id, message.request, message.args)) # if the contact is not known to be bad (yet) and we haven't yet queried it, send it a ping so that it # will be added to our routing table if successful if remoteContact.contact_is_good is None and remoteContact.lastReplied is None: df.addCallback(lambda _: self._ping_queue.enqueue_maybe_ping( remoteContact)) elif isinstance(message, msgtypes.ErrorMessage): # The RPC request raised a remote exception; raise it locally if message.exceptionType in BUILTIN_EXCEPTIONS: exception_type = BUILTIN_EXCEPTIONS[message.exceptionType] else: exception_type = UnknownRemoteException remoteException = exception_type(message.response) log.error("DHT RECV REMOTE EXCEPTION FROM %s:%i: %s", address[0], address[1], remoteException) if message.id in self._sentMessages: # Cancel timeout timer for this RPC remoteContact, df, timeoutCall, timeoutCanceller, method = self._sentMessages[ message.id][0:5] timeoutCanceller() del self._sentMessages[message.id] # reject replies coming from a different address than what we sent our request to if (remoteContact.address, remoteContact.port) != address: log.warning( "Sent request to node %s at %s:%i, got reply from %s:%i", remoteContact.log_id(), remoteContact.address, remoteContact.port, address[0], address[1]) df.errback(TimeoutError(remoteContact.id)) return # this error is returned by nodes that can be contacted but have an old # and broken version of the ping command, if they return it the node can # be contacted, so we'll treat it as a successful ping old_ping_error = "ping() got an unexpected keyword argument '_rpcNodeContact'" if isinstance(remoteException, TypeError) and \ remoteException.message == old_ping_error: log.debug("old pong error") df.callback('pong') else: df.errback(remoteException) elif isinstance(message, msgtypes.ResponseMessage): # Find the message that triggered this response if message.id in self._sentMessages: # Cancel timeout timer for this RPC remoteContact, df, timeoutCall, timeoutCanceller, method = self._sentMessages[ message.id][0:5] timeoutCanceller() del self._sentMessages[message.id] log.debug("%s:%i RECV response to %s from %s:%i", self._node.externalIP, self._node.port, method, remoteContact.address, remoteContact.port) # When joining the network we made Contact objects for the seed nodes with node ids set to None # Thus, the sent_to_id will also be None, and the contact objects need the ids to be manually set. # These replies have be distinguished from those where the node id in the datagram does not match # the node id of the node we sent a message to (these messages are treated as an error) if remoteContact.id and remoteContact.id != message.nodeID: # sent_to_id will be None for bootstrap log.debug("mismatch: (%s) %s:%i (%s vs %s)", method, remoteContact.address, remoteContact.port, remoteContact.log_id(False), message.nodeID.encode('hex')) df.errback(TimeoutError(remoteContact.id)) return elif not remoteContact.id: remoteContact.set_id(message.nodeID) # We got a result from the RPC df.callback(message.response) else: # If the original message isn't found, it must have timed out # TODO: we should probably do something with this... pass