Skip to content

Commit

Permalink
6lowpan: handling 6lowpan fragmentation via inet_frag api
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
alexaring authored and davem330 committed Feb 28, 2014
1 parent 633fc86 commit 7240cde
Show file tree
Hide file tree
Showing 5 changed files with 689 additions and 208 deletions.
9 changes: 9 additions & 0 deletions include/net/netns/ieee802154_6lowpan.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
256 changes: 49 additions & 207 deletions net/ieee802154/6lowpan_rtnl.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include <net/ieee802154_netdev.h>
#include <net/ipv6.h>

#include "reassembly.h"
#include "6lowpan.h"

static LIST_HEAD(lowpan_devices);
Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -603,54 +439,63 @@ 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;
}
}

return NET_RX_SUCCESS;

drop:
drop_skb:
kfree_skb(skb);
drop:
return NET_RX_DROP;
}

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion net/ieee802154/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 7240cde

Please sign in to comment.