/* XFRM tunnel handlers.  */
 struct xfrm_tunnel {
        int (*handler)(struct sk_buff *skb);
-       void (*err_handler)(struct sk_buff *skb, __u32 info);
+       int (*err_handler)(struct sk_buff *skb, __u32 info);
+
+       struct xfrm_tunnel *next;
+       int priority;
 };
 
 struct xfrm6_tunnel {
-       int (*handler)(struct sk_buff **pskb);
-       void (*err_handler)(struct sk_buff *skb, struct inet6_skb_parm *opt,
-                           int type, int code, int offset, __u32 info);
+       int (*handler)(struct sk_buff *skb);
+       int (*err_handler)(struct sk_buff *skb, struct inet6_skb_parm *opt,
+                          int type, int code, int offset, __u32 info);
+
+       struct xfrm6_tunnel *next;
+       int priority;
 };
 
 extern void xfrm_init(void);
 extern int xfrm4_output(struct sk_buff *skb);
 extern int xfrm4_tunnel_register(struct xfrm_tunnel *handler);
 extern int xfrm4_tunnel_deregister(struct xfrm_tunnel *handler);
-extern int xfrm6_rcv_spi(struct sk_buff **pskb, u32 spi);
+extern int xfrm6_rcv_spi(struct sk_buff *skb, u32 spi);
 extern int xfrm6_rcv(struct sk_buff **pskb);
 extern int xfrm6_tunnel_register(struct xfrm6_tunnel *handler);
 extern int xfrm6_tunnel_deregister(struct xfrm6_tunnel *handler);
 
 #   bool '    IP: ARP support' CONFIG_IP_PNP_ARP               
 config NET_IPIP
        tristate "IP: tunneling"
+       select INET_TUNNEL
        ---help---
          Tunneling means encapsulating data of one protocol type within
          another protocol and sending it over a channel that understands the
 config INET_IPCOMP
        tristate "IP: IPComp transformation"
        select XFRM
-       select INET_TUNNEL
+       select INET_XFRM_TUNNEL
        select CRYPTO
        select CRYPTO_DEFLATE
        ---help---
          
          If unsure, say Y.
 
+config INET_XFRM_TUNNEL
+       tristate
+       select INET_TUNNEL
+       default n
+
 config INET_TUNNEL
-       tristate "IP: tunnel transformation"
-       select XFRM
-       ---help---
-         Support for generic IP tunnel transformation, which is required by
-         the IP tunneling module as well as tunnel mode IPComp.
-         
-         If unsure, say Y.
+       tristate
+       default n
 
 config INET_DIAG
        tristate "INET: socket monitoring interface"
 
 obj-$(CONFIG_INET_AH) += ah4.o
 obj-$(CONFIG_INET_ESP) += esp4.o
 obj-$(CONFIG_INET_IPCOMP) += ipcomp.o
-obj-$(CONFIG_INET_TUNNEL) += xfrm4_tunnel.o 
+obj-$(CONFIG_INET_XFRM_TUNNEL) += xfrm4_tunnel.o
+obj-$(CONFIG_INET_TUNNEL) += tunnel4.o
 obj-$(CONFIG_IP_PNP) += ipconfig.o
 obj-$(CONFIG_IP_ROUTE_MULTIPATH_RR) += multipath_rr.o
 obj-$(CONFIG_IP_ROUTE_MULTIPATH_RANDOM) += multipath_random.o
 
 #include <net/sock.h>
 #include <net/ip.h>
 #include <net/icmp.h>
-#include <net/protocol.h>
 #include <net/ipip.h>
 #include <net/inet_ecn.h>
 #include <net/xfrm.h>
        dev_put(dev);
 }
 
-static void ipip_err(struct sk_buff *skb, u32 info)
+static int ipip_err(struct sk_buff *skb, u32 info)
 {
 #ifndef I_WISH_WORLD_WERE_PERFECT
 
        int type = skb->h.icmph->type;
        int code = skb->h.icmph->code;
        struct ip_tunnel *t;
+       int err;
 
        switch (type) {
        default:
        case ICMP_PARAMETERPROB:
-               return;
+               return 0;
 
        case ICMP_DEST_UNREACH:
                switch (code) {
                case ICMP_SR_FAILED:
                case ICMP_PORT_UNREACH:
                        /* Impossible event. */
-                       return;
+                       return 0;
                case ICMP_FRAG_NEEDED:
                        /* Soft state for pmtu is maintained by IP core. */
-                       return;
+                       return 0;
                default:
                        /* All others are translated to HOST_UNREACH.
                           rfc2003 contains "deep thoughts" about NET_UNREACH,
                break;
        case ICMP_TIME_EXCEEDED:
                if (code != ICMP_EXC_TTL)
-                       return;
+                       return 0;
                break;
        }
 
+       err = -ENOENT;
+
        read_lock(&ipip_lock);
        t = ipip_tunnel_lookup(iph->daddr, iph->saddr);
        if (t == NULL || t->parms.iph.daddr == 0)
                goto out;
+
+       err = 0;
        if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
                goto out;
 
        t->err_time = jiffies;
 out:
        read_unlock(&ipip_lock);
-       return;
+       return err;
 #else
        struct iphdr *iph = (struct iphdr*)dp;
        int hlen = iph->ihl<<2;
        struct rtable *rt;
 
        if (len < hlen + sizeof(struct iphdr))
-               return;
+               return 0;
        eiph = (struct iphdr*)(dp + hlen);
 
        switch (type) {
        default:
-               return;
+               return 0;
        case ICMP_PARAMETERPROB:
                if (skb->h.icmph->un.gateway < hlen)
-                       return;
+                       return 0;
 
                /* So... This guy found something strange INSIDE encapsulated
                   packet. Well, he is fool, but what can we do ?
                case ICMP_SR_FAILED:
                case ICMP_PORT_UNREACH:
                        /* Impossible event. */
-                       return;
+                       return 0;
                case ICMP_FRAG_NEEDED:
                        /* And it is the only really necessary thing :-) */
                        rel_info = ntohs(skb->h.icmph->un.frag.mtu);
                        if (rel_info < hlen+68)
-                               return;
+                               return 0;
                        rel_info -= hlen;
                        /* BSD 4.2 MORE DOES NOT EXIST IN NATURE. */
                        if (rel_info > ntohs(eiph->tot_len))
-                               return;
+                               return 0;
                        break;
                default:
                        /* All others are translated to HOST_UNREACH.
                break;
        case ICMP_TIME_EXCEEDED:
                if (code != ICMP_EXC_TTL)
-                       return;
+                       return 0;
                break;
        }
 
        /* Prepare fake skb to feed it to icmp_send */
        skb2 = skb_clone(skb, GFP_ATOMIC);
        if (skb2 == NULL)
-               return;
+               return 0;
        dst_release(skb2->dst);
        skb2->dst = NULL;
        skb_pull(skb2, skb->data - (u8*)eiph);
        fl.proto = IPPROTO_IPIP;
        if (ip_route_output_key(&rt, &key)) {
                kfree_skb(skb2);
-               return;
+               return 0;
        }
        skb2->dev = rt->u.dst.dev;
 
                    rt->u.dst.dev->type != ARPHRD_TUNNEL) {
                        ip_rt_put(rt);
                        kfree_skb(skb2);
-                       return;
+                       return 0;
                }
        } else {
                ip_rt_put(rt);
                if (ip_route_input(skb2, eiph->daddr, eiph->saddr, eiph->tos, skb2->dev) ||
                    skb2->dst->dev->type != ARPHRD_TUNNEL) {
                        kfree_skb(skb2);
-                       return;
+                       return 0;
                }
        }
 
        if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
                if (rel_info > dst_mtu(skb2->dst)) {
                        kfree_skb(skb2);
-                       return;
+                       return 0;
                }
                skb2->dst->ops->update_pmtu(skb2->dst, rel_info);
                rel_info = htonl(rel_info);
 
        icmp_send(skb2, rel_type, rel_code, rel_info);
        kfree_skb(skb2);
-       return;
+       return 0;
 #endif
 }
 
        return 0;
 }
 
-#ifdef CONFIG_INET_TUNNEL
 static struct xfrm_tunnel ipip_handler = {
        .handler        =       ipip_rcv,
        .err_handler    =       ipip_err,
+       .priority       =       1,
 };
 
-static inline int ipip_register(void)
-{
-       return xfrm4_tunnel_register(&ipip_handler);
-}
-
-static inline int ipip_unregister(void)
-{
-       return xfrm4_tunnel_deregister(&ipip_handler);
-}
-#else
-static struct net_protocol ipip_protocol = {
-       .handler        =       ipip_rcv,
-       .err_handler    =       ipip_err,
-       .no_policy      =       1,
-};
-
-static inline int ipip_register(void)
-{
-       return inet_add_protocol(&ipip_protocol, IPPROTO_IPIP);
-}
-
-static inline int ipip_unregister(void)
-{
-       return inet_del_protocol(&ipip_protocol, IPPROTO_IPIP);
-}
-#endif
-
 static char banner[] __initdata =
        KERN_INFO "IPv4 over IPv4 tunneling driver\n";
 
 
        printk(banner);
 
-       if (ipip_register() < 0) {
+       if (xfrm4_tunnel_register(&ipip_handler)) {
                printk(KERN_INFO "ipip init: can't register tunnel\n");
                return -EAGAIN;
        }
  err2:
        free_netdev(ipip_fb_tunnel_dev);
  err1:
-       ipip_unregister();
+       xfrm4_tunnel_deregister(&ipip_handler);
        goto out;
 }
 
 
 static void __exit ipip_fini(void)
 {
-       if (ipip_unregister() < 0)
+       if (xfrm4_tunnel_deregister(&ipip_handler))
                printk(KERN_INFO "ipip close: can't deregister tunnel\n");
 
        rtnl_lock();
 
--- /dev/null
+/* tunnel4.c: Generic IP tunnel transformer.
+ *
+ * Copyright (C) 2003 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/protocol.h>
+#include <net/xfrm.h>
+
+static struct xfrm_tunnel *tunnel4_handlers;
+static DEFINE_MUTEX(tunnel4_mutex);
+
+int xfrm4_tunnel_register(struct xfrm_tunnel *handler)
+{
+       struct xfrm_tunnel **pprev;
+       int ret = -EEXIST;
+       int priority = handler->priority;
+
+       mutex_lock(&tunnel4_mutex);
+
+       for (pprev = &tunnel4_handlers; *pprev; pprev = &(*pprev)->next) {
+               if ((*pprev)->priority > priority)
+                       break;
+               if ((*pprev)->priority == priority)
+                       goto err;
+       }
+
+       handler->next = *pprev;
+       *pprev = handler;
+
+       ret = 0;
+
+err:
+       mutex_unlock(&tunnel4_mutex);
+
+       return ret;
+}
+
+EXPORT_SYMBOL(xfrm4_tunnel_register);
+
+int xfrm4_tunnel_deregister(struct xfrm_tunnel *handler)
+{
+       struct xfrm_tunnel **pprev;
+       int ret = -ENOENT;
+
+       mutex_lock(&tunnel4_mutex);
+
+       for (pprev = &tunnel4_handlers; *pprev; pprev = &(*pprev)->next) {
+               if (*pprev == handler) {
+                       *pprev = handler->next;
+                       ret = 0;
+                       break;
+               }
+       }
+
+       mutex_unlock(&tunnel4_mutex);
+
+       synchronize_net();
+
+       return ret;
+}
+
+EXPORT_SYMBOL(xfrm4_tunnel_deregister);
+
+static int tunnel4_rcv(struct sk_buff *skb)
+{
+       struct xfrm_tunnel *handler;
+
+       for (handler = tunnel4_handlers; handler; handler = handler->next)
+               if (!handler->handler(skb))
+                       return 0;
+
+       kfree_skb(skb);
+       return 0;
+}
+
+static void tunnel4_err(struct sk_buff *skb, u32 info)
+{
+       struct xfrm_tunnel *handler;
+
+       for (handler = tunnel4_handlers; handler; handler = handler->next)
+               if (!handler->err_handler(skb, info))
+                       break;
+}
+
+static struct net_protocol tunnel4_protocol = {
+       .handler        =       tunnel4_rcv,
+       .err_handler    =       tunnel4_err,
+       .no_policy      =       1,
+};
+
+static int __init tunnel4_init(void)
+{
+       if (inet_add_protocol(&tunnel4_protocol, IPPROTO_IPIP)) {
+               printk(KERN_ERR "tunnel4 init: can't add protocol\n");
+               return -EAGAIN;
+       }
+       return 0;
+}
+
+static void __exit tunnel4_fini(void)
+{
+       if (inet_del_protocol(&tunnel4_protocol, IPPROTO_IPIP))
+               printk(KERN_ERR "tunnel4 close: can't remove protocol\n");
+}
+
+module_init(tunnel4_init);
+module_exit(tunnel4_fini);
+MODULE_LICENSE("GPL");
 
        return 0;
 }
 
-static struct xfrm_tunnel *ipip_handler;
-static DEFINE_MUTEX(xfrm4_tunnel_mutex);
-
-int xfrm4_tunnel_register(struct xfrm_tunnel *handler)
-{
-       int ret;
-
-       mutex_lock(&xfrm4_tunnel_mutex);
-       ret = 0;
-       if (ipip_handler != NULL)
-               ret = -EINVAL;
-       if (!ret)
-               ipip_handler = handler;
-       mutex_unlock(&xfrm4_tunnel_mutex);
-
-       return ret;
-}
-
-EXPORT_SYMBOL(xfrm4_tunnel_register);
-
-int xfrm4_tunnel_deregister(struct xfrm_tunnel *handler)
-{
-       int ret;
-
-       mutex_lock(&xfrm4_tunnel_mutex);
-       ret = 0;
-       if (ipip_handler != handler)
-               ret = -EINVAL;
-       if (!ret)
-               ipip_handler = NULL;
-       mutex_unlock(&xfrm4_tunnel_mutex);
-
-       synchronize_net();
-
-       return ret;
-}
-
-EXPORT_SYMBOL(xfrm4_tunnel_deregister);
-
-static int ipip_rcv(struct sk_buff *skb)
-{
-       struct xfrm_tunnel *handler = ipip_handler;
-
-       /* Tunnel devices take precedence.  */
-       if (handler && handler->handler(skb) == 0)
-               return 0;
-
-       return xfrm4_rcv(skb);
-}
-
-static void ipip_err(struct sk_buff *skb, u32 info)
-{
-       struct xfrm_tunnel *handler = ipip_handler;
-
-       if (handler)
-               handler->err_handler(skb, info);
-}
-
 static int ipip_init_state(struct xfrm_state *x)
 {
        if (!x->props.mode)
        .output         = ipip_output
 };
 
-static struct net_protocol ipip_protocol = {
-       .handler        =       ipip_rcv,
-       .err_handler    =       ipip_err,
-       .no_policy      =       1,
+static int xfrm_tunnel_err(struct sk_buff *skb, u32 info)
+{
+       return -ENOENT;
+}
+
+static struct xfrm_tunnel xfrm_tunnel_handler = {
+       .handler        =       xfrm4_rcv,
+       .err_handler    =       xfrm_tunnel_err,
+       .priority       =       2,
 };
 
 static int __init ipip_init(void)
                printk(KERN_INFO "ipip init: can't add xfrm type\n");
                return -EAGAIN;
        }
-       if (inet_add_protocol(&ipip_protocol, IPPROTO_IPIP) < 0) {
-               printk(KERN_INFO "ipip init: can't add protocol\n");
+       if (xfrm4_tunnel_register(&xfrm_tunnel_handler)) {
+               printk(KERN_INFO "ipip init: can't add xfrm handler\n");
                xfrm_unregister_type(&ipip_type, AF_INET);
                return -EAGAIN;
        }
 
 static void __exit ipip_fini(void)
 {
-       if (inet_del_protocol(&ipip_protocol, IPPROTO_IPIP) < 0)
-               printk(KERN_INFO "ipip close: can't remove protocol\n");
+       if (xfrm4_tunnel_deregister(&xfrm_tunnel_handler))
+               printk(KERN_INFO "ipip close: can't remove xfrm handler\n");
        if (xfrm_unregister_type(&ipip_type, AF_INET) < 0)
                printk(KERN_INFO "ipip close: can't remove xfrm type\n");
 }
 
        tristate "IPv6: IPComp transformation"
        depends on IPV6
        select XFRM
-       select INET6_TUNNEL
+       select INET6_XFRM_TUNNEL
        select CRYPTO
        select CRYPTO_DEFLATE
        ---help---
 
          If unsure, say Y.
 
+config INET6_XFRM_TUNNEL
+       tristate
+       select INET6_TUNNEL
+       default n
+
 config INET6_TUNNEL
-       tristate "IPv6: tunnel transformation"
-       depends on IPV6
-       select XFRM
-       ---help---
-         Support for generic IPv6-in-IPv6 tunnel transformation, which is
-         required by the IPv6-in-IPv6 tunneling module as well as tunnel mode
-         IPComp.
-         
-         If unsure, say Y.
+       tristate
+       default n
 
 config IPV6_TUNNEL
        tristate "IPv6: IPv6-in-IPv6 tunnel"
+       select INET6_TUNNEL
        depends on IPV6
        ---help---
          Support for IPv6-in-IPv6 tunnels described in RFC 2473.
 
 obj-$(CONFIG_INET6_AH) += ah6.o
 obj-$(CONFIG_INET6_ESP) += esp6.o
 obj-$(CONFIG_INET6_IPCOMP) += ipcomp6.o
-obj-$(CONFIG_INET6_TUNNEL) += xfrm6_tunnel.o 
+obj-$(CONFIG_INET6_XFRM_TUNNEL) += xfrm6_tunnel.o
+obj-$(CONFIG_INET6_TUNNEL) += tunnel6.o
 obj-$(CONFIG_NETFILTER)        += netfilter/
 
 obj-$(CONFIG_IPV6_TUNNEL) += ip6_tunnel.o
 
 
 #include <net/ip.h>
 #include <net/ipv6.h>
-#include <net/protocol.h>
 #include <net/ip6_route.h>
 #include <net/addrconf.h>
 #include <net/ip6_tunnel.h>
  *   to the specifications in RFC 2473.
  **/
 
-static void 
+static int
 ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
           int type, int code, int offset, __u32 info)
 {
        int rel_code = ICMPV6_ADDR_UNREACH;
        __u32 rel_info = 0;
        __u16 len;
+       int err = -ENOENT;
 
        /* If the packet doesn't contain the original IPv6 header we are 
           in trouble since we might need the source address for further 
        if ((t = ip6ip6_tnl_lookup(&ipv6h->daddr, &ipv6h->saddr)) == NULL)
                goto out;
 
+       err = 0;
+
        switch (type) {
                __u32 teli;
                struct ipv6_tlv_tnl_enc_lim *tel;
        }
 out:
        read_unlock(&ip6ip6_lock);
+       return err;
 }
 
 static inline void ip6ip6_ecn_decapsulate(struct ipv6hdr *outer_iph,
  **/
 
 static int 
-ip6ip6_rcv(struct sk_buff **pskb)
+ip6ip6_rcv(struct sk_buff *skb)
 {
-       struct sk_buff *skb = *pskb;
        struct ipv6hdr *ipv6h;
        struct ip6_tnl *t;
 
        return 0;
 }
 
-#ifdef CONFIG_INET6_TUNNEL
 static struct xfrm6_tunnel ip6ip6_handler = {
        .handler        = ip6ip6_rcv,
        .err_handler    = ip6ip6_err,
+       .priority       =       1,
 };
 
-static inline int ip6ip6_register(void)
-{
-       return xfrm6_tunnel_register(&ip6ip6_handler);
-}
-
-static inline int ip6ip6_unregister(void)
-{
-       return xfrm6_tunnel_deregister(&ip6ip6_handler);
-}
-#else
-static struct inet6_protocol xfrm6_tunnel_protocol = {
-       .handler        = ip6ip6_rcv,
-       .err_handler    = ip6ip6_err,
-       .flags          = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
-};
-
-static inline int ip6ip6_register(void)
-{
-       return inet6_add_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6);
-}
-
-static inline int ip6ip6_unregister(void)
-{
-       return inet6_del_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6);
-}
-#endif
-
 /**
  * ip6_tunnel_init - register protocol and reserve needed resources
  *
 {
        int  err;
 
-       if (ip6ip6_register() < 0) {
+       if (xfrm6_tunnel_register(&ip6ip6_handler)) {
                printk(KERN_ERR "ip6ip6 init: can't register tunnel\n");
                return -EAGAIN;
        }
        }
        return 0;
 fail:
-       ip6ip6_unregister();
+       xfrm6_tunnel_deregister(&ip6ip6_handler);
        return err;
 }
 
 
 static void __exit ip6_tunnel_cleanup(void)
 {
-       if (ip6ip6_unregister() < 0)
+       if (xfrm6_tunnel_deregister(&ip6ip6_handler))
                printk(KERN_INFO "ip6ip6 close: can't deregister tunnel\n");
 
        unregister_netdev(ip6ip6_fb_tnl_dev);
 
--- /dev/null
+/*
+ * Copyright (C)2003,2004 USAGI/WIDE Project
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Authors     Mitsuru KANDA  <mk@linux-ipv6.org>
+ *             YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/protocol.h>
+#include <net/xfrm.h>
+
+static struct xfrm6_tunnel *tunnel6_handlers;
+static DEFINE_MUTEX(tunnel6_mutex);
+
+int xfrm6_tunnel_register(struct xfrm6_tunnel *handler)
+{
+       struct xfrm6_tunnel **pprev;
+       int ret = -EEXIST;
+       int priority = handler->priority;
+
+       mutex_lock(&tunnel6_mutex);
+
+       for (pprev = &tunnel6_handlers; *pprev; pprev = &(*pprev)->next) {
+               if ((*pprev)->priority > priority)
+                       break;
+               if ((*pprev)->priority == priority)
+                       goto err;
+       }
+
+       handler->next = *pprev;
+       *pprev = handler;
+
+       ret = 0;
+
+err:
+       mutex_unlock(&tunnel6_mutex);
+
+       return ret;
+}
+
+EXPORT_SYMBOL(xfrm6_tunnel_register);
+
+int xfrm6_tunnel_deregister(struct xfrm6_tunnel *handler)
+{
+       struct xfrm6_tunnel **pprev;
+       int ret = -ENOENT;
+
+       mutex_lock(&tunnel6_mutex);
+
+       for (pprev = &tunnel6_handlers; *pprev; pprev = &(*pprev)->next) {
+               if (*pprev == handler) {
+                       *pprev = handler->next;
+                       ret = 0;
+                       break;
+               }
+       }
+
+       mutex_unlock(&tunnel6_mutex);
+
+       synchronize_net();
+
+       return ret;
+}
+
+EXPORT_SYMBOL(xfrm6_tunnel_deregister);
+
+static int tunnel6_rcv(struct sk_buff **pskb)
+{
+       struct sk_buff *skb = *pskb;
+       struct xfrm6_tunnel *handler;
+
+       for (handler = tunnel6_handlers; handler; handler = handler->next)
+               if (!handler->handler(skb))
+                       return 0;
+
+       kfree_skb(skb);
+       return 0;
+}
+
+static void tunnel6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+                       int type, int code, int offset, __u32 info)
+{
+       struct xfrm6_tunnel *handler;
+
+       for (handler = tunnel6_handlers; handler; handler = handler->next)
+               if (!handler->err_handler(skb, opt, type, code, offset, info))
+                       break;
+}
+
+static struct inet6_protocol tunnel6_protocol = {
+       .handler        = tunnel6_rcv,
+       .err_handler    = tunnel6_err,
+       .flags          = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
+};
+
+static int __init tunnel6_init(void)
+{
+       if (inet6_add_protocol(&tunnel6_protocol, IPPROTO_IPV6)) {
+               printk(KERN_ERR "tunnel6 init(): can't add protocol\n");
+               return -EAGAIN;
+       }
+       return 0;
+}
+
+static void __exit tunnel6_fini(void)
+{
+       if (inet6_del_protocol(&tunnel6_protocol, IPPROTO_IPV6))
+               printk(KERN_ERR "tunnel6 close: can't remove protocol\n");
+}
+
+module_init(tunnel6_init);
+module_exit(tunnel6_fini);
+MODULE_LICENSE("GPL");
 
                IP6_ECN_set_ce(inner_iph);
 }
 
-int xfrm6_rcv_spi(struct sk_buff **pskb, u32 spi)
+int xfrm6_rcv_spi(struct sk_buff *skb, u32 spi)
 {
-       struct sk_buff *skb = *pskb;
        int err;
        u32 seq;
        struct sec_decap_state xfrm_vec[XFRM_MAX_DEPTH];
 
 int xfrm6_rcv(struct sk_buff **pskb)
 {
-       return xfrm6_rcv_spi(pskb, 0);
+       return xfrm6_rcv_spi(*pskb, 0);
 }
 
 #include <net/ip.h>
 #include <net/xfrm.h>
 #include <net/ipv6.h>
-#include <net/protocol.h>
 #include <linux/ipv6.h>
 #include <linux/icmpv6.h>
 #include <linux/mutex.h>
        return 0;
 }
 
-static struct xfrm6_tunnel *xfrm6_tunnel_handler;
-static DEFINE_MUTEX(xfrm6_tunnel_mutex);
-
-int xfrm6_tunnel_register(struct xfrm6_tunnel *handler)
+static int xfrm6_tunnel_rcv(struct sk_buff *skb)
 {
-       int ret;
-
-       mutex_lock(&xfrm6_tunnel_mutex);
-       ret = 0;
-       if (xfrm6_tunnel_handler != NULL)
-               ret = -EINVAL;
-       if (!ret)
-               xfrm6_tunnel_handler = handler;
-       mutex_unlock(&xfrm6_tunnel_mutex);
-
-       return ret;
-}
-
-EXPORT_SYMBOL(xfrm6_tunnel_register);
-
-int xfrm6_tunnel_deregister(struct xfrm6_tunnel *handler)
-{
-       int ret;
-
-       mutex_lock(&xfrm6_tunnel_mutex);
-       ret = 0;
-       if (xfrm6_tunnel_handler != handler)
-               ret = -EINVAL;
-       if (!ret)
-               xfrm6_tunnel_handler = NULL;
-       mutex_unlock(&xfrm6_tunnel_mutex);
-
-       synchronize_net();
-
-       return ret;
-}
-
-EXPORT_SYMBOL(xfrm6_tunnel_deregister);
-
-static int xfrm6_tunnel_rcv(struct sk_buff **pskb)
-{
-       struct sk_buff *skb = *pskb;
-       struct xfrm6_tunnel *handler = xfrm6_tunnel_handler;
        struct ipv6hdr *iph = skb->nh.ipv6h;
        u32 spi;
 
-       /* device-like_ip6ip6_handler() */
-       if (handler && handler->handler(pskb) == 0)
-               return 0;
-
        spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&iph->saddr);
-       return xfrm6_rcv_spi(pskb, spi);
+       return xfrm6_rcv_spi(skb, spi);
 }
 
-static void xfrm6_tunnel_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
-                            int type, int code, int offset, __u32 info)
+static int xfrm6_tunnel_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+                           int type, int code, int offset, __u32 info)
 {
-       struct xfrm6_tunnel *handler = xfrm6_tunnel_handler;
-
-       /* call here first for device-like ip6ip6 err handling */
-       if (handler) {
-               handler->err_handler(skb, opt, type, code, offset, info);
-               return;
-       }
-
        /* xfrm6_tunnel native err handling */
        switch (type) {
        case ICMPV6_DEST_UNREACH: 
        default:
                break;
        }
-       return;
+
+       return 0;
 }
 
 static int xfrm6_tunnel_init_state(struct xfrm_state *x)
        .output         = xfrm6_tunnel_output,
 };
 
-static struct inet6_protocol xfrm6_tunnel_protocol = {
+static struct xfrm6_tunnel xfrm6_tunnel_handler = {
        .handler        = xfrm6_tunnel_rcv,
-       .err_handler    = xfrm6_tunnel_err, 
-       .flags          = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
+       .err_handler    = xfrm6_tunnel_err,
+       .priority       = 2,
 };
 
 static int __init xfrm6_tunnel_init(void)
                           "xfrm6_tunnel init: can't add xfrm type\n");
                return -EAGAIN;
        }
-       if (inet6_add_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6) < 0) {
+       if (xfrm6_tunnel_register(&xfrm6_tunnel_handler)) {
                X6TPRINTK1(KERN_ERR
-                          "xfrm6_tunnel init(): can't add protocol\n");
+                          "xfrm6_tunnel init(): can't add handler\n");
                xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
                return -EAGAIN;
        }
        if (xfrm6_tunnel_spi_init() < 0) {
                X6TPRINTK1(KERN_ERR
                           "xfrm6_tunnel init: failed to initialize spi\n");
-               inet6_del_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6);
+               xfrm6_tunnel_deregister(&xfrm6_tunnel_handler);
                xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
                return -EAGAIN;
        }
        X6TPRINTK3(KERN_DEBUG "%s()\n", __FUNCTION__);
 
        xfrm6_tunnel_spi_fini();
-       if (inet6_del_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6) < 0)
+       if (xfrm6_tunnel_deregister(&xfrm6_tunnel_handler))
                X6TPRINTK1(KERN_ERR 
-                          "xfrm6_tunnel close: can't remove protocol\n");
+                          "xfrm6_tunnel close: can't remove handler\n");
        if (xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6) < 0)
                X6TPRINTK1(KERN_ERR
                           "xfrm6_tunnel close: can't remove xfrm type\n");