diff options
author | Alexander Aring <alex.aring@gmail.com> | 2014-02-28 01:32:50 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-02-28 17:05:22 -0500 |
commit | 7240cdec60b136f3e64a453c7fbded4ed1aa047e (patch) | |
tree | 1ef358c776a1493ba1d2e6786bf070b05e960c48 /net/ieee802154 | |
parent | 633fc86ff621bba79dcddfd4c67fb07ae5f8467c (diff) |
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 <alex.aring@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ieee802154')
-rw-r--r-- | net/ieee802154/6lowpan_rtnl.c | 256 | ||||
-rw-r--r-- | net/ieee802154/Makefile | 2 | ||||
-rw-r--r-- | net/ieee802154/reassembly.c | 564 | ||||
-rw-r--r-- | net/ieee802154/reassembly.h | 66 |
4 files changed, 680 insertions, 208 deletions
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 @@ | |||
54 | #include <net/ieee802154_netdev.h> | 54 | #include <net/ieee802154_netdev.h> |
55 | #include <net/ipv6.h> | 55 | #include <net/ipv6.h> |
56 | 56 | ||
57 | #include "reassembly.h" | ||
57 | #include "6lowpan.h" | 58 | #include "6lowpan.h" |
58 | 59 | ||
59 | static LIST_HEAD(lowpan_devices); | 60 | static LIST_HEAD(lowpan_devices); |
@@ -70,18 +71,6 @@ struct lowpan_dev_record { | |||
70 | struct list_head list; | 71 | struct list_head list; |
71 | }; | 72 | }; |
72 | 73 | ||
73 | struct lowpan_fragment { | ||
74 | struct sk_buff *skb; /* skb to be assembled */ | ||
75 | u16 length; /* length to be assemled */ | ||
76 | u32 bytes_rcv; /* bytes received */ | ||
77 | u16 tag; /* current fragment tag */ | ||
78 | struct timer_list timer; /* assembling timer */ | ||
79 | struct list_head list; /* fragments list */ | ||
80 | }; | ||
81 | |||
82 | static LIST_HEAD(lowpan_fragments); | ||
83 | static DEFINE_SPINLOCK(flist_lock); | ||
84 | |||
85 | static inline struct | 74 | static inline struct |
86 | lowpan_dev_info *lowpan_dev_info(const struct net_device *dev) | 75 | lowpan_dev_info *lowpan_dev_info(const struct net_device *dev) |
87 | { | 76 | { |
@@ -179,69 +168,6 @@ static int lowpan_give_skb_to_devices(struct sk_buff *skb, | |||
179 | return stat; | 168 | return stat; |
180 | } | 169 | } |
181 | 170 | ||
182 | static void lowpan_fragment_timer_expired(unsigned long entry_addr) | ||
183 | { | ||
184 | struct lowpan_fragment *entry = (struct lowpan_fragment *)entry_addr; | ||
185 | |||
186 | pr_debug("timer expired for frame with tag %d\n", entry->tag); | ||
187 | |||
188 | list_del(&entry->list); | ||
189 | dev_kfree_skb(entry->skb); | ||
190 | kfree(entry); | ||
191 | } | ||
192 | |||
193 | static struct lowpan_fragment * | ||
194 | lowpan_alloc_new_frame(struct sk_buff *skb, u16 len, u16 tag) | ||
195 | { | ||
196 | struct lowpan_fragment *frame; | ||
197 | |||
198 | frame = kzalloc(sizeof(struct lowpan_fragment), | ||
199 | GFP_ATOMIC); | ||
200 | if (!frame) | ||
201 | goto frame_err; | ||
202 | |||
203 | INIT_LIST_HEAD(&frame->list); | ||
204 | |||
205 | frame->length = len; | ||
206 | frame->tag = tag; | ||
207 | |||
208 | /* allocate buffer for frame assembling */ | ||
209 | frame->skb = netdev_alloc_skb_ip_align(skb->dev, frame->length + | ||
210 | sizeof(struct ipv6hdr)); | ||
211 | |||
212 | if (!frame->skb) | ||
213 | goto skb_err; | ||
214 | |||
215 | frame->skb->priority = skb->priority; | ||
216 | |||
217 | /* reserve headroom for uncompressed ipv6 header */ | ||
218 | skb_reserve(frame->skb, sizeof(struct ipv6hdr)); | ||
219 | skb_put(frame->skb, frame->length); | ||
220 | |||
221 | /* copy the first control block to keep a | ||
222 | * trace of the link-layer addresses in case | ||
223 | * of a link-local compressed address | ||
224 | */ | ||
225 | memcpy(frame->skb->cb, skb->cb, sizeof(skb->cb)); | ||
226 | |||
227 | init_timer(&frame->timer); | ||
228 | /* time out is the same as for ipv6 - 60 sec */ | ||
229 | frame->timer.expires = jiffies + LOWPAN_FRAG_TIMEOUT; | ||
230 | frame->timer.data = (unsigned long)frame; | ||
231 | frame->timer.function = lowpan_fragment_timer_expired; | ||
232 | |||
233 | add_timer(&frame->timer); | ||
234 | |||
235 | list_add_tail(&frame->list, &lowpan_fragments); | ||
236 | |||
237 | return frame; | ||
238 | |||
239 | skb_err: | ||
240 | kfree(frame); | ||
241 | frame_err: | ||
242 | return NULL; | ||
243 | } | ||
244 | |||
245 | static int process_data(struct sk_buff *skb) | 171 | static int process_data(struct sk_buff *skb) |
246 | { | 172 | { |
247 | u8 iphc0, iphc1; | 173 | u8 iphc0, iphc1; |
@@ -255,94 +181,6 @@ static int process_data(struct sk_buff *skb) | |||
255 | if (lowpan_fetch_skb_u8(skb, &iphc0)) | 181 | if (lowpan_fetch_skb_u8(skb, &iphc0)) |
256 | goto drop; | 182 | goto drop; |
257 | 183 | ||
258 | /* fragments assembling */ | ||
259 | switch (iphc0 & LOWPAN_DISPATCH_MASK) { | ||
260 | case LOWPAN_DISPATCH_FRAG1: | ||
261 | case LOWPAN_DISPATCH_FRAGN: | ||
262 | { | ||
263 | struct lowpan_fragment *frame; | ||
264 | /* slen stores the rightmost 8 bits of the 11 bits length */ | ||
265 | u8 slen, offset = 0; | ||
266 | u16 len, tag; | ||
267 | bool found = false; | ||
268 | |||
269 | if (lowpan_fetch_skb_u8(skb, &slen) || /* frame length */ | ||
270 | lowpan_fetch_skb_u16(skb, &tag)) /* fragment tag */ | ||
271 | goto drop; | ||
272 | |||
273 | /* adds the 3 MSB to the 8 LSB to retrieve the 11 bits length */ | ||
274 | len = ((iphc0 & 7) << 8) | slen; | ||
275 | |||
276 | if ((iphc0 & LOWPAN_DISPATCH_MASK) == LOWPAN_DISPATCH_FRAG1) { | ||
277 | pr_debug("%s received a FRAG1 packet (tag: %d, " | ||
278 | "size of the entire IP packet: %d)", | ||
279 | __func__, tag, len); | ||
280 | } else { /* FRAGN */ | ||
281 | if (lowpan_fetch_skb_u8(skb, &offset)) | ||
282 | goto unlock_and_drop; | ||
283 | pr_debug("%s received a FRAGN packet (tag: %d, " | ||
284 | "size of the entire IP packet: %d, " | ||
285 | "offset: %d)", __func__, tag, len, offset * 8); | ||
286 | } | ||
287 | |||
288 | /* | ||
289 | * check if frame assembling with the same tag is | ||
290 | * already in progress | ||
291 | */ | ||
292 | spin_lock_bh(&flist_lock); | ||
293 | |||
294 | list_for_each_entry(frame, &lowpan_fragments, list) | ||
295 | if (frame->tag == tag) { | ||
296 | found = true; | ||
297 | break; | ||
298 | } | ||
299 | |||
300 | /* alloc new frame structure */ | ||
301 | if (!found) { | ||
302 | pr_debug("%s first fragment received for tag %d, " | ||
303 | "begin packet reassembly", __func__, tag); | ||
304 | frame = lowpan_alloc_new_frame(skb, len, tag); | ||
305 | if (!frame) | ||
306 | goto unlock_and_drop; | ||
307 | } | ||
308 | |||
309 | /* if payload fits buffer, copy it */ | ||
310 | if (likely((offset * 8 + skb->len) <= frame->length)) | ||
311 | skb_copy_to_linear_data_offset(frame->skb, offset * 8, | ||
312 | skb->data, skb->len); | ||
313 | else | ||
314 | goto unlock_and_drop; | ||
315 | |||
316 | frame->bytes_rcv += skb->len; | ||
317 | |||
318 | /* frame assembling complete */ | ||
319 | if ((frame->bytes_rcv == frame->length) && | ||
320 | frame->timer.expires > jiffies) { | ||
321 | /* if timer haven't expired - first of all delete it */ | ||
322 | del_timer_sync(&frame->timer); | ||
323 | list_del(&frame->list); | ||
324 | spin_unlock_bh(&flist_lock); | ||
325 | |||
326 | pr_debug("%s successfully reassembled fragment " | ||
327 | "(tag %d)", __func__, tag); | ||
328 | |||
329 | dev_kfree_skb(skb); | ||
330 | skb = frame->skb; | ||
331 | kfree(frame); | ||
332 | |||
333 | if (lowpan_fetch_skb_u8(skb, &iphc0)) | ||
334 | goto drop; | ||
335 | |||
336 | break; | ||
337 | } | ||
338 | spin_unlock_bh(&flist_lock); | ||
339 | |||
340 | return kfree_skb(skb), 0; | ||
341 | } | ||
342 | default: | ||
343 | break; | ||
344 | } | ||
345 | |||
346 | if (lowpan_fetch_skb_u8(skb, &iphc1)) | 184 | if (lowpan_fetch_skb_u8(skb, &iphc1)) |
347 | goto drop; | 185 | goto drop; |
348 | 186 | ||
@@ -355,8 +193,6 @@ static int process_data(struct sk_buff *skb) | |||
355 | IEEE802154_ADDR_LEN, iphc0, iphc1, | 193 | IEEE802154_ADDR_LEN, iphc0, iphc1, |
356 | lowpan_give_skb_to_devices); | 194 | lowpan_give_skb_to_devices); |
357 | 195 | ||
358 | unlock_and_drop: | ||
359 | spin_unlock_bh(&flist_lock); | ||
360 | drop: | 196 | drop: |
361 | kfree_skb(skb); | 197 | kfree_skb(skb); |
362 | return -EINVAL; | 198 | return -EINVAL; |
@@ -603,44 +439,53 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev, | |||
603 | struct packet_type *pt, struct net_device *orig_dev) | 439 | struct packet_type *pt, struct net_device *orig_dev) |
604 | { | 440 | { |
605 | struct sk_buff *local_skb; | 441 | struct sk_buff *local_skb; |
442 | int ret; | ||
606 | 443 | ||
607 | if (!netif_running(dev)) | 444 | if (!netif_running(dev)) |
608 | goto drop; | 445 | goto drop_skb; |
609 | 446 | ||
610 | if (dev->type != ARPHRD_IEEE802154) | 447 | if (dev->type != ARPHRD_IEEE802154) |
611 | goto drop; | 448 | goto drop_skb; |
449 | |||
450 | local_skb = skb_clone(skb, GFP_ATOMIC); | ||
451 | if (!local_skb) | ||
452 | goto drop_skb; | ||
453 | |||
454 | kfree_skb(skb); | ||
612 | 455 | ||
613 | /* check that it's our buffer */ | 456 | /* check that it's our buffer */ |
614 | if (skb->data[0] == LOWPAN_DISPATCH_IPV6) { | 457 | if (skb->data[0] == LOWPAN_DISPATCH_IPV6) { |
615 | /* Copy the packet so that the IPv6 header is | ||
616 | * properly aligned. | ||
617 | */ | ||
618 | local_skb = skb_copy_expand(skb, NET_SKB_PAD - 1, | ||
619 | skb_tailroom(skb), GFP_ATOMIC); | ||
620 | if (!local_skb) | ||
621 | goto drop; | ||
622 | |||
623 | local_skb->protocol = htons(ETH_P_IPV6); | 458 | local_skb->protocol = htons(ETH_P_IPV6); |
624 | local_skb->pkt_type = PACKET_HOST; | 459 | local_skb->pkt_type = PACKET_HOST; |
625 | 460 | ||
626 | /* Pull off the 1-byte of 6lowpan header. */ | 461 | /* Pull off the 1-byte of 6lowpan header. */ |
627 | skb_pull(local_skb, 1); | 462 | skb_pull(local_skb, 1); |
628 | 463 | ||
629 | lowpan_give_skb_to_devices(local_skb, NULL); | 464 | ret = lowpan_give_skb_to_devices(local_skb, NULL); |
630 | 465 | if (ret == NET_RX_DROP) | |
631 | kfree_skb(local_skb); | 466 | goto drop; |
632 | kfree_skb(skb); | ||
633 | } else { | 467 | } else { |
634 | switch (skb->data[0] & 0xe0) { | 468 | switch (skb->data[0] & 0xe0) { |
635 | case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */ | 469 | case LOWPAN_DISPATCH_IPHC: /* ipv6 datagram */ |
470 | ret = process_data(local_skb); | ||
471 | if (ret == NET_RX_DROP) | ||
472 | goto drop; | ||
473 | break; | ||
636 | case LOWPAN_DISPATCH_FRAG1: /* first fragment header */ | 474 | case LOWPAN_DISPATCH_FRAG1: /* first fragment header */ |
475 | ret = lowpan_frag_rcv(local_skb, LOWPAN_DISPATCH_FRAG1); | ||
476 | if (ret == 1) { | ||
477 | ret = process_data(local_skb); | ||
478 | if (ret == NET_RX_DROP) | ||
479 | goto drop; | ||
480 | } | ||
481 | break; | ||
637 | case LOWPAN_DISPATCH_FRAGN: /* next fragments headers */ | 482 | case LOWPAN_DISPATCH_FRAGN: /* next fragments headers */ |
638 | local_skb = skb_clone(skb, GFP_ATOMIC); | 483 | ret = lowpan_frag_rcv(local_skb, LOWPAN_DISPATCH_FRAGN); |
639 | if (!local_skb) | 484 | if (ret == 1) { |
640 | goto drop; | 485 | ret = process_data(local_skb); |
641 | process_data(local_skb); | 486 | if (ret == NET_RX_DROP) |
642 | 487 | goto drop; | |
643 | kfree_skb(skb); | 488 | } |
644 | break; | 489 | break; |
645 | default: | 490 | default: |
646 | break; | 491 | break; |
@@ -648,9 +493,9 @@ static int lowpan_rcv(struct sk_buff *skb, struct net_device *dev, | |||
648 | } | 493 | } |
649 | 494 | ||
650 | return NET_RX_SUCCESS; | 495 | return NET_RX_SUCCESS; |
651 | 496 | drop_skb: | |
652 | drop: | ||
653 | kfree_skb(skb); | 497 | kfree_skb(skb); |
498 | drop: | ||
654 | return NET_RX_DROP; | 499 | return NET_RX_DROP; |
655 | } | 500 | } |
656 | 501 | ||
@@ -778,43 +623,40 @@ static int __init lowpan_init_module(void) | |||
778 | { | 623 | { |
779 | int err = 0; | 624 | int err = 0; |
780 | 625 | ||
781 | err = lowpan_netlink_init(); | 626 | err = lowpan_net_frag_init(); |
782 | if (err < 0) | 627 | if (err < 0) |
783 | goto out; | 628 | goto out; |
784 | 629 | ||
630 | err = lowpan_netlink_init(); | ||
631 | if (err < 0) | ||
632 | goto out_frag; | ||
633 | |||
785 | dev_add_pack(&lowpan_packet_type); | 634 | dev_add_pack(&lowpan_packet_type); |
786 | 635 | ||
787 | err = register_netdevice_notifier(&lowpan_dev_notifier); | 636 | err = register_netdevice_notifier(&lowpan_dev_notifier); |
788 | if (err < 0) { | 637 | if (err < 0) |
789 | dev_remove_pack(&lowpan_packet_type); | 638 | goto out_pack; |
790 | lowpan_netlink_fini(); | 639 | |
791 | } | 640 | return 0; |
641 | |||
642 | out_pack: | ||
643 | dev_remove_pack(&lowpan_packet_type); | ||
644 | lowpan_netlink_fini(); | ||
645 | out_frag: | ||
646 | lowpan_net_frag_exit(); | ||
792 | out: | 647 | out: |
793 | return err; | 648 | return err; |
794 | } | 649 | } |
795 | 650 | ||
796 | static void __exit lowpan_cleanup_module(void) | 651 | static void __exit lowpan_cleanup_module(void) |
797 | { | 652 | { |
798 | struct lowpan_fragment *frame, *tframe; | ||
799 | |||
800 | lowpan_netlink_fini(); | 653 | lowpan_netlink_fini(); |
801 | 654 | ||
802 | dev_remove_pack(&lowpan_packet_type); | 655 | dev_remove_pack(&lowpan_packet_type); |
803 | 656 | ||
804 | unregister_netdevice_notifier(&lowpan_dev_notifier); | 657 | lowpan_net_frag_exit(); |
805 | 658 | ||
806 | /* Now 6lowpan packet_type is removed, so no new fragments are | 659 | unregister_netdevice_notifier(&lowpan_dev_notifier); |
807 | * expected on RX, therefore that's the time to clean incomplete | ||
808 | * fragments. | ||
809 | */ | ||
810 | spin_lock_bh(&flist_lock); | ||
811 | list_for_each_entry_safe(frame, tframe, &lowpan_fragments, list) { | ||
812 | del_timer_sync(&frame->timer); | ||
813 | list_del(&frame->list); | ||
814 | dev_kfree_skb(frame->skb); | ||
815 | kfree(frame); | ||
816 | } | ||
817 | spin_unlock_bh(&flist_lock); | ||
818 | } | 660 | } |
819 | 661 | ||
820 | module_init(lowpan_init_module); | 662 | 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 | |||
2 | obj-$(CONFIG_IEEE802154_6LOWPAN) += 6lowpan.o | 2 | obj-$(CONFIG_IEEE802154_6LOWPAN) += 6lowpan.o |
3 | obj-$(CONFIG_6LOWPAN_IPHC) += 6lowpan_iphc.o | 3 | obj-$(CONFIG_6LOWPAN_IPHC) += 6lowpan_iphc.o |
4 | 4 | ||
5 | 6lowpan-y := 6lowpan_rtnl.o | 5 | 6lowpan-y := 6lowpan_rtnl.o reassembly.o |
6 | ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o wpan-class.o | 6 | ieee802154-y := netlink.o nl-mac.o nl-phy.o nl_policy.o wpan-class.o |
7 | af_802154-y := af_ieee802154.o raw.o dgram.o | 7 | 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 @@ | |||
1 | /* 6LoWPAN fragment reassembly | ||
2 | * | ||
3 | * | ||
4 | * Authors: | ||
5 | * Alexander Aring <aar@pengutronix.de> | ||
6 | * | ||
7 | * Based on: net/ipv6/reassembly.c | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or | ||
10 | * modify it under the terms of the GNU General Public License | ||
11 | * as published by the Free Software Foundation; either version | ||
12 | * 2 of the License, or (at your option) any later version. | ||
13 | */ | ||
14 | |||
15 | #define pr_fmt(fmt) "6LoWPAN: " fmt | ||
16 | |||
17 | #include <linux/net.h> | ||
18 | #include <linux/list.h> | ||
19 | #include <linux/netdevice.h> | ||
20 | #include <linux/random.h> | ||
21 | #include <linux/jhash.h> | ||
22 | #include <linux/skbuff.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/export.h> | ||
25 | |||
26 | #include <net/ieee802154_netdev.h> | ||
27 | #include <net/ipv6.h> | ||
28 | #include <net/inet_frag.h> | ||
29 | |||
30 | #include "6lowpan.h" | ||
31 | #include "reassembly.h" | ||
32 | |||
33 | static struct inet_frags lowpan_frags; | ||
34 | |||
35 | static int lowpan_frag_reasm(struct lowpan_frag_queue *fq, | ||
36 | struct sk_buff *prev, struct net_device *dev); | ||
37 | |||
38 | static unsigned int lowpan_hash_frag(__be16 tag, __be16 d_size, | ||
39 | const struct ieee802154_addr *saddr, | ||
40 | const struct ieee802154_addr *daddr) | ||
41 | { | ||
42 | u32 c; | ||
43 | |||
44 | net_get_random_once(&lowpan_frags.rnd, sizeof(lowpan_frags.rnd)); | ||
45 | c = jhash_3words(ieee802154_addr_hash(saddr), | ||
46 | ieee802154_addr_hash(daddr), | ||
47 | (__force u32)(tag + (d_size << 16)), | ||
48 | lowpan_frags.rnd); | ||
49 | |||
50 | return c & (INETFRAGS_HASHSZ - 1); | ||
51 | } | ||
52 | |||
53 | static unsigned int lowpan_hashfn(struct inet_frag_queue *q) | ||
54 | { | ||
55 | struct lowpan_frag_queue *fq; | ||
56 | |||
57 | fq = container_of(q, struct lowpan_frag_queue, q); | ||
58 | return lowpan_hash_frag(fq->tag, fq->d_size, &fq->saddr, &fq->daddr); | ||
59 | } | ||
60 | |||
61 | bool lowpan_frag_match(struct inet_frag_queue *q, void *a) | ||
62 | { | ||
63 | struct lowpan_frag_queue *fq; | ||
64 | struct lowpan_create_arg *arg = a; | ||
65 | |||
66 | fq = container_of(q, struct lowpan_frag_queue, q); | ||
67 | return fq->tag == arg->tag && fq->d_size == arg->d_size && | ||
68 | ieee802154_addr_addr_equal(&fq->saddr, arg->src) && | ||
69 | ieee802154_addr_addr_equal(&fq->daddr, arg->dst); | ||
70 | } | ||
71 | EXPORT_SYMBOL(lowpan_frag_match); | ||
72 | |||
73 | void lowpan_frag_init(struct inet_frag_queue *q, void *a) | ||
74 | { | ||
75 | struct lowpan_frag_queue *fq; | ||
76 | struct lowpan_create_arg *arg = a; | ||
77 | |||
78 | fq = container_of(q, struct lowpan_frag_queue, q); | ||
79 | |||
80 | fq->tag = arg->tag; | ||
81 | fq->d_size = arg->d_size; | ||
82 | fq->saddr = *arg->src; | ||
83 | fq->daddr = *arg->dst; | ||
84 | } | ||
85 | EXPORT_SYMBOL(lowpan_frag_init); | ||
86 | |||
87 | void lowpan_expire_frag_queue(struct frag_queue *fq, struct inet_frags *frags) | ||
88 | { | ||
89 | spin_lock(&fq->q.lock); | ||
90 | |||
91 | if (fq->q.last_in & INET_FRAG_COMPLETE) | ||
92 | goto out; | ||
93 | |||
94 | inet_frag_kill(&fq->q, frags); | ||
95 | out: | ||
96 | spin_unlock(&fq->q.lock); | ||
97 | inet_frag_put(&fq->q, frags); | ||
98 | } | ||
99 | EXPORT_SYMBOL(lowpan_expire_frag_queue); | ||
100 | |||
101 | static void lowpan_frag_expire(unsigned long data) | ||
102 | { | ||
103 | struct frag_queue *fq; | ||
104 | struct net *net; | ||
105 | |||
106 | fq = container_of((struct inet_frag_queue *)data, struct frag_queue, q); | ||
107 | net = container_of(fq->q.net, struct net, ieee802154_lowpan.frags); | ||
108 | |||
109 | lowpan_expire_frag_queue(fq, &lowpan_frags); | ||
110 | } | ||
111 | |||
112 | static inline struct lowpan_frag_queue * | ||
113 | fq_find(struct net *net, const struct ieee802154_frag_info *frag_info, | ||
114 | const struct ieee802154_addr *src, const struct ieee802154_addr *dst) | ||
115 | { | ||
116 | struct inet_frag_queue *q; | ||
117 | struct lowpan_create_arg arg; | ||
118 | unsigned int hash; | ||
119 | |||
120 | arg.tag = frag_info->d_tag; | ||
121 | arg.d_size = frag_info->d_size; | ||
122 | arg.src = src; | ||
123 | arg.dst = dst; | ||
124 | |||
125 | read_lock(&lowpan_frags.lock); | ||
126 | hash = lowpan_hash_frag(frag_info->d_tag, frag_info->d_size, src, dst); | ||
127 | |||
128 | q = inet_frag_find(&net->ieee802154_lowpan.frags, | ||
129 | &lowpan_frags, &arg, hash); | ||
130 | if (IS_ERR_OR_NULL(q)) { | ||
131 | inet_frag_maybe_warn_overflow(q, pr_fmt()); | ||
132 | return NULL; | ||
133 | } | ||
134 | return container_of(q, struct lowpan_frag_queue, q); | ||
135 | } | ||
136 | |||
137 | static int lowpan_frag_queue(struct lowpan_frag_queue *fq, | ||
138 | struct sk_buff *skb, const u8 frag_type) | ||
139 | { | ||
140 | struct sk_buff *prev, *next; | ||
141 | struct net_device *dev; | ||
142 | int end, offset; | ||
143 | |||
144 | if (fq->q.last_in & INET_FRAG_COMPLETE) | ||
145 | goto err; | ||
146 | |||
147 | offset = mac_cb(skb)->frag_info.d_offset << 3; | ||
148 | end = mac_cb(skb)->frag_info.d_size; | ||
149 | |||
150 | /* Is this the final fragment? */ | ||
151 | if (offset + skb->len == end) { | ||
152 | /* If we already have some bits beyond end | ||
153 | * or have different end, the segment is corrupted. | ||
154 | */ | ||
155 | if (end < fq->q.len || | ||
156 | ((fq->q.last_in & INET_FRAG_LAST_IN) && end != fq->q.len)) | ||
157 | goto err; | ||
158 | fq->q.last_in |= INET_FRAG_LAST_IN; | ||
159 | fq->q.len = end; | ||
160 | } else { | ||
161 | if (end > fq->q.len) { | ||
162 | /* Some bits beyond end -> corruption. */ | ||
163 | if (fq->q.last_in & INET_FRAG_LAST_IN) | ||
164 | goto err; | ||
165 | fq->q.len = end; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | /* Find out which fragments are in front and at the back of us | ||
170 | * in the chain of fragments so far. We must know where to put | ||
171 | * this fragment, right? | ||
172 | */ | ||
173 | prev = fq->q.fragments_tail; | ||
174 | if (!prev || mac_cb(prev)->frag_info.d_offset < | ||
175 | mac_cb(skb)->frag_info.d_offset) { | ||
176 | next = NULL; | ||
177 | goto found; | ||
178 | } | ||
179 | prev = NULL; | ||
180 | for (next = fq->q.fragments; next != NULL; next = next->next) { | ||
181 | if (mac_cb(next)->frag_info.d_offset >= | ||
182 | mac_cb(skb)->frag_info.d_offset) | ||
183 | break; /* bingo! */ | ||
184 | prev = next; | ||
185 | } | ||
186 | |||
187 | found: | ||
188 | /* Insert this fragment in the chain of fragments. */ | ||
189 | skb->next = next; | ||
190 | if (!next) | ||
191 | fq->q.fragments_tail = skb; | ||
192 | if (prev) | ||
193 | prev->next = skb; | ||
194 | else | ||
195 | fq->q.fragments = skb; | ||
196 | |||
197 | dev = skb->dev; | ||
198 | if (dev) | ||
199 | skb->dev = NULL; | ||
200 | |||
201 | fq->q.stamp = skb->tstamp; | ||
202 | if (frag_type == LOWPAN_DISPATCH_FRAG1) { | ||
203 | /* Calculate uncomp. 6lowpan header to estimate full size */ | ||
204 | fq->q.meat += lowpan_uncompress_size(skb, NULL); | ||
205 | fq->q.last_in |= INET_FRAG_FIRST_IN; | ||
206 | } else { | ||
207 | fq->q.meat += skb->len; | ||
208 | } | ||
209 | add_frag_mem_limit(&fq->q, skb->truesize); | ||
210 | |||
211 | if (fq->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) && | ||
212 | fq->q.meat == fq->q.len) { | ||
213 | int res; | ||
214 | unsigned long orefdst = skb->_skb_refdst; | ||
215 | |||
216 | skb->_skb_refdst = 0UL; | ||
217 | res = lowpan_frag_reasm(fq, prev, dev); | ||
218 | skb->_skb_refdst = orefdst; | ||
219 | return res; | ||
220 | } | ||
221 | |||
222 | inet_frag_lru_move(&fq->q); | ||
223 | return -1; | ||
224 | err: | ||
225 | kfree_skb(skb); | ||
226 | return -1; | ||
227 | } | ||
228 | |||
229 | /* Check if this packet is complete. | ||
230 | * Returns NULL on failure by any reason, and pointer | ||
231 | * to current nexthdr field in reassembled frame. | ||
232 | * | ||
233 | * It is called with locked fq, and caller must check that | ||
234 | * queue is eligible for reassembly i.e. it is not COMPLETE, | ||
235 | * the last and the first frames arrived and all the bits are here. | ||
236 | */ | ||
237 | static int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *prev, | ||
238 | struct net_device *dev) | ||
239 | { | ||
240 | struct sk_buff *fp, *head = fq->q.fragments; | ||
241 | int sum_truesize; | ||
242 | |||
243 | inet_frag_kill(&fq->q, &lowpan_frags); | ||
244 | |||
245 | /* Make the one we just received the head. */ | ||
246 | if (prev) { | ||
247 | head = prev->next; | ||
248 | fp = skb_clone(head, GFP_ATOMIC); | ||
249 | |||
250 | if (!fp) | ||
251 | goto out_oom; | ||
252 | |||
253 | fp->next = head->next; | ||
254 | if (!fp->next) | ||
255 | fq->q.fragments_tail = fp; | ||
256 | prev->next = fp; | ||
257 | |||
258 | skb_morph(head, fq->q.fragments); | ||
259 | head->next = fq->q.fragments->next; | ||
260 | |||
261 | consume_skb(fq->q.fragments); | ||
262 | fq->q.fragments = head; | ||
263 | } | ||
264 | |||
265 | /* Head of list must not be cloned. */ | ||
266 | if (skb_unclone(head, GFP_ATOMIC)) | ||
267 | goto out_oom; | ||
268 | |||
269 | /* If the first fragment is fragmented itself, we split | ||
270 | * it to two chunks: the first with data and paged part | ||
271 | * and the second, holding only fragments. | ||
272 | */ | ||
273 | if (skb_has_frag_list(head)) { | ||
274 | struct sk_buff *clone; | ||
275 | int i, plen = 0; | ||
276 | |||
277 | clone = alloc_skb(0, GFP_ATOMIC); | ||
278 | if (!clone) | ||
279 | goto out_oom; | ||
280 | clone->next = head->next; | ||
281 | head->next = clone; | ||
282 | skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list; | ||
283 | skb_frag_list_init(head); | ||
284 | for (i = 0; i < skb_shinfo(head)->nr_frags; i++) | ||
285 | plen += skb_frag_size(&skb_shinfo(head)->frags[i]); | ||
286 | clone->len = head->data_len - plen; | ||
287 | clone->data_len = clone->len; | ||
288 | head->data_len -= clone->len; | ||
289 | head->len -= clone->len; | ||
290 | add_frag_mem_limit(&fq->q, clone->truesize); | ||
291 | } | ||
292 | |||
293 | WARN_ON(head == NULL); | ||
294 | |||
295 | sum_truesize = head->truesize; | ||
296 | for (fp = head->next; fp;) { | ||
297 | bool headstolen; | ||
298 | int delta; | ||
299 | struct sk_buff *next = fp->next; | ||
300 | |||
301 | sum_truesize += fp->truesize; | ||
302 | if (skb_try_coalesce(head, fp, &headstolen, &delta)) { | ||
303 | kfree_skb_partial(fp, headstolen); | ||
304 | } else { | ||
305 | if (!skb_shinfo(head)->frag_list) | ||
306 | skb_shinfo(head)->frag_list = fp; | ||
307 | head->data_len += fp->len; | ||
308 | head->len += fp->len; | ||
309 | head->truesize += fp->truesize; | ||
310 | } | ||
311 | fp = next; | ||
312 | } | ||
313 | sub_frag_mem_limit(&fq->q, sum_truesize); | ||
314 | |||
315 | head->next = NULL; | ||
316 | head->dev = dev; | ||
317 | head->tstamp = fq->q.stamp; | ||
318 | |||
319 | fq->q.fragments = NULL; | ||
320 | fq->q.fragments_tail = NULL; | ||
321 | |||
322 | return 1; | ||
323 | out_oom: | ||
324 | net_dbg_ratelimited("lowpan_frag_reasm: no memory for reassembly\n"); | ||
325 | return -1; | ||
326 | } | ||
327 | |||
328 | static int lowpan_get_frag_info(struct sk_buff *skb, const u8 frag_type, | ||
329 | struct ieee802154_frag_info *frag_info) | ||
330 | { | ||
331 | bool fail; | ||
332 | u8 pattern = 0, low = 0; | ||
333 | |||
334 | fail = lowpan_fetch_skb(skb, &pattern, 1); | ||
335 | fail |= lowpan_fetch_skb(skb, &low, 1); | ||
336 | frag_info->d_size = (pattern & 7) << 8 | low; | ||
337 | fail |= lowpan_fetch_skb(skb, &frag_info->d_tag, 2); | ||
338 | |||
339 | if (frag_type == LOWPAN_DISPATCH_FRAGN) { | ||
340 | fail |= lowpan_fetch_skb(skb, &frag_info->d_offset, 1); | ||
341 | } else { | ||
342 | skb_reset_network_header(skb); | ||
343 | frag_info->d_offset = 0; | ||
344 | } | ||
345 | |||
346 | if (unlikely(fail)) | ||
347 | return -EIO; | ||
348 | |||
349 | return 0; | ||
350 | } | ||
351 | |||
352 | int lowpan_frag_rcv(struct sk_buff *skb, const u8 frag_type) | ||
353 | { | ||
354 | struct lowpan_frag_queue *fq; | ||
355 | struct net *net = dev_net(skb->dev); | ||
356 | struct ieee802154_frag_info *frag_info = &mac_cb(skb)->frag_info; | ||
357 | int err; | ||
358 | |||
359 | err = lowpan_get_frag_info(skb, frag_type, frag_info); | ||
360 | if (err < 0) | ||
361 | goto err; | ||
362 | |||
363 | if (frag_info->d_size > net->ieee802154_lowpan.max_dsize) | ||
364 | goto err; | ||
365 | |||
366 | inet_frag_evictor(&net->ieee802154_lowpan.frags, &lowpan_frags, false); | ||
367 | |||
368 | fq = fq_find(net, frag_info, &mac_cb(skb)->sa, &mac_cb(skb)->da); | ||
369 | if (fq != NULL) { | ||
370 | int ret; | ||
371 | spin_lock(&fq->q.lock); | ||
372 | ret = lowpan_frag_queue(fq, skb, frag_type); | ||
373 | spin_unlock(&fq->q.lock); | ||
374 | |||
375 | inet_frag_put(&fq->q, &lowpan_frags); | ||
376 | return ret; | ||
377 | } | ||
378 | |||
379 | err: | ||
380 | kfree_skb(skb); | ||
381 | return -1; | ||
382 | } | ||
383 | EXPORT_SYMBOL(lowpan_frag_rcv); | ||
384 | |||
385 | #ifdef CONFIG_SYSCTL | ||
386 | static struct ctl_table lowpan_frags_ns_ctl_table[] = { | ||
387 | { | ||
388 | .procname = "6lowpanfrag_high_thresh", | ||
389 | .data = &init_net.ieee802154_lowpan.frags.high_thresh, | ||
390 | .maxlen = sizeof(int), | ||
391 | .mode = 0644, | ||
392 | .proc_handler = proc_dointvec | ||
393 | }, | ||
394 | { | ||
395 | .procname = "6lowpanfrag_low_thresh", | ||
396 | .data = &init_net.ieee802154_lowpan.frags.low_thresh, | ||
397 | .maxlen = sizeof(int), | ||
398 | .mode = 0644, | ||
399 | .proc_handler = proc_dointvec | ||
400 | }, | ||
401 | { | ||
402 | .procname = "6lowpanfrag_time", | ||
403 | .data = &init_net.ieee802154_lowpan.frags.timeout, | ||
404 | .maxlen = sizeof(int), | ||
405 | .mode = 0644, | ||
406 | .proc_handler = proc_dointvec_jiffies, | ||
407 | }, | ||
408 | { | ||
409 | .procname = "6lowpanfrag_max_datagram_size", | ||
410 | .data = &init_net.ieee802154_lowpan.max_dsize, | ||
411 | .maxlen = sizeof(int), | ||
412 | .mode = 0644, | ||
413 | .proc_handler = proc_dointvec | ||
414 | }, | ||
415 | { } | ||
416 | }; | ||
417 | |||
418 | static struct ctl_table lowpan_frags_ctl_table[] = { | ||
419 | { | ||
420 | .procname = "6lowpanfrag_secret_interval", | ||
421 | .data = &lowpan_frags.secret_interval, | ||
422 | .maxlen = sizeof(int), | ||
423 | .mode = 0644, | ||
424 | .proc_handler = proc_dointvec_jiffies, | ||
425 | }, | ||
426 | { } | ||
427 | }; | ||
428 | |||
429 | static int __net_init lowpan_frags_ns_sysctl_register(struct net *net) | ||
430 | { | ||
431 | struct ctl_table *table; | ||
432 | struct ctl_table_header *hdr; | ||
433 | |||
434 | table = lowpan_frags_ns_ctl_table; | ||
435 | if (!net_eq(net, &init_net)) { | ||
436 | table = kmemdup(table, sizeof(lowpan_frags_ns_ctl_table), | ||
437 | GFP_KERNEL); | ||
438 | if (table == NULL) | ||
439 | goto err_alloc; | ||
440 | |||
441 | table[0].data = &net->ieee802154_lowpan.frags.high_thresh; | ||
442 | table[1].data = &net->ieee802154_lowpan.frags.low_thresh; | ||
443 | table[2].data = &net->ieee802154_lowpan.frags.timeout; | ||
444 | table[2].data = &net->ieee802154_lowpan.max_dsize; | ||
445 | |||
446 | /* Don't export sysctls to unprivileged users */ | ||
447 | if (net->user_ns != &init_user_ns) | ||
448 | table[0].procname = NULL; | ||
449 | } | ||
450 | |||
451 | hdr = register_net_sysctl(net, "net/ieee802154/6lowpan", table); | ||
452 | if (hdr == NULL) | ||
453 | goto err_reg; | ||
454 | |||
455 | net->ieee802154_lowpan.sysctl.frags_hdr = hdr; | ||
456 | return 0; | ||
457 | |||
458 | err_reg: | ||
459 | if (!net_eq(net, &init_net)) | ||
460 | kfree(table); | ||
461 | err_alloc: | ||
462 | return -ENOMEM; | ||
463 | } | ||
464 | |||
465 | static void __net_exit lowpan_frags_ns_sysctl_unregister(struct net *net) | ||
466 | { | ||
467 | struct ctl_table *table; | ||
468 | |||
469 | table = net->ieee802154_lowpan.sysctl.frags_hdr->ctl_table_arg; | ||
470 | unregister_net_sysctl_table(net->ieee802154_lowpan.sysctl.frags_hdr); | ||
471 | if (!net_eq(net, &init_net)) | ||
472 | kfree(table); | ||
473 | } | ||
474 | |||
475 | static struct ctl_table_header *lowpan_ctl_header; | ||
476 | |||
477 | static int lowpan_frags_sysctl_register(void) | ||
478 | { | ||
479 | lowpan_ctl_header = register_net_sysctl(&init_net, | ||
480 | "net/ieee802154/6lowpan", | ||
481 | lowpan_frags_ctl_table); | ||
482 | return lowpan_ctl_header == NULL ? -ENOMEM : 0; | ||
483 | } | ||
484 | |||
485 | static void lowpan_frags_sysctl_unregister(void) | ||
486 | { | ||
487 | unregister_net_sysctl_table(lowpan_ctl_header); | ||
488 | } | ||
489 | #else | ||
490 | static inline int lowpan_frags_ns_sysctl_register(struct net *net) | ||
491 | { | ||
492 | return 0; | ||
493 | } | ||
494 | |||
495 | static inline void lowpan_frags_ns_sysctl_unregister(struct net *net) | ||
496 | { | ||
497 | } | ||
498 | |||
499 | static inline int lowpan_frags_sysctl_register(void) | ||
500 | { | ||
501 | return 0; | ||
502 | } | ||
503 | |||
504 | static inline void lowpan_frags_sysctl_unregister(void) | ||
505 | { | ||
506 | } | ||
507 | #endif | ||
508 | |||
509 | static int __net_init lowpan_frags_init_net(struct net *net) | ||
510 | { | ||
511 | net->ieee802154_lowpan.frags.high_thresh = IPV6_FRAG_HIGH_THRESH; | ||
512 | net->ieee802154_lowpan.frags.low_thresh = IPV6_FRAG_LOW_THRESH; | ||
513 | net->ieee802154_lowpan.frags.timeout = IPV6_FRAG_TIMEOUT; | ||
514 | net->ieee802154_lowpan.max_dsize = 0xFFFF; | ||
515 | |||
516 | inet_frags_init_net(&net->ieee802154_lowpan.frags); | ||
517 | |||
518 | return lowpan_frags_ns_sysctl_register(net); | ||
519 | } | ||
520 | |||
521 | static void __net_exit lowpan_frags_exit_net(struct net *net) | ||
522 | { | ||
523 | lowpan_frags_ns_sysctl_unregister(net); | ||
524 | inet_frags_exit_net(&net->ieee802154_lowpan.frags, &lowpan_frags); | ||
525 | } | ||
526 | |||
527 | static struct pernet_operations lowpan_frags_ops = { | ||
528 | .init = lowpan_frags_init_net, | ||
529 | .exit = lowpan_frags_exit_net, | ||
530 | }; | ||
531 | |||
532 | int __init lowpan_net_frag_init(void) | ||
533 | { | ||
534 | int ret; | ||
535 | |||
536 | ret = lowpan_frags_sysctl_register(); | ||
537 | if (ret) | ||
538 | goto out; | ||
539 | |||
540 | ret = register_pernet_subsys(&lowpan_frags_ops); | ||
541 | if (ret) | ||
542 | goto err_pernet; | ||
543 | |||
544 | lowpan_frags.hashfn = lowpan_hashfn; | ||
545 | lowpan_frags.constructor = lowpan_frag_init; | ||
546 | lowpan_frags.destructor = NULL; | ||
547 | lowpan_frags.skb_free = NULL; | ||
548 | lowpan_frags.qsize = sizeof(struct frag_queue); | ||
549 | lowpan_frags.match = lowpan_frag_match; | ||
550 | lowpan_frags.frag_expire = lowpan_frag_expire; | ||
551 | lowpan_frags.secret_interval = 10 * 60 * HZ; | ||
552 | inet_frags_init(&lowpan_frags); | ||
553 | err_pernet: | ||
554 | lowpan_frags_sysctl_unregister(); | ||
555 | out: | ||
556 | return ret; | ||
557 | } | ||
558 | |||
559 | void lowpan_net_frag_exit(void) | ||
560 | { | ||
561 | inet_frags_fini(&lowpan_frags); | ||
562 | lowpan_frags_sysctl_unregister(); | ||
563 | unregister_pernet_subsys(&lowpan_frags_ops); | ||
564 | } | ||
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 @@ | |||
1 | #ifndef __IEEE802154_6LOWPAN_REASSEMBLY_H__ | ||
2 | #define __IEEE802154_6LOWPAN_REASSEMBLY_H__ | ||
3 | |||
4 | #include <net/inet_frag.h> | ||
5 | |||
6 | struct lowpan_create_arg { | ||
7 | __be16 tag; | ||
8 | u16 d_size; | ||
9 | const struct ieee802154_addr *src; | ||
10 | const struct ieee802154_addr *dst; | ||
11 | }; | ||
12 | |||
13 | /* Equivalent of ipv4 struct ip | ||
14 | */ | ||
15 | struct lowpan_frag_queue { | ||
16 | struct inet_frag_queue q; | ||
17 | |||
18 | __be16 tag; | ||
19 | u16 d_size; | ||
20 | struct ieee802154_addr saddr; | ||
21 | struct ieee802154_addr daddr; | ||
22 | }; | ||
23 | |||
24 | static inline u32 ieee802154_addr_hash(const struct ieee802154_addr *a) | ||
25 | { | ||
26 | switch (a->addr_type) { | ||
27 | case IEEE802154_ADDR_LONG: | ||
28 | return (__force u32)((((u32 *)a->hwaddr))[0] ^ | ||
29 | ((u32 *)(a->hwaddr))[1]); | ||
30 | case IEEE802154_ADDR_SHORT: | ||
31 | return (__force u32)(a->short_addr); | ||
32 | default: | ||
33 | return 0; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | static inline bool ieee802154_addr_addr_equal(const struct ieee802154_addr *a1, | ||
38 | const struct ieee802154_addr *a2) | ||
39 | { | ||
40 | if (a1->pan_id != a2->pan_id) | ||
41 | return false; | ||
42 | |||
43 | if (a1->addr_type != a2->addr_type) | ||
44 | return false; | ||
45 | |||
46 | switch (a1->addr_type) { | ||
47 | case IEEE802154_ADDR_LONG: | ||
48 | if (memcmp(a1->hwaddr, a2->hwaddr, IEEE802154_ADDR_LEN)) | ||
49 | return false; | ||
50 | break; | ||
51 | case IEEE802154_ADDR_SHORT: | ||
52 | if (a1->short_addr != a2->short_addr) | ||
53 | return false; | ||
54 | break; | ||
55 | default: | ||
56 | return false; | ||
57 | } | ||
58 | |||
59 | return true; | ||
60 | } | ||
61 | |||
62 | int lowpan_frag_rcv(struct sk_buff *skb, const u8 frag_type); | ||
63 | void lowpan_net_frag_exit(void); | ||
64 | int lowpan_net_frag_init(void); | ||
65 | |||
66 | #endif /* __IEEE802154_6LOWPAN_REASSEMBLY_H__ */ | ||