n0-computer/iroh · error · RelayUpgradeReqError

InvalidHeader

InvalidHeader

Error message

invalid header value for {header}: {details}

What it means

During a relay websocket upgrade, a required HTTP header was present but its value was invalid. The server validates upgrade headers (e.g. the Upgrade header must equal "websocket") and returns RelayUpgradeReqError::InvalidHeader when the value does not match the expected static value.

Solutions

  1. Send Upgrade: websocket (exactly, case per HeaderValue::from_static comparison) on the websocket handshake request.
  2. Check intermediate proxies/load balancers for header rewriting; disable websocket header normalization or pass headers through untouched.
  3. Use a standard websocket client library to perform the handshake instead of a hand-rolled HTTP request.
  4. Ensure HTTP/1.1 is used for the upgrade; HTTP/2 requires extended CONNECT, which this endpoint does not accept.

Example fix

// before: custom request with wrong upgrade value
let req = Request::builder()
    .header("Upgrade", "WebSocket/1.0")
    .header("Connection", "keep-alive");
// after: standard websocket upgrade headers
let req = Request::builder()
    .header("Upgrade", "websocket")
    .header("Connection", "Upgrade")
    .header("Sec-WebSocket-Key", base64_key)
    .header("Sec-WebSocket-Version", "13");
Defensive patterns

Strategy: validation

Validate before calling

const REQUIRED_UPGRADE: &str = "websocket";
fn upgrade_header_ok(req: &http::Request<impl _>) -> bool {
    req.headers()
        .get(http::header::UPGRADE)
        .and_then(|v| v.to_str().ok())
        .map(|v| v.eq_ignore_ascii_case(REQUIRED_UPGRADE))
        .unwrap_or(false)
}

Try / catch

match handle_relay_ws_upgrade(req, tunnel_service).await {
    Err(RelayUpgradeReqError::InvalidHeader { header, details }) => {
        tracing::debug!("bad {header:?} in upgrade: {details}");
        Response::builder().status(400).body("invalid upgrade headers")
    }
    other => other,
}

Prevention

When it happens

Trigger: handle_relay_ws_upgrade checks a request header (here UPGRADE) whose value is not the required constant — e.g. Upgrade: h2c, HTTP/2.0, or a modified value, instead of "websocket".

Common situations: Proxies or gateways rewriting/dropping the Upgrade header, custom HTTP clients not setting the websocket upgrade correctly, using HTTP/2 (where the classic Upgrade handshake doesn't apply), or missing the Connection: Upgrade header pairing.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at iroh-relay/src/server/http_server.rs:587

        res
    }

    /// Upgrades the HTTP connection to the relay protocol, runs relay client.
    fn handle_relay_ws_upgrade(
        &self,
        mut req: Request<Incoming>,
    ) -> Result<Response<BytesBody>, RelayUpgradeReqError> {
        fn expect_header(
            req: &Request<Incoming>,
            header: http::HeaderName,
        ) -> Result<&HeaderValue, RelayUpgradeReqError> {
            req.headers()
                .get(&header)
                .ok_or_else(|| e!(RelayUpgradeReqError::MissingHeader { header }))
        }

        let upgrade_header = expect_header(&req, UPGRADE)?;
        ensure!(
            upgrade_header == HeaderValue::from_static(WEBSOCKET_UPGRADE_PROTOCOL),
            RelayUpgradeReqError::InvalidHeader {
                header: UPGRADE,
                details: format!("value must be {WEBSOCKET_UPGRADE_PROTOCOL}")
            }
        );

        let key = expect_header(&req, SEC_WEBSOCKET_KEY)?.clone();
        let version = expect_header(&req, SEC_WEBSOCKET_VERSION)?.clone();

        ensure!(
            version.as_bytes() == SUPPORTED_WEBSOCKET_VERSION.as_bytes(),
            RelayUpgradeReqError::UnsupportedWebsocketVersion
        );

        let subprotocols = expect_header(&req, SEC_WEBSOCKET_PROTOCOL)?
            .to_str()
            .ok()

View on GitHub (pinned to 2b4de030ce)