# L'eseguibile si ottiene con pyinstaller --onefile --clean --noconsole server_UDP_crypto_protocollo_TLV_multicast_relay_simm_csharp.py # compatibile Windows 11 # compatibile Ubuntu 24.10 # compatibile python 3.12.7 # server.py (relay ibrido + controllo TCP non bloccante) import argparse import socket import struct import os import threading import time import queue # Per la comunicazione tra thread (output) import select # Per accept non bloccante nel TCP server from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.backends import default_backend from cryptography.fernet import Fernet # --- Configurazione --- MULTICAST_GROUP = '224.1.1.1' MULTICAST_PORT = 5007 TCP_CONTROL_PORT = 5008 # Porta per il controllo TCP MAX_PACKET_SIZE = 65507 FRAGMENT_SIZE = 1024 CLIENT_TIMEOUT = 60 # --- Costanti TLV (UDP) --- TLV_FRAGMENT = 1 TLV_SERVER_PUBLIC_KEY_RESPONSE = 5 TLV_REQUEST_SERVER_PUBLIC_KEY = 7 TLV_DISCONNECT_REQUEST = 8 TLV_KEEPALIVE = 9 TLV_SENDER_ID = 10 TLV_NOTIFY_CONNECT = 11 TLV_NOTIFY_DISCONNECT = 12 TLV_ENCRYPTED_SYMMETRIC_KEY = 13 TLV_SYMMETRICALLY_ENCRYPTED_MESSAGE = 14 # --- Coda e Evento per Comunicazione Inter-Thread --- output_queue = queue.Queue() shutdown_event = threading.Event() # --- Funzione per Log/Output centralizzato --- def log_message(message): """Invia messaggi di log alla coda per l'inoltro TCP.""" timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) try: output_queue.put(f"[{timestamp}] {message}") except Exception as e: # Fallback sulla console se la coda dà problemi (improbabile) print(f"[Fallback Log Error: {e}] {timestamp} {message}") # --- Funzioni di utilità (invariate, tranne print -> log_message) --- def create_tlv(type, value): type_bytes = struct.pack('!H', type) length_bytes = struct.pack('!H', len(value)) return type_bytes + length_bytes + value def parse_tlv(data): if len(data) < 4: raise ValueError("Dati TLV insufficienti.") type, length = struct.unpack('!HH', data[:4]) if len(data) < 4 + length: raise ValueError("Lunghezza TLV non corrisponde ai dati.") value = data[4:4 + length] remaining_data = data[4 + length:] return type, value, remaining_data def encrypt_data_asymmetric(data, public_key): return public_key.encrypt(data, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)) def decrypt_data_asymmetric(ciphertext, private_key): try: return private_key.decrypt(ciphertext, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)) except Exception as e: log_message(f"Errore durante la decrittazione asimmetrica: {e}") # LOG return None def encrypt_data_symmetric(data, fernet_key): f = Fernet(fernet_key) return f.encrypt(data) def decrypt_data_symmetric(token, fernet_key): f = Fernet(fernet_key) try: return f.decrypt(token) except Exception as e: log_message(f"Errore durante la decrittazione simmetrica: {e}") # LOG return None def fragment_data(data, fragment_size): return [data[i:i+fragment_size] for i in range(0, len(data), fragment_size)] def defragment_data(fragments): return b''.join(fragments) def create_relay_message_symmetric(sender_address, message_plaintext, recipient_fernet_key): sender_address_bytes = sender_address[0].encode() + struct.pack("!H", sender_address[1]) tlv_sender = create_tlv(TLV_SENDER_ID, sender_address_bytes) encrypted_message = encrypt_data_symmetric(message_plaintext, recipient_fernet_key) tlv_encrypted_message = create_tlv(TLV_SYMMETRICALLY_ENCRYPTED_MESSAGE, encrypted_message) return tlv_sender + tlv_encrypted_message # --- Classe UDPServerThread (Leggermente modificata per usare log_message) --- class UDPServerThread(threading.Thread): def __init__(self, sock, private_key, public_key): super().__init__() self.sock = sock self.private_key = private_key self.public_key = public_key self.running = True self.received_fragments = {} self.client_keys = {} self.lock = threading.Lock() def run(self): cleanup_thread = threading.Thread(target=self.cleanup_inactive_clients, daemon=True) cleanup_thread.start() log_message("UDP Server thread avviato.") # LOG while self.running: try: # Usiamo select per rendere recvfrom interrompibile da self.running readable, _, _ = select.select([self.sock], [], [], 0.5) if readable: data, address = self.sock.recvfrom(MAX_PACKET_SIZE) self.process_data(data, address) except socket.timeout: # Select timeout continue except (ValueError, struct.error, OSError) as e: if self.running: log_message(f"UDP Errore socket/parsing (client {address}): {e}") # LOG except Exception as ex: if self.running: log_message(f"UDP Errore non gestito nel loop principale (client {address}): {ex}") # LOG def process_data(self, data, address): is_registered = False with self.lock: if address in self.client_keys: is_registered = True self.client_keys[address] = (self.client_keys[address][0], time.time()) try: offset = 0 while offset < len(data): type, value, _ = parse_tlv(data[offset:]) offset += 4 + len(value) if type == TLV_FRAGMENT: self.handle_fragment(value, address) elif type == TLV_REQUEST_SERVER_PUBLIC_KEY: self.send_server_public_key(address) elif type == TLV_ENCRYPTED_SYMMETRIC_KEY: self.register_client_symmetric_key(value, address) elif type == TLV_KEEPALIVE: pass # log_message(f"Keep-alive ricevuto da {address}") else: log_message(f"Tipo TLV UDP {type} ricevuto al livello esterno da {address}. Ignorato.") # LOG except ValueError as e: log_message(f"Errore parsing TLV UDP da {address}: {e}") # LOG except Exception as e: log_message(f"Errore durante l'elaborazione dei dati UDP da {address}: {e}") # LOG def register_client_symmetric_key(self, encrypted_key_value, address): decrypted_fernet_key = decrypt_data_asymmetric(encrypted_key_value, self.private_key) if decrypted_fernet_key: try: Fernet(decrypted_fernet_key) with self.lock: # Notifica gli altri prima di aggiungere for other_address, (other_key, _) in self.client_keys.items(): if other_address != address: try: address_bytes = address[0].encode() + struct.pack("!H", address[1]) tlv_connect_notify = create_tlv(TLV_NOTIFY_CONNECT, address_bytes) self.sock.sendto(tlv_connect_notify, other_address) except Exception as e: log_message(f"Errore invio notifica connessione a {other_address}: {e}") # LOG # Aggiungi nuovo client self.client_keys[address] = (decrypted_fernet_key, time.time()) log_message(f"Chiave simmetrica del client UDP {address} registrata con successo.") # LOG # Notifica al nuovo chi c'era già with self.lock: current_clients = list(self.client_keys.keys()) for existing_addr in current_clients: if existing_addr != address: try: addr_bytes = existing_addr[0].encode() + struct.pack("!H", existing_addr[1]) tlv_notify = create_tlv(TLV_NOTIFY_CONNECT, addr_bytes) self.sock.sendto(tlv_notify, address) except Exception as e: log_message(f"Errore invio notifica client esistente {existing_addr} al nuovo client {address}: {e}") # LOG except (ValueError, TypeError) as e: log_message(f"Errore: chiave simmetrica ricevuta da UDP {address} non valida: {e}") # LOG else: log_message(f"Errore: impossibile decrittare la chiave simmetrica da UDP {address}.") # LOG def send_server_public_key(self, address): public_pem = self.public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) tlv_response = create_tlv(TLV_SERVER_PUBLIC_KEY_RESPONSE, public_pem) try: self.sock.sendto(tlv_response, address) log_message(f"Chiave pubblica del server inviata a UDP {address}") # LOG except Exception as e: log_message(f"Errore invio chiave pubblica a UDP {address}: {e}") # LOG def handle_fragment(self, value, address): try: msg_id = struct.unpack('!I', value[:4])[0] seq_num, total_fragments = struct.unpack('!HH', value[4:8]) fragment_data_content = value[8:] if msg_id not in self.received_fragments: self.received_fragments[msg_id] = {'fragments': {}, 'address': address, 'timestamp': time.time()} if self.received_fragments[msg_id]['address'] != address: log_message(f"WARN UDP: Indirizzo mittente cambiato per msg_id {msg_id}. Atteso {self.received_fragments[msg_id]['address']}, ricevuto da {address}. Frammento scartato.") # LOG return self.received_fragments[msg_id]['fragments'][seq_num] = fragment_data_content self.received_fragments[msg_id]['timestamp'] = time.time() if len(self.received_fragments[msg_id]['fragments']) == total_fragments: self.reassemble_decrypt_and_process(msg_id) except struct.error as e: log_message(f"Errore unpacking frammento UDP da {address}: {e}") # LOG except Exception as e: log_message(f"Errore gestione frammento UDP da {address}: {e}") # LOG def reassemble_decrypt_and_process(self, msg_id): fragment_info = self.received_fragments.pop(msg_id, None) if not fragment_info: return sender_address = fragment_info['address'] fragments_dict = fragment_info['fragments'] total_fragments = len(fragments_dict) log_message(f"UDP Riassemblando {total_fragments} frammenti per msg_id {msg_id} da {sender_address}") # LOG if set(fragments_dict.keys()) != set(range(total_fragments)): log_message(f"Errore UDP: Frammenti mancanti per msg_id {msg_id} da {sender_address}. Scartato.") # LOG return sorted_fragments = [fragments_dict[i] for i in range(total_fragments)] reassembled_data = defragment_data(sorted_fragments) try: inner_type, inner_value, _ = parse_tlv(reassembled_data) if inner_type == TLV_SYMMETRICALLY_ENCRYPTED_MESSAGE: with self.lock: if sender_address not in self.client_keys: log_message(f"Messaggio UDP ricevuto da client non registrato o disconnesso ({sender_address}). Ignorato.") # LOG return sender_fernet_key = self.client_keys[sender_address][0] decrypted_message = decrypt_data_symmetric(inner_value, sender_fernet_key) if decrypted_message: try: message_text = decrypted_message.decode('utf-8') log_message(f"UDP Messaggio decrittato da {sender_address}: {message_text}") # LOG if message_text == "DISCONNECT": self.handle_disconnect_request(sender_address) else: self.relay_message_to_others(decrypted_message, sender_address) except UnicodeDecodeError: log_message(f"UDP Dati binari decrittati ricevuti da {sender_address}, relay in corso...") # LOG self.relay_message_to_others(decrypted_message, sender_address) else: log_message(f"Errore UDP nella decrittazione simmetrica del messaggio da {sender_address}.") # LOG else: log_message(f"Tipo TLV interno UDP ({inner_type}) non previsto dopo riassemblaggio da {sender_address}.") # LOG except ValueError as e: log_message(f"Errore parsing TLV interno UDP dopo riassemblaggio da {sender_address}: {e}") # LOG except Exception as e: log_message(f"Errore generico UDP durante processamento messaggio riassemblato da {sender_address}: {e}") # LOG def relay_message_to_others(self, message_plaintext, sender_address): with self.lock: clients_to_relay = list(self.client_keys.items()) for recipient_address, (recipient_fernet_key, _) in clients_to_relay: if recipient_address != sender_address: try: relay_msg_payload = create_relay_message_symmetric(sender_address, message_plaintext, recipient_fernet_key) if len(relay_msg_payload) > MAX_PACKET_SIZE: log_message(f"WARN UDP: Messaggio relayato da {sender_address} a {recipient_address} troppo grande ({len(relay_msg_payload)} bytes). Non inviato.") # LOG continue self.sock.sendto(relay_msg_payload, recipient_address) except Exception as e: log_message(f"Errore nel relay UDP del messaggio da {sender_address} a {recipient_address}: {e}") # LOG def handle_disconnect_request(self, address): log_message(f"Ricevuta richiesta di disconnessione UDP da {address}") # LOG self.remove_client(address, notify_others=True, reason="richiesta esplicita") def cleanup_inactive_clients(self): while self.running: time.sleep(CLIENT_TIMEOUT / 2) now = time.time() clients_to_remove = [] fragments_to_remove = [] current_fragment_keys = list(self.received_fragments.keys()) for msg_id in current_fragment_keys: if msg_id in self.received_fragments: if now - self.received_fragments[msg_id]['timestamp'] > CLIENT_TIMEOUT * 2: fragments_to_remove.append(msg_id) for msg_id in fragments_to_remove: if msg_id in self.received_fragments: removed_info = self.received_fragments.pop(msg_id) log_message(f"Rimosso messaggio UDP frammentato incompleto/vecchio (msg_id {msg_id} da {removed_info['address']})") # LOG with self.lock: current_clients = list(self.client_keys.items()) for address, (_, last_activity) in current_clients: if now - last_activity > CLIENT_TIMEOUT: clients_to_remove.append(address) for address in clients_to_remove: self.remove_client(address, notify_others=True, reason="timeout") def remove_client(self, address, notify_others=True, reason="sconosciuta"): removed_key = None with self.lock: if address in self.client_keys: removed_key = self.client_keys.pop(address) log_message(f"Client UDP {address} rimosso ({reason}).") # LOG else: return if notify_others and removed_key: with self.lock: remaining_clients = list(self.client_keys.keys()) for other_address in remaining_clients: try: address_bytes = address[0].encode() + struct.pack("!H", address[1]) tlv_disconnect_notify = create_tlv(TLV_NOTIFY_DISCONNECT, address_bytes) self.sock.sendto(tlv_disconnect_notify, other_address) except Exception as e: log_message(f"Errore invio notifica disconnessione UDP ({reason}) di {address} a {other_address}: {e}") # LOG def stop(self): log_message("Arresto UDP server thread...") # LOG self.running = False # --- Classe TCPServerThread --- class TCPServerThread(threading.Thread): def __init__(self, listen_socket, output_q, shutdown_evt): super().__init__() self.listen_socket = listen_socket self.output_queue = output_q self.main_shutdown_event = shutdown_evt # Evento per segnalare shutdown all'app principale self.running = True self.client_socket = None self.client_address = None def run(self): #log_message(f"TCP Control server in ascolto su porta {TCP_CONTROL_PORT}") # LOG self.listen_socket.listen(1) while self.running: # Attesa non bloccante di una connessione readable, _, _ = select.select([self.listen_socket], [], [], 1.0) # Timeout 1 sec if self.listen_socket in readable: try: self.client_socket, self.client_address = self.listen_socket.accept() self.client_socket.settimeout(0.5) # Timeout per recv non bloccante log_message(f"TCP Control client connesso da {self.client_address}") # LOG self.handle_client() # Gestisci il client connesso except OSError as e: if self.running: # Ignora errori se ci stiamo fermando log_message(f"Errore TCP accept: {e}") # LOG time.sleep(0.5) except Exception as e: log_message(f"Errore imprevisto TCP accept: {e}") # LOG if self.client_socket: self.client_socket.close() self.client_socket = None self.client_address = None # Se siamo qui significa che select è scaduto o accept ha fallito # Controlliamo se dobbiamo fermarci if self.main_shutdown_event.is_set(): log_message("TCP Server: Rilevato evento di shutdown principale.") # LOG self.stop() # Ferma questo thread log_message("TCP Control server thread terminato.") # LOG if self.listen_socket: self.listen_socket.close() def handle_client(self): """ Gestisce la ricezione di comandi e l'invio di output per un client connesso. """ while self.running and self.client_socket: # 1. Invia output in coda (se presente) try: while not self.output_queue.empty(): message = self.output_queue.get_nowait() try: self.client_socket.sendall((message + "\r\n").encode('utf-8')) except (socket.error, OSError) as send_err: log_message(f"Errore invio TCP a {self.client_address}: {send_err}. Disconnessione.") # LOG self.close_client_connection() return # Esce da handle_client self.output_queue.task_done() # Segnala che l'item è stato processato except queue.Empty: pass # Normale, nessuna uscita da inviare except Exception as q_err: log_message(f"Errore imprevisto gestione coda output TCP: {q_err}") # LOG # 2. Ricevi comandi (non bloccante grazie a settimeout) try: data = self.client_socket.recv(1024) # ------------ NUOVA RIGA DI DEBUG ------------ log_message(f"TCP Raw data received: {data!r}") # Logga i byte grezzi ricevuti # ------------------------------------------- if data: command = data.decode('utf-8').strip().lower() log_message(f"TCP Comando ricevuto da {self.client_address}: '{command}'") # LOG if command == "exit": log_message("Ricevuto comando 'exit'. Avvio shutdown generale.") # LOG self.main_shutdown_event.set() # Segnala all'app principale di fermarsi self.stop() # Ferma anche questo thread TCP # Non chiudere la connessione qui, verrà fatto nel ciclo esterno o in stop() return # Esce da handle_client # Aggiungere qui altri comandi se necessario else: try: # Invia un riscontro per comandi sconosciuti self.client_socket.sendall(f"Comando sconosciuto: {command}\r\n".encode('utf-8')) except (socket.error, OSError): self.close_client_connection() return else: # Recv ha restituito 0 bytes -> client disconnesso correttamente log_message(f"TCP Control client {self.client_address} disconnesso.") # LOG self.close_client_connection() return # Esce da handle_client (attenderà nuova connessione) except socket.timeout: # Nessun comando ricevuto, continua il ciclo continue except (socket.error, OSError) as recv_err: # Errore durante la ricezione (es. connessione interrotta) log_message(f"Errore ricezione TCP da {self.client_address}: {recv_err}. Disconnessione.") # LOG self.close_client_connection() return # Esce da handle_client except UnicodeDecodeError: log_message(f"Errore decodifica comando TCP da {self.client_address}.") # LOG try: self.client_socket.sendall("Errore: Inviare comandi testuali UTF-8.\r\n".encode('utf-8')) except (socket.error, OSError): self.close_client_connection() return except Exception as e: log_message(f"Errore imprevisto gestione client TCP {self.client_address}: {e}") # LOG self.close_client_connection() return # Breve pausa per non consumare CPU al 100% se non c'è attività # time.sleep(0.01) # Rimosso, select/timeout gestiscono l'attesa def close_client_connection(self): """ Chiude la connessione con il client corrente in modo sicuro. """ if self.client_socket: try: self.client_socket.shutdown(socket.SHUT_RDWR) except (OSError, socket.error): pass # Ignora errori se già chiuso try: self.client_socket.close() except (OSError, socket.error): pass # Ignora errori se già chiuso self.client_socket = None self.client_address = None def stop(self): """ Ferma il thread del server TCP. """ log_message("Arresto TCP Control server thread...") # LOG self.running = False self.close_client_connection() # Chiudere il listen_socket qui può causare problemi se accept è bloccato # Viene chiuso alla fine del run() o in caso di errore # Se necessario forzare sblocco di accept (raro con select): # try: # # Tentativo di connessione dummy per sbloccare accept # with socket.create_connection(("127.0.0.1", TCP_CONTROL_PORT), timeout=0.1): pass # except: pass if self.listen_socket: # Metti il listening socket in non-blocking mode prima di chiuderlo # Per evitare problemi su alcune piattaforme se accept() fosse bloccato # (Anche se usiamo select, è una sicurezza aggiuntiva) try: self.listen_socket.setblocking(False) self.listen_socket.close() log_message("TCP listening socket chiuso.") except Exception as e: log_message(f"Errore chiusura TCP listening socket: {e}") # --- Funzione principale del server (Modificata) --- def run_server(multicast_group, multicast_port, tcp_port): #def run_server(): global output_queue, shutdown_event # Rendi accessibili globalmente # --- Setup Socket UDP Multicast --- udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) udp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: #bind_addr = MULTICAST_GROUP if os.name != 'nt' else '0.0.0.0' bind_addr = multicast_group if os.name != 'nt' else '0.0.0.0' #udp_sock.bind((bind_addr, MULTICAST_PORT)) udp_sock.bind((bind_addr, multicast_port)) #log_message(f"UDP Server binding su {bind_addr}:{MULTICAST_PORT}") # LOG log_message(f"UDP Server binding su {bind_addr}:{multicast_port}") # LOG except OSError as e: log_message(f"Errore durante il bind del socket UDP: {e}") # LOG log_message("Potrebbe essere necessario eseguire come amministratore/root o la porta è già in uso.") # LOG return #group = socket.inet_aton(MULTICAST_GROUP) group = socket.inet_aton(multicast_group) mreq = group + socket.inet_aton('0.0.0.0') try: udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) #log_message(f"UDP Aggiunta membership al gruppo multicast {MULTICAST_GROUP}") # LOG log_message(f"UDP Aggiunta membership al gruppo multicast {multicast_group}") # LOG except OSError as e: log_message(f"Errore UDP nell'aggiungere la membership multicast: {e}") # LOG log_message("Verifica che l'indirizzo multicast sia valido e che le impostazioni di rete lo permettano.") # LOG udp_sock.close() return # udp_sock.settimeout(1.0) # Non più necessario qui, select gestisce il timeout nel thread #log_message(f"UDP Server in ascolto su {MULTICAST_GROUP}:{MULTICAST_PORT}") # LOG log_message(f"UDP Server in ascolto su {multicast_group}:{multicast_port}") # LOG # --- Setup Socket TCP Control --- tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: tcp_sock.bind(('0.0.0.0', tcp_port)) # Ascolta su tutte le interfacce #tcp_sock.bind(('0.0.0.0', TCP_CONTROL_PORT)) # Ascolta su tutte le interfacce #log_message(f"TCP Control Server binding su 0.0.0.0:{TCP_CONTROL_PORT}") # LOG log_message(f"TCP Control Server binding su 0.0.0.0:{tcp_port}") # LOG except OSError as e: log_message(f"Errore durante il bind del socket TCP: {e}") # LOG #log_message(f"La porta TCP {TCP_CONTROL_PORT} potrebbe essere già in uso.") # LOG log_message(f"La porta TCP {tcp_port} potrebbe essere già in uso.") # LOG udp_sock.close() return # --- Genera chiavi RSA --- private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) public_key = private_key.public_key() log_message("Chiavi RSA del server generate.") # LOG # --- Avvio Threads --- udp_server_thread = UDPServerThread(udp_sock, private_key, public_key) tcp_server_thread = TCPServerThread(tcp_sock, output_queue, shutdown_event) udp_server_thread.start() tcp_server_thread.start() # --- Loop Principale di Attesa/Controllo --- try: # Attendi finché non viene segnalato l'evento di shutdown # o uno dei thread principali termina inaspettatamente while not shutdown_event.is_set() and udp_server_thread.is_alive() and tcp_server_thread.is_alive(): # Possiamo dormire qui, l'evento o la terminazione dei thread ci sveglierà # Oppure possiamo fare controlli periodici se necessario shutdown_event.wait(timeout=1.0) # Attende l'evento o scade dopo 1 sec if not udp_server_thread.is_alive(): log_message("WARN: UDP Server thread terminato inaspettatamente.") shutdown_event.set() # Assicura lo shutdown anche del TCP if not tcp_server_thread.is_alive(): log_message("WARN: TCP Server thread terminato inaspettatamente.") shutdown_event.set() # Assicura lo shutdown anche dell'UDP except KeyboardInterrupt: log_message("\r\nServer terminato (Ctrl+C). Inizio shutdown...") # LOG shutdown_event.set() # Segnala ai thread di fermarsi finally: log_message("Avvio procedura di shutdown coordinato...") # LOG # Segnala e attendi la terminazione dei thread if tcp_server_thread.is_alive(): # shutdown_event è già settato, basta chiamare stop e join tcp_server_thread.stop() tcp_server_thread.join(timeout=3.0) # Timeout più lungo per TCP if tcp_server_thread.is_alive(): log_message("WARN: TCP Server thread non terminato entro il timeout.") # LOG if udp_server_thread.is_alive(): udp_server_thread.stop() udp_server_thread.join(timeout=2.0) if udp_server_thread.is_alive(): log_message("WARN: UDP Server thread non terminato entro il timeout.") # LOG # Pulizia Socket UDP (il socket TCP è chiuso dal suo thread) try: # Rimuovi membership multicast (best effort) if udp_sock.fileno() != -1 : # Controlla se il socket non è già stato chiuso try: udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_DROP_MEMBERSHIP, mreq) log_message("UDP Rimossa membership multicast.") # LOG except OSError as e: # Potrebbe fallire se il socket è già stato chiuso o per altri motivi # Non è critico bloccare lo shutdown per questo if e.errno != 9: # errno 9: Bad file descriptor (socket già chiuso?) log_message(f"Errore durante la rimozione della membership multicast UDP: {e}") # LOG except Exception as ex: log_message(f"Errore generico durante la rimozione membership UDP: {ex}") # LOG udp_sock.close() log_message("Socket UDP del server chiuso.") # LOG except Exception as final_e: log_message(f"Errore durante la pulizia finale del socket UDP: {final_e}") # LOG log_message("Server Terminato Correttamente") # LOG # Qui l'applicazione finisce. Se ci sono ancora messaggi nella coda, andranno persi. if __name__ == "__main__": parser = argparse.ArgumentParser(description="server") parser.add_argument( '--multicast-group', type=str, default="224.1.1.1", # Mantieni i default originali help="indirizzo multicast (default: 224.1.1.1)" ) parser.add_argument( '--multicast-port', type=int, default=5007, # Mantieni i default originali help="Porta multicast su cui ascoltare per le connessioni UDP (default: 5007)" ) parser.add_argument( '--tcp-control-port', type=int, default=5008, # Mantieni i default originali help="Porta locale su cui ascoltare per le connessioni TCP di controllo (default: 5008)" ) args = parser.parse_args() multicast_group = args.multicast_group multicast_port = args.multicast_port tcp_port = args.tcp_control_port run_server(multicast_group, multicast_port, tcp_port)