Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 26 additions & 22 deletions crates/ironrdp-acceptor/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ use super::finalization::FinalizationSequence;
use crate::util::{self, wrap_share_data};

const IO_CHANNEL_ID: u16 = 1003;
// Also the fixed MCS server channel ID (0x03EA) that MS-RDPBCGR 3.3.1.5 defines,
// which is why it doubles as the `initiator` on every server-to-client Send Data
// Indication in this file (License, Demand Active, Initiate Multitransport Request
// per 3.3.5.15.1) rather than the user's own MCS channel ID.
const USER_CHANNEL_ID: u16 = 1002;

pub struct Acceptor {
Expand Down Expand Up @@ -362,14 +366,15 @@ impl Acceptor {
)
}

/// If `data` (an MCS SendDataRequest already decoded from the wire) is on
/// the message channel while a multitransport request is outstanding AND
/// its payload strictly decodes as an Initiate Multitransport Response,
/// logs it against the outstanding request (matching request IDs) and
/// returns it. MS-RDPBCGR 3.2.5.15.1 gives this response no fixed
/// position relative to the rest of the handshake: it depends on when
/// the client resolves its own bootstrapping and whether the sideband
/// attempt failed, so both `CapabilitiesWaitConfirm` and
/// Returns `true` if `data` (an MCS SendDataRequest already decoded from
/// the wire) is on the message channel while a multitransport request is
/// outstanding AND its payload strictly decodes as an Initiate
/// Multitransport Response, after logging the response against the
/// outstanding request (matching request IDs); `false` otherwise, leaving
/// the caller to handle `data`. MS-RDPBCGR 3.2.5.15.1 gives this response
/// no fixed position relative to the rest of the handshake: it depends on
/// when the client resolves its own bootstrapping and whether the
/// sideband attempt failed, so both `CapabilitiesWaitConfirm` and
/// `ConnectionFinalization` tolerate it landing wherever it actually
/// shows up rather than only where `MultitransportBootstrapping`'s own
/// comment describes as typical.
Expand All @@ -381,23 +386,24 @@ impl Acceptor {
/// handling for anything that isn't really a response, mirroring how
/// `ClientConnectorState::ConnectTimeAutoDetection` demuxes the same
/// channel client-side.
fn late_multitransport_response(
&self,
data: &mcs::SendDataRequest<'_>,
) -> Option<rdp::multitransport::MultitransportResponsePdu> {
let sent = self.sent_multitransport_request.as_ref()?;
fn is_late_multitransport_response(&self, data: &mcs::SendDataRequest<'_>) -> bool {
let Some(sent) = self.sent_multitransport_request.as_ref() else {
return false;
};
if Some(data.channel_id) != self.message_channel_id {
return None;
return false;
}
// `decode` alone would accept a valid response followed by trailing
// bytes. This acceptor advertises ENCRYPTION_LEVEL_NONE, so the
// response carries the 4-byte Basic Security Header (MS-RDPBCGR
// 2.2.15.2) and is exactly 12 bytes: anything left over means the
// payload is something else.
let mut cursor = ReadCursor::new(data.user_data.as_ref());
let response = decode_cursor::<rdp::multitransport::MultitransportResponsePdu>(&mut cursor).ok()?;
let Ok(response) = decode_cursor::<rdp::multitransport::MultitransportResponsePdu>(&mut cursor) else {
return false;
};
if !cursor.is_empty() {
return None;
return false;
}
if response.request_id == sent.request_id {
debug!(
Expand All @@ -412,7 +418,7 @@ impl Acceptor {
"Initiate Multitransport Response request ID does not match the sent request"
);
}
Some(response)
true
}

pub fn new_deactivation_reactivation(
Expand Down Expand Up @@ -1274,7 +1280,7 @@ impl Sequence for Acceptor {
// response at all (Auto-Detect Response, Heartbeat), so it
// falls through to the Confirm Active handling below
// instead.
if self.late_multitransport_response(&data).is_some() {
if self.is_late_multitransport_response(&data) {
self.state = prev_state;
return Ok(Written::Nothing);
}
Expand Down Expand Up @@ -1329,7 +1335,7 @@ impl Sequence for Acceptor {
client_capabilities,
} => {
// A late Initiate Multitransport Response can land in any
// finalization sub-state (see `late_multitransport_response`);
// finalization sub-state (see `is_late_multitransport_response`);
// none of FinalizationSequence's own PDU decoders expect it, and
// depending which sub-state is active it would otherwise be
// silently swallowed while advancing a state, propagated as a
Expand All @@ -1338,9 +1344,7 @@ impl Sequence for Acceptor {
// finalization ever sees the bytes, mirroring
// `CapabilitiesWaitConfirm`'s handling.
let is_late_multitransport_response = match decode::<X224<mcs::McsMessage<'_>>>(input) {
Ok(X224(mcs::McsMessage::SendDataRequest(data))) => {
self.late_multitransport_response(&data).is_some()
}
Ok(X224(mcs::McsMessage::SendDataRequest(data))) => self.is_late_multitransport_response(&data),
_ => false,
};

Expand Down
56 changes: 56 additions & 0 deletions crates/ironrdp-acceptor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,75 @@ where
}

pub async fn accept_finalize<S>(
framed: Framed<S>,
acceptor: &mut Acceptor,
) -> ConnectorResult<(Framed<S>, AcceptorResult)>
where
S: FramedRead + FramedWrite,
{
accept_finalize_with_multitransport(framed, acceptor, |_, _| async {}).await
}

/// Completes the connection sequence, notifying `multitransport_handler` each
/// time the acceptor sends an Initiate Multitransport Request, so the caller
/// can establish the sideband UDP transport (RDPEUDP2 + TLS + RDPEMT).
///
/// Unlike [`ironrdp_async::connect_finalize_with_multitransport`] on the
/// client side, `multitransport_handler` does not report an outcome back
/// into the sequence: by the time it runs, the acceptor has already sent the
/// request and moved on to capability negotiation (see the doc comment on
/// [`AcceptorState::MultitransportBootstrapping`]), and
/// nothing here waits for the sideband transport to come up. Establishing it
/// is the caller's job, driven independently of this function's return.
/// `multitransport_handler` is awaited once per request, synchronously,
/// immediately after the sequence step that sent it: it should return
/// promptly (for example, by spawning the actual UDP-accept work on the
/// caller's own runtime) rather than driving the transport to completion
/// inline, or the RDP handshake stalls behind it.
///
/// # Panics
///
/// Panics if `multitransport_soft_sync_negotiated()` returns `None` right
/// after `multitransport_request()` returned `Some`, which the two methods'
/// own contract does not allow.
pub async fn accept_finalize_with_multitransport<S, H>(
mut framed: Framed<S>,
acceptor: &mut Acceptor,
mut multitransport_handler: H,
) -> ConnectorResult<(Framed<S>, AcceptorResult)>
where
S: FramedRead + FramedWrite,
H: AsyncFnMut(ironrdp_pdu::rdp::multitransport::MultitransportRequestPdu, bool),
{
let mut buf = WriteBuf::new();
// `multitransport_request()` borrows rather than consumes (the field it
// reads also gates `CapabilitiesWaitConfirm`'s tolerance for a late
// Initiate Multitransport Response), so this driver tracks locally
// whether it has already notified the caller instead. Seeded from
// whatever is already present rather than `false`: a Deactivation-
// Reactivation Sequence rebuilds the acceptor via
// `Acceptor::new_deactivation_reactivation()`, which carries the
// original request forward, then this function is called again on the
// rebuilt acceptor. Bootstrapping does not run a second time, so without
// this the first loop iteration of that fresh call would treat the
// carried-over request as newly sent and notify the handler again.
let mut notified = acceptor.multitransport_request().is_some();

loop {
if let Some(result) = acceptor.get_result() {
return Ok((framed, result));
}

single_sequence_step(&mut framed, acceptor, &mut buf).await?;

if !notified && let Some(request) = acceptor.multitransport_request() {
let request = request.clone();
let soft_sync = acceptor
.multitransport_soft_sync_negotiated()
.expect("multitransport_request() just returned Some, so a request was sent");
multitransport_handler(request, soft_sync).await;
notified = true;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/ironrdp-testsuite-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ hex = "0.4"
ironrdp-cliprdr-format.path = "../ironrdp-cliprdr-format"
ironrdp-cliprdr = { path = "../ironrdp-cliprdr", features = ["__test"] }
ironrdp-acceptor.path = "../ironrdp-acceptor"
ironrdp-async.path = "../ironrdp-async"
ironrdp-tokio.path = "../ironrdp-tokio"
ironrdp-bulk.path = "../ironrdp-bulk"
ironrdp-connector.path = "../ironrdp-connector"
ironrdp-displaycontrol.path = "../ironrdp-displaycontrol"
Expand Down
18 changes: 10 additions & 8 deletions crates/ironrdp-testsuite-core/tests/server/acceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ fn neg_failure_hybrid_required() {
}
}

fn encode_send_data_request(initiator_id: u16, channel_id: u16, user_data: &[u8]) -> Vec<u8> {
pub(super) fn encode_send_data_request(initiator_id: u16, channel_id: u16, user_data: &[u8]) -> Vec<u8> {
let mut buf = WriteBuf::new();
ironrdp_core::encode_buf(
&X224(mcs::SendDataRequest {
Expand Down Expand Up @@ -315,7 +315,7 @@ fn drive_to_secure_settings_exchange(
)
}

fn client_gcc_with_message_channel_and_multitransport(
pub(super) fn client_gcc_with_message_channel_and_multitransport(
offer: Option<MultiTransportFlags>,
) -> ironrdp_pdu::gcc::ClientGccBlocks {
let mut blocks = CLIENT_GCC_WITHOUT_OPTIONAL_FIELDS.clone();
Expand All @@ -327,8 +327,8 @@ fn client_gcc_with_message_channel_and_multitransport(

/// Builds an `Acceptor` for the multitransport tests below, at a common
/// 1920x1080 desktop size with no static channels or credentials. `offer` is
/// passed to `set_multitransport_offer` when `Some`; pass `None` to exercise
/// the default-disabled path.
/// passed straight through to `set_multitransport_offer`; pass `None` to
/// exercise the default-disabled path.
fn multitransport_acceptor(offer: Option<MultiTransportFlags>) -> Acceptor {
let mut acceptor = Acceptor::new(
SecurityProtocol::SSL,
Expand All @@ -339,9 +339,7 @@ fn multitransport_acceptor(offer: Option<MultiTransportFlags>) -> Acceptor {
Vec::new(),
None,
);
if let Some(offer) = offer {
acceptor.set_multitransport_offer(Some(offer));
}
acceptor.set_multitransport_offer(offer);
acceptor
}

Expand Down Expand Up @@ -641,7 +639,11 @@ fn multitransport_not_offered_by_default() {

let client_blocks =
client_gcc_with_message_channel_and_multitransport(Some(MultiTransportFlags::TRANSPORT_TYPE_UDP_FECR));
drive_to_secure_settings_exchange(&mut acceptor, client_blocks);
let (.., server_multitransport) = drive_to_secure_settings_exchange(&mut acceptor, client_blocks);
assert_eq!(
server_multitransport, None,
"server must not advertise MultiTransportChannelData when multitransport is disabled"
);

acceptor.step(&[], None, &mut WriteBuf::new()).unwrap(); // LicensingExchange
let written = acceptor.step(&[], None, &mut WriteBuf::new()).unwrap(); // MultitransportBootstrapping
Expand Down
1 change: 1 addition & 0 deletions crates/ironrdp-testsuite-core/tests/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod connection_policy;
mod credential_validator;
mod fast_path;
mod finalize_timeout;
mod multitransport_finalize;
mod rdpdr;
mod rdpei;
mod remotefx_entropy_coder;
Loading
Loading