# compatibile Windows 11 # compatibile Ubuntu 24.10 # compatibile python 3.12.7 # client.py (relay + keep-alive + sender ID + connect/disconnect notify) import socket import struct import os import threading import time from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding, rsa # --- Configurazione --- MULTICAST_GROUP = '224.1.1.1' MULTICAST_PORT = 5007 FRAGMENT_SIZE = 256 MAX_PACKET_SIZE = 65507 KEEPALIVE_INTERVAL = 20 # --- Funzioni di utilità --- 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.") value = data[4:4 + length] remaining_data = data[4 + length:] return type, value, remaining_data def encrypt_data(data, public_key): return public_key.encrypt(data, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)) def decrypt_data(ciphertext, private_key): try: return private_key.decrypt(ciphertext, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)) except Exception: print(f"Errore decrittazione"); 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 register_with_server(sock, private_key): public_pem = private_key.public_key().public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) tlv_register = create_tlv(4, public_pem) sock.sendto(tlv_register, (MULTICAST_GROUP, MULTICAST_PORT)) print("Chiave pubblica inviata.") tlv_request = create_tlv(7, b"") sock.sendto(tlv_request, (MULTICAST_GROUP, MULTICAST_PORT)) print("Richiesta chiave pubblica server...") sock.settimeout(5.0) try: data, _ = sock.recvfrom(MAX_PACKET_SIZE) type, value, _ = parse_tlv(data) if type == 5: server_public_key = serialization.load_pem_public_key(value, backend=default_backend()) print("Chiave pubblica server ricevuta.") return server_public_key else: print("Risposta non valida (richiesta chiave)."); return None except (socket.timeout, Exception) as e: print("Errore", e); return None def send_message(message, server_public_key, sock): message_bytes = message.encode('utf-8') if isinstance(message, str) else message key_size = server_public_key.key_size // 8 max_block_size = key_size - 2 * hashes.SHA256.digest_size - 2 pre_fragments = [message_bytes[i:i + max_block_size] for i in range(0, len(message_bytes), max_block_size)] if len(message_bytes) > max_block_size else [message_bytes] encrypted_tlvs = [create_tlv(3, encrypt_data(pre_frag, server_public_key)) for pre_frag in pre_fragments] msg_id = os.urandom(4) combined_data = b"".join(encrypted_tlvs) if len(combined_data) > FRAGMENT_SIZE: udp_fragments = fragment_data(combined_data, FRAGMENT_SIZE) for seq_num, udp_frag in enumerate(udp_fragments): tlv_data = struct.pack('!I', int.from_bytes(msg_id, byteorder='big')) + struct.pack('!H', seq_num) + struct.pack('!H', len(udp_fragments)) + udp_frag tlv_udp = create_tlv(1, tlv_data) sock.sendto(tlv_udp, (MULTICAST_GROUP, MULTICAST_PORT)) print(f"Frammento UDP {seq_num + 1}/{len(udp_fragments)}") else: tlv_data = struct.pack('!I', int.from_bytes(msg_id, byteorder='big')) + struct.pack('!H', 0) + struct.pack('!H', 1) + combined_data tlv_udp = create_tlv(1, tlv_data) sock.sendto(tlv_udp, (MULTICAST_GROUP, MULTICAST_PORT)) print("Messaggio inviato (no frammentazione)") print("Messaggio/Disconnessione inviato.") def receive_message(sock, private_key, stop_event): while not stop_event.is_set(): try: sock.settimeout(1.0) data, _ = sock.recvfrom(MAX_PACKET_SIZE) type, value, remaining_data = parse_tlv(data) sender_address = None if type == 10: sender_ip = value[:-2].decode() sender_port = struct.unpack("!H", value[-2:])[0] sender_address = (sender_ip, sender_port) type, value, remaining_data = parse_tlv(remaining_data) if type == 6: decrypted_message = decrypt_data(value, private_key) if decrypted_message: print(f"\nMessaggio da {sender_address}:", decrypted_message.decode('utf-8', errors='replace')) if sender_address else print("\nMessaggio (mittente sconosciuto):", decrypted_message.decode('utf-8', errors='replace')) else: print("Errore decrittazione relay.") elif type == 11: # Connessione address = (value[:-2].decode(), struct.unpack("!H", value[-2:])[0]) print(f"\nNuovo client connesso: {address}") elif type == 12: # Disconnessione address = (value[:-2].decode(), struct.unpack("!H", value[-2:])[0]) print(f"\nClient disconnesso: {address}") elif type != 10: print(f"Tipo messaggio non gestito: {type}") except socket.timeout: continue except OSError as e: if stop_event.is_set(): break print(f"Errore ricezione (OSError): {e}") except Exception as e: print(f"Errore ricezione: {e}") def send_keepalive(sock): tlv_keepalive = create_tlv(9, b"") sock.sendto(tlv_keepalive, (MULTICAST_GROUP, MULTICAST_PORT)) def keepalive_thread_function(sock, stop_event): while not stop_event.is_set(): send_keepalive(sock) time.sleep(KEEPALIVE_INTERVAL) def run_client(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ttl = struct.pack('b', 1) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl) private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) server_public_key = register_with_server(sock, private_key) if server_public_key is None: sock.close(); return stop_event = threading.Event() receive_thread = threading.Thread(target=receive_message, args=(sock, private_key, stop_event)) receive_thread.start() keepalive_thread = threading.Thread(target=keepalive_thread_function, args=(sock, stop_event)) keepalive_thread.start() #gviva connect_message = b"CONNECT"; send_message(connect_message,server_public_key, sock) try: while True: message = input("Messaggio (Ctrl+C per uscire): ") send_message(message, server_public_key, sock) except KeyboardInterrupt: print("\nChiusura (Ctrl+C), Attendere...") finally: try: disconnect_message = b"DISCONNECT"; send_message(disconnect_message,server_public_key, sock) except Exception as e: print(f"Errore disconnessione: {e}") stop_event.set() receive_thread.join() keepalive_thread.join() sock.close() print("Client chiuso.") if __name__ == "__main__": run_client()