gravitational/teleport · info
unexpected message type
Error message
unexpected message type
What it means
Sentinel error returned when a TDP message read while waiting for MFA authentication messages is not one of the expected MFA types. Callers treat it as a signal to skip the message and keep reading, not a fatal failure.
Source
Thrown at lib/srv/desktop/tdp/mfa.go:31
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package tdp
import (
"context"
"errors"
"log/slog"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/mfa"
)
var (
ErrUnexpectedTDPMessageType = errors.New("unexpected message type")
)
// convertChallenge converts an MFA challenge to a Message. Returns
// a non-nil error if the conversion fails
type convertChallenge func(*proto.MFAAuthenticateChallenge) (Message, error)
// asMFAResponse returns:
// - ErrUnexpectedTDPMessageType if a valid messages was received but was not an MFA message.
// - Any other non-nil error if there was an error interpreting the message.
// - nil if a valid, non-nil MFA messages was found.
type asMFAResponse func(Message) (*proto.MFAAuthenticateResponse, error)
// NewMfaPrompt constructs a function that reads, encodes, and sends an MFA challenge to the client,
// then waits for the corresponding MFA response message. It caches any non-MFA messages received so
// that they may be forwarded to the server later on.
func NewMFAPrompt(rw MessageReadWriter, asResponse asMFAResponse, toMessage convertChallenge, withheld *[]Message, log *slog.Logger) mfa.PromptFunc {
return func(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) {
challengeMsg, err := toMessage(chal)View on GitHub (pinned to 1283425b60)
Solutions
- No action needed in MFA read loops — match with errors.Is(err, ErrUnexpectedTDPMessageType) and continue reading
- If seen from legacy protocol/legacy/mfa.go default case unexpectedly, verify the peer's TDP protocol version
- Check for protocol desync (message stream corrupted or wrong wire format) if this error repeats abnormally
Example fix
// before
resp, err := asResponse(msg)
if err != nil { return nil, err }
// after
resp, err := asResponse(msg)
if errors.Is(err, tdp.ErrUnexpectedTDPMessageType) { continue }
if err != nil { return nil, err } Defensive patterns
Strategy: try-catch
Try / catch
resp, err := asResponse(msg)
if errors.Is(err, tdp.ErrUnexpectedTDPMessageType) {
continue // skip non-MFA message
}
if err != nil { return nil, trace.Wrap(err) } Prevention
- Always use errors.Is against the sentinel, never string compare
- Keep read loops tolerant of interleaved non-MFA messages
- Track unexpected types in debug logs to spot protocol desync
When it happens
Trigger: A non-MFA TDP message (e.g. mouse move, screen data) arrives while ReadMFAAuthenticateResponse-style code is waiting for an MFA challenge/response; also returned by legacy mfa.go converters in their default switch case.
Common situations: During RDP login with MFA, interleaved normal streaming messages arrive between MFA frames; protocol version mismatch causing unexpected message types.
Related errors
- credential not found
- user has only invalid WebAuthn registrations, consider a use
- failed to fetch MySQL version
- decoded unknown TDPB message
- message is TDP, not TDPB
AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02).
Data as JSON: /api/errors/ceabc59ca8126aeb.
Report an issue: GitHub.