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