gnome-remote-desktop/connection-throttling.patch

1707 lines
52 KiB
Diff

From 7a7a6d0757829f2efb2fc416b1ba55aa52216c4b 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.49.0
From 8dcf988fb40ca328d685d3ca3cf2e7f5e2e19467 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 b99f6181..2f6ed024 100644
--- a/src/grd-daemon.c
+++ b/src/grd-daemon.c
@@ -986,7 +986,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"));
@@ -996,7 +996,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;
}
@@ -1045,7 +1044,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.49.0
From dfa19a9498f7245ac0ce3efd290dde1bdd8b865f 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 4fddfaa2..6ee93c5c 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -470,12 +470,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)
{
@@ -498,7 +492,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.49.0
From 0a5400d11a032ae0e1b9e75bc72915b892713a77 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.49.0
From 6a965f7c383a62e06a1dfb5033e7820ddff5c242 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 6ee93c5c..537d4e30 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -35,6 +35,7 @@
#define RDP_SERVER_N_BINDING_ATTEMPTS 10
#define RDP_SERVER_BINDING_ATTEMPT_INTERVAL_MS 500
+#define RDP_SERVER_SOCKET_BACKLOG_COUNT 5
enum
{
@@ -302,6 +303,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.49.0
From bc1ab53a393b886d3e5fae5b33efb8f2d3205fbc 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 6e433246..d9ae0801 100644
--- a/src/grd-utils.h
+++ b/src/grd-utils.h
@@ -98,4 +98,22 @@ gboolean grd_systemd_unit_get_active_state (GDBusProxy *unit_pro
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);
+}
+
#endif /* GRD_UTILS_H */
--
2.49.0
From 0f90337b241b200fc33565d49b6344e43d93bd0c 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 9ded2092..20e6bffa 100644
--- a/src/grd-utils.c
+++ b/src/grd-utils.c
@@ -461,3 +461,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 d9ae0801..eeb0a01c 100644
--- a/src/grd-utils.h
+++ b/src/grd-utils.h
@@ -98,6 +98,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.49.0
From 4e09eee9a71e8da5bc1fb0fcaac999fc8efff953 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 f92f9f1e..1e2cc69c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -72,6 +72,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.49.0
From 1dbffe778f0392a64c69152083290f125bfb450f 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 537d4e30..228939b5 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -31,6 +31,7 @@
#include "grd-context.h"
#include "grd-hwaccel-nvidia.h"
#include "grd-session-rdp.h"
+#include "grd-throttler.h"
#include "grd-utils.h"
#define RDP_SERVER_N_BINDING_ATTEMPTS 10
@@ -59,6 +60,8 @@ struct _GrdRdpServer
{
GSocketService parent;
+ GrdThrottler *throttler;
+
GList *sessions;
GList *stopped_sessions;
@@ -216,13 +219,25 @@ 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_nvidia)))
- return TRUE;
+ return;
rdp_server->sessions = g_list_append (rdp_server->sessions, session_rdp);
@@ -233,8 +248,6 @@ on_incoming (GSocketService *service,
g_signal_connect (session_rdp, "post-connected",
G_CALLBACK (on_session_post_connect),
rdp_server);
-
- return TRUE;
}
void
@@ -410,6 +423,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);
@@ -468,6 +483,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);
@@ -477,6 +493,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 3bf1fb7a..bdccc981 100644
--- a/src/grd-session-rdp.c
+++ b/src/grd-session-rdp.c
@@ -47,6 +47,7 @@
#include "grd-rdp-session-metrics.h"
#include "grd-rdp-telemetry.h"
#include "grd-settings.h"
+#include "grd-utils.h"
#define MAX_MONITOR_COUNT_HEADLESS 16
#define MAX_MONITOR_COUNT_SCREEN_SHARE 1
@@ -1577,6 +1578,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);
@@ -1761,6 +1763,10 @@ grd_session_rdp_dispose (GObject *object)
g_clear_object (&session_rdp->layout_manager);
clear_rdp_peer (session_rdp);
+ 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.49.0
From 61aa2a856d920ecc64abf38dfb1ca089f2626192 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 228939b5..207d9e2d 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -493,7 +493,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.49.0
From a0fa2c8afb072d2aa0886012925eccc36c2cdfb9 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 6e118d88..8f4caec0 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.49.0
From 5ba32deb4fca13b0a56d724f1bf79c5c76c39154 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 2f6ed024..2802bb1e 100644
--- a/src/grd-daemon.c
+++ b/src/grd-daemon.c
@@ -1007,7 +1007,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.49.0
From e94d1dd3015df17e88a7ee78f45d79aadfd45be6 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 2802bb1e..c04475a7 100644
--- a/src/grd-daemon.c
+++ b/src/grd-daemon.c
@@ -48,6 +48,8 @@
#define RDP_SERVER_RESTART_DELAY_MS 3000
+#define DEFAULT_MAX_PARALLEL_CONNECTIONS 10
+
enum
{
PROP_0,
@@ -91,6 +93,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
@@ -966,6 +971,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,
@@ -982,6 +988,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;
@@ -1011,6 +1021,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)
@@ -1057,5 +1078,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 207d9e2d..b4952935 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -491,11 +491,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;
@@ -506,6 +509,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
@@ -516,6 +526,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 f3475010..0720fcc4 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 547b1417..5abf8e4e 100644
--- a/src/grd-settings.h
+++ b/src/grd-settings.h
@@ -38,6 +38,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.49.0