Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  nexthop.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/* Generic nexthop implementation
 *
 * Copyright (c) 2017-19 Cumulus Networks
 * Copyright (c) 2017-19 David Ahern <dsa@cumulusnetworks.com>
 */


#include <linux/nexthop.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <net/arp.h>
#include <net/ipv6_stubs.h>
#include <net/lwtunnel.h>
#include <net/ndisc.h>
#include <net/nexthop.h>
#include <net/route.h>
#include <net/sock.h>

#define NH_RES_DEFAULT_IDLE_TIMER (120 * HZ)
#define NH_RES_DEFAULT_UNBALANCED_TIMER 0 /* No forced rebalancing. */

static void remove_nexthop(struct net *net, struct nexthop *nh,
      struct nl_info *nlinfo);

#define NH_DEV_HASHBITS  8
#define NH_DEV_HASHSIZE (1U << NH_DEV_HASHBITS)

#define NHA_OP_FLAGS_DUMP_ALL (NHA_OP_FLAG_DUMP_STATS |  \
          NHA_OP_FLAG_DUMP_HW_STATS)

static const struct nla_policy rtm_nh_policy_new[] = {
 [NHA_ID]  = { .type = NLA_U32 },
 [NHA_GROUP]  = { .type = NLA_BINARY },
 [NHA_GROUP_TYPE] = { .type = NLA_U16 },
 [NHA_BLACKHOLE]  = { .type = NLA_FLAG },
 [NHA_OIF]  = { .type = NLA_U32 },
 [NHA_GATEWAY]  = { .type = NLA_BINARY },
 [NHA_ENCAP_TYPE] = { .type = NLA_U16 },
 [NHA_ENCAP]  = { .type = NLA_NESTED },
 [NHA_FDB]  = { .type = NLA_FLAG },
 [NHA_RES_GROUP]  = { .type = NLA_NESTED },
 [NHA_HW_STATS_ENABLE] = NLA_POLICY_MAX(NLA_U32, true),
};

static const struct nla_policy rtm_nh_policy_get[] = {
 [NHA_ID]  = { .type = NLA_U32 },
 [NHA_OP_FLAGS]  = NLA_POLICY_MASK(NLA_U32,
        NHA_OP_FLAGS_DUMP_ALL),
};

static const struct nla_policy rtm_nh_policy_del[] = {
 [NHA_ID]  = { .type = NLA_U32 },
};

static const struct nla_policy rtm_nh_policy_dump[] = {
 [NHA_OIF]  = { .type = NLA_U32 },
 [NHA_GROUPS]  = { .type = NLA_FLAG },
 [NHA_MASTER]  = { .type = NLA_U32 },
 [NHA_FDB]  = { .type = NLA_FLAG },
 [NHA_OP_FLAGS]  = NLA_POLICY_MASK(NLA_U32,
        NHA_OP_FLAGS_DUMP_ALL),
};

static const struct nla_policy rtm_nh_res_policy_new[] = {
 [NHA_RES_GROUP_BUCKETS]   = { .type = NLA_U16 },
 [NHA_RES_GROUP_IDLE_TIMER]  = { .type = NLA_U32 },
 [NHA_RES_GROUP_UNBALANCED_TIMER] = { .type = NLA_U32 },
};

static const struct nla_policy rtm_nh_policy_dump_bucket[] = {
 [NHA_ID]  = { .type = NLA_U32 },
 [NHA_OIF]  = { .type = NLA_U32 },
 [NHA_MASTER]  = { .type = NLA_U32 },
 [NHA_RES_BUCKET] = { .type = NLA_NESTED },
};

static const struct nla_policy rtm_nh_res_bucket_policy_dump[] = {
 [NHA_RES_BUCKET_NH_ID] = { .type = NLA_U32 },
};

static const struct nla_policy rtm_nh_policy_get_bucket[] = {
 [NHA_ID]  = { .type = NLA_U32 },
 [NHA_RES_BUCKET] = { .type = NLA_NESTED },
};

static const struct nla_policy rtm_nh_res_bucket_policy_get[] = {
 [NHA_RES_BUCKET_INDEX] = { .type = NLA_U16 },
};

static bool nexthop_notifiers_is_empty(struct net *net)
{
 return !net->nexthop.notifier_chain.head;
}

static void
__nh_notifier_single_info_init(struct nh_notifier_single_info *nh_info,
          const struct nh_info *nhi)
{
 nh_info->dev = nhi->fib_nhc.nhc_dev;
 nh_info->gw_family = nhi->fib_nhc.nhc_gw_family;
 if (nh_info->gw_family == AF_INET)
  nh_info->ipv4 = nhi->fib_nhc.nhc_gw.ipv4;
 else if (nh_info->gw_family == AF_INET6)
  nh_info->ipv6 = nhi->fib_nhc.nhc_gw.ipv6;

 nh_info->id = nhi->nh_parent->id;
 nh_info->is_reject = nhi->reject_nh;
 nh_info->is_fdb = nhi->fdb_nh;
 nh_info->has_encap = !!nhi->fib_nhc.nhc_lwtstate;
}

static int nh_notifier_single_info_init(struct nh_notifier_info *info,
     const struct nexthop *nh)
{
 struct nh_info *nhi = rtnl_dereference(nh->nh_info);

 info->type = NH_NOTIFIER_INFO_TYPE_SINGLE;
 info->nh = kzalloc(sizeof(*info->nh), GFP_KERNEL);
 if (!info->nh)
  return -ENOMEM;

 __nh_notifier_single_info_init(info->nh, nhi);

 return 0;
}

static void nh_notifier_single_info_fini(struct nh_notifier_info *info)
{
 kfree(info->nh);
}

static int nh_notifier_mpath_info_init(struct nh_notifier_info *info,
           struct nh_group *nhg)
{
 u16 num_nh = nhg->num_nh;
 int i;

 info->type = NH_NOTIFIER_INFO_TYPE_GRP;
 info->nh_grp = kzalloc(struct_size(info->nh_grp, nh_entries, num_nh),
          GFP_KERNEL);
 if (!info->nh_grp)
  return -ENOMEM;

 info->nh_grp->num_nh = num_nh;
 info->nh_grp->is_fdb = nhg->fdb_nh;
 info->nh_grp->hw_stats = nhg->hw_stats;

 for (i = 0; i < num_nh; i++) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];
  struct nh_info *nhi;

  nhi = rtnl_dereference(nhge->nh->nh_info);
  info->nh_grp->nh_entries[i].weight = nhge->weight;
  __nh_notifier_single_info_init(&info->nh_grp->nh_entries[i].nh,
            nhi);
 }

 return 0;
}

static int nh_notifier_res_table_info_init(struct nh_notifier_info *info,
        struct nh_group *nhg)
{
 struct nh_res_table *res_table = rtnl_dereference(nhg->res_table);
 u16 num_nh_buckets = res_table->num_nh_buckets;
 unsigned long size;
 u16 i;

 info->type = NH_NOTIFIER_INFO_TYPE_RES_TABLE;
 size = struct_size(info->nh_res_table, nhs, num_nh_buckets);
 info->nh_res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO |
           __GFP_NOWARN);
 if (!info->nh_res_table)
  return -ENOMEM;

 info->nh_res_table->num_nh_buckets = num_nh_buckets;
 info->nh_res_table->hw_stats = nhg->hw_stats;

 for (i = 0; i < num_nh_buckets; i++) {
  struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
  struct nh_grp_entry *nhge;
  struct nh_info *nhi;

  nhge = rtnl_dereference(bucket->nh_entry);
  nhi = rtnl_dereference(nhge->nh->nh_info);
  __nh_notifier_single_info_init(&info->nh_res_table->nhs[i],
            nhi);
 }

 return 0;
}

static int nh_notifier_grp_info_init(struct nh_notifier_info *info,
         const struct nexthop *nh)
{
 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);

 if (nhg->hash_threshold)
  return nh_notifier_mpath_info_init(info, nhg);
 else if (nhg->resilient)
  return nh_notifier_res_table_info_init(info, nhg);
 return -EINVAL;
}

static void nh_notifier_grp_info_fini(struct nh_notifier_info *info,
          const struct nexthop *nh)
{
 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);

 if (nhg->hash_threshold)
  kfree(info->nh_grp);
 else if (nhg->resilient)
  vfree(info->nh_res_table);
}

static int nh_notifier_info_init(struct nh_notifier_info *info,
     const struct nexthop *nh)
{
 info->id = nh->id;

 if (nh->is_group)
  return nh_notifier_grp_info_init(info, nh);
 else
  return nh_notifier_single_info_init(info, nh);
}

static void nh_notifier_info_fini(struct nh_notifier_info *info,
      const struct nexthop *nh)
{
 if (nh->is_group)
  nh_notifier_grp_info_fini(info, nh);
 else
  nh_notifier_single_info_fini(info);
}

static int call_nexthop_notifiers(struct net *net,
      enum nexthop_event_type event_type,
      struct nexthop *nh,
      struct netlink_ext_ack *extack)
{
 struct nh_notifier_info info = {
  .net = net,
  .extack = extack,
 };
 int err;

 ASSERT_RTNL();

 if (nexthop_notifiers_is_empty(net))
  return 0;

 err = nh_notifier_info_init(&info, nh);
 if (err) {
  NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info");
  return err;
 }

 err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
        event_type, &info);
 nh_notifier_info_fini(&info, nh);

 return notifier_to_errno(err);
}

static int
nh_notifier_res_bucket_idle_timer_get(const struct nh_notifier_info *info,
          bool force, unsigned int *p_idle_timer_ms)
{
 struct nh_res_table *res_table;
 struct nh_group *nhg;
 struct nexthop *nh;
 int err = 0;

 /* When 'force' is false, nexthop bucket replacement is performed
 * because the bucket was deemed to be idle. In this case, capable
 * listeners can choose to perform an atomic replacement: The bucket is
 * only replaced if it is inactive. However, if the idle timer interval
 * is smaller than the interval in which a listener is querying
 * buckets' activity from the device, then atomic replacement should
 * not be tried. Pass the idle timer value to listeners, so that they
 * could determine which type of replacement to perform.
 */

 if (force) {
  *p_idle_timer_ms = 0;
  return 0;
 }

 rcu_read_lock();

 nh = nexthop_find_by_id(info->net, info->id);
 if (!nh) {
  err = -EINVAL;
  goto out;
 }

 nhg = rcu_dereference(nh->nh_grp);
 res_table = rcu_dereference(nhg->res_table);
 *p_idle_timer_ms = jiffies_to_msecs(res_table->idle_timer);

out:
 rcu_read_unlock();

 return err;
}

static int nh_notifier_res_bucket_info_init(struct nh_notifier_info *info,
         u16 bucket_index, bool force,
         struct nh_info *oldi,
         struct nh_info *newi)
{
 unsigned int idle_timer_ms;
 int err;

 err = nh_notifier_res_bucket_idle_timer_get(info, force,
          &idle_timer_ms);
 if (err)
  return err;

 info->type = NH_NOTIFIER_INFO_TYPE_RES_BUCKET;
 info->nh_res_bucket = kzalloc(sizeof(*info->nh_res_bucket),
          GFP_KERNEL);
 if (!info->nh_res_bucket)
  return -ENOMEM;

 info->nh_res_bucket->bucket_index = bucket_index;
 info->nh_res_bucket->idle_timer_ms = idle_timer_ms;
 info->nh_res_bucket->force = force;
 __nh_notifier_single_info_init(&info->nh_res_bucket->old_nh, oldi);
 __nh_notifier_single_info_init(&info->nh_res_bucket->new_nh, newi);
 return 0;
}

static void nh_notifier_res_bucket_info_fini(struct nh_notifier_info *info)
{
 kfree(info->nh_res_bucket);
}

static int __call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id,
            u16 bucket_index, bool force,
            struct nh_info *oldi,
            struct nh_info *newi,
            struct netlink_ext_ack *extack)
{
 struct nh_notifier_info info = {
  .net = net,
  .extack = extack,
  .id = nhg_id,
 };
 int err;

 if (nexthop_notifiers_is_empty(net))
  return 0;

 err = nh_notifier_res_bucket_info_init(&info, bucket_index, force,
            oldi, newi);
 if (err)
  return err;

 err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
        NEXTHOP_EVENT_BUCKET_REPLACE, &info);
 nh_notifier_res_bucket_info_fini(&info);

 return notifier_to_errno(err);
}

/* There are three users of RES_TABLE, and NHs etc. referenced from there:
 *
 * 1) a collection of callbacks for NH maintenance. This operates under
 *    RTNL,
 * 2) the delayed work that gradually balances the resilient table,
 * 3) and nexthop_select_path(), operating under RCU.
 *
 * Both the delayed work and the RTNL block are writers, and need to
 * maintain mutual exclusion. Since there are only two and well-known
 * writers for each table, the RTNL code can make sure it has exclusive
 * access thus:
 *
 * - Have the DW operate without locking;
 * - synchronously cancel the DW;
 * - do the writing;
 * - if the write was not actually a delete, call upkeep, which schedules
 *   DW again if necessary.
 *
 * The functions that are always called from the RTNL context use
 * rtnl_dereference(). The functions that can also be called from the DW do
 * a raw dereference and rely on the above mutual exclusion scheme.
 */

#define nh_res_dereference(p) (rcu_dereference_raw(p))

static int call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id,
          u16 bucket_index, bool force,
          struct nexthop *old_nh,
          struct nexthop *new_nh,
          struct netlink_ext_ack *extack)
{
 struct nh_info *oldi = nh_res_dereference(old_nh->nh_info);
 struct nh_info *newi = nh_res_dereference(new_nh->nh_info);

 return __call_nexthop_res_bucket_notifiers(net, nhg_id, bucket_index,
         force, oldi, newi, extack);
}

static int call_nexthop_res_table_notifiers(struct net *net, struct nexthop *nh,
         struct netlink_ext_ack *extack)
{
 struct nh_notifier_info info = {
  .net = net,
  .extack = extack,
  .id = nh->id,
 };
 struct nh_group *nhg;
 int err;

 ASSERT_RTNL();

 if (nexthop_notifiers_is_empty(net))
  return 0;

 /* At this point, the nexthop buckets are still not populated. Only
 * emit a notification with the logical nexthops, so that a listener
 * could potentially veto it in case of unsupported configuration.
 */

 nhg = rtnl_dereference(nh->nh_grp);
 err = nh_notifier_mpath_info_init(&info, nhg);
 if (err) {
  NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info");
  return err;
 }

 err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
        NEXTHOP_EVENT_RES_TABLE_PRE_REPLACE,
        &info);
 kfree(info.nh_grp);

 return notifier_to_errno(err);
}

static int call_nexthop_notifier(struct notifier_block *nb, struct net *net,
     enum nexthop_event_type event_type,
     struct nexthop *nh,
     struct netlink_ext_ack *extack)
{
 struct nh_notifier_info info = {
  .net = net,
  .extack = extack,
 };
 int err;

 err = nh_notifier_info_init(&info, nh);
 if (err)
  return err;

 err = nb->notifier_call(nb, event_type, &info);
 nh_notifier_info_fini(&info, nh);

 return notifier_to_errno(err);
}

static unsigned int nh_dev_hashfn(unsigned int val)
{
 unsigned int mask = NH_DEV_HASHSIZE - 1;

 return (val ^
  (val >> NH_DEV_HASHBITS) ^
  (val >> (NH_DEV_HASHBITS * 2))) & mask;
}

static void nexthop_devhash_add(struct net *net, struct nh_info *nhi)
{
 struct net_device *dev = nhi->fib_nhc.nhc_dev;
 struct hlist_head *head;
 unsigned int hash;

 WARN_ON(!dev);

 hash = nh_dev_hashfn(dev->ifindex);
 head = &net->nexthop.devhash[hash];
 hlist_add_head(&nhi->dev_hash, head);
}

static void nexthop_free_group(struct nexthop *nh)
{
 struct nh_group *nhg;
 int i;

 nhg = rcu_dereference_raw(nh->nh_grp);
 for (i = 0; i < nhg->num_nh; ++i) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];

  WARN_ON(!list_empty(&nhge->nh_list));
  free_percpu(nhge->stats);
  nexthop_put(nhge->nh);
 }

 WARN_ON(nhg->spare == nhg);

 if (nhg->resilient)
  vfree(rcu_dereference_raw(nhg->res_table));

 kfree(nhg->spare);
 kfree(nhg);
}

static void nexthop_free_single(struct nexthop *nh)
{
 struct nh_info *nhi;

 nhi = rcu_dereference_raw(nh->nh_info);
 switch (nhi->family) {
 case AF_INET:
  fib_nh_release(nh->net, &nhi->fib_nh);
  break;
 case AF_INET6:
  ipv6_stub->fib6_nh_release(&nhi->fib6_nh);
  break;
 }
 kfree(nhi);
}

void nexthop_free_rcu(struct rcu_head *head)
{
 struct nexthop *nh = container_of(head, struct nexthop, rcu);

 if (nh->is_group)
  nexthop_free_group(nh);
 else
  nexthop_free_single(nh);

 kfree(nh);
}
EXPORT_SYMBOL_GPL(nexthop_free_rcu);

static struct nexthop *nexthop_alloc(void)
{
 struct nexthop *nh;

 nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
 if (nh) {
  INIT_LIST_HEAD(&nh->fi_list);
  INIT_LIST_HEAD(&nh->f6i_list);
  INIT_LIST_HEAD(&nh->grp_list);
  INIT_LIST_HEAD(&nh->fdb_list);
  spin_lock_init(&nh->lock);
 }
 return nh;
}

static struct nh_group *nexthop_grp_alloc(u16 num_nh)
{
 struct nh_group *nhg;

 nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL);
 if (nhg)
  nhg->num_nh = num_nh;

 return nhg;
}

static void nh_res_table_upkeep_dw(struct work_struct *work);

static struct nh_res_table *
nexthop_res_table_alloc(struct net *net, u32 nhg_id, struct nh_config *cfg)
{
 const u16 num_nh_buckets = cfg->nh_grp_res_num_buckets;
 struct nh_res_table *res_table;
 unsigned long size;

 size = struct_size(res_table, nh_buckets, num_nh_buckets);
 res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN);
 if (!res_table)
  return NULL;

 res_table->net = net;
 res_table->nhg_id = nhg_id;
 INIT_DELAYED_WORK(&res_table->upkeep_dw, &nh_res_table_upkeep_dw);
 INIT_LIST_HEAD(&res_table->uw_nh_entries);
 res_table->idle_timer = cfg->nh_grp_res_idle_timer;
 res_table->unbalanced_timer = cfg->nh_grp_res_unbalanced_timer;
 res_table->num_nh_buckets = num_nh_buckets;
 return res_table;
}

static void nh_base_seq_inc(struct net *net)
{
 while (++net->nexthop.seq == 0)
  ;
}

/* no reference taken; rcu lock or rtnl must be held */
struct nexthop *nexthop_find_by_id(struct net *net, u32 id)
{
 struct rb_node **pp, *parent = NULL, *next;

 pp = &net->nexthop.rb_root.rb_node;
 while (1) {
  struct nexthop *nh;

  next = rcu_dereference_raw(*pp);
  if (!next)
   break;
  parent = next;

  nh = rb_entry(parent, struct nexthop, rb_node);
  if (id < nh->id)
   pp = &next->rb_left;
  else if (id > nh->id)
   pp = &next->rb_right;
  else
   return nh;
 }
 return NULL;
}
EXPORT_SYMBOL_GPL(nexthop_find_by_id);

/* used for auto id allocation; called with rtnl held */
static u32 nh_find_unused_id(struct net *net)
{
 u32 id_start = net->nexthop.last_id_allocated;

 while (1) {
  net->nexthop.last_id_allocated++;
  if (net->nexthop.last_id_allocated == id_start)
   break;

  if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated))
   return net->nexthop.last_id_allocated;
 }
 return 0;
}

static void nh_res_time_set_deadline(unsigned long next_time,
         unsigned long *deadline)
{
 if (time_before(next_time, *deadline))
  *deadline = next_time;
}

static clock_t nh_res_table_unbalanced_time(struct nh_res_table *res_table)
{
 if (list_empty(&res_table->uw_nh_entries))
  return 0;
 return jiffies_delta_to_clock_t(jiffies - res_table->unbalanced_since);
}

static int nla_put_nh_group_res(struct sk_buff *skb, struct nh_group *nhg)
{
 struct nh_res_table *res_table = rtnl_dereference(nhg->res_table);
 struct nlattr *nest;

 nest = nla_nest_start(skb, NHA_RES_GROUP);
 if (!nest)
  return -EMSGSIZE;

 if (nla_put_u16(skb, NHA_RES_GROUP_BUCKETS,
   res_table->num_nh_buckets) ||
     nla_put_u32(skb, NHA_RES_GROUP_IDLE_TIMER,
   jiffies_to_clock_t(res_table->idle_timer)) ||
     nla_put_u32(skb, NHA_RES_GROUP_UNBALANCED_TIMER,
   jiffies_to_clock_t(res_table->unbalanced_timer)) ||
     nla_put_u64_64bit(skb, NHA_RES_GROUP_UNBALANCED_TIME,
         nh_res_table_unbalanced_time(res_table),
         NHA_RES_GROUP_PAD))
  goto nla_put_failure;

 nla_nest_end(skb, nest);
 return 0;

nla_put_failure:
 nla_nest_cancel(skb, nest);
 return -EMSGSIZE;
}

static void nh_grp_entry_stats_inc(struct nh_grp_entry *nhge)
{
 struct nh_grp_entry_stats *cpu_stats;

 cpu_stats = get_cpu_ptr(nhge->stats);
 u64_stats_update_begin(&cpu_stats->syncp);
 u64_stats_inc(&cpu_stats->packets);
 u64_stats_update_end(&cpu_stats->syncp);
 put_cpu_ptr(cpu_stats);
}

static void nh_grp_entry_stats_read(struct nh_grp_entry *nhge,
        u64 *ret_packets)
{
 int i;

 *ret_packets = 0;

 for_each_possible_cpu(i) {
  struct nh_grp_entry_stats *cpu_stats;
  unsigned int start;
  u64 packets;

  cpu_stats = per_cpu_ptr(nhge->stats, i);
  do {
   start = u64_stats_fetch_begin(&cpu_stats->syncp);
   packets = u64_stats_read(&cpu_stats->packets);
  } while (u64_stats_fetch_retry(&cpu_stats->syncp, start));

  *ret_packets += packets;
 }
}

static int nh_notifier_grp_hw_stats_init(struct nh_notifier_info *info,
      const struct nexthop *nh)
{
 struct nh_group *nhg;
 int i;

 ASSERT_RTNL();
 nhg = rtnl_dereference(nh->nh_grp);

 info->id = nh->id;
 info->type = NH_NOTIFIER_INFO_TYPE_GRP_HW_STATS;
 info->nh_grp_hw_stats = kzalloc(struct_size(info->nh_grp_hw_stats,
          stats, nhg->num_nh),
     GFP_KERNEL);
 if (!info->nh_grp_hw_stats)
  return -ENOMEM;

 info->nh_grp_hw_stats->num_nh = nhg->num_nh;
 for (i = 0; i < nhg->num_nh; i++) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];

  info->nh_grp_hw_stats->stats[i].id = nhge->nh->id;
 }

 return 0;
}

static void nh_notifier_grp_hw_stats_fini(struct nh_notifier_info *info)
{
 kfree(info->nh_grp_hw_stats);
}

void nh_grp_hw_stats_report_delta(struct nh_notifier_grp_hw_stats_info *info,
      unsigned int nh_idx,
      u64 delta_packets)
{
 info->hw_stats_used = true;
 info->stats[nh_idx].packets += delta_packets;
}
EXPORT_SYMBOL(nh_grp_hw_stats_report_delta);

static void nh_grp_hw_stats_apply_update(struct nexthop *nh,
      struct nh_notifier_info *info)
{
 struct nh_group *nhg;
 int i;

 ASSERT_RTNL();
 nhg = rtnl_dereference(nh->nh_grp);

 for (i = 0; i < nhg->num_nh; i++) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];

  nhge->packets_hw += info->nh_grp_hw_stats->stats[i].packets;
 }
}

static int nh_grp_hw_stats_update(struct nexthop *nh, bool *hw_stats_used)
{
 struct nh_notifier_info info = {
  .net = nh->net,
 };
 struct net *net = nh->net;
 int err;

 if (nexthop_notifiers_is_empty(net)) {
  *hw_stats_used = false;
  return 0;
 }

 err = nh_notifier_grp_hw_stats_init(&info, nh);
 if (err)
  return err;

 err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
        NEXTHOP_EVENT_HW_STATS_REPORT_DELTA,
        &info);

 /* Cache whatever we got, even if there was an error, otherwise the
 * successful stats retrievals would get lost.
 */

 nh_grp_hw_stats_apply_update(nh, &info);
 *hw_stats_used = info.nh_grp_hw_stats->hw_stats_used;

 nh_notifier_grp_hw_stats_fini(&info);
 return notifier_to_errno(err);
}

static int nla_put_nh_group_stats_entry(struct sk_buff *skb,
     struct nh_grp_entry *nhge,
     u32 op_flags)
{
 struct nlattr *nest;
 u64 packets;

 nh_grp_entry_stats_read(nhge, &packets);

 nest = nla_nest_start(skb, NHA_GROUP_STATS_ENTRY);
 if (!nest)
  return -EMSGSIZE;

 if (nla_put_u32(skb, NHA_GROUP_STATS_ENTRY_ID, nhge->nh->id) ||
     nla_put_uint(skb, NHA_GROUP_STATS_ENTRY_PACKETS,
    packets + nhge->packets_hw))
  goto nla_put_failure;

 if (op_flags & NHA_OP_FLAG_DUMP_HW_STATS &&
     nla_put_uint(skb, NHA_GROUP_STATS_ENTRY_PACKETS_HW,
    nhge->packets_hw))
  goto nla_put_failure;

 nla_nest_end(skb, nest);
 return 0;

nla_put_failure:
 nla_nest_cancel(skb, nest);
 return -EMSGSIZE;
}

static int nla_put_nh_group_stats(struct sk_buff *skb, struct nexthop *nh,
      u32 op_flags)
{
 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
 struct nlattr *nest;
 bool hw_stats_used;
 int err;
 int i;

 if (nla_put_u32(skb, NHA_HW_STATS_ENABLE, nhg->hw_stats))
  goto err_out;

 if (op_flags & NHA_OP_FLAG_DUMP_HW_STATS &&
     nhg->hw_stats) {
  err = nh_grp_hw_stats_update(nh, &hw_stats_used);
  if (err)
   goto out;

  if (nla_put_u32(skb, NHA_HW_STATS_USED, hw_stats_used))
   goto err_out;
 }

 nest = nla_nest_start(skb, NHA_GROUP_STATS);
 if (!nest)
  goto err_out;

 for (i = 0; i < nhg->num_nh; i++)
  if (nla_put_nh_group_stats_entry(skb, &nhg->nh_entries[i],
       op_flags))
   goto cancel_out;

 nla_nest_end(skb, nest);
 return 0;

cancel_out:
 nla_nest_cancel(skb, nest);
err_out:
 err = -EMSGSIZE;
out:
 return err;
}

static int nla_put_nh_group(struct sk_buff *skb, struct nexthop *nh,
       u32 op_flags, u32 *resp_op_flags)
{
 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
 struct nexthop_grp *p;
 size_t len = nhg->num_nh * sizeof(*p);
 struct nlattr *nla;
 u16 group_type = 0;
 u16 weight;
 int i;

 *resp_op_flags |= NHA_OP_FLAG_RESP_GRP_RESVD_0;

 if (nhg->hash_threshold)
  group_type = NEXTHOP_GRP_TYPE_MPATH;
 else if (nhg->resilient)
  group_type = NEXTHOP_GRP_TYPE_RES;

 if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type))
  goto nla_put_failure;

 nla = nla_reserve(skb, NHA_GROUP, len);
 if (!nla)
  goto nla_put_failure;

 p = nla_data(nla);
 for (i = 0; i < nhg->num_nh; ++i) {
  weight = nhg->nh_entries[i].weight - 1;

  *p++ = (struct nexthop_grp) {
   .id = nhg->nh_entries[i].nh->id,
   .weight = weight,
   .weight_high = weight >> 8,
  };
 }

 if (nhg->resilient && nla_put_nh_group_res(skb, nhg))
  goto nla_put_failure;

 if (op_flags & NHA_OP_FLAG_DUMP_STATS &&
     (nla_put_u32(skb, NHA_HW_STATS_ENABLE, nhg->hw_stats) ||
      nla_put_nh_group_stats(skb, nh, op_flags)))
  goto nla_put_failure;

 return 0;

nla_put_failure:
 return -EMSGSIZE;
}

static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
   int event, u32 portid, u32 seq, unsigned int nlflags,
   u32 op_flags)
{
 struct fib6_nh *fib6_nh;
 struct fib_nh *fib_nh;
 struct nlmsghdr *nlh;
 struct nh_info *nhi;
 struct nhmsg *nhm;

 nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
 if (!nlh)
  return -EMSGSIZE;

 nhm = nlmsg_data(nlh);
 nhm->nh_family = AF_UNSPEC;
 nhm->nh_flags = nh->nh_flags;
 nhm->nh_protocol = nh->protocol;
 nhm->nh_scope = 0;
 nhm->resvd = 0;

 if (nla_put_u32(skb, NHA_ID, nh->id))
  goto nla_put_failure;

 if (nh->is_group) {
  struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
  u32 resp_op_flags = 0;

  if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB))
   goto nla_put_failure;
  if (nla_put_nh_group(skb, nh, op_flags, &resp_op_flags) ||
      nla_put_u32(skb, NHA_OP_FLAGS, resp_op_flags))
   goto nla_put_failure;
  goto out;
 }

 nhi = rtnl_dereference(nh->nh_info);
 nhm->nh_family = nhi->family;
 if (nhi->reject_nh) {
  if (nla_put_flag(skb, NHA_BLACKHOLE))
   goto nla_put_failure;
  goto out;
 } else if (nhi->fdb_nh) {
  if (nla_put_flag(skb, NHA_FDB))
   goto nla_put_failure;
 } else {
  const struct net_device *dev;

  dev = nhi->fib_nhc.nhc_dev;
  if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex))
   goto nla_put_failure;
 }

 nhm->nh_scope = nhi->fib_nhc.nhc_scope;
 switch (nhi->family) {
 case AF_INET:
  fib_nh = &nhi->fib_nh;
  if (fib_nh->fib_nh_gw_family &&
      nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4))
   goto nla_put_failure;
  break;

 case AF_INET6:
  fib6_nh = &nhi->fib6_nh;
  if (fib6_nh->fib_nh_gw_family &&
      nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6))
   goto nla_put_failure;
  break;
 }

 if (lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate,
    NHA_ENCAP, NHA_ENCAP_TYPE) < 0)
  goto nla_put_failure;

out:
 nlmsg_end(skb, nlh);
 return 0;

nla_put_failure:
 nlmsg_cancel(skb, nlh);
 return -EMSGSIZE;
}

static size_t nh_nlmsg_size_grp_res(struct nh_group *nhg)
{
 return nla_total_size(0) + /* NHA_RES_GROUP */
  nla_total_size(2) + /* NHA_RES_GROUP_BUCKETS */
  nla_total_size(4) + /* NHA_RES_GROUP_IDLE_TIMER */
  nla_total_size(4) + /* NHA_RES_GROUP_UNBALANCED_TIMER */
  nla_total_size_64bit(8);/* NHA_RES_GROUP_UNBALANCED_TIME */
}

static size_t nh_nlmsg_size_grp(struct nexthop *nh)
{
 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
 size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh;
 size_t tot = nla_total_size(sz) +
  nla_total_size(2); /* NHA_GROUP_TYPE */

 if (nhg->resilient)
  tot += nh_nlmsg_size_grp_res(nhg);

 return tot;
}

static size_t nh_nlmsg_size_single(struct nexthop *nh)
{
 struct nh_info *nhi = rtnl_dereference(nh->nh_info);
 size_t sz;

 /* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE
 * are mutually exclusive
 */

 sz = nla_total_size(4);  /* NHA_OIF */

 switch (nhi->family) {
 case AF_INET:
  if (nhi->fib_nh.fib_nh_gw_family)
   sz += nla_total_size(4);  /* NHA_GATEWAY */
  break;

 case AF_INET6:
  /* NHA_GATEWAY */
  if (nhi->fib6_nh.fib_nh_gw_family)
   sz += nla_total_size(sizeof(const struct in6_addr));
  break;
 }

 if (nhi->fib_nhc.nhc_lwtstate) {
  sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
  sz += nla_total_size(2);  /* NHA_ENCAP_TYPE */
 }

 return sz;
}

static size_t nh_nlmsg_size(struct nexthop *nh)
{
 size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg));

 sz += nla_total_size(4); /* NHA_ID */

 if (nh->is_group)
  sz += nh_nlmsg_size_grp(nh) +
        nla_total_size(4) + /* NHA_OP_FLAGS */
        0;
 else
  sz += nh_nlmsg_size_single(nh);

 return sz;
}

static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info)
{
 unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0;
 u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
 struct sk_buff *skb;
 int err = -ENOBUFS;

 skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any());
 if (!skb)
  goto errout;

 err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags, 0);
 if (err < 0) {
  /* -EMSGSIZE implies BUG in nh_nlmsg_size() */
  WARN_ON(err == -EMSGSIZE);
  kfree_skb(skb);
  goto errout;
 }

 rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP,
      info->nlh, gfp_any());
 return;
errout:
 rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err);
}

static unsigned long nh_res_bucket_used_time(const struct nh_res_bucket *bucket)
{
 return (unsigned long)atomic_long_read(&bucket->used_time);
}

static unsigned long
nh_res_bucket_idle_point(const struct nh_res_table *res_table,
    const struct nh_res_bucket *bucket,
    unsigned long now)
{
 unsigned long time = nh_res_bucket_used_time(bucket);

 /* Bucket was not used since it was migrated. The idle time is now. */
 if (time == bucket->migrated_time)
  return now;

 return time + res_table->idle_timer;
}

static unsigned long
nh_res_table_unb_point(const struct nh_res_table *res_table)
{
 return res_table->unbalanced_since + res_table->unbalanced_timer;
}

static void nh_res_bucket_set_idle(const struct nh_res_table *res_table,
       struct nh_res_bucket *bucket)
{
 unsigned long now = jiffies;

 atomic_long_set(&bucket->used_time, (long)now);
 bucket->migrated_time = now;
}

static void nh_res_bucket_set_busy(struct nh_res_bucket *bucket)
{
 atomic_long_set(&bucket->used_time, (long)jiffies);
}

static clock_t nh_res_bucket_idle_time(const struct nh_res_bucket *bucket)
{
 unsigned long used_time = nh_res_bucket_used_time(bucket);

 return jiffies_delta_to_clock_t(jiffies - used_time);
}

static int nh_fill_res_bucket(struct sk_buff *skb, struct nexthop *nh,
         struct nh_res_bucket *bucket, u16 bucket_index,
         int event, u32 portid, u32 seq,
         unsigned int nlflags,
         struct netlink_ext_ack *extack)
{
 struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry);
 struct nlmsghdr *nlh;
 struct nlattr *nest;
 struct nhmsg *nhm;

 nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
 if (!nlh)
  return -EMSGSIZE;

 nhm = nlmsg_data(nlh);
 nhm->nh_family = AF_UNSPEC;
 nhm->nh_flags = bucket->nh_flags;
 nhm->nh_protocol = nh->protocol;
 nhm->nh_scope = 0;
 nhm->resvd = 0;

 if (nla_put_u32(skb, NHA_ID, nh->id))
  goto nla_put_failure;

 nest = nla_nest_start(skb, NHA_RES_BUCKET);
 if (!nest)
  goto nla_put_failure;

 if (nla_put_u16(skb, NHA_RES_BUCKET_INDEX, bucket_index) ||
     nla_put_u32(skb, NHA_RES_BUCKET_NH_ID, nhge->nh->id) ||
     nla_put_u64_64bit(skb, NHA_RES_BUCKET_IDLE_TIME,
         nh_res_bucket_idle_time(bucket),
         NHA_RES_BUCKET_PAD))
  goto nla_put_failure_nest;

 nla_nest_end(skb, nest);
 nlmsg_end(skb, nlh);
 return 0;

nla_put_failure_nest:
 nla_nest_cancel(skb, nest);
nla_put_failure:
 nlmsg_cancel(skb, nlh);
 return -EMSGSIZE;
}

static void nexthop_bucket_notify(struct nh_res_table *res_table,
      u16 bucket_index)
{
 struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index];
 struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry);
 struct nexthop *nh = nhge->nh_parent;
 struct sk_buff *skb;
 int err = -ENOBUFS;

 skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
 if (!skb)
  goto errout;

 err = nh_fill_res_bucket(skb, nh, bucket, bucket_index,
     RTM_NEWNEXTHOPBUCKET, 0, 0, NLM_F_REPLACE,
     NULL);
 if (err < 0) {
  kfree_skb(skb);
  goto errout;
 }

 rtnl_notify(skb, nh->net, 0, RTNLGRP_NEXTHOP, NULL, GFP_KERNEL);
 return;
errout:
 rtnl_set_sk_err(nh->net, RTNLGRP_NEXTHOP, err);
}

static bool valid_group_nh(struct nexthop *nh, unsigned int npaths,
      bool *is_fdb, struct netlink_ext_ack *extack)
{
 if (nh->is_group) {
  struct nh_group *nhg = rtnl_dereference(nh->nh_grp);

  /* Nesting groups within groups is not supported. */
  if (nhg->hash_threshold) {
   NL_SET_ERR_MSG(extack,
           "Hash-threshold group can not be a nexthop within a group");
   return false;
  }
  if (nhg->resilient) {
   NL_SET_ERR_MSG(extack,
           "Resilient group can not be a nexthop within a group");
   return false;
  }
  *is_fdb = nhg->fdb_nh;
 } else {
  struct nh_info *nhi = rtnl_dereference(nh->nh_info);

  if (nhi->reject_nh && npaths > 1) {
   NL_SET_ERR_MSG(extack,
           "Blackhole nexthop can not be used in a group with more than 1 path");
   return false;
  }
  *is_fdb = nhi->fdb_nh;
 }

 return true;
}

static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family,
       struct netlink_ext_ack *extack)
{
 struct nh_info *nhi;

 nhi = rtnl_dereference(nh->nh_info);

 if (!nhi->fdb_nh) {
  NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops");
  return -EINVAL;
 }

 if (*nh_family == AF_UNSPEC) {
  *nh_family = nhi->family;
 } else if (*nh_family != nhi->family) {
  NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops");
  return -EINVAL;
 }

 return 0;
}

static int nh_check_attr_group(struct net *net,
          struct nlattr *tb[], size_t tb_size,
          u16 nh_grp_type, struct netlink_ext_ack *extack)
{
 unsigned int len = nla_len(tb[NHA_GROUP]);
 struct nexthop_grp *nhg;
 unsigned int i, j;

 if (!len || len & (sizeof(struct nexthop_grp) - 1)) {
  NL_SET_ERR_MSG(extack,
          "Invalid length for nexthop group attribute");
  return -EINVAL;
 }

 /* convert len to number of nexthop ids */
 len /= sizeof(*nhg);

 nhg = nla_data(tb[NHA_GROUP]);
 for (i = 0; i < len; ++i) {
  if (nhg[i].resvd2) {
   NL_SET_ERR_MSG(extack, "Reserved field in nexthop_grp must be 0");
   return -EINVAL;
  }
  if (nexthop_grp_weight(&nhg[i]) == 0) {
   /* 0xffff got passed in, representing weight of 0x10000,
 * which is too heavy.
 */

   NL_SET_ERR_MSG(extack, "Invalid value for weight");
   return -EINVAL;
  }
  for (j = i + 1; j < len; ++j) {
   if (nhg[i].id == nhg[j].id) {
    NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group");
    return -EINVAL;
   }
  }
 }

 nhg = nla_data(tb[NHA_GROUP]);
 for (i = NHA_GROUP_TYPE + 1; i < tb_size; ++i) {
  if (!tb[i])
   continue;
  switch (i) {
  case NHA_HW_STATS_ENABLE:
  case NHA_FDB:
   continue;
  case NHA_RES_GROUP:
   if (nh_grp_type == NEXTHOP_GRP_TYPE_RES)
    continue;
   break;
  }
  NL_SET_ERR_MSG(extack,
          "No other attributes can be set in nexthop groups");
  return -EINVAL;
 }

 return 0;
}

static int nh_check_attr_group_rtnl(struct net *net, struct nlattr *tb[],
        struct netlink_ext_ack *extack)
{
 u8 nh_family = AF_UNSPEC;
 struct nexthop_grp *nhg;
 unsigned int len;
 unsigned int i;
 u8 nhg_fdb;

 len = nla_len(tb[NHA_GROUP]) / sizeof(*nhg);
 nhg = nla_data(tb[NHA_GROUP]);
 nhg_fdb = !!tb[NHA_FDB];

 for (i = 0; i < len; i++) {
  struct nexthop *nh;
  bool is_fdb_nh;

  nh = nexthop_find_by_id(net, nhg[i].id);
  if (!nh) {
   NL_SET_ERR_MSG(extack, "Invalid nexthop id");
   return -EINVAL;
  }
  if (!valid_group_nh(nh, len, &is_fdb_nh, extack))
   return -EINVAL;

  if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack))
   return -EINVAL;

  if (!nhg_fdb && is_fdb_nh) {
   NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops");
   return -EINVAL;
  }
 }

 return 0;
}

static bool ipv6_good_nh(const struct fib6_nh *nh)
{
 int state = NUD_REACHABLE;
 struct neighbour *n;

 rcu_read_lock();

 n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
 if (n)
  state = READ_ONCE(n->nud_state);

 rcu_read_unlock();

 return !!(state & NUD_VALID);
}

static bool ipv4_good_nh(const struct fib_nh *nh)
{
 int state = NUD_REACHABLE;
 struct neighbour *n;

 rcu_read_lock();

 n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
          (__force u32)nh->fib_nh_gw4);
 if (n)
  state = READ_ONCE(n->nud_state);

 rcu_read_unlock();

 return !!(state & NUD_VALID);
}

static bool nexthop_is_good_nh(const struct nexthop *nh)
{
 struct nh_info *nhi = rcu_dereference(nh->nh_info);

 switch (nhi->family) {
 case AF_INET:
  return ipv4_good_nh(&nhi->fib_nh);
 case AF_INET6:
  return ipv6_good_nh(&nhi->fib6_nh);
 }

 return false;
}

static struct nexthop *nexthop_select_path_fdb(struct nh_group *nhg, int hash)
{
 int i;

 for (i = 0; i < nhg->num_nh; i++) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];

  if (hash > atomic_read(&nhge->hthr.upper_bound))
   continue;

  nh_grp_entry_stats_inc(nhge);
  return nhge->nh;
 }

 WARN_ON_ONCE(1);
 return NULL;
}

static struct nexthop *nexthop_select_path_hthr(struct nh_group *nhg, int hash)
{
 struct nh_grp_entry *nhge0 = NULL;
 int i;

 if (nhg->fdb_nh)
  return nexthop_select_path_fdb(nhg, hash);

 for (i = 0; i < nhg->num_nh; ++i) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];

  /* nexthops always check if it is good and does
 * not rely on a sysctl for this behavior
 */

  if (!nexthop_is_good_nh(nhge->nh))
   continue;

  if (!nhge0)
   nhge0 = nhge;

  if (hash > atomic_read(&nhge->hthr.upper_bound))
   continue;

  nh_grp_entry_stats_inc(nhge);
  return nhge->nh;
 }

 if (!nhge0)
  nhge0 = &nhg->nh_entries[0];
 nh_grp_entry_stats_inc(nhge0);
 return nhge0->nh;
}

static struct nexthop *nexthop_select_path_res(struct nh_group *nhg, int hash)
{
 struct nh_res_table *res_table = rcu_dereference(nhg->res_table);
 u16 bucket_index = hash % res_table->num_nh_buckets;
 struct nh_res_bucket *bucket;
 struct nh_grp_entry *nhge;

 /* nexthop_select_path() is expected to return a non-NULL value, so
 * skip protocol validation and just hand out whatever there is.
 */

 bucket = &res_table->nh_buckets[bucket_index];
 nh_res_bucket_set_busy(bucket);
 nhge = rcu_dereference(bucket->nh_entry);
 nh_grp_entry_stats_inc(nhge);
 return nhge->nh;
}

struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
{
 struct nh_group *nhg;

 if (!nh->is_group)
  return nh;

 nhg = rcu_dereference(nh->nh_grp);
 if (nhg->hash_threshold)
  return nexthop_select_path_hthr(nhg, hash);
 else if (nhg->resilient)
  return nexthop_select_path_res(nhg, hash);

 /* Unreachable. */
 return NULL;
}
EXPORT_SYMBOL_GPL(nexthop_select_path);

int nexthop_for_each_fib6_nh(struct nexthop *nh,
        int (*cb)(struct fib6_nh *nh, void *arg),
        void *arg)
{
 struct nh_info *nhi;
 int err;

 if (nh->is_group) {
  struct nh_group *nhg;
  int i;

  nhg = rcu_dereference_rtnl(nh->nh_grp);
  for (i = 0; i < nhg->num_nh; i++) {
   struct nh_grp_entry *nhge = &nhg->nh_entries[i];

   nhi = rcu_dereference_rtnl(nhge->nh->nh_info);
   err = cb(&nhi->fib6_nh, arg);
   if (err)
    return err;
  }
 } else {
  nhi = rcu_dereference_rtnl(nh->nh_info);
  err = cb(&nhi->fib6_nh, arg);
  if (err)
   return err;
 }

 return 0;
}
EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh);

static int check_src_addr(const struct in6_addr *saddr,
     struct netlink_ext_ack *extack)
{
 if (!ipv6_addr_any(saddr)) {
  NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
  return -EINVAL;
 }
 return 0;
}

int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
         struct netlink_ext_ack *extack)
{
 struct nh_info *nhi;
 bool is_fdb_nh;

 /* fib6_src is unique to a fib6_info and limits the ability to cache
 * routes in fib6_nh within a nexthop that is potentially shared
 * across multiple fib entries. If the config wants to use source
 * routing it can not use nexthop objects. mlxsw also does not allow
 * fib6_src on routes.
 */

 if (cfg && check_src_addr(&cfg->fc_src, extack) < 0)
  return -EINVAL;

 if (nh->is_group) {
  struct nh_group *nhg;

  nhg = rcu_dereference_rtnl(nh->nh_grp);
  if (nhg->has_v4)
   goto no_v4_nh;
  is_fdb_nh = nhg->fdb_nh;
 } else {
  nhi = rcu_dereference_rtnl(nh->nh_info);
  if (nhi->family == AF_INET)
   goto no_v4_nh;
  is_fdb_nh = nhi->fdb_nh;
 }

 if (is_fdb_nh) {
  NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
  return -EINVAL;
 }

 return 0;
no_v4_nh:
 NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
 return -EINVAL;
}
EXPORT_SYMBOL_GPL(fib6_check_nexthop);

/* if existing nexthop has ipv6 routes linked to it, need
 * to verify this new spec works with ipv6
 */

static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new,
         struct netlink_ext_ack *extack)
{
 struct fib6_info *f6i;

 if (list_empty(&old->f6i_list))
  return 0;

 list_for_each_entry(f6i, &old->f6i_list, nh_list) {
  if (check_src_addr(&f6i->fib6_src.addr, extack) < 0)
   return -EINVAL;
 }

 return fib6_check_nexthop(new, NULL, extack);
}

static int nexthop_check_scope(struct nh_info *nhi, u8 scope,
          struct netlink_ext_ack *extack)
{
 if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) {
  NL_SET_ERR_MSG(extack,
          "Route with host scope can not have a gateway");
  return -EINVAL;
 }

 if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) {
  NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop");
  return -EINVAL;
 }

 return 0;
}

/* Invoked by fib add code to verify nexthop by id is ok with
 * config for prefix; parts of fib_check_nh not done when nexthop
 * object is used.
 */

int fib_check_nexthop(struct nexthop *nh, u8 scope,
        struct netlink_ext_ack *extack)
{
 struct nh_info *nhi;
 int err = 0;

 if (nh->is_group) {
  struct nh_group *nhg;

  nhg = rtnl_dereference(nh->nh_grp);
  if (nhg->fdb_nh) {
   NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
   err = -EINVAL;
   goto out;
  }

  if (scope == RT_SCOPE_HOST) {
   NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops");
   err = -EINVAL;
   goto out;
  }

  /* all nexthops in a group have the same scope */
  nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info);
  err = nexthop_check_scope(nhi, scope, extack);
 } else {
  nhi = rtnl_dereference(nh->nh_info);
  if (nhi->fdb_nh) {
   NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
   err = -EINVAL;
   goto out;
  }
  err = nexthop_check_scope(nhi, scope, extack);
 }

out:
 return err;
}

static int fib_check_nh_list(struct nexthop *old, struct nexthop *new,
        struct netlink_ext_ack *extack)
{
 struct fib_info *fi;

 list_for_each_entry(fi, &old->fi_list, nh_list) {
  int err;

  err = fib_check_nexthop(new, fi->fib_scope, extack);
  if (err)
   return err;
 }
 return 0;
}

static bool nh_res_nhge_is_balanced(const struct nh_grp_entry *nhge)
{
 return nhge->res.count_buckets == nhge->res.wants_buckets;
}

static bool nh_res_nhge_is_ow(const struct nh_grp_entry *nhge)
{
 return nhge->res.count_buckets > nhge->res.wants_buckets;
}

static bool nh_res_nhge_is_uw(const struct nh_grp_entry *nhge)
{
 return nhge->res.count_buckets < nhge->res.wants_buckets;
}

static bool nh_res_table_is_balanced(const struct nh_res_table *res_table)
{
 return list_empty(&res_table->uw_nh_entries);
}

static void nh_res_bucket_unset_nh(struct nh_res_bucket *bucket)
{
 struct nh_grp_entry *nhge;

 if (bucket->occupied) {
  nhge = nh_res_dereference(bucket->nh_entry);
  nhge->res.count_buckets--;
  bucket->occupied = false;
 }
}

static void nh_res_bucket_set_nh(struct nh_res_bucket *bucket,
     struct nh_grp_entry *nhge)
{
 nh_res_bucket_unset_nh(bucket);

 bucket->occupied = true;
 rcu_assign_pointer(bucket->nh_entry, nhge);
 nhge->res.count_buckets++;
}

static bool nh_res_bucket_should_migrate(struct nh_res_table *res_table,
      struct nh_res_bucket *bucket,
      unsigned long *deadline, bool *force)
{
 unsigned long now = jiffies;
 struct nh_grp_entry *nhge;
 unsigned long idle_point;

 if (!bucket->occupied) {
  /* The bucket is not occupied, its NHGE pointer is either
 * NULL or obsolete. We _have to_ migrate: set force.
 */

  *force = true;
  return true;
 }

 nhge = nh_res_dereference(bucket->nh_entry);

 /* If the bucket is populated by an underweight or balanced
 * nexthop, do not migrate.
 */

 if (!nh_res_nhge_is_ow(nhge))
  return false;

 /* At this point we know that the bucket is populated with an
 * overweight nexthop. It needs to be migrated to a new nexthop if
 * the idle timer of unbalanced timer expired.
 */


 idle_point = nh_res_bucket_idle_point(res_table, bucket, now);
 if (time_after_eq(now, idle_point)) {
  /* The bucket is idle. We _can_ migrate: unset force. */
  *force = false;
  return true;
 }

 /* Unbalanced timer of 0 means "never force". */
 if (res_table->unbalanced_timer) {
  unsigned long unb_point;

  unb_point = nh_res_table_unb_point(res_table);
  if (time_after(now, unb_point)) {
   /* The bucket is not idle, but the unbalanced timer
 * expired. We _can_ migrate, but set force anyway,
 * so that drivers know to ignore activity reports
 * from the HW.
 */

   *force = true;
   return true;
  }

  nh_res_time_set_deadline(unb_point, deadline);
 }

 nh_res_time_set_deadline(idle_point, deadline);
 return false;
}

static bool nh_res_bucket_migrate(struct nh_res_table *res_table,
      u16 bucket_index, bool notify,
      bool notify_nl, bool force)
{
 struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index];
 struct nh_grp_entry *new_nhge;
 struct netlink_ext_ack extack;
 int err;

 new_nhge = list_first_entry_or_null(&res_table->uw_nh_entries,
         struct nh_grp_entry,
         res.uw_nh_entry);
 if (WARN_ON_ONCE(!new_nhge))
  /* If this function is called, "bucket" is either not
 * occupied, or it belongs to a next hop that is
 * overweight. In either case, there ought to be a
 * corresponding underweight next hop.
 */

  return false;

 if (notify) {
  struct nh_grp_entry *old_nhge;

  old_nhge = nh_res_dereference(bucket->nh_entry);
  err = call_nexthop_res_bucket_notifiers(res_table->net,
       res_table->nhg_id,
       bucket_index, force,
       old_nhge->nh,
       new_nhge->nh, &extack);
  if (err) {
   pr_err_ratelimited("%s\n", extack._msg);
   if (!force)
    return false;
   /* It is not possible to veto a forced replacement, so
 * just clear the hardware flags from the nexthop
 * bucket to indicate to user space that this bucket is
 * not correctly populated in hardware.
 */

   bucket->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP);
  }
 }

 nh_res_bucket_set_nh(bucket, new_nhge);
 nh_res_bucket_set_idle(res_table, bucket);

 if (notify_nl)
  nexthop_bucket_notify(res_table, bucket_index);

 if (nh_res_nhge_is_balanced(new_nhge))
  list_del(&new_nhge->res.uw_nh_entry);
 return true;
}

#define NH_RES_UPKEEP_DW_MINIMUM_INTERVAL (HZ / 2)

static void nh_res_table_upkeep(struct nh_res_table *res_table,
    bool notify, bool notify_nl)
{
 unsigned long now = jiffies;
 unsigned long deadline;
 u16 i;

 /* Deadline is the next time that upkeep should be run. It is the
 * earliest time at which one of the buckets might be migrated.
 * Start at the most pessimistic estimate: either unbalanced_timer
 * from now, or if there is none, idle_timer from now. For each
 * encountered time point, call nh_res_time_set_deadline() to
 * refine the estimate.
 */

 if (res_table->unbalanced_timer)
  deadline = now + res_table->unbalanced_timer;
 else
  deadline = now + res_table->idle_timer;

 for (i = 0; i < res_table->num_nh_buckets; i++) {
  struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
  bool force;

  if (nh_res_bucket_should_migrate(res_table, bucket,
       &deadline, &force)) {
   if (!nh_res_bucket_migrate(res_table, i, notify,
         notify_nl, force)) {
    unsigned long idle_point;

    /* A driver can override the migration
 * decision if the HW reports that the
 * bucket is actually not idle. Therefore
 * remark the bucket as busy again and
 * update the deadline.
 */

    nh_res_bucket_set_busy(bucket);
    idle_point = nh_res_bucket_idle_point(res_table,
              bucket,
              now);
    nh_res_time_set_deadline(idle_point, &deadline);
   }
  }
 }

 /* If the group is still unbalanced, schedule the next upkeep to
 * either the deadline computed above, or the minimum deadline,
 * whichever comes later.
 */

 if (!nh_res_table_is_balanced(res_table)) {
  unsigned long now = jiffies;
  unsigned long min_deadline;

  min_deadline = now + NH_RES_UPKEEP_DW_MINIMUM_INTERVAL;
  if (time_before(deadline, min_deadline))
   deadline = min_deadline;

  queue_delayed_work(system_power_efficient_wq,
       &res_table->upkeep_dw, deadline - now);
 }
}

static void nh_res_table_upkeep_dw(struct work_struct *work)
{
 struct delayed_work *dw = to_delayed_work(work);
 struct nh_res_table *res_table;

 res_table = container_of(dw, struct nh_res_table, upkeep_dw);
 nh_res_table_upkeep(res_table, truetrue);
}

static void nh_res_table_cancel_upkeep(struct nh_res_table *res_table)
{
 cancel_delayed_work_sync(&res_table->upkeep_dw);
}

static void nh_res_group_rebalance(struct nh_group *nhg,
       struct nh_res_table *res_table)
{
 u16 prev_upper_bound = 0;
 u32 total = 0;
 u32 w = 0;
 int i;

 INIT_LIST_HEAD(&res_table->uw_nh_entries);

 for (i = 0; i < nhg->num_nh; ++i)
  total += nhg->nh_entries[i].weight;

 for (i = 0; i < nhg->num_nh; ++i) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];
  u16 upper_bound;
  u64 btw;

  w += nhge->weight;
  btw = ((u64)res_table->num_nh_buckets) * w;
  upper_bound = DIV_ROUND_CLOSEST_ULL(btw, total);
  nhge->res.wants_buckets = upper_bound - prev_upper_bound;
  prev_upper_bound = upper_bound;

  if (nh_res_nhge_is_uw(nhge)) {
   if (list_empty(&res_table->uw_nh_entries))
    res_table->unbalanced_since = jiffies;
   list_add(&nhge->res.uw_nh_entry,
     &res_table->uw_nh_entries);
  }
 }
}

/* Migrate buckets in res_table so that they reference NHGE's from NHG with
 * the right NH ID. Set those buckets that do not have a corresponding NHGE
 * entry in NHG as not occupied.
 */

static void nh_res_table_migrate_buckets(struct nh_res_table *res_table,
      struct nh_group *nhg)
{
 u16 i;

 for (i = 0; i < res_table->num_nh_buckets; i++) {
  struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
  u32 id = rtnl_dereference(bucket->nh_entry)->nh->id;
  bool found = false;
  int j;

  for (j = 0; j < nhg->num_nh; j++) {
   struct nh_grp_entry *nhge = &nhg->nh_entries[j];

   if (nhge->nh->id == id) {
    nh_res_bucket_set_nh(bucket, nhge);
    found = true;
    break;
   }
  }

  if (!found)
   nh_res_bucket_unset_nh(bucket);
 }
}

static void replace_nexthop_grp_res(struct nh_group *oldg,
        struct nh_group *newg)
{
 /* For NH group replacement, the new NHG might only have a stub
 * hash table with 0 buckets, because the number of buckets was not
 * specified. For NH removal, oldg and newg both reference the same
 * res_table. So in any case, in the following, we want to work
 * with oldg->res_table.
 */

 struct nh_res_table *old_res_table = rtnl_dereference(oldg->res_table);
 unsigned long prev_unbalanced_since = old_res_table->unbalanced_since;
 bool prev_has_uw = !list_empty(&old_res_table->uw_nh_entries);

 nh_res_table_cancel_upkeep(old_res_table);
 nh_res_table_migrate_buckets(old_res_table, newg);
 nh_res_group_rebalance(newg, old_res_table);
 if (prev_has_uw && !list_empty(&old_res_table->uw_nh_entries))
  old_res_table->unbalanced_since = prev_unbalanced_since;
 nh_res_table_upkeep(old_res_table, truefalse);
}

static void nh_hthr_group_rebalance(struct nh_group *nhg)
{
 u32 total = 0;
 u32 w = 0;
 int i;

 for (i = 0; i < nhg->num_nh; ++i)
  total += nhg->nh_entries[i].weight;

 for (i = 0; i < nhg->num_nh; ++i) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];
  u32 upper_bound;

  w += nhge->weight;
  upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1;
  atomic_set(&nhge->hthr.upper_bound, upper_bound);
 }
}

static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge,
    struct nl_info *nlinfo)
{
 struct nh_grp_entry *nhges, *new_nhges;
 struct nexthop *nhp = nhge->nh_parent;
 struct netlink_ext_ack extack;
 struct nexthop *nh = nhge->nh;
 struct nh_group *nhg, *newg;
 int i, j, err;

 WARN_ON(!nh);

 nhg = rtnl_dereference(nhp->nh_grp);
 newg = nhg->spare;

 /* last entry, keep it visible and remove the parent */
 if (nhg->num_nh == 1) {
  remove_nexthop(net, nhp, nlinfo);
  return;
 }

 newg->has_v4 = false;
 newg->is_multipath = nhg->is_multipath;
 newg->hash_threshold = nhg->hash_threshold;
 newg->resilient = nhg->resilient;
 newg->fdb_nh = nhg->fdb_nh;
 newg->num_nh = nhg->num_nh;

 /* copy old entries to new except the one getting removed */
 nhges = nhg->nh_entries;
 new_nhges = newg->nh_entries;
 for (i = 0, j = 0; i < nhg->num_nh; ++i) {
  struct nh_info *nhi;

  /* current nexthop getting removed */
  if (nhg->nh_entries[i].nh == nh) {
   newg->num_nh--;
   continue;
  }

  nhi = rtnl_dereference(nhges[i].nh->nh_info);
  if (nhi->family == AF_INET)
   newg->has_v4 = true;

  list_del(&nhges[i].nh_list);
  new_nhges[j].stats = nhges[i].stats;
  new_nhges[j].nh_parent = nhges[i].nh_parent;
  new_nhges[j].nh = nhges[i].nh;
  new_nhges[j].weight = nhges[i].weight;
  list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list);
  j++;
 }

 if (newg->hash_threshold)
  nh_hthr_group_rebalance(newg);
 else if (newg->resilient)
  replace_nexthop_grp_res(nhg, newg);

 rcu_assign_pointer(nhp->nh_grp, newg);

 list_del(&nhge->nh_list);
 free_percpu(nhge->stats);
 nexthop_put(nhge->nh);

 /* Removal of a NH from a resilient group is notified through
 * bucket notifications.
 */

 if (newg->hash_threshold) {
  err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp,
          &extack);
  if (err)
   pr_err("%s\n", extack._msg);
 }

 if (nlinfo)
  nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo);
}

static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
           struct nl_info *nlinfo)
{
 struct nh_grp_entry *nhge, *tmp;

 /* If there is nothing to do, let's avoid the costly call to
 * synchronize_net()
 */

 if (list_empty(&nh->grp_list))
  return;

 list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list)
  remove_nh_grp_entry(net, nhge, nlinfo);

 /* make sure all see the newly published array before releasing rtnl */
 synchronize_net();
}

static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
{
 struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp);
 struct nh_res_table *res_table;
 int i, num_nh = nhg->num_nh;

 for (i = 0; i < num_nh; ++i) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];

  if (WARN_ON(!nhge->nh))
   continue;

  list_del_init(&nhge->nh_list);
 }

 if (nhg->resilient) {
  res_table = rtnl_dereference(nhg->res_table);
  nh_res_table_cancel_upkeep(res_table);
 }
}

/* not called for nexthop replace */
static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
{
 struct fib6_info *f6i;
 bool do_flush = false;
 struct fib_info *fi;

 list_for_each_entry(fi, &nh->fi_list, nh_list) {
  fi->fib_flags |= RTNH_F_DEAD;
  do_flush = true;
 }
 if (do_flush)
  fib_flush(net);

 spin_lock_bh(&nh->lock);

 nh->dead = true;

 while (!list_empty(&nh->f6i_list)) {
  f6i = list_first_entry(&nh->f6i_list, typeof(*f6i), nh_list);

  /* __ip6_del_rt does a release, so do a hold here */
  fib6_info_hold(f6i);

  spin_unlock_bh(&nh->lock);
  ipv6_stub->ip6_del_rt(net, f6i,
          !READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode));

  spin_lock_bh(&nh->lock);
 }

 spin_unlock_bh(&nh->lock);
}

static void __remove_nexthop(struct net *net, struct nexthop *nh,
        struct nl_info *nlinfo)
{
 __remove_nexthop_fib(net, nh);

 if (nh->is_group) {
  remove_nexthop_group(nh, nlinfo);
 } else {
  struct nh_info *nhi;

  nhi = rtnl_dereference(nh->nh_info);
  if (nhi->fib_nhc.nhc_dev)
   hlist_del(&nhi->dev_hash);

  remove_nexthop_from_groups(net, nh, nlinfo);
 }
}

static void remove_nexthop(struct net *net, struct nexthop *nh,
      struct nl_info *nlinfo)
{
 call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh, NULL);

 /* remove from the tree */
 rb_erase(&nh->rb_node, &net->nexthop.rb_root);

 if (nlinfo)
  nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo);

 __remove_nexthop(net, nh, nlinfo);
 nh_base_seq_inc(net);

 nexthop_put(nh);
}

/* if any FIB entries reference this nexthop, any dst entries
 * need to be regenerated
 */

static void nh_rt_cache_flush(struct net *net, struct nexthop *nh,
         struct nexthop *replaced_nh)
{
 struct fib6_info *f6i;
 struct nh_group *nhg;
 int i;

 if (!list_empty(&nh->fi_list))
  rt_cache_flush(net);

 list_for_each_entry(f6i, &nh->f6i_list, nh_list)
  ipv6_stub->fib6_update_sernum(net, f6i);

 /* if an IPv6 group was replaced, we have to release all old
 * dsts to make sure all refcounts are released
 */

 if (!replaced_nh->is_group)
  return;

 nhg = rtnl_dereference(replaced_nh->nh_grp);
 for (i = 0; i < nhg->num_nh; i++) {
  struct nh_grp_entry *nhge = &nhg->nh_entries[i];
  struct nh_info *nhi = rtnl_dereference(nhge->nh->nh_info);

  if (nhi->family == AF_INET6)
   ipv6_stub->fib6_nh_release_dsts(&nhi->fib6_nh);
 }
}

static int replace_nexthop_grp(struct net *net, struct nexthop *old,
          struct nexthop *newconst struct nh_config *cfg,
          struct netlink_ext_ack *extack)
{
 struct nh_res_table *tmp_table = NULL;
 struct nh_res_table *new_res_table;
 struct nh_res_table *old_res_table;
 struct nh_group *oldg, *newg;
 int i, err;

 if (!new->is_group) {
  NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop.");
  return -EINVAL;
 }

 oldg = rtnl_dereference(old->nh_grp);
 newg = rtnl_dereference(new->nh_grp);

 if (newg->hash_threshold != oldg->hash_threshold) {
  NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with one of a different type.");
  return -EINVAL;
 }

 if (newg->hash_threshold) {
  err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new,
          extack);
  if (err)
   return err;
 } else if (newg->resilient) {
  new_res_table = rtnl_dereference(newg->res_table);
  old_res_table = rtnl_dereference(oldg->res_table);

  /* Accept if num_nh_buckets was not given, but if it was
 * given, demand that the value be correct.
 */

  if (cfg->nh_grp_res_has_num_buckets &&
      cfg->nh_grp_res_num_buckets !=
      old_res_table->num_nh_buckets) {
   NL_SET_ERR_MSG(extack, "Can not change number of buckets of a resilient nexthop group.");
   return -EINVAL;
  }

  /* Emit a pre-replace notification so that listeners could veto
 * a potentially unsupported configuration. Otherwise,
 * individual bucket replacement notifications would need to be
 * vetoed, which is something that should only happen if the
 * bucket is currently active.
 */

  err = call_nexthop_res_table_notifiers(net, new, extack);
  if (err)
   return err;

  if (cfg->nh_grp_res_has_idle_timer)
   old_res_table->idle_timer = cfg->nh_grp_res_idle_timer;
  if (cfg->nh_grp_res_has_unbalanced_timer)
   old_res_table->unbalanced_timer =
    cfg->nh_grp_res_unbalanced_timer;

  replace_nexthop_grp_res(oldg, newg);

  tmp_table = new_res_table;
  rcu_assign_pointer(newg->res_table, old_res_table);
  rcu_assign_pointer(newg->spare->res_table, old_res_table);
 }

 /* update parents - used by nexthop code for cleanup */
 for (i = 0; i < newg->num_nh; i++)
  newg->nh_entries[i].nh_parent = old;

 rcu_assign_pointer(old->nh_grp, newg);

 /* Make sure concurrent readers are not using 'oldg' anymore. */
 synchronize_net();

 if (newg->resilient) {
  rcu_assign_pointer(oldg->res_table, tmp_table);
  rcu_assign_pointer(oldg->spare->res_table, tmp_table);
 }

 for (i = 0; i < oldg->num_nh; i++)
  oldg->nh_entries[i].nh_parent = new;

 rcu_assign_pointer(new->nh_grp, oldg);

 return 0;
}

static void nh_group_v4_update(struct nh_group *nhg)
{
 struct nh_grp_entry *nhges;
 bool has_v4 = false;
 int i;

 nhges = nhg->nh_entries;
 for (i = 0; i < nhg->num_nh; i++) {
  struct nh_info *nhi;

  nhi = rtnl_dereference(nhges[i].nh->nh_info);
  if (nhi->family == AF_INET)
   has_v4 = true;
 }
 nhg->has_v4 = has_v4;
}

static int replace_nexthop_single_notify_res(struct net *net,
          struct nh_res_table *res_table,
          struct nexthop *old,
          struct nh_info *oldi,
          struct nh_info *newi,
          struct netlink_ext_ack *extack)
{
 u32 nhg_id = res_table->nhg_id;
 int err;
 u16 i;

 for (i = 0; i < res_table->num_nh_buckets; i++) {
  struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
  struct nh_grp_entry *nhge;

  nhge = rtnl_dereference(bucket->nh_entry);
  if (nhge->nh == old) {
   err = __call_nexthop_res_bucket_notifiers(net, nhg_id,
          i, true,
          oldi, newi,
          extack);
   if (err)
    goto err_notify;
  }
 }

 return 0;

err_notify:
 while (i-- > 0) {
  struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
  struct nh_grp_entry *nhge;

  nhge = rtnl_dereference(bucket->nh_entry);
  if (nhge->nh == old)
   __call_nexthop_res_bucket_notifiers(net, nhg_id, i,
           true, newi, oldi,
           extack);
 }
 return err;
}

static int replace_nexthop_single_notify(struct net *net,
      struct nexthop *group_nh,
      struct nexthop *old,
      struct nh_info *oldi,
      struct nh_info *newi,
      struct netlink_ext_ack *extack)
{
 struct nh_group *nhg = rtnl_dereference(group_nh->nh_grp);
 struct nh_res_table *res_table;

 if (nhg->hash_threshold) {
  return call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE,
           group_nh, extack);
 } else if (nhg->resilient) {
  res_table = rtnl_dereference(nhg->res_table);
  return replace_nexthop_single_notify_res(net, res_table,
        old, oldi, newi,
        extack);
 }

 return -EINVAL;
}

static int replace_nexthop_single(struct net *net, struct nexthop *old,
      struct nexthop *new,
      struct netlink_ext_ack *extack)
{
 u8 old_protocol, old_nh_flags;
 struct nh_info *oldi, *newi;
 struct nh_grp_entry *nhge;
 int err;

 if (new->is_group) {
  NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group.");
  return -EINVAL;
 }

 if (!list_empty(&old->grp_list) &&
     rtnl_dereference(new->nh_info)->fdb_nh !=
     rtnl_dereference(old->nh_info)->fdb_nh) {
  NL_SET_ERR_MSG(extack, "Cannot change nexthop FDB status while in a group");
  return -EINVAL;
 }

 err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack);
 if (err)
  return err;

 /* Hardware flags were set on 'old' as 'new' is not in the red-black
 * tree. Therefore, inherit the flags from 'old' to 'new'.
 */

 new->nh_flags |= old->nh_flags & (RTNH_F_OFFLOAD | RTNH_F_TRAP);

 oldi = rtnl_dereference(old->nh_info);
 newi = rtnl_dereference(new->nh_info);

 newi->nh_parent = old;
 oldi->nh_parent = new;

 old_protocol = old->protocol;
 old_nh_flags = old->nh_flags;

 old->protocol = new->protocol;
 old->nh_flags = new->nh_flags;

 rcu_assign_pointer(old->nh_info, newi);
 rcu_assign_pointer(new->nh_info, oldi);

 /* Send a replace notification for all the groups using the nexthop. */
 list_for_each_entry(nhge, &old->grp_list, nh_list) {
  struct nexthop *nhp = nhge->nh_parent;

  err = replace_nexthop_single_notify(net, nhp, old, oldi, newi,
          extack);
  if (err)
   goto err_notify;
 }

 /* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially
 * update IPv4 indication in all the groups using the nexthop.
 */

 if (oldi->family == AF_INET && newi->family == AF_INET6) {
  list_for_each_entry(nhge, &old->grp_list, nh_list) {
   struct nexthop *nhp = nhge->nh_parent;
   struct nh_group *nhg;

   nhg = rtnl_dereference(nhp->nh_grp);
   nh_group_v4_update(nhg);
  }
 }

 return 0;

err_notify:
 rcu_assign_pointer(new->nh_info, newi);
 rcu_assign_pointer(old->nh_info, oldi);
 old->nh_flags = old_nh_flags;
 old->protocol = old_protocol;
 oldi->nh_parent = old;
 newi->nh_parent = new;
 list_for_each_entry_continue_reverse(nhge, &old->grp_list, nh_list) {
  struct nexthop *nhp = nhge->nh_parent;

  replace_nexthop_single_notify(net, nhp, old, newi, oldi, NULL);
 }
 call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, old, extack);
 return err;
}

static void __nexthop_replace_notify(struct net *net, struct nexthop *nh,
         struct nl_info *info)
{
 struct fib6_info *f6i;

 if (!list_empty(&nh->fi_list)) {
  struct fib_info *fi;

  /* expectation is a few fib_info per nexthop and then
 * a lot of routes per fib_info. So mark the fib_info
 * and then walk the fib tables once
 */

  list_for_each_entry(fi, &nh->fi_list, nh_list)
   fi->nh_updated = true;

  fib_info_notify_update(net, info);

  list_for_each_entry(fi, &nh->fi_list, nh_list)
   fi->nh_updated = false;
 }

 list_for_each_entry(f6i, &nh->f6i_list, nh_list)
  ipv6_stub->fib6_rt_update(net, f6i, info);
}

/* send RTM_NEWROUTE with REPLACE flag set for all FIB entries
 * linked to this nexthop and for all groups that the nexthop
 * is a member of
 */

static void nexthop_replace_notify(struct net *net, struct nexthop *nh,
       struct nl_info *info)
{
 struct nh_grp_entry *nhge;

 __nexthop_replace_notify(net, nh, info);

 list_for_each_entry(nhge, &nh->grp_list, nh_list)
  __nexthop_replace_notify(net, nhge->nh_parent, info);
}

static int replace_nexthop(struct net *net, struct nexthop *old,
      struct nexthop *newconst struct nh_config *cfg,
      struct netlink_ext_ack *extack)
{
 bool new_is_reject = false;
 struct nh_grp_entry *nhge;
 int err;

 /* check that existing FIB entries are ok with the
 * new nexthop definition
 */

 err = fib_check_nh_list(old, new, extack);
 if (err)
  return err;

 err = fib6_check_nh_list(old, new, extack);
 if (err)
  return err;

 if (!new->is_group) {
  struct nh_info *nhi = rtnl_dereference(new->nh_info);

  new_is_reject = nhi->reject_nh;
 }

 list_for_each_entry(nhge, &old->grp_list, nh_list) {
  /* if new nexthop is a blackhole, any groups using this
 * nexthop cannot have more than 1 path
 */

  if (new_is_reject &&
      nexthop_num_path(nhge->nh_parent) > 1) {
   NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path");
   return -EINVAL;
  }

  err = fib_check_nh_list(nhge->nh_parent, new, extack);
  if (err)
   return err;

  err = fib6_check_nh_list(nhge->nh_parent, new, extack);
  if (err)
   return err;
 }

 if (old->is_group)
  err = replace_nexthop_grp(net, old, new, cfg, extack);
 else
  err = replace_nexthop_single(net, old, new, extack);

 if (!err) {
  nh_rt_cache_flush(net, old, new);

  __remove_nexthop(net, new, NULL);
  nexthop_put(new);
 }

 return err;
}

/* called with rtnl_lock held */
static int insert_nexthop(struct net *net, struct nexthop *new_nh,
     struct nh_config *cfg, struct netlink_ext_ack *extack)
{
 struct rb_node **pp, *parent = NULL, *next;
 struct rb_root *root = &net->nexthop.rb_root;
 bool replace = !!(cfg->nlflags & NLM_F_REPLACE);
--> --------------------

--> maximum size reached

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

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

¤ Dauer der Verarbeitung: 0.26 Sekunden  ¤

*© Formatika GbR, Deutschland






Normalansicht

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge