@@ -164,7 +164,7 @@ struct CleanPathResult {
164
164
destination : TargetAddr ,
165
165
server_addr : SocketAddr ,
166
166
server_stream : tokio_rustls:: client:: TlsStream < tokio:: net:: TcpStream > ,
167
- x224_rsp : Vec < u8 > ,
167
+ x224_rsp : Option < Vec < u8 > > ,
168
168
}
169
169
170
170
async fn process_cleanpath (
@@ -234,45 +234,99 @@ async fn process_cleanpath(
234
234
debug ! ( %selected_target, "Connected to destination server" ) ;
235
235
span. record ( "target" , selected_target. to_string ( ) ) ;
236
236
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
+ } ) ?;
241
279
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" ) ;
248
283
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 ) ?;
250
289
251
- trace ! ( "Receiving X224 response" ) ;
290
+ server_stream . write_all ( x224_req . as_bytes ( ) ) . await ? ;
252
291
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" ) ;
257
299
258
- trace ! ( "Establishing TLS connection with server" ) ;
300
+ // Receive server X224 connection response
259
301
260
- // Establish TLS connection with server
302
+ trace ! ( "Receiving X224 response" ) ;
261
303
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
+ } ;
268
322
269
- Ok ( CleanPathResult {
323
+ return Ok ( CleanPathResult {
270
324
destination : selected_target. to_owned ( ) ,
271
325
claims,
272
326
server_addr,
273
327
server_stream,
274
328
x224_rsp,
275
- } )
329
+ } ) ;
276
330
}
277
331
278
332
#[ allow( clippy:: too_many_arguments) ]
@@ -295,7 +349,7 @@ pub async fn handle(
295
349
. await
296
350
. context ( "couldn’t read clean cleanpath PDU" ) ?;
297
351
298
- trace ! ( "Processing RDCleanPath" ) ;
352
+ trace ! ( RDCleanPath = ?cleanpath_pdu , "Processing RDCleanPath" ) ;
299
353
300
354
let CleanPathResult {
301
355
claims,
0 commit comments