FreeRDP
Loading...
Searching...
No Matches
multitransport.c
1
20#include <winpr/assert.h>
21#include <winpr/collections.h>
22#include <winpr/synch.h>
23#include <winpr/winsock.h>
24#include <freerdp/config.h>
25
26#if !defined(_WIN32)
27#include <netdb.h>
28#include <sys/socket.h>
29#endif
30#include <freerdp/log.h>
31#include <freerdp/multitransport.h>
32#include <freerdp/channels/drdynvc.h>
33
34#include "settings.h"
35#include "rdp.h"
36#include "transport.h"
37#include "rdpemt.h"
38#include "multitransport.h"
39
40typedef enum
41{
42 MT_EVENT_READY,
43 MT_EVENT_FAILED,
44 MT_EVENT_DATA,
45 MT_EVENT_AUTODETECT_REQUEST
46} MT_EVENT_TYPE;
47
48/* Tunnel events travel from the tunnel thread to the thread that runs the main loop, which is
49 * where the Initiate Multitransport Response goes out and where the dynamic channel code is
50 * called. */
51typedef struct
52{
53 MT_EVENT_TYPE type;
54 wStream* s; /* sealed, at position 0 */
55 size_t length;
56} mt_event;
57
58struct rdp_multitransport
59{
60 rdpRdp* rdp;
61
62 MultiTransportRequestCb MtRequest;
63 MultiTransportResponseCb MtResponse;
64
65 /* server-side data */
66 UINT32 reliableReqId;
67
68 BYTE reliableCookie[RDPUDP_COOKIE_LEN];
69 BYTE reliableCookieHash[RDPUDP_COOKIE_HASHLEN];
70
71 /* client-side data */
73 HANDLE event;
74 wQueue* events;
75 rdpEmt* reliable;
76 FreeRDP_MultitransportTunnelState reliableState;
77 BOOL reliableResponseSent;
78
79 /* Written on the thread that runs the main loop, which reads them there without locking:
80 * receiving tunnel data must not wait for a sender, which holds lock while it writes to the
81 * tunnel. The tunnel thread reads them only while it hands data on directly, see
82 * multitransport_direct. */
84 void* custom;
85
86 /* Main loop thread only: the connection was ended because its tunnel broke down. */
87 BOOL endedConnection;
88
89 /* Set by the tunnel thread when it could not queue an event, received data that was
90 * acknowledged to the server already among them. Accessed with Interlocked functions. */
91 LONG tunnelEventLost;
92};
93
94enum
95{
96 RDPTUNNEL_ACTION_CREATEREQUEST = 0x00,
97 RDPTUNNEL_ACTION_CREATERESPONSE = 0x01,
98 RDPTUNNEL_ACTION_DATA = 0x02
99};
100
101#define TAG FREERDP_TAG("core.multitransport")
102
103state_run_t multitransport_recv_request(rdpMultitransport* multi, wStream* s)
104{
105 WINPR_ASSERT(multi);
106 rdpSettings* settings = multi->rdp->settings;
107
108 if (freerdp_settings_get_bool(settings, FreeRDP_ServerMode))
109 {
110 WLog_ERR(TAG, "not expecting a multi-transport request in server mode");
111 return STATE_RUN_FAILED;
112 }
113
114 if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
115 return STATE_RUN_FAILED;
116
117 UINT32 requestId = 0;
118 UINT16 requestedProto = 0;
119 UINT16 reserved = 0;
120 const BYTE* cookie = nullptr;
121
122 Stream_Read_UINT32(s, requestId); /* requestId (4 bytes) */
123 Stream_Read_UINT16(s, requestedProto); /* requestedProtocol (2 bytes) */
124 Stream_Read_UINT16(s, reserved); /* reserved (2 bytes) */
125 cookie = Stream_ConstPointer(s);
126 Stream_Seek(s, RDPUDP_COOKIE_LEN); /* securityCookie (16 bytes) */
127 if (reserved != 0)
128 {
129 /*
130 * If the reserved filed is not 0 the request PDU seems to contain some extra data.
131 * If the reserved value is 1, then two bytes of 0 (probably a version field)
132 * are followed by a JSON payload (not null terminated, until the end of the packet.
133 * There seems to be no dedicated length field)
134 *
135 * for now just ignore all that
136 */
137 WLog_WARN(TAG,
138 "reserved is %" PRIu16 " instead of 0, skipping %" PRIuz "bytes of unknown data",
139 reserved, Stream_GetRemainingLength(s));
140 if (!Stream_SafeSeek(s, Stream_GetRemainingLength(s)))
141 return STATE_RUN_FAILED;
142 }
143
144 WINPR_ASSERT(multi->MtRequest);
145 return multi->MtRequest(multi, requestId, requestedProto, cookie);
146}
147
148WINPR_ATTR_NODISCARD
149static BOOL multitransport_request_send(rdpMultitransport* multi, UINT32 reqId, UINT16 reqProto,
150 const BYTE* cookie)
151{
152 WINPR_ASSERT(multi);
153 WINPR_ASSERT(cookie);
154 UINT16 sec_flags = 0;
155 wStream* s = rdp_message_channel_pdu_init(multi->rdp, &sec_flags);
156 if (!s)
157 return FALSE;
158
159 if (!Stream_EnsureRemainingCapacity(s, 24))
160 {
161 Stream_Release(s);
162 return FALSE;
163 }
164
165 Stream_Write_UINT32(s, reqId); /* requestId (4 bytes) */
166 Stream_Write_UINT16(s, reqProto); /* requestedProtocol (2 bytes) */
167 Stream_Zero(s, 2); /* reserved (2 bytes) */
168 Stream_Write(s, cookie, RDPUDP_COOKIE_LEN); /* securityCookie (16 bytes) */
169
170 return rdp_send_message_channel_pdu(multi->rdp, s, sec_flags | SEC_TRANSPORT_REQ);
171}
172
173state_run_t multitransport_server_request(rdpMultitransport* multi, UINT16 reqProto)
174{
175 WINPR_ASSERT(multi);
176
177 /* TODO: move this static variable to the listener */
178 static UINT32 reqId = 0;
179
180 if (reqProto == INITIATE_REQUEST_PROTOCOL_UDPFECR)
181 {
182 multi->reliableReqId = reqId++;
183 if (winpr_RAND(multi->reliableCookie, sizeof(multi->reliableCookie)) < 0)
184 return STATE_RUN_FAILED;
185
186 return multitransport_request_send(multi, multi->reliableReqId, reqProto,
187 multi->reliableCookie)
188 ? STATE_RUN_SUCCESS
189 : STATE_RUN_FAILED;
190 }
191
192 WLog_ERR(TAG, "only reliable transport is supported");
193 return STATE_RUN_CONTINUE;
194}
195
196BOOL multitransport_client_send_response(rdpMultitransport* multi, UINT32 reqId, HRESULT hr)
197{
198 WINPR_ASSERT(multi);
199
200 UINT16 sec_flags = 0;
201 wStream* s = rdp_message_channel_pdu_init(multi->rdp, &sec_flags);
202 if (!s)
203 return FALSE;
204
205 if (!Stream_EnsureRemainingCapacity(s, 28))
206 {
207 Stream_Release(s);
208 return FALSE;
209 }
210
211 Stream_Write_UINT32(s, reqId); /* requestId (4 bytes) */
212
213 /* [MS-RDPBCGR] 2.2.15.2 Client Initiate Multitransport Response PDU defines this as 4byte
214 * UNSIGNED but https://learn.microsoft.com/en-us/windows/win32/learnwin32/error-codes-in-com
215 * defines this as signed... assume the spec is (implicitly) assuming twos complement. */
216 Stream_Write_INT32(s, hr); /* HResult (4 bytes) */
217 return rdp_send_message_channel_pdu(multi->rdp, s, sec_flags | SEC_TRANSPORT_RSP);
218}
219
220state_run_t multitransport_recv_response(rdpMultitransport* multi, wStream* s)
221{
222 WINPR_ASSERT(multi && multi->rdp);
223 WINPR_ASSERT(s);
224
225 rdpSettings* settings = multi->rdp->settings;
226 WINPR_ASSERT(settings);
227
228 if (!freerdp_settings_get_bool(settings, FreeRDP_ServerMode))
229 {
230 WLog_ERR(TAG, "client is not expecting a multi-transport resp packet");
231 return STATE_RUN_FAILED;
232 }
233
234 if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
235 return STATE_RUN_FAILED;
236
237 UINT32 requestId = 0;
238 UINT32 hr = 0;
239
240 Stream_Read_UINT32(s, requestId); /* requestId (4 bytes) */
241 Stream_Read_UINT32(s, hr); /* hrResponse (4 bytes) */
242
243 state_run_t res = STATE_RUN_SUCCESS;
244 IFCALLRET(multi->MtResponse, res, multi, requestId, hr);
245 return res;
246}
247
248WINPR_ATTR_NODISCARD
249static BOOL multitransport_soft_sync(const rdpMultitransport* multi)
250{
251 WINPR_ASSERT(multi);
252 WINPR_ASSERT(multi->rdp);
253 const UINT32 flags =
254 freerdp_settings_get_uint32(multi->rdp->settings, FreeRDP_MultitransportFlags);
255 return (flags & SOFTSYNC_TCP_TO_UDP) != 0;
256}
257
258WINPR_ATTR_NODISCARD
259static state_run_t multitransport_decline(rdpMultitransport* multi, UINT32 reqId)
260{
261 WINPR_ASSERT(multi);
262 return multitransport_client_send_response(multi, reqId, E_ABORT) ? STATE_RUN_SUCCESS
263 : STATE_RUN_FAILED;
264}
265
266static void mt_event_free(void* obj)
267{
268 mt_event* ev = obj;
269 if (!ev)
270 return;
271 if (ev->s)
272 Stream_Release(ev->s);
273 free(ev);
274}
275
276/* Runs on the tunnel thread, once multitransport_direct has let it. */
277WINPR_ATTR_NODISCARD
278static BOOL multitransport_direct_data(void* custom, wStream* s)
279{
280 rdpMultitransport* multi = custom;
281 WINPR_ASSERT(multi);
282 return multi->callbacks.TunnelDataReceived(multi->custom, TUNNELTYPE_UDPFECR, s);
283}
284
285/* Runs on the tunnel thread. */
286static void multitransport_tunnel_event(void* custom, rdpEmt* emt, RDPEMT_EVENT event,
287 const BYTE* data, size_t length)
288{
289 rdpMultitransport* multi = custom;
290 WINPR_ASSERT(multi);
291 WINPR_UNUSED(emt);
292
293 mt_event* ev = calloc(1, sizeof(mt_event));
294 if (!ev)
295 goto fail;
296
297 switch (event)
298 {
299 case RDPEMT_EVENT_READY:
300 ev->type = MT_EVENT_READY;
301 break;
302 case RDPEMT_EVENT_FAILED:
303 ev->type = MT_EVENT_FAILED;
304 break;
305 case RDPEMT_EVENT_AUTODETECT_REQUEST:
306 case RDPEMT_EVENT_DATA:
307 default:
308 ev->type = (event == RDPEMT_EVENT_AUTODETECT_REQUEST) ? MT_EVENT_AUTODETECT_REQUEST
309 : MT_EVENT_DATA;
310 if (length == 0)
311 break; /* nothing to hand up, malloc(0) may return nullptr */
312 WINPR_ASSERT(data);
313 /* the receiver takes this stream over, the data is not copied again on the way */
314 ev->s = Stream_New(nullptr, length);
315 if (!ev->s)
316 goto fail;
317 Stream_Write(ev->s, data, length);
318 Stream_SealLength(ev->s);
319 Stream_ResetPosition(ev->s);
320 ev->length = length;
321 break;
322 }
323
324 if (!Queue_Enqueue(multi->events, ev))
325 goto fail;
326 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): Queue_Enqueue takes ownership of ev
327 (void)SetEvent(multi->event);
328 return;
329
330fail:
331 /* A lost event leaves the tunnel in an unknown state, the main loop ends the connection. */
332 mt_event_free(ev);
333 /* compare-exchange: WinPR's InterlockedExchange reads the old value non-atomically */
334 if (InterlockedCompareExchange(&multi->tunnelEventLost, 1, 0) == 0)
335 WLog_ERR(TAG, "out of memory, a UDP tunnel event was lost");
336 (void)SetEvent(multi->event);
337}
338
339/* The UDP listener sits on the same address and port as the TCP one. */
340WINPR_ATTR_NODISCARD
341static BOOL multitransport_resolve_server(const rdpSettings* settings,
342 struct sockaddr_storage* address, size_t* length)
343{
344 WINPR_ASSERT(settings);
345 WINPR_ASSERT(address);
346 WINPR_ASSERT(length);
347
348 const char* hostname = freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
349 if (!hostname)
350 return FALSE;
351
352 char port[16] = WINPR_C_ARRAY_INIT;
353 (void)_snprintf(port, sizeof(port), "%" PRIu32,
354 freerdp_settings_get_uint32(settings, FreeRDP_ServerPort));
355
356 struct addrinfo hints = WINPR_C_ARRAY_INIT;
357 hints.ai_family = AF_UNSPEC;
358 hints.ai_socktype = SOCK_DGRAM;
359 struct addrinfo* result = nullptr;
360 if ((getaddrinfo(hostname, port, &hints, &result) != 0) || !result)
361 return FALSE;
362
363 BOOL rc = FALSE;
364 if (result->ai_addrlen <= sizeof(*address))
365 {
366 memcpy(address, result->ai_addr, result->ai_addrlen);
367 *length = result->ai_addrlen;
368 rc = TRUE;
369 }
370 freeaddrinfo(result);
371 return rc;
372}
373
374WINPR_ATTR_NODISCARD
375static state_run_t multitransport_client_request(rdpMultitransport* multi, UINT32 reqId,
376 UINT16 reqProto, const BYTE* cookie)
377{
378 WINPR_ASSERT(multi);
379 rdpRdp* rdp = multi->rdp;
380 WINPR_ASSERT(rdp);
381 rdpSettings* settings = rdp->settings;
382 WINPR_ASSERT(settings);
383
384 WLog_DBG(TAG, "Initiate Multitransport Request: requestId %" PRIu32 ", protocol 0x%04" PRIx16,
385 reqId, reqProto);
386
387 if (reqProto != INITIATE_REQUEST_PROTOCOL_UDPFECR)
388 {
389 WLog_INFO(TAG, "declining lossy UDP, only the reliable transport is implemented");
390 return multitransport_decline(multi, reqId);
391 }
392
393 if (!rdpemt_is_supported())
394 {
395 WLog_DBG(TAG, "declining UDP, built without -DWITH_RDPEUDP=ON");
396 return multitransport_decline(multi, reqId);
397 }
398
399 if (!freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransportUdp))
400 {
401 WLog_DBG(TAG, "declining UDP, switching transport disabled (/multitransport:tcp-only)");
402 return multitransport_decline(multi, reqId);
403 }
404
405 if (!freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport) ||
406 !(freerdp_settings_get_uint32(settings, FreeRDP_MultitransportFlags) &
407 TRANSPORT_TYPE_UDP_FECR))
408 return multitransport_decline(multi, reqId);
409
410 EnterCriticalSection(&multi->lock);
411 const BOOL busy = (multi->reliable != nullptr);
412 LeaveCriticalSection(&multi->lock);
413 if (busy)
414 {
415 WLog_WARN(TAG, "a reliable UDP tunnel already exists, declining request %" PRIu32, reqId);
416 return multitransport_decline(multi, reqId);
417 }
418
419 /* The tunnel's TLS session has to end at the server the main connection was verified
420 * against, which needs the main connection to be TLS directly to that server. */
421 const BYTE* publicKey = nullptr;
422 DWORD publicKeyLength = 0;
423 if (!transport_get_public_key(rdp->transport, &publicKey, &publicKeyLength) || !publicKey ||
424 (publicKeyLength == 0))
425 {
426 WLog_INFO(TAG, "declining UDP, the main connection does not use TLS");
427 return multitransport_decline(multi, reqId);
428 }
429
430 /* The transport says whether the gateway is used: with /gateway:usage-method:detect the
431 * connection can go to the server directly while FreeRDP_GatewayEnabled stays set. */
432 const TRANSPORT_LAYER layer = transport_get_layer(rdp->transport);
433 if ((layer == TRANSPORT_LAYER_TSG) || (layer == TRANSPORT_LAYER_TSG_TLS))
434 {
435 WLog_INFO(TAG, "declining UDP, the main connection goes through a gateway");
436 return multitransport_decline(multi, reqId);
437 }
438 if (freerdp_settings_get_uint32(settings, FreeRDP_ProxyType) != PROXY_TYPE_NONE)
439 {
440 /* the TCP peer is the proxy, the server's UDP port is not reachable through it */
441 WLog_INFO(TAG, "declining UDP, the main connection goes through a proxy");
442 return multitransport_decline(multi, reqId);
443 }
444
445 struct sockaddr_storage address = WINPR_C_ARRAY_INIT;
446 size_t addressLength = 0;
447 if (!transport_get_peer_address(rdp->transport, &address, &addressLength) &&
448 !multitransport_resolve_server(settings, &address, &addressLength))
449 {
450 WLog_INFO(TAG, "declining UDP, the server address is unknown");
451 return multitransport_decline(multi, reqId);
452 }
453
454 rdpEmt* emt = rdpemt_new(WLog_Get(TAG), reqId, cookie,
455 freerdp_settings_get_string(settings, FreeRDP_ServerHostname),
456 publicKey, publicKeyLength, multitransport_tunnel_event, multi);
457 if (!emt)
458 return multitransport_decline(multi, reqId);
459
460 EnterCriticalSection(&multi->lock);
461 multi->reliable = emt;
462 multi->reliableState = FREERDP_MULTITRANSPORT_TUNNEL_PENDING;
463 multi->reliableResponseSent = FALSE;
464 LeaveCriticalSection(&multi->lock);
465
466 if (!rdpemt_start(emt, (const struct sockaddr*)&address, addressLength))
467 {
468 EnterCriticalSection(&multi->lock);
469 multi->reliable = nullptr;
470 multi->reliableState = FREERDP_MULTITRANSPORT_TUNNEL_NONE;
471 LeaveCriticalSection(&multi->lock);
472 rdpemt_free(emt);
473 return multitransport_decline(multi, reqId);
474 }
475
476 /* The response goes out once the tunnel is up, or has failed. Answering before the server
477 * has accepted the tunnel makes it drop the session. */
478 WLog_INFO(TAG, "setting up a reliable UDP tunnel for request %" PRIu32, reqId);
479 return STATE_RUN_SUCCESS;
480}
481
482/* A failed callback ends the connection like a broken main transport. */
483WINPR_ATTR_NODISCARD
484static BOOL multitransport_callback_failed(rdpMultitransport* multi, const char* what)
485{
486 WINPR_ASSERT(multi);
487 WINPR_ASSERT(multi->rdp);
488 WINPR_ASSERT(what);
489
490 WLog_ERR(TAG, "the dynamic channel code failed to handle %s, ending the connection", what);
491 freerdp_set_last_error_if_not(multi->rdp->context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
492 return FALSE;
493}
494
495WINPR_ATTR_NODISCARD
496static BOOL multitransport_notify_state(rdpMultitransport* multi,
497 FreeRDP_MultitransportTunnelState state)
498{
499 WINPR_ASSERT(multi);
500
501 if (!multi->callbacks.TunnelStateChanged)
502 return TRUE;
503 if (!multi->callbacks.TunnelStateChanged(multi->custom, TUNNELTYPE_UDPFECR, state))
504 return multitransport_callback_failed(multi, "a tunnel state change");
505 return TRUE;
506}
507
508WINPR_ATTR_NODISCARD
509static BOOL multitransport_deliver_data(rdpMultitransport* multi, mt_event* ev)
510{
511 WINPR_ASSERT(multi);
512 WINPR_ASSERT(multi->rdp);
513 WINPR_ASSERT(ev);
514
515 /* [MS-RDPBCGR] 3.2.5.17: a bandwidth measurement counts the data after the tunnel header */
516 rdpAutoDetect* autodetect = multi->rdp->autodetect;
517 if (autodetect && autodetect->bandwidthMeasureStarted)
518 autodetect->bandwidthMeasureByteCount += WINPR_ASSERTING_INT_CAST(UINT32, ev->length);
519
520 if (!multi->callbacks.TunnelDataReceived || !ev->s)
521 {
522 WLog_DBG(TAG, "dropping %" PRIuz " bytes of tunnel data, no receiver", ev->length);
523 return TRUE;
524 }
525 /* The data was acknowledged to the server, losing it leaves a hole in the channel stream. */
526 wStream* s = ev->s;
527 ev->s = nullptr; /* the callback owns it now */
528 if (!multi->callbacks.TunnelDataReceived(multi->custom, TUNNELTYPE_UDPFECR, s))
529 return multitransport_callback_failed(multi, "tunnel data");
530 return TRUE;
531}
532
533WINPR_ATTR_NODISCARD
534static BOOL multitransport_deliver_autodetect(rdpMultitransport* multi, const mt_event* ev)
535{
536 WINPR_ASSERT(multi);
537 WINPR_ASSERT(multi->rdp);
538 WINPR_ASSERT(ev);
539
540 if (!ev->s)
541 return TRUE;
542 return !state_run_failed(
543 autodetect_recv_tunnel_request(multi->rdp->autodetect, RDP_TRANSPORT_UDP_R, ev->s));
544}
545
546WINPR_ATTR_NODISCARD
547static BOOL multitransport_handle_event(rdpMultitransport* multi, mt_event* ev)
548{
549 WINPR_ASSERT(multi);
550 WINPR_ASSERT(ev);
551
552 switch (ev->type)
553 {
554 case MT_EVENT_READY:
555 {
556 EnterCriticalSection(&multi->lock);
557 const UINT32 reqId = multi->reliable ? rdpemt_get_request_id(multi->reliable) : 0;
558 const BOOL haveTunnel = multi->reliable != nullptr;
559 LeaveCriticalSection(&multi->lock);
560 if (!haveTunnel)
561 return TRUE;
562
563 /* [MS-RDPBCGR] 2.2.15.2: S_OK only goes to a server that does Soft-Sync. Without
564 * it, success is not reported at all. */
565 if (multitransport_soft_sync(multi))
566 {
567 if (!multitransport_client_send_response(multi, reqId, S_OK))
568 return FALSE;
569 }
570
571 EnterCriticalSection(&multi->lock);
572 multi->reliableResponseSent = TRUE;
573 multi->reliableState = FREERDP_MULTITRANSPORT_TUNNEL_READY;
574 LeaveCriticalSection(&multi->lock);
575 if (!multitransport_notify_state(multi, FREERDP_MULTITRANSPORT_TUNNEL_READY))
576 return FALSE;
577 }
578 break;
579
580 case MT_EVENT_FAILED:
581 {
582 EnterCriticalSection(&multi->lock);
583 rdpEmt* emt = multi->reliable;
584 const BOOL responseSent = multi->reliableResponseSent;
585 const BOOL wasReady = multi->reliableState == FREERDP_MULTITRANSPORT_TUNNEL_READY;
586 multi->reliable = nullptr;
587 multi->reliableState = FREERDP_MULTITRANSPORT_TUNNEL_NONE;
588 LeaveCriticalSection(&multi->lock);
589 if (!emt)
590 return TRUE;
591
592 const UINT32 reqId = rdpemt_get_request_id(emt);
593 rdpemt_free(emt);
594
595 /* Soft-Sync only moves channels from TCP to UDP ([MS-RDPEDYC] 3.1.5.3), there is no
596 * way back. What was in flight on the tunnel is lost in both directions, so the
597 * channels on it cannot carry on over TCP: the connection ends like a broken main
598 * transport, and the client reconnects. */
599 if (wasReady)
600 {
601 WLog_WARN(TAG, "the UDP tunnel broke down, ending the connection");
602 multi->endedConnection = TRUE;
603 freerdp_set_last_error_if_not(multi->rdp->context,
604 FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
605 return FALSE;
606 }
607 if (!responseSent)
608 {
609 if (!multitransport_client_send_response(multi, reqId, E_ABORT))
610 return FALSE;
611 }
612 if (!multitransport_notify_state(multi, FREERDP_MULTITRANSPORT_TUNNEL_NONE))
613 return FALSE;
614 }
615 break;
616
617 case MT_EVENT_AUTODETECT_REQUEST:
618 return multitransport_deliver_autodetect(multi, ev);
619
620 case MT_EVENT_DATA:
621 default:
622 return multitransport_deliver_data(multi, ev);
623 }
624 return TRUE;
625}
626
627/* Main loop thread. Once the channels know the tunnel is ready and no data waits in events, a
628 * channel plugin with its own thread gets tunnel data on the tunnel thread, without the way
629 * through the main loop. */
630static void multitransport_direct(rdpMultitransport* multi)
631{
632 WINPR_ASSERT(multi);
633
634 /* reliable and reliableState change on this thread only */
635 if (!multi->reliable || (multi->reliableState != FREERDP_MULTITRANSPORT_TUNNEL_READY) ||
636 !multi->callbacks.DataOnTunnelThread || !multi->callbacks.TunnelDataReceived)
637 return;
638 if (!rdpemt_set_receiver(multi->reliable, multitransport_direct_data, multi, multi->events))
639 WLog_DBG(TAG, "tunnel data still waits in events, the main loop comes back for it");
640}
641
642BOOL multitransport_check(rdpMultitransport* multi)
643{
644 WINPR_ASSERT(multi);
645
646 (void)ResetEvent(multi->event);
647 if (InterlockedCompareExchange(&multi->tunnelEventLost, 0, 0) != 0)
648 {
649 WLog_ERR(TAG, "out of memory queueing UDP tunnel events, ending the connection");
650 freerdp_set_last_error_if_not(multi->rdp->context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED);
651 return FALSE;
652 }
653#if defined(WITH_RDPEUDP)
654 /* [MS-RDPBCGR] 3.2.5.17: tunnel data that went to the channels directly counts as the main
655 * loop gets here, like the data it hands on below */
656 if (multi->reliable)
657 {
658 const UINT32 direct = rdpemt_take_direct_bytes(multi->reliable);
659 rdpAutoDetect* autodetect = multi->rdp->autodetect;
660 if (autodetect && autodetect->bandwidthMeasureStarted)
661 autodetect->bandwidthMeasureByteCount += direct;
662 }
663#endif
664
665 BOOL handled = FALSE;
666 while (Queue_Count(multi->events) > 0)
667 {
668 mt_event* ev = Queue_Dequeue(multi->events);
669 if (!ev)
670 break;
671 const BOOL rc = multitransport_handle_event(multi, ev);
672 mt_event_free(ev);
673 if (!rc)
674 return FALSE;
675 handled = TRUE;
676 }
677 if (handled)
678 multitransport_direct(multi);
679 return TRUE;
680}
681
682HANDLE multitransport_get_event_handle(rdpMultitransport* multi)
683{
684 WINPR_ASSERT(multi);
685 return multi->event;
686}
687
688BOOL multitransport_send_autodetect_response(rdpMultitransport* multi, const BYTE* data,
689 size_t length)
690{
691 WINPR_ASSERT(multi);
692
693 BOOL rc = FALSE;
694 EnterCriticalSection(&multi->lock);
695 if (multi->reliable && (multi->reliableState == FREERDP_MULTITRANSPORT_TUNNEL_READY))
696 rc = rdpemt_send_subheader(multi->reliable, data, length);
697 LeaveCriticalSection(&multi->lock);
698 return rc;
699}
700
701BOOL multitransport_ended_connection(const rdpMultitransport* multi)
702{
703 WINPR_ASSERT(multi);
704 return multi->endedConnection;
705}
706
707void multitransport_reset(rdpMultitransport* multi)
708{
709 if (!multi)
710 return;
711
712 EnterCriticalSection(&multi->lock);
713 rdpEmt* emt = multi->reliable;
714 multi->reliable = nullptr;
715 multi->reliableState = FREERDP_MULTITRANSPORT_TUNNEL_NONE;
716 multi->reliableResponseSent = FALSE;
717 LeaveCriticalSection(&multi->lock);
718 multi->endedConnection = FALSE;
719
720 rdpemt_free(emt);
721 if (multi->events)
722 {
723 /* not Queue_Clear(), it misses the events of a queue that is exactly full */
724 mt_event* ev = nullptr;
725 while ((ev = Queue_Dequeue(multi->events)))
726 mt_event_free(ev);
727 }
728 if (InterlockedCompareExchange(&multi->tunnelEventLost, 0, 1) != 0)
729 WLog_DBG(TAG, "a lost UDP tunnel event no longer matters, the tunnel is gone");
730 if (multi->event)
731 (void)ResetEvent(multi->event);
732}
733
734WINPR_ATTR_NODISCARD
735static state_run_t multitransport_server_handle_response(rdpMultitransport* multi,
736 WINPR_ATTR_UNUSED UINT32 reqId,
737 WINPR_ATTR_UNUSED UINT32 hrResponse)
738{
739 WINPR_ASSERT(multi);
740 rdpRdp* rdp = multi->rdp;
741
742 /* Only the answer to the request sent in MULTITRANSPORT_BOOTSTRAPPING_REQUEST continues the
743 * connection sequence. The message channel stays open for the whole session, a response in
744 * any other state must not restart the capability exchange. */
745 if (rdp_get_state(rdp) != CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_RESPONSE)
746 {
747 WLog_WARN(TAG, "ignoring multi-transport response in state %s, expected %s",
748 rdp_get_state_string(rdp),
749 rdp_state_string(CONNECTION_STATE_MULTITRANSPORT_BOOTSTRAPPING_RESPONSE));
750 return STATE_RUN_FAILED;
751 }
752
753 if (reqId != multi->reliableReqId)
754 {
755 WLog_WARN(
756 TAG, "multi-transport response for request %" PRIu32 ", expected %" PRIu32 ", ignoring",
757 reqId, multi->reliableReqId);
758 }
759
760 if (!rdp_server_transition_to_state(rdp, CONNECTION_STATE_CAPABILITIES_EXCHANGE_DEMAND_ACTIVE))
761 return STATE_RUN_FAILED;
762
763 return STATE_RUN_CONTINUE;
764}
765
766rdpMultitransport* multitransport_new(rdpRdp* rdp, WINPR_ATTR_UNUSED UINT16 protocol)
767{
768 WINPR_ASSERT(rdp);
769
770 rdpSettings* settings = rdp->settings;
771 WINPR_ASSERT(settings);
772
773 rdpMultitransport* multi = calloc(1, sizeof(rdpMultitransport));
774 if (!multi)
775 return nullptr;
776
777 if (!InitializeCriticalSectionAndSpinCount(&multi->lock, 4000))
778 {
779 free(multi);
780 return nullptr;
781 }
782
783 multi->rdp = rdp;
784 multi->event = CreateEvent(nullptr, TRUE, FALSE, nullptr);
785 multi->events = Queue_New(TRUE, -1, -1);
786 if (!multi->event || !multi->events)
787 goto fail;
788 {
789 wObject* obj = Queue_Object(multi->events);
790 obj->fnObjectFree = mt_event_free;
791 }
792
793 if (freerdp_settings_get_bool(settings, FreeRDP_ServerMode))
794 {
795 multi->MtResponse = multitransport_server_handle_response;
796 }
797 else
798 {
799 multi->MtRequest = multitransport_client_request;
800 }
801
802 return multi;
803fail:
804 WINPR_PRAGMA_DIAG_PUSH
805 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
806 multitransport_free(multi);
807 WINPR_PRAGMA_DIAG_POP
808 return nullptr;
809}
810
811void multitransport_free(rdpMultitransport* multitransport)
812{
813 if (!multitransport)
814 return;
815
816 multitransport_reset(multitransport);
817 Queue_Free(multitransport->events);
818 if (multitransport->event)
819 (void)CloseHandle(multitransport->event);
820 DeleteCriticalSection(&multitransport->lock);
821 free(multitransport);
822}
823
824WINPR_ATTR_NODISCARD
825static rdpMultitransport* multitransport_from_context(rdpContext* context)
826{
827 if (!context || !context->rdp)
828 return nullptr;
829 return context->rdp->multitransport;
830}
831
832BOOL freerdp_multitransport_set_dvc_callbacks(rdpContext* context,
833 const FreeRDP_MultitransportDvcCallbacks* callbacks,
834 void* custom)
835{
836 rdpMultitransport* multi = multitransport_from_context(context);
837 if (!multi)
838 return FALSE;
839
840 /* multitransport_check() reads the hooks without locking, it runs on this thread (channel
841 * events come from freerdp_connect() and freerdp_disconnect(), tunnel events from
842 * freerdp_check_event_handles()). The tunnel thread may be in the old TunnelDataReceived:
843 * once it is back to raising events, it is out of it. */
844 if (multi->reliable)
845 rdpemt_clear_receiver(multi->reliable);
846 if (callbacks)
847 multi->callbacks = *callbacks;
848 else
849 memset(&multi->callbacks, 0, sizeof(multi->callbacks));
850 multi->custom = callbacks ? custom : nullptr;
851
852 multitransport_direct(multi);
853 return TRUE;
854}
855
856FreeRDP_MultitransportTunnelState freerdp_multitransport_get_tunnel_state(rdpContext* context,
857 UINT32 tunnelType)
858{
859 rdpMultitransport* multi = multitransport_from_context(context);
860 if (!multi || (tunnelType != TUNNELTYPE_UDPFECR))
861 return FREERDP_MULTITRANSPORT_TUNNEL_NONE;
862
863 EnterCriticalSection(&multi->lock);
864 const FreeRDP_MultitransportTunnelState state = multi->reliableState;
865 LeaveCriticalSection(&multi->lock);
866 return state;
867}
868
869BOOL freerdp_multitransport_send_dvc(rdpContext* context, UINT32 tunnelType, const BYTE* data,
870 size_t length)
871{
872 rdpMultitransport* multi = multitransport_from_context(context);
873 if (!multi || (tunnelType != TUNNELTYPE_UDPFECR))
874 return FALSE;
875
876 BOOL rc = FALSE;
877 EnterCriticalSection(&multi->lock);
878 if (multi->reliable && (multi->reliableState == FREERDP_MULTITRANSPORT_TUNNEL_READY))
879 rc = rdpemt_send_data(multi->reliable, data, length);
880 LeaveCriticalSection(&multi->lock);
881 return rc;
882}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
Dynamic virtual channel hooks of the client side tunnels.
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59