shadow1ng/fscan · error

MCS DISCONNECT_PROVIDER_ULTIMATUM

Error message

MCS DISCONNECT_PROVIDER_ULTIMATUM

What it means

In recvData, the steady-state MCS data handler, the first check tests whether the incoming PDU is a DISCONNECT_PROVIDER_ULTIMATUM (opcode 8). If so, the server has torn down the MCS domain/connection, and this error is emitted before closing the transport. It means the server unilaterally disconnected the session.

Source

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

	buff := &bytes.Buffer{}
	writeMCSPDUHeader(CHANNEL_JOIN_REQUEST, 0, buff)
	per.WriteInteger16(c.userId-MCS_USERCHANNEL_BASE, buff)
	per.WriteInteger16(channelId, buff)
	c.transport.Write(buff.Bytes())
}

func (c *MCSClient) recvData(s []byte) {
	glog.Debug("msc on data recvData:", hex.EncodeToString(s))

	r := bytes.NewReader(s)
	option, err := core.ReadUInt8(r)
	if err != nil {
		c.Emit("error", err)
		return
	}

	if readMCSPDUHeader(option, DISCONNECT_PROVIDER_ULTIMATUM) {
		c.Emit("error", errors.New("MCS DISCONNECT_PROVIDER_ULTIMATUM"))
		c.transport.Close()
		return
	} else if !readMCSPDUHeader(option, c.recvOpCode) {
		c.Emit("error", errors.New("Invalid expected MCS opcode receive data"))
		return
	}

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

	channelId, _ := per.ReadInteger16(r)
	per.ReadEnumerates(r)
	size, _ := per.ReadLength(r)
	// channel ID doesn't match a requested layer
	found := false
	channelName := ""
	for _, channel := range c.channels {
		if channel.ID == channelId {

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Treat it as a server-initiated disconnect: close client resources and reconnect if the session should persist.
  2. Implement keep-alive/application-level activity to prevent idle-timeout disconnects.
  3. Check server event logs for the disconnect reason around the time of failure.
  4. Add automatic reconnection logic in the consumer of the 'error' event.

Example fix

// before
c.Emit("error", errors.New("MCS DISCONNECT_PROVIDER_ULTIMATUM"))
c.transport.Close()
return
// after
c.Emit("error", errors.New("MCS DISCONNECT_PROVIDER_ULTIMATUM: server disconnected the session"))
c.transport.Close()
c.scheduleReconnect() // consumer-side auto-reconnect
Defensive patterns

Strategy: try-catch

Validate before calling

// Detect server disconnect PDU early in the data stream:
if (buf[0]>>2) == 8 { // DISCONNECT_PROVIDER_ULTIMATUM
    // server is tearing the session down
}

Try / catch

mcs.On("error", func(err error) {
    if strings.Contains(err.Error(), "DISCONNECT_PROVIDER_ULTIMATUM") {
        // clean up, notify user, and trigger reconnect flow
    }
})

Prevention

When it happens

Trigger: Any 'data' event handled by recvData whose first byte decodes (option>>2) to DISCONNECT_PROVIDER_ULTIMATUM — the server sends this PDU instead of a SEND_DATA_INDICATION.

Common situations: Idle session disconnected by server policy/timeout; administrator logs off or disconnects the session; server crashes or restarts; license expiry; network device (NAT/firewall) idle-timeout prompting server teardown.

Related errors


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