cloudflare/quiche · error

datagram extension not enabled by peer

Error message

datagram extension not enabled by peer

What it means

h3i's execute_action sends a QUIC datagram via conn.dgram_send() and panics with this message if the call returns an error. The QUIC datagram extension (RFC 9221) must be negotiated by both peers during the handshake; if the server did not advertise the DATAGRAM extension (or its max_datagram_frame_size is 0), sending fails and this expect() turns that into a panic.

Solutions

  1. Enable datagram support on the server (advertise max_datagram_frame_size, e.g. quiche Config.enable_dgram(true,..)).
  2. Remove the SendDatagram action from the h3i action file, or gate it on the peer's negotiated extensions.
  3. Check conn.dgram_send() result in h3i and surface a graceful error instead of panicking.

Example fix

// before (h3i action file)
{"action":"send_datagram","payload":"..."}
// after
{"action":"send_frame","stream_id":0,"frame":{"type":"settings",...}} // or enable datagrams on the server first
Defensive patterns

Strategy: validation

Validate before calling

if !conn.dgram_max_writable_len().map_or(false, |l| l > 0) {
    eprintln!("peer did not negotiate datagram extension; skipping SendDatagram");
    return Ok(());
}

Try / catch

// replace expect with error propagation
conn.dgram_send(payload).unwrap_or_else(|e| eprintln!("dgram_send failed: {e}"));

Prevention

When it happens

Trigger: Running an h3i action file containing a SendDatagram action against a server that did not negotiate the datagram extension, or before the handshake completed.

Common situations: Pointing h3i at a server without QUIC datagram support enabled; missing --enable-datagram / max_datagram_frame_size config on the peer; testing MASQUE/CONNECT-UDP against a plain HTTP/3 server.

Related errors


AI-assisted analysis of cloudflare/quiche@9f96daa2c2 (2026-09-08). Data as JSON: /api/errors/f29b799dfb4ed83f. Report an issue: GitHub.

Appendix: source

Thrown at h3i/src/client/mod.rs:303

            log::info!(
                "stream bytes tx id={} len={} fin={}",
                stream_id,
                bytes.len(),
                fin_stream
            );
            let result = conn.stream_send(*stream_id, bytes, *fin_stream);
            validate_stream_send_result(result, expected_result, *stream_id);

            stream_parsers
                .entry(*stream_id)
                .or_insert_with(|| FrameParser::new(*stream_id));
        },

        Action::SendDatagram { payload } => {
            log::info!("dgram tx len={}", payload.len(),);

            conn.dgram_send(payload)
                .expect("datagram extension not enabled by peer");
        },

        Action::ResetStream {
            stream_id,
            error_code,
        } => {
            log::info!(
                "reset_stream stream_id={stream_id} error_code={error_code}"
            );
            if let Err(e) = conn.stream_shutdown(
                *stream_id,
                quiche::Shutdown::Write,
                *error_code,
            ) {
                log::error!("can't send reset_stream: {e}");
                // Clients cannot reset streams they do not own. If attempted,
                // `stream_shutdown()` fails and no parser should be created.
                return;

View on GitHub (pinned to 9f96daa2c2)