Added a better connection approval mechanism than query_accept.
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
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()
|
||||
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user