12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2ea81ac2eSAlexander Aring /*	6LoWPAN fragment reassembly
3ea81ac2eSAlexander Aring  *
4ea81ac2eSAlexander Aring  *	Authors:
5ea81ac2eSAlexander Aring  *	Alexander Aring		<aar@pengutronix.de>
6ea81ac2eSAlexander Aring  *
7ea81ac2eSAlexander Aring  *	Based on: net/ipv6/reassembly.c
8ea81ac2eSAlexander Aring  */
9ea81ac2eSAlexander Aring 
10ea81ac2eSAlexander Aring #define pr_fmt(fmt) "6LoWPAN: " fmt
11ea81ac2eSAlexander Aring 
12ea81ac2eSAlexander Aring #include <linux/net.h>
13ea81ac2eSAlexander Aring #include <linux/list.h>
14ea81ac2eSAlexander Aring #include <linux/netdevice.h>
15ea81ac2eSAlexander Aring #include <linux/random.h>
16ea81ac2eSAlexander Aring #include <linux/jhash.h>
17ea81ac2eSAlexander Aring #include <linux/skbuff.h>
18ea81ac2eSAlexander Aring #include <linux/slab.h>
19ea81ac2eSAlexander Aring #include <linux/export.h>
20ea81ac2eSAlexander Aring 
21ea81ac2eSAlexander Aring #include <net/ieee802154_netdev.h>
22ea81ac2eSAlexander Aring #include <net/6lowpan.h>
2370b095c8SFlorian Westphal #include <net/ipv6_frag.h>
24ea81ac2eSAlexander Aring #include <net/inet_frag.h>
25254c5dbeSPeter Oskolkov #include <net/ip.h>
26ea81ac2eSAlexander Aring 
278691ee59SAlexander Aring #include "6lowpan_i.h"
28ea81ac2eSAlexander Aring 
29ea81ac2eSAlexander Aring static const char lowpan_frags_cache_name[] = "lowpan-frags";
30ea81ac2eSAlexander Aring 
31ea81ac2eSAlexander Aring static struct inet_frags lowpan_frags;
32ea81ac2eSAlexander Aring 
33254c5dbeSPeter Oskolkov static int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *skb,
34f4606583SAlexander Aring 			     struct sk_buff *prev,  struct net_device *ldev);
35ea81ac2eSAlexander Aring 
lowpan_frag_init(struct inet_frag_queue * q,const void * a)36ea81ac2eSAlexander Aring static void lowpan_frag_init(struct inet_frag_queue *q, const void *a)
37ea81ac2eSAlexander Aring {
38648700f7SEric Dumazet 	const struct frag_lowpan_compare_key *key = a;
39ea81ac2eSAlexander Aring 
40648700f7SEric Dumazet 	BUILD_BUG_ON(sizeof(*key) > sizeof(q->key));
41648700f7SEric Dumazet 	memcpy(&q->key, key, sizeof(*key));
42ea81ac2eSAlexander Aring }
43ea81ac2eSAlexander Aring 
lowpan_frag_expire(struct timer_list * t)4478802011SKees Cook static void lowpan_frag_expire(struct timer_list *t)
45ea81ac2eSAlexander Aring {
4678802011SKees Cook 	struct inet_frag_queue *frag = from_timer(frag, t, timer);
47ea81ac2eSAlexander Aring 	struct frag_queue *fq;
48ea81ac2eSAlexander Aring 
4978802011SKees Cook 	fq = container_of(frag, struct frag_queue, q);
50ea81ac2eSAlexander Aring 
51ea81ac2eSAlexander Aring 	spin_lock(&fq->q.lock);
52ea81ac2eSAlexander Aring 
53ea81ac2eSAlexander Aring 	if (fq->q.flags & INET_FRAG_COMPLETE)
54ea81ac2eSAlexander Aring 		goto out;
55ea81ac2eSAlexander Aring 
56093ba729SEric Dumazet 	inet_frag_kill(&fq->q);
57ea81ac2eSAlexander Aring out:
58ea81ac2eSAlexander Aring 	spin_unlock(&fq->q.lock);
59093ba729SEric Dumazet 	inet_frag_put(&fq->q);
60ea81ac2eSAlexander Aring }
61ea81ac2eSAlexander Aring 
62ea81ac2eSAlexander Aring static inline struct lowpan_frag_queue *
fq_find(struct net * net,const struct lowpan_802154_cb * cb,const struct ieee802154_addr * src,const struct ieee802154_addr * dst)6372a5e6bbSAlexander Aring fq_find(struct net *net, const struct lowpan_802154_cb *cb,
64ea81ac2eSAlexander Aring 	const struct ieee802154_addr *src,
65ea81ac2eSAlexander Aring 	const struct ieee802154_addr *dst)
66ea81ac2eSAlexander Aring {
67ea81ac2eSAlexander Aring 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
68ea81ac2eSAlexander Aring 		net_ieee802154_lowpan(net);
69f18fa5deSAlexander Aring 	struct frag_lowpan_compare_key key = {};
70648700f7SEric Dumazet 	struct inet_frag_queue *q;
71ea81ac2eSAlexander Aring 
72f18fa5deSAlexander Aring 	key.tag = cb->d_tag;
73f18fa5deSAlexander Aring 	key.d_size = cb->d_size;
74f18fa5deSAlexander Aring 	key.src = *src;
75f18fa5deSAlexander Aring 	key.dst = *dst;
76f18fa5deSAlexander Aring 
774907abc6SEric Dumazet 	q = inet_frag_find(ieee802154_lowpan->fqdir, &key);
782d44ed22SEric Dumazet 	if (!q)
79ea81ac2eSAlexander Aring 		return NULL;
802d44ed22SEric Dumazet 
81ea81ac2eSAlexander Aring 	return container_of(q, struct lowpan_frag_queue, q);
82ea81ac2eSAlexander Aring }
83ea81ac2eSAlexander Aring 
lowpan_frag_queue(struct lowpan_frag_queue * fq,struct sk_buff * skb,u8 frag_type)84ea81ac2eSAlexander Aring static int lowpan_frag_queue(struct lowpan_frag_queue *fq,
8572a5e6bbSAlexander Aring 			     struct sk_buff *skb, u8 frag_type)
86ea81ac2eSAlexander Aring {
87254c5dbeSPeter Oskolkov 	struct sk_buff *prev_tail;
88f4606583SAlexander Aring 	struct net_device *ldev;
89254c5dbeSPeter Oskolkov 	int end, offset, err;
90254c5dbeSPeter Oskolkov 
91254c5dbeSPeter Oskolkov 	/* inet_frag_queue_* functions use skb->cb; see struct ipfrag_skb_cb
92254c5dbeSPeter Oskolkov 	 * in inet_fragment.c
93254c5dbeSPeter Oskolkov 	 */
94254c5dbeSPeter Oskolkov 	BUILD_BUG_ON(sizeof(struct lowpan_802154_cb) > sizeof(struct inet_skb_parm));
95254c5dbeSPeter Oskolkov 	BUILD_BUG_ON(sizeof(struct lowpan_802154_cb) > sizeof(struct inet6_skb_parm));
96ea81ac2eSAlexander Aring 
97ea81ac2eSAlexander Aring 	if (fq->q.flags & INET_FRAG_COMPLETE)
98ea81ac2eSAlexander Aring 		goto err;
99ea81ac2eSAlexander Aring 
10072a5e6bbSAlexander Aring 	offset = lowpan_802154_cb(skb)->d_offset << 3;
10172a5e6bbSAlexander Aring 	end = lowpan_802154_cb(skb)->d_size;
102ea81ac2eSAlexander Aring 
103ea81ac2eSAlexander Aring 	/* Is this the final fragment? */
104ea81ac2eSAlexander Aring 	if (offset + skb->len == end) {
105ea81ac2eSAlexander Aring 		/* If we already have some bits beyond end
106ea81ac2eSAlexander Aring 		 * or have different end, the segment is corrupted.
107ea81ac2eSAlexander Aring 		 */
108ea81ac2eSAlexander Aring 		if (end < fq->q.len ||
109ea81ac2eSAlexander Aring 		    ((fq->q.flags & INET_FRAG_LAST_IN) && end != fq->q.len))
110ea81ac2eSAlexander Aring 			goto err;
111ea81ac2eSAlexander Aring 		fq->q.flags |= INET_FRAG_LAST_IN;
112ea81ac2eSAlexander Aring 		fq->q.len = end;
113ea81ac2eSAlexander Aring 	} else {
114ea81ac2eSAlexander Aring 		if (end > fq->q.len) {
115ea81ac2eSAlexander Aring 			/* Some bits beyond end -> corruption. */
116ea81ac2eSAlexander Aring 			if (fq->q.flags & INET_FRAG_LAST_IN)
117ea81ac2eSAlexander Aring 				goto err;
118ea81ac2eSAlexander Aring 			fq->q.len = end;
119ea81ac2eSAlexander Aring 		}
120ea81ac2eSAlexander Aring 	}
121ea81ac2eSAlexander Aring 
122f4606583SAlexander Aring 	ldev = skb->dev;
123f4606583SAlexander Aring 	if (ldev)
124ea81ac2eSAlexander Aring 		skb->dev = NULL;
125254c5dbeSPeter Oskolkov 	barrier();
126254c5dbeSPeter Oskolkov 
127254c5dbeSPeter Oskolkov 	prev_tail = fq->q.fragments_tail;
128254c5dbeSPeter Oskolkov 	err = inet_frag_queue_insert(&fq->q, skb, offset, end);
129254c5dbeSPeter Oskolkov 	if (err)
130254c5dbeSPeter Oskolkov 		goto err;
131ea81ac2eSAlexander Aring 
132ea81ac2eSAlexander Aring 	fq->q.stamp = skb->tstamp;
133335c8cf3SMartin KaFai Lau 	fq->q.mono_delivery_time = skb->mono_delivery_time;
13472a5e6bbSAlexander Aring 	if (frag_type == LOWPAN_DISPATCH_FRAG1)
135ea81ac2eSAlexander Aring 		fq->q.flags |= INET_FRAG_FIRST_IN;
13672a5e6bbSAlexander Aring 
137ea81ac2eSAlexander Aring 	fq->q.meat += skb->len;
1386ce3b4dcSEric Dumazet 	add_frag_mem_limit(fq->q.fqdir, skb->truesize);
139ea81ac2eSAlexander Aring 
140ea81ac2eSAlexander Aring 	if (fq->q.flags == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
141ea81ac2eSAlexander Aring 	    fq->q.meat == fq->q.len) {
142ea81ac2eSAlexander Aring 		int res;
143ea81ac2eSAlexander Aring 		unsigned long orefdst = skb->_skb_refdst;
144ea81ac2eSAlexander Aring 
145ea81ac2eSAlexander Aring 		skb->_skb_refdst = 0UL;
146254c5dbeSPeter Oskolkov 		res = lowpan_frag_reasm(fq, skb, prev_tail, ldev);
147ea81ac2eSAlexander Aring 		skb->_skb_refdst = orefdst;
148ea81ac2eSAlexander Aring 		return res;
149ea81ac2eSAlexander Aring 	}
150254c5dbeSPeter Oskolkov 	skb_dst_drop(skb);
151ea81ac2eSAlexander Aring 
152ea81ac2eSAlexander Aring 	return -1;
153ea81ac2eSAlexander Aring err:
154ea81ac2eSAlexander Aring 	kfree_skb(skb);
155ea81ac2eSAlexander Aring 	return -1;
156ea81ac2eSAlexander Aring }
157ea81ac2eSAlexander Aring 
158ea81ac2eSAlexander Aring /*	Check if this packet is complete.
159ea81ac2eSAlexander Aring  *
160ea81ac2eSAlexander Aring  *	It is called with locked fq, and caller must check that
161ea81ac2eSAlexander Aring  *	queue is eligible for reassembly i.e. it is not COMPLETE,
162ea81ac2eSAlexander Aring  *	the last and the first frames arrived and all the bits are here.
163ea81ac2eSAlexander Aring  */
lowpan_frag_reasm(struct lowpan_frag_queue * fq,struct sk_buff * skb,struct sk_buff * prev_tail,struct net_device * ldev)164254c5dbeSPeter Oskolkov static int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *skb,
165254c5dbeSPeter Oskolkov 			     struct sk_buff *prev_tail, struct net_device *ldev)
166ea81ac2eSAlexander Aring {
167254c5dbeSPeter Oskolkov 	void *reasm_data;
168ea81ac2eSAlexander Aring 
169093ba729SEric Dumazet 	inet_frag_kill(&fq->q);
170ea81ac2eSAlexander Aring 
171254c5dbeSPeter Oskolkov 	reasm_data = inet_frag_reasm_prepare(&fq->q, skb, prev_tail);
172254c5dbeSPeter Oskolkov 	if (!reasm_data)
173ea81ac2eSAlexander Aring 		goto out_oom;
174891584f4SGuillaume Nault 	inet_frag_reasm_finish(&fq->q, skb, reasm_data, false);
175ea81ac2eSAlexander Aring 
176254c5dbeSPeter Oskolkov 	skb->dev = ldev;
177254c5dbeSPeter Oskolkov 	skb->tstamp = fq->q.stamp;
178254c5dbeSPeter Oskolkov 	fq->q.rb_fragments = RB_ROOT;
179ea81ac2eSAlexander Aring 	fq->q.fragments_tail = NULL;
180254c5dbeSPeter Oskolkov 	fq->q.last_run_head = NULL;
181ea81ac2eSAlexander Aring 
182ea81ac2eSAlexander Aring 	return 1;
183ea81ac2eSAlexander Aring out_oom:
184ea81ac2eSAlexander Aring 	net_dbg_ratelimited("lowpan_frag_reasm: no memory for reassembly\n");
185ea81ac2eSAlexander Aring 	return -1;
186ea81ac2eSAlexander Aring }
187ea81ac2eSAlexander Aring 
lowpan_frag_rx_handlers_result(struct sk_buff * skb,lowpan_rx_result res)18872a5e6bbSAlexander Aring static int lowpan_frag_rx_handlers_result(struct sk_buff *skb,
18972a5e6bbSAlexander Aring 					  lowpan_rx_result res)
19072a5e6bbSAlexander Aring {
19172a5e6bbSAlexander Aring 	switch (res) {
19272a5e6bbSAlexander Aring 	case RX_QUEUED:
19372a5e6bbSAlexander Aring 		return NET_RX_SUCCESS;
19472a5e6bbSAlexander Aring 	case RX_CONTINUE:
19572a5e6bbSAlexander Aring 		/* nobody cared about this packet */
19672a5e6bbSAlexander Aring 		net_warn_ratelimited("%s: received unknown dispatch\n",
19772a5e6bbSAlexander Aring 				     __func__);
19872a5e6bbSAlexander Aring 
199df561f66SGustavo A. R. Silva 		fallthrough;
20072a5e6bbSAlexander Aring 	default:
20172a5e6bbSAlexander Aring 		/* all others failure */
20272a5e6bbSAlexander Aring 		return NET_RX_DROP;
20372a5e6bbSAlexander Aring 	}
20472a5e6bbSAlexander Aring }
20572a5e6bbSAlexander Aring 
lowpan_frag_rx_h_iphc(struct sk_buff * skb)20672a5e6bbSAlexander Aring static lowpan_rx_result lowpan_frag_rx_h_iphc(struct sk_buff *skb)
20772a5e6bbSAlexander Aring {
20872a5e6bbSAlexander Aring 	int ret;
20972a5e6bbSAlexander Aring 
21072a5e6bbSAlexander Aring 	if (!lowpan_is_iphc(*skb_network_header(skb)))
21172a5e6bbSAlexander Aring 		return RX_CONTINUE;
21272a5e6bbSAlexander Aring 
21372a5e6bbSAlexander Aring 	ret = lowpan_iphc_decompress(skb);
21472a5e6bbSAlexander Aring 	if (ret < 0)
21572a5e6bbSAlexander Aring 		return RX_DROP;
21672a5e6bbSAlexander Aring 
21772a5e6bbSAlexander Aring 	return RX_QUEUED;
21872a5e6bbSAlexander Aring }
21972a5e6bbSAlexander Aring 
lowpan_invoke_frag_rx_handlers(struct sk_buff * skb)22072a5e6bbSAlexander Aring static int lowpan_invoke_frag_rx_handlers(struct sk_buff *skb)
22172a5e6bbSAlexander Aring {
22272a5e6bbSAlexander Aring 	lowpan_rx_result res;
22372a5e6bbSAlexander Aring 
22472a5e6bbSAlexander Aring #define CALL_RXH(rxh)			\
22572a5e6bbSAlexander Aring 	do {				\
22672a5e6bbSAlexander Aring 		res = rxh(skb);	\
22772a5e6bbSAlexander Aring 		if (res != RX_CONTINUE)	\
22872a5e6bbSAlexander Aring 			goto rxh_next;	\
22972a5e6bbSAlexander Aring 	} while (0)
23072a5e6bbSAlexander Aring 
23172a5e6bbSAlexander Aring 	/* likely at first */
23272a5e6bbSAlexander Aring 	CALL_RXH(lowpan_frag_rx_h_iphc);
23372a5e6bbSAlexander Aring 	CALL_RXH(lowpan_rx_h_ipv6);
23472a5e6bbSAlexander Aring 
23572a5e6bbSAlexander Aring rxh_next:
23672a5e6bbSAlexander Aring 	return lowpan_frag_rx_handlers_result(skb, res);
23772a5e6bbSAlexander Aring #undef CALL_RXH
23872a5e6bbSAlexander Aring }
23972a5e6bbSAlexander Aring 
24072a5e6bbSAlexander Aring #define LOWPAN_FRAG_DGRAM_SIZE_HIGH_MASK	0x07
24172a5e6bbSAlexander Aring #define LOWPAN_FRAG_DGRAM_SIZE_HIGH_SHIFT	8
24272a5e6bbSAlexander Aring 
lowpan_get_cb(struct sk_buff * skb,u8 frag_type,struct lowpan_802154_cb * cb)24372a5e6bbSAlexander Aring static int lowpan_get_cb(struct sk_buff *skb, u8 frag_type,
24472a5e6bbSAlexander Aring 			 struct lowpan_802154_cb *cb)
245ea81ac2eSAlexander Aring {
246ea81ac2eSAlexander Aring 	bool fail;
24772a5e6bbSAlexander Aring 	u8 high = 0, low = 0;
248ea81ac2eSAlexander Aring 	__be16 d_tag = 0;
249ea81ac2eSAlexander Aring 
25072a5e6bbSAlexander Aring 	fail = lowpan_fetch_skb(skb, &high, 1);
251ea81ac2eSAlexander Aring 	fail |= lowpan_fetch_skb(skb, &low, 1);
25272a5e6bbSAlexander Aring 	/* remove the dispatch value and use first three bits as high value
25372a5e6bbSAlexander Aring 	 * for the datagram size
25472a5e6bbSAlexander Aring 	 */
25572a5e6bbSAlexander Aring 	cb->d_size = (high & LOWPAN_FRAG_DGRAM_SIZE_HIGH_MASK) <<
25672a5e6bbSAlexander Aring 		LOWPAN_FRAG_DGRAM_SIZE_HIGH_SHIFT | low;
257ea81ac2eSAlexander Aring 	fail |= lowpan_fetch_skb(skb, &d_tag, 2);
25872a5e6bbSAlexander Aring 	cb->d_tag = ntohs(d_tag);
259ea81ac2eSAlexander Aring 
260ea81ac2eSAlexander Aring 	if (frag_type == LOWPAN_DISPATCH_FRAGN) {
26172a5e6bbSAlexander Aring 		fail |= lowpan_fetch_skb(skb, &cb->d_offset, 1);
262ea81ac2eSAlexander Aring 	} else {
263ea81ac2eSAlexander Aring 		skb_reset_network_header(skb);
26472a5e6bbSAlexander Aring 		cb->d_offset = 0;
26572a5e6bbSAlexander Aring 		/* check if datagram_size has ipv6hdr on FRAG1 */
26672a5e6bbSAlexander Aring 		fail |= cb->d_size < sizeof(struct ipv6hdr);
26772a5e6bbSAlexander Aring 		/* check if we can dereference the dispatch value */
26872a5e6bbSAlexander Aring 		fail |= !skb->len;
269ea81ac2eSAlexander Aring 	}
270ea81ac2eSAlexander Aring 
271ea81ac2eSAlexander Aring 	if (unlikely(fail))
272ea81ac2eSAlexander Aring 		return -EIO;
273ea81ac2eSAlexander Aring 
274ea81ac2eSAlexander Aring 	return 0;
275ea81ac2eSAlexander Aring }
276ea81ac2eSAlexander Aring 
lowpan_frag_rcv(struct sk_buff * skb,u8 frag_type)27772a5e6bbSAlexander Aring int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
278ea81ac2eSAlexander Aring {
279ea81ac2eSAlexander Aring 	struct lowpan_frag_queue *fq;
280ea81ac2eSAlexander Aring 	struct net *net = dev_net(skb->dev);
28172a5e6bbSAlexander Aring 	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
282f18fa5deSAlexander Aring 	struct ieee802154_hdr hdr = {};
283ea81ac2eSAlexander Aring 	int err;
284ea81ac2eSAlexander Aring 
28572a5e6bbSAlexander Aring 	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)
28672a5e6bbSAlexander Aring 		goto err;
287ea81ac2eSAlexander Aring 
28872a5e6bbSAlexander Aring 	err = lowpan_get_cb(skb, frag_type, cb);
289ea81ac2eSAlexander Aring 	if (err < 0)
290ea81ac2eSAlexander Aring 		goto err;
291ea81ac2eSAlexander Aring 
29272a5e6bbSAlexander Aring 	if (frag_type == LOWPAN_DISPATCH_FRAG1) {
29372a5e6bbSAlexander Aring 		err = lowpan_invoke_frag_rx_handlers(skb);
29472a5e6bbSAlexander Aring 		if (err == NET_RX_DROP)
29572a5e6bbSAlexander Aring 			goto err;
29672a5e6bbSAlexander Aring 	}
29772a5e6bbSAlexander Aring 
29872a5e6bbSAlexander Aring 	if (cb->d_size > IPV6_MIN_MTU) {
299ea81ac2eSAlexander Aring 		net_warn_ratelimited("lowpan_frag_rcv: datagram size exceeds MTU\n");
300ea81ac2eSAlexander Aring 		goto err;
301ea81ac2eSAlexander Aring 	}
302ea81ac2eSAlexander Aring 
30372a5e6bbSAlexander Aring 	fq = fq_find(net, cb, &hdr.source, &hdr.dest);
304ea81ac2eSAlexander Aring 	if (fq != NULL) {
305ea81ac2eSAlexander Aring 		int ret;
306ea81ac2eSAlexander Aring 
307ea81ac2eSAlexander Aring 		spin_lock(&fq->q.lock);
308ea81ac2eSAlexander Aring 		ret = lowpan_frag_queue(fq, skb, frag_type);
309ea81ac2eSAlexander Aring 		spin_unlock(&fq->q.lock);
310ea81ac2eSAlexander Aring 
311093ba729SEric Dumazet 		inet_frag_put(&fq->q);
312ea81ac2eSAlexander Aring 		return ret;
313ea81ac2eSAlexander Aring 	}
314ea81ac2eSAlexander Aring 
315ea81ac2eSAlexander Aring err:
316ea81ac2eSAlexander Aring 	kfree_skb(skb);
317ea81ac2eSAlexander Aring 	return -1;
318ea81ac2eSAlexander Aring }
319ea81ac2eSAlexander Aring 
320ea81ac2eSAlexander Aring #ifdef CONFIG_SYSCTL
321ea81ac2eSAlexander Aring 
322ea81ac2eSAlexander Aring static struct ctl_table lowpan_frags_ns_ctl_table[] = {
323ea81ac2eSAlexander Aring 	{
324ea81ac2eSAlexander Aring 		.procname	= "6lowpanfrag_high_thresh",
3253e67f106SEric Dumazet 		.maxlen		= sizeof(unsigned long),
326ea81ac2eSAlexander Aring 		.mode		= 0644,
3273e67f106SEric Dumazet 		.proc_handler	= proc_doulongvec_minmax,
328ea81ac2eSAlexander Aring 	},
329ea81ac2eSAlexander Aring 	{
330ea81ac2eSAlexander Aring 		.procname	= "6lowpanfrag_low_thresh",
3313e67f106SEric Dumazet 		.maxlen		= sizeof(unsigned long),
332ea81ac2eSAlexander Aring 		.mode		= 0644,
3333e67f106SEric Dumazet 		.proc_handler	= proc_doulongvec_minmax,
334ea81ac2eSAlexander Aring 	},
335ea81ac2eSAlexander Aring 	{
336ea81ac2eSAlexander Aring 		.procname	= "6lowpanfrag_time",
337ea81ac2eSAlexander Aring 		.maxlen		= sizeof(int),
338ea81ac2eSAlexander Aring 		.mode		= 0644,
339ea81ac2eSAlexander Aring 		.proc_handler	= proc_dointvec_jiffies,
340ea81ac2eSAlexander Aring 	},
341ea81ac2eSAlexander Aring 	{ }
342ea81ac2eSAlexander Aring };
343ea81ac2eSAlexander Aring 
344ea81ac2eSAlexander Aring /* secret interval has been deprecated */
345ea81ac2eSAlexander Aring static int lowpan_frags_secret_interval_unused;
346ea81ac2eSAlexander Aring static struct ctl_table lowpan_frags_ctl_table[] = {
347ea81ac2eSAlexander Aring 	{
348ea81ac2eSAlexander Aring 		.procname	= "6lowpanfrag_secret_interval",
349ea81ac2eSAlexander Aring 		.data		= &lowpan_frags_secret_interval_unused,
350ea81ac2eSAlexander Aring 		.maxlen		= sizeof(int),
351ea81ac2eSAlexander Aring 		.mode		= 0644,
352ea81ac2eSAlexander Aring 		.proc_handler	= proc_dointvec_jiffies,
353ea81ac2eSAlexander Aring 	},
354ea81ac2eSAlexander Aring 	{ }
355ea81ac2eSAlexander Aring };
356ea81ac2eSAlexander Aring 
lowpan_frags_ns_sysctl_register(struct net * net)357ea81ac2eSAlexander Aring static int __net_init lowpan_frags_ns_sysctl_register(struct net *net)
358ea81ac2eSAlexander Aring {
359ea81ac2eSAlexander Aring 	struct ctl_table *table;
360ea81ac2eSAlexander Aring 	struct ctl_table_header *hdr;
361ea81ac2eSAlexander Aring 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
362ea81ac2eSAlexander Aring 		net_ieee802154_lowpan(net);
363*c899710fSJoel Granados 	size_t table_size = ARRAY_SIZE(lowpan_frags_ns_ctl_table);
364ea81ac2eSAlexander Aring 
365ea81ac2eSAlexander Aring 	table = lowpan_frags_ns_ctl_table;
366ea81ac2eSAlexander Aring 	if (!net_eq(net, &init_net)) {
367ea81ac2eSAlexander Aring 		table = kmemdup(table, sizeof(lowpan_frags_ns_ctl_table),
368ea81ac2eSAlexander Aring 				GFP_KERNEL);
369ea81ac2eSAlexander Aring 		if (table == NULL)
370ea81ac2eSAlexander Aring 			goto err_alloc;
371ea81ac2eSAlexander Aring 
372d2dfd435SEric Dumazet 		/* Don't export sysctls to unprivileged users */
373*c899710fSJoel Granados 		if (net->user_ns != &init_user_ns) {
374d2dfd435SEric Dumazet 			table[0].procname = NULL;
375*c899710fSJoel Granados 			table_size = 0;
376*c899710fSJoel Granados 		}
377d2dfd435SEric Dumazet 	}
378d2dfd435SEric Dumazet 
3794907abc6SEric Dumazet 	table[0].data	= &ieee802154_lowpan->fqdir->high_thresh;
3804907abc6SEric Dumazet 	table[0].extra1	= &ieee802154_lowpan->fqdir->low_thresh;
3814907abc6SEric Dumazet 	table[1].data	= &ieee802154_lowpan->fqdir->low_thresh;
3824907abc6SEric Dumazet 	table[1].extra2	= &ieee802154_lowpan->fqdir->high_thresh;
3834907abc6SEric Dumazet 	table[2].data	= &ieee802154_lowpan->fqdir->timeout;
384ea81ac2eSAlexander Aring 
385*c899710fSJoel Granados 	hdr = register_net_sysctl_sz(net, "net/ieee802154/6lowpan", table,
386*c899710fSJoel Granados 				     table_size);
387ea81ac2eSAlexander Aring 	if (hdr == NULL)
388ea81ac2eSAlexander Aring 		goto err_reg;
389ea81ac2eSAlexander Aring 
390ea81ac2eSAlexander Aring 	ieee802154_lowpan->sysctl.frags_hdr = hdr;
391ea81ac2eSAlexander Aring 	return 0;
392ea81ac2eSAlexander Aring 
393ea81ac2eSAlexander Aring err_reg:
394ea81ac2eSAlexander Aring 	if (!net_eq(net, &init_net))
395ea81ac2eSAlexander Aring 		kfree(table);
396ea81ac2eSAlexander Aring err_alloc:
397ea81ac2eSAlexander Aring 	return -ENOMEM;
398ea81ac2eSAlexander Aring }
399ea81ac2eSAlexander Aring 
lowpan_frags_ns_sysctl_unregister(struct net * net)400ea81ac2eSAlexander Aring static void __net_exit lowpan_frags_ns_sysctl_unregister(struct net *net)
401ea81ac2eSAlexander Aring {
402ea81ac2eSAlexander Aring 	struct ctl_table *table;
403ea81ac2eSAlexander Aring 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
404ea81ac2eSAlexander Aring 		net_ieee802154_lowpan(net);
405ea81ac2eSAlexander Aring 
406ea81ac2eSAlexander Aring 	table = ieee802154_lowpan->sysctl.frags_hdr->ctl_table_arg;
407ea81ac2eSAlexander Aring 	unregister_net_sysctl_table(ieee802154_lowpan->sysctl.frags_hdr);
408ea81ac2eSAlexander Aring 	if (!net_eq(net, &init_net))
409ea81ac2eSAlexander Aring 		kfree(table);
410ea81ac2eSAlexander Aring }
411ea81ac2eSAlexander Aring 
412ea81ac2eSAlexander Aring static struct ctl_table_header *lowpan_ctl_header;
413ea81ac2eSAlexander Aring 
lowpan_frags_sysctl_register(void)414ea81ac2eSAlexander Aring static int __init lowpan_frags_sysctl_register(void)
415ea81ac2eSAlexander Aring {
416ea81ac2eSAlexander Aring 	lowpan_ctl_header = register_net_sysctl(&init_net,
417ea81ac2eSAlexander Aring 						"net/ieee802154/6lowpan",
418ea81ac2eSAlexander Aring 						lowpan_frags_ctl_table);
419ea81ac2eSAlexander Aring 	return lowpan_ctl_header == NULL ? -ENOMEM : 0;
420ea81ac2eSAlexander Aring }
421ea81ac2eSAlexander Aring 
lowpan_frags_sysctl_unregister(void)422ea81ac2eSAlexander Aring static void lowpan_frags_sysctl_unregister(void)
423ea81ac2eSAlexander Aring {
424ea81ac2eSAlexander Aring 	unregister_net_sysctl_table(lowpan_ctl_header);
425ea81ac2eSAlexander Aring }
426ea81ac2eSAlexander Aring #else
lowpan_frags_ns_sysctl_register(struct net * net)427ea81ac2eSAlexander Aring static inline int lowpan_frags_ns_sysctl_register(struct net *net)
428ea81ac2eSAlexander Aring {
429ea81ac2eSAlexander Aring 	return 0;
430ea81ac2eSAlexander Aring }
431ea81ac2eSAlexander Aring 
lowpan_frags_ns_sysctl_unregister(struct net * net)432ea81ac2eSAlexander Aring static inline void lowpan_frags_ns_sysctl_unregister(struct net *net)
433ea81ac2eSAlexander Aring {
434ea81ac2eSAlexander Aring }
435ea81ac2eSAlexander Aring 
lowpan_frags_sysctl_register(void)436ea81ac2eSAlexander Aring static inline int __init lowpan_frags_sysctl_register(void)
437ea81ac2eSAlexander Aring {
438ea81ac2eSAlexander Aring 	return 0;
439ea81ac2eSAlexander Aring }
440ea81ac2eSAlexander Aring 
lowpan_frags_sysctl_unregister(void)441ea81ac2eSAlexander Aring static inline void lowpan_frags_sysctl_unregister(void)
442ea81ac2eSAlexander Aring {
443ea81ac2eSAlexander Aring }
444ea81ac2eSAlexander Aring #endif
445ea81ac2eSAlexander Aring 
lowpan_frags_init_net(struct net * net)446ea81ac2eSAlexander Aring static int __net_init lowpan_frags_init_net(struct net *net)
447ea81ac2eSAlexander Aring {
448ea81ac2eSAlexander Aring 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
449ea81ac2eSAlexander Aring 		net_ieee802154_lowpan(net);
450787bea77SEric Dumazet 	int res;
451ea81ac2eSAlexander Aring 
452ea81ac2eSAlexander Aring 
453a39aca67SEric Dumazet 	res = fqdir_init(&ieee802154_lowpan->fqdir, &lowpan_frags, net);
454787bea77SEric Dumazet 	if (res < 0)
455787bea77SEric Dumazet 		return res;
4564907abc6SEric Dumazet 
4574907abc6SEric Dumazet 	ieee802154_lowpan->fqdir->high_thresh = IPV6_FRAG_HIGH_THRESH;
4584907abc6SEric Dumazet 	ieee802154_lowpan->fqdir->low_thresh = IPV6_FRAG_LOW_THRESH;
4594907abc6SEric Dumazet 	ieee802154_lowpan->fqdir->timeout = IPV6_FRAG_TIMEOUT;
4604907abc6SEric Dumazet 
461787bea77SEric Dumazet 	res = lowpan_frags_ns_sysctl_register(net);
462787bea77SEric Dumazet 	if (res < 0)
4634907abc6SEric Dumazet 		fqdir_exit(ieee802154_lowpan->fqdir);
464787bea77SEric Dumazet 	return res;
465ea81ac2eSAlexander Aring }
466ea81ac2eSAlexander Aring 
lowpan_frags_pre_exit_net(struct net * net)467d5dd8879SEric Dumazet static void __net_exit lowpan_frags_pre_exit_net(struct net *net)
468d5dd8879SEric Dumazet {
469d5dd8879SEric Dumazet 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
470d5dd8879SEric Dumazet 		net_ieee802154_lowpan(net);
471d5dd8879SEric Dumazet 
472d5dd8879SEric Dumazet 	fqdir_pre_exit(ieee802154_lowpan->fqdir);
473d5dd8879SEric Dumazet }
474d5dd8879SEric Dumazet 
lowpan_frags_exit_net(struct net * net)475ea81ac2eSAlexander Aring static void __net_exit lowpan_frags_exit_net(struct net *net)
476ea81ac2eSAlexander Aring {
477ea81ac2eSAlexander Aring 	struct netns_ieee802154_lowpan *ieee802154_lowpan =
478ea81ac2eSAlexander Aring 		net_ieee802154_lowpan(net);
479ea81ac2eSAlexander Aring 
480ea81ac2eSAlexander Aring 	lowpan_frags_ns_sysctl_unregister(net);
4814907abc6SEric Dumazet 	fqdir_exit(ieee802154_lowpan->fqdir);
482ea81ac2eSAlexander Aring }
483ea81ac2eSAlexander Aring 
484ea81ac2eSAlexander Aring static struct pernet_operations lowpan_frags_ops = {
485ea81ac2eSAlexander Aring 	.init		= lowpan_frags_init_net,
486d5dd8879SEric Dumazet 	.pre_exit	= lowpan_frags_pre_exit_net,
487ea81ac2eSAlexander Aring 	.exit		= lowpan_frags_exit_net,
488ea81ac2eSAlexander Aring };
489ea81ac2eSAlexander Aring 
lowpan_key_hashfn(const void * data,u32 len,u32 seed)490648700f7SEric Dumazet static u32 lowpan_key_hashfn(const void *data, u32 len, u32 seed)
491648700f7SEric Dumazet {
492648700f7SEric Dumazet 	return jhash2(data,
493648700f7SEric Dumazet 		      sizeof(struct frag_lowpan_compare_key) / sizeof(u32), seed);
494648700f7SEric Dumazet }
495648700f7SEric Dumazet 
lowpan_obj_hashfn(const void * data,u32 len,u32 seed)496648700f7SEric Dumazet static u32 lowpan_obj_hashfn(const void *data, u32 len, u32 seed)
497648700f7SEric Dumazet {
498648700f7SEric Dumazet 	const struct inet_frag_queue *fq = data;
499648700f7SEric Dumazet 
500648700f7SEric Dumazet 	return jhash2((const u32 *)&fq->key,
501648700f7SEric Dumazet 		      sizeof(struct frag_lowpan_compare_key) / sizeof(u32), seed);
502648700f7SEric Dumazet }
503648700f7SEric Dumazet 
lowpan_obj_cmpfn(struct rhashtable_compare_arg * arg,const void * ptr)504648700f7SEric Dumazet static int lowpan_obj_cmpfn(struct rhashtable_compare_arg *arg, const void *ptr)
505648700f7SEric Dumazet {
506648700f7SEric Dumazet 	const struct frag_lowpan_compare_key *key = arg->key;
507648700f7SEric Dumazet 	const struct inet_frag_queue *fq = ptr;
508648700f7SEric Dumazet 
509648700f7SEric Dumazet 	return !!memcmp(&fq->key, key, sizeof(*key));
510648700f7SEric Dumazet }
511648700f7SEric Dumazet 
512648700f7SEric Dumazet static const struct rhashtable_params lowpan_rhash_params = {
513648700f7SEric Dumazet 	.head_offset		= offsetof(struct inet_frag_queue, node),
514648700f7SEric Dumazet 	.hashfn			= lowpan_key_hashfn,
515648700f7SEric Dumazet 	.obj_hashfn		= lowpan_obj_hashfn,
516648700f7SEric Dumazet 	.obj_cmpfn		= lowpan_obj_cmpfn,
517648700f7SEric Dumazet 	.automatic_shrinking	= true,
518648700f7SEric Dumazet };
519648700f7SEric Dumazet 
lowpan_net_frag_init(void)520ea81ac2eSAlexander Aring int __init lowpan_net_frag_init(void)
521ea81ac2eSAlexander Aring {
522ea81ac2eSAlexander Aring 	int ret;
523ea81ac2eSAlexander Aring 
524ea81ac2eSAlexander Aring 	lowpan_frags.constructor = lowpan_frag_init;
525ea81ac2eSAlexander Aring 	lowpan_frags.destructor = NULL;
526ea81ac2eSAlexander Aring 	lowpan_frags.qsize = sizeof(struct frag_queue);
527ea81ac2eSAlexander Aring 	lowpan_frags.frag_expire = lowpan_frag_expire;
528ea81ac2eSAlexander Aring 	lowpan_frags.frags_cache_name = lowpan_frags_cache_name;
529648700f7SEric Dumazet 	lowpan_frags.rhash_params = lowpan_rhash_params;
530ea81ac2eSAlexander Aring 	ret = inet_frags_init(&lowpan_frags);
531ea81ac2eSAlexander Aring 	if (ret)
532807f1844SEric Dumazet 		goto out;
533ea81ac2eSAlexander Aring 
534807f1844SEric Dumazet 	ret = lowpan_frags_sysctl_register();
535807f1844SEric Dumazet 	if (ret)
536807f1844SEric Dumazet 		goto err_sysctl;
537807f1844SEric Dumazet 
538807f1844SEric Dumazet 	ret = register_pernet_subsys(&lowpan_frags_ops);
539807f1844SEric Dumazet 	if (ret)
540807f1844SEric Dumazet 		goto err_pernet;
541807f1844SEric Dumazet out:
542ea81ac2eSAlexander Aring 	return ret;
543ea81ac2eSAlexander Aring err_pernet:
544ea81ac2eSAlexander Aring 	lowpan_frags_sysctl_unregister();
545807f1844SEric Dumazet err_sysctl:
546807f1844SEric Dumazet 	inet_frags_fini(&lowpan_frags);
547ea81ac2eSAlexander Aring 	return ret;
548ea81ac2eSAlexander Aring }
549ea81ac2eSAlexander Aring 
lowpan_net_frag_exit(void)550ea81ac2eSAlexander Aring void lowpan_net_frag_exit(void)
551ea81ac2eSAlexander Aring {
552ea81ac2eSAlexander Aring 	lowpan_frags_sysctl_unregister();
553ea81ac2eSAlexander Aring 	unregister_pernet_subsys(&lowpan_frags_ops);
554ae7352d3SEric Dumazet 	inet_frags_fini(&lowpan_frags);
555ea81ac2eSAlexander Aring }
556