fix: accept stream on server side

This commit is contained in:
Akash Mondal
2025-07-02 12:40:21 +00:00
committed by lla-dane
parent 6c45862fe9
commit c15c317514
9 changed files with 1444 additions and 1743 deletions

View File

@ -18,6 +18,7 @@ from libp2p.stream_muxer.exceptions import (
MuxedStreamError,
MuxedStreamReset,
)
from libp2p.transport.quic.exceptions import QUICStreamClosedError, QUICStreamResetError
from .exceptions import (
StreamClosed,
@ -174,7 +175,7 @@ class NetStream(INetStream):
print("NETSTREAM: READ ERROR, NEW STATE -> CLOSE_READ")
self.__stream_state = StreamState.CLOSE_READ
raise StreamEOF() from error
except MuxedStreamReset as error:
except (MuxedStreamReset, QUICStreamClosedError, QUICStreamResetError) as error:
print("NETSTREAM: READ ERROR, MUXED STREAM RESET")
async with self._state_lock:
if self.__stream_state in [
@ -205,7 +206,12 @@ class NetStream(INetStream):
try:
await self.muxed_stream.write(data)
except (MuxedStreamClosed, MuxedStreamError) as error:
except (
MuxedStreamClosed,
MuxedStreamError,
QUICStreamClosedError,
QUICStreamResetError,
) as error:
async with self._state_lock:
if self.__stream_state == StreamState.OPEN:
self.__stream_state = StreamState.CLOSE_WRITE

View File

@ -179,7 +179,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
"connection_id_changes": 0,
}
logger.debug(
print(
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()
logger.debug(f"Starting QUIC connection to {self._remote_peer_id}")
print(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()
logger.debug(f"QUIC connection to {self._remote_peer_id} started")
print(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:
logger.debug("Creating new socket for outbound connection")
print("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()
logger.debug(f"Initiated QUIC connection to {self._remote_addr}")
print(f"Initiated QUIC connection to {self._remote_addr}")
except Exception as e:
logger.error(f"Failed to initiate connection: {e}")
@ -340,10 +340,10 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Start background event processing
if not self._background_tasks_started:
logger.debug("STARTING BACKGROUND TASK")
print("STARTING BACKGROUND TASK")
await self._start_background_tasks()
else:
logger.debug("BACKGROUND TASK ALREADY STARTED")
print("BACKGROUND TASK ALREADY STARTED")
# Wait for handshake completion with timeout
with trio.move_on_after(
@ -357,11 +357,13 @@ class QUICConnection(IRawConnection, IMuxedConn):
f"{self.CONNECTION_HANDSHAKE_TIMEOUT}s"
)
print("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")
self._established = True
logger.info(f"QUIC connection established with {self._remote_peer_id}")
print(f"QUIC connection established with {self._remote_peer_id}")
except Exception as e:
logger.error(f"Failed to establish connection: {e}")
@ -375,21 +377,26 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._background_tasks_started = True
if self.__is_initiator: # Only for client connections
if self.__is_initiator:
print(f"CLIENT CONNECTION {id(self)}: Starting processing event loop")
self._nursery.start_soon(async_fn=self._client_packet_receiver)
# Start event processing task
self._nursery.start_soon(async_fn=self._event_processing_loop)
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._periodic_maintenance)
logger.debug("Started background tasks for QUIC connection")
print("Started background tasks for QUIC connection")
async def _event_processing_loop(self) -> None:
"""Main event processing loop for the connection."""
logger.debug("Started QUIC event processing loop")
print("Started QUIC event processing loop")
print(
f"Started QUIC event processing loop for connection id: {id(self)} "
f"and local peer id {str(self.local_peer_id())}"
)
try:
while not self._closed:
@ -409,7 +416,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
logger.error(f"Error in event processing loop: {e}")
await self._handle_connection_error(e)
finally:
logger.debug("QUIC event processing loop finished")
print("QUIC event processing loop finished")
async def _periodic_maintenance(self) -> None:
"""Perform periodic connection maintenance."""
@ -424,7 +431,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
# *** NEW: Log connection ID status periodically ***
if logger.isEnabledFor(logging.DEBUG):
cid_stats = self.get_connection_id_stats()
logger.debug(f"Connection ID stats: {cid_stats}")
print(f"Connection ID stats: {cid_stats}")
# Sleep for maintenance interval
await trio.sleep(30.0) # 30 seconds
@ -434,7 +441,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
async def _client_packet_receiver(self) -> None:
"""Receive packets for client connections."""
logger.debug("Starting client packet receiver")
print("Starting client packet receiver")
print("Started QUIC client packet receiver")
try:
@ -454,7 +461,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
await self._transmit()
except trio.ClosedResourceError:
logger.debug("Client socket closed")
print("Client socket closed")
break
except Exception as e:
logger.error(f"Error receiving client packet: {e}")
@ -464,7 +471,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
logger.info("Client packet receiver cancelled")
raise
finally:
logger.debug("Client packet receiver terminated")
print("Client packet receiver terminated")
# Security and identity methods
@ -534,14 +541,14 @@ class QUICConnection(IRawConnection, IMuxedConn):
# aioquic stores the peer certificate as cryptography
# x509.Certificate
self._peer_certificate = tls_context._peer_certificate
logger.debug(
print(
f"Extracted peer certificate: {self._peer_certificate.subject}"
)
else:
logger.debug("No peer certificate found in TLS context")
print("No peer certificate found in TLS context")
else:
logger.debug("No TLS context available for certificate extraction")
print("No TLS context available for certificate extraction")
except Exception as e:
logger.warning(f"Failed to extract peer certificate: {e}")
@ -554,12 +561,10 @@ class QUICConnection(IRawConnection, IMuxedConn):
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")
print("Found local certificate in configuration")
except Exception as inner_e:
logger.debug(
f"Alternative certificate extraction also failed: {inner_e}"
)
print(f"Alternative certificate extraction also failed: {inner_e}")
async def get_peer_certificate(self) -> x509.Certificate | None:
"""
@ -591,7 +596,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
subject = self._peer_certificate.subject
serial_number = self._peer_certificate.serial_number
logger.debug(
print(
f"Certificate validation - Subject: {subject}, Serial: {serial_number}"
)
return True
@ -716,7 +721,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._outbound_stream_count += 1
self._stats["streams_opened"] += 1
logger.debug(f"Opened outbound QUIC stream {stream_id}")
print(f"Opened outbound QUIC stream {stream_id}")
return stream
raise QUICStreamTimeoutError(f"Stream creation timed out after {timeout}s")
@ -749,7 +754,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
async with self._accept_queue_lock:
if self._stream_accept_queue:
stream = self._stream_accept_queue.pop(0)
logger.debug(f"Accepted inbound stream {stream.stream_id}")
print(f"Accepted inbound stream {stream.stream_id}")
return stream
if self._closed:
@ -777,7 +782,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
"""
self._stream_handler = handler_function
logger.debug("Set stream handler for incoming streams")
print("Set stream handler for incoming streams")
def _remove_stream(self, stream_id: int) -> None:
"""
@ -804,7 +809,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
if self._nursery:
self._nursery.start_soon(update_counts)
logger.debug(f"Removed stream {stream_id} from connection")
print(f"Removed stream {stream_id} from connection")
# *** UPDATED: Complete QUIC event handling - FIXES THE ORIGINAL ISSUE ***
@ -826,14 +831,14 @@ class QUICConnection(IRawConnection, IMuxedConn):
await self._handle_quic_event(event)
if events_processed > 0:
logger.debug(f"Processed {events_processed} QUIC events")
print(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."""
logger.debug(f"Handling QUIC event: {type(event).__name__}")
print(f"Handling QUIC event: {type(event).__name__}")
print(f"QUIC event: {type(event).__name__}")
try:
@ -860,7 +865,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
elif isinstance(event, events.StopSendingReceived):
await self._handle_stop_sending_received(event)
else:
logger.debug(f"Unhandled QUIC event type: {type(event).__name__}")
print(f"Unhandled QUIC event type: {type(event).__name__}")
print(f"Unhandled QUIC event: {type(event).__name__}")
except Exception as e:
@ -891,7 +896,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Update statistics
self._stats["connection_ids_issued"] += 1
logger.debug(f"Available connection IDs: {len(self._available_connection_ids)}")
print(f"Available connection IDs: {len(self._available_connection_ids)}")
print(f"Available connection IDs: {len(self._available_connection_ids)}")
async def _handle_connection_id_retired(
@ -932,7 +937,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
async def _handle_ping_acknowledged(self, event: events.PingAcknowledged) -> None:
"""Handle ping acknowledgment."""
logger.debug(f"Ping acknowledged: uid={event.uid}")
print(f"Ping acknowledged: uid={event.uid}")
async def _handle_protocol_negotiated(
self, event: events.ProtocolNegotiated
@ -944,7 +949,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self, event: events.StopSendingReceived
) -> None:
"""Handle stop sending request from peer."""
logger.debug(
print(
f"Stop sending received: stream_id={event.stream_id}, error_code={event.error_code}"
)
@ -960,7 +965,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self, event: events.HandshakeCompleted
) -> None:
"""Handle handshake completion with security integration."""
logger.debug("QUIC handshake completed")
print("QUIC handshake completed")
self._handshake_completed = True
# Store handshake event for security verification
@ -969,6 +974,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Try to extract certificate information after handshake
await self._extract_peer_certificate()
print("✅ Setting connected event")
self._connected_event.set()
async def _handle_connection_terminated(
@ -1100,7 +1106,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
except Exception as e:
logger.error(f"Error in stream handler for stream {stream_id}: {e}")
logger.debug(f"Created inbound stream {stream_id}")
print(f"Created inbound stream {stream_id}")
return stream
def _is_incoming_stream(self, stream_id: int) -> bool:
@ -1127,7 +1133,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
try:
stream = self._streams[stream_id]
await stream.handle_reset(event.error_code)
logger.debug(
print(
f"Handled reset for stream {stream_id}"
f"with error code {event.error_code}"
)
@ -1136,13 +1142,13 @@ class QUICConnection(IRawConnection, IMuxedConn):
# Force remove the stream
self._remove_stream(stream_id)
else:
logger.debug(f"Received reset for unknown stream {stream_id}")
print(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)."""
logger.debug(f"Datagram frame received: size={len(event.data)}")
print(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:
@ -1205,7 +1211,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
return
self._closed = True
logger.debug(f"Closing QUIC connection to {self._remote_peer_id}")
print(f"Closing QUIC connection to {self._remote_peer_id}")
try:
# Close all streams gracefully
@ -1247,7 +1253,7 @@ class QUICConnection(IRawConnection, IMuxedConn):
self._streams.clear()
self._closed_event.set()
logger.debug(f"QUIC connection to {self._remote_peer_id} closed")
print(f"QUIC connection to {self._remote_peer_id} closed")
except Exception as e:
logger.error(f"Error during connection close: {e}")
@ -1262,15 +1268,13 @@ class QUICConnection(IRawConnection, IMuxedConn):
try:
if self._transport:
await self._transport._cleanup_terminated_connection(self)
logger.debug("Notified transport of connection termination")
print("Notified transport of connection termination")
return
for listener in self._transport._listeners:
try:
await listener._remove_connection_by_object(self)
logger.debug(
"Found and notified listener of connection termination"
)
print("Found and notified listener of connection termination")
return
except Exception:
continue
@ -1294,12 +1298,12 @@ 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)
logger.debug(
print(
f"Removed connection {tracked_cid.hex()} by object reference"
)
return
logger.debug("Fallback cleanup by connection ID completed")
print("Fallback cleanup by connection ID completed")
except Exception as e:
logger.error(f"Error in fallback cleanup: {e}")

View File

@ -130,8 +130,6 @@ class QUICListener(IListener):
"invalid_packets": 0,
}
logger.debug("Initialized enhanced QUIC listener with connection ID support")
def _get_supported_versions(self) -> set[int]:
"""Get wire format versions for all supported QUIC configurations."""
versions: set[int] = set()
@ -274,87 +272,82 @@ class QUICListener(IListener):
return value, 8
async def _process_packet(self, data: bytes, addr: tuple[str, int]) -> None:
"""
Enhanced packet processing with better connection ID routing and debugging.
"""
"""Process incoming QUIC packet with fine-grained locking."""
try:
# self._stats["packets_processed"] += 1
# self._stats["bytes_received"] += len(data)
self._stats["packets_processed"] += 1
self._stats["bytes_received"] += len(data)
print(f"🔧 PACKET: Processing {len(data)} bytes from {addr}")
# Parse packet to extract connection information
# 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")
self._stats["invalid_packets"] += 1
return
dest_cid = packet_info.destination_cid
print(f"🔧 DEBUG: Packet info: {packet_info is not None}")
if packet_info:
print(f"🔧 DEBUG: Packet type: {packet_info.packet_type}")
print(
f"🔧 DEBUG: Is short header: {packet_info.packet_type == QuicPacketType.ONE_RTT}"
)
print(f"🔧 DEBUG: Packet type: {packet_info.packet_type}")
print(
f"🔧 DEBUG: Is short header: {packet_info.packet_type.name != 'INITIAL'}"
)
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()]}"
)
# 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:
if packet_info:
# 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: Parsed packet - version: 0x{packet_info.version:08x}, "
f"dest_cid: {packet_info.destination_cid.hex()}, "
f"src_cid: {packet_info.source_cid.hex()}"
f" PACKET: Routing to established connection {dest_cid.hex()}"
)
# Check for version negotiation
if packet_info.version == 0:
logger.warning(
f"Received version negotiation packet from {addr}"
)
return
# Check if version is supported
if packet_info.version not in self._supported_versions:
print(
f"❌ PACKET: Unsupported version 0x{packet_info.version:08x}"
)
await self._send_version_negotiation(
addr, packet_info.source_cid
)
return
# Route based on destination connection ID
dest_cid = packet_info.destination_cid
# First, try exact connection ID match
if dest_cid in self._connections:
print(
f"✅ PACKET: Routing to established connection {dest_cid.hex()}"
)
connection = self._connections[dest_cid]
await self._route_to_connection(connection, data, addr)
return
elif dest_cid in self._pending_connections:
print(
f"✅ PACKET: Routing to pending connection {dest_cid.hex()}"
)
quic_conn = self._pending_connections[dest_cid]
await self._handle_pending_connection(
quic_conn, data, addr, dest_cid
)
return
# No existing connection found, create new one
print(f"🔧 PACKET: Creating new connection for {addr}")
await self._handle_new_connection(data, addr, packet_info)
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()}")
else:
# Failed to parse packet
print(f"❌ PACKET: Failed to parse packet from {addr}")
await self._handle_short_header_packet(data, addr)
# 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}")
# 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
if connection_obj:
# Handle established connection
await self._handle_established_connection_packet(
connection_obj, data, addr, dest_cid
)
elif pending_quic_conn:
# Handle pending connection
await self._handle_pending_connection_packet(
pending_quic_conn, data, addr, dest_cid
)
except Exception as e:
logger.error(f"Error processing packet from {addr}: {e}")
@ -362,6 +355,66 @@ class QUICListener(IListener):
traceback.print_exc()
async def _handle_established_connection_packet(
self,
connection_obj: QUICConnection,
data: bytes,
addr: tuple[str, int],
dest_cid: bytes,
) -> None:
"""Handle packet for established connection WITHOUT holding connection lock."""
try:
print(f"🔧 ESTABLISHED: Handling packet for connection {dest_cid.hex()}")
# Forward packet to connection object
# This may trigger event processing and stream creation
await self._route_to_connection(connection_obj, data, addr)
except Exception as e:
logger.error(f"Error handling established connection packet: {e}")
async def _handle_pending_connection_packet(
self,
quic_conn: QuicConnection,
data: bytes,
addr: tuple[str, int],
dest_cid: bytes,
) -> 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}")
# Feed data to QUIC connection
quic_conn.receive_datagram(data, addr, now=time.time())
print("✅ PENDING: Datagram received by QUIC connection")
# 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
print("🔧 PENDING: Transmitting response...")
await self._transmit_for_connection(quic_conn, addr)
# Check if handshake completed (with minimal locking)
if (
hasattr(quic_conn, "_handshake_complete")
and quic_conn._handshake_complete
):
print("✅ PENDING: Handshake completed, promoting connection")
await self._promote_pending_connection(quic_conn, addr, dest_cid)
else:
print("🔧 PENDING: Handshake still in progress")
except Exception as e:
logger.error(f"Error handling pending connection {dest_cid.hex()}: {e}")
import traceback
traceback.print_exc()
async def _send_version_negotiation(
self, addr: tuple[str, int], source_cid: bytes
) -> None:
@ -784,6 +837,9 @@ class QUICListener(IListener):
# Forward to established connection if available
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):
@ -892,6 +948,7 @@ class QUICListener(IListener):
print(
f"🔄 PROMOTION: Using existing QUICConnection {id(connection)} for {dest_cid.hex()}"
)
else:
from .connection import QUICConnection
@ -924,7 +981,9 @@ class QUICListener(IListener):
# Rest of the existing promotion code...
if self._nursery:
connection._nursery = self._nursery
await connection.connect(self._nursery)
print("QUICListener: Connection connected succesfully")
if self._security_manager:
try:
@ -939,6 +998,11 @@ class QUICListener(IListener):
await connection.close()
return
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)
@ -946,6 +1010,14 @@ class QUICListener(IListener):
f"🔄 PROMOTION: Successfully added connection {id(connection)} to swarm"
)
if self._handler:
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}"

View File

@ -88,7 +88,7 @@ class QUICTransport(ITransport):
def __init__(
self, private_key: PrivateKey, config: QUICTransportConfig | None = None
):
) -> None:
"""
Initialize QUIC transport with security integration.
@ -119,7 +119,7 @@ class QUICTransport(ITransport):
self._nursery_manager = trio.CapacityLimiter(1)
self._background_nursery: trio.Nursery | None = None
self._swarm = None
self._swarm: Swarm | None = None
print(f"Initialized QUIC transport with security for peer {self._peer_id}")
@ -233,13 +233,19 @@ class QUICTransport(ITransport):
raise QUICSecurityError(f"Failed to apply TLS configuration: {e}") from e
# type: ignore
async def dial(self, maddr: multiaddr.Multiaddr, peer_id: ID) -> QUICConnection:
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.
Args:
maddr: Multiaddr of the remote peer (e.g., /ip4/1.2.3.4/udp/4001/quic-v1)
peer_id: Expected peer ID for verification
nursery: Nursery to execute the background tasks
Returns:
Raw connection interface to the remote peer
@ -278,7 +284,6 @@ class QUICTransport(ITransport):
# Create QUIC connection using aioquic's sans-IO core
native_quic_connection = NativeQUICConnection(configuration=config)
print("QUIC Connection Created")
# Create trio-based QUIC connection wrapper with security
connection = QUICConnection(
quic_connection=native_quic_connection,
@ -290,25 +295,22 @@ class QUICTransport(ITransport):
transport=self,
security_manager=self._security_manager,
)
print("QUIC Connection Created")
# Establish connection using trio
if self._background_nursery:
# Use swarm's long-lived nursery - background tasks persist!
await connection.connect(self._background_nursery)
print("Using background nursery for connection tasks")
else:
# Fallback to temporary nursery (with warning)
print(
"No background nursery available. Connection background tasks "
"may be cancelled when dial completes."
)
async with trio.open_nursery() as temp_nursery:
await connection.connect(temp_nursery)
active_nursery = nursery or self._background_nursery
if active_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)
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}"
self._connections[conn_id] = connection