Re: [PATCH v1] gdbus: Skip BLE random-address devices and remove broad match rule

Xiuzhuo Shang <[email protected]> Tue, 14 Jul 2026 19:04:01 +0800
Newsgroups dev.linux.lists.ofono,org.kernel.vger.linux-bluetooth
Message-ID <[email protected]>

On 7/13/2026 10:00 PM, Luiz Augusto von Dentz wrote:
> Hi Xiuzhuo,
> 
> On Mon, Jul 13, 2026 at 3:03 AM Xiuzhuo Shang
> <[email protected]> wrote:
>>
>>
>>
>> On 7/10/2026 10:12 PM, Luiz Augusto von Dentz wrote:
>>> Hi Xiuzhuo,
>>>
>>> On Fri, Jul 10, 2026 at 3:55 AM Xiuzhuo Shang
>>> <[email protected]> wrote:
>>>>
>>>> Two related changes to reduce dbus-daemon load caused by high-rate
>>>> BLE advertising signals:
>>>>
>>>> 1. In parse_properties(), skip creating a GDBusProxy for Device1
>>>>    objects whose AddressType is 'random' (BLE-only devices). Each
>>>>    proxy registers a per-device PropertiesChanged watch via
>>>>    g_dbus_add_properties_watch(); with hundreds of BLE peripherals
>>>>    advertising simultaneously, this results in hundreds of match rules
>>>>    and dbus-daemon routing thousands of RSSI PropertiesChanged signals
>>>>    per second to ofono, none of which ofono processes. The
>>>>    AddressType property is available in the InterfacesAdded dict at
>>>>    the time parse_properties() is called, so the check is reliable
>>>>    and race-free.
>>>>
>>>>    BR/EDR devices (AddressType='public') are unaffected: their proxies
>>>>    are created as before, and the property_changed callback continues
>>>>    to receive Paired and ServicesResolved updates needed for HFP/HSP.
>>>>
>>>> 2. In g_dbus_client_new_full(), remove the broad
>>>>    type='signal',sender=<service>,path_namespace=<path> match rule.
>>>>    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
>>>>    (including all BLE PropertiesChanged) to ofono.
>>>>
>>>>    InterfacesAdded and InterfacesRemoved are already covered by the
>>>>    precise watches registered via g_dbus_add_signal_watch() earlier in
>>>>    g_dbus_client_new_full(), so no functionality is lost.
>>>
>>> Looks like this is on ofono though, why are you chaning the BlueZ side?
>>
>> Regarding your suggestion to move the BLE filter to hfp_hf_bluez5.c: filtering in proxy_added()
>> would only prevent ofono from processing BLE devices at the application level. The per-device
>> PropertiesChanged watch is registered inside proxy_new(), which is called before proxy_added(). So
>> dbus-daemon would still route all RSSI signals to the process — the D-Bus pressure problem would
>> remain unchanged.
>>
>> I will drop the change from the gdbus patch per your feedback
>>
>>>
>>>> Together these two changes prevent dbus-daemon from routing BLE
>>>> advertising PropertiesChanged signals to ofono entirely, eliminating
>>>> the dbus-daemon memory growth and bluetoothd socket backpressure seen
>>>> after hours of BLE scanning.
>>>>
>>>> Signed-off-by: Xiuzhuo Shang <[email protected]>
>>>> ---
>>>>  gdbus/client.c | 46 ++++++++++++++++++++++++++++++++++++++--------
>>>>  1 file changed, 38 insertions(+), 8 deletions(-)
>>>>
>>>> diff --git a/gdbus/client.c b/gdbus/client.c
>>>> index 48711ae8..dd3c17f9 100644
>>>> --- a/gdbus/client.c
>>>> +++ b/gdbus/client.c
>>>> @@ -937,6 +937,44 @@ static void parse_properties(GDBusClient *client, const char *path,
>>>>         if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE)
>>>>                 return;
>>>>
>>>> +       /*
>>>> +        * Skip BLE devices (AddressType='random') to avoid registering a
>>>> +        * per-device PropertiesChanged watch for every advertising BLE
>>>> +        * peripheral. ofono only needs BR/EDR devices for HFP/HSP; the
>>>> +        * high-rate RSSI PropertiesChanged signals from BLE scanners cause
>>>> +        * dbus-daemon memory bloat and socket backpressure in bluetoothd.
>>>> +        *
>>>> +        * The AddressType property is present in the InterfacesAdded dict
>>>> +        * (iter) at this point, so it can be checked before proxy_new().
>>>> +        */
>>>> +       if (g_str_equal(interface, "org.bluez.Device1") == TRUE) {
>>>> +               DBusMessageIter props, entry;
>>>> +               DBusMessageIter copy = *iter;
>>>> +
>>>> +               if (dbus_message_iter_get_arg_type(&copy) == DBUS_TYPE_ARRAY) {
>>>> +                       dbus_message_iter_recurse(&copy, &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);
>>>> +                                       if (g_str_equal(addr_type,
>>>> +                                                       "random") == TRUE)
>>>> +                                               return;
>>>> +                                       break;
>>>> +                               }
>>>> +                               dbus_message_iter_next(&props);
>>>> +                       }
>>>> +               }
>>>> +       }
>>>
>>> Nack, not going to introduce BlueZ specific logic into gdbus like
>>> this. If we need to rate limit the RSSI notification then it means
>>> it's not suitable to be a property, but I believe we have a threshold
>>> logic in the likes of device_set_rssi_with_delta so perhaps we shall
>>> check why it is not working in your case.
>>
>> I investigated why device_set_rssi_with_delta is not working in our case.
>>
>> Root cause in BlueZ
>>
>>   The customer scans with the following bluetoothctl sequence:
>>
>>   menu scan → transport le → duplicate-data off → back → scan le
> 
> Ok, that is 1 not the default behavior and 2 if they are setting
> duplicate-data off that means they want to see all advertisements, so
> this is working as intended.

Hi Luiz,

Thank you for the clarification. I need to correct some points
regarding duplicate-data off and the actual signal rate.

What DuplicateData does in BlueZ vs. the controller

BlueZ's DuplicateData parameter in SetDiscoveryFilter and the
controller's hardware duplicate filter are two independent mechanisms:

- The controller hardware duplicate filter (filter_dup in
  hci_le_set_scan_enable) is controlled by the kernel (hci_sync.c),
  defaulting to LE_SCAN_FILTER_DUP_ENABLE. It is only disabled for
  specific cases (AdvMonitor, Mesh, or
  HCI_QUIRK_STRICT_DUPLICATE_FILTER).
  SetDiscoveryFilter.DuplicateData does NOT directly control this.

- BlueZ's DuplicateData=true flag (parse_duplicate_data() in
  src/adapter.c:2680) controls only whether device EIR data
  (manufacturer data, service data) is updated on duplicate
  advertisements at the application layer
  (device_set_manufacturer_data(), device_set_service_data(),
  src/adapter.c:7392).

duplicate-data off has no effect

In bluetoothctl, duplicate-data off sets filter.duplicate = false
(client/main.c:1449). In set_discovery_filter_setup()
(client/main.c:1251):

  if (args->duplicate)    /* false -> DuplicateData is NOT sent */
      g_dbus_dict_append_entry(&dict, "DuplicateData", ...);

Since false is the default, DuplicateData is never included in the
SetDiscoveryFilter call. The customer's duplicate-data off step has
no effect — the controller hardware duplicate filter remains enabled
throughout.

Why RSSI delta=0 occurs

The root cause is Transport=le on a dual-mode adapter
(src/adapter.c:85):

  #define SCAN_TYPE_LE   ((1 << BDADDR_LE_PUBLIC) |
                          (1 << BDADDR_LE_RANDOM))  /* = 6 */
  #define SCAN_TYPE_DUAL (SCAN_TYPE_BREDR | SCAN_TYPE_LE) /* = 7 */

get_scan_type(adapter) returns 7 (DUAL) since the adapter supports
both BR/EDR and LE. When Transport=le is set, filter->type = 6. In
merge_discovery_filters() (src/adapter.c:2254):

  item->type == adapter_scan_type    /* 6 != 7 -> condition fails */

This triggers has_filtered_discovery = true -> filtered_discovery =
true -> device_set_rssi_with_delta(dev, rssi, 0)
(src/adapter.c:7362). With delta_threshold=0, every advertisement
emits PropertiesChanged(RSSI) regardless of RSSI change.

We have captured a bluetoothd debug log (QCS6490 board, BlueZ 5.72)
that directly confirms this sequence. The following lines are from
the log with the customer's exact scan sequence
(menu scan -> transport le -> duplicate-data off -> back -> scan le):

  # SetDiscoveryFilter called with Transport=le only, no RSSI/pathloss
  adapter.c:parse_discovery_filter_dict() filtered discovery params:
      transport: 6  rssi: 32767  pathloss: 32767
      duplicate data: false  discoverable false  pattern (null)

  # current_discovery_filter is non-NULL — confirms filtered_discovery
  # will be set to true in start_discovery_complete()
  adapter.c:start_discovery_timeout() adapter->current_discovery_filter == 1

  # filtered_discovery=1 directly confirmed by our added debug print
  adapter.c:start_discovery_complete() filtered_discovery=1 (current_discovery_filter=set)

  # delta_threshold=0 directly confirmed — every advertisement emits
  # PropertiesChanged(RSSI) regardless of whether RSSI changed
  device.c:device_set_rssi_with_delta() rssi=-86 delta_threshold=0
  device.c:device_set_rssi_with_delta() rssi=-79 delta_threshold=0
  device.c:device_set_rssi_with_delta() rssi=-72 delta_threshold=0
  ...

The log shows delta_threshold=0 on every single call. With hundreds
of BLE devices advertising, this produces an unthrottled stream of
PropertiesChanged(RSSI) signals.


> 
>>   scan le calls SetDiscoveryFilter({'Transport': 'le'}) — a transport-only filter with no RSSI or
>>   pathloss condition. In btd_adapter_device_found() (src/adapter.c):
>>
>>   if (adapter->filtered_discovery)
>>       device_set_rssi_with_delta(dev, rssi, 0);   /* delta forced to 0 */
>>   else
>>       device_set_rssi(dev, rssi);                  /* delta = RSSI_THRESHOLD = 8 */
>>
>>   filtered_discovery is set true whenever current_discovery_filter is non-NULL (adapter.c:1872), which
>>    happens for any SetDiscoveryFilter() call — including a transport-only filter. With
>>   delta_threshold=0, the condition delta < 0 is never true, so g_dbus_emit_property_changed("RSSI")
>>   fires on every BLE advertisement regardless of whether the RSSI value changed. With 280+ BLE devices
>>    advertising continuously, this produces ~95 PropertiesChanged(RSSI) signals per second that flood
>>   dbus-daemon.
> 
> Yeah, that is what you get to set duplicate-data off, that said ~95
> PropertiesChanged per seconds in not a lot of data, perhaps you are
> hitting a process that don't consume the events immediately as it
> should then the D-Bus daemon have to queue more and more memory?


Actual signal rate from the captured log

The log (30_Jun_2026/bluetooth.log.gz) shows duplicate data: true
with Transport=le. The device_found_callback distribution over 1200
active seconds is:

    0-99  events/s :   69 seconds  (6%)
  100-199 events/s :  187 seconds (16%)
  200-299 events/s :   75 seconds  (6%)
  300+    events/s :  869 seconds (72%)  <- burst, peak 392/s

The average is ~95/s but that conceals the burst pattern: 72% of the
time the rate exceeds 300/s. With 280+ BLE devices advertising,
delta=0 means every single device_found_callback unconditionally
emits a PropertiesChanged(RSSI) signal — including during bursts at
392/s.

The actual bottleneck

As you pointed out, ~95/s average may not seem large. The real
bottleneck is that WirePlumber and ofono both register a broad
path_namespace='/' match rule, causing dbus-daemon to route all
these signals to processes that immediately discard them
(message_filter() drops PropertiesChanged silently). Over 8 hours,
dbus-daemon's routing table grows to 357 MB, making routing 
progressively slower until bluetoothd's socket fills and its 
GMainLoop stalls.

How do you think the logic of bluez about delta_threshold = 0 
when just Transport=le?

> 
>>   The delta=0 path was presumably intended for clients that set RSSI/pathloss proximity filters, so
>>   that is_filter_match() gets precise RSSI tracking. A transport-only filter does not involve any
>>   proximity condition, so forcing delta=0 is unnecessary in that case.
>>
>> Proposed BlueZ fix direction
>>
>>   Only apply delta=0 when at least one active client filter has a real proximity condition (RSSI or
>>   pathloss):
>>
>>   static bool discovery_filter_has_proximity(struct btd_adapter *adapter)
>>   {
>>       GSList *l;
>>       for (l = adapter->discovery_list; l; l = g_slist_next(l)) {
>>           struct discovery_client *client = l->data;
>>           struct discovery_filter *item = client->discovery_filter;
>>           if (item && (item->rssi != DISTANCE_VAL_INVALID ||
>>                        item->pathloss != DISTANCE_VAL_INVALID))
>>               return true;
>>       }
>>       return false;
>>   }
> 
> Duplicate data does consider RSSI as well, so the real problem seems
> to be why disabling duplicate filtering is required since that is what
> causes the RSSI threshold logic not to be used. Or perhaps you don't
> trust your controller to do duplicate filtering. If so, we've gone
> full circle because if something causes too much spamming, it's also
> bad at the HCI driver, into the mgmt interface, etc, until it reaches
> D-Bus, which is why BlueZ defaults to duplicate filtering.
> 
>>   /* in btd_adapter_device_found() */
>>   if (adapter->filtered_discovery &&
>>       discovery_filter_has_proximity(adapter))
>>       device_set_rssi_with_delta(dev, rssi, 0);
>>   else
>>       device_set_rssi(dev, rssi);
>>
>>   Could you advise whether this is the right approach, or if there is a better place to fix this?
>>
>>   then I will Submit a separate BlueZ patch for the delta=0 fix once we agree on the approach.
>>
>>>
>>>>         proxy = proxy_lookup(client, path, interface);
>>>>         if (proxy) {
>>>>                 update_properties(proxy, iter, FALSE);
>>>> @@ -1255,14 +1293,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));
>>>> -       }
>>>
>>> This perhaps has a merit, but I can't recall what this watch was for.
>>> Is it matching every signal from the sender? It seems it was introduce
>>> before the watch infra existed, so if we can safely remove it we can
>>> perhaps remove the whole client-->match_rules.
>>>
>>>>         return g_dbus_client_ref(client);
>>>>  }
>>>> --
>>>> 2.43.0
>>>>
>>>
>>>
>>
> 
>