Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0-RTT early data not included in initial datagram #492

Open
MWedl opened this issue Apr 13, 2024 · 1 comment
Open

0-RTT early data not included in initial datagram #492

MWedl opened this issue Apr 13, 2024 · 1 comment

Comments

@MWedl
Copy link

MWedl commented Apr 13, 2024

First of all, thanks for developing and maintaining this amazing library.

Description

I am trying use the full potential of HTTP/3 and QUIC by utilizing 0-RTT handshakes and inculde early data (HTTP/3 requests) in the 0-RTT handshakes. aioquic sends the Initial packet and 0-RTT packet as two separate UDP datagrams. This means that early data requests are not included in the transmitted datagram.

The problem seems to be how aioquic pads initial packets. aioquic adds PADDING frames to the initial packet. This fills the UDP datagram leaving no space for the 0-RTT packet anymore, which results in the 0-RTT packet being sent in the next datagram.

Wireshark Screenshot

In order to solve this issue, aioquic should add padding to UDP datagrams instead of QUIC packets. UDP datagrams can be padded by adding zero bytes after all QUIC packets inside a UDP datagram. This UDP datagram padding works for datagrams containing only packets with long headers, because the long header features a length field. This UDP padding is used by other QUIC clients such as Firefox.

Reproducable example

import asyncio
from pathlib import Path
import ssl
from urllib.parse import urlparse

from aioquic.asyncio.client import connect
from aioquic.h3.connection import H3_ALPN
from aioquic.quic.configuration import QuicConfiguration

from example_aioquic_http3_client import HttpClient  # examples/http3_client.py


class HttpClient0Rtt(HttpClient):
    def connect(self, addr) -> None:
        self._quic.connect(addr, now=self._loop.time())
        # Do not transmit handshake packets yet. We might want to add a 0-RTT request first.
        # self.transmit()

    async def wait_connected(self) -> None:
        """
        Wait for the TLS handshake to complete.
        """
        assert self._connected_waiter is None, "already awaiting connected"
        if not self._connected:
            self._connected_waiter = self._loop.create_future()

            # Transmit handshake packets
            self.transmit()

            await asyncio.shield(self._connected_waiter)


async def get_session_ticket(url):    
    # Connect to server and store session ticket for 0-RTT
    session_tickets = []
    async with connect(
            host=url.hostname,
            port=url.port or 443,
            configuration=QuicConfiguration(
                is_client=True,
                alpn_protocols=H3_ALPN,
                verify_mode=ssl.CERT_NONE,
                secrets_log_file=open(Path(__file__).parent.parent / 'quic_secrets.log', 'a'),
                server_name=url.hostname,
            ),
            create_protocol=HttpClient0Rtt,
            session_ticket_handler=lambda t: session_tickets.append(t),
            wait_connected=True
    ) as client:
        await client.get(url.geturl())

    return session_tickets[-1]


async def main() -> None:
    url = urlparse('https://127.0.0.1:4431/')
    session_ticket = await get_session_ticket(url)

    # Try to send a HTTP/3 request in 0-RTT early data
    async with connect(
            host=url.hostname,
            port=url.port or 443,
            configuration=QuicConfiguration(
                is_client=True,
                alpn_protocols=H3_ALPN,
                verify_mode=ssl.CERT_NONE,
                server_name=url.hostname,
                session_ticket=session_ticket,
            ),
            create_protocol=HttpClient0Rtt,
            wait_connected=False
    ) as client:
        await client.get(url.geturl())


if __name__ == "__main__":
    asyncio.run(main())
@rthalley
Copy link
Contributor

We are planning work on the padding system that should address this and some other issues. No timeline at the moment though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants