Linux bluetooth development
 help / color / mirror / Atom feed
From: Philipp Dunkel <pip@pipobscure.com>
To: linux-bluetooth@vger.kernel.org
Cc: Philipp Dunkel <pip@pipobscure.com>
Subject: [PATCH BlueZ v2] adapter: Infer BR/EDR only at public LE addresses
Date: Fri, 17 Jul 2026 10:23:47 +0200	[thread overview]
Message-ID: <20260717082347.8429-1-pip@pipobscure.com> (raw)
In-Reply-To: <20260716204514.79356-1-pip@pipobscure.com>

btd_adapter_device_found() records BR/EDR support for any LE device
whose advertising Flags lack "BR/EDR Not Supported" (EIR_BREDR_UNSUP).
The address type is not considered:

    if (bdaddr_type != BDADDR_BREDR && eir_data.flags &&
                    !(eir_data.flags & EIR_BREDR_UNSUP)) {
            device_set_bredr_support(dev);

When the advertisement uses a random address the device gets a BR/EDR
bearer whose address is that random address, and start_discovery_cb()
then prefers SDP over GATT:

    if (device->bredr)
            device_browse_sdp(device, NULL);
    else
            device_browse_gatt(device, NULL);

SDP requires a BR/EDR ACL, so bluetoothd pages the random address. A
random address is not a BD_ADDR and cannot be paged, so the attempt
always ends in Page Timeout, and the failure tears down the working LE
link:

  connect_failed_callback() hci0 5A:B4:98:1F:CC:04 status 4
  bonding_attempt_complete() hci0 bdaddr 5A:B4:98:1F:CC:04
      type 0 status 0x4
  device_bonding_complete() bonding (nil) status 0x04
  browse_request_cancel()
  btd_gatt_database_att_disconnected()

Nothing requested the connection ("bonding (nil)") and the LE link was
healthy. Any LE association that outlives the page timeout is killed by
it; measured on one host, applications get about 4.7 s from connect
before the link disappears, of which GATT discovery already takes 2.3 s.

Current phones advertise Flags 0x1a (LE General Discoverable plus
Simultaneous LE and BR/EDR, with BR/EDR Not Supported clear) from
resolvable private addresses, so this triggers routinely rather than in
some corner case.

The Flags bits describe what the device supports. They do not promise
that the address it is advertising from is a BD_ADDR. A dual-mode device
uses the same Public Device Address for BR/EDR and LE, so the inference
is sound only for BDADDR_LE_PUBLIC. Restrict it to that.

The other two device_set_bredr_support() callers already require the
device to have been seen over BR/EDR (bdaddr_type == BDADDR_BREDR), so
they are unaffected. A device later seen over BR/EDR, or whose RPA is
resolved to a public identity, still gets BR/EDR support recorded there.
---
Changes in v2:
- Commit message only; the code change is byte-for-byte identical to v1.
- Dropped the "Assisted-by:" trailer, which checkpatch rejected as a
  non-standard signature with an unrecognized email address. The
  disclosure is kept below the --- instead, see the note at the end.
- Quoted the two code excerpts with spaces rather than hard tabs
  (gitlint B3).
- Wrapped the bonding_attempt_complete() log line, which was 76 chars,
  onto a continuation line to fit 75. The text is unchanged.

This patch was developed with assistance from Claude Code
(claude-opus-4-8). It was reviewed line by line and the results below
reproduced on real hardware by me before submitting; nothing here is
inferred.

Verified on a dual-mode controller with ControllerMode=dual, against iOS and
Android peers advertising Flags 0x1a from resolvable private addresses. A GATT
association that includes a ~7 s user prompt, which previously failed every
time at ~4.7 s with the Page Timeout above, now completes:

  before:  connect +0.0s, GATT resolved +2.3s, first exchange +2.7s,
           Page Timeout +4.7s -> LE link destroyed, transfer never completed
  after:   same host, same peers, classic still enabled, transfer completes
           with the user prompt taking 7.0s of it

Setting ControllerMode=le instead of applying the patch also makes the failure
go away, which independently confirms the mechanism: with no classic radio
there is no page to time out. The patch achieves the same without giving up
BR/EDR, and A2DP/HFP continue to work on the host afterwards.

make is clean with no new warnings in src/adapter.c, and make check passes
39/39.

No unit test is included: src/adapter.c is linked only into bluetoothd and not
into any unit test target, so btd_adapter_device_found() cannot be exercised by
the existing harness. Extracting the check into a unit-testable helper looked
like scope creep for a bug fix, but I am happy to add that if preferred.

 src/adapter.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/adapter.c b/src/adapter.c
index 4ffa32a..30e7679 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -7415,8 +7415,17 @@ void btd_adapter_device_found(struct btd_adapter *adapter,
 	 * older kernels send separate adv_ind and scan_rsp. Newer
 	 * kernels send them merged, so once we know which mgmt version
 	 * supports this we can make the non-zero check conditional.
+	 *
+	 * Only infer BR/EDR support when the LE address is a Public Device
+	 * Address. A dual-mode device uses the same Public Device Address for
+	 * BR/EDR and LE, so in that case the advertised address is also a valid
+	 * BD_ADDR to page. A random address (static, resolvable or
+	 * non-resolvable) has no defined relationship to the device's BD_ADDR,
+	 * so recording BR/EDR support against it makes the device look pageable
+	 * at an address that can never be paged: SDP discovery then fails with
+	 * Page Timeout and takes the working LE link down with it.
 	 */
-	if (bdaddr_type != BDADDR_BREDR && eir_data.flags &&
+	if (bdaddr_type == BDADDR_LE_PUBLIC && eir_data.flags &&
 					!(eir_data.flags & EIR_BREDR_UNSUP)) {
 		device_set_bredr_support(dev);
 		/* Update last seen for BR/EDR in case its flag is set */
-- 
2.55.0


  parent reply	other threads:[~2026-07-17  8:24 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-16 20:45 [PATCH BlueZ] adapter: Infer BR/EDR only at public LE addresses Philipp Dunkel
2026-07-16 22:20 ` [BlueZ] " bluez.test.bot
2026-07-17  8:23 ` Philipp Dunkel [this message]
2026-07-17 10:53   ` [BlueZ,v2] " bluez.test.bot
2026-07-17 14:14   ` [PATCH BlueZ v2] " Luiz Augusto von Dentz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260717082347.8429-1-pip@pipobscure.com \
    --to=pip@pipobscure.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox