xref: /linux/net/6lowpan/nhc.c (revision 92aa7c65d295f3cbb96904afe335f683e55584b8)
1*92aa7c65SAlexander Aring /*
2*92aa7c65SAlexander Aring  *	6LoWPAN next header compression
3*92aa7c65SAlexander Aring  *
4*92aa7c65SAlexander Aring  *
5*92aa7c65SAlexander Aring  *	Authors:
6*92aa7c65SAlexander Aring  *	Alexander Aring		<aar@pengutronix.de>
7*92aa7c65SAlexander Aring  *
8*92aa7c65SAlexander Aring  *	This program is free software; you can redistribute it and/or
9*92aa7c65SAlexander Aring  *	modify it under the terms of the GNU General Public License
10*92aa7c65SAlexander Aring  *	as published by the Free Software Foundation; either version
11*92aa7c65SAlexander Aring  *	2 of the License, or (at your option) any later version.
12*92aa7c65SAlexander Aring  */
13*92aa7c65SAlexander Aring 
14*92aa7c65SAlexander Aring #include <linux/netdevice.h>
15*92aa7c65SAlexander Aring 
16*92aa7c65SAlexander Aring #include <net/ipv6.h>
17*92aa7c65SAlexander Aring 
18*92aa7c65SAlexander Aring #include "nhc.h"
19*92aa7c65SAlexander Aring 
20*92aa7c65SAlexander Aring static struct rb_root rb_root = RB_ROOT;
21*92aa7c65SAlexander Aring static struct lowpan_nhc *lowpan_nexthdr_nhcs[NEXTHDR_MAX];
22*92aa7c65SAlexander Aring static DEFINE_SPINLOCK(lowpan_nhc_lock);
23*92aa7c65SAlexander Aring 
24*92aa7c65SAlexander Aring static int lowpan_nhc_insert(struct lowpan_nhc *nhc)
25*92aa7c65SAlexander Aring {
26*92aa7c65SAlexander Aring 	struct rb_node **new = &rb_root.rb_node, *parent = NULL;
27*92aa7c65SAlexander Aring 
28*92aa7c65SAlexander Aring 	/* Figure out where to put new node */
29*92aa7c65SAlexander Aring 	while (*new) {
30*92aa7c65SAlexander Aring 		struct lowpan_nhc *this = container_of(*new, struct lowpan_nhc,
31*92aa7c65SAlexander Aring 						       node);
32*92aa7c65SAlexander Aring 		int result, len_dif, len;
33*92aa7c65SAlexander Aring 
34*92aa7c65SAlexander Aring 		len_dif = nhc->idlen - this->idlen;
35*92aa7c65SAlexander Aring 
36*92aa7c65SAlexander Aring 		if (nhc->idlen < this->idlen)
37*92aa7c65SAlexander Aring 			len = nhc->idlen;
38*92aa7c65SAlexander Aring 		else
39*92aa7c65SAlexander Aring 			len = this->idlen;
40*92aa7c65SAlexander Aring 
41*92aa7c65SAlexander Aring 		result = memcmp(nhc->id, this->id, len);
42*92aa7c65SAlexander Aring 		if (!result)
43*92aa7c65SAlexander Aring 			result = len_dif;
44*92aa7c65SAlexander Aring 
45*92aa7c65SAlexander Aring 		parent = *new;
46*92aa7c65SAlexander Aring 		if (result < 0)
47*92aa7c65SAlexander Aring 			new = &((*new)->rb_left);
48*92aa7c65SAlexander Aring 		else if (result > 0)
49*92aa7c65SAlexander Aring 			new = &((*new)->rb_right);
50*92aa7c65SAlexander Aring 		else
51*92aa7c65SAlexander Aring 			return -EEXIST;
52*92aa7c65SAlexander Aring 	}
53*92aa7c65SAlexander Aring 
54*92aa7c65SAlexander Aring 	/* Add new node and rebalance tree. */
55*92aa7c65SAlexander Aring 	rb_link_node(&nhc->node, parent, new);
56*92aa7c65SAlexander Aring 	rb_insert_color(&nhc->node, &rb_root);
57*92aa7c65SAlexander Aring 
58*92aa7c65SAlexander Aring 	return 0;
59*92aa7c65SAlexander Aring }
60*92aa7c65SAlexander Aring 
61*92aa7c65SAlexander Aring static void lowpan_nhc_remove(struct lowpan_nhc *nhc)
62*92aa7c65SAlexander Aring {
63*92aa7c65SAlexander Aring 	rb_erase(&nhc->node, &rb_root);
64*92aa7c65SAlexander Aring }
65*92aa7c65SAlexander Aring 
66*92aa7c65SAlexander Aring static struct lowpan_nhc *lowpan_nhc_by_nhcid(const struct sk_buff *skb)
67*92aa7c65SAlexander Aring {
68*92aa7c65SAlexander Aring 	struct rb_node *node = rb_root.rb_node;
69*92aa7c65SAlexander Aring 	const u8 *nhcid_skb_ptr = skb->data;
70*92aa7c65SAlexander Aring 
71*92aa7c65SAlexander Aring 	while (node) {
72*92aa7c65SAlexander Aring 		struct lowpan_nhc *nhc = container_of(node, struct lowpan_nhc,
73*92aa7c65SAlexander Aring 						      node);
74*92aa7c65SAlexander Aring 		u8 nhcid_skb_ptr_masked[LOWPAN_NHC_MAX_ID_LEN];
75*92aa7c65SAlexander Aring 		int result, i;
76*92aa7c65SAlexander Aring 
77*92aa7c65SAlexander Aring 		if (nhcid_skb_ptr + nhc->idlen > skb->data + skb->len)
78*92aa7c65SAlexander Aring 			return NULL;
79*92aa7c65SAlexander Aring 
80*92aa7c65SAlexander Aring 		/* copy and mask afterwards the nhid value from skb */
81*92aa7c65SAlexander Aring 		memcpy(nhcid_skb_ptr_masked, nhcid_skb_ptr, nhc->idlen);
82*92aa7c65SAlexander Aring 		for (i = 0; i < nhc->idlen; i++)
83*92aa7c65SAlexander Aring 			nhcid_skb_ptr_masked[i] &= nhc->idmask[i];
84*92aa7c65SAlexander Aring 
85*92aa7c65SAlexander Aring 		result = memcmp(nhcid_skb_ptr_masked, nhc->id, nhc->idlen);
86*92aa7c65SAlexander Aring 		if (result < 0)
87*92aa7c65SAlexander Aring 			node = node->rb_left;
88*92aa7c65SAlexander Aring 		else if (result > 0)
89*92aa7c65SAlexander Aring 			node = node->rb_right;
90*92aa7c65SAlexander Aring 		else
91*92aa7c65SAlexander Aring 			return nhc;
92*92aa7c65SAlexander Aring 	}
93*92aa7c65SAlexander Aring 
94*92aa7c65SAlexander Aring 	return NULL;
95*92aa7c65SAlexander Aring }
96*92aa7c65SAlexander Aring 
97*92aa7c65SAlexander Aring int lowpan_nhc_check_compression(struct sk_buff *skb,
98*92aa7c65SAlexander Aring 				 const struct ipv6hdr *hdr, u8 **hc_ptr,
99*92aa7c65SAlexander Aring 				 u8 *iphc0)
100*92aa7c65SAlexander Aring {
101*92aa7c65SAlexander Aring 	struct lowpan_nhc *nhc;
102*92aa7c65SAlexander Aring 
103*92aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
104*92aa7c65SAlexander Aring 
105*92aa7c65SAlexander Aring 	nhc = lowpan_nexthdr_nhcs[hdr->nexthdr];
106*92aa7c65SAlexander Aring 	if (nhc && nhc->compress)
107*92aa7c65SAlexander Aring 		*iphc0 |= LOWPAN_IPHC_NH_C;
108*92aa7c65SAlexander Aring 	else
109*92aa7c65SAlexander Aring 		lowpan_push_hc_data(hc_ptr, &hdr->nexthdr,
110*92aa7c65SAlexander Aring 				    sizeof(hdr->nexthdr));
111*92aa7c65SAlexander Aring 
112*92aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
113*92aa7c65SAlexander Aring 
114*92aa7c65SAlexander Aring 	return 0;
115*92aa7c65SAlexander Aring }
116*92aa7c65SAlexander Aring 
117*92aa7c65SAlexander Aring int lowpan_nhc_do_compression(struct sk_buff *skb, const struct ipv6hdr *hdr,
118*92aa7c65SAlexander Aring 			      u8 **hc_ptr)
119*92aa7c65SAlexander Aring {
120*92aa7c65SAlexander Aring 	int ret;
121*92aa7c65SAlexander Aring 	struct lowpan_nhc *nhc;
122*92aa7c65SAlexander Aring 
123*92aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
124*92aa7c65SAlexander Aring 
125*92aa7c65SAlexander Aring 	nhc = lowpan_nexthdr_nhcs[hdr->nexthdr];
126*92aa7c65SAlexander Aring 	/* check if the nhc module was removed in unlocked part.
127*92aa7c65SAlexander Aring 	 * TODO: this is a workaround we should prevent unloading
128*92aa7c65SAlexander Aring 	 * of nhc modules while unlocked part, this will always drop
129*92aa7c65SAlexander Aring 	 * the lowpan packet but it's very unlikely.
130*92aa7c65SAlexander Aring 	 *
131*92aa7c65SAlexander Aring 	 * Solution isn't easy because we need to decide at
132*92aa7c65SAlexander Aring 	 * lowpan_nhc_check_compression if we do a compression or not.
133*92aa7c65SAlexander Aring 	 * Because the inline data which is added to skb, we can't move this
134*92aa7c65SAlexander Aring 	 * handling.
135*92aa7c65SAlexander Aring 	 */
136*92aa7c65SAlexander Aring 	if (unlikely(!nhc || !nhc->compress)) {
137*92aa7c65SAlexander Aring 		ret = -EINVAL;
138*92aa7c65SAlexander Aring 		goto out;
139*92aa7c65SAlexander Aring 	}
140*92aa7c65SAlexander Aring 
141*92aa7c65SAlexander Aring 	/* In the case of RAW sockets the transport header is not set by
142*92aa7c65SAlexander Aring 	 * the ip6 stack so we must set it ourselves
143*92aa7c65SAlexander Aring 	 */
144*92aa7c65SAlexander Aring 	if (skb->transport_header == skb->network_header)
145*92aa7c65SAlexander Aring 		skb_set_transport_header(skb, sizeof(struct ipv6hdr));
146*92aa7c65SAlexander Aring 
147*92aa7c65SAlexander Aring 	ret = nhc->compress(skb, hc_ptr);
148*92aa7c65SAlexander Aring 	if (ret < 0)
149*92aa7c65SAlexander Aring 		goto out;
150*92aa7c65SAlexander Aring 
151*92aa7c65SAlexander Aring 	/* skip the transport header */
152*92aa7c65SAlexander Aring 	skb_pull(skb, nhc->nexthdrlen);
153*92aa7c65SAlexander Aring 
154*92aa7c65SAlexander Aring out:
155*92aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
156*92aa7c65SAlexander Aring 
157*92aa7c65SAlexander Aring 	return ret;
158*92aa7c65SAlexander Aring }
159*92aa7c65SAlexander Aring 
160*92aa7c65SAlexander Aring int lowpan_nhc_do_uncompression(struct sk_buff *skb, struct net_device *dev,
161*92aa7c65SAlexander Aring 				struct ipv6hdr *hdr)
162*92aa7c65SAlexander Aring {
163*92aa7c65SAlexander Aring 	struct lowpan_nhc *nhc;
164*92aa7c65SAlexander Aring 	int ret;
165*92aa7c65SAlexander Aring 
166*92aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
167*92aa7c65SAlexander Aring 
168*92aa7c65SAlexander Aring 	nhc = lowpan_nhc_by_nhcid(skb);
169*92aa7c65SAlexander Aring 	if (nhc) {
170*92aa7c65SAlexander Aring 		if (nhc->uncompress) {
171*92aa7c65SAlexander Aring 			ret = nhc->uncompress(skb, sizeof(struct ipv6hdr) +
172*92aa7c65SAlexander Aring 					      nhc->nexthdrlen);
173*92aa7c65SAlexander Aring 			if (ret < 0) {
174*92aa7c65SAlexander Aring 				spin_unlock_bh(&lowpan_nhc_lock);
175*92aa7c65SAlexander Aring 				return ret;
176*92aa7c65SAlexander Aring 			}
177*92aa7c65SAlexander Aring 		} else {
178*92aa7c65SAlexander Aring 			spin_unlock_bh(&lowpan_nhc_lock);
179*92aa7c65SAlexander Aring 			netdev_warn(dev, "received nhc id for %s which is not implemented.\n",
180*92aa7c65SAlexander Aring 				    nhc->name);
181*92aa7c65SAlexander Aring 			return -ENOTSUPP;
182*92aa7c65SAlexander Aring 		}
183*92aa7c65SAlexander Aring 	} else {
184*92aa7c65SAlexander Aring 		spin_unlock_bh(&lowpan_nhc_lock);
185*92aa7c65SAlexander Aring 		netdev_warn(dev, "received unknown nhc id which was not found.\n");
186*92aa7c65SAlexander Aring 		return -ENOENT;
187*92aa7c65SAlexander Aring 	}
188*92aa7c65SAlexander Aring 
189*92aa7c65SAlexander Aring 	hdr->nexthdr = nhc->nexthdr;
190*92aa7c65SAlexander Aring 	skb_reset_transport_header(skb);
191*92aa7c65SAlexander Aring 	raw_dump_table(__func__, "raw transport header dump",
192*92aa7c65SAlexander Aring 		       skb_transport_header(skb), nhc->nexthdrlen);
193*92aa7c65SAlexander Aring 
194*92aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
195*92aa7c65SAlexander Aring 
196*92aa7c65SAlexander Aring 	return 0;
197*92aa7c65SAlexander Aring }
198*92aa7c65SAlexander Aring 
199*92aa7c65SAlexander Aring int lowpan_nhc_add(struct lowpan_nhc *nhc)
200*92aa7c65SAlexander Aring {
201*92aa7c65SAlexander Aring 	int ret;
202*92aa7c65SAlexander Aring 
203*92aa7c65SAlexander Aring 	if (!nhc->idlen || !nhc->idsetup)
204*92aa7c65SAlexander Aring 		return -EINVAL;
205*92aa7c65SAlexander Aring 
206*92aa7c65SAlexander Aring 	WARN_ONCE(nhc->idlen > LOWPAN_NHC_MAX_ID_LEN,
207*92aa7c65SAlexander Aring 		  "LOWPAN_NHC_MAX_ID_LEN should be updated to %zd.\n",
208*92aa7c65SAlexander Aring 		  nhc->idlen);
209*92aa7c65SAlexander Aring 
210*92aa7c65SAlexander Aring 	nhc->idsetup(nhc);
211*92aa7c65SAlexander Aring 
212*92aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
213*92aa7c65SAlexander Aring 
214*92aa7c65SAlexander Aring 	if (lowpan_nexthdr_nhcs[nhc->nexthdr]) {
215*92aa7c65SAlexander Aring 		ret = -EEXIST;
216*92aa7c65SAlexander Aring 		goto out;
217*92aa7c65SAlexander Aring 	}
218*92aa7c65SAlexander Aring 
219*92aa7c65SAlexander Aring 	ret = lowpan_nhc_insert(nhc);
220*92aa7c65SAlexander Aring 	if (ret < 0)
221*92aa7c65SAlexander Aring 		goto out;
222*92aa7c65SAlexander Aring 
223*92aa7c65SAlexander Aring 	lowpan_nexthdr_nhcs[nhc->nexthdr] = nhc;
224*92aa7c65SAlexander Aring out:
225*92aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
226*92aa7c65SAlexander Aring 	return ret;
227*92aa7c65SAlexander Aring }
228*92aa7c65SAlexander Aring EXPORT_SYMBOL(lowpan_nhc_add);
229*92aa7c65SAlexander Aring 
230*92aa7c65SAlexander Aring void lowpan_nhc_del(struct lowpan_nhc *nhc)
231*92aa7c65SAlexander Aring {
232*92aa7c65SAlexander Aring 	spin_lock_bh(&lowpan_nhc_lock);
233*92aa7c65SAlexander Aring 
234*92aa7c65SAlexander Aring 	lowpan_nhc_remove(nhc);
235*92aa7c65SAlexander Aring 	lowpan_nexthdr_nhcs[nhc->nexthdr] = NULL;
236*92aa7c65SAlexander Aring 
237*92aa7c65SAlexander Aring 	spin_unlock_bh(&lowpan_nhc_lock);
238*92aa7c65SAlexander Aring 
239*92aa7c65SAlexander Aring 	synchronize_net();
240*92aa7c65SAlexander Aring }
241*92aa7c65SAlexander Aring EXPORT_SYMBOL(lowpan_nhc_del);
242