Skip to content

Commit

Permalink
Add remote hub support
Browse files Browse the repository at this point in the history
  • Loading branch information
Kolfering committed May 25, 2024
1 parent 734b383 commit e5d573a
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 5 deletions.
1 change: 1 addition & 0 deletions GameInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(self, gid, user_id):
self.port = None
self.visible = True
self.game_data = None
self.remote_hub_id = None

def dataChunk(self, verb):
_fmt = struct.Struct('>L4sHBxlLH10x')
Expand Down
17 changes: 15 additions & 2 deletions MetaPackets.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ class LogoutPacket:
def __init__(self, data):
pass # should be empty

class RemoteHubRequestPacket:
code = 122

def __init__(self, data):
(self.network_version, stringlen) = unpack_strings(data)

class RoomLoginPacket:
code = 101
_fmt = struct.Struct('>32s')
Expand All @@ -163,10 +169,10 @@ def __init__(self, data):

class CreateGamePacket:
code = 104
_fmt = struct.Struct('>H2x')
_fmt = struct.Struct('>HH')

def __init__(self, data):
self.port, = self._fmt.unpack_from(data)
self.port, self.remote_server_id = self._fmt.unpack_from(data)
self.game_data = data[self._fmt.size:len(data)]

class StartGamePacket:
Expand Down Expand Up @@ -234,6 +240,13 @@ class RoomListPacket:

def __init__(self, host, port):
self.data = self._fmt.pack(socket.inet_aton(host), port)

class RemoteHubListPacket:
code = 18
_fmt = struct.Struct('>H4sH')

def __init__(self, remote_servers):
self.data = b''.join(self._fmt.pack(server_id, socket.inet_aton(host), port) for server_id, host, port in remote_servers)

class RoomLoginSuccessfulPacket:
code = 9
Expand Down
2 changes: 1 addition & 1 deletion MetaProtocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class MetaProtocol(Protocol, TimeoutMixin):
MAX_LENGTH = 10240
SIGNATURE = 0xDEAD
_fmt = struct.Struct('>HHL')
_all_recv_packets = [ LoginPacket, PasswordResponsePacket, LocalizationPacket, LogoutPacket, RoomLoginPacket, PlayerDataPacket, PlayerModePacket, CreateGamePacket, StartGamePacket, RemoveGamePacket, IncomingChatPacket, IncomingPrivateMessagePacket, IncomingKeepAlivePacket ]
_all_recv_packets = [ LoginPacket, PasswordResponsePacket, RemoteHubRequestPacket, LocalizationPacket, LogoutPacket, RoomLoginPacket, PlayerDataPacket, PlayerModePacket, CreateGamePacket, StartGamePacket, RemoveGamePacket, IncomingChatPacket, IncomingPrivateMessagePacket, IncomingKeepAlivePacket ]

def __init__(self):
# Protocol.__init__(self)
Expand Down
37 changes: 35 additions & 2 deletions Roomd.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,15 @@ def handleCreateGamePacket(self, packet):

self.game_info.port = packet.port
self.game_info.game_data = packet.game_data

if packet.remote_server_id > 0:
deferred = self.dbpool.runQuery("SELECT host, port FROM remotehub WHERE id = %s", (packet.remote_server_id, ))
deferred.addCallback(lambda result: self.remoteCreateGameResult(result, packet.remote_server_id, verb))
deferred.addErrback(self.remoteCreateGameFailure)
else:
# announce game to everyone
self.sendGameList(self.game_info.game_id, 0, verb)

# announce game to everyone
self.sendGameList(self.game_info.game_id, 0, verb)
return True

def handleStartGamePacket(self, packet):
Expand Down Expand Up @@ -304,6 +310,33 @@ def sendPacketToRoom(self, packet, recip=0):
conn = info.roomd_connection
if conn is not None and not conn.deaf:
conn.sendPacket(packet)

def remoteCreateGameResult(self, rs, server_id, verb):
if self.state != self.LOGGED_IN:
log.msg("Remote hub create game in wrong context")
self.transport.loseConnection()
return

if len(rs) != 1:
log.msg("Found % remote servers in database for id %s. Was expecting 1." % len(rs), server_id)
self.transport.loseConnection()
return

try:
ipaddress = socket.gethostbyname(rs[0][0])
except:
log.msg("Can't resolve remote hub address anymore from host %s" % rs[0][0])
self.transport.loseConnection()
return

self.game_info.host = ipaddress
self.game_info.port = rs[0][1]
self.game_info.remote_hub_id = server_id
self.sendGameList(self.game_info.game_id, 0, verb)

def remoteCreateGameFailure(self, failure):
log.msg("Remote hub lookup failure: %s" % str(failure))
self.transport.loseConnection()

def logChat(self, message):
if self.log_chat:
Expand Down
40 changes: 40 additions & 0 deletions Userd.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,22 @@ def packetReceived(self, packet):
return self.handlePasswordResponsePacket(packet)
if isinstance(packet, LocalizationPacket):
return self.handleLocalizationPacket(packet)
if isinstance(packet, RemoteHubRequestPacket):
return self.handleRemoteHubRequestPacket(packet)
if isinstance(packet, LogoutPacket):
return self.handleLogoutPacket(packet)
return False

def handleRemoteHubRequestPacket(self, packet):
if self.state != self.LOGGED_IN:
self.sendMessage(MessagePacket.SYNTAX_ERROR)
return False

deferred = self.dbpool.runQuery("SELECT id, host, port FROM remotehub WHERE network_version = %s", (packet.network_version, ))
deferred.addCallback(self.remoteHubLookupResult)
deferred.addErrback(self.remoteHubLookupFailure)
return True

def handleLoginPacket(self, packet):
if self.state != self.NEED_LOGIN:
self.sendMessage(MessagePacket.SYNTAX_ERROR)
Expand Down Expand Up @@ -173,6 +185,34 @@ def passwordLookupResult(self, rs, saved_pw):
def passwordLookupFailure(self, failure):
log.msg("Password lookup failure: %s" % str(failure))
self.transport.loseConnection()

def remoteHubLookupResult(self, rs):
if self.state != self.LOGGED_IN:
log.msg("Remote hub request lookup called in wrong context")
self.transport.loseConnection()
return

games = self.globals['games'].values()
remote_servers = []

for server_row in rs:

if any(game.remote_hub_id == server_row[0] for game in games):
continue

try:
ipaddress = socket.gethostbyname(server_row[1])
except:
log.msg("Can't resolve remote hub address from host %s" % server_row[1])
continue

remote_servers.append((server_row[0], ipaddress, server_row[2]))

self.sendPacket(RemoteHubListPacket(remote_servers))

def remoteHubLookupFailure(self, failure):
log.msg("Remote hub lookup failure: %s" % str(failure))
self.transport.loseConnection()

def handleLocalizationPacket(self, packet):
if self.state != self.NEED_VERSION:
Expand Down
15 changes: 15 additions & 0 deletions database.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ Non-guest users are looked up by username in the `user` table. Only SELECT queri
meta_login_token varbinary(16),
meta_login_token_date datetime );

Remote hub servers are manually registered in the `remotehub` table and are sent to clients when requested. Clients in this case are gatherers willing to use remote hubs.
Each record corresponds to a running remote hub instance.

* The `id` field is automatically generated by the database and does not have to be manually inserted. It is used to lookup on which address to advertise and as a way to know which servers are already busy hosting for a gatherer.
* The `host` field can either contain a hostname or an ipv4. For hostnames, ipv4 resolving is done server side and only addresses are sent to clients.
* The `port` field contains the listening port of the remote hub server.
* The `network_version` field contains the Aleph One kNetworkSetupProtocolID. It is used to filter the remote hub list based on gatherers network version to send them only compatible servers.

CREATE TABLE remotehub(
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
host VARCHAR(255) NOT NULL,
port SMALLINT UNSIGNED NOT NULL,
network_version VARCHAR(50) NOT NULL,
UNIQUE (host, port) );

Other tables are used for logging. Only INSERT statements are run; no selects or updates are done. Other fields, such as an auto-increment ID, may be added to the tables. If logging is disabled in the config file, these tables do not need to be present.

Aleph One currently uses MacRoman character encoding for non-ASCII text. The server does no character conversion; strings are stored as-is in the database. Fields that may contain MacRoman text are designated below as `varbinary` or `blob`.
Expand Down

0 comments on commit e5d573a

Please sign in to comment.