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