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