@@ -157,7 +157,7 @@ struct CleanPathResult {
157
157
destination : TargetAddr ,
158
158
server_addr : SocketAddr ,
159
159
server_stream : tokio_rustls:: client:: TlsStream < tokio:: net:: TcpStream > ,
160
- x224_rsp : Vec < u8 > ,
160
+ x224_rsp : Option < Vec < u8 > > ,
161
161
}
162
162
163
163
async fn process_cleanpath (
@@ -212,45 +212,99 @@ async fn process_cleanpath(
212
212
debug ! ( %selected_target, "Connected to destination server" ) ;
213
213
span. record ( "target" , selected_target. to_string ( ) ) ;
214
214
215
- // Send preconnection blob if applicable
216
- if let Some ( pcb) = cleanpath_pdu. preconnection_blob {
217
- server_stream. write_all ( pcb. as_bytes ( ) ) . await ?;
218
- }
215
+ // Preconnection Blob (PCB) is currently only used for Hyper-V VMs.
216
+ //
217
+ // Connection sequence with Hyper-V VMs (PCB enabled):
218
+ // ┌─────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
219
+ // │ handled by │ │ handled by IronRDP client │
220
+ // │ Gateway │ │ │
221
+ // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘
222
+ // │PCB → TLS handshake │ → │CredSSP → X224 connection request → X224 connection response │
223
+ // └─────────────────────┘ └─────────────────────────────────────────────────────────────┘
224
+ //
225
+ // Connection sequence without Hyper-V VMs (PCB disabled):
226
+ // ┌─────────────────────────────────────────────────────────────┐ ┌──────────────────────┐
227
+ // │ handled by Gateway │ │ handled by IronRDP │
228
+ // │ │ │ client │
229
+ // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘
230
+ // │X224 connection request → X224 connection response → TLS hs │ → │CredSSP → ... │
231
+ // └─────────────────────────────────────────────────────────────┘ └──────────────────────┘
232
+ //
233
+ // Summary:
234
+ // - With PCB: Gateway handles (1) sending PCB, (2) TLS handshake, then leaves CredSSP
235
+ // and X224 connection request/response to IronRDP client
236
+ // - Without PCB: Gateway handles (1) X224 connection request, (2) X224 connection response,
237
+ // then leaves TLS handshake and CredSSP to IronRDP client
238
+ let ( server_stream, x224_rsp) = if let Some ( pcb_string) = cleanpath_pdu. preconnection_blob {
239
+ let pcb = ironrdp_pdu:: pcb:: PreconnectionBlob {
240
+ version : ironrdp_pdu:: pcb:: PcbVersion :: V2 ,
241
+ id : 0 ,
242
+ v2_payload : Some ( pcb_string) ,
243
+ } ;
244
+
245
+ let encoded = ironrdp_core:: encode_vec ( & pcb)
246
+ . context ( "failed to encode preconnection blob" )
247
+ . map_err ( CleanPathError :: BadRequest ) ?;
248
+
249
+ server_stream. write_all ( & encoded) . await ?;
250
+
251
+ let server_stream = crate :: tls:: connect ( selected_target. host ( ) , server_stream)
252
+ . await
253
+ . map_err ( |source| CleanPathError :: TlsHandshake {
254
+ source,
255
+ target_server : selected_target. to_owned ( ) ,
256
+ } ) ?;
219
257
220
- // Send X224 connection request
221
- let x224_req = cleanpath_pdu
222
- . x224_connection_pdu
223
- . context ( "request is missing X224 connection PDU" )
224
- . map_err ( CleanPathError :: BadRequest ) ?;
225
- server_stream. write_all ( x224_req. as_bytes ( ) ) . await ?;
258
+ ( server_stream, None )
259
+ } else {
260
+ debug ! ( "Preconnection blob sent" ) ;
226
261
227
- // Receive server X224 connection response
262
+ // Send X224 connection request
263
+ let x224_req = cleanpath_pdu
264
+ . x224_connection_pdu
265
+ . context ( "request is missing X224 connection PDU" )
266
+ . map_err ( CleanPathError :: BadRequest ) ?;
228
267
229
- trace ! ( "Receiving X224 response" ) ;
268
+ server_stream . write_all ( x224_req . as_bytes ( ) ) . await ? ;
230
269
231
- let x224_rsp = read_x224_response ( & mut server_stream)
232
- . await
233
- . with_context ( || format ! ( "read X224 response from {selected_target}" ) )
234
- . map_err ( CleanPathError :: BadRequest ) ?;
270
+ let server_stream = crate :: tls:: connect ( selected_target. host ( ) . to_owned ( ) , server_stream)
271
+ . await
272
+ . map_err ( |source| CleanPathError :: TlsHandshake {
273
+ source,
274
+ target_server : selected_target. to_owned ( ) ,
275
+ } ) ?;
276
+ debug ! ( "X224 connection request sent" ) ;
235
277
236
- trace ! ( "Establishing TLS connection with server" ) ;
278
+ // Receive server X224 connection response
237
279
238
- // Establish TLS connection with server
280
+ trace ! ( "Receiving X224 response" ) ;
239
281
240
- let server_stream = crate :: tls:: connect ( selected_target. host ( ) . to_owned ( ) , server_stream)
241
- . await
242
- . map_err ( |source| CleanPathError :: TlsHandshake {
243
- source,
244
- target_server : selected_target. to_owned ( ) ,
245
- } ) ?;
282
+ let x224_rsp = read_x224_response ( & mut server_stream)
283
+ . await
284
+ . with_context ( || format ! ( "read X224 response from {selected_target}" ) )
285
+ . map_err ( CleanPathError :: BadRequest ) ?;
286
+
287
+ trace ! ( "Establishing TLS connection with server" ) ;
288
+
289
+ // Establish TLS connection with server
290
+
291
+ let server_stream = crate :: tls:: connect ( selected_target. host ( ) , server_stream)
292
+ . await
293
+ . map_err ( |source| CleanPathError :: TlsHandshake {
294
+ source,
295
+ target_server : selected_target. to_owned ( ) ,
296
+ } ) ?;
297
+
298
+ ( server_stream, Some ( x224_rsp) )
299
+ } ;
246
300
247
- Ok ( CleanPathResult {
301
+ return Ok ( CleanPathResult {
248
302
destination : selected_target. to_owned ( ) ,
249
303
claims,
250
304
server_addr,
251
305
server_stream,
252
306
x224_rsp,
253
- } )
307
+ } ) ;
254
308
}
255
309
256
310
#[ allow( clippy:: too_many_arguments) ]
@@ -273,7 +327,7 @@ pub async fn handle(
273
327
. await
274
328
. context ( "couldn’t read clean cleanpath PDU" ) ?;
275
329
276
- trace ! ( "Processing RDCleanPath" ) ;
330
+ trace ! ( RDCleanPath = ?cleanpath_pdu , "Processing RDCleanPath" ) ;
277
331
278
332
let CleanPathResult {
279
333
claims,
0 commit comments