chore: cleanup and near v1 quic impl

This commit is contained in:
Akash Mondal
2025-07-02 16:51:16 +00:00
committed by lla-dane
parent c15c317514
commit 03bf071739
12 changed files with 311 additions and 2124 deletions

View File

@ -1,371 +0,0 @@
def debug_quic_connection_state(conn, name="Connection"):
"""Enhanced debugging function for QUIC connection state."""
print(f"\n🔍 === {name} Debug Info ===")
# Basic connection state
print(f"State: {getattr(conn, '_state', 'unknown')}")
print(f"Handshake complete: {getattr(conn, '_handshake_complete', False)}")
# Connection IDs
if hasattr(conn, "_host_connection_id"):
print(
f"Host CID: {conn._host_connection_id.hex() if conn._host_connection_id else 'None'}"
)
if hasattr(conn, "_peer_connection_id"):
print(
f"Peer CID: {conn._peer_connection_id.hex() if conn._peer_connection_id else 'None'}"
)
# Check for connection ID sequences
if hasattr(conn, "_local_connection_ids"):
print(
f"Local CID sequence: {[cid.cid.hex() for cid in conn._local_connection_ids]}"
)
if hasattr(conn, "_remote_connection_ids"):
print(
f"Remote CID sequence: {[cid.cid.hex() for cid in conn._remote_connection_ids]}"
)
# TLS state
if hasattr(conn, "tls") and conn.tls:
tls_state = getattr(conn.tls, "state", "unknown")
print(f"TLS state: {tls_state}")
# Check for certificates
peer_cert = getattr(conn.tls, "_peer_certificate", None)
print(f"Has peer certificate: {peer_cert is not None}")
# Transport parameters
if hasattr(conn, "_remote_transport_parameters"):
params = conn._remote_transport_parameters
if params:
print(f"Remote transport parameters received: {len(params)} params")
print(f"=== End {name} Debug ===\n")
def debug_firstflight_event(server_conn, name="Server"):
"""Debug connection ID changes specifically around FIRSTFLIGHT event."""
print(f"\n🎯 === {name} FIRSTFLIGHT Event Debug ===")
# Connection state
state = getattr(server_conn, "_state", "unknown")
print(f"Connection State: {state}")
# Connection IDs
peer_cid = getattr(server_conn, "_peer_connection_id", None)
host_cid = getattr(server_conn, "_host_connection_id", None)
original_dcid = getattr(server_conn, "original_destination_connection_id", None)
print(f"Peer CID: {peer_cid.hex() if peer_cid else 'None'}")
print(f"Host CID: {host_cid.hex() if host_cid else 'None'}")
print(f"Original DCID: {original_dcid.hex() if original_dcid else 'None'}")
print(f"=== End {name} FIRSTFLIGHT Debug ===\n")
def create_minimal_quic_test():
"""Simplified test to isolate FIRSTFLIGHT connection ID issues."""
print("\n=== MINIMAL QUIC FIRSTFLIGHT CONNECTION ID TEST ===")
from time import time
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.connection import QuicConnection
from aioquic.buffer import Buffer
from aioquic.quic.packet import pull_quic_header
# Minimal configs without certificates first
client_config = QuicConfiguration(
is_client=True, alpn_protocols=["libp2p"], connection_id_length=8
)
server_config = QuicConfiguration(
is_client=False, alpn_protocols=["libp2p"], connection_id_length=8
)
# Create client and connect
client_conn = QuicConnection(configuration=client_config)
server_addr = ("127.0.0.1", 4321)
print("🔗 Client calling connect()...")
client_conn.connect(server_addr, now=time())
# Debug client state after connect
debug_quic_connection_state(client_conn, "Client After Connect")
# Get initial client packet
initial_packets = client_conn.datagrams_to_send(now=time())
if not initial_packets:
print("❌ No initial packets from client")
return False
initial_packet = initial_packets[0][0]
# Parse header to get client's source CID (what server should use as peer CID)
header = pull_quic_header(Buffer(data=initial_packet), host_cid_length=8)
client_source_cid = header.source_cid
client_dest_cid = header.destination_cid
print(f"📦 Initial packet analysis:")
print(
f" Client Source CID: {client_source_cid.hex()} (server should use as peer CID)"
)
print(f" Client Dest CID: {client_dest_cid.hex()}")
# Create server with proper ODCID
print(
f"\n🏗️ Creating server with original_destination_connection_id={client_dest_cid.hex()}..."
)
server_conn = QuicConnection(
configuration=server_config,
original_destination_connection_id=client_dest_cid,
)
# Debug server state after creation (before FIRSTFLIGHT)
debug_firstflight_event(server_conn, "Server After Creation (Pre-FIRSTFLIGHT)")
# 🎯 CRITICAL: Process initial packet (this triggers FIRSTFLIGHT event)
print(f"🚀 Processing initial packet (triggering FIRSTFLIGHT)...")
client_addr = ("127.0.0.1", 1234)
# Before receive_datagram
print(f"📊 BEFORE receive_datagram (FIRSTFLIGHT):")
print(f" Server state: {getattr(server_conn, '_state', 'unknown')}")
print(
f" Server peer CID: {server_conn._peer_cid.cid.hex()}"
)
print(f" Expected peer CID after FIRSTFLIGHT: {client_source_cid.hex()}")
# This call triggers FIRSTFLIGHT: FIRSTFLIGHT -> CONNECTED
server_conn.receive_datagram(initial_packet, client_addr, now=time())
# After receive_datagram (FIRSTFLIGHT should have happened)
print(f"📊 AFTER receive_datagram (Post-FIRSTFLIGHT):")
print(f" Server state: {getattr(server_conn, '_state', 'unknown')}")
print(
f" Server peer CID: {server_conn._peer_cid.cid.hex()}"
)
# Check if FIRSTFLIGHT set peer CID correctly
actual_peer_cid = server_conn._peer_cid.cid
if actual_peer_cid == client_source_cid:
print("✅ FIRSTFLIGHT correctly set peer CID from client source CID")
firstflight_success = True
else:
print("❌ FIRSTFLIGHT BUG: peer CID not set correctly!")
print(f" Expected: {client_source_cid.hex()}")
print(f" Actual: {actual_peer_cid.hex() if actual_peer_cid else 'None'}")
firstflight_success = False
# Debug both connections after FIRSTFLIGHT
debug_firstflight_event(server_conn, "Server After FIRSTFLIGHT")
debug_quic_connection_state(client_conn, "Client After Server Processing")
# Check server response packets
print(f"\n📤 Checking server response packets...")
server_packets = server_conn.datagrams_to_send(now=time())
if server_packets:
response_packet = server_packets[0][0]
response_header = pull_quic_header(
Buffer(data=response_packet), host_cid_length=8
)
print(f"📊 Server response packet:")
print(f" Source CID: {response_header.source_cid.hex()}")
print(f" Dest CID: {response_header.destination_cid.hex()}")
print(f" Expected dest CID: {client_source_cid.hex()}")
# Final verification
if response_header.destination_cid == client_source_cid:
print("✅ Server response uses correct destination CID!")
return True
else:
print(f"❌ Server response uses WRONG destination CID!")
print(f" This proves the FIRSTFLIGHT bug - peer CID not set correctly")
print(f" Expected: {client_source_cid.hex()}")
print(f" Actual: {response_header.destination_cid.hex()}")
return False
else:
print("❌ Server did not generate response packet")
return False
def create_minimal_quic_test_with_config(client_config, server_config):
"""Run FIRSTFLIGHT test with provided configurations."""
from time import time
from aioquic.buffer import Buffer
from aioquic.quic.connection import QuicConnection
from aioquic.quic.packet import pull_quic_header
print("\n=== FIRSTFLIGHT TEST WITH CERTIFICATES ===")
# Create client and connect
client_conn = QuicConnection(configuration=client_config)
server_addr = ("127.0.0.1", 4321)
print("🔗 Client calling connect() with certificates...")
client_conn.connect(server_addr, now=time())
# Get initial packets and extract client source CID
initial_packets = client_conn.datagrams_to_send(now=time())
if not initial_packets:
print("❌ No initial packets from client")
return False
# Extract client source CID from initial packet
initial_packet = initial_packets[0][0]
header = pull_quic_header(Buffer(data=initial_packet), host_cid_length=8)
client_source_cid = header.source_cid
print(f"📦 Client source CID (expected server peer CID): {client_source_cid.hex()}")
# Create server with client's source CID as original destination
server_conn = QuicConnection(
configuration=server_config,
original_destination_connection_id=client_source_cid,
)
# Debug server before FIRSTFLIGHT
print(f"\n📊 BEFORE FIRSTFLIGHT (server creation):")
print(f" Server state: {getattr(server_conn, '_state', 'unknown')}")
print(
f" Server peer CID: {server_conn._peer_cid.cid.hex()}"
)
print(
f" Server original DCID: {server_conn.original_destination_connection_id.hex()}"
)
# Process initial packet (triggers FIRSTFLIGHT)
client_addr = ("127.0.0.1", 1234)
print(f"\n🚀 Triggering FIRSTFLIGHT by processing initial packet...")
for datagram, _ in initial_packets:
header = pull_quic_header(Buffer(data=datagram))
print(
f" Processing packet: src={header.source_cid.hex()}, dst={header.destination_cid.hex()}"
)
# This triggers FIRSTFLIGHT
server_conn.receive_datagram(datagram, client_addr, now=time())
# Debug immediately after FIRSTFLIGHT
print(f"\n📊 AFTER FIRSTFLIGHT:")
print(f" Server state: {getattr(server_conn, '_state', 'unknown')}")
print(
f" Server peer CID: {server_conn._peer_cid.cid.hex()}"
)
print(f" Expected peer CID: {header.source_cid.hex()}")
# Check if FIRSTFLIGHT worked correctly
actual_peer_cid = getattr(server_conn, "_peer_connection_id", None)
if actual_peer_cid == header.source_cid:
print("✅ FIRSTFLIGHT correctly set peer CID")
else:
print("❌ FIRSTFLIGHT failed to set peer CID correctly")
print(f" This is the root cause of the handshake failure!")
# Check server response
server_packets = server_conn.datagrams_to_send(now=time())
if server_packets:
response_packet = server_packets[0][0]
response_header = pull_quic_header(
Buffer(data=response_packet), host_cid_length=8
)
print(f"\n📤 Server response analysis:")
print(f" Response dest CID: {response_header.destination_cid.hex()}")
print(f" Expected dest CID: {client_source_cid.hex()}")
if response_header.destination_cid == client_source_cid:
print("✅ Server response uses correct destination CID!")
return True
else:
print("❌ FIRSTFLIGHT bug confirmed - wrong destination CID in response!")
print(
" This proves aioquic doesn't set peer CID correctly during FIRSTFLIGHT"
)
return False
print("❌ No server response packets")
return False
async def test_with_certificates():
"""Test with proper certificate setup and FIRSTFLIGHT debugging."""
print("\n=== CERTIFICATE-BASED FIRSTFLIGHT TEST ===")
# Import your existing certificate creation functions
from libp2p.crypto.ed25519 import create_new_key_pair
from libp2p.peer.id import ID
from libp2p.transport.quic.security import create_quic_security_transport
# Create security configs
client_key_pair = create_new_key_pair()
server_key_pair = create_new_key_pair()
client_security_config = create_quic_security_transport(
client_key_pair.private_key, ID.from_pubkey(client_key_pair.public_key)
)
server_security_config = create_quic_security_transport(
server_key_pair.private_key, ID.from_pubkey(server_key_pair.public_key)
)
# Apply the minimal test logic with certificates
from aioquic.quic.configuration import QuicConfiguration
client_config = QuicConfiguration(
is_client=True, alpn_protocols=["libp2p"], connection_id_length=8
)
client_config.certificate = client_security_config.tls_config.certificate
client_config.private_key = client_security_config.tls_config.private_key
client_config.verify_mode = (
client_security_config.create_client_config().verify_mode
)
server_config = QuicConfiguration(
is_client=False, alpn_protocols=["libp2p"], connection_id_length=8
)
server_config.certificate = server_security_config.tls_config.certificate
server_config.private_key = server_security_config.tls_config.private_key
server_config.verify_mode = (
server_security_config.create_server_config().verify_mode
)
# Run the FIRSTFLIGHT test with certificates
return create_minimal_quic_test_with_config(client_config, server_config)
async def main():
print("🎯 Testing FIRSTFLIGHT connection ID behavior...")
# # First test without certificates
# print("\n" + "=" * 60)
# print("PHASE 1: Testing FIRSTFLIGHT without certificates")
# print("=" * 60)
# minimal_success = create_minimal_quic_test()
# Then test with certificates
print("\n" + "=" * 60)
print("PHASE 2: Testing FIRSTFLIGHT with certificates")
print("=" * 60)
cert_success = await test_with_certificates()
# Summary
print("\n" + "=" * 60)
print("FIRSTFLIGHT TEST SUMMARY")
print("=" * 60)
# print(f"Minimal test (no certs): {'✅ PASS' if minimal_success else '❌ FAIL'}")
print(f"Certificate test: {'✅ PASS' if cert_success else '❌ FAIL'}")
if not cert_success:
print("\n🔥 FIRSTFLIGHT BUG CONFIRMED:")
print(" - aioquic fails to set peer CID correctly during FIRSTFLIGHT event")
print(" - Server uses wrong destination CID in response packets")
print(" - Client drops responses → handshake fails")
print(" - Fix: Override _peer_connection_id after receive_datagram()")
if __name__ == "__main__":
import trio
trio.run(main)

View File

@ -1,205 +0,0 @@
from aioquic._buffer import Buffer
from aioquic.quic.packet import pull_quic_header
from aioquic.quic.connection import QuicConnection
from aioquic.quic.configuration import QuicConfiguration
from tempfile import NamedTemporaryFile
from libp2p.peer.id import ID
from libp2p.transport.quic.security import create_quic_security_transport
from libp2p.crypto.ed25519 import create_new_key_pair
from time import time
import os
import trio
async def test_full_handshake_and_certificate_exchange():
"""
Test a full handshake to ensure it completes and peer certificates are exchanged.
FIXED VERSION: Corrects connection ID management and address handling.
"""
print("\n=== TESTING FULL HANDSHAKE AND CERTIFICATE EXCHANGE (FIXED) ===")
# 1. Generate KeyPairs and create libp2p security configs for client and server.
client_key_pair = create_new_key_pair()
server_key_pair = create_new_key_pair()
client_security_config = create_quic_security_transport(
client_key_pair.private_key, ID.from_pubkey(client_key_pair.public_key)
)
server_security_config = create_quic_security_transport(
server_key_pair.private_key, ID.from_pubkey(server_key_pair.public_key)
)
print("✅ libp2p security configs created.")
# 2. Create aioquic configurations with consistent settings
client_secrets_log_file = NamedTemporaryFile(
mode="w", delete=False, suffix="-client.log"
)
client_aioquic_config = QuicConfiguration(
is_client=True,
alpn_protocols=["libp2p"],
secrets_log_file=client_secrets_log_file,
connection_id_length=8, # Set consistent CID length
)
client_aioquic_config.certificate = client_security_config.tls_config.certificate
client_aioquic_config.private_key = client_security_config.tls_config.private_key
client_aioquic_config.verify_mode = (
client_security_config.create_client_config().verify_mode
)
server_secrets_log_file = NamedTemporaryFile(
mode="w", delete=False, suffix="-server.log"
)
server_aioquic_config = QuicConfiguration(
is_client=False,
alpn_protocols=["libp2p"],
secrets_log_file=server_secrets_log_file,
connection_id_length=8, # Set consistent CID length
)
server_aioquic_config.certificate = server_security_config.tls_config.certificate
server_aioquic_config.private_key = server_security_config.tls_config.private_key
server_aioquic_config.verify_mode = (
server_security_config.create_server_config().verify_mode
)
print("✅ aioquic configurations created and configured.")
print(f"🔑 Client secrets will be logged to: {client_secrets_log_file.name}")
print(f"🔑 Server secrets will be logged to: {server_secrets_log_file.name}")
# 3. Use consistent addresses - this is crucial!
# The client will connect TO the server address, but packets will come FROM client address
client_address = ("127.0.0.1", 1234) # Client binds to this
server_address = ("127.0.0.1", 4321) # Server binds to this
# 4. Create client connection and initiate connection
client_conn = QuicConnection(configuration=client_aioquic_config)
# Client connects to server address - this sets up the initial packet with proper CIDs
client_conn.connect(server_address, now=time())
print("✅ Client connection initiated.")
# 5. Get the initial client packet and extract ODCID properly
client_datagrams = client_conn.datagrams_to_send(now=time())
if not client_datagrams:
raise AssertionError("❌ Client did not generate initial packet")
client_initial_packet = client_datagrams[0][0]
header = pull_quic_header(Buffer(data=client_initial_packet), host_cid_length=8)
original_dcid = header.destination_cid
client_source_cid = header.source_cid
print(f"📊 Client ODCID: {original_dcid.hex()}")
print(f"📊 Client source CID: {client_source_cid.hex()}")
# 6. Create server connection with the correct ODCID
server_conn = QuicConnection(
configuration=server_aioquic_config,
original_destination_connection_id=original_dcid,
)
print("✅ Server connection created with correct ODCID.")
# 7. Feed the initial client packet to server
# IMPORTANT: Use client_address as the source for the packet
for datagram, _ in client_datagrams:
header = pull_quic_header(Buffer(data=datagram))
print(
f"📤 Client -> Server: src={header.source_cid.hex()}, dst={header.destination_cid.hex()}"
)
server_conn.receive_datagram(datagram, client_address, now=time())
# 8. Manual handshake loop with proper packet tracking
max_duration_s = 3 # Increased timeout
start_time = time()
packet_count = 0
while time() - start_time < max_duration_s:
# Process client -> server packets
client_packets = list(client_conn.datagrams_to_send(now=time()))
for datagram, _ in client_packets:
header = pull_quic_header(Buffer(data=datagram))
print(
f"📤 Client -> Server: src={header.source_cid.hex()}, dst={header.destination_cid.hex()}"
)
server_conn.receive_datagram(datagram, client_address, now=time())
packet_count += 1
# Process server -> client packets
server_packets = list(server_conn.datagrams_to_send(now=time()))
for datagram, _ in server_packets:
header = pull_quic_header(Buffer(data=datagram))
print(
f"📤 Server -> Client: src={header.source_cid.hex()}, dst={header.destination_cid.hex()}"
)
# CRITICAL: Server sends back to client_address, not server_address
client_conn.receive_datagram(datagram, server_address, now=time())
packet_count += 1
# Check for completion
client_complete = getattr(client_conn, "_handshake_complete", False)
server_complete = getattr(server_conn, "_handshake_complete", False)
print(
f"🔄 Handshake status: Client={client_complete}, Server={server_complete}, Packets={packet_count}"
)
if client_complete and server_complete:
print("🎉 Handshake completed for both peers!")
break
# If no packets were exchanged in this iteration, wait a bit
if not client_packets and not server_packets:
await trio.sleep(0.01)
# Safety check - if too many packets, something is wrong
if packet_count > 50:
print("⚠️ Too many packets exchanged, possible handshake loop")
break
# 9. Enhanced handshake completion checks
client_handshake_complete = getattr(client_conn, "_handshake_complete", False)
server_handshake_complete = getattr(server_conn, "_handshake_complete", False)
# Debug additional state information
print(f"🔍 Final client state: {getattr(client_conn, '_state', 'unknown')}")
print(f"🔍 Final server state: {getattr(server_conn, '_state', 'unknown')}")
if hasattr(client_conn, "tls") and client_conn.tls:
print(f"🔍 Client TLS state: {getattr(client_conn.tls, 'state', 'unknown')}")
if hasattr(server_conn, "tls") and server_conn.tls:
print(f"🔍 Server TLS state: {getattr(server_conn.tls, 'state', 'unknown')}")
# 10. Cleanup and assertions
client_secrets_log_file.close()
server_secrets_log_file.close()
os.unlink(client_secrets_log_file.name)
os.unlink(server_secrets_log_file.name)
# Final assertions
assert client_handshake_complete, (
f"❌ Client handshake did not complete. "
f"State: {getattr(client_conn, '_state', 'unknown')}, "
f"Packets: {packet_count}"
)
assert server_handshake_complete, (
f"❌ Server handshake did not complete. "
f"State: {getattr(server_conn, '_state', 'unknown')}, "
f"Packets: {packet_count}"
)
print("✅ Handshake completed for both peers.")
# Certificate exchange verification
client_peer_cert = getattr(client_conn.tls, "_peer_certificate", None)
server_peer_cert = getattr(server_conn.tls, "_peer_certificate", None)
assert client_peer_cert is not None, (
"❌ Client FAILED to receive server certificate."
)
print("✅ Client successfully received server certificate.")
assert server_peer_cert is not None, (
"❌ Server FAILED to receive client certificate."
)
print("✅ Server successfully received client certificate.")
print("🎉 Test Passed: Full handshake and certificate exchange successful.")
return True
if __name__ == "__main__":
trio.run(test_full_handshake_and_certificate_exchange)

View File

@ -1,461 +0,0 @@
#!/usr/bin/env python3
"""
Fixed QUIC handshake test to debug connection issues.
"""
import logging
import os
from pathlib import Path
import secrets
import sys
from tempfile import NamedTemporaryFile
from time import time
from aioquic._buffer import Buffer
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.connection import QuicConnection
from aioquic.quic.logger import QuicFileLogger
from aioquic.quic.packet import pull_quic_header
import trio
from libp2p.crypto.ed25519 import create_new_key_pair
from libp2p.peer.id import ID
from libp2p.transport.quic.security import (
LIBP2P_TLS_EXTENSION_OID,
create_quic_security_transport,
)
from libp2p.transport.quic.transport import QUICTransport, QUICTransportConfig
from libp2p.transport.quic.utils import create_quic_multiaddr
logging.basicConfig(
format="%(asctime)s %(levelname)s %(name)s %(message)s", level=logging.DEBUG
)
# Adjust this path to your project structure
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
# Setup logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
async def test_certificate_generation():
"""Test certificate generation in isolation."""
print("\n=== TESTING CERTIFICATE GENERATION ===")
try:
from libp2p.peer.id import ID
from libp2p.transport.quic.security import create_quic_security_transport
# Create key pair
private_key = create_new_key_pair().private_key
peer_id = ID.from_pubkey(private_key.get_public_key())
print(f"Generated peer ID: {peer_id}")
# Create security manager
security_manager = create_quic_security_transport(private_key, peer_id)
print("✅ Security manager created")
# Test server config
server_config = security_manager.create_server_config()
print("✅ Server config created")
# Validate certificate
cert = server_config.certificate
private_key_obj = server_config.private_key
print(f"Certificate type: {type(cert)}")
print(f"Private key type: {type(private_key_obj)}")
print(f"Certificate subject: {cert.subject}")
print(f"Certificate issuer: {cert.issuer}")
# Check for libp2p extension
has_libp2p_ext = False
for ext in cert.extensions:
if ext.oid == LIBP2P_TLS_EXTENSION_OID:
has_libp2p_ext = True
print(f"✅ Found libp2p extension: {ext.oid}")
print(f"Extension critical: {ext.critical}")
break
if not has_libp2p_ext:
print("❌ No libp2p extension found!")
print("Available extensions:")
for ext in cert.extensions:
print(f" - {ext.oid} (critical: {ext.critical})")
# Check certificate/key match
from cryptography.hazmat.primitives import serialization
cert_public_key = cert.public_key()
private_public_key = private_key_obj.public_key()
cert_pub_bytes = cert_public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
private_pub_bytes = private_public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
if cert_pub_bytes == private_pub_bytes:
print("✅ Certificate and private key match")
return has_libp2p_ext
else:
print("❌ Certificate and private key DO NOT match")
return False
except Exception as e:
print(f"❌ Certificate test failed: {e}")
import traceback
traceback.print_exc()
return False
async def test_basic_quic_connection():
"""Test basic QUIC connection with proper server setup."""
print("\n=== TESTING BASIC QUIC CONNECTION ===")
try:
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.connection import QuicConnection
from libp2p.peer.id import ID
from libp2p.transport.quic.security import create_quic_security_transport
# Create certificates
server_key = create_new_key_pair().private_key
server_peer_id = ID.from_pubkey(server_key.get_public_key())
server_security = create_quic_security_transport(server_key, server_peer_id)
client_key = create_new_key_pair().private_key
client_peer_id = ID.from_pubkey(client_key.get_public_key())
client_security = create_quic_security_transport(client_key, client_peer_id)
# Create server config
server_tls_config = server_security.create_server_config()
server_config = QuicConfiguration(
is_client=False,
certificate=server_tls_config.certificate,
private_key=server_tls_config.private_key,
alpn_protocols=["libp2p"],
)
# Create client config
client_tls_config = client_security.create_client_config()
client_config = QuicConfiguration(
is_client=True,
certificate=client_tls_config.certificate,
private_key=client_tls_config.private_key,
alpn_protocols=["libp2p"],
)
print("✅ QUIC configurations created")
# Test creating connections with proper parameters
# For server, we need to provide original_destination_connection_id
original_dcid = secrets.token_bytes(8)
server_conn = QuicConnection(
configuration=server_config,
original_destination_connection_id=original_dcid,
)
# For client, no original_destination_connection_id needed
client_conn = QuicConnection(configuration=client_config)
print("✅ QUIC connections created")
print(f"Server state: {server_conn._state}")
print(f"Client state: {client_conn._state}")
# Test that certificates are valid
print(f"Server has certificate: {server_config.certificate is not None}")
print(f"Server has private key: {server_config.private_key is not None}")
print(f"Client has certificate: {client_config.certificate is not None}")
print(f"Client has private key: {client_config.private_key is not None}")
return True
except Exception as e:
print(f"❌ Basic QUIC test failed: {e}")
import traceback
traceback.print_exc()
return False
async def test_server_startup():
"""Test server startup with timeout."""
print("\n=== TESTING SERVER STARTUP ===")
try:
# Create transport
private_key = create_new_key_pair().private_key
config = QUICTransportConfig(
idle_timeout=10.0, # Reduced timeout for testing
connection_timeout=10.0,
enable_draft29=False,
)
transport = QUICTransport(private_key, config)
print("✅ Transport created successfully")
# Test configuration
print(f"Available configs: {list(transport._quic_configs.keys())}")
config_valid = True
for config_key, quic_config in transport._quic_configs.items():
print(f"\n--- Testing config: {config_key} ---")
print(f"is_client: {quic_config.is_client}")
print(f"has_certificate: {quic_config.certificate is not None}")
print(f"has_private_key: {quic_config.private_key is not None}")
print(f"alpn_protocols: {quic_config.alpn_protocols}")
print(f"verify_mode: {quic_config.verify_mode}")
if quic_config.certificate:
cert = quic_config.certificate
print(f"Certificate subject: {cert.subject}")
# Check for libp2p extension
has_libp2p_ext = False
for ext in cert.extensions:
if ext.oid == LIBP2P_TLS_EXTENSION_OID:
has_libp2p_ext = True
break
print(f"Has libp2p extension: {has_libp2p_ext}")
if not has_libp2p_ext:
config_valid = False
if not config_valid:
print("❌ Transport configuration invalid - missing libp2p extensions")
return False
# Create listener
async def dummy_handler(connection):
print(f"New connection: {connection}")
listener = transport.create_listener(dummy_handler)
print("✅ Listener created successfully")
# Try to bind with timeout
maddr = create_quic_multiaddr("127.0.0.1", 0, "quic-v1")
async with trio.open_nursery() as nursery:
result = await listener.listen(maddr, nursery)
if result:
print("✅ Server bound successfully")
addresses = listener.get_addresses()
print(f"Listening on: {addresses}")
# Keep running for a short time
with trio.move_on_after(3.0): # 3 second timeout
await trio.sleep(5.0)
print("✅ Server test completed (timed out normally)")
nursery.cancel_scope.cancel()
return True
else:
print("❌ Failed to bind server")
return False
except Exception as e:
print(f"❌ Server test failed: {e}")
import traceback
traceback.print_exc()
return False
async def test_full_handshake_and_certificate_exchange():
"""
Test a full handshake to ensure it completes and peer certificates are exchanged.
This version is corrected to use the actual APIs available in the codebase.
"""
print("\n=== TESTING FULL HANDSHAKE AND CERTIFICATE EXCHANGE (CORRECTED) ===")
# 1. Generate KeyPairs and create libp2p security configs for client and server.
# The `create_quic_security_transport` function from `test_quic.py` is the
# correct helper to use, and it requires a `KeyPair` argument.
client_key_pair = create_new_key_pair()
server_key_pair = create_new_key_pair()
# This is the correct way to get the security configuration objects.
client_security_config = create_quic_security_transport(
client_key_pair.private_key, ID.from_pubkey(client_key_pair.public_key)
)
server_security_config = create_quic_security_transport(
server_key_pair.private_key, ID.from_pubkey(server_key_pair.public_key)
)
print("✅ libp2p security configs created.")
# 2. Create aioquic configurations and manually apply security settings,
# mimicking what the `QUICTransport` class does internally.
client_secrets_log_file = NamedTemporaryFile(
mode="w", delete=False, suffix="-client.log"
)
client_aioquic_config = QuicConfiguration(
is_client=True,
alpn_protocols=["libp2p"],
secrets_log_file=client_secrets_log_file,
)
client_aioquic_config.certificate = client_security_config.tls_config.certificate
client_aioquic_config.private_key = client_security_config.tls_config.private_key
client_aioquic_config.verify_mode = (
client_security_config.create_client_config().verify_mode
)
client_aioquic_config.quic_logger = QuicFileLogger(
"/home/akmo/GitHub/py-libp2p/examples/echo/logs"
)
server_secrets_log_file = NamedTemporaryFile(
mode="w", delete=False, suffix="-server.log"
)
server_aioquic_config = QuicConfiguration(
is_client=False,
alpn_protocols=["libp2p"],
secrets_log_file=server_secrets_log_file,
)
server_aioquic_config.certificate = server_security_config.tls_config.certificate
server_aioquic_config.private_key = server_security_config.tls_config.private_key
server_aioquic_config.verify_mode = (
server_security_config.create_server_config().verify_mode
)
server_aioquic_config.quic_logger = QuicFileLogger(
"/home/akmo/GitHub/py-libp2p/examples/echo/logs"
)
print("✅ aioquic configurations created and configured.")
print(f"🔑 Client secrets will be logged to: {client_secrets_log_file.name}")
print(f"🔑 Server secrets will be logged to: {server_secrets_log_file.name}")
# 3. Instantiate client, initiate its `connect` call, and get the ODCID for the server.
client_address = ("127.0.0.1", 1234)
server_address = ("127.0.0.1", 4321)
client_aioquic_config.connection_id_length = 8
client_conn = QuicConnection(configuration=client_aioquic_config)
client_conn.connect(server_address, now=time())
print("✅ aioquic connections instantiated correctly.")
print("🔧 Client CIDs")
print("Local Init CID: ", client_conn._local_initial_source_connection_id.hex())
print(
"Remote Init CID: ",
(client_conn._remote_initial_source_connection_id or b"").hex(),
)
print(
"Original Destination CID: ",
client_conn.original_destination_connection_id.hex(),
)
print(f"Host CID: {client_conn._host_cids[0].cid.hex()}")
# 4. Instantiate the server with the ODCID from the client.
server_aioquic_config.connection_id_length = 8
server_conn = QuicConnection(
configuration=server_aioquic_config,
original_destination_connection_id=client_conn.original_destination_connection_id,
)
print("✅ aioquic connections instantiated correctly.")
# 5. Manually drive the handshake process by exchanging datagrams.
max_duration_s = 5
start_time = time()
while time() - start_time < max_duration_s:
for datagram, _ in client_conn.datagrams_to_send(now=time()):
header = pull_quic_header(Buffer(data=datagram), host_cid_length=8)
print("Client packet source connection id", header.source_cid.hex())
print(
"Client packet destination connection id", header.destination_cid.hex()
)
print("--SERVER INJESTING CLIENT PACKET---")
server_conn.receive_datagram(datagram, client_address, now=time())
print(
f"Server remote initial source id: {(server_conn._remote_initial_source_connection_id or b'').hex()}"
)
for datagram, _ in server_conn.datagrams_to_send(now=time()):
header = pull_quic_header(Buffer(data=datagram), host_cid_length=8)
print("Server packet source connection id", header.source_cid.hex())
print(
"Server packet destination connection id", header.destination_cid.hex()
)
print("--CLIENT INJESTING SERVER PACKET---")
client_conn.receive_datagram(datagram, server_address, now=time())
# Check for completion
if client_conn._handshake_complete and server_conn._handshake_complete:
break
await trio.sleep(0.01)
# 6. Assertions to verify the outcome.
assert client_conn._handshake_complete, "❌ Client handshake did not complete."
assert server_conn._handshake_complete, "❌ Server handshake did not complete."
print("✅ Handshake completed for both peers.")
# The key assertion: check if the peer certificate was received.
client_peer_cert = getattr(client_conn.tls, "_peer_certificate", None)
server_peer_cert = getattr(server_conn.tls, "_peer_certificate", None)
client_secrets_log_file.close()
server_secrets_log_file.close()
os.unlink(client_secrets_log_file.name)
os.unlink(server_secrets_log_file.name)
assert client_peer_cert is not None, (
"❌ Client FAILED to receive server certificate."
)
print("✅ Client successfully received server certificate.")
print("🎉 Test Passed: Full handshake and certificate exchange successful.")
return True
async def main():
"""Run all tests with better error handling."""
print("Starting QUIC diagnostic tests...")
handshake_ok = await test_full_handshake_and_certificate_exchange()
if not handshake_ok:
print("\n❌ CRITICAL: Handshake failed!")
print("Apply the handshake fix and try again.")
return
# Test 1: Certificate generation
cert_ok = await test_certificate_generation()
if not cert_ok:
print("\n❌ CRITICAL: Certificate generation failed!")
print("Apply the certificate generation fix and try again.")
return
# Test 2: Basic QUIC connection
quic_ok = await test_basic_quic_connection()
if not quic_ok:
print("\n❌ CRITICAL: Basic QUIC connection test failed!")
return
# Test 3: Server startup
server_ok = await test_server_startup()
if not server_ok:
print("\n❌ Server startup test failed!")
return
print("\n✅ ALL TESTS PASSED!")
print("=== DIAGNOSTIC COMPLETE ===")
print("Your QUIC implementation should now work correctly.")
print("Try running your echo example again.")
if __name__ == "__main__":
trio.run(main)