Skip to content

Commit

Permalink
Fix: multiple servers announcing themselves with the same invite-code (
Browse files Browse the repository at this point in the history
…#43)

A user can be silly and use the same configuration for two servers
on different ports.
To deal with the situation, send the old server an error and
disconnect it. And let the new server claim the invite-code.
  • Loading branch information
TrueBrain committed Aug 17, 2021
1 parent c93c0d9 commit 7bcea73
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 7 deletions.
44 changes: 39 additions & 5 deletions game_coordinator/application/coordinator.py
Expand Up @@ -54,6 +54,14 @@ def disconnect(self, source):
def delete_token(self, token):
del self._tokens[token]

def _remove_broken_server(self, server_id, error_no, error_detail):
broken_server = self._servers[server_id]
if isinstance(broken_server, ServerExternal):
return

asyncio.create_task(broken_server.send_error_and_close(error_no, error_detail))
del self._servers[server_id]

async def add_turn_server(self, connection_string):
if connection_string not in self.turn_servers:
self.turn_servers.append(connection_string)
Expand All @@ -76,8 +84,15 @@ async def update_external_server(self, server_id, info):
self._servers[server_id] = ServerExternal(self, server_id)

if not isinstance(self._servers[server_id], ServerExternal):
log.error("Internal error: update_external_server() called on a server managed by us")
return
# Two servers could announce themselves with the same server-id.
# Best way to deal with the situation is to assume the new instance
# is in good contact with the server, and for us to drop our
# connection with the old.

self._remove_broken_server(
server_id, NetworkCoordinatorErrorType.NETWORK_COORDINATOR_ERROR_REUSE_OF_INVITE_CODE, server_id
)
self._servers[server_id] = ServerExternal(self, server_id)

await self._servers[server_id].update(info)

Expand All @@ -86,8 +101,15 @@ async def update_newgrf_external_server(self, server_id, newgrfs_indexed):
self._servers[server_id] = ServerExternal(self, server_id)

if not isinstance(self._servers[server_id], ServerExternal):
log.error("Internal error: update_external_server() called on a server managed by us")
return
# Two servers could announce themselves with the same server-id.
# Best way to deal with the situation is to assume the new instance
# is in good contact with the server, and for us to drop our
# connection with the old.

self._remove_broken_server(
server_id, NetworkCoordinatorErrorType.NETWORK_COORDINATOR_ERROR_REUSE_OF_INVITE_CODE, server_id
)
self._servers[server_id] = ServerExternal(self, server_id)

await self._servers[server_id].update_newgrf(newgrfs_indexed)

Expand Down Expand Up @@ -183,7 +205,19 @@ async def receive_PACKET_COORDINATOR_SERVER_REGISTER(

invite_code_secret = generate_invite_code_secret(self._shared_secret, server_id)

source.server = Server(self, server_id, game_type, source, server_port, invite_code_secret)
source.server = Server(self, server_id, game_type, source, protocol_version, server_port, invite_code_secret)

if source.server.server_id in self._servers:
# We replace a server already known; possibly two servers are using
# the same invite-code. There is not much we can do about this,
# other than disconnect the old, and hope the server-owner notices
# that they are constantly battling for the same invite-code.
self._remove_broken_server(
source.server.server_id,
NetworkCoordinatorErrorType.NETWORK_COORDINATOR_ERROR_REUSE_OF_INVITE_CODE,
source.server.server_id,
)

self._servers[source.server.server_id] = source.server

# Find an unused token.
Expand Down
22 changes: 21 additions & 1 deletion game_coordinator/application/helpers/server.py
Expand Up @@ -6,6 +6,7 @@
NewGRFSerializationType,
ServerGameType,
)
from openttd_protocol.wire.exceptions import SocketClosed

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -74,10 +75,11 @@ async def send_connect_failed(self, protocol_version, token):


class Server:
def __init__(self, application, server_id, game_type, source, server_port, invite_code_secret):
def __init__(self, application, server_id, game_type, source, protocol_version, server_port, invite_code_secret):
self._application = application
self._source = source
self._invite_code_secret = invite_code_secret
self._protocol_version = protocol_version

self.info = {}
self.game_type = game_type
Expand All @@ -94,6 +96,24 @@ def __init__(self, application, server_id, game_type, source, server_port, invit
async def disconnect(self):
await self._application.database.server_offline(self.server_id)

async def send_error_and_close(self, error_no, error_detail):
try:
await self._source.protocol.send_PACKET_COORDINATOR_GC_ERROR(
self._protocol_version,
error_no,
error_detail,
)

# Give it a second for the above packet to arrive.
await asyncio.sleep(1)
except SocketClosed:
# Socket already closed, so we can clean up the socket.
pass

# Make sure disconnect() is not called on the object anymore.
del self._source.server
self._source.protocol.transport.abort()

async def update_newgrf(self, newgrf_serialization_type, newgrfs):
if newgrfs is None:
return
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Expand Up @@ -9,7 +9,7 @@ hiredis==2.0.0
idna==3.2
multidict==5.1.0
openttd-helpers==1.0.1
openttd-protocol==1.0.0
openttd-protocol==1.1.0
pproxy==2.7.8
sentry-sdk==1.1.0
typing-extensions==3.10.0.0
Expand Down

0 comments on commit 7bcea73

Please sign in to comment.