shadow1ng/fscan · error

NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM

Error message

NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM

What it means

After sending MCS Channel Join Requests, the client reads the Channel Join Confirm PDU and validates its header via readMCSPDUHeader(option, CHANNEL_JOIN_CONFIRM). If the PDU received is not a Channel Join Confirm (wrong type or malformed header), the library emits this sentinel error and aborts the join sequence. It is a protocol-state error: the server responded out of the expected T.125 state machine order.

Solutions

  1. Log the actual PDU type received to identify what the server sent instead of the join confirm.
  2. Retry the connection once — transient server-side disconnects are common under load.
  3. Verify server RDP protocol support; older/proprietary servers may not follow the standard MCS join flow.
  4. Check that no earlier parsing step (X224/SEC) left unread bytes causing stream desync.

Example fix

// before
if !readMCSPDUHeader(option, CHANNEL_JOIN_CONFIRM) {
    c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM"))
    return
}
// after
if !readMCSPDUHeader(option, CHANNEL_JOIN_CONFIRM) {
    c.Emit("error", fmt.Errorf("mcs: expected CHANNEL_JOIN_CONFIRM, got pdu type %d", option))
    return
}
Defensive patterns

Strategy: retry

Try / catch

c.On("error", func(err error) {
    if strings.Contains(err.Error(), "WAIT_CHANNEL_JOIN_CONFIRM") {
        log.Println("unexpected MCS PDU during join; retrying handshake")
        retryConnect(1)
    }
})

Prevention

When it happens

Trigger: recvChannelJoinConfirm reads a PDU whose header does not match CHANNEL_JOIN_CONFIRM — e.g. the server sent a Disconnect Provider Ultimatum, a different MCS PDU type, or garbage bytes after the user channel join request.

Common situations: Connecting to a non-standard RDP server or gateway that interleaves MCS PDUs differently; server rejected the connection earlier and sent a disconnect PDU; desynced stream due to a preceding parsing bug leaving bytes in the buffer.

Related errors


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

Appendix: source

Thrown at libs/grdp/protocol/t125/mcs.go:497

	if err != nil {
		c.Emit("error", errors.New(fmt.Sprintf("mcs recvData get data error %v", err)))
		return
	}
	glog.Debugf("mcs emit channel<%s>:%v", channelName, left)
	c.Emit("sec", channelName, left)
}

func (c *MCSClient) recvChannelJoinConfirm(s []byte) {
	glog.Debug("mcs recvChannelJoinConfirm", hex.EncodeToString(s))
	r := bytes.NewReader(s)
	option, err := core.ReadUInt8(r)
	if err != nil {
		c.Emit("error", err)
		return
	}

	if !readMCSPDUHeader(option, CHANNEL_JOIN_CONFIRM) {
		c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM"))
		return
	}

	confirm, _ := per.ReadEnumerates(r)
	userId, _ := per.ReadInteger16(r)
	userId += MCS_USERCHANNEL_BASE

	if c.userId != userId {
		c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID"))
		return
	}

	channelId, _ := per.ReadInteger16(r)
	if (confirm != 0) && (channelId == uint16(MCS_GLOBAL_CHANNEL_ID) || channelId == c.userId) {
		c.Emit("error", errors.New("NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNEL"))
		return
	}
	glog.Debug("Confirm channelId:", channelId)

View on GitHub (pinned to 95cc12e753)