gravitational/teleport · warning

message is TDP, not TDPB

Error message

message is TDP, not TDPB

What it means

Sentinel error returned by DecodeWithTDPDiscard/ReadMessage when the frame's first byte indicates a legacy TDP message rather than a TDPB message (TDPB frames start with a zero first byte, since it holds the high 8 bits of the message length).

Source

Thrown at lib/srv/desktop/tdp/protocol/tdpb/tdpb.go:44

	"errors"
	"io"

	"github.com/gravitational/trace"
	"google.golang.org/protobuf/proto"

	tdpbv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/desktop/v1"
	"github.com/gravitational/teleport/lib/srv/desktop/tdp"
)

// ProtocolName is the identifier for the TDPB protocol.
const ProtocolName = "teleport-tdpb-1.0"

// ErrUnknownMessage is returned when an unknown message is decoded.
var ErrUnknownMessage = errors.New("decoded unknown TDPB message")

// ErrIsTDP is returned when a legacy TDP message is received
// during or after a connection upgrade to TDPB.
var ErrIsTDP = errors.New("message is TDP, not TDPB")

const (
	// We can differentiate between TDP and TDPB messages on the wire
	// by inspecting the first byte received. A non-empty first byte
	// is presumed to be a TDP message, otherwise, TDPB.
	// Since the first byte of a TDPB message is the high 8 bits of its
	// length, we must take care not to allow TDPB messages that
	// meet or exceed length 2^24 (16MiB).
	// Once TDP is fully deprecated we can relax this constraint, although
	// it's unlikely we would ever want messages anywhere near this size.
	maxMessageLength = (1 << 24) - 1
	tdpbHeaderLength = 4 // sizeof(uint32)
)

// ClientHello is the first message sent by the client, and advertises
// client capabilities and connection properties.
type ClientHello tdpbv1.ClientHello

View on GitHub (pinned to 1283425b60)

Solutions

  1. Ensure the sender stops sending legacy TDP messages once the connection is upgraded to TDPB
  2. In readers, treat ErrIsTDP with errors.Is and discard/skip the frame as intended by DecodeWithTDPDiscard
  3. Investigate upgrade sequencing if legacy frames persist after upgrade completion

Example fix

// before
msg, err := tdpb.ReadMessage(conn)
if err != nil { return err }
// after
msg, err := tdpb.ReadMessage(conn)
if errors.Is(err, tdpb.ErrIsTDP) { continue } // legacy frame, discard
if err != nil { return trace.Wrap(err) }
Defensive patterns

Strategy: try-catch

Try / catch

msg, err := tdpb.DecodeWithTDPDiscard(rdr)
if errors.Is(err, tdpb.ErrIsTDP) {
    continue // legacy TDP frame, discard
}
if err != nil { return nil, trace.Wrap(err) }

Prevention

When it happens

Trigger: A connection upgraded to TDPB still receives a legacy TDP frame (non-zero first byte), so DecodeWithTDPDiscard returns ErrIsTDP instead of a TDPB message.

Common situations: Upgrade handshake race where the client sends leftover TDP frames after upgrade; misconfigured client not honoring the protocol upgrade; tests asserting the discard behavior.

Related errors


AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02). Data as JSON: /api/errors/ce9acde44a84e6c5. Report an issue: GitHub.