FreeRDP
Loading...
Searching...
No Matches
shadow_autodetect.c
1
19#include <freerdp/config.h>
20
21#include <winpr/assert.h>
22#include <winpr/sysinfo.h>
23
24#include <freerdp/freerdp.h>
25#include <freerdp/autodetect.h>
26#include <freerdp/log.h>
27
28#include "shadow.h"
29#include "shadow_autodetect.h"
30
31#define TAG SERVER_TAG("shadow.autodetect")
32
33/* an RTT Measure Request (6 bytes) and a Network Characteristics Result (18 bytes) this often */
34#define SHADOW_AUTODETECT_RTT_INTERVAL_MS 2000
35/* an unanswered RTT Measure Request is given up after this long */
36#define SHADOW_AUTODETECT_RTT_GIVEUP_MS 10000
37/* the connect-time burst: this many Bandwidth Measure Payload messages and the Stop with one
38 * more payload of this size, 32 KiB in all, before the licensing */
39#define SHADOW_AUTODETECT_BW_PAYLOADS 3
40#define SHADOW_AUTODETECT_BW_PAYLOAD_SIZE 8192
41
42typedef enum
43{
44 SHADOW_AUTODETECT_PHASE_NONE,
45 SHADOW_AUTODETECT_PHASE_CONNECT_RTT,
46 SHADOW_AUTODETECT_PHASE_CONNECT_BW,
47 SHADOW_AUTODETECT_PHASE_CONTINUOUS
48} SHADOW_AUTODETECT_PHASE;
49
50typedef struct
51{
52 rdpShadowClient* client;
53 SHADOW_AUTODETECT_PHASE phase;
54 UINT16 sequenceNumber;
55
56 BOOL rttPending;
57 UINT16 rttSequenceNumber;
58 UINT64 rttSentNs;
59 UINT64 rttSentMs;
60 UINT64 nextRttMs;
61
62 UINT64 rttSamples;
63 UINT64 baseRttUs;
64 UINT64 averageRttUs;
65
66 BOOL bandwidthDone;
67 UINT32 bandwidthKbps;
68} ShadowAutoDetect;
69
70static ShadowAutoDetect* shadow_autodetect_get(rdpAutoDetect* autodetect)
71{
72 WINPR_ASSERT(autodetect);
73 return (ShadowAutoDetect*)autodetect->custom;
74}
75
76static BOOL shadow_autodetect_enabled(const ShadowAutoDetect* ad)
77{
78 WINPR_ASSERT(ad);
79 WINPR_ASSERT(ad->client);
80 return freerdp_settings_get_bool(ad->client->context.settings, FreeRDP_NetworkAutoDetect);
81}
82
83/* the Result carries milliseconds: anything measured is at least 1 ms, so that a fast link
84 * does not read as not measured */
85static UINT32 shadow_autodetect_us_to_ms(UINT64 us)
86{
87 const UINT64 ms = (us + 999) / 1000;
88 return (UINT32)MIN(MAX(ms, 1), UINT32_MAX);
89}
90
91static BOOL shadow_autodetect_send_rtt(ShadowAutoDetect* ad, rdpAutoDetect* autodetect)
92{
93 WINPR_ASSERT(ad);
94 WINPR_ASSERT(autodetect);
95
96 if (!autodetect->RTTMeasureRequest)
97 return FALSE;
98
99 ad->rttSequenceNumber = ad->sequenceNumber++;
100 ad->rttPending = TRUE;
101 ad->rttSentMs = GetTickCount64();
102 ad->rttSentNs = winpr_GetTickCount64NS();
103 return autodetect->RTTMeasureRequest(autodetect, RDP_TRANSPORT_TCP, ad->rttSequenceNumber);
104}
105
106static BOOL shadow_autodetect_send_bandwidth_burst(ShadowAutoDetect* ad, rdpAutoDetect* autodetect)
107{
108 WINPR_ASSERT(ad);
109 WINPR_ASSERT(autodetect);
110
111 if (!autodetect->BandwidthMeasureStart || !autodetect->BandwidthMeasurePayload ||
112 !autodetect->BandwidthMeasureStop)
113 return FALSE;
114
115 const UINT16 sequenceNumber = ad->sequenceNumber++;
116 if (!autodetect->BandwidthMeasureStart(autodetect, RDP_TRANSPORT_TCP, sequenceNumber))
117 return FALSE;
118 for (size_t x = 0; x < SHADOW_AUTODETECT_BW_PAYLOADS; x++)
119 {
120 if (!autodetect->BandwidthMeasurePayload(autodetect, RDP_TRANSPORT_TCP, sequenceNumber,
121 SHADOW_AUTODETECT_BW_PAYLOAD_SIZE))
122 return FALSE;
123 }
124 return autodetect->BandwidthMeasureStop(autodetect, RDP_TRANSPORT_TCP, sequenceNumber,
125 SHADOW_AUTODETECT_BW_PAYLOAD_SIZE);
126}
127
128static BOOL shadow_autodetect_send_result(ShadowAutoDetect* ad, rdpAutoDetect* autodetect)
129{
130 WINPR_ASSERT(ad);
131 WINPR_ASSERT(autodetect);
132
133 if (!autodetect->NetworkCharacteristicsResult || (ad->rttSamples == 0))
134 return TRUE;
135
136 rdpNetworkCharacteristicsResult result = { .type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_AVG_RTT,
137 .baseRTT = shadow_autodetect_us_to_ms(ad->baseRttUs),
138 .averageRTT =
139 shadow_autodetect_us_to_ms(ad->averageRttUs),
140 .bandwidth = 0 };
141 if (ad->bandwidthKbps > 0)
142 {
143 result.type = RDP_NETCHAR_RESULT_TYPE_BASE_RTT_BW_AVG_RTT;
144 result.bandwidth = ad->bandwidthKbps;
145 }
146
147 WLog_DBG(TAG,
148 "Network Characteristics Result: base RTT %" PRIu32 " ms, average RTT %" PRIu32
149 " ms, bandwidth %" PRIu32 " kbit/s",
150 result.baseRTT, result.averageRTT, result.bandwidth);
151 return autodetect->NetworkCharacteristicsResult(autodetect, RDP_TRANSPORT_TCP,
152 ad->sequenceNumber++, &result);
153}
154
155static BOOL shadow_autodetect_rtt_response(rdpAutoDetect* autodetect,
156 WINPR_ATTR_UNUSED RDP_TRANSPORT_TYPE transport,
157 UINT16 sequenceNumber)
158{
159 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
160 if (!ad || !ad->rttPending || (sequenceNumber != ad->rttSequenceNumber))
161 return TRUE;
162
163 ad->rttPending = FALSE;
164 const UINT64 now = winpr_GetTickCount64NS();
165 const UINT64 sample = (now > ad->rttSentNs) ? (now - ad->rttSentNs) / 1000 : 0;
166
167 /* the lowest one seen, and a running average as TCP's smoothed RTT (RFC 6298, 1/8) */
168 if ((ad->rttSamples == 0) || (sample < ad->baseRttUs))
169 ad->baseRttUs = sample;
170 if (ad->rttSamples == 0)
171 ad->averageRttUs = sample;
172 else
173 ad->averageRttUs = (ad->averageRttUs * 7 + sample) / 8;
174 ad->rttSamples++;
175
176 WLog_DBG(TAG, "RTT %" PRIu64 " us (base %" PRIu64 " us, average %" PRIu64 " us)", sample,
177 ad->baseRttUs, ad->averageRttUs);
178
179 /* at connect time the progress callback sends the next step */
180 if (ad->phase == SHADOW_AUTODETECT_PHASE_CONTINUOUS)
181 return shadow_autodetect_send_result(ad, autodetect);
182 return TRUE;
183}
184
185static BOOL shadow_autodetect_bandwidth_results(rdpAutoDetect* autodetect,
186 WINPR_ATTR_UNUSED RDP_TRANSPORT_TYPE transport,
187 WINPR_ATTR_UNUSED UINT16 sequenceNumber,
188 UINT16 responseType, UINT32 timeDelta,
189 UINT32 byteCount)
190{
191 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
192 if (!ad || (responseType != RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME))
193 return TRUE;
194
195 /* bits per millisecond are kbit/s; a burst that came in less than a millisecond is counted
196 * as 1 ms, so the bandwidth is then at least the value shown (262 Mbit/s for 32 KiB) */
197 const UINT64 kbps = (8ull * byteCount) / MAX(timeDelta, 1);
198 ad->bandwidthKbps = (UINT32)MIN(MAX(kbps, 1), UINT32_MAX);
199 ad->bandwidthDone = TRUE;
200 WLog_INFO(TAG, "bandwidth %" PRIu32 " kbit/s (%" PRIu32 " bytes in %" PRIu32 " ms)",
201 ad->bandwidthKbps, byteCount, timeDelta);
202 return TRUE;
203}
204
205/* an auto-reconnect may answer the burst with the values the client had before */
206static BOOL shadow_autodetect_netchar_sync(rdpAutoDetect* autodetect,
207 WINPR_ATTR_UNUSED RDP_TRANSPORT_TYPE transport,
208 WINPR_ATTR_UNUSED UINT16 sequenceNumber,
209 UINT32 bandwidth, UINT32 rtt)
210{
211 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
212 if (!ad)
213 return TRUE;
214
215 ad->bandwidthKbps = bandwidth;
216 ad->bandwidthDone = TRUE;
217 if ((ad->rttSamples == 0) && (rtt > 0))
218 {
219 ad->baseRttUs = ad->averageRttUs = 1000ull * rtt;
220 ad->rttSamples = 1;
221 }
222 WLog_INFO(TAG, "bandwidth %" PRIu32 " kbit/s, RTT %" PRIu32 " ms (from the client)", bandwidth,
223 rtt);
224 return TRUE;
225}
226
227static FREERDP_AUTODETECT_STATE shadow_autodetect_connect_time_begin(rdpAutoDetect* autodetect)
228{
229 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
230 WINPR_ASSERT(ad);
231
232 ad->phase = SHADOW_AUTODETECT_PHASE_CONNECT_RTT;
233 if (!shadow_autodetect_send_rtt(ad, autodetect))
234 return FREERDP_AUTODETECT_STATE_FAIL;
235 return FREERDP_AUTODETECT_STATE_REQUEST;
236}
237
238static FREERDP_AUTODETECT_STATE shadow_autodetect_connect_time_progress(rdpAutoDetect* autodetect)
239{
240 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
241 WINPR_ASSERT(ad);
242
243 if (autodetect->state == FREERDP_AUTODETECT_STATE_FAIL)
244 return FREERDP_AUTODETECT_STATE_FAIL;
245
246 switch (ad->phase)
247 {
248 case SHADOW_AUTODETECT_PHASE_CONNECT_RTT:
249 if (ad->rttPending)
250 return FREERDP_AUTODETECT_STATE_REQUEST;
251 ad->phase = SHADOW_AUTODETECT_PHASE_CONNECT_BW;
252 if (!shadow_autodetect_send_bandwidth_burst(ad, autodetect))
253 return FREERDP_AUTODETECT_STATE_FAIL;
254 return FREERDP_AUTODETECT_STATE_REQUEST;
255
256 case SHADOW_AUTODETECT_PHASE_CONNECT_BW:
257 if (!ad->bandwidthDone)
258 return FREERDP_AUTODETECT_STATE_REQUEST;
259 if (!shadow_autodetect_send_result(ad, autodetect))
260 return FREERDP_AUTODETECT_STATE_FAIL;
261 ad->phase = SHADOW_AUTODETECT_PHASE_CONTINUOUS;
262 ad->nextRttMs = GetTickCount64() + SHADOW_AUTODETECT_RTT_INTERVAL_MS;
263 return FREERDP_AUTODETECT_STATE_COMPLETE;
264
265 default:
266 return FREERDP_AUTODETECT_STATE_COMPLETE;
267 }
268}
269
270BOOL shadow_client_autodetect_init(rdpShadowClient* client)
271{
272 WINPR_ASSERT(client);
273
274 rdpAutoDetect* autodetect = client->context.autodetect;
275 WINPR_ASSERT(autodetect);
276 WINPR_ASSERT(!autodetect->custom);
277
278 ShadowAutoDetect* ad = calloc(1, sizeof(ShadowAutoDetect));
279 if (!ad)
280 return FALSE;
281 ad->client = client;
282 autodetect->custom = ad;
283
284 autodetect->RTTMeasureResponse = shadow_autodetect_rtt_response;
285 autodetect->BandwidthMeasureResults = shadow_autodetect_bandwidth_results;
286 autodetect->NetworkCharacteristicsSync = shadow_autodetect_netchar_sync;
287 autodetect->OnConnectTimeAutoDetectBegin = shadow_autodetect_connect_time_begin;
288 autodetect->OnConnectTimeAutoDetectProgress = shadow_autodetect_connect_time_progress;
289 return TRUE;
290}
291
292void shadow_client_autodetect_uninit(rdpShadowClient* client)
293{
294 if (!client)
295 return;
296
297 rdpAutoDetect* autodetect = client->context.autodetect;
298 if (!autodetect)
299 return;
300
301 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
302 if (!ad)
303 return;
304 if (ad->rttSamples > 0)
305 WLog_DBG(TAG, "%" PRIu64 " RTT measurements", ad->rttSamples);
306 autodetect->custom = nullptr;
307 free(ad);
308}
309
310DWORD shadow_client_autodetect_timeout(rdpShadowClient* client)
311{
312 WINPR_ASSERT(client);
313
314 rdpAutoDetect* autodetect = client->context.autodetect;
315 if (!autodetect)
316 return INFINITE;
317
318 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
319 if (!ad || (ad->phase != SHADOW_AUTODETECT_PHASE_CONTINUOUS) || !shadow_autodetect_enabled(ad))
320 return INFINITE;
321
322 const UINT64 now = GetTickCount64();
323 if (now >= ad->nextRttMs)
324 return 0;
325 return (DWORD)MIN(ad->nextRttMs - now, SHADOW_AUTODETECT_RTT_GIVEUP_MS);
326}
327
328BOOL shadow_client_autodetect_poll(rdpShadowClient* client)
329{
330 WINPR_ASSERT(client);
331
332 rdpAutoDetect* autodetect = client->context.autodetect;
333 if (!autodetect)
334 return TRUE;
335
336 ShadowAutoDetect* ad = shadow_autodetect_get(autodetect);
337 if (!ad || (ad->phase != SHADOW_AUTODETECT_PHASE_CONTINUOUS) || !shadow_autodetect_enabled(ad))
338 return TRUE;
339
340 const UINT64 now = GetTickCount64();
341 if (now < ad->nextRttMs)
342 return TRUE;
343
344 /* not while a resize reactivates the session: try again an interval later, so that the
345 * timeout does not stay 0 and the client thread does not spin until it is active */
346 if (freerdp_get_state(&client->context) != CONNECTION_STATE_ACTIVE)
347 {
348 ad->nextRttMs = now + SHADOW_AUTODETECT_RTT_INTERVAL_MS;
349 return TRUE;
350 }
351
352 if (ad->rttPending && (now - ad->rttSentMs < SHADOW_AUTODETECT_RTT_GIVEUP_MS))
353 {
354 ad->nextRttMs = ad->rttSentMs + SHADOW_AUTODETECT_RTT_GIVEUP_MS;
355 return TRUE;
356 }
357
358 ad->nextRttMs = now + SHADOW_AUTODETECT_RTT_INTERVAL_MS;
359 return shadow_autodetect_send_rtt(ad, autodetect);
360}
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.