Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 750f679c authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch '6lowpan'

Alexander Aring says:

====================
6lowpan: reimplementation of fragmentation handling

this patch series reimplementation the fragmentation handling of 6lowpan
accroding to rfc4944 [1].

The first big note is, that the current fragmentation behaviour isn't rfc
complaint. The main issue is a wrong datagram_size value which needs to be:
datagram_size = ipv6_payload + ipv6 header + (maybe compressed transport header,
                currently only udp is supported)

but the current datagram_size value is calculated as:
datagram_size = ipv6_payload

Fragmentation work in a linux<->linux communication only.

Why reimplementation?

I reimplemted the reassembly side only. The current behaviour is to allocate a
skb with the reassembled size and hold all fragments in a list, protected by a
spinlock. After we received all fragments (detected by the sum of all fragments,
it begins to place all fragments into the allocated skb).

This reassembly implementation has some race condition. Additional I make it more
rfc complaint. The current implementation match on the tag value inside the frag
header only, but rfc4944 says we need to match on dst addr(mac), src addr(mac),
tag value, datagram_size value. [2]

The new reassembly handling use the inet_frag api (I mean the callback interface
of ipv6 and ipv4 reassembly). I looked into ipv6 and wanted to see how ipv6 is
dealing with reassembly, so I based my code on this implementation.

On the sending side to generate the fragments I improved the current code to use
the nearest 8 divided payload. (We can do that, because the mac layer has a
dynamic size, so it depends on mac_header how big we can do the payload).

Of course I fix also the reassembly/sending side to be rfc complaint now.

Regards
Alexander Aring

[1] http://tools.ietf.org/html/rfc4944
[2] http://tools.ietf.org/html/rfc4944#section-5.3



changes since v2:
 - rework checkpatch code style issue patch.
   Merge two pr_debugs into one pr_debug.

changes since v3:
 - rename 6lowpan.ko to 6lowpan_rtnl.c in commit msg of patch 5/8.

changes since v4:
 - Add a new patch 2/8 to introduce lowpan_uncompress_size function. Also
   improving this function a little bit.
 - Add a new patch 4/8 to change tag value to __be16.
 - use skb_header_reset function on FRAG1 only, which should have the
   lowpan header. See lowpan_get_frag_info function. (slightly improving
   of fragmentation header parsing).
 - changes types of variables to u16 in lowpan_skb_fragmentation.
 - use lowpan_uncompress_size instead of storing necessary information
   in skb control block, this can be destroyed after dev_queue_xmit call.
   Thanks David for this hint.
 - remove Tested-by: Martin Townsend <martin.townsend@xsilon.com>, because
   too many funcionality change.

changes since v5:
 - handle lowpan_addr_mode_size with lookup table.

changes since v6:
 - remove unnecessary parameter in lowpan_frag_queue.
 - fix commit message in patch 8/8 which included a describtion of adding the
   lownpan_uncompress_size function. This was splitted in a seperate patch.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 4e76ca7f 7240cdec
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -29,6 +29,12 @@

#include <net/af_ieee802154.h>

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)
+4 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#include <net/netns/packet.h>
#include <net/netns/ipv4.h>
#include <net/netns/ipv6.h>
#include <net/netns/ieee802154_6lowpan.h>
#include <net/netns/sctp.h>
#include <net/netns/dccp.h>
#include <net/netns/netfilter.h>
@@ -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
+22 −0
Original line number Diff line number Diff line
/*
 * ieee802154 6lowpan in net namespaces
 */

#include <net/inet_frag.h>

#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
+113 −0
Original line number Diff line number Diff line
@@ -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,
+87 −236
Original line number Diff line number Diff line
/*
 * Copyright 2011, Siemens AG
/* Copyright 2011, Siemens AG
 * written by Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
 */

/*
 * Based on patches from Jon Smirl <jonsmirl@gmail.com>
/* Based on patches from Jon Smirl <jonsmirl@gmail.com>
 * Copyright (c) 2011 Jon Smirl <jonsmirl@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
@@ -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:
@@ -60,6 +54,7 @@
#include <net/ieee802154_netdev.h>
#include <net/ipv6.h>

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

static LIST_HEAD(lowpan_devices);
@@ -68,7 +63,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 {
@@ -76,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)
{
@@ -124,13 +107,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 +126,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)) {
@@ -188,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;
@@ -264,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;

@@ -364,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;
@@ -422,51 +249,69 @@ 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;
	__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 | ((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);
			pr_debug("%s unable to send a FRAGN packet. (tag: %d, offset: %d)\n",
				 __func__, tag, offset);
			goto exit;
		}

		offset += len;
		dgram_offset += len;
	}

exit:
@@ -594,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;

	/* 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);
	local_skb = skb_clone(skb, GFP_ATOMIC);
	if (!local_skb)
			goto drop;
		goto drop_skb;

	kfree_skb(skb);

	/* check that it's our buffer */
	if (skb->data[0] == LOWPAN_DISPATCH_IPV6) {
		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)
			ret = lowpan_frag_rcv(local_skb, LOWPAN_DISPATCH_FRAGN);
			if (ret == 1) {
				ret = process_data(local_skb);
				if (ret == NET_RX_DROP)
					goto drop;
			process_data(local_skb);

			kfree_skb(skb);
			}
			break;
		default:
			break;
@@ -639,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;
}

@@ -668,7 +522,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;
@@ -769,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) {
	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);
Loading