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
- Ensure the sender stops sending legacy TDP messages once the connection is upgraded to TDPB
- In readers, treat ErrIsTDP with errors.Is and discard/skip the frame as intended by DecodeWithTDPDiscard
- 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
- Complete the TDP-to-TDPB upgrade handshake before streaming TDPB
- Use errors.Is on the sentinel to discard legacy frames
- Verify clients honor protocol upgrade responses
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.