Skip to content

connector: server Auto-Detect Requests sent after the ConnectTimeAutoDetection phase kill the connection (license exchange, then capabilities exchange) #1629

Description

@holovchenko

ClientConnector answers Auto-Detect Requests only inside its own ConnectTimeAutoDetection phase, which ends at the first PDU off the MCS message channel. A Windows RDS host we connect to keeps sending SEC_AUTODETECT_REQ probes after that phase has closed. Every later phase hands whatever arrives straight to its own decoder, so the connection dies before the session opens.

mstsc connects to the same host without complaint, and other RDP hosts work — they simply never start a licensing exchange.

Environment

  • ironrdp-connector 0.10.0, ironrdp-pdu 0.9.0, ironrdp-session 0.11.0, ironrdp-core 0.2.1 (latest published on crates.io)
  • Server: Windows host with RDS licensing (CAL) — licensing exchange runs, mstsc fine
  • Client: custom sans-I/O driver on the connector's Sequence API

Symptom

ConnectFailed("[decode during SERVER_NEW_LICENSE/LicenseExchangeState::UpgradeLicense
 @ .../core/src/ops/function.rs:250] decode error")

The licensing exchange runs correctly right up to the last step — ClientNewLicenseRequest sent, ServerPlatformChallenge received, ClientPlatformChallengeResponse sent — and then the next PDU fails to decode.

The SERVER_NEW_LICENSE in that message is only the state's static with_context label; it says nothing about which PDU actually arrived. It sent our first round of diagnosis down the wrong path entirely (we assumed a truncated licensing PDU).

What actually arrives

Upstream prints no bytes with that error, so we had to patch the crate locally just to log them:

user_data_len = 10
user_data     = [0, 16, 0, 0, 6, 0, 0, 0, 20, 0]
error         = invalid `securityHeaderFlags`
Field Bytes Value
BasicSecurityHeader.flags 00 10 0x1000SEC_AUTODETECT_REQ, not SEC_LICENSE_PKT (0x0080)
headerLength 06 6
headerTypeId 00 TYPE_ID_AUTODETECT_REQUEST
sequenceNumber 00 00 0
requestType 14 00 0x0014RDP_RTT_REQUEST

So it is not a licensing PDU at all. license_exchange.rs feeds whatever arrives into LicensePdu::decode without checking the security header, and an unrelated advisory PDU takes down the whole connection.

Fixing that alone only moves the failure: with licensing completing, the same probe then reaches decode_share_control in connection_activation.rs's CapabilitiesExchange.

Suggested shape of a fix

What worked for us, in a local fork of 0.10.0 (~97 changed lines across license_exchange.rs, connection_activation.rs and one pub(crate) in connection.rs):

  1. license_exchange.rs — each of the three receiving states (NewLicenseRequest, PlatformChallenge, UpgradeLicense) checks BasicSecurityHeader.flags for SEC_LICENSE_PKT before decoding. A payload without it is logged at DEBUG and skipped, and the state machine stays in the same state to await the real licensing PDU.
  2. connection_activation.rs — in CapabilitiesExchange, decode the AutoDetectReqPdu and answer it through connection::respond_to_connect_time_autodetect, then stay in the state still waiting for Server Demand Active. This is the same skip-and-stay shape the code already applies a few lines below for ServerDeactivateAll.

Staying put is the load-bearing part. An earlier attempt treated the undecodable packet as the end of licensing and advanced to LicenseExchanged; the exchange was then left half-finished and the connection failed one step later instead. Skipping and waiting matches what MS-RDPBCGR implies: auto-detect is advisory, and a client that never answers an RTT probe is fine — the server proceeds regardless.

Verified against the live host, in order: probe skipped mid-licensing, ServerUpgradeLicense (2196 bytes) received, License verified with success, probe answered during Capabilities Exchange, eight Connection Finalization steps, Connected with success, frames rendering.

We deliberately did not touch ironrdp-pdu — the parser is fine; the issue is purely which PDUs get handed to it.

Relationship to existing work

Happy to open a PR with the patch above if the maintainers agree with the shape — in particular whether answering the probe in CapabilitiesExchange (rather than skipping it as licensing does) is the behaviour you want.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions