Skip to content

feat(rdpeudp): carry tunnel sub-headers through the UDP transport - #2030

Open
Greg Lamberson (glamberson) wants to merge 4 commits into
Devolutions:masterfrom
lamco-admin:feat/rdpeudp-tunnel-sub-headers
Open

Greg Lamberson (glamberson) wants to merge 4 commits into
Devolutions:masterfrom
lamco-admin:feat/rdpeudp-tunnel-sub-headers

Conversation

@glamberson

@glamberson Greg Lamberson (glamberson) commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • The RDPEMT codec already encodes and decodes Tunnel Data sub-headers, but UdpTransport only ever sent an empty list and dropped the ones it received. MS-RDPBCGR 1.3.9 carries Continuous Auto-Detection for a sideband channel in use in those sub-headers (Bandwidth Measure Start and Stop, Network Characteristics Result, Bandwidth Measure Results), so none of it could be sent or received over UDP.
  • New TunnelMessage holds a PDU's data and its sub-headers. send_message and recv_message use it; send and recv keep their signatures and behavior, recv dropping any sub-headers as before.
  • send_message checks the sub-headers against the one-byte HeaderLength synchronously, like the existing payload-length check, so an oversized message fails at the call instead of ending the write pump. New UdpTransportErrorKind::SubHeadersTooLarge for it.
  • Additive: UdpTransportErrorKind is #[non_exhaustive].

Validation

cargo xtask check fmt/lints/tests/typos/locks all pass. New tests cover the write pump encoding sub-headers, the data loop forwarding them, recv against recv_message, the size check at 255 and 256 bytes, and a full-stack loopback round trip including a message with sub-headers and no data.

Notes

The server side uses this in #2031 to measure bandwidth over the tunnel, and the client side in #2009.

@github-actions github-actions Bot added kind/protocol Affects RDP or related protocol behavior risk/medium Behavioral change that does not substantially alter a core public API size/M Size: up to 449 counted lines and 10 files; exceeds S in either measure triage/overlap Possible overlap with another pull request; advisory only labels Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

This pull request may overlap with #2009.

PR #2009 describes #2030 carrying RDPEMT tunnel sub-headers through the transport with recv_message and send_message so auto-detect requests on the UDP tunnel can be answered; this PR adds the same TunnelMessage sub-header carriage plus recv_message/send_message and sub-header forwarding in ironrdp-rdpeudp-tokio, sharing that scope.

This notice is advisory only. Automated review continues as usual, and how these pull requests relate is for maintainers and authors to decide.

Note

LLM-assisted content (no human feedback).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The PR plumbs RDPEMT Tunnel Data sub-headers through the tokio UDP transport via a new TunnelMessage, additively adding send_message/recv_message and SubHeadersTooLarge while preserving send/recv behavior. I independently verified the synchronous guard matches TunnelData::encode's HeaderLength constraint (4 + sub-headers <= 255, also covering the per-sub-header u8 limit), that ironrdp-client and FramedRead keep their observable behavior, and that no protocol defect is introduced. Published: the new message API still has no in-repo consumer outside tests (open question pending the announced follow-up server PR), the unused Default derive on TunnelMessage, and a now-stale FramedRead comment claiming the tunnel consumes sub-headers. Rejected the TunnelData-deduplication suggestion as a deliberate layering choice.

  1. [skeptical] New TunnelMessage/send_message/recv_message API has no consumer outside this crate's tests — low 🟡 ❓ — crates/ironrdp-rdpeudp-tokio/src/transport.rs
    Verified in the pr-head tree: send_message, recv_message and TunnelMessage are referenced only by this crate's unit tests and the new full-stack test; ironrdp-client/src/rdp.rs still uses transport.send/transport.recv, and FramedRead for UdpTransport is built on recv(), which drops sub-headers. So the capability this PR exists to enable ([MS-RDPBCGR] 1.3.9 auto-detect sub-headers reaching an application) is reachable only from tests until the follow-up server PR announced in the PR body lands, which the provided context cannot confirm. The change is additive and behavior-preserving with the deferred-dispatch intent documented, so this stays a low-severity open question rather than a defect.
  2. [general] FramedRead comment about the tunnel consuming sub-headers is now stale — low 🟡 — crates/ironrdp-rdpeudp-tokio/src/framed.rs
    The PR now forwards sub-headers to the application channel inside TunnelMessage, and nothing consumes them before FramedRead's recv(), which drops them. The comment stating 'The tunnel has already taken what it needs from those subheaders by the time we get here' described the old tunnel_data_loop behavior and is no longer true, so it misleads maintainers about where sub-headers go. The skip-empty-payload behavior itself is unchanged and correct; only the justification is stale. The comment lines predate this PR, so no line range is cited.

Comment thread crates/ironrdp-rdpeudp-tokio/src/transport.rs Outdated
@github-actions github-actions Bot added the ai-reviewed/1 One automated review completed label Sep 28, 2026
@glamberson

Copy link
Copy Markdown
Contributor Author

Thanks for the review. The Default derive is gone (answered on its thread). The FramedRead comment is fixed: it now says recv drops the subheaders and that a caller who needs them uses recv_message, instead of claiming the tunnel consumes them. On the API having no consumer outside tests: it has both now. #2031 uses send_message to put Bandwidth Measure Start and Stop in tunnel sub-headers and recv_message to read the client's results, verified against mstsc over RDP-UDP (140 Bandwidth Measure Results decoded, none failed), and #2009 now reads the tunnel with recv_message and answers with send_message on the client side.

@github-actions github-actions Bot added the needs-review A human reviewer is the current next actor label Sep 28, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The PR correctly surfaces RDP_TUNNEL_SUBHEADER content in both directions via the new TunnelMessage: the write pump encodes message.sub_headers, the data loop forwards received sub_headers, and send/recv keep their behavior. Verified against the head tree: the send_message precheck (4 + summed wire_size > 255) exactly matches ironrdp-rdpemt's TunnelData encoder invariant, with tests pinning the 255/256 boundary; PayloadTooLarge still correctly covers only higher-layer data; sub-headers stay bundled with their PDU's data in FIFO order; the new error variant is additive on a #[non_exhaustive] enum. No correctness, protocol, or API defect found. The only publishable concern is a low-severity cross-crate drift hazard: the precheck restates the rdpemt encoder's private bound with a hard-coded 4. The From<Vec<u8>> style suggestion was rejected as a defensible ergonomics choice.

  1. [skeptical + code-compressor] send_message precheck re-implements the rdpemt HeaderLength bound with a hard-coded header size — low 🟡 — crates/ironrdp-rdpeudp-tokio/src/transport.rs
    The `4 /* RDP_TUNNEL_HEADER */` literal and the `4 + sub_headers_len > 255` rule duplicate the invariant owned by ironrdp-rdpemt's TunnelData encoder (header_length_untruncated = pub(crate) TunnelHeader::MIN_SIZE + summed wire_size, refused beyond the one-byte HeaderLength field). The two agree today and the 255/256-byte tests pin the boundary, but if rdpemt's header size or bound ever changes, the precheck could accept a message the write pump's encode then rejects — the exact per-call-failure behavior this check exists to guarantee. A follow-up exposing a checked sizing helper from ironrdp-rdpemt would give the invariant one owner; that cross-crate public-API change is out of this PR's scope.

@github-actions github-actions Bot added ai-reviewed/2 Final automated review completed and removed ai-reviewed/1 One automated review completed needs-review A human reviewer is the current next actor labels Sep 29, 2026
@github-actions github-actions Bot added the needs-review A human reviewer is the current next actor label Sep 29, 2026
@glamberson

Copy link
Copy Markdown
Contributor Author

Thanks for the second review. The send_message precheck now has a comment citing MS-RDPEMT 2.2.1.1 for the one byte HeaderLength that counts the 4 byte fixed part, and the boundary test also runs the largest accepted message (249 bytes of sub-header data) and the smallest refused one (250) through the real TunnelData encoder, so the precheck and the encoder cannot drift apart unnoticed. Changing either side alone now fails the test. I left the sizing helper in ironrdp-rdpemt out, since it would be a public API change in another crate that this test makes unnecessary.

@CBenoit Benoît Cortier (CBenoit) left a comment •

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.

Thank you, LGTM

Note to agent: I couldn’t merge immediately because there is a conflict with master. You can merge on my behalf if conflicts are addressed, and no significant change is made by the author.

@CBenoit Benoît Cortier (CBenoit) added needs-author-action The pull request author is the current next actor and removed needs-review A human reviewer is the current next actor labels Oct 1, 2026
The RDPEMT codec already handled sub-headers, but UdpTransport exposed only a PDU's data, so the Continuous Auto-Detection messages MS-RDPBCGR 1.3.9 carries there could be neither sent nor received. TunnelMessage carries both; send_message and recv_message use it, and send/recv keep working on data alone.
The sub-header examples carried a whole Bandwidth Measure Start, header
bytes included. An auto-detect sub-header is the auto-detect structure
itself (MS-RDPEMT 2.2.1.1.1): its SubHeaderLength and SubHeaderType are the
structure's headerLength and headerTypeId, so on the wire those examples
would repeat the header. The transport treats the data as opaque and the
tests passed either way; the examples now carry only what follows the
header.
…ment

Nothing builds a default TunnelMessage, and a default one would be an
empty Tunnel Data PDU. The FramedRead comment said the tunnel consumes the
sub-headers before this point; recv drops them, and a caller that needs
them uses recv_message.
The send_message precheck restates the HeaderLength bound that
ironrdp-rdpemt's TunnelData encoder owns. The boundary test now also runs the
largest accepted message and the smallest refused one through the real
encoder, so the two cannot drift apart unnoticed, and the comment on the
check cites MS-RDPEMT 2.2.1.1.
auto-merge was automatically disabled October 1, 2026 13:52

Head branch was pushed to by a user without write access

@glamberson
Greg Lamberson (glamberson) force-pushed the feat/rdpeudp-tunnel-sub-headers branch from 13f95f6 to efe1777 Compare October 1, 2026 13:52
@glamberson

Copy link
Copy Markdown
Contributor Author

Rebased onto master. #1954 added a cloneable UdpTransportSender to this crate, which this PR's channel change has to follow: its channel now carries a TunnelMessage like UdpTransport's does, it gets its own send_message, and UdpTransport::send_message forwards to it. UdpTransportSender joins TunnelMessage in the pub use list. That's a small addition beyond the conflict itself; everything else is unchanged.

@github-actions github-actions Bot added risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny needs-review A human reviewer is the current next actor and removed needs-author-action The pull request author is the current next actor risk/medium Behavioral change that does not substantially alter a core public API labels Oct 1, 2026

This branch was successfully deployed

1 active deployment
llm-providers — efe17775 Deployed Oct 1, 2026 by glamberson via Classify pull request #1535
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed/2 Final automated review completed kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny size/M Size: up to 449 counted lines and 10 files; exceeds S in either measure triage/overlap Possible overlap with another pull request; advisory only

Development

Successfully merging this pull request may close these issues.

2 participants