shadow1ng/fscan · error

unexpected opcode: %d

Error message

unexpected opcode: %d

What it means

Guard in doCassandraAuth: after sending the SASL PLAIN auth token, the next CQL frame carried neither the AUTH_SUCCESS nor the ERROR opcode expected at this point in the handshake. An unexpected protocol opcode means the server response does not match the CQL conversation state.

Source

Thrown at plugins/services/cassandra.go:145

		}
		// SASL PLAIN: \x00username\x00password
		saslToken := []byte("\x00" + cred.Username + "\x00" + cred.Password)
		if err := cqlSend(conn, cqlOpAuthRsp, saslToken); err != nil {
			state.IncrementTCPFailedPacketCount()
			return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
		}
		opcode, body, err = cqlRecv(conn)
		if err != nil {
			state.IncrementTCPFailedPacketCount()
			return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
		}
		// AUTH_SUCCESS → 认证成功
		// ERROR → 认证失败
		if opcode == cqlOpError {
			return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("authentication failed: %s", string(body))}
		}
		if opcode != cqlOpAuthOk && opcode != cqlOpReady {
			return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("unexpected opcode: %d", opcode)}
		}
	}

	// Step 4: 发送测试查询
	queryBody := cqlLongString("SELECT cluster_name FROM system.local")
	// 添加 consistency level (ONE=1)
	queryBody = append(queryBody, 0x00, 0x01) // flags=0, consistency=ONE
	if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil {
		state.IncrementTCPFailedPacketCount()
		return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
	}
	opcode, body, err = cqlRecv(conn)
	if err != nil {
		state.IncrementTCPFailedPacketCount()
		return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
	}
	if err := validateCQLQueryResponse(opcode, body); err != nil {
		return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: err}

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Note the opcode value in the error and compare with the Cassandra native protocol spec to identify the reply type.
  2. Confirm the target really is a Cassandra node and no TCP proxy is rewriting the stream.
  3. Try pinning a different CQL protocol version so framing/opcode negotiation matches the server.
Defensive patterns

Strategy: retry

Try / catch

res := doCassandraAuth(conn, cred, state)
if !res.Success && strings.HasPrefix(res.Error.Error(), "unexpected opcode") {
    conn.Close()
    conn = dial(...) // fresh connection resyncs framing
}

Prevention

When it happens

Trigger: doCassandraAuth reads the post-SASL response; opcode is not cqlOpAuthOk, cqlOpReady, or cqlOpError — e.g. a protocol violation or an intermediary answering with an unexpected frame.

Common situations: Target is not really Cassandra (a proxy/load balancer speaking something else); protocol version mismatch causing garbled framing; server extension or fork deviating from the native protocol v3/v4 spec.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/9928da084103a38c. Report an issue: GitHub.