xref: /linux/net/6lowpan/nhc.c (revision 607b0bd3f2b9ac118f2c67dbd18c55f1f5aefeb1)
192aa7c65SAlexander Aring /*
292aa7c65SAlexander Aring  *	6LoWPAN next header compression
392aa7c65SAlexander Aring  *
492aa7c65SAlexander Aring  *
592aa7c65SAlexander Aring  *	Authors:
692aa7c65SAlexander Aring  *	Alexander Aring		<aar@pengutronix.de>
792aa7c65SAlexander Aring  *
892aa7c65SAlexander Aring  *	This program is free software; you can redistribute it and/or
992aa7c65SAlexander Aring  *	modify it under the terms of the GNU General Public License
1092aa7c65SAlexander Aring  *	as published by the Free Software Foundation; either version
1192aa7c65SAlexander Aring  *	2 of the License, or (at your option) any later version.
1292aa7c65SAlexander Aring  */
1392aa7c65SAlexander Aring 
1492aa7c65SAlexander Aring #include <linux/netdevice.h>
1592aa7c65SAlexander Aring 
1692aa7c65SAlexander Aring #include <net/ipv6.h>
1792aa7c65SAlexander Aring 
1892aa7c65SAlexander Aring #include "nhc.h"
1992aa7c65SAlexander Aring 
2092aa7c65SAlexander Aring static struct rb_root rb_root = RB_ROOT;
2192aa7c65SAlexander Aring static struct lowpan_nhc *lowpan_nexthdr_nhcs[NEXTHDR_MAX];
2292aa7c65SAlexander Aring static DEFINE_SPINLOCK(lowpan_nhc_lock);
2392aa7c65SAlexander Aring 
2492aa7c65SAlexander Aring static int lowpan_nhc_insert(struct lowpan_nhc *nhc)
2592aa7c65SAlexander Aring {
2692aa7c65SAlexander Aring 	struct rb_node **new = &rb_root.rb_node, *parent = NULL;
2792aa7c65SAlexander Aring 
2892aa7c65SAlexander Aring 	/* Figure out where to put new node */
2992aa7c65SAlexander Aring 	while (*new) {
3092aa7c65SAlexander Aring 		struct lowpan_nhc *this = container_of(*new, struct lowpan_nhc,
3192aa7c65SAlexander Aring 						       node);
3292aa7c65SAlexander Aring 		int result, len_dif, len;
3392aa7c65SAlexander Aring 
3492aa7c65SAlexander Aring 		len_dif = nhc->idlen - this->idlen;
3592aa7c65SAlexander Aring 
3692aa7c65SAlexander Aring 		if (nhc->idlen < this->idlen)
3792aa7c65SAlexander Aring 			len = nhc->idlen;
3892aa7c65SAlexander Aring 		else
3992aa7c65SAlexander Aring 			len = this->idlen;
4092aa7c65SAlexander Aring 
4192aa7c65SAlexander Aring 		result = memcmp(nhc->id, this->id, len);
4292aa7c65SAlexander Aring 		if (!result)
4392aa7c65SAlexander Aring 			result = len_dif;
4492aa7c65SAlexander Aring 
4592aa7c65SAlexander Aring 		parent = *new;
4692aa7c65SAlexander Aring 		if (result < 0)
4792aa7c65SAlexander Aring 			new = &((*new)->rb_left);
4892aa7c65SAlexander Aring 		else if (result > 0)
4992aa7c65SAlexander Aring 			new = &((*new)->rb_right);
5092aa7c65SAlexander Aring 		else
5192aa7c65SAlexander Aring 			return -EEXIST;
5292aa7c65SAlexander Aring 	}
5392aa7c65SAlexander Aring 
5492aa7c65SAlexander Aring 	/* Add new node and rebalance tree. */
5592aa7c65SAlexander Aring 	rb_link_node(&nhc->node, parent, new);
5692aa7c65SAlexander Aring 	rb_insert_color(&nhc->node, &rb_root);
5792aa7c65SAlexander Aring 
5892aa7c65SAlexander Aring 	return 0;
5992aa7c65SAlexander Aring }
6092aa7c65SAlexander Aring 
6192aa7c65SAlexander Aring static void lowpan_nhc_remove(struct lowpan_nhc *nhc)
6292aa7c65SAlexander Aring {
6392aa7c65SAlexander Aring 	rb_erase(&nhc->node, &rb_root);
6492aa7c65SAlexander Aring }
6592aa7c65SAlexander Aring 
6692aa7c65SAlexander Aring static struct lowpan_nhc *lowpan_nhc_by_nhcid(const struct sk_buff *skb)
6792aa7c65SAlexander Aring {
6892aa7c65SAlexander Aring 	struct rb_node *node = rb_root.rb_node;
6992aa7c65SAlexander Aring 	const u8 *nhcid_skb_ptr = skb->data;
7092aa7c65SAlexander Aring 
7192aa7c65SAlexander Aring 	while (node) {
7292aa7c65SAlexander Aring 		struct lowpan_nhc *nhc = container_of(node, struct lowpan_nhc,
7392aa7c65SAlexander Aring 						      node);
7492aa7c65SAlexander Aring 		u8 nhcid_skb_ptr_masked[LOWPAN_NHC_MAX_ID_LEN];
7592aa7c65SAlexander Aring 		int result, i;
7692aa7c65SAlexander Aring 
7792aa7c65SAlexander Aring 		if (nhcid_skb_ptr + nhc->idlen > skb->data + skb->len)
7892aa7c65SAlexander Aring 			return NULL;
7992aa7c65SAlexander Aring 
8092aa7c65SAlexander Aring 		/* copy and mask afterwards the nhid value from skb */
8192aa7c65SAlexander Aring 		memcpy(nhcid_skb_ptr_masked, nhcid_skb_ptr, nhc->idlen);
8292aa7c65SAlexander Aring 		for (i = 0; i < nhc->idlen; i++)
8392aa7c65SAlexander Aring 			nhcid_skb_ptr_masked[i] &= nhc->idmask[i];
8492aa7c65SAlexander Aring 
8592aa7c65SAlexander Aring 		result = memcmp(nhcid_skb_ptr_masked, nhc->id, nhc->idlen);
8692aa7c65SAlexander Aring 		if (result < 0)
8792aa7c65SAlexander Aring 			node = node->rb_left;
8892aa7c65SAlexander Aring 		else if (result > 0)
8992aa7c65SAlexander Aring 			node = node->rb_right;
9092aa7c65SAlexander Aring 		else
9192aa7c65SAlexander Aring 			return nhc;
9292aa7c65SAlexander Aring 	}
9392aa7c65SAlexander Aring 
9492aa7c65SAlexander Aring 	return NULL;
9592aa7c65SAlexander Aring }
9692aa7c65SAlexander Aring 
9792aa7c65SAlexander Aring int lowpan_nhc_check_compression(struct sk_buff *skb,
98*607b0bd3SAlexander Aring 				 const struct ipv6hdr *hdr, u8 **hc_ptr)
9992aa7c65SAlexander Aring {
10092aa7c65SAlexander Aring 	struct lowpan_nhc *nhc;
101*607b0bd3SAlexander Aring 	int ret = 0;
10292aa7c65SAlexander Aring 
10392aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
10492aa7c65SAlexander Aring 
10592aa7c65SAlexander Aring 	nhc = lowpan_nexthdr_nhcs[hdr->nexthdr];
106*607b0bd3SAlexander Aring 	if (!(nhc && nhc->compress))
107*607b0bd3SAlexander Aring 		ret = -ENOENT;
10892aa7c65SAlexander Aring 
10992aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
11092aa7c65SAlexander Aring 
111*607b0bd3SAlexander Aring 	return ret;
11292aa7c65SAlexander Aring }
11392aa7c65SAlexander Aring 
11492aa7c65SAlexander Aring int lowpan_nhc_do_compression(struct sk_buff *skb, const struct ipv6hdr *hdr,
11592aa7c65SAlexander Aring 			      u8 **hc_ptr)
11692aa7c65SAlexander Aring {
11792aa7c65SAlexander Aring 	int ret;
11892aa7c65SAlexander Aring 	struct lowpan_nhc *nhc;
11992aa7c65SAlexander Aring 
12092aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
12192aa7c65SAlexander Aring 
12292aa7c65SAlexander Aring 	nhc = lowpan_nexthdr_nhcs[hdr->nexthdr];
12392aa7c65SAlexander Aring 	/* check if the nhc module was removed in unlocked part.
12492aa7c65SAlexander Aring 	 * TODO: this is a workaround we should prevent unloading
12592aa7c65SAlexander Aring 	 * of nhc modules while unlocked part, this will always drop
12692aa7c65SAlexander Aring 	 * the lowpan packet but it's very unlikely.
12792aa7c65SAlexander Aring 	 *
12892aa7c65SAlexander Aring 	 * Solution isn't easy because we need to decide at
12992aa7c65SAlexander Aring 	 * lowpan_nhc_check_compression if we do a compression or not.
13092aa7c65SAlexander Aring 	 * Because the inline data which is added to skb, we can't move this
13192aa7c65SAlexander Aring 	 * handling.
13292aa7c65SAlexander Aring 	 */
13392aa7c65SAlexander Aring 	if (unlikely(!nhc || !nhc->compress)) {
13492aa7c65SAlexander Aring 		ret = -EINVAL;
13592aa7c65SAlexander Aring 		goto out;
13692aa7c65SAlexander Aring 	}
13792aa7c65SAlexander Aring 
13892aa7c65SAlexander Aring 	/* In the case of RAW sockets the transport header is not set by
13992aa7c65SAlexander Aring 	 * the ip6 stack so we must set it ourselves
14092aa7c65SAlexander Aring 	 */
14192aa7c65SAlexander Aring 	if (skb->transport_header == skb->network_header)
14292aa7c65SAlexander Aring 		skb_set_transport_header(skb, sizeof(struct ipv6hdr));
14392aa7c65SAlexander Aring 
14492aa7c65SAlexander Aring 	ret = nhc->compress(skb, hc_ptr);
14592aa7c65SAlexander Aring 	if (ret < 0)
14692aa7c65SAlexander Aring 		goto out;
14792aa7c65SAlexander Aring 
14892aa7c65SAlexander Aring 	/* skip the transport header */
14992aa7c65SAlexander Aring 	skb_pull(skb, nhc->nexthdrlen);
15092aa7c65SAlexander Aring 
15192aa7c65SAlexander Aring out:
15292aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
15392aa7c65SAlexander Aring 
15492aa7c65SAlexander Aring 	return ret;
15592aa7c65SAlexander Aring }
15692aa7c65SAlexander Aring 
1578911d774SAlexander Aring int lowpan_nhc_do_uncompression(struct sk_buff *skb,
1588911d774SAlexander Aring 				const struct net_device *dev,
15992aa7c65SAlexander Aring 				struct ipv6hdr *hdr)
16092aa7c65SAlexander Aring {
16192aa7c65SAlexander Aring 	struct lowpan_nhc *nhc;
16292aa7c65SAlexander Aring 	int ret;
16392aa7c65SAlexander Aring 
16492aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
16592aa7c65SAlexander Aring 
16692aa7c65SAlexander Aring 	nhc = lowpan_nhc_by_nhcid(skb);
16792aa7c65SAlexander Aring 	if (nhc) {
16892aa7c65SAlexander Aring 		if (nhc->uncompress) {
16992aa7c65SAlexander Aring 			ret = nhc->uncompress(skb, sizeof(struct ipv6hdr) +
17092aa7c65SAlexander Aring 					      nhc->nexthdrlen);
17192aa7c65SAlexander Aring 			if (ret < 0) {
17292aa7c65SAlexander Aring 				spin_unlock_bh(&lowpan_nhc_lock);
17392aa7c65SAlexander Aring 				return ret;
17492aa7c65SAlexander Aring 			}
17592aa7c65SAlexander Aring 		} else {
17692aa7c65SAlexander Aring 			spin_unlock_bh(&lowpan_nhc_lock);
17792aa7c65SAlexander Aring 			netdev_warn(dev, "received nhc id for %s which is not implemented.\n",
17892aa7c65SAlexander Aring 				    nhc->name);
17992aa7c65SAlexander Aring 			return -ENOTSUPP;
18092aa7c65SAlexander Aring 		}
18192aa7c65SAlexander Aring 	} else {
18292aa7c65SAlexander Aring 		spin_unlock_bh(&lowpan_nhc_lock);
18392aa7c65SAlexander Aring 		netdev_warn(dev, "received unknown nhc id which was not found.\n");
18492aa7c65SAlexander Aring 		return -ENOENT;
18592aa7c65SAlexander Aring 	}
18692aa7c65SAlexander Aring 
18792aa7c65SAlexander Aring 	hdr->nexthdr = nhc->nexthdr;
18892aa7c65SAlexander Aring 	skb_reset_transport_header(skb);
18992aa7c65SAlexander Aring 	raw_dump_table(__func__, "raw transport header dump",
19092aa7c65SAlexander Aring 		       skb_transport_header(skb), nhc->nexthdrlen);
19192aa7c65SAlexander Aring 
19292aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
19392aa7c65SAlexander Aring 
19492aa7c65SAlexander Aring 	return 0;
19592aa7c65SAlexander Aring }
19692aa7c65SAlexander Aring 
19792aa7c65SAlexander Aring int lowpan_nhc_add(struct lowpan_nhc *nhc)
19892aa7c65SAlexander Aring {
19992aa7c65SAlexander Aring 	int ret;
20092aa7c65SAlexander Aring 
20192aa7c65SAlexander Aring 	if (!nhc->idlen || !nhc->idsetup)
20292aa7c65SAlexander Aring 		return -EINVAL;
20392aa7c65SAlexander Aring 
20492aa7c65SAlexander Aring 	WARN_ONCE(nhc->idlen > LOWPAN_NHC_MAX_ID_LEN,
20592aa7c65SAlexander Aring 		  "LOWPAN_NHC_MAX_ID_LEN should be updated to %zd.\n",
20692aa7c65SAlexander Aring 		  nhc->idlen);
20792aa7c65SAlexander Aring 
20892aa7c65SAlexander Aring 	nhc->idsetup(nhc);
20992aa7c65SAlexander Aring 
21092aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
21192aa7c65SAlexander Aring 
21292aa7c65SAlexander Aring 	if (lowpan_nexthdr_nhcs[nhc->nexthdr]) {
21392aa7c65SAlexander Aring 		ret = -EEXIST;
21492aa7c65SAlexander Aring 		goto out;
21592aa7c65SAlexander Aring 	}
21692aa7c65SAlexander Aring 
21792aa7c65SAlexander Aring 	ret = lowpan_nhc_insert(nhc);
21892aa7c65SAlexander Aring 	if (ret < 0)
21992aa7c65SAlexander Aring 		goto out;
22092aa7c65SAlexander Aring 
22192aa7c65SAlexander Aring 	lowpan_nexthdr_nhcs[nhc->nexthdr] = nhc;
22292aa7c65SAlexander Aring out:
22392aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
22492aa7c65SAlexander Aring 	return ret;
22592aa7c65SAlexander Aring }
22692aa7c65SAlexander Aring EXPORT_SYMBOL(lowpan_nhc_add);
22792aa7c65SAlexander Aring 
22892aa7c65SAlexander Aring void lowpan_nhc_del(struct lowpan_nhc *nhc)
22992aa7c65SAlexander Aring {
23092aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
23192aa7c65SAlexander Aring 
23292aa7c65SAlexander Aring 	lowpan_nhc_remove(nhc);
23392aa7c65SAlexander Aring 	lowpan_nexthdr_nhcs[nhc->nexthdr] = NULL;
23492aa7c65SAlexander Aring 
23592aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
23692aa7c65SAlexander Aring 
23792aa7c65SAlexander Aring 	synchronize_net();
23892aa7c65SAlexander Aring }
23992aa7c65SAlexander Aring EXPORT_SYMBOL(lowpan_nhc_del);
240