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

View File

@@ -1,13 +1,18 @@
import pe.app
from ..common import PacketServerConnection
from .constants import default_server_config
import packetserver.common
from packetserver.server.constants import default_server_config
from copy import deepcopy
import ax25
from pathlib import Path
import ZODB, ZODB.FileStorage
from BTrees.OOBTree import OOBTree
from .requests import process_incoming_data
from .requests import standard_handlers
from persistent.mapping import PersistentMapping
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:
@@ -26,34 +31,69 @@ class Server:
if data_path.joinpath("data.zopedb").exists():
if not data_path.joinpath("data.zopedb").is_file():
raise FileExistsError("data.zopedb exists as non-file in specified path")
self.home_dir = data_dir
self.home_dir = data_path
else:
if data_path.exists():
raise FileExistsError(f"Non-Directory path '{data_dir}' already exists.")
else:
data_path.mkdir()
self.home_dir = data_dir
self.home_dir = data_path
self.storage = ZODB.FileStorage.FileStorage(self.data_file)
self.db = ZODB.DB(self.storage)
with self.db.transaction() as conn:
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():
conn.root.users = OOBTree()
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
def data_file(self) -> str:
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)
def start(self):
self.app.start(self.pe_server, self.pe_port)
self.app.register_callsigns(self.callsign)
def exit_gracefully(self, signum, frame):
self.stop()
def stop(self):
self.app.stop()
cm = self.app._engine._active_handler._handlers[1]._connection_map
for key in cm._connections.keys():
cm._connections[key].close()
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."""
from . import PacketServerConnection
from . import Server
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.compression = Message.CompressionType.BZIP2
operator = ""
@@ -22,7 +23,9 @@ def handle_root_get(req: Request, conn: PacketServerConnection, server: Server):
}
if conn.state.name == "CONNECTED":
logging.debug(f"sending response: {response}, {response.compression}, {response.payload}")
conn.send_data(response.pack())
logging.debug("response sent successfully")
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."""
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.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)
return
logging.warning(f"unhandled request found: {req}")
response_404 = Response.blank()
response_404.status_code = 404
if conn.state.name == "CONNECTED":
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."""
logging.debug("Running process_incoming_data on connection")
with connection.data_lock:
logging.debug("Data lock acquired")
while True:
try:
msg = Message.partial_unpack(connection.data.unpack())
logging.debug(f"parsed a Message from data received")
except OutOfData:
logging.debug("no complete message yet, done until more data arrives")
break
except ValueError:
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"
connection.send_data(r.pack())
connection.send_data(b"BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE")
try:
request = Request(msg)
logging.debug(f"parsed Message into request {request}")
except ValueError:
r = Response.blank()
r.status_code = 400
r.payload = "BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE."
connection.send_data(r.pack())
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")