Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/net/ipv6/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 170 kB image not shown  

Quelle  route.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Linux INET6 implementation
 * FIB front-end.
 *
 * Authors:
 * Pedro Roque <roque@di.fc.ul.pt>
 */


/* Changes:
 *
 * YOSHIFUJI Hideaki @USAGI
 * reworked default router selection.
 * - respect outgoing interface
 * - select from (probably) reachable routers (i.e.
 * routers in REACHABLE, STALE, DELAY or PROBE states).
 * - always select the same router if it is (probably)
 * reachable.  otherwise, round-robin the list.
 * Ville Nuorvala
 * Fixed routing subtrees.
 */


#define pr_fmt(fmt) "IPv6: " fmt

#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/times.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/route.h>
#include <linux/netdevice.h>
#include <linux/in6.h>
#include <linux/mroute6.h>
#include <linux/init.h>
#include <linux/if_arp.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/nsproxy.h>
#include <linux/slab.h>
#include <linux/jhash.h>
#include <linux/siphash.h>
#include <net/net_namespace.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/ip6_fib.h>
#include <net/ip6_route.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/tcp.h>
#include <linux/rtnetlink.h>
#include <net/dst.h>
#include <net/dst_metadata.h>
#include <net/xfrm.h>
#include <net/netevent.h>
#include <net/netlink.h>
#include <net/rtnh.h>
#include <net/lwtunnel.h>
#include <net/ip_tunnels.h>
#include <net/l3mdev.h>
#include <net/ip.h>
#include <linux/uaccess.h>
#include <linux/btf_ids.h>

#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#endif

static int ip6_rt_type_to_error(u8 fib6_type);

#define CREATE_TRACE_POINTS
#include <trace/events/fib6.h>
EXPORT_TRACEPOINT_SYMBOL_GPL(fib6_table_lookup);
#undef CREATE_TRACE_POINTS

enum rt6_nud_state {
 RT6_NUD_FAIL_HARD = -3,
 RT6_NUD_FAIL_PROBE = -2,
 RT6_NUD_FAIL_DO_RR = -1,
 RT6_NUD_SUCCEED = 1
};

INDIRECT_CALLABLE_SCOPE
struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie);
static unsigned int  ip6_default_advmss(const struct dst_entry *dst);
INDIRECT_CALLABLE_SCOPE
unsigned int  ip6_mtu(const struct dst_entry *dst);
static void  ip6_negative_advice(struct sock *sk,
         struct dst_entry *dst);
static void  ip6_dst_destroy(struct dst_entry *);
static void  ip6_dst_ifdown(struct dst_entry *,
           struct net_device *dev);
static void   ip6_dst_gc(struct dst_ops *ops);

static int  ip6_pkt_discard(struct sk_buff *skb);
static int  ip6_pkt_discard_out(struct net *net, struct sock *sk, struct sk_buff *skb);
static int  ip6_pkt_prohibit(struct sk_buff *skb);
static int  ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb);
static void  ip6_link_failure(struct sk_buff *skb);
static void  ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk,
        struct sk_buff *skb, u32 mtu,
        bool confirm_neigh);
static void  rt6_do_redirect(struct dst_entry *dst, struct sock *sk,
     struct sk_buff *skb);
static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
      int strict);
static size_t rt6_nlmsg_size(struct fib6_info *f6i);
static int rt6_fill_node(struct net *net, struct sk_buff *skb,
    struct fib6_info *rt, struct dst_entry *dst,
    struct in6_addr *dest, struct in6_addr *src,
    int iif, int type, u32 portid, u32 seq,
    unsigned int flags);
static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,
        const struct in6_addr *daddr,
        const struct in6_addr *saddr);

#ifdef CONFIG_IPV6_ROUTE_INFO
static struct fib6_info *rt6_add_route_info(struct net *net,
        const struct in6_addr *prefix, int prefixlen,
        const struct in6_addr *gwaddr,
        struct net_device *dev,
        unsigned int pref);
static struct fib6_info *rt6_get_route_info(struct net *net,
        const struct in6_addr *prefix, int prefixlen,
        const struct in6_addr *gwaddr,
        struct net_device *dev);
#endif

struct uncached_list {
 spinlock_t  lock;
 struct list_head head;
};

static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt6_uncached_list);

void rt6_uncached_list_add(struct rt6_info *rt)
{
 struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list);

 rt->dst.rt_uncached_list = ul;

 spin_lock_bh(&ul->lock);
 list_add_tail(&rt->dst.rt_uncached, &ul->head);
 spin_unlock_bh(&ul->lock);
}

void rt6_uncached_list_del(struct rt6_info *rt)
{
 if (!list_empty(&rt->dst.rt_uncached)) {
  struct uncached_list *ul = rt->dst.rt_uncached_list;

  spin_lock_bh(&ul->lock);
  list_del_init(&rt->dst.rt_uncached);
  spin_unlock_bh(&ul->lock);
 }
}

static void rt6_uncached_list_flush_dev(struct net_device *dev)
{
 int cpu;

 for_each_possible_cpu(cpu) {
  struct uncached_list *ul = per_cpu_ptr(&rt6_uncached_list, cpu);
  struct rt6_info *rt, *safe;

  if (list_empty(&ul->head))
   continue;

  spin_lock_bh(&ul->lock);
  list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) {
   struct inet6_dev *rt_idev = rt->rt6i_idev;
   struct net_device *rt_dev = rt->dst.dev;
   bool handled = false;

   if (rt_idev && rt_idev->dev == dev) {
    rt->rt6i_idev = in6_dev_get(blackhole_netdev);
    in6_dev_put(rt_idev);
    handled = true;
   }

   if (rt_dev == dev) {
    rt->dst.dev = blackhole_netdev;
    netdev_ref_replace(rt_dev, blackhole_netdev,
         &rt->dst.dev_tracker,
         GFP_ATOMIC);
    handled = true;
   }
   if (handled)
    list_del_init(&rt->dst.rt_uncached);
  }
  spin_unlock_bh(&ul->lock);
 }
}

static inline const void *choose_neigh_daddr(const struct in6_addr *p,
          struct sk_buff *skb,
          const void *daddr)
{
 if (!ipv6_addr_any(p))
  return (const void *) p;
 else if (skb)
  return &ipv6_hdr(skb)->daddr;
 return daddr;
}

struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,
       struct net_device *dev,
       struct sk_buff *skb,
       const void *daddr)
{
 struct neighbour *n;

 daddr = choose_neigh_daddr(gw, skb, daddr);
 n = __ipv6_neigh_lookup(dev, daddr);
 if (n)
  return n;

 n = neigh_create(&nd_tbl, daddr, dev);
 return IS_ERR(n) ? NULL : n;
}

static struct neighbour *ip6_dst_neigh_lookup(const struct dst_entry *dst,
           struct sk_buff *skb,
           const void *daddr)
{
 const struct rt6_info *rt = dst_rt6_info(dst);

 return ip6_neigh_lookup(rt6_nexthop(rt, &in6addr_any),
    dst_dev(dst), skb, daddr);
}

static void ip6_confirm_neigh(const struct dst_entry *dst, const void *daddr)
{
 const struct rt6_info *rt = dst_rt6_info(dst);
 struct net_device *dev = dst_dev(dst);

 daddr = choose_neigh_daddr(rt6_nexthop(rt, &in6addr_any), NULL, daddr);
 if (!daddr)
  return;
 if (dev->flags & (IFF_NOARP | IFF_LOOPBACK))
  return;
 if (ipv6_addr_is_multicast((const struct in6_addr *)daddr))
  return;
 __ipv6_confirm_neigh(dev, daddr);
}

static struct dst_ops ip6_dst_ops_template = {
 .family   = AF_INET6,
 .gc   = ip6_dst_gc,
 .gc_thresh  = 1024,
 .check   = ip6_dst_check,
 .default_advmss  = ip6_default_advmss,
 .mtu   = ip6_mtu,
 .cow_metrics  = dst_cow_metrics_generic,
 .destroy  = ip6_dst_destroy,
 .ifdown   = ip6_dst_ifdown,
 .negative_advice = ip6_negative_advice,
 .link_failure  = ip6_link_failure,
 .update_pmtu  = ip6_rt_update_pmtu,
 .redirect  = rt6_do_redirect,
 .local_out  = __ip6_local_out,
 .neigh_lookup  = ip6_dst_neigh_lookup,
 .confirm_neigh  = ip6_confirm_neigh,
};

static struct dst_ops ip6_dst_blackhole_ops = {
 .family   = AF_INET6,
 .default_advmss  = ip6_default_advmss,
 .neigh_lookup  = ip6_dst_neigh_lookup,
 .check   = ip6_dst_check,
 .destroy  = ip6_dst_destroy,
 .cow_metrics  = dst_cow_metrics_generic,
 .update_pmtu  = dst_blackhole_update_pmtu,
 .redirect  = dst_blackhole_redirect,
 .mtu   = dst_blackhole_mtu,
};

static const u32 ip6_template_metrics[RTAX_MAX] = {
 [RTAX_HOPLIMIT - 1] = 0,
};

static const struct fib6_info fib6_null_entry_template = {
 .fib6_flags = (RTF_REJECT | RTF_NONEXTHOP),
 .fib6_protocol  = RTPROT_KERNEL,
 .fib6_metric = ~(u32)0,
 .fib6_ref = REFCOUNT_INIT(1),
 .fib6_type = RTN_UNREACHABLE,
 .fib6_metrics = (struct dst_metrics *)&dst_default_metrics,
};

static const struct rt6_info ip6_null_entry_template = {
 .dst = {
  .__rcuref = RCUREF_INIT(1),
  .__use  = 1,
  .obsolete = DST_OBSOLETE_FORCE_CHK,
  .error  = -ENETUNREACH,
  .input  = ip6_pkt_discard,
  .output  = ip6_pkt_discard_out,
 },
 .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP),
};

#ifdef CONFIG_IPV6_MULTIPLE_TABLES

static const struct rt6_info ip6_prohibit_entry_template = {
 .dst = {
  .__rcuref = RCUREF_INIT(1),
  .__use  = 1,
  .obsolete = DST_OBSOLETE_FORCE_CHK,
  .error  = -EACCES,
  .input  = ip6_pkt_prohibit,
  .output  = ip6_pkt_prohibit_out,
 },
 .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP),
};

static const struct rt6_info ip6_blk_hole_entry_template = {
 .dst = {
  .__rcuref = RCUREF_INIT(1),
  .__use  = 1,
  .obsolete = DST_OBSOLETE_FORCE_CHK,
  .error  = -EINVAL,
  .input  = dst_discard,
  .output  = dst_discard_out,
 },
 .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP),
};

#endif

static void rt6_info_init(struct rt6_info *rt)
{
 memset_after(rt, 0, dst);
}

/* allocate dst with ip6_dst_ops */
struct rt6_info *ip6_dst_alloc(struct net *net, struct net_device *dev,
          int flags)
{
 struct rt6_info *rt = dst_alloc(&net->ipv6.ip6_dst_ops, dev,
     DST_OBSOLETE_FORCE_CHK, flags);

 if (rt) {
  rt6_info_init(rt);
  atomic_inc(&net->ipv6.rt6_stats->fib_rt_alloc);
 }

 return rt;
}
EXPORT_SYMBOL(ip6_dst_alloc);

static void ip6_dst_destroy(struct dst_entry *dst)
{
 struct rt6_info *rt = dst_rt6_info(dst);
 struct fib6_info *from;
 struct inet6_dev *idev;

 ip_dst_metrics_put(dst);
 rt6_uncached_list_del(rt);

 idev = rt->rt6i_idev;
 if (idev) {
  rt->rt6i_idev = NULL;
  in6_dev_put(idev);
 }

 from = unrcu_pointer(xchg(&rt->from, NULL));
 fib6_info_release(from);
}

static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev)
{
 struct rt6_info *rt = dst_rt6_info(dst);
 struct inet6_dev *idev = rt->rt6i_idev;
 struct fib6_info *from;

 if (idev && idev->dev != blackhole_netdev) {
  struct inet6_dev *blackhole_idev = in6_dev_get(blackhole_netdev);

  if (blackhole_idev) {
   rt->rt6i_idev = blackhole_idev;
   in6_dev_put(idev);
  }
 }
 from = unrcu_pointer(xchg(&rt->from, NULL));
 fib6_info_release(from);
}

static bool __rt6_check_expired(const struct rt6_info *rt)
{
 if (rt->rt6i_flags & RTF_EXPIRES)
  return time_after(jiffies, READ_ONCE(rt->dst.expires));
 return false;
}

static bool rt6_check_expired(const struct rt6_info *rt)
{
 struct fib6_info *from;

 from = rcu_dereference(rt->from);

 if (rt->rt6i_flags & RTF_EXPIRES) {
  if (time_after(jiffies, READ_ONCE(rt->dst.expires)))
   return true;
 } else if (from) {
  return READ_ONCE(rt->dst.obsolete) != DST_OBSOLETE_FORCE_CHK ||
   fib6_check_expired(from);
 }
 return false;
}

static struct fib6_info *
rt6_multipath_first_sibling_rcu(const struct fib6_info *rt)
{
 struct fib6_info *iter;
 struct fib6_node *fn;

 fn = rcu_dereference(rt->fib6_node);
 if (!fn)
  goto out;
 iter = rcu_dereference(fn->leaf);
 if (!iter)
  goto out;

 while (iter) {
  if (iter->fib6_metric == rt->fib6_metric &&
      rt6_qualify_for_ecmp(iter))
   return iter;
  iter = rcu_dereference(iter->fib6_next);
 }

out:
 return NULL;
}

void fib6_select_path(const struct net *net, struct fib6_result *res,
        struct flowi6 *fl6, int oif, bool have_oif_match,
        const struct sk_buff *skb, int strict)
{
 struct fib6_info *first, *match = res->f6i;
 struct fib6_info *sibling;
 int hash;

 if (!match->nh && (!match->fib6_nsiblings || have_oif_match))
  goto out;

 if (match->nh && have_oif_match && res->nh)
  return;

 if (skb)
  IP6CB(skb)->flags |= IP6SKB_MULTIPATH;

 /* We might have already computed the hash for ICMPv6 errors. In such
 * case it will always be non-zero. Otherwise now is the time to do it.
 */

 if (!fl6->mp_hash &&
     (!match->nh || nexthop_is_multipath(match->nh)))
  fl6->mp_hash = rt6_multipath_hash(net, fl6, skb, NULL);

 if (unlikely(match->nh)) {
  nexthop_path_fib6_result(res, fl6->mp_hash);
  return;
 }

 first = rt6_multipath_first_sibling_rcu(match);
 if (!first)
  goto out;

 hash = fl6->mp_hash;
 if (hash <= atomic_read(&first->fib6_nh->fib_nh_upper_bound)) {
  if (rt6_score_route(first->fib6_nh, first->fib6_flags, oif,
        strict) >= 0)
   match = first;
  goto out;
 }

 list_for_each_entry_rcu(sibling, &first->fib6_siblings,
    fib6_siblings) {
  const struct fib6_nh *nh = sibling->fib6_nh;
  int nh_upper_bound;

  nh_upper_bound = atomic_read(&nh->fib_nh_upper_bound);
  if (hash > nh_upper_bound)
   continue;
  if (rt6_score_route(nh, sibling->fib6_flags, oif, strict) < 0)
   break;
  match = sibling;
  break;
 }

out:
 res->f6i = match;
 res->nh = match->fib6_nh;
}

/*
 * Route lookup. rcu_read_lock() should be held.
 */


static bool __rt6_device_match(struct net *net, const struct fib6_nh *nh,
          const struct in6_addr *saddr, int oif, int flags)
{
 const struct net_device *dev;

 if (nh->fib_nh_flags & RTNH_F_DEAD)
  return false;

 dev = nh->fib_nh_dev;
 if (oif) {
  if (dev->ifindex == oif)
   return true;
 } else {
  if (ipv6_chk_addr(net, saddr, dev,
      flags & RT6_LOOKUP_F_IFACE))
   return true;
 }

 return false;
}

struct fib6_nh_dm_arg {
 struct net  *net;
 const struct in6_addr *saddr;
 int   oif;
 int   flags;
 struct fib6_nh  *nh;
};

static int __rt6_nh_dev_match(struct fib6_nh *nh, void *_arg)
{
 struct fib6_nh_dm_arg *arg = _arg;

 arg->nh = nh;
 return __rt6_device_match(arg->net, nh, arg->saddr, arg->oif,
      arg->flags);
}

/* returns fib6_nh from nexthop or NULL */
static struct fib6_nh *rt6_nh_dev_match(struct net *net, struct nexthop *nh,
     struct fib6_result *res,
     const struct in6_addr *saddr,
     int oif, int flags)
{
 struct fib6_nh_dm_arg arg = {
  .net   = net,
  .saddr = saddr,
  .oif   = oif,
  .flags = flags,
 };

 if (nexthop_is_blackhole(nh))
  return NULL;

 if (nexthop_for_each_fib6_nh(nh, __rt6_nh_dev_match, &arg))
  return arg.nh;

 return NULL;
}

static void rt6_device_match(struct net *net, struct fib6_result *res,
        const struct in6_addr *saddr, int oif, int flags)
{
 struct fib6_info *f6i = res->f6i;
 struct fib6_info *spf6i;
 struct fib6_nh *nh;

 if (!oif && ipv6_addr_any(saddr)) {
  if (unlikely(f6i->nh)) {
   nh = nexthop_fib6_nh(f6i->nh);
   if (nexthop_is_blackhole(f6i->nh))
    goto out_blackhole;
  } else {
   nh = f6i->fib6_nh;
  }
  if (!(nh->fib_nh_flags & RTNH_F_DEAD))
   goto out;
 }

 for (spf6i = f6i; spf6i; spf6i = rcu_dereference(spf6i->fib6_next)) {
  bool matched = false;

  if (unlikely(spf6i->nh)) {
   nh = rt6_nh_dev_match(net, spf6i->nh, res, saddr,
           oif, flags);
   if (nh)
    matched = true;
  } else {
   nh = spf6i->fib6_nh;
   if (__rt6_device_match(net, nh, saddr, oif, flags))
    matched = true;
  }
  if (matched) {
   res->f6i = spf6i;
   goto out;
  }
 }

 if (oif && flags & RT6_LOOKUP_F_IFACE) {
  res->f6i = net->ipv6.fib6_null_entry;
  nh = res->f6i->fib6_nh;
  goto out;
 }

 if (unlikely(f6i->nh)) {
  nh = nexthop_fib6_nh(f6i->nh);
  if (nexthop_is_blackhole(f6i->nh))
   goto out_blackhole;
 } else {
  nh = f6i->fib6_nh;
 }

 if (nh->fib_nh_flags & RTNH_F_DEAD) {
  res->f6i = net->ipv6.fib6_null_entry;
  nh = res->f6i->fib6_nh;
 }
out:
 res->nh = nh;
 res->fib6_type = res->f6i->fib6_type;
 res->fib6_flags = res->f6i->fib6_flags;
 return;

out_blackhole:
 res->fib6_flags |= RTF_REJECT;
 res->fib6_type = RTN_BLACKHOLE;
 res->nh = nh;
}

#ifdef CONFIG_IPV6_ROUTER_PREF
struct __rt6_probe_work {
 struct work_struct work;
 struct in6_addr target;
 struct net_device *dev;
 netdevice_tracker dev_tracker;
};

static void rt6_probe_deferred(struct work_struct *w)
{
 struct in6_addr mcaddr;
 struct __rt6_probe_work *work =
  container_of(w, struct __rt6_probe_work, work);

 addrconf_addr_solict_mult(&work->target, &mcaddr);
 ndisc_send_ns(work->dev, &work->target, &mcaddr, NULL, 0);
 netdev_put(work->dev, &work->dev_tracker);
 kfree(work);
}

static void rt6_probe(struct fib6_nh *fib6_nh)
{
 struct __rt6_probe_work *work = NULL;
 const struct in6_addr *nh_gw;
 unsigned long last_probe;
 struct neighbour *neigh;
 struct net_device *dev;
 struct inet6_dev *idev;

 /*
 * Okay, this does not seem to be appropriate
 * for now, however, we need to check if it
 * is really so; aka Router Reachability Probing.
 *
 * Router Reachability Probe MUST be rate-limited
 * to no more than one per minute.
 */

 if (!fib6_nh->fib_nh_gw_family)
  return;

 nh_gw = &fib6_nh->fib_nh_gw6;
 dev = fib6_nh->fib_nh_dev;
 rcu_read_lock();
 last_probe = READ_ONCE(fib6_nh->last_probe);
 idev = __in6_dev_get(dev);
 if (!idev)
  goto out;
 neigh = __ipv6_neigh_lookup_noref(dev, nh_gw);
 if (neigh) {
  if (READ_ONCE(neigh->nud_state) & NUD_VALID)
   goto out;

  write_lock_bh(&neigh->lock);
  if (!(neigh->nud_state & NUD_VALID) &&
      time_after(jiffies,
          neigh->updated +
          READ_ONCE(idev->cnf.rtr_probe_interval))) {
   work = kmalloc(sizeof(*work), GFP_ATOMIC);
   if (work)
    __neigh_set_probe_once(neigh);
  }
  write_unlock_bh(&neigh->lock);
 } else if (time_after(jiffies, last_probe +
           READ_ONCE(idev->cnf.rtr_probe_interval))) {
  work = kmalloc(sizeof(*work), GFP_ATOMIC);
 }

 if (!work || cmpxchg(&fib6_nh->last_probe,
        last_probe, jiffies) != last_probe) {
  kfree(work);
 } else {
  INIT_WORK(&work->work, rt6_probe_deferred);
  work->target = *nh_gw;
  netdev_hold(dev, &work->dev_tracker, GFP_ATOMIC);
  work->dev = dev;
  schedule_work(&work->work);
 }

out:
 rcu_read_unlock();
}
#else
static inline void rt6_probe(struct fib6_nh *fib6_nh)
{
}
#endif

/*
 * Default Router Selection (RFC 2461 6.3.6)
 */

static enum rt6_nud_state rt6_check_neigh(const struct fib6_nh *fib6_nh)
{
 enum rt6_nud_state ret = RT6_NUD_FAIL_HARD;
 struct neighbour *neigh;

 rcu_read_lock();
 neigh = __ipv6_neigh_lookup_noref(fib6_nh->fib_nh_dev,
       &fib6_nh->fib_nh_gw6);
 if (neigh) {
  u8 nud_state = READ_ONCE(neigh->nud_state);

  if (nud_state & NUD_VALID)
   ret = RT6_NUD_SUCCEED;
#ifdef CONFIG_IPV6_ROUTER_PREF
  else if (!(nud_state & NUD_FAILED))
   ret = RT6_NUD_SUCCEED;
  else
   ret = RT6_NUD_FAIL_PROBE;
#endif
 } else {
  ret = IS_ENABLED(CONFIG_IPV6_ROUTER_PREF) ?
        RT6_NUD_SUCCEED : RT6_NUD_FAIL_DO_RR;
 }
 rcu_read_unlock();

 return ret;
}

static int rt6_score_route(const struct fib6_nh *nh, u32 fib6_flags, int oif,
      int strict)
{
 int m = 0;

 if (!oif || nh->fib_nh_dev->ifindex == oif)
  m = 2;

 if (!m && (strict & RT6_LOOKUP_F_IFACE))
  return RT6_NUD_FAIL_HARD;
#ifdef CONFIG_IPV6_ROUTER_PREF
 m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(fib6_flags)) << 2;
#endif
 if ((strict & RT6_LOOKUP_F_REACHABLE) &&
     !(fib6_flags & RTF_NONEXTHOP) && nh->fib_nh_gw_family) {
  int n = rt6_check_neigh(nh);
  if (n < 0)
   return n;
 }
 return m;
}

static bool find_match(struct fib6_nh *nh, u32 fib6_flags,
         int oif, int strict, int *mpri, bool *do_rr)
{
 bool match_do_rr = false;
 bool rc = false;
 int m;

 if (nh->fib_nh_flags & RTNH_F_DEAD)
  goto out;

 if (ip6_ignore_linkdown(nh->fib_nh_dev) &&
     nh->fib_nh_flags & RTNH_F_LINKDOWN &&
     !(strict & RT6_LOOKUP_F_IGNORE_LINKSTATE))
  goto out;

 m = rt6_score_route(nh, fib6_flags, oif, strict);
 if (m == RT6_NUD_FAIL_DO_RR) {
  match_do_rr = true;
  m = 0; /* lowest valid score */
 } else if (m == RT6_NUD_FAIL_HARD) {
  goto out;
 }

 if (strict & RT6_LOOKUP_F_REACHABLE)
  rt6_probe(nh);

 /* note that m can be RT6_NUD_FAIL_PROBE at this point */
 if (m > *mpri) {
  *do_rr = match_do_rr;
  *mpri = m;
  rc = true;
 }
out:
 return rc;
}

struct fib6_nh_frl_arg {
 u32  flags;
 int  oif;
 int  strict;
 int  *mpri;
 bool  *do_rr;
 struct fib6_nh *nh;
};

static int rt6_nh_find_match(struct fib6_nh *nh, void *_arg)
{
 struct fib6_nh_frl_arg *arg = _arg;

 arg->nh = nh;
 return find_match(nh, arg->flags, arg->oif, arg->strict,
     arg->mpri, arg->do_rr);
}

static void __find_rr_leaf(struct fib6_info *f6i_start,
      struct fib6_info *nomatch, u32 metric,
      struct fib6_result *res, struct fib6_info **cont,
      int oif, int strict, bool *do_rr, int *mpri)
{
 struct fib6_info *f6i;

 for (f6i = f6i_start;
      f6i && f6i != nomatch;
      f6i = rcu_dereference(f6i->fib6_next)) {
  bool matched = false;
  struct fib6_nh *nh;

  if (cont && f6i->fib6_metric != metric) {
   *cont = f6i;
   return;
  }

  if (fib6_check_expired(f6i))
   continue;

  if (unlikely(f6i->nh)) {
   struct fib6_nh_frl_arg arg = {
    .flags  = f6i->fib6_flags,
    .oif    = oif,
    .strict = strict,
    .mpri   = mpri,
    .do_rr  = do_rr
   };

   if (nexthop_is_blackhole(f6i->nh)) {
    res->fib6_flags = RTF_REJECT;
    res->fib6_type = RTN_BLACKHOLE;
    res->f6i = f6i;
    res->nh = nexthop_fib6_nh(f6i->nh);
    return;
   }
   if (nexthop_for_each_fib6_nh(f6i->nh, rt6_nh_find_match,
           &arg)) {
    matched = true;
    nh = arg.nh;
   }
  } else {
   nh = f6i->fib6_nh;
   if (find_match(nh, f6i->fib6_flags, oif, strict,
           mpri, do_rr))
    matched = true;
  }
  if (matched) {
   res->f6i = f6i;
   res->nh = nh;
   res->fib6_flags = f6i->fib6_flags;
   res->fib6_type = f6i->fib6_type;
  }
 }
}

static void find_rr_leaf(struct fib6_node *fn, struct fib6_info *leaf,
    struct fib6_info *rr_head, int oif, int strict,
    bool *do_rr, struct fib6_result *res)
{
 u32 metric = rr_head->fib6_metric;
 struct fib6_info *cont = NULL;
 int mpri = -1;

 __find_rr_leaf(rr_head, NULL, metric, res, &cont,
         oif, strict, do_rr, &mpri);

 __find_rr_leaf(leaf, rr_head, metric, res, &cont,
         oif, strict, do_rr, &mpri);

 if (res->f6i || !cont)
  return;

 __find_rr_leaf(cont, NULL, metric, res, NULL,
         oif, strict, do_rr, &mpri);
}

static void rt6_select(struct net *net, struct fib6_node *fn, int oif,
         struct fib6_result *res, int strict)
{
 struct fib6_info *leaf = rcu_dereference(fn->leaf);
 struct fib6_info *rt0;
 bool do_rr = false;
 int key_plen;

 /* make sure this function or its helpers sets f6i */
 res->f6i = NULL;

 if (!leaf || leaf == net->ipv6.fib6_null_entry)
  goto out;

 rt0 = rcu_dereference(fn->rr_ptr);
 if (!rt0)
  rt0 = leaf;

 /* Double check to make sure fn is not an intermediate node
 * and fn->leaf does not points to its child's leaf
 * (This might happen if all routes under fn are deleted from
 * the tree and fib6_repair_tree() is called on the node.)
 */

 key_plen = rt0->fib6_dst.plen;
#ifdef CONFIG_IPV6_SUBTREES
 if (rt0->fib6_src.plen)
  key_plen = rt0->fib6_src.plen;
#endif
 if (fn->fn_bit != key_plen)
  goto out;

 find_rr_leaf(fn, leaf, rt0, oif, strict, &do_rr, res);
 if (do_rr) {
  struct fib6_info *next = rcu_dereference(rt0->fib6_next);

  /* no entries matched; do round-robin */
  if (!next || next->fib6_metric != rt0->fib6_metric)
   next = leaf;

  if (next != rt0) {
   spin_lock_bh(&leaf->fib6_table->tb6_lock);
   /* make sure next is not being deleted from the tree */
   if (next->fib6_node)
    rcu_assign_pointer(fn->rr_ptr, next);
   spin_unlock_bh(&leaf->fib6_table->tb6_lock);
  }
 }

out:
 if (!res->f6i) {
  res->f6i = net->ipv6.fib6_null_entry;
  res->nh = res->f6i->fib6_nh;
  res->fib6_flags = res->f6i->fib6_flags;
  res->fib6_type = res->f6i->fib6_type;
 }
}

static bool rt6_is_gw_or_nonexthop(const struct fib6_result *res)
{
 return (res->f6i->fib6_flags & RTF_NONEXTHOP) ||
        res->nh->fib_nh_gw_family;
}

#ifdef CONFIG_IPV6_ROUTE_INFO
int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
    const struct in6_addr *gwaddr)
{
 struct net *net = dev_net(dev);
 struct route_info *rinfo = (struct route_info *) opt;
 struct in6_addr prefix_buf, *prefix;
 struct fib6_table *table;
 unsigned int pref;
 unsigned long lifetime;
 struct fib6_info *rt;

 if (len < sizeof(struct route_info)) {
  return -EINVAL;
 }

 /* Sanity check for prefix_len and length */
 if (rinfo->length > 3) {
  return -EINVAL;
 } else if (rinfo->prefix_len > 128) {
  return -EINVAL;
 } else if (rinfo->prefix_len > 64) {
  if (rinfo->length < 2) {
   return -EINVAL;
  }
 } else if (rinfo->prefix_len > 0) {
  if (rinfo->length < 1) {
   return -EINVAL;
  }
 }

 pref = rinfo->route_pref;
 if (pref == ICMPV6_ROUTER_PREF_INVALID)
  return -EINVAL;

 lifetime = addrconf_timeout_fixup(ntohl(rinfo->lifetime), HZ);

 if (rinfo->length == 3)
  prefix = (struct in6_addr *)rinfo->prefix;
 else {
  /* this function is safe */
  ipv6_addr_prefix(&prefix_buf,
     (struct in6_addr *)rinfo->prefix,
     rinfo->prefix_len);
  prefix = &prefix_buf;
 }

 if (rinfo->prefix_len == 0)
  rt = rt6_get_dflt_router(net, gwaddr, dev);
 else
  rt = rt6_get_route_info(net, prefix, rinfo->prefix_len,
     gwaddr, dev);

 if (rt && !lifetime) {
  ip6_del_rt(net, rt, false);
  rt = NULL;
 }

 if (!rt && lifetime)
  rt = rt6_add_route_info(net, prefix, rinfo->prefix_len, gwaddr,
     dev, pref);
 else if (rt)
  rt->fib6_flags = RTF_ROUTEINFO |
     (rt->fib6_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);

 if (rt) {
  table = rt->fib6_table;
  spin_lock_bh(&table->tb6_lock);

  if (!addrconf_finite_timeout(lifetime)) {
   fib6_clean_expires(rt);
   fib6_remove_gc_list(rt);
  } else {
   fib6_set_expires(rt, jiffies + HZ * lifetime);
   fib6_add_gc_list(rt);
  }

  spin_unlock_bh(&table->tb6_lock);

  fib6_info_release(rt);
 }
 return 0;
}
#endif

/*
 * Misc support functions
 */


/* called with rcu_lock held */
static struct net_device *ip6_rt_get_dev_rcu(const struct fib6_result *res)
{
 struct net_device *dev = res->nh->fib_nh_dev;

 if (res->fib6_flags & (RTF_LOCAL | RTF_ANYCAST)) {
  /* for copies of local routes, dst->dev needs to be the
 * device if it is a master device, the master device if
 * device is enslaved, and the loopback as the default
 */

  if (netif_is_l3_slave(dev) &&
      !rt6_need_strict(&res->f6i->fib6_dst.addr))
   dev = l3mdev_master_dev_rcu(dev);
  else if (!netif_is_l3_master(dev))
   dev = dev_net(dev)->loopback_dev;
  /* last case is netif_is_l3_master(dev) is true in which
 * case we want dev returned to be dev
 */

 }

 return dev;
}

static const int fib6_prop[RTN_MAX + 1] = {
 [RTN_UNSPEC] = 0,
 [RTN_UNICAST] = 0,
 [RTN_LOCAL] = 0,
 [RTN_BROADCAST] = 0,
 [RTN_ANYCAST] = 0,
 [RTN_MULTICAST] = 0,
 [RTN_BLACKHOLE] = -EINVAL,
 [RTN_UNREACHABLE] = -EHOSTUNREACH,
 [RTN_PROHIBIT] = -EACCES,
 [RTN_THROW] = -EAGAIN,
 [RTN_NAT] = -EINVAL,
 [RTN_XRESOLVE] = -EINVAL,
};

static int ip6_rt_type_to_error(u8 fib6_type)
{
 return fib6_prop[fib6_type];
}

static unsigned short fib6_info_dst_flags(struct fib6_info *rt)
{
 unsigned short flags = 0;

 if (rt->dst_nocount)
  flags |= DST_NOCOUNT;
 if (rt->dst_nopolicy)
  flags |= DST_NOPOLICY;

 return flags;
}

static void ip6_rt_init_dst_reject(struct rt6_info *rt, u8 fib6_type)
{
 rt->dst.error = ip6_rt_type_to_error(fib6_type);

 switch (fib6_type) {
 case RTN_BLACKHOLE:
  rt->dst.output = dst_discard_out;
  rt->dst.input = dst_discard;
  break;
 case RTN_PROHIBIT:
  rt->dst.output = ip6_pkt_prohibit_out;
  rt->dst.input = ip6_pkt_prohibit;
  break;
 case RTN_THROW:
 case RTN_UNREACHABLE:
 default:
  rt->dst.output = ip6_pkt_discard_out;
  rt->dst.input = ip6_pkt_discard;
  break;
 }
}

static void ip6_rt_init_dst(struct rt6_info *rt, const struct fib6_result *res)
{
 struct fib6_info *f6i = res->f6i;

 if (res->fib6_flags & RTF_REJECT) {
  ip6_rt_init_dst_reject(rt, res->fib6_type);
  return;
 }

 rt->dst.error = 0;
 rt->dst.output = ip6_output;

 if (res->fib6_type == RTN_LOCAL || res->fib6_type == RTN_ANYCAST) {
  rt->dst.input = ip6_input;
 } else if (ipv6_addr_type(&f6i->fib6_dst.addr) & IPV6_ADDR_MULTICAST) {
  rt->dst.input = ip6_mc_input;
  rt->dst.output = ip6_mr_output;
 } else {
  rt->dst.input = ip6_forward;
 }

 if (res->nh->fib_nh_lws) {
  rt->dst.lwtstate = lwtstate_get(res->nh->fib_nh_lws);
  lwtunnel_set_redirect(&rt->dst);
 }

 rt->dst.lastuse = jiffies;
}

/* Caller must already hold reference to @from */
static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
{
 rt->rt6i_flags &= ~RTF_EXPIRES;
 rcu_assign_pointer(rt->from, from);
 ip_dst_init_metrics(&rt->dst, from->fib6_metrics);
}

/* Caller must already hold reference to f6i in result */
static void ip6_rt_copy_init(struct rt6_info *rt, const struct fib6_result *res)
{
 const struct fib6_nh *nh = res->nh;
 const struct net_device *dev = nh->fib_nh_dev;
 struct fib6_info *f6i = res->f6i;

 ip6_rt_init_dst(rt, res);

 rt->rt6i_dst = f6i->fib6_dst;
 rt->rt6i_idev = dev ? in6_dev_get(dev) : NULL;
 rt->rt6i_flags = res->fib6_flags;
 if (nh->fib_nh_gw_family) {
  rt->rt6i_gateway = nh->fib_nh_gw6;
  rt->rt6i_flags |= RTF_GATEWAY;
 }
 rt6_set_from(rt, f6i);
#ifdef CONFIG_IPV6_SUBTREES
 rt->rt6i_src = f6i->fib6_src;
#endif
}

static struct fib6_node* fib6_backtrack(struct fib6_node *fn,
     struct in6_addr *saddr)
{
 struct fib6_node *pn, *sn;
 while (1) {
  if (fn->fn_flags & RTN_TL_ROOT)
   return NULL;
  pn = rcu_dereference(fn->parent);
  sn = FIB6_SUBTREE(pn);
  if (sn && sn != fn)
   fn = fib6_node_lookup(sn, NULL, saddr);
  else
   fn = pn;
  if (fn->fn_flags & RTN_RTINFO)
   return fn;
 }
}

static bool ip6_hold_safe(struct net *net, struct rt6_info **prt)
{
 struct rt6_info *rt = *prt;

 if (dst_hold_safe(&rt->dst))
  return true;
 if (net) {
  rt = net->ipv6.ip6_null_entry;
  dst_hold(&rt->dst);
 } else {
  rt = NULL;
 }
 *prt = rt;
 return false;
}

/* called with rcu_lock held */
static struct rt6_info *ip6_create_rt_rcu(const struct fib6_result *res)
{
 struct net_device *dev = res->nh->fib_nh_dev;
 struct fib6_info *f6i = res->f6i;
 unsigned short flags;
 struct rt6_info *nrt;

 if (!fib6_info_hold_safe(f6i))
  goto fallback;

 flags = fib6_info_dst_flags(f6i);
 nrt = ip6_dst_alloc(dev_net(dev), dev, flags);
 if (!nrt) {
  fib6_info_release(f6i);
  goto fallback;
 }

 ip6_rt_copy_init(nrt, res);
 return nrt;

fallback:
 nrt = dev_net(dev)->ipv6.ip6_null_entry;
 dst_hold(&nrt->dst);
 return nrt;
}

INDIRECT_CALLABLE_SCOPE struct rt6_info *ip6_pol_route_lookup(struct net *net,
          struct fib6_table *table,
          struct flowi6 *fl6,
          const struct sk_buff *skb,
          int flags)
{
 struct fib6_result res = {};
 struct fib6_node *fn;
 struct rt6_info *rt;

 rcu_read_lock();
 fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
restart:
 res.f6i = rcu_dereference(fn->leaf);
 if (!res.f6i)
  res.f6i = net->ipv6.fib6_null_entry;
 else
  rt6_device_match(net, &res, &fl6->saddr, fl6->flowi6_oif,
     flags);

 if (res.f6i == net->ipv6.fib6_null_entry) {
  fn = fib6_backtrack(fn, &fl6->saddr);
  if (fn)
   goto restart;

  rt = net->ipv6.ip6_null_entry;
  dst_hold(&rt->dst);
  goto out;
 } else if (res.fib6_flags & RTF_REJECT) {
  goto do_create;
 }

 fib6_select_path(net, &res, fl6, fl6->flowi6_oif,
    fl6->flowi6_oif != 0, skb, flags);

 /* Search through exception table */
 rt = rt6_find_cached_rt(&res, &fl6->daddr, &fl6->saddr);
 if (rt) {
  if (ip6_hold_safe(net, &rt))
   dst_use_noref(&rt->dst, jiffies);
 } else {
do_create:
  rt = ip6_create_rt_rcu(&res);
 }

out:
 trace_fib6_table_lookup(net, &res, table, fl6);

 rcu_read_unlock();

 return rt;
}

struct dst_entry *ip6_route_lookup(struct net *net, struct flowi6 *fl6,
       const struct sk_buff *skb, int flags)
{
 return fib6_rule_lookup(net, fl6, skb, flags, ip6_pol_route_lookup);
}
EXPORT_SYMBOL_GPL(ip6_route_lookup);

struct rt6_info *rt6_lookup(struct net *net, const struct in6_addr *daddr,
       const struct in6_addr *saddr, int oif,
       const struct sk_buff *skb, int strict)
{
 struct flowi6 fl6 = {
  .flowi6_oif = oif,
  .daddr = *daddr,
 };
 struct dst_entry *dst;
 int flags = strict ? RT6_LOOKUP_F_IFACE : 0;

 if (saddr) {
  memcpy(&fl6.saddr, saddr, sizeof(*saddr));
  flags |= RT6_LOOKUP_F_HAS_SADDR;
 }

 dst = fib6_rule_lookup(net, &fl6, skb, flags, ip6_pol_route_lookup);
 if (dst->error == 0)
  return dst_rt6_info(dst);

 dst_release(dst);

 return NULL;
}
EXPORT_SYMBOL(rt6_lookup);

/* ip6_ins_rt is called with FREE table->tb6_lock.
 * It takes new route entry, the addition fails by any reason the
 * route is released.
 * Caller must hold dst before calling it.
 */


static int __ip6_ins_rt(struct fib6_info *rt, struct nl_info *info,
   struct netlink_ext_ack *extack)
{
 int err;
 struct fib6_table *table;

 table = rt->fib6_table;
 spin_lock_bh(&table->tb6_lock);
 err = fib6_add(&table->tb6_root, rt, info, extack);
 spin_unlock_bh(&table->tb6_lock);

 return err;
}

int ip6_ins_rt(struct net *net, struct fib6_info *rt)
{
 struct nl_info info = { .nl_net = net, };

 return __ip6_ins_rt(rt, &info, NULL);
}

static struct rt6_info *ip6_rt_cache_alloc(const struct fib6_result *res,
        const struct in6_addr *daddr,
        const struct in6_addr *saddr)
{
 struct fib6_info *f6i = res->f6i;
 struct net_device *dev;
 struct rt6_info *rt;

 /*
 * Clone the route.
 */


 if (!fib6_info_hold_safe(f6i))
  return NULL;

 dev = ip6_rt_get_dev_rcu(res);
 rt = ip6_dst_alloc(dev_net(dev), dev, 0);
 if (!rt) {
  fib6_info_release(f6i);
  return NULL;
 }

 ip6_rt_copy_init(rt, res);
 rt->rt6i_flags |= RTF_CACHE;
 rt->rt6i_dst.addr = *daddr;
 rt->rt6i_dst.plen = 128;

 if (!rt6_is_gw_or_nonexthop(res)) {
  if (f6i->fib6_dst.plen != 128 &&
      ipv6_addr_equal(&f6i->fib6_dst.addr, daddr))
   rt->rt6i_flags |= RTF_ANYCAST;
#ifdef CONFIG_IPV6_SUBTREES
  if (rt->rt6i_src.plen && saddr) {
   rt->rt6i_src.addr = *saddr;
   rt->rt6i_src.plen = 128;
  }
#endif
 }

 return rt;
}

static struct rt6_info *ip6_rt_pcpu_alloc(const struct fib6_result *res)
{
 struct fib6_info *f6i = res->f6i;
 unsigned short flags = fib6_info_dst_flags(f6i);
 struct net_device *dev;
 struct rt6_info *pcpu_rt;

 if (!fib6_info_hold_safe(f6i))
  return NULL;

 rcu_read_lock();
 dev = ip6_rt_get_dev_rcu(res);
 pcpu_rt = ip6_dst_alloc(dev_net(dev), dev, flags | DST_NOCOUNT);
 rcu_read_unlock();
 if (!pcpu_rt) {
  fib6_info_release(f6i);
  return NULL;
 }
 ip6_rt_copy_init(pcpu_rt, res);
 pcpu_rt->rt6i_flags |= RTF_PCPU;

 if (f6i->nh)
  pcpu_rt->sernum = rt_genid_ipv6(dev_net(dev));

 return pcpu_rt;
}

static bool rt6_is_valid(const struct rt6_info *rt6)
{
 return rt6->sernum == rt_genid_ipv6(dev_net(rt6->dst.dev));
}

/* It should be called with rcu_read_lock() acquired */
static struct rt6_info *rt6_get_pcpu_route(const struct fib6_result *res)
{
 struct rt6_info *pcpu_rt;

 pcpu_rt = this_cpu_read(*res->nh->rt6i_pcpu);

 if (pcpu_rt && pcpu_rt->sernum && !rt6_is_valid(pcpu_rt)) {
  struct rt6_info *prev, **p;

  p = this_cpu_ptr(res->nh->rt6i_pcpu);
  /* Paired with READ_ONCE() in __fib6_drop_pcpu_from() */
  prev = xchg(p, NULL);
  if (prev) {
   dst_dev_put(&prev->dst);
   dst_release(&prev->dst);
  }

  pcpu_rt = NULL;
 }

 return pcpu_rt;
}

static struct rt6_info *rt6_make_pcpu_route(struct net *net,
         const struct fib6_result *res)
{
 struct rt6_info *pcpu_rt, *prev, **p;

 pcpu_rt = ip6_rt_pcpu_alloc(res);
 if (!pcpu_rt)
  return NULL;

 p = this_cpu_ptr(res->nh->rt6i_pcpu);
 prev = cmpxchg(p, NULL, pcpu_rt);
 BUG_ON(prev);

 if (res->f6i->fib6_destroying) {
  struct fib6_info *from;

  from = unrcu_pointer(xchg(&pcpu_rt->from, NULL));
  fib6_info_release(from);
 }

 return pcpu_rt;
}

/* exception hash table implementation
 */

static DEFINE_SPINLOCK(rt6_exception_lock);

/* Remove rt6_ex from hash table and free the memory
 * Caller must hold rt6_exception_lock
 */

static void rt6_remove_exception(struct rt6_exception_bucket *bucket,
     struct rt6_exception *rt6_ex)
{
 struct net *net;

 if (!bucket || !rt6_ex)
  return;

 net = dev_net(rt6_ex->rt6i->dst.dev);
 net->ipv6.rt6_stats->fib_rt_cache--;

 /* purge completely the exception to allow releasing the held resources:
 * some [sk] cache may keep the dst around for unlimited time
 */

 dst_dev_put(&rt6_ex->rt6i->dst);

 hlist_del_rcu(&rt6_ex->hlist);
 dst_release(&rt6_ex->rt6i->dst);
 kfree_rcu(rt6_ex, rcu);
 WARN_ON_ONCE(!bucket->depth);
 bucket->depth--;
}

/* Remove oldest rt6_ex in bucket and free the memory
 * Caller must hold rt6_exception_lock
 */

static void rt6_exception_remove_oldest(struct rt6_exception_bucket *bucket)
{
 struct rt6_exception *rt6_ex, *oldest = NULL;

 if (!bucket)
  return;

 hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) {
  if (!oldest || time_before(rt6_ex->stamp, oldest->stamp))
   oldest = rt6_ex;
 }
 rt6_remove_exception(bucket, oldest);
}

static u32 rt6_exception_hash(const struct in6_addr *dst,
         const struct in6_addr *src)
{
 static siphash_aligned_key_t rt6_exception_key;
 struct {
  struct in6_addr dst;
  struct in6_addr src;
 } __aligned(SIPHASH_ALIGNMENT) combined = {
  .dst = *dst,
 };
 u64 val;

 net_get_random_once(&rt6_exception_key, sizeof(rt6_exception_key));

#ifdef CONFIG_IPV6_SUBTREES
 if (src)
  combined.src = *src;
#endif
 val = siphash(&combined, sizeof(combined), &rt6_exception_key);

 return hash_64(val, FIB6_EXCEPTION_BUCKET_SIZE_SHIFT);
}

/* Helper function to find the cached rt in the hash table
 * and update bucket pointer to point to the bucket for this
 * (daddr, saddr) pair
 * Caller must hold rt6_exception_lock
 */

static struct rt6_exception *
__rt6_find_exception_spinlock(struct rt6_exception_bucket **bucket,
         const struct in6_addr *daddr,
         const struct in6_addr *saddr)
{
 struct rt6_exception *rt6_ex;
 u32 hval;

 if (!(*bucket) || !daddr)
  return NULL;

 hval = rt6_exception_hash(daddr, saddr);
 *bucket += hval;

 hlist_for_each_entry(rt6_ex, &(*bucket)->chain, hlist) {
  struct rt6_info *rt6 = rt6_ex->rt6i;
  bool matched = ipv6_addr_equal(daddr, &rt6->rt6i_dst.addr);

#ifdef CONFIG_IPV6_SUBTREES
  if (matched && saddr)
   matched = ipv6_addr_equal(saddr, &rt6->rt6i_src.addr);
#endif
  if (matched)
   return rt6_ex;
 }
 return NULL;
}

/* Helper function to find the cached rt in the hash table
 * and update bucket pointer to point to the bucket for this
 * (daddr, saddr) pair
 * Caller must hold rcu_read_lock()
 */

static struct rt6_exception *
__rt6_find_exception_rcu(struct rt6_exception_bucket **bucket,
    const struct in6_addr *daddr,
    const struct in6_addr *saddr)
{
 struct rt6_exception *rt6_ex;
 u32 hval;

 WARN_ON_ONCE(!rcu_read_lock_held());

 if (!(*bucket) || !daddr)
  return NULL;

 hval = rt6_exception_hash(daddr, saddr);
 *bucket += hval;

 hlist_for_each_entry_rcu(rt6_ex, &(*bucket)->chain, hlist) {
  struct rt6_info *rt6 = rt6_ex->rt6i;
  bool matched = ipv6_addr_equal(daddr, &rt6->rt6i_dst.addr);

#ifdef CONFIG_IPV6_SUBTREES
  if (matched && saddr)
   matched = ipv6_addr_equal(saddr, &rt6->rt6i_src.addr);
#endif
  if (matched)
   return rt6_ex;
 }
 return NULL;
}

static unsigned int fib6_mtu(const struct fib6_result *res)
{
 const struct fib6_nh *nh = res->nh;
 unsigned int mtu;

 if (res->f6i->fib6_pmtu) {
  mtu = res->f6i->fib6_pmtu;
 } else {
  struct net_device *dev = nh->fib_nh_dev;
  struct inet6_dev *idev;

  rcu_read_lock();
  idev = __in6_dev_get(dev);
  mtu = READ_ONCE(idev->cnf.mtu6);
  rcu_read_unlock();
 }

 mtu = min_t(unsigned int, mtu, IP6_MAX_MTU);

 return mtu - lwtunnel_headroom(nh->fib_nh_lws, mtu);
}

#define FIB6_EXCEPTION_BUCKET_FLUSHED  0x1UL

/* used when the flushed bit is not relevant, only access to the bucket
 * (ie., all bucket users except rt6_insert_exception);
 *
 * called under rcu lock; sometimes called with rt6_exception_lock held
 */

static
struct rt6_exception_bucket *fib6_nh_get_excptn_bucket(const struct fib6_nh *nh,
             spinlock_t *lock)
{
 struct rt6_exception_bucket *bucket;

 if (lock)
  bucket = rcu_dereference_protected(nh->rt6i_exception_bucket,
         lockdep_is_held(lock));
 else
  bucket = rcu_dereference(nh->rt6i_exception_bucket);

 /* remove bucket flushed bit if set */
 if (bucket) {
  unsigned long p = (unsigned long)bucket;

  p &= ~FIB6_EXCEPTION_BUCKET_FLUSHED;
  bucket = (struct rt6_exception_bucket *)p;
 }

 return bucket;
}

static bool fib6_nh_excptn_bucket_flushed(struct rt6_exception_bucket *bucket)
{
 unsigned long p = (unsigned long)bucket;

 return !!(p & FIB6_EXCEPTION_BUCKET_FLUSHED);
}

/* called with rt6_exception_lock held */
static void fib6_nh_excptn_bucket_set_flushed(struct fib6_nh *nh,
           spinlock_t *lock)
{
 struct rt6_exception_bucket *bucket;
 unsigned long p;

 bucket = rcu_dereference_protected(nh->rt6i_exception_bucket,
        lockdep_is_held(lock));

 p = (unsigned long)bucket;
 p |= FIB6_EXCEPTION_BUCKET_FLUSHED;
 bucket = (struct rt6_exception_bucket *)p;
 rcu_assign_pointer(nh->rt6i_exception_bucket, bucket);
}

static int rt6_insert_exception(struct rt6_info *nrt,
    const struct fib6_result *res)
{
 struct net *net = dev_net(nrt->dst.dev);
 struct rt6_exception_bucket *bucket;
 struct fib6_info *f6i = res->f6i;
 struct in6_addr *src_key = NULL;
 struct rt6_exception *rt6_ex;
 struct fib6_nh *nh = res->nh;
 int max_depth;
 int err = 0;

 spin_lock_bh(&rt6_exception_lock);

 bucket = rcu_dereference_protected(nh->rt6i_exception_bucket,
       lockdep_is_held(&rt6_exception_lock));
 if (!bucket) {
  bucket = kcalloc(FIB6_EXCEPTION_BUCKET_SIZE, sizeof(*bucket),
     GFP_ATOMIC);
  if (!bucket) {
   err = -ENOMEM;
   goto out;
  }
  rcu_assign_pointer(nh->rt6i_exception_bucket, bucket);
 } else if (fib6_nh_excptn_bucket_flushed(bucket)) {
  err = -EINVAL;
  goto out;
 }

#ifdef CONFIG_IPV6_SUBTREES
 /* fib6_src.plen != 0 indicates f6i is in subtree
 * and exception table is indexed by a hash of
 * both fib6_dst and fib6_src.
 * Otherwise, the exception table is indexed by
 * a hash of only fib6_dst.
 */

 if (f6i->fib6_src.plen)
  src_key = &nrt->rt6i_src.addr;
#endif
 /* rt6_mtu_change() might lower mtu on f6i.
 * Only insert this exception route if its mtu
 * is less than f6i's mtu value.
 */

 if (dst_metric_raw(&nrt->dst, RTAX_MTU) >= fib6_mtu(res)) {
  err = -EINVAL;
  goto out;
 }

 rt6_ex = __rt6_find_exception_spinlock(&bucket, &nrt->rt6i_dst.addr,
            src_key);
 if (rt6_ex)
  rt6_remove_exception(bucket, rt6_ex);

 rt6_ex = kzalloc(sizeof(*rt6_ex), GFP_ATOMIC);
 if (!rt6_ex) {
  err = -ENOMEM;
  goto out;
 }
 rt6_ex->rt6i = nrt;
 rt6_ex->stamp = jiffies;
 hlist_add_head_rcu(&rt6_ex->hlist, &bucket->chain);
 bucket->depth++;
 net->ipv6.rt6_stats->fib_rt_cache++;

 /* Randomize max depth to avoid some side channels attacks. */
 max_depth = FIB6_MAX_DEPTH + get_random_u32_below(FIB6_MAX_DEPTH);
 while (bucket->depth > max_depth)
  rt6_exception_remove_oldest(bucket);

out:
 spin_unlock_bh(&rt6_exception_lock);

 /* Update fn->fn_sernum to invalidate all cached dst */
 if (!err) {
  spin_lock_bh(&f6i->fib6_table->tb6_lock);
  fib6_update_sernum(net, f6i);
  fib6_add_gc_list(f6i);
  spin_unlock_bh(&f6i->fib6_table->tb6_lock);
  fib6_force_start_gc(net);
 }

 return err;
}

static void fib6_nh_flush_exceptions(struct fib6_nh *nh, struct fib6_info *from)
{
 struct rt6_exception_bucket *bucket;
 struct rt6_exception *rt6_ex;
 struct hlist_node *tmp;
 int i;

 spin_lock_bh(&rt6_exception_lock);

 bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
 if (!bucket)
  goto out;

 /* Prevent rt6_insert_exception() to recreate the bucket list */
 if (!from)
  fib6_nh_excptn_bucket_set_flushed(nh, &rt6_exception_lock);

 for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
  hlist_for_each_entry_safe(rt6_ex, tmp, &bucket->chain, hlist) {
   if (!from ||
       rcu_access_pointer(rt6_ex->rt6i->from) == from)
    rt6_remove_exception(bucket, rt6_ex);
  }
  WARN_ON_ONCE(!from && bucket->depth);
  bucket++;
 }
out:
 spin_unlock_bh(&rt6_exception_lock);
}

static int rt6_nh_flush_exceptions(struct fib6_nh *nh, void *arg)
{
 struct fib6_info *f6i = arg;

 fib6_nh_flush_exceptions(nh, f6i);

 return 0;
}

void rt6_flush_exceptions(struct fib6_info *f6i)
{
 if (f6i->nh) {
  rcu_read_lock();
  nexthop_for_each_fib6_nh(f6i->nh, rt6_nh_flush_exceptions, f6i);
  rcu_read_unlock();
 } else {
  fib6_nh_flush_exceptions(f6i->fib6_nh, f6i);
 }
}

/* Find cached rt in the hash table inside passed in rt
 * Caller has to hold rcu_read_lock()
 */

static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,
        const struct in6_addr *daddr,
        const struct in6_addr *saddr)
{
 const struct in6_addr *src_key = NULL;
 struct rt6_exception_bucket *bucket;
 struct rt6_exception *rt6_ex;
 struct rt6_info *ret = NULL;

#ifdef CONFIG_IPV6_SUBTREES
 /* fib6i_src.plen != 0 indicates f6i is in subtree
 * and exception table is indexed by a hash of
 * both fib6_dst and fib6_src.
 * However, the src addr used to create the hash
 * might not be exactly the passed in saddr which
 * is a /128 addr from the flow.
 * So we need to use f6i->fib6_src to redo lookup
 * if the passed in saddr does not find anything.
 * (See the logic in ip6_rt_cache_alloc() on how
 * rt->rt6i_src is updated.)
 */

 if (res->f6i->fib6_src.plen)
  src_key = saddr;
find_ex:
#endif
 bucket = fib6_nh_get_excptn_bucket(res->nh, NULL);
 rt6_ex = __rt6_find_exception_rcu(&bucket, daddr, src_key);

 if (rt6_ex && !rt6_check_expired(rt6_ex->rt6i))
  ret = rt6_ex->rt6i;

#ifdef CONFIG_IPV6_SUBTREES
 /* Use fib6_src as src_key and redo lookup */
 if (!ret && src_key && src_key != &res->f6i->fib6_src.addr) {
  src_key = &res->f6i->fib6_src.addr;
  goto find_ex;
 }
#endif

 return ret;
}

/* Remove the passed in cached rt from the hash table that contains it */
static int fib6_nh_remove_exception(const struct fib6_nh *nh, int plen,
        const struct rt6_info *rt)
{
 const struct in6_addr *src_key = NULL;
 struct rt6_exception_bucket *bucket;
 struct rt6_exception *rt6_ex;
 int err;

 if (!rcu_access_pointer(nh->rt6i_exception_bucket))
  return -ENOENT;

 spin_lock_bh(&rt6_exception_lock);
 bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);

#ifdef CONFIG_IPV6_SUBTREES
 /* rt6i_src.plen != 0 indicates 'from' is in subtree
 * and exception table is indexed by a hash of
 * both rt6i_dst and rt6i_src.
 * Otherwise, the exception table is indexed by
 * a hash of only rt6i_dst.
 */

 if (plen)
  src_key = &rt->rt6i_src.addr;
#endif
 rt6_ex = __rt6_find_exception_spinlock(&bucket,
            &rt->rt6i_dst.addr,
            src_key);
 if (rt6_ex) {
  rt6_remove_exception(bucket, rt6_ex);
  err = 0;
 } else {
  err = -ENOENT;
 }

 spin_unlock_bh(&rt6_exception_lock);
 return err;
}

struct fib6_nh_excptn_arg {
 struct rt6_info *rt;
 int  plen;
};

static int rt6_nh_remove_exception_rt(struct fib6_nh *nh, void *_arg)
{
 struct fib6_nh_excptn_arg *arg = _arg;
 int err;

 err = fib6_nh_remove_exception(nh, arg->plen, arg->rt);
 if (err == 0)
  return 1;

 return 0;
}

static int rt6_remove_exception_rt(struct rt6_info *rt)
{
 struct fib6_info *from;

 from = rcu_dereference(rt->from);
 if (!from || !(rt->rt6i_flags & RTF_CACHE))
  return -EINVAL;

 if (from->nh) {
  struct fib6_nh_excptn_arg arg = {
   .rt = rt,
   .plen = from->fib6_src.plen
  };
  int rc;

  /* rc = 1 means an entry was found */
  rc = nexthop_for_each_fib6_nh(from->nh,
           rt6_nh_remove_exception_rt,
           &arg);
  return rc ? 0 : -ENOENT;
 }

 return fib6_nh_remove_exception(from->fib6_nh,
     from->fib6_src.plen, rt);
}

/* Find rt6_ex which contains the passed in rt cache and
 * refresh its stamp
 */

static void fib6_nh_update_exception(const struct fib6_nh *nh, int plen,
         const struct rt6_info *rt)
{
 const struct in6_addr *src_key = NULL;
 struct rt6_exception_bucket *bucket;
 struct rt6_exception *rt6_ex;

 bucket = fib6_nh_get_excptn_bucket(nh, NULL);
#ifdef CONFIG_IPV6_SUBTREES
 /* rt6i_src.plen != 0 indicates 'from' is in subtree
 * and exception table is indexed by a hash of
 * both rt6i_dst and rt6i_src.
 * Otherwise, the exception table is indexed by
 * a hash of only rt6i_dst.
 */

 if (plen)
  src_key = &rt->rt6i_src.addr;
#endif
 rt6_ex = __rt6_find_exception_rcu(&bucket, &rt->rt6i_dst.addr, src_key);
 if (rt6_ex)
  rt6_ex->stamp = jiffies;
}

struct fib6_nh_match_arg {
 const struct net_device *dev;
 const struct in6_addr *gw;
 struct fib6_nh  *match;
};

/* determine if fib6_nh has given device and gateway */
static int fib6_nh_find_match(struct fib6_nh *nh, void *_arg)
{
 struct fib6_nh_match_arg *arg = _arg;

 if (arg->dev != nh->fib_nh_dev ||
     (arg->gw && !nh->fib_nh_gw_family) ||
     (!arg->gw && nh->fib_nh_gw_family) ||
     (arg->gw && !ipv6_addr_equal(arg->gw, &nh->fib_nh_gw6)))
  return 0;

 arg->match = nh;

 /* found a match, break the loop */
 return 1;
}

static void rt6_update_exception_stamp_rt(struct rt6_info *rt)
{
 struct fib6_info *from;
 struct fib6_nh *fib6_nh;

 rcu_read_lock();

 from = rcu_dereference(rt->from);
 if (!from || !(rt->rt6i_flags & RTF_CACHE))
  goto unlock;

 if (from->nh) {
  struct fib6_nh_match_arg arg = {
   .dev = rt->dst.dev,
   .gw = &rt->rt6i_gateway,
  };

  nexthop_for_each_fib6_nh(from->nh, fib6_nh_find_match, &arg);

  if (!arg.match)
   goto unlock;
  fib6_nh = arg.match;
 } else {
  fib6_nh = from->fib6_nh;
 }
 fib6_nh_update_exception(fib6_nh, from->fib6_src.plen, rt);
unlock:
 rcu_read_unlock();
}

static bool rt6_mtu_change_route_allowed(struct inet6_dev *idev,
      struct rt6_info *rt, int mtu)
{
 /* If the new MTU is lower than the route PMTU, this new MTU will be the
 * lowest MTU in the path: always allow updating the route PMTU to
 * reflect PMTU decreases.
 *
 * If the new MTU is higher, and the route PMTU is equal to the local
 * MTU, this means the old MTU is the lowest in the path, so allow
 * updating it: if other nodes now have lower MTUs, PMTU discovery will
 * handle this.
 */


 if (dst_mtu(&rt->dst) >= mtu)
  return true;

 if (dst_mtu(&rt->dst) == idev->cnf.mtu6)
  return true;

 return false;
}

static void rt6_exceptions_update_pmtu(struct inet6_dev *idev,
           const struct fib6_nh *nh, int mtu)
{
 struct rt6_exception_bucket *bucket;
 struct rt6_exception *rt6_ex;
 int i;

 bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
 if (!bucket)
  return;

 for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
  hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) {
   struct rt6_info *entry = rt6_ex->rt6i;

   /* For RTF_CACHE with rt6i_pmtu == 0 (i.e. a redirected
 * route), the metrics of its rt->from have already
 * been updated.
 */

   if (dst_metric_raw(&entry->dst, RTAX_MTU) &&
       rt6_mtu_change_route_allowed(idev, entry, mtu))
    dst_metric_set(&entry->dst, RTAX_MTU, mtu);
  }
  bucket++;
 }
}

#define RTF_CACHE_GATEWAY (RTF_GATEWAY | RTF_CACHE)

static void fib6_nh_exceptions_clean_tohost(const struct fib6_nh *nh,
         const struct in6_addr *gateway)
{
 struct rt6_exception_bucket *bucket;
 struct rt6_exception *rt6_ex;
 struct hlist_node *tmp;
 int i;

 if (!rcu_access_pointer(nh->rt6i_exception_bucket))
  return;

 spin_lock_bh(&rt6_exception_lock);
 bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
 if (bucket) {
  for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
   hlist_for_each_entry_safe(rt6_ex, tmp,
        &bucket->chain, hlist) {
    struct rt6_info *entry = rt6_ex->rt6i;

    if ((entry->rt6i_flags & RTF_CACHE_GATEWAY) ==
        RTF_CACHE_GATEWAY &&
        ipv6_addr_equal(gateway,
          &entry->rt6i_gateway)) {
     rt6_remove_exception(bucket, rt6_ex);
    }
   }
   bucket++;
  }
 }

 spin_unlock_bh(&rt6_exception_lock);
}

static void rt6_age_examine_exception(struct rt6_exception_bucket *bucket,
          struct rt6_exception *rt6_ex,
          struct fib6_gc_args *gc_args,
          unsigned long now)
{
 struct rt6_info *rt = rt6_ex->rt6i;

 /* we are pruning and obsoleting aged-out and non gateway exceptions
 * even if others have still references to them, so that on next
 * dst_check() such references can be dropped.
 * EXPIRES exceptions - e.g. pmtu-generated ones are pruned when
 * expired, independently from their aging, as per RFC 8201 section 4
 */

 if (!(rt->rt6i_flags & RTF_EXPIRES)) {
  if (time_after_eq(now, READ_ONCE(rt->dst.lastuse) +
           gc_args->timeout)) {
   pr_debug("aging clone %p\n", rt);
   rt6_remove_exception(bucket, rt6_ex);
   return;
  }
 } else if (time_after(jiffies, READ_ONCE(rt->dst.expires))) {
  pr_debug("purging expired route %p\n", rt);
  rt6_remove_exception(bucket, rt6_ex);
  return;
 }

 if (rt->rt6i_flags & RTF_GATEWAY) {
  struct neighbour *neigh;

  neigh = __ipv6_neigh_lookup_noref(rt->dst.dev, &rt->rt6i_gateway);

  if (!(neigh && (neigh->flags & NTF_ROUTER))) {
   pr_debug("purging route %p via non-router but gateway\n",
     rt);
   rt6_remove_exception(bucket, rt6_ex);
   return;
  }
 }

 gc_args->more++;
}

static void fib6_nh_age_exceptions(const struct fib6_nh *nh,
       struct fib6_gc_args *gc_args,
       unsigned long now)
{
 struct rt6_exception_bucket *bucket;
 struct rt6_exception *rt6_ex;
 struct hlist_node *tmp;
 int i;

 if (!rcu_access_pointer(nh->rt6i_exception_bucket))
  return;

 rcu_read_lock_bh();
 spin_lock(&rt6_exception_lock);
 bucket = fib6_nh_get_excptn_bucket(nh, &rt6_exception_lock);
 if (bucket) {
  for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) {
   hlist_for_each_entry_safe(rt6_ex, tmp,
        &bucket->chain, hlist) {
    rt6_age_examine_exception(bucket, rt6_ex,
         gc_args, now);
   }
   bucket++;
  }
 }
 spin_unlock(&rt6_exception_lock);
 rcu_read_unlock_bh();
}

struct fib6_nh_age_excptn_arg {
 struct fib6_gc_args *gc_args;
 unsigned long  now;
};

static int rt6_nh_age_exceptions(struct fib6_nh *nh, void *_arg)
{
 struct fib6_nh_age_excptn_arg *arg = _arg;

 fib6_nh_age_exceptions(nh, arg->gc_args, arg->now);
 return 0;
}

void rt6_age_exceptions(struct fib6_info *f6i,
   struct fib6_gc_args *gc_args,
   unsigned long now)
{
 if (f6i->nh) {
  struct fib6_nh_age_excptn_arg arg = {
   .gc_args = gc_args,
   .now = now
  };

  nexthop_for_each_fib6_nh(f6i->nh, rt6_nh_age_exceptions,
      &arg);
 } else {
  fib6_nh_age_exceptions(f6i->fib6_nh, gc_args, now);
 }
}

/* must be called with rcu lock held */
int fib6_table_lookup(struct net *net, struct fib6_table *table, int oif,
        struct flowi6 *fl6, struct fib6_result *res, int strict)
{
 struct fib6_node *fn, *saved_fn;

 fn = fib6_node_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
 saved_fn = fn;

redo_rt6_select:
 rt6_select(net, fn, oif, res, strict);
 if (res->f6i == net->ipv6.fib6_null_entry) {
  fn = fib6_backtrack(fn, &fl6->saddr);
  if (fn)
   goto redo_rt6_select;
  else if (strict & RT6_LOOKUP_F_REACHABLE) {
   /* also consider unreachable route */
   strict &= ~RT6_LOOKUP_F_REACHABLE;
   fn = saved_fn;
   goto redo_rt6_select;
  }
 }

 trace_fib6_table_lookup(net, res, table, fl6);

 return 0;
}

struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
          int oif, struct flowi6 *fl6,
          const struct sk_buff *skb, int flags)
{
 struct fib6_result res = {};
 struct rt6_info *rt = NULL;
 int strict = 0;

 WARN_ON_ONCE((flags & RT6_LOOKUP_F_DST_NOREF) &&
       !rcu_read_lock_held());

 strict |= flags & RT6_LOOKUP_F_IFACE;
 strict |= flags & RT6_LOOKUP_F_IGNORE_LINKSTATE;
 if (READ_ONCE(net->ipv6.devconf_all->forwarding) == 0)
  strict |= RT6_LOOKUP_F_REACHABLE;

 rcu_read_lock();

 fib6_table_lookup(net, table, oif, fl6, &res, strict);
 if (res.f6i == net->ipv6.fib6_null_entry)
  goto out;

 fib6_select_path(net, &res, fl6, oif, false, skb, strict);

 /*Search through exception table */
 rt = rt6_find_cached_rt(&res, &fl6->daddr, &fl6->saddr);
 if (rt) {
  goto out;
 } else if (unlikely((fl6->flowi6_flags & FLOWI_FLAG_KNOWN_NH) &&
       !res.nh->fib_nh_gw_family)) {
  /* Create a RTF_CACHE clone which will not be
 * owned by the fib6 tree.  It is for the special case where
 * the daddr in the skb during the neighbor look-up is different
 * from the fl6->daddr used to look-up route here.
 */

  rt = ip6_rt_cache_alloc(&res, &fl6->daddr, NULL);

  if (rt) {
   /* 1 refcnt is taken during ip6_rt_cache_alloc().
 * As rt6_uncached_list_add() does not consume refcnt,
 * this refcnt is always returned to the caller even
 * if caller sets RT6_LOOKUP_F_DST_NOREF flag.
 */

   rt6_uncached_list_add(rt);
   rcu_read_unlock();

   return rt;
  }
 } else {
  /* Get a percpu copy */
  local_bh_disable();
  rt = rt6_get_pcpu_route(&res);

  if (!rt)
   rt = rt6_make_pcpu_route(net, &res);

  local_bh_enable();
 }
out:
 if (!rt)
  rt = net->ipv6.ip6_null_entry;
 if (!(flags & RT6_LOOKUP_F_DST_NOREF))
  ip6_hold_safe(net, &rt);
 rcu_read_unlock();

 return rt;
}
EXPORT_SYMBOL_GPL(ip6_pol_route);

INDIRECT_CALLABLE_SCOPE struct rt6_info *ip6_pol_route_input(struct net *net,
         struct fib6_table *table,
         struct flowi6 *fl6,
         const struct sk_buff *skb,
         int flags)
{
 return ip6_pol_route(net, table, fl6->flowi6_iif, fl6, skb, flags);
}

struct dst_entry *ip6_route_input_lookup(struct net *net,
      struct net_device *dev,
      struct flowi6 *fl6,
      const struct sk_buff *skb,
      int flags)
{
 if (rt6_need_strict(&fl6->daddr) && dev->type != ARPHRD_PIMREG)
  flags |= RT6_LOOKUP_F_IFACE;

 return fib6_rule_lookup(net, fl6, skb, flags, ip6_pol_route_input);
}
EXPORT_SYMBOL_GPL(ip6_route_input_lookup);

static void ip6_multipath_l3_keys(const struct sk_buff *skb,
      struct flow_keys *keys,
      struct flow_keys *flkeys)
{
 const struct ipv6hdr *outer_iph = ipv6_hdr(skb);
 const struct ipv6hdr *key_iph = outer_iph;
 struct flow_keys *_flkeys = flkeys;
 const struct ipv6hdr *inner_iph;
 const struct icmp6hdr *icmph;
 struct ipv6hdr _inner_iph;
 struct icmp6hdr _icmph;

 if (likely(outer_iph->nexthdr != IPPROTO_ICMPV6))
  goto out;

 icmph = skb_header_pointer(skb, skb_transport_offset(skb),
       sizeof(_icmph), &_icmph);
 if (!icmph)
  goto out;

 if (!icmpv6_is_err(icmph->icmp6_type))
  goto out;

 inner_iph = skb_header_pointer(skb,
           skb_transport_offset(skb) + sizeof(*icmph),
           sizeof(_inner_iph), &_inner_iph);
 if (!inner_iph)
  goto out;

 key_iph = inner_iph;
 _flkeys = NULL;
out:
 if (_flkeys) {
  keys->addrs.v6addrs.src = _flkeys->addrs.v6addrs.src;
  keys->addrs.v6addrs.dst = _flkeys->addrs.v6addrs.dst;
  keys->tags.flow_label = _flkeys->tags.flow_label;
  keys->basic.ip_proto = _flkeys->basic.ip_proto;
 } else {
  keys->addrs.v6addrs.src = key_iph->saddr;
  keys->addrs.v6addrs.dst = key_iph->daddr;
  keys->tags.flow_label = ip6_flowlabel(key_iph);
  keys->basic.ip_proto = key_iph->nexthdr;
 }
}

static u32 rt6_multipath_custom_hash_outer(const struct net *net,
        const struct sk_buff *skb,
        bool *p_has_inner)
{
 u32 hash_fields = ip6_multipath_hash_fields(net);
 struct flow_keys keys, hash_keys;

 if (!(hash_fields & FIB_MULTIPATH_HASH_FIELD_OUTER_MASK))
  return 0;

 memset(&hash_keys, 0, sizeof(hash_keys));
 skb_flow_dissect_flow_keys(skb, &keys, FLOW_DISSECTOR_F_STOP_AT_ENCAP);

 hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_IP)
  hash_keys.addrs.v6addrs.src = keys.addrs.v6addrs.src;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_IP)
  hash_keys.addrs.v6addrs.dst = keys.addrs.v6addrs.dst;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_IP_PROTO)
  hash_keys.basic.ip_proto = keys.basic.ip_proto;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_FLOWLABEL)
  hash_keys.tags.flow_label = keys.tags.flow_label;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_PORT)
  hash_keys.ports.src = keys.ports.src;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_PORT)
  hash_keys.ports.dst = keys.ports.dst;

 *p_has_inner = !!(keys.control.flags & FLOW_DIS_ENCAPSULATION);
 return fib_multipath_hash_from_keys(net, &hash_keys);
}

static u32 rt6_multipath_custom_hash_inner(const struct net *net,
        const struct sk_buff *skb,
        bool has_inner)
{
 u32 hash_fields = ip6_multipath_hash_fields(net);
 struct flow_keys keys, hash_keys;

 /* We assume the packet carries an encapsulation, but if none was
 * encountered during dissection of the outer flow, then there is no
 * point in calling the flow dissector again.
 */

 if (!has_inner)
  return 0;

 if (!(hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_MASK))
  return 0;

 memset(&hash_keys, 0, sizeof(hash_keys));
 skb_flow_dissect_flow_keys(skb, &keys, 0);

 if (!(keys.control.flags & FLOW_DIS_ENCAPSULATION))
  return 0;

 if (keys.control.addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
  hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
  if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_SRC_IP)
   hash_keys.addrs.v4addrs.src = keys.addrs.v4addrs.src;
  if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_DST_IP)
   hash_keys.addrs.v4addrs.dst = keys.addrs.v4addrs.dst;
 } else if (keys.control.addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
  hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
  if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_SRC_IP)
   hash_keys.addrs.v6addrs.src = keys.addrs.v6addrs.src;
  if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_DST_IP)
   hash_keys.addrs.v6addrs.dst = keys.addrs.v6addrs.dst;
  if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_FLOWLABEL)
   hash_keys.tags.flow_label = keys.tags.flow_label;
 }

 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_IP_PROTO)
  hash_keys.basic.ip_proto = keys.basic.ip_proto;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_SRC_PORT)
  hash_keys.ports.src = keys.ports.src;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_INNER_DST_PORT)
  hash_keys.ports.dst = keys.ports.dst;

 return fib_multipath_hash_from_keys(net, &hash_keys);
}

static u32 rt6_multipath_custom_hash_skb(const struct net *net,
      const struct sk_buff *skb)
{
 u32 mhash, mhash_inner;
 bool has_inner = true;

 mhash = rt6_multipath_custom_hash_outer(net, skb, &has_inner);
 mhash_inner = rt6_multipath_custom_hash_inner(net, skb, has_inner);

 return jhash_2words(mhash, mhash_inner, 0);
}

static u32 rt6_multipath_custom_hash_fl6(const struct net *net,
      const struct flowi6 *fl6)
{
 u32 hash_fields = ip6_multipath_hash_fields(net);
 struct flow_keys hash_keys;

 if (!(hash_fields & FIB_MULTIPATH_HASH_FIELD_OUTER_MASK))
  return 0;

 memset(&hash_keys, 0, sizeof(hash_keys));
 hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_IP)
  hash_keys.addrs.v6addrs.src = fl6->saddr;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_IP)
  hash_keys.addrs.v6addrs.dst = fl6->daddr;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_IP_PROTO)
  hash_keys.basic.ip_proto = fl6->flowi6_proto;
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_FLOWLABEL)
  hash_keys.tags.flow_label = (__force u32)flowi6_get_flowlabel(fl6);
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_SRC_PORT) {
  if (fl6->flowi6_flags & FLOWI_FLAG_ANY_SPORT)
   hash_keys.ports.src = (__force __be16)get_random_u16();
  else
   hash_keys.ports.src = fl6->fl6_sport;
 }
 if (hash_fields & FIB_MULTIPATH_HASH_FIELD_DST_PORT)
  hash_keys.ports.dst = fl6->fl6_dport;

 return fib_multipath_hash_from_keys(net, &hash_keys);
}

/* if skb is set it will be used and fl6 can be NULL */
u32 rt6_multipath_hash(const struct net *net, const struct flowi6 *fl6,
         const struct sk_buff *skb, struct flow_keys *flkeys)
{
 struct flow_keys hash_keys;
 u32 mhash = 0;

 switch (ip6_multipath_hash_policy(net)) {
 case 0:
  memset(&hash_keys, 0, sizeof(hash_keys));
  hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
  if (skb) {
   ip6_multipath_l3_keys(skb, &hash_keys, flkeys);
  } else {
   hash_keys.addrs.v6addrs.src = fl6->saddr;
   hash_keys.addrs.v6addrs.dst = fl6->daddr;
   hash_keys.tags.flow_label = (__force u32)flowi6_get_flowlabel(fl6);
   hash_keys.basic.ip_proto = fl6->flowi6_proto;
  }
  mhash = fib_multipath_hash_from_keys(net, &hash_keys);
  break;
 case 1:
  if (skb) {
   unsigned int flag = FLOW_DISSECTOR_F_STOP_AT_ENCAP;
   struct flow_keys keys;

   /* short-circuit if we already have L4 hash present */
   if (skb->l4_hash)
    return skb_get_hash_raw(skb) >> 1;

   memset(&hash_keys, 0, sizeof(hash_keys));

   if (!flkeys) {
    skb_flow_dissect_flow_keys(skb, &keys, flag);
    flkeys = &keys;
   }
   hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
   hash_keys.addrs.v6addrs.src = flkeys->addrs.v6addrs.src;
   hash_keys.addrs.v6addrs.dst = flkeys->addrs.v6addrs.dst;
   hash_keys.ports.src = flkeys->ports.src;
   hash_keys.ports.dst = flkeys->ports.dst;
   hash_keys.basic.ip_proto = flkeys->basic.ip_proto;
  } else {
   memset(&hash_keys, 0, sizeof(hash_keys));
   hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
   hash_keys.addrs.v6addrs.src = fl6->saddr;
   hash_keys.addrs.v6addrs.dst = fl6->daddr;
   if (fl6->flowi6_flags & FLOWI_FLAG_ANY_SPORT)
    hash_keys.ports.src = (__force __be16)get_random_u16();
   else
    hash_keys.ports.src = fl6->fl6_sport;
   hash_keys.ports.dst = fl6->fl6_dport;
   hash_keys.basic.ip_proto = fl6->flowi6_proto;
  }
  mhash = fib_multipath_hash_from_keys(net, &hash_keys);
  break;
 case 2:
  memset(&hash_keys, 0, sizeof(hash_keys));
  hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
  if (skb) {
   struct flow_keys keys;

   if (!flkeys) {
    skb_flow_dissect_flow_keys(skb, &keys, 0);
    flkeys = &keys;
   }

   /* Inner can be v4 or v6 */
   if (flkeys->control.addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
    hash_keys.control.addr_type = FLOW_DISSECTOR_KEY_IPV4_ADDRS;
    hash_keys.addrs.v4addrs.src = flkeys->addrs.v4addrs.src;
    hash_keys.addrs.v4addrs.dst = flkeys->addrs.v4addrs.dst;
   } else if (flkeys->control.addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=93 G=93

¤ Dauer der Verarbeitung: 0.26 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.