aioquic - Man Page

Name

aioquic — aioquic  LicenseVersionPython versionsTestsCoverage

aioquic is a library for the QUIC network protocol in Python. It features several APIs:

Design

Sans-IO APIs

Both the QUIC and the HTTP/3 APIs follow the sans I/O pattern, leaving actual I/O operations to the API user. This approach has a number of advantages including making the code testable and allowing integration with different concurrency models.

TLS and encryption

TLS 1.3

aioquic features a minimal TLS 1.3 implementation built upon the cryptography library. This is because QUIC requires some APIs which are currently unavailable in mainstream TLS implementations such as OpenSSL:

  • the ability to extract traffic secrets
  • the ability to operate directly on TLS messages, without using the TLS record layer

Header protection and payload encryption

QUIC makes extensive use of cryptographic operations to protect QUIC packet headers and encrypt packet payloads. These operations occur for every single packet and are a determining factor for performance. For this reason, they are implemented as a C extension linked to OpenSSL.

Quic API

The QUIC API performs no I/O on its own, leaving this to the API user. This allows you to integrate QUIC in any Python application, regardless of the concurrency model you are using.

Connection

class aioquic.quic.connection.QuicConnection(*, configuration, original_destination_connection_id=None, retry_source_connection_id=None, session_ticket_fetcher=None, session_ticket_handler=None, token_handler=None)

A QUIC connection.

The state machine is driven by three kinds of sources:

  • the API user requesting data to be send out (see connect(), reset_stream(), send_ping(), send_datagram_frame() and send_stream_data())
  • data being received from the network (see receive_datagram())
  • a timer firing (see handle_timer())
Parameters

configuration (QuicConfiguration) -- The QUIC configuration to use.

change_connection_id()

Switch to the next available connection ID and retire the previous one.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Return type

None

close(error_code=QuicErrorCode.NO_ERROR, frame_type=None, reason_phrase='')

Close the connection.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • error_code (int) -- An error code indicating why the connection is being closed.
  • reason_phrase (str) -- A human-readable explanation of why the connection is being closed.
Return type

None

connect(addr, now)

Initiate the TLS handshake.

This method can only be called for clients and a single time.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • addr (Any) -- The network address of the remote peer.
  • now (float) -- The current time.
Return type

None

datagrams_to_send(now)

Return a list of (data, addr) tuples of datagrams which need to be sent, and the network address to which they need to be sent.

After calling this method call get_timer() to know when the next timer needs to be set.

Parameters

now (float) -- The current time.

Return type

List[Tuple[bytes, Any]]

get_next_available_stream_id(is_unidirectional=False)

Return the stream ID for the next stream created by this endpoint.

Return type

int

get_timer()

Return the time at which the timer should fire or None if no timer is needed.

Return type

Optional[float]

handle_timer(now)

Handle the timer.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters

now (float) -- The current time.

Return type

None

next_event()

Retrieve the next event from the event buffer.

Returns None if there are no buffered events.

Return type

Optional[QuicEvent]

receive_datagram(data, addr, now)

Handle an incoming datagram.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • data (bytes) -- The datagram which was received.
  • addr (Any) -- The network address from which the datagram was received.
  • now (float) -- The current time.
Return type

None

request_key_update()

Request an update of the encryption keys.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Return type

None

reset_stream(stream_id, error_code)

Abruptly terminate the sending part of a stream.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • stream_id (int) -- The stream's ID.
  • error_code (int) -- An error code indicating why the stream is being reset.
Return type

None

send_datagram_frame(data)

Send a DATAGRAM frame.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters

data (bytes) -- The data to be sent.

Return type

None

send_ping(uid)

Send a PING frame to the peer.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters

uid (int) -- A unique ID for this PING.

Return type

None

send_stream_data(stream_id, data, end_stream=False)

Send data on the specific stream.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • stream_id (int) -- The stream's ID.
  • data (bytes) -- The data to be sent.
  • end_stream (bool) -- If set to True, the FIN bit will be set.
Return type

None

stop_stream(stream_id, error_code)

Request termination of the receiving part of a stream.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • stream_id (int) -- The stream's ID.
  • error_code (int) -- An error code indicating why the stream is being stopped.
Return type

None

Configuration

class aioquic.quic.configuration.QuicConfiguration(alpn_protocols=None, congestion_control_algorithm='reno', connection_id_length=8, idle_timeout=60.0, is_client=True, max_data=1048576, max_datagram_size=1200, max_stream_data=1048576, quic_logger=None, secrets_log_file=None, server_name=None, session_ticket=None, token=b'', cadata=None, cafile=None, capath=None, certificate=None, certificate_chain=<factory>, cipher_suites=None, initial_rtt=0.1, max_datagram_frame_size=None, original_version=None, private_key=None, quantum_readiness_test=False, supported_versions=<factory>, verify_mode=None)

A QUIC configuration.

alpn_protocols: Optional[List[str]] = None

A list of supported ALPN protocols.

congestion_control_algorithm: str = 'reno'

The name of the congestion control algorithm to use.

Currently supported algorithms: "reno", `"cubic".

connection_id_length: int = 8

The length in bytes of local connection IDs.

idle_timeout: float = 60.0

The idle timeout in seconds.

The connection is terminated if nothing is received for the given duration.

is_client: bool = True

Whether this is the client side of the QUIC connection.

load_cert_chain(certfile, keyfile=None, password=None)

Load a private key and the corresponding certificate.

Return type

None

load_verify_locations(cafile=None, capath=None, cadata=None)

Load a set of "certification authority" (CA) certificates used to validate other peers' certificates.

Return type

None

max_data: int = 1048576

Connection-wide flow control limit.

max_datagram_size: int = 1200

The maximum QUIC payload size in bytes to send, excluding UDP or IP overhead.

max_stream_data: int = 1048576

Per-stream flow control limit.

quic_logger: Optional[QuicLogger] = None

The QuicLogger instance to log events to.

secrets_log_file: TextIO = None

A file-like object in which to log traffic secrets.

This is useful to analyze traffic captures with Wireshark.

server_name: Optional[str] = None

The server name to use when verifying the server's TLS certificate, which can either be a DNS name or an IP address.

If it is a DNS name, it is also sent during the TLS handshake in the Server Name Indication (SNI) extension.

NOTE:

This is only used by clients.

session_ticket: Optional[SessionTicket] = None

The TLS session ticket which should be used for session resumption.

token: bytes = b''

The address validation token that can be used to validate future connections.

NOTE:

This is only used by clients.

class aioquic.quic.logger.QuicLogger

A QUIC event logger which stores traces in memory.

to_dict()

Return the traces as a dictionary which can be written as JSON.

Return type

Dict[str, Any]

Events

class aioquic.quic.events.QuicEvent

Base class for QUIC events.

class aioquic.quic.events.ConnectionTerminated(error_code, frame_type, reason_phrase)

The ConnectionTerminated event is fired when the QUIC connection is terminated.

error_code: int

The error code which was specified when closing the connection.

frame_type: Optional[int]

The frame type which caused the connection to be closed, or None.

reason_phrase: str

The human-readable reason for which the connection was closed.

class aioquic.quic.events.HandshakeCompleted(alpn_protocol, early_data_accepted, session_resumed)

The HandshakeCompleted event is fired when the TLS handshake completes.

alpn_protocol: Optional[str]

The protocol which was negotiated using ALPN, or None.

early_data_accepted: bool

Whether early (0-RTT) data was accepted by the remote peer.

session_resumed: bool

Whether a TLS session was resumed.

class aioquic.quic.events.PingAcknowledged(uid)

The PingAcknowledged event is fired when a PING frame is acknowledged.

uid: int

The unique ID of the PING.

class aioquic.quic.events.StopSendingReceived(error_code, stream_id)

The StopSendingReceived event is fired when the remote peer requests stopping data transmission on a stream.

error_code: int

The error code that was sent from the peer.

stream_id: int

The ID of the stream that the peer requested stopping data transmission.

class aioquic.quic.events.StreamDataReceived(data, end_stream, stream_id)

The StreamDataReceived event is fired whenever data is received on a stream.

data: bytes

The data which was received.

end_stream: bool

Whether the STREAM frame had the FIN bit set.

stream_id: int

The ID of the stream the data was received for.

class aioquic.quic.events.StreamReset(error_code, stream_id)

The StreamReset event is fired when the remote peer resets a stream.

error_code: int

The error code that triggered the reset.

stream_id: int

The ID of the stream that was reset.

HTTP/3 API

The HTTP/3 API performs no I/O on its own, leaving this to the API user. This allows you to integrate HTTP/3 in any Python application, regardless of the concurrency model you are using.

Connection

class aioquic.h3.connection.H3Connection(quic, enable_webtransport=False)

A low-level HTTP/3 connection object.

Parameters

quic (QuicConnection) -- A QuicConnection instance.

create_webtransport_stream(session_id, is_unidirectional=False)

Create a WebTransport stream and return the stream ID.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • session_id (int) -- The WebTransport session identifier.
  • is_unidirectional (bool) -- Whether to create a unidirectional stream.
Return type

int

handle_event(event)

Handle a QUIC event and return a list of HTTP events.

Parameters

event (QuicEvent) -- The QUIC event to handle.

Return type

List[H3Event]

property received_settings: Dict[int, int] | None

Return the received SETTINGS frame, or None.

send_data(stream_id, data, end_stream)

Send data on the given stream.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • stream_id (int) -- The stream ID on which to send the data.
  • data (bytes) -- The data to send.
  • end_stream (bool) -- Whether to end the stream.
Return type

None

send_datagram(stream_id, data)

Send a datagram for the specified stream.

If the stream ID is not a client-initiated bidirectional stream, an InvalidStreamTypeError exception is raised.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • stream_id (int) -- The stream ID.
  • data (bytes) -- The HTTP/3 datagram payload.
Return type

None

send_headers(stream_id, headers, end_stream=False)

Send headers on the given stream.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • stream_id (int) -- The stream ID on which to send the headers.
  • headers (List[Tuple[bytes, bytes]]) -- The HTTP headers to send.
  • end_stream (bool) -- Whether to end the stream.
Return type

None

send_push_promise(stream_id, headers)

Send a push promise related to the specified stream.

Returns the stream ID on which headers and data can be sent.

If the stream ID is not a client-initiated bidirectional stream, an InvalidStreamTypeError exception is raised.

If there are not available push IDs, an NoAvailablePushIDError exception is raised.

NOTE:

After calling this method you need to call the QUIC connection datagrams_to_send() method to retrieve data which needs to be sent over the network. If you are using the asyncio API, calling the transmit() method will do it for you.

Parameters
  • stream_id (int) -- The stream ID on which to send the data.
  • headers (List[Tuple[bytes, bytes]]) -- The HTTP request headers for this push.
Return type

int

property sent_settings: Dict[int, int] | None

Return the sent SETTINGS frame, or None.

Events

class aioquic.h3.events.H3Event

Base class for HTTP/3 events.

class aioquic.h3.events.DatagramReceived(data, stream_id)

The DatagramReceived is fired whenever a datagram is received from the the remote peer.

data: bytes

The data which was received.

stream_id: int

The ID of the stream the data was received for.

class aioquic.h3.events.DataReceived(data, stream_id, stream_ended, push_id=None)

The DataReceived event is fired whenever data is received on a stream from the remote peer.

data: bytes

The data which was received.

push_id: Optional[int] = None

The Push ID or None if this is not a push.

stream_ended: bool

Whether the STREAM frame had the FIN bit set.

stream_id: int

The ID of the stream the data was received for.

class aioquic.h3.events.HeadersReceived(headers, stream_id, stream_ended, push_id=None)

The HeadersReceived event is fired whenever headers are received.

headers: List[Tuple[bytes, bytes]]

The headers.

push_id: Optional[int] = None

The Push ID or None if this is not a push.

stream_ended: bool

Whether the STREAM frame had the FIN bit set.

stream_id: int

The ID of the stream the headers were received for.

class aioquic.h3.events.PushPromiseReceived(headers, push_id, stream_id)

The PushedStreamReceived event is fired whenever a pushed stream has been received from the remote peer.

headers: List[Tuple[bytes, bytes]]

The request headers.

push_id: int

The Push ID of the push promise.

stream_id: int

The Stream ID of the stream that the push is related to.

class aioquic.h3.events.WebTransportStreamDataReceived(data, stream_id, stream_ended, session_id)

The WebTransportStreamDataReceived is fired whenever data is received for a WebTransport stream.

data: bytes

The data which was received.

session_id: int

The ID of the session the data was received for.

stream_ended: bool

Whether the STREAM frame had the FIN bit set.

stream_id: int

The ID of the stream the data was received for.

Exceptions

class aioquic.h3.exceptions.H3Error

Base class for HTTP/3 exceptions.

class aioquic.h3.exceptions.InvalidStreamTypeError

An action was attempted on an invalid stream type.

class aioquic.h3.exceptions.NoAvailablePushIDError

There are no available push IDs left, or push is not supported by the remote party.

Asyncio API

The asyncio API provides a high-level Quic API built on top of asyncio, Python's standard asynchronous I/O framework.

aioquic comes with a selection of examples, including:

The examples can be browsed on GitHub:

https://github.com/aiortc/aioquic/tree/main/examples

Client

async with aioquic.asyncio.connect(host, port, *, configuration=None, create_protocol=<class 'aioquic.asyncio.protocol.QuicConnectionProtocol'>, session_ticket_handler=None, stream_handler=None, token_handler=None, wait_connected=True, local_port=0)

Connect to a QUIC server at the given host and port.

connect() returns an awaitable. Awaiting it yields a QuicConnectionProtocol which can be used to create streams.

connect() also accepts the following optional arguments: :rtype: AsyncGenerator[QuicConnectionProtocol, None]

  • configuration is a QuicConfiguration configuration object.
  • create_protocol allows customizing the Protocol that manages the connection. It should be a callable or class accepting the same arguments as QuicConnectionProtocol and returning an instance of QuicConnectionProtocol or a subclass.
  • session_ticket_handler is a callback which is invoked by the TLS engine when a new session ticket is received.
  • stream_handler is a callback which is invoked whenever a stream is created. It must accept two arguments: a asyncio.StreamReader and a asyncio.StreamWriter.
  • wait_connected indicates whether the context manager should wait for the connection to be established before yielding the QuicConnectionProtocol. By default this is True but you can set it to False if you want to immediately start sending data using 0-RTT.
  • local_port is the UDP port number that this client wants to bind.

Server

await aioquic.asyncio.serve(host, port, *, configuration, create_protocol=<class 'aioquic.asyncio.protocol.QuicConnectionProtocol'>, session_ticket_fetcher=None, session_ticket_handler=None, retry=False, stream_handler=None)

Start a QUIC server at the given host and port.

serve() requires a QuicConfiguration containing TLS certificate and private key as the configuration argument.

serve() also accepts the following optional arguments: :rtype: QuicServer

  • create_protocol allows customizing the Protocol that manages the connection. It should be a callable or class accepting the same arguments as QuicConnectionProtocol and returning an instance of QuicConnectionProtocol or a subclass.
  • session_ticket_fetcher is a callback which is invoked by the TLS engine when a session ticket is presented by the peer. It should return the session ticket with the specified ID or None if it is not found.
  • session_ticket_handler is a callback which is invoked by the TLS engine when a new session ticket is issued. It should store the session ticket for future lookup.
  • retry specifies whether client addresses should be validated prior to the cryptographic handshake using a retry packet.
  • stream_handler is a callback which is invoked whenever a stream is created. It must accept two arguments: a asyncio.StreamReader and a asyncio.StreamWriter.

Common

class aioquic.asyncio.QuicConnectionProtocol(quic, stream_handler=None)
change_connection_id()

Change the connection ID used to communicate with the peer.

The previous connection ID will be retired.

Return type

None

close(error_code=QuicErrorCode.NO_ERROR, reason_phrase='')

Close the connection.

Parameters
  • error_code (int) -- An error code indicating why the connection is being closed.
  • reason_phrase (str) -- A human-readable explanation of why the connection is being closed.
Return type

None

connect(addr, transmit=True)

Initiate the TLS handshake.

This method can only be called for clients and a single time.

Return type

None

await create_stream(is_unidirectional=False)

Create a QUIC stream and return a pair of (reader, writer) objects.

The returned reader and writer objects are instances of asyncio.StreamReader and asyncio.StreamWriter classes.

Return type

Tuple[StreamReader, StreamWriter]

await ping()

Ping the peer and wait for the response.

Return type

None

quic_event_received(event)

Called when a QUIC event is received.

Reimplement this in your subclass to handle the events.

Return type

None

request_key_update()

Request an update of the encryption keys.

Return type

None

transmit()

Send pending datagrams to the peer and arm the timer if needed.

This method is called automatically when data is received from the peer or when a timer goes off. If you interact directly with the underlying QuicConnection, make sure you call this method whenever data needs to be sent out to the network.

Return type

None

await wait_closed()

Wait for the connection to be closed.

Return type

None

await wait_connected()

Wait for the TLS handshake to complete.

Return type

None

Changelog

1.2.0

  • Add support for compatible version handling as defined in RFC 9368.
  • Add support for QUIC Version 2, as defined in RFC 9369.
  • Drop support for draft QUIC versions which were obsoleted by RFC 9000.
  • Improve datagram padding to allow better packet coalescing and reduce the number of roundtrips during connection establishement.
  • Fix server anti-amplification checks during address validation to take into account invalid packets, such as datagram-level padding.
  • Allow asyncio clients to make efficient use of 0-RTT by passing wait_connected=False to connect().
  • Add command-line arguments to the http3_client example for client certificates and negotiating QUIC Version 2.

1.1.0

  • Improve path challenge handling and compliance with RFC 9000.
  • Limit the amount of buffered CRYPTO data to avoid memory exhaustion.
  • Enable SHA-384 based signature algorithms and SECP384R1 key exchange.
  • Build binary wheels against OpenSSL 3.3.0.

1.0.0

  • Ensure no data is sent after a stream reset.
  • Make H3Connection's send_datagram() and send_push_promise() methods raise an InvalidStreamTypeError exception if an invalid stream ID is specified.
  • Improve the documentation for QuicConnectionProtocol's transmit() method.
  • Fix utcnow() deprecation warning on Python 3.12 by using cryptography 42.0 and timezone-aware datetime instances when validating TLS certificates.
  • Build binary wheels against OpenSSL 3.2.0.
  • Ignore any non-ASCII ALPN values received.
  • Perform more extensive HTTP/3 header validation in H3Connection.
  • Fix exceptions when draining stream writers in the asyncio API.
  • Set the QuicConnection idle timer according to RFC 9000 section 10.1.
  • Implement fairer stream scheduling in QuicConnection to avoid head-of-line blocking.
  • Only load certifi root certificates if none was specified in the QuicConfiguration.
  • Improve padding of UDP datagrams containing Initial packets to comply with RFC 9000 section 14.1.
  • Limit the number of pending connection IDs marked for retirement to prevent a possible DoS attack.

License

Copyright (c) Jeremy Lainé.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name of aioquic nor the names of its contributors may
      be used to endorse or promote products derived from this software without
      specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Author

Jeremy Lainé

Info

Nov 22, 2024