Bluetooth: hci_sock: Add support for BT_{SND,RCV}BUF
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Thu, 16 Sep 2021 20:10:46 +0000 (13:10 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Tue, 21 Sep 2021 08:44:52 +0000 (10:44 +0200)
This adds support for BT_{SND,RCV}BUF so userspace can set MTU based on
the channel usage.

Fixes: https://github.com/bluez/bluez/issues/201
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
net/bluetooth/hci_sock.c

index 55b0d177375bdf515d5352a39446e40a5f072e0d..99de17922bda9e76ee324f7bd17e6eca88152b55 100644 (file)
@@ -57,6 +57,7 @@ struct hci_pinfo {
        unsigned long     flags;
        __u32             cookie;
        char              comm[TASK_COMM_LEN];
+       __u16             mtu;
 };
 
 static struct hci_dev *hci_hdev_from_sock(struct sock *sk)
@@ -1374,6 +1375,10 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
                break;
        }
 
+       /* Default MTU to HCI_MAX_FRAME_SIZE if not set */
+       if (!hci_pi(sk)->mtu)
+               hci_pi(sk)->mtu = HCI_MAX_FRAME_SIZE;
+
        sk->sk_state = BT_BOUND;
 
 done:
@@ -1719,7 +1724,7 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
        if (flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL | MSG_ERRQUEUE | MSG_CMSG_COMPAT))
                return -EINVAL;
 
-       if (len < 4 || len > HCI_MAX_FRAME_SIZE)
+       if (len < 4 || len > hci_pi(sk)->mtu)
                return -EINVAL;
 
        buf = kmalloc(len, GFP_KERNEL);
@@ -1849,8 +1854,8 @@ drop:
        goto done;
 }
 
-static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
-                              sockptr_t optval, unsigned int len)
+static int hci_sock_setsockopt_old(struct socket *sock, int level, int optname,
+                                  sockptr_t optval, unsigned int len)
 {
        struct hci_ufilter uf = { .opcode = 0 };
        struct sock *sk = sock->sk;
@@ -1858,9 +1863,6 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
 
        BT_DBG("sk %p, opt %d", sk, optname);
 
-       if (level != SOL_HCI)
-               return -ENOPROTOOPT;
-
        lock_sock(sk);
 
        if (hci_pi(sk)->channel != HCI_CHANNEL_RAW) {
@@ -1935,18 +1937,63 @@ done:
        return err;
 }
 
-static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
-                              char __user *optval, int __user *optlen)
+static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
+                              sockptr_t optval, unsigned int len)
 {
-       struct hci_ufilter uf;
        struct sock *sk = sock->sk;
-       int len, opt, err = 0;
+       int err = 0, opt = 0;
 
        BT_DBG("sk %p, opt %d", sk, optname);
 
-       if (level != SOL_HCI)
+       if (level == SOL_HCI)
+               return hci_sock_setsockopt_old(sock, level, optname, optval,
+                                              len);
+
+       if (level != SOL_BLUETOOTH)
                return -ENOPROTOOPT;
 
+       lock_sock(sk);
+
+       switch (optname) {
+       case BT_SNDMTU:
+       case BT_RCVMTU:
+               switch (hci_pi(sk)->channel) {
+               /* Don't allow changing MTU for channels that are meant for HCI
+                * traffic only.
+                */
+               case HCI_CHANNEL_RAW:
+               case HCI_CHANNEL_USER:
+                       err = -ENOPROTOOPT;
+                       goto done;
+               }
+
+               if (copy_from_sockptr(&opt, optval, sizeof(u16))) {
+                       err = -EFAULT;
+                       break;
+               }
+
+               hci_pi(sk)->mtu = opt;
+               break;
+
+       default:
+               err = -ENOPROTOOPT;
+               break;
+       }
+
+done:
+       release_sock(sk);
+       return err;
+}
+
+static int hci_sock_getsockopt_old(struct socket *sock, int level, int optname,
+                                  char __user *optval, int __user *optlen)
+{
+       struct hci_ufilter uf;
+       struct sock *sk = sock->sk;
+       int len, opt, err = 0;
+
+       BT_DBG("sk %p, opt %d", sk, optname);
+
        if (get_user(len, optlen))
                return -EFAULT;
 
@@ -2004,6 +2051,39 @@ done:
        return err;
 }
 
+static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
+                              char __user *optval, int __user *optlen)
+{
+       struct sock *sk = sock->sk;
+       int err = 0;
+
+       BT_DBG("sk %p, opt %d", sk, optname);
+
+       if (level == SOL_HCI)
+               return hci_sock_getsockopt_old(sock, level, optname, optval,
+                                              optlen);
+
+       if (level != SOL_BLUETOOTH)
+               return -ENOPROTOOPT;
+
+       lock_sock(sk);
+
+       switch (optname) {
+       case BT_SNDMTU:
+       case BT_RCVMTU:
+               if (put_user(hci_pi(sk)->mtu, (u16 __user *)optval))
+                       err = -EFAULT;
+               break;
+
+       default:
+               err = -ENOPROTOOPT;
+               break;
+       }
+
+       release_sock(sk);
+       return err;
+}
+
 static const struct proto_ops hci_sock_ops = {
        .family         = PF_BLUETOOTH,
        .owner          = THIS_MODULE,