From bef2415ee7f4f7516a25eb7821cd2fcc60bb9549 Mon Sep 17 00:00:00 2001 From: John Burwell Date: Sun, 27 Apr 2025 12:30:22 -0500 Subject: [PATCH] Add logging and multithreading over AX.25 --- base_station/server.py | 140 ++++++++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 36 deletions(-) diff --git a/base_station/server.py b/base_station/server.py index 15b519e..003caf3 100644 --- a/base_station/server.py +++ b/base_station/server.py @@ -1,16 +1,22 @@ -import socket +#!/usr/bin/env python3 + import time import threading +import os +import logging +import logging.handlers +import socket import ax25 +import ax25.ports import ax25.socket +# Settings BASE_CALLSIGN = "KI5QKX-10" NETWORK_NAME = "HAMNET-HOUSTON" - BEACON_INTERVAL = 10 # seconds -BEACON_CALLSIGN = "CRAPR-0" +BEACON_CALLSIGN = "QST-0" -# Fake IP pool +# IP pool for assigning to clients IP_POOL = [ "44.127.254.12", "44.127.254.13", @@ -24,72 +30,134 @@ DNS_SERVER = "44.127.254.1" active_leases = {} +# Setup logging +os.makedirs("logs", exist_ok=True) +logger = logging.getLogger("craprniac") +logger.setLevel(logging.INFO) + +log_handler = logging.handlers.TimedRotatingFileHandler( + "logs/craprniac.log", + when="midnight", + interval=1, + backupCount=7 +) +log_handler.setFormatter(logging.Formatter( + "%(asctime)s [%(levelname)s] %(message)s" +)) +logger.addHandler(log_handler) + +console_handler = logging.StreamHandler() +console_handler.setFormatter(logging.Formatter( + "%(asctime)s [%(levelname)s] %(message)s" +)) +logger.addHandler(console_handler) + +# Packet builders def build_beacon(): return f"0.1|CRAP_BEACON|{BASE_CALLSIGN}|{NETWORK_NAME}" def build_accept(client_callsign, ip_address): return f"0.1|CRAP_ACCEPT|{client_callsign}|{NETWORK_NAME}|{ip_address}/{NETMASK}|{GATEWAY}|{DNS_SERVER}|{LEASE_TIME}" -def handle_request(data, addr, sock): +# Beaconing loop +def beacon_loop(beacon_sock): + while True: + try: + # beacon_sock.connect(BEACON_CALLSIGN) + beacon_sock.sendto(build_beacon().encode('utf-8'), "QST-0") + logger.info("Sent beacon") + except Exception as e: + logger.error(f"Beacon send failed: {e}") + time.sleep(BEACON_INTERVAL) + +# Handle a single client connection +def handle_client(conn, addr): + try: + logger.info(f"[{addr}] Connection established.") + + while True: + data = conn.recv(1024) + if not data: + logger.info(f"[{addr}] Connection closed.") + break + + decoded = data.decode('utf-8') + handle_request(decoded, addr, conn) + + except Exception as e: + logger.error(f"[{addr}] Error: {e}") + finally: + conn.close() + +# Handle incoming requests +def handle_request(data, src_callsign, conn): parts = data.split('|') + if len(parts) < 2: + logger.warning(f"[{src_callsign}] Received malformed message: {data}") + return + if parts[1] == "CRAP_BEACON": return - if len(parts) < 4: - print(f"Received malformed message from {addr}: {data}") - return if parts[1] != "CRAP_REQUEST": - print(f"Ignoring non-request message: {data}") + logger.warning(f"[{src_callsign}] Ignoring unknown message: {data}") return - + client_callsign = parts[2] network_requested = parts[3] if network_requested != NETWORK_NAME: - print(f"Client {client_callsign} requested wrong network ({network_requested}). Ignored.") + logger.warning(f"[{client_callsign}] Requested wrong network ({network_requested}). Ignored.") return - print(f"Received join request from {client_callsign} ({addr}).") + logger.info(f"[{client_callsign}] Join request received.") # Assign IP if client_callsign in active_leases: assigned_ip = active_leases[client_callsign] - print(f"Client {client_callsign} already has IP {assigned_ip}.") + logger.info(f"[{client_callsign}] Already assigned {assigned_ip}.") elif IP_POOL: assigned_ip = IP_POOL.pop(0) active_leases[client_callsign] = assigned_ip - print(f"Assigned IP {assigned_ip} to {client_callsign}.") + logger.info(f"[{client_callsign}] Assigned new IP {assigned_ip}.") else: - print(f"No available IPs for {client_callsign}!") + logger.error(f"[{client_callsign}] No available IPs to assign!") return - # Send ACCEPT - accept_message = build_accept(client_callsign, assigned_ip) - sock.sendto(accept_message.encode('utf-8'), addr) - print(f"Sent CRAP_ACCEPT to {client_callsign}.") - -def beacon_loop(sock): - while True: - beacon = build_beacon().encode('utf-8') - sock.sendto(beacon, (BEACON_CALLSIGN,)) - print(f"Sent beacon: {beacon.decode('utf-8')}") - time.sleep(BEACON_INTERVAL) + # Send CRAP_ACCEPT + try: + conn.send(build_accept(client_callsign, assigned_ip).encode('utf-8')) + logger.info(f"[{client_callsign}] Sent CRAP_ACCEPT.") + except Exception as e: + logger.error(f"[{client_callsign}] Failed to send CRAP_ACCEPT: {e}") +# Main server function def main(): - # sock = socket.socket(socket.AF_AX25, socket.SOCK_DGRAM) # Old python networking way - sock = ax25.socket.Socket() - sock.bind((BASE_CALLSIGN,)) # Bind to our station callsign - - print(f"Base Station {BASE_CALLSIGN} starting up on {NETWORK_NAME}...") + # Beaconing socket + beacon_sock = ax25.socket.Socket(socket.SOCK_DGRAM) + beacon_sock.bind(BASE_CALLSIGN) - # Start beaconing in a separate thread - threading.Thread(target=beacon_loop, args=(sock,), daemon=True).start() + # Server listening socket + server_sock = ax25.socket.Socket() + server_sock.bind(BASE_CALLSIGN) + server_sock.listen() + + logger.info(f"Base Station {BASE_CALLSIGN} starting up on {NETWORK_NAME}...") + + threading.Thread(target=beacon_loop, args=(beacon_sock,), daemon=True).start() while True: - data, addr = sock.recvfrom(1024) - decoded = data.decode('utf-8') - handle_request(decoded, addr, sock) + try: + conn, addr = server_sock.accept() + logger.info(f"Accepted connection from {addr}") + + client_thread = threading.Thread(target=handle_client, args=(conn, addr)) + client_thread.daemon = True + client_thread.start() + + except Exception as e: + logger.error(f"Error accepting connection: {e}") if __name__ == "__main__": main()