gnome-remote-desktop/connection-throttling.patch
2025-11-14 10:49:39 +01:00

1707 lines
52 KiB
Diff

From 202114575558c4bd0573215ef0ce82deae9d9e73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Mon, 26 May 2025 16:08:54 +0200
Subject: [PATCH 01/13] control: Allow controlling all daemon types
Useful for gracefully terminating daemon without sending signals.
---
src/grd-control.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/grd-control.c b/src/grd-control.c
index 858ab10a..284cb113 100644
--- a/src/grd-control.c
+++ b/src/grd-control.c
@@ -33,13 +33,25 @@ main (int argc, char **argv)
{
g_autoptr(GApplication) app = NULL;
gboolean terminate = FALSE;
+ gboolean headless = FALSE;
+ gboolean system = FALSE;
+ gboolean handover = FALSE;
GOptionEntry entries[] = {
{ "terminate", 0, 0, G_OPTION_ARG_NONE, &terminate,
"Terminate the daemon", NULL },
+ { "headless", 0, 0, G_OPTION_ARG_NONE, &headless,
+ "Control headless daemon", NULL },
+#if defined(HAVE_RDP) && defined(HAVE_LIBSYSTEMD)
+ { "system", 0, 0, G_OPTION_ARG_NONE, &system,
+ "Control system daemon", NULL },
+ { "handover", 0, 0, G_OPTION_ARG_NONE, &handover,
+ "Control handover daemon", NULL },
+#endif /* HAVE_RDP && HAVE_LIBSYSTEMD */
{ NULL }
};
GError *error = NULL;
GOptionContext *context;
+ const char *app_id;
context = g_option_context_new ("- control gnome-remote-desktop");
g_option_context_add_main_entries (context, entries, NULL);
@@ -56,7 +68,16 @@ main (int argc, char **argv)
return 1;
}
- app = g_application_new (GRD_DAEMON_USER_APPLICATION_ID, 0);
+ if (headless)
+ app_id = GRD_DAEMON_HEADLESS_APPLICATION_ID;
+ else if (system)
+ app_id = GRD_DAEMON_SYSTEM_APPLICATION_ID;
+ else if (handover)
+ app_id = GRD_DAEMON_HANDOVER_APPLICATION_ID;
+ else
+ app_id = GRD_DAEMON_USER_APPLICATION_ID;
+
+ app = g_application_new (app_id, G_APPLICATION_DEFAULT_FLAGS);
if (!g_application_register (app, NULL, NULL))
{
g_warning ("Failed to register with application\n");
--
2.51.0
From 1a627a3840de1a71f3a02860aeb3ef965b412f35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Mon, 26 May 2025 16:09:45 +0200
Subject: [PATCH 02/13] daemon: Use GError auto pointer
---
src/grd-daemon.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/grd-daemon.c b/src/grd-daemon.c
index c082c6b5..98954200 100644
--- a/src/grd-daemon.c
+++ b/src/grd-daemon.c
@@ -989,7 +989,7 @@ main (int argc, char **argv)
};
g_autoptr (GOptionContext) option_context = NULL;
g_autoptr (GrdDaemon) daemon = NULL;
- GError *error = NULL;
+ g_autoptr (GError) error = NULL;
GrdRuntimeMode runtime_mode;
g_set_application_name (_("GNOME Remote Desktop"));
@@ -999,7 +999,6 @@ main (int argc, char **argv)
if (!g_option_context_parse (option_context, &argc, &argv, &error))
{
g_printerr ("Invalid option: %s\n", error->message);
- g_error_free (error);
return EXIT_FAILURE;
}
@@ -1048,7 +1047,6 @@ main (int argc, char **argv)
if (!daemon)
{
g_printerr ("Failed to initialize: %s\n", error->message);
- g_error_free (error);
return EXIT_FAILURE;
}
--
2.51.0
From ae398fb3a36a057af814312bfbd8d979120b8a27 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Mon, 26 May 2025 17:04:45 +0200
Subject: [PATCH 03/13] rdp-server: Remove empty constructor vfunc
No need to implement it if it doesn't do anything.
---
src/grd-rdp-server.c | 7 -------
1 file changed, 7 deletions(-)
diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c
index c3124b8b..0ac486df 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -482,12 +482,6 @@ grd_rdp_server_dispose (GObject *object)
G_OBJECT_CLASS (grd_rdp_server_parent_class)->dispose (object);
}
-static void
-grd_rdp_server_constructed (GObject *object)
-{
- G_OBJECT_CLASS (grd_rdp_server_parent_class)->constructed (object);
-}
-
static void
grd_rdp_server_init (GrdRdpServer *rdp_server)
{
@@ -510,7 +504,6 @@ grd_rdp_server_class_init (GrdRdpServerClass *klass)
object_class->set_property = grd_rdp_server_set_property;
object_class->get_property = grd_rdp_server_get_property;
object_class->dispose = grd_rdp_server_dispose;
- object_class->constructed = grd_rdp_server_constructed;
g_object_class_install_property (object_class,
PROP_CONTEXT,
--
2.51.0
From 0f5071b5fd8d690f2fa49c5bec4d86c907c39281 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Wed, 28 May 2025 14:08:12 +0200
Subject: [PATCH 04/13] rdp-sam: Dup fd kept in struct
The SAM file instance intends to keeps the file descriptor alive so that
it doesn't go away; however it failed to do this due to fdopen() taking
ownership of it, thus closing it during fclose(). This meant we'd close
an arbitrary potentially reused file descriptor when the SAM file
instance eventually got cleaned up. Address this by reordering things a
bit, while putting a duplicated file descriptor in the SAM file instance
struct.
---
src/grd-rdp-sam.c | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/src/grd-rdp-sam.c b/src/grd-rdp-sam.c
index 5898825a..bcadcb59 100644
--- a/src/grd-rdp-sam.c
+++ b/src/grd-rdp-sam.c
@@ -76,7 +76,7 @@ grd_rdp_sam_create_sam_file (const char *username,
g_autofree char *file_dir = NULL;
g_autofree char *filename = NULL;
g_autofree char *sam_string = NULL;
- int fd;
+ g_autofd int fd = -1;
FILE *sam_file;
file_dir = g_strdup_printf ("%s%s", g_get_user_runtime_dir (), grd_path);
@@ -98,20 +98,21 @@ grd_rdp_sam_create_sam_file (const char *username,
return NULL;
}
- rdp_sam_file = g_new0 (GrdRdpSAMFile, 1);
- rdp_sam_file->fd = fd;
- rdp_sam_file->filename = g_steal_pointer (&filename);
-
- sam_string = create_sam_string (username, password);
-
- sam_file = fdopen (rdp_sam_file->fd, "w+");
+ sam_file = fdopen (fd, "w+");
if (!sam_file)
{
g_warning ("[RDP] Failed to open SAM database: %s", g_strerror (errno));
- grd_rdp_sam_free_sam_file (rdp_sam_file);
return NULL;
}
+ rdp_sam_file = g_new0 (GrdRdpSAMFile, 1);
+ rdp_sam_file->fd = dup (fd);
+ rdp_sam_file->filename = g_steal_pointer (&filename);
+
+ g_steal_fd (&fd);
+
+ sam_string = create_sam_string (username, password);
+
fputs (sam_string, sam_file);
fclose (sam_file);
--
2.51.0
From dfadd22d899a981804ff1b68358082ffb2bbe0c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Wed, 28 May 2025 22:33:54 +0200
Subject: [PATCH 05/13] rdp-server: Set socket backlog count to 5
This limits the number of pending connections.
---
src/grd-rdp-server.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c
index 0ac486df..2691a65d 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -36,6 +36,7 @@
#define RDP_SERVER_N_BINDING_ATTEMPTS 10
#define RDP_SERVER_BINDING_ATTEMPT_INTERVAL_MS 500
+#define RDP_SERVER_SOCKET_BACKLOG_COUNT 5
enum
{
@@ -312,6 +313,9 @@ bind_socket (GrdRdpServer *rdp_server,
uint16_t selected_rdp_port = 0;
gboolean negotiate_port;
+ g_socket_listener_set_backlog (G_SOCKET_LISTENER (rdp_server),
+ RDP_SERVER_SOCKET_BACKLOG_COUNT);
+
g_object_get (G_OBJECT (settings),
"rdp-port", &rdp_port,
"rdp-negotiate-port", &negotiate_port,
--
2.51.0
From 1a5f6718c68695ec4221c530c0427061f2e50ac9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Wed, 28 May 2025 22:55:20 +0200
Subject: [PATCH 06/13] utils: Add some time conversion helpers
Useful when converting between second, microseconds, etc.
---
src/grd-utils.h | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/grd-utils.h b/src/grd-utils.h
index 30a3b9ec..099da563 100644
--- a/src/grd-utils.h
+++ b/src/grd-utils.h
@@ -99,3 +99,21 @@ gboolean grd_systemd_get_unit (GBusType bus_type,
gboolean grd_systemd_unit_get_active_state (GDBusProxy *unit_proxy,
GrdSystemdUnitActiveState *active_state,
GError **error);
+
+static inline int64_t
+us (int64_t us)
+{
+ return us;
+}
+
+static inline int64_t
+ms2us (int64_t ms)
+{
+ return us (ms * 1000);
+}
+
+static inline int64_t
+s2us (uint64_t s)
+{
+ return ms2us (s * 1000);
+}
--
2.51.0
From 1abd4d5bb0840965a0a01d2230a8e0fa63768f52 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Mon, 30 Jun 2025 16:16:31 +0200
Subject: [PATCH 07/13] utils: Add helper to close connection and notify
Closing doesn't notify that it was closed, so lets add a helper that
does so, that we then can uses to rely on the closed property being up
to date.
---
src/grd-utils.c | 7 +++++++
src/grd-utils.h | 2 ++
2 files changed, 9 insertions(+)
diff --git a/src/grd-utils.c b/src/grd-utils.c
index 32739a93..bf208828 100644
--- a/src/grd-utils.c
+++ b/src/grd-utils.c
@@ -478,3 +478,10 @@ grd_systemd_unit_get_active_state (GDBusProxy *unit_proxy,
return TRUE;
}
+
+void
+grd_close_connection_and_notify (GSocketConnection *connection)
+{
+ g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
+ g_object_notify (G_OBJECT (connection), "closed");
+}
diff --git a/src/grd-utils.h b/src/grd-utils.h
index 099da563..4703db8f 100644
--- a/src/grd-utils.h
+++ b/src/grd-utils.h
@@ -100,6 +100,8 @@ gboolean grd_systemd_unit_get_active_state (GDBusProxy *unit_pro
GrdSystemdUnitActiveState *active_state,
GError **error);
+void grd_close_connection_and_notify (GSocketConnection *connection);
+
static inline int64_t
us (int64_t us)
{
--
2.51.0
From 8dccf29b75560838843b0b373bcb6346548211c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Wed, 28 May 2025 22:40:38 +0200
Subject: [PATCH 08/13] Introduce throttler class
This handles the throttling of incoming connections, and is intended to
act as a first line of defence against denial of service attacks. It
implements the following:
* It limits the global number of concurrent active connections
* It limits the number of concurrent active connections per peer
(roughly IP address)
* It limits the number of new connections per second coming from a peer
* It maintains a limited number of pending connections that is waiting
to be handled
This avoids running into situations where an uncontrolled number of
connection attempts causes resources to be exhausted, e.g. number of
file descriptor exceeding the process limit.
The limits are currently hard coded.
Related: CVE-2025-5024
---
src/grd-throttler.c | 467 ++++++++++++++++++++++++++++++++++++++++++++
src/grd-throttler.h | 41 ++++
src/meson.build | 2 +
3 files changed, 510 insertions(+)
create mode 100644 src/grd-throttler.c
create mode 100644 src/grd-throttler.h
diff --git a/src/grd-throttler.c b/src/grd-throttler.c
new file mode 100644
index 00000000..e584433a
--- /dev/null
+++ b/src/grd-throttler.c
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2025 Red Hat
+ *
+ * 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-throttler.h"
+
+#include "grd-utils.h"
+
+#define MAX_GLOBAL_CONNECTIONS 10
+#define MAX_CONNECTIONS_PER_PEER 5
+#define MAX_PENDING_CONNECTIONS 5
+#define MAX_ATTEMPTS_PER_SECOND 5
+
+#define PRUNE_TIME_CUTOFF_US (s2us (1))
+
+typedef struct _GrdPeer
+{
+ GrdThrottler *throttler;
+ char *name;
+ int active_connections;
+ GArray *connect_timestamps;
+ GQueue *delayed_connections;
+ int64_t last_accept_us;
+} GrdPeer;
+
+struct _GrdThrottler
+{
+ GObject parent;
+
+ int active_connections;
+
+ GrdThrottlerAllowCallback allow_callback;
+ gpointer user_data;
+
+ GHashTable *peers;
+
+ GSource *delayed_connections_source;
+};
+
+G_DEFINE_TYPE (GrdThrottler, grd_throttler, G_TYPE_OBJECT)
+
+static GQuark quark_remote_address;
+
+static void
+maybe_queue_timeout (GrdThrottler *throttler);
+
+static void
+prune_old_timestamps (GArray *timestamps,
+ int64_t now_us)
+{
+ size_t i;
+
+ if (!timestamps)
+ return;
+
+ for (i = 0; i < timestamps->len; i++)
+ {
+ int64_t ts_us = g_array_index (timestamps, int64_t, i);
+
+ if (now_us - ts_us < PRUNE_TIME_CUTOFF_US)
+ break;
+ }
+ g_array_remove_range (timestamps, 0, i);
+}
+
+static void
+maybe_dispose_peer (GrdThrottler *throttler,
+ GrdPeer *peer,
+ GHashTableIter *iter)
+{
+ if (peer->active_connections > 0)
+ return;
+
+ if (peer->connect_timestamps && peer->connect_timestamps->len > 0)
+ return;
+
+ if (!g_queue_is_empty (peer->delayed_connections))
+ return;
+
+ if (iter)
+ g_hash_table_iter_remove (iter);
+ else
+ g_hash_table_remove (throttler->peers, peer->name);
+}
+
+static void
+grd_throttler_register_connection (GrdThrottler *throttler,
+ GrdPeer *peer)
+{
+ int64_t now_us;
+
+ peer->active_connections++;
+ throttler->active_connections++;
+
+ if (!peer->connect_timestamps)
+ peer->connect_timestamps = g_array_new (FALSE, FALSE, sizeof (int64_t));
+
+ now_us = g_get_monotonic_time ();
+
+ prune_old_timestamps (peer->connect_timestamps, now_us);
+ g_array_append_val (peer->connect_timestamps, now_us);
+}
+
+static void
+grd_throttler_unregister_connection (GrdThrottler *throttler,
+ GSocketConnection *connection,
+ GrdPeer *peer)
+{
+ g_return_if_fail (peer->active_connections > 0);
+ g_return_if_fail (throttler->active_connections > 0);
+
+ peer->active_connections--;
+ throttler->active_connections--;
+
+ maybe_dispose_peer (throttler, peer, NULL);
+ maybe_queue_timeout (throttler);
+}
+
+static void
+grd_throttler_deny_connection (GrdThrottler *throttler,
+ const char *peer_name,
+ GSocketConnection *connection)
+{
+ g_debug ("Denying connection from %s", peer_name);
+ g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
+}
+
+static void
+on_connection_closed_changed (GSocketConnection *connection,
+ GParamSpec *pspec,
+ GrdThrottler *throttler)
+{
+ const char *peer_name;
+ GrdPeer *peer;
+
+ g_assert (g_io_stream_is_closed (G_IO_STREAM (connection)));
+
+ peer_name = g_object_get_qdata (G_OBJECT (connection), quark_remote_address);
+ peer = g_hash_table_lookup (throttler->peers, peer_name);
+ grd_throttler_unregister_connection (throttler, connection, peer);
+}
+
+static void
+grd_throttler_allow_connection (GrdThrottler *throttler,
+ GSocketConnection *connection,
+ GrdPeer *peer)
+{
+ g_debug ("Accepting connection from %s", peer->name);
+
+ throttler->allow_callback (throttler, connection, throttler->user_data);
+
+ peer->last_accept_us = g_get_monotonic_time ();
+
+ g_object_set_qdata_full (G_OBJECT (connection), quark_remote_address,
+ g_strdup (peer->name), g_free);
+ grd_throttler_register_connection (throttler, peer);
+ g_signal_connect (connection, "notify::closed",
+ G_CALLBACK (on_connection_closed_changed), throttler);
+}
+
+static gboolean
+source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ g_source_set_ready_time (source, -1);
+
+ return callback (user_data);
+}
+
+static GSourceFuncs source_funcs =
+{
+ .dispatch = source_dispatch,
+};
+
+static void
+prune_closed_connections (GQueue *queue)
+{
+ GList *l;
+
+ l = queue->head;
+ while (l)
+ {
+ GSocketConnection *connection = G_SOCKET_CONNECTION (l->data);
+ GList *l_next = l->next;
+
+ if (g_io_stream_is_closed (G_IO_STREAM (connection)))
+ {
+ g_queue_delete_link (queue, l);
+ g_object_unref (connection);
+ }
+
+ l = l_next;
+ }
+}
+
+static gboolean
+is_connection_limit_reached (GrdThrottler *throttler,
+ GrdPeer *peer)
+{
+ if (peer->active_connections >= MAX_CONNECTIONS_PER_PEER)
+ return TRUE;
+
+ if (throttler->active_connections >= MAX_GLOBAL_CONNECTIONS)
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+is_new_connection_allowed (GrdThrottler *throttler,
+ GrdPeer *peer)
+{
+ if (is_connection_limit_reached (throttler, peer))
+ return FALSE;
+
+ if (peer->connect_timestamps &&
+ peer->connect_timestamps->len >= MAX_ATTEMPTS_PER_SECOND)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+dispatch_delayed_connections (gpointer user_data)
+{
+ GrdThrottler *throttler = GRD_THROTTLER (user_data);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, throttler->peers);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GrdPeer *peer = value;
+ GQueue *queue = peer->delayed_connections;
+ GSocketConnection *connection;
+
+ prune_closed_connections (queue);
+ connection = g_queue_peek_tail (queue);
+
+ if (!connection)
+ {
+ maybe_dispose_peer (throttler, peer, &iter);
+ continue;
+ }
+
+ if (is_new_connection_allowed (throttler, peer))
+ {
+ g_queue_pop_tail (queue);
+ grd_throttler_allow_connection (throttler, connection, peer);
+ g_object_unref (connection);
+ }
+ }
+
+ maybe_queue_timeout (throttler);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+ensure_delayed_connections_source (GrdThrottler *throttler)
+{
+ if (throttler->delayed_connections_source)
+ return;
+
+ throttler->delayed_connections_source = g_source_new (&source_funcs,
+ sizeof (GSource));
+ g_source_set_callback (throttler->delayed_connections_source,
+ dispatch_delayed_connections, throttler, NULL);
+ g_source_attach (throttler->delayed_connections_source, NULL);
+ g_source_unref (throttler->delayed_connections_source);
+}
+
+static void
+maybe_queue_timeout (GrdThrottler *throttler)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ int64_t next_timeout_us = INT64_MAX;
+
+
+ g_hash_table_iter_init (&iter, throttler->peers);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ GrdPeer *peer = value;
+
+ if (is_connection_limit_reached (throttler, peer))
+ continue;
+
+ if (g_queue_is_empty (peer->delayed_connections))
+ continue;
+
+ next_timeout_us = MIN (next_timeout_us,
+ peer->last_accept_us +
+ G_USEC_PER_SEC / MAX_ATTEMPTS_PER_SECOND);
+ }
+
+
+ if (next_timeout_us == INT64_MAX)
+ next_timeout_us = -1;
+
+ if (next_timeout_us >= 0)
+ {
+ ensure_delayed_connections_source (throttler);
+ g_source_set_ready_time (throttler->delayed_connections_source,
+ next_timeout_us);
+ }
+}
+
+static void
+maybe_delay_connection (GrdThrottler *throttler,
+ GSocketConnection *connection,
+ GrdPeer *peer,
+ int64_t now_us)
+{
+ GQueue *delayed_connections;
+
+ delayed_connections = peer->delayed_connections;
+ if (!delayed_connections)
+ {
+ delayed_connections = g_queue_new ();
+ peer->delayed_connections = delayed_connections;
+ }
+
+ if (g_queue_get_length (delayed_connections) > MAX_PENDING_CONNECTIONS)
+ {
+ grd_throttler_deny_connection (throttler, peer->name, connection);
+ return;
+ }
+
+ g_debug ("Delaying connection from %s", peer->name);
+
+ g_queue_push_head (delayed_connections, g_object_ref (connection));
+ maybe_queue_timeout (throttler);
+}
+
+static GrdPeer *
+ensure_peer (GrdThrottler *throttler,
+ const char *peer_name)
+{
+ GrdPeer *peer;
+
+ peer = g_hash_table_lookup (throttler->peers, peer_name);
+ if (peer)
+ return peer;
+
+ peer = g_new0 (GrdPeer, 1);
+ peer->throttler = throttler;
+ peer->name = g_strdup (peer_name);
+ peer->delayed_connections = g_queue_new ();
+
+ g_hash_table_insert (throttler->peers,
+ g_strdup (peer_name), peer);
+
+ return peer;
+}
+
+void
+grd_throttler_handle_connection (GrdThrottler *throttler,
+ GSocketConnection *connection)
+{
+ g_autoptr (GError) error = NULL;
+ g_autoptr (GSocketAddress) remote_address = NULL;
+ GInetAddress *inet_address;
+ g_autofree char *peer_name = NULL;
+ GrdPeer *peer;
+ int64_t now_us;
+
+ remote_address = g_socket_connection_get_remote_address (connection, &error);
+ if (!remote_address)
+ {
+ g_warning ("Failed to get remote address: %s", error->message);
+ grd_throttler_deny_connection (throttler, "unknown peer", connection);
+ return;
+ }
+
+ inet_address =
+ g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (remote_address));
+ peer_name = g_inet_address_to_string (inet_address);
+
+ g_debug ("New incoming connection from %s", peer_name);
+
+ peer = ensure_peer (throttler, peer_name);
+
+ prune_closed_connections (peer->delayed_connections);
+
+ now_us = g_get_monotonic_time ();
+ prune_old_timestamps (peer->connect_timestamps, now_us);
+ if (is_new_connection_allowed (throttler, peer) &&
+ g_queue_get_length (peer->delayed_connections) == 0)
+ {
+ grd_throttler_allow_connection (throttler, connection, peer);
+ return;
+ }
+
+ maybe_delay_connection (throttler, connection, peer, now_us);
+}
+
+GrdThrottler *
+grd_throttler_new (GrdThrottlerAllowCallback allow_callback,
+ gpointer user_data)
+{
+ GrdThrottler *throttler;
+
+ throttler = g_object_new (GRD_TYPE_THROTTLER, NULL);
+ throttler->allow_callback = allow_callback;
+ throttler->user_data = user_data;
+
+ return throttler;
+}
+
+static void
+grd_peer_free (GrdPeer *peer)
+{
+ if (peer->delayed_connections)
+ g_queue_free_full (peer->delayed_connections, g_object_unref);
+ g_clear_pointer (&peer->connect_timestamps, g_array_unref);
+ g_clear_pointer (&peer->name, g_free);
+ g_free (peer);
+}
+
+static void
+grd_throttler_finalize (GObject *object)
+{
+ GrdThrottler *throttler = GRD_THROTTLER(object);
+
+ g_clear_pointer (&throttler->delayed_connections_source, g_source_destroy);
+ g_clear_pointer (&throttler->peers, g_hash_table_unref);
+
+ G_OBJECT_CLASS (grd_throttler_parent_class)->finalize (object);
+}
+
+static void
+grd_throttler_init (GrdThrottler *throttler)
+{
+ throttler->peers =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) grd_peer_free);
+}
+
+static void
+grd_throttler_class_init (GrdThrottlerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = grd_throttler_finalize;
+
+ quark_remote_address =
+ g_quark_from_static_string ("grd-remote-address-string");
+}
diff --git a/src/grd-throttler.h b/src/grd-throttler.h
new file mode 100644
index 00000000..d57d653a
--- /dev/null
+++ b/src/grd-throttler.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 Red Hat
+ *
+ * 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_THROTTLER_H
+#define GRD_THROTTLER_H
+
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#define GRD_TYPE_THROTTLER (grd_throttler_get_type())
+G_DECLARE_FINAL_TYPE (GrdThrottler, grd_throttler, GRD, THROTTLER, GObject)
+
+typedef void (* GrdThrottlerAllowCallback) (GrdThrottler *throttler,
+ GSocketConnection *connection,
+ gpointer user_data);
+
+void
+grd_throttler_handle_connection (GrdThrottler *throttler,
+ GSocketConnection *connection);
+
+GrdThrottler *
+grd_throttler_new (GrdThrottlerAllowCallback allow_callback,
+ gpointer user_data);
+
+#endif /* GRD_THROTTLER_H */
diff --git a/src/meson.build b/src/meson.build
index 6b8b517e..3cc63c8b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -74,6 +74,8 @@ daemon_sources = files([
'grd-settings-user.h',
'grd-stream.c',
'grd-stream.h',
+ 'grd-throttler.c',
+ 'grd-throttler.h',
'grd-types.h',
'grd-utils.c',
'grd-utils.h',
--
2.51.0
From f5e80ab4c6db6e15adebefc11d716f57af8e2eb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Wed, 28 May 2025 22:46:27 +0200
Subject: [PATCH 09/13] rdp-server: Throttle connections using GrdThrottler
This avoids system resource exhaustion by a single attacker.
Related: CVE-2025-5024
---
src/grd-rdp-server.c | 27 +++++++++++++++++++++++----
src/grd-session-rdp.c | 6 ++++++
2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c
index 2691a65d..b8622a13 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -32,6 +32,7 @@
#include "grd-hwaccel-vulkan.h"
#include "grd-rdp-routing-token.h"
#include "grd-session-rdp.h"
+#include "grd-throttler.h"
#include "grd-utils.h"
#define RDP_SERVER_N_BINDING_ATTEMPTS 10
@@ -60,6 +61,8 @@ struct _GrdRdpServer
{
GSocketService parent;
+ GrdThrottler *throttler;
+
GList *sessions;
GList *stopped_sessions;
@@ -225,14 +228,26 @@ on_incoming (GSocketService *service,
GSocketConnection *connection)
{
GrdRdpServer *rdp_server = GRD_RDP_SERVER (service);
+
+ grd_throttler_handle_connection (rdp_server->throttler,
+ connection);
+ return TRUE;
+}
+
+static void
+allow_connection_cb (GrdThrottler *throttler,
+ GSocketConnection *connection,
+ gpointer user_data)
+{
+ GrdRdpServer *rdp_server = GRD_RDP_SERVER (user_data);
GrdSessionRdp *session_rdp;
- g_debug ("New incoming RDP connection");
+ g_debug ("Creating new RDP session");
if (!(session_rdp = grd_session_rdp_new (rdp_server, connection,
rdp_server->hwaccel_vulkan,
rdp_server->hwaccel_nvidia)))
- return TRUE;
+ return;
rdp_server->sessions = g_list_append (rdp_server->sessions, session_rdp);
@@ -243,8 +258,6 @@ on_incoming (GSocketService *service,
g_signal_connect (session_rdp, "post-connected",
G_CALLBACK (on_session_post_connect),
rdp_server);
-
- return TRUE;
}
void
@@ -420,6 +433,8 @@ grd_rdp_server_stop (GrdRdpServer *rdp_server)
g_clear_handle_id (&rdp_server->cleanup_sessions_idle_id, g_source_remove);
grd_rdp_server_cleanup_stopped_sessions (rdp_server);
+ g_clear_object (&rdp_server->throttler);
+
if (rdp_server->cancellable)
{
g_cancellable_cancel (rdp_server->cancellable);
@@ -479,6 +494,7 @@ grd_rdp_server_dispose (GObject *object)
g_assert (!rdp_server->binding_timeout_source_id);
g_assert (!rdp_server->cleanup_sessions_idle_id);
g_assert (!rdp_server->stopped_sessions);
+ g_assert (!rdp_server->throttler);
g_assert (!rdp_server->hwaccel_nvidia);
g_assert (!rdp_server->hwaccel_vulkan);
@@ -489,6 +505,9 @@ grd_rdp_server_dispose (GObject *object)
static void
grd_rdp_server_init (GrdRdpServer *rdp_server)
{
+ rdp_server->throttler = grd_throttler_new (allow_connection_cb,
+ rdp_server);
+
rdp_server->pending_binding_attempts = RDP_SERVER_N_BINDING_ATTEMPTS;
winpr_InitializeSSL (WINPR_SSL_INIT_DEFAULT);
diff --git a/src/grd-session-rdp.c b/src/grd-session-rdp.c
index cc026064..fcebadd1 100644
--- a/src/grd-session-rdp.c
+++ b/src/grd-session-rdp.c
@@ -48,6 +48,7 @@
#include "grd-rdp-server.h"
#include "grd-rdp-session-metrics.h"
#include "grd-settings.h"
+#include "grd-utils.h"
#define MAX_MONITOR_COUNT_HEADLESS 16
#define MAX_MONITOR_COUNT_SCREEN_SHARE 1
@@ -1635,6 +1636,7 @@ grd_session_rdp_stop (GrdSession *session)
g_clear_object (&session_rdp->renderer);
peer->Close (peer);
+ grd_close_connection_and_notify (session_rdp->connection);
g_clear_object (&session_rdp->connection);
g_clear_object (&rdp_peer_context->network_autodetection);
@@ -1835,6 +1837,10 @@ grd_session_rdp_dispose (GObject *object)
clear_rdp_peer (session_rdp);
g_clear_object (&session_rdp->connection);
+ if (session_rdp->connection)
+ grd_close_connection_and_notify (session_rdp->connection);
+ g_clear_object (&session_rdp->connection);
+
g_clear_object (&session_rdp->renderer);
g_clear_object (&session_rdp->rdp_event_queue);
--
2.51.0
From 13dfafb9b24964919a513f3d787101390697e62a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Fri, 30 May 2025 16:54:00 +0200
Subject: [PATCH 10/13] throttler: Introduce limits struct
This will be used by the server implementation to customize throttling
limits. This will be used by the VNC server to limit to one global
session at a time, replacing it's own existing condition.
---
src/grd-rdp-server.c | 3 ++-
src/grd-throttler.c | 64 ++++++++++++++++++++++++++++++++++++--------
src/grd-throttler.h | 14 ++++++++--
3 files changed, 67 insertions(+), 14 deletions(-)
diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c
index b8622a13..43eb2925 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -505,7 +505,8 @@ grd_rdp_server_dispose (GObject *object)
static void
grd_rdp_server_init (GrdRdpServer *rdp_server)
{
- rdp_server->throttler = grd_throttler_new (allow_connection_cb,
+ rdp_server->throttler = grd_throttler_new (grd_throttler_limits_new (),
+ allow_connection_cb,
rdp_server);
rdp_server->pending_binding_attempts = RDP_SERVER_N_BINDING_ATTEMPTS;
diff --git a/src/grd-throttler.c b/src/grd-throttler.c
index e584433a..0429a089 100644
--- a/src/grd-throttler.c
+++ b/src/grd-throttler.c
@@ -23,10 +23,18 @@
#include "grd-utils.h"
-#define MAX_GLOBAL_CONNECTIONS 10
-#define MAX_CONNECTIONS_PER_PEER 5
-#define MAX_PENDING_CONNECTIONS 5
-#define MAX_ATTEMPTS_PER_SECOND 5
+#define DEFAULT_MAX_GLOBAL_CONNECTIONS 10
+#define DEFAULT_MAX_CONNECTIONS_PER_PEER 5
+#define DEFAULT_MAX_PENDING_CONNECTIONS 5
+#define DEFAULT_MAX_ATTEMPTS_PER_SECOND 10
+
+struct _GrdThrottlerLimits
+{
+ int max_global_connections;
+ int max_connections_per_peer;
+ int max_pending_connections;
+ int max_attempts_per_second;
+};
#define PRUNE_TIME_CUTOFF_US (s2us (1))
@@ -44,6 +52,8 @@ struct _GrdThrottler
{
GObject parent;
+ GrdThrottlerLimits *limits;
+
int active_connections;
GrdThrottlerAllowCallback allow_callback;
@@ -215,10 +225,12 @@ static gboolean
is_connection_limit_reached (GrdThrottler *throttler,
GrdPeer *peer)
{
- if (peer->active_connections >= MAX_CONNECTIONS_PER_PEER)
+ GrdThrottlerLimits *limits = throttler->limits;
+
+ if (peer->active_connections >= limits->max_connections_per_peer)
return TRUE;
- if (throttler->active_connections >= MAX_GLOBAL_CONNECTIONS)
+ if (throttler->active_connections >= limits->max_global_connections)
return TRUE;
return FALSE;
@@ -228,11 +240,13 @@ static gboolean
is_new_connection_allowed (GrdThrottler *throttler,
GrdPeer *peer)
{
+ GrdThrottlerLimits *limits = throttler->limits;
+
if (is_connection_limit_reached (throttler, peer))
return FALSE;
if (peer->connect_timestamps &&
- peer->connect_timestamps->len >= MAX_ATTEMPTS_PER_SECOND)
+ peer->connect_timestamps->len >= limits->max_attempts_per_second)
return FALSE;
return TRUE;
@@ -291,6 +305,7 @@ ensure_delayed_connections_source (GrdThrottler *throttler)
static void
maybe_queue_timeout (GrdThrottler *throttler)
{
+ GrdThrottlerLimits *limits = throttler->limits;
GHashTableIter iter;
gpointer key, value;
int64_t next_timeout_us = INT64_MAX;
@@ -309,7 +324,7 @@ maybe_queue_timeout (GrdThrottler *throttler)
next_timeout_us = MIN (next_timeout_us,
peer->last_accept_us +
- G_USEC_PER_SEC / MAX_ATTEMPTS_PER_SECOND);
+ G_USEC_PER_SEC / limits->max_attempts_per_second);
}
@@ -330,6 +345,7 @@ maybe_delay_connection (GrdThrottler *throttler,
GrdPeer *peer,
int64_t now_us)
{
+ GrdThrottlerLimits *limits = throttler->limits;
GQueue *delayed_connections;
delayed_connections = peer->delayed_connections;
@@ -339,7 +355,7 @@ maybe_delay_connection (GrdThrottler *throttler,
peer->delayed_connections = delayed_connections;
}
- if (g_queue_get_length (delayed_connections) > MAX_PENDING_CONNECTIONS)
+ if (g_queue_get_length (delayed_connections) > limits->max_pending_connections)
{
grd_throttler_deny_connection (throttler, peer->name, connection);
return;
@@ -413,15 +429,40 @@ grd_throttler_handle_connection (GrdThrottler *throttler,
maybe_delay_connection (throttler, connection, peer, now_us);
}
+void
+grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits,
+ int limit)
+{
+ limits->max_global_connections = limit;
+}
+
+GrdThrottlerLimits *
+grd_throttler_limits_new (void)
+{
+ GrdThrottlerLimits *limits;
+
+ limits = g_new0 (GrdThrottlerLimits, 1);
+ limits->max_global_connections = DEFAULT_MAX_GLOBAL_CONNECTIONS;
+ limits->max_connections_per_peer = DEFAULT_MAX_CONNECTIONS_PER_PEER;
+ limits->max_pending_connections = DEFAULT_MAX_PENDING_CONNECTIONS;
+ limits->max_attempts_per_second = DEFAULT_MAX_ATTEMPTS_PER_SECOND;
+
+ return limits;
+}
+
GrdThrottler *
-grd_throttler_new (GrdThrottlerAllowCallback allow_callback,
- gpointer user_data)
+grd_throttler_new (GrdThrottlerLimits *limits,
+ GrdThrottlerAllowCallback allow_callback,
+ gpointer user_data)
{
GrdThrottler *throttler;
+ g_assert (limits);
+
throttler = g_object_new (GRD_TYPE_THROTTLER, NULL);
throttler->allow_callback = allow_callback;
throttler->user_data = user_data;
+ throttler->limits = limits;
return throttler;
}
@@ -443,6 +484,7 @@ grd_throttler_finalize (GObject *object)
g_clear_pointer (&throttler->delayed_connections_source, g_source_destroy);
g_clear_pointer (&throttler->peers, g_hash_table_unref);
+ g_clear_pointer (&throttler->limits, g_free);
G_OBJECT_CLASS (grd_throttler_parent_class)->finalize (object);
}
diff --git a/src/grd-throttler.h b/src/grd-throttler.h
index d57d653a..9e72b8f1 100644
--- a/src/grd-throttler.h
+++ b/src/grd-throttler.h
@@ -23,6 +23,8 @@
#include <gio/gio.h>
#include <glib-object.h>
+typedef struct _GrdThrottlerLimits GrdThrottlerLimits;
+
#define GRD_TYPE_THROTTLER (grd_throttler_get_type())
G_DECLARE_FINAL_TYPE (GrdThrottler, grd_throttler, GRD, THROTTLER, GObject)
@@ -34,8 +36,16 @@ void
grd_throttler_handle_connection (GrdThrottler *throttler,
GSocketConnection *connection);
+void
+grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits,
+ int limit);
+
+GrdThrottlerLimits *
+grd_throttler_limits_new (void);
+
GrdThrottler *
-grd_throttler_new (GrdThrottlerAllowCallback allow_callback,
- gpointer user_data);
+grd_throttler_new (GrdThrottlerLimits *limits,
+ GrdThrottlerAllowCallback allow_callback,
+ gpointer user_data);
#endif /* GRD_THROTTLER_H */
--
2.51.0
From 296492022673e322df037c2fe9baa6b485887676 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Fri, 30 May 2025 16:55:46 +0200
Subject: [PATCH 11/13] vnc-server: Hook up VNC server to the throttler
This allows us to replace existing single session limitation with
limiting the throttler to one accepted connection at any given time.
---
src/grd-session-vnc.c | 2 ++
src/grd-vnc-server.c | 48 +++++++++++++++++++++++++++++++------------
2 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c
index 87cc7d86..ba0926f2 100644
--- a/src/grd-session-vnc.c
+++ b/src/grd-session-vnc.c
@@ -34,6 +34,7 @@
#include "grd-prompt.h"
#include "grd-settings.h"
#include "grd-stream.h"
+#include "grd-utils.h"
#include "grd-vnc-server.h"
#include "grd-vnc-pipewire-stream.h"
@@ -942,6 +943,7 @@ grd_session_vnc_stop (GrdSession *session)
grd_session_vnc_pause (session_vnc);
+ grd_close_connection_and_notify (session_vnc->connection);
g_clear_object (&session_vnc->connection);
g_clear_object (&session_vnc->clipboard_vnc);
g_clear_pointer (&session_vnc->rfb_screen->frameBuffer, g_free);
diff --git a/src/grd-vnc-server.c b/src/grd-vnc-server.c
index 2f8229b2..fc5020f0 100644
--- a/src/grd-vnc-server.c
+++ b/src/grd-vnc-server.c
@@ -31,6 +31,7 @@
#include "grd-context.h"
#include "grd-debug.h"
#include "grd-session-vnc.h"
+#include "grd-throttler.h"
#include "grd-utils.h"
#include "grd-vnc-tls.h"
@@ -45,6 +46,8 @@ struct _GrdVncServer
{
GSocketService parent;
+ GrdThrottler *throttler;
+
GList *sessions;
GList *stopped_sessions;
@@ -55,6 +58,11 @@ struct _GrdVncServer
G_DEFINE_TYPE (GrdVncServer, grd_vnc_server, G_TYPE_SOCKET_SERVICE)
+static void
+allow_connection_cb (GrdThrottler *throttler,
+ GSocketConnection *connection,
+ gpointer user_data);
+
GrdContext *
grd_vnc_server_get_context (GrdVncServer *vnc_server)
{
@@ -105,22 +113,15 @@ on_session_stopped (GrdSession *session, GrdVncServer *vnc_server)
}
}
-static gboolean
-on_incoming (GSocketService *service,
- GSocketConnection *connection)
+static void
+allow_connection_cb (GrdThrottler *throttler,
+ GSocketConnection *connection,
+ gpointer user_data)
{
- GrdVncServer *vnc_server = GRD_VNC_SERVER (service);
+ GrdVncServer *vnc_server = GRD_VNC_SERVER (user_data);
GrdSessionVnc *session_vnc;
- g_debug ("New incoming VNC connection");
-
- if (vnc_server->sessions)
- {
- /* TODO: Add the rfbScreen instance to GrdVncServer to support multiple
- * sessions. */
- g_message ("Refusing new VNC connection: already an active session");
- return TRUE;
- }
+ g_debug ("Creating new VNC session");
session_vnc = grd_session_vnc_new (vnc_server, connection);
vnc_server->sessions = g_list_append (vnc_server->sessions, session_vnc);
@@ -128,7 +129,16 @@ on_incoming (GSocketService *service,
g_signal_connect (session_vnc, "stopped",
G_CALLBACK (on_session_stopped),
vnc_server);
+}
+static gboolean
+on_incoming (GSocketService *service,
+ GSocketConnection *connection)
+{
+ GrdVncServer *vnc_server = GRD_VNC_SERVER (service);
+
+ grd_throttler_handle_connection (vnc_server->throttler,
+ connection);
return TRUE;
}
@@ -228,6 +238,8 @@ grd_vnc_server_stop (GrdVncServer *vnc_server)
grd_vnc_server_cleanup_stopped_sessions (vnc_server);
g_clear_handle_id (&vnc_server->cleanup_sessions_idle_id,
g_source_remove);
+
+ g_clear_object (&vnc_server->throttler);
}
static void
@@ -276,6 +288,7 @@ grd_vnc_server_dispose (GObject *object)
g_assert (!vnc_server->sessions);
g_assert (!vnc_server->stopped_sessions);
g_assert (!vnc_server->cleanup_sessions_idle_id);
+ g_assert (!vnc_server->throttler);
G_OBJECT_CLASS (grd_vnc_server_parent_class)->dispose (object);
}
@@ -302,6 +315,15 @@ grd_vnc_server_constructed (GObject *object)
static void
grd_vnc_server_init (GrdVncServer *vnc_server)
{
+ GrdThrottlerLimits *limits;
+
+ limits = grd_throttler_limits_new ();
+ /* TODO: Add the rfbScreen instance to GrdVncServer to support multiple
+ * sessions. */
+ grd_throttler_limits_set_max_global_connections (limits, 1);
+ vnc_server->throttler = grd_throttler_new (limits,
+ allow_connection_cb,
+ vnc_server);
}
static void
--
2.51.0
From a57cf99969a71a44eabca06fbf5e548310fcb9e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Mon, 30 Jun 2025 16:01:12 +0200
Subject: [PATCH 12/13] daemon: Add missing newline to error message
---
src/grd-daemon.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/grd-daemon.c b/src/grd-daemon.c
index 98954200..ea17df1d 100644
--- a/src/grd-daemon.c
+++ b/src/grd-daemon.c
@@ -1010,7 +1010,7 @@ main (int argc, char **argv)
if (count_trues (3, headless, system, handover) > 1)
{
- g_printerr ("Invalid option: More than one runtime mode specified");
+ g_printerr ("Invalid option: More than one runtime mode specified\n");
return EXIT_FAILURE;
}
--
2.51.0
From 97e2e5d6f42f102cdeec962ce83887a4920b1b48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Mon, 30 Jun 2025 16:01:37 +0200
Subject: [PATCH 13/13] throttler: Allow overriding hard coded parallel
connections limit
Overriding is done via an argument passed to the daemon. This allows
using the same daemon with a larger amount of users, which one would
achieve by overriding the relevant systemd service file by both adding
the --max-parallel-connections argument, as well as bumping the limit of
number of open file descriptors to something adequate.
---
src/grd-daemon.c | 24 ++++++++++++++++++++++++
src/grd-rdp-server.c | 19 +++++++++++++++----
src/grd-settings.c | 19 +++++++++++++++++++
src/grd-settings.h | 5 +++++
src/grd-throttler.c | 9 ++++++---
src/grd-throttler.h | 4 +++-
src/grd-vnc-server.c | 18 +++++++++---------
7 files changed, 81 insertions(+), 17 deletions(-)
diff --git a/src/grd-daemon.c b/src/grd-daemon.c
index ea17df1d..7e1a0a34 100644
--- a/src/grd-daemon.c
+++ b/src/grd-daemon.c
@@ -49,6 +49,8 @@
#define RDP_SERVER_RESTART_DELAY_MS 3000
+#define DEFAULT_MAX_PARALLEL_CONNECTIONS 10
+
enum
{
PROP_0,
@@ -92,6 +94,9 @@ typedef struct _GrdDaemonPrivate
G_DEFINE_TYPE_WITH_PRIVATE (GrdDaemon, grd_daemon, G_TYPE_APPLICATION)
+#define QUOTE1(a) #a
+#define QUOTE(a) QUOTE1(a)
+
#ifdef HAVE_RDP
static void maybe_start_rdp_server (GrdDaemon *daemon);
#endif
@@ -969,6 +974,7 @@ main (int argc, char **argv)
gboolean handover = FALSE;
int rdp_port = -1;
int vnc_port = -1;
+ int max_parallel_connections = DEFAULT_MAX_PARALLEL_CONNECTIONS;
GOptionEntry entries[] = {
{ "version", 0, 0, G_OPTION_ARG_NONE, &print_version,
@@ -985,6 +991,10 @@ main (int argc, char **argv)
"RDP port", NULL },
{ "vnc-port", 0, 0, G_OPTION_ARG_INT, &vnc_port,
"VNC port", NULL },
+ { "max-parallel-connections", 0, 0,
+ G_OPTION_ARG_INT, &max_parallel_connections,
+ "Max number of parallel connections (0 for unlimited, "
+ "default: " QUOTE(DEFAULT_MAX_PARALLEL_CONNECTIONS) ")", NULL },
{ NULL }
};
g_autoptr (GOptionContext) option_context = NULL;
@@ -1014,6 +1024,17 @@ main (int argc, char **argv)
return EXIT_FAILURE;
}
+ if (max_parallel_connections == 0)
+ {
+ max_parallel_connections = INT_MAX;
+ }
+ else if (max_parallel_connections < 0)
+ {
+ g_printerr ("Invalid number of max parallel connections: %d\n",
+ max_parallel_connections);
+ return EXIT_FAILURE;
+ }
+
if (headless)
runtime_mode = GRD_RUNTIME_MODE_HEADLESS;
else if (system)
@@ -1060,5 +1081,8 @@ main (int argc, char **argv)
if (vnc_port != -1)
grd_settings_override_vnc_port (settings, vnc_port);
+ grd_settings_override_max_parallel_connections (settings,
+ max_parallel_connections);
+
return g_application_run (G_APPLICATION (daemon), argc, argv);
}
diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c
index 43eb2925..be1c315e 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -503,11 +503,14 @@ grd_rdp_server_dispose (GObject *object)
}
static void
-grd_rdp_server_init (GrdRdpServer *rdp_server)
+grd_rdp_server_constructed (GObject *object)
{
- rdp_server->throttler = grd_throttler_new (grd_throttler_limits_new (),
- allow_connection_cb,
- rdp_server);
+ GrdRdpServer *rdp_server = GRD_RDP_SERVER (object);
+
+ rdp_server->throttler =
+ grd_throttler_new (grd_throttler_limits_new (rdp_server->context),
+ allow_connection_cb,
+ rdp_server);
rdp_server->pending_binding_attempts = RDP_SERVER_N_BINDING_ATTEMPTS;
@@ -518,6 +521,13 @@ grd_rdp_server_init (GrdRdpServer *rdp_server)
* Run the primitives benchmark here to save time, when initializing a session
*/
primitives_get ();
+
+ G_OBJECT_CLASS (grd_rdp_server_parent_class)->constructed (object);
+}
+
+static void
+grd_rdp_server_init (GrdRdpServer *rdp_server)
+{
}
static void
@@ -528,6 +538,7 @@ grd_rdp_server_class_init (GrdRdpServerClass *klass)
object_class->set_property = grd_rdp_server_set_property;
object_class->get_property = grd_rdp_server_get_property;
object_class->dispose = grd_rdp_server_dispose;
+ object_class->constructed = grd_rdp_server_constructed;
g_object_class_install_property (object_class,
PROP_CONTEXT,
diff --git a/src/grd-settings.c b/src/grd-settings.c
index a65385ef..46b8ad4b 100644
--- a/src/grd-settings.c
+++ b/src/grd-settings.c
@@ -87,6 +87,8 @@ typedef struct _GrdSettingsPrivate
GrdVncAuthMethod auth_method;
GrdVncEncryption encryption;
} vnc;
+
+ int max_parallel_connections;
} GrdSettingsPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GrdSettings, grd_settings, G_TYPE_OBJECT)
@@ -103,6 +105,23 @@ grd_settings_get_runtime_mode (GrdSettings *settings)
return priv->runtime_mode;
}
+void
+grd_settings_override_max_parallel_connections (GrdSettings *settings,
+ int max_parallel_connections)
+{
+ GrdSettingsPrivate *priv = grd_settings_get_instance_private (settings);
+
+ priv->max_parallel_connections = max_parallel_connections;
+}
+
+int
+grd_settings_get_max_parallel_connections (GrdSettings *settings)
+{
+ GrdSettingsPrivate *priv = grd_settings_get_instance_private (settings);
+
+ return priv->max_parallel_connections;
+}
+
void
grd_settings_override_rdp_port (GrdSettings *settings,
int port)
diff --git a/src/grd-settings.h b/src/grd-settings.h
index 6660b962..94dfd3b2 100644
--- a/src/grd-settings.h
+++ b/src/grd-settings.h
@@ -37,6 +37,11 @@ struct _GrdSettingsClass
GrdRuntimeMode grd_settings_get_runtime_mode (GrdSettings *settings);
+void grd_settings_override_max_parallel_connections (GrdSettings *settings,
+ int max_parallel_connections);
+
+int grd_settings_get_max_parallel_connections (GrdSettings *settings);
+
void grd_settings_override_rdp_port (GrdSettings *settings,
int port);
diff --git a/src/grd-throttler.c b/src/grd-throttler.c
index 0429a089..9ec31b35 100644
--- a/src/grd-throttler.c
+++ b/src/grd-throttler.c
@@ -21,9 +21,10 @@
#include "grd-throttler.h"
+#include "grd-context.h"
+#include "grd-settings.h"
#include "grd-utils.h"
-#define DEFAULT_MAX_GLOBAL_CONNECTIONS 10
#define DEFAULT_MAX_CONNECTIONS_PER_PEER 5
#define DEFAULT_MAX_PENDING_CONNECTIONS 5
#define DEFAULT_MAX_ATTEMPTS_PER_SECOND 10
@@ -437,12 +438,14 @@ grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits,
}
GrdThrottlerLimits *
-grd_throttler_limits_new (void)
+grd_throttler_limits_new (GrdContext *context)
{
+ GrdSettings *settings = grd_context_get_settings (context);
GrdThrottlerLimits *limits;
limits = g_new0 (GrdThrottlerLimits, 1);
- limits->max_global_connections = DEFAULT_MAX_GLOBAL_CONNECTIONS;
+ limits->max_global_connections =
+ grd_settings_get_max_parallel_connections (settings);
limits->max_connections_per_peer = DEFAULT_MAX_CONNECTIONS_PER_PEER;
limits->max_pending_connections = DEFAULT_MAX_PENDING_CONNECTIONS;
limits->max_attempts_per_second = DEFAULT_MAX_ATTEMPTS_PER_SECOND;
diff --git a/src/grd-throttler.h b/src/grd-throttler.h
index 9e72b8f1..a9554202 100644
--- a/src/grd-throttler.h
+++ b/src/grd-throttler.h
@@ -23,6 +23,8 @@
#include <gio/gio.h>
#include <glib-object.h>
+#include "grd-types.h"
+
typedef struct _GrdThrottlerLimits GrdThrottlerLimits;
#define GRD_TYPE_THROTTLER (grd_throttler_get_type())
@@ -41,7 +43,7 @@ grd_throttler_limits_set_max_global_connections (GrdThrottlerLimits *limits,
int limit);
GrdThrottlerLimits *
-grd_throttler_limits_new (void);
+grd_throttler_limits_new (GrdContext *context);
GrdThrottler *
grd_throttler_new (GrdThrottlerLimits *limits,
diff --git a/src/grd-vnc-server.c b/src/grd-vnc-server.c
index fc5020f0..705ed416 100644
--- a/src/grd-vnc-server.c
+++ b/src/grd-vnc-server.c
@@ -298,6 +298,7 @@ grd_vnc_server_constructed (GObject *object)
{
GrdVncServer *vnc_server = GRD_VNC_SERVER (object);
GrdSettings *settings = grd_context_get_settings (vnc_server->context);
+ GrdThrottlerLimits *limits;
if (grd_get_debug_flags () & GRD_DEBUG_VNC)
rfbLogEnable (1);
@@ -309,21 +310,20 @@ grd_vnc_server_constructed (GObject *object)
vnc_server);
sync_encryption_settings (vnc_server);
- G_OBJECT_CLASS (grd_vnc_server_parent_class)->constructed (object);
-}
-
-static void
-grd_vnc_server_init (GrdVncServer *vnc_server)
-{
- GrdThrottlerLimits *limits;
-
- limits = grd_throttler_limits_new ();
+ limits = grd_throttler_limits_new (vnc_server->context);
/* TODO: Add the rfbScreen instance to GrdVncServer to support multiple
* sessions. */
grd_throttler_limits_set_max_global_connections (limits, 1);
vnc_server->throttler = grd_throttler_new (limits,
allow_connection_cb,
vnc_server);
+
+ G_OBJECT_CLASS (grd_vnc_server_parent_class)->constructed (object);
+}
+
+static void
+grd_vnc_server_init (GrdVncServer *vnc_server)
+{
}
static void
--
2.51.0