Added a better connection approval mechanism than query_accept.

This commit is contained in:
Michael Woods
2025-01-04 12:12:23 -05:00
parent 9755809929
commit bb5f07dbad
3 changed files with 86 additions and 18 deletions

View File

@@ -6,6 +6,8 @@ from enum import Enum
import bz2 import bz2
from typing import Union, Self from typing import Union, Self
import datetime import datetime
import logging
import ax25
class PacketServerConnection(Connection): class PacketServerConnection(Connection):
@@ -20,6 +22,7 @@ class PacketServerConnection(Connection):
self.data_lock = Lock() self.data_lock = Lock()
self.connection_created = datetime.datetime.now(datetime.UTC) self.connection_created = datetime.datetime.now(datetime.UTC)
self.connection_last_activity = datetime.datetime.now(datetime.UTC) self.connection_last_activity = datetime.datetime.now(datetime.UTC)
self.closing = False
@property @property
@@ -38,20 +41,26 @@ class PacketServerConnection(Connection):
def connected(self): def connected(self):
print("connected") print("connected")
logging.debug(f"new connection from {self.call_from} to {self.call_to}")
for fn in PacketServerConnection.connection_subscribers: for fn in PacketServerConnection.connection_subscribers:
fn(self) fn(self)
def disconnected(self): def disconnected(self):
pass logging.debug(f"connection disconnected: {self.call_from} -> {self.call_to}")
def data_received(self, pid, data): def data_received(self, pid, data):
self.connection_last_activity = datetime.datetime.now(datetime.UTC) self.connection_last_activity = datetime.datetime.now(datetime.UTC)
logging.debug(f"received data: {data}")
with self.data_lock: with self.data_lock:
logging.debug(f"fed received data to unpacker {data}")
self.data.feed(data) self.data.feed(data)
for fn in PacketServerConnection.receive_subscribers: for fn in PacketServerConnection.receive_subscribers:
logging.debug("found function to notify about received data")
fn(self) fn(self)
logging.debug("notified function about received data")
def send_data(self, data: Union[bytes, bytearray]): def send_data(self, data: Union[bytes, bytearray]):
logging.debug(f"sending data: {data}")
self.connection_last_activity = datetime.datetime.now(datetime.UTC) self.connection_last_activity = datetime.datetime.now(datetime.UTC)
super().send_data(data) super().send_data(data)

View File

@@ -1,13 +1,18 @@
import pe.app import pe.app
from ..common import PacketServerConnection import packetserver.common
from .constants import default_server_config from packetserver.server.constants import default_server_config
from copy import deepcopy from copy import deepcopy
import ax25 import ax25
from pathlib import Path from pathlib import Path
import ZODB, ZODB.FileStorage import ZODB, ZODB.FileStorage
from BTrees.OOBTree import OOBTree from BTrees.OOBTree import OOBTree
from .requests import process_incoming_data from persistent.mapping import PersistentMapping
from .requests import standard_handlers from persistent.list import PersistentList
from packetserver.server.requests import process_incoming_data
from packetserver.server.requests import standard_handlers
import logging
import signal
import time
class Server: class Server:
@@ -26,34 +31,69 @@ class Server:
if data_path.joinpath("data.zopedb").exists(): if data_path.joinpath("data.zopedb").exists():
if not data_path.joinpath("data.zopedb").is_file(): if not data_path.joinpath("data.zopedb").is_file():
raise FileExistsError("data.zopedb exists as non-file in specified path") raise FileExistsError("data.zopedb exists as non-file in specified path")
self.home_dir = data_dir self.home_dir = data_path
else: else:
if data_path.exists(): if data_path.exists():
raise FileExistsError(f"Non-Directory path '{data_dir}' already exists.") raise FileExistsError(f"Non-Directory path '{data_dir}' already exists.")
else: else:
data_path.mkdir() data_path.mkdir()
self.home_dir = data_dir self.home_dir = data_path
self.storage = ZODB.FileStorage.FileStorage(self.data_file) self.storage = ZODB.FileStorage.FileStorage(self.data_file)
self.db = ZODB.DB(self.storage) self.db = ZODB.DB(self.storage)
with self.db.transaction() as conn: with self.db.transaction() as conn:
if 'config' not in conn.root(): if 'config' not in conn.root():
conn.root.config = deepcopy(default_server_config) conn.root.config = PersistentMapping(deepcopy(default_server_config))
conn.root.config['blacklist'] = PersistentList()
if 'users' not in conn.root(): if 'users' not in conn.root():
conn.root.users = OOBTree() conn.root.users = OOBTree()
self.app = pe.app.Application() self.app = pe.app.Application()
PacketServerConnection.receive_subscribers.append(lambda x: self.server_receiver(x)) packetserver.common.PacketServerConnection.receive_subscribers.append(lambda x: self.server_receiver(x))
packetserver.common.PacketServerConnection.connection_subscribers.append(lambda x: self.server_connection_bouncer(x))
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
@property @property
def data_file(self) -> str: def data_file(self) -> str:
return str(Path(self.home_dir).joinpath('data.zopedb')) return str(Path(self.home_dir).joinpath('data.zopedb'))
def server_receiver(self, conn: PacketServerConnection): def server_connection_bouncer(self, conn: packetserver.common.PacketServerConnection):
logging.debug("new connection bouncer checking for blacklist")
# blacklist check
blacklisted = False
with self.db.transaction() as storage:
if 'blacklist' in storage.root.config:
bl = storage.root.config['blacklist']
logging.debug(f"A blacklist exists: {bl}")
base = ax25.Address(conn.remote_callsign).call
logging.debug(f"Checking callsign {base.upper()}")
if base.upper() in bl:
logging.debug(f"Connection from blacklisted callsign {base}")
conn.closing = True
blacklisted = True
if blacklisted:
count = 0
while count < 10:
time.sleep(.5)
if conn.state.name == "CONNECTED":
break
conn.close()
def server_receiver(self, conn: packetserver.common.PacketServerConnection):
logging.debug("running server receiver")
process_incoming_data(conn, self) process_incoming_data(conn, self)
def start(self): def start(self):
self.app.start(self.pe_server, self.pe_port) self.app.start(self.pe_server, self.pe_port)
self.app.register_callsigns(self.callsign) self.app.register_callsigns(self.callsign)
def exit_gracefully(self, signum, frame):
self.stop()
def stop(self): def stop(self):
cm = self.app._engine._active_handler._handlers[1]._connection_map
for key in cm._connections.keys():
cm._connections[key].close()
self.app.stop() self.app.stop()
self.storage.close()
self.db.close()

View File

@@ -1,11 +1,12 @@
"""Module for handling requests as they arrive to connection objects and servers.""" """Module for handling requests as they arrive to connection objects and servers."""
from . import PacketServerConnection
from . import Server
from msgpack.exceptions import OutOfData from msgpack.exceptions import OutOfData
from ..common import Message, Request, Response from packetserver.common import Message, Request, Response, PacketServerConnection
import logging
def handle_root_get(req: Request, conn: PacketServerConnection, server: Server): def handle_root_get(req: Request, conn: PacketServerConnection,
server: 'packetserver.server.Server'):
logging.debug(f"Received request: {req}")
response = Response.blank() response = Response.blank()
response.compression = Message.CompressionType.BZIP2 response.compression = Message.CompressionType.BZIP2
operator = "" operator = ""
@@ -22,7 +23,9 @@ def handle_root_get(req: Request, conn: PacketServerConnection, server: Server):
} }
if conn.state.name == "CONNECTED": if conn.state.name == "CONNECTED":
logging.debug(f"sending response: {response}, {response.compression}, {response.payload}")
conn.send_data(response.pack()) conn.send_data(response.pack())
logging.debug("response sent successfully")
standard_handlers = { standard_handlers = {
"": { "": {
@@ -30,24 +33,37 @@ standard_handlers = {
} }
} }
def handle_request(req: Request, conn: PacketServerConnection, server: Server): def handle_request(req: Request, conn: PacketServerConnection,
server: 'packetserver.server.Server'):
"""Handles a proper request by handing off to the appropriate function depending on method and Path.""" """Handles a proper request by handing off to the appropriate function depending on method and Path."""
logging.debug(f"asked to handle request: {req}")
if conn.closing:
logging.debug("Connection marked as closing. Ignoring it.")
return
if req.path in server.handlers: if req.path in server.handlers:
if req.method.name in server.handlers[req.path]: if req.method.name in server.handlers[req.path]:
logging.debug(f"found handler for req {req}")
server.handlers[req.path][req.method.name](req, conn, server) server.handlers[req.path][req.method.name](req, conn, server)
return return
logging.warning(f"unhandled request found: {req}")
response_404 = Response.blank() response_404 = Response.blank()
response_404.status_code = 404 response_404.status_code = 404
if conn.state.name == "CONNECTED": if conn.state.name == "CONNECTED":
conn.send_data(response_404.pack()) conn.send_data(response_404.pack())
logging.debug(f"Sent 404 in response to {req}")
def process_incoming_data(connection: PacketServerConnection, server: Server): def process_incoming_data(connection: 'packetserver.common.PacketServerConnection',
server: 'packetserver.server.Server'):
"""Handles incoming data.""" """Handles incoming data."""
logging.debug("Running process_incoming_data on connection")
with connection.data_lock: with connection.data_lock:
logging.debug("Data lock acquired")
while True: while True:
try: try:
msg = Message.partial_unpack(connection.data.unpack()) msg = Message.partial_unpack(connection.data.unpack())
logging.debug(f"parsed a Message from data received")
except OutOfData: except OutOfData:
logging.debug("no complete message yet, done until more data arrives")
break break
except ValueError: except ValueError:
r = Response.blank() r = Response.blank()
@@ -55,12 +71,15 @@ def process_incoming_data(connection: PacketServerConnection, server: Server):
r.payload = "BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE" r.payload = "BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE"
connection.send_data(r.pack()) connection.send_data(r.pack())
connection.send_data(b"BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE") connection.send_data(b"BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE")
try: try:
request = Request(msg) request = Request(msg)
logging.debug(f"parsed Message into request {request}")
except ValueError: except ValueError:
r = Response.blank() r = Response.blank()
r.status_code = 400 r.status_code = 400
r.payload = "BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE." r.payload = "BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE."
connection.send_data(r.pack()) connection.send_data(r.pack())
connection.send_data(b"BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE.") connection.send_data(b"BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE.")
logging.debug(f"attempting to handle request {request}")
handle_request(request, connection, server)
logging.debug("request handled")