162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*	6LoWPAN fragment reassembly
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci *	Authors:
562306a36Sopenharmony_ci *	Alexander Aring		<aar@pengutronix.de>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *	Based on: net/ipv6/reassembly.c
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define pr_fmt(fmt) "6LoWPAN: " fmt
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/net.h>
1362306a36Sopenharmony_ci#include <linux/list.h>
1462306a36Sopenharmony_ci#include <linux/netdevice.h>
1562306a36Sopenharmony_ci#include <linux/random.h>
1662306a36Sopenharmony_ci#include <linux/jhash.h>
1762306a36Sopenharmony_ci#include <linux/skbuff.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <linux/export.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <net/ieee802154_netdev.h>
2262306a36Sopenharmony_ci#include <net/6lowpan.h>
2362306a36Sopenharmony_ci#include <net/ipv6_frag.h>
2462306a36Sopenharmony_ci#include <net/inet_frag.h>
2562306a36Sopenharmony_ci#include <net/ip.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include "6lowpan_i.h"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic const char lowpan_frags_cache_name[] = "lowpan-frags";
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic struct inet_frags lowpan_frags;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *skb,
3462306a36Sopenharmony_ci			     struct sk_buff *prev,  struct net_device *ldev);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic void lowpan_frag_init(struct inet_frag_queue *q, const void *a)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	const struct frag_lowpan_compare_key *key = a;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(*key) > sizeof(q->key));
4162306a36Sopenharmony_ci	memcpy(&q->key, key, sizeof(*key));
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic void lowpan_frag_expire(struct timer_list *t)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	struct inet_frag_queue *frag = from_timer(frag, t, timer);
4762306a36Sopenharmony_ci	struct frag_queue *fq;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	fq = container_of(frag, struct frag_queue, q);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	spin_lock(&fq->q.lock);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (fq->q.flags & INET_FRAG_COMPLETE)
5462306a36Sopenharmony_ci		goto out;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	inet_frag_kill(&fq->q);
5762306a36Sopenharmony_ciout:
5862306a36Sopenharmony_ci	spin_unlock(&fq->q.lock);
5962306a36Sopenharmony_ci	inet_frag_put(&fq->q);
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic inline struct lowpan_frag_queue *
6362306a36Sopenharmony_cifq_find(struct net *net, const struct lowpan_802154_cb *cb,
6462306a36Sopenharmony_ci	const struct ieee802154_addr *src,
6562306a36Sopenharmony_ci	const struct ieee802154_addr *dst)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
6862306a36Sopenharmony_ci		net_ieee802154_lowpan(net);
6962306a36Sopenharmony_ci	struct frag_lowpan_compare_key key = {};
7062306a36Sopenharmony_ci	struct inet_frag_queue *q;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	key.tag = cb->d_tag;
7362306a36Sopenharmony_ci	key.d_size = cb->d_size;
7462306a36Sopenharmony_ci	key.src = *src;
7562306a36Sopenharmony_ci	key.dst = *dst;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	q = inet_frag_find(ieee802154_lowpan->fqdir, &key);
7862306a36Sopenharmony_ci	if (!q)
7962306a36Sopenharmony_ci		return NULL;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return container_of(q, struct lowpan_frag_queue, q);
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int lowpan_frag_queue(struct lowpan_frag_queue *fq,
8562306a36Sopenharmony_ci			     struct sk_buff *skb, u8 frag_type)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct sk_buff *prev_tail;
8862306a36Sopenharmony_ci	struct net_device *ldev;
8962306a36Sopenharmony_ci	int end, offset, err;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	/* inet_frag_queue_* functions use skb->cb; see struct ipfrag_skb_cb
9262306a36Sopenharmony_ci	 * in inet_fragment.c
9362306a36Sopenharmony_ci	 */
9462306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct lowpan_802154_cb) > sizeof(struct inet_skb_parm));
9562306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct lowpan_802154_cb) > sizeof(struct inet6_skb_parm));
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	if (fq->q.flags & INET_FRAG_COMPLETE)
9862306a36Sopenharmony_ci		goto err;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	offset = lowpan_802154_cb(skb)->d_offset << 3;
10162306a36Sopenharmony_ci	end = lowpan_802154_cb(skb)->d_size;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	/* Is this the final fragment? */
10462306a36Sopenharmony_ci	if (offset + skb->len == end) {
10562306a36Sopenharmony_ci		/* If we already have some bits beyond end
10662306a36Sopenharmony_ci		 * or have different end, the segment is corrupted.
10762306a36Sopenharmony_ci		 */
10862306a36Sopenharmony_ci		if (end < fq->q.len ||
10962306a36Sopenharmony_ci		    ((fq->q.flags & INET_FRAG_LAST_IN) && end != fq->q.len))
11062306a36Sopenharmony_ci			goto err;
11162306a36Sopenharmony_ci		fq->q.flags |= INET_FRAG_LAST_IN;
11262306a36Sopenharmony_ci		fq->q.len = end;
11362306a36Sopenharmony_ci	} else {
11462306a36Sopenharmony_ci		if (end > fq->q.len) {
11562306a36Sopenharmony_ci			/* Some bits beyond end -> corruption. */
11662306a36Sopenharmony_ci			if (fq->q.flags & INET_FRAG_LAST_IN)
11762306a36Sopenharmony_ci				goto err;
11862306a36Sopenharmony_ci			fq->q.len = end;
11962306a36Sopenharmony_ci		}
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	ldev = skb->dev;
12362306a36Sopenharmony_ci	if (ldev)
12462306a36Sopenharmony_ci		skb->dev = NULL;
12562306a36Sopenharmony_ci	barrier();
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	prev_tail = fq->q.fragments_tail;
12862306a36Sopenharmony_ci	err = inet_frag_queue_insert(&fq->q, skb, offset, end);
12962306a36Sopenharmony_ci	if (err)
13062306a36Sopenharmony_ci		goto err;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	fq->q.stamp = skb->tstamp;
13362306a36Sopenharmony_ci	fq->q.mono_delivery_time = skb->mono_delivery_time;
13462306a36Sopenharmony_ci	if (frag_type == LOWPAN_DISPATCH_FRAG1)
13562306a36Sopenharmony_ci		fq->q.flags |= INET_FRAG_FIRST_IN;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	fq->q.meat += skb->len;
13862306a36Sopenharmony_ci	add_frag_mem_limit(fq->q.fqdir, skb->truesize);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (fq->q.flags == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
14162306a36Sopenharmony_ci	    fq->q.meat == fq->q.len) {
14262306a36Sopenharmony_ci		int res;
14362306a36Sopenharmony_ci		unsigned long orefdst = skb->_skb_refdst;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci		skb->_skb_refdst = 0UL;
14662306a36Sopenharmony_ci		res = lowpan_frag_reasm(fq, skb, prev_tail, ldev);
14762306a36Sopenharmony_ci		skb->_skb_refdst = orefdst;
14862306a36Sopenharmony_ci		return res;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci	skb_dst_drop(skb);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return -1;
15362306a36Sopenharmony_cierr:
15462306a36Sopenharmony_ci	kfree_skb(skb);
15562306a36Sopenharmony_ci	return -1;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci/*	Check if this packet is complete.
15962306a36Sopenharmony_ci *
16062306a36Sopenharmony_ci *	It is called with locked fq, and caller must check that
16162306a36Sopenharmony_ci *	queue is eligible for reassembly i.e. it is not COMPLETE,
16262306a36Sopenharmony_ci *	the last and the first frames arrived and all the bits are here.
16362306a36Sopenharmony_ci */
16462306a36Sopenharmony_cistatic int lowpan_frag_reasm(struct lowpan_frag_queue *fq, struct sk_buff *skb,
16562306a36Sopenharmony_ci			     struct sk_buff *prev_tail, struct net_device *ldev)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	void *reasm_data;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	inet_frag_kill(&fq->q);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	reasm_data = inet_frag_reasm_prepare(&fq->q, skb, prev_tail);
17262306a36Sopenharmony_ci	if (!reasm_data)
17362306a36Sopenharmony_ci		goto out_oom;
17462306a36Sopenharmony_ci	inet_frag_reasm_finish(&fq->q, skb, reasm_data, false);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	skb->dev = ldev;
17762306a36Sopenharmony_ci	skb->tstamp = fq->q.stamp;
17862306a36Sopenharmony_ci	fq->q.rb_fragments = RB_ROOT;
17962306a36Sopenharmony_ci	fq->q.fragments_tail = NULL;
18062306a36Sopenharmony_ci	fq->q.last_run_head = NULL;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return 1;
18362306a36Sopenharmony_ciout_oom:
18462306a36Sopenharmony_ci	net_dbg_ratelimited("lowpan_frag_reasm: no memory for reassembly\n");
18562306a36Sopenharmony_ci	return -1;
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic int lowpan_frag_rx_handlers_result(struct sk_buff *skb,
18962306a36Sopenharmony_ci					  lowpan_rx_result res)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	switch (res) {
19262306a36Sopenharmony_ci	case RX_QUEUED:
19362306a36Sopenharmony_ci		return NET_RX_SUCCESS;
19462306a36Sopenharmony_ci	case RX_CONTINUE:
19562306a36Sopenharmony_ci		/* nobody cared about this packet */
19662306a36Sopenharmony_ci		net_warn_ratelimited("%s: received unknown dispatch\n",
19762306a36Sopenharmony_ci				     __func__);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci		fallthrough;
20062306a36Sopenharmony_ci	default:
20162306a36Sopenharmony_ci		/* all others failure */
20262306a36Sopenharmony_ci		return NET_RX_DROP;
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic lowpan_rx_result lowpan_frag_rx_h_iphc(struct sk_buff *skb)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	int ret;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	if (!lowpan_is_iphc(*skb_network_header(skb)))
21162306a36Sopenharmony_ci		return RX_CONTINUE;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	ret = lowpan_iphc_decompress(skb);
21462306a36Sopenharmony_ci	if (ret < 0)
21562306a36Sopenharmony_ci		return RX_DROP;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	return RX_QUEUED;
21862306a36Sopenharmony_ci}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic int lowpan_invoke_frag_rx_handlers(struct sk_buff *skb)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	lowpan_rx_result res;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci#define CALL_RXH(rxh)			\
22562306a36Sopenharmony_ci	do {				\
22662306a36Sopenharmony_ci		res = rxh(skb);	\
22762306a36Sopenharmony_ci		if (res != RX_CONTINUE)	\
22862306a36Sopenharmony_ci			goto rxh_next;	\
22962306a36Sopenharmony_ci	} while (0)
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* likely at first */
23262306a36Sopenharmony_ci	CALL_RXH(lowpan_frag_rx_h_iphc);
23362306a36Sopenharmony_ci	CALL_RXH(lowpan_rx_h_ipv6);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cirxh_next:
23662306a36Sopenharmony_ci	return lowpan_frag_rx_handlers_result(skb, res);
23762306a36Sopenharmony_ci#undef CALL_RXH
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci#define LOWPAN_FRAG_DGRAM_SIZE_HIGH_MASK	0x07
24162306a36Sopenharmony_ci#define LOWPAN_FRAG_DGRAM_SIZE_HIGH_SHIFT	8
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic int lowpan_get_cb(struct sk_buff *skb, u8 frag_type,
24462306a36Sopenharmony_ci			 struct lowpan_802154_cb *cb)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	bool fail;
24762306a36Sopenharmony_ci	u8 high = 0, low = 0;
24862306a36Sopenharmony_ci	__be16 d_tag = 0;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	fail = lowpan_fetch_skb(skb, &high, 1);
25162306a36Sopenharmony_ci	fail |= lowpan_fetch_skb(skb, &low, 1);
25262306a36Sopenharmony_ci	/* remove the dispatch value and use first three bits as high value
25362306a36Sopenharmony_ci	 * for the datagram size
25462306a36Sopenharmony_ci	 */
25562306a36Sopenharmony_ci	cb->d_size = (high & LOWPAN_FRAG_DGRAM_SIZE_HIGH_MASK) <<
25662306a36Sopenharmony_ci		LOWPAN_FRAG_DGRAM_SIZE_HIGH_SHIFT | low;
25762306a36Sopenharmony_ci	fail |= lowpan_fetch_skb(skb, &d_tag, 2);
25862306a36Sopenharmony_ci	cb->d_tag = ntohs(d_tag);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	if (frag_type == LOWPAN_DISPATCH_FRAGN) {
26162306a36Sopenharmony_ci		fail |= lowpan_fetch_skb(skb, &cb->d_offset, 1);
26262306a36Sopenharmony_ci	} else {
26362306a36Sopenharmony_ci		skb_reset_network_header(skb);
26462306a36Sopenharmony_ci		cb->d_offset = 0;
26562306a36Sopenharmony_ci		/* check if datagram_size has ipv6hdr on FRAG1 */
26662306a36Sopenharmony_ci		fail |= cb->d_size < sizeof(struct ipv6hdr);
26762306a36Sopenharmony_ci		/* check if we can dereference the dispatch value */
26862306a36Sopenharmony_ci		fail |= !skb->len;
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if (unlikely(fail))
27262306a36Sopenharmony_ci		return -EIO;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	return 0;
27562306a36Sopenharmony_ci}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ciint lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	struct lowpan_frag_queue *fq;
28062306a36Sopenharmony_ci	struct net *net = dev_net(skb->dev);
28162306a36Sopenharmony_ci	struct lowpan_802154_cb *cb = lowpan_802154_cb(skb);
28262306a36Sopenharmony_ci	struct ieee802154_hdr hdr = {};
28362306a36Sopenharmony_ci	int err;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	if (ieee802154_hdr_peek_addrs(skb, &hdr) < 0)
28662306a36Sopenharmony_ci		goto err;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	err = lowpan_get_cb(skb, frag_type, cb);
28962306a36Sopenharmony_ci	if (err < 0)
29062306a36Sopenharmony_ci		goto err;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	if (frag_type == LOWPAN_DISPATCH_FRAG1) {
29362306a36Sopenharmony_ci		err = lowpan_invoke_frag_rx_handlers(skb);
29462306a36Sopenharmony_ci		if (err == NET_RX_DROP)
29562306a36Sopenharmony_ci			goto err;
29662306a36Sopenharmony_ci	}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	if (cb->d_size > IPV6_MIN_MTU) {
29962306a36Sopenharmony_ci		net_warn_ratelimited("lowpan_frag_rcv: datagram size exceeds MTU\n");
30062306a36Sopenharmony_ci		goto err;
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	fq = fq_find(net, cb, &hdr.source, &hdr.dest);
30462306a36Sopenharmony_ci	if (fq != NULL) {
30562306a36Sopenharmony_ci		int ret;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci		spin_lock(&fq->q.lock);
30862306a36Sopenharmony_ci		ret = lowpan_frag_queue(fq, skb, frag_type);
30962306a36Sopenharmony_ci		spin_unlock(&fq->q.lock);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		inet_frag_put(&fq->q);
31262306a36Sopenharmony_ci		return ret;
31362306a36Sopenharmony_ci	}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_cierr:
31662306a36Sopenharmony_ci	kfree_skb(skb);
31762306a36Sopenharmony_ci	return -1;
31862306a36Sopenharmony_ci}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci#ifdef CONFIG_SYSCTL
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic struct ctl_table lowpan_frags_ns_ctl_table[] = {
32362306a36Sopenharmony_ci	{
32462306a36Sopenharmony_ci		.procname	= "6lowpanfrag_high_thresh",
32562306a36Sopenharmony_ci		.maxlen		= sizeof(unsigned long),
32662306a36Sopenharmony_ci		.mode		= 0644,
32762306a36Sopenharmony_ci		.proc_handler	= proc_doulongvec_minmax,
32862306a36Sopenharmony_ci	},
32962306a36Sopenharmony_ci	{
33062306a36Sopenharmony_ci		.procname	= "6lowpanfrag_low_thresh",
33162306a36Sopenharmony_ci		.maxlen		= sizeof(unsigned long),
33262306a36Sopenharmony_ci		.mode		= 0644,
33362306a36Sopenharmony_ci		.proc_handler	= proc_doulongvec_minmax,
33462306a36Sopenharmony_ci	},
33562306a36Sopenharmony_ci	{
33662306a36Sopenharmony_ci		.procname	= "6lowpanfrag_time",
33762306a36Sopenharmony_ci		.maxlen		= sizeof(int),
33862306a36Sopenharmony_ci		.mode		= 0644,
33962306a36Sopenharmony_ci		.proc_handler	= proc_dointvec_jiffies,
34062306a36Sopenharmony_ci	},
34162306a36Sopenharmony_ci	{ }
34262306a36Sopenharmony_ci};
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci/* secret interval has been deprecated */
34562306a36Sopenharmony_cistatic int lowpan_frags_secret_interval_unused;
34662306a36Sopenharmony_cistatic struct ctl_table lowpan_frags_ctl_table[] = {
34762306a36Sopenharmony_ci	{
34862306a36Sopenharmony_ci		.procname	= "6lowpanfrag_secret_interval",
34962306a36Sopenharmony_ci		.data		= &lowpan_frags_secret_interval_unused,
35062306a36Sopenharmony_ci		.maxlen		= sizeof(int),
35162306a36Sopenharmony_ci		.mode		= 0644,
35262306a36Sopenharmony_ci		.proc_handler	= proc_dointvec_jiffies,
35362306a36Sopenharmony_ci	},
35462306a36Sopenharmony_ci	{ }
35562306a36Sopenharmony_ci};
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_cistatic int __net_init lowpan_frags_ns_sysctl_register(struct net *net)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	struct ctl_table *table;
36062306a36Sopenharmony_ci	struct ctl_table_header *hdr;
36162306a36Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
36262306a36Sopenharmony_ci		net_ieee802154_lowpan(net);
36362306a36Sopenharmony_ci	size_t table_size = ARRAY_SIZE(lowpan_frags_ns_ctl_table);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	table = lowpan_frags_ns_ctl_table;
36662306a36Sopenharmony_ci	if (!net_eq(net, &init_net)) {
36762306a36Sopenharmony_ci		table = kmemdup(table, sizeof(lowpan_frags_ns_ctl_table),
36862306a36Sopenharmony_ci				GFP_KERNEL);
36962306a36Sopenharmony_ci		if (table == NULL)
37062306a36Sopenharmony_ci			goto err_alloc;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci		/* Don't export sysctls to unprivileged users */
37362306a36Sopenharmony_ci		if (net->user_ns != &init_user_ns) {
37462306a36Sopenharmony_ci			table[0].procname = NULL;
37562306a36Sopenharmony_ci			table_size = 0;
37662306a36Sopenharmony_ci		}
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	table[0].data	= &ieee802154_lowpan->fqdir->high_thresh;
38062306a36Sopenharmony_ci	table[0].extra1	= &ieee802154_lowpan->fqdir->low_thresh;
38162306a36Sopenharmony_ci	table[1].data	= &ieee802154_lowpan->fqdir->low_thresh;
38262306a36Sopenharmony_ci	table[1].extra2	= &ieee802154_lowpan->fqdir->high_thresh;
38362306a36Sopenharmony_ci	table[2].data	= &ieee802154_lowpan->fqdir->timeout;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	hdr = register_net_sysctl_sz(net, "net/ieee802154/6lowpan", table,
38662306a36Sopenharmony_ci				     table_size);
38762306a36Sopenharmony_ci	if (hdr == NULL)
38862306a36Sopenharmony_ci		goto err_reg;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	ieee802154_lowpan->sysctl.frags_hdr = hdr;
39162306a36Sopenharmony_ci	return 0;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cierr_reg:
39462306a36Sopenharmony_ci	if (!net_eq(net, &init_net))
39562306a36Sopenharmony_ci		kfree(table);
39662306a36Sopenharmony_cierr_alloc:
39762306a36Sopenharmony_ci	return -ENOMEM;
39862306a36Sopenharmony_ci}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_cistatic void __net_exit lowpan_frags_ns_sysctl_unregister(struct net *net)
40162306a36Sopenharmony_ci{
40262306a36Sopenharmony_ci	struct ctl_table *table;
40362306a36Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
40462306a36Sopenharmony_ci		net_ieee802154_lowpan(net);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	table = ieee802154_lowpan->sysctl.frags_hdr->ctl_table_arg;
40762306a36Sopenharmony_ci	unregister_net_sysctl_table(ieee802154_lowpan->sysctl.frags_hdr);
40862306a36Sopenharmony_ci	if (!net_eq(net, &init_net))
40962306a36Sopenharmony_ci		kfree(table);
41062306a36Sopenharmony_ci}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_cistatic struct ctl_table_header *lowpan_ctl_header;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_cistatic int __init lowpan_frags_sysctl_register(void)
41562306a36Sopenharmony_ci{
41662306a36Sopenharmony_ci	lowpan_ctl_header = register_net_sysctl(&init_net,
41762306a36Sopenharmony_ci						"net/ieee802154/6lowpan",
41862306a36Sopenharmony_ci						lowpan_frags_ctl_table);
41962306a36Sopenharmony_ci	return lowpan_ctl_header == NULL ? -ENOMEM : 0;
42062306a36Sopenharmony_ci}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_cistatic void lowpan_frags_sysctl_unregister(void)
42362306a36Sopenharmony_ci{
42462306a36Sopenharmony_ci	unregister_net_sysctl_table(lowpan_ctl_header);
42562306a36Sopenharmony_ci}
42662306a36Sopenharmony_ci#else
42762306a36Sopenharmony_cistatic inline int lowpan_frags_ns_sysctl_register(struct net *net)
42862306a36Sopenharmony_ci{
42962306a36Sopenharmony_ci	return 0;
43062306a36Sopenharmony_ci}
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_cistatic inline void lowpan_frags_ns_sysctl_unregister(struct net *net)
43362306a36Sopenharmony_ci{
43462306a36Sopenharmony_ci}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_cistatic inline int __init lowpan_frags_sysctl_register(void)
43762306a36Sopenharmony_ci{
43862306a36Sopenharmony_ci	return 0;
43962306a36Sopenharmony_ci}
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_cistatic inline void lowpan_frags_sysctl_unregister(void)
44262306a36Sopenharmony_ci{
44362306a36Sopenharmony_ci}
44462306a36Sopenharmony_ci#endif
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_cistatic int __net_init lowpan_frags_init_net(struct net *net)
44762306a36Sopenharmony_ci{
44862306a36Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
44962306a36Sopenharmony_ci		net_ieee802154_lowpan(net);
45062306a36Sopenharmony_ci	int res;
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	res = fqdir_init(&ieee802154_lowpan->fqdir, &lowpan_frags, net);
45462306a36Sopenharmony_ci	if (res < 0)
45562306a36Sopenharmony_ci		return res;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	ieee802154_lowpan->fqdir->high_thresh = IPV6_FRAG_HIGH_THRESH;
45862306a36Sopenharmony_ci	ieee802154_lowpan->fqdir->low_thresh = IPV6_FRAG_LOW_THRESH;
45962306a36Sopenharmony_ci	ieee802154_lowpan->fqdir->timeout = IPV6_FRAG_TIMEOUT;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	res = lowpan_frags_ns_sysctl_register(net);
46262306a36Sopenharmony_ci	if (res < 0)
46362306a36Sopenharmony_ci		fqdir_exit(ieee802154_lowpan->fqdir);
46462306a36Sopenharmony_ci	return res;
46562306a36Sopenharmony_ci}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_cistatic void __net_exit lowpan_frags_pre_exit_net(struct net *net)
46862306a36Sopenharmony_ci{
46962306a36Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
47062306a36Sopenharmony_ci		net_ieee802154_lowpan(net);
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	fqdir_pre_exit(ieee802154_lowpan->fqdir);
47362306a36Sopenharmony_ci}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_cistatic void __net_exit lowpan_frags_exit_net(struct net *net)
47662306a36Sopenharmony_ci{
47762306a36Sopenharmony_ci	struct netns_ieee802154_lowpan *ieee802154_lowpan =
47862306a36Sopenharmony_ci		net_ieee802154_lowpan(net);
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	lowpan_frags_ns_sysctl_unregister(net);
48162306a36Sopenharmony_ci	fqdir_exit(ieee802154_lowpan->fqdir);
48262306a36Sopenharmony_ci}
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_cistatic struct pernet_operations lowpan_frags_ops = {
48562306a36Sopenharmony_ci	.init		= lowpan_frags_init_net,
48662306a36Sopenharmony_ci	.pre_exit	= lowpan_frags_pre_exit_net,
48762306a36Sopenharmony_ci	.exit		= lowpan_frags_exit_net,
48862306a36Sopenharmony_ci};
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_cistatic u32 lowpan_key_hashfn(const void *data, u32 len, u32 seed)
49162306a36Sopenharmony_ci{
49262306a36Sopenharmony_ci	return jhash2(data,
49362306a36Sopenharmony_ci		      sizeof(struct frag_lowpan_compare_key) / sizeof(u32), seed);
49462306a36Sopenharmony_ci}
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_cistatic u32 lowpan_obj_hashfn(const void *data, u32 len, u32 seed)
49762306a36Sopenharmony_ci{
49862306a36Sopenharmony_ci	const struct inet_frag_queue *fq = data;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	return jhash2((const u32 *)&fq->key,
50162306a36Sopenharmony_ci		      sizeof(struct frag_lowpan_compare_key) / sizeof(u32), seed);
50262306a36Sopenharmony_ci}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_cistatic int lowpan_obj_cmpfn(struct rhashtable_compare_arg *arg, const void *ptr)
50562306a36Sopenharmony_ci{
50662306a36Sopenharmony_ci	const struct frag_lowpan_compare_key *key = arg->key;
50762306a36Sopenharmony_ci	const struct inet_frag_queue *fq = ptr;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	return !!memcmp(&fq->key, key, sizeof(*key));
51062306a36Sopenharmony_ci}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_cistatic const struct rhashtable_params lowpan_rhash_params = {
51362306a36Sopenharmony_ci	.head_offset		= offsetof(struct inet_frag_queue, node),
51462306a36Sopenharmony_ci	.hashfn			= lowpan_key_hashfn,
51562306a36Sopenharmony_ci	.obj_hashfn		= lowpan_obj_hashfn,
51662306a36Sopenharmony_ci	.obj_cmpfn		= lowpan_obj_cmpfn,
51762306a36Sopenharmony_ci	.automatic_shrinking	= true,
51862306a36Sopenharmony_ci};
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ciint __init lowpan_net_frag_init(void)
52162306a36Sopenharmony_ci{
52262306a36Sopenharmony_ci	int ret;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	lowpan_frags.constructor = lowpan_frag_init;
52562306a36Sopenharmony_ci	lowpan_frags.destructor = NULL;
52662306a36Sopenharmony_ci	lowpan_frags.qsize = sizeof(struct frag_queue);
52762306a36Sopenharmony_ci	lowpan_frags.frag_expire = lowpan_frag_expire;
52862306a36Sopenharmony_ci	lowpan_frags.frags_cache_name = lowpan_frags_cache_name;
52962306a36Sopenharmony_ci	lowpan_frags.rhash_params = lowpan_rhash_params;
53062306a36Sopenharmony_ci	ret = inet_frags_init(&lowpan_frags);
53162306a36Sopenharmony_ci	if (ret)
53262306a36Sopenharmony_ci		goto out;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	ret = lowpan_frags_sysctl_register();
53562306a36Sopenharmony_ci	if (ret)
53662306a36Sopenharmony_ci		goto err_sysctl;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	ret = register_pernet_subsys(&lowpan_frags_ops);
53962306a36Sopenharmony_ci	if (ret)
54062306a36Sopenharmony_ci		goto err_pernet;
54162306a36Sopenharmony_ciout:
54262306a36Sopenharmony_ci	return ret;
54362306a36Sopenharmony_cierr_pernet:
54462306a36Sopenharmony_ci	lowpan_frags_sysctl_unregister();
54562306a36Sopenharmony_cierr_sysctl:
54662306a36Sopenharmony_ci	inet_frags_fini(&lowpan_frags);
54762306a36Sopenharmony_ci	return ret;
54862306a36Sopenharmony_ci}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_civoid lowpan_net_frag_exit(void)
55162306a36Sopenharmony_ci{
55262306a36Sopenharmony_ci	lowpan_frags_sysctl_unregister();
55362306a36Sopenharmony_ci	unregister_pernet_subsys(&lowpan_frags_ops);
55462306a36Sopenharmony_ci	inet_frags_fini(&lowpan_frags);
55562306a36Sopenharmony_ci}
556