Skip to content

feat(rdpdr): Notify RdpdrBackend of 'User Logged On' Messages#1211

Open
rhammonds-teleport wants to merge 3 commits intoDevolutions:masterfrom
rhammonds-teleport:rhammonds/rdpdr-handle-user-logged-on
Open

feat(rdpdr): Notify RdpdrBackend of 'User Logged On' Messages#1211
rhammonds-teleport wants to merge 3 commits intoDevolutions:masterfrom
rhammonds-teleport:rhammonds/rdpdr-handle-user-logged-on

Conversation

@rhammonds-teleport
Copy link
Copy Markdown
Contributor

At Teleport, we've noticed a regression with filesystem device redirection with newer versions of Windows (Windows Server 2025 in particular) where our device list announcements are ignored by the server. I wasn't able to find any case where Teleport and/or IronRDP are violating spec, but after a lot of experimentation I was able to find two workarounds:

  • Announce our filesystem devices and smart card device simultaneously after receiving a Client ID Confirm message from the server.
  • Respond to the User Logged On message by removing our smart card device. This seems to trigger some state change on the server where it will now acknowledge subsequent device announce/remove messages.

The first isn't a great option for us, since we've always allowed ad-hoc directory sharing mid-session. We're also in the midst of adding support for sharing multiple directories as well as shared directory removal without restarting the session.

This PR adds a new method to the RdpdrBackend trait that is called when a DR_CORE_USER_LOGGEDON message is received. According to MS-RDPEFS, this is an appropriate time to announce additional devices. This also provides an ergonomic way for the Teleport backend implementation to remove its smart card device after log on.

It also includes a fix for handle_device_io_request, which previously returned a fatal error when it encounters an unknown device ID. According to the spec these should be ignored.

…ves the backend an opportunity to modify the device list with the context that logon is complete.

Additionally, do not throw a fatal error when handling an IO request for an unkown device identifier. Per the spec, these SHOULD be ignored.
@rhammonds-teleport rhammonds-teleport force-pushed the rhammonds/rdpdr-handle-user-logged-on branch from 32a3c7f to b86822b Compare April 8, 2026 18:30
… reference to the base Rdpdr instance so that it may call add/remove methods that construct the appropriate device announce/remove messages.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a User Logged On hook to the RDPDR backend so consumers can react to DR_CORE_USER_LOGGEDON (e.g., re-announce/remove devices at the spec-recommended time), and adjusts device I/O handling to ignore unknown device IDs per MS-RDPEFS.

Changes:

  • Add handle_user_logged_on(&mut self, rdpdr: &mut Rdpdr) to RdpdrBackend and invoke it on RdpdrPdu::UserLoggedon.
  • Change Rdpdr to store the backend as an Option<Box<dyn RdpdrBackend>> to allow temporarily moving it while calling the new hook.
  • Update device I/O request handling to ignore requests for unknown/removed device IDs instead of treating them as fatal.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
crates/ironrdp-rdpdr/src/lib.rs Dispatch UserLoggedon to backend hook; ignore unknown DeviceId I/O requests; make backend optional for temporary move-out.
crates/ironrdp-rdpdr/src/backend/mod.rs Extend RdpdrBackend trait with handle_user_logged_on.
crates/ironrdp-rdpdr/src/backend/noop.rs Implement new trait method for the noop backend.
crates/ironrdp-rdpdr-native/src/nix/backend.rs Implement new trait method for the nix backend.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +150 to +152
let mut backend = self.backend.take().expect("missing rdpdr backend");
let res = backend.handle_user_logged_on(self)?;
self.backend = Some(backend);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle_user_logged_on temporarily take()s the backend, but if backend.handle_user_logged_on(self) returns Err, the ? early-returns before self.backend is restored. That leaves self.backend = None permanently and can cause later processing to fail/panic. Restore the backend before propagating the error (e.g., capture the result first, put the backend back, then ?), and avoid expect() panics by returning a PduError like the other handlers.

Suggested change
let mut backend = self.backend.take().expect("missing rdpdr backend");
let res = backend.handle_user_logged_on(self)?;
self.backend = Some(backend);
let mut backend = self
.backend
.take()
.ok_or_else(|| pdu_other_err!("missing rdpdr backend"))?;
let res = backend.handle_user_logged_on(self);
self.backend = Some(backend);
let res = res?;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in latest commit!

fn handle_server_device_announce_response(&mut self, pdu: ServerDeviceAnnounceResponse) -> PduResult<()>;
fn handle_scard_call(&mut self, req: DeviceControlRequest<ScardIoCtlCode>, call: ScardCall) -> PduResult<()>;
fn handle_drive_io_request(&mut self, req: ServerDriveIoRequest) -> PduResult<Vec<SvcMessage>>;
fn handle_user_logged_on(&mut self, rdpdr: &mut Rdpdr) -> PduResult<Vec<SvcMessage>>;
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a new required method to the public RdpdrBackend trait is a breaking change for downstream crates implementing the trait. Consider providing a default implementation for handle_user_logged_on (e.g., returning Ok(Vec::new())) so existing external backends keep compiling unless they need custom behavior.

Suggested change
fn handle_user_logged_on(&mut self, rdpdr: &mut Rdpdr) -> PduResult<Vec<SvcMessage>>;
fn handle_user_logged_on(&mut self, _rdpdr: &mut Rdpdr) -> PduResult<Vec<SvcMessage>> {
Ok(Vec::new())
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking changes are fine for now, although it may make sense to have a default implementation nonetheless, if that’s not something that is necessary for all backends.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and added a default implementation. No good reason not to.

…d after take.

Add default implementation of 'handle_user_logged_on' to avoid breaking existing implementations.
@rhammonds-teleport
Copy link
Copy Markdown
Contributor Author

Hi Benoît Cortier (@CBenoit), do you have any other feedback or concerns with this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants