cregit-Linux how code gets into the kernel

Release 4.11 net/ipv6/ip6_tunnel.c

Directory: net/ipv6
/*
 *      IPv6 tunneling device
 *      Linux INET6 implementation
 *
 *      Authors:
 *      Ville Nuorvala          <vnuorval@tcs.hut.fi>
 *      Yasuyuki Kozakai        <kozakai@linux-ipv6.org>
 *
 *      Based on:
 *      linux/net/ipv6/sit.c and linux/net/ipv4/ipip.c
 *
 *      RFC 2473
 *
 *      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.
 *
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/sockios.h>
#include <linux/icmp.h>
#include <linux/if.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/icmpv6.h>
#include <linux/init.h>
#include <linux/route.h>
#include <linux/rtnetlink.h>
#include <linux/netfilter_ipv6.h>
#include <linux/slab.h>
#include <linux/hash.h>
#include <linux/etherdevice.h>

#include <linux/uaccess.h>
#include <linux/atomic.h>

#include <net/icmp.h>
#include <net/ip.h>
#include <net/ip_tunnels.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <net/ip6_tunnel.h>
#include <net/xfrm.h>
#include <net/dsfield.h>
#include <net/inet_ecn.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <net/dst_metadata.h>

MODULE_AUTHOR("Ville Nuorvala");
MODULE_DESCRIPTION("IPv6 tunneling device");
MODULE_LICENSE("GPL");
MODULE_ALIAS_RTNL_LINK("ip6tnl");
MODULE_ALIAS_NETDEV("ip6tnl0");


#define IP6_TUNNEL_HASH_SIZE_SHIFT  5

#define IP6_TUNNEL_HASH_SIZE (1 << IP6_TUNNEL_HASH_SIZE_SHIFT)


static bool log_ecn_error = true;
module_param(log_ecn_error, bool, 0644);
MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");


static u32 HASH(const struct in6_addr *addr1, const struct in6_addr *addr2) { u32 hash = ipv6_addr_hash(addr1) ^ ipv6_addr_hash(addr2); return hash_32(hash, IP6_TUNNEL_HASH_SIZE_SHIFT); }

Contributors

PersonTokensPropCommitsCommitProp
Eric Dumazet3589.74%133.33%
Ville Nuorvala37.69%133.33%
Jiri Kosina12.56%133.33%
Total39100.00%3100.00%

static int ip6_tnl_dev_init(struct net_device *dev); static void ip6_tnl_dev_setup(struct net_device *dev); static struct rtnl_link_ops ip6_link_ops __read_mostly; static unsigned int ip6_tnl_net_id __read_mostly; struct ip6_tnl_net { /* the IPv6 tunnel fallback device */ struct net_device *fb_tnl_dev; /* lists for storing tunnels in use */ struct ip6_tnl __rcu *tnls_r_l[IP6_TUNNEL_HASH_SIZE]; struct ip6_tnl __rcu *tnls_wc[1]; struct ip6_tnl __rcu **tnls[2]; struct ip6_tnl __rcu *collect_md_tun; };
static struct net_device_stats *ip6_get_stats(struct net_device *dev) { struct pcpu_sw_netstats tmp, sum = { 0 }; int i; for_each_possible_cpu(i) { unsigned int start; const struct pcpu_sw_netstats *tstats = per_cpu_ptr(dev->tstats, i); do { start = u64_stats_fetch_begin_irq(&tstats->syncp); tmp.rx_packets = tstats->rx_packets; tmp.rx_bytes = tstats->rx_bytes; tmp.tx_packets = tstats->tx_packets; tmp.tx_bytes = tstats->tx_bytes; } while (u64_stats_fetch_retry_irq(&tstats->syncp, start)); sum.rx_packets += tmp.rx_packets; sum.rx_bytes += tmp.rx_bytes; sum.tx_packets += tmp.tx_packets; sum.tx_bytes += tmp.tx_bytes; } dev->stats.rx_packets = sum.rx_packets; dev->stats.rx_bytes = sum.rx_bytes; dev->stats.tx_packets = sum.tx_packets; dev->stats.tx_bytes = sum.tx_bytes; return &dev->stats; }

Contributors

PersonTokensPropCommitsCommitProp
Eric Dumazet11360.43%125.00%
Li RongQing7238.50%250.00%
Eric W. Biedermann21.07%125.00%
Total187100.00%4100.00%

/** * ip6_tnl_lookup - fetch tunnel matching the end-point addresses * @remote: the address of the tunnel exit-point * @local: the address of the tunnel entry-point * * Return: * tunnel matching given end-points if found, * else fallback tunnel if its device is up, * else %NULL **/ #define for_each_ip6_tunnel_rcu(start) \ for (t = rcu_dereference(start); t; t = rcu_dereference(t->next))
static struct ip6_tnl * ip6_tnl_lookup(struct net *net, const struct in6_addr *remote, const struct in6_addr *local) { unsigned int hash = HASH(remote, local); struct ip6_tnl *t; struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id); struct in6_addr any; for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) { if (ipv6_addr_equal(local, &t->parms.laddr) && ipv6_addr_equal(remote, &t->parms.raddr) && (t->dev->flags & IFF_UP)) return t; } memset(&any, 0, sizeof(any)); hash = HASH(&any, local); for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) { if (ipv6_addr_equal(local, &t->parms.laddr) && ipv6_addr_any(&t->parms.raddr) && (t->dev->flags & IFF_UP)) return t; } hash = HASH(remote, &any); for_each_ip6_tunnel_rcu(ip6n->tnls_r_l[hash]) { if (ipv6_addr_equal(remote, &t->parms.raddr) && ipv6_addr_any(&t->parms.laddr) && (t->dev->flags & IFF_UP)) return t; } t = rcu_dereference(ip6n->collect_md_tun); if (t) return t; t = rcu_dereference(ip6n->tnls_wc[0]); if (t && (t->dev->flags & IFF_UP)) return t; return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Steffen Klassert10338.29%17.69%
Ville Nuorvala9334.57%17.69%
Pavel Emelyanov197.06%215.38%
Vadim Fedorenko186.69%17.69%
Eric Dumazet165.95%430.77%
Alexei Starovoitov165.95%17.69%
Hideaki Yoshifuji / 吉藤英明31.12%215.38%
Yasuyuki Kozakai10.37%17.69%
Total269100.00%13100.00%

/** * ip6_tnl_bucket - get head of list matching given tunnel parameters * @p: parameters containing tunnel end-points * * Description: * ip6_tnl_bucket() returns the head of the list matching the * &struct in6_addr entries laddr and raddr in @p. * * Return: head of IPv6 tunnel list **/
static struct ip6_tnl __rcu ** ip6_tnl_bucket(struct ip6_tnl_net *ip6n, const struct __ip6_tnl_parm *p) { const struct in6_addr *remote = &p->raddr; const struct in6_addr *local = &p->laddr; unsigned int h = 0; int prio = 0; if (!ipv6_addr_any(remote) || !ipv6_addr_any(local)) { prio = 1; h = HASH(remote, local); } return &ip6n->tnls[prio][h]; }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala8084.21%111.11%
Pavel Emelyanov77.37%222.22%
Eric Dumazet66.32%444.44%
Yasuyuki Kozakai11.05%111.11%
Dmitry Kozlov11.05%111.11%
Total95100.00%9100.00%

/** * ip6_tnl_link - add tunnel to hash table * @t: tunnel to be added **/
static void ip6_tnl_link(struct ip6_tnl_net *ip6n, struct ip6_tnl *t) { struct ip6_tnl __rcu **tp = ip6_tnl_bucket(ip6n, &t->parms); if (t->parms.collect_md) rcu_assign_pointer(ip6n->collect_md_tun, t); rcu_assign_pointer(t->next , rtnl_dereference(*tp)); rcu_assign_pointer(*tp, t); }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala3447.89%114.29%
Alexei Starovoitov1723.94%114.29%
Eric Dumazet1115.49%342.86%
Pavel Emelyanov79.86%114.29%
Yasuyuki Kozakai22.82%114.29%
Total71100.00%7100.00%

/** * ip6_tnl_unlink - remove tunnel from hash table * @t: tunnel to be removed **/
static void ip6_tnl_unlink(struct ip6_tnl_net *ip6n, struct ip6_tnl *t) { struct ip6_tnl __rcu **tp; struct ip6_tnl *iter; if (t->parms.collect_md) rcu_assign_pointer(ip6n->collect_md_tun, NULL); for (tp = ip6_tnl_bucket(ip6n, &t->parms); (iter = rtnl_dereference(*tp)) != NULL; tp = &iter->next) { if (t == iter) { rcu_assign_pointer(*tp, t->next); break; } } }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala5454.55%116.67%
Eric Dumazet1919.19%233.33%
Alexei Starovoitov1717.17%116.67%
Pavel Emelyanov77.07%116.67%
Yasuyuki Kozakai22.02%116.67%
Total99100.00%6100.00%


static void ip6_dev_free(struct net_device *dev) { struct ip6_tnl *t = netdev_priv(dev); gro_cells_destroy(&t->gro_cells); dst_cache_destroy(&t->dst_cache); free_percpu(dev->tstats); free_netdev(dev); }

Contributors

PersonTokensPropCommitsCommitProp
Eric Dumazet2346.94%125.00%
Martin KaFai Lau1428.57%125.00%
Tom Herbert816.33%125.00%
Paolo Abeni48.16%125.00%
Total49100.00%4100.00%


static int ip6_tnl_create2(struct net_device *dev) { struct ip6_tnl *t = netdev_priv(dev); struct net *net = dev_net(dev); struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id); int err; t = netdev_priv(dev); dev->rtnl_link_ops = &ip6_link_ops; err = register_netdevice(dev); if (err < 0) goto out; strcpy(t->parms.name, dev->name); dev_hold(dev); ip6_tnl_link(ip6n, t); return 0; out: return err; }

Contributors

PersonTokensPropCommitsCommitProp
Nicolas Dichtel10293.58%150.00%
Thadeu Lima de Souza Cascardo76.42%150.00%
Total109100.00%2100.00%

/** * ip6_tnl_create - create a new tunnel * @p: tunnel parameters * @pt: pointer to new tunnel * * Description: * Create tunnel matching given parameters. * * Return: * created tunnel or error pointer **/
static struct ip6_tnl *ip6_tnl_create(struct net *net, struct __ip6_tnl_parm *p) { struct net_device *dev; struct ip6_tnl *t; char name[IFNAMSIZ]; int err = -ENOMEM; if (p->name[0]) strlcpy(name, p->name, IFNAMSIZ); else sprintf(name, "ip6tnl%%d"); dev = alloc_netdev(sizeof(*t), name, NET_NAME_UNKNOWN, ip6_tnl_dev_setup); if (!dev) goto failed; dev_net_set(dev, net); t = netdev_priv(dev); t->parms = *p; t->net = dev_net(dev); err = ip6_tnl_create2(dev); if (err < 0) goto failed_free; return t; failed_free: ip6_dev_free(dev); failed: return ERR_PTR(err); }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala9963.06%317.65%
Pavel Emelyanov1912.10%423.53%
Nicolas Dichtel1710.83%317.65%
Eric Dumazet106.37%15.88%
Noriaki Takamiya42.55%15.88%
Patrick McHardy31.91%15.88%
Tom Gundersen21.27%15.88%
Dmitry Kozlov10.64%15.88%
Yasuyuki Kozakai10.64%15.88%
Ian Morris10.64%15.88%
Total157100.00%17100.00%

/** * ip6_tnl_locate - find or create tunnel matching given parameters * @p: tunnel parameters * @create: != 0 if allowed to create new tunnel if no match found * * Description: * ip6_tnl_locate() first tries to locate an existing tunnel * based on @parms. If this is unsuccessful, but @create is set a new * tunnel device is created and registered for use. * * Return: * matching tunnel or error pointer **/
static struct ip6_tnl *ip6_tnl_locate(struct net *net, struct __ip6_tnl_parm *p, int create) { const struct in6_addr *remote = &p->raddr; const struct in6_addr *local = &p->laddr; struct ip6_tnl __rcu **tp; struct ip6_tnl *t; struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id); for (tp = ip6_tnl_bucket(ip6n, p); (t = rtnl_dereference(*tp)) != NULL; tp = &t->next) { if (ipv6_addr_equal(local, &t->parms.laddr) && ipv6_addr_equal(remote, &t->parms.raddr)) { if (create) return ERR_PTR(-EEXIST); return t; } } if (!create) return ERR_PTR(-ENODEV); return ip6_tnl_create(net, p); }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala9559.01%220.00%
Eric Dumazet2213.66%220.00%
Pavel Emelyanov2113.04%110.00%
Nicolas Dichtel106.21%110.00%
Steffen Klassert84.97%110.00%
Hideaki Yoshifuji / 吉藤英明21.24%110.00%
Yasuyuki Kozakai21.24%110.00%
Dmitry Kozlov10.62%110.00%
Total161100.00%10100.00%

/** * ip6_tnl_dev_uninit - tunnel device uninitializer * @dev: the device to be destroyed * * Description: * ip6_tnl_dev_uninit() removes tunnel from its list **/
static void ip6_tnl_dev_uninit(struct net_device *dev) { struct ip6_tnl *t = netdev_priv(dev); struct net *net = t->net; struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id); if (dev == ip6n->fb_tnl_dev) RCU_INIT_POINTER(ip6n->tnls_wc[0], NULL); else ip6_tnl_unlink(ip6n, t); dst_cache_reset(&t->dst_cache); dev_put(dev); }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala4453.01%325.00%
Pavel Emelyanov2530.12%325.00%
Paolo Abeni44.82%18.33%
Patrick McHardy33.61%18.33%
Nicolas Dichtel33.61%18.33%
Yasuyuki Kozakai22.41%18.33%
Stephen Hemminger11.20%18.33%
Eric Dumazet11.20%18.33%
Total83100.00%12100.00%

/** * parse_tvl_tnl_enc_lim - handle encapsulation limit option * @skb: received socket buffer * * Return: * 0 if none was found, * else index to encapsulation limit **/
__u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw) { const struct ipv6hdr *ipv6h = (const struct ipv6hdr *)raw; unsigned int nhoff = raw - skb->data; unsigned int off = nhoff + sizeof(*ipv6h); u8 next, nexthdr = ipv6h->nexthdr; while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) { struct ipv6_opt_hdr *hdr; u16 optlen; if (!pskb_may_pull(skb, off + sizeof(*hdr))) break; hdr = (struct ipv6_opt_hdr *)(skb->data + off); if (nexthdr == NEXTHDR_FRAGMENT) { struct frag_hdr *frag_hdr = (struct frag_hdr *) hdr; if (frag_hdr->frag_off) break; optlen = 8; } else if (nexthdr == NEXTHDR_AUTH) { optlen = (hdr->hdrlen + 2) << 2; } else { optlen = ipv6_optlen(hdr); } /* cache hdr->nexthdr, since pskb_may_pull() might * invalidate hdr */ next = hdr->nexthdr; if (nexthdr == NEXTHDR_DEST) { u16 i = 2; /* Remember : hdr is no longer valid at this point. */ if (!pskb_may_pull(skb, off + optlen)) break; while (1) { struct ipv6_tlv_tnl_enc_lim *tel; /* No more room for encapsulation limit */ if (i + sizeof(*tel) > optlen) break; tel = (struct ipv6_tlv_tnl_enc_lim *)(skb->data + off + i); /* return index of option if found and valid */ if (tel->type == IPV6_TLV_TNL_ENCAP_LIMIT && tel->length == 1) return i + off - nhoff; /* else jump to next option */ if (tel->type) i += tel->length + 2; else i++; } } nexthdr = next; off += optlen; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala23878.81%120.00%
Eric Dumazet6120.20%240.00%
Dan Carpenter20.66%120.00%
Dmitry Kozlov10.33%120.00%
Total302100.00%5100.00%

EXPORT_SYMBOL(ip6_tnl_parse_tlv_enc_lim); /** * ip6_tnl_err - tunnel error handler * * Description: * ip6_tnl_err() should handle errors in the tunnel according * to the specifications in RFC 2473. **/
static int ip6_tnl_err(struct sk_buff *skb, __u8 ipproto, struct inet6_skb_parm *opt, u8 *type, u8 *code, int *msg, __u32 *info, int offset) { const struct ipv6hdr *ipv6h = (const struct ipv6hdr *) skb->data; struct ip6_tnl *t; int rel_msg = 0; u8 rel_type = ICMPV6_DEST_UNREACH; u8 rel_code = ICMPV6_ADDR_UNREACH; u8 tproto; __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 processing of the error. */ rcu_read_lock(); t = ip6_tnl_lookup(dev_net(skb->dev), &ipv6h->daddr, &ipv6h->saddr); if (!t) goto out; tproto = ACCESS_ONCE(t->parms.proto); if (tproto != ipproto && tproto != 0) goto out; err = 0; switch (*type) { __u32 teli; struct ipv6_tlv_tnl_enc_lim *tel; __u32 mtu; case ICMPV6_DEST_UNREACH: net_dbg_ratelimited("%s: Path to destination invalid or inactive!\n", t->parms.name); rel_msg = 1; break; case ICMPV6_TIME_EXCEED: if ((*code) == ICMPV6_EXC_HOPLIMIT) { net_dbg_ratelimited("%s: Too small hop limit or routing loop in tunnel!\n", t->parms.name); rel_msg = 1; } break; case ICMPV6_PARAMPROB: teli = 0; if ((*code) == ICMPV6_HDR_FIELD) teli = ip6_tnl_parse_tlv_enc_lim(skb, skb->data); if (teli && teli == *info - 2) { tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->data[teli]; if (tel->encap_limit == 0) { net_dbg_ratelimited("%s: Too small encapsulation limit or routing loop in tunnel!\n", t->parms.name); rel_msg = 1; } } else { net_dbg_ratelimited("%s: Recipient unable to parse tunneled packet!\n", t->parms.name); } break; case ICMPV6_PKT_TOOBIG: mtu = *info - offset; if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU; t->dev->mtu = mtu; len = sizeof(*ipv6h) + ntohs(ipv6h->payload_len); if (len > mtu) { rel_type = ICMPV6_PKT_TOOBIG; rel_code = 0; rel_info = mtu; rel_msg = 1; } break; } *type = rel_type; *code = rel_code; *info = rel_info; *msg = rel_msg; out: rcu_read_unlock(); return err; }

Contributors

PersonTokensPropCommitsCommitProp
Ville Nuorvala28168.37%417.39%
Yasuyuki Kozakai6315.33%313.04%
Alexey Andriyanov133.16%14.35%
Herbert Xu112.68%14.35%
Ian Morris92.19%28.70%
Pavel Emelyanov81.95%28.70%
Eric Dumazet61.46%28.70%
Joe Perches51.22%14.35%
Brian Haley40.97%14.35%
Matt Bennett40.97%14.35%
Al Viro40.97%28.70%
Hideaki Yoshifuji / 吉藤英明20.49%28.70%
Dmitry Kozlov10.24%14.35%
Total411100.00%23100.00%


static int ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, u8 type, u8 code, int offset, __be32 info) { int rel_msg = 0; u8 rel_type = type; u8 rel_code = code; __u32 rel_info = ntohl(info); int err; struct sk_buff *skb2; const struct iphdr *eiph; struct rtable *rt; struct flowi4 fl4; err = ip6_tnl_err(skb, IPPROTO_IPIP, opt, &rel_type, &rel_code, &rel_msg, &rel_info, offset); if (err < 0) return err; if (rel_msg == 0) return 0; switch (rel_type) { case ICMPV6_DEST_UNREACH: if (rel_code != ICMPV6_ADDR_UNREACH) return 0; rel_type = ICMP_DEST_UNREACH; rel_code = ICMP_HOST_UNREACH; break; case ICMPV6_PKT_TOOBIG: if (rel_code != 0) return 0; rel_type = ICMP_DEST_UNREACH; rel_code = ICMP_FRAG_NEEDED; break; case NDISC_REDIRECT: rel_type = ICMP_REDIRECT; rel_code = ICMP_REDIR_HOST; default: return 0; } if (!pskb_may_pull(skb, offset + sizeof(struct iphdr))) return 0; skb2 = skb_clone(skb, GFP_ATOMIC); if (!skb2) return 0; skb_dst_drop(skb2); skb_pull(skb2, offset); skb_reset_network_header(skb2); eiph = ip_hdr(skb2); /* Try to guess incoming interface */ rt = ip_route_output_ports(dev_net(skb->dev), &fl4, NULL, eiph->saddr, 0, 0, 0, IPPROTO_IPIP, RT_TOS(eiph->tos), 0); if (IS_ERR(rt)) goto out; skb2->dev = rt->dst.dev; /* route "incoming" packet */ if (rt->rt_flags & RTCF_LOCAL) { ip_rt_put(rt); rt = NULL; rt = ip_route_output_ports(dev_net(skb->dev), &fl4, NULL, eiph->daddr, eiph->saddr, 0, 0, IPPROTO_IPIP, RT_TOS(eiph->tos), 0); if (IS_ERR(rt) || rt->dst.dev->type != ARPHRD_TUNNEL) { if (!IS_ERR(rt)) ip_rt_put(rt); goto out; } skb_dst_set(skb2, &rt->dst); } else { ip_rt_put(rt); if (ip_route_input(skb2, eiph->daddr, eiph->saddr, eiph->tos, skb2->dev) || skb_dst(skb2)->dev->type != ARPHRD_TUNNEL) goto out; } /* change mtu on this route */ if (rel_type == ICMP_DEST_UNREACH && rel_code == ICMP_FRAG_NEEDED) { if (rel_info > dst_mtu(skb_dst(skb2))) goto out; skb_dst(skb2)->ops->update_pmtu(skb_dst(skb2), NULL, skb2, rel_info); } if (rel_type == ICMP_REDIRECT) skb_dst(skb2)->ops->redirect(skb_dst(skb2), NULL, skb2); icmp_send(skb2, rel_type, rel_code, htonl(rel_info)); out: kfree_skb(skb2); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Yasuyuki Kozakai33861.79%420.00%
David S. Miller11821.57%525.00%
Ville Nuorvala498.96%15.00%
Eric Dumazet183.29%210.00%
Al Viro71.28%15.00%
Arnaldo Carvalho de Melo61.10%210.00%
Denis V. Lunev50.91%210.00%
Brian Haley40.73%15.00%
Herbert Xu10.18%15.00%
Pavel Emelyanov10.18%15.00%
Total547100.00%20100.00%


static int ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, u8 type, u8 code, int offset, __be32 info) { int rel_msg = 0; u8 rel_type = type; u8 rel_code = code; __u32 rel_info = ntohl(info); int err; err = ip6_tnl_err(skb, IPPROTO_IPV6, opt, &rel_type, &rel_code, &rel_msg, &rel_info, offset); if (err < 0) return err; if (rel_msg && pskb_may_pull(skb, offset + sizeof(struct ipv6hdr))) { struct rt6_info *rt; struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); if (!skb2) return 0; skb_dst_drop(skb2); skb_pull(skb2, offset); skb_reset_network_header(skb2); /* Try to guess incoming interface */