mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
732
net/ieee802154/6lowpan_rtnl.c
Normal file
732
net/ieee802154/6lowpan_rtnl.c
Normal file
|
@ -0,0 +1,732 @@
|
|||
/* Copyright 2011, Siemens AG
|
||||
* written by Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
|
||||
*/
|
||||
|
||||
/* Based on patches from Jon Smirl <jonsmirl@gmail.com>
|
||||
* Copyright (c) 2011 Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*/
|
||||
|
||||
/* Jon's code is based on 6lowpan implementation for Contiki which is:
|
||||
* Copyright (c) 2008, Swedish Institute of Computer Science.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <net/af_ieee802154.h>
|
||||
#include <net/ieee802154.h>
|
||||
#include <net/ieee802154_netdev.h>
|
||||
#include <net/6lowpan.h>
|
||||
#include <net/ipv6.h>
|
||||
|
||||
#include "reassembly.h"
|
||||
|
||||
static LIST_HEAD(lowpan_devices);
|
||||
|
||||
/* private device info */
|
||||
struct lowpan_dev_info {
|
||||
struct net_device *real_dev; /* real WPAN device ptr */
|
||||
struct mutex dev_list_mtx; /* mutex for list ops */
|
||||
__be16 fragment_tag;
|
||||
};
|
||||
|
||||
struct lowpan_dev_record {
|
||||
struct net_device *ldev;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/* don't save pan id, it's intra pan */
|
||||
struct lowpan_addr {
|
||||
u8 mode;
|
||||
union {
|
||||
/* IPv6 needs big endian here */
|
||||
__be64 extended_addr;
|
||||
__be16 short_addr;
|
||||
} u;
|
||||
};
|
||||
|
||||
struct lowpan_addr_info {
|
||||
struct lowpan_addr daddr;
|
||||
struct lowpan_addr saddr;
|
||||
};
|
||||
|
||||
static inline struct
|
||||
lowpan_dev_info *lowpan_dev_info(const struct net_device *dev)
|
||||
{
|
||||
return netdev_priv(dev);
|
||||
}
|
||||
|
||||
static inline struct
|
||||
lowpan_addr_info *lowpan_skb_priv(const struct sk_buff *skb)
|
||||
{
|
||||
WARN_ON_ONCE(skb_headroom(skb) < sizeof(struct lowpan_addr_info));
|
||||
return (struct lowpan_addr_info *)(skb->data -
|
||||
sizeof(struct lowpan_addr_info));
|
||||
}
|
||||
|
||||
static int lowpan_header_create(struct sk_buff *skb, struct net_device *dev,
|
||||
unsigned short type, const void *_daddr,
|
||||
const void *_saddr, unsigned int len)
|
||||
{
|
||||
const u8 *saddr = _saddr;
|
||||
const u8 *daddr = _daddr;
|
||||
struct lowpan_addr_info *info;
|
||||
|
||||
/* TODO:
|
||||
* if this package isn't ipv6 one, where should it be routed?
|
||||
*/
|
||||
if (type != ETH_P_IPV6)
|
||||
return 0;
|
||||
|
||||
if (!saddr)
|
||||
saddr = dev->dev_addr;
|
||||
|
||||
raw_dump_inline(__func__, "saddr", (unsigned char *)saddr, 8);
|
||||
raw_dump_inline(__func__, "daddr", (unsigned char *)daddr, 8);
|
||||
|
||||
info = lowpan_skb_priv(skb);
|
||||
|
||||
/* TODO: Currently we only support extended_addr */
|
||||
info->daddr.mode = IEEE802154_ADDR_LONG;
|
||||
memcpy(&info->daddr.u.extended_addr, daddr,
|
||||
sizeof(info->daddr.u.extended_addr));
|
||||
info->saddr.mode = IEEE802154_ADDR_LONG;
|
||||
memcpy(&info->saddr.u.extended_addr, saddr,
|
||||
sizeof(info->daddr.u.extended_addr));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lowpan_give_skb_to_devices(struct sk_buff *skb,
|
||||
struct net_device *dev)
|
||||
{
|
||||
struct lowpan_dev_record *entry;
|
||||
struct sk_buff *skb_cp;
|
||||
int stat = NET_RX_SUCCESS;
|
||||
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(entry, &lowpan_devices, list)
|
||||
if (lowpan_dev_info(entry->ldev)->real_dev == skb->dev) {
|
||||
skb_cp = skb_copy(skb, GFP_ATOMIC);
|
||||
if (!skb_cp) {
|
||||
stat = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
skb_cp->dev = entry->ldev;
|
||||
stat = netif_rx(skb_cp);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
static int process_data(struct sk_buff *skb, const struct ieee802154_hdr *hdr)
|
||||
{
|
||||
u8 iphc0, iphc1;
|
||||
struct ieee802154_addr_sa sa, da;
|
||||
void *sap, *dap;
|
||||
|
||||
raw_dump_table(__func__, "raw skb data dump", skb->data, skb->len);
|
||||
/* at least two bytes will be used for the encoding */
|
||||
if (skb->len < 2)
|
||||
goto drop;
|
||||
|
||||
if (lowpan_fetch_skb_u8(skb, &iphc0))
|
||||
goto drop;
|
||||
|
||||
if (lowpan_fetch_skb_u8(skb, &iphc1))
|
||||
goto drop;
|
||||
|
||||
ieee802154_addr_to_sa(&sa, &hdr->source);
|
||||
ieee802154_addr_to_sa(&da, &hdr->dest);
|
||||
|
||||
if (sa.addr_type == IEEE802154_ADDR_SHORT)
|
||||
sap = &sa.short_addr;
|
||||
else
|
||||
sap = &sa.hwaddr;
|
||||
|
||||
if (da.addr_type == IEEE802154_ADDR_SHORT)
|
||||
dap = &da.short_addr;
|
||||
else
|
||||
dap = &da.hwaddr;
|
||||
|
||||
return lowpan_process_data(skb, skb->dev, sap, sa.addr_type,
|
||||
IEEE802154_ADDR_LEN, dap, da.addr_type,
|
||||
IEEE802154_ADDR_LEN, iphc0, iphc1,
|
||||
lowpan_give_skb_to_devices);
|
||||
|
||||
drop:
|
||||
kfree_skb(skb);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int lowpan_set_address(struct net_device *dev, void *p)
|
||||
{
|
||||
struct sockaddr *sa = p;
|
||||
|
||||
if (netif_running(dev))
|
||||
return -EBUSY;
|
||||
|
||||
/* TODO: validate addr */
|
||||
memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sk_buff*
|
||||
lowpan_alloc_frag(struct sk_buff *skb, int size,
|
||||
const struct ieee802154_hdr *master_hdr)
|
||||
{
|
||||
struct net_device *real_dev = lowpan_dev_info(skb->dev)->real_dev;
|
||||
struct sk_buff *frag;
|
||||
int rc;
|
||||
|
||||
frag = alloc_skb(real_dev->hard_header_len +
|
||||
real_dev->needed_tailroom + size,
|
||||
GFP_ATOMIC);
|
||||
|
||||
if (likely(frag)) {
|
||||
frag->dev = real_dev;
|
||||
frag->priority = skb->priority;
|
||||
skb_reserve(frag, real_dev->hard_header_len);
|
||||
skb_reset_network_header(frag);
|
||||
*mac_cb(frag) = *mac_cb(skb);
|
||||
|
||||
rc = dev_hard_header(frag, real_dev, 0, &master_hdr->dest,
|
||||
&master_hdr->source, size);
|
||||
if (rc < 0) {
|
||||
kfree_skb(frag);
|
||||
return ERR_PTR(-rc);
|
||||
}
|
||||
} else {
|
||||
frag = ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
static int
|
||||
lowpan_xmit_fragment(struct sk_buff *skb, const struct ieee802154_hdr *wpan_hdr,
|
||||
u8 *frag_hdr, int frag_hdrlen,
|
||||
int offset, int len)
|
||||
{
|
||||
struct sk_buff *frag;
|
||||
|
||||
raw_dump_inline(__func__, " fragment header", frag_hdr, frag_hdrlen);
|
||||
|
||||
frag = lowpan_alloc_frag(skb, frag_hdrlen + len, wpan_hdr);
|
||||
if (IS_ERR(frag))
|
||||
return -PTR_ERR(frag);
|
||||
|
||||
memcpy(skb_put(frag, frag_hdrlen), frag_hdr, frag_hdrlen);
|
||||
memcpy(skb_put(frag, len), skb_network_header(skb) + offset, len);
|
||||
|
||||
raw_dump_table(__func__, " fragment dump", frag->data, frag->len);
|
||||
|
||||
return dev_queue_xmit(frag);
|
||||
}
|
||||
|
||||
static int
|
||||
lowpan_xmit_fragmented(struct sk_buff *skb, struct net_device *dev,
|
||||
const struct ieee802154_hdr *wpan_hdr)
|
||||
{
|
||||
u16 dgram_size, dgram_offset;
|
||||
__be16 frag_tag;
|
||||
u8 frag_hdr[5];
|
||||
int frag_cap, frag_len, payload_cap, rc;
|
||||
int skb_unprocessed, skb_offset;
|
||||
|
||||
dgram_size = lowpan_uncompress_size(skb, &dgram_offset) -
|
||||
skb->mac_len;
|
||||
frag_tag = lowpan_dev_info(dev)->fragment_tag++;
|
||||
|
||||
frag_hdr[0] = LOWPAN_DISPATCH_FRAG1 | ((dgram_size >> 8) & 0x07);
|
||||
frag_hdr[1] = dgram_size & 0xff;
|
||||
memcpy(frag_hdr + 2, &frag_tag, sizeof(frag_tag));
|
||||
|
||||
payload_cap = ieee802154_max_payload(wpan_hdr);
|
||||
|
||||
frag_len = round_down(payload_cap - LOWPAN_FRAG1_HEAD_SIZE -
|
||||
skb_network_header_len(skb), 8);
|
||||
|
||||
skb_offset = skb_network_header_len(skb);
|
||||
skb_unprocessed = skb->len - skb->mac_len - skb_offset;
|
||||
|
||||
rc = lowpan_xmit_fragment(skb, wpan_hdr, frag_hdr,
|
||||
LOWPAN_FRAG1_HEAD_SIZE, 0,
|
||||
frag_len + skb_network_header_len(skb));
|
||||
if (rc) {
|
||||
pr_debug("%s unable to send FRAG1 packet (tag: %d)",
|
||||
__func__, frag_tag);
|
||||
goto err;
|
||||
}
|
||||
|
||||
frag_hdr[0] &= ~LOWPAN_DISPATCH_FRAG1;
|
||||
frag_hdr[0] |= LOWPAN_DISPATCH_FRAGN;
|
||||
frag_cap = round_down(payload_cap - LOWPAN_FRAGN_HEAD_SIZE, 8);
|
||||
|
||||
do {
|
||||
dgram_offset += frag_len;
|
||||
skb_offset += frag_len;
|
||||
skb_unprocessed -= frag_len;
|
||||
frag_len = min(frag_cap, skb_unprocessed);
|
||||
|
||||
frag_hdr[4] = dgram_offset >> 3;
|
||||
|
||||
rc = lowpan_xmit_fragment(skb, wpan_hdr, frag_hdr,
|
||||
LOWPAN_FRAGN_HEAD_SIZE, skb_offset,
|
||||
frag_len);
|
||||
if (rc) {
|
||||
pr_debug("%s unable to send a FRAGN packet. (tag: %d, offset: %d)\n",
|
||||
__func__, frag_tag, skb_offset);
|
||||
goto err;
|
||||
}
|
||||
} while (skb_unprocessed > frag_cap);
|
||||
|
||||
consume_skb(skb);
|
||||
return NET_XMIT_SUCCESS;
|
||||
|
||||
err:
|
||||
kfree_skb(skb);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lowpan_header(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct ieee802154_addr sa, da;
|
||||
struct ieee802154_mac_cb *cb = mac_cb_init(skb);
|
||||
struct lowpan_addr_info info;
|
||||
void *daddr, *saddr;
|
||||
|
||||
memcpy(&info, lowpan_skb_priv(skb), sizeof(info));
|
||||
|
||||
/* TODO: Currently we only support extended_addr */
|
||||
daddr = &info.daddr.u.extended_addr;
|
||||
saddr = &info.saddr.u.extended_addr;
|
||||
|
||||
lowpan_header_compress(skb, dev, ETH_P_IPV6, daddr, saddr, skb->len);
|
||||
|
||||
cb->type = IEEE802154_FC_TYPE_DATA;
|
||||
|
||||
/* prepare wpan address data */
|
||||
sa.mode = IEEE802154_ADDR_LONG;
|
||||
sa.pan_id = ieee802154_mlme_ops(dev)->get_pan_id(dev);
|
||||
sa.extended_addr = ieee802154_devaddr_from_raw(saddr);
|
||||
|
||||
/* intra-PAN communications */
|
||||
da.pan_id = sa.pan_id;
|
||||
|
||||
/* if the destination address is the broadcast address, use the
|
||||
* corresponding short address
|
||||
*/
|
||||
if (lowpan_is_addr_broadcast((const u8 *)daddr)) {
|
||||
da.mode = IEEE802154_ADDR_SHORT;
|
||||
da.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
|
||||
cb->ackreq = false;
|
||||
} else {
|
||||
da.mode = IEEE802154_ADDR_LONG;
|
||||
da.extended_addr = ieee802154_devaddr_from_raw(daddr);
|
||||
cb->ackreq = true;
|
||||
}
|
||||
|
||||
return dev_hard_header(skb, lowpan_dev_info(dev)->real_dev,
|
||||
ETH_P_IPV6, (void *)&da, (void *)&sa, 0);
|
||||
}
|
||||
|
||||
static netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct ieee802154_hdr wpan_hdr;
|
||||
int max_single, ret;
|
||||
|
||||
pr_debug("package xmit\n");
|
||||
|
||||
/* We must take a copy of the skb before we modify/replace the ipv6
|
||||
* header as the header could be used elsewhere
|
||||
*/
|
||||
skb = skb_unshare(skb, GFP_ATOMIC);
|
||||
if (!skb)
|
||||
return NET_XMIT_DROP;
|
||||
|
||||
ret = lowpan_header(skb, dev);
|
||||
if (ret < 0) {
|
||||
kfree_skb(skb);
|
||||
return NET_XMIT_DROP;
|
||||
}
|
||||
|
||||
if (ieee802154_hdr_peek(skb, &wpan_hdr) < 0) {
|
||||
kfree_skb(skb);
|
||||
return NET_XMIT_DROP;
|
||||
}
|
||||
|
||||
max_single = ieee802154_max_payload(&wpan_hdr);
|
||||
|
||||
if (skb_tail_pointer(skb) - skb_network_header(skb) <= max_single) {
|
||||
skb->dev = lowpan_dev_info(dev)->real_dev;
|
||||
return dev_queue_xmit(skb);
|
||||
} else {
|
||||
netdev_tx_t rc;
|
||||
|
||||
pr_debug("frame is too big, fragmentation is needed\n");
|
||||
rc = lowpan_xmit_fragmented(skb, dev, &wpan_hdr);
|
||||
|
||||
return rc < 0 ? NET_XMIT_DROP : rc;
|
||||
}
|
||||
}
|
||||
|
||||
static struct wpan_phy *lowpan_get_phy(const struct net_device *dev)
|
||||
{
|
||||
struct net_device *real_dev = lowpan_dev_info(dev)->real_dev;
|
||||
|
||||
return ieee802154_mlme_ops(real_dev)->get_phy(real_dev);
|
||||
}
|
||||
|
||||
static __le16 lowpan_get_pan_id(const struct net_device *dev)
|
||||
{
|
||||
struct net_device *real_dev = lowpan_dev_info(dev)->real_dev;
|
||||
|
||||
return ieee802154_mlme_ops(real_dev)->get_pan_id(real_dev);
|
||||
}
|
||||
|
||||
static __le16 lowpan_get_short_addr(const struct net_device *dev)
|
||||
{
|
||||
struct net_device *real_dev = lowpan_dev_info(dev)->real_dev;
|
||||
|
||||
return ieee802154_mlme_ops(real_dev)->get_short_addr(real_dev);
|
||||
}
|
||||
|
||||
static u8 lowpan_get_dsn(const struct net_device *dev)
|
||||
{
|
||||
struct net_device *real_dev = lowpan_dev_info(dev)->real_dev;
|
||||
|
||||
return ieee802154_mlme_ops(real_dev)->get_dsn(real_dev);
|
||||
}
|
||||
|
||||
static struct header_ops lowpan_header_ops = {
|
||||
.create = lowpan_header_create,
|
||||
};
|
||||
|
||||
static struct lock_class_key lowpan_tx_busylock;
|
||||
static struct lock_class_key lowpan_netdev_xmit_lock_key;
|
||||
|
||||
static void lowpan_set_lockdep_class_one(struct net_device *dev,
|
||||
struct netdev_queue *txq,
|
||||
void *_unused)
|
||||
{
|
||||
lockdep_set_class(&txq->_xmit_lock,
|
||||
&lowpan_netdev_xmit_lock_key);
|
||||
}
|
||||
|
||||
|
||||
static int lowpan_dev_init(struct net_device *dev)
|
||||
{
|
||||
netdev_for_each_tx_queue(dev, lowpan_set_lockdep_class_one, NULL);
|
||||
dev->qdisc_tx_busylock = &lowpan_tx_busylock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct net_device_ops lowpan_netdev_ops = {
|
||||
.ndo_init = lowpan_dev_init,
|
||||
.ndo_start_xmit = lowpan_xmit,
|
||||
.ndo_set_mac_address = lowpan_set_address,
|
||||
};
|
||||
|
||||
static struct ieee802154_mlme_ops lowpan_mlme = {
|
||||
.get_pan_id = lowpan_get_pan_id,
|
||||
.get_phy = lowpan_get_phy,
|
||||
.get_short_addr = lowpan_get_short_addr,
|
||||
.get_dsn = lowpan_get_dsn,
|
||||
};
|
||||
|
||||
static void lowpan_setup(struct net_device *dev)
|
||||
{
|
||||
dev->addr_len = IEEE802154_ADDR_LEN;
|
||||
memset(dev->broadcast, 0xff, IEEE802154_ADDR_LEN);
|
||||
dev->type = ARPHRD_IEEE802154;
|
||||
/* Frame Control + Sequence Number + Address fields + Security Header */
|
||||
dev->hard_header_len = 2 + 1 + 20 + 14;
|
||||
dev->needed_tailroom = 2; /* FCS */
|
||||
dev->mtu = IPV6_MIN_MTU;
|
||||
dev->tx_queue_len = 0;
|
||||
dev->flags = IFF_BROADCAST | IFF_MULTICAST;
|
||||
dev->watchdog_timeo = 0;
|
||||
|
||||
dev->netdev_ops = &lowpan_netdev_ops;
|
||||
dev->header_ops = &lowpan_header_ops;
|
||||
dev->ml_priv = &lowpan_mlme;
|
||||
dev->destructor = free_netdev;
|
||||
}
|
||||
|
||||
static int lowpan_validate(struct nlattr *tb[], struct nlattr *data[])
|
||||
{
|
||||
if (tb[IFLA_ADDRESS]) {
|
||||
if (nla_len(tb[IFLA_ADDRESS]) != IEEE802154_ADDR_LEN)
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev)
|
||||
{
|
||||
struct ieee802154_hdr hdr;
|
||||
int ret;
|
||||
|
||||
skb = skb_share_check(skb, GFP_ATOMIC);
|
||||
if (!skb)
|
||||
goto drop;
|
||||
|
||||
if (!netif_running(dev))
|
||||
goto drop_skb;
|
||||
|
||||
if (dev->type != ARPHRD_IEEE802154)
|
||||
goto drop_skb;
|
||||
|
||||
if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)
|
||||
goto drop_skb;
|
||||
|
||||
/* check that it's our buffer */
|
||||
if (skb->data[0] == LOWPAN_DISPATCH_IPV6) {
|
||||
skb->protocol = htons(ETH_P_IPV6);
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
|
||||
/* Pull off the 1-byte of 6lowpan header. */
|
||||
skb_pull(skb, 1);
|
||||
|
||||
ret = lowpan_give_skb_to_devices(skb, NULL);
|
||||
if (ret == NET_RX_DROP)
|
||||
goto drop;
|
||||
} else {
|
||||
switch (skb->data[0] & 0xe0) {
|
||||
case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */
|
||||
ret = process_data(skb, &hdr);
|
||||
if (ret == NET_RX_DROP)
|
||||
goto drop;
|
||||
break;
|
||||
case LOWPAN_DISPATCH_FRAG1: /* first fragment header */
|
||||
ret = lowpan_frag_rcv(skb, LOWPAN_DISPATCH_FRAG1);
|
||||
if (ret == 1) {
|
||||
ret = process_data(skb, &hdr);
|
||||
if (ret == NET_RX_DROP)
|
||||
goto drop;
|
||||
}
|
||||
break;
|
||||
case LOWPAN_DISPATCH_FRAGN: /* next fragments headers */
|
||||
ret = lowpan_frag_rcv(skb, LOWPAN_DISPATCH_FRAGN);
|
||||
if (ret == 1) {
|
||||
ret = process_data(skb, &hdr);
|
||||
if (ret == NET_RX_DROP)
|
||||
goto drop;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NET_RX_SUCCESS;
|
||||
drop_skb:
|
||||
kfree_skb(skb);
|
||||
drop:
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
static int lowpan_newlink(struct net *src_net, struct net_device *dev,
|
||||
struct nlattr *tb[], struct nlattr *data[])
|
||||
{
|
||||
struct net_device *real_dev;
|
||||
struct lowpan_dev_record *entry;
|
||||
|
||||
pr_debug("adding new link\n");
|
||||
|
||||
if (!tb[IFLA_LINK])
|
||||
return -EINVAL;
|
||||
/* find and hold real wpan device */
|
||||
real_dev = dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
|
||||
if (!real_dev)
|
||||
return -ENODEV;
|
||||
if (real_dev->type != ARPHRD_IEEE802154) {
|
||||
dev_put(real_dev);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
lowpan_dev_info(dev)->real_dev = real_dev;
|
||||
mutex_init(&lowpan_dev_info(dev)->dev_list_mtx);
|
||||
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
if (!entry) {
|
||||
dev_put(real_dev);
|
||||
lowpan_dev_info(dev)->real_dev = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
entry->ldev = dev;
|
||||
|
||||
/* Set the lowpan harware address to the wpan hardware address. */
|
||||
memcpy(dev->dev_addr, real_dev->dev_addr, IEEE802154_ADDR_LEN);
|
||||
|
||||
mutex_lock(&lowpan_dev_info(dev)->dev_list_mtx);
|
||||
INIT_LIST_HEAD(&entry->list);
|
||||
list_add_tail(&entry->list, &lowpan_devices);
|
||||
mutex_unlock(&lowpan_dev_info(dev)->dev_list_mtx);
|
||||
|
||||
register_netdevice(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lowpan_dellink(struct net_device *dev, struct list_head *head)
|
||||
{
|
||||
struct lowpan_dev_info *lowpan_dev = lowpan_dev_info(dev);
|
||||
struct net_device *real_dev = lowpan_dev->real_dev;
|
||||
struct lowpan_dev_record *entry, *tmp;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
mutex_lock(&lowpan_dev_info(dev)->dev_list_mtx);
|
||||
list_for_each_entry_safe(entry, tmp, &lowpan_devices, list) {
|
||||
if (entry->ldev == dev) {
|
||||
list_del(&entry->list);
|
||||
kfree(entry);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&lowpan_dev_info(dev)->dev_list_mtx);
|
||||
|
||||
mutex_destroy(&lowpan_dev_info(dev)->dev_list_mtx);
|
||||
|
||||
unregister_netdevice_queue(dev, head);
|
||||
|
||||
dev_put(real_dev);
|
||||
}
|
||||
|
||||
static struct rtnl_link_ops lowpan_link_ops __read_mostly = {
|
||||
.kind = "lowpan",
|
||||
.priv_size = sizeof(struct lowpan_dev_info),
|
||||
.setup = lowpan_setup,
|
||||
.newlink = lowpan_newlink,
|
||||
.dellink = lowpan_dellink,
|
||||
.validate = lowpan_validate,
|
||||
};
|
||||
|
||||
static inline int __init lowpan_netlink_init(void)
|
||||
{
|
||||
return rtnl_link_register(&lowpan_link_ops);
|
||||
}
|
||||
|
||||
static inline void lowpan_netlink_fini(void)
|
||||
{
|
||||
rtnl_link_unregister(&lowpan_link_ops);
|
||||
}
|
||||
|
||||
static int lowpan_device_event(struct notifier_block *unused,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
||||
LIST_HEAD(del_list);
|
||||
struct lowpan_dev_record *entry, *tmp;
|
||||
|
||||
if (dev->type != ARPHRD_IEEE802154)
|
||||
goto out;
|
||||
|
||||
if (event == NETDEV_UNREGISTER) {
|
||||
list_for_each_entry_safe(entry, tmp, &lowpan_devices, list) {
|
||||
if (lowpan_dev_info(entry->ldev)->real_dev == dev)
|
||||
lowpan_dellink(entry->ldev, &del_list);
|
||||
}
|
||||
|
||||
unregister_netdevice_many(&del_list);
|
||||
}
|
||||
|
||||
out:
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block lowpan_dev_notifier = {
|
||||
.notifier_call = lowpan_device_event,
|
||||
};
|
||||
|
||||
static struct packet_type lowpan_packet_type = {
|
||||
.type = htons(ETH_P_IEEE802154),
|
||||
.func = lowpan_rcv,
|
||||
};
|
||||
|
||||
static int __init lowpan_init_module(void)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
err = lowpan_net_frag_init();
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
err = lowpan_netlink_init();
|
||||
if (err < 0)
|
||||
goto out_frag;
|
||||
|
||||
dev_add_pack(&lowpan_packet_type);
|
||||
|
||||
err = register_netdevice_notifier(&lowpan_dev_notifier);
|
||||
if (err < 0)
|
||||
goto out_pack;
|
||||
|
||||
return 0;
|
||||
|
||||
out_pack:
|
||||
dev_remove_pack(&lowpan_packet_type);
|
||||
lowpan_netlink_fini();
|
||||
out_frag:
|
||||
lowpan_net_frag_exit();
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit lowpan_cleanup_module(void)
|
||||
{
|
||||
lowpan_netlink_fini();
|
||||
|
||||
dev_remove_pack(&lowpan_packet_type);
|
||||
|
||||
lowpan_net_frag_exit();
|
||||
|
||||
unregister_netdevice_notifier(&lowpan_dev_notifier);
|
||||
}
|
||||
|
||||
module_init(lowpan_init_module);
|
||||
module_exit(lowpan_cleanup_module);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_RTNL_LINK("lowpan");
|
17
net/ieee802154/Kconfig
Normal file
17
net/ieee802154/Kconfig
Normal file
|
@ -0,0 +1,17 @@
|
|||
config IEEE802154
|
||||
tristate "IEEE Std 802.15.4 Low-Rate Wireless Personal Area Networks support"
|
||||
---help---
|
||||
IEEE Std 802.15.4 defines a low data rate, low power and low
|
||||
complexity short range wireless personal area networks. It was
|
||||
designed to organise networks of sensors, switches, etc automation
|
||||
devices. Maximum allowed data rate is 250 kb/s and typical personal
|
||||
operating space around 10m.
|
||||
|
||||
Say Y here to compile LR-WPAN support into the kernel or say M to
|
||||
compile it as modules.
|
||||
|
||||
config IEEE802154_6LOWPAN
|
||||
tristate "6lowpan support over IEEE 802.15.4"
|
||||
depends on IEEE802154 && 6LOWPAN
|
||||
---help---
|
||||
IPv6 compression over IEEE 802.15.4.
|
9
net/ieee802154/Makefile
Normal file
9
net/ieee802154/Makefile
Normal file
|
@ -0,0 +1,9 @@
|
|||
obj-$(CONFIG_IEEE802154) += ieee802154.o af_802154.o
|
||||
obj-$(CONFIG_IEEE802154_6LOWPAN) += ieee802154_6lowpan.o
|
||||
|
||||
ieee802154_6lowpan-y := 6lowpan_rtnl.o reassembly.o
|
||||
ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o wpan-class.o \
|
||||
header_ops.o
|
||||
af_802154-y := af_ieee802154.o raw.o dgram.o
|
||||
|
||||
ccflags-y += -D__CHECK_ENDIAN__
|
37
net/ieee802154/af802154.h
Normal file
37
net/ieee802154/af802154.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Internal interfaces for ieee 802.15.4 address family.
|
||||
*
|
||||
* Copyright 2007, 2008, 2009 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Written by:
|
||||
* Sergey Lapin <slapin@ossfans.org>
|
||||
* Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef AF802154_H
|
||||
#define AF802154_H
|
||||
|
||||
struct sk_buff;
|
||||
struct net_device;
|
||||
struct ieee802154_addr;
|
||||
extern struct proto ieee802154_raw_prot;
|
||||
extern struct proto ieee802154_dgram_prot;
|
||||
void ieee802154_raw_deliver(struct net_device *dev, struct sk_buff *skb);
|
||||
int ieee802154_dgram_deliver(struct net_device *dev, struct sk_buff *skb);
|
||||
struct net_device *ieee802154_get_dev(struct net *net,
|
||||
const struct ieee802154_addr *addr);
|
||||
|
||||
#endif
|
373
net/ieee802154/af_ieee802154.c
Normal file
373
net/ieee802154/af_ieee802154.c
Normal file
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* IEEE802154.4 socket interface
|
||||
*
|
||||
* Copyright 2007, 2008 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Written by:
|
||||
* Sergey Lapin <slapin@ossfans.org>
|
||||
* Maxim Gorbachyov <maxim.gorbachev@siemens.com>
|
||||
*/
|
||||
|
||||
#include <linux/net.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/termios.h> /* For TIOCOUTQ/INQ */
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/datalink.h>
|
||||
#include <net/psnap.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/tcp_states.h>
|
||||
#include <net/route.h>
|
||||
|
||||
#include <net/af_ieee802154.h>
|
||||
#include <net/ieee802154_netdev.h>
|
||||
|
||||
#include "af802154.h"
|
||||
|
||||
/* Utility function for families */
|
||||
struct net_device*
|
||||
ieee802154_get_dev(struct net *net, const struct ieee802154_addr *addr)
|
||||
{
|
||||
struct net_device *dev = NULL;
|
||||
struct net_device *tmp;
|
||||
__le16 pan_id, short_addr;
|
||||
u8 hwaddr[IEEE802154_ADDR_LEN];
|
||||
|
||||
switch (addr->mode) {
|
||||
case IEEE802154_ADDR_LONG:
|
||||
ieee802154_devaddr_to_raw(hwaddr, addr->extended_addr);
|
||||
rcu_read_lock();
|
||||
dev = dev_getbyhwaddr_rcu(net, ARPHRD_IEEE802154, hwaddr);
|
||||
if (dev)
|
||||
dev_hold(dev);
|
||||
rcu_read_unlock();
|
||||
break;
|
||||
case IEEE802154_ADDR_SHORT:
|
||||
if (addr->pan_id == cpu_to_le16(IEEE802154_PANID_BROADCAST) ||
|
||||
addr->short_addr == cpu_to_le16(IEEE802154_ADDR_UNDEF) ||
|
||||
addr->short_addr == cpu_to_le16(IEEE802154_ADDR_BROADCAST))
|
||||
break;
|
||||
|
||||
rtnl_lock();
|
||||
|
||||
for_each_netdev(net, tmp) {
|
||||
if (tmp->type != ARPHRD_IEEE802154)
|
||||
continue;
|
||||
|
||||
pan_id = ieee802154_mlme_ops(tmp)->get_pan_id(tmp);
|
||||
short_addr =
|
||||
ieee802154_mlme_ops(tmp)->get_short_addr(tmp);
|
||||
|
||||
if (pan_id == addr->pan_id &&
|
||||
short_addr == addr->short_addr) {
|
||||
dev = tmp;
|
||||
dev_hold(dev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rtnl_unlock();
|
||||
break;
|
||||
default:
|
||||
pr_warn("Unsupported ieee802154 address type: %d\n",
|
||||
addr->mode);
|
||||
break;
|
||||
}
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static int ieee802154_sock_release(struct socket *sock)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
if (sk) {
|
||||
sock->sk = NULL;
|
||||
sk->sk_prot->close(sk, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int ieee802154_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
|
||||
struct msghdr *msg, size_t len)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
return sk->sk_prot->sendmsg(iocb, sk, msg, len);
|
||||
}
|
||||
|
||||
static int ieee802154_sock_bind(struct socket *sock, struct sockaddr *uaddr,
|
||||
int addr_len)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
if (sk->sk_prot->bind)
|
||||
return sk->sk_prot->bind(sk, uaddr, addr_len);
|
||||
|
||||
return sock_no_bind(sock, uaddr, addr_len);
|
||||
}
|
||||
|
||||
static int ieee802154_sock_connect(struct socket *sock, struct sockaddr *uaddr,
|
||||
int addr_len, int flags)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
if (addr_len < sizeof(uaddr->sa_family))
|
||||
return -EINVAL;
|
||||
|
||||
if (uaddr->sa_family == AF_UNSPEC)
|
||||
return sk->sk_prot->disconnect(sk, flags);
|
||||
|
||||
return sk->sk_prot->connect(sk, uaddr, addr_len);
|
||||
}
|
||||
|
||||
static int ieee802154_dev_ioctl(struct sock *sk, struct ifreq __user *arg,
|
||||
unsigned int cmd)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
int ret = -ENOIOCTLCMD;
|
||||
struct net_device *dev;
|
||||
|
||||
if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
|
||||
return -EFAULT;
|
||||
|
||||
ifr.ifr_name[IFNAMSIZ-1] = 0;
|
||||
|
||||
dev_load(sock_net(sk), ifr.ifr_name);
|
||||
dev = dev_get_by_name(sock_net(sk), ifr.ifr_name);
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
if (dev->type == ARPHRD_IEEE802154 && dev->netdev_ops->ndo_do_ioctl)
|
||||
ret = dev->netdev_ops->ndo_do_ioctl(dev, &ifr, cmd);
|
||||
|
||||
if (!ret && copy_to_user(arg, &ifr, sizeof(struct ifreq)))
|
||||
ret = -EFAULT;
|
||||
dev_put(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ieee802154_sock_ioctl(struct socket *sock, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct sock *sk = sock->sk;
|
||||
|
||||
switch (cmd) {
|
||||
case SIOCGSTAMP:
|
||||
return sock_get_timestamp(sk, (struct timeval __user *)arg);
|
||||
case SIOCGSTAMPNS:
|
||||
return sock_get_timestampns(sk, (struct timespec __user *)arg);
|
||||
case SIOCGIFADDR:
|
||||
case SIOCSIFADDR:
|
||||
return ieee802154_dev_ioctl(sk, (struct ifreq __user *)arg,
|
||||
cmd);
|
||||
default:
|
||||
if (!sk->sk_prot->ioctl)
|
||||
return -ENOIOCTLCMD;
|
||||
return sk->sk_prot->ioctl(sk, cmd, arg);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct proto_ops ieee802154_raw_ops = {
|
||||
.family = PF_IEEE802154,
|
||||
.owner = THIS_MODULE,
|
||||
.release = ieee802154_sock_release,
|
||||
.bind = ieee802154_sock_bind,
|
||||
.connect = ieee802154_sock_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = sock_no_getname,
|
||||
.poll = datagram_poll,
|
||||
.ioctl = ieee802154_sock_ioctl,
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = sock_no_shutdown,
|
||||
.setsockopt = sock_common_setsockopt,
|
||||
.getsockopt = sock_common_getsockopt,
|
||||
.sendmsg = ieee802154_sock_sendmsg,
|
||||
.recvmsg = sock_common_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
.sendpage = sock_no_sendpage,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_setsockopt = compat_sock_common_setsockopt,
|
||||
.compat_getsockopt = compat_sock_common_getsockopt,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const struct proto_ops ieee802154_dgram_ops = {
|
||||
.family = PF_IEEE802154,
|
||||
.owner = THIS_MODULE,
|
||||
.release = ieee802154_sock_release,
|
||||
.bind = ieee802154_sock_bind,
|
||||
.connect = ieee802154_sock_connect,
|
||||
.socketpair = sock_no_socketpair,
|
||||
.accept = sock_no_accept,
|
||||
.getname = sock_no_getname,
|
||||
.poll = datagram_poll,
|
||||
.ioctl = ieee802154_sock_ioctl,
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = sock_no_shutdown,
|
||||
.setsockopt = sock_common_setsockopt,
|
||||
.getsockopt = sock_common_getsockopt,
|
||||
.sendmsg = ieee802154_sock_sendmsg,
|
||||
.recvmsg = sock_common_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
.sendpage = sock_no_sendpage,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_setsockopt = compat_sock_common_setsockopt,
|
||||
.compat_getsockopt = compat_sock_common_getsockopt,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
/* Create a socket. Initialise the socket, blank the addresses
|
||||
* set the state.
|
||||
*/
|
||||
static int ieee802154_create(struct net *net, struct socket *sock,
|
||||
int protocol, int kern)
|
||||
{
|
||||
struct sock *sk;
|
||||
int rc;
|
||||
struct proto *proto;
|
||||
const struct proto_ops *ops;
|
||||
|
||||
if (!net_eq(net, &init_net))
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
switch (sock->type) {
|
||||
case SOCK_RAW:
|
||||
proto = &ieee802154_raw_prot;
|
||||
ops = &ieee802154_raw_ops;
|
||||
break;
|
||||
case SOCK_DGRAM:
|
||||
proto = &ieee802154_dgram_prot;
|
||||
ops = &ieee802154_dgram_ops;
|
||||
break;
|
||||
default:
|
||||
rc = -ESOCKTNOSUPPORT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = -ENOMEM;
|
||||
sk = sk_alloc(net, PF_IEEE802154, GFP_KERNEL, proto);
|
||||
if (!sk)
|
||||
goto out;
|
||||
rc = 0;
|
||||
|
||||
sock->ops = ops;
|
||||
|
||||
sock_init_data(sock, sk);
|
||||
/* FIXME: sk->sk_destruct */
|
||||
sk->sk_family = PF_IEEE802154;
|
||||
|
||||
/* Checksums on by default */
|
||||
sock_set_flag(sk, SOCK_ZAPPED);
|
||||
|
||||
if (sk->sk_prot->hash)
|
||||
sk->sk_prot->hash(sk);
|
||||
|
||||
if (sk->sk_prot->init) {
|
||||
rc = sk->sk_prot->init(sk);
|
||||
if (rc)
|
||||
sk_common_release(sk);
|
||||
}
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct net_proto_family ieee802154_family_ops = {
|
||||
.family = PF_IEEE802154,
|
||||
.create = ieee802154_create,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int ieee802154_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev)
|
||||
{
|
||||
if (!netif_running(dev))
|
||||
goto drop;
|
||||
pr_debug("got frame, type %d, dev %p\n", dev->type, dev);
|
||||
#ifdef DEBUG
|
||||
print_hex_dump_bytes("ieee802154_rcv ",
|
||||
DUMP_PREFIX_NONE, skb->data, skb->len);
|
||||
#endif
|
||||
|
||||
if (!net_eq(dev_net(dev), &init_net))
|
||||
goto drop;
|
||||
|
||||
ieee802154_raw_deliver(dev, skb);
|
||||
|
||||
if (dev->type != ARPHRD_IEEE802154)
|
||||
goto drop;
|
||||
|
||||
if (skb->pkt_type != PACKET_OTHERHOST)
|
||||
return ieee802154_dgram_deliver(dev, skb);
|
||||
|
||||
drop:
|
||||
kfree_skb(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
|
||||
static struct packet_type ieee802154_packet_type = {
|
||||
.type = htons(ETH_P_IEEE802154),
|
||||
.func = ieee802154_rcv,
|
||||
};
|
||||
|
||||
static int __init af_ieee802154_init(void)
|
||||
{
|
||||
int rc = -EINVAL;
|
||||
|
||||
rc = proto_register(&ieee802154_raw_prot, 1);
|
||||
if (rc)
|
||||
goto out;
|
||||
|
||||
rc = proto_register(&ieee802154_dgram_prot, 1);
|
||||
if (rc)
|
||||
goto err_dgram;
|
||||
|
||||
/* Tell SOCKET that we are alive */
|
||||
rc = sock_register(&ieee802154_family_ops);
|
||||
if (rc)
|
||||
goto err_sock;
|
||||
dev_add_pack(&ieee802154_packet_type);
|
||||
|
||||
rc = 0;
|
||||
goto out;
|
||||
|
||||
err_sock:
|
||||
proto_unregister(&ieee802154_dgram_prot);
|
||||
err_dgram:
|
||||
proto_unregister(&ieee802154_raw_prot);
|
||||
out:
|
||||
return rc;
|
||||
}
|
||||
static void __exit af_ieee802154_remove(void)
|
||||
{
|
||||
dev_remove_pack(&ieee802154_packet_type);
|
||||
sock_unregister(PF_IEEE802154);
|
||||
proto_unregister(&ieee802154_dgram_prot);
|
||||
proto_unregister(&ieee802154_raw_prot);
|
||||
}
|
||||
|
||||
module_init(af_ieee802154_init);
|
||||
module_exit(af_ieee802154_remove);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_NETPROTO(PF_IEEE802154);
|
554
net/ieee802154/dgram.c
Normal file
554
net/ieee802154/dgram.c
Normal file
|
@ -0,0 +1,554 @@
|
|||
/*
|
||||
* IEEE 802.15.4 dgram socket interface
|
||||
*
|
||||
* Copyright 2007, 2008 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Written by:
|
||||
* Sergey Lapin <slapin@ossfans.org>
|
||||
* Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/af_ieee802154.h>
|
||||
#include <net/ieee802154.h>
|
||||
#include <net/ieee802154_netdev.h>
|
||||
|
||||
#include <asm/ioctls.h>
|
||||
|
||||
#include "af802154.h"
|
||||
|
||||
static HLIST_HEAD(dgram_head);
|
||||
static DEFINE_RWLOCK(dgram_lock);
|
||||
|
||||
struct dgram_sock {
|
||||
struct sock sk;
|
||||
|
||||
struct ieee802154_addr src_addr;
|
||||
struct ieee802154_addr dst_addr;
|
||||
|
||||
unsigned int bound:1;
|
||||
unsigned int connected:1;
|
||||
unsigned int want_ack:1;
|
||||
unsigned int secen:1;
|
||||
unsigned int secen_override:1;
|
||||
unsigned int seclevel:3;
|
||||
unsigned int seclevel_override:1;
|
||||
};
|
||||
|
||||
static inline struct dgram_sock *dgram_sk(const struct sock *sk)
|
||||
{
|
||||
return container_of(sk, struct dgram_sock, sk);
|
||||
}
|
||||
|
||||
static void dgram_hash(struct sock *sk)
|
||||
{
|
||||
write_lock_bh(&dgram_lock);
|
||||
sk_add_node(sk, &dgram_head);
|
||||
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
|
||||
write_unlock_bh(&dgram_lock);
|
||||
}
|
||||
|
||||
static void dgram_unhash(struct sock *sk)
|
||||
{
|
||||
write_lock_bh(&dgram_lock);
|
||||
if (sk_del_node_init(sk))
|
||||
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
|
||||
write_unlock_bh(&dgram_lock);
|
||||
}
|
||||
|
||||
static int dgram_init(struct sock *sk)
|
||||
{
|
||||
struct dgram_sock *ro = dgram_sk(sk);
|
||||
|
||||
ro->want_ack = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dgram_close(struct sock *sk, long timeout)
|
||||
{
|
||||
sk_common_release(sk);
|
||||
}
|
||||
|
||||
static int dgram_bind(struct sock *sk, struct sockaddr *uaddr, int len)
|
||||
{
|
||||
struct sockaddr_ieee802154 *addr = (struct sockaddr_ieee802154 *)uaddr;
|
||||
struct ieee802154_addr haddr;
|
||||
struct dgram_sock *ro = dgram_sk(sk);
|
||||
int err = -EINVAL;
|
||||
struct net_device *dev;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
ro->bound = 0;
|
||||
|
||||
if (len < sizeof(*addr))
|
||||
goto out;
|
||||
|
||||
if (addr->family != AF_IEEE802154)
|
||||
goto out;
|
||||
|
||||
ieee802154_addr_from_sa(&haddr, &addr->addr);
|
||||
dev = ieee802154_get_dev(sock_net(sk), &haddr);
|
||||
if (!dev) {
|
||||
err = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dev->type != ARPHRD_IEEE802154) {
|
||||
err = -ENODEV;
|
||||
goto out_put;
|
||||
}
|
||||
|
||||
ro->src_addr = haddr;
|
||||
|
||||
ro->bound = 1;
|
||||
err = 0;
|
||||
out_put:
|
||||
dev_put(dev);
|
||||
out:
|
||||
release_sock(sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int dgram_ioctl(struct sock *sk, int cmd, unsigned long arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case SIOCOUTQ:
|
||||
{
|
||||
int amount = sk_wmem_alloc_get(sk);
|
||||
|
||||
return put_user(amount, (int __user *)arg);
|
||||
}
|
||||
|
||||
case SIOCINQ:
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
unsigned long amount;
|
||||
|
||||
amount = 0;
|
||||
spin_lock_bh(&sk->sk_receive_queue.lock);
|
||||
skb = skb_peek(&sk->sk_receive_queue);
|
||||
if (skb != NULL) {
|
||||
/* We will only return the amount
|
||||
* of this packet since that is all
|
||||
* that will be read.
|
||||
*/
|
||||
amount = skb->len - ieee802154_hdr_length(skb);
|
||||
}
|
||||
spin_unlock_bh(&sk->sk_receive_queue.lock);
|
||||
return put_user(amount, (int __user *)arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
/* FIXME: autobind */
|
||||
static int dgram_connect(struct sock *sk, struct sockaddr *uaddr,
|
||||
int len)
|
||||
{
|
||||
struct sockaddr_ieee802154 *addr = (struct sockaddr_ieee802154 *)uaddr;
|
||||
struct dgram_sock *ro = dgram_sk(sk);
|
||||
int err = 0;
|
||||
|
||||
if (len < sizeof(*addr))
|
||||
return -EINVAL;
|
||||
|
||||
if (addr->family != AF_IEEE802154)
|
||||
return -EINVAL;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
if (!ro->bound) {
|
||||
err = -ENETUNREACH;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ieee802154_addr_from_sa(&ro->dst_addr, &addr->addr);
|
||||
ro->connected = 1;
|
||||
|
||||
out:
|
||||
release_sock(sk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int dgram_disconnect(struct sock *sk, int flags)
|
||||
{
|
||||
struct dgram_sock *ro = dgram_sk(sk);
|
||||
|
||||
lock_sock(sk);
|
||||
ro->connected = 0;
|
||||
release_sock(sk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dgram_sendmsg(struct kiocb *iocb, struct sock *sk,
|
||||
struct msghdr *msg, size_t size)
|
||||
{
|
||||
struct net_device *dev;
|
||||
unsigned int mtu;
|
||||
struct sk_buff *skb;
|
||||
struct ieee802154_mac_cb *cb;
|
||||
struct dgram_sock *ro = dgram_sk(sk);
|
||||
struct ieee802154_addr dst_addr;
|
||||
int hlen, tlen;
|
||||
int err;
|
||||
|
||||
if (msg->msg_flags & MSG_OOB) {
|
||||
pr_debug("msg->msg_flags = 0x%x\n", msg->msg_flags);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (!ro->connected && !msg->msg_name)
|
||||
return -EDESTADDRREQ;
|
||||
else if (ro->connected && msg->msg_name)
|
||||
return -EISCONN;
|
||||
|
||||
if (!ro->bound)
|
||||
dev = dev_getfirstbyhwtype(sock_net(sk), ARPHRD_IEEE802154);
|
||||
else
|
||||
dev = ieee802154_get_dev(sock_net(sk), &ro->src_addr);
|
||||
|
||||
if (!dev) {
|
||||
pr_debug("no dev\n");
|
||||
err = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
mtu = dev->mtu;
|
||||
pr_debug("name = %s, mtu = %u\n", dev->name, mtu);
|
||||
|
||||
if (size > mtu) {
|
||||
pr_debug("size = %Zu, mtu = %u\n", size, mtu);
|
||||
err = -EMSGSIZE;
|
||||
goto out_dev;
|
||||
}
|
||||
|
||||
hlen = LL_RESERVED_SPACE(dev);
|
||||
tlen = dev->needed_tailroom;
|
||||
skb = sock_alloc_send_skb(sk, hlen + tlen + size,
|
||||
msg->msg_flags & MSG_DONTWAIT,
|
||||
&err);
|
||||
if (!skb)
|
||||
goto out_dev;
|
||||
|
||||
skb_reserve(skb, hlen);
|
||||
|
||||
skb_reset_network_header(skb);
|
||||
|
||||
cb = mac_cb_init(skb);
|
||||
cb->type = IEEE802154_FC_TYPE_DATA;
|
||||
cb->ackreq = ro->want_ack;
|
||||
|
||||
if (msg->msg_name) {
|
||||
DECLARE_SOCKADDR(struct sockaddr_ieee802154*,
|
||||
daddr, msg->msg_name);
|
||||
|
||||
ieee802154_addr_from_sa(&dst_addr, &daddr->addr);
|
||||
} else {
|
||||
dst_addr = ro->dst_addr;
|
||||
}
|
||||
|
||||
cb->secen = ro->secen;
|
||||
cb->secen_override = ro->secen_override;
|
||||
cb->seclevel = ro->seclevel;
|
||||
cb->seclevel_override = ro->seclevel_override;
|
||||
|
||||
err = dev_hard_header(skb, dev, ETH_P_IEEE802154, &dst_addr,
|
||||
ro->bound ? &ro->src_addr : NULL, size);
|
||||
if (err < 0)
|
||||
goto out_skb;
|
||||
|
||||
err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
|
||||
if (err < 0)
|
||||
goto out_skb;
|
||||
|
||||
skb->dev = dev;
|
||||
skb->sk = sk;
|
||||
skb->protocol = htons(ETH_P_IEEE802154);
|
||||
|
||||
dev_put(dev);
|
||||
|
||||
err = dev_queue_xmit(skb);
|
||||
if (err > 0)
|
||||
err = net_xmit_errno(err);
|
||||
|
||||
return err ?: size;
|
||||
|
||||
out_skb:
|
||||
kfree_skb(skb);
|
||||
out_dev:
|
||||
dev_put(dev);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int dgram_recvmsg(struct kiocb *iocb, struct sock *sk,
|
||||
struct msghdr *msg, size_t len, int noblock,
|
||||
int flags, int *addr_len)
|
||||
{
|
||||
size_t copied = 0;
|
||||
int err = -EOPNOTSUPP;
|
||||
struct sk_buff *skb;
|
||||
DECLARE_SOCKADDR(struct sockaddr_ieee802154 *, saddr, msg->msg_name);
|
||||
|
||||
skb = skb_recv_datagram(sk, flags, noblock, &err);
|
||||
if (!skb)
|
||||
goto out;
|
||||
|
||||
copied = skb->len;
|
||||
if (len < copied) {
|
||||
msg->msg_flags |= MSG_TRUNC;
|
||||
copied = len;
|
||||
}
|
||||
|
||||
/* FIXME: skip headers if necessary ?! */
|
||||
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
sock_recv_ts_and_drops(msg, sk, skb);
|
||||
|
||||
if (saddr) {
|
||||
saddr->family = AF_IEEE802154;
|
||||
ieee802154_addr_to_sa(&saddr->addr, &mac_cb(skb)->source);
|
||||
*addr_len = sizeof(*saddr);
|
||||
}
|
||||
|
||||
if (flags & MSG_TRUNC)
|
||||
copied = skb->len;
|
||||
done:
|
||||
skb_free_datagram(sk, skb);
|
||||
out:
|
||||
if (err)
|
||||
return err;
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int dgram_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
skb = skb_share_check(skb, GFP_ATOMIC);
|
||||
if (!skb)
|
||||
return NET_RX_DROP;
|
||||
|
||||
if (sock_queue_rcv_skb(sk, skb) < 0) {
|
||||
kfree_skb(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
return NET_RX_SUCCESS;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ieee802154_match_sock(__le64 hw_addr, __le16 pan_id, __le16 short_addr,
|
||||
struct dgram_sock *ro)
|
||||
{
|
||||
if (!ro->bound)
|
||||
return true;
|
||||
|
||||
if (ro->src_addr.mode == IEEE802154_ADDR_LONG &&
|
||||
hw_addr == ro->src_addr.extended_addr)
|
||||
return true;
|
||||
|
||||
if (ro->src_addr.mode == IEEE802154_ADDR_SHORT &&
|
||||
pan_id == ro->src_addr.pan_id &&
|
||||
short_addr == ro->src_addr.short_addr)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int ieee802154_dgram_deliver(struct net_device *dev, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk, *prev = NULL;
|
||||
int ret = NET_RX_SUCCESS;
|
||||
__le16 pan_id, short_addr;
|
||||
__le64 hw_addr;
|
||||
|
||||
/* Data frame processing */
|
||||
BUG_ON(dev->type != ARPHRD_IEEE802154);
|
||||
|
||||
pan_id = ieee802154_mlme_ops(dev)->get_pan_id(dev);
|
||||
short_addr = ieee802154_mlme_ops(dev)->get_short_addr(dev);
|
||||
hw_addr = ieee802154_devaddr_from_raw(dev->dev_addr);
|
||||
|
||||
read_lock(&dgram_lock);
|
||||
sk_for_each(sk, &dgram_head) {
|
||||
if (ieee802154_match_sock(hw_addr, pan_id, short_addr,
|
||||
dgram_sk(sk))) {
|
||||
if (prev) {
|
||||
struct sk_buff *clone;
|
||||
|
||||
clone = skb_clone(skb, GFP_ATOMIC);
|
||||
if (clone)
|
||||
dgram_rcv_skb(prev, clone);
|
||||
}
|
||||
|
||||
prev = sk;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
dgram_rcv_skb(prev, skb);
|
||||
} else {
|
||||
kfree_skb(skb);
|
||||
ret = NET_RX_DROP;
|
||||
}
|
||||
read_unlock(&dgram_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dgram_getsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
struct dgram_sock *ro = dgram_sk(sk);
|
||||
|
||||
int val, len;
|
||||
|
||||
if (level != SOL_IEEE802154)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (get_user(len, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
len = min_t(unsigned int, len, sizeof(int));
|
||||
|
||||
switch (optname) {
|
||||
case WPAN_WANTACK:
|
||||
val = ro->want_ack;
|
||||
break;
|
||||
case WPAN_SECURITY:
|
||||
if (!ro->secen_override)
|
||||
val = WPAN_SECURITY_DEFAULT;
|
||||
else if (ro->secen)
|
||||
val = WPAN_SECURITY_ON;
|
||||
else
|
||||
val = WPAN_SECURITY_OFF;
|
||||
break;
|
||||
case WPAN_SECURITY_LEVEL:
|
||||
if (!ro->seclevel_override)
|
||||
val = WPAN_SECURITY_LEVEL_DEFAULT;
|
||||
else
|
||||
val = ro->seclevel;
|
||||
break;
|
||||
default:
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
|
||||
if (put_user(len, optlen))
|
||||
return -EFAULT;
|
||||
if (copy_to_user(optval, &val, len))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dgram_setsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, unsigned int optlen)
|
||||
{
|
||||
struct dgram_sock *ro = dgram_sk(sk);
|
||||
struct net *net = sock_net(sk);
|
||||
int val;
|
||||
int err = 0;
|
||||
|
||||
if (optlen < sizeof(int))
|
||||
return -EINVAL;
|
||||
|
||||
if (get_user(val, (int __user *)optval))
|
||||
return -EFAULT;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
switch (optname) {
|
||||
case WPAN_WANTACK:
|
||||
ro->want_ack = !!val;
|
||||
break;
|
||||
case WPAN_SECURITY:
|
||||
if (!ns_capable(net->user_ns, CAP_NET_ADMIN) &&
|
||||
!ns_capable(net->user_ns, CAP_NET_RAW)) {
|
||||
err = -EPERM;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (val) {
|
||||
case WPAN_SECURITY_DEFAULT:
|
||||
ro->secen_override = 0;
|
||||
break;
|
||||
case WPAN_SECURITY_ON:
|
||||
ro->secen_override = 1;
|
||||
ro->secen = 1;
|
||||
break;
|
||||
case WPAN_SECURITY_OFF:
|
||||
ro->secen_override = 1;
|
||||
ro->secen = 0;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WPAN_SECURITY_LEVEL:
|
||||
if (!ns_capable(net->user_ns, CAP_NET_ADMIN) &&
|
||||
!ns_capable(net->user_ns, CAP_NET_RAW)) {
|
||||
err = -EPERM;
|
||||
break;
|
||||
}
|
||||
|
||||
if (val < WPAN_SECURITY_LEVEL_DEFAULT ||
|
||||
val > IEEE802154_SCF_SECLEVEL_ENC_MIC128) {
|
||||
err = -EINVAL;
|
||||
} else if (val == WPAN_SECURITY_LEVEL_DEFAULT) {
|
||||
ro->seclevel_override = 0;
|
||||
} else {
|
||||
ro->seclevel_override = 1;
|
||||
ro->seclevel = val;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
err = -ENOPROTOOPT;
|
||||
break;
|
||||
}
|
||||
|
||||
release_sock(sk);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct proto ieee802154_dgram_prot = {
|
||||
.name = "IEEE-802.15.4-MAC",
|
||||
.owner = THIS_MODULE,
|
||||
.obj_size = sizeof(struct dgram_sock),
|
||||
.init = dgram_init,
|
||||
.close = dgram_close,
|
||||
.bind = dgram_bind,
|
||||
.sendmsg = dgram_sendmsg,
|
||||
.recvmsg = dgram_recvmsg,
|
||||
.hash = dgram_hash,
|
||||
.unhash = dgram_unhash,
|
||||
.connect = dgram_connect,
|
||||
.disconnect = dgram_disconnect,
|
||||
.ioctl = dgram_ioctl,
|
||||
.getsockopt = dgram_getsockopt,
|
||||
.setsockopt = dgram_setsockopt,
|
||||
};
|
||||
|
325
net/ieee802154/header_ops.c
Normal file
325
net/ieee802154/header_ops.c
Normal file
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Fraunhofer ITWM
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* Written by:
|
||||
* Phoebe Buckheister <phoebe.buckheister@itwm.fraunhofer.de>
|
||||
*/
|
||||
|
||||
#include <net/mac802154.h>
|
||||
#include <net/ieee802154.h>
|
||||
#include <net/ieee802154_netdev.h>
|
||||
|
||||
static int
|
||||
ieee802154_hdr_push_addr(u8 *buf, const struct ieee802154_addr *addr,
|
||||
bool omit_pan)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
if (addr->mode == IEEE802154_ADDR_NONE)
|
||||
return 0;
|
||||
|
||||
if (!omit_pan) {
|
||||
memcpy(buf + pos, &addr->pan_id, 2);
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
switch (addr->mode) {
|
||||
case IEEE802154_ADDR_SHORT:
|
||||
memcpy(buf + pos, &addr->short_addr, 2);
|
||||
pos += 2;
|
||||
break;
|
||||
|
||||
case IEEE802154_ADDR_LONG:
|
||||
memcpy(buf + pos, &addr->extended_addr, IEEE802154_ADDR_LEN);
|
||||
pos += IEEE802154_ADDR_LEN;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static int
|
||||
ieee802154_hdr_push_sechdr(u8 *buf, const struct ieee802154_sechdr *hdr)
|
||||
{
|
||||
int pos = 5;
|
||||
|
||||
memcpy(buf, hdr, 1);
|
||||
memcpy(buf + 1, &hdr->frame_counter, 4);
|
||||
|
||||
switch (hdr->key_id_mode) {
|
||||
case IEEE802154_SCF_KEY_IMPLICIT:
|
||||
return pos;
|
||||
|
||||
case IEEE802154_SCF_KEY_INDEX:
|
||||
break;
|
||||
|
||||
case IEEE802154_SCF_KEY_SHORT_INDEX:
|
||||
memcpy(buf + pos, &hdr->short_src, 4);
|
||||
pos += 4;
|
||||
break;
|
||||
|
||||
case IEEE802154_SCF_KEY_HW_INDEX:
|
||||
memcpy(buf + pos, &hdr->extended_src, IEEE802154_ADDR_LEN);
|
||||
pos += IEEE802154_ADDR_LEN;
|
||||
break;
|
||||
}
|
||||
|
||||
buf[pos++] = hdr->key_id;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int
|
||||
ieee802154_hdr_push(struct sk_buff *skb, const struct ieee802154_hdr *hdr)
|
||||
{
|
||||
u8 buf[MAC802154_FRAME_HARD_HEADER_LEN];
|
||||
int pos = 2;
|
||||
int rc;
|
||||
struct ieee802154_hdr_fc fc = hdr->fc;
|
||||
|
||||
buf[pos++] = hdr->seq;
|
||||
|
||||
fc.dest_addr_mode = hdr->dest.mode;
|
||||
|
||||
rc = ieee802154_hdr_push_addr(buf + pos, &hdr->dest, false);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
pos += rc;
|
||||
|
||||
fc.source_addr_mode = hdr->source.mode;
|
||||
|
||||
if (hdr->source.pan_id == hdr->dest.pan_id &&
|
||||
hdr->dest.mode != IEEE802154_ADDR_NONE)
|
||||
fc.intra_pan = true;
|
||||
|
||||
rc = ieee802154_hdr_push_addr(buf + pos, &hdr->source, fc.intra_pan);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
pos += rc;
|
||||
|
||||
if (fc.security_enabled) {
|
||||
fc.version = 1;
|
||||
|
||||
rc = ieee802154_hdr_push_sechdr(buf + pos, &hdr->sec);
|
||||
if (rc < 0)
|
||||
return -EINVAL;
|
||||
|
||||
pos += rc;
|
||||
}
|
||||
|
||||
memcpy(buf, &fc, 2);
|
||||
|
||||
memcpy(skb_push(skb, pos), buf, pos);
|
||||
|
||||
return pos;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee802154_hdr_push);
|
||||
|
||||
static int
|
||||
ieee802154_hdr_get_addr(const u8 *buf, int mode, bool omit_pan,
|
||||
struct ieee802154_addr *addr)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
addr->mode = mode;
|
||||
|
||||
if (mode == IEEE802154_ADDR_NONE)
|
||||
return 0;
|
||||
|
||||
if (!omit_pan) {
|
||||
memcpy(&addr->pan_id, buf + pos, 2);
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
if (mode == IEEE802154_ADDR_SHORT) {
|
||||
memcpy(&addr->short_addr, buf + pos, 2);
|
||||
return pos + 2;
|
||||
} else {
|
||||
memcpy(&addr->extended_addr, buf + pos, IEEE802154_ADDR_LEN);
|
||||
return pos + IEEE802154_ADDR_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
static int ieee802154_hdr_addr_len(int mode, bool omit_pan)
|
||||
{
|
||||
int pan_len = omit_pan ? 0 : 2;
|
||||
|
||||
switch (mode) {
|
||||
case IEEE802154_ADDR_NONE: return 0;
|
||||
case IEEE802154_ADDR_SHORT: return 2 + pan_len;
|
||||
case IEEE802154_ADDR_LONG: return IEEE802154_ADDR_LEN + pan_len;
|
||||
default: return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
ieee802154_hdr_get_sechdr(const u8 *buf, struct ieee802154_sechdr *hdr)
|
||||
{
|
||||
int pos = 5;
|
||||
|
||||
memcpy(hdr, buf, 1);
|
||||
memcpy(&hdr->frame_counter, buf + 1, 4);
|
||||
|
||||
switch (hdr->key_id_mode) {
|
||||
case IEEE802154_SCF_KEY_IMPLICIT:
|
||||
return pos;
|
||||
|
||||
case IEEE802154_SCF_KEY_INDEX:
|
||||
break;
|
||||
|
||||
case IEEE802154_SCF_KEY_SHORT_INDEX:
|
||||
memcpy(&hdr->short_src, buf + pos, 4);
|
||||
pos += 4;
|
||||
break;
|
||||
|
||||
case IEEE802154_SCF_KEY_HW_INDEX:
|
||||
memcpy(&hdr->extended_src, buf + pos, IEEE802154_ADDR_LEN);
|
||||
pos += IEEE802154_ADDR_LEN;
|
||||
break;
|
||||
}
|
||||
|
||||
hdr->key_id = buf[pos++];
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static int ieee802154_sechdr_lengths[4] = {
|
||||
[IEEE802154_SCF_KEY_IMPLICIT] = 5,
|
||||
[IEEE802154_SCF_KEY_INDEX] = 6,
|
||||
[IEEE802154_SCF_KEY_SHORT_INDEX] = 10,
|
||||
[IEEE802154_SCF_KEY_HW_INDEX] = 14,
|
||||
};
|
||||
|
||||
static int ieee802154_hdr_sechdr_len(u8 sc)
|
||||
{
|
||||
return ieee802154_sechdr_lengths[IEEE802154_SCF_KEY_ID_MODE(sc)];
|
||||
}
|
||||
|
||||
static int ieee802154_hdr_minlen(const struct ieee802154_hdr *hdr)
|
||||
{
|
||||
int dlen, slen;
|
||||
|
||||
dlen = ieee802154_hdr_addr_len(hdr->fc.dest_addr_mode, false);
|
||||
slen = ieee802154_hdr_addr_len(hdr->fc.source_addr_mode,
|
||||
hdr->fc.intra_pan);
|
||||
|
||||
if (slen < 0 || dlen < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return 3 + dlen + slen + hdr->fc.security_enabled;
|
||||
}
|
||||
|
||||
static int
|
||||
ieee802154_hdr_get_addrs(const u8 *buf, struct ieee802154_hdr *hdr)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
pos += ieee802154_hdr_get_addr(buf + pos, hdr->fc.dest_addr_mode,
|
||||
false, &hdr->dest);
|
||||
pos += ieee802154_hdr_get_addr(buf + pos, hdr->fc.source_addr_mode,
|
||||
hdr->fc.intra_pan, &hdr->source);
|
||||
|
||||
if (hdr->fc.intra_pan)
|
||||
hdr->source.pan_id = hdr->dest.pan_id;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int
|
||||
ieee802154_hdr_pull(struct sk_buff *skb, struct ieee802154_hdr *hdr)
|
||||
{
|
||||
int pos = 3, rc;
|
||||
|
||||
if (!pskb_may_pull(skb, 3))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(hdr, skb->data, 3);
|
||||
|
||||
rc = ieee802154_hdr_minlen(hdr);
|
||||
if (rc < 0 || !pskb_may_pull(skb, rc))
|
||||
return -EINVAL;
|
||||
|
||||
pos += ieee802154_hdr_get_addrs(skb->data + pos, hdr);
|
||||
|
||||
if (hdr->fc.security_enabled) {
|
||||
int want = pos + ieee802154_hdr_sechdr_len(skb->data[pos]);
|
||||
|
||||
if (!pskb_may_pull(skb, want))
|
||||
return -EINVAL;
|
||||
|
||||
pos += ieee802154_hdr_get_sechdr(skb->data + pos, &hdr->sec);
|
||||
}
|
||||
|
||||
skb_pull(skb, pos);
|
||||
return pos;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee802154_hdr_pull);
|
||||
|
||||
int
|
||||
ieee802154_hdr_peek_addrs(const struct sk_buff *skb, struct ieee802154_hdr *hdr)
|
||||
{
|
||||
const u8 *buf = skb_mac_header(skb);
|
||||
int pos = 3, rc;
|
||||
|
||||
if (buf + 3 > skb_tail_pointer(skb))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(hdr, buf, 3);
|
||||
|
||||
rc = ieee802154_hdr_minlen(hdr);
|
||||
if (rc < 0 || buf + rc > skb_tail_pointer(skb))
|
||||
return -EINVAL;
|
||||
|
||||
pos += ieee802154_hdr_get_addrs(buf + pos, hdr);
|
||||
return pos;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee802154_hdr_peek_addrs);
|
||||
|
||||
int
|
||||
ieee802154_hdr_peek(const struct sk_buff *skb, struct ieee802154_hdr *hdr)
|
||||
{
|
||||
const u8 *buf = skb_mac_header(skb);
|
||||
int pos;
|
||||
|
||||
pos = ieee802154_hdr_peek_addrs(skb, hdr);
|
||||
if (pos < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (hdr->fc.security_enabled) {
|
||||
u8 key_id_mode = IEEE802154_SCF_KEY_ID_MODE(*(buf + pos));
|
||||
int want = pos + ieee802154_sechdr_lengths[key_id_mode];
|
||||
|
||||
if (buf + want > skb_tail_pointer(skb))
|
||||
return -EINVAL;
|
||||
|
||||
pos += ieee802154_hdr_get_sechdr(buf + pos, &hdr->sec);
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee802154_hdr_peek);
|
||||
|
||||
int ieee802154_max_payload(const struct ieee802154_hdr *hdr)
|
||||
{
|
||||
int hlen = ieee802154_hdr_minlen(hdr);
|
||||
|
||||
if (hdr->fc.security_enabled) {
|
||||
hlen += ieee802154_sechdr_lengths[hdr->sec.key_id_mode] - 1;
|
||||
hlen += ieee802154_sechdr_authtag_len(&hdr->sec);
|
||||
}
|
||||
|
||||
return IEEE802154_MTU - hlen - IEEE802154_MFR_SIZE;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee802154_max_payload);
|
90
net/ieee802154/ieee802154.h
Normal file
90
net/ieee802154/ieee802154.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) 2007, 2008, 2009 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
#ifndef IEEE_802154_LOCAL_H
|
||||
#define IEEE_802154_LOCAL_H
|
||||
|
||||
int __init ieee802154_nl_init(void);
|
||||
void __exit ieee802154_nl_exit(void);
|
||||
|
||||
#define IEEE802154_OP(_cmd, _func) \
|
||||
{ \
|
||||
.cmd = _cmd, \
|
||||
.policy = ieee802154_policy, \
|
||||
.doit = _func, \
|
||||
.dumpit = NULL, \
|
||||
.flags = GENL_ADMIN_PERM, \
|
||||
}
|
||||
|
||||
#define IEEE802154_DUMP(_cmd, _func, _dump) \
|
||||
{ \
|
||||
.cmd = _cmd, \
|
||||
.policy = ieee802154_policy, \
|
||||
.doit = _func, \
|
||||
.dumpit = _dump, \
|
||||
}
|
||||
|
||||
struct genl_info;
|
||||
|
||||
struct sk_buff *ieee802154_nl_create(int flags, u8 req);
|
||||
int ieee802154_nl_mcast(struct sk_buff *msg, unsigned int group);
|
||||
struct sk_buff *ieee802154_nl_new_reply(struct genl_info *info,
|
||||
int flags, u8 req);
|
||||
int ieee802154_nl_reply(struct sk_buff *msg, struct genl_info *info);
|
||||
|
||||
extern struct genl_family nl802154_family;
|
||||
|
||||
/* genetlink ops/groups */
|
||||
int ieee802154_list_phy(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_dump_phy(struct sk_buff *skb, struct netlink_callback *cb);
|
||||
int ieee802154_add_iface(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_del_iface(struct sk_buff *skb, struct genl_info *info);
|
||||
|
||||
enum ieee802154_mcgrp_ids {
|
||||
IEEE802154_COORD_MCGRP,
|
||||
IEEE802154_BEACON_MCGRP,
|
||||
};
|
||||
|
||||
int ieee802154_associate_req(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_associate_resp(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_disassociate_req(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_scan_req(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_start_req(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_list_iface(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_dump_iface(struct sk_buff *skb, struct netlink_callback *cb);
|
||||
int ieee802154_set_macparams(struct sk_buff *skb, struct genl_info *info);
|
||||
|
||||
int ieee802154_llsec_getparams(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_setparams(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_add_key(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_del_key(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_dump_keys(struct sk_buff *skb,
|
||||
struct netlink_callback *cb);
|
||||
int ieee802154_llsec_add_dev(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_del_dev(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_dump_devs(struct sk_buff *skb,
|
||||
struct netlink_callback *cb);
|
||||
int ieee802154_llsec_add_devkey(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_del_devkey(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_dump_devkeys(struct sk_buff *skb,
|
||||
struct netlink_callback *cb);
|
||||
int ieee802154_llsec_add_seclevel(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_del_seclevel(struct sk_buff *skb, struct genl_info *info);
|
||||
int ieee802154_llsec_dump_seclevels(struct sk_buff *skb,
|
||||
struct netlink_callback *cb);
|
||||
|
||||
#endif
|
165
net/ieee802154/netlink.c
Normal file
165
net/ieee802154/netlink.c
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Netlink inteface for IEEE 802.15.4 stack
|
||||
*
|
||||
* Copyright 2007, 2008 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Written by:
|
||||
* Sergey Lapin <slapin@ossfans.org>
|
||||
* Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
||||
* Maxim Osipov <maxim.osipov@siemens.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <net/genetlink.h>
|
||||
#include <linux/nl802154.h>
|
||||
|
||||
#include "ieee802154.h"
|
||||
|
||||
static unsigned int ieee802154_seq_num;
|
||||
static DEFINE_SPINLOCK(ieee802154_seq_lock);
|
||||
|
||||
struct genl_family nl802154_family = {
|
||||
.id = GENL_ID_GENERATE,
|
||||
.hdrsize = 0,
|
||||
.name = IEEE802154_NL_NAME,
|
||||
.version = 1,
|
||||
.maxattr = IEEE802154_ATTR_MAX,
|
||||
};
|
||||
|
||||
/* Requests to userspace */
|
||||
struct sk_buff *ieee802154_nl_create(int flags, u8 req)
|
||||
{
|
||||
void *hdr;
|
||||
struct sk_buff *msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
|
||||
unsigned long f;
|
||||
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
spin_lock_irqsave(&ieee802154_seq_lock, f);
|
||||
hdr = genlmsg_put(msg, 0, ieee802154_seq_num++,
|
||||
&nl802154_family, flags, req);
|
||||
spin_unlock_irqrestore(&ieee802154_seq_lock, f);
|
||||
if (!hdr) {
|
||||
nlmsg_free(msg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
int ieee802154_nl_mcast(struct sk_buff *msg, unsigned int group)
|
||||
{
|
||||
struct nlmsghdr *nlh = nlmsg_hdr(msg);
|
||||
void *hdr = genlmsg_data(nlmsg_data(nlh));
|
||||
|
||||
if (genlmsg_end(msg, hdr) < 0)
|
||||
goto out;
|
||||
|
||||
return genlmsg_multicast(&nl802154_family, msg, 0, group, GFP_ATOMIC);
|
||||
out:
|
||||
nlmsg_free(msg);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
struct sk_buff *ieee802154_nl_new_reply(struct genl_info *info,
|
||||
int flags, u8 req)
|
||||
{
|
||||
void *hdr;
|
||||
struct sk_buff *msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
|
||||
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
hdr = genlmsg_put_reply(msg, info,
|
||||
&nl802154_family, flags, req);
|
||||
if (!hdr) {
|
||||
nlmsg_free(msg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
int ieee802154_nl_reply(struct sk_buff *msg, struct genl_info *info)
|
||||
{
|
||||
struct nlmsghdr *nlh = nlmsg_hdr(msg);
|
||||
void *hdr = genlmsg_data(nlmsg_data(nlh));
|
||||
|
||||
if (genlmsg_end(msg, hdr) < 0)
|
||||
goto out;
|
||||
|
||||
return genlmsg_reply(msg, info);
|
||||
out:
|
||||
nlmsg_free(msg);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
static const struct genl_ops ieee8021154_ops[] = {
|
||||
/* see nl-phy.c */
|
||||
IEEE802154_DUMP(IEEE802154_LIST_PHY, ieee802154_list_phy,
|
||||
ieee802154_dump_phy),
|
||||
IEEE802154_OP(IEEE802154_ADD_IFACE, ieee802154_add_iface),
|
||||
IEEE802154_OP(IEEE802154_DEL_IFACE, ieee802154_del_iface),
|
||||
/* see nl-mac.c */
|
||||
IEEE802154_OP(IEEE802154_ASSOCIATE_REQ, ieee802154_associate_req),
|
||||
IEEE802154_OP(IEEE802154_ASSOCIATE_RESP, ieee802154_associate_resp),
|
||||
IEEE802154_OP(IEEE802154_DISASSOCIATE_REQ, ieee802154_disassociate_req),
|
||||
IEEE802154_OP(IEEE802154_SCAN_REQ, ieee802154_scan_req),
|
||||
IEEE802154_OP(IEEE802154_START_REQ, ieee802154_start_req),
|
||||
IEEE802154_DUMP(IEEE802154_LIST_IFACE, ieee802154_list_iface,
|
||||
ieee802154_dump_iface),
|
||||
IEEE802154_OP(IEEE802154_SET_MACPARAMS, ieee802154_set_macparams),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_GETPARAMS, ieee802154_llsec_getparams),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_SETPARAMS, ieee802154_llsec_setparams),
|
||||
IEEE802154_DUMP(IEEE802154_LLSEC_LIST_KEY, NULL,
|
||||
ieee802154_llsec_dump_keys),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_ADD_KEY, ieee802154_llsec_add_key),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_DEL_KEY, ieee802154_llsec_del_key),
|
||||
IEEE802154_DUMP(IEEE802154_LLSEC_LIST_DEV, NULL,
|
||||
ieee802154_llsec_dump_devs),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_ADD_DEV, ieee802154_llsec_add_dev),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_DEL_DEV, ieee802154_llsec_del_dev),
|
||||
IEEE802154_DUMP(IEEE802154_LLSEC_LIST_DEVKEY, NULL,
|
||||
ieee802154_llsec_dump_devkeys),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_ADD_DEVKEY, ieee802154_llsec_add_devkey),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_DEL_DEVKEY, ieee802154_llsec_del_devkey),
|
||||
IEEE802154_DUMP(IEEE802154_LLSEC_LIST_SECLEVEL, NULL,
|
||||
ieee802154_llsec_dump_seclevels),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_ADD_SECLEVEL,
|
||||
ieee802154_llsec_add_seclevel),
|
||||
IEEE802154_OP(IEEE802154_LLSEC_DEL_SECLEVEL,
|
||||
ieee802154_llsec_del_seclevel),
|
||||
};
|
||||
|
||||
static const struct genl_multicast_group ieee802154_mcgrps[] = {
|
||||
[IEEE802154_COORD_MCGRP] = { .name = IEEE802154_MCAST_COORD_NAME, },
|
||||
[IEEE802154_BEACON_MCGRP] = { .name = IEEE802154_MCAST_BEACON_NAME, },
|
||||
};
|
||||
|
||||
|
||||
int __init ieee802154_nl_init(void)
|
||||
{
|
||||
return genl_register_family_with_ops_groups(&nl802154_family,
|
||||
ieee8021154_ops,
|
||||
ieee802154_mcgrps);
|
||||
}
|
||||
|
||||
void __exit ieee802154_nl_exit(void)
|
||||
{
|
||||
genl_unregister_family(&nl802154_family);
|
||||
}
|
1532
net/ieee802154/nl-mac.c
Normal file
1532
net/ieee802154/nl-mac.c
Normal file
File diff suppressed because it is too large
Load diff
355
net/ieee802154/nl-phy.c
Normal file
355
net/ieee802154/nl-phy.c
Normal file
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Netlink inteface for IEEE 802.15.4 stack
|
||||
*
|
||||
* Copyright 2007, 2008 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Written by:
|
||||
* Sergey Lapin <slapin@ossfans.org>
|
||||
* Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
||||
* Maxim Osipov <maxim.osipov@siemens.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <net/netlink.h>
|
||||
#include <net/genetlink.h>
|
||||
#include <net/wpan-phy.h>
|
||||
#include <net/af_ieee802154.h>
|
||||
#include <net/ieee802154_netdev.h>
|
||||
#include <net/rtnetlink.h> /* for rtnl_{un,}lock */
|
||||
#include <linux/nl802154.h>
|
||||
|
||||
#include "ieee802154.h"
|
||||
|
||||
static int ieee802154_nl_fill_phy(struct sk_buff *msg, u32 portid,
|
||||
u32 seq, int flags, struct wpan_phy *phy)
|
||||
{
|
||||
void *hdr;
|
||||
int i, pages = 0;
|
||||
uint32_t *buf = kzalloc(32 * sizeof(uint32_t), GFP_KERNEL);
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (!buf)
|
||||
return -EMSGSIZE;
|
||||
|
||||
hdr = genlmsg_put(msg, 0, seq, &nl802154_family, flags,
|
||||
IEEE802154_LIST_PHY);
|
||||
if (!hdr)
|
||||
goto out;
|
||||
|
||||
mutex_lock(&phy->pib_lock);
|
||||
if (nla_put_string(msg, IEEE802154_ATTR_PHY_NAME, wpan_phy_name(phy)) ||
|
||||
nla_put_u8(msg, IEEE802154_ATTR_PAGE, phy->current_page) ||
|
||||
nla_put_u8(msg, IEEE802154_ATTR_CHANNEL, phy->current_channel))
|
||||
goto nla_put_failure;
|
||||
for (i = 0; i < 32; i++) {
|
||||
if (phy->channels_supported[i])
|
||||
buf[pages++] = phy->channels_supported[i] | (i << 27);
|
||||
}
|
||||
if (pages &&
|
||||
nla_put(msg, IEEE802154_ATTR_CHANNEL_PAGE_LIST,
|
||||
pages * sizeof(uint32_t), buf))
|
||||
goto nla_put_failure;
|
||||
mutex_unlock(&phy->pib_lock);
|
||||
kfree(buf);
|
||||
return genlmsg_end(msg, hdr);
|
||||
|
||||
nla_put_failure:
|
||||
mutex_unlock(&phy->pib_lock);
|
||||
genlmsg_cancel(msg, hdr);
|
||||
out:
|
||||
kfree(buf);
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
int ieee802154_list_phy(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
/* Request for interface name, index, type, IEEE address,
|
||||
* PAN Id, short address
|
||||
*/
|
||||
struct sk_buff *msg;
|
||||
struct wpan_phy *phy;
|
||||
const char *name;
|
||||
int rc = -ENOBUFS;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (!info->attrs[IEEE802154_ATTR_PHY_NAME])
|
||||
return -EINVAL;
|
||||
|
||||
name = nla_data(info->attrs[IEEE802154_ATTR_PHY_NAME]);
|
||||
if (name[nla_len(info->attrs[IEEE802154_ATTR_PHY_NAME]) - 1] != '\0')
|
||||
return -EINVAL; /* phy name should be null-terminated */
|
||||
|
||||
|
||||
phy = wpan_phy_find(name);
|
||||
if (!phy)
|
||||
return -ENODEV;
|
||||
|
||||
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
||||
if (!msg)
|
||||
goto out_dev;
|
||||
|
||||
rc = ieee802154_nl_fill_phy(msg, info->snd_portid, info->snd_seq,
|
||||
0, phy);
|
||||
if (rc < 0)
|
||||
goto out_free;
|
||||
|
||||
wpan_phy_put(phy);
|
||||
|
||||
return genlmsg_reply(msg, info);
|
||||
out_free:
|
||||
nlmsg_free(msg);
|
||||
out_dev:
|
||||
wpan_phy_put(phy);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct dump_phy_data {
|
||||
struct sk_buff *skb;
|
||||
struct netlink_callback *cb;
|
||||
int idx, s_idx;
|
||||
};
|
||||
|
||||
static int ieee802154_dump_phy_iter(struct wpan_phy *phy, void *_data)
|
||||
{
|
||||
int rc;
|
||||
struct dump_phy_data *data = _data;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (data->idx++ < data->s_idx)
|
||||
return 0;
|
||||
|
||||
rc = ieee802154_nl_fill_phy(data->skb,
|
||||
NETLINK_CB(data->cb->skb).portid,
|
||||
data->cb->nlh->nlmsg_seq,
|
||||
NLM_F_MULTI,
|
||||
phy);
|
||||
|
||||
if (rc < 0) {
|
||||
data->idx--;
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ieee802154_dump_phy(struct sk_buff *skb, struct netlink_callback *cb)
|
||||
{
|
||||
struct dump_phy_data data = {
|
||||
.cb = cb,
|
||||
.skb = skb,
|
||||
.s_idx = cb->args[0],
|
||||
.idx = 0,
|
||||
};
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
wpan_phy_for_each(ieee802154_dump_phy_iter, &data);
|
||||
|
||||
cb->args[0] = data.idx;
|
||||
|
||||
return skb->len;
|
||||
}
|
||||
|
||||
int ieee802154_add_iface(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct sk_buff *msg;
|
||||
struct wpan_phy *phy;
|
||||
const char *name;
|
||||
const char *devname;
|
||||
int rc = -ENOBUFS;
|
||||
struct net_device *dev;
|
||||
int type = __IEEE802154_DEV_INVALID;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (!info->attrs[IEEE802154_ATTR_PHY_NAME])
|
||||
return -EINVAL;
|
||||
|
||||
name = nla_data(info->attrs[IEEE802154_ATTR_PHY_NAME]);
|
||||
if (name[nla_len(info->attrs[IEEE802154_ATTR_PHY_NAME]) - 1] != '\0')
|
||||
return -EINVAL; /* phy name should be null-terminated */
|
||||
|
||||
if (info->attrs[IEEE802154_ATTR_DEV_NAME]) {
|
||||
devname = nla_data(info->attrs[IEEE802154_ATTR_DEV_NAME]);
|
||||
if (devname[nla_len(info->attrs[IEEE802154_ATTR_DEV_NAME]) - 1]
|
||||
!= '\0')
|
||||
return -EINVAL; /* phy name should be null-terminated */
|
||||
} else {
|
||||
devname = "wpan%d";
|
||||
}
|
||||
|
||||
if (strlen(devname) >= IFNAMSIZ)
|
||||
return -ENAMETOOLONG;
|
||||
|
||||
phy = wpan_phy_find(name);
|
||||
if (!phy)
|
||||
return -ENODEV;
|
||||
|
||||
msg = ieee802154_nl_new_reply(info, 0, IEEE802154_ADD_IFACE);
|
||||
if (!msg)
|
||||
goto out_dev;
|
||||
|
||||
if (!phy->add_iface) {
|
||||
rc = -EINVAL;
|
||||
goto nla_put_failure;
|
||||
}
|
||||
|
||||
if (info->attrs[IEEE802154_ATTR_HW_ADDR] &&
|
||||
nla_len(info->attrs[IEEE802154_ATTR_HW_ADDR]) !=
|
||||
IEEE802154_ADDR_LEN) {
|
||||
rc = -EINVAL;
|
||||
goto nla_put_failure;
|
||||
}
|
||||
|
||||
if (info->attrs[IEEE802154_ATTR_DEV_TYPE]) {
|
||||
type = nla_get_u8(info->attrs[IEEE802154_ATTR_DEV_TYPE]);
|
||||
if (type >= __IEEE802154_DEV_MAX) {
|
||||
rc = -EINVAL;
|
||||
goto nla_put_failure;
|
||||
}
|
||||
}
|
||||
|
||||
dev = phy->add_iface(phy, devname, type);
|
||||
if (IS_ERR(dev)) {
|
||||
rc = PTR_ERR(dev);
|
||||
goto nla_put_failure;
|
||||
}
|
||||
|
||||
if (info->attrs[IEEE802154_ATTR_HW_ADDR]) {
|
||||
struct sockaddr addr;
|
||||
|
||||
addr.sa_family = ARPHRD_IEEE802154;
|
||||
nla_memcpy(&addr.sa_data, info->attrs[IEEE802154_ATTR_HW_ADDR],
|
||||
IEEE802154_ADDR_LEN);
|
||||
|
||||
/* strangely enough, some callbacks (inetdev_event) from
|
||||
* dev_set_mac_address require RTNL_LOCK
|
||||
*/
|
||||
rtnl_lock();
|
||||
rc = dev_set_mac_address(dev, &addr);
|
||||
rtnl_unlock();
|
||||
if (rc)
|
||||
goto dev_unregister;
|
||||
}
|
||||
|
||||
if (nla_put_string(msg, IEEE802154_ATTR_PHY_NAME, wpan_phy_name(phy)) ||
|
||||
nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, dev->name))
|
||||
goto nla_put_failure;
|
||||
dev_put(dev);
|
||||
|
||||
wpan_phy_put(phy);
|
||||
|
||||
return ieee802154_nl_reply(msg, info);
|
||||
|
||||
dev_unregister:
|
||||
rtnl_lock(); /* del_iface must be called with RTNL lock */
|
||||
phy->del_iface(phy, dev);
|
||||
dev_put(dev);
|
||||
rtnl_unlock();
|
||||
nla_put_failure:
|
||||
nlmsg_free(msg);
|
||||
out_dev:
|
||||
wpan_phy_put(phy);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int ieee802154_del_iface(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct sk_buff *msg;
|
||||
struct wpan_phy *phy;
|
||||
const char *name;
|
||||
int rc;
|
||||
struct net_device *dev;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (!info->attrs[IEEE802154_ATTR_DEV_NAME])
|
||||
return -EINVAL;
|
||||
|
||||
name = nla_data(info->attrs[IEEE802154_ATTR_DEV_NAME]);
|
||||
if (name[nla_len(info->attrs[IEEE802154_ATTR_DEV_NAME]) - 1] != '\0')
|
||||
return -EINVAL; /* name should be null-terminated */
|
||||
|
||||
dev = dev_get_by_name(genl_info_net(info), name);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
phy = ieee802154_mlme_ops(dev)->get_phy(dev);
|
||||
BUG_ON(!phy);
|
||||
|
||||
rc = -EINVAL;
|
||||
/* phy name is optional, but should be checked if it's given */
|
||||
if (info->attrs[IEEE802154_ATTR_PHY_NAME]) {
|
||||
struct wpan_phy *phy2;
|
||||
|
||||
const char *pname =
|
||||
nla_data(info->attrs[IEEE802154_ATTR_PHY_NAME]);
|
||||
if (pname[nla_len(info->attrs[IEEE802154_ATTR_PHY_NAME]) - 1]
|
||||
!= '\0')
|
||||
/* name should be null-terminated */
|
||||
goto out_dev;
|
||||
|
||||
phy2 = wpan_phy_find(pname);
|
||||
if (!phy2)
|
||||
goto out_dev;
|
||||
|
||||
if (phy != phy2) {
|
||||
wpan_phy_put(phy2);
|
||||
goto out_dev;
|
||||
}
|
||||
}
|
||||
|
||||
rc = -ENOBUFS;
|
||||
|
||||
msg = ieee802154_nl_new_reply(info, 0, IEEE802154_DEL_IFACE);
|
||||
if (!msg)
|
||||
goto out_dev;
|
||||
|
||||
if (!phy->del_iface) {
|
||||
rc = -EINVAL;
|
||||
goto nla_put_failure;
|
||||
}
|
||||
|
||||
rtnl_lock();
|
||||
phy->del_iface(phy, dev);
|
||||
|
||||
/* We don't have device anymore */
|
||||
dev_put(dev);
|
||||
dev = NULL;
|
||||
|
||||
rtnl_unlock();
|
||||
|
||||
if (nla_put_string(msg, IEEE802154_ATTR_PHY_NAME, wpan_phy_name(phy)) ||
|
||||
nla_put_string(msg, IEEE802154_ATTR_DEV_NAME, name))
|
||||
goto nla_put_failure;
|
||||
wpan_phy_put(phy);
|
||||
|
||||
return ieee802154_nl_reply(msg, info);
|
||||
|
||||
nla_put_failure:
|
||||
nlmsg_free(msg);
|
||||
out_dev:
|
||||
wpan_phy_put(phy);
|
||||
if (dev)
|
||||
dev_put(dev);
|
||||
|
||||
return rc;
|
||||
}
|
82
net/ieee802154/nl_policy.c
Normal file
82
net/ieee802154/nl_policy.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* nl802154.h
|
||||
*
|
||||
* Copyright (C) 2007, 2008 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <net/netlink.h>
|
||||
#include <linux/nl802154.h>
|
||||
|
||||
#define NLA_HW_ADDR NLA_U64
|
||||
|
||||
const struct nla_policy ieee802154_policy[IEEE802154_ATTR_MAX + 1] = {
|
||||
[IEEE802154_ATTR_DEV_NAME] = { .type = NLA_STRING, },
|
||||
[IEEE802154_ATTR_DEV_INDEX] = { .type = NLA_U32, },
|
||||
[IEEE802154_ATTR_PHY_NAME] = { .type = NLA_STRING, },
|
||||
|
||||
[IEEE802154_ATTR_STATUS] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_SHORT_ADDR] = { .type = NLA_U16, },
|
||||
[IEEE802154_ATTR_HW_ADDR] = { .type = NLA_HW_ADDR, },
|
||||
[IEEE802154_ATTR_PAN_ID] = { .type = NLA_U16, },
|
||||
[IEEE802154_ATTR_CHANNEL] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_PAGE] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_COORD_SHORT_ADDR] = { .type = NLA_U16, },
|
||||
[IEEE802154_ATTR_COORD_HW_ADDR] = { .type = NLA_HW_ADDR, },
|
||||
[IEEE802154_ATTR_COORD_PAN_ID] = { .type = NLA_U16, },
|
||||
[IEEE802154_ATTR_SRC_SHORT_ADDR] = { .type = NLA_U16, },
|
||||
[IEEE802154_ATTR_SRC_HW_ADDR] = { .type = NLA_HW_ADDR, },
|
||||
[IEEE802154_ATTR_SRC_PAN_ID] = { .type = NLA_U16, },
|
||||
[IEEE802154_ATTR_DEST_SHORT_ADDR] = { .type = NLA_U16, },
|
||||
[IEEE802154_ATTR_DEST_HW_ADDR] = { .type = NLA_HW_ADDR, },
|
||||
[IEEE802154_ATTR_DEST_PAN_ID] = { .type = NLA_U16, },
|
||||
|
||||
[IEEE802154_ATTR_CAPABILITY] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_REASON] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_SCAN_TYPE] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_CHANNELS] = { .type = NLA_U32, },
|
||||
[IEEE802154_ATTR_DURATION] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_ED_LIST] = { .len = 27 },
|
||||
[IEEE802154_ATTR_CHANNEL_PAGE_LIST] = { .len = 32 * 4, },
|
||||
|
||||
[IEEE802154_ATTR_TXPOWER] = { .type = NLA_S8, },
|
||||
[IEEE802154_ATTR_LBT_ENABLED] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_CCA_MODE] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_CCA_ED_LEVEL] = { .type = NLA_S32, },
|
||||
[IEEE802154_ATTR_CSMA_RETRIES] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_CSMA_MIN_BE] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_CSMA_MAX_BE] = { .type = NLA_U8, },
|
||||
|
||||
[IEEE802154_ATTR_FRAME_RETRIES] = { .type = NLA_S8, },
|
||||
|
||||
[IEEE802154_ATTR_LLSEC_ENABLED] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_SECLEVEL] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_KEY_MODE] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_KEY_SOURCE_SHORT] = { .type = NLA_U32, },
|
||||
[IEEE802154_ATTR_LLSEC_KEY_SOURCE_EXTENDED] = { .type = NLA_HW_ADDR, },
|
||||
[IEEE802154_ATTR_LLSEC_KEY_ID] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_FRAME_COUNTER] = { .type = NLA_U32 },
|
||||
[IEEE802154_ATTR_LLSEC_KEY_BYTES] = { .len = 16, },
|
||||
[IEEE802154_ATTR_LLSEC_KEY_USAGE_FRAME_TYPES] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_KEY_USAGE_COMMANDS] = { .len = 258 / 8 },
|
||||
[IEEE802154_ATTR_LLSEC_FRAME_TYPE] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_CMD_FRAME_ID] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_SECLEVELS] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_DEV_OVERRIDE] = { .type = NLA_U8, },
|
||||
[IEEE802154_ATTR_LLSEC_DEV_KEY_MODE] = { .type = NLA_U8, },
|
||||
};
|
||||
|
275
net/ieee802154/raw.c
Normal file
275
net/ieee802154/raw.c
Normal file
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* Raw IEEE 802.15.4 sockets
|
||||
*
|
||||
* Copyright 2007, 2008 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Written by:
|
||||
* Sergey Lapin <slapin@ossfans.org>
|
||||
* Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/net.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/af_ieee802154.h>
|
||||
#include <net/ieee802154_netdev.h>
|
||||
|
||||
#include "af802154.h"
|
||||
|
||||
static HLIST_HEAD(raw_head);
|
||||
static DEFINE_RWLOCK(raw_lock);
|
||||
|
||||
static void raw_hash(struct sock *sk)
|
||||
{
|
||||
write_lock_bh(&raw_lock);
|
||||
sk_add_node(sk, &raw_head);
|
||||
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
|
||||
write_unlock_bh(&raw_lock);
|
||||
}
|
||||
|
||||
static void raw_unhash(struct sock *sk)
|
||||
{
|
||||
write_lock_bh(&raw_lock);
|
||||
if (sk_del_node_init(sk))
|
||||
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
|
||||
write_unlock_bh(&raw_lock);
|
||||
}
|
||||
|
||||
static void raw_close(struct sock *sk, long timeout)
|
||||
{
|
||||
sk_common_release(sk);
|
||||
}
|
||||
|
||||
static int raw_bind(struct sock *sk, struct sockaddr *_uaddr, int len)
|
||||
{
|
||||
struct ieee802154_addr addr;
|
||||
struct sockaddr_ieee802154 *uaddr = (struct sockaddr_ieee802154 *)_uaddr;
|
||||
int err = 0;
|
||||
struct net_device *dev = NULL;
|
||||
|
||||
if (len < sizeof(*uaddr))
|
||||
return -EINVAL;
|
||||
|
||||
uaddr = (struct sockaddr_ieee802154 *)_uaddr;
|
||||
if (uaddr->family != AF_IEEE802154)
|
||||
return -EINVAL;
|
||||
|
||||
lock_sock(sk);
|
||||
|
||||
ieee802154_addr_from_sa(&addr, &uaddr->addr);
|
||||
dev = ieee802154_get_dev(sock_net(sk), &addr);
|
||||
if (!dev) {
|
||||
err = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dev->type != ARPHRD_IEEE802154) {
|
||||
err = -ENODEV;
|
||||
goto out_put;
|
||||
}
|
||||
|
||||
sk->sk_bound_dev_if = dev->ifindex;
|
||||
sk_dst_reset(sk);
|
||||
|
||||
out_put:
|
||||
dev_put(dev);
|
||||
out:
|
||||
release_sock(sk);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int raw_connect(struct sock *sk, struct sockaddr *uaddr,
|
||||
int addr_len)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static int raw_disconnect(struct sock *sk, int flags)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int raw_sendmsg(struct kiocb *iocb, struct sock *sk,
|
||||
struct msghdr *msg, size_t size)
|
||||
{
|
||||
struct net_device *dev;
|
||||
unsigned int mtu;
|
||||
struct sk_buff *skb;
|
||||
int hlen, tlen;
|
||||
int err;
|
||||
|
||||
if (msg->msg_flags & MSG_OOB) {
|
||||
pr_debug("msg->msg_flags = 0x%x\n", msg->msg_flags);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
lock_sock(sk);
|
||||
if (!sk->sk_bound_dev_if)
|
||||
dev = dev_getfirstbyhwtype(sock_net(sk), ARPHRD_IEEE802154);
|
||||
else
|
||||
dev = dev_get_by_index(sock_net(sk), sk->sk_bound_dev_if);
|
||||
release_sock(sk);
|
||||
|
||||
if (!dev) {
|
||||
pr_debug("no dev\n");
|
||||
err = -ENXIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mtu = dev->mtu;
|
||||
pr_debug("name = %s, mtu = %u\n", dev->name, mtu);
|
||||
|
||||
if (size > mtu) {
|
||||
pr_debug("size = %Zu, mtu = %u\n", size, mtu);
|
||||
err = -EINVAL;
|
||||
goto out_dev;
|
||||
}
|
||||
|
||||
hlen = LL_RESERVED_SPACE(dev);
|
||||
tlen = dev->needed_tailroom;
|
||||
skb = sock_alloc_send_skb(sk, hlen + tlen + size,
|
||||
msg->msg_flags & MSG_DONTWAIT, &err);
|
||||
if (!skb)
|
||||
goto out_dev;
|
||||
|
||||
skb_reserve(skb, hlen);
|
||||
|
||||
skb_reset_mac_header(skb);
|
||||
skb_reset_network_header(skb);
|
||||
|
||||
err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
|
||||
if (err < 0)
|
||||
goto out_skb;
|
||||
|
||||
skb->dev = dev;
|
||||
skb->sk = sk;
|
||||
skb->protocol = htons(ETH_P_IEEE802154);
|
||||
|
||||
dev_put(dev);
|
||||
|
||||
err = dev_queue_xmit(skb);
|
||||
if (err > 0)
|
||||
err = net_xmit_errno(err);
|
||||
|
||||
return err ?: size;
|
||||
|
||||
out_skb:
|
||||
kfree_skb(skb);
|
||||
out_dev:
|
||||
dev_put(dev);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int raw_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
|
||||
size_t len, int noblock, int flags, int *addr_len)
|
||||
{
|
||||
size_t copied = 0;
|
||||
int err = -EOPNOTSUPP;
|
||||
struct sk_buff *skb;
|
||||
|
||||
skb = skb_recv_datagram(sk, flags, noblock, &err);
|
||||
if (!skb)
|
||||
goto out;
|
||||
|
||||
copied = skb->len;
|
||||
if (len < copied) {
|
||||
msg->msg_flags |= MSG_TRUNC;
|
||||
copied = len;
|
||||
}
|
||||
|
||||
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
||||
if (err)
|
||||
goto done;
|
||||
|
||||
sock_recv_ts_and_drops(msg, sk, skb);
|
||||
|
||||
if (flags & MSG_TRUNC)
|
||||
copied = skb->len;
|
||||
done:
|
||||
skb_free_datagram(sk, skb);
|
||||
out:
|
||||
if (err)
|
||||
return err;
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int raw_rcv_skb(struct sock *sk, struct sk_buff *skb)
|
||||
{
|
||||
skb = skb_share_check(skb, GFP_ATOMIC);
|
||||
if (!skb)
|
||||
return NET_RX_DROP;
|
||||
|
||||
if (sock_queue_rcv_skb(sk, skb) < 0) {
|
||||
kfree_skb(skb);
|
||||
return NET_RX_DROP;
|
||||
}
|
||||
|
||||
return NET_RX_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void ieee802154_raw_deliver(struct net_device *dev, struct sk_buff *skb)
|
||||
{
|
||||
struct sock *sk;
|
||||
|
||||
read_lock(&raw_lock);
|
||||
sk_for_each(sk, &raw_head) {
|
||||
bh_lock_sock(sk);
|
||||
if (!sk->sk_bound_dev_if ||
|
||||
sk->sk_bound_dev_if == dev->ifindex) {
|
||||
struct sk_buff *clone;
|
||||
|
||||
clone = skb_clone(skb, GFP_ATOMIC);
|
||||
if (clone)
|
||||
raw_rcv_skb(sk, clone);
|
||||
}
|
||||
bh_unlock_sock(sk);
|
||||
}
|
||||
read_unlock(&raw_lock);
|
||||
}
|
||||
|
||||
static int raw_getsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int raw_setsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, unsigned int optlen)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
struct proto ieee802154_raw_prot = {
|
||||
.name = "IEEE-802.15.4-RAW",
|
||||
.owner = THIS_MODULE,
|
||||
.obj_size = sizeof(struct sock),
|
||||
.close = raw_close,
|
||||
.bind = raw_bind,
|
||||
.sendmsg = raw_sendmsg,
|
||||
.recvmsg = raw_recvmsg,
|
||||
.hash = raw_hash,
|
||||
.unhash = raw_unhash,
|
||||
.connect = raw_connect,
|
||||
.disconnect = raw_disconnect,
|
||||
.getsockopt = raw_getsockopt,
|
||||
.setsockopt = raw_setsockopt,
|
||||
};
|
583
net/ieee802154/reassembly.c
Normal file
583
net/ieee802154/reassembly.c
Normal file
|
@ -0,0 +1,583 @@
|
|||
/* 6LoWPAN fragment reassembly
|
||||
*
|
||||
*
|
||||
* Authors:
|
||||
* Alexander Aring <aar@pengutronix.de>
|
||||
*
|
||||
* Based on: net/ipv6/reassembly.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "6LoWPAN: " fmt
|
||||
|
||||
#include <linux/net.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/jhash.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#include <net/ieee802154_netdev.h>
|
||||
#include <net/6lowpan.h>
|
||||
#include <net/ipv6.h>
|
||||
#include <net/inet_frag.h>
|
||||
|
||||
#include "reassembly.h"
|
||||
|
||||
static const char lowpan_frags_cache_name[] = "lowpan-frags";
|
||||
|
||||
struct lowpan_frag_info {
|
||||
__be16 d_tag;
|
||||
u16 d_size;
|
||||
u8 d_offset;
|
||||
};
|
||||
|
||||
static struct lowpan_frag_info *lowpan_cb(struct sk_buff *skb)
|
||||
{
|
||||
return (struct lowpan_frag_info *)skb->cb;
|
||||
}
|
||||
|
||||
static struct inet_frags lowpan_frags;
|
||||
|
||||
static int lowpan_frag_reasm(struct lowpan_frag_queue *fq,
|
||||
struct sk_buff *prev, struct net_device *dev);
|
||||
|
||||
static unsigned int lowpan_hash_frag(__be16 tag, u16 d_size,
|
||||
const struct ieee802154_addr *saddr,
|
||||
const struct ieee802154_addr *daddr)
|
||||
{
|
||||
net_get_random_once(&lowpan_frags.rnd, sizeof(lowpan_frags.rnd));
|
||||
return jhash_3words(ieee802154_addr_hash(saddr),
|
||||
ieee802154_addr_hash(daddr),
|
||||
(__force u32)(tag + (d_size << 16)),
|
||||
lowpan_frags.rnd);
|
||||
}
|
||||
|
||||
static unsigned int lowpan_hashfn(const struct inet_frag_queue *q)
|
||||
{
|
||||
const struct lowpan_frag_queue *fq;
|
||||
|
||||
fq = container_of(q, struct lowpan_frag_queue, q);
|
||||
return lowpan_hash_frag(fq->tag, fq->d_size, &fq->saddr, &fq->daddr);
|
||||
}
|
||||
|
||||
static bool lowpan_frag_match(const struct inet_frag_queue *q, const void *a)
|
||||
{
|
||||
const struct lowpan_frag_queue *fq;
|
||||
const struct lowpan_create_arg *arg = a;
|
||||
|
||||
fq = container_of(q, struct lowpan_frag_queue, q);
|
||||
return fq->tag == arg->tag && fq->d_size == arg->d_size &&
|
||||
ieee802154_addr_equal(&fq->saddr, arg->src) &&
|
||||
ieee802154_addr_equal(&fq->daddr, arg->dst);
|
||||
}
|
||||
|
||||
static void lowpan_frag_init(struct inet_frag_queue *q, const void *a)
|
||||
{
|
||||
const struct lowpan_create_arg *arg = a;
|
||||
struct lowpan_frag_queue *fq;
|
||||
|
||||
fq = container_of(q, struct lowpan_frag_queue, q);
|
||||
|
||||
fq->tag = arg->tag;
|
||||
fq->d_size = arg->d_size;
|
||||
fq->saddr = *arg->src;
|
||||
fq->daddr = *arg->dst;
|
||||
}
|
||||
|
||||
static void lowpan_frag_expire(unsigned long data)
|
||||
{
|
||||
struct frag_queue *fq;
|
||||
struct net *net;
|
||||
|
||||
fq = container_of((struct inet_frag_queue *)data, struct frag_queue, q);
|
||||
net = container_of(fq->q.net, struct net, ieee802154_lowpan.frags);
|
||||
|
||||
spin_lock(&fq->q.lock);
|
||||
|
||||
if (fq->q.flags & INET_FRAG_COMPLETE)
|
||||
goto out;
|
||||
|
||||
inet_frag_kill(&fq->q, &lowpan_frags);
|
||||
out:
|
||||
spin_unlock(&fq->q.lock);
|
||||
inet_frag_put(&fq->q, &lowpan_frags);
|
||||
}
|
||||
|
||||
static inline struct lowpan_frag_queue *
|
||||
fq_find(struct net *net, const struct lowpan_frag_info *frag_info,
|
||||
const struct ieee802154_addr *src,
|
||||
const struct ieee802154_addr *dst)
|
||||
{
|
||||
struct inet_frag_queue *q;
|
||||
struct lowpan_create_arg arg;
|
||||
unsigned int hash;
|
||||
struct netns_ieee802154_lowpan *ieee802154_lowpan =
|
||||
net_ieee802154_lowpan(net);
|
||||
|
||||
arg.tag = frag_info->d_tag;
|
||||
arg.d_size = frag_info->d_size;
|
||||
arg.src = src;
|
||||
arg.dst = dst;
|
||||
|
||||
hash = lowpan_hash_frag(frag_info->d_tag, frag_info->d_size, src, dst);
|
||||
|
||||
q = inet_frag_find(&ieee802154_lowpan->frags,
|
||||
&lowpan_frags, &arg, hash);
|
||||
if (IS_ERR_OR_NULL(q)) {
|
||||
inet_frag_maybe_warn_overflow(q, pr_fmt());
|
||||
return NULL;
|
||||
}
|
||||
return container_of(q, struct lowpan_frag_queue, q);
|
||||
}
|
||||
|
||||
static int lowpan_frag_queue(struct lowpan_frag_queue *fq,
|
||||
struct sk_buff *skb, const u8 frag_type)
|
||||
{
|
||||
struct sk_buff *prev, *next;
|
||||
struct net_device *dev;
|
||||
int end, offset;
|
||||
|
||||
if (fq->q.flags & INET_FRAG_COMPLETE)
|
||||
goto err;
|
||||
|
||||
offset = lowpan_cb(skb)->d_offset << 3;
|
||||
end = lowpan_cb(skb)->d_size;
|
||||
|
||||
/* Is this the final fragment? */
|
||||
if (offset + skb->len == end) {
|
||||
/* If we already have some bits beyond end
|
||||
* or have different end, the segment is corrupted.
|
||||
*/
|
||||
if (end < fq->q.len ||
|
||||
((fq->q.flags & INET_FRAG_LAST_IN) && end != fq->q.len))
|
||||
goto err;
|
||||
fq->q.flags |= INET_FRAG_LAST_IN;
|
||||
fq->q.len = end;
|
||||
} else {
|
||||
if (end > fq->q.len) {
|
||||
/* Some bits beyond end -> corruption. */
|
||||
if (fq->q.flags & INET_FRAG_LAST_IN)
|
||||
goto err;
|
||||
fq->q.len = end;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find out which fragments are in front and at the back of us
|
||||
* in the chain of fragments so far. We must know where to put
|
||||
* this fragment, right?
|
||||
*/
|
||||
prev = fq->q.fragments_tail;
|
||||
if (!prev || lowpan_cb(prev)->d_offset < lowpan_cb(skb)->d_offset) {
|
||||
next = NULL;
|
||||
goto found;
|
||||
}
|
||||
prev = NULL;
|
||||
for (next = fq->q.fragments; next != NULL; next = next->next) {
|
||||
if (lowpan_cb(next)->d_offset >= lowpan_cb(skb)->d_offset)
|
||||
break; /* bingo! */
|
||||
prev = next;
|
||||
}
|
||||
|
||||
found:
|
||||
/* Insert this fragment in the chain of fragments. */
|
||||
skb->next = next;
|
||||
if (!next)
|
||||
fq->q.fragments_tail = skb;
|
||||
if (prev)
|
||||
prev->next = skb;
|
||||
else
|
||||
fq->q.fragments = skb;
|
||||
|
||||
dev = skb->dev;
|
||||
if (dev)
|
||||
skb->dev = NULL;
|
||||
|
||||
fq->q.stamp = skb->tstamp;
|
||||
if (frag_type == LOWPAN_DISPATCH_FRAG1) {
|
||||
/* Calculate uncomp. 6lowpan header to estimate full size */
|
||||
fq->q.meat += lowpan_uncompress_size(skb, NULL);
|
||||
fq->q.flags |= INET_FRAG_FIRST_IN;
|
||||
} else {
|
||||
fq->q.meat += skb->len;
|
||||
}
|
||||
add_frag_mem_limit(&fq->q, skb->truesize);
|
||||
|
||||
if (fq->q.flags == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
|
||||
fq->q.meat == fq->q.len) {
|
||||
int res;
|
||||
unsigned long orefdst = skb->_skb_refdst;
|
||||
|
||||
skb->_skb_refdst = 0UL;
|
||||
res = lowpan_frag_reasm(fq, prev, dev);
|
||||
skb->_skb_refdst = orefdst;
|
||||
return res;
|
||||
}
|
||||
|
||||
return -1;
|
||||
err:
|
||||
kfree_skb(skb);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check if this packet is complete.
|
||||
* Returns NULL on failure by any reason, and pointer
|
||||
* to current nexthdr field in reassembled frame.
|
||||
*
|
||||
* It is called with locked fq, and caller must check that
|
||||
* queue is eligible for reassembly i.e. it is not COMPLETE,
|
||||
* the last and the first frames arrived and all the bits are here.
|
||||
*/
|
||||
static int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *prev,
|
||||
struct net_device *dev)
|
||||
{
|
||||
struct sk_buff *fp, *head = fq->q.fragments;
|
||||
int sum_truesize;
|
||||
|
||||
inet_frag_kill(&fq->q, &lowpan_frags);
|
||||
|
||||
/* Make the one we just received the head. */
|
||||
if (prev) {
|
||||
head = prev->next;
|
||||
fp = skb_clone(head, GFP_ATOMIC);
|
||||
|
||||
if (!fp)
|
||||
goto out_oom;
|
||||
|
||||
fp->next = head->next;
|
||||
if (!fp->next)
|
||||
fq->q.fragments_tail = fp;
|
||||
prev->next = fp;
|
||||
|
||||
skb_morph(head, fq->q.fragments);
|
||||
head->next = fq->q.fragments->next;
|
||||
|
||||
consume_skb(fq->q.fragments);
|
||||
fq->q.fragments = head;
|
||||
}
|
||||
|
||||
/* Head of list must not be cloned. */
|
||||
if (skb_unclone(head, GFP_ATOMIC))
|
||||
goto out_oom;
|
||||
|
||||
/* If the first fragment is fragmented itself, we split
|
||||
* it to two chunks: the first with data and paged part
|
||||
* and the second, holding only fragments.
|
||||
*/
|
||||
if (skb_has_frag_list(head)) {
|
||||
struct sk_buff *clone;
|
||||
int i, plen = 0;
|
||||
|
||||
clone = alloc_skb(0, GFP_ATOMIC);
|
||||
if (!clone)
|
||||
goto out_oom;
|
||||
clone->next = head->next;
|
||||
head->next = clone;
|
||||
skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
|
||||
skb_frag_list_init(head);
|
||||
for (i = 0; i < skb_shinfo(head)->nr_frags; i++)
|
||||
plen += skb_frag_size(&skb_shinfo(head)->frags[i]);
|
||||
clone->len = head->data_len - plen;
|
||||
clone->data_len = clone->len;
|
||||
head->data_len -= clone->len;
|
||||
head->len -= clone->len;
|
||||
add_frag_mem_limit(&fq->q, clone->truesize);
|
||||
}
|
||||
|
||||
WARN_ON(head == NULL);
|
||||
|
||||
sum_truesize = head->truesize;
|
||||
for (fp = head->next; fp;) {
|
||||
bool headstolen;
|
||||
int delta;
|
||||
struct sk_buff *next = fp->next;
|
||||
|
||||
sum_truesize += fp->truesize;
|
||||
if (skb_try_coalesce(head, fp, &headstolen, &delta)) {
|
||||
kfree_skb_partial(fp, headstolen);
|
||||
} else {
|
||||
if (!skb_shinfo(head)->frag_list)
|
||||
skb_shinfo(head)->frag_list = fp;
|
||||
head->data_len += fp->len;
|
||||
head->len += fp->len;
|
||||
head->truesize += fp->truesize;
|
||||
}
|
||||
fp = next;
|
||||
}
|
||||
sub_frag_mem_limit(&fq->q, sum_truesize);
|
||||
|
||||
head->next = NULL;
|
||||
head->dev = dev;
|
||||
head->tstamp = fq->q.stamp;
|
||||
|
||||
fq->q.fragments = NULL;
|
||||
fq->q.fragments_tail = NULL;
|
||||
|
||||
return 1;
|
||||
out_oom:
|
||||
net_dbg_ratelimited("lowpan_frag_reasm: no memory for reassembly\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int lowpan_get_frag_info(struct sk_buff *skb, const u8 frag_type,
|
||||
struct lowpan_frag_info *frag_info)
|
||||
{
|
||||
bool fail;
|
||||
u8 pattern = 0, low = 0;
|
||||
|
||||
fail = lowpan_fetch_skb(skb, &pattern, 1);
|
||||
fail |= lowpan_fetch_skb(skb, &low, 1);
|
||||
frag_info->d_size = (pattern & 7) << 8 | low;
|
||||
fail |= lowpan_fetch_skb(skb, &frag_info->d_tag, 2);
|
||||
|
||||
if (frag_type == LOWPAN_DISPATCH_FRAGN) {
|
||||
fail |= lowpan_fetch_skb(skb, &frag_info->d_offset, 1);
|
||||
} else {
|
||||
skb_reset_network_header(skb);
|
||||
frag_info->d_offset = 0;
|
||||
}
|
||||
|
||||
if (unlikely(fail))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lowpan_frag_rcv(struct sk_buff *skb, const u8 frag_type)
|
||||
{
|
||||
struct lowpan_frag_queue *fq;
|
||||
struct net *net = dev_net(skb->dev);
|
||||
struct lowpan_frag_info *frag_info = lowpan_cb(skb);
|
||||
struct ieee802154_addr source, dest;
|
||||
int err;
|
||||
|
||||
source = mac_cb(skb)->source;
|
||||
dest = mac_cb(skb)->dest;
|
||||
|
||||
err = lowpan_get_frag_info(skb, frag_type, frag_info);
|
||||
if (err < 0)
|
||||
goto err;
|
||||
|
||||
if (frag_info->d_size > IPV6_MIN_MTU) {
|
||||
net_warn_ratelimited("lowpan_frag_rcv: datagram size exceeds MTU\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
fq = fq_find(net, frag_info, &source, &dest);
|
||||
if (fq != NULL) {
|
||||
int ret;
|
||||
|
||||
spin_lock(&fq->q.lock);
|
||||
ret = lowpan_frag_queue(fq, skb, frag_type);
|
||||
spin_unlock(&fq->q.lock);
|
||||
|
||||
inet_frag_put(&fq->q, &lowpan_frags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
err:
|
||||
kfree_skb(skb);
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL(lowpan_frag_rcv);
|
||||
|
||||
#ifdef CONFIG_SYSCTL
|
||||
static int zero;
|
||||
|
||||
static struct ctl_table lowpan_frags_ns_ctl_table[] = {
|
||||
{
|
||||
.procname = "6lowpanfrag_high_thresh",
|
||||
.data = &init_net.ieee802154_lowpan.frags.high_thresh,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec_minmax,
|
||||
.extra1 = &init_net.ieee802154_lowpan.frags.low_thresh
|
||||
},
|
||||
{
|
||||
.procname = "6lowpanfrag_low_thresh",
|
||||
.data = &init_net.ieee802154_lowpan.frags.low_thresh,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec_minmax,
|
||||
.extra1 = &zero,
|
||||
.extra2 = &init_net.ieee802154_lowpan.frags.high_thresh
|
||||
},
|
||||
{
|
||||
.procname = "6lowpanfrag_time",
|
||||
.data = &init_net.ieee802154_lowpan.frags.timeout,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec_jiffies,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
/* secret interval has been deprecated */
|
||||
static int lowpan_frags_secret_interval_unused;
|
||||
static struct ctl_table lowpan_frags_ctl_table[] = {
|
||||
{
|
||||
.procname = "6lowpanfrag_secret_interval",
|
||||
.data = &lowpan_frags_secret_interval_unused,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec_jiffies,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static int __net_init lowpan_frags_ns_sysctl_register(struct net *net)
|
||||
{
|
||||
struct ctl_table *table;
|
||||
struct ctl_table_header *hdr;
|
||||
struct netns_ieee802154_lowpan *ieee802154_lowpan =
|
||||
net_ieee802154_lowpan(net);
|
||||
|
||||
table = lowpan_frags_ns_ctl_table;
|
||||
if (!net_eq(net, &init_net)) {
|
||||
table = kmemdup(table, sizeof(lowpan_frags_ns_ctl_table),
|
||||
GFP_KERNEL);
|
||||
if (table == NULL)
|
||||
goto err_alloc;
|
||||
|
||||
table[0].data = &ieee802154_lowpan->frags.high_thresh;
|
||||
table[0].extra1 = &ieee802154_lowpan->frags.low_thresh;
|
||||
table[0].extra2 = &init_net.ieee802154_lowpan.frags.high_thresh;
|
||||
table[1].data = &ieee802154_lowpan->frags.low_thresh;
|
||||
table[1].extra2 = &ieee802154_lowpan->frags.high_thresh;
|
||||
table[2].data = &ieee802154_lowpan->frags.timeout;
|
||||
|
||||
/* Don't export sysctls to unprivileged users */
|
||||
if (net->user_ns != &init_user_ns)
|
||||
table[0].procname = NULL;
|
||||
}
|
||||
|
||||
hdr = register_net_sysctl(net, "net/ieee802154/6lowpan", table);
|
||||
if (hdr == NULL)
|
||||
goto err_reg;
|
||||
|
||||
ieee802154_lowpan->sysctl.frags_hdr = hdr;
|
||||
return 0;
|
||||
|
||||
err_reg:
|
||||
if (!net_eq(net, &init_net))
|
||||
kfree(table);
|
||||
err_alloc:
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void __net_exit lowpan_frags_ns_sysctl_unregister(struct net *net)
|
||||
{
|
||||
struct ctl_table *table;
|
||||
struct netns_ieee802154_lowpan *ieee802154_lowpan =
|
||||
net_ieee802154_lowpan(net);
|
||||
|
||||
table = ieee802154_lowpan->sysctl.frags_hdr->ctl_table_arg;
|
||||
unregister_net_sysctl_table(ieee802154_lowpan->sysctl.frags_hdr);
|
||||
if (!net_eq(net, &init_net))
|
||||
kfree(table);
|
||||
}
|
||||
|
||||
static struct ctl_table_header *lowpan_ctl_header;
|
||||
|
||||
static int __init lowpan_frags_sysctl_register(void)
|
||||
{
|
||||
lowpan_ctl_header = register_net_sysctl(&init_net,
|
||||
"net/ieee802154/6lowpan",
|
||||
lowpan_frags_ctl_table);
|
||||
return lowpan_ctl_header == NULL ? -ENOMEM : 0;
|
||||
}
|
||||
|
||||
static void lowpan_frags_sysctl_unregister(void)
|
||||
{
|
||||
unregister_net_sysctl_table(lowpan_ctl_header);
|
||||
}
|
||||
#else
|
||||
static inline int lowpan_frags_ns_sysctl_register(struct net *net)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void lowpan_frags_ns_sysctl_unregister(struct net *net)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int __init lowpan_frags_sysctl_register(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void lowpan_frags_sysctl_unregister(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __net_init lowpan_frags_init_net(struct net *net)
|
||||
{
|
||||
struct netns_ieee802154_lowpan *ieee802154_lowpan =
|
||||
net_ieee802154_lowpan(net);
|
||||
|
||||
ieee802154_lowpan->frags.high_thresh = IPV6_FRAG_HIGH_THRESH;
|
||||
ieee802154_lowpan->frags.low_thresh = IPV6_FRAG_LOW_THRESH;
|
||||
ieee802154_lowpan->frags.timeout = IPV6_FRAG_TIMEOUT;
|
||||
|
||||
inet_frags_init_net(&ieee802154_lowpan->frags);
|
||||
|
||||
return lowpan_frags_ns_sysctl_register(net);
|
||||
}
|
||||
|
||||
static void __net_exit lowpan_frags_exit_net(struct net *net)
|
||||
{
|
||||
struct netns_ieee802154_lowpan *ieee802154_lowpan =
|
||||
net_ieee802154_lowpan(net);
|
||||
|
||||
lowpan_frags_ns_sysctl_unregister(net);
|
||||
inet_frags_exit_net(&ieee802154_lowpan->frags, &lowpan_frags);
|
||||
}
|
||||
|
||||
static struct pernet_operations lowpan_frags_ops = {
|
||||
.init = lowpan_frags_init_net,
|
||||
.exit = lowpan_frags_exit_net,
|
||||
};
|
||||
|
||||
int __init lowpan_net_frag_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lowpan_frags_sysctl_register();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = register_pernet_subsys(&lowpan_frags_ops);
|
||||
if (ret)
|
||||
goto err_pernet;
|
||||
|
||||
lowpan_frags.hashfn = lowpan_hashfn;
|
||||
lowpan_frags.constructor = lowpan_frag_init;
|
||||
lowpan_frags.destructor = NULL;
|
||||
lowpan_frags.skb_free = NULL;
|
||||
lowpan_frags.qsize = sizeof(struct frag_queue);
|
||||
lowpan_frags.match = lowpan_frag_match;
|
||||
lowpan_frags.frag_expire = lowpan_frag_expire;
|
||||
lowpan_frags.frags_cache_name = lowpan_frags_cache_name;
|
||||
ret = inet_frags_init(&lowpan_frags);
|
||||
if (ret)
|
||||
goto err_pernet;
|
||||
|
||||
return ret;
|
||||
err_pernet:
|
||||
lowpan_frags_sysctl_unregister();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void lowpan_net_frag_exit(void)
|
||||
{
|
||||
inet_frags_fini(&lowpan_frags);
|
||||
lowpan_frags_sysctl_unregister();
|
||||
unregister_pernet_subsys(&lowpan_frags_ops);
|
||||
}
|
41
net/ieee802154/reassembly.h
Normal file
41
net/ieee802154/reassembly.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef __IEEE802154_6LOWPAN_REASSEMBLY_H__
|
||||
#define __IEEE802154_6LOWPAN_REASSEMBLY_H__
|
||||
|
||||
#include <net/inet_frag.h>
|
||||
|
||||
struct lowpan_create_arg {
|
||||
__be16 tag;
|
||||
u16 d_size;
|
||||
const struct ieee802154_addr *src;
|
||||
const struct ieee802154_addr *dst;
|
||||
};
|
||||
|
||||
/* Equivalent of ipv4 struct ip
|
||||
*/
|
||||
struct lowpan_frag_queue {
|
||||
struct inet_frag_queue q;
|
||||
|
||||
__be16 tag;
|
||||
u16 d_size;
|
||||
struct ieee802154_addr saddr;
|
||||
struct ieee802154_addr daddr;
|
||||
};
|
||||
|
||||
static inline u32 ieee802154_addr_hash(const struct ieee802154_addr *a)
|
||||
{
|
||||
switch (a->mode) {
|
||||
case IEEE802154_ADDR_LONG:
|
||||
return (((__force u64)a->extended_addr) >> 32) ^
|
||||
(((__force u64)a->extended_addr) & 0xffffffff);
|
||||
case IEEE802154_ADDR_SHORT:
|
||||
return (__force u32)(a->short_addr);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int lowpan_frag_rcv(struct sk_buff *skb, const u8 frag_type);
|
||||
void lowpan_net_frag_exit(void);
|
||||
int lowpan_net_frag_init(void);
|
||||
|
||||
#endif /* __IEEE802154_6LOWPAN_REASSEMBLY_H__ */
|
230
net/ieee802154/wpan-class.c
Normal file
230
net/ieee802154/wpan-class.c
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright (C) 2007, 2008, 2009 Siemens AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include <net/wpan-phy.h>
|
||||
|
||||
#include "ieee802154.h"
|
||||
|
||||
#define MASTER_SHOW_COMPLEX(name, format_string, args...) \
|
||||
static ssize_t name ## _show(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct wpan_phy *phy = container_of(dev, struct wpan_phy, dev); \
|
||||
int ret; \
|
||||
\
|
||||
mutex_lock(&phy->pib_lock); \
|
||||
ret = snprintf(buf, PAGE_SIZE, format_string "\n", args); \
|
||||
mutex_unlock(&phy->pib_lock); \
|
||||
return ret; \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(name);
|
||||
|
||||
#define MASTER_SHOW(field, format_string) \
|
||||
MASTER_SHOW_COMPLEX(field, format_string, phy->field)
|
||||
|
||||
MASTER_SHOW(current_channel, "%d");
|
||||
MASTER_SHOW(current_page, "%d");
|
||||
MASTER_SHOW(transmit_power, "%d +- 1 dB");
|
||||
MASTER_SHOW(cca_mode, "%d");
|
||||
|
||||
static ssize_t channels_supported_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct wpan_phy *phy = container_of(dev, struct wpan_phy, dev);
|
||||
int ret;
|
||||
int i, len = 0;
|
||||
|
||||
mutex_lock(&phy->pib_lock);
|
||||
for (i = 0; i < 32; i++) {
|
||||
ret = snprintf(buf + len, PAGE_SIZE - len,
|
||||
"%#09x\n", phy->channels_supported[i]);
|
||||
if (ret < 0)
|
||||
break;
|
||||
len += ret;
|
||||
}
|
||||
mutex_unlock(&phy->pib_lock);
|
||||
return len;
|
||||
}
|
||||
static DEVICE_ATTR_RO(channels_supported);
|
||||
|
||||
static struct attribute *pmib_attrs[] = {
|
||||
&dev_attr_current_channel.attr,
|
||||
&dev_attr_current_page.attr,
|
||||
&dev_attr_channels_supported.attr,
|
||||
&dev_attr_transmit_power.attr,
|
||||
&dev_attr_cca_mode.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(pmib);
|
||||
|
||||
static void wpan_phy_release(struct device *d)
|
||||
{
|
||||
struct wpan_phy *phy = container_of(d, struct wpan_phy, dev);
|
||||
|
||||
kfree(phy);
|
||||
}
|
||||
|
||||
static struct class wpan_phy_class = {
|
||||
.name = "ieee802154",
|
||||
.dev_release = wpan_phy_release,
|
||||
.dev_groups = pmib_groups,
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(wpan_phy_mutex);
|
||||
static int wpan_phy_idx;
|
||||
|
||||
static int wpan_phy_match(struct device *dev, const void *data)
|
||||
{
|
||||
return !strcmp(dev_name(dev), (const char *)data);
|
||||
}
|
||||
|
||||
struct wpan_phy *wpan_phy_find(const char *str)
|
||||
{
|
||||
struct device *dev;
|
||||
|
||||
if (WARN_ON(!str))
|
||||
return NULL;
|
||||
|
||||
dev = class_find_device(&wpan_phy_class, NULL, str, wpan_phy_match);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
return container_of(dev, struct wpan_phy, dev);
|
||||
}
|
||||
EXPORT_SYMBOL(wpan_phy_find);
|
||||
|
||||
struct wpan_phy_iter_data {
|
||||
int (*fn)(struct wpan_phy *phy, void *data);
|
||||
void *data;
|
||||
};
|
||||
|
||||
static int wpan_phy_iter(struct device *dev, void *_data)
|
||||
{
|
||||
struct wpan_phy_iter_data *wpid = _data;
|
||||
struct wpan_phy *phy = container_of(dev, struct wpan_phy, dev);
|
||||
|
||||
return wpid->fn(phy, wpid->data);
|
||||
}
|
||||
|
||||
int wpan_phy_for_each(int (*fn)(struct wpan_phy *phy, void *data),
|
||||
void *data)
|
||||
{
|
||||
struct wpan_phy_iter_data wpid = {
|
||||
.fn = fn,
|
||||
.data = data,
|
||||
};
|
||||
|
||||
return class_for_each_device(&wpan_phy_class, NULL,
|
||||
&wpid, wpan_phy_iter);
|
||||
}
|
||||
EXPORT_SYMBOL(wpan_phy_for_each);
|
||||
|
||||
static int wpan_phy_idx_valid(int idx)
|
||||
{
|
||||
return idx >= 0;
|
||||
}
|
||||
|
||||
struct wpan_phy *wpan_phy_alloc(size_t priv_size)
|
||||
{
|
||||
struct wpan_phy *phy = kzalloc(sizeof(*phy) + priv_size,
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!phy)
|
||||
goto out;
|
||||
mutex_lock(&wpan_phy_mutex);
|
||||
phy->idx = wpan_phy_idx++;
|
||||
if (unlikely(!wpan_phy_idx_valid(phy->idx))) {
|
||||
wpan_phy_idx--;
|
||||
mutex_unlock(&wpan_phy_mutex);
|
||||
kfree(phy);
|
||||
goto out;
|
||||
}
|
||||
mutex_unlock(&wpan_phy_mutex);
|
||||
|
||||
mutex_init(&phy->pib_lock);
|
||||
|
||||
device_initialize(&phy->dev);
|
||||
dev_set_name(&phy->dev, "wpan-phy%d", phy->idx);
|
||||
|
||||
phy->dev.class = &wpan_phy_class;
|
||||
|
||||
phy->current_channel = -1; /* not initialised */
|
||||
phy->current_page = 0; /* for compatibility */
|
||||
|
||||
return phy;
|
||||
|
||||
out:
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(wpan_phy_alloc);
|
||||
|
||||
int wpan_phy_register(struct wpan_phy *phy)
|
||||
{
|
||||
return device_add(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(wpan_phy_register);
|
||||
|
||||
void wpan_phy_unregister(struct wpan_phy *phy)
|
||||
{
|
||||
device_del(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(wpan_phy_unregister);
|
||||
|
||||
void wpan_phy_free(struct wpan_phy *phy)
|
||||
{
|
||||
put_device(&phy->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(wpan_phy_free);
|
||||
|
||||
static int __init wpan_phy_class_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = class_register(&wpan_phy_class);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
rc = ieee802154_nl_init();
|
||||
if (rc)
|
||||
goto err_nl;
|
||||
|
||||
return 0;
|
||||
err_nl:
|
||||
class_unregister(&wpan_phy_class);
|
||||
err:
|
||||
return rc;
|
||||
}
|
||||
subsys_initcall(wpan_phy_class_init);
|
||||
|
||||
static void __exit wpan_phy_class_exit(void)
|
||||
{
|
||||
ieee802154_nl_exit();
|
||||
class_unregister(&wpan_phy_class);
|
||||
}
|
||||
module_exit(wpan_phy_class_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("IEEE 802.15.4 configuration interface");
|
||||
MODULE_AUTHOR("Dmitry Eremin-Solenikov");
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue