n0-computer/iroh · error · SendError

ExceedsMaxPacketSize

ExceedsMaxPacketSize

Error message

Exceeds max packet size ({MAX_PACKET_SIZE}): {size}

What it means

This SendError variant is raised by the relay client connection's Sink::start_send when a ClientToRelayMsg serializes to more than MAX_PACKET_SIZE bytes. The relay protocol enforces a fixed per-packet size limit, so oversized frames are rejected client-side before being written to the connection. It guards against wasted bandwidth and relay-side rejection.

Solutions

  1. Check frame.encoded_len() against MAX_PACKET_SIZE before sending and split or truncate the payload into multiple datagram batches.
  2. Reduce per-datagram payload size so the total encoded frame stays under MAX_PACKET_SIZE.
  3. If large transfers are needed, move the data over a direct connection and use the relay only for hole-punching/control messages.

Example fix

// before
let msg = ClientToRelayMsg::Datagrams { .. }; // many datagrams batched
client.send(msg).await?;

// after
if msg.encoded_len() > MAX_PACKET_SIZE {
    for chunk in split_into_batches(datagrams, MAX_PACKET_SIZE) {
        client.send(ClientToRelayMsg::Datagrams { datagrams: chunk, .. }).await?;
    }
} else {
    client.send(msg).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

fn fits_in_packet(msg: &ClientToRelayMsg) -> bool {
    msg.encoded_len() <= MAX_PACKET_SIZE
}

Prevention

When it happens

Trigger: Calling send on a RelayClient connection with a Datagrams message whose accumulated payload encodes to more than MAX_PACKET_SIZE bytes; batching too many/large datagrams into a single ClientToRelayMsg::Datagrams frame.

Common situations: Applications stuffing large payloads (e.g. big messages or file chunks) into relay datagrams, or batching code that keeps appending datagrams without checking cumulative encoded size before flushing.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08). Data as JSON: /api/errors/f05f2552e66d6c4a. Report an issue: GitHub.

Appendix: source

Thrown at iroh-relay/src/client/conn.rs:149

                    RelayToClientMsg::from_bytes(msg, &self.key_cache, self.protocol_version);
                Poll::Ready(Some(message.map_err(Into::into)))
            }
            Some(Err(e)) => Poll::Ready(Some(Err(anyerr!(e).into()))),
            None => Poll::Ready(None),
        }
    }
}

impl Sink<ClientToRelayMsg> for Conn {
    type Error = SendError;

    fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Pin::new(&mut self.conn).poll_ready(cx).map_err(Into::into)
    }

    fn start_send(mut self: Pin<&mut Self>, frame: ClientToRelayMsg) -> Result<(), Self::Error> {
        let size = frame.encoded_len();
        ensure!(
            size <= MAX_PACKET_SIZE,
            SendError::ExceedsMaxPacketSize { size }
        );
        if let ClientToRelayMsg::Datagrams { datagrams, .. } = &frame {
            ensure!(!datagrams.contents.is_empty(), SendError::EmptyPacket);
        }

        Pin::new(&mut self.conn)
            .start_send(frame.to_bytes().freeze())
            .map_err(Into::into)
    }

    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Pin::new(&mut self.conn).poll_flush(cx).map_err(Into::into)
    }

    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Pin::new(&mut self.conn).poll_close(cx).map_err(Into::into)

View on GitHub (pinned to 2b4de030ce)