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