t8y2/dbx · error

ZooKeeper DIGEST-MD5 negotiation exceeded %d rounds

Error message

ZooKeeper DIGEST-MD5 negotiation exceeded %d rounds

What it means

The DIGEST-MD5 handshake is bounded by zooKeeperSASLMaxRounds rounds. If the loop completes all rounds without saslClient.Complete() returning true, the negotiation is deemed stuck and this error is returned. It guards against infinite or divergent challenge/response loops with a misbehaving peer.

Source

Thrown at agents/drivers/zookeeper/sasl.go:116

		}
		if saslClient.Complete() {
			if len(challenge) != 0 {
				return errors.New("ZooKeeper sent an unexpected token after DIGEST-MD5 completion")
			}
			return nil
		}
		token, err = saslClient.Step(challenge)
		if err != nil {
			return fmt.Errorf("continue ZooKeeper DIGEST-MD5 negotiation at round %d: %w", round+1, err)
		}
		if saslClient.Complete() {
			if len(token) != 0 {
				return errors.New("ZooKeeper DIGEST-MD5 completed with an unexpected client token")
			}
			return nil
		}
	}
	return fmt.Errorf("ZooKeeper DIGEST-MD5 negotiation exceeded %d rounds", zooKeeperSASLMaxRounds)
}

func zooKeeperSASLRound(connection net.Conn, xid int32, token []byte) ([]byte, error) {
	payload := make([]byte, 12+len(token))
	binary.BigEndian.PutUint32(payload[0:4], uint32(xid))
	binary.BigEndian.PutUint32(payload[4:8], uint32(zooKeeperSASLOpcode))
	binary.BigEndian.PutUint32(payload[8:12], uint32(len(token)))
	copy(payload[12:], token)
	if err := writeZooKeeperFrame(connection, payload); err != nil {
		return nil, err
	}
	response, err := readZooKeeperFrame(connection)
	if err != nil {
		return nil, err
	}
	if len(response) < 20 {
		return nil, errors.New("ZooKeeper SASL response is truncated")
	}

View on GitHub (pinned to c0390bff16)

Solutions

  1. Capture a wire log of the SASL exchange to see whether the server keeps re-challenging.
  2. Verify both sides agree on DIGEST-MD5 qop and that no proxy is mangling/replaying challenges.
  3. Check credentials are correct; some flows fail to converge with wrong secrets.
  4. Increase scrutiny of server version — nonstandard ZooKeeper SASL implementations may require more rounds than zooKeeperSASLMaxRounds allows.
Defensive patterns

Strategy: try-catch

Try / catch

if err := negotiateSASLDigest(conn, creds); err != nil {
	if strings.Contains(err.Error(), "exceeded") {
		log.Printf("SASL did not converge in %d rounds; check peer/proxy behavior", maxRounds)
	}
	return err
}

Prevention

When it happens

Trigger: authenticateSASLDigest → negotiateSASLDigest runs zooKeeperSASLMaxRounds iterations; each round yields a challenge and a token, but saslClient.Complete() never becomes true, so the function exits via the final return.

Common situations: Server keeps issuing challenges (unusual/qop mismatch), a man-in-the-middle or proxy replaying challenges, or a SASL client/step-count mismatch causing the exchange never to converge.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/5410f18456f53703. Report an issue: GitHub.