Bluetooth: ISO: Avoid creating child socket if PA sync is terminating
authorIulia Tanasescu <iulia.tanasescu@nxp.com>
Tue, 5 Dec 2023 16:11:40 +0000 (18:11 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 5 Feb 2024 20:14:25 +0000 (20:14 +0000)
[ Upstream commit 9f150019f176078144b02c4b9b9dbe7fd5a2fcc3 ]

When a PA sync socket is closed, the associated hcon is also unlinked
and cleaned up. If there are no other hcons marked with the
HCI_CONN_PA_SYNC flag, HCI_OP_LE_PA_TERM_SYNC is sent to controller.

Between the time of the command and the moment PA sync is terminated
in controller, residual BIGInfo reports might continue to come.
This causes a new PA sync hcon to be added, and a new socket to be
notified to user space.

This commit fixs this by adding a flag on a Broadcast listening
socket to mark when the PA sync child has been closed.

This flag is checked when BIGInfo reports are indicated in
iso_connect_ind, to avoid recreating a hcon and socket if
residual reports arrive before PA sync is terminated.

Signed-off-by: Iulia Tanasescu <iulia.tanasescu@nxp.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
net/bluetooth/iso.c

index 2132a16..0eeec64 100644 (file)
@@ -52,6 +52,7 @@ static void iso_sock_kill(struct sock *sk);
 enum {
        BT_SK_BIG_SYNC,
        BT_SK_PA_SYNC,
+       BT_SK_PA_SYNC_TERM,
 };
 
 struct iso_pinfo {
@@ -80,6 +81,11 @@ static bool iso_match_sid(struct sock *sk, void *data);
 static bool iso_match_sync_handle(struct sock *sk, void *data);
 static void iso_sock_disconn(struct sock *sk);
 
+typedef bool (*iso_sock_match_t)(struct sock *sk, void *data);
+
+static struct sock *iso_get_sock_listen(bdaddr_t *src, bdaddr_t *dst,
+                                       iso_sock_match_t match, void *data);
+
 /* ---- ISO timers ---- */
 #define ISO_CONN_TIMEOUT       (HZ * 40)
 #define ISO_DISCONN_TIMEOUT    (HZ * 2)
@@ -188,10 +194,21 @@ static void iso_chan_del(struct sock *sk, int err)
        sock_set_flag(sk, SOCK_ZAPPED);
 }
 
+static bool iso_match_conn_sync_handle(struct sock *sk, void *data)
+{
+       struct hci_conn *hcon = data;
+
+       if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags))
+               return false;
+
+       return hcon->sync_handle == iso_pi(sk)->sync_handle;
+}
+
 static void iso_conn_del(struct hci_conn *hcon, int err)
 {
        struct iso_conn *conn = hcon->iso_data;
        struct sock *sk;
+       struct sock *parent;
 
        if (!conn)
                return;
@@ -207,6 +224,25 @@ static void iso_conn_del(struct hci_conn *hcon, int err)
 
        if (sk) {
                lock_sock(sk);
+
+               /* While a PA sync hcon is in the process of closing,
+                * mark parent socket with a flag, so that any residual
+                * BIGInfo adv reports that arrive before PA sync is
+                * terminated are not processed anymore.
+                */
+               if (test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) {
+                       parent = iso_get_sock_listen(&hcon->src,
+                                                    &hcon->dst,
+                                                    iso_match_conn_sync_handle,
+                                                    hcon);
+
+                       if (parent) {
+                               set_bit(BT_SK_PA_SYNC_TERM,
+                                       &iso_pi(parent)->flags);
+                               sock_put(parent);
+                       }
+               }
+
                iso_sock_clear_timer(sk);
                iso_chan_del(sk, err);
                release_sock(sk);
@@ -543,8 +579,6 @@ static struct sock *__iso_get_sock_listen_by_sid(bdaddr_t *ba, bdaddr_t *bc,
        return NULL;
 }
 
-typedef bool (*iso_sock_match_t)(struct sock *sk, void *data);
-
 /* Find socket listening:
  * source bdaddr (Unicast)
  * destination bdaddr (Broadcast only)
@@ -1756,9 +1790,20 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags)
                /* Try to get PA sync listening socket, if it exists */
                sk = iso_get_sock_listen(&hdev->bdaddr, bdaddr,
                                                iso_match_pa_sync_flag, NULL);
-               if (!sk)
+
+               if (!sk) {
                        sk = iso_get_sock_listen(&hdev->bdaddr, bdaddr,
                                                 iso_match_sync_handle, ev2);
+
+                       /* If PA Sync is in process of terminating,
+                        * do not handle any more BIGInfo adv reports.
+                        */
+
+                       if (sk && test_bit(BT_SK_PA_SYNC_TERM,
+                                          &iso_pi(sk)->flags))
+                               return lm;
+               }
+
                if (sk) {
                        int err;