Hi,
this is 6LoWPAN code for BT LE as described in
http://tools.ietf.org/html/draft-ietf-6lowpan-btle-12
v2:
- Change ARPHRD_IEEE802154 to ARPHRD_RAWIP. The generic code
in patches 1 and 2 is also sent to netdev mailing list.
- Sending route exporting patch 5 to netdev ml
- Check private/public BT address and toggle universal/local bit
accordingly in patch 3.
- The virtual interface template name is now shorter (bt%d)
- Various function name renames
- devtype of the interface set to "bluetooth"
v1:
- initial release
TODO:
- Discovery of 6LoWPAN service needs be automatic.
- Refactor compression and fragmentation code after these are fixed
in net/ieee802154/6lowpan.c. Bluetooth 6LoWPAN should be able to share
that part of the code.
Cheers,
Jukka
Jukka Rissanen (7):
net: if_arp: add ARPHRD_RAWIP type
ipv6: Add checks for RAWIP ARP type
Bluetooth: Initial skeleton code for BT 6LoWPAN
Bluetooth: Enable 6LoWPAN support for BT LE devices
route: Exporting ip6_route_add() so that Bluetooth 6LoWPAN can use it
Bluetooth: Set route to peer for 6LoWPAN
Bluetooth: Manually enable or disable 6LoWPAN between devices
include/net/bluetooth/l2cap.h | 1 +
include/uapi/linux/if_arp.h | 1 +
net/bluetooth/6lowpan.c | 1722 +++++++++++++++++++++++++++++++++++++++++
net/bluetooth/6lowpan.h | 28 +
net/bluetooth/Makefile | 2 +-
net/bluetooth/hci_core.c | 4 +
net/bluetooth/l2cap_core.c | 20 +-
net/ipv6/addrconf.c | 14 +-
net/ipv6/route.c | 1 +
9 files changed, 1790 insertions(+), 3 deletions(-)
create mode 100644 net/bluetooth/6lowpan.c
create mode 100644 net/bluetooth/6lowpan.h
--
1.7.11.7
Hi Jukka,
> Signed-off-by: Jukka Rissanen <[email protected]>
> ---
> net/bluetooth/6lowpan.c | 1128 ++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 1127 insertions(+), 1 deletion(-)
>
> diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
> index aff478b..1cba7fd 100644
> --- a/net/bluetooth/6lowpan.c
> +++ b/net/bluetooth/6lowpan.c
> @@ -11,6 +11,20 @@
> GNU General Public License for more details.
> */
>
> +/*
> + * The compression, uncompression and IPv6 packet fragmentation code
> + * is from net/ieee802154/6lowpan.c with these copyrights
> + *
> + * Copyright 2011, Siemens AG
> + * written by Alexander Smirnov <[email protected]>
> + *
> + * Based on patches from Jon Smirl <[email protected]>
> + * Copyright (c) 2011 Jon Smirl <[email protected]>
> + *
> + * Jon's code is based on 6lowpan implementation for Contiki which is:
> + * Copyright (c) 2008, Swedish Institute of Computer Science.
> + */
> +
I really think this should consolidated into either net/core/6lowpan.c or net/6lowpan/6lowpan.c.
Regards
Marcel
Hi Jukka,
> This is a temporary patch where user can manually enable or
> disable BT 6LoWPAN functionality between devices.
> Eventually the connection is established automatically if
> the devices are advertising suitable capability and this patch
> can be removed.
>
> If you have two devices with these BT addresses
> device1 00:11:22:33:44:55
> device2 66:77:88:99:00:11
>
> First add the desired devices manually into kernel
>
> root@dev1# echo 66:77:88:99:00:11 > /sys/kernel/debug/bluetooth/hci0/6lowpan
> root@dev2# echo 00:11:22:33:44:55 > /sys/kernel/debug/bluetooth/hci0/6lowpan
>
> then connect the devices
>
> root@dev1# hciconfig hci0 leadv
> root@dev2# hcitool lecc 00:11:22:33:44:55
>
> if the connection is established, then you can send IPv6 packets
> between these two systems using the link local addresses
>
> root@dev1# ping6 fe80::6477:88ff:fe99:0011
> root@dev2# ping6 fe80::211:22ff:fe33:4455
>
> By default 6LoWPAN connection is not established between devices,
> so you need to add the MAC addresses manually into the
> /sys/kernel/debug/bluetooth/hci0/6lowpan file.
> If you want to prevent further connections you can remove
> MAC address from the debugfs file like this
> echo "00:11:22:33:44:55 d" > /sys/kernel/debug/bluetooth/hci0/6lowpan
> Rebooting or unloading the bluetooth kernel module will also clear the
> settings from the kernel.
>
> Signed-off-by: Jukka Rissanen <[email protected]>
> ---
> net/bluetooth/6lowpan.c | 158 +++++++++++++++++++++++++++++++++++++++++++++
> net/bluetooth/6lowpan.h | 2 +
> net/bluetooth/hci_core.c | 4 ++
> net/bluetooth/l2cap_core.c | 12 ++--
> 4 files changed, 169 insertions(+), 7 deletions(-)
>
> diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
> index 4ec3813..55f455a 100644
> --- a/net/bluetooth/6lowpan.c
> +++ b/net/bluetooth/6lowpan.c
> @@ -26,6 +26,7 @@
> */
>
> #include <linux/version.h>
> +#include <linux/debugfs.h>
> #include <linux/bitops.h>
> #include <linux/if_arp.h>
> #include <linux/netdevice.h>
> @@ -1553,6 +1554,163 @@ static struct notifier_block bt_6lowpan_dev_notifier = {
> .notifier_call = device_event,
> };
>
> +static LIST_HEAD(user_enabled);
> +DEFINE_RWLOCK(user_enabled_list_lock);
> +
> +struct lowpan_enabled {
> + __u8 dev_name[HCI_MAX_NAME_LENGTH];
> + bdaddr_t addr;
> + struct list_head list;
> +};
while I would not keep doing it like this and favor a simple switch like HCI_6LOWPAN_ENABLED in hdev->dev_flags, you however need to start also tracking the address type.
Personally I think we need to just make up a UUID-128 and just add it to AD data once HCI_6LOWPAN has been enabled. We are getting close to the background scanning patches. And then we can just have code that auto-connects devices with that specific UUID-128 if 6loWPAN is enabled. That will avoid all this list storage and complex debugfs. In the end it is just 6loWPAN on/off per hdev.
What we however do have to address is the case when one LE master is connected to two LE slaves. We should not have two 6loWPAN network devices. It should be only one since they form one network.
So maybe the creating of the net_device should be bound to debugfs/hci0/6lowpan switch for on/off. Just thinking out loud here.
> +
> +bool bt_6lowpan_is_enabled(struct hci_dev *hdev, bdaddr_t *dst)
> +{
> + struct lowpan_enabled *entry, *tmp;
> + bool found = false;
> +
> + write_lock(&user_enabled_list_lock);
> + list_for_each_entry_safe(entry, tmp, &user_enabled, list) {
> + if (!strncmp(entry->dev_name, hdev->dev_name,
> + HCI_MAX_NAME_LENGTH) &&
> + !bacmp(dst, &entry->addr)) {
> + found = true;
> + break;
> + }
> + }
> + write_unlock(&user_enabled_list_lock);
> +
> + /* Check also the device list just in case we removed the
> + * device from debugfs before disconnecting.
> + */
> + if (!found) {
> + struct lowpan_dev *entry, *tmp;
> + struct lowpan_info *info;
> +
> + write_lock(&net_dev_list_lock);
> + list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices,
> + list) {
> + info = lowpan_info(entry->dev);
> + if (info->conn->hcon->hdev == hdev &&
> + !bacmp(&info->addr, dst)) {
> + found = true;
> + break;
> + }
> + }
> + write_unlock(&net_dev_list_lock);
> + }
> +
> + return found;
> +}
> +
> +static int debugfs_show(struct seq_file *f, void *p)
> +{
> + struct lowpan_enabled *entry, *tmp;
> +
> + write_lock(&user_enabled_list_lock);
> + list_for_each_entry_safe(entry, tmp, &user_enabled, list)
> + seq_printf(f, "%pMR\n", &entry->addr);
> +
> + write_unlock(&user_enabled_list_lock);
> +
> + return 0;
> +}
> +
> +static int debugfs_open(struct inode *inode, struct file *file)
> +{
> + return single_open(file, debugfs_show, inode->i_private);
> +}
> +
> +static ssize_t writer(struct file *fp, const char __user *user_buffer,
> + size_t count, loff_t *position)
> +{
> +#define MAC_STR_LEN 17
> + char mac_buf[MAC_STR_LEN + 1];
> + ssize_t ret;
> + bool delete_mode = false;
> + struct lowpan_enabled *entry = NULL, *tmp;
> + bdaddr_t bdaddr;
> +
> + if(count > (MAC_STR_LEN + 1))
> + delete_mode = true;
> + else if (count < MAC_STR_LEN)
> + return count;
> +
> + BT_DBG("count %zd mode %d", count, delete_mode);
> +
> + memset(mac_buf, 0, MAC_STR_LEN + 1);
> + ret = simple_write_to_buffer(mac_buf, MAC_STR_LEN, position,
> + user_buffer, count);
> + if (ret <= 0)
> + return ret;
> +
> + if (sscanf(mac_buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
> + &bdaddr.b[5], &bdaddr.b[4],
> + &bdaddr.b[3], &bdaddr.b[2],
> + &bdaddr.b[1], &bdaddr.b[0]) != 6)
> + return -EINVAL;
> +
> + BT_DBG("user %pMR", &bdaddr);
> +
> + write_lock(&user_enabled_list_lock);
> + list_for_each_entry_safe(entry, tmp, &user_enabled, list) {
> + if (!bacmp(&entry->addr, &bdaddr)) {
> + struct hci_dev *hdev = fp->f_inode->i_private;
> +
> + if (!strncmp(entry->dev_name, hdev->dev_name,
> + HCI_MAX_NAME_LENGTH) && delete_mode) {
> + break;
> + } else {
> + ret = -EEXIST;
> + break;
> + }
> + }
> + }
> + write_unlock(&user_enabled_list_lock);
> +
> + if (ret < 0)
> + return ret;
> +
> + if (delete_mode) {
> + write_lock(&user_enabled_list_lock);
> + list_del(&entry->list);
> + kfree(entry);
> + write_unlock(&user_enabled_list_lock);
> + } else {
> + struct hci_dev *hdev = fp->f_inode->i_private;
> +
> + entry = kzalloc(sizeof(*entry), GFP_KERNEL);
> + if (!entry)
> + return -ENOMEM;
> +
> + strncpy(entry->dev_name, hdev->dev_name, HCI_MAX_NAME_LENGTH);
> + entry->addr = bdaddr;
> +
> + write_lock(&user_enabled_list_lock);
> + INIT_LIST_HEAD(&entry->list);
> + list_add(&entry->list, &user_enabled);
> + write_unlock(&user_enabled_list_lock);
> + }
> +
> + return ret;
> +}
> +
> +static const struct file_operations ble_6lowpan_debugfs_fops = {
> + .open = debugfs_open,
> + .read = seq_read,
> + .write = writer,
> + .llseek = seq_lseek,
> + .release = single_release,
> +};
> +
> +static struct dentry *bt_6lowpan_debugfs;
> +
> +void bt_6lowpan_add_debugfs(struct hci_dev *hdev)
> +{
> + if (hdev->debugfs)
Please check if the calling place not already verified that hdev->debugfs is valid.
> + bt_6lowpan_debugfs = debugfs_create_file("6lowpan", 0644,
> + hdev->debugfs, hdev, &ble_6lowpan_debugfs_fops);
Also no point in bothering to store its return value. We use recursive cleanup. Nobody cares about the value.
> +}
> +
> int bt_6lowpan_init(void)
> {
> return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
> diff --git a/net/bluetooth/6lowpan.h b/net/bluetooth/6lowpan.h
> index 680eac8..549fc51 100644
> --- a/net/bluetooth/6lowpan.h
> +++ b/net/bluetooth/6lowpan.h
> @@ -22,5 +22,7 @@ int bt_6lowpan_add_conn(struct l2cap_conn *conn);
> int bt_6lowpan_del_conn(struct l2cap_conn *conn);
> int bt_6lowpan_init(void);
> void bt_6lowpan_cleanup(void);
> +bool bt_6lowpan_is_enabled(struct hci_dev *hdev, bdaddr_t *dst);
> +void bt_6lowpan_add_debugfs(struct hci_dev *hdev);
>
> #endif /* __6LOWPAN_H */
> diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
> index 6ccc4eb..444b4bc 100644
> --- a/net/bluetooth/hci_core.c
> +++ b/net/bluetooth/hci_core.c
> @@ -34,6 +34,8 @@
> #include <net/bluetooth/bluetooth.h>
> #include <net/bluetooth/hci_core.h>
>
> +#include "6lowpan.h"
> +
> static void hci_rx_work(struct work_struct *work);
> static void hci_cmd_work(struct work_struct *work);
> static void hci_tx_work(struct work_struct *work);
> @@ -1406,6 +1408,8 @@ static int __hci_init(struct hci_dev *hdev)
> hdev, &conn_max_interval_fops);
> }
>
> + bt_6lowpan_add_debugfs(hdev);
> +
Might only want to add 6lowpan debugfs when we actually support LE.
> return 0;
> }
>
> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> index c47215f..d02241e 100644
> --- a/net/bluetooth/l2cap_core.c
> +++ b/net/bluetooth/l2cap_core.c
> @@ -6512,11 +6512,6 @@ int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> return exact ? lm1 : lm2;
> }
>
> -static bool is_bt_6lowpan(void)
> -{
> - return false;
> -}
> -
> void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
> {
> struct l2cap_conn *conn;
> @@ -6528,7 +6523,9 @@ void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
> if (conn) {
> l2cap_conn_ready(conn);
>
> - if (hcon->type == LE_LINK && is_bt_6lowpan())
> + if (hcon->type == LE_LINK &&
> + bt_6lowpan_is_enabled(hcon->hdev,
> + &hcon->dst))
> bt_6lowpan_add_conn(conn);
> }
> } else {
> @@ -6551,7 +6548,8 @@ void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
> {
> BT_DBG("hcon %p reason %d", hcon, reason);
>
> - if (hcon->type == LE_LINK && is_bt_6lowpan())
> + if (hcon->type == LE_LINK && bt_6lowpan_is_enabled(hcon->hdev,
> + &hcon->dst))
> bt_6lowpan_del_conn(hcon->l2cap_data);
>
> l2cap_conn_del(hcon, bt_to_errno(reason));
Regards
Marcel
Hi Jukka,
> Signed-off-by: Jukka Rissanen <[email protected]>
> ---
> include/net/bluetooth/l2cap.h | 1 +
> net/bluetooth/6lowpan.c | 401 ++++++++++++++++++++++++++++++++++++++++++
> net/bluetooth/6lowpan.h | 26 +++
> net/bluetooth/Makefile | 2 +-
> net/bluetooth/l2cap_core.c | 22 ++-
> 5 files changed, 450 insertions(+), 2 deletions(-)
> create mode 100644 net/bluetooth/6lowpan.c
> create mode 100644 net/bluetooth/6lowpan.h
>
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index 5132990..c28ac0d 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -133,6 +133,7 @@ struct l2cap_conninfo {
> #define L2CAP_FC_L2CAP 0x02
> #define L2CAP_FC_CONNLESS 0x04
> #define L2CAP_FC_A2MP 0x08
> +#define L2CAP_FC_6LOWPAN 0x3e
>
> /* L2CAP Control Field bit masks */
> #define L2CAP_CTRL_SAR 0xC000
> diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
> new file mode 100644
> index 0000000..aff478b
> --- /dev/null
> +++ b/net/bluetooth/6lowpan.c
> @@ -0,0 +1,401 @@
> +/*
> + Copyright (c) 2013 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.
> +*/
> +
> +#include <linux/version.h>
> +#include <linux/bitops.h>
> +#include <linux/if_arp.h>
> +#include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
> +
> +#include <net/ipv6.h>
> +#include <net/ip6_route.h>
> +#include <net/addrconf.h>
> +
> +#include <net/af_ieee802154.h>
> +
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +#include <net/bluetooth/l2cap.h>
> +
> +#include "../ieee802154/6lowpan.h" /* for the compression defines */
this is fine for now, but please check on netdev (aka send patches) to see if we should have something like net/core/6lowpan.[ch]. Or if they prefer net/6lowpan/6lowpan.[ch].
> +
> +#define IFACE_NAME_TEMPLATE "bt%d"
> +
> +/*
> + * The devices list contains those devices that we are acting
> + * as a proxy. The BT 6LoWPAN device is a virtual device that
> + * connects to the Bluetooth LE device. The real connection to
> + * BT device is done via l2cap layer. There exists one
> + * virtual device / one BT 6LoWPAN device. The list contains
> + * struct lowpan_dev elements.
> + */
> +static LIST_HEAD(bt_6lowpan_devices);
> +DEFINE_RWLOCK(net_dev_list_lock);
You need to find a better name for the lock. And you can also make that one static I assume.
> +
> +struct lowpan_dev {
> + struct net_device *dev;
> + struct delayed_work delete_timer;
> + struct list_head list;
> +};
> +
> +struct lowpan_info {
> + struct net_device *net;
> + struct l2cap_conn *conn;
> + uint16_t ifindex;
> + bdaddr_t myaddr;
> +
> + /* peer addresses in various formats */
> + bdaddr_t addr;
> + unsigned char ieee802154_addr[IEEE802154_ADDR_LEN];
> + struct in6_addr peer;
> +};
> +
> +struct lowpan_fragment {
> + struct sk_buff *skb; /* skb to be assembled */
> + u16 length; /* length to be assemled */
> + u32 bytes_rcv; /* bytes received */
> + u16 tag; /* current fragment tag */
> + struct timer_list timer; /* assembling timer */
> + struct list_head list; /* fragments list */
> +};
> +
> +#define DELETE_TIMEOUT msecs_to_jiffies(1)
Maybe a comment why this is 1 msecs would be good here. It is a rather tiny timeout.
> +
> +/* TTL uncompression values */
> +static const u8 lowpan_ttl_values[] = {0, 1, 64, 255};
Personally I would have { 0, .., 255 }; here. So extra spaces between { and }.
> +
> +static inline struct
> +lowpan_info *lowpan_info(const struct net_device *dev)
Linebreak between struct and lowpan_info looks weird to me.
> +{
> + return netdev_priv(dev);
> +}
Not sure what the standard in the network subsystem here, but maybe a #define would be better.
> +
> +/* print data in line */
> +static inline void raw_dump_inline(const char *caller, char *msg,
> + unsigned char *buf, int len)
> +{
> +#ifdef DEBUG
> + if (msg)
> + pr_debug("%s():%s: ", caller, msg);
> + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_NONE,
> + 16, 1, buf, len, false);
> +#endif /* DEBUG */
> +}
> +
> +/*
> + * print data in a table format:
> + *
> + * addr: xx xx xx xx xx xx
> + * addr: xx xx xx xx xx xx
> + * ...
> + */
> +static inline void raw_dump_table(const char *caller, char *msg,
> + unsigned char *buf, int len)
> +{
> +#ifdef DEBUG
> + if (msg)
> + pr_debug("%s():%s:\n", caller, msg);
> + print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_OFFSET,
> + 16, 1, buf, len, false);
> +#endif /* DEBUG */
> +}
> +
> +static int recv_pkt(struct sk_buff *skb, struct net_device *dev)
> +{
> + kfree_skb(skb);
> + return NET_RX_DROP;
> +}
> +
> +/* Packet from BT LE device */
> +int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb)
> +{
> + struct lowpan_dev *entry, *tmp;
> + struct net_device *dev = NULL;
> + int status = -ENOENT;
> +
> + write_lock(&net_dev_list_lock);
> +
> + list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices, list) {
> + if (lowpan_info(entry->dev)->conn == conn) {
> + dev = lowpan_info(entry->dev)->net;
> + break;
> + }
> + }
> +
> + write_unlock(&net_dev_list_lock);
> +
> + if (dev) {
> + status = recv_pkt(skb, dev);
> + BT_DBG("recv pkt %d", status);
> + }
> +
> + return status;
> +}
> +
> +static void do_send(struct l2cap_conn *conn, struct sk_buff *skb)
> +{
> + BT_DBG("conn %p, skb %p len %d priority %u", conn, skb, skb->len,
> + skb->priority);
> +
> + return;
> +}
> +
> +static int conn_send(struct l2cap_conn *conn,
> + void *msg, size_t len, u32 priority,
> + struct net_device *dev)
> +{
> + struct sk_buff *skb = {0};
> +
> + do_send(conn, skb);
> + return 0;
> +}
> +
> +/* Packet to BT LE device */
> +static int send_pkt(struct l2cap_conn *conn, const void *saddr,
> + const void *daddr, struct sk_buff *skb,
> + struct net_device *dev)
This goes for all the function, we need to follow the network subsystem coding style. Aka calling checkpatch.pl with --strict or the better name ?subjective.
I know that there are still bad examples in some places, but for new code, we have to follow it.
> +{
> + raw_dump_table(__func__, "raw skb data dump before fragmentation",
> + skb->data, skb->len);
> +
> + return conn_send(conn, skb->data, skb->len, 0, dev);
> +}
> +
> +static netdev_tx_t xmit(struct sk_buff *skb, struct net_device *dev)
> +{
> + int err = -1;
> +
> + pr_debug("ble 6lowpan packet xmit\n");
> +
> + if (lowpan_info(dev)->conn)
> + err = send_pkt(lowpan_info(dev)->conn,
> + dev->dev_addr,
> + &lowpan_info(dev)->ieee802154_addr,
> + skb,
> + dev);
> + else
> + BT_DBG("ERROR: no BT LE 6LoWPAN device found?);
Can this case actually happen? In case our LE connection goes away, we should just flush all queues, bring the netdev down and unregister it.
> +
> + dev_kfree_skb(skb);
> +
> + if (err)
> + BT_DBG("ERROR: xmit failed (%d)", err);
> +
> + return (err < 0) ? NET_XMIT_DROP : err;
> +}
> +
> +static const struct net_device_ops netdev_ops = {
> + .ndo_start_xmit = xmit,
> +};
> +
> +static void setup(struct net_device *dev)
Maybe netdev_setup, lowpan_netdev, rawip_setup or something is better name than just setup.
> +{
> + dev->addr_len = IEEE802154_ADDR_LEN;
> + dev->type = ARPHRD_RAWIP;
> +
> + dev->hard_header_len = 0;
> + dev->needed_tailroom = 0;
> + dev->mtu = IPV6_MIN_MTU;
> + dev->tx_queue_len = 0;
> + dev->flags = IFF_RUNNING | IFF_POINTOPOINT;
> + dev->watchdog_timeo = 0;
> +
> + dev->netdev_ops = &netdev_ops;
> + dev->destructor = free_netdev;
> +}
> +
> +static struct device_type bt_type = {
> + .name = "bluetooth",
> +};
> +
> +static void set_addr(u8 *eui, u8 *addr, u8 addr_type)
> +{
> + /* addr is the BT address in little-endian format */
> + eui[0] = addr[5];
> + eui[1] = addr[4];
> + eui[2] = addr[3];
> + eui[3] = 0xFF;
> + eui[4] = 0xFE;
> + eui[5] = addr[2];
> + eui[6] = addr[1];
> + eui[7] = addr[0];
> +
> + eui[0] ^= 2;
> +
> + if (addr_type == BDADDR_LE_PUBLIC)
> + eui[0] &= ~1;
> + else
> + eui[0] |= 1;
I would quote the RFC here to mention how the address is assigned.
> +}
> +
> +static void set_dev_addr(struct net_device *net, bdaddr_t *addr,
> + u8 addr_type)
> +{
> + net->addr_assign_type = NET_ADDR_PERM;
> + set_addr(net->dev_addr, addr->b, addr_type);
> + net->dev_addr[0] ^= 2;
> +}
> +
> +static void ifup(struct net_device *net)
> +{
> + int status;
> +
> + rtnl_lock();
> + if ((status = dev_open(net)) < 0)
Assign it to status first and then check status. I know it is valid C code, but we normally don?t do this. Or if we did in the past, we are stepping away from these constructs.
Also you might want to call the variable err instead of status. I do not know where the status is coming from. At least in the Bluetooth subsystem we never used it like that. Is that some netdev thing?
> + BT_INFO("iface %s cannot be opened (%d)", net->name, status);
> + rtnl_unlock();
> +}
> +
> +/*
> + * This gets called when BT LE 6LoWPAN device is connected. We then
> + * create network device that acts as a proxy between BT LE device
> + * and kernel network stack.
> + */
> +int bt_6lowpan_add_conn(struct l2cap_conn *conn)
> +{
> + struct net_device *net;
> + struct lowpan_info *dev;
> + struct lowpan_dev *entry;
> + struct inet6_dev *idev;
> + int status;
> +
> + net = alloc_netdev(sizeof(struct lowpan_info), IFACE_NAME_TEMPLATE,
> + setup);
This ones also need to properly aligned according to the network subsystem coding style.
> + if (!net)
> + return -ENOMEM;
> +
> + dev = netdev_priv(net);
> + dev->net = net;
> +
> + memcpy(&dev->myaddr, &conn->hcon->src, sizeof(bdaddr_t));
> + memcpy(&dev->addr, &conn->hcon->dst, sizeof(bdaddr_t));
Do we need to really store these addresses? It is rather useless to just store the address, we also need to store the address type.
> +
> + set_dev_addr(net, &dev->myaddr, conn->hcon->src_type);
> +
> + dev->conn = conn;
> +
> + net->netdev_ops = &netdev_ops;
> + SET_NETDEV_DEV(net, &conn->hcon->dev);
> + SET_NETDEV_DEVTYPE(net, &bt_type);
> +
> + status = register_netdev(net);
> + if (status < 0) {
Same here. I would use err instead of status. Maybe ret in the cases where it makes more sense. Personally I prefer actually err.
> + BT_INFO("register_netdev failed %d", status);
> + free_netdev(net);
I think you better create an error label for this and use goto.
> + return status;
> + }
> +
> + BT_DBG("ifindex %d peer bdaddr %pMR my addr %pMR",
> + net->ifindex, &dev->addr, &dev->myaddr);
> + dev->ifindex = net->ifindex;
> + set_bit(__LINK_STATE_PRESENT, &net->state);
> +
> + idev = in6_dev_get(net);
> + if (idev) {
> + idev->cnf.autoconf = 1;
> + idev->cnf.forwarding = 1;
> + idev->cnf.accept_ra = 2;
> +
> + in6_dev_put(idev);
> + }
> +
> + entry = kzalloc(sizeof(struct lowpan_dev), GFP_KERNEL);
> + if (!entry)
> + return -ENOMEM;
Isn?t this leaking the net_device.
> +
> + entry->dev = net;
> +
> + write_lock(&net_dev_list_lock);
> + INIT_LIST_HEAD(&entry->list);
> + list_add(&entry->list, &bt_6lowpan_devices);
> + write_unlock(&net_dev_list_lock);
> +
> + ifup(net);
> +
> + return 0;
> +}
> +
> +static void delete_timeout(struct work_struct *work)
> +{
> + struct lowpan_dev *entry = container_of(work, struct lowpan_dev,
> + delete_timer.work);
> +
> + unregister_netdev(entry->dev);
> + kfree(entry);
> +}
I was wondering why we need this. You might need to explain it so I stop asking stupid questions. At least for me this is not obvious.
> +
> +int bt_6lowpan_del_conn(struct l2cap_conn *conn)
> +{
> + struct lowpan_dev *entry, *tmp;
> + int status = -ENOENT;
> +
> + write_lock(&net_dev_list_lock);
> +
> + list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices, list) {
> + if (lowpan_info(entry->dev)->conn == conn) {
> + list_del(&entry->list);
> + status = 0;
> + break;
> + }
> + }
> +
> + write_unlock(&net_dev_list_lock);
> +
> + if (!status) {
> + INIT_DELAYED_WORK(&entry->delete_timer, delete_timeout);
> + schedule_delayed_work(&entry->delete_timer, DELETE_TIMEOUT);
> + }
> +
> + return status;
> +}
> +
> +static int device_event(struct notifier_block *unused,
> + unsigned long event, void *ptr)
> +{
> + struct net_device *dev = netdev_notifier_info_to_dev(ptr);
> + struct lowpan_dev *entry, *tmp;
> +
> + if (dev->type != ARPHRD_RAWIP)
> + return NOTIFY_DONE;
> +
> + switch (event) {
> + case NETDEV_UNREGISTER:
> + write_lock(&net_dev_list_lock);
> + list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices,
> + list) {
> + if (entry->dev == dev) {
> + list_del(&entry->list);
> + kfree(entry);
> + break;
> + }
> + }
> + write_unlock(&net_dev_list_lock);
> + break;
> + }
> +
> + return NOTIFY_DONE;
> +}
I think we have a bit of too much duplicated code that is all just deleting the device. Can we clean this out please.
> +
> +static struct notifier_block bt_6lowpan_dev_notifier = {
> + .notifier_call = device_event,
> +};
> +
> +int bt_6lowpan_init(void)
> +{
> + return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
> +}
> +
> +void bt_6lowpan_cleanup(void)
> +{
> + unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
> +}
> diff --git a/net/bluetooth/6lowpan.h b/net/bluetooth/6lowpan.h
> new file mode 100644
> index 0000000..680eac8
> --- /dev/null
> +++ b/net/bluetooth/6lowpan.h
> @@ -0,0 +1,26 @@
> +/*
> + Copyright (c) 2013 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.
> +*/
> +
> +#ifndef __6LOWPAN_H
> +#define __6LOWPAN_H
> +
> +#include <linux/skbuff.h>
> +#include <net/bluetooth/l2cap.h>
> +
> +int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb);
> +int bt_6lowpan_add_conn(struct l2cap_conn *conn);
> +int bt_6lowpan_del_conn(struct l2cap_conn *conn);
> +int bt_6lowpan_init(void);
> +void bt_6lowpan_cleanup(void);
> +
> +#endif /* __6LOWPAN_H */
> diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
> index 6a791e7..80cb215 100644
> --- a/net/bluetooth/Makefile
> +++ b/net/bluetooth/Makefile
> @@ -10,6 +10,6 @@ obj-$(CONFIG_BT_HIDP) += hidp/
>
> 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 \
> - a2mp.o amp.o
> + a2mp.o amp.o 6lowpan.o
>
> subdir-ccflags-y += -D__CHECK_ENDIAN__
> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> index 0cef677..c47215f 100644
> --- a/net/bluetooth/l2cap_core.c
> +++ b/net/bluetooth/l2cap_core.c
> @@ -40,6 +40,7 @@
> #include "smp.h"
> #include "a2mp.h"
> #include "amp.h"
> +#include "6lowpan.h"
>
> bool disable_ertm;
>
> @@ -6470,6 +6471,10 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
> l2cap_conn_del(conn->hcon, EACCES);
> break;
>
> + case L2CAP_FC_6LOWPAN:
> + bt_6lowpan_recv(conn, skb);
> + break;
> +
> default:
> l2cap_data_channel(conn, cid, skb);
> break;
> @@ -6507,6 +6512,11 @@ int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
> return exact ? lm1 : lm2;
> }
>
> +static bool is_bt_6lowpan(void)
> +{
> + return false;
> +}
> +
> void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
> {
> struct l2cap_conn *conn;
> @@ -6515,8 +6525,12 @@ void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
>
> if (!status) {
> conn = l2cap_conn_add(hcon);
> - if (conn)
> + if (conn) {
> l2cap_conn_ready(conn);
> +
> + if (hcon->type == LE_LINK && is_bt_6lowpan())
> + bt_6lowpan_add_conn(conn);
So I am actually thinking that we might be a bit more aggressive with this code. At least this gives us a bit better testing exposure.
One idea might be to just create a hdev->dev_flags with HCI_6LOWPAN_ENABLED that you can set via debugfs (which defaults to off). And for every LE connection we have a hcon->flags with HCI_CONN_6LOWPAN that defaults to the value from hdev. And we just try to run 6loWPAN. Only if it fails, we delete the net_device.
Since this code is still in testing, we can be a bit more aggressive. Actually I like to see what happens if we have LE connections and use 6loWPAN against other LE devices that do not have it.
> + }
> } else {
> l2cap_conn_del(hcon, bt_to_errno(status));
> }
> @@ -6537,6 +6551,9 @@ void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
> {
> BT_DBG("hcon %p reason %d", hcon, reason);
>
> + if (hcon->type == LE_LINK && is_bt_6lowpan())
> + bt_6lowpan_del_conn(hcon->l2cap_data);
Is using l2cap_data here really a good idea. Don?t you have l2cap_conn or l2cap_chan available.
> +
> l2cap_conn_del(hcon, bt_to_errno(reason));
> }
>
> @@ -6814,11 +6831,14 @@ int __init l2cap_init(void)
> l2cap_debugfs = debugfs_create_file("l2cap", 0444, bt_debugfs,
> NULL, &l2cap_debugfs_fops);
>
> + bt_6lowpan_init();
> +
> return 0;
> }
>
> void l2cap_exit(void)
> {
> + bt_6lowpan_cleanup();
> debugfs_remove(l2cap_debugfs);
> l2cap_cleanup_sockets();
> }
Regards
Marcel
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/bluetooth/6lowpan.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 1cba7fd..4ec3813 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -1372,6 +1372,39 @@ static void set_dev_addr(struct net_device *net, bdaddr_t *addr,
net->dev_addr[0] ^= 2;
}
+static int add_peer_route(struct lowpan_info *dev, struct net_device *net,
+ bdaddr_t *addr, u8 addr_type)
+{
+ struct in6_addr peer;
+ struct fib6_config cfg = {
+ .fc_table = RT6_TABLE_MAIN,
+ .fc_metric = IP6_RT_PRIO_ADDRCONF,
+ .fc_ifindex = net->ifindex,
+ .fc_dst_len = 128,
+ .fc_flags = RTF_ADDRCONF | RTF_UP |
+ RTF_PREF(ICMPV6_ROUTER_PREF_MEDIUM),
+ .fc_nlinfo.portid = 0,
+ .fc_nlinfo.nlh = NULL,
+ .fc_nlinfo.nl_net = dev_net(net),
+ };
+
+ memset(&peer, 0, sizeof(struct in6_addr));
+
+ /* RFC 2464 ch. 5 */
+ peer.s6_addr[0] = 0xFE;
+ peer.s6_addr[1] = 0x80;
+ set_addr((u8 *)&peer.s6_addr + 8, addr->b, addr_type);
+
+ memcpy(&dev->ieee802154_addr, (u8 *)&peer.s6_addr + 8,
+ IEEE802154_ADDR_LEN);
+
+ BT_DBG("peer address %pI6c", (u8 *)&peer.s6_addr);
+
+ cfg.fc_dst = dev->peer = peer;
+
+ return ip6_route_add(&cfg);
+}
+
static void ifup(struct net_device *net)
{
int status;
@@ -1448,6 +1481,10 @@ int bt_6lowpan_add_conn(struct l2cap_conn *conn)
ifup(net);
+ if ((status = add_peer_route(dev, net, &dev->addr,
+ conn->hcon->dst_type)) < 0)
+ BT_INFO("Setting route failed %d", status);
+
return 0;
}
--
1.7.11.7
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/bluetooth/6lowpan.c | 1128 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1127 insertions(+), 1 deletion(-)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index aff478b..1cba7fd 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -11,6 +11,20 @@
GNU General Public License for more details.
*/
+/*
+ * The compression, uncompression and IPv6 packet fragmentation code
+ * is from net/ieee802154/6lowpan.c with these copyrights
+ *
+ * Copyright 2011, Siemens AG
+ * written by Alexander Smirnov <[email protected]>
+ *
+ * Based on patches from Jon Smirl <[email protected]>
+ * Copyright (c) 2011 Jon Smirl <[email protected]>
+ *
+ * Jon's code is based on 6lowpan implementation for Contiki which is:
+ * Copyright (c) 2008, Swedish Institute of Computer Science.
+ */
+
#include <linux/version.h>
#include <linux/bitops.h>
#include <linux/if_arp.h>
@@ -69,6 +83,9 @@ struct lowpan_fragment {
struct list_head list; /* fragments list */
};
+static LIST_HEAD(lowpan_fragments);
+static DEFINE_SPINLOCK(flist_lock);
+
#define DELETE_TIMEOUT msecs_to_jiffies(1)
/* TTL uncompression values */
@@ -110,8 +127,723 @@ static inline void raw_dump_table(const char *caller, char *msg,
#endif /* DEBUG */
}
+static inline bool fetch_skb(struct sk_buff *skb,
+ void *data, const unsigned int len)
+{
+ if (unlikely(!pskb_may_pull(skb, len)))
+ return true;
+
+ skb_copy_from_linear_data(skb, data, len);
+ skb_pull(skb, len);
+
+ return false;
+}
+
+/*
+ * Uncompress address function for source and
+ * destination address(non-multicast).
+ *
+ * address_mode is sam value or dam value.
+ */
+static int uncompress_addr(struct sk_buff *skb,
+ struct in6_addr *ipaddr,
+ const u8 address_mode,
+ const u8 *lladdr)
+{
+ bool fail;
+
+ switch (address_mode) {
+ case LOWPAN_IPHC_ADDR_00:
+ /* for global link addresses */
+ fail = fetch_skb(skb, ipaddr->s6_addr, 16);
+ break;
+ case LOWPAN_IPHC_ADDR_01:
+ /* fe:80::XXXX:XXXX:XXXX:XXXX */
+ ipaddr->s6_addr[0] = 0xFE;
+ ipaddr->s6_addr[1] = 0x80;
+ fail = fetch_skb(skb, &ipaddr->s6_addr[8], 8);
+ break;
+ case LOWPAN_IPHC_ADDR_02:
+ /* fe:80::ff:fe00:XXXX */
+ ipaddr->s6_addr[0] = 0xFE;
+ ipaddr->s6_addr[1] = 0x80;
+ ipaddr->s6_addr[11] = 0xFF;
+ ipaddr->s6_addr[12] = 0xFE;
+ fail = fetch_skb(skb, &ipaddr->s6_addr[14], 2);
+ break;
+ case LOWPAN_IPHC_ADDR_03:
+ fail = false;
+ /* XXX: support only normal addr (IEEE802154_ADDR_LONG) atm */
+
+ /* fe:80::XXXX:XXXX:XXXX:XXXX
+ * \_________________/
+ * hwaddr
+ */
+ ipaddr->s6_addr[0] = 0xFE;
+ ipaddr->s6_addr[1] = 0x80;
+ memcpy(&ipaddr->s6_addr[8], lladdr, IEEE802154_ADDR_LEN);
+ break;
+ default:
+ pr_debug("Invalid address mode value: 0x%x\n", address_mode);
+ return -EINVAL;
+ }
+
+ if (fail) {
+ pr_debug("Failed to fetch skb data\n");
+ return -EIO;
+ }
+
+ raw_dump_inline(NULL, "Reconstructed ipv6 addr is",
+ ipaddr->s6_addr, 16);
+
+ return 0;
+}
+
+/*
+ * Uncompress address function for source context
+ * based address(non-multicast).
+ */
+static int uncompress_context_based_src_addr(struct sk_buff *skb,
+ struct in6_addr *ipaddr,
+ const u8 sam)
+{
+ switch (sam) {
+ case LOWPAN_IPHC_ADDR_00:
+ /* unspec address ::
+ * Do nothing, address is already ::
+ */
+ break;
+ case LOWPAN_IPHC_ADDR_01:
+ /* TODO */
+ case LOWPAN_IPHC_ADDR_02:
+ /* TODO */
+ case LOWPAN_IPHC_ADDR_03:
+ /* TODO */
+ netdev_warn(skb->dev, "SAM value 0x%x not supported\n", sam);
+ return -EINVAL;
+ default:
+ pr_debug("Invalid sam value: 0x%x\n", sam);
+ return -EINVAL;
+ }
+
+ raw_dump_inline(NULL,
+ "Reconstructed context based ipv6 src addr is",
+ ipaddr->s6_addr, 16);
+
+ return 0;
+}
+
+/*
+ * This func is called when the packet from BT LE device
+ * needs to be sent upper layers.
+ */
+static int give_skb_to_upper(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct sk_buff *skb_cp;
+ int ret = NET_RX_SUCCESS;
+
+ skb_cp = skb_copy(skb, GFP_ATOMIC);
+ if (!skb_cp) {
+ ret = -ENOMEM;
+ } else {
+ ret = netif_rx(skb_cp);
+
+ BT_DBG("receive skb %d", ret);
+ if (ret < 0)
+ ret = NET_RX_DROP;
+ }
+
+ return ret;
+}
+
+static inline int fetch_skb_u8(struct sk_buff *skb, u8 *val)
+{
+ if (unlikely(!pskb_may_pull(skb, 1)))
+ return -EINVAL;
+
+ *val = skb->data[0];
+ skb_pull(skb, 1);
+
+ return 0;
+}
+
+static inline int fetch_skb_u16(struct sk_buff *skb, u16 *val)
+{
+ if (unlikely(!pskb_may_pull(skb, 2)))
+ return -EINVAL;
+
+ *val = (skb->data[0] << 8) | skb->data[1];
+ skb_pull(skb, 2);
+
+ return 0;
+}
+
+static int skb_deliver(struct sk_buff *skb, struct ipv6hdr *hdr,
+ struct net_device *dev)
+{
+ struct sk_buff *new;
+ int stat;
+
+ new = skb_copy_expand(skb, sizeof(struct ipv6hdr), skb_tailroom(skb),
+ GFP_ATOMIC);
+ kfree_skb(skb);
+
+ if (!new)
+ return -ENOMEM;
+
+ skb_push(new, sizeof(struct ipv6hdr));
+ skb_reset_network_header(new);
+ skb_copy_to_linear_data(new, hdr, sizeof(struct ipv6hdr));
+
+ new->protocol = htons(ETH_P_IPV6);
+ new->pkt_type = PACKET_HOST;
+ new->dev = dev;
+
+ raw_dump_table(__func__,
+ "raw skb data dump before receiving",
+ new->data, new->len);
+
+ stat = give_skb_to_upper(new, dev);
+
+ kfree_skb(new);
+
+ return stat;
+}
+
+static void lowpan_fragment_timer_expired(unsigned long entry_addr)
+{
+ struct lowpan_fragment *entry = (struct lowpan_fragment *)entry_addr;
+
+ pr_debug("timer expired for frame with tag %d\n", entry->tag);
+
+ list_del(&entry->list);
+ dev_kfree_skb(entry->skb);
+ kfree(entry);
+}
+
+static struct lowpan_fragment *alloc_new_frame(struct sk_buff *skb,
+ u16 len, u16 tag)
+{
+ struct lowpan_fragment *frame;
+
+ frame = kzalloc(sizeof(struct lowpan_fragment),
+ GFP_ATOMIC);
+ if (!frame)
+ goto frame_err;
+
+ INIT_LIST_HEAD(&frame->list);
+
+ frame->length = len;
+ frame->tag = tag;
+
+ /* allocate buffer for frame assembling */
+ frame->skb = netdev_alloc_skb_ip_align(skb->dev, frame->length +
+ sizeof(struct ipv6hdr));
+
+ if (!frame->skb)
+ goto skb_err;
+
+ frame->skb->priority = skb->priority;
+ frame->skb->dev = skb->dev;
+
+ /* reserve headroom for uncompressed ipv6 header */
+ skb_reserve(frame->skb, sizeof(struct ipv6hdr));
+ skb_put(frame->skb, frame->length);
+
+ /* copy the first control block to keep a
+ * trace of the link-layer addresses in case
+ * of a link-local compressed address
+ */
+ memcpy(frame->skb->cb, skb->cb, sizeof(skb->cb));
+
+ init_timer(&frame->timer);
+ /* time out is the same as for ipv6 - 60 sec */
+ frame->timer.expires = jiffies + LOWPAN_FRAG_TIMEOUT;
+ frame->timer.data = (unsigned long)frame;
+ frame->timer.function = lowpan_fragment_timer_expired;
+
+ add_timer(&frame->timer);
+
+ list_add_tail(&frame->list, &lowpan_fragments);
+
+ return frame;
+
+skb_err:
+ kfree(frame);
+frame_err:
+ return NULL;
+}
+
+/* Uncompress function for multicast destination address,
+ * when M bit is set.
+ */
+static int
+lowpan_uncompress_multicast_daddr(struct sk_buff *skb,
+ struct in6_addr *ipaddr,
+ const u8 dam)
+{
+ bool fail;
+
+ switch (dam) {
+ case LOWPAN_IPHC_DAM_00:
+ /* 00: 128 bits. The full address
+ * is carried in-line.
+ */
+ fail = fetch_skb(skb, ipaddr->s6_addr, 16);
+ break;
+ case LOWPAN_IPHC_DAM_01:
+ /* 01: 48 bits. The address takes
+ * the form ffXX::00XX:XXXX:XXXX.
+ */
+ ipaddr->s6_addr[0] = 0xFF;
+ fail = fetch_skb(skb, &ipaddr->s6_addr[1], 1);
+ fail |= fetch_skb(skb, &ipaddr->s6_addr[11], 5);
+ break;
+ case LOWPAN_IPHC_DAM_10:
+ /* 10: 32 bits. The address takes
+ * the form ffXX::00XX:XXXX.
+ */
+ ipaddr->s6_addr[0] = 0xFF;
+ fail = fetch_skb(skb, &ipaddr->s6_addr[1], 1);
+ fail |= fetch_skb(skb, &ipaddr->s6_addr[13], 3);
+ break;
+ case LOWPAN_IPHC_DAM_11:
+ /* 11: 8 bits. The address takes
+ * the form ff02::00XX.
+ */
+ ipaddr->s6_addr[0] = 0xFF;
+ ipaddr->s6_addr[1] = 0x02;
+ fail = fetch_skb(skb, &ipaddr->s6_addr[15], 1);
+ break;
+ default:
+ pr_debug("DAM value has a wrong value: 0x%x\n", dam);
+ return -EINVAL;
+ }
+
+ if (fail) {
+ pr_debug("Failed to fetch skb data\n");
+ return -EIO;
+ }
+
+ raw_dump_inline(NULL,
+ "Reconstructed ipv6 multicast addr is",
+ ipaddr->s6_addr, 16);
+
+ return 0;
+}
+
+static int
+uncompress_udp_header(struct sk_buff *skb, struct udphdr *uh)
+{
+ u8 tmp;
+
+ if (!uh)
+ goto err;
+
+ if (fetch_skb_u8(skb, &tmp))
+ goto err;
+
+ if ((tmp & LOWPAN_NHC_UDP_MASK) == LOWPAN_NHC_UDP_ID) {
+ pr_debug("UDP header uncompression\n");
+ switch (tmp & LOWPAN_NHC_UDP_CS_P_11) {
+ case LOWPAN_NHC_UDP_CS_P_00:
+ memcpy(&uh->source, &skb->data[0], 2);
+ memcpy(&uh->dest, &skb->data[2], 2);
+ skb_pull(skb, 4);
+ break;
+ case LOWPAN_NHC_UDP_CS_P_01:
+ memcpy(&uh->source, &skb->data[0], 2);
+ uh->dest =
+ skb->data[2] + LOWPAN_NHC_UDP_8BIT_PORT;
+ skb_pull(skb, 3);
+ break;
+ case LOWPAN_NHC_UDP_CS_P_10:
+ uh->source = skb->data[0] + LOWPAN_NHC_UDP_8BIT_PORT;
+ memcpy(&uh->dest, &skb->data[1], 2);
+ skb_pull(skb, 3);
+ break;
+ case LOWPAN_NHC_UDP_CS_P_11:
+ uh->source =
+ LOWPAN_NHC_UDP_4BIT_PORT + (skb->data[0] >> 4);
+ uh->dest =
+ LOWPAN_NHC_UDP_4BIT_PORT + (skb->data[0] & 0x0f);
+ skb_pull(skb, 1);
+ break;
+ default:
+ pr_debug("ERROR: unknown UDP format\n");
+ goto err;
+ break;
+ }
+
+ pr_debug("uncompressed UDP ports: src = %d, dst = %d\n",
+ uh->source, uh->dest);
+
+ /* copy checksum */
+ memcpy(&uh->check, &skb->data[0], 2);
+ skb_pull(skb, 2);
+
+ /*
+ * UDP lenght needs to be infered from the lower layers
+ * here, we obtain the hint from the remaining size of the
+ * frame
+ */
+ uh->len = htons(skb->len + sizeof(struct udphdr));
+ pr_debug("uncompressed UDP length: src = %d", uh->len);
+ } else {
+ pr_debug("ERROR: unsupported NH format\n");
+ goto err;
+ }
+
+ return 0;
+err:
+ return -EINVAL;
+}
+
+static int process_data(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ipv6hdr hdr = {};
+ u8 tmp, iphc0, iphc1, num_context = 0;
+ const u8 *_saddr = NULL, *_daddr = NULL;
+ struct lowpan_info *info;
+ int err;
+
+ raw_dump_table(__func__, "raw skb data dump uncompressed",
+ skb->data, skb->len);
+
+ /* at least two bytes will be used for the encoding */
+ if (skb->len < 2)
+ goto drop;
+
+ if (fetch_skb_u8(skb, &iphc0))
+ goto drop;
+
+ info = lowpan_info(dev);
+
+ /* fragments assembling */
+ switch (iphc0 & LOWPAN_DISPATCH_MASK) {
+ case LOWPAN_DISPATCH_FRAG1:
+ case LOWPAN_DISPATCH_FRAGN:
+ {
+ struct lowpan_fragment *frame;
+ /* slen stores the rightmost 8 bits of the 11 bits length */
+ u8 slen, offset = 0;
+ u16 len, tag;
+ bool found = false;
+
+ if (fetch_skb_u8(skb, &slen) || /* frame length */
+ fetch_skb_u16(skb, &tag)) /* fragment tag */
+ goto drop;
+
+ /* adds the 3 MSB to the 8 LSB to retrieve the 11 bits length */
+ len = ((iphc0 & 7) << 8) | slen;
+
+ if ((iphc0 & LOWPAN_DISPATCH_MASK) == LOWPAN_DISPATCH_FRAG1) {
+ pr_debug("%s received a FRAG1 packet (tag: %d, "
+ "size of the entire IP packet: %d)\n",
+ __func__, tag, len);
+ } else { /* FRAGN */
+ if (fetch_skb_u8(skb, &offset))
+ goto unlock_and_drop;
+ pr_debug("%s received a FRAGN packet (tag: %d, "
+ "size of the entire IP packet: %d, "
+ "offset: %d)\n", __func__, tag, len,
+ offset * 8);
+ }
+
+ /*
+ * check if frame assembling with the same tag is
+ * already in progress
+ */
+ spin_lock_bh(&flist_lock);
+
+ list_for_each_entry(frame, &lowpan_fragments, list)
+ if (frame->tag == tag) {
+ found = true;
+ break;
+ }
+
+ /* alloc new frame structure */
+ if (!found) {
+ pr_debug("%s first fragment received for tag %d, "
+ "begin packet reassembly\n", __func__, tag);
+ frame = alloc_new_frame(skb, len, tag);
+ if (!frame)
+ goto unlock_and_drop;
+ }
+
+ /* if payload fits buffer, copy it */
+ if (likely((offset * 8 + skb->len) <= frame->length))
+ skb_copy_to_linear_data_offset(frame->skb, offset * 8,
+ skb->data, skb->len);
+ else
+ goto unlock_and_drop;
+
+ frame->bytes_rcv += skb->len;
+
+ /* frame assembling complete */
+ if ((frame->bytes_rcv == frame->length) &&
+ frame->timer.expires > jiffies) {
+ /* if timer haven't expired - first of all delete it */
+ del_timer_sync(&frame->timer);
+ list_del(&frame->list);
+ spin_unlock_bh(&flist_lock);
+
+ pr_debug("%s successfully reassembled fragment "
+ "(tag %d)\n", __func__, tag);
+
+ dev_kfree_skb(skb);
+ skb = frame->skb;
+ kfree(frame);
+
+ if (fetch_skb_u8(skb, &iphc0))
+ goto drop;
+
+ break;
+ }
+ spin_unlock_bh(&flist_lock);
+
+ return kfree_skb(skb), 0;
+ }
+ default:
+ break;
+ }
+
+ if (fetch_skb_u8(skb, &iphc1))
+ goto drop;
+
+ _saddr = info->ieee802154_addr;
+ _daddr = info->net->dev_addr;
+
+ /* another if the CID flag is set */
+ if (iphc1 & LOWPAN_IPHC_CID) {
+ pr_debug("CID flag is set, increase header with one\n");
+ if (fetch_skb_u8(skb, &num_context))
+ goto drop;
+ }
+
+ hdr.version = 6;
+
+ /* Traffic Class and Flow Label */
+ switch ((iphc0 & LOWPAN_IPHC_TF) >> 3) {
+ /*
+ * Traffic Class and FLow Label carried in-line
+ * ECN + DSCP + 4-bit Pad + Flow Label (4 bytes)
+ */
+ case 0: /* 00b */
+ if (fetch_skb_u8(skb, &tmp))
+ goto drop;
+
+ memcpy(&hdr.flow_lbl, &skb->data[0], 3);
+ skb_pull(skb, 3);
+ hdr.priority = ((tmp >> 2) & 0x0f);
+ hdr.flow_lbl[0] = ((tmp >> 2) & 0x30) | (tmp << 6) |
+ (hdr.flow_lbl[0] & 0x0f);
+ break;
+ /*
+ * Traffic class carried in-line
+ * ECN + DSCP (1 byte), Flow Label is elided
+ */
+ case 1: /* 10b */
+ if (fetch_skb_u8(skb, &tmp))
+ goto drop;
+
+ hdr.priority = ((tmp >> 2) & 0x0f);
+ hdr.flow_lbl[0] = ((tmp << 6) & 0xC0) | ((tmp >> 2) & 0x30);
+ break;
+ /*
+ * Flow Label carried in-line
+ * ECN + 2-bit Pad + Flow Label (3 bytes), DSCP is elided
+ */
+ case 2: /* 01b */
+ if (fetch_skb_u8(skb, &tmp))
+ goto drop;
+
+ hdr.flow_lbl[0] = (skb->data[0] & 0x0F) | ((tmp >> 2) & 0x30);
+ memcpy(&hdr.flow_lbl[1], &skb->data[0], 2);
+ skb_pull(skb, 2);
+ break;
+ /* Traffic Class and Flow Label are elided */
+ case 3: /* 11b */
+ break;
+ default:
+ break;
+ }
+
+ /* Next Header */
+ if ((iphc0 & LOWPAN_IPHC_NH_C) == 0) {
+ /* Next header is carried inline */
+ if (fetch_skb_u8(skb, &(hdr.nexthdr)))
+ goto drop;
+
+ pr_debug("NH flag is set, next header carried inline: %02x\n",
+ hdr.nexthdr);
+ }
+
+ /* Hop Limit */
+ if ((iphc0 & 0x03) != LOWPAN_IPHC_TTL_I)
+ hdr.hop_limit = lowpan_ttl_values[iphc0 & 0x03];
+ else {
+ if (fetch_skb_u8(skb, &(hdr.hop_limit)))
+ goto drop;
+ }
+
+ /* Extract SAM to the tmp variable */
+ tmp = ((iphc1 & LOWPAN_IPHC_SAM) >> LOWPAN_IPHC_SAM_BIT) & 0x03;
+
+ if (iphc1 & LOWPAN_IPHC_SAC) {
+ /* Source address context based uncompression */
+ pr_debug("SAC bit is set. Handle context based source address.\n");
+ err = uncompress_context_based_src_addr(
+ skb, &hdr.saddr, tmp);
+ } else {
+ /* Source address uncompression */
+ pr_debug("source address stateless compression\n");
+ err = uncompress_addr(skb, &hdr.saddr, tmp, _saddr);
+ }
+
+ /* Check on error of previous branch */
+ if (err)
+ goto drop;
+
+ /* Extract DAM to the tmp variable */
+ tmp = ((iphc1 & LOWPAN_IPHC_DAM_11) >> LOWPAN_IPHC_DAM_BIT) & 0x03;
+
+ /* check for Multicast Compression */
+ if (iphc1 & LOWPAN_IPHC_M) {
+ if (iphc1 & LOWPAN_IPHC_DAC) {
+ pr_debug("dest: context-based mcast compression\n");
+ /* TODO: implement this */
+ } else {
+ err = lowpan_uncompress_multicast_daddr(
+ skb, &hdr.daddr, tmp);
+ if (err)
+ goto drop;
+ }
+ } else {
+ pr_debug("dest: stateless compression\n");
+ err = uncompress_addr(skb, &hdr.daddr, tmp, _daddr);
+ if (err)
+ goto drop;
+ }
+
+ /* UDP data uncompression */
+ if (iphc0 & LOWPAN_IPHC_NH_C) {
+ struct udphdr uh;
+ struct sk_buff *new;
+ if (uncompress_udp_header(skb, &uh))
+ goto drop;
+
+ /*
+ * replace the compressed UDP head by the uncompressed UDP
+ * header
+ */
+ new = skb_copy_expand(skb, sizeof(struct udphdr),
+ skb_tailroom(skb), GFP_ATOMIC);
+ kfree_skb(skb);
+
+ if (!new)
+ return -ENOMEM;
+
+ skb = new;
+
+ skb_push(skb, sizeof(struct udphdr));
+ skb_reset_transport_header(skb);
+ skb_copy_to_linear_data(skb, &uh, sizeof(struct udphdr));
+
+ raw_dump_table(__func__, "raw UDP header dump",
+ (u8 *)&uh, sizeof(uh));
+
+ hdr.nexthdr = UIP_PROTO_UDP;
+ }
+
+ /* Not fragmented package */
+ hdr.payload_len = htons(skb->len);
+
+ pr_debug("skb headroom size = %d, data length = %d\n",
+ skb_headroom(skb), skb->len);
+
+ pr_debug("IPv6 header dump:\n\tversion = %d\n\tlength = %d\n\t"
+ "nexthdr = 0x%02x\n\thop_lim = %d\n", hdr.version,
+ ntohs(hdr.payload_len), hdr.nexthdr, hdr.hop_limit);
+
+ raw_dump_table(__func__, "raw header dump", (u8 *)&hdr,
+ sizeof(hdr));
+
+ return skb_deliver(skb, &hdr, dev);
+
+unlock_and_drop:
+ spin_unlock_bh(&flist_lock);
+drop:
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
static int recv_pkt(struct sk_buff *skb, struct net_device *dev)
{
+ struct sk_buff *local_skb;
+
+ if (!netif_running(dev))
+ goto drop;
+
+ if (dev->type != ARPHRD_RAWIP)
+ goto drop;
+
+ raw_dump_table(__func__, "raw recv dump", skb->head,
+ skb->len);
+
+ /* check that it's our buffer */
+ if (skb->data[0] == LOWPAN_DISPATCH_IPV6) {
+ /* Copy the packet so that the IPv6 header is
+ * properly aligned.
+ */
+ local_skb = skb_copy_expand(skb, NET_SKB_PAD - 1,
+ skb_tailroom(skb), GFP_ATOMIC);
+ if (!local_skb)
+ goto drop;
+
+ local_skb->protocol = htons(ETH_P_IPV6);
+ local_skb->pkt_type = PACKET_HOST;
+
+ skb_reset_network_header(local_skb);
+ skb_set_transport_header(local_skb, sizeof(struct ipv6hdr));
+
+ if (give_skb_to_upper(local_skb,
+ dev) != NET_RX_SUCCESS) {
+ kfree_skb(local_skb);
+ goto drop;
+ }
+
+ dev->stats.rx_bytes += skb->len;
+ dev->stats.rx_packets++;
+
+ kfree_skb(local_skb);
+ kfree_skb(skb);
+ } else {
+ switch (skb->data[0] & 0xe0) {
+ case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */
+ case LOWPAN_DISPATCH_FRAG1: /* first fragment header */
+ case LOWPAN_DISPATCH_FRAGN: /* next fragments headers */
+ local_skb = skb_clone(skb, GFP_ATOMIC);
+ if (!local_skb)
+ goto drop;
+ if (process_data(local_skb,
+ dev) != NET_RX_SUCCESS)
+ goto drop;
+
+ dev->stats.rx_bytes += skb->len;
+ dev->stats.rx_packets++;
+
+ kfree_skb(skb);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return NET_RX_SUCCESS;
+
+drop:
kfree_skb(skb);
return NET_RX_DROP;
}
@@ -147,19 +879,408 @@ static void do_send(struct l2cap_conn *conn, struct sk_buff *skb)
BT_DBG("conn %p, skb %p len %d priority %u", conn, skb, skb->len,
skb->priority);
+ hci_send_acl(conn->hchan, skb, ACL_START);
return;
}
+static inline int skbuff_copy(void *msg, int len,
+ int count, int mtu,
+ struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct sk_buff **frag;
+ int sent = 0;
+
+ memcpy(skb_put(skb, count), msg, count);
+
+ sent += count;
+ msg += count;
+ len -= count;
+
+ dev->stats.tx_bytes += count;
+ dev->stats.tx_packets++;
+
+ raw_dump_table(__func__, "Sending", skb->data, skb->len);
+
+ /* Continuation fragments (no L2CAP header) */
+ frag = &skb_shinfo(skb)->frag_list;
+ while (len > 0) {
+ struct sk_buff *tmp;
+
+ count = min_t(unsigned int, mtu, len);
+
+ tmp = bt_skb_alloc(count, GFP_KERNEL);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+
+ *frag = tmp;
+
+ memcpy(skb_put(*frag, count), msg, count);
+
+ raw_dump_table(__func__, "Sending fragment",
+ (*frag)->data, count);
+
+ (*frag)->priority = skb->priority;
+
+ sent += count;
+ msg += count;
+ len -= count;
+
+ skb->len += (*frag)->len;
+ skb->data_len += (*frag)->len;
+
+ frag = &(*frag)->next;
+
+ dev->stats.tx_bytes += count;
+ dev->stats.tx_packets++;
+ }
+
+ return sent;
+}
+
+static struct sk_buff *create_pdu(struct l2cap_conn *conn,
+ void *msg, size_t len,
+ u32 priority,
+ struct net_device *dev)
+{
+ struct sk_buff *skb;
+ int err, count;
+ struct l2cap_hdr *lh;
+
+ if (conn->mtu > (L2CAP_LE_MIN_MTU + L2CAP_HDR_SIZE))
+ /* XXX: This should be not needed and atm is only used for
+ * testing purposes */
+ conn->mtu = L2CAP_LE_MIN_MTU + L2CAP_HDR_SIZE;
+
+ count = min_t(unsigned int, (conn->mtu - L2CAP_HDR_SIZE), len);
+
+ BT_DBG("conn %p len %zu mtu %d count %d", conn, len, conn->mtu, count);
+
+ skb = bt_skb_alloc(count + L2CAP_HDR_SIZE, GFP_KERNEL);
+ if (IS_ERR(skb))
+ return skb;
+
+ skb->priority = priority;
+
+ lh = (struct l2cap_hdr *)skb_put(skb, L2CAP_HDR_SIZE);
+ lh->cid = cpu_to_le16(L2CAP_FC_6LOWPAN);
+ lh->len = cpu_to_le16(len);
+
+ err = skbuff_copy(msg, len, count, conn->mtu, skb, dev);
+ if (unlikely(err < 0)) {
+ kfree_skb(skb);
+ BT_DBG("skbuff copy %d failed", err);
+ return ERR_PTR(err);
+ }
+
+ return skb;
+}
+
static int conn_send(struct l2cap_conn *conn,
void *msg, size_t len, u32 priority,
struct net_device *dev)
{
- struct sk_buff *skb = {0};
+ struct sk_buff *skb;
+
+ skb = create_pdu(conn, msg, len, priority, dev);
+ if (IS_ERR(skb))
+ return -EINVAL;
do_send(conn, skb);
return 0;
}
+static u8 lowpan_compress_addr_64(u8 **hc06_ptr, u8 shift,
+ const struct in6_addr *ipaddr,
+ const unsigned char *lladdr)
+{
+ u8 val = 0;
+
+ if (is_addr_mac_addr_based(ipaddr, lladdr))
+ val = 3; /* 0-bits */
+ else if (lowpan_is_iid_16_bit_compressable(ipaddr)) {
+ /* compress IID to 16 bits xxxx::XXXX */
+ memcpy(*hc06_ptr, &ipaddr->s6_addr16[7], 2);
+ *hc06_ptr += 2;
+ val = 2; /* 16-bits */
+ } else {
+ /* do not compress IID => xxxx::IID */
+ memcpy(*hc06_ptr, &ipaddr->s6_addr16[4], 8);
+ *hc06_ptr += 8;
+ val = 1; /* 64-bits */
+ }
+
+ return rol8(val, shift);
+}
+
+static void compress_udp_header(u8 **hc06_ptr, struct sk_buff *skb)
+{
+ struct udphdr *uh = udp_hdr(skb);
+
+ if (((uh->source & LOWPAN_NHC_UDP_4BIT_MASK) ==
+ LOWPAN_NHC_UDP_4BIT_PORT) &&
+ ((uh->dest & LOWPAN_NHC_UDP_4BIT_MASK) ==
+ LOWPAN_NHC_UDP_4BIT_PORT)) {
+ pr_debug("UDP header: both ports compression to 4 bits\n");
+ **hc06_ptr = LOWPAN_NHC_UDP_CS_P_11;
+ **(hc06_ptr + 1) = /* subtraction is faster */
+ (u8)((uh->dest - LOWPAN_NHC_UDP_4BIT_PORT) +
+ ((uh->source & LOWPAN_NHC_UDP_4BIT_PORT) << 4));
+ *hc06_ptr += 2;
+ } else if ((uh->dest & LOWPAN_NHC_UDP_8BIT_MASK) ==
+ LOWPAN_NHC_UDP_8BIT_PORT) {
+ pr_debug("UDP header: remove 8 bits of dest\n");
+ **hc06_ptr = LOWPAN_NHC_UDP_CS_P_01;
+ memcpy(*hc06_ptr + 1, &uh->source, 2);
+ **(hc06_ptr + 3) = (u8)(uh->dest - LOWPAN_NHC_UDP_8BIT_PORT);
+ *hc06_ptr += 4;
+ } else if ((uh->source & LOWPAN_NHC_UDP_8BIT_MASK) ==
+ LOWPAN_NHC_UDP_8BIT_PORT) {
+ pr_debug("UDP header: remove 8 bits of source\n");
+ **hc06_ptr = LOWPAN_NHC_UDP_CS_P_10;
+ memcpy(*hc06_ptr + 1, &uh->dest, 2);
+ **(hc06_ptr + 3) = (u8)(uh->source - LOWPAN_NHC_UDP_8BIT_PORT);
+ *hc06_ptr += 4;
+ } else {
+ pr_debug("UDP header: can't compress\n");
+ **hc06_ptr = LOWPAN_NHC_UDP_CS_P_00;
+ memcpy(*hc06_ptr + 1, &uh->source, 2);
+ memcpy(*hc06_ptr + 3, &uh->dest, 2);
+ *hc06_ptr += 5;
+ }
+
+ /* checksum is always inline */
+ memcpy(*hc06_ptr, &uh->check, 2);
+ *hc06_ptr += 2;
+
+ /* skip the UDP header */
+ skb_pull(skb, sizeof(struct udphdr));
+}
+
+static inline int lowpan_fetch_skb_u8(struct sk_buff *skb, u8 *val)
+{
+ if (unlikely(!pskb_may_pull(skb, 1)))
+ return -EINVAL;
+
+ *val = skb->data[0];
+ skb_pull(skb, 1);
+
+ return 0;
+}
+
+static inline int lowpan_fetch_skb_u16(struct sk_buff *skb, u16 *val)
+{
+ if (unlikely(!pskb_may_pull(skb, 2)))
+ return -EINVAL;
+
+ *val = (skb->data[0] << 8) | skb->data[1];
+ skb_pull(skb, 2);
+
+ return 0;
+}
+
+static int header_create(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, const void *_daddr,
+ const void *_saddr, unsigned int len)
+{
+ u8 tmp, iphc0, iphc1, *hc06_ptr;
+ struct ipv6hdr *hdr;
+ u8 *saddr, *daddr;
+ u8 head[100];
+ struct lowpan_info *info;
+
+ if (type != ETH_P_IPV6)
+ return -EINVAL;
+
+ info = lowpan_info(dev);
+ daddr = info->ieee802154_addr;
+ saddr = info->net->dev_addr;
+
+ hdr = ipv6_hdr(skb);
+ hc06_ptr = head + 2;
+
+ pr_debug("IPv6 header dump:\n\tversion = %d\n\tlength = %d\n"
+ "\tnexthdr = 0x%02x\n\thop_lim = %d\n", hdr->version,
+ ntohs(hdr->payload_len), hdr->nexthdr, hdr->hop_limit);
+
+ raw_dump_table(__func__, "raw skb network header dump",
+ skb_network_header(skb), sizeof(struct ipv6hdr));
+
+ raw_dump_inline(__func__, "saddr",
+ (unsigned char *)saddr, 8);
+
+ /*
+ * As we copy some bit-length fields, in the IPHC encoding bytes,
+ * we sometimes use |=
+ * If the field is 0, and the current bit value in memory is 1,
+ * this does not work. We therefore reset the IPHC encoding here
+ */
+ iphc0 = LOWPAN_DISPATCH_IPHC;
+ iphc1 = 0;
+
+ /* TODO: context lookup */
+
+ raw_dump_inline(__func__, "daddr",
+ (unsigned char *)daddr, 8);
+
+ raw_dump_table(__func__,
+ "sending raw skb network uncompressed packet",
+ skb->data, skb->len);
+
+ /*
+ * Traffic class, flow label
+ * If flow label is 0, compress it. If traffic class is 0, compress it
+ * We have to process both in the same time as the offset of traffic
+ * class depends on the presence of version and flow label
+ */
+
+ /* hc06 format of TC is ECN | DSCP , original one is DSCP | ECN */
+ tmp = (hdr->priority << 4) | (hdr->flow_lbl[0] >> 4);
+ tmp = ((tmp & 0x03) << 6) | (tmp >> 2);
+
+ if (((hdr->flow_lbl[0] & 0x0F) == 0) &&
+ (hdr->flow_lbl[1] == 0) && (hdr->flow_lbl[2] == 0)) {
+ /* flow label can be compressed */
+ iphc0 |= LOWPAN_IPHC_FL_C;
+ if ((hdr->priority == 0) &&
+ ((hdr->flow_lbl[0] & 0xF0) == 0)) {
+ /* compress (elide) all */
+ iphc0 |= LOWPAN_IPHC_TC_C;
+ } else {
+ /* compress only the flow label */
+ *hc06_ptr = tmp;
+ hc06_ptr += 1;
+ }
+ } else {
+ /* Flow label cannot be compressed */
+ if ((hdr->priority == 0) &&
+ ((hdr->flow_lbl[0] & 0xF0) == 0)) {
+ /* compress only traffic class */
+ iphc0 |= LOWPAN_IPHC_TC_C;
+ *hc06_ptr = (tmp & 0xc0) | (hdr->flow_lbl[0] & 0x0F);
+ memcpy(hc06_ptr + 1, &hdr->flow_lbl[1], 2);
+ hc06_ptr += 3;
+ } else {
+ /* compress nothing */
+ memcpy(hc06_ptr, &hdr, 4);
+ /* replace the top byte with new ECN | DSCP format */
+ *hc06_ptr = tmp;
+ hc06_ptr += 4;
+ }
+ }
+
+ /* NOTE: payload length is always compressed */
+
+ /* Next Header is compress if UDP */
+ if (hdr->nexthdr == UIP_PROTO_UDP)
+ iphc0 |= LOWPAN_IPHC_NH_C;
+
+ if ((iphc0 & LOWPAN_IPHC_NH_C) == 0) {
+ *hc06_ptr = hdr->nexthdr;
+ hc06_ptr += 1;
+ }
+
+ /*
+ * Hop limit
+ * if 1: compress, encoding is 01
+ * if 64: compress, encoding is 10
+ * if 255: compress, encoding is 11
+ * else do not compress
+ */
+ switch (hdr->hop_limit) {
+ case 1:
+ iphc0 |= LOWPAN_IPHC_TTL_1;
+ break;
+ case 64:
+ iphc0 |= LOWPAN_IPHC_TTL_64;
+ break;
+ case 255:
+ iphc0 |= LOWPAN_IPHC_TTL_255;
+ break;
+ default:
+ *hc06_ptr = hdr->hop_limit;
+ hc06_ptr += 1;
+ break;
+ }
+
+ /* source address compression */
+ if (is_addr_unspecified(&hdr->saddr)) {
+ pr_debug("source address is unspecified, setting SAC\n");
+ iphc1 |= LOWPAN_IPHC_SAC;
+ /* TODO: context lookup */
+ } else if (is_addr_link_local(&hdr->saddr)) {
+ pr_debug("source address is link-local\n");
+ iphc1 |= lowpan_compress_addr_64(&hc06_ptr,
+ LOWPAN_IPHC_SAM_BIT, &hdr->saddr, saddr);
+ } else {
+ pr_debug("send the full source address\n");
+ memcpy(hc06_ptr, &hdr->saddr.s6_addr16[0], 16);
+ hc06_ptr += 16;
+ }
+
+ /* destination address compression */
+ if (is_addr_mcast(&hdr->daddr)) {
+ pr_debug("destination address is multicast: ");
+ iphc1 |= LOWPAN_IPHC_M;
+ if (lowpan_is_mcast_addr_compressable8(&hdr->daddr)) {
+ pr_debug("compressed to 1 octet\n");
+ iphc1 |= LOWPAN_IPHC_DAM_11;
+ /* use last byte */
+ *hc06_ptr = hdr->daddr.s6_addr[15];
+ hc06_ptr += 1;
+ } else if (lowpan_is_mcast_addr_compressable32(&hdr->daddr)) {
+ pr_debug("compressed to 4 octets\n");
+ iphc1 |= LOWPAN_IPHC_DAM_10;
+ /* second byte + the last three */
+ *hc06_ptr = hdr->daddr.s6_addr[1];
+ memcpy(hc06_ptr + 1, &hdr->daddr.s6_addr[13], 3);
+ hc06_ptr += 4;
+ } else if (lowpan_is_mcast_addr_compressable48(&hdr->daddr)) {
+ pr_debug("compressed to 6 octets\n");
+ iphc1 |= LOWPAN_IPHC_DAM_01;
+ /* second byte + the last five */
+ *hc06_ptr = hdr->daddr.s6_addr[1];
+ memcpy(hc06_ptr + 1, &hdr->daddr.s6_addr[11], 5);
+ hc06_ptr += 6;
+ } else {
+ pr_debug("using full address\n");
+ iphc1 |= LOWPAN_IPHC_DAM_00;
+ memcpy(hc06_ptr, &hdr->daddr.s6_addr[0], 16);
+ hc06_ptr += 16;
+ }
+ } else {
+ /* TODO: context lookup */
+ if (is_addr_link_local(&hdr->daddr)) {
+ pr_debug("dest address is unicast and link-local\n");
+ iphc1 |= lowpan_compress_addr_64(&hc06_ptr,
+ LOWPAN_IPHC_DAM_BIT, &hdr->daddr, daddr);
+ } else {
+ pr_debug("dest address is unicast: using full one\n");
+ memcpy(hc06_ptr, &hdr->daddr.s6_addr16[0], 16);
+ hc06_ptr += 16;
+ }
+ }
+
+ /* UDP header compression */
+ if (hdr->nexthdr == UIP_PROTO_UDP)
+ compress_udp_header(&hc06_ptr, skb);
+
+ head[0] = iphc0;
+ head[1] = iphc1;
+
+ skb_pull(skb, sizeof(struct ipv6hdr));
+
+ memcpy(skb_push(skb, hc06_ptr - head), head, hc06_ptr - head);
+
+ BT_DBG("header len %d skb %u", (int)(hc06_ptr - head), skb->len);
+
+ skb_reset_network_header(skb);
+
+ raw_dump_table(__func__, "raw skb data dump compressed",
+ skb->data, skb->len);
+ return 0;
+}
+
/* Packet to BT LE device */
static int send_pkt(struct l2cap_conn *conn, const void *saddr,
const void *daddr, struct sk_buff *skb,
@@ -198,6 +1319,10 @@ static const struct net_device_ops netdev_ops = {
.ndo_start_xmit = xmit,
};
+static struct header_ops header_ops = {
+ .create = header_create,
+};
+
static void setup(struct net_device *dev)
{
dev->addr_len = IEEE802154_ADDR_LEN;
@@ -211,6 +1336,7 @@ static void setup(struct net_device *dev)
dev->watchdog_timeo = 0;
dev->netdev_ops = &netdev_ops;
+ dev->header_ops = &header_ops;
dev->destructor = free_netdev;
}
--
1.7.11.7
This is a temporary patch where user can manually enable or
disable BT 6LoWPAN functionality between devices.
Eventually the connection is established automatically if
the devices are advertising suitable capability and this patch
can be removed.
If you have two devices with these BT addresses
device1 00:11:22:33:44:55
device2 66:77:88:99:00:11
First add the desired devices manually into kernel
root@dev1# echo 66:77:88:99:00:11 > /sys/kernel/debug/bluetooth/hci0/6lowpan
root@dev2# echo 00:11:22:33:44:55 > /sys/kernel/debug/bluetooth/hci0/6lowpan
then connect the devices
root@dev1# hciconfig hci0 leadv
root@dev2# hcitool lecc 00:11:22:33:44:55
if the connection is established, then you can send IPv6 packets
between these two systems using the link local addresses
root@dev1# ping6 fe80::6477:88ff:fe99:0011
root@dev2# ping6 fe80::211:22ff:fe33:4455
By default 6LoWPAN connection is not established between devices,
so you need to add the MAC addresses manually into the
/sys/kernel/debug/bluetooth/hci0/6lowpan file.
If you want to prevent further connections you can remove
MAC address from the debugfs file like this
echo "00:11:22:33:44:55 d" > /sys/kernel/debug/bluetooth/hci0/6lowpan
Rebooting or unloading the bluetooth kernel module will also clear the
settings from the kernel.
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/bluetooth/6lowpan.c | 158 +++++++++++++++++++++++++++++++++++++++++++++
net/bluetooth/6lowpan.h | 2 +
net/bluetooth/hci_core.c | 4 ++
net/bluetooth/l2cap_core.c | 12 ++--
4 files changed, 169 insertions(+), 7 deletions(-)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 4ec3813..55f455a 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -26,6 +26,7 @@
*/
#include <linux/version.h>
+#include <linux/debugfs.h>
#include <linux/bitops.h>
#include <linux/if_arp.h>
#include <linux/netdevice.h>
@@ -1553,6 +1554,163 @@ static struct notifier_block bt_6lowpan_dev_notifier = {
.notifier_call = device_event,
};
+static LIST_HEAD(user_enabled);
+DEFINE_RWLOCK(user_enabled_list_lock);
+
+struct lowpan_enabled {
+ __u8 dev_name[HCI_MAX_NAME_LENGTH];
+ bdaddr_t addr;
+ struct list_head list;
+};
+
+bool bt_6lowpan_is_enabled(struct hci_dev *hdev, bdaddr_t *dst)
+{
+ struct lowpan_enabled *entry, *tmp;
+ bool found = false;
+
+ write_lock(&user_enabled_list_lock);
+ list_for_each_entry_safe(entry, tmp, &user_enabled, list) {
+ if (!strncmp(entry->dev_name, hdev->dev_name,
+ HCI_MAX_NAME_LENGTH) &&
+ !bacmp(dst, &entry->addr)) {
+ found = true;
+ break;
+ }
+ }
+ write_unlock(&user_enabled_list_lock);
+
+ /* Check also the device list just in case we removed the
+ * device from debugfs before disconnecting.
+ */
+ if (!found) {
+ struct lowpan_dev *entry, *tmp;
+ struct lowpan_info *info;
+
+ write_lock(&net_dev_list_lock);
+ list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices,
+ list) {
+ info = lowpan_info(entry->dev);
+ if (info->conn->hcon->hdev == hdev &&
+ !bacmp(&info->addr, dst)) {
+ found = true;
+ break;
+ }
+ }
+ write_unlock(&net_dev_list_lock);
+ }
+
+ return found;
+}
+
+static int debugfs_show(struct seq_file *f, void *p)
+{
+ struct lowpan_enabled *entry, *tmp;
+
+ write_lock(&user_enabled_list_lock);
+ list_for_each_entry_safe(entry, tmp, &user_enabled, list)
+ seq_printf(f, "%pMR\n", &entry->addr);
+
+ write_unlock(&user_enabled_list_lock);
+
+ return 0;
+}
+
+static int debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debugfs_show, inode->i_private);
+}
+
+static ssize_t writer(struct file *fp, const char __user *user_buffer,
+ size_t count, loff_t *position)
+{
+#define MAC_STR_LEN 17
+ char mac_buf[MAC_STR_LEN + 1];
+ ssize_t ret;
+ bool delete_mode = false;
+ struct lowpan_enabled *entry = NULL, *tmp;
+ bdaddr_t bdaddr;
+
+ if(count > (MAC_STR_LEN + 1))
+ delete_mode = true;
+ else if (count < MAC_STR_LEN)
+ return count;
+
+ BT_DBG("count %zd mode %d", count, delete_mode);
+
+ memset(mac_buf, 0, MAC_STR_LEN + 1);
+ ret = simple_write_to_buffer(mac_buf, MAC_STR_LEN, position,
+ user_buffer, count);
+ if (ret <= 0)
+ return ret;
+
+ if (sscanf(mac_buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &bdaddr.b[5], &bdaddr.b[4],
+ &bdaddr.b[3], &bdaddr.b[2],
+ &bdaddr.b[1], &bdaddr.b[0]) != 6)
+ return -EINVAL;
+
+ BT_DBG("user %pMR", &bdaddr);
+
+ write_lock(&user_enabled_list_lock);
+ list_for_each_entry_safe(entry, tmp, &user_enabled, list) {
+ if (!bacmp(&entry->addr, &bdaddr)) {
+ struct hci_dev *hdev = fp->f_inode->i_private;
+
+ if (!strncmp(entry->dev_name, hdev->dev_name,
+ HCI_MAX_NAME_LENGTH) && delete_mode) {
+ break;
+ } else {
+ ret = -EEXIST;
+ break;
+ }
+ }
+ }
+ write_unlock(&user_enabled_list_lock);
+
+ if (ret < 0)
+ return ret;
+
+ if (delete_mode) {
+ write_lock(&user_enabled_list_lock);
+ list_del(&entry->list);
+ kfree(entry);
+ write_unlock(&user_enabled_list_lock);
+ } else {
+ struct hci_dev *hdev = fp->f_inode->i_private;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ strncpy(entry->dev_name, hdev->dev_name, HCI_MAX_NAME_LENGTH);
+ entry->addr = bdaddr;
+
+ write_lock(&user_enabled_list_lock);
+ INIT_LIST_HEAD(&entry->list);
+ list_add(&entry->list, &user_enabled);
+ write_unlock(&user_enabled_list_lock);
+ }
+
+ return ret;
+}
+
+static const struct file_operations ble_6lowpan_debugfs_fops = {
+ .open = debugfs_open,
+ .read = seq_read,
+ .write = writer,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct dentry *bt_6lowpan_debugfs;
+
+void bt_6lowpan_add_debugfs(struct hci_dev *hdev)
+{
+ if (hdev->debugfs)
+ bt_6lowpan_debugfs = debugfs_create_file("6lowpan", 0644,
+ hdev->debugfs, hdev, &ble_6lowpan_debugfs_fops);
+}
+
int bt_6lowpan_init(void)
{
return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
diff --git a/net/bluetooth/6lowpan.h b/net/bluetooth/6lowpan.h
index 680eac8..549fc51 100644
--- a/net/bluetooth/6lowpan.h
+++ b/net/bluetooth/6lowpan.h
@@ -22,5 +22,7 @@ int bt_6lowpan_add_conn(struct l2cap_conn *conn);
int bt_6lowpan_del_conn(struct l2cap_conn *conn);
int bt_6lowpan_init(void);
void bt_6lowpan_cleanup(void);
+bool bt_6lowpan_is_enabled(struct hci_dev *hdev, bdaddr_t *dst);
+void bt_6lowpan_add_debugfs(struct hci_dev *hdev);
#endif /* __6LOWPAN_H */
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 6ccc4eb..444b4bc 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -34,6 +34,8 @@
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
+#include "6lowpan.h"
+
static void hci_rx_work(struct work_struct *work);
static void hci_cmd_work(struct work_struct *work);
static void hci_tx_work(struct work_struct *work);
@@ -1406,6 +1408,8 @@ static int __hci_init(struct hci_dev *hdev)
hdev, &conn_max_interval_fops);
}
+ bt_6lowpan_add_debugfs(hdev);
+
return 0;
}
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index c47215f..d02241e 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -6512,11 +6512,6 @@ int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
return exact ? lm1 : lm2;
}
-static bool is_bt_6lowpan(void)
-{
- return false;
-}
-
void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
{
struct l2cap_conn *conn;
@@ -6528,7 +6523,9 @@ void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
if (conn) {
l2cap_conn_ready(conn);
- if (hcon->type == LE_LINK && is_bt_6lowpan())
+ if (hcon->type == LE_LINK &&
+ bt_6lowpan_is_enabled(hcon->hdev,
+ &hcon->dst))
bt_6lowpan_add_conn(conn);
}
} else {
@@ -6551,7 +6548,8 @@ void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
{
BT_DBG("hcon %p reason %d", hcon, reason);
- if (hcon->type == LE_LINK && is_bt_6lowpan())
+ if (hcon->type == LE_LINK && bt_6lowpan_is_enabled(hcon->hdev,
+ &hcon->dst))
bt_6lowpan_del_conn(hcon->l2cap_data);
l2cap_conn_del(hcon, bt_to_errno(reason));
--
1.7.11.7
Signed-off-by: Jukka Rissanen <[email protected]>
---
include/net/bluetooth/l2cap.h | 1 +
net/bluetooth/6lowpan.c | 401 ++++++++++++++++++++++++++++++++++++++++++
net/bluetooth/6lowpan.h | 26 +++
net/bluetooth/Makefile | 2 +-
net/bluetooth/l2cap_core.c | 22 ++-
5 files changed, 450 insertions(+), 2 deletions(-)
create mode 100644 net/bluetooth/6lowpan.c
create mode 100644 net/bluetooth/6lowpan.h
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 5132990..c28ac0d 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -133,6 +133,7 @@ struct l2cap_conninfo {
#define L2CAP_FC_L2CAP 0x02
#define L2CAP_FC_CONNLESS 0x04
#define L2CAP_FC_A2MP 0x08
+#define L2CAP_FC_6LOWPAN 0x3e
/* L2CAP Control Field bit masks */
#define L2CAP_CTRL_SAR 0xC000
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
new file mode 100644
index 0000000..aff478b
--- /dev/null
+++ b/net/bluetooth/6lowpan.c
@@ -0,0 +1,401 @@
+/*
+ Copyright (c) 2013 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.
+*/
+
+#include <linux/version.h>
+#include <linux/bitops.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+
+#include <net/ipv6.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+
+#include <net/af_ieee802154.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/l2cap.h>
+
+#include "../ieee802154/6lowpan.h" /* for the compression defines */
+
+#define IFACE_NAME_TEMPLATE "bt%d"
+
+/*
+ * The devices list contains those devices that we are acting
+ * as a proxy. The BT 6LoWPAN device is a virtual device that
+ * connects to the Bluetooth LE device. The real connection to
+ * BT device is done via l2cap layer. There exists one
+ * virtual device / one BT 6LoWPAN device. The list contains
+ * struct lowpan_dev elements.
+ */
+static LIST_HEAD(bt_6lowpan_devices);
+DEFINE_RWLOCK(net_dev_list_lock);
+
+struct lowpan_dev {
+ struct net_device *dev;
+ struct delayed_work delete_timer;
+ struct list_head list;
+};
+
+struct lowpan_info {
+ struct net_device *net;
+ struct l2cap_conn *conn;
+ uint16_t ifindex;
+ bdaddr_t myaddr;
+
+ /* peer addresses in various formats */
+ bdaddr_t addr;
+ unsigned char ieee802154_addr[IEEE802154_ADDR_LEN];
+ struct in6_addr peer;
+};
+
+struct lowpan_fragment {
+ struct sk_buff *skb; /* skb to be assembled */
+ u16 length; /* length to be assemled */
+ u32 bytes_rcv; /* bytes received */
+ u16 tag; /* current fragment tag */
+ struct timer_list timer; /* assembling timer */
+ struct list_head list; /* fragments list */
+};
+
+#define DELETE_TIMEOUT msecs_to_jiffies(1)
+
+/* TTL uncompression values */
+static const u8 lowpan_ttl_values[] = {0, 1, 64, 255};
+
+static inline struct
+lowpan_info *lowpan_info(const struct net_device *dev)
+{
+ return netdev_priv(dev);
+}
+
+/* print data in line */
+static inline void raw_dump_inline(const char *caller, char *msg,
+ unsigned char *buf, int len)
+{
+#ifdef DEBUG
+ if (msg)
+ pr_debug("%s():%s: ", caller, msg);
+ print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_NONE,
+ 16, 1, buf, len, false);
+#endif /* DEBUG */
+}
+
+/*
+ * print data in a table format:
+ *
+ * addr: xx xx xx xx xx xx
+ * addr: xx xx xx xx xx xx
+ * ...
+ */
+static inline void raw_dump_table(const char *caller, char *msg,
+ unsigned char *buf, int len)
+{
+#ifdef DEBUG
+ if (msg)
+ pr_debug("%s():%s:\n", caller, msg);
+ print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_OFFSET,
+ 16, 1, buf, len, false);
+#endif /* DEBUG */
+}
+
+static int recv_pkt(struct sk_buff *skb, struct net_device *dev)
+{
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+/* Packet from BT LE device */
+int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb)
+{
+ struct lowpan_dev *entry, *tmp;
+ struct net_device *dev = NULL;
+ int status = -ENOENT;
+
+ write_lock(&net_dev_list_lock);
+
+ list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices, list) {
+ if (lowpan_info(entry->dev)->conn == conn) {
+ dev = lowpan_info(entry->dev)->net;
+ break;
+ }
+ }
+
+ write_unlock(&net_dev_list_lock);
+
+ if (dev) {
+ status = recv_pkt(skb, dev);
+ BT_DBG("recv pkt %d", status);
+ }
+
+ return status;
+}
+
+static void do_send(struct l2cap_conn *conn, struct sk_buff *skb)
+{
+ BT_DBG("conn %p, skb %p len %d priority %u", conn, skb, skb->len,
+ skb->priority);
+
+ return;
+}
+
+static int conn_send(struct l2cap_conn *conn,
+ void *msg, size_t len, u32 priority,
+ struct net_device *dev)
+{
+ struct sk_buff *skb = {0};
+
+ do_send(conn, skb);
+ return 0;
+}
+
+/* Packet to BT LE device */
+static int send_pkt(struct l2cap_conn *conn, const void *saddr,
+ const void *daddr, struct sk_buff *skb,
+ struct net_device *dev)
+{
+ raw_dump_table(__func__, "raw skb data dump before fragmentation",
+ skb->data, skb->len);
+
+ return conn_send(conn, skb->data, skb->len, 0, dev);
+}
+
+static netdev_tx_t xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ int err = -1;
+
+ pr_debug("ble 6lowpan packet xmit\n");
+
+ if (lowpan_info(dev)->conn)
+ err = send_pkt(lowpan_info(dev)->conn,
+ dev->dev_addr,
+ &lowpan_info(dev)->ieee802154_addr,
+ skb,
+ dev);
+ else
+ BT_DBG("ERROR: no BT LE 6LoWPAN device found");
+
+ dev_kfree_skb(skb);
+
+ if (err)
+ BT_DBG("ERROR: xmit failed (%d)", err);
+
+ return (err < 0) ? NET_XMIT_DROP : err;
+}
+
+static const struct net_device_ops netdev_ops = {
+ .ndo_start_xmit = xmit,
+};
+
+static void setup(struct net_device *dev)
+{
+ dev->addr_len = IEEE802154_ADDR_LEN;
+ dev->type = ARPHRD_RAWIP;
+
+ dev->hard_header_len = 0;
+ dev->needed_tailroom = 0;
+ dev->mtu = IPV6_MIN_MTU;
+ dev->tx_queue_len = 0;
+ dev->flags = IFF_RUNNING | IFF_POINTOPOINT;
+ dev->watchdog_timeo = 0;
+
+ dev->netdev_ops = &netdev_ops;
+ dev->destructor = free_netdev;
+}
+
+static struct device_type bt_type = {
+ .name = "bluetooth",
+};
+
+static void set_addr(u8 *eui, u8 *addr, u8 addr_type)
+{
+ /* addr is the BT address in little-endian format */
+ eui[0] = addr[5];
+ eui[1] = addr[4];
+ eui[2] = addr[3];
+ eui[3] = 0xFF;
+ eui[4] = 0xFE;
+ eui[5] = addr[2];
+ eui[6] = addr[1];
+ eui[7] = addr[0];
+
+ eui[0] ^= 2;
+
+ if (addr_type == BDADDR_LE_PUBLIC)
+ eui[0] &= ~1;
+ else
+ eui[0] |= 1;
+}
+
+static void set_dev_addr(struct net_device *net, bdaddr_t *addr,
+ u8 addr_type)
+{
+ net->addr_assign_type = NET_ADDR_PERM;
+ set_addr(net->dev_addr, addr->b, addr_type);
+ net->dev_addr[0] ^= 2;
+}
+
+static void ifup(struct net_device *net)
+{
+ int status;
+
+ rtnl_lock();
+ if ((status = dev_open(net)) < 0)
+ BT_INFO("iface %s cannot be opened (%d)", net->name, status);
+ rtnl_unlock();
+}
+
+/*
+ * This gets called when BT LE 6LoWPAN device is connected. We then
+ * create network device that acts as a proxy between BT LE device
+ * and kernel network stack.
+ */
+int bt_6lowpan_add_conn(struct l2cap_conn *conn)
+{
+ struct net_device *net;
+ struct lowpan_info *dev;
+ struct lowpan_dev *entry;
+ struct inet6_dev *idev;
+ int status;
+
+ net = alloc_netdev(sizeof(struct lowpan_info), IFACE_NAME_TEMPLATE,
+ setup);
+ if (!net)
+ return -ENOMEM;
+
+ dev = netdev_priv(net);
+ dev->net = net;
+
+ memcpy(&dev->myaddr, &conn->hcon->src, sizeof(bdaddr_t));
+ memcpy(&dev->addr, &conn->hcon->dst, sizeof(bdaddr_t));
+
+ set_dev_addr(net, &dev->myaddr, conn->hcon->src_type);
+
+ dev->conn = conn;
+
+ net->netdev_ops = &netdev_ops;
+ SET_NETDEV_DEV(net, &conn->hcon->dev);
+ SET_NETDEV_DEVTYPE(net, &bt_type);
+
+ status = register_netdev(net);
+ if (status < 0) {
+ BT_INFO("register_netdev failed %d", status);
+ free_netdev(net);
+ return status;
+ }
+
+ BT_DBG("ifindex %d peer bdaddr %pMR my addr %pMR",
+ net->ifindex, &dev->addr, &dev->myaddr);
+ dev->ifindex = net->ifindex;
+ set_bit(__LINK_STATE_PRESENT, &net->state);
+
+ idev = in6_dev_get(net);
+ if (idev) {
+ idev->cnf.autoconf = 1;
+ idev->cnf.forwarding = 1;
+ idev->cnf.accept_ra = 2;
+
+ in6_dev_put(idev);
+ }
+
+ entry = kzalloc(sizeof(struct lowpan_dev), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ entry->dev = net;
+
+ write_lock(&net_dev_list_lock);
+ INIT_LIST_HEAD(&entry->list);
+ list_add(&entry->list, &bt_6lowpan_devices);
+ write_unlock(&net_dev_list_lock);
+
+ ifup(net);
+
+ return 0;
+}
+
+static void delete_timeout(struct work_struct *work)
+{
+ struct lowpan_dev *entry = container_of(work, struct lowpan_dev,
+ delete_timer.work);
+
+ unregister_netdev(entry->dev);
+ kfree(entry);
+}
+
+int bt_6lowpan_del_conn(struct l2cap_conn *conn)
+{
+ struct lowpan_dev *entry, *tmp;
+ int status = -ENOENT;
+
+ write_lock(&net_dev_list_lock);
+
+ list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices, list) {
+ if (lowpan_info(entry->dev)->conn == conn) {
+ list_del(&entry->list);
+ status = 0;
+ break;
+ }
+ }
+
+ write_unlock(&net_dev_list_lock);
+
+ if (!status) {
+ INIT_DELAYED_WORK(&entry->delete_timer, delete_timeout);
+ schedule_delayed_work(&entry->delete_timer, DELETE_TIMEOUT);
+ }
+
+ return status;
+}
+
+static int device_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct lowpan_dev *entry, *tmp;
+
+ if (dev->type != ARPHRD_RAWIP)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_UNREGISTER:
+ write_lock(&net_dev_list_lock);
+ list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices,
+ list) {
+ if (entry->dev == dev) {
+ list_del(&entry->list);
+ kfree(entry);
+ break;
+ }
+ }
+ write_unlock(&net_dev_list_lock);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block bt_6lowpan_dev_notifier = {
+ .notifier_call = device_event,
+};
+
+int bt_6lowpan_init(void)
+{
+ return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
+}
+
+void bt_6lowpan_cleanup(void)
+{
+ unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
+}
diff --git a/net/bluetooth/6lowpan.h b/net/bluetooth/6lowpan.h
new file mode 100644
index 0000000..680eac8
--- /dev/null
+++ b/net/bluetooth/6lowpan.h
@@ -0,0 +1,26 @@
+/*
+ Copyright (c) 2013 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.
+*/
+
+#ifndef __6LOWPAN_H
+#define __6LOWPAN_H
+
+#include <linux/skbuff.h>
+#include <net/bluetooth/l2cap.h>
+
+int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb);
+int bt_6lowpan_add_conn(struct l2cap_conn *conn);
+int bt_6lowpan_del_conn(struct l2cap_conn *conn);
+int bt_6lowpan_init(void);
+void bt_6lowpan_cleanup(void);
+
+#endif /* __6LOWPAN_H */
diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
index 6a791e7..80cb215 100644
--- a/net/bluetooth/Makefile
+++ b/net/bluetooth/Makefile
@@ -10,6 +10,6 @@ obj-$(CONFIG_BT_HIDP) += hidp/
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 \
- a2mp.o amp.o
+ a2mp.o amp.o 6lowpan.o
subdir-ccflags-y += -D__CHECK_ENDIAN__
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 0cef677..c47215f 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -40,6 +40,7 @@
#include "smp.h"
#include "a2mp.h"
#include "amp.h"
+#include "6lowpan.h"
bool disable_ertm;
@@ -6470,6 +6471,10 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
l2cap_conn_del(conn->hcon, EACCES);
break;
+ case L2CAP_FC_6LOWPAN:
+ bt_6lowpan_recv(conn, skb);
+ break;
+
default:
l2cap_data_channel(conn, cid, skb);
break;
@@ -6507,6 +6512,11 @@ int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr)
return exact ? lm1 : lm2;
}
+static bool is_bt_6lowpan(void)
+{
+ return false;
+}
+
void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
{
struct l2cap_conn *conn;
@@ -6515,8 +6525,12 @@ void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
if (!status) {
conn = l2cap_conn_add(hcon);
- if (conn)
+ if (conn) {
l2cap_conn_ready(conn);
+
+ if (hcon->type == LE_LINK && is_bt_6lowpan())
+ bt_6lowpan_add_conn(conn);
+ }
} else {
l2cap_conn_del(hcon, bt_to_errno(status));
}
@@ -6537,6 +6551,9 @@ void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
{
BT_DBG("hcon %p reason %d", hcon, reason);
+ if (hcon->type == LE_LINK && is_bt_6lowpan())
+ bt_6lowpan_del_conn(hcon->l2cap_data);
+
l2cap_conn_del(hcon, bt_to_errno(reason));
}
@@ -6814,11 +6831,14 @@ int __init l2cap_init(void)
l2cap_debugfs = debugfs_create_file("l2cap", 0444, bt_debugfs,
NULL, &l2cap_debugfs_fops);
+ bt_6lowpan_init();
+
return 0;
}
void l2cap_exit(void)
{
+ bt_6lowpan_cleanup();
debugfs_remove(l2cap_debugfs);
l2cap_cleanup_sockets();
}
--
1.7.11.7
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/ipv6/route.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index c979dd9..d7c200f 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -1662,6 +1662,7 @@ out:
dst_free(&rt->dst);
return err;
}
+EXPORT_SYMBOL_GPL(ip6_route_add);
static int __ip6_del_rt(struct rt6_info *rt, struct nl_info *info)
{
--
1.7.11.7
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/ipv6/addrconf.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index d6ff126..60bf947 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1783,6 +1783,15 @@ static int addrconf_ifid_ip6tnl(u8 *eui, struct net_device *dev)
return 0;
}
+static int addrconf_ifid_rawip(u8 *eui, struct net_device *dev)
+{
+ if (dev->addr_len != 8)
+ return -1;
+ memcpy(eui, dev->dev_addr, 8);
+ eui[0] ^= 2;
+ return 0;
+}
+
static int ipv6_generate_eui64(u8 *eui, struct net_device *dev)
{
switch (dev->type) {
@@ -1803,6 +1812,8 @@ static int ipv6_generate_eui64(u8 *eui, struct net_device *dev)
return addrconf_ifid_ieee1394(eui, dev);
case ARPHRD_TUNNEL6:
return addrconf_ifid_ip6tnl(eui, dev);
+ case ARPHRD_RAWIP:
+ return addrconf_ifid_rawip(eui, dev);
}
return -1;
}
@@ -2681,7 +2692,8 @@ static void addrconf_dev_config(struct net_device *dev)
(dev->type != ARPHRD_INFINIBAND) &&
(dev->type != ARPHRD_IEEE802154) &&
(dev->type != ARPHRD_IEEE1394) &&
- (dev->type != ARPHRD_TUNNEL6)) {
+ (dev->type != ARPHRD_TUNNEL6) &&
+ (dev->type != ARPHRD_RAWIP)) {
/* Alas, we support only Ethernet autoconfiguration. */
return;
}
--
1.7.11.7
This is used when there is no L2 header before IP header.
Example of this is Bluetooth 6LoWPAN network.
The RAWIP header type value is already used in some Android kernels
so same value is used here in order not to break userspace.
Signed-off-by: Jukka Rissanen <[email protected]>
---
include/uapi/linux/if_arp.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/uapi/linux/if_arp.h b/include/uapi/linux/if_arp.h
index d7fea34..06fc69f 100644
--- a/include/uapi/linux/if_arp.h
+++ b/include/uapi/linux/if_arp.h
@@ -59,6 +59,7 @@
#define ARPHRD_LAPB 516 /* LAPB */
#define ARPHRD_DDCMP 517 /* Digital's DDCMP protocol */
#define ARPHRD_RAWHDLC 518 /* Raw HDLC */
+#define ARPHRD_RAWIP 530 /* Raw IP */
#define ARPHRD_TUNNEL 768 /* IPIP tunnel */
#define ARPHRD_TUNNEL6 769 /* IP6IP6 tunnel */
--
1.7.11.7