]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - net/ipv6/xfrm6_protocol.c
xfrm6: Add IPsec protocol multiplexer
[mirror_ubuntu-artful-kernel.git] / net / ipv6 / xfrm6_protocol.c
diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c
new file mode 100644 (file)
index 0000000..6ab989c
--- /dev/null
@@ -0,0 +1,270 @@
+/* xfrm6_protocol.c - Generic xfrm protocol multiplexer for ipv6.
+ *
+ * Copyright (C) 2013 secunet Security Networks AG
+ *
+ * Author:
+ * Steffen Klassert <steffen.klassert@secunet.com>
+ *
+ * Based on:
+ * net/ipv4/xfrm4_protocol.c
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License
+ *     as published by the Free Software Foundation; either version
+ *     2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/skbuff.h>
+#include <linux/icmpv6.h>
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/xfrm.h>
+
+static struct xfrm6_protocol __rcu *esp6_handlers __read_mostly;
+static struct xfrm6_protocol __rcu *ah6_handlers __read_mostly;
+static struct xfrm6_protocol __rcu *ipcomp6_handlers __read_mostly;
+static DEFINE_MUTEX(xfrm6_protocol_mutex);
+
+static inline struct xfrm6_protocol __rcu **proto_handlers(u8 protocol)
+{
+       switch (protocol) {
+       case IPPROTO_ESP:
+               return &esp6_handlers;
+       case IPPROTO_AH:
+               return &ah6_handlers;
+       case IPPROTO_COMP:
+               return &ipcomp6_handlers;
+       }
+
+       return NULL;
+}
+
+#define for_each_protocol_rcu(head, handler)           \
+       for (handler = rcu_dereference(head);           \
+            handler != NULL;                           \
+            handler = rcu_dereference(handler->next))  \
+
+int xfrm6_rcv_cb(struct sk_buff *skb, u8 protocol, int err)
+{
+       int ret;
+       struct xfrm6_protocol *handler;
+
+       for_each_protocol_rcu(*proto_handlers(protocol), handler)
+               if ((ret = handler->cb_handler(skb, err)) <= 0)
+                       return ret;
+
+       return 0;
+}
+EXPORT_SYMBOL(xfrm6_rcv_cb);
+
+static int xfrm6_esp_rcv(struct sk_buff *skb)
+{
+       int ret;
+       struct xfrm6_protocol *handler;
+
+       XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL;
+
+       for_each_protocol_rcu(esp6_handlers, handler)
+               if ((ret = handler->handler(skb)) != -EINVAL)
+                       return ret;
+
+       icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0);
+
+       kfree_skb(skb);
+       return 0;
+}
+
+static void xfrm6_esp_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+                         u8 type, u8 code, int offset, __be32 info)
+{
+       struct xfrm6_protocol *handler;
+
+       for_each_protocol_rcu(esp6_handlers, handler)
+               if (!handler->err_handler(skb, opt, type, code, offset, info))
+                       break;
+}
+
+static int xfrm6_ah_rcv(struct sk_buff *skb)
+{
+       int ret;
+       struct xfrm6_protocol *handler;
+
+       XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL;
+
+       for_each_protocol_rcu(ah6_handlers, handler)
+               if ((ret = handler->handler(skb)) != -EINVAL)
+                       return ret;
+
+       icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0);
+
+       kfree_skb(skb);
+       return 0;
+}
+
+static void xfrm6_ah_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+                        u8 type, u8 code, int offset, __be32 info)
+{
+       struct xfrm6_protocol *handler;
+
+       for_each_protocol_rcu(ah6_handlers, handler)
+               if (!handler->err_handler(skb, opt, type, code, offset, info))
+                       break;
+}
+
+static int xfrm6_ipcomp_rcv(struct sk_buff *skb)
+{
+       int ret;
+       struct xfrm6_protocol *handler;
+
+       XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL;
+
+       for_each_protocol_rcu(ipcomp6_handlers, handler)
+               if ((ret = handler->handler(skb)) != -EINVAL)
+                       return ret;
+
+       icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0);
+
+       kfree_skb(skb);
+       return 0;
+}
+
+static void xfrm6_ipcomp_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+                            u8 type, u8 code, int offset, __be32 info)
+{
+       struct xfrm6_protocol *handler;
+
+       for_each_protocol_rcu(ipcomp6_handlers, handler)
+               if (!handler->err_handler(skb, opt, type, code, offset, info))
+                       break;
+}
+
+static const struct inet6_protocol esp6_protocol = {
+       .handler        =       xfrm6_esp_rcv,
+       .err_handler    =       xfrm6_esp_err,
+       .flags          =       INET6_PROTO_NOPOLICY,
+};
+
+static const struct inet6_protocol ah6_protocol = {
+       .handler        =       xfrm6_ah_rcv,
+       .err_handler    =       xfrm6_ah_err,
+       .flags          =       INET6_PROTO_NOPOLICY,
+};
+
+static const struct inet6_protocol ipcomp6_protocol = {
+       .handler        =       xfrm6_ipcomp_rcv,
+       .err_handler    =       xfrm6_ipcomp_err,
+       .flags          =       INET6_PROTO_NOPOLICY,
+};
+
+static struct xfrm_input_afinfo xfrm6_input_afinfo = {
+       .family         =       AF_INET6,
+       .owner          =       THIS_MODULE,
+       .callback       =       xfrm6_rcv_cb,
+};
+
+static inline const struct inet6_protocol *netproto(unsigned char protocol)
+{
+       switch (protocol) {
+       case IPPROTO_ESP:
+               return &esp6_protocol;
+       case IPPROTO_AH:
+               return &ah6_protocol;
+       case IPPROTO_COMP:
+               return &ipcomp6_protocol;
+       }
+
+       return NULL;
+}
+
+int xfrm6_protocol_register(struct xfrm6_protocol *handler,
+                           unsigned char protocol)
+{
+       struct xfrm6_protocol __rcu **pprev;
+       struct xfrm6_protocol *t;
+       bool add_netproto = false;
+
+       int ret = -EEXIST;
+       int priority = handler->priority;
+
+       mutex_lock(&xfrm6_protocol_mutex);
+
+       if (!rcu_dereference_protected(*proto_handlers(protocol),
+                                      lockdep_is_held(&xfrm6_protocol_mutex)))
+               add_netproto = true;
+
+       for (pprev = proto_handlers(protocol);
+            (t = rcu_dereference_protected(*pprev,
+                       lockdep_is_held(&xfrm6_protocol_mutex))) != NULL;
+            pprev = &t->next) {
+               if (t->priority < priority)
+                       break;
+               if (t->priority == priority)
+                       goto err;
+       }
+
+       handler->next = *pprev;
+       rcu_assign_pointer(*pprev, handler);
+
+       ret = 0;
+
+err:
+       mutex_unlock(&xfrm6_protocol_mutex);
+
+       if (add_netproto) {
+               if (inet6_add_protocol(netproto(protocol), protocol)) {
+                       pr_err("%s: can't add protocol\n", __func__);
+                       ret = -EAGAIN;
+               }
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(xfrm6_protocol_register);
+
+int xfrm6_protocol_deregister(struct xfrm6_protocol *handler,
+                             unsigned char protocol)
+{
+       struct xfrm6_protocol __rcu **pprev;
+       struct xfrm6_protocol *t;
+       int ret = -ENOENT;
+
+       mutex_lock(&xfrm6_protocol_mutex);
+
+       for (pprev = proto_handlers(protocol);
+            (t = rcu_dereference_protected(*pprev,
+                       lockdep_is_held(&xfrm6_protocol_mutex))) != NULL;
+            pprev = &t->next) {
+               if (t == handler) {
+                       *pprev = handler->next;
+                       ret = 0;
+                       break;
+               }
+       }
+
+       if (!rcu_dereference_protected(*proto_handlers(protocol),
+                                      lockdep_is_held(&xfrm6_protocol_mutex))) {
+               if (inet6_del_protocol(netproto(protocol), protocol) < 0) {
+                       pr_err("%s: can't remove protocol\n", __func__);
+                       ret = -EAGAIN;
+               }
+       }
+
+       mutex_unlock(&xfrm6_protocol_mutex);
+
+       synchronize_net();
+
+       return ret;
+}
+EXPORT_SYMBOL(xfrm6_protocol_deregister);
+
+int __init xfrm6_protocol_init(void)
+{
+       return xfrm_input_register_afinfo(&xfrm6_input_afinfo);
+}
+
+void xfrm6_protocol_fini(void)
+{
+       xfrm_input_unregister_afinfo(&xfrm6_input_afinfo);
+}