From 89745c9c41b093e4041f290343e8916f74f26051 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:43 +0100 Subject: 6lowpan: add frag information struct This patch adds a 6lowpan fragmentation struct into cb of skb which is necessary to hold fragmentation information. Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- include/net/ieee802154_netdev.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/net/ieee802154_netdev.h b/include/net/ieee802154_netdev.h index 8196d5d40359..97b2e34d87f7 100644 --- a/include/net/ieee802154_netdev.h +++ b/include/net/ieee802154_netdev.h @@ -29,6 +29,12 @@ #include +struct ieee802154_frag_info { + __be16 d_tag; + u16 d_size; + u8 d_offset; +}; + /* * A control block of skb passed between the ARPHRD_IEEE802154 device * and other stack parts. @@ -39,6 +45,7 @@ struct ieee802154_mac_cb { struct ieee802154_addr da; u8 flags; u8 seq; + struct ieee802154_frag_info frag_info; }; static inline struct ieee802154_mac_cb *mac_cb(struct sk_buff *skb) -- cgit v1.2.3-59-g8ed1b From 349aa7bc29ee2469f94bba97bb1c9c270fffa215 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:44 +0100 Subject: 6lowpan: add uncompress header size function This patch add a lookup function for uncompressed 6LoWPAN header size. This is needed to estimate the real size after uncompress the 6LoWPAN header. Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- net/ieee802154/6lowpan.h | 113 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/net/ieee802154/6lowpan.h b/net/ieee802154/6lowpan.h index 2b835db3bda8..0dccf62434d5 100644 --- a/net/ieee802154/6lowpan.h +++ b/net/ieee802154/6lowpan.h @@ -306,6 +306,119 @@ static inline void lowpan_push_hc_data(u8 **hc_ptr, const void *data, *hc_ptr += len; } +static inline u8 lowpan_addr_mode_size(const u8 addr_mode) +{ + static const u8 addr_sizes[] = { + [LOWPAN_IPHC_ADDR_00] = 16, + [LOWPAN_IPHC_ADDR_01] = 8, + [LOWPAN_IPHC_ADDR_02] = 2, + [LOWPAN_IPHC_ADDR_03] = 0, + }; + return addr_sizes[addr_mode]; +} + +static inline u8 lowpan_next_hdr_size(const u8 h_enc, u16 *uncomp_header) +{ + u8 ret = 1; + + if ((h_enc & LOWPAN_NHC_UDP_MASK) == LOWPAN_NHC_UDP_ID) { + *uncomp_header += sizeof(struct udphdr); + + switch (h_enc & LOWPAN_NHC_UDP_CS_P_11) { + case LOWPAN_NHC_UDP_CS_P_00: + ret += 4; + break; + case LOWPAN_NHC_UDP_CS_P_01: + case LOWPAN_NHC_UDP_CS_P_10: + ret += 3; + break; + case LOWPAN_NHC_UDP_CS_P_11: + ret++; + break; + default: + break; + } + + if (!(h_enc & LOWPAN_NHC_UDP_CS_C)) + ret += 2; + } + + return ret; +} + +/** + * lowpan_uncompress_size - returns skb->len size with uncompressed header + * @skb: sk_buff with 6lowpan header inside + * @datagram_offset: optional to get the datagram_offset value + * + * Returns the skb->len with uncompressed header + */ +static inline u16 +lowpan_uncompress_size(const struct sk_buff *skb, u16 *dgram_offset) +{ + u16 ret = 2, uncomp_header = sizeof(struct ipv6hdr); + u8 iphc0, iphc1, h_enc; + + iphc0 = skb_network_header(skb)[0]; + iphc1 = skb_network_header(skb)[1]; + + switch ((iphc0 & LOWPAN_IPHC_TF) >> 3) { + case 0: + ret += 4; + break; + case 1: + ret += 3; + break; + case 2: + ret++; + break; + default: + break; + } + + if (!(iphc0 & LOWPAN_IPHC_NH_C)) + ret++; + + if (!(iphc0 & 0x03)) + ret++; + + ret += lowpan_addr_mode_size((iphc1 & LOWPAN_IPHC_SAM) >> + LOWPAN_IPHC_SAM_BIT); + + if (iphc1 & LOWPAN_IPHC_M) { + switch ((iphc1 & LOWPAN_IPHC_DAM_11) >> + LOWPAN_IPHC_DAM_BIT) { + case LOWPAN_IPHC_DAM_00: + ret += 16; + break; + case LOWPAN_IPHC_DAM_01: + ret += 6; + break; + case LOWPAN_IPHC_DAM_10: + ret += 4; + break; + case LOWPAN_IPHC_DAM_11: + ret++; + break; + default: + break; + } + } else { + ret += lowpan_addr_mode_size((iphc1 & LOWPAN_IPHC_DAM_11) >> + LOWPAN_IPHC_DAM_BIT); + } + + if (iphc0 & LOWPAN_IPHC_NH_C) { + h_enc = skb_network_header(skb)[ret]; + ret += lowpan_next_hdr_size(h_enc, &uncomp_header); + } + + if (dgram_offset) + *dgram_offset = uncomp_header; + + return skb->len + uncomp_header - ret; +} + typedef int (*skb_delivery_cb)(struct sk_buff *skb, struct net_device *dev); int lowpan_process_data(struct sk_buff *skb, struct net_device *dev, -- cgit v1.2.3-59-g8ed1b From 96cb3eb7a1a5f0c3598500a2348f7d2cc76afbd2 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:45 +0100 Subject: 6lowpan: fix fragmentation on sending side This patch fix the fragmentation on sending side according to rfc4944. Also add improvement to use the full payload of a PDU which calculate the nearest divided to 8 payload length for the fragmentation datagram size attribute. The main issue is that the datagram size of fragmentation header use the ipv6 payload length, but rfc4944 says it's the ipv6 payload length inclusive network header size (and transport header size if compressed). Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- net/ieee802154/6lowpan.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/net/ieee802154/6lowpan.c b/net/ieee802154/6lowpan.c index 8edfea5da572..872c8f97a30c 100644 --- a/net/ieee802154/6lowpan.c +++ b/net/ieee802154/6lowpan.c @@ -422,44 +422,60 @@ lowpan_fragment_xmit(struct sk_buff *skb, u8 *head, static int lowpan_skb_fragmentation(struct sk_buff *skb, struct net_device *dev) { - int err, header_length, payload_length, tag, offset = 0; + int err; + u16 dgram_offset, dgram_size, payload_length, header_length, + lowpan_size, frag_plen, offset, tag; u8 head[5]; header_length = skb->mac_len; payload_length = skb->len - header_length; tag = lowpan_dev_info(dev)->fragment_tag++; + lowpan_size = skb_network_header_len(skb); + dgram_size = lowpan_uncompress_size(skb, &dgram_offset) - + header_length; /* first fragment header */ - head[0] = LOWPAN_DISPATCH_FRAG1 | ((payload_length >> 8) & 0x7); - head[1] = payload_length & 0xff; + head[0] = LOWPAN_DISPATCH_FRAG1 | ((dgram_size >> 8) & 0x7); + head[1] = dgram_size & 0xff; head[2] = tag >> 8; head[3] = tag & 0xff; - err = lowpan_fragment_xmit(skb, head, header_length, LOWPAN_FRAG_SIZE, - 0, LOWPAN_DISPATCH_FRAG1); + /* calc the nearest payload length(divided to 8) for first fragment + * which fits into a IEEE802154_MTU + */ + frag_plen = round_down(IEEE802154_MTU - header_length - + LOWPAN_FRAG1_HEAD_SIZE - lowpan_size - + IEEE802154_MFR_SIZE, 8); + err = lowpan_fragment_xmit(skb, head, header_length, + frag_plen + lowpan_size, 0, + LOWPAN_DISPATCH_FRAG1); if (err) { pr_debug("%s unable to send FRAG1 packet (tag: %d)", __func__, tag); goto exit; } - offset = LOWPAN_FRAG_SIZE; + offset = lowpan_size + frag_plen; + dgram_offset += frag_plen; /* next fragment header */ head[0] &= ~LOWPAN_DISPATCH_FRAG1; head[0] |= LOWPAN_DISPATCH_FRAGN; + frag_plen = round_down(IEEE802154_MTU - header_length - + LOWPAN_FRAGN_HEAD_SIZE - IEEE802154_MFR_SIZE, 8); + while (payload_length - offset > 0) { - int len = LOWPAN_FRAG_SIZE; + int len = frag_plen; - head[4] = offset / 8; + head[4] = dgram_offset >> 3; if (payload_length - offset < len) len = payload_length - offset; - err = lowpan_fragment_xmit(skb, head, header_length, - len, offset, LOWPAN_DISPATCH_FRAGN); + err = lowpan_fragment_xmit(skb, head, header_length, len, + offset, LOWPAN_DISPATCH_FRAGN); if (err) { pr_debug("%s unable to send a subsequent FRAGN packet " "(tag: %d, offset: %d", __func__, tag, offset); @@ -467,6 +483,7 @@ lowpan_skb_fragmentation(struct sk_buff *skb, struct net_device *dev) } offset += len; + dgram_offset += len; } exit: -- cgit v1.2.3-59-g8ed1b From 02600d0de677b70305393af770a5a46be514e5b8 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:46 +0100 Subject: 6lowpan: change tag type to __be16 Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- net/ieee802154/6lowpan.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/net/ieee802154/6lowpan.c b/net/ieee802154/6lowpan.c index 872c8f97a30c..6b7d17fd5481 100644 --- a/net/ieee802154/6lowpan.c +++ b/net/ieee802154/6lowpan.c @@ -68,7 +68,7 @@ static LIST_HEAD(lowpan_devices); struct lowpan_dev_info { struct net_device *real_dev; /* real WPAN device ptr */ struct mutex dev_list_mtx; /* mutex for list ops */ - unsigned short fragment_tag; + __be16 fragment_tag; }; struct lowpan_dev_record { @@ -424,7 +424,8 @@ lowpan_skb_fragmentation(struct sk_buff *skb, struct net_device *dev) { int err; u16 dgram_offset, dgram_size, payload_length, header_length, - lowpan_size, frag_plen, offset, tag; + lowpan_size, frag_plen, offset; + __be16 tag; u8 head[5]; header_length = skb->mac_len; -- cgit v1.2.3-59-g8ed1b From 01348b34485eceace5d9ca9756ba40679cf22ac6 Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:47 +0100 Subject: 6lowpan: move 6lowpan.c to 6lowpan_rtnl.c We have a 6lowpan.c file and 6lowpan.ko file. To avoid confusing we should move 6lowpan.c to 6lowpan_rtnl.c. Then we can support multiple source files for 6lowpan module. Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- net/ieee802154/6lowpan.c | 832 ------------------------------------------ net/ieee802154/6lowpan_rtnl.c | 832 ++++++++++++++++++++++++++++++++++++++++++ net/ieee802154/Makefile | 1 + 3 files changed, 833 insertions(+), 832 deletions(-) delete mode 100644 net/ieee802154/6lowpan.c create mode 100644 net/ieee802154/6lowpan_rtnl.c diff --git a/net/ieee802154/6lowpan.c b/net/ieee802154/6lowpan.c deleted file mode 100644 index 6b7d17fd5481..000000000000 --- a/net/ieee802154/6lowpan.c +++ /dev/null @@ -1,832 +0,0 @@ -/* - * Copyright 2011, Siemens AG - * written by Alexander Smirnov - */ - -/* - * Based on patches from Jon Smirl - * Copyright (c) 2011 Jon Smirl - * - * 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. - */ - -/* 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include "6lowpan.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; -}; - -struct lowpan_fragment { - struct sk_buff *skb; /* skb to be assembled */ - u16 length; /* length to be assemled */ - u32 bytes_rcv; /* bytes received */ - u16 tag; /* current fragment tag */ - struct timer_list timer; /* assembling timer */ - struct list_head list; /* fragments list */ -}; - -static LIST_HEAD(lowpan_fragments); -static DEFINE_SPINLOCK(flist_lock); - -static inline struct -lowpan_dev_info *lowpan_dev_info(const struct net_device *dev) -{ - return netdev_priv(dev); -} - -static inline void lowpan_address_flip(u8 *src, u8 *dest) -{ - int i; - for (i = 0; i < IEEE802154_ADDR_LEN; i++) - (dest)[IEEE802154_ADDR_LEN - i - 1] = (src)[i]; -} - -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 ieee802154_addr sa, da; - - /* 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); - - lowpan_header_compress(skb, dev, type, daddr, saddr, len); - - /* - * NOTE1: I'm still unsure about the fact that compression and WPAN - * header are created here and not later in the xmit. So wait for - * an opinion of net maintainers. - */ - /* - * NOTE2: to be absolutely correct, we must derive PANid information - * from MAC subif of the 'dev' and 'real_dev' network devices, but - * this isn't implemented in mainline yet, so currently we assign 0xff - */ - mac_cb(skb)->flags = IEEE802154_FC_TYPE_DATA; - mac_cb(skb)->seq = ieee802154_mlme_ops(dev)->get_dsn(dev); - - /* prepare wpan address data */ - sa.addr_type = IEEE802154_ADDR_LONG; - sa.pan_id = ieee802154_mlme_ops(dev)->get_pan_id(dev); - - memcpy(&(sa.hwaddr), saddr, 8); - /* intra-PAN communications */ - da.pan_id = ieee802154_mlme_ops(dev)->get_pan_id(dev); - - /* - * if the destination address is the broadcast address, use the - * corresponding short address - */ - if (lowpan_is_addr_broadcast(daddr)) { - da.addr_type = IEEE802154_ADDR_SHORT; - da.short_addr = IEEE802154_ADDR_BROADCAST; - } else { - da.addr_type = IEEE802154_ADDR_LONG; - memcpy(&(da.hwaddr), daddr, IEEE802154_ADDR_LEN); - - /* request acknowledgment */ - mac_cb(skb)->flags |= MAC_CB_FLAG_ACKREQ; - } - - return dev_hard_header(skb, lowpan_dev_info(dev)->real_dev, - type, (void *)&da, (void *)&sa, skb->len); -} - -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 void lowpan_fragment_timer_expired(unsigned long entry_addr) -{ - struct lowpan_fragment *entry = (struct lowpan_fragment *)entry_addr; - - pr_debug("timer expired for frame with tag %d\n", entry->tag); - - list_del(&entry->list); - dev_kfree_skb(entry->skb); - kfree(entry); -} - -static struct lowpan_fragment * -lowpan_alloc_new_frame(struct sk_buff *skb, u16 len, u16 tag) -{ - struct lowpan_fragment *frame; - - frame = kzalloc(sizeof(struct lowpan_fragment), - GFP_ATOMIC); - if (!frame) - goto frame_err; - - INIT_LIST_HEAD(&frame->list); - - frame->length = len; - frame->tag = tag; - - /* allocate buffer for frame assembling */ - frame->skb = netdev_alloc_skb_ip_align(skb->dev, frame->length + - sizeof(struct ipv6hdr)); - - if (!frame->skb) - goto skb_err; - - frame->skb->priority = skb->priority; - - /* reserve headroom for uncompressed ipv6 header */ - skb_reserve(frame->skb, sizeof(struct ipv6hdr)); - skb_put(frame->skb, frame->length); - - /* copy the first control block to keep a - * trace of the link-layer addresses in case - * of a link-local compressed address - */ - memcpy(frame->skb->cb, skb->cb, sizeof(skb->cb)); - - init_timer(&frame->timer); - /* time out is the same as for ipv6 - 60 sec */ - frame->timer.expires = jiffies + LOWPAN_FRAG_TIMEOUT; - frame->timer.data = (unsigned long)frame; - frame->timer.function = lowpan_fragment_timer_expired; - - add_timer(&frame->timer); - - list_add_tail(&frame->list, &lowpan_fragments); - - return frame; - -skb_err: - kfree(frame); -frame_err: - return NULL; -} - -static int process_data(struct sk_buff *skb) -{ - u8 iphc0, iphc1; - const struct ieee802154_addr *_saddr, *_daddr; - - 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; - - /* fragments assembling */ - switch (iphc0 & LOWPAN_DISPATCH_MASK) { - case LOWPAN_DISPATCH_FRAG1: - case LOWPAN_DISPATCH_FRAGN: - { - struct lowpan_fragment *frame; - /* slen stores the rightmost 8 bits of the 11 bits length */ - u8 slen, offset = 0; - u16 len, tag; - bool found = false; - - if (lowpan_fetch_skb_u8(skb, &slen) || /* frame length */ - lowpan_fetch_skb_u16(skb, &tag)) /* fragment tag */ - goto drop; - - /* adds the 3 MSB to the 8 LSB to retrieve the 11 bits length */ - len = ((iphc0 & 7) << 8) | slen; - - if ((iphc0 & LOWPAN_DISPATCH_MASK) == LOWPAN_DISPATCH_FRAG1) { - pr_debug("%s received a FRAG1 packet (tag: %d, " - "size of the entire IP packet: %d)", - __func__, tag, len); - } else { /* FRAGN */ - if (lowpan_fetch_skb_u8(skb, &offset)) - goto unlock_and_drop; - pr_debug("%s received a FRAGN packet (tag: %d, " - "size of the entire IP packet: %d, " - "offset: %d)", __func__, tag, len, offset * 8); - } - - /* - * check if frame assembling with the same tag is - * already in progress - */ - spin_lock_bh(&flist_lock); - - list_for_each_entry(frame, &lowpan_fragments, list) - if (frame->tag == tag) { - found = true; - break; - } - - /* alloc new frame structure */ - if (!found) { - pr_debug("%s first fragment received for tag %d, " - "begin packet reassembly", __func__, tag); - frame = lowpan_alloc_new_frame(skb, len, tag); - if (!frame) - goto unlock_and_drop; - } - - /* if payload fits buffer, copy it */ - if (likely((offset * 8 + skb->len) <= frame->length)) - skb_copy_to_linear_data_offset(frame->skb, offset * 8, - skb->data, skb->len); - else - goto unlock_and_drop; - - frame->bytes_rcv += skb->len; - - /* frame assembling complete */ - if ((frame->bytes_rcv == frame->length) && - frame->timer.expires > jiffies) { - /* if timer haven't expired - first of all delete it */ - del_timer_sync(&frame->timer); - list_del(&frame->list); - spin_unlock_bh(&flist_lock); - - pr_debug("%s successfully reassembled fragment " - "(tag %d)", __func__, tag); - - dev_kfree_skb(skb); - skb = frame->skb; - kfree(frame); - - if (lowpan_fetch_skb_u8(skb, &iphc0)) - goto drop; - - break; - } - spin_unlock_bh(&flist_lock); - - return kfree_skb(skb), 0; - } - default: - break; - } - - if (lowpan_fetch_skb_u8(skb, &iphc1)) - goto drop; - - _saddr = &mac_cb(skb)->sa; - _daddr = &mac_cb(skb)->da; - - return lowpan_process_data(skb, skb->dev, (u8 *)_saddr->hwaddr, - _saddr->addr_type, IEEE802154_ADDR_LEN, - (u8 *)_daddr->hwaddr, _daddr->addr_type, - IEEE802154_ADDR_LEN, iphc0, iphc1, - lowpan_give_skb_to_devices); - -unlock_and_drop: - spin_unlock_bh(&flist_lock); -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 int -lowpan_fragment_xmit(struct sk_buff *skb, u8 *head, - int mlen, int plen, int offset, int type) -{ - struct sk_buff *frag; - int hlen; - - hlen = (type == LOWPAN_DISPATCH_FRAG1) ? - LOWPAN_FRAG1_HEAD_SIZE : LOWPAN_FRAGN_HEAD_SIZE; - - raw_dump_inline(__func__, "6lowpan fragment header", head, hlen); - - frag = netdev_alloc_skb(skb->dev, - hlen + mlen + plen + IEEE802154_MFR_SIZE); - if (!frag) - return -ENOMEM; - - frag->priority = skb->priority; - - /* copy header, MFR and payload */ - skb_put(frag, mlen); - skb_copy_to_linear_data(frag, skb_mac_header(skb), mlen); - - skb_put(frag, hlen); - skb_copy_to_linear_data_offset(frag, mlen, head, hlen); - - skb_put(frag, plen); - skb_copy_to_linear_data_offset(frag, mlen + hlen, - skb_network_header(skb) + offset, plen); - - raw_dump_table(__func__, " raw fragment dump", frag->data, frag->len); - - return dev_queue_xmit(frag); -} - -static int -lowpan_skb_fragmentation(struct sk_buff *skb, struct net_device *dev) -{ - int err; - u16 dgram_offset, dgram_size, payload_length, header_length, - lowpan_size, frag_plen, offset; - __be16 tag; - u8 head[5]; - - header_length = skb->mac_len; - payload_length = skb->len - header_length; - tag = lowpan_dev_info(dev)->fragment_tag++; - lowpan_size = skb_network_header_len(skb); - dgram_size = lowpan_uncompress_size(skb, &dgram_offset) - - header_length; - - /* first fragment header */ - head[0] = LOWPAN_DISPATCH_FRAG1 | ((dgram_size >> 8) & 0x7); - head[1] = dgram_size & 0xff; - head[2] = tag >> 8; - head[3] = tag & 0xff; - - /* calc the nearest payload length(divided to 8) for first fragment - * which fits into a IEEE802154_MTU - */ - frag_plen = round_down(IEEE802154_MTU - header_length - - LOWPAN_FRAG1_HEAD_SIZE - lowpan_size - - IEEE802154_MFR_SIZE, 8); - - err = lowpan_fragment_xmit(skb, head, header_length, - frag_plen + lowpan_size, 0, - LOWPAN_DISPATCH_FRAG1); - if (err) { - pr_debug("%s unable to send FRAG1 packet (tag: %d)", - __func__, tag); - goto exit; - } - - offset = lowpan_size + frag_plen; - dgram_offset += frag_plen; - - /* next fragment header */ - head[0] &= ~LOWPAN_DISPATCH_FRAG1; - head[0] |= LOWPAN_DISPATCH_FRAGN; - - frag_plen = round_down(IEEE802154_MTU - header_length - - LOWPAN_FRAGN_HEAD_SIZE - IEEE802154_MFR_SIZE, 8); - - while (payload_length - offset > 0) { - int len = frag_plen; - - head[4] = dgram_offset >> 3; - - if (payload_length - offset < len) - len = payload_length - offset; - - err = lowpan_fragment_xmit(skb, head, header_length, len, - offset, LOWPAN_DISPATCH_FRAGN); - if (err) { - pr_debug("%s unable to send a subsequent FRAGN packet " - "(tag: %d, offset: %d", __func__, tag, offset); - goto exit; - } - - offset += len; - dgram_offset += len; - } - -exit: - return err; -} - -static netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *dev) -{ - int err = -1; - - pr_debug("package xmit\n"); - - skb->dev = lowpan_dev_info(dev)->real_dev; - if (skb->dev == NULL) { - pr_debug("ERROR: no real wpan device found\n"); - goto error; - } - - /* Send directly if less than the MTU minus the 2 checksum bytes. */ - if (skb->len <= IEEE802154_MTU - IEEE802154_MFR_SIZE) { - err = dev_queue_xmit(skb); - goto out; - } - - pr_debug("frame is too big, fragmentation is needed\n"); - err = lowpan_skb_fragmentation(skb, dev); -error: - dev_kfree_skb(skb); -out: - if (err) - pr_debug("ERROR: xmit failed\n"); - - return (err < 0) ? NET_XMIT_DROP : err; -} - -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 u16 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 u16 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 = 1281; - 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 sk_buff *local_skb; - - if (!netif_running(dev)) - goto drop; - - if (dev->type != ARPHRD_IEEE802154) - goto drop; - - /* check that it's our buffer */ - if (skb->data[0] == LOWPAN_DISPATCH_IPV6) { - /* Copy the packet so that the IPv6 header is - * properly aligned. - */ - local_skb = skb_copy_expand(skb, NET_SKB_PAD - 1, - skb_tailroom(skb), GFP_ATOMIC); - if (!local_skb) - goto drop; - - local_skb->protocol = htons(ETH_P_IPV6); - local_skb->pkt_type = PACKET_HOST; - - /* Pull off the 1-byte of 6lowpan header. */ - skb_pull(local_skb, 1); - - lowpan_give_skb_to_devices(local_skb, NULL); - - kfree_skb(local_skb); - kfree_skb(skb); - } else { - switch (skb->data[0] & 0xe0) { - case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */ - case LOWPAN_DISPATCH_FRAG1: /* first fragment header */ - case LOWPAN_DISPATCH_FRAGN: /* next fragments headers */ - local_skb = skb_clone(skb, GFP_ATOMIC); - if (!local_skb) - goto drop; - process_data(local_skb); - - kfree_skb(skb); - break; - default: - break; - } - } - - return NET_RX_SUCCESS; - -drop: - kfree_skb(skb); - 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; - lowpan_dev_info(dev)->fragment_tag = 0; - mutex_init(&lowpan_dev_info(dev)->dev_list_mtx); - - entry = kzalloc(sizeof(struct lowpan_dev_record), 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 = __constant_htons(ETH_P_IEEE802154), - .func = lowpan_rcv, -}; - -static int __init lowpan_init_module(void) -{ - int err = 0; - - err = lowpan_netlink_init(); - if (err < 0) - goto out; - - dev_add_pack(&lowpan_packet_type); - - err = register_netdevice_notifier(&lowpan_dev_notifier); - if (err < 0) { - dev_remove_pack(&lowpan_packet_type); - lowpan_netlink_fini(); - } -out: - return err; -} - -static void __exit lowpan_cleanup_module(void) -{ - struct lowpan_fragment *frame, *tframe; - - lowpan_netlink_fini(); - - dev_remove_pack(&lowpan_packet_type); - - unregister_netdevice_notifier(&lowpan_dev_notifier); - - /* Now 6lowpan packet_type is removed, so no new fragments are - * expected on RX, therefore that's the time to clean incomplete - * fragments. - */ - spin_lock_bh(&flist_lock); - list_for_each_entry_safe(frame, tframe, &lowpan_fragments, list) { - del_timer_sync(&frame->timer); - list_del(&frame->list); - dev_kfree_skb(frame->skb); - kfree(frame); - } - spin_unlock_bh(&flist_lock); -} - -module_init(lowpan_init_module); -module_exit(lowpan_cleanup_module); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_RTNL_LINK("lowpan"); diff --git a/net/ieee802154/6lowpan_rtnl.c b/net/ieee802154/6lowpan_rtnl.c new file mode 100644 index 000000000000..6b7d17fd5481 --- /dev/null +++ b/net/ieee802154/6lowpan_rtnl.c @@ -0,0 +1,832 @@ +/* + * Copyright 2011, Siemens AG + * written by Alexander Smirnov + */ + +/* + * Based on patches from Jon Smirl + * Copyright (c) 2011 Jon Smirl + * + * 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. + */ + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "6lowpan.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; +}; + +struct lowpan_fragment { + struct sk_buff *skb; /* skb to be assembled */ + u16 length; /* length to be assemled */ + u32 bytes_rcv; /* bytes received */ + u16 tag; /* current fragment tag */ + struct timer_list timer; /* assembling timer */ + struct list_head list; /* fragments list */ +}; + +static LIST_HEAD(lowpan_fragments); +static DEFINE_SPINLOCK(flist_lock); + +static inline struct +lowpan_dev_info *lowpan_dev_info(const struct net_device *dev) +{ + return netdev_priv(dev); +} + +static inline void lowpan_address_flip(u8 *src, u8 *dest) +{ + int i; + for (i = 0; i < IEEE802154_ADDR_LEN; i++) + (dest)[IEEE802154_ADDR_LEN - i - 1] = (src)[i]; +} + +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 ieee802154_addr sa, da; + + /* 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); + + lowpan_header_compress(skb, dev, type, daddr, saddr, len); + + /* + * NOTE1: I'm still unsure about the fact that compression and WPAN + * header are created here and not later in the xmit. So wait for + * an opinion of net maintainers. + */ + /* + * NOTE2: to be absolutely correct, we must derive PANid information + * from MAC subif of the 'dev' and 'real_dev' network devices, but + * this isn't implemented in mainline yet, so currently we assign 0xff + */ + mac_cb(skb)->flags = IEEE802154_FC_TYPE_DATA; + mac_cb(skb)->seq = ieee802154_mlme_ops(dev)->get_dsn(dev); + + /* prepare wpan address data */ + sa.addr_type = IEEE802154_ADDR_LONG; + sa.pan_id = ieee802154_mlme_ops(dev)->get_pan_id(dev); + + memcpy(&(sa.hwaddr), saddr, 8); + /* intra-PAN communications */ + da.pan_id = ieee802154_mlme_ops(dev)->get_pan_id(dev); + + /* + * if the destination address is the broadcast address, use the + * corresponding short address + */ + if (lowpan_is_addr_broadcast(daddr)) { + da.addr_type = IEEE802154_ADDR_SHORT; + da.short_addr = IEEE802154_ADDR_BROADCAST; + } else { + da.addr_type = IEEE802154_ADDR_LONG; + memcpy(&(da.hwaddr), daddr, IEEE802154_ADDR_LEN); + + /* request acknowledgment */ + mac_cb(skb)->flags |= MAC_CB_FLAG_ACKREQ; + } + + return dev_hard_header(skb, lowpan_dev_info(dev)->real_dev, + type, (void *)&da, (void *)&sa, skb->len); +} + +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 void lowpan_fragment_timer_expired(unsigned long entry_addr) +{ + struct lowpan_fragment *entry = (struct lowpan_fragment *)entry_addr; + + pr_debug("timer expired for frame with tag %d\n", entry->tag); + + list_del(&entry->list); + dev_kfree_skb(entry->skb); + kfree(entry); +} + +static struct lowpan_fragment * +lowpan_alloc_new_frame(struct sk_buff *skb, u16 len, u16 tag) +{ + struct lowpan_fragment *frame; + + frame = kzalloc(sizeof(struct lowpan_fragment), + GFP_ATOMIC); + if (!frame) + goto frame_err; + + INIT_LIST_HEAD(&frame->list); + + frame->length = len; + frame->tag = tag; + + /* allocate buffer for frame assembling */ + frame->skb = netdev_alloc_skb_ip_align(skb->dev, frame->length + + sizeof(struct ipv6hdr)); + + if (!frame->skb) + goto skb_err; + + frame->skb->priority = skb->priority; + + /* reserve headroom for uncompressed ipv6 header */ + skb_reserve(frame->skb, sizeof(struct ipv6hdr)); + skb_put(frame->skb, frame->length); + + /* copy the first control block to keep a + * trace of the link-layer addresses in case + * of a link-local compressed address + */ + memcpy(frame->skb->cb, skb->cb, sizeof(skb->cb)); + + init_timer(&frame->timer); + /* time out is the same as for ipv6 - 60 sec */ + frame->timer.expires = jiffies + LOWPAN_FRAG_TIMEOUT; + frame->timer.data = (unsigned long)frame; + frame->timer.function = lowpan_fragment_timer_expired; + + add_timer(&frame->timer); + + list_add_tail(&frame->list, &lowpan_fragments); + + return frame; + +skb_err: + kfree(frame); +frame_err: + return NULL; +} + +static int process_data(struct sk_buff *skb) +{ + u8 iphc0, iphc1; + const struct ieee802154_addr *_saddr, *_daddr; + + 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; + + /* fragments assembling */ + switch (iphc0 & LOWPAN_DISPATCH_MASK) { + case LOWPAN_DISPATCH_FRAG1: + case LOWPAN_DISPATCH_FRAGN: + { + struct lowpan_fragment *frame; + /* slen stores the rightmost 8 bits of the 11 bits length */ + u8 slen, offset = 0; + u16 len, tag; + bool found = false; + + if (lowpan_fetch_skb_u8(skb, &slen) || /* frame length */ + lowpan_fetch_skb_u16(skb, &tag)) /* fragment tag */ + goto drop; + + /* adds the 3 MSB to the 8 LSB to retrieve the 11 bits length */ + len = ((iphc0 & 7) << 8) | slen; + + if ((iphc0 & LOWPAN_DISPATCH_MASK) == LOWPAN_DISPATCH_FRAG1) { + pr_debug("%s received a FRAG1 packet (tag: %d, " + "size of the entire IP packet: %d)", + __func__, tag, len); + } else { /* FRAGN */ + if (lowpan_fetch_skb_u8(skb, &offset)) + goto unlock_and_drop; + pr_debug("%s received a FRAGN packet (tag: %d, " + "size of the entire IP packet: %d, " + "offset: %d)", __func__, tag, len, offset * 8); + } + + /* + * check if frame assembling with the same tag is + * already in progress + */ + spin_lock_bh(&flist_lock); + + list_for_each_entry(frame, &lowpan_fragments, list) + if (frame->tag == tag) { + found = true; + break; + } + + /* alloc new frame structure */ + if (!found) { + pr_debug("%s first fragment received for tag %d, " + "begin packet reassembly", __func__, tag); + frame = lowpan_alloc_new_frame(skb, len, tag); + if (!frame) + goto unlock_and_drop; + } + + /* if payload fits buffer, copy it */ + if (likely((offset * 8 + skb->len) <= frame->length)) + skb_copy_to_linear_data_offset(frame->skb, offset * 8, + skb->data, skb->len); + else + goto unlock_and_drop; + + frame->bytes_rcv += skb->len; + + /* frame assembling complete */ + if ((frame->bytes_rcv == frame->length) && + frame->timer.expires > jiffies) { + /* if timer haven't expired - first of all delete it */ + del_timer_sync(&frame->timer); + list_del(&frame->list); + spin_unlock_bh(&flist_lock); + + pr_debug("%s successfully reassembled fragment " + "(tag %d)", __func__, tag); + + dev_kfree_skb(skb); + skb = frame->skb; + kfree(frame); + + if (lowpan_fetch_skb_u8(skb, &iphc0)) + goto drop; + + break; + } + spin_unlock_bh(&flist_lock); + + return kfree_skb(skb), 0; + } + default: + break; + } + + if (lowpan_fetch_skb_u8(skb, &iphc1)) + goto drop; + + _saddr = &mac_cb(skb)->sa; + _daddr = &mac_cb(skb)->da; + + return lowpan_process_data(skb, skb->dev, (u8 *)_saddr->hwaddr, + _saddr->addr_type, IEEE802154_ADDR_LEN, + (u8 *)_daddr->hwaddr, _daddr->addr_type, + IEEE802154_ADDR_LEN, iphc0, iphc1, + lowpan_give_skb_to_devices); + +unlock_and_drop: + spin_unlock_bh(&flist_lock); +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 int +lowpan_fragment_xmit(struct sk_buff *skb, u8 *head, + int mlen, int plen, int offset, int type) +{ + struct sk_buff *frag; + int hlen; + + hlen = (type == LOWPAN_DISPATCH_FRAG1) ? + LOWPAN_FRAG1_HEAD_SIZE : LOWPAN_FRAGN_HEAD_SIZE; + + raw_dump_inline(__func__, "6lowpan fragment header", head, hlen); + + frag = netdev_alloc_skb(skb->dev, + hlen + mlen + plen + IEEE802154_MFR_SIZE); + if (!frag) + return -ENOMEM; + + frag->priority = skb->priority; + + /* copy header, MFR and payload */ + skb_put(frag, mlen); + skb_copy_to_linear_data(frag, skb_mac_header(skb), mlen); + + skb_put(frag, hlen); + skb_copy_to_linear_data_offset(frag, mlen, head, hlen); + + skb_put(frag, plen); + skb_copy_to_linear_data_offset(frag, mlen + hlen, + skb_network_header(skb) + offset, plen); + + raw_dump_table(__func__, " raw fragment dump", frag->data, frag->len); + + return dev_queue_xmit(frag); +} + +static int +lowpan_skb_fragmentation(struct sk_buff *skb, struct net_device *dev) +{ + int err; + u16 dgram_offset, dgram_size, payload_length, header_length, + lowpan_size, frag_plen, offset; + __be16 tag; + u8 head[5]; + + header_length = skb->mac_len; + payload_length = skb->len - header_length; + tag = lowpan_dev_info(dev)->fragment_tag++; + lowpan_size = skb_network_header_len(skb); + dgram_size = lowpan_uncompress_size(skb, &dgram_offset) - + header_length; + + /* first fragment header */ + head[0] = LOWPAN_DISPATCH_FRAG1 | ((dgram_size >> 8) & 0x7); + head[1] = dgram_size & 0xff; + head[2] = tag >> 8; + head[3] = tag & 0xff; + + /* calc the nearest payload length(divided to 8) for first fragment + * which fits into a IEEE802154_MTU + */ + frag_plen = round_down(IEEE802154_MTU - header_length - + LOWPAN_FRAG1_HEAD_SIZE - lowpan_size - + IEEE802154_MFR_SIZE, 8); + + err = lowpan_fragment_xmit(skb, head, header_length, + frag_plen + lowpan_size, 0, + LOWPAN_DISPATCH_FRAG1); + if (err) { + pr_debug("%s unable to send FRAG1 packet (tag: %d)", + __func__, tag); + goto exit; + } + + offset = lowpan_size + frag_plen; + dgram_offset += frag_plen; + + /* next fragment header */ + head[0] &= ~LOWPAN_DISPATCH_FRAG1; + head[0] |= LOWPAN_DISPATCH_FRAGN; + + frag_plen = round_down(IEEE802154_MTU - header_length - + LOWPAN_FRAGN_HEAD_SIZE - IEEE802154_MFR_SIZE, 8); + + while (payload_length - offset > 0) { + int len = frag_plen; + + head[4] = dgram_offset >> 3; + + if (payload_length - offset < len) + len = payload_length - offset; + + err = lowpan_fragment_xmit(skb, head, header_length, len, + offset, LOWPAN_DISPATCH_FRAGN); + if (err) { + pr_debug("%s unable to send a subsequent FRAGN packet " + "(tag: %d, offset: %d", __func__, tag, offset); + goto exit; + } + + offset += len; + dgram_offset += len; + } + +exit: + return err; +} + +static netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *dev) +{ + int err = -1; + + pr_debug("package xmit\n"); + + skb->dev = lowpan_dev_info(dev)->real_dev; + if (skb->dev == NULL) { + pr_debug("ERROR: no real wpan device found\n"); + goto error; + } + + /* Send directly if less than the MTU minus the 2 checksum bytes. */ + if (skb->len <= IEEE802154_MTU - IEEE802154_MFR_SIZE) { + err = dev_queue_xmit(skb); + goto out; + } + + pr_debug("frame is too big, fragmentation is needed\n"); + err = lowpan_skb_fragmentation(skb, dev); +error: + dev_kfree_skb(skb); +out: + if (err) + pr_debug("ERROR: xmit failed\n"); + + return (err < 0) ? NET_XMIT_DROP : err; +} + +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 u16 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 u16 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 = 1281; + 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 sk_buff *local_skb; + + if (!netif_running(dev)) + goto drop; + + if (dev->type != ARPHRD_IEEE802154) + goto drop; + + /* check that it's our buffer */ + if (skb->data[0] == LOWPAN_DISPATCH_IPV6) { + /* Copy the packet so that the IPv6 header is + * properly aligned. + */ + local_skb = skb_copy_expand(skb, NET_SKB_PAD - 1, + skb_tailroom(skb), GFP_ATOMIC); + if (!local_skb) + goto drop; + + local_skb->protocol = htons(ETH_P_IPV6); + local_skb->pkt_type = PACKET_HOST; + + /* Pull off the 1-byte of 6lowpan header. */ + skb_pull(local_skb, 1); + + lowpan_give_skb_to_devices(local_skb, NULL); + + kfree_skb(local_skb); + kfree_skb(skb); + } else { + switch (skb->data[0] & 0xe0) { + case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */ + case LOWPAN_DISPATCH_FRAG1: /* first fragment header */ + case LOWPAN_DISPATCH_FRAGN: /* next fragments headers */ + local_skb = skb_clone(skb, GFP_ATOMIC); + if (!local_skb) + goto drop; + process_data(local_skb); + + kfree_skb(skb); + break; + default: + break; + } + } + + return NET_RX_SUCCESS; + +drop: + kfree_skb(skb); + 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; + lowpan_dev_info(dev)->fragment_tag = 0; + mutex_init(&lowpan_dev_info(dev)->dev_list_mtx); + + entry = kzalloc(sizeof(struct lowpan_dev_record), 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 = __constant_htons(ETH_P_IEEE802154), + .func = lowpan_rcv, +}; + +static int __init lowpan_init_module(void) +{ + int err = 0; + + err = lowpan_netlink_init(); + if (err < 0) + goto out; + + dev_add_pack(&lowpan_packet_type); + + err = register_netdevice_notifier(&lowpan_dev_notifier); + if (err < 0) { + dev_remove_pack(&lowpan_packet_type); + lowpan_netlink_fini(); + } +out: + return err; +} + +static void __exit lowpan_cleanup_module(void) +{ + struct lowpan_fragment *frame, *tframe; + + lowpan_netlink_fini(); + + dev_remove_pack(&lowpan_packet_type); + + unregister_netdevice_notifier(&lowpan_dev_notifier); + + /* Now 6lowpan packet_type is removed, so no new fragments are + * expected on RX, therefore that's the time to clean incomplete + * fragments. + */ + spin_lock_bh(&flist_lock); + list_for_each_entry_safe(frame, tframe, &lowpan_fragments, list) { + del_timer_sync(&frame->timer); + list_del(&frame->list); + dev_kfree_skb(frame->skb); + kfree(frame); + } + spin_unlock_bh(&flist_lock); +} + +module_init(lowpan_init_module); +module_exit(lowpan_cleanup_module); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_RTNL_LINK("lowpan"); diff --git a/net/ieee802154/Makefile b/net/ieee802154/Makefile index e8f05885ced6..3d08adfcd175 100644 --- a/net/ieee802154/Makefile +++ b/net/ieee802154/Makefile @@ -2,5 +2,6 @@ obj-$(CONFIG_IEEE802154) += ieee802154.o af_802154.o obj-$(CONFIG_IEEE802154_6LOWPAN) += 6lowpan.o obj-$(CONFIG_6LOWPAN_IPHC) += 6lowpan_iphc.o +6lowpan-y := 6lowpan_rtnl.o ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o wpan-class.o af_802154-y := af_ieee802154.o raw.o dgram.o -- cgit v1.2.3-59-g8ed1b From d57fec84fb103f2c96522f0c7d7a9fa49b50498f Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:48 +0100 Subject: 6lowpan: fix some checkpatch issues Detected with: ./scripts/checkpatch.pl --strict -f net/ieee802154/6lowpan_rtnl.c Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- net/ieee802154/6lowpan_rtnl.c | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/net/ieee802154/6lowpan_rtnl.c b/net/ieee802154/6lowpan_rtnl.c index 6b7d17fd5481..f9c954824ddb 100644 --- a/net/ieee802154/6lowpan_rtnl.c +++ b/net/ieee802154/6lowpan_rtnl.c @@ -1,10 +1,8 @@ -/* - * Copyright 2011, Siemens AG +/* Copyright 2011, Siemens AG * written by Alexander Smirnov */ -/* - * Based on patches from Jon Smirl +/* Based on patches from Jon Smirl * Copyright (c) 2011 Jon Smirl * * This program is free software; you can redistribute it and/or modify @@ -15,10 +13,6 @@ * 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. */ /* Jon's code is based on 6lowpan implementation for Contiki which is: @@ -124,13 +118,11 @@ static int lowpan_header_create(struct sk_buff *skb, lowpan_header_compress(skb, dev, type, daddr, saddr, len); - /* - * NOTE1: I'm still unsure about the fact that compression and WPAN + /* NOTE1: I'm still unsure about the fact that compression and WPAN * header are created here and not later in the xmit. So wait for * an opinion of net maintainers. */ - /* - * NOTE2: to be absolutely correct, we must derive PANid information + /* NOTE2: to be absolutely correct, we must derive PANid information * from MAC subif of the 'dev' and 'real_dev' network devices, but * this isn't implemented in mainline yet, so currently we assign 0xff */ @@ -145,8 +137,7 @@ static int lowpan_header_create(struct sk_buff *skb, /* intra-PAN communications */ da.pan_id = ieee802154_mlme_ops(dev)->get_pan_id(dev); - /* - * if the destination address is the broadcast address, use the + /* if the destination address is the broadcast address, use the * corresponding short address */ if (lowpan_is_addr_broadcast(daddr)) { @@ -386,7 +377,7 @@ static int lowpan_set_address(struct net_device *dev, void *p) static int lowpan_fragment_xmit(struct sk_buff *skb, u8 *head, - int mlen, int plen, int offset, int type) + int mlen, int plen, int offset, int type) { struct sk_buff *frag; int hlen; @@ -478,8 +469,8 @@ lowpan_skb_fragmentation(struct sk_buff *skb, struct net_device *dev) err = lowpan_fragment_xmit(skb, head, header_length, len, offset, LOWPAN_DISPATCH_FRAGN); if (err) { - pr_debug("%s unable to send a subsequent FRAGN packet " - "(tag: %d, offset: %d", __func__, tag, offset); + pr_debug("%s unable to send a FRAGN packet. (tag: %d, offset: %d)\n", + __func__, tag, offset); goto exit; } @@ -686,7 +677,7 @@ static int lowpan_newlink(struct net *src_net, struct net_device *dev, lowpan_dev_info(dev)->fragment_tag = 0; mutex_init(&lowpan_dev_info(dev)->dev_list_mtx); - entry = kzalloc(sizeof(struct lowpan_dev_record), GFP_KERNEL); + entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { dev_put(real_dev); lowpan_dev_info(dev)->real_dev = NULL; -- cgit v1.2.3-59-g8ed1b From 633fc86ff621bba79dcddfd4c67fb07ae5f8467c Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:49 +0100 Subject: net: ns: add ieee802154_6lowpan namespace This patch adds necessary ieee802154 6lowpan namespace to provide the inet_frag information. This is a initial support for handling 6lowpan fragmentation with the inet_frag api. Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- include/net/net_namespace.h | 4 ++++ include/net/netns/ieee802154_6lowpan.h | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 include/net/netns/ieee802154_6lowpan.h diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 991dcd94cbbf..79387f73f875 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,9 @@ struct net { #if IS_ENABLED(CONFIG_IPV6) struct netns_ipv6 ipv6; #endif +#if IS_ENABLED(CONFIG_IEEE802154_6LOWPAN) + struct netns_ieee802154_lowpan ieee802154_lowpan; +#endif #if defined(CONFIG_IP_SCTP) || defined(CONFIG_IP_SCTP_MODULE) struct netns_sctp sctp; #endif diff --git a/include/net/netns/ieee802154_6lowpan.h b/include/net/netns/ieee802154_6lowpan.h new file mode 100644 index 000000000000..88110b7e2428 --- /dev/null +++ b/include/net/netns/ieee802154_6lowpan.h @@ -0,0 +1,13 @@ +/* + * ieee802154 6lowpan in net namespaces + */ + +#include + +#ifndef __NETNS_IEEE802154_6LOWPAN_H__ +#define __NETNS_IEEE802154_6LOWPAN_H__ + +struct netns_ieee802154_lowpan { +}; + +#endif -- cgit v1.2.3-59-g8ed1b From 7240cdec60b136f3e64a453c7fbded4ed1aa047e Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Fri, 28 Feb 2014 07:32:50 +0100 Subject: 6lowpan: handling 6lowpan fragmentation via inet_frag api This patch drops the current way of 6lowpan fragmentation on receiving side and replace it with a implementation which use the inet_frag api. The old fragmentation handling has some race conditions and isn't rfc4944 compatible. Also adding support to match fragments on destination address, source address, tag value and datagram_size which is missing in the current implementation. Signed-off-by: Alexander Aring Signed-off-by: David S. Miller --- include/net/netns/ieee802154_6lowpan.h | 9 + net/ieee802154/6lowpan_rtnl.c | 256 +++------------ net/ieee802154/Makefile | 2 +- net/ieee802154/reassembly.c | 564 +++++++++++++++++++++++++++++++++ net/ieee802154/reassembly.h | 66 ++++ 5 files changed, 689 insertions(+), 208 deletions(-) create mode 100644 net/ieee802154/reassembly.c create mode 100644 net/ieee802154/reassembly.h diff --git a/include/net/netns/ieee802154_6lowpan.h b/include/net/netns/ieee802154_6lowpan.h index 88110b7e2428..079030c853d8 100644 --- a/include/net/netns/ieee802154_6lowpan.h +++ b/include/net/netns/ieee802154_6lowpan.h @@ -7,7 +7,16 @@ #ifndef __NETNS_IEEE802154_6LOWPAN_H__ #define __NETNS_IEEE802154_6LOWPAN_H__ +struct netns_sysctl_lowpan { +#ifdef CONFIG_SYSCTL + struct ctl_table_header *frags_hdr; +#endif +}; + struct netns_ieee802154_lowpan { + struct netns_sysctl_lowpan sysctl; + struct netns_frags frags; + u16 max_dsize; }; #endif diff --git a/net/ieee802154/6lowpan_rtnl.c b/net/ieee802154/6lowpan_rtnl.c index f9c954824ddb..c7bd8b55f7ce 100644 --- a/net/ieee802154/6lowpan_rtnl.c +++ b/net/ieee802154/6lowpan_rtnl.c @@ -54,6 +54,7 @@ #include #include +#include "reassembly.h" #include "6lowpan.h" static LIST_HEAD(lowpan_devices); @@ -70,18 +71,6 @@ struct lowpan_dev_record { struct list_head list; }; -struct lowpan_fragment { - struct sk_buff *skb; /* skb to be assembled */ - u16 length; /* length to be assemled */ - u32 bytes_rcv; /* bytes received */ - u16 tag; /* current fragment tag */ - struct timer_list timer; /* assembling timer */ - struct list_head list; /* fragments list */ -}; - -static LIST_HEAD(lowpan_fragments); -static DEFINE_SPINLOCK(flist_lock); - static inline struct lowpan_dev_info *lowpan_dev_info(const struct net_device *dev) { @@ -179,69 +168,6 @@ static int lowpan_give_skb_to_devices(struct sk_buff *skb, return stat; } -static void lowpan_fragment_timer_expired(unsigned long entry_addr) -{ - struct lowpan_fragment *entry = (struct lowpan_fragment *)entry_addr; - - pr_debug("timer expired for frame with tag %d\n", entry->tag); - - list_del(&entry->list); - dev_kfree_skb(entry->skb); - kfree(entry); -} - -static struct lowpan_fragment * -lowpan_alloc_new_frame(struct sk_buff *skb, u16 len, u16 tag) -{ - struct lowpan_fragment *frame; - - frame = kzalloc(sizeof(struct lowpan_fragment), - GFP_ATOMIC); - if (!frame) - goto frame_err; - - INIT_LIST_HEAD(&frame->list); - - frame->length = len; - frame->tag = tag; - - /* allocate buffer for frame assembling */ - frame->skb = netdev_alloc_skb_ip_align(skb->dev, frame->length + - sizeof(struct ipv6hdr)); - - if (!frame->skb) - goto skb_err; - - frame->skb->priority = skb->priority; - - /* reserve headroom for uncompressed ipv6 header */ - skb_reserve(frame->skb, sizeof(struct ipv6hdr)); - skb_put(frame->skb, frame->length); - - /* copy the first control block to keep a - * trace of the link-layer addresses in case - * of a link-local compressed address - */ - memcpy(frame->skb->cb, skb->cb, sizeof(skb->cb)); - - init_timer(&frame->timer); - /* time out is the same as for ipv6 - 60 sec */ - frame->timer.expires = jiffies + LOWPAN_FRAG_TIMEOUT; - frame->timer.data = (unsigned long)frame; - frame->timer.function = lowpan_fragment_timer_expired; - - add_timer(&frame->timer); - - list_add_tail(&frame->list, &lowpan_fragments); - - return frame; - -skb_err: - kfree(frame); -frame_err: - return NULL; -} - static int process_data(struct sk_buff *skb) { u8 iphc0, iphc1; @@ -255,94 +181,6 @@ static int process_data(struct sk_buff *skb) if (lowpan_fetch_skb_u8(skb, &iphc0)) goto drop; - /* fragments assembling */ - switch (iphc0 & LOWPAN_DISPATCH_MASK) { - case LOWPAN_DISPATCH_FRAG1: - case LOWPAN_DISPATCH_FRAGN: - { - struct lowpan_fragment *frame; - /* slen stores the rightmost 8 bits of the 11 bits length */ - u8 slen, offset = 0; - u16 len, tag; - bool found = false; - - if (lowpan_fetch_skb_u8(skb, &slen) || /* frame length */ - lowpan_fetch_skb_u16(skb, &tag)) /* fragment tag */ - goto drop; - - /* adds the 3 MSB to the 8 LSB to retrieve the 11 bits length */ - len = ((iphc0 & 7) << 8) | slen; - - if ((iphc0 & LOWPAN_DISPATCH_MASK) == LOWPAN_DISPATCH_FRAG1) { - pr_debug("%s received a FRAG1 packet (tag: %d, " - "size of the entire IP packet: %d)", - __func__, tag, len); - } else { /* FRAGN */ - if (lowpan_fetch_skb_u8(skb, &offset)) - goto unlock_and_drop; - pr_debug("%s received a FRAGN packet (tag: %d, " - "size of the entire IP packet: %d, " - "offset: %d)", __func__, tag, len, offset * 8); - } - - /* - * check if frame assembling with the same tag is - * already in progress - */ - spin_lock_bh(&flist_lock); - - list_for_each_entry(frame, &lowpan_fragments, list) - if (frame->tag == tag) { - found = true; - break; - } - - /* alloc new frame structure */ - if (!found) { - pr_debug("%s first fragment received for tag %d, " - "begin packet reassembly", __func__, tag); - frame = lowpan_alloc_new_frame(skb, len, tag); - if (!frame) - goto unlock_and_drop; - } - - /* if payload fits buffer, copy it */ - if (likely((offset * 8 + skb->len) <= frame->length)) - skb_copy_to_linear_data_offset(frame->skb, offset * 8, - skb->data, skb->len); - else - goto unlock_and_drop; - - frame->bytes_rcv += skb->len; - - /* frame assembling complete */ - if ((frame->bytes_rcv == frame->length) && - frame->timer.expires > jiffies) { - /* if timer haven't expired - first of all delete it */ - del_timer_sync(&frame->timer); - list_del(&frame->list); - spin_unlock_bh(&flist_lock); - - pr_debug("%s successfully reassembled fragment " - "(tag %d)", __func__, tag); - - dev_kfree_skb(skb); - skb = frame->skb; - kfree(frame); - - if (lowpan_fetch_skb_u8(skb, &iphc0)) - goto drop; - - break; - } - spin_unlock_bh(&flist_lock); - - return kfree_skb(skb), 0; - } - default: - break; - } - if (lowpan_fetch_skb_u8(skb, &iphc1)) goto drop; @@ -355,8 +193,6 @@ static int process_data(struct sk_buff *skb) IEEE802154_ADDR_LEN, iphc0, iphc1, lowpan_give_skb_to_devices); -unlock_and_drop: - spin_unlock_bh(&flist_lock); drop: kfree_skb(skb); return -EINVAL; @@ -603,44 +439,53 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { struct sk_buff *local_skb; + int ret; if (!netif_running(dev)) - goto drop; + goto drop_skb; if (dev->type != ARPHRD_IEEE802154) - goto drop; + goto drop_skb; + + local_skb = skb_clone(skb, GFP_ATOMIC); + if (!local_skb) + goto drop_skb; + + kfree_skb(skb); /* check that it's our buffer */ if (skb->data[0] == LOWPAN_DISPATCH_IPV6) { - /* Copy the packet so that the IPv6 header is - * properly aligned. - */ - local_skb = skb_copy_expand(skb, NET_SKB_PAD - 1, - skb_tailroom(skb), GFP_ATOMIC); - if (!local_skb) - goto drop; - local_skb->protocol = htons(ETH_P_IPV6); local_skb->pkt_type = PACKET_HOST; /* Pull off the 1-byte of 6lowpan header. */ skb_pull(local_skb, 1); - lowpan_give_skb_to_devices(local_skb, NULL); - - kfree_skb(local_skb); - kfree_skb(skb); + ret = lowpan_give_skb_to_devices(local_skb, NULL); + if (ret == NET_RX_DROP) + goto drop; } else { switch (skb->data[0] & 0xe0) { case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */ + ret = process_data(local_skb); + if (ret == NET_RX_DROP) + goto drop; + break; case LOWPAN_DISPATCH_FRAG1: /* first fragment header */ + ret = lowpan_frag_rcv(local_skb, LOWPAN_DISPATCH_FRAG1); + if (ret == 1) { + ret = process_data(local_skb); + if (ret == NET_RX_DROP) + goto drop; + } + break; case LOWPAN_DISPATCH_FRAGN: /* next fragments headers */ - local_skb = skb_clone(skb, GFP_ATOMIC); - if (!local_skb) - goto drop; - process_data(local_skb); - - kfree_skb(skb); + ret = lowpan_frag_rcv(local_skb, LOWPAN_DISPATCH_FRAGN); + if (ret == 1) { + ret = process_data(local_skb); + if (ret == NET_RX_DROP) + goto drop; + } break; default: break; @@ -648,9 +493,9 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev, } return NET_RX_SUCCESS; - -drop: +drop_skb: kfree_skb(skb); +drop: return NET_RX_DROP; } @@ -778,43 +623,40 @@ static int __init lowpan_init_module(void) { int err = 0; - err = lowpan_netlink_init(); + 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) { - dev_remove_pack(&lowpan_packet_type); - lowpan_netlink_fini(); - } + 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) { - struct lowpan_fragment *frame, *tframe; - lowpan_netlink_fini(); dev_remove_pack(&lowpan_packet_type); - unregister_netdevice_notifier(&lowpan_dev_notifier); + lowpan_net_frag_exit(); - /* Now 6lowpan packet_type is removed, so no new fragments are - * expected on RX, therefore that's the time to clean incomplete - * fragments. - */ - spin_lock_bh(&flist_lock); - list_for_each_entry_safe(frame, tframe, &lowpan_fragments, list) { - del_timer_sync(&frame->timer); - list_del(&frame->list); - dev_kfree_skb(frame->skb); - kfree(frame); - } - spin_unlock_bh(&flist_lock); + unregister_netdevice_notifier(&lowpan_dev_notifier); } module_init(lowpan_init_module); diff --git a/net/ieee802154/Makefile b/net/ieee802154/Makefile index 3d08adfcd175..b113fc4be3e0 100644 --- a/net/ieee802154/Makefile +++ b/net/ieee802154/Makefile @@ -2,6 +2,6 @@ obj-$(CONFIG_IEEE802154) += ieee802154.o af_802154.o obj-$(CONFIG_IEEE802154_6LOWPAN) += 6lowpan.o obj-$(CONFIG_6LOWPAN_IPHC) += 6lowpan_iphc.o -6lowpan-y := 6lowpan_rtnl.o +6lowpan-y := 6lowpan_rtnl.o reassembly.o ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o wpan-class.o af_802154-y := af_ieee802154.o raw.o dgram.o diff --git a/net/ieee802154/reassembly.c b/net/ieee802154/reassembly.c new file mode 100644 index 000000000000..eb5995e74316 --- /dev/null +++ b/net/ieee802154/reassembly.c @@ -0,0 +1,564 @@ +/* 6LoWPAN fragment reassembly + * + * + * Authors: + * Alexander Aring + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "6lowpan.h" +#include "reassembly.h" + +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, __be16 d_size, + const struct ieee802154_addr *saddr, + const struct ieee802154_addr *daddr) +{ + u32 c; + + net_get_random_once(&lowpan_frags.rnd, sizeof(lowpan_frags.rnd)); + c = jhash_3words(ieee802154_addr_hash(saddr), + ieee802154_addr_hash(daddr), + (__force u32)(tag + (d_size << 16)), + lowpan_frags.rnd); + + return c & (INETFRAGS_HASHSZ - 1); +} + +static unsigned int lowpan_hashfn(struct inet_frag_queue *q) +{ + 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); +} + +bool lowpan_frag_match(struct inet_frag_queue *q, void *a) +{ + struct lowpan_frag_queue *fq; + 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_addr_equal(&fq->saddr, arg->src) && + ieee802154_addr_addr_equal(&fq->daddr, arg->dst); +} +EXPORT_SYMBOL(lowpan_frag_match); + +void lowpan_frag_init(struct inet_frag_queue *q, void *a) +{ + struct lowpan_frag_queue *fq; + struct lowpan_create_arg *arg = a; + + 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; +} +EXPORT_SYMBOL(lowpan_frag_init); + +void lowpan_expire_frag_queue(struct frag_queue *fq, struct inet_frags *frags) +{ + spin_lock(&fq->q.lock); + + if (fq->q.last_in & INET_FRAG_COMPLETE) + goto out; + + inet_frag_kill(&fq->q, frags); +out: + spin_unlock(&fq->q.lock); + inet_frag_put(&fq->q, frags); +} +EXPORT_SYMBOL(lowpan_expire_frag_queue); + +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); + + lowpan_expire_frag_queue(fq, &lowpan_frags); +} + +static inline struct lowpan_frag_queue * +fq_find(struct net *net, const struct ieee802154_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; + + arg.tag = frag_info->d_tag; + arg.d_size = frag_info->d_size; + arg.src = src; + arg.dst = dst; + + read_lock(&lowpan_frags.lock); + hash = lowpan_hash_frag(frag_info->d_tag, frag_info->d_size, src, dst); + + q = inet_frag_find(&net->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.last_in & INET_FRAG_COMPLETE) + goto err; + + offset = mac_cb(skb)->frag_info.d_offset << 3; + end = mac_cb(skb)->frag_info.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.last_in & INET_FRAG_LAST_IN) && end != fq->q.len)) + goto err; + fq->q.last_in |= INET_FRAG_LAST_IN; + fq->q.len = end; + } else { + if (end > fq->q.len) { + /* Some bits beyond end -> corruption. */ + if (fq->q.last_in & 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 || mac_cb(prev)->frag_info.d_offset < + mac_cb(skb)->frag_info.d_offset) { + next = NULL; + goto found; + } + prev = NULL; + for (next = fq->q.fragments; next != NULL; next = next->next) { + if (mac_cb(next)->frag_info.d_offset >= + mac_cb(skb)->frag_info.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.last_in |= INET_FRAG_FIRST_IN; + } else { + fq->q.meat += skb->len; + } + add_frag_mem_limit(&fq->q, skb->truesize); + + if (fq->q.last_in == (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; + } + + inet_frag_lru_move(&fq->q); + 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 ieee802154_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 ieee802154_frag_info *frag_info = &mac_cb(skb)->frag_info; + int err; + + err = lowpan_get_frag_info(skb, frag_type, frag_info); + if (err < 0) + goto err; + + if (frag_info->d_size > net->ieee802154_lowpan.max_dsize) + goto err; + + inet_frag_evictor(&net->ieee802154_lowpan.frags, &lowpan_frags, false); + + fq = fq_find(net, frag_info, &mac_cb(skb)->sa, &mac_cb(skb)->da); + 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 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 + }, + { + .procname = "6lowpanfrag_low_thresh", + .data = &init_net.ieee802154_lowpan.frags.low_thresh, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec + }, + { + .procname = "6lowpanfrag_time", + .data = &init_net.ieee802154_lowpan.frags.timeout, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_jiffies, + }, + { + .procname = "6lowpanfrag_max_datagram_size", + .data = &init_net.ieee802154_lowpan.max_dsize, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec + }, + { } +}; + +static struct ctl_table lowpan_frags_ctl_table[] = { + { + .procname = "6lowpanfrag_secret_interval", + .data = &lowpan_frags.secret_interval, + .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; + + 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 = &net->ieee802154_lowpan.frags.high_thresh; + table[1].data = &net->ieee802154_lowpan.frags.low_thresh; + table[2].data = &net->ieee802154_lowpan.frags.timeout; + table[2].data = &net->ieee802154_lowpan.max_dsize; + + /* 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; + + net->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; + + table = net->ieee802154_lowpan.sysctl.frags_hdr->ctl_table_arg; + unregister_net_sysctl_table(net->ieee802154_lowpan.sysctl.frags_hdr); + if (!net_eq(net, &init_net)) + kfree(table); +} + +static struct ctl_table_header *lowpan_ctl_header; + +static int 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 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) +{ + net->ieee802154_lowpan.frags.high_thresh = IPV6_FRAG_HIGH_THRESH; + net->ieee802154_lowpan.frags.low_thresh = IPV6_FRAG_LOW_THRESH; + net->ieee802154_lowpan.frags.timeout = IPV6_FRAG_TIMEOUT; + net->ieee802154_lowpan.max_dsize = 0xFFFF; + + inet_frags_init_net(&net->ieee802154_lowpan.frags); + + return lowpan_frags_ns_sysctl_register(net); +} + +static void __net_exit lowpan_frags_exit_net(struct net *net) +{ + lowpan_frags_ns_sysctl_unregister(net); + inet_frags_exit_net(&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) + goto out; + + 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.secret_interval = 10 * 60 * HZ; + inet_frags_init(&lowpan_frags); +err_pernet: + lowpan_frags_sysctl_unregister(); +out: + return ret; +} + +void lowpan_net_frag_exit(void) +{ + inet_frags_fini(&lowpan_frags); + lowpan_frags_sysctl_unregister(); + unregister_pernet_subsys(&lowpan_frags_ops); +} diff --git a/net/ieee802154/reassembly.h b/net/ieee802154/reassembly.h new file mode 100644 index 000000000000..055518b9da2d --- /dev/null +++ b/net/ieee802154/reassembly.h @@ -0,0 +1,66 @@ +#ifndef __IEEE802154_6LOWPAN_REASSEMBLY_H__ +#define __IEEE802154_6LOWPAN_REASSEMBLY_H__ + +#include + +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->addr_type) { + case IEEE802154_ADDR_LONG: + return (__force u32)((((u32 *)a->hwaddr))[0] ^ + ((u32 *)(a->hwaddr))[1]); + case IEEE802154_ADDR_SHORT: + return (__force u32)(a->short_addr); + default: + return 0; + } +} + +static inline bool ieee802154_addr_addr_equal(const struct ieee802154_addr *a1, + const struct ieee802154_addr *a2) +{ + if (a1->pan_id != a2->pan_id) + return false; + + if (a1->addr_type != a2->addr_type) + return false; + + switch (a1->addr_type) { + case IEEE802154_ADDR_LONG: + if (memcmp(a1->hwaddr, a2->hwaddr, IEEE802154_ADDR_LEN)) + return false; + break; + case IEEE802154_ADDR_SHORT: + if (a1->short_addr != a2->short_addr) + return false; + break; + default: + return false; + } + + return true; +} + +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__ */ -- cgit v1.2.3-59-g8ed1b