Re: [PATCH 2/2] Bluetooth: put the peer's on-air address on air when we cannot resolve
From: Luiz Augusto von Dentz
Date: Tue Sep 08 2026 - 11:51:01 EST
Hi Radek,
On Mon, Sep 7, 2026 at 7:21 PM Radek Podgorny <radek@xxxxxxxxxxx> wrote:
>
> An identity address only reaches a peer that is advertising an RPA if the
> controller resolves on our behalf. Where it cannot, the host has to put the
> peer's on-air address on air itself.
>
> hci_connect_le() used to do exactly that, swapping the caller's identity
> address for the peer's cached RPA before creating the connection:
>
> irk = hci_find_irk_by_addr(hdev, dst, dst_type);
> if (irk && bacmp(&irk->rpa, BDADDR_ANY)) {
> dst = &irk->rpa;
> dst_type = ADDR_LE_DEV_RANDOM;
> }
>
> __hci_conn_add() now resolves that RPA back to the identity address when it
> stores it, so the swap no longer survives into conn->dst and the identity
> address is what goes out. Storing the identity is right for host
> bookkeeping, but nothing translates it again on the way to the controller.
>
> A peer advertising an RPA cannot answer an identity address, so the attempt
> burns a full create-connection timeout. That is not merely a slow connect:
> a controller without extended scanning cannot scan while it is initiating,
> so every dead attempt also takes the scanner off the air for the whole
> timeout.
>
> Measured on a CYW43438, which reports neither LL Privacy nor extended
> advertising (LE features 3f 00 00 08 00 00 00 00), against a peer
> advertising a resolvable private address the host holds the IRK for, with
> the connection requested on the peer's identity address:
>
> before: LE Create Connection to the identity address, public type
> 1.61s -> 22.07s, then LE Create Connection Cancel
> LE Connection Complete: Unknown Connection Identifier (0x02)
> after: LE Create Connection to the peer's RPA, random type
> LE Connection Complete: Success
Ok, the problem is then that we are trying to an indentity address to
connect when LL Privacy is not supported, so we probably can just
revert to use the private address at hci_conn_add in LL Pirvacy is not
supported or we didn't program the address in the resolving list.
> Advertising reports reaching the host per second, same window, same five
> unrelated devices on the adapter:
>
> before 1s:2 [nothing from 2s through 21s] 22s:5 23s:3
> after 0s:11 1s:5 2s:2 3s:5 4s:3 5s:4 ... 21s:2 22s:1 23s:2
>
> One dead connect costs twenty seconds of scanning for every device on the
> adapter, not just the one being dialled. Enough of them in a row and the
> host's advertisement monitor sees nothing for long enough to power-cycle
> the adapter, dropping every link on it.
>
> Choose the address to dial rather than assuming conn->dst:
>
> - if the controller is resolving and this peer's IRK is programmed, the
> identity address is correct and the resolving list translates it.
> Testing ll_privacy_capable() alone is not enough: it reports the
> feature bit, not whether resolution is switched on and not whether this
> peer is in the list. Resolution is cleared with the other volatile
> flags on power-off and switched off again while suspend pauses
> scanning, and a peer's IRK is only programmed along the accept list
> path, so a direct-connect target, a peer without
> HCI_CONN_FLAG_ADDRESS_RESOLUTION, and one that did not fit in a full
> list are all absent from it;
> - a dst that is already a private address is what the peer is on air with
> and needs no translation;
> - otherwise use the last RPA resolved for this peer, while it is still
> fresh. A stale RPA is worse than none: the peer has already rotated
> away from it.
>
> The first branch was measured on an Intel controller that does report LL
> Privacy. With the peer's IRK programmed into the controller's resolving
> list the host puts the identity address on air and the controller
> translates it, reporting Resolved Public with the peer's RPA
> 6D:CA:DB:24:14:E9 in LE Enhanced Connection Complete. With the peer
> absent from the list the same setup dials that RPA itself.
>
> The address is chosen once in hci_le_create_conn_sync() and handed to
> whichever command builder runs, the same way own_addr_type already is, so
> the two paths cannot disagree.
>
> Store the chosen address in conn->dst when it is not already there. The
> connection complete event names the address that was dialled and
> hci_conn_hash_lookup_role() finds a connection by conn->dst, so leaving the
> identity address behind would make the event miss this connection and add a
> second one while this one waits out its timeout. le_conn_complete_evt()
> resolves the address back to the identity once the link is up, which is the
> same round trip hci_connect_le() has always relied on.
>
> Fixes: 14b06c3a88f7 ("Bluetooth: HCI: Always use the identity address when initializing a connection")
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Radek Podgorny <radek@xxxxxxxxxxx>
> ---
> net/bluetooth/hci_sync.c | 82 ++++++++++++++++++++++++++++++++++++----
> 1 file changed, 75 insertions(+), 7 deletions(-)
>
> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> index 5376ade2cdc1..136d801baa85 100644
> --- a/net/bluetooth/hci_sync.c
> +++ b/net/bluetooth/hci_sync.c
> @@ -6794,8 +6794,60 @@ static void set_ext_conn_params(struct hci_conn *conn,
> p->max_ce_len = cpu_to_le16(0x0000);
> }
>
> +/* An RPA resolved more recently than this is taken to still be what the peer
> + * is on air with. The spec-recommended rotation period is the best estimate
> + * the host has; a stale RPA costs one failed connect, while falling back to an
> + * identity address the controller cannot translate costs a full
> + * create-connection timeout that cannot succeed.
> + */
> +#define HCI_RPA_FRESH_TIMEOUT secs_to_jiffies(HCI_DEFAULT_RPA_TIMEOUT)
Not really following why this is needed though, we always scan before
connecting, so perhaps we need to update the rpa whenever we resolve
at process_adv_report:
/* Check if we need to convert to identity address */
irk = hci_get_irk(hdev, bdaddr, bdaddr_type);
if (irk) {
bdaddr = &irk->bdaddr;
bdaddr_type = irk->addr_type;
}
So we do translate the advertisements to back to the identity address,
but we don't update the irk->rpa, which is probably what shall be used
in case the controller cannot resolve.
> +/* Pick the address to put on air for an outgoing LE connection.
> + *
> + * hci_conn_add() stores the peer identity address whenever an IRK resolves,
> + * which is what host bookkeeping wants but not what reaches the peer: an
> + * identity address only gets there if the controller resolves on our behalf.
> + * Prefer an address the peer has actually been seen using.
> + *
> + * This function requires the caller holds hdev->lock.
> + */
> +static void hci_conn_select_peer_addr(struct hci_dev *hdev,
> + struct hci_conn *conn,
> + bdaddr_t *peer_addr, u8 *peer_addr_type)
> +{
> + struct smp_irk *irk;
> +
> + /* conn->dst is right both when the controller translates it for us and
> + * when it is already a private address.
> + */
> + bacpy(peer_addr, &conn->dst);
> + *peer_addr_type = conn->dst_type;
> +
> + /* Supporting LL Privacy is not enough: resolution has to be switched on
> + * and this peer's IRK actually programmed, which only happens along the
> + * accept list path.
> + */
> + if (hci_dev_test_flag(hdev, HCI_LL_RPA_RESOLUTION) &&
> + hci_bdaddr_list_lookup_with_irk(&hdev->le_resolv_list, &conn->dst,
> + conn->dst_type))
> + return;
> +
> + if (hci_bdaddr_is_rpa(&conn->dst, conn->dst_type))
> + return;
> +
> + irk = hci_find_irk_by_addr(hdev, &conn->dst, conn->dst_type);
> + if (!irk || !bacmp(&irk->rpa, BDADDR_ANY) ||
> + !time_before(jiffies, READ_ONCE(irk->rpa_jiffies) +
> + HCI_RPA_FRESH_TIMEOUT))
> + return;
> +
> + bacpy(peer_addr, &irk->rpa);
> + *peer_addr_type = ADDR_LE_DEV_RANDOM;
> +}
> +
> static int hci_le_ext_create_conn_sync(struct hci_dev *hdev,
> - struct hci_conn *conn, u8 own_addr_type)
> + struct hci_conn *conn, u8 own_addr_type,
> + bdaddr_t *peer_addr, u8 peer_addr_type)
> {
> struct hci_cp_le_ext_create_conn *cp;
> struct hci_cp_le_ext_conn_param *p;
> @@ -6807,8 +6859,8 @@ static int hci_le_ext_create_conn_sync(struct hci_dev *hdev,
>
> memset(cp, 0, sizeof(*cp));
>
> - bacpy(&cp->peer_addr, &conn->dst);
> - cp->peer_addr_type = conn->dst_type;
> + bacpy(&cp->peer_addr, peer_addr);
> + cp->peer_addr_type = peer_addr_type;
> cp->own_addr_type = own_addr_type;
>
> plen = sizeof(*cp);
> @@ -6849,7 +6901,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> {
> struct hci_cp_le_create_conn cp;
> struct hci_conn_params *params;
> - u8 own_addr_type;
> + u8 own_addr_type, peer_addr_type;
> + bdaddr_t peer_addr;
> int err;
> struct hci_conn *conn = data;
>
> @@ -6927,9 +6980,24 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> */
> set_bit(HCI_CONN_CREATE, &conn->flags);
>
> + hci_dev_lock(hdev);
> + hci_conn_select_peer_addr(hdev, conn, &peer_addr, &peer_addr_type);
> +
> + /* The connection complete event names the address that was dialled and
> + * hci_conn_hash_lookup_role() finds a connection by conn->dst, so
> + * leaving the identity address there would make the event miss this
> + * connection and build a second one. Follow the dialled address
> + * instead; le_conn_complete_evt() resolves it back once the link is
> + * up.
> + */
> + bacpy(&conn->dst, &peer_addr);
> + conn->dst_type = peer_addr_type;
> + hci_dev_unlock(hdev);
> +
> /* Send command LE Extended Create Connection if supported */
> if (use_ext_conn(hdev)) {
> - err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type);
> + err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type,
> + &peer_addr, peer_addr_type);
> goto done;
> }
>
> @@ -6938,8 +7006,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> cp.scan_interval = cpu_to_le16(hdev->le_scan_int_connect);
> cp.scan_window = cpu_to_le16(hdev->le_scan_window_connect);
>
> - bacpy(&cp.peer_addr, &conn->dst);
> - cp.peer_addr_type = conn->dst_type;
> + bacpy(&cp.peer_addr, &peer_addr);
> + cp.peer_addr_type = peer_addr_type;
> cp.own_address_type = own_addr_type;
> cp.conn_interval_min = cpu_to_le16(conn->le_conn_min_interval);
> cp.conn_interval_max = cpu_to_le16(conn->le_conn_max_interval);
> --
> 2.55.0
>
--
Luiz Augusto von Dentz