Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Fix rejection of invites to unreachable servers #2145

Merged
merged 5 commits into from
Apr 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions contrib/cmdclient/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,13 @@ def put_json(self, url, data):
the request body. This will be encoded as JSON.
Returns:
Deferred: Succeeds when we get *any* HTTP response.
The result of the deferred is a tuple of `(code, response)`,
where `response` is a dict representing the decoded JSON body.
Deferred: Succeeds when we get a 2xx HTTP response. The result
will be the decoded JSON body.
"""
pass

def get_json(self, url, args=None):
""" Get's some json from the given host homeserver and path
""" Gets some json from the given host homeserver and path
Args:
url (str): The URL to GET data from.
Expand All @@ -54,10 +52,8 @@ def get_json(self, url, args=None):
and *not* a string.
Returns:
Deferred: Succeeds when we get *any* HTTP response.
The result of the deferred is a tuple of `(code, response)`,
where `response` is a dict representing the decoded JSON body.
Deferred: Succeeds when we get a 2xx HTTP response. The result
will be the decoded JSON body.
"""
pass

Expand Down Expand Up @@ -214,4 +210,4 @@ def pauseProducing(self):
pass

def stopProducing(self):
pass
pass
50 changes: 48 additions & 2 deletions synapse/federation/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,8 +474,13 @@ def make_membership_event(self, destinations, room_id, user_id, membership,
content (object): Any additional data to put into the content field
of the event.
Return:
A tuple of (origin (str), event (object)) where origin is the remote
homeserver which generated the event.
Deferred: resolves to a tuple of (origin (str), event (object))
where origin is the remote homeserver which generated the event.

Fails with a ``CodeMessageException`` if the chosen remote server
returns a 300/400 code.

Fails with a ``RuntimeError`` if no servers were reachable.
"""
valid_memberships = {Membership.JOIN, Membership.LEAVE}
if membership not in valid_memberships:
Expand Down Expand Up @@ -528,6 +533,27 @@ def make_membership_event(self, destinations, room_id, user_id, membership,

@defer.inlineCallbacks
def send_join(self, destinations, pdu):
"""Sends a join event to one of a list of homeservers.

Doing so will cause the remote server to add the event to the graph,
and send the event out to the rest of the federation.

Args:
destinations (str): Candidate homeservers which are probably
participating in the room.
pdu (BaseEvent): event to be sent

Return:
Deferred: resolves to a dict with members ``origin`` (a string
giving the serer the event was sent to, ``state`` (?) and
``auth_chain``.

Fails with a ``CodeMessageException`` if the chosen remote server
returns a 300/400 code.

Fails with a ``RuntimeError`` if no servers were reachable.
"""

for destination in destinations:
if destination == self.server_name:
continue
Expand Down Expand Up @@ -635,6 +661,26 @@ def send_invite(self, destination, room_id, event_id, pdu):

@defer.inlineCallbacks
def send_leave(self, destinations, pdu):
"""Sends a leave event to one of a list of homeservers.

Doing so will cause the remote server to add the event to the graph,
and send the event out to the rest of the federation.

This is mostly useful to reject received invites.

Args:
destinations (str): Candidate homeservers which are probably
participating in the room.
pdu (BaseEvent): event to be sent

Return:
Deferred: resolves to None.

Fails with a ``CodeMessageException`` if the chosen remote server
returns a non-200 code.

Fails with a ``RuntimeError`` if no servers were reachable.
"""
for destination in destinations:
if destination == self.server_name:
continue
Expand Down
40 changes: 39 additions & 1 deletion synapse/federation/transport/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,26 @@ def make_query(self, destination, query_type, args, retry_on_dns_fail,
@defer.inlineCallbacks
@log_function
def make_membership_event(self, destination, room_id, user_id, membership):
"""Asks a remote server to build and sign us a membership event

Note that this does not append any events to any graphs.

Args:
destination (str): address of remote homeserver
room_id (str): room to join/leave
user_id (str): user to be joined/left
membership (str): one of join/leave

Returns:
Deferred: Succeeds when we get a 2xx HTTP response. The result
will be the decoded JSON body (ie, the new event).

Fails with ``HTTPRequestException`` if we get an HTTP response
code >= 300.

Fails with ``NotRetryingDestination`` if we are not yet ready
to retry this server.
"""
valid_memberships = {Membership.JOIN, Membership.LEAVE}
if membership not in valid_memberships:
raise RuntimeError(
Expand All @@ -201,11 +221,23 @@ def make_membership_event(self, destination, room_id, user_id, membership):
)
path = PREFIX + "/make_%s/%s/%s" % (membership, room_id, user_id)

ignore_backoff = False
retry_on_dns_fail = False

if membership == Membership.LEAVE:
# we particularly want to do our best to send leave events. The
# problem is that if it fails, we won't retry it later, so if the
# remote server was just having a momentary blip, the room will be
# out of sync.
ignore_backoff = True
retry_on_dns_fail = True

content = yield self.client.get_json(
destination=destination,
path=path,
retry_on_dns_fail=False,
retry_on_dns_fail=retry_on_dns_fail,
timeout=20000,
ignore_backoff=ignore_backoff,
)

defer.returnValue(content)
Expand All @@ -232,6 +264,12 @@ def send_leave(self, destination, room_id, event_id, content):
destination=destination,
path=path,
data=content,

# we want to do our best to send this through. The problem is
# that if it fails, we won't retry it later, so if the remote
# server was just having a momentary blip, the room will be out of
# sync.
ignore_backoff=True,
)

defer.returnValue(response)
Expand Down
34 changes: 11 additions & 23 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1090,19 +1090,13 @@ def on_invite_request(self, origin, pdu):

@defer.inlineCallbacks
def do_remotely_reject_invite(self, target_hosts, room_id, user_id):
try:
origin, event = yield self._make_and_verify_event(
target_hosts,
room_id,
user_id,
"leave"
)
event = self._sign_event(event)
except SynapseError:
raise
except CodeMessageException as e:
logger.warn("Failed to reject invite: %s", e)
raise SynapseError(500, "Failed to reject invite")
origin, event = yield self._make_and_verify_event(
target_hosts,
room_id,
user_id,
"leave"
)
event = self._sign_event(event)

# Try the host that we succesfully called /make_leave/ on first for
# the /send_leave/ request.
Expand All @@ -1112,16 +1106,10 @@ def do_remotely_reject_invite(self, target_hosts, room_id, user_id):
except ValueError:
pass

try:
yield self.replication_layer.send_leave(
target_hosts,
event
)
except SynapseError:
raise
except CodeMessageException as e:
logger.warn("Failed to reject invite: %s", e)
raise SynapseError(500, "Failed to reject invite")
yield self.replication_layer.send_leave(
target_hosts,
event
)

context = yield self.state_handler.compute_event_context(event)

Expand Down
23 changes: 12 additions & 11 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,6 @@ def remote_join(self, remote_room_hosts, room_id, user, content):
)
yield user_joined_room(self.distributor, user, room_id)

def reject_remote_invite(self, user_id, room_id, remote_room_hosts):
return self.hs.get_handlers().federation_handler.do_remotely_reject_invite(
remote_room_hosts,
room_id,
user_id
)

@defer.inlineCallbacks
def update_membership(
self,
Expand Down Expand Up @@ -286,13 +279,21 @@ def _update_membership(
else:
# send the rejection to the inviter's HS.
remote_room_hosts = remote_room_hosts + [inviter.domain]

fed_handler = self.hs.get_handlers().federation_handler
try:
ret = yield self.reject_remote_invite(
target.to_string(), room_id, remote_room_hosts
ret = yield fed_handler.do_remotely_reject_invite(
remote_room_hosts,
room_id,
target.to_string(),
)
defer.returnValue(ret)
except SynapseError as e:
except Exception as e:
# if we were unable to reject the exception, just mark
# it as rejected on our end and plough ahead.
#
# The 'except' clause is very broad, but we need to
# capture everything from DNS failures upwards
#
logger.warn("Failed to reject invite: %s", e)

yield self.store.locally_reject_invite(
Expand Down
21 changes: 14 additions & 7 deletions synapse/http/matrixfederationclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def _request(self, destination, method, path,
code >= 300.
Fails with ``NotRetryingDestination`` if we are not yet ready
to retry this server.
(May also fail with plenty of other Exceptions for things like DNS
failures, connection failures, SSL failures.)
"""
limiter = yield synapse.util.retryutils.get_retry_limiter(
destination,
Expand Down Expand Up @@ -302,8 +304,10 @@ def put_json(self, destination, path, data={}, json_data_callback=None,

Returns:
Deferred: Succeeds when we get a 2xx HTTP response. The result
will be the decoded JSON body. On a 4xx or 5xx error response a
CodeMessageException is raised.
will be the decoded JSON body.

Fails with ``HTTPRequestException`` if we get an HTTP response
code >= 300.

Fails with ``NotRetryingDestination`` if we are not yet ready
to retry this server.
Expand Down Expand Up @@ -360,8 +364,10 @@ def post_json(self, destination, path, data={}, long_retries=False,
try the request anyway.
Returns:
Deferred: Succeeds when we get a 2xx HTTP response. The result
will be the decoded JSON body. On a 4xx or 5xx error response a
CodeMessageException is raised.
will be the decoded JSON body.

Fails with ``HTTPRequestException`` if we get an HTTP response
code >= 300.

Fails with ``NotRetryingDestination`` if we are not yet ready
to retry this server.
Expand Down Expand Up @@ -410,10 +416,11 @@ def get_json(self, destination, path, args={}, retry_on_dns_fail=True,
ignore_backoff (bool): true to ignore the historical backoff data
and try the request anyway.
Returns:
Deferred: Succeeds when we get *any* HTTP response.
Deferred: Succeeds when we get a 2xx HTTP response. The result
will be the decoded JSON body.

The result of the deferred is a tuple of `(code, response)`,
where `response` is a dict representing the decoded JSON body.
Fails with ``HTTPRequestException`` if we get an HTTP response
code >= 300.

Fails with ``NotRetryingDestination`` if we are not yet ready
to retry this server.
Expand Down