@@ -164,7 +164,7 @@ struct CleanPathResult {
164164 destination : TargetAddr ,
165165 server_addr : SocketAddr ,
166166 server_stream : tokio_rustls:: client:: TlsStream < tokio:: net:: TcpStream > ,
167- x224_rsp : Vec < u8 > ,
167+ x224_rsp : Option < Vec < u8 > > ,
168168}
169169
170170async fn process_cleanpath (
@@ -234,45 +234,99 @@ async fn process_cleanpath(
234234 debug ! ( %selected_target, "Connected to destination server" ) ;
235235 span. record ( "target" , selected_target. to_string ( ) ) ;
236236
237- // Send preconnection blob if applicable
238- if let Some ( pcb) = cleanpath_pdu. preconnection_blob {
239- server_stream. write_all ( pcb. as_bytes ( ) ) . await ?;
240- }
237+ // Preconnection Blob (PCB) is currently only used for Hyper-V VMs.
238+ //
239+ // Connection sequence with Hyper-V VMs (PCB enabled):
240+ // ┌─────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
241+ // │ handled by │ │ handled by IronRDP client │
242+ // │ Gateway │ │ │
243+ // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘
244+ // │PCB → TLS handshake │ → │CredSSP → X224 connection request → X224 connection response │
245+ // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘
246+ //
247+ // Connection sequence without Hyper-V VMs (PCB disabled):
248+ // ┌─────────────────────────────────────────────────────────────┐ ┌──────────────────────┐
249+ // │ handled by Gateway │ │ handled by IronRDP │
250+ // │ │ │ client │
251+ // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘
252+ // │X224 connection request → X224 connection response → TLS hs │ → │CredSSP → ... │
253+ // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘
254+ //
255+ // Summary:
256+ // - With PCB: Gateway handles (1) sending PCB, (2) TLS handshake, then leaves CredSSP
257+ // and X224 connection request/response to IronRDP client
258+ // - Without PCB: Gateway handles (1) X224 connection request, (2) X224 connection response,
259+ // then leaves TLS handshake and CredSSP to IronRDP client
260+ let ( server_stream, x224_rsp) = if let Some ( pcb_string) = cleanpath_pdu. preconnection_blob {
261+ let pcb = ironrdp_pdu:: pcb:: PreconnectionBlob {
262+ version : ironrdp_pdu:: pcb:: PcbVersion :: V2 ,
263+ id : 0 ,
264+ v2_payload : Some ( pcb_string) ,
265+ } ;
266+
267+ let encoded = ironrdp_core:: encode_vec ( & pcb)
268+ . context ( "failed to encode preconnection blob" )
269+ . map_err ( CleanPathError :: BadRequest ) ?;
270+
271+ server_stream. write_all ( & encoded) . await ?;
272+
273+ let server_stream = crate :: tls:: connect ( selected_target. host ( ) , server_stream)
274+ . await
275+ . map_err ( |source| CleanPathError :: TlsHandshake {
276+ source,
277+ target_server : selected_target. to_owned ( ) ,
278+ } ) ?;
241279
242- // Send X224 connection request
243- let x224_req = cleanpath_pdu
244- . x224_connection_pdu
245- . context ( "request is missing X224 connection PDU" )
246- . map_err ( CleanPathError :: BadRequest ) ?;
247- server_stream. write_all ( x224_req. as_bytes ( ) ) . await ?;
280+ ( server_stream, None )
281+ } else {
282+ debug ! ( "Preconnection blob sent" ) ;
248283
249- // Receive server X224 connection response
284+ // Send X224 connection request
285+ let x224_req = cleanpath_pdu
286+ . x224_connection_pdu
287+ . context ( "request is missing X224 connection PDU" )
288+ . map_err ( CleanPathError :: BadRequest ) ?;
250289
251- trace ! ( "Receiving X224 response" ) ;
290+ server_stream . write_all ( x224_req . as_bytes ( ) ) . await ? ;
252291
253- let x224_rsp = read_x224_response ( & mut server_stream)
254- . await
255- . with_context ( || format ! ( "read X224 response from {selected_target}" ) )
256- . map_err ( CleanPathError :: BadRequest ) ?;
292+ let server_stream = crate :: tls:: connect ( selected_target. host ( ) . to_owned ( ) , server_stream)
293+ . await
294+ . map_err ( |source| CleanPathError :: TlsHandshake {
295+ source,
296+ target_server : selected_target. to_owned ( ) ,
297+ } ) ?;
298+ debug ! ( "X224 connection request sent" ) ;
257299
258- trace ! ( "Establishing TLS connection with server" ) ;
300+ // Receive server X224 connection response
259301
260- // Establish TLS connection with server
302+ trace ! ( "Receiving X224 response" ) ;
261303
262- let server_stream = crate :: tls:: connect ( selected_target. host ( ) . to_owned ( ) , server_stream)
263- . await
264- . map_err ( |source| CleanPathError :: TlsHandshake {
265- source,
266- target_server : selected_target. to_owned ( ) ,
267- } ) ?;
304+ let x224_rsp = read_x224_response ( & mut server_stream)
305+ . await
306+ . with_context ( || format ! ( "read X224 response from {selected_target}" ) )
307+ . map_err ( CleanPathError :: BadRequest ) ?;
308+
309+ trace ! ( "Establishing TLS connection with server" ) ;
310+
311+ // Establish TLS connection with server
312+
313+ let server_stream = crate :: tls:: connect ( selected_target. host ( ) , server_stream)
314+ . await
315+ . map_err ( |source| CleanPathError :: TlsHandshake {
316+ source,
317+ target_server : selected_target. to_owned ( ) ,
318+ } ) ?;
319+
320+ ( server_stream, Some ( x224_rsp) )
321+ } ;
268322
269- Ok ( CleanPathResult {
323+ return Ok ( CleanPathResult {
270324 destination : selected_target. to_owned ( ) ,
271325 claims,
272326 server_addr,
273327 server_stream,
274328 x224_rsp,
275- } )
329+ } ) ;
276330}
277331
278332#[ allow( clippy:: too_many_arguments) ]
@@ -295,7 +349,7 @@ pub async fn handle(
295349 . await
296350 . context ( "couldn’t read clean cleanpath PDU" ) ?;
297351
298- trace ! ( "Processing RDCleanPath" ) ;
352+ trace ! ( RDCleanPath = ?cleanpath_pdu , "Processing RDCleanPath" ) ;
299353
300354 let CleanPathResult {
301355 claims,
0 commit comments