plugin XEP-0260, XEP-0065: fixed session hash handling:
with XEP-0260, we need to manage 2 hashes (one with SHA1(SID + Initiator JID + Responder JID) and the other with SHA1(SID + Responder JID + Initiator JID)). This was not handled correclty, resulting in transfer failure in several cases.
This commit is contained in:
parent
84fbbbc299
commit
7d54ddff12
1 changed files with 43 additions and 47 deletions
|
|
@ -283,15 +283,15 @@ class Candidate(object):
|
|||
self.factory.startTransfer(session_hash, chunk_size=chunk_size)
|
||||
|
||||
|
||||
def getSessionHash(from_jid, to_jid, sid):
|
||||
def getSessionHash(requester_jid, target_jid, sid):
|
||||
"""Calculate SHA1 Hash according to XEP-0065 §5.3.2
|
||||
|
||||
@param from_jid(jid.JID): jid of the requester
|
||||
@param to_jid(jid.JID): jid of the target
|
||||
@param requester_jid(jid.JID): jid of the requester (the one which activate the proxy)
|
||||
@param target_jid(jid.JID): jid of the target
|
||||
@param sid(unicode): session id
|
||||
@return (str): hash
|
||||
"""
|
||||
return hashlib.sha1((sid + from_jid.full() + to_jid.full()).encode('utf-8')).hexdigest()
|
||||
return hashlib.sha1((sid + requester_jid.full() + target_jid.full()).encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
class SOCKSv5(protocol.Protocol, FileSender):
|
||||
|
|
@ -617,14 +617,15 @@ class Socks5ServerFactory(protocol.ServerFactory):
|
|||
class Socks5ClientFactory(protocol.ClientFactory):
|
||||
protocol = SOCKSv5
|
||||
|
||||
def __init__(self, parent, session_hash, profile):
|
||||
def __init__(self, parent, session, session_hash, profile):
|
||||
"""Init the Client Factory
|
||||
|
||||
@param session_hash(unicode): hash of the session
|
||||
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
|
||||
@param session(dict): session data
|
||||
@param session_hash(unicode): hash used for peer_connection
|
||||
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
|
||||
@param profile(unciode): %(doc_profile)s
|
||||
"""
|
||||
self.session = parent.getSession(session_hash, profile)
|
||||
self.session = session
|
||||
self.session_hash = session_hash
|
||||
self.profile = profile
|
||||
self.connection = defer.Deferred()
|
||||
|
|
@ -862,18 +863,28 @@ class XEP_0065(object):
|
|||
candidate.factory.connector = connector
|
||||
return candidate.factory.connection
|
||||
|
||||
def connectCandidate(self, candidate, session_hash, delay=None, profile=C.PROF_KEY_NONE):
|
||||
def connectCandidate(self, candidate, session_hash, peer_session_hash=None, delay=None, profile=C.PROF_KEY_NONE):
|
||||
"""Connect to a candidate
|
||||
|
||||
Connection will be done with a Socks5ClientFactory
|
||||
@param candidate(Candidate): candidate to connect to
|
||||
@param session_hash(unicode): hash of the session
|
||||
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
|
||||
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
|
||||
@param peer_session_hash(unicode, None): hash used with the peer
|
||||
None to use session_hash.
|
||||
None must be used in 2 cases:
|
||||
- when XEP-0065 is used with XEP-0096
|
||||
- when a peer connect to a proxy *he proposed himself*
|
||||
in practice, peer_session_hash is only used by tryCandidates
|
||||
@param delay(None, float): optional delay to wait before connection, in seconds
|
||||
@param profile: %(doc_profile)s
|
||||
@return (D): Deferred launched when TCP connection + Socks5 connection is done
|
||||
"""
|
||||
factory = Socks5ClientFactory(self, session_hash, profile)
|
||||
if peer_session_hash is None:
|
||||
# for XEP-0065, only one hash is needed
|
||||
peer_session_hash = session_hash
|
||||
session = self.getSession(session_hash, profile)
|
||||
factory = Socks5ClientFactory(self, session, peer_session_hash, profile)
|
||||
candidate.factory = factory
|
||||
if delay is None:
|
||||
d = defer.succeed(candidate.host)
|
||||
|
|
@ -883,14 +894,14 @@ class XEP_0065(object):
|
|||
d.addCallback(self._addConnector, candidate)
|
||||
return d
|
||||
|
||||
def tryCandidates(self, candidates, session_hash, connection_cb=None, connection_eb=None, profile=C.PROF_KEY_NONE):
|
||||
def tryCandidates(self, candidates, session_hash, peer_session_hash, connection_cb=None, connection_eb=None, profile=C.PROF_KEY_NONE):
|
||||
defers_list = []
|
||||
|
||||
for candidate in candidates:
|
||||
delay = CANDIDATE_DELAY * len(defers_list)
|
||||
if candidate.type == XEP_0065.TYPE_PROXY:
|
||||
delay += CANDIDATE_DELAY_PROXY
|
||||
d = self.connectCandidate(candidate, session_hash, delay, profile)
|
||||
d = self.connectCandidate(candidate, session_hash, peer_session_hash, delay, profile)
|
||||
if connection_cb is not None:
|
||||
d.addCallback(lambda dummy, candidate=candidate, profile=profile: connection_cb(candidate, profile))
|
||||
if connection_eb is not None:
|
||||
|
|
@ -899,12 +910,14 @@ class XEP_0065(object):
|
|||
|
||||
return defers_list
|
||||
|
||||
def getBestCandidate(self, candidates, session_hash, profile=C.PROF_KEY_NONE):
|
||||
def getBestCandidate(self, candidates, session_hash, peer_session_hash=None, profile=C.PROF_KEY_NONE):
|
||||
"""Get best candidate (according to priority) which can connect
|
||||
|
||||
@param candidates(iterable[Candidate]): candidates to test
|
||||
@param session_hash(unicode): hash of the session
|
||||
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
|
||||
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
|
||||
@param peer_session_hash(unicode, None): hash of the other peer
|
||||
only useful for XEP-0260, must be None for XEP-0065 streamhost candidates
|
||||
@param profile: %(doc_profile)s
|
||||
@return (D(None, Candidate)): best candidate or None if none can connect
|
||||
"""
|
||||
|
|
@ -934,7 +947,7 @@ class XEP_0065(object):
|
|||
good_candidates = [c for c in candidates if c]
|
||||
return good_candidates[0] if good_candidates else None
|
||||
|
||||
defer_candidates = self.tryCandidates(candidates, session_hash, connectionCb, connectionEb, profile)
|
||||
defer_candidates = self.tryCandidates(candidates, session_hash, peer_session_hash, connectionCb, connectionEb, profile)
|
||||
d_list = defer.DeferredList(defer_candidates)
|
||||
d_list.addCallback(allTested)
|
||||
return d_list
|
||||
|
|
@ -966,12 +979,8 @@ class XEP_0065(object):
|
|||
))
|
||||
|
||||
try:
|
||||
# XXX: we need to be sure that hash is removed from self.hash_profiles_map
|
||||
# ONLY if it's the profile requesting the session killing
|
||||
# otherwise, this will result in a missing hash when the 2 peers
|
||||
# are on the same instance
|
||||
if self.hash_profiles_map[session_hash] == client.profile:
|
||||
del self.hash_profiles_map[session_hash]
|
||||
assert self.hash_profiles_map[session_hash] == client.profile
|
||||
del self.hash_profiles_map[session_hash]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
|
@ -1121,7 +1130,7 @@ class XEP_0065(object):
|
|||
"""Return session data
|
||||
|
||||
@param session_hash(unicode): hash of the session
|
||||
hash is the same as hostname computer in XEP-0065 § 5.3.2 #1
|
||||
hash is the same as hostname computed in XEP-0065 § 5.3.2 #1
|
||||
@param profile(None, unicode): profile of the peer
|
||||
None is used only if profile is unknown (this is only the case
|
||||
for incoming request received by Socks5ServerFactory). None must
|
||||
|
|
@ -1165,23 +1174,8 @@ class XEP_0065(object):
|
|||
if file_obj is not None:
|
||||
session_data['file'] = file_obj
|
||||
|
||||
if session_hash in self.hash_profiles_map:
|
||||
# The only case when 2 profiles want to register the same hash
|
||||
# is when they are on the same instance
|
||||
log.info(u"Both Socks5 peers are on the same instance")
|
||||
# XXX:If both peers are on the same instance, they'll register the same
|
||||
# session_hash, so we'll have 2 profiles for the same hash. The first
|
||||
# one will be the responder (and so the second one the initiator).
|
||||
# As we'll keep the initiator choosed candidate (see XEP-0260 § 2.4 #4),
|
||||
# responder will handle the Socks5 server. Only the server will use
|
||||
# self.hash_profiles_map to get the profile, so we can ignore the second
|
||||
# one (the initiator profile).
|
||||
# There is no easy way to known if the incoming connection
|
||||
# to the Socks5Server is from initiator or responder, so this seams a
|
||||
# reasonable workaround.
|
||||
# NOTE: this workaround is only used with XEP-0260
|
||||
else:
|
||||
self.hash_profiles_map[session_hash] = profile
|
||||
assert session_hash not in self.hash_profiles_map
|
||||
self.hash_profiles_map[session_hash] = profile
|
||||
|
||||
return session_data
|
||||
|
||||
|
|
@ -1234,7 +1228,7 @@ class XEP_0065(object):
|
|||
for candidate in candidates:
|
||||
log.info(u"Candidate proposed: {}".format(candidate))
|
||||
|
||||
d = self.getBestCandidate(candidates, session_data['hash'], profile)
|
||||
d = self.getBestCandidate(candidates, session_data['hash'], profile=profile)
|
||||
d.addCallback(self._ackStream, iq_elt, session_data, client)
|
||||
|
||||
def _ackStream(self, candidate, iq_elt, session_data, client):
|
||||
|
|
|
|||
Loading…
Reference in a new issue