18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*	6LoWPAN fragment reassembly
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci *	Authors:
58c2ecf20Sopenharmony_ci *	Alexander Aring		<aar@pengutronix.de>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *	Based on: net/ipv6/reassembly.c
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "6LoWPAN: " fmt
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/net.h>
138c2ecf20Sopenharmony_ci#include <linux/list.h>
148c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
158c2ecf20Sopenharmony_ci#include <linux/random.h>
168c2ecf20Sopenharmony_ci#include <linux/jhash.h>
178c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/export.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <net/ieee802154_netdev.h>
228c2ecf20Sopenharmony_ci#include <net/6lowpan.h>
238c2ecf20Sopenharmony_ci#include <net/ipv6_frag.h>
248c2ecf20Sopenharmony_ci#include <net/inet_frag.h>
258c2ecf20Sopenharmony_ci#include <net/ip.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include "6lowpan_i.h"
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic const char lowpan_frags_cache_name[] = "lowpan-frags";
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic struct inet_frags lowpan_frags;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *skb,
348c2ecf20Sopenharmony_ci			     struct sk_buff *prev,  struct net_device *ldev);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic void lowpan_frag_init(struct inet_frag_queue *q, const void *a)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	const struct frag_lowpan_compare_key *key = a;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	BUILD_BUG_ON(sizeof(*key) > sizeof(q->key));
418c2ecf20Sopenharmony_ci	memcpy(&q->key, key, sizeof(*key));
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic void lowpan_frag_expire(struct timer_list *t)
458c2ecf20Sopenharmony_ci{
468c2ecf20Sopenharmony_ci	struct inet_frag_queue *frag = from_timer(frag, t, timer);
478c2ecf20Sopenharmony_ci	struct frag_queue *fq;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	fq = container_of(frag, struct frag_queue, q);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	spin_lock(&fq->q.lock);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	if (fq->q.flags & INET_FRAG_COMPLETE)
548c2ecf20Sopenharmony_ci		goto out;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	inet_frag_kill(&fq->q);
578c2ecf20Sopenharmony_ciout:
588c2ecf20Sopenharmony_ci	spin_unlock(&fq->q.lock);
598c2ecf20Sopenharmony_ci	inet_frag_put(&fq->q);
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic inline struct lowpan_frag_queue *
638c2ecf20Sopenharmony_cifq_find(struct net *net, const struct lowpan_802154_cb *cb,
648c2ecf20Sopenharmony_ci	const struct ieee802154_addr *src,
658c2ecf20Sopenharmony_ci	const struct ieee802154_addr *dst)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
688c2ecf20Sopenharmony_ci		net_ieee802154_lowpan(net);
698c2ecf20Sopenharmony_ci	struct frag_lowpan_compare_key key = {};
708c2ecf20Sopenharmony_ci	struct inet_frag_queue *q;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	key.tag = cb->d_tag;
738c2ecf20Sopenharmony_ci	key.d_size = cb->d_size;
748c2ecf20Sopenharmony_ci	key.src = *src;
758c2ecf20Sopenharmony_ci	key.dst = *dst;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	q = inet_frag_find(ieee802154_lowpan->fqdir, &key);
788c2ecf20Sopenharmony_ci	if (!q)
798c2ecf20Sopenharmony_ci		return NULL;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	return container_of(q, struct lowpan_frag_queue, q);
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic int lowpan_frag_queue(struct lowpan_frag_queue *fq,
858c2ecf20Sopenharmony_ci			     struct sk_buff *skb, u8 frag_type)
868c2ecf20Sopenharmony_ci{
878c2ecf20Sopenharmony_ci	struct sk_buff *prev_tail;
888c2ecf20Sopenharmony_ci	struct net_device *ldev;
898c2ecf20Sopenharmony_ci	int end, offset, err;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	/* inet_frag_queue_* functions use skb->cb; see struct ipfrag_skb_cb
928c2ecf20Sopenharmony_ci	 * in inet_fragment.c
938c2ecf20Sopenharmony_ci	 */
948c2ecf20Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct lowpan_802154_cb) > sizeof(struct inet_skb_parm));
958c2ecf20Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct lowpan_802154_cb) > sizeof(struct inet6_skb_parm));
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	if (fq->q.flags & INET_FRAG_COMPLETE)
988c2ecf20Sopenharmony_ci		goto err;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	offset = lowpan_802154_cb(skb)->d_offset << 3;
1018c2ecf20Sopenharmony_ci	end = lowpan_802154_cb(skb)->d_size;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	/* Is this the final fragment? */
1048c2ecf20Sopenharmony_ci	if (offset + skb->len == end) {
1058c2ecf20Sopenharmony_ci		/* If we already have some bits beyond end
1068c2ecf20Sopenharmony_ci		 * or have different end, the segment is corrupted.
1078c2ecf20Sopenharmony_ci		 */
1088c2ecf20Sopenharmony_ci		if (end < fq->q.len ||
1098c2ecf20Sopenharmony_ci		    ((fq->q.flags & INET_FRAG_LAST_IN) && end != fq->q.len))
1108c2ecf20Sopenharmony_ci			goto err;
1118c2ecf20Sopenharmony_ci		fq->q.flags |= INET_FRAG_LAST_IN;
1128c2ecf20Sopenharmony_ci		fq->q.len = end;
1138c2ecf20Sopenharmony_ci	} else {
1148c2ecf20Sopenharmony_ci		if (end > fq->q.len) {
1158c2ecf20Sopenharmony_ci			/* Some bits beyond end -> corruption. */
1168c2ecf20Sopenharmony_ci			if (fq->q.flags & INET_FRAG_LAST_IN)
1178c2ecf20Sopenharmony_ci				goto err;
1188c2ecf20Sopenharmony_ci			fq->q.len = end;
1198c2ecf20Sopenharmony_ci		}
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	ldev = skb->dev;
1238c2ecf20Sopenharmony_ci	if (ldev)
1248c2ecf20Sopenharmony_ci		skb->dev = NULL;
1258c2ecf20Sopenharmony_ci	barrier();
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	prev_tail = fq->q.fragments_tail;
1288c2ecf20Sopenharmony_ci	err = inet_frag_queue_insert(&fq->q, skb, offset, end);
1298c2ecf20Sopenharmony_ci	if (err)
1308c2ecf20Sopenharmony_ci		goto err;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	fq->q.stamp = skb->tstamp;
1338c2ecf20Sopenharmony_ci	if (frag_type == LOWPAN_DISPATCH_FRAG1)
1348c2ecf20Sopenharmony_ci		fq->q.flags |= INET_FRAG_FIRST_IN;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	fq->q.meat += skb->len;
1378c2ecf20Sopenharmony_ci	add_frag_mem_limit(fq->q.fqdir, skb->truesize);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	if (fq->q.flags == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
1408c2ecf20Sopenharmony_ci	    fq->q.meat == fq->q.len) {
1418c2ecf20Sopenharmony_ci		int res;
1428c2ecf20Sopenharmony_ci		unsigned long orefdst = skb->_skb_refdst;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci		skb->_skb_refdst = 0UL;
1458c2ecf20Sopenharmony_ci		res = lowpan_frag_reasm(fq, skb, prev_tail, ldev);
1468c2ecf20Sopenharmony_ci		skb->_skb_refdst = orefdst;
1478c2ecf20Sopenharmony_ci		return res;
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci	skb_dst_drop(skb);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	return -1;
1528c2ecf20Sopenharmony_cierr:
1538c2ecf20Sopenharmony_ci	kfree_skb(skb);
1548c2ecf20Sopenharmony_ci	return -1;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci/*	Check if this packet is complete.
1588c2ecf20Sopenharmony_ci *
1598c2ecf20Sopenharmony_ci *	It is called with locked fq, and caller must check that
1608c2ecf20Sopenharmony_ci *	queue is eligible for reassembly i.e. it is not COMPLETE,
1618c2ecf20Sopenharmony_ci *	the last and the first frames arrived and all the bits are here.
1628c2ecf20Sopenharmony_ci */
1638c2ecf20Sopenharmony_cistatic int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *skb,
1648c2ecf20Sopenharmony_ci			     struct sk_buff *prev_tail, struct net_device *ldev)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	void *reasm_data;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	inet_frag_kill(&fq->q);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	reasm_data = inet_frag_reasm_prepare(&fq->q, skb, prev_tail);
1718c2ecf20Sopenharmony_ci	if (!reasm_data)
1728c2ecf20Sopenharmony_ci		goto out_oom;
1738c2ecf20Sopenharmony_ci	inet_frag_reasm_finish(&fq->q, skb, reasm_data, false);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	skb->dev = ldev;
1768c2ecf20Sopenharmony_ci	skb->tstamp = fq->q.stamp;
1778c2ecf20Sopenharmony_ci	fq->q.rb_fragments = RB_ROOT;
1788c2ecf20Sopenharmony_ci	fq->q.fragments_tail = NULL;
1798c2ecf20Sopenharmony_ci	fq->q.last_run_head = NULL;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	return 1;
1828c2ecf20Sopenharmony_ciout_oom:
1838c2ecf20Sopenharmony_ci	net_dbg_ratelimited("lowpan_frag_reasm: no memory for reassembly\n");
1848c2ecf20Sopenharmony_ci	return -1;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic int lowpan_frag_rx_handlers_result(struct sk_buff *skb,
1888c2ecf20Sopenharmony_ci					  lowpan_rx_result res)
1898c2ecf20Sopenharmony_ci{
1908c2ecf20Sopenharmony_ci	switch (res) {
1918c2ecf20Sopenharmony_ci	case RX_QUEUED:
1928c2ecf20Sopenharmony_ci		return NET_RX_SUCCESS;
1938c2ecf20Sopenharmony_ci	case RX_CONTINUE:
1948c2ecf20Sopenharmony_ci		/* nobody cared about this packet */
1958c2ecf20Sopenharmony_ci		net_warn_ratelimited("%s: received unknown dispatch\n",
1968c2ecf20Sopenharmony_ci				     __func__);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		fallthrough;
1998c2ecf20Sopenharmony_ci	default:
2008c2ecf20Sopenharmony_ci		/* all others failure */
2018c2ecf20Sopenharmony_ci		return NET_RX_DROP;
2028c2ecf20Sopenharmony_ci	}
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic lowpan_rx_result lowpan_frag_rx_h_iphc(struct sk_buff *skb)
2068c2ecf20Sopenharmony_ci{
2078c2ecf20Sopenharmony_ci	int ret;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	if (!lowpan_is_iphc(*skb_network_header(skb)))
2108c2ecf20Sopenharmony_ci		return RX_CONTINUE;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	ret = lowpan_iphc_decompress(skb);
2138c2ecf20Sopenharmony_ci	if (ret < 0)
2148c2ecf20Sopenharmony_ci		return RX_DROP;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	return RX_QUEUED;
2178c2ecf20Sopenharmony_ci}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_cistatic int lowpan_invoke_frag_rx_handlers(struct sk_buff *skb)
2208c2ecf20Sopenharmony_ci{
2218c2ecf20Sopenharmony_ci	lowpan_rx_result res;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci#define CALL_RXH(rxh)			\
2248c2ecf20Sopenharmony_ci	do {				\
2258c2ecf20Sopenharmony_ci		res = rxh(skb);	\
2268c2ecf20Sopenharmony_ci		if (res != RX_CONTINUE)	\
2278c2ecf20Sopenharmony_ci			goto rxh_next;	\
2288c2ecf20Sopenharmony_ci	} while (0)
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	/* likely at first */
2318c2ecf20Sopenharmony_ci	CALL_RXH(lowpan_frag_rx_h_iphc);
2328c2ecf20Sopenharmony_ci	CALL_RXH(lowpan_rx_h_ipv6);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cirxh_next:
2358c2ecf20Sopenharmony_ci	return lowpan_frag_rx_handlers_result(skb, res);
2368c2ecf20Sopenharmony_ci#undef CALL_RXH
2378c2ecf20Sopenharmony_ci}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci#define LOWPAN_FRAG_DGRAM_SIZE_HIGH_MASK	0x07
2408c2ecf20Sopenharmony_ci#define LOWPAN_FRAG_DGRAM_SIZE_HIGH_SHIFT	8
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_cistatic int lowpan_get_cb(struct sk_buff *skb, u8 frag_type,
2438c2ecf20Sopenharmony_ci			 struct lowpan_802154_cb *cb)
2448c2ecf20Sopenharmony_ci{
2458c2ecf20Sopenharmony_ci	bool fail;
2468c2ecf20Sopenharmony_ci	u8 high = 0, low = 0;
2478c2ecf20Sopenharmony_ci	__be16 d_tag = 0;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	fail = lowpan_fetch_skb(skb, &high, 1);
2508c2ecf20Sopenharmony_ci	fail |= lowpan_fetch_skb(skb, &low, 1);
2518c2ecf20Sopenharmony_ci	/* remove the dispatch value and use first three bits as high value
2528c2ecf20Sopenharmony_ci	 * for the datagram size
2538c2ecf20Sopenharmony_ci	 */
2548c2ecf20Sopenharmony_ci	cb->d_size = (high & LOWPAN_FRAG_DGRAM_SIZE_HIGH_MASK) <<
2558c2ecf20Sopenharmony_ci		LOWPAN_FRAG_DGRAM_SIZE_HIGH_SHIFT | low;
2568c2ecf20Sopenharmony_ci	fail |= lowpan_fetch_skb(skb, &d_tag, 2);
2578c2ecf20Sopenharmony_ci	cb->d_tag = ntohs(d_tag);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (frag_type == LOWPAN_DISPATCH_FRAGN) {
2608c2ecf20Sopenharmony_ci		fail |= lowpan_fetch_skb(skb, &cb->d_offset, 1);
2618c2ecf20Sopenharmony_ci	} else {
2628c2ecf20Sopenharmony_ci		skb_reset_network_header(skb);
2638c2ecf20Sopenharmony_ci		cb->d_offset = 0;
2648c2ecf20Sopenharmony_ci		/* check if datagram_size has ipv6hdr on FRAG1 */
2658c2ecf20Sopenharmony_ci		fail |= cb->d_size < sizeof(struct ipv6hdr);
2668c2ecf20Sopenharmony_ci		/* check if we can dereference the dispatch value */
2678c2ecf20Sopenharmony_ci		fail |= !skb->len;
2688c2ecf20Sopenharmony_ci	}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	if (unlikely(fail))
2718c2ecf20Sopenharmony_ci		return -EIO;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return 0;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ciint lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	struct lowpan_frag_queue *fq;
2798c2ecf20Sopenharmony_ci	struct net *net = dev_net(skb->dev);
2808c2ecf20Sopenharmony_ci	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
2818c2ecf20Sopenharmony_ci	struct ieee802154_hdr hdr = {};
2828c2ecf20Sopenharmony_ci	int err;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)
2858c2ecf20Sopenharmony_ci		goto err;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	err = lowpan_get_cb(skb, frag_type, cb);
2888c2ecf20Sopenharmony_ci	if (err < 0)
2898c2ecf20Sopenharmony_ci		goto err;
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	if (frag_type == LOWPAN_DISPATCH_FRAG1) {
2928c2ecf20Sopenharmony_ci		err = lowpan_invoke_frag_rx_handlers(skb);
2938c2ecf20Sopenharmony_ci		if (err == NET_RX_DROP)
2948c2ecf20Sopenharmony_ci			goto err;
2958c2ecf20Sopenharmony_ci	}
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ci	if (cb->d_size > IPV6_MIN_MTU) {
2988c2ecf20Sopenharmony_ci		net_warn_ratelimited("lowpan_frag_rcv: datagram size exceeds MTU\n");
2998c2ecf20Sopenharmony_ci		goto err;
3008c2ecf20Sopenharmony_ci	}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	fq = fq_find(net, cb, &hdr.source, &hdr.dest);
3038c2ecf20Sopenharmony_ci	if (fq != NULL) {
3048c2ecf20Sopenharmony_ci		int ret;
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci		spin_lock(&fq->q.lock);
3078c2ecf20Sopenharmony_ci		ret = lowpan_frag_queue(fq, skb, frag_type);
3088c2ecf20Sopenharmony_ci		spin_unlock(&fq->q.lock);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci		inet_frag_put(&fq->q);
3118c2ecf20Sopenharmony_ci		return ret;
3128c2ecf20Sopenharmony_ci	}
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_cierr:
3158c2ecf20Sopenharmony_ci	kfree_skb(skb);
3168c2ecf20Sopenharmony_ci	return -1;
3178c2ecf20Sopenharmony_ci}
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci#ifdef CONFIG_SYSCTL
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_cistatic struct ctl_table lowpan_frags_ns_ctl_table[] = {
3228c2ecf20Sopenharmony_ci	{
3238c2ecf20Sopenharmony_ci		.procname	= "6lowpanfrag_high_thresh",
3248c2ecf20Sopenharmony_ci		.maxlen		= sizeof(unsigned long),
3258c2ecf20Sopenharmony_ci		.mode		= 0644,
3268c2ecf20Sopenharmony_ci		.proc_handler	= proc_doulongvec_minmax,
3278c2ecf20Sopenharmony_ci	},
3288c2ecf20Sopenharmony_ci	{
3298c2ecf20Sopenharmony_ci		.procname	= "6lowpanfrag_low_thresh",
3308c2ecf20Sopenharmony_ci		.maxlen		= sizeof(unsigned long),
3318c2ecf20Sopenharmony_ci		.mode		= 0644,
3328c2ecf20Sopenharmony_ci		.proc_handler	= proc_doulongvec_minmax,
3338c2ecf20Sopenharmony_ci	},
3348c2ecf20Sopenharmony_ci	{
3358c2ecf20Sopenharmony_ci		.procname	= "6lowpanfrag_time",
3368c2ecf20Sopenharmony_ci		.maxlen		= sizeof(int),
3378c2ecf20Sopenharmony_ci		.mode		= 0644,
3388c2ecf20Sopenharmony_ci		.proc_handler	= proc_dointvec_jiffies,
3398c2ecf20Sopenharmony_ci	},
3408c2ecf20Sopenharmony_ci	{ }
3418c2ecf20Sopenharmony_ci};
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci/* secret interval has been deprecated */
3448c2ecf20Sopenharmony_cistatic int lowpan_frags_secret_interval_unused;
3458c2ecf20Sopenharmony_cistatic struct ctl_table lowpan_frags_ctl_table[] = {
3468c2ecf20Sopenharmony_ci	{
3478c2ecf20Sopenharmony_ci		.procname	= "6lowpanfrag_secret_interval",
3488c2ecf20Sopenharmony_ci		.data		= &lowpan_frags_secret_interval_unused,
3498c2ecf20Sopenharmony_ci		.maxlen		= sizeof(int),
3508c2ecf20Sopenharmony_ci		.mode		= 0644,
3518c2ecf20Sopenharmony_ci		.proc_handler	= proc_dointvec_jiffies,
3528c2ecf20Sopenharmony_ci	},
3538c2ecf20Sopenharmony_ci	{ }
3548c2ecf20Sopenharmony_ci};
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_cistatic int __net_init lowpan_frags_ns_sysctl_register(struct net *net)
3578c2ecf20Sopenharmony_ci{
3588c2ecf20Sopenharmony_ci	struct ctl_table *table;
3598c2ecf20Sopenharmony_ci	struct ctl_table_header *hdr;
3608c2ecf20Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
3618c2ecf20Sopenharmony_ci		net_ieee802154_lowpan(net);
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	table = lowpan_frags_ns_ctl_table;
3648c2ecf20Sopenharmony_ci	if (!net_eq(net, &init_net)) {
3658c2ecf20Sopenharmony_ci		table = kmemdup(table, sizeof(lowpan_frags_ns_ctl_table),
3668c2ecf20Sopenharmony_ci				GFP_KERNEL);
3678c2ecf20Sopenharmony_ci		if (table == NULL)
3688c2ecf20Sopenharmony_ci			goto err_alloc;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci		/* Don't export sysctls to unprivileged users */
3718c2ecf20Sopenharmony_ci		if (net->user_ns != &init_user_ns)
3728c2ecf20Sopenharmony_ci			table[0].procname = NULL;
3738c2ecf20Sopenharmony_ci	}
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	table[0].data	= &ieee802154_lowpan->fqdir->high_thresh;
3768c2ecf20Sopenharmony_ci	table[0].extra1	= &ieee802154_lowpan->fqdir->low_thresh;
3778c2ecf20Sopenharmony_ci	table[1].data	= &ieee802154_lowpan->fqdir->low_thresh;
3788c2ecf20Sopenharmony_ci	table[1].extra2	= &ieee802154_lowpan->fqdir->high_thresh;
3798c2ecf20Sopenharmony_ci	table[2].data	= &ieee802154_lowpan->fqdir->timeout;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	hdr = register_net_sysctl(net, "net/ieee802154/6lowpan", table);
3828c2ecf20Sopenharmony_ci	if (hdr == NULL)
3838c2ecf20Sopenharmony_ci		goto err_reg;
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	ieee802154_lowpan->sysctl.frags_hdr = hdr;
3868c2ecf20Sopenharmony_ci	return 0;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_cierr_reg:
3898c2ecf20Sopenharmony_ci	if (!net_eq(net, &init_net))
3908c2ecf20Sopenharmony_ci		kfree(table);
3918c2ecf20Sopenharmony_cierr_alloc:
3928c2ecf20Sopenharmony_ci	return -ENOMEM;
3938c2ecf20Sopenharmony_ci}
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_cistatic void __net_exit lowpan_frags_ns_sysctl_unregister(struct net *net)
3968c2ecf20Sopenharmony_ci{
3978c2ecf20Sopenharmony_ci	struct ctl_table *table;
3988c2ecf20Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
3998c2ecf20Sopenharmony_ci		net_ieee802154_lowpan(net);
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	table = ieee802154_lowpan->sysctl.frags_hdr->ctl_table_arg;
4028c2ecf20Sopenharmony_ci	unregister_net_sysctl_table(ieee802154_lowpan->sysctl.frags_hdr);
4038c2ecf20Sopenharmony_ci	if (!net_eq(net, &init_net))
4048c2ecf20Sopenharmony_ci		kfree(table);
4058c2ecf20Sopenharmony_ci}
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_cistatic struct ctl_table_header *lowpan_ctl_header;
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_cistatic int __init lowpan_frags_sysctl_register(void)
4108c2ecf20Sopenharmony_ci{
4118c2ecf20Sopenharmony_ci	lowpan_ctl_header = register_net_sysctl(&init_net,
4128c2ecf20Sopenharmony_ci						"net/ieee802154/6lowpan",
4138c2ecf20Sopenharmony_ci						lowpan_frags_ctl_table);
4148c2ecf20Sopenharmony_ci	return lowpan_ctl_header == NULL ? -ENOMEM : 0;
4158c2ecf20Sopenharmony_ci}
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_cistatic void lowpan_frags_sysctl_unregister(void)
4188c2ecf20Sopenharmony_ci{
4198c2ecf20Sopenharmony_ci	unregister_net_sysctl_table(lowpan_ctl_header);
4208c2ecf20Sopenharmony_ci}
4218c2ecf20Sopenharmony_ci#else
4228c2ecf20Sopenharmony_cistatic inline int lowpan_frags_ns_sysctl_register(struct net *net)
4238c2ecf20Sopenharmony_ci{
4248c2ecf20Sopenharmony_ci	return 0;
4258c2ecf20Sopenharmony_ci}
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_cistatic inline void lowpan_frags_ns_sysctl_unregister(struct net *net)
4288c2ecf20Sopenharmony_ci{
4298c2ecf20Sopenharmony_ci}
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_cistatic inline int __init lowpan_frags_sysctl_register(void)
4328c2ecf20Sopenharmony_ci{
4338c2ecf20Sopenharmony_ci	return 0;
4348c2ecf20Sopenharmony_ci}
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_cistatic inline void lowpan_frags_sysctl_unregister(void)
4378c2ecf20Sopenharmony_ci{
4388c2ecf20Sopenharmony_ci}
4398c2ecf20Sopenharmony_ci#endif
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_cistatic int __net_init lowpan_frags_init_net(struct net *net)
4428c2ecf20Sopenharmony_ci{
4438c2ecf20Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
4448c2ecf20Sopenharmony_ci		net_ieee802154_lowpan(net);
4458c2ecf20Sopenharmony_ci	int res;
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	res = fqdir_init(&ieee802154_lowpan->fqdir, &lowpan_frags, net);
4498c2ecf20Sopenharmony_ci	if (res < 0)
4508c2ecf20Sopenharmony_ci		return res;
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	ieee802154_lowpan->fqdir->high_thresh = IPV6_FRAG_HIGH_THRESH;
4538c2ecf20Sopenharmony_ci	ieee802154_lowpan->fqdir->low_thresh = IPV6_FRAG_LOW_THRESH;
4548c2ecf20Sopenharmony_ci	ieee802154_lowpan->fqdir->timeout = IPV6_FRAG_TIMEOUT;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	res = lowpan_frags_ns_sysctl_register(net);
4578c2ecf20Sopenharmony_ci	if (res < 0)
4588c2ecf20Sopenharmony_ci		fqdir_exit(ieee802154_lowpan->fqdir);
4598c2ecf20Sopenharmony_ci	return res;
4608c2ecf20Sopenharmony_ci}
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_cistatic void __net_exit lowpan_frags_pre_exit_net(struct net *net)
4638c2ecf20Sopenharmony_ci{
4648c2ecf20Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
4658c2ecf20Sopenharmony_ci		net_ieee802154_lowpan(net);
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	fqdir_pre_exit(ieee802154_lowpan->fqdir);
4688c2ecf20Sopenharmony_ci}
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_cistatic void __net_exit lowpan_frags_exit_net(struct net *net)
4718c2ecf20Sopenharmony_ci{
4728c2ecf20Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
4738c2ecf20Sopenharmony_ci		net_ieee802154_lowpan(net);
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	lowpan_frags_ns_sysctl_unregister(net);
4768c2ecf20Sopenharmony_ci	fqdir_exit(ieee802154_lowpan->fqdir);
4778c2ecf20Sopenharmony_ci}
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_cistatic struct pernet_operations lowpan_frags_ops = {
4808c2ecf20Sopenharmony_ci	.init		= lowpan_frags_init_net,
4818c2ecf20Sopenharmony_ci	.pre_exit	= lowpan_frags_pre_exit_net,
4828c2ecf20Sopenharmony_ci	.exit		= lowpan_frags_exit_net,
4838c2ecf20Sopenharmony_ci};
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_cistatic u32 lowpan_key_hashfn(const void *data, u32 len, u32 seed)
4868c2ecf20Sopenharmony_ci{
4878c2ecf20Sopenharmony_ci	return jhash2(data,
4888c2ecf20Sopenharmony_ci		      sizeof(struct frag_lowpan_compare_key) / sizeof(u32), seed);
4898c2ecf20Sopenharmony_ci}
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_cistatic u32 lowpan_obj_hashfn(const void *data, u32 len, u32 seed)
4928c2ecf20Sopenharmony_ci{
4938c2ecf20Sopenharmony_ci	const struct inet_frag_queue *fq = data;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	return jhash2((const u32 *)&fq->key,
4968c2ecf20Sopenharmony_ci		      sizeof(struct frag_lowpan_compare_key) / sizeof(u32), seed);
4978c2ecf20Sopenharmony_ci}
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_cistatic int lowpan_obj_cmpfn(struct rhashtable_compare_arg *arg, const void *ptr)
5008c2ecf20Sopenharmony_ci{
5018c2ecf20Sopenharmony_ci	const struct frag_lowpan_compare_key *key = arg->key;
5028c2ecf20Sopenharmony_ci	const struct inet_frag_queue *fq = ptr;
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci	return !!memcmp(&fq->key, key, sizeof(*key));
5058c2ecf20Sopenharmony_ci}
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_cistatic const struct rhashtable_params lowpan_rhash_params = {
5088c2ecf20Sopenharmony_ci	.head_offset		= offsetof(struct inet_frag_queue, node),
5098c2ecf20Sopenharmony_ci	.hashfn			= lowpan_key_hashfn,
5108c2ecf20Sopenharmony_ci	.obj_hashfn		= lowpan_obj_hashfn,
5118c2ecf20Sopenharmony_ci	.obj_cmpfn		= lowpan_obj_cmpfn,
5128c2ecf20Sopenharmony_ci	.automatic_shrinking	= true,
5138c2ecf20Sopenharmony_ci};
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ciint __init lowpan_net_frag_init(void)
5168c2ecf20Sopenharmony_ci{
5178c2ecf20Sopenharmony_ci	int ret;
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	lowpan_frags.constructor = lowpan_frag_init;
5208c2ecf20Sopenharmony_ci	lowpan_frags.destructor = NULL;
5218c2ecf20Sopenharmony_ci	lowpan_frags.qsize = sizeof(struct frag_queue);
5228c2ecf20Sopenharmony_ci	lowpan_frags.frag_expire = lowpan_frag_expire;
5238c2ecf20Sopenharmony_ci	lowpan_frags.frags_cache_name = lowpan_frags_cache_name;
5248c2ecf20Sopenharmony_ci	lowpan_frags.rhash_params = lowpan_rhash_params;
5258c2ecf20Sopenharmony_ci	ret = inet_frags_init(&lowpan_frags);
5268c2ecf20Sopenharmony_ci	if (ret)
5278c2ecf20Sopenharmony_ci		goto out;
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci	ret = lowpan_frags_sysctl_register();
5308c2ecf20Sopenharmony_ci	if (ret)
5318c2ecf20Sopenharmony_ci		goto err_sysctl;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	ret = register_pernet_subsys(&lowpan_frags_ops);
5348c2ecf20Sopenharmony_ci	if (ret)
5358c2ecf20Sopenharmony_ci		goto err_pernet;
5368c2ecf20Sopenharmony_ciout:
5378c2ecf20Sopenharmony_ci	return ret;
5388c2ecf20Sopenharmony_cierr_pernet:
5398c2ecf20Sopenharmony_ci	lowpan_frags_sysctl_unregister();
5408c2ecf20Sopenharmony_cierr_sysctl:
5418c2ecf20Sopenharmony_ci	inet_frags_fini(&lowpan_frags);
5428c2ecf20Sopenharmony_ci	return ret;
5438c2ecf20Sopenharmony_ci}
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_civoid lowpan_net_frag_exit(void)
5468c2ecf20Sopenharmony_ci{
5478c2ecf20Sopenharmony_ci	lowpan_frags_sysctl_unregister();
5488c2ecf20Sopenharmony_ci	unregister_pernet_subsys(&lowpan_frags_ops);
5498c2ecf20Sopenharmony_ci	inet_frags_fini(&lowpan_frags);
5508c2ecf20Sopenharmony_ci}
551