From 523c4ac95816e73e22a6a7dc60760b091dbfee64 Mon Sep 17 00:00:00 2001 From: John Burwell Date: Sun, 27 Apr 2025 12:39:30 -0500 Subject: [PATCH] apply threading and ax.25 to client --- client_node/client.py | 142 ++++++++++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 55 deletions(-) diff --git a/client_node/client.py b/client_node/client.py index e11692b..97fcf49 100644 --- a/client_node/client.py +++ b/client_node/client.py @@ -1,82 +1,114 @@ -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 CLIENT_CALLSIGN = "N0CALL-7" +BEACON_CALLSIGN = "KI5QKX-10" # We expect the server to be beaconing from here +# Setup logging +os.makedirs("logs", exist_ok=True) +logger = logging.getLogger("craprniac_client") +logger.setLevel(logging.INFO) + +log_handler = logging.handlers.TimedRotatingFileHandler( + "logs/craprniac_client.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) + +# Globals for network state configured = False lease_expiration = None -def parse_message(data): - parts = data.split('|') - return parts - +# Packet builders def build_request(network_name): - return f"0.1|CRAP_REQUEST|{CLIENT_CALLSIGN}|{network_name}" + return f"0.1|CRAP_REQUEST|{CLIENT_CALLSIGN}|{network_name}".encode('utf-8') +# Apply network configuration (mock) def apply_network_config(assigned_ip, gateway, dns, lease_time): global configured, lease_expiration - print("\n[Network Configuration]") - print(f" Bringing up interface: ax0") - print(f" Assigned IP address: {assigned_ip}") - print(f" Default Gateway: {gateway}") - print(f" DNS Server: {dns}") - print(f" Lease Time: {lease_time} seconds") - print(" Interface ax0 configured successfully! 🚀") - print("----------------------------------------") - + logger.info(f"Applying network config:") + logger.info(f" Assigned IP: {assigned_ip}") + logger.info(f" Gateway: {gateway}") + logger.info(f" DNS Server: {dns}") + logger.info(f" Lease Time: {lease_time} seconds") configured = True lease_expiration = time.time() + int(lease_time) -def main(): +# Reset network configuration (mock) +def reset_network_config(): global configured, lease_expiration + logger.info("Resetting network configuration (lease expired)") + configured = False + lease_expiration = None - sock = socket.socket(socket.AF_AX25, socket.SOCK_DGRAM) - sock.bind((CLIENT_CALLSIGN,)) # Bind to our own callsign - - print(f"Client {CLIENT_CALLSIGN} listening for CRAPRNIAC beacons...") +# Main Client Logic +def main(): + # Setup DGRAM socket to listen for beacons + beacon_sock = ax25.socket.Socket(socket.SOCK_DGRAM) + beacon_sock.bind(CLIENT_CALLSIGN) + + logger.info(f"Client {CLIENT_CALLSIGN} started and waiting for beacons...") while True: - # Check lease expiration - if configured and lease_expiration and time.time() >= lease_expiration: - print("\n[Lease Expired]") - print(" Tearing down interface ax0...") - print(" Ready to rejoin network.") - print("----------------------------------------") - configured = False - lease_expiration = None + try: + if configured and lease_expiration and time.time() >= lease_expiration: + reset_network_config() - data, addr = sock.recvfrom(1024) - decoded = data.decode('utf-8') - parts = parse_message(decoded) + data, src_callsign = beacon_sock.recvfrom(1024) # recvfrom, not recv + parts = data.decode('utf-8').split('|') - if len(parts) < 2: - continue - - if parts[1] == "CRAP_BEACON": - if not configured: + if len(parts) < 2: + continue + + if parts[1] == "CRAP_BEACON" and not configured: base_callsign = parts[2] network_name = parts[3] - print(f"\nHeard beacon from {base_callsign} ({addr}):") - print(f" Network: {network_name}") - print("Sending join request...") - request = build_request(network_name).encode('utf-8') - sock.sendto(request, (base_callsign,)) - - elif parts[1] == "CRAP_ACCEPT": - client_callsign = parts[2] - network_name = parts[3] - assigned_ip = parts[4] - gateway = parts[5] - dns = parts[6] - lease_time = parts[7] - print(f"\nReceived CRAP_ACCEPT:") - print(f" Assigned IP: {assigned_ip}") - print(f" Gateway: {gateway}") - print(f" DNS Server: {dns}") - print(f" Lease Time: {lease_time} seconds") + logger.info(f"Received beacon from {base_callsign} (network {network_name})") - apply_network_config(assigned_ip, gateway, dns, lease_time) + # Now create a connection socket for CRAP_REQUEST + session_sock = ax25.socket.Socket() # SOCK_STREAM by default + session_sock.bind(CLIENT_CALLSIGN) + session_sock.connect(base_callsign) + session_sock.send(build_request(network_name)) + logger.info(f"Sent CRAP_REQUEST to {base_callsign}") + + response = session_sock.recv(1024) + parts = response.decode('utf-8').split('|') + + if parts[1] == "CRAP_ACCEPT": + assigned_ip = parts[4] + gateway = parts[5] + dns = parts[6] + lease_time = parts[7] + logger.info(f"Received CRAP_ACCEPT from {base_callsign}") + apply_network_config(assigned_ip, gateway, dns, lease_time) + + session_sock.close() + + except Exception as e: + logger.error(f"Client error: {e}") + time.sleep(5) if __name__ == "__main__": main()