[gnome-remote-desktop] rdp: Add class for detecting network characteristics



commit 4c646d45a77598345ab10fee863a733a34d83c8d
Author: Pascal Nowack <Pascal Nowack gmx de>
Date:   Mon May 31 14:42:34 2021 +0200

    rdp: Add class for detecting network characteristics
    
    Starting with Windows 8, Microsoft added a few PDUs, that allow the
    server to detect network characteristics, such as the round trip time
    (RTT) or the available bandwidth.
    These characteristics are measured with the RTT
    Measure-Request/-Response and Bandwidth Measure-Start/-Stop PDUs.
    
    In order to make use of these PDUs, add a new class that hooks up to
    these PDUs.
    Currently, only RTT detection is implemented.
    
    RTT detection works by putting a sequence number to a hash table,
    saving the time for the sequence, sending an RTTRequest to the client
    with the sequence number and when the client responds with the
    RTTResponse, calculate the time difference between the response and the
    request.
    This RTT value will then be forwarded to a consumer, like the graphics
    pipeline.
    The graphics pipeline will forward the RTT value to the GFX surfaces,
    which will then use that value to calculate the activate threshold for
    the throttling mechanism.
    
    To smooth out spikes in the RTT value, ignore out-of-order RTTResponses
    and calculate the average RTT value from the RTTs of the last 500ms.
    Also limit the maximum RTT value to 1000ms, as RTTs above that value
    are extremely hard to handle, if they can be handled.
    
    The ping interval will for now always be 70ms and the RTTRequsts will
    only be sent, when there is an RTT consumer.

 src/grd-rdp-network-autodetection.c | 337 ++++++++++++++++++++++++++++++++++++
 src/grd-rdp-network-autodetection.h |  46 +++++
 src/grd-rdp-private.h               |   2 +
 src/grd-types.h                     |   1 +
 src/meson.build                     |   2 +
 5 files changed, 388 insertions(+)
---
diff --git a/src/grd-rdp-network-autodetection.c b/src/grd-rdp-network-autodetection.c
new file mode 100644
index 0000000..d79dcff
--- /dev/null
+++ b/src/grd-rdp-network-autodetection.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2021 Pascal Nowack
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "grd-rdp-network-autodetection.h"
+
+#include "grd-rdp-graphics-pipeline.h"
+#include "grd-rdp-private.h"
+
+#define BW_MEASURE_SEQUENCE_NUMBER 0
+#define PING_INTERVAL_MS 70
+#define RTT_AVG_PERIOD_US (500 * 1000)
+
+typedef struct _PingInfo
+{
+  uint16_t sequence_number;
+  int64_t ping_time_us;
+} PingInfo;
+
+typedef struct _RTTInfo
+{
+  int64_t round_trip_time_us;
+  int64_t response_time_us;
+} RTTInfo;
+
+struct _GrdRdpNetworkAutodetection
+{
+  GObject parent;
+
+  rdpContext *rdp_context;
+  rdpAutoDetect *rdp_autodetect;
+  GMutex shutdown_mutex;
+  gboolean in_shutdown;
+
+  GMutex consumer_mutex;
+  GrdRdpNwAutodetectRTTConsumer rtt_consumers;
+
+  GMutex sequence_mutex;
+  GHashTable *sequences;
+
+  GSource *ping_source;
+  GQueue *pings;
+  GQueue *round_trip_times;
+
+  uint16_t next_sequence_number;
+};
+
+G_DEFINE_TYPE (GrdRdpNetworkAutodetection, grd_rdp_network_autodetection, G_TYPE_OBJECT);
+
+void
+grd_rdp_network_autodetection_invoke_shutdown (GrdRdpNetworkAutodetection *network_autodetection)
+{
+  g_mutex_lock (&network_autodetection->shutdown_mutex);
+  network_autodetection->in_shutdown = TRUE;
+  g_mutex_unlock (&network_autodetection->shutdown_mutex);
+}
+
+static uint16_t
+get_next_free_sequence_number (GrdRdpNetworkAutodetection *network_autodetection)
+{
+  uint16_t sequence_number = network_autodetection->next_sequence_number;
+
+  while (sequence_number == BW_MEASURE_SEQUENCE_NUMBER ||
+         g_hash_table_contains (network_autodetection->sequences,
+                                GUINT_TO_POINTER (sequence_number)))
+    ++sequence_number;
+
+  network_autodetection->next_sequence_number = sequence_number + 1;
+
+  return sequence_number;
+}
+
+static gboolean
+emit_ping (gpointer user_data)
+{
+  GrdRdpNetworkAutodetection *network_autodetection = user_data;
+  rdpAutoDetect *rdp_autodetect = network_autodetection->rdp_autodetect;
+  PingInfo *ping_info;
+
+  ping_info = g_malloc0 (sizeof (PingInfo));
+
+  g_mutex_lock (&network_autodetection->sequence_mutex);
+  ping_info->sequence_number = get_next_free_sequence_number (network_autodetection);
+  ping_info->ping_time_us = g_get_monotonic_time ();
+
+  g_hash_table_add (network_autodetection->sequences,
+                    GUINT_TO_POINTER (ping_info->sequence_number));
+  g_queue_push_tail (network_autodetection->pings, ping_info);
+  g_mutex_unlock (&network_autodetection->sequence_mutex);
+
+  rdp_autodetect->RTTMeasureRequest (network_autodetection->rdp_context,
+                                     ping_info->sequence_number);
+
+  return G_SOURCE_CONTINUE;
+}
+
+void
+grd_rdp_network_autodetection_ensure_rtt_consumer (GrdRdpNetworkAutodetection    *network_autodetection,
+                                                   GrdRdpNwAutodetectRTTConsumer  rtt_consumer)
+{
+  g_assert (rtt_consumer != GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE);
+
+  g_mutex_lock (&network_autodetection->consumer_mutex);
+  network_autodetection->rtt_consumers |= rtt_consumer;
+
+  if (!network_autodetection->ping_source)
+    {
+      emit_ping (network_autodetection);
+
+      network_autodetection->ping_source = g_timeout_source_new (PING_INTERVAL_MS);
+      g_source_set_callback (network_autodetection->ping_source, emit_ping,
+                             network_autodetection, NULL);
+      g_source_attach (network_autodetection->ping_source, NULL);
+    }
+  g_mutex_unlock (&network_autodetection->consumer_mutex);
+}
+
+void
+grd_rdp_network_autodetection_remove_rtt_consumer (GrdRdpNetworkAutodetection    *network_autodetection,
+                                                   GrdRdpNwAutodetectRTTConsumer  rtt_consumer)
+{
+  g_mutex_lock (&network_autodetection->consumer_mutex);
+  network_autodetection->rtt_consumers &= ~rtt_consumer;
+
+  if (network_autodetection->rtt_consumers == GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE &&
+      network_autodetection->ping_source)
+    {
+      g_source_destroy (network_autodetection->ping_source);
+      g_clear_pointer (&network_autodetection->ping_source, g_source_unref);
+    }
+  g_mutex_unlock (&network_autodetection->consumer_mutex);
+}
+
+static gboolean
+has_rtt_consumer (GrdRdpNetworkAutodetection    *network_autodetection,
+                  GrdRdpNwAutodetectRTTConsumer  rtt_consumer)
+{
+  g_assert (!g_mutex_trylock (&network_autodetection->consumer_mutex));
+
+  return !!(network_autodetection->rtt_consumers & rtt_consumer);
+}
+
+static void
+track_round_trip_time (GrdRdpNetworkAutodetection *network_autodetection,
+                       int64_t                     ping_time_us,
+                       int64_t                     pong_time_us)
+{
+  RTTInfo *rtt_info;
+
+  rtt_info = g_malloc0 (sizeof (RTTInfo));
+  rtt_info->round_trip_time_us = MIN (pong_time_us - ping_time_us, G_USEC_PER_SEC);
+  rtt_info->response_time_us = pong_time_us;
+
+  g_queue_push_tail (network_autodetection->round_trip_times, rtt_info);
+}
+
+static void
+remove_old_round_trip_times (GrdRdpNetworkAutodetection *network_autodetection)
+{
+  int64_t current_time_us;
+  RTTInfo *rtt_info;
+
+  current_time_us = g_get_monotonic_time ();
+  while ((rtt_info = g_queue_peek_head (network_autodetection->round_trip_times)) &&
+         current_time_us - rtt_info->response_time_us >= RTT_AVG_PERIOD_US)
+    g_free (g_queue_pop_head (network_autodetection->round_trip_times));
+}
+
+static int64_t
+get_current_avg_round_trip_time_us (GrdRdpNetworkAutodetection *network_autodetection)
+{
+  int64_t sum_round_trip_times_us = 0;
+  uint32_t total_round_trip_times;
+  RTTInfo *rtt_info;
+  GQueue *tmp;
+
+  remove_old_round_trip_times (network_autodetection);
+  if (!g_queue_get_length (network_autodetection->round_trip_times))
+    return 0;
+
+  tmp = g_queue_copy (network_autodetection->round_trip_times);
+  total_round_trip_times = g_queue_get_length (tmp);
+
+  while ((rtt_info = g_queue_pop_head (tmp)))
+    sum_round_trip_times_us += rtt_info->round_trip_time_us;
+
+  g_queue_free (tmp);
+
+  return sum_round_trip_times_us / total_round_trip_times;
+}
+
+static BOOL
+autodetect_rtt_measure_response (rdpContext *rdp_context,
+                                 uint16_t    sequence_number)
+{
+  RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context;
+  GrdRdpNetworkAutodetection *network_autodetection;
+  PingInfo *ping_info;
+  int64_t pong_time_us;
+  int64_t avg_round_trip_time_us;
+  gboolean has_rtt_consumer_rdpgfx = FALSE;
+
+  network_autodetection = rdp_peer_context->network_autodetection;
+
+  pong_time_us = g_get_monotonic_time ();
+
+  g_mutex_lock (&network_autodetection->sequence_mutex);
+  if (!g_hash_table_contains (network_autodetection->sequences,
+                              GUINT_TO_POINTER (sequence_number)))
+    {
+      g_mutex_unlock (&network_autodetection->sequence_mutex);
+      return TRUE;
+    }
+
+  while ((ping_info = g_queue_pop_head (network_autodetection->pings)) &&
+         ping_info->sequence_number != sequence_number)
+    {
+      g_hash_table_remove (network_autodetection->sequences,
+                           GUINT_TO_POINTER (ping_info->sequence_number));
+      g_clear_pointer (&ping_info, g_free);
+    }
+
+  if (ping_info)
+    {
+      int64_t ping_time_us = ping_info->ping_time_us;
+
+      g_assert (ping_info->sequence_number == sequence_number);
+
+      track_round_trip_time (network_autodetection, ping_time_us, pong_time_us);
+      avg_round_trip_time_us =
+        get_current_avg_round_trip_time_us (network_autodetection);
+
+      g_hash_table_remove (network_autodetection->sequences,
+                           GUINT_TO_POINTER (ping_info->sequence_number));
+    }
+  g_mutex_unlock (&network_autodetection->sequence_mutex);
+
+  g_mutex_lock (&network_autodetection->consumer_mutex);
+  has_rtt_consumer_rdpgfx = has_rtt_consumer (
+    network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX);
+  g_mutex_unlock (&network_autodetection->consumer_mutex);
+
+  g_mutex_lock (&network_autodetection->shutdown_mutex);
+  if (ping_info && !network_autodetection->in_shutdown && has_rtt_consumer_rdpgfx)
+    {
+      grd_rdp_graphics_pipeline_notify_new_round_trip_time (
+        rdp_peer_context->graphics_pipeline, avg_round_trip_time_us);
+    }
+  g_mutex_unlock (&network_autodetection->shutdown_mutex);
+
+  g_free (ping_info);
+
+  return TRUE;
+}
+
+GrdRdpNetworkAutodetection *
+grd_rdp_network_autodetection_new (rdpContext *rdp_context)
+{
+  GrdRdpNetworkAutodetection *network_autodetection;
+  rdpAutoDetect *rdp_autodetect = rdp_context->autodetect;
+
+  network_autodetection = g_object_new (GRD_TYPE_RDP_NETWORK_AUTODETECTION,
+                                        NULL);
+
+  network_autodetection->rdp_context = rdp_context;
+  network_autodetection->rdp_autodetect = rdp_autodetect;
+
+  rdp_autodetect->RTTMeasureResponse = autodetect_rtt_measure_response;
+
+  return network_autodetection;
+}
+
+static void
+grd_rdp_network_autodetection_dispose (GObject *object)
+{
+  GrdRdpNetworkAutodetection *network_autodetection =
+    GRD_RDP_NETWORK_AUTODETECTION (object);
+
+  if (network_autodetection->ping_source)
+    {
+      g_source_destroy (network_autodetection->ping_source);
+      g_clear_pointer (&network_autodetection->ping_source, g_source_unref);
+    }
+
+  if (network_autodetection->round_trip_times)
+    {
+      g_queue_free_full (network_autodetection->round_trip_times, g_free);
+      network_autodetection->round_trip_times = NULL;
+    }
+
+  if (network_autodetection->pings)
+    {
+      g_queue_free_full (network_autodetection->pings, g_free);
+      network_autodetection->pings = NULL;
+    }
+
+  g_clear_pointer (&network_autodetection->sequences, g_hash_table_destroy);
+
+  G_OBJECT_CLASS (grd_rdp_network_autodetection_parent_class)->dispose (object);
+}
+
+static void
+grd_rdp_network_autodetection_init (GrdRdpNetworkAutodetection *network_autodetection)
+{
+  network_autodetection->sequences = g_hash_table_new (NULL, NULL);
+  network_autodetection->pings = g_queue_new ();
+  network_autodetection->round_trip_times = g_queue_new ();
+
+  g_mutex_init (&network_autodetection->shutdown_mutex);
+  g_mutex_init (&network_autodetection->consumer_mutex);
+  g_mutex_init (&network_autodetection->sequence_mutex);
+}
+
+static void
+grd_rdp_network_autodetection_class_init (GrdRdpNetworkAutodetectionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = grd_rdp_network_autodetection_dispose;
+}
diff --git a/src/grd-rdp-network-autodetection.h b/src/grd-rdp-network-autodetection.h
new file mode 100644
index 0000000..73d1ad8
--- /dev/null
+++ b/src/grd-rdp-network-autodetection.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 Pascal Nowack
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef GRD_RDP_NETWORK_AUTODETECTION_H
+#define GRD_RDP_NETWORK_AUTODETECTION_H
+
+#include <freerdp/freerdp.h>
+#include <glib-object.h>
+
+#define GRD_TYPE_RDP_NETWORK_AUTODETECTION (grd_rdp_network_autodetection_get_type ())
+G_DECLARE_FINAL_TYPE (GrdRdpNetworkAutodetection, grd_rdp_network_autodetection,
+                      GRD, RDP_NETWORK_AUTODETECTION, GObject);
+
+typedef enum _GrdRdpNwAutodetectRTTConsumer
+{
+  GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE   = 0,
+  GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX = 1 << 0,
+} GrdRdpNwAutodetectRTTConsumer;
+
+GrdRdpNetworkAutodetection *grd_rdp_network_autodetection_new (rdpContext *rdp_context);
+
+void grd_rdp_network_autodetection_invoke_shutdown (GrdRdpNetworkAutodetection *network_autodetection);
+
+void grd_rdp_network_autodetection_ensure_rtt_consumer (GrdRdpNetworkAutodetection    *network_autodetection,
+                                                        GrdRdpNwAutodetectRTTConsumer  rtt_consumer);
+
+void grd_rdp_network_autodetection_remove_rtt_consumer (GrdRdpNetworkAutodetection    *network_autodetection,
+                                                        GrdRdpNwAutodetectRTTConsumer  rtt_consumer);
+
+#endif /* GRD_RDP_NETWORK_AUTODETECTION_H */
diff --git a/src/grd-rdp-private.h b/src/grd-rdp-private.h
index afb0c6f..eebc2ce 100644
--- a/src/grd-rdp-private.h
+++ b/src/grd-rdp-private.h
@@ -36,6 +36,8 @@ typedef struct _RdpPeerContext
   RFX_CONTEXT *rfx_context;
   wStream *encode_stream;
 
+  GrdRdpNetworkAutodetection *network_autodetection;
+
   /* Virtual Channel Manager */
   HANDLE vcm;
 
diff --git a/src/grd-types.h b/src/grd-types.h
index b004d08..edea7f0 100644
--- a/src/grd-types.h
+++ b/src/grd-types.h
@@ -31,6 +31,7 @@ typedef struct _GrdRdpEventQueue GrdRdpEventQueue;
 typedef struct _GrdRdpGfxFrameLog GrdRdpGfxFrameLog;
 typedef struct _GrdRdpGfxSurface GrdRdpGfxSurface;
 typedef struct _GrdRdpGraphicsPipeline GrdRdpGraphicsPipeline;
+typedef struct _GrdRdpNetworkAutodetection GrdRdpNetworkAutodetection;
 typedef struct _GrdRdpSAMFile GrdRdpSAMFile;
 typedef struct _GrdRdpServer GrdRdpServer;
 typedef struct _GrdRdpSurface GrdRdpSurface;
diff --git a/src/meson.build b/src/meson.build
index e9bcc9f..ef29d7b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -48,6 +48,8 @@ if have_rdp
     'grd-rdp-gfx-surface.h',
     'grd-rdp-graphics-pipeline.c',
     'grd-rdp-graphics-pipeline.h',
+    'grd-rdp-network-autodetection.c',
+    'grd-rdp-network-autodetection.h',
     'grd-rdp-pipewire-stream.c',
     'grd-rdp-pipewire-stream.h',
     'grd-rdp-private.h',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]