Skip to content

QUIC / HTTP3 breaks on large volume of requests to different hosts #8043

@bedros-p

Description

@bedros-p

Problem Description

A large volume of QUIC requests to multiple hosts leads to lots of requests failing.

When transparently intercepting process traffic with mitmproxy, requests error out with "stream already ended" (compared to consistently flawless execution otherwise). Error is thrown from the following lines.

elif isinstance(event, QuicStreamDataReceived):
# Discard contents if we have already sent STOP_SENDING on this stream.
if event.stream_id in self._closed_streams:
return []
elif self._get_or_create_stream(event.stream_id).ended:
# aioquic will not send us any data events once a stream has ended.
# Instead, it will close the connection. We simulate this here for H3 tests.
self.close_connection(
error_code=QuicErrorCode.PROTOCOL_VIOLATION,
reason_phrase="stream already ended",
)
return []
else:
return self.handle_event(
StreamDataReceived(event.data, event.end_stream, event.stream_id)
)

My guess is this is due to the large volume of QUERY requests / DNS queries - by the time the proxy starts processing the HTTP3 requests, the server has transmitted the proper data and closed the connection. I've made a minimal repo for reproduction.

Steps to reproduce the behavior:

  1. I have created a repo that sends ~300 requests to multiple hosts (Googleapis, and each is for a document. This won't cause any disruption, it's pretty static. And they're a big company) for reproduction purposes in https://github.com/bedros-p/quicmess - it is easy to review the source, as there isn't much to it.
  2. go build creates a binary quicmess. Run with quicmess multidomain.txt and confirm that there aren't any errors. This should take ~30 seconds and produce "All requests completed" at the end.
  3. mitmproxy -> Local Applications -> Intercept traffic for process quicmess
  4. Run the binary with ./quicmess multidomain.txt, see the errors stream already ended
  5. Run the binary with ./quicmess samedomain.txt, see the lack of errors.

Here is what it looks like, with the highlighting indicating the moment I start intercepting it.
Terminal showcasing the program running with a wordlist of multiple domains, twice, with no errors. After a highlighted line, the program is run again, but with an error this time. The highlighted line indicates when the intercepting began. The program is run again after that error, but this time all the requests are sent to the same domain, and there are no errors.

Here's mitmweb. After all the QUERYs, some make it through, then a couple don't. I couldn't find any patterns with the timings or size. Couldn't find any patterns to link with the entry preceding each error either.

Image

System Information

Mitmproxy: 12.2.1 binary
Python:    3.14.0
OpenSSL:   OpenSSL 3.5.4 30 Sep 2025
Platform:  Linux-6.8.0-90-generic-x86_64-with-glibc2.35

Checklist

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions