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