{"record":{"id":"5dae9cb6062e7a54","repo":"AlexxIT/go2rtc","slug":"multitrans-auth-failed-res-status","errorCode":null,"errorMessage":"multitrans: auth failed: ${res.Status}","messagePattern":"multitrans: auth failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/multitrans/client.go","lineNumber":152,"sourceCode":"\tresponse := tcp.HexMD5(ha1, nonce, ha2)\n\n\tauthHeader := fmt.Sprintf(`Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"`,\n\t\tuser, realm, nonce, uri, response)\n\n\tdata = fmt.Sprintf(\"MULTITRANS %s RTSP/1.0\\r\\nCSeq: 1\\r\\nAuthorization: %s\\r\\nX-Client-UUID: %s\\r\\n\\r\\n\",\n\t\turi, authHeader, uid)\n\n\tif _, err = c.conn.Write([]byte(data)); err != nil {\n\t\treturn err\n\t}\n\n\tres, err = tcp.ReadResponse(c.rd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif res.StatusCode != http.StatusOK {\n\t\treturn errors.New(\"multitrans: auth failed: \" + res.Status)\n\t}\n\n\t// Session: 7116520596809429228\n\tsession := res.Header.Get(\"Session\")\n\tif session == \"\" {\n\t\treturn errors.New(\"multitrans: no session\")\n\t}\n\n\treturn c.openTalkChannel(uri, session)\n}\n\nfunc (c *Client) openTalkChannel(uri, session string) error {\n\tpayload := `{\"type\":\"request\",\"seq\":0,\"params\":{\"method\":\"get\",\"talk\":{\"mode\":\"full_duplex\"}}}`\n\n\tdata := fmt.Sprintf(\"MULTITRANS %s RTSP/1.0\\r\\nCSeq: 2\\r\\nSession: %s\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\turi, session, len(payload), payload)\n\n\tif _, err := c.conn.Write([]byte(data)); err != nil {","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/multitrans/client.go#L134-L170","documentation":"In the second leg of the multitrans digest handshake, after the client sends the Authorization header, the server must reply 200 OK with a Session header. Any other status means authentication was rejected or the challenge failed, so the handshake aborts with the server's status line embedded in the message.","triggerScenarios":"Calling Dial; the digest Authorization response sent in handshake gets a non-200 status (typically 401 again after the computed digest response is wrong).","commonSituations":"Wrong username/password in client config; realm/nonce mismatch caused by clock skew or retries; device account locked; firmware expecting a different digest algorithm (MD5 vs SHA-256).","solutions":["Verify the credentials (username/password) configured on the multitrans client match the device account.","Check the device supports the digest algorithm the client uses; update firmware or client if it requires SHA-256.","Re-attempt Dial in case of a stale nonce (nonce counts/nc issues); avoid sharing one device session between processes.","Check device logs for auth rejection reasons (locked account, IP filtering)."],"exampleFix":"// before\nclient := multitrans.New(host, \"admin\", \"wrongpass\")\n\n// after\nclient := multitrans.New(host, \"admin\", os.Getenv(\"CAMERA_PASSWORD\"))","handlingStrategy":"retry","validationCode":"// verify credentials with a plain digest probe before Dial\nreq, _ := http.NewRequest(\"GET\", deviceURL, nil)\nresp, err := httpClient.Do(req) // then perform digest round-trip manually\nif err != nil || resp.StatusCode == http.StatusUnauthorized {\n    return errors.New(\"credentials rejected by device\")\n}","typeGuard":null,"tryCatchPattern":"var lastErr error\nfor i := 0; i < 3; i++ {\n    if err := client.Dial(ctx); err == nil { break }\n    else if strings.Contains(err.Error(), \"auth failed\") {\n        lastErr = err\n        time.Sleep(500 * time.Millisecond) // retry in case of stale nonce\n        continue\n    }\n    return err\n}","preventionTips":["Store device passwords in a secret manager, not hardcoded config.","Test credentials against the device web UI when integrating.","Avoid sharing one camera account across many concurrent clients.","Check firmware digest algorithm support (MD5 vs SHA-256) up front."],"tags":["http","authentication","digest-auth","handshake"],"backgroundTag":"authentication-required","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}