idz: add socket servers

This commit is contained in:
Hay1tsme 2024-01-09 12:21:31 -05:00
parent 4b9db8be3b
commit f65aa4d60a
4 changed files with 77 additions and 114 deletions

View File

@ -4,16 +4,11 @@ import logging
from core.config import CoreConfig from core.config import CoreConfig
from .config import IDZConfig from .config import IDZConfig
class IDZEcho:
def connection_made(self, transport):
self.transport = transport
class IDZEcho(DatagramProtocol): def datagram_received(self, data, addr):
def __init__(self, cfg: CoreConfig, game_cfg: IDZConfig) -> None: message = data.decode()
super().__init__() self.logger.debug(f'Received echo from {addr}')
self.core_config = cfg self.transport.sendto(data, addr)
self.game_config = game_cfg
self.logger = logging.getLogger("idz")
def datagramReceived(self, data, addr):
self.logger.debug(
f"Echo from from {addr[0]}:{addr[1]} -> {self.transport.getHost().port} - {data.hex()}"
)
self.transport.write(data, addr)

View File

@ -91,7 +91,7 @@ class IDZHandlerLoadServerInfo(IDZHandlerBase):
) )
struct.pack_into("<I", ret, 0x39C + offset, self.game_cfg.ports.echo) struct.pack_into("<I", ret, 0x39C + offset, self.game_cfg.ports.echo)
struct.pack_into("<I", ret, 0x39E + offset, self.game_cfg.ports.echo + 1) struct.pack_into("<I", ret, 0x39E + offset, self.game_cfg.ports.echo) # TODO: Test
struct.pack_into(f"{len_news}s", ret, 0x03A0 + offset, news_str.encode()) struct.pack_into(f"{len_news}s", ret, 0x03A0 + offset, news_str.encode())
struct.pack_into(f"{len_error}s", ret, 0x0424 + offset, err_str.encode()) struct.pack_into(f"{len_error}s", ret, 0x0424 + offset, err_str.encode())

View File

@ -8,12 +8,13 @@ from logging.handlers import TimedRotatingFileHandler
from os import path from os import path
from typing import Tuple, List, Dict from typing import Tuple, List, Dict
import importlib import importlib
import asyncio
from core.config import CoreConfig from core.config import CoreConfig
from core.title import BaseServlet from core.title import BaseServlet
from .config import IDZConfig from .config import IDZConfig
from .const import IDZConstants from .const import IDZConstants
from .userdb import IDZUserDBFactory, IDZKey from .userdb import IDZUserDB, IDZKey
from .echo import IDZEcho from .echo import IDZEcho
@ -135,27 +136,27 @@ class IDZServlet(BaseServlet):
except AttributeError as e: except AttributeError as e:
continue continue
""" loop = asyncio.get_running_loop()
endpoints.serverFromString( IDZUserDB(self.core_cfg, self.game_cfg, self.rsa_keys, handler_map)
reactor, asyncio.create_task(
f"tcp:{self.game_cfg.ports.userdb}:interface={self.core_cfg.server.listen_address}", loop.create_datagram_endpoint(
).listen( lambda: IDZEcho(),
IDZUserDBFactory(self.core_cfg, self.game_cfg, self.rsa_keys, handler_map) local_addr=(self.core_cfg.server.listen_address, self.game_cfg.ports.echo)
)
)
asyncio.create_task(
loop.create_datagram_endpoint(
lambda: IDZEcho(),
local_addr=(self.core_cfg.server.listen_address, self.game_cfg.ports.match)
)
)
asyncio.create_task(
loop.create_datagram_endpoint(
lambda: IDZEcho(),
local_addr=(self.core_cfg.server.listen_address, self.game_cfg.ports.userdb + 1)
)
) )
reactor.listenUDP(
self.game_cfg.ports.echo, IDZEcho(self.core_cfg, self.game_cfg)
)
reactor.listenUDP(
self.game_cfg.ports.echo + 1, IDZEcho(self.core_cfg, self.game_cfg)
)
reactor.listenUDP(
self.game_cfg.ports.match, IDZEcho(self.core_cfg, self.game_cfg)
)
reactor.listenUDP(
self.game_cfg.ports.userdb + 1, IDZEcho(self.core_cfg, self.game_cfg)
)
"""
self.logger.info(f"UserDB Listening on port {self.game_cfg.ports.userdb}") self.logger.info(f"UserDB Listening on port {self.game_cfg.ports.userdb}")
async def render_GET(self, request: Request) -> bytes: async def render_GET(self, request: Request) -> bytes:

View File

@ -1,15 +1,9 @@
from twisted.internet.protocol import Factory, Protocol import logging
import logging, coloredlogs
from Crypto.Cipher import AES from Crypto.Cipher import AES
import struct import struct
from typing import Dict, Optional, List, Type from typing import Dict, Optional, List, Type
from twisted.web import server, resource
from twisted.internet import reactor, endpoints
from starlette.requests import Request
from routes import Mapper
import random import random
from os import walk import asyncio
import importlib
from core.config import CoreConfig from core.config import CoreConfig
from .database import IDZData from .database import IDZData
@ -28,7 +22,7 @@ class IDZKey:
self.hashN = hashN self.hashN = hashN
class IDZUserDBProtocol(Protocol): class IDZUserDB:
def __init__( def __init__(
self, self,
core_cfg: CoreConfig, core_cfg: CoreConfig,
@ -45,6 +39,10 @@ class IDZUserDBProtocol(Protocol):
self.version = None self.version = None
self.version_internal = None self.version_internal = None
self.skip_next = False self.skip_next = False
def start(self) -> None:
self.logger.info(f"Start on port {self.config.aimedb.port}")
asyncio.create_task(asyncio.start_server(self.connection_cb, self.config.server.listen_address, self.config.aimedb.port))
def append_padding(self, data: bytes): def append_padding(self, data: bytes):
"""Appends 0s to the end of the data until it's at the correct size""" """Appends 0s to the end of the data until it's at the correct size"""
@ -52,43 +50,54 @@ class IDZUserDBProtocol(Protocol):
padding_size = length[0] - len(data) padding_size = length[0] - len(data)
data += bytes(padding_size) data += bytes(padding_size)
return data return data
async def connection_cb(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
self.logger.debug(f"Connection made from {writer.get_extra_info('peername')[0]}")
while True:
try:
base = 0
def connectionMade(self) -> None: for i in range(len(self.static_key) - 1):
self.logger.debug(f"{self.transport.getPeer().host} Connected") shift = 8 * i
base = 0 byte = self.static_key[i]
for i in range(len(self.static_key) - 1): base |= byte << shift
shift = 8 * i
byte = self.static_key[i]
base |= byte << shift rsa_key = random.choice(self.rsa_keys)
key_enc: int = pow(base, rsa_key.e, rsa_key.N)
result = (
key_enc.to_bytes(0x40, "little")
+ struct.pack("<I", 0x01020304)
+ rsa_key.hashN.to_bytes(4, "little")
)
rsa_key = random.choice(self.rsa_keys) self.logger.debug(f"Send handshake {result.hex()}")
key_enc: int = pow(base, rsa_key.e, rsa_key.N)
result = (
key_enc.to_bytes(0x40, "little")
+ struct.pack("<I", 0x01020304)
+ rsa_key.hashN.to_bytes(4, "little")
)
self.logger.debug(f"Send handshake {result.hex()}") writer.write(result)
await writer.drain()
self.transport.write(result) data: bytes = await reader.read(4096)
if len(data) == 0:
self.logger.debug("Connection closed")
return
await self.dataReceived(data, reader, writer)
await writer.drain()
except ConnectionResetError as e:
self.logger.debug("Connection reset, disconnecting")
return
def connectionLost(self, reason) -> None: def dataReceived(self, data: bytes, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
self.logger.debug(
f"{self.transport.getPeer().host} Disconnected - {reason.value}"
)
def dataReceived(self, data: bytes) -> None:
self.logger.debug(f"Receive data {data.hex()}") self.logger.debug(f"Receive data {data.hex()}")
client_ip = writer.get_extra_info('peername')[0]
crypt = AES.new(self.static_key, AES.MODE_ECB) crypt = AES.new(self.static_key, AES.MODE_ECB)
try: try:
data_dec = crypt.decrypt(data) data_dec = crypt.decrypt(data)
except Exception as e: except Exception as e:
self.logger.error(f"Failed to decrypt UserDB request from {self.transport.getPeer().host} because {e} - {data.hex()}") self.logger.error(f"Failed to decrypt UserDB request from {client_ip} because {e} - {data.hex()}")
self.logger.debug(f"Decrypt data {data_dec.hex()}") self.logger.debug(f"Decrypt data {data_dec.hex()}")
@ -99,7 +108,7 @@ class IDZUserDBProtocol(Protocol):
self.logger.info(f"Userdb serverbox request {data_dec.hex()}") self.logger.info(f"Userdb serverbox request {data_dec.hex()}")
self.skip_next = True self.skip_next = True
self.transport.write(b"\x00") writer.write(b"\x00")
return return
elif magic == 0x01020304: elif magic == 0x01020304:
@ -119,21 +128,21 @@ class IDZUserDBProtocol(Protocol):
self.version_internal = None self.version_internal = None
self.logger.debug( self.logger.debug(
f"Userdb v{self.version} handshake response from {self.transport.getPeer().host}" f"Userdb v{self.version} handshake response from {client_ip}"
) )
return return
elif self.skip_next: elif self.skip_next:
self.skip_next = False self.skip_next = False
self.transport.write(b"\x00") writer.write(b"\x00")
return return
elif self.version is None: elif self.version is None:
# We didn't get a handshake before, and this isn't one now, so we're up the creek # We didn't get a handshake before, and this isn't one now, so we're up the creek
self.logger.info( self.logger.info(
f"Bad UserDB request from from {self.transport.getPeer().host}" f"Bad UserDB request from from {client_ip}"
) )
self.transport.write(b"\x00") writer.write(b"\x00")
return return
cmd = struct.unpack_from("<H", data_dec, 0)[0] cmd = struct.unpack_from("<H", data_dec, 0)[0]
@ -147,7 +156,7 @@ class IDZUserDBProtocol(Protocol):
handler = handler_cls(self.core_config, self.game_config, self.version_internal) handler = handler_cls(self.core_config, self.game_config, self.version_internal)
self.logger.info( self.logger.info(
f"Userdb v{self.version} {handler.name} request from {self.transport.getPeer().host}" f"Userdb v{self.version} {handler.name} request from {client_ip}"
) )
response = handler.handle(data_dec) response = handler.handle(data_dec)
@ -156,46 +165,4 @@ class IDZUserDBProtocol(Protocol):
crypt = AES.new(self.static_key, AES.MODE_ECB) crypt = AES.new(self.static_key, AES.MODE_ECB)
response_enc = crypt.encrypt(response) response_enc = crypt.encrypt(response)
self.transport.write(response_enc) writer.write(response_enc)
class IDZUserDBFactory(Factory):
protocol = IDZUserDBProtocol
def __init__(
self,
cfg: CoreConfig,
game_cfg: IDZConfig,
keys: List[IDZKey],
handlers: List[Dict],
) -> None:
self.core_config = cfg
self.game_config = game_cfg
self.keys = keys
self.handlers = handlers
def buildProtocol(self, addr):
return IDZUserDBProtocol(
self.core_config, self.game_config, self.keys, self.handlers
)
class IDZUserDBWeb(resource.Resource):
def __init__(self, core_cfg: CoreConfig, game_cfg: IDZConfig):
super().__init__()
self.isLeaf = True
self.core_config = core_cfg
self.game_config = game_cfg
self.logger = logging.getLogger("idz")
def render_POST(self, request: Request) -> bytes:
self.logger.info(
f"IDZUserDBWeb POST from {request.getClientAddress().host} to {request.uri} with data {request.content.getvalue()}"
)
return b""
def render_GET(self, request: Request) -> bytes:
self.logger.info(
f"IDZUserDBWeb GET from {request.getClientAddress().host} to {request.uri}"
)
return b""