Return-Path: From: Jukka Rissanen To: linux-bluetooth@vger.kernel.org Subject: [RFC v2] Bluetooth: 6lowpan: Use new network management channel to connect/disconnect Date: Fri, 19 Dec 2014 14:01:22 +0200 Message-Id: <1418990482-27038-1-git-send-email-jukka.rissanen@linux.intel.com> Sender: linux-bluetooth-owner@vger.kernel.org List-ID: Use new HCI_CHANNEL_NETWORK management channel which is used by user space to connect and disconnect a 6lowpan connection. Signed-off-by: Jukka Rissanen --- Hi, this is another proposal using a new HCI_CHANNEL_NETWORK channel. The code compiles ok but is probably not useful yet. Anyway, sending this to get feedback. Marcel, is this something similar you had in mind? Cheers, Jukka include/net/bluetooth/hci_core.h | 3 + include/net/bluetooth/hci_sock.h | 1 + include/net/bluetooth/mgmt.h | 15 ++ net/bluetooth/6lowpan.c | 4 +- net/bluetooth/6lowpan.h | 18 +++ net/bluetooth/Makefile | 2 +- net/bluetooth/hci_core.c | 1 + net/bluetooth/hci_sock.c | 41 +++++ net/bluetooth/mgmt-utils.h | 95 +++++++++++ net/bluetooth/mgmt.c | 88 +---------- net/bluetooth/network.c | 331 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 513 insertions(+), 86 deletions(-) create mode 100644 net/bluetooth/6lowpan.h create mode 100644 net/bluetooth/mgmt-utils.h create mode 100644 net/bluetooth/network.c diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 3c78270..d642bc0 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1323,6 +1323,7 @@ void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 opcode); /* ----- HCI Sockets ----- */ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb); void hci_send_to_control(struct sk_buff *skb, struct sock *skip_sk); +void hci_send_to_network(struct sk_buff *skb, struct sock *skip_sk); void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb); void hci_sock_dev_event(struct hci_dev *hdev, int event); @@ -1412,6 +1413,8 @@ void mgmt_new_conn_param(struct hci_dev *hdev, bdaddr_t *bdaddr, void mgmt_reenable_advertising(struct hci_dev *hdev); void mgmt_smp_complete(struct hci_conn *conn, bool complete); +int network_channel(struct sock *sk, struct msghdr *msg, size_t msglen); + u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, u16 to_multiplier); void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __le64 rand, diff --git a/include/net/bluetooth/hci_sock.h b/include/net/bluetooth/hci_sock.h index 9a46d66..403c358 100644 --- a/include/net/bluetooth/hci_sock.h +++ b/include/net/bluetooth/hci_sock.h @@ -45,6 +45,7 @@ struct sockaddr_hci { #define HCI_CHANNEL_USER 1 #define HCI_CHANNEL_MONITOR 2 #define HCI_CHANNEL_CONTROL 3 +#define HCI_CHANNEL_NETWORK 4 struct hci_filter { unsigned long type_mask; diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h index 95c34d5..ae69822 100644 --- a/include/net/bluetooth/mgmt.h +++ b/include/net/bluetooth/mgmt.h @@ -507,6 +507,14 @@ struct mgmt_cp_start_service_discovery { } __packed; #define MGMT_START_SERVICE_DISCOVERY_SIZE 4 +#define MGMT_OP_6LOWPAN_CONNECT 0x003B +#define MGMT_OP_6LOWPAN_DISCONNECT 0x003C +struct mgmt_cp_6lowpan_info { + uint8_t addr_type; + bdaddr_t bdaddr; +} __packed; +#define MGMT_6LOWPAN_INFO_SIZE 7 + #define MGMT_EV_CMD_COMPLETE 0x0001 struct mgmt_ev_cmd_complete { __le16 opcode; @@ -689,3 +697,10 @@ struct mgmt_ev_new_conn_param { #define MGMT_EV_UNCONF_INDEX_REMOVED 0x001e #define MGMT_EV_NEW_CONFIG_OPTIONS 0x001f + +#define MGMT_EV_6LOWPAN_CONNECTED 0x0020 +#define MGMT_EV_6LOWPAN_DISCONNECTED 0x0021 +struct mgmt_ev_6lowpan_conn_param { + struct mgmt_addr_info addr; + uint32_t ifindex; +} __packed; diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c index 76617be..9589d98 100644 --- a/net/bluetooth/6lowpan.c +++ b/net/bluetooth/6lowpan.c @@ -1077,7 +1077,7 @@ static struct l2cap_chan *chan_get(void) return pchan; } -static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type) +int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type) { struct l2cap_chan *pchan; int err; @@ -1096,7 +1096,7 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type) return err; } -static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type) +int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type) { struct lowpan_peer *peer; diff --git a/net/bluetooth/6lowpan.h b/net/bluetooth/6lowpan.h new file mode 100644 index 0000000..ec233c6 --- /dev/null +++ b/net/bluetooth/6lowpan.h @@ -0,0 +1,18 @@ +/* + Copyright (c) 2014 Intel Corp. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 and + only version 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type); +int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type); + +void network_6lowpan_send_event(struct hci_dev *hdev, struct hci_conn *conn, + u8 event_type, uint32_t ifindex); diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index a5432a6..1f2a115 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -9,7 +9,7 @@ obj-$(CONFIG_BT_CMTP) += cmtp/ obj-$(CONFIG_BT_HIDP) += hidp/ obj-$(CONFIG_BT_6LOWPAN) += bluetooth_6lowpan.o -bluetooth_6lowpan-y := 6lowpan.o +bluetooth_6lowpan-y := 6lowpan.o network.o bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \ hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o sco.o lib.o \ diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 5dcacf9..85b5b33 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2042,6 +2042,7 @@ struct hci_dev *hci_dev_get(int index) read_unlock(&hci_dev_list_lock); return hdev; } +EXPORT_SYMBOL_GPL(hci_dev_get); /* ---- Inquiry support ---- */ diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 2c245fd..12e727d 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -216,6 +216,40 @@ void hci_send_to_control(struct sk_buff *skb, struct sock *skip_sk) read_unlock(&hci_sk_list.lock); } +/* Send frame to network socket */ +void hci_send_to_network(struct sk_buff *skb, struct sock *skip_sk) +{ + struct sock *sk; + + BT_DBG("len %d", skb->len); + + read_lock(&hci_sk_list.lock); + + sk_for_each(sk, &hci_sk_list.head) { + struct sk_buff *nskb; + + /* Skip the original socket */ + if (sk == skip_sk) + continue; + + if (sk->sk_state != BT_BOUND) + continue; + + if (hci_pi(sk)->channel != HCI_CHANNEL_NETWORK) + continue; + + nskb = skb_clone(skb, GFP_ATOMIC); + if (!nskb) + continue; + + if (sock_queue_rcv_skb(sk, nskb)) + kfree_skb(nskb); + } + + read_unlock(&hci_sk_list.lock); +} +EXPORT_SYMBOL_GPL(hci_send_to_network); + /* Send frame to monitor socket */ void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb) { @@ -927,6 +961,13 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock, case HCI_CHANNEL_MONITOR: err = -EOPNOTSUPP; goto done; + case HCI_CHANNEL_NETWORK: +#ifdef CONFIG_BT_6LOWPAN + err = network_channel(sk, msg, len); +#else + err = -EOPNOTSUPP; +#endif + goto done; default: err = -EINVAL; goto done; diff --git a/net/bluetooth/mgmt-utils.h b/net/bluetooth/mgmt-utils.h new file mode 100644 index 0000000..42c5aa0 --- /dev/null +++ b/net/bluetooth/mgmt-utils.h @@ -0,0 +1,95 @@ +/* + Copyright (c) 2014 Intel Corp. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 and + only version 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +static inline int cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status) +{ + struct sk_buff *skb; + struct mgmt_hdr *hdr; + struct mgmt_ev_cmd_status *ev; + int err; + + BT_DBG("sock %p, index %u, cmd %u, status %u", sk, index, cmd, status); + + skb = alloc_skb(sizeof(*hdr) + sizeof(*ev), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + hdr = (void *) skb_put(skb, sizeof(*hdr)); + + hdr->opcode = cpu_to_le16(MGMT_EV_CMD_STATUS); + hdr->index = cpu_to_le16(index); + hdr->len = cpu_to_le16(sizeof(*ev)); + + ev = (void *) skb_put(skb, sizeof(*ev)); + ev->status = status; + ev->opcode = cpu_to_le16(cmd); + + err = sock_queue_rcv_skb(sk, skb); + if (err < 0) + kfree_skb(skb); + + return err; +} + +static inline int cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status, + void *rp, size_t rp_len) +{ + struct sk_buff *skb; + struct mgmt_hdr *hdr; + struct mgmt_ev_cmd_complete *ev; + int err; + + BT_DBG("sock %p", sk); + + skb = alloc_skb(sizeof(*hdr) + sizeof(*ev) + rp_len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + hdr = (void *) skb_put(skb, sizeof(*hdr)); + + hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE); + hdr->index = cpu_to_le16(index); + hdr->len = cpu_to_le16(sizeof(*ev) + rp_len); + + ev = (void *) skb_put(skb, sizeof(*ev) + rp_len); + ev->opcode = cpu_to_le16(cmd); + ev->status = status; + + if (rp) + memcpy(ev->data, rp, rp_len); + + err = sock_queue_rcv_skb(sk, skb); + if (err < 0) + kfree_skb(skb); + + return err; +} + +static inline u8 link_to_bdaddr(u8 link_type, u8 addr_type) +{ + switch (link_type) { + case LE_LINK: + switch (addr_type) { + case ADDR_LE_DEV_PUBLIC: + return BDADDR_LE_PUBLIC; + + default: + /* Fallback to LE Random address type */ + return BDADDR_LE_RANDOM; + } + + default: + /* Fallback to BR/EDR type */ + return BDADDR_BREDR; + } +} diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 693ce8b..dcbeb96 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -33,6 +33,7 @@ #include #include "smp.h" +#include "mgmt-utils.h" #define MGMT_VERSION 1 #define MGMT_REVISION 8 @@ -244,70 +245,6 @@ static int mgmt_event(u16 event, struct hci_dev *hdev, void *data, u16 data_len, return 0; } -static int cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status) -{ - struct sk_buff *skb; - struct mgmt_hdr *hdr; - struct mgmt_ev_cmd_status *ev; - int err; - - BT_DBG("sock %p, index %u, cmd %u, status %u", sk, index, cmd, status); - - skb = alloc_skb(sizeof(*hdr) + sizeof(*ev), GFP_KERNEL); - if (!skb) - return -ENOMEM; - - hdr = (void *) skb_put(skb, sizeof(*hdr)); - - hdr->opcode = cpu_to_le16(MGMT_EV_CMD_STATUS); - hdr->index = cpu_to_le16(index); - hdr->len = cpu_to_le16(sizeof(*ev)); - - ev = (void *) skb_put(skb, sizeof(*ev)); - ev->status = status; - ev->opcode = cpu_to_le16(cmd); - - err = sock_queue_rcv_skb(sk, skb); - if (err < 0) - kfree_skb(skb); - - return err; -} - -static int cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status, - void *rp, size_t rp_len) -{ - struct sk_buff *skb; - struct mgmt_hdr *hdr; - struct mgmt_ev_cmd_complete *ev; - int err; - - BT_DBG("sock %p", sk); - - skb = alloc_skb(sizeof(*hdr) + sizeof(*ev) + rp_len, GFP_KERNEL); - if (!skb) - return -ENOMEM; - - hdr = (void *) skb_put(skb, sizeof(*hdr)); - - hdr->opcode = cpu_to_le16(MGMT_EV_CMD_COMPLETE); - hdr->index = cpu_to_le16(index); - hdr->len = cpu_to_le16(sizeof(*ev) + rp_len); - - ev = (void *) skb_put(skb, sizeof(*ev) + rp_len); - ev->opcode = cpu_to_le16(cmd); - ev->status = status; - - if (rp) - memcpy(ev->data, rp, rp_len); - - err = sock_queue_rcv_skb(sk, skb); - if (err < 0) - kfree_skb(skb); - - return err; -} - static int read_version(struct sock *sk, struct hci_dev *hdev, void *data, u16 data_len) { @@ -2898,25 +2835,6 @@ failed: return err; } -static u8 link_to_bdaddr(u8 link_type, u8 addr_type) -{ - switch (link_type) { - case LE_LINK: - switch (addr_type) { - case ADDR_LE_DEV_PUBLIC: - return BDADDR_LE_PUBLIC; - - default: - /* Fallback to LE Random address type */ - return BDADDR_LE_RANDOM; - } - - default: - /* Fallback to BR/EDR type */ - return BDADDR_BREDR; - } -} - static int get_connections(struct sock *sk, struct hci_dev *hdev, void *data, u16 data_len) { @@ -5892,6 +5810,10 @@ static const struct mgmt_handler { { set_external_config, false, MGMT_SET_EXTERNAL_CONFIG_SIZE }, { set_public_address, false, MGMT_SET_PUBLIC_ADDRESS_SIZE }, { start_service_discovery,true, MGMT_START_SERVICE_DISCOVERY_SIZE }, + + /* Next two 6LoWPAN operations are handled in network.c */ + { NULL, false, MGMT_6LOWPAN_INFO_SIZE }, + { NULL, false, MGMT_6LOWPAN_INFO_SIZE }, }; int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) diff --git a/net/bluetooth/network.c b/net/bluetooth/network.c new file mode 100644 index 0000000..09ed244 --- /dev/null +++ b/net/bluetooth/network.c @@ -0,0 +1,331 @@ +/* + Copyright (c) 2014 Intel Corp. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 and + only version 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +/* Bluetooth HCI Management interface for networking */ + +#include +#include + +#include +#include +#include +#include + +#include "mgmt-utils.h" +#include "6lowpan.h" + +static int network_event(u16 event, struct hci_dev *hdev, void *data, + u16 data_len, struct sock *skip_sk) +{ + struct sk_buff *skb; + struct mgmt_hdr *hdr; + + skb = alloc_skb(sizeof(*hdr) + data_len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + hdr = (void *) skb_put(skb, sizeof(*hdr)); + hdr->opcode = cpu_to_le16(event); + if (hdev) + hdr->index = cpu_to_le16(hdev->id); + else + hdr->index = cpu_to_le16(MGMT_INDEX_NONE); + hdr->len = cpu_to_le16(data_len); + + if (data) + memcpy(skb_put(skb, data_len), data, data_len); + + /* Time stamp */ + __net_timestamp(skb); + + hci_send_to_network(skb, skip_sk); + kfree_skb(skb); + + return 0; +} + +void network_6lowpan_send_event(struct hci_dev *hdev, struct hci_conn *conn, + u8 event_type, uint32_t ifindex) +{ + struct mgmt_ev_6lowpan_conn_param ev; + + BT_DBG("%s 6lowpan %s", hdev->name, + event_type == MGMT_EV_6LOWPAN_CONNECTED ? "connected" : + "disconnected"); + + bacpy(&ev.addr.bdaddr, &conn->dst); + ev.addr.type = link_to_bdaddr(conn->type, conn->dst_type); + ev.ifindex = ifindex; + + network_event(event_type, hdev, &ev, sizeof(ev), NULL); +} + +static int connect_6lowpan(struct sock *sk, struct hci_dev *hdev, void *data, + u16 len) +{ + struct mgmt_cp_6lowpan_info *cp = data; + struct mgmt_cp_6lowpan_info rp; + struct hci_conn *hcon; + int err; + + BT_DBG(""); + + memset(&rp, 0, sizeof(rp)); + bacpy(&rp.bdaddr, &cp->bdaddr); + rp.addr_type = cp->addr_type; + + if (!bdaddr_type_is_valid(cp->addr_type)) + return cmd_complete(sk, hdev->id, MGMT_OP_6LOWPAN_CONNECT, + MGMT_STATUS_INVALID_PARAMS, &rp, + sizeof(rp)); + + if (cp->addr_type == BDADDR_BREDR) + return cmd_complete(sk, hdev->id, MGMT_OP_6LOWPAN_CONNECT, + MGMT_STATUS_NOT_SUPPORTED, &rp, + sizeof(rp)); + + hci_dev_lock(hdev); + + hcon = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + if (!hcon) { + err = cmd_complete(sk, hdev->id, MGMT_OP_6LOWPAN_CONNECT, + MGMT_STATUS_NO_RESOURCES, &rp, + sizeof(rp)); + goto failed; + } + + err = bt_6lowpan_connect(&cp->bdaddr, cp->addr_type); + +failed: + hci_dev_unlock(hdev); + return err; +} + +static int disconnect_6lowpan(struct sock *sk, struct hci_dev *hdev, + void *data, u16 len) +{ + struct mgmt_cp_6lowpan_info *cp = data; + struct mgmt_cp_6lowpan_info rp; + struct hci_conn *hcon; + int err; + + BT_DBG(""); + + memset(&rp, 0, sizeof(rp)); + bacpy(&rp.bdaddr, &cp->bdaddr); + rp.addr_type = cp->addr_type; + + if (!bdaddr_type_is_valid(cp->addr_type)) + return cmd_complete(sk, hdev->id, MGMT_OP_6LOWPAN_DISCONNECT, + MGMT_STATUS_INVALID_PARAMS, &rp, + sizeof(rp)); + + if (cp->addr_type == BDADDR_BREDR) + return cmd_complete(sk, hdev->id, MGMT_OP_6LOWPAN_DISCONNECT, + MGMT_STATUS_NOT_SUPPORTED, &rp, + sizeof(rp)); + + hci_dev_lock(hdev); + + hcon = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); + if (!hcon) { + err = cmd_complete(sk, hdev->id, MGMT_OP_6LOWPAN_DISCONNECT, + MGMT_STATUS_NOT_CONNECTED, &rp, + sizeof(rp)); + goto failed; + } + + err = bt_6lowpan_disconnect((struct l2cap_conn *)hcon->l2cap_data, + cp->addr_type); + +failed: + hci_dev_unlock(hdev); + return err; +} + +static const struct network_handler { + int (*func) (struct sock *sk, struct hci_dev *hdev, void *data, + u16 data_len); + bool var_len; + size_t data_len; +} network_handlers[] = { + { NULL }, /* 0x0000 (no command) */ + + /* The following NULL handlers are not used by this module */ + { NULL, false, MGMT_READ_VERSION_SIZE }, + { NULL, false, MGMT_READ_COMMANDS_SIZE }, + { NULL, false, MGMT_READ_INDEX_LIST_SIZE }, + { NULL, false, MGMT_READ_INFO_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SET_DISCOVERABLE_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SET_DEV_CLASS_SIZE }, + { NULL, false, MGMT_SET_LOCAL_NAME_SIZE }, + { NULL, false, MGMT_ADD_UUID_SIZE }, + { NULL, false, MGMT_REMOVE_UUID_SIZE }, + { NULL, true, MGMT_LOAD_LINK_KEYS_SIZE }, + { NULL, true, MGMT_LOAD_LONG_TERM_KEYS_SIZE }, + { NULL, false, MGMT_DISCONNECT_SIZE }, + { NULL, false, MGMT_GET_CONNECTIONS_SIZE }, + { NULL, false, MGMT_PIN_CODE_REPLY_SIZE }, + { NULL, false, MGMT_PIN_CODE_NEG_REPLY_SIZE }, + { NULL, false, MGMT_SET_IO_CAPABILITY_SIZE }, + { NULL, false, MGMT_PAIR_DEVICE_SIZE }, + { NULL, false, MGMT_CANCEL_PAIR_DEVICE_SIZE }, + { NULL, false, MGMT_UNPAIR_DEVICE_SIZE }, + { NULL, false, MGMT_USER_CONFIRM_REPLY_SIZE }, + { NULL, false, MGMT_USER_CONFIRM_NEG_REPLY_SIZE }, + { NULL, false, MGMT_USER_PASSKEY_REPLY_SIZE }, + { NULL, false, MGMT_USER_PASSKEY_NEG_REPLY_SIZE }, + { NULL, false, MGMT_READ_LOCAL_OOB_DATA_SIZE }, + { NULL, true, MGMT_ADD_REMOTE_OOB_DATA_SIZE }, + { NULL, false, MGMT_REMOVE_REMOTE_OOB_DATA_SIZE }, + { NULL, false, MGMT_START_DISCOVERY_SIZE }, + { NULL, false, MGMT_STOP_DISCOVERY_SIZE }, + { NULL, false, MGMT_CONFIRM_NAME_SIZE }, + { NULL, false, MGMT_BLOCK_DEVICE_SIZE }, + { NULL, false, MGMT_UNBLOCK_DEVICE_SIZE }, + { NULL, false, MGMT_SET_DEVICE_ID_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SET_STATIC_ADDRESS_SIZE }, + { NULL, false, MGMT_SET_SCAN_PARAMS_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SETTING_SIZE }, + { NULL, false, MGMT_SET_PRIVACY_SIZE }, + { NULL, true, MGMT_LOAD_IRKS_SIZE }, + { NULL, false, MGMT_GET_CONN_INFO_SIZE }, + { NULL, false, MGMT_GET_CLOCK_INFO_SIZE }, + { NULL, false, MGMT_ADD_DEVICE_SIZE }, + { NULL, false, MGMT_REMOVE_DEVICE_SIZE }, + { NULL, true, MGMT_LOAD_CONN_PARAM_SIZE }, + { NULL, false, MGMT_READ_UNCONF_INDEX_LIST_SIZE }, + { NULL, false, MGMT_READ_CONFIG_INFO_SIZE }, + { NULL, false, MGMT_SET_EXTERNAL_CONFIG_SIZE }, + { NULL, false, MGMT_SET_PUBLIC_ADDRESS_SIZE }, + { NULL, true, MGMT_START_SERVICE_DISCOVERY_SIZE }, + + { connect_6lowpan, false, MGMT_6LOWPAN_INFO_SIZE }, + { disconnect_6lowpan, false, MGMT_6LOWPAN_INFO_SIZE }, +}; + +int network_channel(struct sock *sk, struct msghdr *msg, size_t msglen) +{ + void *buf; + u8 *cp; + struct mgmt_hdr *hdr; + u16 opcode, index, len; + struct hci_dev *hdev = NULL; + const struct network_handler *handler; + int err; + + BT_DBG("got %zu bytes", msglen); + + if (msglen < sizeof(*hdr)) + return -EINVAL; + + buf = kmalloc(msglen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (memcpy_from_msg(buf, msg, msglen)) { + err = -EFAULT; + goto done; + } + + hdr = buf; + opcode = __le16_to_cpu(hdr->opcode); + index = __le16_to_cpu(hdr->index); + len = __le16_to_cpu(hdr->len); + + if (len != msglen - sizeof(*hdr)) { + err = -EINVAL; + goto done; + } + + if (index != MGMT_INDEX_NONE) { + hdev = hci_dev_get(index); + if (!hdev) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_INDEX); + goto done; + } + + if (test_bit(HCI_SETUP, &hdev->dev_flags) || + test_bit(HCI_CONFIG, &hdev->dev_flags) || + test_bit(HCI_USER_CHANNEL, &hdev->dev_flags)) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_INDEX); + goto done; + } + + if (test_bit(HCI_UNCONFIGURED, &hdev->dev_flags)) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_INDEX); + goto done; + } + } + + if (opcode >= ARRAY_SIZE(network_handlers) || + network_handlers[opcode].func == NULL) { + BT_DBG("Unknown op %u", opcode); + err = cmd_status(sk, index, opcode, + MGMT_STATUS_UNKNOWN_COMMAND); + goto done; + } + + if (hdev && (opcode <= MGMT_OP_READ_INDEX_LIST || + opcode == MGMT_OP_READ_UNCONF_INDEX_LIST)) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_INDEX); + goto done; + } + + if (!hdev && (opcode > MGMT_OP_READ_INDEX_LIST && + opcode != MGMT_OP_READ_UNCONF_INDEX_LIST)) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_INDEX); + goto done; + } + + handler = &network_handlers[opcode]; + + if ((handler->var_len && len < handler->data_len) || + (!handler->var_len && len != handler->data_len)) { + err = cmd_status(sk, index, opcode, + MGMT_STATUS_INVALID_PARAMS); + goto done; + } + + cp = buf + sizeof(*hdr); + + err = handler->func(sk, hdev, cp, len); + if (err < 0) + goto done; + + err = msglen; + +done: + if (hdev) + hci_dev_put(hdev); + + kfree(buf); + return err; +} +EXPORT_SYMBOL_GPL(network_channel); -- 1.8.3.1