[PATCH v2] gdbus: Remove broad match rule and add proxy filter support

Xiuzhuo Shang <[email protected]> Wed, 15 Jul 2026 16:59:50 +0800
Newsgroups dev.linux.lists.ofono,org.kernel.vger.linux-bluetooth
Message-ID <[email protected]>
Problem
-------
On embedded platforms running continuous BLE scanning, bluetoothd
eventually stalls with its D-Bus socket to dbus-daemon full. strace
on a hung bluetoothd shows a repeating pattern:

  sendmsg(7, {org.bluez.Device1 PropertiesChanged}, MSG_NOSIGNAL)
      = -1 EAGAIN (Resource temporarily unavailable)
  ppoll([{fd=7, events=POLLOUT}], 1, {tv_sec=0, tv_nsec=0}) = 0 (Timeout)

fd=7 never becomes writable; bluetoothd's GMainLoop remains stuck
waiting for POLLOUT and cannot dispatch any further D-Bus events,
making the daemon appear hung and unresponsive to commands.

The backpressure chain that causes this:
  1. BLE scanning generates high-rate PropertiesChanged(RSSI) signals
     (~400/s with typical BLE traffic).
  2. ofono's broad path_namespace='/' match rule causes dbus-daemon to
     route all these signals to ofono even though ofono has no use for
     BLE RSSI data.
  3. ofono's single-threaded GLib loop cannot consume them fast enough;
     undelivered messages accumulate inside dbus-daemon (457 MB
     observed after ~3 hours of scanning).
  4. dbus-daemon, busy draining its write queue toward ofono, stops
     reading from bluetoothd's socket in time; bluetoothd's kernel
     send buffer fills up and sendmsg() returns EAGAIN.
  5. With POLLOUT registered on fd=7, bluetoothd's GMainLoop stalls
     and can no longer send D-Bus replies or signals.

Fix
---
Three related changes:

1. Remove the broad type='signal',sender=<svc>,path_namespace=<path>
   match rule from g_dbus_client_new_full(). This rule was the sole
   feeder for the signal_func path in message_filter(). ofono never
   calls g_dbus_client_set_signal_watch() so signal_func is always
   NULL; the broad rule therefore served no purpose and caused
   dbus-daemon to route every bluetoothd signal to ofono.

2. Remove the now-empty match_rules GPtrArray infrastructure
   (field declaration, init, AddMatch loop, RemoveMatch loop, free).
   No match rules are added to this array any more.

3. Add a generic GDBusProxyFilterFunction callback and
   g_dbus_client_set_proxy_filter() API to GDBusClient. The filter
   is called from parse_properties() before proxy_new(), so a FALSE
   return prevents both proxy creation and per-device
   PropertiesChanged watch registration. This keeps all BlueZ-
   specific logic out of the gdbus layer.

   Use this in hfp_hf_bluez5.c to skip Device1 proxies for BLE
   random-address devices: ofono only needs BR/EDR (AddressType=
   'public') devices for HFP/HSP. Skipping BLE proxies prevents
   dbus-daemon from registering per-device PropertiesChanged match
   rules for advertising peripherals and eliminates the remaining
   RSSI signal delivery to ofono.

Together these changes prevent dbus-daemon from routing BLE
advertising signals to ofono, breaking the backpressure chain:
dbus-daemon memory stops growing, its write queue drains, and
bluetoothd's send buffer clears so that sendmsg() no longer returns
EAGAIN and the GMainLoop stall is resolved.

Signed-off-by: Xiuzhuo Shang <[email protected]>
---
Changes in v2:
 - Drop Change 1 (BLE address-type filter in parse_properties()) per
   review feedback; BlueZ-specific logic does not belong in the gdbus
   layer.
 - Add generic GDBusProxyFilterFunction callback and
   g_dbus_client_set_proxy_filter() API to GDBusClient. The filter is
   invoked before proxy_new() so a FALSE return prevents both proxy
   creation and per-device PropertiesChanged watch registration.
 - Use the new filter in hfp_hf_bluez5.c to skip Device1 proxies for
   BLE random-address devices, keeping all BlueZ-specific logic in the
   plugin as suggested.
 - Remove now-empty match_rules GPtrArray infrastructure (field,
   init, AddMatch loop, RemoveMatch loop, free) and unused variables.
 - Link to v1:
   https://lore.kernel.org/ofono/[email protected]/

 gdbus/client.c          | 45 +++++++++++++++++++++--------------------
 gdbus/gdbus.h           |  8 ++++++++
 plugins/hfp_hf_bluez5.c | 38 ++++++++++++++++++++++++++++++++++
 3 files changed, 69 insertions(+), 22 deletions(-)

diff --git a/gdbus/client.c b/gdbus/client.c
index 48711ae8..fa2e75c0 100644
--- a/gdbus/client.c
+++ b/gdbus/client.c
@@ -46,7 +46,6 @@ struct GDBusClient {
 	guint watch;
 	guint added_watch;
 	guint removed_watch;
-	GPtrArray *match_rules;
 	DBusPendingCall *pending_call;
 	DBusPendingCall *get_objects_call;
 	GDBusWatchFunction connect_func;
@@ -61,6 +60,8 @@ struct GDBusClient {
 	GDBusClientFunction ready;
 	void *ready_data;
 	GDBusPropertyFunction property_changed;
+	GDBusProxyFilterFunction proxy_filter;
+	void *filter_user_data;
 	void *user_data;
 	GList *proxy_list;
 };
@@ -943,6 +944,14 @@ static void parse_properties(GDBusClient *client, const char *path,
 		return;
 	}
 
+	if (client->proxy_filter) {
+		DBusMessageIter copy = *iter;
+
+		if (!client->proxy_filter(client, path, interface,
+					&copy, client->filter_user_data))
+			return;
+	}
+
 	proxy = proxy_new(client, path, interface);
 	if (proxy == NULL)
 		return;
@@ -1211,7 +1220,6 @@ GDBusClient *g_dbus_client_new_full(DBusConnection *connection,
 							const char *root_path)
 {
 	GDBusClient *client;
-	unsigned int i;
 
 	if (!connection || !service)
 		return NULL;
@@ -1232,9 +1240,6 @@ GDBusClient *g_dbus_client_new_full(DBusConnection *connection,
 	client->root_path = g_strdup(root_path);
 	client->connected = FALSE;
 
-	client->match_rules = g_ptr_array_sized_new(1);
-	g_ptr_array_set_free_func(client->match_rules, g_free);
-
 	client->watch = g_dbus_add_service_watch(connection, service,
 						service_connect,
 						service_disconnect,
@@ -1255,14 +1260,6 @@ GDBusClient *g_dbus_client_new_full(DBusConnection *connection,
 						"InterfacesRemoved",
 						interfaces_removed,
 						client, NULL);
-	g_ptr_array_add(client->match_rules, g_strdup_printf("type='signal',"
-				"sender='%s',path_namespace='%s'",
-				client->service_name, client->base_path));
-
-	for (i = 0; i < client->match_rules->len; i++) {
-		modify_match(client->dbus_conn, "AddMatch",
-				g_ptr_array_index(client->match_rules, i));
-	}
 
 	return g_dbus_client_ref(client);
 }
@@ -1279,8 +1276,6 @@ GDBusClient *g_dbus_client_ref(GDBusClient *client)
 
 void g_dbus_client_unref(GDBusClient *client)
 {
-	unsigned int i;
-
 	if (client == NULL)
 		return;
 
@@ -1297,13 +1292,6 @@ void g_dbus_client_unref(GDBusClient *client)
 		dbus_pending_call_unref(client->get_objects_call);
 	}
 
-	for (i = 0; i < client->match_rules->len; i++) {
-		modify_match(client->dbus_conn, "RemoveMatch",
-				g_ptr_array_index(client->match_rules, i));
-	}
-
-	g_ptr_array_free(client->match_rules, TRUE);
-
 	dbus_connection_remove_filter(client->dbus_conn,
 						message_filter, client);
 
@@ -1396,3 +1384,16 @@ gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client,
 
 	return TRUE;
 }
+
+gboolean g_dbus_client_set_proxy_filter(GDBusClient *client,
+					GDBusProxyFilterFunction proxy_filter,
+					void *user_data)
+{
+	if (client == NULL)
+		return FALSE;
+
+	client->proxy_filter = proxy_filter;
+	client->filter_user_data = user_data;
+
+	return TRUE;
+}
diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h
index d99c2549..cc3c4e16 100644
--- a/gdbus/gdbus.h
+++ b/gdbus/gdbus.h
@@ -347,6 +347,11 @@ typedef void (* GDBusClientFunction) (GDBusClient *client, void *user_data);
 typedef void (* GDBusProxyFunction) (GDBusProxy *proxy, void *user_data);
 typedef void (* GDBusPropertyFunction) (GDBusProxy *proxy, const char *name,
 					DBusMessageIter *iter, void *user_data);
+typedef gboolean (* GDBusProxyFilterFunction) (GDBusClient *client,
+					const char *path,
+					const char *interface,
+					DBusMessageIter *iter,
+					void *user_data);
 
 gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy,
 			GDBusPropertyFunction function, void *user_data);
@@ -377,6 +382,9 @@ gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client,
 					GDBusProxyFunction proxy_removed,
 					GDBusPropertyFunction property_changed,
 					void *user_data);
+gboolean g_dbus_client_set_proxy_filter(GDBusClient *client,
+					GDBusProxyFilterFunction proxy_filter,
+					void *user_data);
 
 #ifdef __cplusplus
 }
diff --git a/plugins/hfp_hf_bluez5.c b/plugins/hfp_hf_bluez5.c
index 5ad1674f..141dc5c4 100644
--- a/plugins/hfp_hf_bluez5.c
+++ b/plugins/hfp_hf_bluez5.c
@@ -791,6 +791,43 @@ static void proxy_added(GDBusProxy *proxy, void *user_data)
 	device_changed(proxy, path);
 }
 
+static gboolean proxy_filter(GDBusClient *client, const char *path,
+				const char *interface, DBusMessageIter *iter,
+				void *user_data)
+{
+	DBusMessageIter props, entry;
+
+	if (g_str_equal(BLUEZ_DEVICE_INTERFACE, interface) == FALSE)
+		return TRUE;
+
+	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY)
+		return TRUE;
+
+	dbus_message_iter_recurse(iter, &props);
+
+	while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) {
+		const char *key;
+
+		dbus_message_iter_recurse(&props, &entry);
+		dbus_message_iter_get_basic(&entry, &key);
+
+		if (g_str_equal(key, "AddressType") == TRUE) {
+			DBusMessageIter var;
+			const char *addr_type;
+
+			dbus_message_iter_next(&entry);
+			dbus_message_iter_recurse(&entry, &var);
+			dbus_message_iter_get_basic(&var, &addr_type);
+
+			return !g_str_equal(addr_type, "random");
+		}
+
+		dbus_message_iter_next(&props);
+	}
+
+	return TRUE;
+}
+
 static void property_changed(GDBusProxy *proxy, const char *name,
 					DBusMessageIter *iter, void *user_data)
 {
@@ -844,6 +881,7 @@ static int hfp_init(void)
 	g_dbus_client_set_connect_watch(bluez, connect_handler, NULL);
 	g_dbus_client_set_proxy_handlers(bluez, proxy_added, NULL,
 						property_changed, NULL);
+	g_dbus_client_set_proxy_filter(bluez, proxy_filter, NULL);
 
 	ofono_handsfree_audio_ref();
 
-- 
2.43.0