AlexxIT/go2rtc · error

multitrans: expected 401, got

Error message

multitrans: expected 401, got ${res.Status}

What it means

The multitrans client's handshake expects the camera/device to respond to the initial request with HTTP 401 Unauthorized containing a WWW-Authenticate digest challenge. Any other status code means the device did not start a digest-auth challenge sequence, so the handshake aborts. This is a protocol-expectation check, not a plain HTTP failure.

Solutions

  1. Verify the target host/port actually runs the multitrans (digest-challenge) service; test the URL in a browser and confirm a 401 with WWW-Authenticate header.
  2. Check the device settings to ensure HTTP/anycast talk service is enabled and authentication is required.
  3. Remove or bypass proxies/firewalls that could rewrite or block the 401 challenge response.
  4. Confirm firmware version matches the protocol this client implements (some firmwares skip the 401 challenge).

Example fix

// before
c, err := multitrans.Dial(ctx, "http://192.168.1.50:80")

// after
// ensure correct port/path for the multitrans endpoint
c, err := multitrans.Dial(ctx, "http://192.168.1.50:8595")
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: confirm the endpoint issues a digest challenge
resp, err := http.Get(deviceURL)
if err != nil || resp.StatusCode != http.StatusUnauthorized ||
    !strings.Contains(resp.Header.Get("WWW-Authenticate"), "realm=") {
    return errors.New("endpoint does not provide multitrans digest challenge")
}

Try / catch

if err := client.Dial(ctx); err != nil {
    var e *string
    if errors.As(err, &e) && strings.Contains(err.Error(), "expected 401") {
        // wrong device/port or firmware without challenge; surface config advice
        return fmt.Errorf("device did not start digest handshake: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling multitrans client.Dial; during handshake, the first tcp.ReadResponse returns a status other than 401 (e.g. 200, 403, 404).

Common situations: Pointing the client at a non-multitrans device or wrong port so a different HTTP server answers; device firmware that does not require auth; a proxy returning 403/502; device web UI disabled.

Related errors


AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07). Data as JSON: /api/errors/bae0ef99e438b171. Report an issue: GitHub.

Appendix: source

Thrown at pkg/multitrans/client.go:121

func (c *Client) handshake(u *url.URL) error {
	// Step 1: Get Challenge
	uid := uuid.New().String()

	uri := fmt.Sprintf("rtsp://%s/multitrans", u.Host)
	data := fmt.Sprintf("MULTITRANS %s RTSP/1.0\r\nCSeq: 0\r\nX-Client-UUID: %s\r\n\r\n", uri, uid)

	if _, err := c.conn.Write([]byte(data)); err != nil {
		return err
	}

	res, err := tcp.ReadResponse(c.rd)
	if err != nil {
		return err
	}

	if res.StatusCode != http.StatusUnauthorized {
		return errors.New("multitrans: expected 401, got " + res.Status)
	}

	auth := res.Header.Get("WWW-Authenticate")
	realm := tcp.Between(auth, `realm="`, `"`)
	nonce := tcp.Between(auth, `nonce="`, `"`)

	// Step 2: Send Auth
	user := u.User.Username()
	pass, _ := u.User.Password()

	ha1 := tcp.HexMD5(user, realm, pass)
	ha2 := tcp.HexMD5("MULTITRANS", uri)
	response := tcp.HexMD5(ha1, nonce, ha2)

	authHeader := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", response="%s"`,
		user, realm, nonce, uri, response)

	data = fmt.Sprintf("MULTITRANS %s RTSP/1.0\r\nCSeq: 1\r\nAuthorization: %s\r\nX-Client-UUID: %s\r\n\r\n",

View on GitHub (pinned to c245815e75)