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

@ -183,14 +183,6 @@ class Swarm(Service, INetworkService):
"""
Try to create a connection to peer_id with addr.
"""
# QUIC Transport
if isinstance(self.transport, QUICTransport):
raw_conn = await self.transport.dial(addr, peer_id)
print("detected QUIC connection, skipping upgrade steps")
swarm_conn = await self.add_conn(raw_conn)
print("successfully dialed peer %s via QUIC", peer_id)
return swarm_conn
try:
raw_conn = await self.transport.dial(addr)
except OpenConnectionError as error:

View File

@ -179,7 +179,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
"connection_id_changes": 0,
}
print(
logger.info(
f"Created QUIC connection to {remote_peer_id} "
f"(initiator: {is_initiator}, addr: {remote_addr}, "
"security: {security_manager is not None})"
@ -278,7 +278,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._started = True
self.event_started.set()
print(f"Starting QUIC connection to {self._remote_peer_id}")
logger.info(f"Starting QUIC connection to {self._remote_peer_id}")
try:
# If this is a client connection, we need to establish the connection
@ -289,7 +289,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._established = True
self._connected_event.set()
print(f"QUIC connection to {self._remote_peer_id} started")
logger.info(f"QUIC connection to {self._remote_peer_id} started")
except Exception as e:
logger.error(f"Failed to start connection: {e}")
@ -300,7 +300,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
try:
with QUICErrorContext("connection_initiation", "connection"):
if not self._socket:
print("Creating new socket for outbound connection")
logger.info("Creating new socket for outbound connection")
self._socket = trio.socket.socket(
family=socket.AF_INET, type=socket.SOCK_DGRAM
)
@ -312,7 +312,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Send initial packet(s)
await self._transmit()
print(f"Initiated QUIC connection to {self._remote_addr}")
logger.info(f"Initiated QUIC connection to {self._remote_addr}")
except Exception as e:
logger.error(f"Failed to initiate connection: {e}")
@ -334,16 +334,16 @@ class QUICConnection(IRawConnection, IMuxedConn):
try:
with QUICErrorContext("connection_establishment", "connection"):
# Start the connection if not already started
print("STARTING TO CONNECT")
logger.info("STARTING TO CONNECT")
if not self._started:
await self.start()
# Start background event processing
if not self._background_tasks_started:
print("STARTING BACKGROUND TASK")
logger.info("STARTING BACKGROUND TASK")
await self._start_background_tasks()
else:
print("BACKGROUND TASK ALREADY STARTED")
logger.info("BACKGROUND TASK ALREADY STARTED")
# Wait for handshake completion with timeout
with trio.move_on_after(
@ -357,13 +357,15 @@ class QUICConnection(IRawConnection, IMuxedConn):
f"{self.CONNECTION_HANDSHAKE_TIMEOUT}s"
)
print("QUICConnection: Verifying peer identity with security manager")
logger.info(
"QUICConnection: Verifying peer identity with security manager"
)
# Verify peer identity using security manager
await self._verify_peer_identity_with_security()
print("QUICConnection: Peer identity verified")
logger.info("QUICConnection: Peer identity verified")
self._established = True
print(f"QUIC connection established with {self._remote_peer_id}")
logger.info(f"QUIC connection established with {self._remote_peer_id}")
except Exception as e:
logger.error(f"Failed to establish connection: {e}")
@ -378,22 +380,16 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._background_tasks_started = True
if self.__is_initiator:
print(f"CLIENT CONNECTION {id(self)}: Starting processing event loop")
self._nursery.start_soon(async_fn=self._client_packet_receiver)
self._nursery.start_soon(async_fn=self._event_processing_loop)
else:
print(
f"SERVER CONNECTION {id(self)}: Using listener event forwarding, not own loop"
)
# Start periodic tasks
self._nursery.start_soon(async_fn=self._event_processing_loop)
self._nursery.start_soon(async_fn=self._periodic_maintenance)
print("Started background tasks for QUIC connection")
logger.info("Started background tasks for QUIC connection")
async def _event_processing_loop(self) -> None:
"""Main event processing loop for the connection."""
print(
logger.info(
f"Started QUIC event processing loop for connection id: {id(self)} "
f"and local peer id {str(self.local_peer_id())}"
)
@ -416,7 +412,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
logger.error(f"Error in event processing loop: {e}")
await self._handle_connection_error(e)
finally:
print("QUIC event processing loop finished")
logger.info("QUIC event processing loop finished")
async def _periodic_maintenance(self) -> None:
"""Perform periodic connection maintenance."""
@ -431,7 +427,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
# *** NEW: Log connection ID status periodically ***
if logger.isEnabledFor(logging.DEBUG):
cid_stats = self.get_connection_id_stats()
print(f"Connection ID stats: {cid_stats}")
logger.info(f"Connection ID stats: {cid_stats}")
# Sleep for maintenance interval
await trio.sleep(30.0) # 30 seconds
@ -441,15 +437,15 @@ class QUICConnection(IRawConnection, IMuxedConn):
async def _client_packet_receiver(self) -> None:
"""Receive packets for client connections."""
print("Starting client packet receiver")
print("Started QUIC client packet receiver")
logger.info("Starting client packet receiver")
logger.info("Started QUIC client packet receiver")
try:
while not self._closed and self._socket:
try:
# Receive UDP packets
data, addr = await self._socket.recvfrom(65536)
print(f"Client received {len(data)} bytes from {addr}")
logger.info(f"Client received {len(data)} bytes from {addr}")
# Feed packet to QUIC connection
self._quic.receive_datagram(data, addr, now=time.time())
@ -461,7 +457,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
await self._transmit()
except trio.ClosedResourceError:
print("Client socket closed")
logger.info("Client socket closed")
break
except Exception as e:
logger.error(f"Error receiving client packet: {e}")
@ -471,7 +467,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
logger.info("Client packet receiver cancelled")
raise
finally:
print("Client packet receiver terminated")
logger.info("Client packet receiver terminated")
# Security and identity methods
@ -483,7 +479,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
QUICPeerVerificationError: If peer verification fails
"""
print("VERIFYING PEER IDENTITY")
logger.info("VERIFYING PEER IDENTITY")
if not self._security_manager:
logger.warning("No security manager available for peer verification")
return
@ -512,7 +508,8 @@ class QUICConnection(IRawConnection, IMuxedConn):
logger.info(f"Discovered peer ID from certificate: {verified_peer_id}")
elif self._remote_peer_id != verified_peer_id:
raise QUICPeerVerificationError(
f"Peer ID mismatch: expected {self._remote_peer_id}, got {verified_peer_id}"
f"Peer ID mismatch: expected {self._remote_peer_id}, "
"got {verified_peer_id}"
)
self._peer_verified = True
@ -541,14 +538,14 @@ class QUICConnection(IRawConnection, IMuxedConn):
# aioquic stores the peer certificate as cryptography
# x509.Certificate
self._peer_certificate = tls_context._peer_certificate
print(
logger.info(
f"Extracted peer certificate: {self._peer_certificate.subject}"
)
else:
print("No peer certificate found in TLS context")
logger.info("No peer certificate found in TLS context")
else:
print("No TLS context available for certificate extraction")
logger.info("No TLS context available for certificate extraction")
except Exception as e:
logger.warning(f"Failed to extract peer certificate: {e}")
@ -556,15 +553,16 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Try alternative approach - check if certificate is in handshake events
try:
# Some versions of aioquic might expose certificate differently
if hasattr(self._quic, "configuration") and self._quic.configuration:
config = self._quic.configuration
if hasattr(config, "certificate") and config.certificate:
# This would be the local certificate, not peer certificate
# but we can use it for debugging
print("Found local certificate in configuration")
config = self._quic.configuration
if hasattr(config, "certificate") and config.certificate:
# This would be the local certificate, not peer certificate
# but we can use it for debugging
logger.debug("Found local certificate in configuration")
except Exception as inner_e:
print(f"Alternative certificate extraction also failed: {inner_e}")
logger.error(
f"Alternative certificate extraction also failed: {inner_e}"
)
async def get_peer_certificate(self) -> x509.Certificate | None:
"""
@ -596,7 +594,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
subject = self._peer_certificate.subject
serial_number = self._peer_certificate.serial_number
print(
logger.info(
f"Certificate validation - Subject: {subject}, Serial: {serial_number}"
)
return True
@ -721,7 +719,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._outbound_stream_count += 1
self._stats["streams_opened"] += 1
print(f"Opened outbound QUIC stream {stream_id}")
logger.info(f"Opened outbound QUIC stream {stream_id}")
return stream
raise QUICStreamTimeoutError(f"Stream creation timed out after {timeout}s")
@ -754,7 +752,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
async with self._accept_queue_lock:
if self._stream_accept_queue:
stream = self._stream_accept_queue.pop(0)
print(f"Accepted inbound stream {stream.stream_id}")
logger.debug(f"Accepted inbound stream {stream.stream_id}")
return stream
if self._closed:
@ -765,8 +763,9 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Wait for new streams
await self._stream_accept_event.wait()
print(
f"{id(self)} ACCEPT STREAM TIMEOUT: CONNECTION STATE {self._closed_event.is_set() or self._closed}"
logger.error(
"Timeout occured while accepting stream for local peer "
f"{self._local_peer_id.to_string()} on QUIC connection"
)
if self._closed_event.is_set() or self._closed:
raise MuxedConnUnavailable("QUIC connection closed during timeout")
@ -782,7 +781,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
"""
self._stream_handler = handler_function
print("Set stream handler for incoming streams")
logger.info("Set stream handler for incoming streams")
def _remove_stream(self, stream_id: int) -> None:
"""
@ -809,7 +808,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
if self._nursery:
self._nursery.start_soon(update_counts)
print(f"Removed stream {stream_id} from connection")
logger.info(f"Removed stream {stream_id} from connection")
# *** UPDATED: Complete QUIC event handling - FIXES THE ORIGINAL ISSUE ***
@ -831,15 +830,15 @@ class QUICConnection(IRawConnection, IMuxedConn):
await self._handle_quic_event(event)
if events_processed > 0:
print(f"Processed {events_processed} QUIC events")
logger.info(f"Processed {events_processed} QUIC events")
finally:
self._event_processing_active = False
async def _handle_quic_event(self, event: events.QuicEvent) -> None:
"""Handle a single QUIC event with COMPLETE event type coverage."""
print(f"Handling QUIC event: {type(event).__name__}")
print(f"QUIC event: {type(event).__name__}")
logger.info(f"Handling QUIC event: {type(event).__name__}")
logger.info(f"QUIC event: {type(event).__name__}")
try:
if isinstance(event, events.ConnectionTerminated):
@ -865,8 +864,8 @@ class QUICConnection(IRawConnection, IMuxedConn):
elif isinstance(event, events.StopSendingReceived):
await self._handle_stop_sending_received(event)
else:
print(f"Unhandled QUIC event type: {type(event).__name__}")
print(f"Unhandled QUIC event: {type(event).__name__}")
logger.info(f"Unhandled QUIC event type: {type(event).__name__}")
logger.info(f"Unhandled QUIC event: {type(event).__name__}")
except Exception as e:
logger.error(f"Error handling QUIC event {type(event).__name__}: {e}")
@ -882,7 +881,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
This is the CRITICAL missing functionality that was causing your issue!
"""
logger.info(f"🆔 NEW CONNECTION ID ISSUED: {event.connection_id.hex()}")
print(f"🆔 NEW CONNECTION ID ISSUED: {event.connection_id.hex()}")
logger.info(f"🆔 NEW CONNECTION ID ISSUED: {event.connection_id.hex()}")
# Add to available connection IDs
self._available_connection_ids.add(event.connection_id)
@ -891,13 +890,13 @@ class QUICConnection(IRawConnection, IMuxedConn):
if self._current_connection_id is None:
self._current_connection_id = event.connection_id
logger.info(f"🆔 Set current connection ID to: {event.connection_id.hex()}")
print(f"🆔 Set current connection ID to: {event.connection_id.hex()}")
logger.info(f"🆔 Set current connection ID to: {event.connection_id.hex()}")
# Update statistics
self._stats["connection_ids_issued"] += 1
print(f"Available connection IDs: {len(self._available_connection_ids)}")
print(f"Available connection IDs: {len(self._available_connection_ids)}")
logger.info(f"Available connection IDs: {len(self._available_connection_ids)}")
logger.info(f"Available connection IDs: {len(self._available_connection_ids)}")
async def _handle_connection_id_retired(
self, event: events.ConnectionIdRetired
@ -908,7 +907,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
This handles when the peer tells us to stop using a connection ID.
"""
logger.info(f"🗑️ CONNECTION ID RETIRED: {event.connection_id.hex()}")
print(f"🗑️ CONNECTION ID RETIRED: {event.connection_id.hex()}")
logger.info(f"🗑️ CONNECTION ID RETIRED: {event.connection_id.hex()}")
# Remove from available IDs and add to retired set
self._available_connection_ids.discard(event.connection_id)
@ -918,17 +917,14 @@ class QUICConnection(IRawConnection, IMuxedConn):
if self._current_connection_id == event.connection_id:
if self._available_connection_ids:
self._current_connection_id = next(iter(self._available_connection_ids))
logger.info(
f"🆔 Switched to new connection ID: {self._current_connection_id.hex()}"
)
print(
f"🆔 Switched to new connection ID: {self._current_connection_id.hex()}"
logger.debug(
f"Switching new connection ID: {self._current_connection_id.hex()}"
)
self._stats["connection_id_changes"] += 1
else:
self._current_connection_id = None
logger.warning("⚠️ No available connection IDs after retirement!")
print("⚠️ No available connection IDs after retirement!")
logger.info("⚠️ No available connection IDs after retirement!")
# Update statistics
self._stats["connection_ids_retired"] += 1
@ -937,7 +933,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
async def _handle_ping_acknowledged(self, event: events.PingAcknowledged) -> None:
"""Handle ping acknowledgment."""
print(f"Ping acknowledged: uid={event.uid}")
logger.info(f"Ping acknowledged: uid={event.uid}")
async def _handle_protocol_negotiated(
self, event: events.ProtocolNegotiated
@ -949,15 +945,15 @@ class QUICConnection(IRawConnection, IMuxedConn):
self, event: events.StopSendingReceived
) -> None:
"""Handle stop sending request from peer."""
print(
f"Stop sending received: stream_id={event.stream_id}, error_code={event.error_code}"
logger.debug(
"Stop sending received: "
f"stream_id={event.stream_id}, error_code={event.error_code}"
)
if event.stream_id in self._streams:
stream = self._streams[event.stream_id]
stream: QUICStream = self._streams[event.stream_id]
# Handle stop sending on the stream if method exists
if hasattr(stream, "handle_stop_sending"):
await stream.handle_stop_sending(event.error_code)
await stream.handle_stop_sending(event.error_code)
# *** EXISTING event handlers (unchanged) ***
@ -965,7 +961,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self, event: events.HandshakeCompleted
) -> None:
"""Handle handshake completion with security integration."""
print("QUIC handshake completed")
logger.info("QUIC handshake completed")
self._handshake_completed = True
# Store handshake event for security verification
@ -974,14 +970,14 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Try to extract certificate information after handshake
await self._extract_peer_certificate()
print("✅ Setting connected event")
logger.info("✅ Setting connected event")
self._connected_event.set()
async def _handle_connection_terminated(
self, event: events.ConnectionTerminated
) -> None:
"""Handle connection termination."""
print(f"QUIC connection terminated: {event.reason_phrase}")
logger.info(f"QUIC connection terminated: {event.reason_phrase}")
# Close all streams
for stream in list(self._streams.values()):
@ -995,7 +991,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._closed_event.set()
self._stream_accept_event.set()
print(f"✅ TERMINATION: Woke up pending accept_stream() calls, {id(self)}")
logger.debug(f"Woke up pending accept_stream() calls, {id(self)}")
await self._notify_parent_of_termination()
@ -1005,11 +1001,9 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._stats["bytes_received"] += len(event.data)
try:
print(f"🔧 STREAM_DATA: Handling data for stream {stream_id}")
if stream_id not in self._streams:
if self._is_incoming_stream(stream_id):
print(f"🔧 STREAM_DATA: Creating new incoming stream {stream_id}")
logger.info(f"Creating new incoming stream {stream_id}")
from .stream import QUICStream, StreamDirection
@ -1027,29 +1021,24 @@ class QUICConnection(IRawConnection, IMuxedConn):
async with self._accept_queue_lock:
self._stream_accept_queue.append(stream)
self._stream_accept_event.set()
print(
f"✅ STREAM_DATA: Added stream {stream_id} to accept queue"
)
logger.debug(f"Added stream {stream_id} to accept queue")
async with self._stream_count_lock:
self._inbound_stream_count += 1
self._stats["streams_opened"] += 1
else:
print(
f"❌ STREAM_DATA: Unexpected outbound stream {stream_id} in data event"
logger.error(
f"Unexpected outbound stream {stream_id} in data event"
)
return
stream = self._streams[stream_id]
await stream.handle_data_received(event.data, event.end_stream)
print(
f"✅ STREAM_DATA: Forwarded {len(event.data)} bytes to stream {stream_id}"
)
except Exception as e:
logger.error(f"Error handling stream data for stream {stream_id}: {e}")
print(f"❌ STREAM_DATA: Error: {e}")
logger.info(f"❌ STREAM_DATA: Error: {e}")
async def _get_or_create_stream(self, stream_id: int) -> QUICStream:
"""Get existing stream or create new inbound stream."""
@ -1106,7 +1095,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
except Exception as e:
logger.error(f"Error in stream handler for stream {stream_id}: {e}")
print(f"Created inbound stream {stream_id}")
logger.info(f"Created inbound stream {stream_id}")
return stream
def _is_incoming_stream(self, stream_id: int) -> bool:
@ -1133,7 +1122,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
try:
stream = self._streams[stream_id]
await stream.handle_reset(event.error_code)
print(
logger.info(
f"Handled reset for stream {stream_id}"
f"with error code {event.error_code}"
)
@ -1142,13 +1131,13 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Force remove the stream
self._remove_stream(stream_id)
else:
print(f"Received reset for unknown stream {stream_id}")
logger.info(f"Received reset for unknown stream {stream_id}")
async def _handle_datagram_received(
self, event: events.DatagramFrameReceived
) -> None:
"""Handle datagram frame (if using QUIC datagrams)."""
print(f"Datagram frame received: size={len(event.data)}")
logger.info(f"Datagram frame received: size={len(event.data)}")
# For now, just log. Could be extended for custom datagram handling
async def _handle_timer_events(self) -> None:
@ -1165,7 +1154,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
"""Transmit pending QUIC packets using available socket."""
sock = self._socket
if not sock:
print("No socket to transmit")
logger.info("No socket to transmit")
return
try:
@ -1183,11 +1172,11 @@ class QUICConnection(IRawConnection, IMuxedConn):
await self._handle_connection_error(e)
# Additional methods for stream data processing
async def _process_quic_event(self, event):
async def _process_quic_event(self, event: events.QuicEvent) -> None:
"""Process a single QUIC event."""
await self._handle_quic_event(event)
async def _transmit_pending_data(self):
async def _transmit_pending_data(self) -> None:
"""Transmit any pending data."""
await self._transmit()
@ -1211,7 +1200,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
return
self._closed = True
print(f"Closing QUIC connection to {self._remote_peer_id}")
logger.info(f"Closing QUIC connection to {self._remote_peer_id}")
try:
# Close all streams gracefully
@ -1253,7 +1242,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._streams.clear()
self._closed_event.set()
print(f"QUIC connection to {self._remote_peer_id} closed")
logger.info(f"QUIC connection to {self._remote_peer_id} closed")
except Exception as e:
logger.error(f"Error during connection close: {e}")
@ -1268,13 +1257,13 @@ class QUICConnection(IRawConnection, IMuxedConn):
try:
if self._transport:
await self._transport._cleanup_terminated_connection(self)
print("Notified transport of connection termination")
logger.info("Notified transport of connection termination")
return
for listener in self._transport._listeners:
try:
await listener._remove_connection_by_object(self)
print("Found and notified listener of connection termination")
logger.info("Found and notified listener of connection termination")
return
except Exception:
continue
@ -1285,7 +1274,8 @@ class QUICConnection(IRawConnection, IMuxedConn):
return
logger.warning(
"Could not notify parent of connection termination - no parent reference found"
"Could not notify parent of connection termination - no"
f" parent reference found for conn host {self._quic.host_cid.hex()}"
)
except Exception as e:
@ -1298,12 +1288,10 @@ class QUICConnection(IRawConnection, IMuxedConn):
for tracked_cid, tracked_conn in list(listener._connections.items()):
if tracked_conn is self:
await listener._remove_connection(tracked_cid)
print(
f"Removed connection {tracked_cid.hex()} by object reference"
)
logger.info(f"Removed connection {tracked_cid.hex()}")
return
print("Fallback cleanup by connection ID completed")
logger.info("Fallback cleanup by connection ID completed")
except Exception as e:
logger.error(f"Error in fallback cleanup: {e}")
@ -1401,6 +1389,9 @@ class QUICConnection(IRawConnection, IMuxedConn):
# String representation
def __repr__(self) -> str:
current_cid: str | None = (
self._current_connection_id.hex() if self._current_connection_id else None
)
return (
f"QUICConnection(peer={self._remote_peer_id}, "
f"addr={self._remote_addr}, "
@ -1408,7 +1399,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
f"verified={self._peer_verified}, "
f"established={self._established}, "
f"streams={len(self._streams)}, "
f"current_cid={self._current_connection_id.hex() if self._current_connection_id else None})"
f"current_cid={current_cid})"
)
def __str__(self) -> str:

View File

@ -42,7 +42,6 @@ if TYPE_CHECKING:
from .transport import QUICTransport
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
@ -277,63 +276,40 @@ class QUICListener(IListener):
self._stats["packets_processed"] += 1
self._stats["bytes_received"] += len(data)
print(f"🔧 PACKET: Processing {len(data)} bytes from {addr}")
logger.debug(f"Processing packet of {len(data)} bytes from {addr}")
# Parse packet header OUTSIDE the lock
packet_info = self.parse_quic_packet(data)
if packet_info is None:
print("❌ PACKET: Failed to parse packet header")
logger.error(f"Failed to parse packet header quic packet from {addr}")
self._stats["invalid_packets"] += 1
return
dest_cid = packet_info.destination_cid
print(f"🔧 DEBUG: Packet info: {packet_info is not None}")
print(f"🔧 DEBUG: Packet type: {packet_info.packet_type}")
print(
f"🔧 DEBUG: Is short header: {packet_info.packet_type.name != 'INITIAL'}"
)
# CRITICAL FIX: Reduce lock scope - only protect connection lookups
# Get connection references with minimal lock time
connection_obj = None
pending_quic_conn = None
async with self._connection_lock:
# Quick lookup operations only
print(
f"🔧 DEBUG: Pending connections: {[cid.hex() for cid in self._pending_connections.keys()]}"
)
print(
f"🔧 DEBUG: Established connections: {[cid.hex() for cid in self._connections.keys()]}"
)
if dest_cid in self._connections:
connection_obj = self._connections[dest_cid]
print(
f"✅ PACKET: Routing to established connection {dest_cid.hex()}"
)
print(f"PACKET: Routing to established connection {dest_cid.hex()}")
elif dest_cid in self._pending_connections:
pending_quic_conn = self._pending_connections[dest_cid]
print(f"PACKET: Routing to pending connection {dest_cid.hex()}")
print(f"PACKET: Routing to pending connection {dest_cid.hex()}")
else:
# Check if this is a new connection
print(
f"🔧 PACKET: Parsed packet - version: {packet_info.version:#x}, dest_cid: {dest_cid.hex()}, src_cid: {packet_info.source_cid.hex()}"
)
if packet_info.packet_type.name == "INITIAL":
print(f"🔧 PACKET: Creating new connection for {addr}")
logger.debug(
f"Received INITIAL Packet Creating new conn for {addr}"
)
# Create new connection INSIDE the lock for safety
pending_quic_conn = await self._handle_new_connection(
data, addr, packet_info
)
else:
print(
f"❌ PACKET: Unknown connection for non-initial packet {dest_cid.hex()}"
)
return
# CRITICAL: Process packets OUTSIDE the lock to prevent deadlock
@ -364,7 +340,7 @@ class QUICListener(IListener):
) -> None:
"""Handle packet for established connection WITHOUT holding connection lock."""
try:
print(f"🔧 ESTABLISHED: Handling packet for connection {dest_cid.hex()}")
print(f" ESTABLISHED: Handling packet for connection {dest_cid.hex()}")
# Forward packet to connection object
# This may trigger event processing and stream creation
@ -382,21 +358,19 @@ class QUICListener(IListener):
) -> None:
"""Handle packet for pending connection WITHOUT holding connection lock."""
try:
print(
f"🔧 PENDING: Handling packet for pending connection {dest_cid.hex()}"
)
print(f"🔧 PENDING: Packet size: {len(data)} bytes from {addr}")
print(f"Handling packet for pending connection {dest_cid.hex()}")
print(f"Packet size: {len(data)} bytes from {addr}")
# Feed data to QUIC connection
quic_conn.receive_datagram(data, addr, now=time.time())
print("PENDING: Datagram received by QUIC connection")
print("PENDING: Datagram received by QUIC connection")
# Process events - this is crucial for handshake progression
print("🔧 PENDING: Processing QUIC events...")
print("Processing QUIC events...")
await self._process_quic_events(quic_conn, addr, dest_cid)
# Send any outgoing packets
print("🔧 PENDING: Transmitting response...")
print("Transmitting response...")
await self._transmit_for_connection(quic_conn, addr)
# Check if handshake completed (with minimal locking)
@ -404,10 +378,10 @@ class QUICListener(IListener):
hasattr(quic_conn, "_handshake_complete")
and quic_conn._handshake_complete
):
print("PENDING: Handshake completed, promoting connection")
print("PENDING: Handshake completed, promoting connection")
await self._promote_pending_connection(quic_conn, addr, dest_cid)
else:
print("🔧 PENDING: Handshake still in progress")
print("Handshake still in progress")
except Exception as e:
logger.error(f"Error handling pending connection {dest_cid.hex()}: {e}")
@ -455,35 +429,28 @@ class QUICListener(IListener):
async def _handle_new_connection(
self, data: bytes, addr: tuple[str, int], packet_info: QUICPacketInfo
) -> None:
) -> QuicConnection | None:
"""Handle new connection with proper connection ID handling."""
try:
print(f"🔧 NEW_CONN: Starting handshake for {addr}")
logger.debug(f"Starting handshake for {addr}")
# Find appropriate QUIC configuration
quic_config = None
config_key = None
for protocol, config in self._quic_configs.items():
wire_versions = custom_quic_version_to_wire_format(protocol)
if wire_versions == packet_info.version:
quic_config = config
config_key = protocol
break
if not quic_config:
print(
f"❌ NEW_CONN: No configuration found for version 0x{packet_info.version:08x}"
)
print(
f"🔧 NEW_CONN: Available configs: {list(self._quic_configs.keys())}"
logger.error(
f"No configuration found for version 0x{packet_info.version:08x}"
)
await self._send_version_negotiation(addr, packet_info.source_cid)
return
print(
f"✅ NEW_CONN: Using config {config_key} for version 0x{packet_info.version:08x}"
)
if not quic_config:
raise QUICListenError("Cannot determine QUIC configuration")
# Create server-side QUIC configuration
server_config = create_server_config_from_base(
@ -492,19 +459,6 @@ class QUICListener(IListener):
transport_config=self._config,
)
# Debug the server configuration
print(f"🔧 NEW_CONN: Server config - is_client: {server_config.is_client}")
print(
f"🔧 NEW_CONN: Server config - has_certificate: {server_config.certificate is not None}"
)
print(
f"🔧 NEW_CONN: Server config - has_private_key: {server_config.private_key is not None}"
)
print(f"🔧 NEW_CONN: Server config - ALPN: {server_config.alpn_protocols}")
print(
f"🔧 NEW_CONN: Server config - verify_mode: {server_config.verify_mode}"
)
# Validate certificate has libp2p extension
if server_config.certificate:
cert = server_config.certificate
@ -513,24 +467,15 @@ class QUICListener(IListener):
if ext.oid == LIBP2P_TLS_EXTENSION_OID:
has_libp2p_ext = True
break
print(
f"🔧 NEW_CONN: Certificate has libp2p extension: {has_libp2p_ext}"
)
logger.debug(f"Certificate has libp2p extension: {has_libp2p_ext}")
if not has_libp2p_ext:
print("❌ NEW_CONN: Certificate missing libp2p extension!")
logger.error("Certificate missing libp2p extension!")
# Generate a new destination connection ID for this connection
import secrets
destination_cid = secrets.token_bytes(8)
print(f"🔧 NEW_CONN: Generated new CID: {destination_cid.hex()}")
print(
f"🔧 NEW_CONN: Original destination CID: {packet_info.destination_cid.hex()}"
logger.debug(
f"Original destination CID: {packet_info.destination_cid.hex()}"
)
# Create QUIC connection with proper parameters for server
quic_conn = QuicConnection(
configuration=server_config,
original_destination_connection_id=packet_info.destination_cid,
@ -540,38 +485,28 @@ class QUICListener(IListener):
# Use the first host CID as our routing CID
if quic_conn._host_cids:
destination_cid = quic_conn._host_cids[0].cid
print(
f"🔧 NEW_CONN: Using host CID as routing CID: {destination_cid.hex()}"
)
logger.debug(f"Using host CID as routing CID: {destination_cid.hex()}")
else:
# Fallback to random if no host CIDs generated
import secrets
destination_cid = secrets.token_bytes(8)
print(f"🔧 NEW_CONN: Fallback to random CID: {destination_cid.hex()}")
logger.debug(f"Fallback to random CID: {destination_cid.hex()}")
print(
f"🔧 NEW_CONN: Original destination CID: {packet_info.destination_cid.hex()}"
logger.debug(f"Generated {len(quic_conn._host_cids)} host CIDs for client")
logger.debug(
f"QUIC connection created for destination CID {destination_cid.hex()}"
)
print(f"🔧 Generated {len(quic_conn._host_cids)} host CIDs for client")
print("✅ NEW_CONN: QUIC connection created successfully")
# Store connection mapping using our generated CID
self._pending_connections[destination_cid] = quic_conn
self._addr_to_cid[addr] = destination_cid
self._cid_to_addr[destination_cid] = addr
print(
f"🔧 NEW_CONN: Stored mappings for {addr} <-> {destination_cid.hex()}"
)
print("Receiving Datagram")
# Process initial packet
quic_conn.receive_datagram(data, addr, now=time.time())
# Debug connection state after receiving packet
await self._debug_quic_connection_state_detailed(quic_conn, destination_cid)
# Process events and send response
await self._process_quic_events(quic_conn, addr, destination_cid)
await self._transmit_for_connection(quic_conn, addr)
@ -581,109 +516,27 @@ class QUICListener(IListener):
f"(version: 0x{packet_info.version:08x}, cid: {destination_cid.hex()})"
)
return quic_conn
except Exception as e:
logger.error(f"Error handling new connection from {addr}: {e}")
import traceback
traceback.print_exc()
self._stats["connections_rejected"] += 1
async def _debug_quic_connection_state_detailed(
self, quic_conn: QuicConnection, connection_id: bytes
):
"""Enhanced connection state debugging."""
try:
print(f"🔧 QUIC_STATE: Debugging connection {connection_id.hex()}")
if not quic_conn:
print("❌ QUIC_STATE: QUIC CONNECTION NOT FOUND")
return
# Check TLS state
if hasattr(quic_conn, "tls") and quic_conn.tls:
print("✅ QUIC_STATE: TLS context exists")
if hasattr(quic_conn.tls, "state"):
print(f"🔧 QUIC_STATE: TLS state: {quic_conn.tls.state}")
# Check if we have peer certificate
if (
hasattr(quic_conn.tls, "_peer_certificate")
and quic_conn.tls._peer_certificate
):
print("✅ QUIC_STATE: Peer certificate available")
else:
print("🔧 QUIC_STATE: No peer certificate yet")
# Check TLS handshake completion
if hasattr(quic_conn.tls, "handshake_complete"):
handshake_status = quic_conn._handshake_complete
print(f"🔧 QUIC_STATE: TLS handshake complete: {handshake_status}")
else:
print("❌ QUIC_STATE: No TLS context!")
# Check connection state
if hasattr(quic_conn, "_state"):
print(f"🔧 QUIC_STATE: Connection state: {quic_conn._state}")
# Check if handshake is complete
if hasattr(quic_conn, "_handshake_complete"):
print(
f"🔧 QUIC_STATE: Handshake complete: {quic_conn._handshake_complete}"
)
# Check configuration
if hasattr(quic_conn, "configuration"):
config = quic_conn.configuration
print(
f"🔧 QUIC_STATE: Config certificate: {config.certificate is not None}"
)
print(
f"🔧 QUIC_STATE: Config private_key: {config.private_key is not None}"
)
print(f"🔧 QUIC_STATE: Config is_client: {config.is_client}")
print(f"🔧 QUIC_STATE: Config verify_mode: {config.verify_mode}")
print(f"🔧 QUIC_STATE: Config ALPN: {config.alpn_protocols}")
if config.certificate:
cert = config.certificate
print(f"🔧 QUIC_STATE: Certificate subject: {cert.subject}")
print(
f"🔧 QUIC_STATE: Certificate valid from: {cert.not_valid_before_utc}"
)
print(
f"🔧 QUIC_STATE: Certificate valid until: {cert.not_valid_after_utc}"
)
# Check for connection errors
if hasattr(quic_conn, "_close_event") and quic_conn._close_event:
print(
f"❌ QUIC_STATE: Connection has close event: {quic_conn._close_event}"
)
# Check for TLS errors
if (
hasattr(quic_conn, "_handshake_complete")
and not quic_conn._handshake_complete
):
print("⚠️ QUIC_STATE: Handshake not yet complete")
except Exception as e:
print(f"❌ QUIC_STATE: Error checking state: {e}")
import traceback
traceback.print_exc()
return None
async def _handle_short_header_packet(
self, data: bytes, addr: tuple[str, int]
) -> None:
"""Handle short header packets for established connections."""
try:
print(f"🔧 SHORT_HDR: Handling short header packet from {addr}")
print(f" SHORT_HDR: Handling short header packet from {addr}")
# First, try address-based lookup
dest_cid = self._addr_to_cid.get(addr)
if dest_cid and dest_cid in self._connections:
print(f"SHORT_HDR: Routing via address mapping to {dest_cid.hex()}")
print(f"SHORT_HDR: Routing via address mapping to {dest_cid.hex()}")
connection = self._connections[dest_cid]
await self._route_to_connection(connection, data, addr)
return
@ -693,9 +546,7 @@ class QUICListener(IListener):
potential_cid = data[1:9]
if potential_cid in self._connections:
print(
f"✅ SHORT_HDR: Routing via extracted CID {potential_cid.hex()}"
)
print(f"SHORT_HDR: Routing via extracted CID {potential_cid.hex()}")
connection = self._connections[potential_cid]
# Update mappings for future packets
@ -734,59 +585,26 @@ class QUICListener(IListener):
addr: tuple[str, int],
dest_cid: bytes,
) -> None:
"""Handle packet for a pending (handshaking) connection with enhanced debugging."""
"""Handle packet for a pending (handshaking) connection."""
try:
print(
f"🔧 PENDING: Handling packet for pending connection {dest_cid.hex()}"
)
print(f"🔧 PENDING: Packet size: {len(data)} bytes from {addr}")
# Check connection state before processing
if hasattr(quic_conn, "_state"):
print(f"🔧 PENDING: Connection state before: {quic_conn._state}")
if (
hasattr(quic_conn, "tls")
and quic_conn.tls
and hasattr(quic_conn.tls, "state")
):
print(f"🔧 PENDING: TLS state before: {quic_conn.tls.state}")
logger.debug(f"Handling packet for pending connection {dest_cid.hex()}")
# Feed data to QUIC connection
quic_conn.receive_datagram(data, addr, now=time.time())
print("✅ PENDING: Datagram received by QUIC connection")
# Check state after receiving packet
if hasattr(quic_conn, "_state"):
print(f"🔧 PENDING: Connection state after: {quic_conn._state}")
if (
hasattr(quic_conn, "tls")
and quic_conn.tls
and hasattr(quic_conn.tls, "state")
):
print(f"🔧 PENDING: TLS state after: {quic_conn.tls.state}")
if quic_conn.tls:
print(f"TLS state after: {quic_conn.tls.state}")
# Process events - this is crucial for handshake progression
print("🔧 PENDING: Processing QUIC events...")
await self._process_quic_events(quic_conn, addr, dest_cid)
# Send any outgoing packets - this is where the response should be sent
print("🔧 PENDING: Transmitting response...")
await self._transmit_for_connection(quic_conn, addr)
# Check if handshake completed
if (
hasattr(quic_conn, "_handshake_complete")
and quic_conn._handshake_complete
):
print("✅ PENDING: Handshake completed, promoting connection")
if quic_conn._handshake_complete:
logger.debug("PENDING: Handshake completed, promoting connection")
await self._promote_pending_connection(quic_conn, addr, dest_cid)
else:
print("🔧 PENDING: Handshake still in progress")
# Debug why handshake might be stuck
await self._debug_handshake_state(quic_conn, dest_cid)
except Exception as e:
logger.error(f"Error handling pending connection {dest_cid.hex()}: {e}")
@ -795,7 +613,7 @@ class QUICListener(IListener):
traceback.print_exc()
# Remove problematic pending connection
print(f"❌ PENDING: Removing problematic connection {dest_cid.hex()}")
logger.error(f"Removing problematic connection {dest_cid.hex()}")
await self._remove_pending_connection(dest_cid)
async def _process_quic_events(
@ -810,15 +628,15 @@ class QUICListener(IListener):
break
events_processed += 1
print(
f"🔧 EVENT: Processing event {events_processed}: {type(event).__name__}"
logger.debug(
"QUIC EVENT: Processing event "
f"{events_processed}: {type(event).__name__}"
)
if isinstance(event, events.ConnectionTerminated):
print(
f"❌ EVENT: Connection terminated - code: {event.error_code}, reason: {event.reason_phrase}"
)
logger.debug(
"QUIC EVENT: Connection terminated "
f"- code: {event.error_code}, reason: {event.reason_phrase}"
f"Connection {dest_cid.hex()} from {addr} "
f"terminated: {event.reason_phrase}"
)
@ -826,47 +644,44 @@ class QUICListener(IListener):
break
elif isinstance(event, events.HandshakeCompleted):
print(
f" EVENT: Handshake completed for connection {dest_cid.hex()}"
logger.debug(
"QUIC EVENT: Handshake completed for connection "
f"{dest_cid.hex()}"
)
logger.debug(f"Handshake completed for connection {dest_cid.hex()}")
await self._promote_pending_connection(quic_conn, addr, dest_cid)
elif isinstance(event, events.StreamDataReceived):
print(f"🔧 EVENT: Stream data received on stream {event.stream_id}")
# Forward to established connection if available
logger.debug(
f"QUIC EVENT: Stream data received on stream {event.stream_id}"
)
if dest_cid in self._connections:
connection = self._connections[dest_cid]
print(
f"📨 FORWARDING: Stream data to connection {id(connection)}"
)
await connection._handle_stream_data(event)
elif isinstance(event, events.StreamReset):
print(f"🔧 EVENT: Stream reset on stream {event.stream_id}")
# Forward to established connection if available
logger.debug(
f"QUIC EVENT: Stream reset on stream {event.stream_id}"
)
if dest_cid in self._connections:
connection = self._connections[dest_cid]
await connection._handle_stream_reset(event)
elif isinstance(event, events.ConnectionIdIssued):
print(
f"🔧 EVENT: Connection ID issued: {event.connection_id.hex()}"
f"QUIC EVENT: Connection ID issued: {event.connection_id.hex()}"
)
# ADD: Update mappings using existing data structures
# Add new CID to the same address mapping
taddr = self._cid_to_addr.get(dest_cid)
if taddr:
# Don't overwrite, but note that this CID is also valid for this address
print(
f"🔧 EVENT: New CID {event.connection_id.hex()} available for {taddr}"
# Don't overwrite, but this CID is also valid for this address
logger.debug(
f"QUIC EVENT: New CID {event.connection_id.hex()} "
f"available for {taddr}"
)
elif isinstance(event, events.ConnectionIdRetired):
print(
f"🔧 EVENT: Connection ID retired: {event.connection_id.hex()}"
)
# ADD: Clean up using existing patterns
print(f"EVENT: Connection ID retired: {event.connection_id.hex()}")
retired_cid = event.connection_id
if retired_cid in self._cid_to_addr:
addr = self._cid_to_addr[retired_cid]
@ -874,16 +689,13 @@ class QUICListener(IListener):
# Only remove addr mapping if this was the active CID
if self._addr_to_cid.get(addr) == retired_cid:
del self._addr_to_cid[addr]
print(
f"🔧 EVENT: Cleaned up mapping for retired CID {retired_cid.hex()}"
)
else:
print(f"🔧 EVENT: Unhandled event type: {type(event).__name__}")
print(f" EVENT: Unhandled event type: {type(event).__name__}")
if events_processed == 0:
print("🔧 EVENT: No events to process")
print(" EVENT: No events to process")
else:
print(f"🔧 EVENT: Processed {events_processed} events total")
print(f" EVENT: Processed {events_processed} events total")
except Exception as e:
print(f"❌ EVENT: Error processing events: {e}")
@ -891,62 +703,18 @@ class QUICListener(IListener):
traceback.print_exc()
async def _debug_quic_connection_state(
self, quic_conn: QuicConnection, connection_id: bytes
):
"""Debug the internal state of the QUIC connection."""
try:
print(f"🔧 QUIC_STATE: Debugging connection {connection_id}")
if not quic_conn:
print("🔧 QUIC_STATE: QUIC CONNECTION NOT FOUND")
return
# Check TLS state
if hasattr(quic_conn, "tls") and quic_conn.tls:
print("🔧 QUIC_STATE: TLS context exists")
if hasattr(quic_conn.tls, "state"):
print(f"🔧 QUIC_STATE: TLS state: {quic_conn.tls.state}")
else:
print("❌ QUIC_STATE: No TLS context!")
# Check connection state
if hasattr(quic_conn, "_state"):
print(f"🔧 QUIC_STATE: Connection state: {quic_conn._state}")
# Check if handshake is complete
if hasattr(quic_conn, "_handshake_complete"):
print(
f"🔧 QUIC_STATE: Handshake complete: {quic_conn._handshake_complete}"
)
# Check configuration
if hasattr(quic_conn, "configuration"):
config = quic_conn.configuration
print(
f"🔧 QUIC_STATE: Config certificate: {config.certificate is not None}"
)
print(
f"🔧 QUIC_STATE: Config private_key: {config.private_key is not None}"
)
print(f"🔧 QUIC_STATE: Config is_client: {config.is_client}")
except Exception as e:
print(f"❌ QUIC_STATE: Error checking state: {e}")
async def _promote_pending_connection(
self, quic_conn: QuicConnection, addr: tuple[str, int], dest_cid: bytes
):
) -> None:
"""Promote pending connection - avoid duplicate creation."""
try:
# Remove from pending connections
self._pending_connections.pop(dest_cid, None)
# CHECK: Does QUICConnection already exist?
if dest_cid in self._connections:
connection = self._connections[dest_cid]
print(
f"🔄 PROMOTION: Using existing QUICConnection {id(connection)} for {dest_cid.hex()}"
logger.debug(
f"Using existing QUICConnection {id(connection)} "
f"for {dest_cid.hex()}"
)
else:
@ -968,22 +736,17 @@ class QUICListener(IListener):
listener_socket=self._socket,
)
print(
f"🔄 PROMOTION: Created NEW QUICConnection {id(connection)} for {dest_cid.hex()}"
)
logger.debug(f"🔄 Created NEW QUICConnection for {dest_cid.hex()}")
# Store the connection
self._connections[dest_cid] = connection
# Update mappings
self._addr_to_cid[addr] = dest_cid
self._cid_to_addr[dest_cid] = addr
# Rest of the existing promotion code...
if self._nursery:
connection._nursery = self._nursery
await connection.connect(self._nursery)
print("QUICListener: Connection connected succesfully")
logger.debug(f"Connection connected succesfully for {dest_cid.hex()}")
if self._security_manager:
try:
@ -1001,27 +764,23 @@ class QUICListener(IListener):
if self._nursery:
connection._nursery = self._nursery
await connection._start_background_tasks()
print(f"Started background tasks for connection {dest_cid.hex()}")
if self._transport._swarm:
print(f"🔄 PROMOTION: Adding connection {id(connection)} to swarm")
await self._transport._swarm.add_conn(connection)
print(
f"🔄 PROMOTION: Successfully added connection {id(connection)} to swarm"
logger.debug(
f"Started background tasks for connection {dest_cid.hex()}"
)
if self._handler:
try:
print(f"Invoking user callback {dest_cid.hex()}")
await self._handler(connection)
if self._transport._swarm:
await self._transport._swarm.add_conn(connection)
logger.debug(f"Successfully added connection {dest_cid.hex()} to swarm")
except Exception as e:
logger.error(f"Error in user callback: {e}")
try:
print(f"Invoking user callback {dest_cid.hex()}")
await self._handler(connection)
except Exception as e:
logger.error(f"Error in user callback: {e}")
self._stats["connections_accepted"] += 1
logger.info(
f"✅ Enhanced connection {dest_cid.hex()} established from {addr}"
)
logger.info(f"Enhanced connection {dest_cid.hex()} established from {addr}")
except Exception as e:
logger.error(f"❌ Error promoting connection {dest_cid.hex()}: {e}")
@ -1062,10 +821,12 @@ class QUICListener(IListener):
if dest_cid:
await self._remove_connection(dest_cid)
async def _transmit_for_connection(self, quic_conn, addr):
async def _transmit_for_connection(
self, quic_conn: QuicConnection, addr: tuple[str, int]
) -> None:
"""Enhanced transmission diagnostics to analyze datagram content."""
try:
print(f"🔧 TRANSMIT: Starting transmission to {addr}")
print(f" TRANSMIT: Starting transmission to {addr}")
# Get current timestamp for timing
import time
@ -1073,56 +834,31 @@ class QUICListener(IListener):
now = time.time()
datagrams = quic_conn.datagrams_to_send(now=now)
print(f"🔧 TRANSMIT: Got {len(datagrams)} datagrams to send")
print(f" TRANSMIT: Got {len(datagrams)} datagrams to send")
if not datagrams:
print("⚠️ TRANSMIT: No datagrams to send")
return
for i, (datagram, dest_addr) in enumerate(datagrams):
print(f"🔧 TRANSMIT: Analyzing datagram {i}")
print(f"🔧 TRANSMIT: Datagram size: {len(datagram)} bytes")
print(f"🔧 TRANSMIT: Destination: {dest_addr}")
print(f"🔧 TRANSMIT: Expected destination: {addr}")
print(f" TRANSMIT: Analyzing datagram {i}")
print(f" TRANSMIT: Datagram size: {len(datagram)} bytes")
print(f" TRANSMIT: Destination: {dest_addr}")
print(f" TRANSMIT: Expected destination: {addr}")
# Analyze datagram content
if len(datagram) > 0:
# QUIC packet format analysis
first_byte = datagram[0]
header_form = (first_byte & 0x80) >> 7 # Bit 7
fixed_bit = (first_byte & 0x40) >> 6 # Bit 6
packet_type = (first_byte & 0x30) >> 4 # Bits 4-5
type_specific = first_byte & 0x0F # Bits 0-3
print(f"🔧 TRANSMIT: First byte: 0x{first_byte:02x}")
print(
f"🔧 TRANSMIT: Header form: {header_form} ({'Long' if header_form else 'Short'})"
)
print(
f"🔧 TRANSMIT: Fixed bit: {fixed_bit} ({'Valid' if fixed_bit else 'INVALID!'})"
)
print(f"🔧 TRANSMIT: Packet type: {packet_type}")
# For long header packets (handshake), analyze further
if header_form == 1: # Long header
packet_types = {
0: "Initial",
1: "0-RTT",
2: "Handshake",
3: "Retry",
}
type_name = packet_types.get(packet_type, "Unknown")
print(f"🔧 TRANSMIT: Long header packet type: {type_name}")
# Look for CRYPTO frame indicators
# CRYPTO frame type is 0x06
crypto_frame_found = False
for offset in range(len(datagram)):
if datagram[offset] == 0x06: # CRYPTO frame type
if datagram[offset] == 0x06:
crypto_frame_found = True
print(
f"✅ TRANSMIT: Found CRYPTO frame at offset {offset}"
)
break
if not crypto_frame_found:
@ -1138,21 +874,11 @@ class QUICListener(IListener):
elif frame_type == 0x06: # CRYPTO
frame_types_found.add("CRYPTO")
print(
f"🔧 TRANSMIT: Frame types detected: {frame_types_found}"
)
# Show first few bytes for debugging
preview_bytes = min(32, len(datagram))
hex_preview = " ".join(f"{b:02x}" for b in datagram[:preview_bytes])
print(f"🔧 TRANSMIT: First {preview_bytes} bytes: {hex_preview}")
# Actually send the datagram
if self._socket:
try:
print(f"🔧 TRANSMIT: Sending datagram {i} via socket...")
print(f" TRANSMIT: Sending datagram {i} via socket...")
await self._socket.sendto(datagram, addr)
print(f"TRANSMIT: Successfully sent datagram {i}")
print(f"TRANSMIT: Successfully sent datagram {i}")
except Exception as send_error:
print(f"❌ TRANSMIT: Socket send failed: {send_error}")
else:
@ -1160,10 +886,9 @@ class QUICListener(IListener):
# Check if there are more datagrams after sending
remaining_datagrams = quic_conn.datagrams_to_send(now=time.time())
print(
f"🔧 TRANSMIT: After sending, {len(remaining_datagrams)} datagrams remain"
logger.debug(
f" TRANSMIT: After sending, {len(remaining_datagrams)} datagrams remain"
)
print("------END OF THIS DATAGRAM LOG-----")
except Exception as e:
print(f"❌ TRANSMIT: Transmission error: {e}")
@ -1184,6 +909,7 @@ class QUICListener(IListener):
logger.debug("Using transport background nursery for listener")
elif nursery:
active_nursery = nursery
self._transport._background_nursery = nursery
logger.debug("Using provided nursery for listener")
else:
raise QUICListenError("No nursery available")
@ -1299,8 +1025,10 @@ class QUICListener(IListener):
except Exception as e:
logger.error(f"Error closing listener: {e}")
async def _remove_connection_by_object(self, connection_obj) -> None:
"""Remove a connection by object reference (called when connection terminates)."""
async def _remove_connection_by_object(
self, connection_obj: QUICConnection
) -> None:
"""Remove a connection by object reference."""
try:
# Find the connection ID for this object
connection_cid = None
@ -1311,19 +1039,12 @@ class QUICListener(IListener):
if connection_cid:
await self._remove_connection(connection_cid)
logger.debug(
f"✅ TERMINATION: Removed connection {connection_cid.hex()} by object reference"
)
print(
f"✅ TERMINATION: Removed connection {connection_cid.hex()} by object reference"
)
logger.debug(f"Removed connection {connection_cid.hex()}")
else:
logger.warning("⚠️ TERMINATION: Connection object not found in tracking")
print("⚠️ TERMINATION: Connection object not found in tracking")
logger.warning("Connection object not found in tracking")
except Exception as e:
logger.error(f"❌ TERMINATION: Error removing connection by object: {e}")
print(f"❌ TERMINATION: Error removing connection by object: {e}")
logger.error(f"Error removing connection by object: {e}")
def get_addresses(self) -> list[Multiaddr]:
"""Get the bound addresses."""
@ -1376,63 +1097,3 @@ class QUICListener(IListener):
stats["active_connections"] = len(self._connections)
stats["pending_connections"] = len(self._pending_connections)
return stats
async def _debug_handshake_state(self, quic_conn: QuicConnection, dest_cid: bytes):
"""Debug why handshake might be stuck."""
try:
print(f"🔧 HANDSHAKE_DEBUG: Analyzing stuck handshake for {dest_cid.hex()}")
# Check TLS handshake state
if hasattr(quic_conn, "tls") and quic_conn.tls:
tls = quic_conn.tls
print(
f"🔧 HANDSHAKE_DEBUG: TLS state: {getattr(tls, 'state', 'Unknown')}"
)
# Check for TLS errors
if hasattr(tls, "_error") and tls._error:
print(f"❌ HANDSHAKE_DEBUG: TLS error: {tls._error}")
# Check certificate validation
if hasattr(tls, "_peer_certificate"):
if tls._peer_certificate:
print("✅ HANDSHAKE_DEBUG: Peer certificate received")
else:
print("❌ HANDSHAKE_DEBUG: No peer certificate")
# Check ALPN negotiation
if hasattr(tls, "_alpn_protocols"):
if tls._alpn_protocols:
print(
f"✅ HANDSHAKE_DEBUG: ALPN negotiated: {tls._alpn_protocols}"
)
else:
print("❌ HANDSHAKE_DEBUG: No ALPN protocol negotiated")
# Check QUIC connection state
if hasattr(quic_conn, "_state"):
state = quic_conn._state
print(f"🔧 HANDSHAKE_DEBUG: QUIC state: {state}")
# Check specific states that might indicate problems
if "FIRSTFLIGHT" in str(state):
print("⚠️ HANDSHAKE_DEBUG: Connection stuck in FIRSTFLIGHT state")
elif "CONNECTED" in str(state):
print(
"⚠️ HANDSHAKE_DEBUG: Connection shows CONNECTED but handshake not complete"
)
# Check for pending crypto data
if hasattr(quic_conn, "_cryptos") and quic_conn._cryptos:
print(
f"🔧 HANDSHAKE_DEBUG: Crypto data present {len(quic_conn._cryptos.keys())}"
)
# Check loss detection state
if hasattr(quic_conn, "_loss") and quic_conn._loss:
loss_detection = quic_conn._loss
if hasattr(loss_detection, "_pto_count"):
print(f"🔧 HANDSHAKE_DEBUG: PTO count: {loss_detection._pto_count}")
except Exception as e:
print(f"❌ HANDSHAKE_DEBUG: Error during debug: {e}")

View File

@ -1,4 +1,3 @@
"""
QUIC Security implementation for py-libp2p Module 5.
Implements libp2p TLS specification for QUIC transport with peer identity integration.
@ -8,7 +7,7 @@ Based on go-libp2p and js-libp2p security patterns.
from dataclasses import dataclass, field
import logging
import ssl
from typing import List, Optional, Union
from typing import Any
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
@ -130,14 +129,16 @@ class LibP2PExtensionHandler:
) from e
@staticmethod
def parse_signed_key_extension(extension: Extension) -> tuple[PublicKey, bytes]:
def parse_signed_key_extension(
extension: Extension[Any],
) -> tuple[PublicKey, bytes]:
"""
Parse the libp2p Public Key Extension with enhanced debugging.
"""
try:
print(f"🔍 Extension type: {type(extension)}")
print(f"🔍 Extension.value type: {type(extension.value)}")
# Extract the raw bytes from the extension
if isinstance(extension.value, UnrecognizedExtension):
# Use the .value property to get the bytes
@ -147,10 +148,10 @@ class LibP2PExtensionHandler:
# Fallback if it's already bytes somehow
raw_bytes = extension.value
print("🔍 Extension.value is already bytes")
print(f"🔍 Total extension length: {len(raw_bytes)} bytes")
print(f"🔍 Extension hex (first 50 bytes): {raw_bytes[:50].hex()}")
if not isinstance(raw_bytes, bytes):
raise QUICCertificateError(f"Expected bytes, got {type(raw_bytes)}")
@ -191,28 +192,37 @@ class LibP2PExtensionHandler:
signature = raw_bytes[offset : offset + signature_length]
print(f"🔍 Extracted signature length: {len(signature)} bytes")
print(f"🔍 Signature hex (first 20 bytes): {signature[:20].hex()}")
print(f"🔍 Signature starts with DER header: {signature[:2].hex() == '3045'}")
print(
f"🔍 Signature starts with DER header: {signature[:2].hex() == '3045'}"
)
# Detailed signature analysis
if len(signature) >= 2:
if signature[0] == 0x30:
der_length = signature[1]
print(f"🔍 DER sequence length field: {der_length}")
print(f"🔍 Expected DER total: {der_length + 2}")
print(f"🔍 Actual signature length: {len(signature)}")
logger.debug(
f"🔍 Expected DER total: {der_length + 2}"
f"🔍 Actual signature length: {len(signature)}"
)
if len(signature) != der_length + 2:
print(f"⚠️ DER length mismatch! Expected {der_length + 2}, got {len(signature)}")
logger.debug(
"⚠️ DER length mismatch! "
f"Expected {der_length + 2}, got {len(signature)}"
)
# Try truncating to correct DER length
if der_length + 2 < len(signature):
print(f"🔧 Truncating signature to correct DER length: {der_length + 2}")
signature = signature[:der_length + 2]
logger.debug(
"🔧 Truncating signature to correct DER length: "
f"{der_length + 2}"
)
signature = signature[: der_length + 2]
# Check if we have extra data
expected_total = 4 + public_key_length + 4 + signature_length
print(f"🔍 Expected total length: {expected_total}")
print(f"🔍 Actual total length: {len(raw_bytes)}")
if len(raw_bytes) > expected_total:
extra_bytes = len(raw_bytes) - expected_total
print(f"⚠️ Extra {extra_bytes} bytes detected!")
@ -221,7 +231,7 @@ class LibP2PExtensionHandler:
# Deserialize the public key
public_key = LibP2PKeyConverter.deserialize_public_key(public_key_bytes)
print(f"🔍 Successfully deserialized public key: {type(public_key)}")
print(f"🔍 Final signature to return: {len(signature)} bytes")
return public_key, signature
@ -229,6 +239,7 @@ class LibP2PExtensionHandler:
except Exception as e:
print(f"❌ Extension parsing failed: {e}")
import traceback
print(f"❌ Traceback: {traceback.format_exc()}")
raise QUICCertificateError(
f"Failed to parse signed key extension: {e}"
@ -470,26 +481,26 @@ class QUICTLSSecurityConfig:
# Core TLS components (required)
certificate: Certificate
private_key: Union[EllipticCurvePrivateKey, RSAPrivateKey]
private_key: EllipticCurvePrivateKey | RSAPrivateKey
# Certificate chain (optional)
certificate_chain: List[Certificate] = field(default_factory=list)
certificate_chain: list[Certificate] = field(default_factory=list)
# ALPN protocols
alpn_protocols: List[str] = field(default_factory=lambda: ["libp2p"])
alpn_protocols: list[str] = field(default_factory=lambda: ["libp2p"])
# TLS verification settings
verify_mode: ssl.VerifyMode = ssl.CERT_NONE
check_hostname: bool = False
# Optional peer ID for validation
peer_id: Optional[ID] = None
peer_id: ID | None = None
# Configuration metadata
is_client_config: bool = False
config_name: Optional[str] = None
config_name: str | None = None
def __post_init__(self):
def __post_init__(self) -> None:
"""Validate configuration after initialization."""
self._validate()
@ -516,46 +527,6 @@ class QUICTLSSecurityConfig:
if not self.alpn_protocols:
raise ValueError("At least one ALPN protocol is required")
def to_dict(self) -> dict:
"""
Convert to dictionary format for compatibility with existing code.
Returns:
Dictionary compatible with the original TSecurityConfig format
"""
return {
"certificate": self.certificate,
"private_key": self.private_key,
"certificate_chain": self.certificate_chain.copy(),
"alpn_protocols": self.alpn_protocols.copy(),
"verify_mode": self.verify_mode,
"check_hostname": self.check_hostname,
}
@classmethod
def from_dict(cls, config_dict: dict, **kwargs) -> "QUICTLSSecurityConfig":
"""
Create instance from dictionary format.
Args:
config_dict: Dictionary in TSecurityConfig format
**kwargs: Additional parameters for the config
Returns:
QUICTLSSecurityConfig instance
"""
return cls(
certificate=config_dict["certificate"],
private_key=config_dict["private_key"],
certificate_chain=config_dict.get("certificate_chain", []),
alpn_protocols=config_dict.get("alpn_protocols", ["libp2p"]),
verify_mode=config_dict.get("verify_mode", False),
check_hostname=config_dict.get("check_hostname", False),
**kwargs,
)
def validate_certificate_key_match(self) -> bool:
"""
Validate that the certificate and private key match.
@ -621,7 +592,7 @@ class QUICTLSSecurityConfig:
except Exception:
return False
def get_certificate_info(self) -> dict:
def get_certificate_info(self) -> dict[Any, Any]:
"""
Get certificate information for debugging.
@ -652,7 +623,7 @@ class QUICTLSSecurityConfig:
print(f"Check hostname: {self.check_hostname}")
print(f"Certificate chain length: {len(self.certificate_chain)}")
cert_info = self.get_certificate_info()
cert_info: dict[Any, Any] = self.get_certificate_info()
for key, value in cert_info.items():
print(f"Certificate {key}: {value}")
@ -663,9 +634,9 @@ class QUICTLSSecurityConfig:
def create_server_tls_config(
certificate: Certificate,
private_key: Union[EllipticCurvePrivateKey, RSAPrivateKey],
peer_id: Optional[ID] = None,
**kwargs,
private_key: EllipticCurvePrivateKey | RSAPrivateKey,
peer_id: ID | None = None,
**kwargs: Any,
) -> QUICTLSSecurityConfig:
"""
Create a server TLS configuration.
@ -694,9 +665,9 @@ def create_server_tls_config(
def create_client_tls_config(
certificate: Certificate,
private_key: Union[EllipticCurvePrivateKey, RSAPrivateKey],
peer_id: Optional[ID] = None,
**kwargs,
private_key: EllipticCurvePrivateKey | RSAPrivateKey,
peer_id: ID | None = None,
**kwargs: Any,
) -> QUICTLSSecurityConfig:
"""
Create a client TLS configuration.
@ -729,7 +700,7 @@ class QUICTLSConfigManager:
Integrates with aioquic's TLS configuration system.
"""
def __init__(self, libp2p_private_key: PrivateKey, peer_id: ID):
def __init__(self, libp2p_private_key: PrivateKey, peer_id: ID) -> None:
self.libp2p_private_key = libp2p_private_key
self.peer_id = peer_id
self.certificate_generator = CertificateGenerator()

View File

@ -472,6 +472,45 @@ class QUICStream(IMuxedStream):
logger.debug(f"Stream {self.stream_id} received FIN")
async def handle_stop_sending(self, error_code: int) -> None:
"""
Handle STOP_SENDING frame from remote peer.
When a STOP_SENDING frame is received, the peer is requesting that we
stop sending data on this stream. We respond by resetting the stream.
Args:
error_code: Error code from the STOP_SENDING frame
"""
logger.debug(
f"Stream {self.stream_id} handling STOP_SENDING (error_code={error_code})"
)
self._write_closed = True
# Wake up any pending write operations
self._backpressure_event.set()
async with self._state_lock:
if self.direction == StreamDirection.OUTBOUND:
self._state = StreamState.CLOSED
elif self._read_closed:
self._state = StreamState.CLOSED
else:
# Only write side closed - add WRITE_CLOSED state if needed
self._state = StreamState.WRITE_CLOSED
# Send RESET_STREAM in response (QUIC protocol requirement)
try:
self._connection._quic.reset_stream(int(self.stream_id), error_code)
await self._connection._transmit()
logger.debug(f"Sent RESET_STREAM for stream {self.stream_id}")
except Exception as e:
logger.warning(
f"Could not send RESET_STREAM for stream {self.stream_id}: {e}"
)
async def handle_reset(self, error_code: int) -> None:
"""
Handle stream reset from remote peer.

View File

@ -128,7 +128,7 @@ class QUICTransport(ITransport):
self._background_nursery = nursery
print("Transport background nursery set")
def set_swarm(self, swarm) -> None:
def set_swarm(self, swarm: Swarm) -> None:
"""Set the swarm for adding incoming connections."""
self._swarm = swarm
@ -232,12 +232,9 @@ class QUICTransport(ITransport):
except Exception as e:
raise QUICSecurityError(f"Failed to apply TLS configuration: {e}") from e
# type: ignore
async def dial(
self,
maddr: multiaddr.Multiaddr,
peer_id: ID,
nursery: trio.Nursery | None = None,
) -> QUICConnection:
"""
Dial a remote peer using QUIC transport with security verification.
@ -261,9 +258,6 @@ class QUICTransport(ITransport):
if not is_quic_multiaddr(maddr):
raise QUICDialError(f"Invalid QUIC multiaddr: {maddr}")
if not peer_id:
raise QUICDialError("Peer id cannot be null")
try:
# Extract connection details from multiaddr
host, port = quic_multiaddr_to_endpoint(maddr)
@ -288,7 +282,7 @@ class QUICTransport(ITransport):
connection = QUICConnection(
quic_connection=native_quic_connection,
remote_addr=(host, port),
remote_peer_id=peer_id,
remote_peer_id=None,
local_peer_id=self._peer_id,
is_initiator=True,
maddr=maddr,
@ -297,25 +291,19 @@ class QUICTransport(ITransport):
)
print("QUIC Connection Created")
active_nursery = nursery or self._background_nursery
if active_nursery is None:
if self._background_nursery is None:
logger.error("No nursery set to execute background tasks")
raise QUICDialError("No nursery found to execute tasks")
await connection.connect(active_nursery)
await connection.connect(self._background_nursery)
print("Starting to verify peer identity")
# Verify peer identity after TLS handshake
if peer_id:
await self._verify_peer_identity(connection, peer_id)
print("Identity verification done")
# Store connection for management
conn_id = f"{host}:{port}:{peer_id}"
conn_id = f"{host}:{port}"
self._connections[conn_id] = connection
print(f"Successfully dialed secure QUIC connection to {peer_id}")
return connection
except Exception as e:
@ -456,7 +444,7 @@ class QUICTransport(ITransport):
print("QUIC transport closed")
async def _cleanup_terminated_connection(self, connection) -> None:
async def _cleanup_terminated_connection(self, connection: QUICConnection) -> None:
"""Clean up a terminated connection from all listeners."""
try:
for listener in self._listeners: