Add logging and multithreading over AX.25

This commit is contained in:
John Burwell 2025-04-27 12:30:22 -05:00
parent 42cd5f37bd
commit bef2415ee7

View File

@ -1,16 +1,22 @@
import socket #!/usr/bin/env python3
import time import time
import threading import threading
import os
import logging
import logging.handlers
import socket
import ax25 import ax25
import ax25.ports
import ax25.socket import ax25.socket
# Settings
BASE_CALLSIGN = "KI5QKX-10" BASE_CALLSIGN = "KI5QKX-10"
NETWORK_NAME = "HAMNET-HOUSTON" NETWORK_NAME = "HAMNET-HOUSTON"
BEACON_INTERVAL = 10 # seconds BEACON_INTERVAL = 10 # seconds
BEACON_CALLSIGN = "CRAPR-0" BEACON_CALLSIGN = "QST-0"
# Fake IP pool # IP pool for assigning to clients
IP_POOL = [ IP_POOL = [
"44.127.254.12", "44.127.254.12",
"44.127.254.13", "44.127.254.13",
@ -24,72 +30,134 @@ DNS_SERVER = "44.127.254.1"
active_leases = {} 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(): def build_beacon():
return f"0.1|CRAP_BEACON|{BASE_CALLSIGN}|{NETWORK_NAME}" return f"0.1|CRAP_BEACON|{BASE_CALLSIGN}|{NETWORK_NAME}"
def build_accept(client_callsign, ip_address): 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}" 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('|') parts = data.split('|')
if len(parts) < 2:
logger.warning(f"[{src_callsign}] Received malformed message: {data}")
return
if parts[1] == "CRAP_BEACON": if parts[1] == "CRAP_BEACON":
return return
if len(parts) < 4:
print(f"Received malformed message from {addr}: {data}")
return
if parts[1] != "CRAP_REQUEST": if parts[1] != "CRAP_REQUEST":
print(f"Ignoring non-request message: {data}") logger.warning(f"[{src_callsign}] Ignoring unknown message: {data}")
return return
client_callsign = parts[2] client_callsign = parts[2]
network_requested = parts[3] network_requested = parts[3]
if network_requested != NETWORK_NAME: 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 return
print(f"Received join request from {client_callsign} ({addr}).") logger.info(f"[{client_callsign}] Join request received.")
# Assign IP # Assign IP
if client_callsign in active_leases: if client_callsign in active_leases:
assigned_ip = active_leases[client_callsign] 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: elif IP_POOL:
assigned_ip = IP_POOL.pop(0) assigned_ip = IP_POOL.pop(0)
active_leases[client_callsign] = assigned_ip 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: else:
print(f"No available IPs for {client_callsign}!") logger.error(f"[{client_callsign}] No available IPs to assign!")
return return
# Send ACCEPT # Send CRAP_ACCEPT
accept_message = build_accept(client_callsign, assigned_ip) try:
sock.sendto(accept_message.encode('utf-8'), addr) conn.send(build_accept(client_callsign, assigned_ip).encode('utf-8'))
print(f"Sent CRAP_ACCEPT to {client_callsign}.") logger.info(f"[{client_callsign}] Sent CRAP_ACCEPT.")
except Exception as e:
def beacon_loop(sock): logger.error(f"[{client_callsign}] Failed to send CRAP_ACCEPT: {e}")
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)
# Main server function
def main(): def main():
# sock = socket.socket(socket.AF_AX25, socket.SOCK_DGRAM) # Old python networking way # Beaconing socket
sock = ax25.socket.Socket() beacon_sock = ax25.socket.Socket(socket.SOCK_DGRAM)
sock.bind((BASE_CALLSIGN,)) # Bind to our station callsign beacon_sock.bind(BASE_CALLSIGN)
print(f"Base Station {BASE_CALLSIGN} starting up on {NETWORK_NAME}...") # Server listening socket
server_sock = ax25.socket.Socket()
server_sock.bind(BASE_CALLSIGN)
server_sock.listen()
# Start beaconing in a separate thread logger.info(f"Base Station {BASE_CALLSIGN} starting up on {NETWORK_NAME}...")
threading.Thread(target=beacon_loop, args=(sock,), daemon=True).start()
threading.Thread(target=beacon_loop, args=(beacon_sock,), daemon=True).start()
while True: while True:
data, addr = sock.recvfrom(1024) try:
decoded = data.decode('utf-8') conn, addr = server_sock.accept()
handle_request(decoded, addr, sock) 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__": if __name__ == "__main__":
main() main()