1ab84be7eSDavid Ahern // SPDX-License-Identifier: GPL-2.0 2ab84be7eSDavid Ahern /* Generic nexthop implementation 3ab84be7eSDavid Ahern * 4ab84be7eSDavid Ahern * Copyright (c) 2017-19 Cumulus Networks 5ab84be7eSDavid Ahern * Copyright (c) 2017-19 David Ahern <dsa@cumulusnetworks.com> 6ab84be7eSDavid Ahern */ 7ab84be7eSDavid Ahern 8ab84be7eSDavid Ahern #include <linux/nexthop.h> 9ab84be7eSDavid Ahern #include <linux/rtnetlink.h> 10ab84be7eSDavid Ahern #include <linux/slab.h> 11b6459415SJakub Kicinski #include <linux/vmalloc.h> 12430a0491SDavid Ahern #include <net/arp.h> 1353010f99SDavid Ahern #include <net/ipv6_stubs.h> 14b513bd03SDavid Ahern #include <net/lwtunnel.h> 15430a0491SDavid Ahern #include <net/ndisc.h> 16ab84be7eSDavid Ahern #include <net/nexthop.h> 17597cfe4fSDavid Ahern #include <net/route.h> 18ab84be7eSDavid Ahern #include <net/sock.h> 19ab84be7eSDavid Ahern 20a2601e2bSPetr Machata #define NH_RES_DEFAULT_IDLE_TIMER (120 * HZ) 21a2601e2bSPetr Machata #define NH_RES_DEFAULT_UNBALANCED_TIMER 0 /* No forced rebalancing. */ 22a2601e2bSPetr Machata 23430a0491SDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh, 24430a0491SDavid Ahern struct nl_info *nlinfo); 25430a0491SDavid Ahern 26597cfe4fSDavid Ahern #define NH_DEV_HASHBITS 8 27597cfe4fSDavid Ahern #define NH_DEV_HASHSIZE (1U << NH_DEV_HASHBITS) 28597cfe4fSDavid Ahern 2995fedd76SIdo Schimmel #define NHA_OP_FLAGS_DUMP_ALL (NHA_OP_FLAG_DUMP_STATS) 3095fedd76SIdo Schimmel 31643d0878SPetr Machata static const struct nla_policy rtm_nh_policy_new[] = { 32ab84be7eSDavid Ahern [NHA_ID] = { .type = NLA_U32 }, 33ab84be7eSDavid Ahern [NHA_GROUP] = { .type = NLA_BINARY }, 34ab84be7eSDavid Ahern [NHA_GROUP_TYPE] = { .type = NLA_U16 }, 35ab84be7eSDavid Ahern [NHA_BLACKHOLE] = { .type = NLA_FLAG }, 36ab84be7eSDavid Ahern [NHA_OIF] = { .type = NLA_U32 }, 37ab84be7eSDavid Ahern [NHA_GATEWAY] = { .type = NLA_BINARY }, 38ab84be7eSDavid Ahern [NHA_ENCAP_TYPE] = { .type = NLA_U16 }, 39ab84be7eSDavid Ahern [NHA_ENCAP] = { .type = NLA_NESTED }, 4038428d68SRoopa Prabhu [NHA_FDB] = { .type = NLA_FLAG }, 41a2601e2bSPetr Machata [NHA_RES_GROUP] = { .type = NLA_NESTED }, 42*746c19a5SIdo Schimmel [NHA_HW_STATS_ENABLE] = NLA_POLICY_MAX(NLA_U32, true), 43ab84be7eSDavid Ahern }; 44ab84be7eSDavid Ahern 4560f5ad5eSPetr Machata static const struct nla_policy rtm_nh_policy_get[] = { 4660f5ad5eSPetr Machata [NHA_ID] = { .type = NLA_U32 }, 4795fedd76SIdo Schimmel [NHA_OP_FLAGS] = NLA_POLICY_MASK(NLA_U32, 4895fedd76SIdo Schimmel NHA_OP_FLAGS_DUMP_ALL), 4960f5ad5eSPetr Machata }; 5060f5ad5eSPetr Machata 512118f939SPetr Machata static const struct nla_policy rtm_nh_policy_del[] = { 522118f939SPetr Machata [NHA_ID] = { .type = NLA_U32 }, 532118f939SPetr Machata }; 542118f939SPetr Machata 5544551bffSPetr Machata static const struct nla_policy rtm_nh_policy_dump[] = { 5644551bffSPetr Machata [NHA_OIF] = { .type = NLA_U32 }, 5744551bffSPetr Machata [NHA_GROUPS] = { .type = NLA_FLAG }, 5844551bffSPetr Machata [NHA_MASTER] = { .type = NLA_U32 }, 5944551bffSPetr Machata [NHA_FDB] = { .type = NLA_FLAG }, 6095fedd76SIdo Schimmel [NHA_OP_FLAGS] = NLA_POLICY_MASK(NLA_U32, 6195fedd76SIdo Schimmel NHA_OP_FLAGS_DUMP_ALL), 6244551bffSPetr Machata }; 6344551bffSPetr Machata 64a2601e2bSPetr Machata static const struct nla_policy rtm_nh_res_policy_new[] = { 65a2601e2bSPetr Machata [NHA_RES_GROUP_BUCKETS] = { .type = NLA_U16 }, 66a2601e2bSPetr Machata [NHA_RES_GROUP_IDLE_TIMER] = { .type = NLA_U32 }, 67a2601e2bSPetr Machata [NHA_RES_GROUP_UNBALANCED_TIMER] = { .type = NLA_U32 }, 68a2601e2bSPetr Machata }; 69a2601e2bSPetr Machata 708a1bbabbSPetr Machata static const struct nla_policy rtm_nh_policy_dump_bucket[] = { 718a1bbabbSPetr Machata [NHA_ID] = { .type = NLA_U32 }, 728a1bbabbSPetr Machata [NHA_OIF] = { .type = NLA_U32 }, 738a1bbabbSPetr Machata [NHA_MASTER] = { .type = NLA_U32 }, 748a1bbabbSPetr Machata [NHA_RES_BUCKET] = { .type = NLA_NESTED }, 758a1bbabbSPetr Machata }; 768a1bbabbSPetr Machata 778a1bbabbSPetr Machata static const struct nla_policy rtm_nh_res_bucket_policy_dump[] = { 788a1bbabbSPetr Machata [NHA_RES_BUCKET_NH_ID] = { .type = NLA_U32 }, 798a1bbabbSPetr Machata }; 808a1bbabbSPetr Machata 81187d4c6bSPetr Machata static const struct nla_policy rtm_nh_policy_get_bucket[] = { 82187d4c6bSPetr Machata [NHA_ID] = { .type = NLA_U32 }, 83187d4c6bSPetr Machata [NHA_RES_BUCKET] = { .type = NLA_NESTED }, 84187d4c6bSPetr Machata }; 85187d4c6bSPetr Machata 86187d4c6bSPetr Machata static const struct nla_policy rtm_nh_res_bucket_policy_get[] = { 87187d4c6bSPetr Machata [NHA_RES_BUCKET_INDEX] = { .type = NLA_U16 }, 88187d4c6bSPetr Machata }; 89187d4c6bSPetr Machata 905ca474f2SIdo Schimmel static bool nexthop_notifiers_is_empty(struct net *net) 915ca474f2SIdo Schimmel { 925ca474f2SIdo Schimmel return !net->nexthop.notifier_chain.head; 935ca474f2SIdo Schimmel } 945ca474f2SIdo Schimmel 955ca474f2SIdo Schimmel static void 965ca474f2SIdo Schimmel __nh_notifier_single_info_init(struct nh_notifier_single_info *nh_info, 9796a85625SPetr Machata const struct nh_info *nhi) 985ca474f2SIdo Schimmel { 995ca474f2SIdo Schimmel nh_info->dev = nhi->fib_nhc.nhc_dev; 1005ca474f2SIdo Schimmel nh_info->gw_family = nhi->fib_nhc.nhc_gw_family; 1015ca474f2SIdo Schimmel if (nh_info->gw_family == AF_INET) 1025ca474f2SIdo Schimmel nh_info->ipv4 = nhi->fib_nhc.nhc_gw.ipv4; 1035ca474f2SIdo Schimmel else if (nh_info->gw_family == AF_INET6) 1045ca474f2SIdo Schimmel nh_info->ipv6 = nhi->fib_nhc.nhc_gw.ipv6; 1055ca474f2SIdo Schimmel 1065ca474f2SIdo Schimmel nh_info->is_reject = nhi->reject_nh; 1075ca474f2SIdo Schimmel nh_info->is_fdb = nhi->fdb_nh; 1085ca474f2SIdo Schimmel nh_info->has_encap = !!nhi->fib_nhc.nhc_lwtstate; 1095ca474f2SIdo Schimmel } 1105ca474f2SIdo Schimmel 1115ca474f2SIdo Schimmel static int nh_notifier_single_info_init(struct nh_notifier_info *info, 1125ca474f2SIdo Schimmel const struct nexthop *nh) 1135ca474f2SIdo Schimmel { 11496a85625SPetr Machata struct nh_info *nhi = rtnl_dereference(nh->nh_info); 11596a85625SPetr Machata 11609ad6becSIdo Schimmel info->type = NH_NOTIFIER_INFO_TYPE_SINGLE; 1175ca474f2SIdo Schimmel info->nh = kzalloc(sizeof(*info->nh), GFP_KERNEL); 1185ca474f2SIdo Schimmel if (!info->nh) 1195ca474f2SIdo Schimmel return -ENOMEM; 1205ca474f2SIdo Schimmel 12196a85625SPetr Machata __nh_notifier_single_info_init(info->nh, nhi); 1225ca474f2SIdo Schimmel 1235ca474f2SIdo Schimmel return 0; 1245ca474f2SIdo Schimmel } 1255ca474f2SIdo Schimmel 1265ca474f2SIdo Schimmel static void nh_notifier_single_info_fini(struct nh_notifier_info *info) 1275ca474f2SIdo Schimmel { 1285ca474f2SIdo Schimmel kfree(info->nh); 1295ca474f2SIdo Schimmel } 1305ca474f2SIdo Schimmel 131de1d1ee3SPetr Machata static int nh_notifier_mpath_info_init(struct nh_notifier_info *info, 132da230501SPetr Machata struct nh_group *nhg) 1335ca474f2SIdo Schimmel { 1345ca474f2SIdo Schimmel u16 num_nh = nhg->num_nh; 1355ca474f2SIdo Schimmel int i; 1365ca474f2SIdo Schimmel 13709ad6becSIdo Schimmel info->type = NH_NOTIFIER_INFO_TYPE_GRP; 1385ca474f2SIdo Schimmel info->nh_grp = kzalloc(struct_size(info->nh_grp, nh_entries, num_nh), 1395ca474f2SIdo Schimmel GFP_KERNEL); 1405ca474f2SIdo Schimmel if (!info->nh_grp) 1415ca474f2SIdo Schimmel return -ENOMEM; 1425ca474f2SIdo Schimmel 1435ca474f2SIdo Schimmel info->nh_grp->num_nh = num_nh; 1445ca474f2SIdo Schimmel info->nh_grp->is_fdb = nhg->fdb_nh; 1455877786fSIdo Schimmel info->nh_grp->hw_stats = nhg->hw_stats; 1465ca474f2SIdo Schimmel 1475ca474f2SIdo Schimmel for (i = 0; i < num_nh; i++) { 1485ca474f2SIdo Schimmel struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 14996a85625SPetr Machata struct nh_info *nhi; 1505ca474f2SIdo Schimmel 15196a85625SPetr Machata nhi = rtnl_dereference(nhge->nh->nh_info); 1525ca474f2SIdo Schimmel info->nh_grp->nh_entries[i].id = nhge->nh->id; 1535ca474f2SIdo Schimmel info->nh_grp->nh_entries[i].weight = nhge->weight; 1545ca474f2SIdo Schimmel __nh_notifier_single_info_init(&info->nh_grp->nh_entries[i].nh, 15596a85625SPetr Machata nhi); 1565ca474f2SIdo Schimmel } 1575ca474f2SIdo Schimmel 1585ca474f2SIdo Schimmel return 0; 1595ca474f2SIdo Schimmel } 1605ca474f2SIdo Schimmel 1617c37c7e0SPetr Machata static int nh_notifier_res_table_info_init(struct nh_notifier_info *info, 1627c37c7e0SPetr Machata struct nh_group *nhg) 1637c37c7e0SPetr Machata { 1647c37c7e0SPetr Machata struct nh_res_table *res_table = rtnl_dereference(nhg->res_table); 1657c37c7e0SPetr Machata u16 num_nh_buckets = res_table->num_nh_buckets; 1667c37c7e0SPetr Machata unsigned long size; 1677c37c7e0SPetr Machata u16 i; 1687c37c7e0SPetr Machata 1697c37c7e0SPetr Machata info->type = NH_NOTIFIER_INFO_TYPE_RES_TABLE; 1707c37c7e0SPetr Machata size = struct_size(info->nh_res_table, nhs, num_nh_buckets); 1717c37c7e0SPetr Machata info->nh_res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO | 1727c37c7e0SPetr Machata __GFP_NOWARN); 1737c37c7e0SPetr Machata if (!info->nh_res_table) 1747c37c7e0SPetr Machata return -ENOMEM; 1757c37c7e0SPetr Machata 1767c37c7e0SPetr Machata info->nh_res_table->num_nh_buckets = num_nh_buckets; 1775877786fSIdo Schimmel info->nh_res_table->hw_stats = nhg->hw_stats; 1787c37c7e0SPetr Machata 1797c37c7e0SPetr Machata for (i = 0; i < num_nh_buckets; i++) { 1807c37c7e0SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 1817c37c7e0SPetr Machata struct nh_grp_entry *nhge; 1827c37c7e0SPetr Machata struct nh_info *nhi; 1837c37c7e0SPetr Machata 1847c37c7e0SPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 1857c37c7e0SPetr Machata nhi = rtnl_dereference(nhge->nh->nh_info); 1867c37c7e0SPetr Machata __nh_notifier_single_info_init(&info->nh_res_table->nhs[i], 1877c37c7e0SPetr Machata nhi); 1887c37c7e0SPetr Machata } 1897c37c7e0SPetr Machata 1907c37c7e0SPetr Machata return 0; 1917c37c7e0SPetr Machata } 1927c37c7e0SPetr Machata 193da230501SPetr Machata static int nh_notifier_grp_info_init(struct nh_notifier_info *info, 194da230501SPetr Machata const struct nexthop *nh) 1955ca474f2SIdo Schimmel { 196da230501SPetr Machata struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 197da230501SPetr Machata 198de1d1ee3SPetr Machata if (nhg->hash_threshold) 199de1d1ee3SPetr Machata return nh_notifier_mpath_info_init(info, nhg); 2007c37c7e0SPetr Machata else if (nhg->resilient) 2017c37c7e0SPetr Machata return nh_notifier_res_table_info_init(info, nhg); 202da230501SPetr Machata return -EINVAL; 203da230501SPetr Machata } 204da230501SPetr Machata 205da230501SPetr Machata static void nh_notifier_grp_info_fini(struct nh_notifier_info *info, 206da230501SPetr Machata const struct nexthop *nh) 207da230501SPetr Machata { 208da230501SPetr Machata struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 209da230501SPetr Machata 210de1d1ee3SPetr Machata if (nhg->hash_threshold) 2115ca474f2SIdo Schimmel kfree(info->nh_grp); 2127c37c7e0SPetr Machata else if (nhg->resilient) 2137c37c7e0SPetr Machata vfree(info->nh_res_table); 2145ca474f2SIdo Schimmel } 2155ca474f2SIdo Schimmel 2165ca474f2SIdo Schimmel static int nh_notifier_info_init(struct nh_notifier_info *info, 2175ca474f2SIdo Schimmel const struct nexthop *nh) 2185ca474f2SIdo Schimmel { 2195ca474f2SIdo Schimmel info->id = nh->id; 2205ca474f2SIdo Schimmel 22109ad6becSIdo Schimmel if (nh->is_group) 2225ca474f2SIdo Schimmel return nh_notifier_grp_info_init(info, nh); 2235ca474f2SIdo Schimmel else 2245ca474f2SIdo Schimmel return nh_notifier_single_info_init(info, nh); 2255ca474f2SIdo Schimmel } 2265ca474f2SIdo Schimmel 22709ad6becSIdo Schimmel static void nh_notifier_info_fini(struct nh_notifier_info *info, 22809ad6becSIdo Schimmel const struct nexthop *nh) 2295ca474f2SIdo Schimmel { 23009ad6becSIdo Schimmel if (nh->is_group) 231da230501SPetr Machata nh_notifier_grp_info_fini(info, nh); 2325ca474f2SIdo Schimmel else 2335ca474f2SIdo Schimmel nh_notifier_single_info_fini(info); 2345ca474f2SIdo Schimmel } 2355ca474f2SIdo Schimmel 2368590ceedSRoopa Prabhu static int call_nexthop_notifiers(struct net *net, 237d8e79f1dSNathan Chancellor enum nexthop_event_type event_type, 2383578d53dSIdo Schimmel struct nexthop *nh, 2393578d53dSIdo Schimmel struct netlink_ext_ack *extack) 2408590ceedSRoopa Prabhu { 2415ca474f2SIdo Schimmel struct nh_notifier_info info = { 2425ca474f2SIdo Schimmel .net = net, 2435ca474f2SIdo Schimmel .extack = extack, 2445ca474f2SIdo Schimmel }; 2458590ceedSRoopa Prabhu int err; 2468590ceedSRoopa Prabhu 2475ca474f2SIdo Schimmel ASSERT_RTNL(); 2485ca474f2SIdo Schimmel 2495ca474f2SIdo Schimmel if (nexthop_notifiers_is_empty(net)) 2505ca474f2SIdo Schimmel return 0; 2515ca474f2SIdo Schimmel 2525ca474f2SIdo Schimmel err = nh_notifier_info_init(&info, nh); 2535ca474f2SIdo Schimmel if (err) { 2545ca474f2SIdo Schimmel NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info"); 2555ca474f2SIdo Schimmel return err; 2565ca474f2SIdo Schimmel } 2575ca474f2SIdo Schimmel 25880690ec6SIdo Schimmel err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 2591ec69d18SIdo Schimmel event_type, &info); 26009ad6becSIdo Schimmel nh_notifier_info_fini(&info, nh); 2615ca474f2SIdo Schimmel 2628590ceedSRoopa Prabhu return notifier_to_errno(err); 2638590ceedSRoopa Prabhu } 2648590ceedSRoopa Prabhu 2657c37c7e0SPetr Machata static int 2667c37c7e0SPetr Machata nh_notifier_res_bucket_idle_timer_get(const struct nh_notifier_info *info, 2677c37c7e0SPetr Machata bool force, unsigned int *p_idle_timer_ms) 2687c37c7e0SPetr Machata { 2697c37c7e0SPetr Machata struct nh_res_table *res_table; 2707c37c7e0SPetr Machata struct nh_group *nhg; 2717c37c7e0SPetr Machata struct nexthop *nh; 2727c37c7e0SPetr Machata int err = 0; 2737c37c7e0SPetr Machata 2747c37c7e0SPetr Machata /* When 'force' is false, nexthop bucket replacement is performed 2757c37c7e0SPetr Machata * because the bucket was deemed to be idle. In this case, capable 2767c37c7e0SPetr Machata * listeners can choose to perform an atomic replacement: The bucket is 2777c37c7e0SPetr Machata * only replaced if it is inactive. However, if the idle timer interval 2787c37c7e0SPetr Machata * is smaller than the interval in which a listener is querying 2797c37c7e0SPetr Machata * buckets' activity from the device, then atomic replacement should 2807c37c7e0SPetr Machata * not be tried. Pass the idle timer value to listeners, so that they 2817c37c7e0SPetr Machata * could determine which type of replacement to perform. 2827c37c7e0SPetr Machata */ 2837c37c7e0SPetr Machata if (force) { 2847c37c7e0SPetr Machata *p_idle_timer_ms = 0; 2857c37c7e0SPetr Machata return 0; 2867c37c7e0SPetr Machata } 2877c37c7e0SPetr Machata 2887c37c7e0SPetr Machata rcu_read_lock(); 2897c37c7e0SPetr Machata 2907c37c7e0SPetr Machata nh = nexthop_find_by_id(info->net, info->id); 2917c37c7e0SPetr Machata if (!nh) { 2927c37c7e0SPetr Machata err = -EINVAL; 2937c37c7e0SPetr Machata goto out; 2947c37c7e0SPetr Machata } 2957c37c7e0SPetr Machata 2967c37c7e0SPetr Machata nhg = rcu_dereference(nh->nh_grp); 2977c37c7e0SPetr Machata res_table = rcu_dereference(nhg->res_table); 2987c37c7e0SPetr Machata *p_idle_timer_ms = jiffies_to_msecs(res_table->idle_timer); 2997c37c7e0SPetr Machata 3007c37c7e0SPetr Machata out: 3017c37c7e0SPetr Machata rcu_read_unlock(); 3027c37c7e0SPetr Machata 3037c37c7e0SPetr Machata return err; 3047c37c7e0SPetr Machata } 3057c37c7e0SPetr Machata 3067c37c7e0SPetr Machata static int nh_notifier_res_bucket_info_init(struct nh_notifier_info *info, 3077c37c7e0SPetr Machata u16 bucket_index, bool force, 3087c37c7e0SPetr Machata struct nh_info *oldi, 3097c37c7e0SPetr Machata struct nh_info *newi) 3107c37c7e0SPetr Machata { 3117c37c7e0SPetr Machata unsigned int idle_timer_ms; 3127c37c7e0SPetr Machata int err; 3137c37c7e0SPetr Machata 3147c37c7e0SPetr Machata err = nh_notifier_res_bucket_idle_timer_get(info, force, 3157c37c7e0SPetr Machata &idle_timer_ms); 3167c37c7e0SPetr Machata if (err) 3177c37c7e0SPetr Machata return err; 3187c37c7e0SPetr Machata 3197c37c7e0SPetr Machata info->type = NH_NOTIFIER_INFO_TYPE_RES_BUCKET; 3207c37c7e0SPetr Machata info->nh_res_bucket = kzalloc(sizeof(*info->nh_res_bucket), 3217c37c7e0SPetr Machata GFP_KERNEL); 3227c37c7e0SPetr Machata if (!info->nh_res_bucket) 3237c37c7e0SPetr Machata return -ENOMEM; 3247c37c7e0SPetr Machata 3257c37c7e0SPetr Machata info->nh_res_bucket->bucket_index = bucket_index; 3267c37c7e0SPetr Machata info->nh_res_bucket->idle_timer_ms = idle_timer_ms; 3277c37c7e0SPetr Machata info->nh_res_bucket->force = force; 3287c37c7e0SPetr Machata __nh_notifier_single_info_init(&info->nh_res_bucket->old_nh, oldi); 3297c37c7e0SPetr Machata __nh_notifier_single_info_init(&info->nh_res_bucket->new_nh, newi); 3307c37c7e0SPetr Machata return 0; 3317c37c7e0SPetr Machata } 3327c37c7e0SPetr Machata 3337c37c7e0SPetr Machata static void nh_notifier_res_bucket_info_fini(struct nh_notifier_info *info) 3347c37c7e0SPetr Machata { 3357c37c7e0SPetr Machata kfree(info->nh_res_bucket); 3367c37c7e0SPetr Machata } 3377c37c7e0SPetr Machata 3387c37c7e0SPetr Machata static int __call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id, 3397c37c7e0SPetr Machata u16 bucket_index, bool force, 3407c37c7e0SPetr Machata struct nh_info *oldi, 3417c37c7e0SPetr Machata struct nh_info *newi, 3427c37c7e0SPetr Machata struct netlink_ext_ack *extack) 3437c37c7e0SPetr Machata { 3447c37c7e0SPetr Machata struct nh_notifier_info info = { 3457c37c7e0SPetr Machata .net = net, 3467c37c7e0SPetr Machata .extack = extack, 3477c37c7e0SPetr Machata .id = nhg_id, 3487c37c7e0SPetr Machata }; 3497c37c7e0SPetr Machata int err; 3507c37c7e0SPetr Machata 3517c37c7e0SPetr Machata if (nexthop_notifiers_is_empty(net)) 3527c37c7e0SPetr Machata return 0; 3537c37c7e0SPetr Machata 3547c37c7e0SPetr Machata err = nh_notifier_res_bucket_info_init(&info, bucket_index, force, 3557c37c7e0SPetr Machata oldi, newi); 3567c37c7e0SPetr Machata if (err) 3577c37c7e0SPetr Machata return err; 3587c37c7e0SPetr Machata 3597c37c7e0SPetr Machata err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 3607c37c7e0SPetr Machata NEXTHOP_EVENT_BUCKET_REPLACE, &info); 3617c37c7e0SPetr Machata nh_notifier_res_bucket_info_fini(&info); 3627c37c7e0SPetr Machata 3637c37c7e0SPetr Machata return notifier_to_errno(err); 3647c37c7e0SPetr Machata } 3657c37c7e0SPetr Machata 366283a72a5SPetr Machata /* There are three users of RES_TABLE, and NHs etc. referenced from there: 367283a72a5SPetr Machata * 368283a72a5SPetr Machata * 1) a collection of callbacks for NH maintenance. This operates under 369283a72a5SPetr Machata * RTNL, 370283a72a5SPetr Machata * 2) the delayed work that gradually balances the resilient table, 371283a72a5SPetr Machata * 3) and nexthop_select_path(), operating under RCU. 372283a72a5SPetr Machata * 373283a72a5SPetr Machata * Both the delayed work and the RTNL block are writers, and need to 374283a72a5SPetr Machata * maintain mutual exclusion. Since there are only two and well-known 375283a72a5SPetr Machata * writers for each table, the RTNL code can make sure it has exclusive 376283a72a5SPetr Machata * access thus: 377283a72a5SPetr Machata * 378283a72a5SPetr Machata * - Have the DW operate without locking; 379283a72a5SPetr Machata * - synchronously cancel the DW; 380283a72a5SPetr Machata * - do the writing; 381283a72a5SPetr Machata * - if the write was not actually a delete, call upkeep, which schedules 382283a72a5SPetr Machata * DW again if necessary. 383283a72a5SPetr Machata * 384283a72a5SPetr Machata * The functions that are always called from the RTNL context use 385283a72a5SPetr Machata * rtnl_dereference(). The functions that can also be called from the DW do 386283a72a5SPetr Machata * a raw dereference and rely on the above mutual exclusion scheme. 387283a72a5SPetr Machata */ 388283a72a5SPetr Machata #define nh_res_dereference(p) (rcu_dereference_raw(p)) 389283a72a5SPetr Machata 3907c37c7e0SPetr Machata static int call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id, 3917c37c7e0SPetr Machata u16 bucket_index, bool force, 3927c37c7e0SPetr Machata struct nexthop *old_nh, 3937c37c7e0SPetr Machata struct nexthop *new_nh, 3947c37c7e0SPetr Machata struct netlink_ext_ack *extack) 3957c37c7e0SPetr Machata { 3967c37c7e0SPetr Machata struct nh_info *oldi = nh_res_dereference(old_nh->nh_info); 3977c37c7e0SPetr Machata struct nh_info *newi = nh_res_dereference(new_nh->nh_info); 3987c37c7e0SPetr Machata 3997c37c7e0SPetr Machata return __call_nexthop_res_bucket_notifiers(net, nhg_id, bucket_index, 4007c37c7e0SPetr Machata force, oldi, newi, extack); 4017c37c7e0SPetr Machata } 4027c37c7e0SPetr Machata 4037c37c7e0SPetr Machata static int call_nexthop_res_table_notifiers(struct net *net, struct nexthop *nh, 4047c37c7e0SPetr Machata struct netlink_ext_ack *extack) 4057c37c7e0SPetr Machata { 4067c37c7e0SPetr Machata struct nh_notifier_info info = { 4077c37c7e0SPetr Machata .net = net, 4087c37c7e0SPetr Machata .extack = extack, 4097c37c7e0SPetr Machata }; 4107c37c7e0SPetr Machata struct nh_group *nhg; 4117c37c7e0SPetr Machata int err; 4127c37c7e0SPetr Machata 4137c37c7e0SPetr Machata ASSERT_RTNL(); 4147c37c7e0SPetr Machata 4157c37c7e0SPetr Machata if (nexthop_notifiers_is_empty(net)) 4167c37c7e0SPetr Machata return 0; 4177c37c7e0SPetr Machata 4187c37c7e0SPetr Machata /* At this point, the nexthop buckets are still not populated. Only 4197c37c7e0SPetr Machata * emit a notification with the logical nexthops, so that a listener 4207c37c7e0SPetr Machata * could potentially veto it in case of unsupported configuration. 4217c37c7e0SPetr Machata */ 4227c37c7e0SPetr Machata nhg = rtnl_dereference(nh->nh_grp); 423de1d1ee3SPetr Machata err = nh_notifier_mpath_info_init(&info, nhg); 4247c37c7e0SPetr Machata if (err) { 4257c37c7e0SPetr Machata NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info"); 4267c37c7e0SPetr Machata return err; 4277c37c7e0SPetr Machata } 4287c37c7e0SPetr Machata 4297c37c7e0SPetr Machata err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 4307c37c7e0SPetr Machata NEXTHOP_EVENT_RES_TABLE_PRE_REPLACE, 4317c37c7e0SPetr Machata &info); 4327c37c7e0SPetr Machata kfree(info.nh_grp); 4337c37c7e0SPetr Machata 4347c37c7e0SPetr Machata return notifier_to_errno(err); 4357c37c7e0SPetr Machata } 4367c37c7e0SPetr Machata 437975ff7f3SIdo Schimmel static int call_nexthop_notifier(struct notifier_block *nb, struct net *net, 438975ff7f3SIdo Schimmel enum nexthop_event_type event_type, 439975ff7f3SIdo Schimmel struct nexthop *nh, 440975ff7f3SIdo Schimmel struct netlink_ext_ack *extack) 441975ff7f3SIdo Schimmel { 442975ff7f3SIdo Schimmel struct nh_notifier_info info = { 443975ff7f3SIdo Schimmel .net = net, 444975ff7f3SIdo Schimmel .extack = extack, 445975ff7f3SIdo Schimmel }; 446975ff7f3SIdo Schimmel int err; 447975ff7f3SIdo Schimmel 448975ff7f3SIdo Schimmel err = nh_notifier_info_init(&info, nh); 449975ff7f3SIdo Schimmel if (err) 450975ff7f3SIdo Schimmel return err; 451975ff7f3SIdo Schimmel 452975ff7f3SIdo Schimmel err = nb->notifier_call(nb, event_type, &info); 45309ad6becSIdo Schimmel nh_notifier_info_fini(&info, nh); 454975ff7f3SIdo Schimmel 455975ff7f3SIdo Schimmel return notifier_to_errno(err); 456975ff7f3SIdo Schimmel } 457975ff7f3SIdo Schimmel 458597cfe4fSDavid Ahern static unsigned int nh_dev_hashfn(unsigned int val) 459597cfe4fSDavid Ahern { 460597cfe4fSDavid Ahern unsigned int mask = NH_DEV_HASHSIZE - 1; 461597cfe4fSDavid Ahern 462597cfe4fSDavid Ahern return (val ^ 463597cfe4fSDavid Ahern (val >> NH_DEV_HASHBITS) ^ 464597cfe4fSDavid Ahern (val >> (NH_DEV_HASHBITS * 2))) & mask; 465597cfe4fSDavid Ahern } 466597cfe4fSDavid Ahern 467597cfe4fSDavid Ahern static void nexthop_devhash_add(struct net *net, struct nh_info *nhi) 468597cfe4fSDavid Ahern { 469597cfe4fSDavid Ahern struct net_device *dev = nhi->fib_nhc.nhc_dev; 470597cfe4fSDavid Ahern struct hlist_head *head; 471597cfe4fSDavid Ahern unsigned int hash; 472597cfe4fSDavid Ahern 473597cfe4fSDavid Ahern WARN_ON(!dev); 474597cfe4fSDavid Ahern 475597cfe4fSDavid Ahern hash = nh_dev_hashfn(dev->ifindex); 476597cfe4fSDavid Ahern head = &net->nexthop.devhash[hash]; 477597cfe4fSDavid Ahern hlist_add_head(&nhi->dev_hash, head); 478597cfe4fSDavid Ahern } 479597cfe4fSDavid Ahern 4805d1f0f09SDavid Ahern static void nexthop_free_group(struct nexthop *nh) 481ab84be7eSDavid Ahern { 482430a0491SDavid Ahern struct nh_group *nhg; 483430a0491SDavid Ahern int i; 484430a0491SDavid Ahern 485430a0491SDavid Ahern nhg = rcu_dereference_raw(nh->nh_grp); 48690f33bffSNikolay Aleksandrov for (i = 0; i < nhg->num_nh; ++i) { 48790f33bffSNikolay Aleksandrov struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 488430a0491SDavid Ahern 48990f33bffSNikolay Aleksandrov WARN_ON(!list_empty(&nhge->nh_list)); 490f4676ea7SIdo Schimmel free_percpu(nhge->stats); 49190f33bffSNikolay Aleksandrov nexthop_put(nhge->nh); 49290f33bffSNikolay Aleksandrov } 49390f33bffSNikolay Aleksandrov 49490f33bffSNikolay Aleksandrov WARN_ON(nhg->spare == nhg); 49590f33bffSNikolay Aleksandrov 496283a72a5SPetr Machata if (nhg->resilient) 497283a72a5SPetr Machata vfree(rcu_dereference_raw(nhg->res_table)); 498283a72a5SPetr Machata 49990f33bffSNikolay Aleksandrov kfree(nhg->spare); 500430a0491SDavid Ahern kfree(nhg); 501430a0491SDavid Ahern } 502430a0491SDavid Ahern 503430a0491SDavid Ahern static void nexthop_free_single(struct nexthop *nh) 504430a0491SDavid Ahern { 505ab84be7eSDavid Ahern struct nh_info *nhi; 506ab84be7eSDavid Ahern 507ab84be7eSDavid Ahern nhi = rcu_dereference_raw(nh->nh_info); 508597cfe4fSDavid Ahern switch (nhi->family) { 509597cfe4fSDavid Ahern case AF_INET: 510597cfe4fSDavid Ahern fib_nh_release(nh->net, &nhi->fib_nh); 511597cfe4fSDavid Ahern break; 51253010f99SDavid Ahern case AF_INET6: 51353010f99SDavid Ahern ipv6_stub->fib6_nh_release(&nhi->fib6_nh); 51453010f99SDavid Ahern break; 515597cfe4fSDavid Ahern } 516ab84be7eSDavid Ahern kfree(nhi); 517430a0491SDavid Ahern } 518430a0491SDavid Ahern 519430a0491SDavid Ahern void nexthop_free_rcu(struct rcu_head *head) 520430a0491SDavid Ahern { 521430a0491SDavid Ahern struct nexthop *nh = container_of(head, struct nexthop, rcu); 522430a0491SDavid Ahern 523430a0491SDavid Ahern if (nh->is_group) 5245d1f0f09SDavid Ahern nexthop_free_group(nh); 525430a0491SDavid Ahern else 526430a0491SDavid Ahern nexthop_free_single(nh); 527ab84be7eSDavid Ahern 528ab84be7eSDavid Ahern kfree(nh); 529ab84be7eSDavid Ahern } 530ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_free_rcu); 531ab84be7eSDavid Ahern 532ab84be7eSDavid Ahern static struct nexthop *nexthop_alloc(void) 533ab84be7eSDavid Ahern { 534ab84be7eSDavid Ahern struct nexthop *nh; 535ab84be7eSDavid Ahern 536ab84be7eSDavid Ahern nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL); 537430a0491SDavid Ahern if (nh) { 5384c7e8084SDavid Ahern INIT_LIST_HEAD(&nh->fi_list); 539f88d8ea6SDavid Ahern INIT_LIST_HEAD(&nh->f6i_list); 540430a0491SDavid Ahern INIT_LIST_HEAD(&nh->grp_list); 54138428d68SRoopa Prabhu INIT_LIST_HEAD(&nh->fdb_list); 542430a0491SDavid Ahern } 543ab84be7eSDavid Ahern return nh; 544ab84be7eSDavid Ahern } 545ab84be7eSDavid Ahern 546430a0491SDavid Ahern static struct nh_group *nexthop_grp_alloc(u16 num_nh) 547430a0491SDavid Ahern { 548430a0491SDavid Ahern struct nh_group *nhg; 549430a0491SDavid Ahern 550d7d49dc7SIdo Schimmel nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL); 551430a0491SDavid Ahern if (nhg) 552430a0491SDavid Ahern nhg->num_nh = num_nh; 553430a0491SDavid Ahern 554430a0491SDavid Ahern return nhg; 555430a0491SDavid Ahern } 556430a0491SDavid Ahern 557283a72a5SPetr Machata static void nh_res_table_upkeep_dw(struct work_struct *work); 558283a72a5SPetr Machata 559283a72a5SPetr Machata static struct nh_res_table * 560283a72a5SPetr Machata nexthop_res_table_alloc(struct net *net, u32 nhg_id, struct nh_config *cfg) 561283a72a5SPetr Machata { 562283a72a5SPetr Machata const u16 num_nh_buckets = cfg->nh_grp_res_num_buckets; 563283a72a5SPetr Machata struct nh_res_table *res_table; 564283a72a5SPetr Machata unsigned long size; 565283a72a5SPetr Machata 566283a72a5SPetr Machata size = struct_size(res_table, nh_buckets, num_nh_buckets); 567283a72a5SPetr Machata res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN); 568283a72a5SPetr Machata if (!res_table) 569283a72a5SPetr Machata return NULL; 570283a72a5SPetr Machata 571283a72a5SPetr Machata res_table->net = net; 572283a72a5SPetr Machata res_table->nhg_id = nhg_id; 573283a72a5SPetr Machata INIT_DELAYED_WORK(&res_table->upkeep_dw, &nh_res_table_upkeep_dw); 574283a72a5SPetr Machata INIT_LIST_HEAD(&res_table->uw_nh_entries); 575283a72a5SPetr Machata res_table->idle_timer = cfg->nh_grp_res_idle_timer; 576283a72a5SPetr Machata res_table->unbalanced_timer = cfg->nh_grp_res_unbalanced_timer; 577283a72a5SPetr Machata res_table->num_nh_buckets = num_nh_buckets; 578283a72a5SPetr Machata return res_table; 579283a72a5SPetr Machata } 580283a72a5SPetr Machata 581ab84be7eSDavid Ahern static void nh_base_seq_inc(struct net *net) 582ab84be7eSDavid Ahern { 583ab84be7eSDavid Ahern while (++net->nexthop.seq == 0) 584ab84be7eSDavid Ahern ; 585ab84be7eSDavid Ahern } 586ab84be7eSDavid Ahern 587ab84be7eSDavid Ahern /* no reference taken; rcu lock or rtnl must be held */ 588ab84be7eSDavid Ahern struct nexthop *nexthop_find_by_id(struct net *net, u32 id) 589ab84be7eSDavid Ahern { 590ab84be7eSDavid Ahern struct rb_node **pp, *parent = NULL, *next; 591ab84be7eSDavid Ahern 592ab84be7eSDavid Ahern pp = &net->nexthop.rb_root.rb_node; 593ab84be7eSDavid Ahern while (1) { 594ab84be7eSDavid Ahern struct nexthop *nh; 595ab84be7eSDavid Ahern 596ab84be7eSDavid Ahern next = rcu_dereference_raw(*pp); 597ab84be7eSDavid Ahern if (!next) 598ab84be7eSDavid Ahern break; 599ab84be7eSDavid Ahern parent = next; 600ab84be7eSDavid Ahern 601ab84be7eSDavid Ahern nh = rb_entry(parent, struct nexthop, rb_node); 602ab84be7eSDavid Ahern if (id < nh->id) 603ab84be7eSDavid Ahern pp = &next->rb_left; 604ab84be7eSDavid Ahern else if (id > nh->id) 605ab84be7eSDavid Ahern pp = &next->rb_right; 606ab84be7eSDavid Ahern else 607ab84be7eSDavid Ahern return nh; 608ab84be7eSDavid Ahern } 609ab84be7eSDavid Ahern return NULL; 610ab84be7eSDavid Ahern } 611ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_find_by_id); 612ab84be7eSDavid Ahern 613ab84be7eSDavid Ahern /* used for auto id allocation; called with rtnl held */ 614ab84be7eSDavid Ahern static u32 nh_find_unused_id(struct net *net) 615ab84be7eSDavid Ahern { 616ab84be7eSDavid Ahern u32 id_start = net->nexthop.last_id_allocated; 617ab84be7eSDavid Ahern 618ab84be7eSDavid Ahern while (1) { 619ab84be7eSDavid Ahern net->nexthop.last_id_allocated++; 620ab84be7eSDavid Ahern if (net->nexthop.last_id_allocated == id_start) 621ab84be7eSDavid Ahern break; 622ab84be7eSDavid Ahern 623ab84be7eSDavid Ahern if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated)) 624ab84be7eSDavid Ahern return net->nexthop.last_id_allocated; 625ab84be7eSDavid Ahern } 626ab84be7eSDavid Ahern return 0; 627ab84be7eSDavid Ahern } 628ab84be7eSDavid Ahern 629283a72a5SPetr Machata static void nh_res_time_set_deadline(unsigned long next_time, 630283a72a5SPetr Machata unsigned long *deadline) 631283a72a5SPetr Machata { 632283a72a5SPetr Machata if (time_before(next_time, *deadline)) 633283a72a5SPetr Machata *deadline = next_time; 634283a72a5SPetr Machata } 635283a72a5SPetr Machata 636a2601e2bSPetr Machata static clock_t nh_res_table_unbalanced_time(struct nh_res_table *res_table) 637a2601e2bSPetr Machata { 638a2601e2bSPetr Machata if (list_empty(&res_table->uw_nh_entries)) 639a2601e2bSPetr Machata return 0; 640a2601e2bSPetr Machata return jiffies_delta_to_clock_t(jiffies - res_table->unbalanced_since); 641a2601e2bSPetr Machata } 642a2601e2bSPetr Machata 643a2601e2bSPetr Machata static int nla_put_nh_group_res(struct sk_buff *skb, struct nh_group *nhg) 644a2601e2bSPetr Machata { 645a2601e2bSPetr Machata struct nh_res_table *res_table = rtnl_dereference(nhg->res_table); 646a2601e2bSPetr Machata struct nlattr *nest; 647a2601e2bSPetr Machata 648a2601e2bSPetr Machata nest = nla_nest_start(skb, NHA_RES_GROUP); 649a2601e2bSPetr Machata if (!nest) 650a2601e2bSPetr Machata return -EMSGSIZE; 651a2601e2bSPetr Machata 652a2601e2bSPetr Machata if (nla_put_u16(skb, NHA_RES_GROUP_BUCKETS, 653a2601e2bSPetr Machata res_table->num_nh_buckets) || 654a2601e2bSPetr Machata nla_put_u32(skb, NHA_RES_GROUP_IDLE_TIMER, 655a2601e2bSPetr Machata jiffies_to_clock_t(res_table->idle_timer)) || 656a2601e2bSPetr Machata nla_put_u32(skb, NHA_RES_GROUP_UNBALANCED_TIMER, 657a2601e2bSPetr Machata jiffies_to_clock_t(res_table->unbalanced_timer)) || 658a2601e2bSPetr Machata nla_put_u64_64bit(skb, NHA_RES_GROUP_UNBALANCED_TIME, 659a2601e2bSPetr Machata nh_res_table_unbalanced_time(res_table), 660a2601e2bSPetr Machata NHA_RES_GROUP_PAD)) 661a2601e2bSPetr Machata goto nla_put_failure; 662a2601e2bSPetr Machata 663a2601e2bSPetr Machata nla_nest_end(skb, nest); 664a2601e2bSPetr Machata return 0; 665a2601e2bSPetr Machata 666a2601e2bSPetr Machata nla_put_failure: 667a2601e2bSPetr Machata nla_nest_cancel(skb, nest); 668a2601e2bSPetr Machata return -EMSGSIZE; 669a2601e2bSPetr Machata } 670a2601e2bSPetr Machata 671f4676ea7SIdo Schimmel static void nh_grp_entry_stats_inc(struct nh_grp_entry *nhge) 672f4676ea7SIdo Schimmel { 673f4676ea7SIdo Schimmel struct nh_grp_entry_stats *cpu_stats; 674f4676ea7SIdo Schimmel 675f4676ea7SIdo Schimmel cpu_stats = this_cpu_ptr(nhge->stats); 676f4676ea7SIdo Schimmel u64_stats_update_begin(&cpu_stats->syncp); 677f4676ea7SIdo Schimmel u64_stats_inc(&cpu_stats->packets); 678f4676ea7SIdo Schimmel u64_stats_update_end(&cpu_stats->syncp); 679f4676ea7SIdo Schimmel } 680f4676ea7SIdo Schimmel 68195fedd76SIdo Schimmel static void nh_grp_entry_stats_read(struct nh_grp_entry *nhge, 68295fedd76SIdo Schimmel u64 *ret_packets) 683430a0491SDavid Ahern { 68495fedd76SIdo Schimmel int i; 68595fedd76SIdo Schimmel 68695fedd76SIdo Schimmel *ret_packets = 0; 68795fedd76SIdo Schimmel 68895fedd76SIdo Schimmel for_each_possible_cpu(i) { 68995fedd76SIdo Schimmel struct nh_grp_entry_stats *cpu_stats; 69095fedd76SIdo Schimmel unsigned int start; 69195fedd76SIdo Schimmel u64 packets; 69295fedd76SIdo Schimmel 69395fedd76SIdo Schimmel cpu_stats = per_cpu_ptr(nhge->stats, i); 69495fedd76SIdo Schimmel do { 69595fedd76SIdo Schimmel start = u64_stats_fetch_begin(&cpu_stats->syncp); 69695fedd76SIdo Schimmel packets = u64_stats_read(&cpu_stats->packets); 69795fedd76SIdo Schimmel } while (u64_stats_fetch_retry(&cpu_stats->syncp, start)); 69895fedd76SIdo Schimmel 69995fedd76SIdo Schimmel *ret_packets += packets; 70095fedd76SIdo Schimmel } 70195fedd76SIdo Schimmel } 70295fedd76SIdo Schimmel 70395fedd76SIdo Schimmel static int nla_put_nh_group_stats_entry(struct sk_buff *skb, 70495fedd76SIdo Schimmel struct nh_grp_entry *nhge) 70595fedd76SIdo Schimmel { 70695fedd76SIdo Schimmel struct nlattr *nest; 70795fedd76SIdo Schimmel u64 packets; 70895fedd76SIdo Schimmel 70995fedd76SIdo Schimmel nh_grp_entry_stats_read(nhge, &packets); 71095fedd76SIdo Schimmel 71195fedd76SIdo Schimmel nest = nla_nest_start(skb, NHA_GROUP_STATS_ENTRY); 71295fedd76SIdo Schimmel if (!nest) 71395fedd76SIdo Schimmel return -EMSGSIZE; 71495fedd76SIdo Schimmel 71595fedd76SIdo Schimmel if (nla_put_u32(skb, NHA_GROUP_STATS_ENTRY_ID, nhge->nh->id) || 71695fedd76SIdo Schimmel nla_put_uint(skb, NHA_GROUP_STATS_ENTRY_PACKETS, packets)) 71795fedd76SIdo Schimmel goto nla_put_failure; 71895fedd76SIdo Schimmel 71995fedd76SIdo Schimmel nla_nest_end(skb, nest); 72095fedd76SIdo Schimmel return 0; 72195fedd76SIdo Schimmel 72295fedd76SIdo Schimmel nla_put_failure: 72395fedd76SIdo Schimmel nla_nest_cancel(skb, nest); 72495fedd76SIdo Schimmel return -EMSGSIZE; 72595fedd76SIdo Schimmel } 72695fedd76SIdo Schimmel 72795fedd76SIdo Schimmel static int nla_put_nh_group_stats(struct sk_buff *skb, struct nexthop *nh) 72895fedd76SIdo Schimmel { 72995fedd76SIdo Schimmel struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 73095fedd76SIdo Schimmel struct nlattr *nest; 73195fedd76SIdo Schimmel int i; 73295fedd76SIdo Schimmel 73395fedd76SIdo Schimmel nest = nla_nest_start(skb, NHA_GROUP_STATS); 73495fedd76SIdo Schimmel if (!nest) 73595fedd76SIdo Schimmel return -EMSGSIZE; 73695fedd76SIdo Schimmel 73795fedd76SIdo Schimmel for (i = 0; i < nhg->num_nh; i++) 73895fedd76SIdo Schimmel if (nla_put_nh_group_stats_entry(skb, &nhg->nh_entries[i])) 73995fedd76SIdo Schimmel goto cancel_out; 74095fedd76SIdo Schimmel 74195fedd76SIdo Schimmel nla_nest_end(skb, nest); 74295fedd76SIdo Schimmel return 0; 74395fedd76SIdo Schimmel 74495fedd76SIdo Schimmel cancel_out: 74595fedd76SIdo Schimmel nla_nest_cancel(skb, nest); 74695fedd76SIdo Schimmel return -EMSGSIZE; 74795fedd76SIdo Schimmel } 74895fedd76SIdo Schimmel 74995fedd76SIdo Schimmel static int nla_put_nh_group(struct sk_buff *skb, struct nexthop *nh, 75095fedd76SIdo Schimmel u32 op_flags) 75195fedd76SIdo Schimmel { 75295fedd76SIdo Schimmel struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 753430a0491SDavid Ahern struct nexthop_grp *p; 754430a0491SDavid Ahern size_t len = nhg->num_nh * sizeof(*p); 755430a0491SDavid Ahern struct nlattr *nla; 756430a0491SDavid Ahern u16 group_type = 0; 757430a0491SDavid Ahern int i; 758430a0491SDavid Ahern 759de1d1ee3SPetr Machata if (nhg->hash_threshold) 760430a0491SDavid Ahern group_type = NEXTHOP_GRP_TYPE_MPATH; 761a2601e2bSPetr Machata else if (nhg->resilient) 762a2601e2bSPetr Machata group_type = NEXTHOP_GRP_TYPE_RES; 763430a0491SDavid Ahern 764430a0491SDavid Ahern if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type)) 765430a0491SDavid Ahern goto nla_put_failure; 766430a0491SDavid Ahern 767430a0491SDavid Ahern nla = nla_reserve(skb, NHA_GROUP, len); 768430a0491SDavid Ahern if (!nla) 769430a0491SDavid Ahern goto nla_put_failure; 770430a0491SDavid Ahern 771430a0491SDavid Ahern p = nla_data(nla); 772430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 773430a0491SDavid Ahern p->id = nhg->nh_entries[i].nh->id; 774430a0491SDavid Ahern p->weight = nhg->nh_entries[i].weight - 1; 775430a0491SDavid Ahern p += 1; 776430a0491SDavid Ahern } 777430a0491SDavid Ahern 778a2601e2bSPetr Machata if (nhg->resilient && nla_put_nh_group_res(skb, nhg)) 779a2601e2bSPetr Machata goto nla_put_failure; 780a2601e2bSPetr Machata 78195fedd76SIdo Schimmel if (op_flags & NHA_OP_FLAG_DUMP_STATS && 782*746c19a5SIdo Schimmel (nla_put_u32(skb, NHA_HW_STATS_ENABLE, nhg->hw_stats) || 783*746c19a5SIdo Schimmel nla_put_nh_group_stats(skb, nh))) 78495fedd76SIdo Schimmel goto nla_put_failure; 78595fedd76SIdo Schimmel 786430a0491SDavid Ahern return 0; 787430a0491SDavid Ahern 788430a0491SDavid Ahern nla_put_failure: 789430a0491SDavid Ahern return -EMSGSIZE; 790430a0491SDavid Ahern } 791430a0491SDavid Ahern 792ab84be7eSDavid Ahern static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh, 79395fedd76SIdo Schimmel int event, u32 portid, u32 seq, unsigned int nlflags, 79495fedd76SIdo Schimmel u32 op_flags) 795ab84be7eSDavid Ahern { 79653010f99SDavid Ahern struct fib6_nh *fib6_nh; 797597cfe4fSDavid Ahern struct fib_nh *fib_nh; 798ab84be7eSDavid Ahern struct nlmsghdr *nlh; 799ab84be7eSDavid Ahern struct nh_info *nhi; 800ab84be7eSDavid Ahern struct nhmsg *nhm; 801ab84be7eSDavid Ahern 802ab84be7eSDavid Ahern nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags); 803ab84be7eSDavid Ahern if (!nlh) 804ab84be7eSDavid Ahern return -EMSGSIZE; 805ab84be7eSDavid Ahern 806ab84be7eSDavid Ahern nhm = nlmsg_data(nlh); 807ab84be7eSDavid Ahern nhm->nh_family = AF_UNSPEC; 808ab84be7eSDavid Ahern nhm->nh_flags = nh->nh_flags; 809ab84be7eSDavid Ahern nhm->nh_protocol = nh->protocol; 810ab84be7eSDavid Ahern nhm->nh_scope = 0; 811ab84be7eSDavid Ahern nhm->resvd = 0; 812ab84be7eSDavid Ahern 813ab84be7eSDavid Ahern if (nla_put_u32(skb, NHA_ID, nh->id)) 814ab84be7eSDavid Ahern goto nla_put_failure; 815ab84be7eSDavid Ahern 816430a0491SDavid Ahern if (nh->is_group) { 817430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 818430a0491SDavid Ahern 819ce9ac056SDavid Ahern if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB)) 820ce9ac056SDavid Ahern goto nla_put_failure; 82195fedd76SIdo Schimmel if (nla_put_nh_group(skb, nh, op_flags)) 822430a0491SDavid Ahern goto nla_put_failure; 823430a0491SDavid Ahern goto out; 824430a0491SDavid Ahern } 825430a0491SDavid Ahern 826ab84be7eSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 827ab84be7eSDavid Ahern nhm->nh_family = nhi->family; 828ab84be7eSDavid Ahern if (nhi->reject_nh) { 829ab84be7eSDavid Ahern if (nla_put_flag(skb, NHA_BLACKHOLE)) 830ab84be7eSDavid Ahern goto nla_put_failure; 831ab84be7eSDavid Ahern goto out; 832ce9ac056SDavid Ahern } else if (nhi->fdb_nh) { 833ce9ac056SDavid Ahern if (nla_put_flag(skb, NHA_FDB)) 834ce9ac056SDavid Ahern goto nla_put_failure; 835ce9ac056SDavid Ahern } else { 836597cfe4fSDavid Ahern const struct net_device *dev; 837597cfe4fSDavid Ahern 838597cfe4fSDavid Ahern dev = nhi->fib_nhc.nhc_dev; 839597cfe4fSDavid Ahern if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex)) 840597cfe4fSDavid Ahern goto nla_put_failure; 841597cfe4fSDavid Ahern } 842597cfe4fSDavid Ahern 843597cfe4fSDavid Ahern nhm->nh_scope = nhi->fib_nhc.nhc_scope; 844597cfe4fSDavid Ahern switch (nhi->family) { 845597cfe4fSDavid Ahern case AF_INET: 846597cfe4fSDavid Ahern fib_nh = &nhi->fib_nh; 847597cfe4fSDavid Ahern if (fib_nh->fib_nh_gw_family && 84833d80996SIdo Schimmel nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4)) 849597cfe4fSDavid Ahern goto nla_put_failure; 850597cfe4fSDavid Ahern break; 85153010f99SDavid Ahern 85253010f99SDavid Ahern case AF_INET6: 85353010f99SDavid Ahern fib6_nh = &nhi->fib6_nh; 85453010f99SDavid Ahern if (fib6_nh->fib_nh_gw_family && 85553010f99SDavid Ahern nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6)) 85653010f99SDavid Ahern goto nla_put_failure; 85753010f99SDavid Ahern break; 858ab84be7eSDavid Ahern } 859ab84be7eSDavid Ahern 860b513bd03SDavid Ahern if (nhi->fib_nhc.nhc_lwtstate && 861b513bd03SDavid Ahern lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate, 862b513bd03SDavid Ahern NHA_ENCAP, NHA_ENCAP_TYPE) < 0) 863b513bd03SDavid Ahern goto nla_put_failure; 864b513bd03SDavid Ahern 865ab84be7eSDavid Ahern out: 866ab84be7eSDavid Ahern nlmsg_end(skb, nlh); 867ab84be7eSDavid Ahern return 0; 868ab84be7eSDavid Ahern 869ab84be7eSDavid Ahern nla_put_failure: 870d69100b8SStephen Worley nlmsg_cancel(skb, nlh); 871ab84be7eSDavid Ahern return -EMSGSIZE; 872ab84be7eSDavid Ahern } 873ab84be7eSDavid Ahern 874a2601e2bSPetr Machata static size_t nh_nlmsg_size_grp_res(struct nh_group *nhg) 875a2601e2bSPetr Machata { 876a2601e2bSPetr Machata return nla_total_size(0) + /* NHA_RES_GROUP */ 877a2601e2bSPetr Machata nla_total_size(2) + /* NHA_RES_GROUP_BUCKETS */ 878a2601e2bSPetr Machata nla_total_size(4) + /* NHA_RES_GROUP_IDLE_TIMER */ 879a2601e2bSPetr Machata nla_total_size(4) + /* NHA_RES_GROUP_UNBALANCED_TIMER */ 880a2601e2bSPetr Machata nla_total_size_64bit(8);/* NHA_RES_GROUP_UNBALANCED_TIME */ 881a2601e2bSPetr Machata } 882a2601e2bSPetr Machata 883430a0491SDavid Ahern static size_t nh_nlmsg_size_grp(struct nexthop *nh) 884430a0491SDavid Ahern { 885430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 886430a0491SDavid Ahern size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh; 887a2601e2bSPetr Machata size_t tot = nla_total_size(sz) + 888430a0491SDavid Ahern nla_total_size(2); /* NHA_GROUP_TYPE */ 889a2601e2bSPetr Machata 890a2601e2bSPetr Machata if (nhg->resilient) 891a2601e2bSPetr Machata tot += nh_nlmsg_size_grp_res(nhg); 892a2601e2bSPetr Machata 893a2601e2bSPetr Machata return tot; 894430a0491SDavid Ahern } 895430a0491SDavid Ahern 896430a0491SDavid Ahern static size_t nh_nlmsg_size_single(struct nexthop *nh) 897ab84be7eSDavid Ahern { 898597cfe4fSDavid Ahern struct nh_info *nhi = rtnl_dereference(nh->nh_info); 899430a0491SDavid Ahern size_t sz; 900ab84be7eSDavid Ahern 901ab84be7eSDavid Ahern /* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE 902ab84be7eSDavid Ahern * are mutually exclusive 903ab84be7eSDavid Ahern */ 904430a0491SDavid Ahern sz = nla_total_size(4); /* NHA_OIF */ 905ab84be7eSDavid Ahern 906597cfe4fSDavid Ahern switch (nhi->family) { 907597cfe4fSDavid Ahern case AF_INET: 908597cfe4fSDavid Ahern if (nhi->fib_nh.fib_nh_gw_family) 909597cfe4fSDavid Ahern sz += nla_total_size(4); /* NHA_GATEWAY */ 910597cfe4fSDavid Ahern break; 91153010f99SDavid Ahern 91253010f99SDavid Ahern case AF_INET6: 91353010f99SDavid Ahern /* NHA_GATEWAY */ 91453010f99SDavid Ahern if (nhi->fib6_nh.fib_nh_gw_family) 91553010f99SDavid Ahern sz += nla_total_size(sizeof(const struct in6_addr)); 91653010f99SDavid Ahern break; 917597cfe4fSDavid Ahern } 918597cfe4fSDavid Ahern 919b513bd03SDavid Ahern if (nhi->fib_nhc.nhc_lwtstate) { 920b513bd03SDavid Ahern sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate); 921b513bd03SDavid Ahern sz += nla_total_size(2); /* NHA_ENCAP_TYPE */ 922b513bd03SDavid Ahern } 923b513bd03SDavid Ahern 924ab84be7eSDavid Ahern return sz; 925ab84be7eSDavid Ahern } 926ab84be7eSDavid Ahern 927430a0491SDavid Ahern static size_t nh_nlmsg_size(struct nexthop *nh) 928430a0491SDavid Ahern { 929f9e95555SStephen Worley size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg)); 930f9e95555SStephen Worley 931f9e95555SStephen Worley sz += nla_total_size(4); /* NHA_ID */ 932430a0491SDavid Ahern 933430a0491SDavid Ahern if (nh->is_group) 934430a0491SDavid Ahern sz += nh_nlmsg_size_grp(nh); 935430a0491SDavid Ahern else 936430a0491SDavid Ahern sz += nh_nlmsg_size_single(nh); 937430a0491SDavid Ahern 938430a0491SDavid Ahern return sz; 939430a0491SDavid Ahern } 940430a0491SDavid Ahern 941ab84be7eSDavid Ahern static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info) 942ab84be7eSDavid Ahern { 943ab84be7eSDavid Ahern unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0; 944ab84be7eSDavid Ahern u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0; 945ab84be7eSDavid Ahern struct sk_buff *skb; 946ab84be7eSDavid Ahern int err = -ENOBUFS; 947ab84be7eSDavid Ahern 948ab84be7eSDavid Ahern skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any()); 949ab84be7eSDavid Ahern if (!skb) 950ab84be7eSDavid Ahern goto errout; 951ab84be7eSDavid Ahern 95295fedd76SIdo Schimmel err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags, 0); 953ab84be7eSDavid Ahern if (err < 0) { 954ab84be7eSDavid Ahern /* -EMSGSIZE implies BUG in nh_nlmsg_size() */ 955ab84be7eSDavid Ahern WARN_ON(err == -EMSGSIZE); 956ab84be7eSDavid Ahern kfree_skb(skb); 957ab84be7eSDavid Ahern goto errout; 958ab84be7eSDavid Ahern } 959ab84be7eSDavid Ahern 960ab84be7eSDavid Ahern rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP, 961ab84be7eSDavid Ahern info->nlh, gfp_any()); 962ab84be7eSDavid Ahern return; 963ab84be7eSDavid Ahern errout: 964ab84be7eSDavid Ahern if (err < 0) 965ab84be7eSDavid Ahern rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err); 966ab84be7eSDavid Ahern } 967ab84be7eSDavid Ahern 968283a72a5SPetr Machata static unsigned long nh_res_bucket_used_time(const struct nh_res_bucket *bucket) 969283a72a5SPetr Machata { 970283a72a5SPetr Machata return (unsigned long)atomic_long_read(&bucket->used_time); 971283a72a5SPetr Machata } 972283a72a5SPetr Machata 973283a72a5SPetr Machata static unsigned long 974283a72a5SPetr Machata nh_res_bucket_idle_point(const struct nh_res_table *res_table, 975283a72a5SPetr Machata const struct nh_res_bucket *bucket, 976283a72a5SPetr Machata unsigned long now) 977283a72a5SPetr Machata { 978283a72a5SPetr Machata unsigned long time = nh_res_bucket_used_time(bucket); 979283a72a5SPetr Machata 980283a72a5SPetr Machata /* Bucket was not used since it was migrated. The idle time is now. */ 981283a72a5SPetr Machata if (time == bucket->migrated_time) 982283a72a5SPetr Machata return now; 983283a72a5SPetr Machata 984283a72a5SPetr Machata return time + res_table->idle_timer; 985283a72a5SPetr Machata } 986283a72a5SPetr Machata 987283a72a5SPetr Machata static unsigned long 988283a72a5SPetr Machata nh_res_table_unb_point(const struct nh_res_table *res_table) 989283a72a5SPetr Machata { 990283a72a5SPetr Machata return res_table->unbalanced_since + res_table->unbalanced_timer; 991283a72a5SPetr Machata } 992283a72a5SPetr Machata 993283a72a5SPetr Machata static void nh_res_bucket_set_idle(const struct nh_res_table *res_table, 994283a72a5SPetr Machata struct nh_res_bucket *bucket) 995283a72a5SPetr Machata { 996283a72a5SPetr Machata unsigned long now = jiffies; 997283a72a5SPetr Machata 998283a72a5SPetr Machata atomic_long_set(&bucket->used_time, (long)now); 999283a72a5SPetr Machata bucket->migrated_time = now; 1000283a72a5SPetr Machata } 1001283a72a5SPetr Machata 1002283a72a5SPetr Machata static void nh_res_bucket_set_busy(struct nh_res_bucket *bucket) 1003283a72a5SPetr Machata { 1004283a72a5SPetr Machata atomic_long_set(&bucket->used_time, (long)jiffies); 1005283a72a5SPetr Machata } 1006283a72a5SPetr Machata 10078a1bbabbSPetr Machata static clock_t nh_res_bucket_idle_time(const struct nh_res_bucket *bucket) 10088a1bbabbSPetr Machata { 10098a1bbabbSPetr Machata unsigned long used_time = nh_res_bucket_used_time(bucket); 10108a1bbabbSPetr Machata 10118a1bbabbSPetr Machata return jiffies_delta_to_clock_t(jiffies - used_time); 10128a1bbabbSPetr Machata } 10138a1bbabbSPetr Machata 10148a1bbabbSPetr Machata static int nh_fill_res_bucket(struct sk_buff *skb, struct nexthop *nh, 10158a1bbabbSPetr Machata struct nh_res_bucket *bucket, u16 bucket_index, 10168a1bbabbSPetr Machata int event, u32 portid, u32 seq, 10178a1bbabbSPetr Machata unsigned int nlflags, 10188a1bbabbSPetr Machata struct netlink_ext_ack *extack) 10198a1bbabbSPetr Machata { 10208a1bbabbSPetr Machata struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry); 10218a1bbabbSPetr Machata struct nlmsghdr *nlh; 10228a1bbabbSPetr Machata struct nlattr *nest; 10238a1bbabbSPetr Machata struct nhmsg *nhm; 10248a1bbabbSPetr Machata 10258a1bbabbSPetr Machata nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags); 10268a1bbabbSPetr Machata if (!nlh) 10278a1bbabbSPetr Machata return -EMSGSIZE; 10288a1bbabbSPetr Machata 10298a1bbabbSPetr Machata nhm = nlmsg_data(nlh); 10308a1bbabbSPetr Machata nhm->nh_family = AF_UNSPEC; 10318a1bbabbSPetr Machata nhm->nh_flags = bucket->nh_flags; 10328a1bbabbSPetr Machata nhm->nh_protocol = nh->protocol; 10338a1bbabbSPetr Machata nhm->nh_scope = 0; 10348a1bbabbSPetr Machata nhm->resvd = 0; 10358a1bbabbSPetr Machata 10368a1bbabbSPetr Machata if (nla_put_u32(skb, NHA_ID, nh->id)) 10378a1bbabbSPetr Machata goto nla_put_failure; 10388a1bbabbSPetr Machata 10398a1bbabbSPetr Machata nest = nla_nest_start(skb, NHA_RES_BUCKET); 10408a1bbabbSPetr Machata if (!nest) 10418a1bbabbSPetr Machata goto nla_put_failure; 10428a1bbabbSPetr Machata 10438a1bbabbSPetr Machata if (nla_put_u16(skb, NHA_RES_BUCKET_INDEX, bucket_index) || 10448a1bbabbSPetr Machata nla_put_u32(skb, NHA_RES_BUCKET_NH_ID, nhge->nh->id) || 10458a1bbabbSPetr Machata nla_put_u64_64bit(skb, NHA_RES_BUCKET_IDLE_TIME, 10468a1bbabbSPetr Machata nh_res_bucket_idle_time(bucket), 10478a1bbabbSPetr Machata NHA_RES_BUCKET_PAD)) 10488a1bbabbSPetr Machata goto nla_put_failure_nest; 10498a1bbabbSPetr Machata 10508a1bbabbSPetr Machata nla_nest_end(skb, nest); 10518a1bbabbSPetr Machata nlmsg_end(skb, nlh); 10528a1bbabbSPetr Machata return 0; 10538a1bbabbSPetr Machata 10548a1bbabbSPetr Machata nla_put_failure_nest: 10558a1bbabbSPetr Machata nla_nest_cancel(skb, nest); 10568a1bbabbSPetr Machata nla_put_failure: 10578a1bbabbSPetr Machata nlmsg_cancel(skb, nlh); 10588a1bbabbSPetr Machata return -EMSGSIZE; 10598a1bbabbSPetr Machata } 10608a1bbabbSPetr Machata 10610b4818aaSPetr Machata static void nexthop_bucket_notify(struct nh_res_table *res_table, 10620b4818aaSPetr Machata u16 bucket_index) 10630b4818aaSPetr Machata { 10640b4818aaSPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index]; 10650b4818aaSPetr Machata struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry); 10660b4818aaSPetr Machata struct nexthop *nh = nhge->nh_parent; 10670b4818aaSPetr Machata struct sk_buff *skb; 10680b4818aaSPetr Machata int err = -ENOBUFS; 10690b4818aaSPetr Machata 10700b4818aaSPetr Machata skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 10710b4818aaSPetr Machata if (!skb) 10720b4818aaSPetr Machata goto errout; 10730b4818aaSPetr Machata 10740b4818aaSPetr Machata err = nh_fill_res_bucket(skb, nh, bucket, bucket_index, 10750b4818aaSPetr Machata RTM_NEWNEXTHOPBUCKET, 0, 0, NLM_F_REPLACE, 10760b4818aaSPetr Machata NULL); 10770b4818aaSPetr Machata if (err < 0) { 10780b4818aaSPetr Machata kfree_skb(skb); 10790b4818aaSPetr Machata goto errout; 10800b4818aaSPetr Machata } 10810b4818aaSPetr Machata 10820b4818aaSPetr Machata rtnl_notify(skb, nh->net, 0, RTNLGRP_NEXTHOP, NULL, GFP_KERNEL); 10830b4818aaSPetr Machata return; 10840b4818aaSPetr Machata errout: 10850b4818aaSPetr Machata if (err < 0) 10860b4818aaSPetr Machata rtnl_set_sk_err(nh->net, RTNLGRP_NEXTHOP, err); 10870b4818aaSPetr Machata } 10880b4818aaSPetr Machata 1089430a0491SDavid Ahern static bool valid_group_nh(struct nexthop *nh, unsigned int npaths, 1090ce9ac056SDavid Ahern bool *is_fdb, struct netlink_ext_ack *extack) 1091597cfe4fSDavid Ahern { 1092430a0491SDavid Ahern if (nh->is_group) { 1093430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 1094430a0491SDavid Ahern 1095283a72a5SPetr Machata /* Nesting groups within groups is not supported. */ 1096de1d1ee3SPetr Machata if (nhg->hash_threshold) { 1097430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1098de1d1ee3SPetr Machata "Hash-threshold group can not be a nexthop within a group"); 1099430a0491SDavid Ahern return false; 1100430a0491SDavid Ahern } 1101283a72a5SPetr Machata if (nhg->resilient) { 1102283a72a5SPetr Machata NL_SET_ERR_MSG(extack, 1103283a72a5SPetr Machata "Resilient group can not be a nexthop within a group"); 1104283a72a5SPetr Machata return false; 1105283a72a5SPetr Machata } 1106ce9ac056SDavid Ahern *is_fdb = nhg->fdb_nh; 1107430a0491SDavid Ahern } else { 1108430a0491SDavid Ahern struct nh_info *nhi = rtnl_dereference(nh->nh_info); 1109430a0491SDavid Ahern 1110430a0491SDavid Ahern if (nhi->reject_nh && npaths > 1) { 1111430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1112430a0491SDavid Ahern "Blackhole nexthop can not be used in a group with more than 1 path"); 1113430a0491SDavid Ahern return false; 1114430a0491SDavid Ahern } 1115ce9ac056SDavid Ahern *is_fdb = nhi->fdb_nh; 1116430a0491SDavid Ahern } 1117430a0491SDavid Ahern 1118430a0491SDavid Ahern return true; 1119430a0491SDavid Ahern } 1120430a0491SDavid Ahern 112138428d68SRoopa Prabhu static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family, 112238428d68SRoopa Prabhu struct netlink_ext_ack *extack) 112338428d68SRoopa Prabhu { 112438428d68SRoopa Prabhu struct nh_info *nhi; 112538428d68SRoopa Prabhu 1126ce9ac056SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 1127ce9ac056SDavid Ahern 1128ce9ac056SDavid Ahern if (!nhi->fdb_nh) { 112938428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops"); 113038428d68SRoopa Prabhu return -EINVAL; 113138428d68SRoopa Prabhu } 113238428d68SRoopa Prabhu 113338428d68SRoopa Prabhu if (*nh_family == AF_UNSPEC) { 113438428d68SRoopa Prabhu *nh_family = nhi->family; 113538428d68SRoopa Prabhu } else if (*nh_family != nhi->family) { 113638428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops"); 113738428d68SRoopa Prabhu return -EINVAL; 113838428d68SRoopa Prabhu } 113938428d68SRoopa Prabhu 114038428d68SRoopa Prabhu return 0; 114138428d68SRoopa Prabhu } 114238428d68SRoopa Prabhu 1143643d0878SPetr Machata static int nh_check_attr_group(struct net *net, 1144643d0878SPetr Machata struct nlattr *tb[], size_t tb_size, 1145a2601e2bSPetr Machata u16 nh_grp_type, struct netlink_ext_ack *extack) 1146430a0491SDavid Ahern { 1147430a0491SDavid Ahern unsigned int len = nla_len(tb[NHA_GROUP]); 114838428d68SRoopa Prabhu u8 nh_family = AF_UNSPEC; 1149430a0491SDavid Ahern struct nexthop_grp *nhg; 1150430a0491SDavid Ahern unsigned int i, j; 115138428d68SRoopa Prabhu u8 nhg_fdb = 0; 1152430a0491SDavid Ahern 1153eeaac363SNikolay Aleksandrov if (!len || len & (sizeof(struct nexthop_grp) - 1)) { 1154430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1155430a0491SDavid Ahern "Invalid length for nexthop group attribute"); 1156430a0491SDavid Ahern return -EINVAL; 1157430a0491SDavid Ahern } 1158430a0491SDavid Ahern 1159430a0491SDavid Ahern /* convert len to number of nexthop ids */ 1160430a0491SDavid Ahern len /= sizeof(*nhg); 1161430a0491SDavid Ahern 1162430a0491SDavid Ahern nhg = nla_data(tb[NHA_GROUP]); 1163430a0491SDavid Ahern for (i = 0; i < len; ++i) { 1164430a0491SDavid Ahern if (nhg[i].resvd1 || nhg[i].resvd2) { 1165430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0"); 1166430a0491SDavid Ahern return -EINVAL; 1167430a0491SDavid Ahern } 1168430a0491SDavid Ahern if (nhg[i].weight > 254) { 1169430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid value for weight"); 1170430a0491SDavid Ahern return -EINVAL; 1171430a0491SDavid Ahern } 1172430a0491SDavid Ahern for (j = i + 1; j < len; ++j) { 1173430a0491SDavid Ahern if (nhg[i].id == nhg[j].id) { 1174430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group"); 1175430a0491SDavid Ahern return -EINVAL; 1176430a0491SDavid Ahern } 1177430a0491SDavid Ahern } 1178430a0491SDavid Ahern } 1179430a0491SDavid Ahern 118038428d68SRoopa Prabhu if (tb[NHA_FDB]) 118138428d68SRoopa Prabhu nhg_fdb = 1; 1182430a0491SDavid Ahern nhg = nla_data(tb[NHA_GROUP]); 1183430a0491SDavid Ahern for (i = 0; i < len; ++i) { 1184430a0491SDavid Ahern struct nexthop *nh; 1185ce9ac056SDavid Ahern bool is_fdb_nh; 1186430a0491SDavid Ahern 1187430a0491SDavid Ahern nh = nexthop_find_by_id(net, nhg[i].id); 1188430a0491SDavid Ahern if (!nh) { 1189430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 1190430a0491SDavid Ahern return -EINVAL; 1191430a0491SDavid Ahern } 1192ce9ac056SDavid Ahern if (!valid_group_nh(nh, len, &is_fdb_nh, extack)) 1193430a0491SDavid Ahern return -EINVAL; 119438428d68SRoopa Prabhu 119538428d68SRoopa Prabhu if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack)) 119638428d68SRoopa Prabhu return -EINVAL; 119738428d68SRoopa Prabhu 1198ce9ac056SDavid Ahern if (!nhg_fdb && is_fdb_nh) { 119938428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops"); 120038428d68SRoopa Prabhu return -EINVAL; 120138428d68SRoopa Prabhu } 1202430a0491SDavid Ahern } 1203643d0878SPetr Machata for (i = NHA_GROUP_TYPE + 1; i < tb_size; ++i) { 1204430a0491SDavid Ahern if (!tb[i]) 1205430a0491SDavid Ahern continue; 1206a2601e2bSPetr Machata switch (i) { 1207*746c19a5SIdo Schimmel case NHA_HW_STATS_ENABLE: 1208a2601e2bSPetr Machata case NHA_FDB: 120938428d68SRoopa Prabhu continue; 1210a2601e2bSPetr Machata case NHA_RES_GROUP: 1211a2601e2bSPetr Machata if (nh_grp_type == NEXTHOP_GRP_TYPE_RES) 1212a2601e2bSPetr Machata continue; 1213a2601e2bSPetr Machata break; 1214a2601e2bSPetr Machata } 1215430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1216430a0491SDavid Ahern "No other attributes can be set in nexthop groups"); 1217430a0491SDavid Ahern return -EINVAL; 1218430a0491SDavid Ahern } 1219430a0491SDavid Ahern 1220430a0491SDavid Ahern return 0; 1221430a0491SDavid Ahern } 1222430a0491SDavid Ahern 1223430a0491SDavid Ahern static bool ipv6_good_nh(const struct fib6_nh *nh) 1224430a0491SDavid Ahern { 1225430a0491SDavid Ahern int state = NUD_REACHABLE; 1226430a0491SDavid Ahern struct neighbour *n; 1227430a0491SDavid Ahern 122809eed119SEric Dumazet rcu_read_lock(); 1229430a0491SDavid Ahern 1230430a0491SDavid Ahern n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6); 1231430a0491SDavid Ahern if (n) 1232b071af52SEric Dumazet state = READ_ONCE(n->nud_state); 1233430a0491SDavid Ahern 123409eed119SEric Dumazet rcu_read_unlock(); 1235430a0491SDavid Ahern 1236430a0491SDavid Ahern return !!(state & NUD_VALID); 1237430a0491SDavid Ahern } 1238430a0491SDavid Ahern 1239430a0491SDavid Ahern static bool ipv4_good_nh(const struct fib_nh *nh) 1240430a0491SDavid Ahern { 1241430a0491SDavid Ahern int state = NUD_REACHABLE; 1242430a0491SDavid Ahern struct neighbour *n; 1243430a0491SDavid Ahern 124409eed119SEric Dumazet rcu_read_lock(); 1245430a0491SDavid Ahern 1246430a0491SDavid Ahern n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev, 1247430a0491SDavid Ahern (__force u32)nh->fib_nh_gw4); 1248430a0491SDavid Ahern if (n) 1249b071af52SEric Dumazet state = READ_ONCE(n->nud_state); 1250430a0491SDavid Ahern 125109eed119SEric Dumazet rcu_read_unlock(); 1252430a0491SDavid Ahern 1253430a0491SDavid Ahern return !!(state & NUD_VALID); 1254430a0491SDavid Ahern } 1255430a0491SDavid Ahern 12564bb5239bSBenjamin Poirier static bool nexthop_is_good_nh(const struct nexthop *nh) 12574bb5239bSBenjamin Poirier { 12584bb5239bSBenjamin Poirier struct nh_info *nhi = rcu_dereference(nh->nh_info); 12594bb5239bSBenjamin Poirier 12604bb5239bSBenjamin Poirier switch (nhi->family) { 12614bb5239bSBenjamin Poirier case AF_INET: 12624bb5239bSBenjamin Poirier return ipv4_good_nh(&nhi->fib_nh); 12634bb5239bSBenjamin Poirier case AF_INET6: 12644bb5239bSBenjamin Poirier return ipv6_good_nh(&nhi->fib6_nh); 12654bb5239bSBenjamin Poirier } 12664bb5239bSBenjamin Poirier 12674bb5239bSBenjamin Poirier return false; 12684bb5239bSBenjamin Poirier } 12694bb5239bSBenjamin Poirier 1270eedd47a6SBenjamin Poirier static struct nexthop *nexthop_select_path_fdb(struct nh_group *nhg, int hash) 1271eedd47a6SBenjamin Poirier { 1272eedd47a6SBenjamin Poirier int i; 1273eedd47a6SBenjamin Poirier 1274eedd47a6SBenjamin Poirier for (i = 0; i < nhg->num_nh; i++) { 1275eedd47a6SBenjamin Poirier struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1276eedd47a6SBenjamin Poirier 1277eedd47a6SBenjamin Poirier if (hash > atomic_read(&nhge->hthr.upper_bound)) 1278eedd47a6SBenjamin Poirier continue; 1279eedd47a6SBenjamin Poirier 1280f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge); 1281eedd47a6SBenjamin Poirier return nhge->nh; 1282eedd47a6SBenjamin Poirier } 1283eedd47a6SBenjamin Poirier 1284eedd47a6SBenjamin Poirier WARN_ON_ONCE(1); 1285eedd47a6SBenjamin Poirier return NULL; 1286eedd47a6SBenjamin Poirier } 1287eedd47a6SBenjamin Poirier 1288de1d1ee3SPetr Machata static struct nexthop *nexthop_select_path_hthr(struct nh_group *nhg, int hash) 1289430a0491SDavid Ahern { 1290f4676ea7SIdo Schimmel struct nh_grp_entry *nhge0 = NULL; 1291430a0491SDavid Ahern int i; 1292430a0491SDavid Ahern 1293eedd47a6SBenjamin Poirier if (nhg->fdb_nh) 1294eedd47a6SBenjamin Poirier return nexthop_select_path_fdb(nhg, hash); 1295eedd47a6SBenjamin Poirier 1296430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 1297430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1298430a0491SDavid Ahern 1299430a0491SDavid Ahern /* nexthops always check if it is good and does 1300430a0491SDavid Ahern * not rely on a sysctl for this behavior 1301430a0491SDavid Ahern */ 130275f5f04cSBenjamin Poirier if (!nexthop_is_good_nh(nhge->nh)) 130375f5f04cSBenjamin Poirier continue; 1304430a0491SDavid Ahern 1305f4676ea7SIdo Schimmel if (!nhge0) 1306f4676ea7SIdo Schimmel nhge0 = nhge; 130775f5f04cSBenjamin Poirier 130875f5f04cSBenjamin Poirier if (hash > atomic_read(&nhge->hthr.upper_bound)) 130975f5f04cSBenjamin Poirier continue; 131075f5f04cSBenjamin Poirier 1311f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge); 131275f5f04cSBenjamin Poirier return nhge->nh; 1313430a0491SDavid Ahern } 1314430a0491SDavid Ahern 1315f4676ea7SIdo Schimmel if (!nhge0) 1316f4676ea7SIdo Schimmel nhge0 = &nhg->nh_entries[0]; 1317f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge0); 1318f4676ea7SIdo Schimmel return nhge0->nh; 1319430a0491SDavid Ahern } 132079bc55e3SPetr Machata 1321283a72a5SPetr Machata static struct nexthop *nexthop_select_path_res(struct nh_group *nhg, int hash) 1322283a72a5SPetr Machata { 1323283a72a5SPetr Machata struct nh_res_table *res_table = rcu_dereference(nhg->res_table); 1324283a72a5SPetr Machata u16 bucket_index = hash % res_table->num_nh_buckets; 1325283a72a5SPetr Machata struct nh_res_bucket *bucket; 1326283a72a5SPetr Machata struct nh_grp_entry *nhge; 1327283a72a5SPetr Machata 1328283a72a5SPetr Machata /* nexthop_select_path() is expected to return a non-NULL value, so 1329283a72a5SPetr Machata * skip protocol validation and just hand out whatever there is. 1330283a72a5SPetr Machata */ 1331283a72a5SPetr Machata bucket = &res_table->nh_buckets[bucket_index]; 1332283a72a5SPetr Machata nh_res_bucket_set_busy(bucket); 1333283a72a5SPetr Machata nhge = rcu_dereference(bucket->nh_entry); 1334f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge); 1335283a72a5SPetr Machata return nhge->nh; 1336283a72a5SPetr Machata } 1337283a72a5SPetr Machata 133879bc55e3SPetr Machata struct nexthop *nexthop_select_path(struct nexthop *nh, int hash) 133979bc55e3SPetr Machata { 134079bc55e3SPetr Machata struct nh_group *nhg; 134179bc55e3SPetr Machata 134279bc55e3SPetr Machata if (!nh->is_group) 134379bc55e3SPetr Machata return nh; 134479bc55e3SPetr Machata 134579bc55e3SPetr Machata nhg = rcu_dereference(nh->nh_grp); 1346de1d1ee3SPetr Machata if (nhg->hash_threshold) 1347de1d1ee3SPetr Machata return nexthop_select_path_hthr(nhg, hash); 1348283a72a5SPetr Machata else if (nhg->resilient) 1349283a72a5SPetr Machata return nexthop_select_path_res(nhg, hash); 135079bc55e3SPetr Machata 135179bc55e3SPetr Machata /* Unreachable. */ 135279bc55e3SPetr Machata return NULL; 135379bc55e3SPetr Machata } 1354430a0491SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_select_path); 1355430a0491SDavid Ahern 1356f88c9aa1SDavid Ahern int nexthop_for_each_fib6_nh(struct nexthop *nh, 1357f88c9aa1SDavid Ahern int (*cb)(struct fib6_nh *nh, void *arg), 1358f88c9aa1SDavid Ahern void *arg) 1359f88c9aa1SDavid Ahern { 1360f88c9aa1SDavid Ahern struct nh_info *nhi; 1361f88c9aa1SDavid Ahern int err; 1362f88c9aa1SDavid Ahern 1363f88c9aa1SDavid Ahern if (nh->is_group) { 1364f88c9aa1SDavid Ahern struct nh_group *nhg; 1365f88c9aa1SDavid Ahern int i; 1366f88c9aa1SDavid Ahern 1367f88c9aa1SDavid Ahern nhg = rcu_dereference_rtnl(nh->nh_grp); 1368f88c9aa1SDavid Ahern for (i = 0; i < nhg->num_nh; i++) { 1369f88c9aa1SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1370f88c9aa1SDavid Ahern 1371f88c9aa1SDavid Ahern nhi = rcu_dereference_rtnl(nhge->nh->nh_info); 1372f88c9aa1SDavid Ahern err = cb(&nhi->fib6_nh, arg); 1373f88c9aa1SDavid Ahern if (err) 1374f88c9aa1SDavid Ahern return err; 1375f88c9aa1SDavid Ahern } 1376f88c9aa1SDavid Ahern } else { 1377f88c9aa1SDavid Ahern nhi = rcu_dereference_rtnl(nh->nh_info); 1378f88c9aa1SDavid Ahern err = cb(&nhi->fib6_nh, arg); 1379f88c9aa1SDavid Ahern if (err) 1380f88c9aa1SDavid Ahern return err; 1381f88c9aa1SDavid Ahern } 1382f88c9aa1SDavid Ahern 1383f88c9aa1SDavid Ahern return 0; 1384f88c9aa1SDavid Ahern } 1385f88c9aa1SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh); 1386f88c9aa1SDavid Ahern 13877bf4796dSDavid Ahern static int check_src_addr(const struct in6_addr *saddr, 13887bf4796dSDavid Ahern struct netlink_ext_ack *extack) 13897bf4796dSDavid Ahern { 13907bf4796dSDavid Ahern if (!ipv6_addr_any(saddr)) { 13917bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects"); 13927bf4796dSDavid Ahern return -EINVAL; 13937bf4796dSDavid Ahern } 13947bf4796dSDavid Ahern return 0; 13957bf4796dSDavid Ahern } 13967bf4796dSDavid Ahern 1397f88d8ea6SDavid Ahern int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg, 1398f88d8ea6SDavid Ahern struct netlink_ext_ack *extack) 1399f88d8ea6SDavid Ahern { 1400f88d8ea6SDavid Ahern struct nh_info *nhi; 1401ce9ac056SDavid Ahern bool is_fdb_nh; 140238428d68SRoopa Prabhu 1403f88d8ea6SDavid Ahern /* fib6_src is unique to a fib6_info and limits the ability to cache 1404f88d8ea6SDavid Ahern * routes in fib6_nh within a nexthop that is potentially shared 1405f88d8ea6SDavid Ahern * across multiple fib entries. If the config wants to use source 1406f88d8ea6SDavid Ahern * routing it can not use nexthop objects. mlxsw also does not allow 1407f88d8ea6SDavid Ahern * fib6_src on routes. 1408f88d8ea6SDavid Ahern */ 14097bf4796dSDavid Ahern if (cfg && check_src_addr(&cfg->fc_src, extack) < 0) 1410f88d8ea6SDavid Ahern return -EINVAL; 1411f88d8ea6SDavid Ahern 1412f88d8ea6SDavid Ahern if (nh->is_group) { 1413f88d8ea6SDavid Ahern struct nh_group *nhg; 1414f88d8ea6SDavid Ahern 1415f88d8ea6SDavid Ahern nhg = rtnl_dereference(nh->nh_grp); 1416f88d8ea6SDavid Ahern if (nhg->has_v4) 1417f88d8ea6SDavid Ahern goto no_v4_nh; 1418ce9ac056SDavid Ahern is_fdb_nh = nhg->fdb_nh; 1419f88d8ea6SDavid Ahern } else { 1420f88d8ea6SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 1421f88d8ea6SDavid Ahern if (nhi->family == AF_INET) 1422f88d8ea6SDavid Ahern goto no_v4_nh; 1423ce9ac056SDavid Ahern is_fdb_nh = nhi->fdb_nh; 1424ce9ac056SDavid Ahern } 1425ce9ac056SDavid Ahern 1426ce9ac056SDavid Ahern if (is_fdb_nh) { 1427ce9ac056SDavid Ahern NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 1428ce9ac056SDavid Ahern return -EINVAL; 1429f88d8ea6SDavid Ahern } 1430f88d8ea6SDavid Ahern 1431f88d8ea6SDavid Ahern return 0; 1432f88d8ea6SDavid Ahern no_v4_nh: 1433f88d8ea6SDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop"); 1434f88d8ea6SDavid Ahern return -EINVAL; 1435f88d8ea6SDavid Ahern } 1436f88d8ea6SDavid Ahern EXPORT_SYMBOL_GPL(fib6_check_nexthop); 1437f88d8ea6SDavid Ahern 14387bf4796dSDavid Ahern /* if existing nexthop has ipv6 routes linked to it, need 14397bf4796dSDavid Ahern * to verify this new spec works with ipv6 14407bf4796dSDavid Ahern */ 14417bf4796dSDavid Ahern static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new, 14427bf4796dSDavid Ahern struct netlink_ext_ack *extack) 14437bf4796dSDavid Ahern { 14447bf4796dSDavid Ahern struct fib6_info *f6i; 14457bf4796dSDavid Ahern 14467bf4796dSDavid Ahern if (list_empty(&old->f6i_list)) 14477bf4796dSDavid Ahern return 0; 14487bf4796dSDavid Ahern 14497bf4796dSDavid Ahern list_for_each_entry(f6i, &old->f6i_list, nh_list) { 14507bf4796dSDavid Ahern if (check_src_addr(&f6i->fib6_src.addr, extack) < 0) 14517bf4796dSDavid Ahern return -EINVAL; 14527bf4796dSDavid Ahern } 14537bf4796dSDavid Ahern 14547bf4796dSDavid Ahern return fib6_check_nexthop(new, NULL, extack); 14557bf4796dSDavid Ahern } 14567bf4796dSDavid Ahern 1457ce9ac056SDavid Ahern static int nexthop_check_scope(struct nh_info *nhi, u8 scope, 14584c7e8084SDavid Ahern struct netlink_ext_ack *extack) 14594c7e8084SDavid Ahern { 14604c7e8084SDavid Ahern if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) { 14614c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, 14624c7e8084SDavid Ahern "Route with host scope can not have a gateway"); 14634c7e8084SDavid Ahern return -EINVAL; 14644c7e8084SDavid Ahern } 14654c7e8084SDavid Ahern 14664c7e8084SDavid Ahern if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) { 14674c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop"); 14684c7e8084SDavid Ahern return -EINVAL; 14694c7e8084SDavid Ahern } 14704c7e8084SDavid Ahern 14714c7e8084SDavid Ahern return 0; 14724c7e8084SDavid Ahern } 14734c7e8084SDavid Ahern 14744c7e8084SDavid Ahern /* Invoked by fib add code to verify nexthop by id is ok with 14754c7e8084SDavid Ahern * config for prefix; parts of fib_check_nh not done when nexthop 14764c7e8084SDavid Ahern * object is used. 14774c7e8084SDavid Ahern */ 14784c7e8084SDavid Ahern int fib_check_nexthop(struct nexthop *nh, u8 scope, 14794c7e8084SDavid Ahern struct netlink_ext_ack *extack) 14804c7e8084SDavid Ahern { 1481ce9ac056SDavid Ahern struct nh_info *nhi; 14824c7e8084SDavid Ahern int err = 0; 14834c7e8084SDavid Ahern 1484ce9ac056SDavid Ahern if (nh->is_group) { 1485ce9ac056SDavid Ahern struct nh_group *nhg; 1486ce9ac056SDavid Ahern 1487ce9ac056SDavid Ahern nhg = rtnl_dereference(nh->nh_grp); 1488ce9ac056SDavid Ahern if (nhg->fdb_nh) { 148938428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 149038428d68SRoopa Prabhu err = -EINVAL; 149138428d68SRoopa Prabhu goto out; 149238428d68SRoopa Prabhu } 149338428d68SRoopa Prabhu 14944c7e8084SDavid Ahern if (scope == RT_SCOPE_HOST) { 14954c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops"); 14964c7e8084SDavid Ahern err = -EINVAL; 14974c7e8084SDavid Ahern goto out; 14984c7e8084SDavid Ahern } 14994c7e8084SDavid Ahern 15004c7e8084SDavid Ahern /* all nexthops in a group have the same scope */ 1501ce9ac056SDavid Ahern nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info); 1502ce9ac056SDavid Ahern err = nexthop_check_scope(nhi, scope, extack); 15034c7e8084SDavid Ahern } else { 1504ce9ac056SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 1505ce9ac056SDavid Ahern if (nhi->fdb_nh) { 1506ce9ac056SDavid Ahern NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 1507ce9ac056SDavid Ahern err = -EINVAL; 1508ce9ac056SDavid Ahern goto out; 15094c7e8084SDavid Ahern } 1510ce9ac056SDavid Ahern err = nexthop_check_scope(nhi, scope, extack); 1511ce9ac056SDavid Ahern } 1512ce9ac056SDavid Ahern 15134c7e8084SDavid Ahern out: 15144c7e8084SDavid Ahern return err; 15154c7e8084SDavid Ahern } 15164c7e8084SDavid Ahern 15177bf4796dSDavid Ahern static int fib_check_nh_list(struct nexthop *old, struct nexthop *new, 15187bf4796dSDavid Ahern struct netlink_ext_ack *extack) 15197bf4796dSDavid Ahern { 15207bf4796dSDavid Ahern struct fib_info *fi; 15217bf4796dSDavid Ahern 15227bf4796dSDavid Ahern list_for_each_entry(fi, &old->fi_list, nh_list) { 15237bf4796dSDavid Ahern int err; 15247bf4796dSDavid Ahern 15257bf4796dSDavid Ahern err = fib_check_nexthop(new, fi->fib_scope, extack); 15267bf4796dSDavid Ahern if (err) 15277bf4796dSDavid Ahern return err; 15287bf4796dSDavid Ahern } 15297bf4796dSDavid Ahern return 0; 15307bf4796dSDavid Ahern } 15317bf4796dSDavid Ahern 1532283a72a5SPetr Machata static bool nh_res_nhge_is_balanced(const struct nh_grp_entry *nhge) 1533283a72a5SPetr Machata { 1534283a72a5SPetr Machata return nhge->res.count_buckets == nhge->res.wants_buckets; 1535283a72a5SPetr Machata } 1536283a72a5SPetr Machata 1537283a72a5SPetr Machata static bool nh_res_nhge_is_ow(const struct nh_grp_entry *nhge) 1538283a72a5SPetr Machata { 1539283a72a5SPetr Machata return nhge->res.count_buckets > nhge->res.wants_buckets; 1540283a72a5SPetr Machata } 1541283a72a5SPetr Machata 1542283a72a5SPetr Machata static bool nh_res_nhge_is_uw(const struct nh_grp_entry *nhge) 1543283a72a5SPetr Machata { 1544283a72a5SPetr Machata return nhge->res.count_buckets < nhge->res.wants_buckets; 1545283a72a5SPetr Machata } 1546283a72a5SPetr Machata 1547283a72a5SPetr Machata static bool nh_res_table_is_balanced(const struct nh_res_table *res_table) 1548283a72a5SPetr Machata { 1549283a72a5SPetr Machata return list_empty(&res_table->uw_nh_entries); 1550283a72a5SPetr Machata } 1551283a72a5SPetr Machata 1552283a72a5SPetr Machata static void nh_res_bucket_unset_nh(struct nh_res_bucket *bucket) 1553283a72a5SPetr Machata { 1554283a72a5SPetr Machata struct nh_grp_entry *nhge; 1555283a72a5SPetr Machata 1556283a72a5SPetr Machata if (bucket->occupied) { 1557283a72a5SPetr Machata nhge = nh_res_dereference(bucket->nh_entry); 1558283a72a5SPetr Machata nhge->res.count_buckets--; 1559283a72a5SPetr Machata bucket->occupied = false; 1560283a72a5SPetr Machata } 1561283a72a5SPetr Machata } 1562283a72a5SPetr Machata 1563283a72a5SPetr Machata static void nh_res_bucket_set_nh(struct nh_res_bucket *bucket, 1564283a72a5SPetr Machata struct nh_grp_entry *nhge) 1565283a72a5SPetr Machata { 1566283a72a5SPetr Machata nh_res_bucket_unset_nh(bucket); 1567283a72a5SPetr Machata 1568283a72a5SPetr Machata bucket->occupied = true; 1569283a72a5SPetr Machata rcu_assign_pointer(bucket->nh_entry, nhge); 1570283a72a5SPetr Machata nhge->res.count_buckets++; 1571283a72a5SPetr Machata } 1572283a72a5SPetr Machata 1573283a72a5SPetr Machata static bool nh_res_bucket_should_migrate(struct nh_res_table *res_table, 1574283a72a5SPetr Machata struct nh_res_bucket *bucket, 1575283a72a5SPetr Machata unsigned long *deadline, bool *force) 1576283a72a5SPetr Machata { 1577283a72a5SPetr Machata unsigned long now = jiffies; 1578283a72a5SPetr Machata struct nh_grp_entry *nhge; 1579283a72a5SPetr Machata unsigned long idle_point; 1580283a72a5SPetr Machata 1581283a72a5SPetr Machata if (!bucket->occupied) { 1582283a72a5SPetr Machata /* The bucket is not occupied, its NHGE pointer is either 1583283a72a5SPetr Machata * NULL or obsolete. We _have to_ migrate: set force. 1584283a72a5SPetr Machata */ 1585283a72a5SPetr Machata *force = true; 1586283a72a5SPetr Machata return true; 1587283a72a5SPetr Machata } 1588283a72a5SPetr Machata 1589283a72a5SPetr Machata nhge = nh_res_dereference(bucket->nh_entry); 1590283a72a5SPetr Machata 1591283a72a5SPetr Machata /* If the bucket is populated by an underweight or balanced 1592283a72a5SPetr Machata * nexthop, do not migrate. 1593283a72a5SPetr Machata */ 1594283a72a5SPetr Machata if (!nh_res_nhge_is_ow(nhge)) 1595283a72a5SPetr Machata return false; 1596283a72a5SPetr Machata 1597283a72a5SPetr Machata /* At this point we know that the bucket is populated with an 1598283a72a5SPetr Machata * overweight nexthop. It needs to be migrated to a new nexthop if 1599283a72a5SPetr Machata * the idle timer of unbalanced timer expired. 1600283a72a5SPetr Machata */ 1601283a72a5SPetr Machata 1602283a72a5SPetr Machata idle_point = nh_res_bucket_idle_point(res_table, bucket, now); 1603283a72a5SPetr Machata if (time_after_eq(now, idle_point)) { 1604283a72a5SPetr Machata /* The bucket is idle. We _can_ migrate: unset force. */ 1605283a72a5SPetr Machata *force = false; 1606283a72a5SPetr Machata return true; 1607283a72a5SPetr Machata } 1608283a72a5SPetr Machata 1609283a72a5SPetr Machata /* Unbalanced timer of 0 means "never force". */ 1610283a72a5SPetr Machata if (res_table->unbalanced_timer) { 1611283a72a5SPetr Machata unsigned long unb_point; 1612283a72a5SPetr Machata 1613283a72a5SPetr Machata unb_point = nh_res_table_unb_point(res_table); 1614283a72a5SPetr Machata if (time_after(now, unb_point)) { 1615283a72a5SPetr Machata /* The bucket is not idle, but the unbalanced timer 1616283a72a5SPetr Machata * expired. We _can_ migrate, but set force anyway, 1617283a72a5SPetr Machata * so that drivers know to ignore activity reports 1618283a72a5SPetr Machata * from the HW. 1619283a72a5SPetr Machata */ 1620283a72a5SPetr Machata *force = true; 1621283a72a5SPetr Machata return true; 1622283a72a5SPetr Machata } 1623283a72a5SPetr Machata 1624283a72a5SPetr Machata nh_res_time_set_deadline(unb_point, deadline); 1625283a72a5SPetr Machata } 1626283a72a5SPetr Machata 1627283a72a5SPetr Machata nh_res_time_set_deadline(idle_point, deadline); 1628283a72a5SPetr Machata return false; 1629283a72a5SPetr Machata } 1630283a72a5SPetr Machata 1631283a72a5SPetr Machata static bool nh_res_bucket_migrate(struct nh_res_table *res_table, 16320b4818aaSPetr Machata u16 bucket_index, bool notify, 16330b4818aaSPetr Machata bool notify_nl, bool force) 1634283a72a5SPetr Machata { 1635283a72a5SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index]; 1636283a72a5SPetr Machata struct nh_grp_entry *new_nhge; 16377c37c7e0SPetr Machata struct netlink_ext_ack extack; 16387c37c7e0SPetr Machata int err; 1639283a72a5SPetr Machata 1640283a72a5SPetr Machata new_nhge = list_first_entry_or_null(&res_table->uw_nh_entries, 1641283a72a5SPetr Machata struct nh_grp_entry, 1642283a72a5SPetr Machata res.uw_nh_entry); 1643283a72a5SPetr Machata if (WARN_ON_ONCE(!new_nhge)) 1644283a72a5SPetr Machata /* If this function is called, "bucket" is either not 1645283a72a5SPetr Machata * occupied, or it belongs to a next hop that is 1646283a72a5SPetr Machata * overweight. In either case, there ought to be a 1647283a72a5SPetr Machata * corresponding underweight next hop. 1648283a72a5SPetr Machata */ 1649283a72a5SPetr Machata return false; 1650283a72a5SPetr Machata 16517c37c7e0SPetr Machata if (notify) { 16527c37c7e0SPetr Machata struct nh_grp_entry *old_nhge; 16537c37c7e0SPetr Machata 16547c37c7e0SPetr Machata old_nhge = nh_res_dereference(bucket->nh_entry); 16557c37c7e0SPetr Machata err = call_nexthop_res_bucket_notifiers(res_table->net, 16567c37c7e0SPetr Machata res_table->nhg_id, 16577c37c7e0SPetr Machata bucket_index, force, 16587c37c7e0SPetr Machata old_nhge->nh, 16597c37c7e0SPetr Machata new_nhge->nh, &extack); 16607c37c7e0SPetr Machata if (err) { 16617c37c7e0SPetr Machata pr_err_ratelimited("%s\n", extack._msg); 16627c37c7e0SPetr Machata if (!force) 16637c37c7e0SPetr Machata return false; 16647c37c7e0SPetr Machata /* It is not possible to veto a forced replacement, so 16657c37c7e0SPetr Machata * just clear the hardware flags from the nexthop 16667c37c7e0SPetr Machata * bucket to indicate to user space that this bucket is 16677c37c7e0SPetr Machata * not correctly populated in hardware. 16687c37c7e0SPetr Machata */ 16697c37c7e0SPetr Machata bucket->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP); 16707c37c7e0SPetr Machata } 16717c37c7e0SPetr Machata } 16727c37c7e0SPetr Machata 1673283a72a5SPetr Machata nh_res_bucket_set_nh(bucket, new_nhge); 1674283a72a5SPetr Machata nh_res_bucket_set_idle(res_table, bucket); 1675283a72a5SPetr Machata 16760b4818aaSPetr Machata if (notify_nl) 16770b4818aaSPetr Machata nexthop_bucket_notify(res_table, bucket_index); 16780b4818aaSPetr Machata 1679283a72a5SPetr Machata if (nh_res_nhge_is_balanced(new_nhge)) 1680283a72a5SPetr Machata list_del(&new_nhge->res.uw_nh_entry); 1681283a72a5SPetr Machata return true; 1682283a72a5SPetr Machata } 1683283a72a5SPetr Machata 1684283a72a5SPetr Machata #define NH_RES_UPKEEP_DW_MINIMUM_INTERVAL (HZ / 2) 1685283a72a5SPetr Machata 16860b4818aaSPetr Machata static void nh_res_table_upkeep(struct nh_res_table *res_table, 16870b4818aaSPetr Machata bool notify, bool notify_nl) 1688283a72a5SPetr Machata { 1689283a72a5SPetr Machata unsigned long now = jiffies; 1690283a72a5SPetr Machata unsigned long deadline; 1691283a72a5SPetr Machata u16 i; 1692283a72a5SPetr Machata 1693283a72a5SPetr Machata /* Deadline is the next time that upkeep should be run. It is the 1694283a72a5SPetr Machata * earliest time at which one of the buckets might be migrated. 1695283a72a5SPetr Machata * Start at the most pessimistic estimate: either unbalanced_timer 1696283a72a5SPetr Machata * from now, or if there is none, idle_timer from now. For each 1697283a72a5SPetr Machata * encountered time point, call nh_res_time_set_deadline() to 1698283a72a5SPetr Machata * refine the estimate. 1699283a72a5SPetr Machata */ 1700283a72a5SPetr Machata if (res_table->unbalanced_timer) 1701283a72a5SPetr Machata deadline = now + res_table->unbalanced_timer; 1702283a72a5SPetr Machata else 1703283a72a5SPetr Machata deadline = now + res_table->idle_timer; 1704283a72a5SPetr Machata 1705283a72a5SPetr Machata for (i = 0; i < res_table->num_nh_buckets; i++) { 1706283a72a5SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 1707283a72a5SPetr Machata bool force; 1708283a72a5SPetr Machata 1709283a72a5SPetr Machata if (nh_res_bucket_should_migrate(res_table, bucket, 1710283a72a5SPetr Machata &deadline, &force)) { 17117c37c7e0SPetr Machata if (!nh_res_bucket_migrate(res_table, i, notify, 17120b4818aaSPetr Machata notify_nl, force)) { 1713283a72a5SPetr Machata unsigned long idle_point; 1714283a72a5SPetr Machata 1715283a72a5SPetr Machata /* A driver can override the migration 1716283a72a5SPetr Machata * decision if the HW reports that the 1717283a72a5SPetr Machata * bucket is actually not idle. Therefore 1718283a72a5SPetr Machata * remark the bucket as busy again and 1719283a72a5SPetr Machata * update the deadline. 1720283a72a5SPetr Machata */ 1721283a72a5SPetr Machata nh_res_bucket_set_busy(bucket); 1722283a72a5SPetr Machata idle_point = nh_res_bucket_idle_point(res_table, 1723283a72a5SPetr Machata bucket, 1724283a72a5SPetr Machata now); 1725283a72a5SPetr Machata nh_res_time_set_deadline(idle_point, &deadline); 1726283a72a5SPetr Machata } 1727283a72a5SPetr Machata } 1728283a72a5SPetr Machata } 1729283a72a5SPetr Machata 1730283a72a5SPetr Machata /* If the group is still unbalanced, schedule the next upkeep to 1731283a72a5SPetr Machata * either the deadline computed above, or the minimum deadline, 1732283a72a5SPetr Machata * whichever comes later. 1733283a72a5SPetr Machata */ 1734283a72a5SPetr Machata if (!nh_res_table_is_balanced(res_table)) { 1735283a72a5SPetr Machata unsigned long now = jiffies; 1736283a72a5SPetr Machata unsigned long min_deadline; 1737283a72a5SPetr Machata 1738283a72a5SPetr Machata min_deadline = now + NH_RES_UPKEEP_DW_MINIMUM_INTERVAL; 1739283a72a5SPetr Machata if (time_before(deadline, min_deadline)) 1740283a72a5SPetr Machata deadline = min_deadline; 1741283a72a5SPetr Machata 1742283a72a5SPetr Machata queue_delayed_work(system_power_efficient_wq, 1743283a72a5SPetr Machata &res_table->upkeep_dw, deadline - now); 1744283a72a5SPetr Machata } 1745283a72a5SPetr Machata } 1746283a72a5SPetr Machata 1747283a72a5SPetr Machata static void nh_res_table_upkeep_dw(struct work_struct *work) 1748283a72a5SPetr Machata { 1749283a72a5SPetr Machata struct delayed_work *dw = to_delayed_work(work); 1750283a72a5SPetr Machata struct nh_res_table *res_table; 1751283a72a5SPetr Machata 1752283a72a5SPetr Machata res_table = container_of(dw, struct nh_res_table, upkeep_dw); 17530b4818aaSPetr Machata nh_res_table_upkeep(res_table, true, true); 1754283a72a5SPetr Machata } 1755283a72a5SPetr Machata 1756283a72a5SPetr Machata static void nh_res_table_cancel_upkeep(struct nh_res_table *res_table) 1757283a72a5SPetr Machata { 1758283a72a5SPetr Machata cancel_delayed_work_sync(&res_table->upkeep_dw); 1759283a72a5SPetr Machata } 1760283a72a5SPetr Machata 1761283a72a5SPetr Machata static void nh_res_group_rebalance(struct nh_group *nhg, 1762283a72a5SPetr Machata struct nh_res_table *res_table) 1763283a72a5SPetr Machata { 1764283a72a5SPetr Machata int prev_upper_bound = 0; 1765283a72a5SPetr Machata int total = 0; 1766283a72a5SPetr Machata int w = 0; 1767283a72a5SPetr Machata int i; 1768283a72a5SPetr Machata 1769283a72a5SPetr Machata INIT_LIST_HEAD(&res_table->uw_nh_entries); 1770283a72a5SPetr Machata 1771283a72a5SPetr Machata for (i = 0; i < nhg->num_nh; ++i) 1772283a72a5SPetr Machata total += nhg->nh_entries[i].weight; 1773283a72a5SPetr Machata 1774283a72a5SPetr Machata for (i = 0; i < nhg->num_nh; ++i) { 1775283a72a5SPetr Machata struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1776283a72a5SPetr Machata int upper_bound; 1777283a72a5SPetr Machata 1778283a72a5SPetr Machata w += nhge->weight; 1779283a72a5SPetr Machata upper_bound = DIV_ROUND_CLOSEST(res_table->num_nh_buckets * w, 1780283a72a5SPetr Machata total); 1781283a72a5SPetr Machata nhge->res.wants_buckets = upper_bound - prev_upper_bound; 1782283a72a5SPetr Machata prev_upper_bound = upper_bound; 1783283a72a5SPetr Machata 1784283a72a5SPetr Machata if (nh_res_nhge_is_uw(nhge)) { 1785283a72a5SPetr Machata if (list_empty(&res_table->uw_nh_entries)) 1786283a72a5SPetr Machata res_table->unbalanced_since = jiffies; 1787283a72a5SPetr Machata list_add(&nhge->res.uw_nh_entry, 1788283a72a5SPetr Machata &res_table->uw_nh_entries); 1789283a72a5SPetr Machata } 1790283a72a5SPetr Machata } 1791283a72a5SPetr Machata } 1792283a72a5SPetr Machata 1793283a72a5SPetr Machata /* Migrate buckets in res_table so that they reference NHGE's from NHG with 1794283a72a5SPetr Machata * the right NH ID. Set those buckets that do not have a corresponding NHGE 1795283a72a5SPetr Machata * entry in NHG as not occupied. 1796283a72a5SPetr Machata */ 1797283a72a5SPetr Machata static void nh_res_table_migrate_buckets(struct nh_res_table *res_table, 1798283a72a5SPetr Machata struct nh_group *nhg) 1799283a72a5SPetr Machata { 1800283a72a5SPetr Machata u16 i; 1801283a72a5SPetr Machata 1802283a72a5SPetr Machata for (i = 0; i < res_table->num_nh_buckets; i++) { 1803283a72a5SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 1804283a72a5SPetr Machata u32 id = rtnl_dereference(bucket->nh_entry)->nh->id; 1805283a72a5SPetr Machata bool found = false; 1806283a72a5SPetr Machata int j; 1807283a72a5SPetr Machata 1808283a72a5SPetr Machata for (j = 0; j < nhg->num_nh; j++) { 1809283a72a5SPetr Machata struct nh_grp_entry *nhge = &nhg->nh_entries[j]; 1810283a72a5SPetr Machata 1811283a72a5SPetr Machata if (nhge->nh->id == id) { 1812283a72a5SPetr Machata nh_res_bucket_set_nh(bucket, nhge); 1813283a72a5SPetr Machata found = true; 1814283a72a5SPetr Machata break; 1815283a72a5SPetr Machata } 1816283a72a5SPetr Machata } 1817283a72a5SPetr Machata 1818283a72a5SPetr Machata if (!found) 1819283a72a5SPetr Machata nh_res_bucket_unset_nh(bucket); 1820283a72a5SPetr Machata } 1821283a72a5SPetr Machata } 1822283a72a5SPetr Machata 1823283a72a5SPetr Machata static void replace_nexthop_grp_res(struct nh_group *oldg, 1824283a72a5SPetr Machata struct nh_group *newg) 1825283a72a5SPetr Machata { 1826283a72a5SPetr Machata /* For NH group replacement, the new NHG might only have a stub 1827283a72a5SPetr Machata * hash table with 0 buckets, because the number of buckets was not 1828283a72a5SPetr Machata * specified. For NH removal, oldg and newg both reference the same 1829283a72a5SPetr Machata * res_table. So in any case, in the following, we want to work 1830283a72a5SPetr Machata * with oldg->res_table. 1831283a72a5SPetr Machata */ 1832283a72a5SPetr Machata struct nh_res_table *old_res_table = rtnl_dereference(oldg->res_table); 1833283a72a5SPetr Machata unsigned long prev_unbalanced_since = old_res_table->unbalanced_since; 1834283a72a5SPetr Machata bool prev_has_uw = !list_empty(&old_res_table->uw_nh_entries); 1835283a72a5SPetr Machata 1836283a72a5SPetr Machata nh_res_table_cancel_upkeep(old_res_table); 1837283a72a5SPetr Machata nh_res_table_migrate_buckets(old_res_table, newg); 1838283a72a5SPetr Machata nh_res_group_rebalance(newg, old_res_table); 1839283a72a5SPetr Machata if (prev_has_uw && !list_empty(&old_res_table->uw_nh_entries)) 1840283a72a5SPetr Machata old_res_table->unbalanced_since = prev_unbalanced_since; 18410b4818aaSPetr Machata nh_res_table_upkeep(old_res_table, true, false); 1842283a72a5SPetr Machata } 1843283a72a5SPetr Machata 1844de1d1ee3SPetr Machata static void nh_hthr_group_rebalance(struct nh_group *nhg) 1845430a0491SDavid Ahern { 1846430a0491SDavid Ahern int total = 0; 1847430a0491SDavid Ahern int w = 0; 1848430a0491SDavid Ahern int i; 1849430a0491SDavid Ahern 1850430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) 1851430a0491SDavid Ahern total += nhg->nh_entries[i].weight; 1852430a0491SDavid Ahern 1853430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 1854430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1855430a0491SDavid Ahern int upper_bound; 1856430a0491SDavid Ahern 1857430a0491SDavid Ahern w += nhge->weight; 1858430a0491SDavid Ahern upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1; 1859de1d1ee3SPetr Machata atomic_set(&nhge->hthr.upper_bound, upper_bound); 1860430a0491SDavid Ahern } 1861430a0491SDavid Ahern } 1862430a0491SDavid Ahern 1863ac21753aSDavid Ahern static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge, 1864430a0491SDavid Ahern struct nl_info *nlinfo) 1865430a0491SDavid Ahern { 186690f33bffSNikolay Aleksandrov struct nh_grp_entry *nhges, *new_nhges; 1867ac21753aSDavid Ahern struct nexthop *nhp = nhge->nh_parent; 1868833a1065SIdo Schimmel struct netlink_ext_ack extack; 1869430a0491SDavid Ahern struct nexthop *nh = nhge->nh; 187090f33bffSNikolay Aleksandrov struct nh_group *nhg, *newg; 1871833a1065SIdo Schimmel int i, j, err; 1872430a0491SDavid Ahern 1873430a0491SDavid Ahern WARN_ON(!nh); 1874430a0491SDavid Ahern 1875ac21753aSDavid Ahern nhg = rtnl_dereference(nhp->nh_grp); 187690f33bffSNikolay Aleksandrov newg = nhg->spare; 1877430a0491SDavid Ahern 187890f33bffSNikolay Aleksandrov /* last entry, keep it visible and remove the parent */ 187990f33bffSNikolay Aleksandrov if (nhg->num_nh == 1) { 188090f33bffSNikolay Aleksandrov remove_nexthop(net, nhp, nlinfo); 1881430a0491SDavid Ahern return; 188290f33bffSNikolay Aleksandrov } 1883430a0491SDavid Ahern 1884863b2558SIdo Schimmel newg->has_v4 = false; 188590e1a9e2SPetr Machata newg->is_multipath = nhg->is_multipath; 1886de1d1ee3SPetr Machata newg->hash_threshold = nhg->hash_threshold; 1887283a72a5SPetr Machata newg->resilient = nhg->resilient; 1888ce9ac056SDavid Ahern newg->fdb_nh = nhg->fdb_nh; 188990f33bffSNikolay Aleksandrov newg->num_nh = nhg->num_nh; 1890430a0491SDavid Ahern 189190f33bffSNikolay Aleksandrov /* copy old entries to new except the one getting removed */ 189290f33bffSNikolay Aleksandrov nhges = nhg->nh_entries; 189390f33bffSNikolay Aleksandrov new_nhges = newg->nh_entries; 189490f33bffSNikolay Aleksandrov for (i = 0, j = 0; i < nhg->num_nh; ++i) { 1895863b2558SIdo Schimmel struct nh_info *nhi; 1896863b2558SIdo Schimmel 189790f33bffSNikolay Aleksandrov /* current nexthop getting removed */ 189890f33bffSNikolay Aleksandrov if (nhg->nh_entries[i].nh == nh) { 189990f33bffSNikolay Aleksandrov newg->num_nh--; 190090f33bffSNikolay Aleksandrov continue; 190190f33bffSNikolay Aleksandrov } 1902430a0491SDavid Ahern 1903863b2558SIdo Schimmel nhi = rtnl_dereference(nhges[i].nh->nh_info); 1904863b2558SIdo Schimmel if (nhi->family == AF_INET) 1905863b2558SIdo Schimmel newg->has_v4 = true; 1906863b2558SIdo Schimmel 190790f33bffSNikolay Aleksandrov list_del(&nhges[i].nh_list); 1908f4676ea7SIdo Schimmel new_nhges[j].stats = nhges[i].stats; 190990f33bffSNikolay Aleksandrov new_nhges[j].nh_parent = nhges[i].nh_parent; 191090f33bffSNikolay Aleksandrov new_nhges[j].nh = nhges[i].nh; 191190f33bffSNikolay Aleksandrov new_nhges[j].weight = nhges[i].weight; 191290f33bffSNikolay Aleksandrov list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list); 191390f33bffSNikolay Aleksandrov j++; 191490f33bffSNikolay Aleksandrov } 191590f33bffSNikolay Aleksandrov 1916de1d1ee3SPetr Machata if (newg->hash_threshold) 1917de1d1ee3SPetr Machata nh_hthr_group_rebalance(newg); 1918283a72a5SPetr Machata else if (newg->resilient) 1919283a72a5SPetr Machata replace_nexthop_grp_res(nhg, newg); 1920283a72a5SPetr Machata 192190f33bffSNikolay Aleksandrov rcu_assign_pointer(nhp->nh_grp, newg); 192290f33bffSNikolay Aleksandrov 192390f33bffSNikolay Aleksandrov list_del(&nhge->nh_list); 1924f4676ea7SIdo Schimmel free_percpu(nhge->stats); 192590f33bffSNikolay Aleksandrov nexthop_put(nhge->nh); 1926430a0491SDavid Ahern 19277c37c7e0SPetr Machata /* Removal of a NH from a resilient group is notified through 19287c37c7e0SPetr Machata * bucket notifications. 19297c37c7e0SPetr Machata */ 1930de1d1ee3SPetr Machata if (newg->hash_threshold) { 19317c37c7e0SPetr Machata err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp, 19327c37c7e0SPetr Machata &extack); 1933833a1065SIdo Schimmel if (err) 1934833a1065SIdo Schimmel pr_err("%s\n", extack._msg); 19357c37c7e0SPetr Machata } 1936833a1065SIdo Schimmel 1937430a0491SDavid Ahern if (nlinfo) 1938ac21753aSDavid Ahern nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo); 1939430a0491SDavid Ahern } 1940430a0491SDavid Ahern 1941430a0491SDavid Ahern static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh, 1942430a0491SDavid Ahern struct nl_info *nlinfo) 1943430a0491SDavid Ahern { 1944430a0491SDavid Ahern struct nh_grp_entry *nhge, *tmp; 1945430a0491SDavid Ahern 1946ac21753aSDavid Ahern list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list) 1947ac21753aSDavid Ahern remove_nh_grp_entry(net, nhge, nlinfo); 1948430a0491SDavid Ahern 194990f33bffSNikolay Aleksandrov /* make sure all see the newly published array before releasing rtnl */ 1950df6afe2fSIdo Schimmel synchronize_net(); 1951430a0491SDavid Ahern } 1952430a0491SDavid Ahern 1953430a0491SDavid Ahern static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo) 1954430a0491SDavid Ahern { 1955430a0491SDavid Ahern struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp); 1956283a72a5SPetr Machata struct nh_res_table *res_table; 1957430a0491SDavid Ahern int i, num_nh = nhg->num_nh; 1958430a0491SDavid Ahern 1959430a0491SDavid Ahern for (i = 0; i < num_nh; ++i) { 1960430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1961430a0491SDavid Ahern 1962430a0491SDavid Ahern if (WARN_ON(!nhge->nh)) 1963430a0491SDavid Ahern continue; 1964430a0491SDavid Ahern 196590f33bffSNikolay Aleksandrov list_del_init(&nhge->nh_list); 1966430a0491SDavid Ahern } 1967283a72a5SPetr Machata 1968283a72a5SPetr Machata if (nhg->resilient) { 1969283a72a5SPetr Machata res_table = rtnl_dereference(nhg->res_table); 1970283a72a5SPetr Machata nh_res_table_cancel_upkeep(res_table); 1971283a72a5SPetr Machata } 1972430a0491SDavid Ahern } 1973430a0491SDavid Ahern 19747bf4796dSDavid Ahern /* not called for nexthop replace */ 19754c7e8084SDavid Ahern static void __remove_nexthop_fib(struct net *net, struct nexthop *nh) 19764c7e8084SDavid Ahern { 1977f88d8ea6SDavid Ahern struct fib6_info *f6i, *tmp; 19784c7e8084SDavid Ahern bool do_flush = false; 19794c7e8084SDavid Ahern struct fib_info *fi; 19804c7e8084SDavid Ahern 19814c7e8084SDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) { 19824c7e8084SDavid Ahern fi->fib_flags |= RTNH_F_DEAD; 19834c7e8084SDavid Ahern do_flush = true; 19844c7e8084SDavid Ahern } 19854c7e8084SDavid Ahern if (do_flush) 19864c7e8084SDavid Ahern fib_flush(net); 1987f88d8ea6SDavid Ahern 1988f88d8ea6SDavid Ahern /* ip6_del_rt removes the entry from this list hence the _safe */ 1989f88d8ea6SDavid Ahern list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) { 1990f88d8ea6SDavid Ahern /* __ip6_del_rt does a release, so do a hold here */ 1991f88d8ea6SDavid Ahern fib6_info_hold(f6i); 19924f80116dSRoopa Prabhu ipv6_stub->ip6_del_rt(net, f6i, 1993bdf00bf2SKuniyuki Iwashima !READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode)); 1994f88d8ea6SDavid Ahern } 19954c7e8084SDavid Ahern } 19964c7e8084SDavid Ahern 1997430a0491SDavid Ahern static void __remove_nexthop(struct net *net, struct nexthop *nh, 1998430a0491SDavid Ahern struct nl_info *nlinfo) 1999430a0491SDavid Ahern { 20004c7e8084SDavid Ahern __remove_nexthop_fib(net, nh); 20014c7e8084SDavid Ahern 2002430a0491SDavid Ahern if (nh->is_group) { 2003430a0491SDavid Ahern remove_nexthop_group(nh, nlinfo); 2004430a0491SDavid Ahern } else { 2005597cfe4fSDavid Ahern struct nh_info *nhi; 2006597cfe4fSDavid Ahern 2007597cfe4fSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 2008597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev) 2009597cfe4fSDavid Ahern hlist_del(&nhi->dev_hash); 2010430a0491SDavid Ahern 2011430a0491SDavid Ahern remove_nexthop_from_groups(net, nh, nlinfo); 2012430a0491SDavid Ahern } 2013597cfe4fSDavid Ahern } 2014597cfe4fSDavid Ahern 2015ab84be7eSDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh, 2016430a0491SDavid Ahern struct nl_info *nlinfo) 2017ab84be7eSDavid Ahern { 20183578d53dSIdo Schimmel call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh, NULL); 20190695564bSIdo Schimmel 2020ab84be7eSDavid Ahern /* remove from the tree */ 2021ab84be7eSDavid Ahern rb_erase(&nh->rb_node, &net->nexthop.rb_root); 2022ab84be7eSDavid Ahern 2023ab84be7eSDavid Ahern if (nlinfo) 2024ab84be7eSDavid Ahern nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo); 2025ab84be7eSDavid Ahern 2026430a0491SDavid Ahern __remove_nexthop(net, nh, nlinfo); 2027ab84be7eSDavid Ahern nh_base_seq_inc(net); 2028ab84be7eSDavid Ahern 2029ab84be7eSDavid Ahern nexthop_put(nh); 2030ab84be7eSDavid Ahern } 2031ab84be7eSDavid Ahern 20327bf4796dSDavid Ahern /* if any FIB entries reference this nexthop, any dst entries 20337bf4796dSDavid Ahern * need to be regenerated 20347bf4796dSDavid Ahern */ 20351005f19bSNikolay Aleksandrov static void nh_rt_cache_flush(struct net *net, struct nexthop *nh, 20361005f19bSNikolay Aleksandrov struct nexthop *replaced_nh) 20377bf4796dSDavid Ahern { 20387bf4796dSDavid Ahern struct fib6_info *f6i; 20391005f19bSNikolay Aleksandrov struct nh_group *nhg; 20401005f19bSNikolay Aleksandrov int i; 20417bf4796dSDavid Ahern 20427bf4796dSDavid Ahern if (!list_empty(&nh->fi_list)) 20437bf4796dSDavid Ahern rt_cache_flush(net); 20447bf4796dSDavid Ahern 20457bf4796dSDavid Ahern list_for_each_entry(f6i, &nh->f6i_list, nh_list) 20467bf4796dSDavid Ahern ipv6_stub->fib6_update_sernum(net, f6i); 20471005f19bSNikolay Aleksandrov 20481005f19bSNikolay Aleksandrov /* if an IPv6 group was replaced, we have to release all old 20491005f19bSNikolay Aleksandrov * dsts to make sure all refcounts are released 20501005f19bSNikolay Aleksandrov */ 20511005f19bSNikolay Aleksandrov if (!replaced_nh->is_group) 20521005f19bSNikolay Aleksandrov return; 20531005f19bSNikolay Aleksandrov 20541005f19bSNikolay Aleksandrov nhg = rtnl_dereference(replaced_nh->nh_grp); 20551005f19bSNikolay Aleksandrov for (i = 0; i < nhg->num_nh; i++) { 20561005f19bSNikolay Aleksandrov struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 20571005f19bSNikolay Aleksandrov struct nh_info *nhi = rtnl_dereference(nhge->nh->nh_info); 20581005f19bSNikolay Aleksandrov 20591005f19bSNikolay Aleksandrov if (nhi->family == AF_INET6) 20601005f19bSNikolay Aleksandrov ipv6_stub->fib6_nh_release_dsts(&nhi->fib6_nh); 20611005f19bSNikolay Aleksandrov } 20627bf4796dSDavid Ahern } 20637bf4796dSDavid Ahern 20647bf4796dSDavid Ahern static int replace_nexthop_grp(struct net *net, struct nexthop *old, 2065597f48e4SPetr Machata struct nexthop *new, const struct nh_config *cfg, 20667bf4796dSDavid Ahern struct netlink_ext_ack *extack) 20677bf4796dSDavid Ahern { 2068283a72a5SPetr Machata struct nh_res_table *tmp_table = NULL; 2069283a72a5SPetr Machata struct nh_res_table *new_res_table; 2070283a72a5SPetr Machata struct nh_res_table *old_res_table; 20717bf4796dSDavid Ahern struct nh_group *oldg, *newg; 2072d144cc5fSIdo Schimmel int i, err; 20737bf4796dSDavid Ahern 20747bf4796dSDavid Ahern if (!new->is_group) { 20757bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop."); 20767bf4796dSDavid Ahern return -EINVAL; 20777bf4796dSDavid Ahern } 20787bf4796dSDavid Ahern 20797bf4796dSDavid Ahern oldg = rtnl_dereference(old->nh_grp); 20807bf4796dSDavid Ahern newg = rtnl_dereference(new->nh_grp); 20817bf4796dSDavid Ahern 2082de1d1ee3SPetr Machata if (newg->hash_threshold != oldg->hash_threshold) { 2083283a72a5SPetr Machata NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with one of a different type."); 2084283a72a5SPetr Machata return -EINVAL; 2085283a72a5SPetr Machata } 2086283a72a5SPetr Machata 2087de1d1ee3SPetr Machata if (newg->hash_threshold) { 2088283a72a5SPetr Machata err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, 2089283a72a5SPetr Machata extack); 2090283a72a5SPetr Machata if (err) 2091283a72a5SPetr Machata return err; 2092283a72a5SPetr Machata } else if (newg->resilient) { 2093283a72a5SPetr Machata new_res_table = rtnl_dereference(newg->res_table); 2094283a72a5SPetr Machata old_res_table = rtnl_dereference(oldg->res_table); 2095283a72a5SPetr Machata 2096283a72a5SPetr Machata /* Accept if num_nh_buckets was not given, but if it was 2097283a72a5SPetr Machata * given, demand that the value be correct. 2098283a72a5SPetr Machata */ 2099283a72a5SPetr Machata if (cfg->nh_grp_res_has_num_buckets && 2100283a72a5SPetr Machata cfg->nh_grp_res_num_buckets != 2101283a72a5SPetr Machata old_res_table->num_nh_buckets) { 2102283a72a5SPetr Machata NL_SET_ERR_MSG(extack, "Can not change number of buckets of a resilient nexthop group."); 2103283a72a5SPetr Machata return -EINVAL; 2104283a72a5SPetr Machata } 2105283a72a5SPetr Machata 21067c37c7e0SPetr Machata /* Emit a pre-replace notification so that listeners could veto 21077c37c7e0SPetr Machata * a potentially unsupported configuration. Otherwise, 21087c37c7e0SPetr Machata * individual bucket replacement notifications would need to be 21097c37c7e0SPetr Machata * vetoed, which is something that should only happen if the 21107c37c7e0SPetr Machata * bucket is currently active. 21117c37c7e0SPetr Machata */ 21127c37c7e0SPetr Machata err = call_nexthop_res_table_notifiers(net, new, extack); 21137c37c7e0SPetr Machata if (err) 21147c37c7e0SPetr Machata return err; 21157c37c7e0SPetr Machata 2116283a72a5SPetr Machata if (cfg->nh_grp_res_has_idle_timer) 2117283a72a5SPetr Machata old_res_table->idle_timer = cfg->nh_grp_res_idle_timer; 2118283a72a5SPetr Machata if (cfg->nh_grp_res_has_unbalanced_timer) 2119283a72a5SPetr Machata old_res_table->unbalanced_timer = 2120283a72a5SPetr Machata cfg->nh_grp_res_unbalanced_timer; 2121283a72a5SPetr Machata 2122283a72a5SPetr Machata replace_nexthop_grp_res(oldg, newg); 2123283a72a5SPetr Machata 2124283a72a5SPetr Machata tmp_table = new_res_table; 2125283a72a5SPetr Machata rcu_assign_pointer(newg->res_table, old_res_table); 2126283a72a5SPetr Machata rcu_assign_pointer(newg->spare->res_table, old_res_table); 2127283a72a5SPetr Machata } 2128283a72a5SPetr Machata 21297bf4796dSDavid Ahern /* update parents - used by nexthop code for cleanup */ 21307bf4796dSDavid Ahern for (i = 0; i < newg->num_nh; i++) 21317bf4796dSDavid Ahern newg->nh_entries[i].nh_parent = old; 21327bf4796dSDavid Ahern 21337bf4796dSDavid Ahern rcu_assign_pointer(old->nh_grp, newg); 21347bf4796dSDavid Ahern 2135563f23b0SIdo Schimmel /* Make sure concurrent readers are not using 'oldg' anymore. */ 2136563f23b0SIdo Schimmel synchronize_net(); 21377709efa6SNikolay Aleksandrov 21387709efa6SNikolay Aleksandrov if (newg->resilient) { 2139283a72a5SPetr Machata rcu_assign_pointer(oldg->res_table, tmp_table); 2140283a72a5SPetr Machata rcu_assign_pointer(oldg->spare->res_table, tmp_table); 2141283a72a5SPetr Machata } 2142283a72a5SPetr Machata 21437bf4796dSDavid Ahern for (i = 0; i < oldg->num_nh; i++) 21447bf4796dSDavid Ahern oldg->nh_entries[i].nh_parent = new; 21457bf4796dSDavid Ahern 21467bf4796dSDavid Ahern rcu_assign_pointer(new->nh_grp, oldg); 21477bf4796dSDavid Ahern 21487bf4796dSDavid Ahern return 0; 21497bf4796dSDavid Ahern } 21507bf4796dSDavid Ahern 2151885a3b15SIdo Schimmel static void nh_group_v4_update(struct nh_group *nhg) 2152885a3b15SIdo Schimmel { 2153885a3b15SIdo Schimmel struct nh_grp_entry *nhges; 2154885a3b15SIdo Schimmel bool has_v4 = false; 2155885a3b15SIdo Schimmel int i; 2156885a3b15SIdo Schimmel 2157885a3b15SIdo Schimmel nhges = nhg->nh_entries; 2158885a3b15SIdo Schimmel for (i = 0; i < nhg->num_nh; i++) { 2159885a3b15SIdo Schimmel struct nh_info *nhi; 2160885a3b15SIdo Schimmel 2161885a3b15SIdo Schimmel nhi = rtnl_dereference(nhges[i].nh->nh_info); 2162885a3b15SIdo Schimmel if (nhi->family == AF_INET) 2163885a3b15SIdo Schimmel has_v4 = true; 2164885a3b15SIdo Schimmel } 2165885a3b15SIdo Schimmel nhg->has_v4 = has_v4; 2166885a3b15SIdo Schimmel } 2167885a3b15SIdo Schimmel 21687c37c7e0SPetr Machata static int replace_nexthop_single_notify_res(struct net *net, 21697c37c7e0SPetr Machata struct nh_res_table *res_table, 21707c37c7e0SPetr Machata struct nexthop *old, 21717c37c7e0SPetr Machata struct nh_info *oldi, 21727c37c7e0SPetr Machata struct nh_info *newi, 21737c37c7e0SPetr Machata struct netlink_ext_ack *extack) 21747c37c7e0SPetr Machata { 21757c37c7e0SPetr Machata u32 nhg_id = res_table->nhg_id; 21767c37c7e0SPetr Machata int err; 21777c37c7e0SPetr Machata u16 i; 21787c37c7e0SPetr Machata 21797c37c7e0SPetr Machata for (i = 0; i < res_table->num_nh_buckets; i++) { 21807c37c7e0SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 21817c37c7e0SPetr Machata struct nh_grp_entry *nhge; 21827c37c7e0SPetr Machata 21837c37c7e0SPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 21847c37c7e0SPetr Machata if (nhge->nh == old) { 21857c37c7e0SPetr Machata err = __call_nexthop_res_bucket_notifiers(net, nhg_id, 21867c37c7e0SPetr Machata i, true, 21877c37c7e0SPetr Machata oldi, newi, 21887c37c7e0SPetr Machata extack); 21897c37c7e0SPetr Machata if (err) 21907c37c7e0SPetr Machata goto err_notify; 21917c37c7e0SPetr Machata } 21927c37c7e0SPetr Machata } 21937c37c7e0SPetr Machata 21947c37c7e0SPetr Machata return 0; 21957c37c7e0SPetr Machata 21967c37c7e0SPetr Machata err_notify: 21977c37c7e0SPetr Machata while (i-- > 0) { 21987c37c7e0SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 21997c37c7e0SPetr Machata struct nh_grp_entry *nhge; 22007c37c7e0SPetr Machata 22017c37c7e0SPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 22027c37c7e0SPetr Machata if (nhge->nh == old) 22037c37c7e0SPetr Machata __call_nexthop_res_bucket_notifiers(net, nhg_id, i, 22047c37c7e0SPetr Machata true, newi, oldi, 22057c37c7e0SPetr Machata extack); 22067c37c7e0SPetr Machata } 22077c37c7e0SPetr Machata return err; 22087c37c7e0SPetr Machata } 22097c37c7e0SPetr Machata 22107c37c7e0SPetr Machata static int replace_nexthop_single_notify(struct net *net, 22117c37c7e0SPetr Machata struct nexthop *group_nh, 22127c37c7e0SPetr Machata struct nexthop *old, 22137c37c7e0SPetr Machata struct nh_info *oldi, 22147c37c7e0SPetr Machata struct nh_info *newi, 22157c37c7e0SPetr Machata struct netlink_ext_ack *extack) 22167c37c7e0SPetr Machata { 22177c37c7e0SPetr Machata struct nh_group *nhg = rtnl_dereference(group_nh->nh_grp); 22187c37c7e0SPetr Machata struct nh_res_table *res_table; 22197c37c7e0SPetr Machata 2220de1d1ee3SPetr Machata if (nhg->hash_threshold) { 22217c37c7e0SPetr Machata return call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, 22227c37c7e0SPetr Machata group_nh, extack); 22237c37c7e0SPetr Machata } else if (nhg->resilient) { 22247c37c7e0SPetr Machata res_table = rtnl_dereference(nhg->res_table); 22257c37c7e0SPetr Machata return replace_nexthop_single_notify_res(net, res_table, 22267c37c7e0SPetr Machata old, oldi, newi, 22277c37c7e0SPetr Machata extack); 22287c37c7e0SPetr Machata } 22297c37c7e0SPetr Machata 22307c37c7e0SPetr Machata return -EINVAL; 22317c37c7e0SPetr Machata } 22327c37c7e0SPetr Machata 22337bf4796dSDavid Ahern static int replace_nexthop_single(struct net *net, struct nexthop *old, 22347bf4796dSDavid Ahern struct nexthop *new, 22357bf4796dSDavid Ahern struct netlink_ext_ack *extack) 22367bf4796dSDavid Ahern { 2237f17bc33dSIdo Schimmel u8 old_protocol, old_nh_flags; 22387bf4796dSDavid Ahern struct nh_info *oldi, *newi; 2239f17bc33dSIdo Schimmel struct nh_grp_entry *nhge; 22408c09c9f9SIdo Schimmel int err; 22417bf4796dSDavid Ahern 22427bf4796dSDavid Ahern if (new->is_group) { 22437bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group."); 22447bf4796dSDavid Ahern return -EINVAL; 22457bf4796dSDavid Ahern } 22467bf4796dSDavid Ahern 22478c09c9f9SIdo Schimmel err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack); 22488c09c9f9SIdo Schimmel if (err) 22498c09c9f9SIdo Schimmel return err; 22508c09c9f9SIdo Schimmel 22518c09c9f9SIdo Schimmel /* Hardware flags were set on 'old' as 'new' is not in the red-black 22528c09c9f9SIdo Schimmel * tree. Therefore, inherit the flags from 'old' to 'new'. 22538c09c9f9SIdo Schimmel */ 22548c09c9f9SIdo Schimmel new->nh_flags |= old->nh_flags & (RTNH_F_OFFLOAD | RTNH_F_TRAP); 22558c09c9f9SIdo Schimmel 22567bf4796dSDavid Ahern oldi = rtnl_dereference(old->nh_info); 22577bf4796dSDavid Ahern newi = rtnl_dereference(new->nh_info); 22587bf4796dSDavid Ahern 22597bf4796dSDavid Ahern newi->nh_parent = old; 22607bf4796dSDavid Ahern oldi->nh_parent = new; 22617bf4796dSDavid Ahern 2262f17bc33dSIdo Schimmel old_protocol = old->protocol; 2263f17bc33dSIdo Schimmel old_nh_flags = old->nh_flags; 2264f17bc33dSIdo Schimmel 22657bf4796dSDavid Ahern old->protocol = new->protocol; 22667bf4796dSDavid Ahern old->nh_flags = new->nh_flags; 22677bf4796dSDavid Ahern 22687bf4796dSDavid Ahern rcu_assign_pointer(old->nh_info, newi); 22697bf4796dSDavid Ahern rcu_assign_pointer(new->nh_info, oldi); 22707bf4796dSDavid Ahern 2271f17bc33dSIdo Schimmel /* Send a replace notification for all the groups using the nexthop. */ 2272f17bc33dSIdo Schimmel list_for_each_entry(nhge, &old->grp_list, nh_list) { 2273f17bc33dSIdo Schimmel struct nexthop *nhp = nhge->nh_parent; 2274f17bc33dSIdo Schimmel 22757c37c7e0SPetr Machata err = replace_nexthop_single_notify(net, nhp, old, oldi, newi, 2276f17bc33dSIdo Schimmel extack); 2277f17bc33dSIdo Schimmel if (err) 2278f17bc33dSIdo Schimmel goto err_notify; 2279f17bc33dSIdo Schimmel } 2280f17bc33dSIdo Schimmel 2281885a3b15SIdo Schimmel /* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially 2282885a3b15SIdo Schimmel * update IPv4 indication in all the groups using the nexthop. 2283885a3b15SIdo Schimmel */ 2284885a3b15SIdo Schimmel if (oldi->family == AF_INET && newi->family == AF_INET6) { 2285885a3b15SIdo Schimmel list_for_each_entry(nhge, &old->grp_list, nh_list) { 2286885a3b15SIdo Schimmel struct nexthop *nhp = nhge->nh_parent; 2287885a3b15SIdo Schimmel struct nh_group *nhg; 2288885a3b15SIdo Schimmel 2289885a3b15SIdo Schimmel nhg = rtnl_dereference(nhp->nh_grp); 2290885a3b15SIdo Schimmel nh_group_v4_update(nhg); 2291885a3b15SIdo Schimmel } 2292885a3b15SIdo Schimmel } 2293885a3b15SIdo Schimmel 22947bf4796dSDavid Ahern return 0; 2295f17bc33dSIdo Schimmel 2296f17bc33dSIdo Schimmel err_notify: 2297f17bc33dSIdo Schimmel rcu_assign_pointer(new->nh_info, newi); 2298f17bc33dSIdo Schimmel rcu_assign_pointer(old->nh_info, oldi); 2299f17bc33dSIdo Schimmel old->nh_flags = old_nh_flags; 2300f17bc33dSIdo Schimmel old->protocol = old_protocol; 2301f17bc33dSIdo Schimmel oldi->nh_parent = old; 2302f17bc33dSIdo Schimmel newi->nh_parent = new; 2303f17bc33dSIdo Schimmel list_for_each_entry_continue_reverse(nhge, &old->grp_list, nh_list) { 2304f17bc33dSIdo Schimmel struct nexthop *nhp = nhge->nh_parent; 2305f17bc33dSIdo Schimmel 23067c37c7e0SPetr Machata replace_nexthop_single_notify(net, nhp, old, newi, oldi, NULL); 2307f17bc33dSIdo Schimmel } 2308f17bc33dSIdo Schimmel call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, old, extack); 2309f17bc33dSIdo Schimmel return err; 23107bf4796dSDavid Ahern } 23117bf4796dSDavid Ahern 23127bf4796dSDavid Ahern static void __nexthop_replace_notify(struct net *net, struct nexthop *nh, 23137bf4796dSDavid Ahern struct nl_info *info) 23147bf4796dSDavid Ahern { 23157bf4796dSDavid Ahern struct fib6_info *f6i; 23167bf4796dSDavid Ahern 23177bf4796dSDavid Ahern if (!list_empty(&nh->fi_list)) { 23187bf4796dSDavid Ahern struct fib_info *fi; 23197bf4796dSDavid Ahern 23207bf4796dSDavid Ahern /* expectation is a few fib_info per nexthop and then 23217bf4796dSDavid Ahern * a lot of routes per fib_info. So mark the fib_info 23227bf4796dSDavid Ahern * and then walk the fib tables once 23237bf4796dSDavid Ahern */ 23247bf4796dSDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) 23257bf4796dSDavid Ahern fi->nh_updated = true; 23267bf4796dSDavid Ahern 23277bf4796dSDavid Ahern fib_info_notify_update(net, info); 23287bf4796dSDavid Ahern 23297bf4796dSDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) 23307bf4796dSDavid Ahern fi->nh_updated = false; 23317bf4796dSDavid Ahern } 23327bf4796dSDavid Ahern 23337bf4796dSDavid Ahern list_for_each_entry(f6i, &nh->f6i_list, nh_list) 23347bf4796dSDavid Ahern ipv6_stub->fib6_rt_update(net, f6i, info); 23357bf4796dSDavid Ahern } 23367bf4796dSDavid Ahern 23377bf4796dSDavid Ahern /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries 23387bf4796dSDavid Ahern * linked to this nexthop and for all groups that the nexthop 23397bf4796dSDavid Ahern * is a member of 23407bf4796dSDavid Ahern */ 23417bf4796dSDavid Ahern static void nexthop_replace_notify(struct net *net, struct nexthop *nh, 23427bf4796dSDavid Ahern struct nl_info *info) 23437bf4796dSDavid Ahern { 23447bf4796dSDavid Ahern struct nh_grp_entry *nhge; 23457bf4796dSDavid Ahern 23467bf4796dSDavid Ahern __nexthop_replace_notify(net, nh, info); 23477bf4796dSDavid Ahern 23487bf4796dSDavid Ahern list_for_each_entry(nhge, &nh->grp_list, nh_list) 23497bf4796dSDavid Ahern __nexthop_replace_notify(net, nhge->nh_parent, info); 23507bf4796dSDavid Ahern } 23517bf4796dSDavid Ahern 2352ab84be7eSDavid Ahern static int replace_nexthop(struct net *net, struct nexthop *old, 2353597f48e4SPetr Machata struct nexthop *new, const struct nh_config *cfg, 2354597f48e4SPetr Machata struct netlink_ext_ack *extack) 2355ab84be7eSDavid Ahern { 23567bf4796dSDavid Ahern bool new_is_reject = false; 23577bf4796dSDavid Ahern struct nh_grp_entry *nhge; 23587bf4796dSDavid Ahern int err; 23597bf4796dSDavid Ahern 23607bf4796dSDavid Ahern /* check that existing FIB entries are ok with the 23617bf4796dSDavid Ahern * new nexthop definition 23627bf4796dSDavid Ahern */ 23637bf4796dSDavid Ahern err = fib_check_nh_list(old, new, extack); 23647bf4796dSDavid Ahern if (err) 23657bf4796dSDavid Ahern return err; 23667bf4796dSDavid Ahern 23677bf4796dSDavid Ahern err = fib6_check_nh_list(old, new, extack); 23687bf4796dSDavid Ahern if (err) 23697bf4796dSDavid Ahern return err; 23707bf4796dSDavid Ahern 23717bf4796dSDavid Ahern if (!new->is_group) { 23727bf4796dSDavid Ahern struct nh_info *nhi = rtnl_dereference(new->nh_info); 23737bf4796dSDavid Ahern 23747bf4796dSDavid Ahern new_is_reject = nhi->reject_nh; 23757bf4796dSDavid Ahern } 23767bf4796dSDavid Ahern 23777bf4796dSDavid Ahern list_for_each_entry(nhge, &old->grp_list, nh_list) { 23787bf4796dSDavid Ahern /* if new nexthop is a blackhole, any groups using this 23797bf4796dSDavid Ahern * nexthop cannot have more than 1 path 23807bf4796dSDavid Ahern */ 23817bf4796dSDavid Ahern if (new_is_reject && 23827bf4796dSDavid Ahern nexthop_num_path(nhge->nh_parent) > 1) { 23837bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path"); 23847bf4796dSDavid Ahern return -EINVAL; 23857bf4796dSDavid Ahern } 23867bf4796dSDavid Ahern 23877bf4796dSDavid Ahern err = fib_check_nh_list(nhge->nh_parent, new, extack); 23887bf4796dSDavid Ahern if (err) 23897bf4796dSDavid Ahern return err; 23907bf4796dSDavid Ahern 23917bf4796dSDavid Ahern err = fib6_check_nh_list(nhge->nh_parent, new, extack); 23927bf4796dSDavid Ahern if (err) 23937bf4796dSDavid Ahern return err; 23947bf4796dSDavid Ahern } 23957bf4796dSDavid Ahern 23967bf4796dSDavid Ahern if (old->is_group) 2397597f48e4SPetr Machata err = replace_nexthop_grp(net, old, new, cfg, extack); 23987bf4796dSDavid Ahern else 23997bf4796dSDavid Ahern err = replace_nexthop_single(net, old, new, extack); 24007bf4796dSDavid Ahern 24017bf4796dSDavid Ahern if (!err) { 24021005f19bSNikolay Aleksandrov nh_rt_cache_flush(net, old, new); 24037bf4796dSDavid Ahern 24047bf4796dSDavid Ahern __remove_nexthop(net, new, NULL); 24057bf4796dSDavid Ahern nexthop_put(new); 24067bf4796dSDavid Ahern } 24077bf4796dSDavid Ahern 24087bf4796dSDavid Ahern return err; 2409ab84be7eSDavid Ahern } 2410ab84be7eSDavid Ahern 2411ab84be7eSDavid Ahern /* called with rtnl_lock held */ 2412ab84be7eSDavid Ahern static int insert_nexthop(struct net *net, struct nexthop *new_nh, 2413ab84be7eSDavid Ahern struct nh_config *cfg, struct netlink_ext_ack *extack) 2414ab84be7eSDavid Ahern { 2415ab84be7eSDavid Ahern struct rb_node **pp, *parent = NULL, *next; 2416ab84be7eSDavid Ahern struct rb_root *root = &net->nexthop.rb_root; 2417ab84be7eSDavid Ahern bool replace = !!(cfg->nlflags & NLM_F_REPLACE); 2418ab84be7eSDavid Ahern bool create = !!(cfg->nlflags & NLM_F_CREATE); 2419ab84be7eSDavid Ahern u32 new_id = new_nh->id; 24207bf4796dSDavid Ahern int replace_notify = 0; 2421ab84be7eSDavid Ahern int rc = -EEXIST; 2422ab84be7eSDavid Ahern 2423ab84be7eSDavid Ahern pp = &root->rb_node; 2424ab84be7eSDavid Ahern while (1) { 2425ab84be7eSDavid Ahern struct nexthop *nh; 2426ab84be7eSDavid Ahern 2427233c6378SIdo Schimmel next = *pp; 2428ab84be7eSDavid Ahern if (!next) 2429ab84be7eSDavid Ahern break; 2430ab84be7eSDavid Ahern 2431ab84be7eSDavid Ahern parent = next; 2432ab84be7eSDavid Ahern 2433ab84be7eSDavid Ahern nh = rb_entry(parent, struct nexthop, rb_node); 2434ab84be7eSDavid Ahern if (new_id < nh->id) { 2435ab84be7eSDavid Ahern pp = &next->rb_left; 2436ab84be7eSDavid Ahern } else if (new_id > nh->id) { 2437ab84be7eSDavid Ahern pp = &next->rb_right; 2438ab84be7eSDavid Ahern } else if (replace) { 2439597f48e4SPetr Machata rc = replace_nexthop(net, nh, new_nh, cfg, extack); 24407bf4796dSDavid Ahern if (!rc) { 2441ab84be7eSDavid Ahern new_nh = nh; /* send notification with old nh */ 24427bf4796dSDavid Ahern replace_notify = 1; 24437bf4796dSDavid Ahern } 2444ab84be7eSDavid Ahern goto out; 2445ab84be7eSDavid Ahern } else { 2446ab84be7eSDavid Ahern /* id already exists and not a replace */ 2447ab84be7eSDavid Ahern goto out; 2448ab84be7eSDavid Ahern } 2449ab84be7eSDavid Ahern } 2450ab84be7eSDavid Ahern 2451ab84be7eSDavid Ahern if (replace && !create) { 2452ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists"); 2453ab84be7eSDavid Ahern rc = -ENOENT; 2454ab84be7eSDavid Ahern goto out; 2455ab84be7eSDavid Ahern } 2456ab84be7eSDavid Ahern 2457283a72a5SPetr Machata if (new_nh->is_group) { 2458283a72a5SPetr Machata struct nh_group *nhg = rtnl_dereference(new_nh->nh_grp); 2459283a72a5SPetr Machata struct nh_res_table *res_table; 2460283a72a5SPetr Machata 2461283a72a5SPetr Machata if (nhg->resilient) { 2462283a72a5SPetr Machata res_table = rtnl_dereference(nhg->res_table); 2463283a72a5SPetr Machata 2464283a72a5SPetr Machata /* Not passing the number of buckets is OK when 2465283a72a5SPetr Machata * replacing, but not when creating a new group. 2466283a72a5SPetr Machata */ 2467283a72a5SPetr Machata if (!cfg->nh_grp_res_has_num_buckets) { 2468283a72a5SPetr Machata NL_SET_ERR_MSG(extack, "Number of buckets not specified for nexthop group insertion"); 2469283a72a5SPetr Machata rc = -EINVAL; 2470283a72a5SPetr Machata goto out; 2471283a72a5SPetr Machata } 2472283a72a5SPetr Machata 2473283a72a5SPetr Machata nh_res_group_rebalance(nhg, res_table); 24747c37c7e0SPetr Machata 24757c37c7e0SPetr Machata /* Do not send bucket notifications, we do full 24767c37c7e0SPetr Machata * notification below. 24777c37c7e0SPetr Machata */ 24780b4818aaSPetr Machata nh_res_table_upkeep(res_table, false, false); 2479283a72a5SPetr Machata } 2480283a72a5SPetr Machata } 2481283a72a5SPetr Machata 2482ab84be7eSDavid Ahern rb_link_node_rcu(&new_nh->rb_node, parent, pp); 2483ab84be7eSDavid Ahern rb_insert_color(&new_nh->rb_node, root); 2484732d167bSIdo Schimmel 2485de1d1ee3SPetr Machata /* The initial insertion is a full notification for hash-threshold as 2486de1d1ee3SPetr Machata * well as resilient groups. 24877c37c7e0SPetr Machata */ 2488732d167bSIdo Schimmel rc = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new_nh, extack); 2489732d167bSIdo Schimmel if (rc) 2490732d167bSIdo Schimmel rb_erase(&new_nh->rb_node, &net->nexthop.rb_root); 2491732d167bSIdo Schimmel 2492ab84be7eSDavid Ahern out: 2493ab84be7eSDavid Ahern if (!rc) { 2494ab84be7eSDavid Ahern nh_base_seq_inc(net); 2495ab84be7eSDavid Ahern nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo); 2496bdf00bf2SKuniyuki Iwashima if (replace_notify && 2497bdf00bf2SKuniyuki Iwashima READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode)) 24987bf4796dSDavid Ahern nexthop_replace_notify(net, new_nh, &cfg->nlinfo); 2499ab84be7eSDavid Ahern } 2500ab84be7eSDavid Ahern 2501ab84be7eSDavid Ahern return rc; 2502ab84be7eSDavid Ahern } 2503ab84be7eSDavid Ahern 2504597cfe4fSDavid Ahern /* rtnl */ 2505597cfe4fSDavid Ahern /* remove all nexthops tied to a device being deleted */ 250676c03bf8SIdo Schimmel static void nexthop_flush_dev(struct net_device *dev, unsigned long event) 2507597cfe4fSDavid Ahern { 2508597cfe4fSDavid Ahern unsigned int hash = nh_dev_hashfn(dev->ifindex); 2509597cfe4fSDavid Ahern struct net *net = dev_net(dev); 2510597cfe4fSDavid Ahern struct hlist_head *head = &net->nexthop.devhash[hash]; 2511597cfe4fSDavid Ahern struct hlist_node *n; 2512597cfe4fSDavid Ahern struct nh_info *nhi; 2513597cfe4fSDavid Ahern 2514597cfe4fSDavid Ahern hlist_for_each_entry_safe(nhi, n, head, dev_hash) { 2515597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev != dev) 2516597cfe4fSDavid Ahern continue; 2517597cfe4fSDavid Ahern 251876c03bf8SIdo Schimmel if (nhi->reject_nh && 251976c03bf8SIdo Schimmel (event == NETDEV_DOWN || event == NETDEV_CHANGE)) 252076c03bf8SIdo Schimmel continue; 252176c03bf8SIdo Schimmel 2522430a0491SDavid Ahern remove_nexthop(net, nhi->nh_parent, NULL); 2523597cfe4fSDavid Ahern } 2524597cfe4fSDavid Ahern } 2525597cfe4fSDavid Ahern 2526ab84be7eSDavid Ahern /* rtnl; called when net namespace is deleted */ 2527ab84be7eSDavid Ahern static void flush_all_nexthops(struct net *net) 2528ab84be7eSDavid Ahern { 2529ab84be7eSDavid Ahern struct rb_root *root = &net->nexthop.rb_root; 2530ab84be7eSDavid Ahern struct rb_node *node; 2531ab84be7eSDavid Ahern struct nexthop *nh; 2532ab84be7eSDavid Ahern 2533ab84be7eSDavid Ahern while ((node = rb_first(root))) { 2534ab84be7eSDavid Ahern nh = rb_entry(node, struct nexthop, rb_node); 2535430a0491SDavid Ahern remove_nexthop(net, nh, NULL); 2536ab84be7eSDavid Ahern cond_resched(); 2537ab84be7eSDavid Ahern } 2538ab84be7eSDavid Ahern } 2539ab84be7eSDavid Ahern 2540430a0491SDavid Ahern static struct nexthop *nexthop_create_group(struct net *net, 2541430a0491SDavid Ahern struct nh_config *cfg) 2542430a0491SDavid Ahern { 2543430a0491SDavid Ahern struct nlattr *grps_attr = cfg->nh_grp; 2544430a0491SDavid Ahern struct nexthop_grp *entry = nla_data(grps_attr); 254590f33bffSNikolay Aleksandrov u16 num_nh = nla_len(grps_attr) / sizeof(*entry); 2546430a0491SDavid Ahern struct nh_group *nhg; 2547430a0491SDavid Ahern struct nexthop *nh; 2548283a72a5SPetr Machata int err; 2549430a0491SDavid Ahern int i; 2550430a0491SDavid Ahern 2551eeaac363SNikolay Aleksandrov if (WARN_ON(!num_nh)) 2552eeaac363SNikolay Aleksandrov return ERR_PTR(-EINVAL); 2553eeaac363SNikolay Aleksandrov 2554430a0491SDavid Ahern nh = nexthop_alloc(); 2555430a0491SDavid Ahern if (!nh) 2556430a0491SDavid Ahern return ERR_PTR(-ENOMEM); 2557430a0491SDavid Ahern 2558430a0491SDavid Ahern nh->is_group = 1; 2559430a0491SDavid Ahern 256090f33bffSNikolay Aleksandrov nhg = nexthop_grp_alloc(num_nh); 2561430a0491SDavid Ahern if (!nhg) { 2562430a0491SDavid Ahern kfree(nh); 2563430a0491SDavid Ahern return ERR_PTR(-ENOMEM); 2564430a0491SDavid Ahern } 2565430a0491SDavid Ahern 256690f33bffSNikolay Aleksandrov /* spare group used for removals */ 256790f33bffSNikolay Aleksandrov nhg->spare = nexthop_grp_alloc(num_nh); 2568dafe2078SPatrick Eigensatz if (!nhg->spare) { 256990f33bffSNikolay Aleksandrov kfree(nhg); 257090f33bffSNikolay Aleksandrov kfree(nh); 2571dafe2078SPatrick Eigensatz return ERR_PTR(-ENOMEM); 257290f33bffSNikolay Aleksandrov } 257390f33bffSNikolay Aleksandrov nhg->spare->spare = nhg; 257490f33bffSNikolay Aleksandrov 2575430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 2576430a0491SDavid Ahern struct nexthop *nhe; 2577430a0491SDavid Ahern struct nh_info *nhi; 2578430a0491SDavid Ahern 2579430a0491SDavid Ahern nhe = nexthop_find_by_id(net, entry[i].id); 2580283a72a5SPetr Machata if (!nexthop_get(nhe)) { 2581283a72a5SPetr Machata err = -ENOENT; 2582430a0491SDavid Ahern goto out_no_nh; 2583283a72a5SPetr Machata } 2584430a0491SDavid Ahern 2585430a0491SDavid Ahern nhi = rtnl_dereference(nhe->nh_info); 2586430a0491SDavid Ahern if (nhi->family == AF_INET) 2587430a0491SDavid Ahern nhg->has_v4 = true; 2588430a0491SDavid Ahern 2589f4676ea7SIdo Schimmel nhg->nh_entries[i].stats = 2590f4676ea7SIdo Schimmel netdev_alloc_pcpu_stats(struct nh_grp_entry_stats); 2591f4676ea7SIdo Schimmel if (!nhg->nh_entries[i].stats) { 2592f4676ea7SIdo Schimmel err = -ENOMEM; 2593f4676ea7SIdo Schimmel nexthop_put(nhe); 2594f4676ea7SIdo Schimmel goto out_no_nh; 2595f4676ea7SIdo Schimmel } 2596430a0491SDavid Ahern nhg->nh_entries[i].nh = nhe; 2597430a0491SDavid Ahern nhg->nh_entries[i].weight = entry[i].weight + 1; 2598430a0491SDavid Ahern list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list); 2599430a0491SDavid Ahern nhg->nh_entries[i].nh_parent = nh; 2600430a0491SDavid Ahern } 2601430a0491SDavid Ahern 260290e1a9e2SPetr Machata if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) { 2603de1d1ee3SPetr Machata nhg->hash_threshold = 1; 260490e1a9e2SPetr Machata nhg->is_multipath = true; 2605710ec562SIdo Schimmel } else if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_RES) { 2606283a72a5SPetr Machata struct nh_res_table *res_table; 2607283a72a5SPetr Machata 2608283a72a5SPetr Machata res_table = nexthop_res_table_alloc(net, cfg->nh_id, cfg); 2609283a72a5SPetr Machata if (!res_table) { 2610283a72a5SPetr Machata err = -ENOMEM; 2611710ec562SIdo Schimmel goto out_no_nh; 261290e1a9e2SPetr Machata } 2613720ccd9aSPetr Machata 2614283a72a5SPetr Machata rcu_assign_pointer(nhg->spare->res_table, res_table); 2615283a72a5SPetr Machata rcu_assign_pointer(nhg->res_table, res_table); 2616283a72a5SPetr Machata nhg->resilient = true; 2617283a72a5SPetr Machata nhg->is_multipath = true; 2618283a72a5SPetr Machata } 2619283a72a5SPetr Machata 2620de1d1ee3SPetr Machata WARN_ON_ONCE(nhg->hash_threshold + nhg->resilient != 1); 2621720ccd9aSPetr Machata 2622de1d1ee3SPetr Machata if (nhg->hash_threshold) 2623de1d1ee3SPetr Machata nh_hthr_group_rebalance(nhg); 2624430a0491SDavid Ahern 262538428d68SRoopa Prabhu if (cfg->nh_fdb) 2626ce9ac056SDavid Ahern nhg->fdb_nh = 1; 262738428d68SRoopa Prabhu 2628*746c19a5SIdo Schimmel if (cfg->nh_hw_stats) 2629*746c19a5SIdo Schimmel nhg->hw_stats = true; 2630*746c19a5SIdo Schimmel 2631430a0491SDavid Ahern rcu_assign_pointer(nh->nh_grp, nhg); 2632430a0491SDavid Ahern 2633430a0491SDavid Ahern return nh; 2634430a0491SDavid Ahern 2635430a0491SDavid Ahern out_no_nh: 26367b01e53eSIdo Schimmel for (i--; i >= 0; --i) { 26377b01e53eSIdo Schimmel list_del(&nhg->nh_entries[i].nh_list); 2638f4676ea7SIdo Schimmel free_percpu(nhg->nh_entries[i].stats); 2639430a0491SDavid Ahern nexthop_put(nhg->nh_entries[i].nh); 26407b01e53eSIdo Schimmel } 2641430a0491SDavid Ahern 264290f33bffSNikolay Aleksandrov kfree(nhg->spare); 2643430a0491SDavid Ahern kfree(nhg); 2644430a0491SDavid Ahern kfree(nh); 2645430a0491SDavid Ahern 2646283a72a5SPetr Machata return ERR_PTR(err); 2647430a0491SDavid Ahern } 2648430a0491SDavid Ahern 2649597cfe4fSDavid Ahern static int nh_create_ipv4(struct net *net, struct nexthop *nh, 2650597cfe4fSDavid Ahern struct nh_info *nhi, struct nh_config *cfg, 2651597cfe4fSDavid Ahern struct netlink_ext_ack *extack) 2652597cfe4fSDavid Ahern { 2653597cfe4fSDavid Ahern struct fib_nh *fib_nh = &nhi->fib_nh; 2654597cfe4fSDavid Ahern struct fib_config fib_cfg = { 2655597cfe4fSDavid Ahern .fc_oif = cfg->nh_ifindex, 2656597cfe4fSDavid Ahern .fc_gw4 = cfg->gw.ipv4, 2657597cfe4fSDavid Ahern .fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0, 2658597cfe4fSDavid Ahern .fc_flags = cfg->nh_flags, 26599aca491eSRyoga Saito .fc_nlinfo = cfg->nlinfo, 2660b513bd03SDavid Ahern .fc_encap = cfg->nh_encap, 2661b513bd03SDavid Ahern .fc_encap_type = cfg->nh_encap_type, 2662597cfe4fSDavid Ahern }; 266338428d68SRoopa Prabhu u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN); 2664c76c9925SColin Ian King int err; 2665597cfe4fSDavid Ahern 2666597cfe4fSDavid Ahern err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack); 2667597cfe4fSDavid Ahern if (err) { 2668597cfe4fSDavid Ahern fib_nh_release(net, fib_nh); 2669597cfe4fSDavid Ahern goto out; 2670597cfe4fSDavid Ahern } 2671597cfe4fSDavid Ahern 2672ce9ac056SDavid Ahern if (nhi->fdb_nh) 267338428d68SRoopa Prabhu goto out; 267438428d68SRoopa Prabhu 2675597cfe4fSDavid Ahern /* sets nh_dev if successful */ 2676597cfe4fSDavid Ahern err = fib_check_nh(net, fib_nh, tb_id, 0, extack); 2677597cfe4fSDavid Ahern if (!err) { 2678597cfe4fSDavid Ahern nh->nh_flags = fib_nh->fib_nh_flags; 2679dcb1ecb5SDavid Ahern fib_info_update_nhc_saddr(net, &fib_nh->nh_common, 2680bac0f937SNicolas Dichtel !fib_nh->fib_nh_scope ? 0 : fib_nh->fib_nh_scope - 1); 2681597cfe4fSDavid Ahern } else { 2682597cfe4fSDavid Ahern fib_nh_release(net, fib_nh); 2683597cfe4fSDavid Ahern } 2684597cfe4fSDavid Ahern out: 2685597cfe4fSDavid Ahern return err; 2686597cfe4fSDavid Ahern } 2687597cfe4fSDavid Ahern 268853010f99SDavid Ahern static int nh_create_ipv6(struct net *net, struct nexthop *nh, 268953010f99SDavid Ahern struct nh_info *nhi, struct nh_config *cfg, 269053010f99SDavid Ahern struct netlink_ext_ack *extack) 269153010f99SDavid Ahern { 269253010f99SDavid Ahern struct fib6_nh *fib6_nh = &nhi->fib6_nh; 269353010f99SDavid Ahern struct fib6_config fib6_cfg = { 269453010f99SDavid Ahern .fc_table = l3mdev_fib_table(cfg->dev), 269553010f99SDavid Ahern .fc_ifindex = cfg->nh_ifindex, 269653010f99SDavid Ahern .fc_gateway = cfg->gw.ipv6, 269753010f99SDavid Ahern .fc_flags = cfg->nh_flags, 26989aca491eSRyoga Saito .fc_nlinfo = cfg->nlinfo, 2699b513bd03SDavid Ahern .fc_encap = cfg->nh_encap, 2700b513bd03SDavid Ahern .fc_encap_type = cfg->nh_encap_type, 270138428d68SRoopa Prabhu .fc_is_fdb = cfg->nh_fdb, 270253010f99SDavid Ahern }; 27036f43e525SColin Ian King int err; 270453010f99SDavid Ahern 270553010f99SDavid Ahern if (!ipv6_addr_any(&cfg->gw.ipv6)) 270653010f99SDavid Ahern fib6_cfg.fc_flags |= RTF_GATEWAY; 270753010f99SDavid Ahern 270853010f99SDavid Ahern /* sets nh_dev if successful */ 270953010f99SDavid Ahern err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL, 271053010f99SDavid Ahern extack); 27111c743127SNikolay Aleksandrov if (err) { 27121c743127SNikolay Aleksandrov /* IPv6 is not enabled, don't call fib6_nh_release */ 27131c743127SNikolay Aleksandrov if (err == -EAFNOSUPPORT) 27141c743127SNikolay Aleksandrov goto out; 271553010f99SDavid Ahern ipv6_stub->fib6_nh_release(fib6_nh); 27161c743127SNikolay Aleksandrov } else { 271753010f99SDavid Ahern nh->nh_flags = fib6_nh->fib_nh_flags; 27181c743127SNikolay Aleksandrov } 27191c743127SNikolay Aleksandrov out: 272053010f99SDavid Ahern return err; 272153010f99SDavid Ahern } 272253010f99SDavid Ahern 2723ab84be7eSDavid Ahern static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg, 2724ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 2725ab84be7eSDavid Ahern { 2726ab84be7eSDavid Ahern struct nh_info *nhi; 2727ab84be7eSDavid Ahern struct nexthop *nh; 2728ab84be7eSDavid Ahern int err = 0; 2729ab84be7eSDavid Ahern 2730ab84be7eSDavid Ahern nh = nexthop_alloc(); 2731ab84be7eSDavid Ahern if (!nh) 2732ab84be7eSDavid Ahern return ERR_PTR(-ENOMEM); 2733ab84be7eSDavid Ahern 2734ab84be7eSDavid Ahern nhi = kzalloc(sizeof(*nhi), GFP_KERNEL); 2735ab84be7eSDavid Ahern if (!nhi) { 2736ab84be7eSDavid Ahern kfree(nh); 2737ab84be7eSDavid Ahern return ERR_PTR(-ENOMEM); 2738ab84be7eSDavid Ahern } 2739ab84be7eSDavid Ahern 2740ab84be7eSDavid Ahern nh->nh_flags = cfg->nh_flags; 2741ab84be7eSDavid Ahern nh->net = net; 2742ab84be7eSDavid Ahern 2743ab84be7eSDavid Ahern nhi->nh_parent = nh; 2744ab84be7eSDavid Ahern nhi->family = cfg->nh_family; 2745ab84be7eSDavid Ahern nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK; 2746ab84be7eSDavid Ahern 274738428d68SRoopa Prabhu if (cfg->nh_fdb) 2748ce9ac056SDavid Ahern nhi->fdb_nh = 1; 274938428d68SRoopa Prabhu 2750ab84be7eSDavid Ahern if (cfg->nh_blackhole) { 2751ab84be7eSDavid Ahern nhi->reject_nh = 1; 2752ab84be7eSDavid Ahern cfg->nh_ifindex = net->loopback_dev->ifindex; 2753ab84be7eSDavid Ahern } 2754ab84be7eSDavid Ahern 2755597cfe4fSDavid Ahern switch (cfg->nh_family) { 2756597cfe4fSDavid Ahern case AF_INET: 2757597cfe4fSDavid Ahern err = nh_create_ipv4(net, nh, nhi, cfg, extack); 2758597cfe4fSDavid Ahern break; 275953010f99SDavid Ahern case AF_INET6: 276053010f99SDavid Ahern err = nh_create_ipv6(net, nh, nhi, cfg, extack); 276153010f99SDavid Ahern break; 2762597cfe4fSDavid Ahern } 2763597cfe4fSDavid Ahern 2764ab84be7eSDavid Ahern if (err) { 2765ab84be7eSDavid Ahern kfree(nhi); 2766ab84be7eSDavid Ahern kfree(nh); 2767ab84be7eSDavid Ahern return ERR_PTR(err); 2768ab84be7eSDavid Ahern } 2769ab84be7eSDavid Ahern 2770597cfe4fSDavid Ahern /* add the entry to the device based hash */ 2771ce9ac056SDavid Ahern if (!nhi->fdb_nh) 2772597cfe4fSDavid Ahern nexthop_devhash_add(net, nhi); 2773597cfe4fSDavid Ahern 2774ab84be7eSDavid Ahern rcu_assign_pointer(nh->nh_info, nhi); 2775ab84be7eSDavid Ahern 2776ab84be7eSDavid Ahern return nh; 2777ab84be7eSDavid Ahern } 2778ab84be7eSDavid Ahern 2779ab84be7eSDavid Ahern /* called with rtnl lock held */ 2780ab84be7eSDavid Ahern static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg, 2781ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 2782ab84be7eSDavid Ahern { 2783ab84be7eSDavid Ahern struct nexthop *nh; 2784ab84be7eSDavid Ahern int err; 2785ab84be7eSDavid Ahern 2786ab84be7eSDavid Ahern if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) { 2787ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Replace requires nexthop id"); 2788ab84be7eSDavid Ahern return ERR_PTR(-EINVAL); 2789ab84be7eSDavid Ahern } 2790ab84be7eSDavid Ahern 2791ab84be7eSDavid Ahern if (!cfg->nh_id) { 2792ab84be7eSDavid Ahern cfg->nh_id = nh_find_unused_id(net); 2793ab84be7eSDavid Ahern if (!cfg->nh_id) { 2794ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "No unused id"); 2795ab84be7eSDavid Ahern return ERR_PTR(-EINVAL); 2796ab84be7eSDavid Ahern } 2797ab84be7eSDavid Ahern } 2798ab84be7eSDavid Ahern 2799430a0491SDavid Ahern if (cfg->nh_grp) 2800430a0491SDavid Ahern nh = nexthop_create_group(net, cfg); 2801430a0491SDavid Ahern else 2802ab84be7eSDavid Ahern nh = nexthop_create(net, cfg, extack); 2803430a0491SDavid Ahern 2804ab84be7eSDavid Ahern if (IS_ERR(nh)) 2805ab84be7eSDavid Ahern return nh; 2806ab84be7eSDavid Ahern 2807ab84be7eSDavid Ahern refcount_set(&nh->refcnt, 1); 2808ab84be7eSDavid Ahern nh->id = cfg->nh_id; 2809ab84be7eSDavid Ahern nh->protocol = cfg->nh_protocol; 2810ab84be7eSDavid Ahern nh->net = net; 2811ab84be7eSDavid Ahern 2812ab84be7eSDavid Ahern err = insert_nexthop(net, nh, cfg, extack); 2813ab84be7eSDavid Ahern if (err) { 2814430a0491SDavid Ahern __remove_nexthop(net, nh, NULL); 2815ab84be7eSDavid Ahern nexthop_put(nh); 2816ab84be7eSDavid Ahern nh = ERR_PTR(err); 2817ab84be7eSDavid Ahern } 2818ab84be7eSDavid Ahern 2819ab84be7eSDavid Ahern return nh; 2820ab84be7eSDavid Ahern } 2821ab84be7eSDavid Ahern 2822a2601e2bSPetr Machata static int rtm_nh_get_timer(struct nlattr *attr, unsigned long fallback, 2823a2601e2bSPetr Machata unsigned long *timer_p, bool *has_p, 2824a2601e2bSPetr Machata struct netlink_ext_ack *extack) 2825a2601e2bSPetr Machata { 2826a2601e2bSPetr Machata unsigned long timer; 2827a2601e2bSPetr Machata u32 value; 2828a2601e2bSPetr Machata 2829a2601e2bSPetr Machata if (!attr) { 2830a2601e2bSPetr Machata *timer_p = fallback; 2831a2601e2bSPetr Machata *has_p = false; 2832a2601e2bSPetr Machata return 0; 2833a2601e2bSPetr Machata } 2834a2601e2bSPetr Machata 2835a2601e2bSPetr Machata value = nla_get_u32(attr); 2836a2601e2bSPetr Machata timer = clock_t_to_jiffies(value); 2837a2601e2bSPetr Machata if (timer == ~0UL) { 2838a2601e2bSPetr Machata NL_SET_ERR_MSG(extack, "Timer value too large"); 2839a2601e2bSPetr Machata return -EINVAL; 2840a2601e2bSPetr Machata } 2841a2601e2bSPetr Machata 2842a2601e2bSPetr Machata *timer_p = timer; 2843a2601e2bSPetr Machata *has_p = true; 2844a2601e2bSPetr Machata return 0; 2845a2601e2bSPetr Machata } 2846a2601e2bSPetr Machata 2847a2601e2bSPetr Machata static int rtm_to_nh_config_grp_res(struct nlattr *res, struct nh_config *cfg, 2848a2601e2bSPetr Machata struct netlink_ext_ack *extack) 2849a2601e2bSPetr Machata { 2850a2601e2bSPetr Machata struct nlattr *tb[ARRAY_SIZE(rtm_nh_res_policy_new)] = {}; 2851a2601e2bSPetr Machata int err; 2852a2601e2bSPetr Machata 2853a2601e2bSPetr Machata if (res) { 2854a2601e2bSPetr Machata err = nla_parse_nested(tb, 2855a2601e2bSPetr Machata ARRAY_SIZE(rtm_nh_res_policy_new) - 1, 2856a2601e2bSPetr Machata res, rtm_nh_res_policy_new, extack); 2857a2601e2bSPetr Machata if (err < 0) 2858a2601e2bSPetr Machata return err; 2859a2601e2bSPetr Machata } 2860a2601e2bSPetr Machata 2861a2601e2bSPetr Machata if (tb[NHA_RES_GROUP_BUCKETS]) { 2862a2601e2bSPetr Machata cfg->nh_grp_res_num_buckets = 2863a2601e2bSPetr Machata nla_get_u16(tb[NHA_RES_GROUP_BUCKETS]); 2864a2601e2bSPetr Machata cfg->nh_grp_res_has_num_buckets = true; 2865a2601e2bSPetr Machata if (!cfg->nh_grp_res_num_buckets) { 2866a2601e2bSPetr Machata NL_SET_ERR_MSG(extack, "Number of buckets needs to be non-0"); 2867a2601e2bSPetr Machata return -EINVAL; 2868a2601e2bSPetr Machata } 2869a2601e2bSPetr Machata } 2870a2601e2bSPetr Machata 2871a2601e2bSPetr Machata err = rtm_nh_get_timer(tb[NHA_RES_GROUP_IDLE_TIMER], 2872a2601e2bSPetr Machata NH_RES_DEFAULT_IDLE_TIMER, 2873a2601e2bSPetr Machata &cfg->nh_grp_res_idle_timer, 2874a2601e2bSPetr Machata &cfg->nh_grp_res_has_idle_timer, 2875a2601e2bSPetr Machata extack); 2876a2601e2bSPetr Machata if (err) 2877a2601e2bSPetr Machata return err; 2878a2601e2bSPetr Machata 2879a2601e2bSPetr Machata return rtm_nh_get_timer(tb[NHA_RES_GROUP_UNBALANCED_TIMER], 2880a2601e2bSPetr Machata NH_RES_DEFAULT_UNBALANCED_TIMER, 2881a2601e2bSPetr Machata &cfg->nh_grp_res_unbalanced_timer, 2882a2601e2bSPetr Machata &cfg->nh_grp_res_has_unbalanced_timer, 2883a2601e2bSPetr Machata extack); 2884a2601e2bSPetr Machata } 2885a2601e2bSPetr Machata 2886ab84be7eSDavid Ahern static int rtm_to_nh_config(struct net *net, struct sk_buff *skb, 2887ab84be7eSDavid Ahern struct nlmsghdr *nlh, struct nh_config *cfg, 2888ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 2889ab84be7eSDavid Ahern { 2890ab84be7eSDavid Ahern struct nhmsg *nhm = nlmsg_data(nlh); 2891643d0878SPetr Machata struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_new)]; 2892ab84be7eSDavid Ahern int err; 2893ab84be7eSDavid Ahern 2894643d0878SPetr Machata err = nlmsg_parse(nlh, sizeof(*nhm), tb, 2895643d0878SPetr Machata ARRAY_SIZE(rtm_nh_policy_new) - 1, 2896643d0878SPetr Machata rtm_nh_policy_new, extack); 2897ab84be7eSDavid Ahern if (err < 0) 2898ab84be7eSDavid Ahern return err; 2899ab84be7eSDavid Ahern 2900ab84be7eSDavid Ahern err = -EINVAL; 2901ab84be7eSDavid Ahern if (nhm->resvd || nhm->nh_scope) { 2902ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid values in ancillary header"); 2903ab84be7eSDavid Ahern goto out; 2904ab84be7eSDavid Ahern } 2905ab84be7eSDavid Ahern if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) { 2906ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header"); 2907ab84be7eSDavid Ahern goto out; 2908ab84be7eSDavid Ahern } 2909ab84be7eSDavid Ahern 2910ab84be7eSDavid Ahern switch (nhm->nh_family) { 2911597cfe4fSDavid Ahern case AF_INET: 291253010f99SDavid Ahern case AF_INET6: 2913597cfe4fSDavid Ahern break; 2914430a0491SDavid Ahern case AF_UNSPEC: 2915430a0491SDavid Ahern if (tb[NHA_GROUP]) 2916430a0491SDavid Ahern break; 2917a8eceea8SJoe Perches fallthrough; 2918ab84be7eSDavid Ahern default: 2919ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid address family"); 2920ab84be7eSDavid Ahern goto out; 2921ab84be7eSDavid Ahern } 2922ab84be7eSDavid Ahern 2923ab84be7eSDavid Ahern memset(cfg, 0, sizeof(*cfg)); 2924ab84be7eSDavid Ahern cfg->nlflags = nlh->nlmsg_flags; 2925ab84be7eSDavid Ahern cfg->nlinfo.portid = NETLINK_CB(skb).portid; 2926ab84be7eSDavid Ahern cfg->nlinfo.nlh = nlh; 2927ab84be7eSDavid Ahern cfg->nlinfo.nl_net = net; 2928ab84be7eSDavid Ahern 2929ab84be7eSDavid Ahern cfg->nh_family = nhm->nh_family; 2930ab84be7eSDavid Ahern cfg->nh_protocol = nhm->nh_protocol; 2931ab84be7eSDavid Ahern cfg->nh_flags = nhm->nh_flags; 2932ab84be7eSDavid Ahern 2933ab84be7eSDavid Ahern if (tb[NHA_ID]) 2934ab84be7eSDavid Ahern cfg->nh_id = nla_get_u32(tb[NHA_ID]); 2935ab84be7eSDavid Ahern 293638428d68SRoopa Prabhu if (tb[NHA_FDB]) { 293738428d68SRoopa Prabhu if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] || 293838428d68SRoopa Prabhu tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE]) { 293938428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole"); 294038428d68SRoopa Prabhu goto out; 294138428d68SRoopa Prabhu } 294238428d68SRoopa Prabhu if (nhm->nh_flags) { 294338428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header"); 294438428d68SRoopa Prabhu goto out; 294538428d68SRoopa Prabhu } 294638428d68SRoopa Prabhu cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]); 294738428d68SRoopa Prabhu } 294838428d68SRoopa Prabhu 2949430a0491SDavid Ahern if (tb[NHA_GROUP]) { 2950430a0491SDavid Ahern if (nhm->nh_family != AF_UNSPEC) { 2951430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid family for group"); 2952430a0491SDavid Ahern goto out; 2953430a0491SDavid Ahern } 2954430a0491SDavid Ahern cfg->nh_grp = tb[NHA_GROUP]; 2955430a0491SDavid Ahern 2956430a0491SDavid Ahern cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH; 2957430a0491SDavid Ahern if (tb[NHA_GROUP_TYPE]) 2958430a0491SDavid Ahern cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]); 2959430a0491SDavid Ahern 2960430a0491SDavid Ahern if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) { 2961430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid group type"); 2962430a0491SDavid Ahern goto out; 2963430a0491SDavid Ahern } 2964a2601e2bSPetr Machata err = nh_check_attr_group(net, tb, ARRAY_SIZE(tb), 2965a2601e2bSPetr Machata cfg->nh_grp_type, extack); 2966a2601e2bSPetr Machata if (err) 2967a2601e2bSPetr Machata goto out; 2968a2601e2bSPetr Machata 2969a2601e2bSPetr Machata if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_RES) 2970a2601e2bSPetr Machata err = rtm_to_nh_config_grp_res(tb[NHA_RES_GROUP], 2971a2601e2bSPetr Machata cfg, extack); 2972430a0491SDavid Ahern 2973*746c19a5SIdo Schimmel if (tb[NHA_HW_STATS_ENABLE]) 2974*746c19a5SIdo Schimmel cfg->nh_hw_stats = nla_get_u32(tb[NHA_HW_STATS_ENABLE]); 2975*746c19a5SIdo Schimmel 2976430a0491SDavid Ahern /* no other attributes should be set */ 2977430a0491SDavid Ahern goto out; 2978430a0491SDavid Ahern } 2979430a0491SDavid Ahern 2980ab84be7eSDavid Ahern if (tb[NHA_BLACKHOLE]) { 2981b513bd03SDavid Ahern if (tb[NHA_GATEWAY] || tb[NHA_OIF] || 298238428d68SRoopa Prabhu tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) { 298338428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb"); 2984ab84be7eSDavid Ahern goto out; 2985ab84be7eSDavid Ahern } 2986ab84be7eSDavid Ahern 2987ab84be7eSDavid Ahern cfg->nh_blackhole = 1; 2988ab84be7eSDavid Ahern err = 0; 2989ab84be7eSDavid Ahern goto out; 2990ab84be7eSDavid Ahern } 2991ab84be7eSDavid Ahern 299238428d68SRoopa Prabhu if (!cfg->nh_fdb && !tb[NHA_OIF]) { 299338428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops"); 2994ab84be7eSDavid Ahern goto out; 2995ab84be7eSDavid Ahern } 2996ab84be7eSDavid Ahern 299738428d68SRoopa Prabhu if (!cfg->nh_fdb && tb[NHA_OIF]) { 2998ab84be7eSDavid Ahern cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]); 2999ab84be7eSDavid Ahern if (cfg->nh_ifindex) 3000ab84be7eSDavid Ahern cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex); 3001ab84be7eSDavid Ahern 3002ab84be7eSDavid Ahern if (!cfg->dev) { 3003ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid device index"); 3004ab84be7eSDavid Ahern goto out; 3005ab84be7eSDavid Ahern } else if (!(cfg->dev->flags & IFF_UP)) { 3006ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop device is not up"); 3007ab84be7eSDavid Ahern err = -ENETDOWN; 3008ab84be7eSDavid Ahern goto out; 3009ab84be7eSDavid Ahern } else if (!netif_carrier_ok(cfg->dev)) { 3010ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down"); 3011ab84be7eSDavid Ahern err = -ENETDOWN; 3012ab84be7eSDavid Ahern goto out; 3013ab84be7eSDavid Ahern } 301438428d68SRoopa Prabhu } 3015ab84be7eSDavid Ahern 3016597cfe4fSDavid Ahern err = -EINVAL; 3017597cfe4fSDavid Ahern if (tb[NHA_GATEWAY]) { 3018597cfe4fSDavid Ahern struct nlattr *gwa = tb[NHA_GATEWAY]; 3019597cfe4fSDavid Ahern 3020597cfe4fSDavid Ahern switch (cfg->nh_family) { 3021597cfe4fSDavid Ahern case AF_INET: 3022597cfe4fSDavid Ahern if (nla_len(gwa) != sizeof(u32)) { 3023597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid gateway"); 3024597cfe4fSDavid Ahern goto out; 3025597cfe4fSDavid Ahern } 3026597cfe4fSDavid Ahern cfg->gw.ipv4 = nla_get_be32(gwa); 3027597cfe4fSDavid Ahern break; 302853010f99SDavid Ahern case AF_INET6: 302953010f99SDavid Ahern if (nla_len(gwa) != sizeof(struct in6_addr)) { 303053010f99SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid gateway"); 303153010f99SDavid Ahern goto out; 303253010f99SDavid Ahern } 303353010f99SDavid Ahern cfg->gw.ipv6 = nla_get_in6_addr(gwa); 303453010f99SDavid Ahern break; 3035597cfe4fSDavid Ahern default: 3036597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, 3037597cfe4fSDavid Ahern "Unknown address family for gateway"); 3038597cfe4fSDavid Ahern goto out; 3039597cfe4fSDavid Ahern } 3040597cfe4fSDavid Ahern } else { 3041597cfe4fSDavid Ahern /* device only nexthop (no gateway) */ 3042597cfe4fSDavid Ahern if (cfg->nh_flags & RTNH_F_ONLINK) { 3043597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, 3044597cfe4fSDavid Ahern "ONLINK flag can not be set for nexthop without a gateway"); 3045597cfe4fSDavid Ahern goto out; 3046597cfe4fSDavid Ahern } 3047597cfe4fSDavid Ahern } 3048597cfe4fSDavid Ahern 3049b513bd03SDavid Ahern if (tb[NHA_ENCAP]) { 3050b513bd03SDavid Ahern cfg->nh_encap = tb[NHA_ENCAP]; 3051b513bd03SDavid Ahern 3052b513bd03SDavid Ahern if (!tb[NHA_ENCAP_TYPE]) { 3053b513bd03SDavid Ahern NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing"); 3054b513bd03SDavid Ahern goto out; 3055b513bd03SDavid Ahern } 3056b513bd03SDavid Ahern 3057b513bd03SDavid Ahern cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]); 3058b513bd03SDavid Ahern err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack); 3059b513bd03SDavid Ahern if (err < 0) 3060b513bd03SDavid Ahern goto out; 3061b513bd03SDavid Ahern 3062b513bd03SDavid Ahern } else if (tb[NHA_ENCAP_TYPE]) { 3063b513bd03SDavid Ahern NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing"); 3064b513bd03SDavid Ahern goto out; 3065b513bd03SDavid Ahern } 3066b513bd03SDavid Ahern 3067*746c19a5SIdo Schimmel if (tb[NHA_HW_STATS_ENABLE]) { 3068*746c19a5SIdo Schimmel NL_SET_ERR_MSG(extack, "Cannot enable nexthop hardware statistics for non-group nexthops"); 3069*746c19a5SIdo Schimmel goto out; 3070*746c19a5SIdo Schimmel } 3071b513bd03SDavid Ahern 3072ab84be7eSDavid Ahern err = 0; 3073ab84be7eSDavid Ahern out: 3074ab84be7eSDavid Ahern return err; 3075ab84be7eSDavid Ahern } 3076ab84be7eSDavid Ahern 3077ab84be7eSDavid Ahern /* rtnl */ 3078ab84be7eSDavid Ahern static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh, 3079ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3080ab84be7eSDavid Ahern { 3081ab84be7eSDavid Ahern struct net *net = sock_net(skb->sk); 3082ab84be7eSDavid Ahern struct nh_config cfg; 3083ab84be7eSDavid Ahern struct nexthop *nh; 3084ab84be7eSDavid Ahern int err; 3085ab84be7eSDavid Ahern 3086ab84be7eSDavid Ahern err = rtm_to_nh_config(net, skb, nlh, &cfg, extack); 3087ab84be7eSDavid Ahern if (!err) { 3088ab84be7eSDavid Ahern nh = nexthop_add(net, &cfg, extack); 3089ab84be7eSDavid Ahern if (IS_ERR(nh)) 3090ab84be7eSDavid Ahern err = PTR_ERR(nh); 3091ab84be7eSDavid Ahern } 3092ab84be7eSDavid Ahern 3093ab84be7eSDavid Ahern return err; 3094ab84be7eSDavid Ahern } 3095ab84be7eSDavid Ahern 30962118f939SPetr Machata static int nh_valid_get_del_req(const struct nlmsghdr *nlh, 3097a207eab1SPetr Machata struct nlattr **tb, u32 *id, u32 *op_flags, 3098ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3099ab84be7eSDavid Ahern { 3100ab84be7eSDavid Ahern struct nhmsg *nhm = nlmsg_data(nlh); 31010bccf8edSPetr Machata 31020bccf8edSPetr Machata if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) { 31030bccf8edSPetr Machata NL_SET_ERR_MSG(extack, "Invalid values in header"); 31040bccf8edSPetr Machata return -EINVAL; 31050bccf8edSPetr Machata } 31060bccf8edSPetr Machata 31070bccf8edSPetr Machata if (!tb[NHA_ID]) { 31080bccf8edSPetr Machata NL_SET_ERR_MSG(extack, "Nexthop id is missing"); 31090bccf8edSPetr Machata return -EINVAL; 31100bccf8edSPetr Machata } 31110bccf8edSPetr Machata 31120bccf8edSPetr Machata *id = nla_get_u32(tb[NHA_ID]); 31130bccf8edSPetr Machata if (!(*id)) { 31140bccf8edSPetr Machata NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 31150bccf8edSPetr Machata return -EINVAL; 31160bccf8edSPetr Machata } 31170bccf8edSPetr Machata 3118a207eab1SPetr Machata if (tb[NHA_OP_FLAGS]) 3119a207eab1SPetr Machata *op_flags = nla_get_u32(tb[NHA_OP_FLAGS]); 3120a207eab1SPetr Machata else 3121a207eab1SPetr Machata *op_flags = 0; 3122a207eab1SPetr Machata 31230bccf8edSPetr Machata return 0; 31240bccf8edSPetr Machata } 31250bccf8edSPetr Machata 3126ab84be7eSDavid Ahern /* rtnl */ 3127ab84be7eSDavid Ahern static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh, 3128ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3129ab84be7eSDavid Ahern { 3130ab84be7eSDavid Ahern struct net *net = sock_net(skb->sk); 31312118f939SPetr Machata struct nlattr *tb[NHA_MAX + 1]; 3132ab84be7eSDavid Ahern struct nl_info nlinfo = { 3133ab84be7eSDavid Ahern .nlh = nlh, 3134ab84be7eSDavid Ahern .nl_net = net, 3135ab84be7eSDavid Ahern .portid = NETLINK_CB(skb).portid, 3136ab84be7eSDavid Ahern }; 3137ab84be7eSDavid Ahern struct nexthop *nh; 3138a207eab1SPetr Machata u32 op_flags; 3139ab84be7eSDavid Ahern int err; 3140ab84be7eSDavid Ahern u32 id; 3141ab84be7eSDavid Ahern 31422118f939SPetr Machata err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, NHA_MAX, 31432118f939SPetr Machata rtm_nh_policy_del, extack); 31442118f939SPetr Machata if (err < 0) 31452118f939SPetr Machata return err; 31462118f939SPetr Machata 3147a207eab1SPetr Machata err = nh_valid_get_del_req(nlh, tb, &id, &op_flags, extack); 3148ab84be7eSDavid Ahern if (err) 3149ab84be7eSDavid Ahern return err; 3150ab84be7eSDavid Ahern 3151ab84be7eSDavid Ahern nh = nexthop_find_by_id(net, id); 3152ab84be7eSDavid Ahern if (!nh) 3153ab84be7eSDavid Ahern return -ENOENT; 3154ab84be7eSDavid Ahern 3155430a0491SDavid Ahern remove_nexthop(net, nh, &nlinfo); 3156ab84be7eSDavid Ahern 3157ab84be7eSDavid Ahern return 0; 3158ab84be7eSDavid Ahern } 3159ab84be7eSDavid Ahern 3160ab84be7eSDavid Ahern /* rtnl */ 3161ab84be7eSDavid Ahern static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh, 3162ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3163ab84be7eSDavid Ahern { 3164ab84be7eSDavid Ahern struct net *net = sock_net(in_skb->sk); 31652118f939SPetr Machata struct nlattr *tb[NHA_MAX + 1]; 3166ab84be7eSDavid Ahern struct sk_buff *skb = NULL; 3167ab84be7eSDavid Ahern struct nexthop *nh; 3168a207eab1SPetr Machata u32 op_flags; 3169ab84be7eSDavid Ahern int err; 3170ab84be7eSDavid Ahern u32 id; 3171ab84be7eSDavid Ahern 31722118f939SPetr Machata err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, NHA_MAX, 31732118f939SPetr Machata rtm_nh_policy_get, extack); 31742118f939SPetr Machata if (err < 0) 31752118f939SPetr Machata return err; 31762118f939SPetr Machata 3177a207eab1SPetr Machata err = nh_valid_get_del_req(nlh, tb, &id, &op_flags, extack); 3178ab84be7eSDavid Ahern if (err) 3179ab84be7eSDavid Ahern return err; 3180ab84be7eSDavid Ahern 3181ab84be7eSDavid Ahern err = -ENOBUFS; 3182ab84be7eSDavid Ahern skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 3183ab84be7eSDavid Ahern if (!skb) 3184ab84be7eSDavid Ahern goto out; 3185ab84be7eSDavid Ahern 3186ab84be7eSDavid Ahern err = -ENOENT; 3187ab84be7eSDavid Ahern nh = nexthop_find_by_id(net, id); 3188ab84be7eSDavid Ahern if (!nh) 3189ab84be7eSDavid Ahern goto errout_free; 3190ab84be7eSDavid Ahern 3191ab84be7eSDavid Ahern err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid, 319295fedd76SIdo Schimmel nlh->nlmsg_seq, 0, op_flags); 3193ab84be7eSDavid Ahern if (err < 0) { 3194ab84be7eSDavid Ahern WARN_ON(err == -EMSGSIZE); 3195ab84be7eSDavid Ahern goto errout_free; 3196ab84be7eSDavid Ahern } 3197ab84be7eSDavid Ahern 3198ab84be7eSDavid Ahern err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); 3199ab84be7eSDavid Ahern out: 3200ab84be7eSDavid Ahern return err; 3201ab84be7eSDavid Ahern errout_free: 3202ab84be7eSDavid Ahern kfree_skb(skb); 3203ab84be7eSDavid Ahern goto out; 3204ab84be7eSDavid Ahern } 3205ab84be7eSDavid Ahern 320656450ec6SPetr Machata struct nh_dump_filter { 32078a1bbabbSPetr Machata u32 nh_id; 320856450ec6SPetr Machata int dev_idx; 320956450ec6SPetr Machata int master_idx; 321056450ec6SPetr Machata bool group_filter; 321156450ec6SPetr Machata bool fdb_filter; 32128a1bbabbSPetr Machata u32 res_bucket_nh_id; 3213a207eab1SPetr Machata u32 op_flags; 321456450ec6SPetr Machata }; 321556450ec6SPetr Machata 321656450ec6SPetr Machata static bool nh_dump_filtered(struct nexthop *nh, 321756450ec6SPetr Machata struct nh_dump_filter *filter, u8 family) 3218ab84be7eSDavid Ahern { 3219ab84be7eSDavid Ahern const struct net_device *dev; 3220ab84be7eSDavid Ahern const struct nh_info *nhi; 3221ab84be7eSDavid Ahern 322256450ec6SPetr Machata if (filter->group_filter && !nh->is_group) 3223430a0491SDavid Ahern return true; 3224430a0491SDavid Ahern 322556450ec6SPetr Machata if (!filter->dev_idx && !filter->master_idx && !family) 3226ab84be7eSDavid Ahern return false; 3227ab84be7eSDavid Ahern 3228430a0491SDavid Ahern if (nh->is_group) 3229430a0491SDavid Ahern return true; 3230430a0491SDavid Ahern 3231ab84be7eSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 3232ab84be7eSDavid Ahern if (family && nhi->family != family) 3233ab84be7eSDavid Ahern return true; 3234ab84be7eSDavid Ahern 3235ab84be7eSDavid Ahern dev = nhi->fib_nhc.nhc_dev; 323656450ec6SPetr Machata if (filter->dev_idx && (!dev || dev->ifindex != filter->dev_idx)) 3237ab84be7eSDavid Ahern return true; 3238ab84be7eSDavid Ahern 323956450ec6SPetr Machata if (filter->master_idx) { 3240ab84be7eSDavid Ahern struct net_device *master; 3241ab84be7eSDavid Ahern 3242ab84be7eSDavid Ahern if (!dev) 3243ab84be7eSDavid Ahern return true; 3244ab84be7eSDavid Ahern 3245ab84be7eSDavid Ahern master = netdev_master_upper_dev_get((struct net_device *)dev); 324656450ec6SPetr Machata if (!master || master->ifindex != filter->master_idx) 3247ab84be7eSDavid Ahern return true; 3248ab84be7eSDavid Ahern } 3249ab84be7eSDavid Ahern 3250ab84be7eSDavid Ahern return false; 3251ab84be7eSDavid Ahern } 3252ab84be7eSDavid Ahern 3253b9ebea12SPetr Machata static int __nh_valid_dump_req(const struct nlmsghdr *nlh, struct nlattr **tb, 325456450ec6SPetr Machata struct nh_dump_filter *filter, 3255b9ebea12SPetr Machata struct netlink_ext_ack *extack) 3256ab84be7eSDavid Ahern { 3257ab84be7eSDavid Ahern struct nhmsg *nhm; 3258ab84be7eSDavid Ahern u32 idx; 3259ab84be7eSDavid Ahern 326044551bffSPetr Machata if (tb[NHA_OIF]) { 326144551bffSPetr Machata idx = nla_get_u32(tb[NHA_OIF]); 3262ab84be7eSDavid Ahern if (idx > INT_MAX) { 3263ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid device index"); 3264ab84be7eSDavid Ahern return -EINVAL; 3265ab84be7eSDavid Ahern } 326656450ec6SPetr Machata filter->dev_idx = idx; 326744551bffSPetr Machata } 326844551bffSPetr Machata if (tb[NHA_MASTER]) { 326944551bffSPetr Machata idx = nla_get_u32(tb[NHA_MASTER]); 3270ab84be7eSDavid Ahern if (idx > INT_MAX) { 3271ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid master device index"); 3272ab84be7eSDavid Ahern return -EINVAL; 3273ab84be7eSDavid Ahern } 327456450ec6SPetr Machata filter->master_idx = idx; 3275ab84be7eSDavid Ahern } 327656450ec6SPetr Machata filter->group_filter = nla_get_flag(tb[NHA_GROUPS]); 327756450ec6SPetr Machata filter->fdb_filter = nla_get_flag(tb[NHA_FDB]); 3278ab84be7eSDavid Ahern 3279ab84be7eSDavid Ahern nhm = nlmsg_data(nlh); 3280ab84be7eSDavid Ahern if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) { 3281ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request"); 3282ab84be7eSDavid Ahern return -EINVAL; 3283ab84be7eSDavid Ahern } 3284ab84be7eSDavid Ahern 3285a207eab1SPetr Machata if (tb[NHA_OP_FLAGS]) 3286a207eab1SPetr Machata filter->op_flags = nla_get_u32(tb[NHA_OP_FLAGS]); 3287a207eab1SPetr Machata else 3288a207eab1SPetr Machata filter->op_flags = 0; 3289a207eab1SPetr Machata 3290ab84be7eSDavid Ahern return 0; 3291ab84be7eSDavid Ahern } 3292ab84be7eSDavid Ahern 3293b9ebea12SPetr Machata static int nh_valid_dump_req(const struct nlmsghdr *nlh, 3294b9ebea12SPetr Machata struct nh_dump_filter *filter, 3295b9ebea12SPetr Machata struct netlink_callback *cb) 3296b9ebea12SPetr Machata { 32972118f939SPetr Machata struct nlattr *tb[NHA_MAX + 1]; 3298b9ebea12SPetr Machata int err; 3299b9ebea12SPetr Machata 33002118f939SPetr Machata err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, NHA_MAX, 3301b9ebea12SPetr Machata rtm_nh_policy_dump, cb->extack); 3302b9ebea12SPetr Machata if (err < 0) 3303b9ebea12SPetr Machata return err; 3304b9ebea12SPetr Machata 3305b9ebea12SPetr Machata return __nh_valid_dump_req(nlh, tb, filter, cb->extack); 3306b9ebea12SPetr Machata } 3307b9ebea12SPetr Machata 3308a6fbbaa6SPetr Machata struct rtm_dump_nh_ctx { 3309a6fbbaa6SPetr Machata u32 idx; 3310a6fbbaa6SPetr Machata }; 3311a6fbbaa6SPetr Machata 3312a6fbbaa6SPetr Machata static struct rtm_dump_nh_ctx * 3313a6fbbaa6SPetr Machata rtm_dump_nh_ctx(struct netlink_callback *cb) 3314a6fbbaa6SPetr Machata { 3315a6fbbaa6SPetr Machata struct rtm_dump_nh_ctx *ctx = (void *)cb->ctx; 3316a6fbbaa6SPetr Machata 3317a6fbbaa6SPetr Machata BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); 3318a6fbbaa6SPetr Machata return ctx; 3319a6fbbaa6SPetr Machata } 3320a6fbbaa6SPetr Machata 3321cbee1807SPetr Machata static int rtm_dump_walk_nexthops(struct sk_buff *skb, 3322cbee1807SPetr Machata struct netlink_callback *cb, 3323cbee1807SPetr Machata struct rb_root *root, 3324cbee1807SPetr Machata struct rtm_dump_nh_ctx *ctx, 3325e948217dSPetr Machata int (*nh_cb)(struct sk_buff *skb, 3326e948217dSPetr Machata struct netlink_callback *cb, 3327e948217dSPetr Machata struct nexthop *nh, void *data), 3328e948217dSPetr Machata void *data) 3329ab84be7eSDavid Ahern { 3330ab84be7eSDavid Ahern struct rb_node *node; 33319e46fb65SIdo Schimmel int s_idx; 3332ab84be7eSDavid Ahern int err; 3333ab84be7eSDavid Ahern 3334a6fbbaa6SPetr Machata s_idx = ctx->idx; 3335ab84be7eSDavid Ahern for (node = rb_first(root); node; node = rb_next(node)) { 3336ab84be7eSDavid Ahern struct nexthop *nh; 3337ab84be7eSDavid Ahern 3338ab84be7eSDavid Ahern nh = rb_entry(node, struct nexthop, rb_node); 33399e46fb65SIdo Schimmel if (nh->id < s_idx) 33409e46fb65SIdo Schimmel continue; 33419e46fb65SIdo Schimmel 33429e46fb65SIdo Schimmel ctx->idx = nh->id; 3343e948217dSPetr Machata err = nh_cb(skb, cb, nh, data); 3344e948217dSPetr Machata if (err) 3345cbee1807SPetr Machata return err; 3346cbee1807SPetr Machata } 3347cbee1807SPetr Machata 3348cbee1807SPetr Machata return 0; 3349cbee1807SPetr Machata } 3350cbee1807SPetr Machata 3351e948217dSPetr Machata static int rtm_dump_nexthop_cb(struct sk_buff *skb, struct netlink_callback *cb, 3352e948217dSPetr Machata struct nexthop *nh, void *data) 3353e948217dSPetr Machata { 3354e948217dSPetr Machata struct nhmsg *nhm = nlmsg_data(cb->nlh); 3355e948217dSPetr Machata struct nh_dump_filter *filter = data; 3356e948217dSPetr Machata 3357e948217dSPetr Machata if (nh_dump_filtered(nh, filter, nhm->nh_family)) 3358e948217dSPetr Machata return 0; 3359e948217dSPetr Machata 3360e948217dSPetr Machata return nh_fill_node(skb, nh, RTM_NEWNEXTHOP, 3361e948217dSPetr Machata NETLINK_CB(cb->skb).portid, 336295fedd76SIdo Schimmel cb->nlh->nlmsg_seq, NLM_F_MULTI, filter->op_flags); 3363e948217dSPetr Machata } 3364e948217dSPetr Machata 3365cbee1807SPetr Machata /* rtnl */ 3366cbee1807SPetr Machata static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb) 3367cbee1807SPetr Machata { 3368cbee1807SPetr Machata struct rtm_dump_nh_ctx *ctx = rtm_dump_nh_ctx(cb); 3369cbee1807SPetr Machata struct net *net = sock_net(skb->sk); 3370cbee1807SPetr Machata struct rb_root *root = &net->nexthop.rb_root; 3371cbee1807SPetr Machata struct nh_dump_filter filter = {}; 3372cbee1807SPetr Machata int err; 3373cbee1807SPetr Machata 3374cbee1807SPetr Machata err = nh_valid_dump_req(cb->nlh, &filter, cb); 3375cbee1807SPetr Machata if (err < 0) 3376cbee1807SPetr Machata return err; 3377cbee1807SPetr Machata 3378e948217dSPetr Machata err = rtm_dump_walk_nexthops(skb, cb, root, ctx, 3379e948217dSPetr Machata &rtm_dump_nexthop_cb, &filter); 3380ab84be7eSDavid Ahern if (err < 0) { 3381ab84be7eSDavid Ahern if (likely(skb->len)) 3382913f60caSIdo Schimmel err = skb->len; 3383ab84be7eSDavid Ahern } 3384ab84be7eSDavid Ahern 3385ab84be7eSDavid Ahern cb->seq = net->nexthop.seq; 3386ab84be7eSDavid Ahern nl_dump_check_consistent(cb, nlmsg_hdr(skb)); 3387ab84be7eSDavid Ahern return err; 3388ab84be7eSDavid Ahern } 3389ab84be7eSDavid Ahern 33908a1bbabbSPetr Machata static struct nexthop * 33918a1bbabbSPetr Machata nexthop_find_group_resilient(struct net *net, u32 id, 33928a1bbabbSPetr Machata struct netlink_ext_ack *extack) 33938a1bbabbSPetr Machata { 33948a1bbabbSPetr Machata struct nh_group *nhg; 33958a1bbabbSPetr Machata struct nexthop *nh; 33968a1bbabbSPetr Machata 33978a1bbabbSPetr Machata nh = nexthop_find_by_id(net, id); 33988a1bbabbSPetr Machata if (!nh) 33998a1bbabbSPetr Machata return ERR_PTR(-ENOENT); 34008a1bbabbSPetr Machata 34018a1bbabbSPetr Machata if (!nh->is_group) { 34028a1bbabbSPetr Machata NL_SET_ERR_MSG(extack, "Not a nexthop group"); 34038a1bbabbSPetr Machata return ERR_PTR(-EINVAL); 34048a1bbabbSPetr Machata } 34058a1bbabbSPetr Machata 34068a1bbabbSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 34078a1bbabbSPetr Machata if (!nhg->resilient) { 34088a1bbabbSPetr Machata NL_SET_ERR_MSG(extack, "Nexthop group not of type resilient"); 34098a1bbabbSPetr Machata return ERR_PTR(-EINVAL); 34108a1bbabbSPetr Machata } 34118a1bbabbSPetr Machata 34128a1bbabbSPetr Machata return nh; 34138a1bbabbSPetr Machata } 34148a1bbabbSPetr Machata 34158a1bbabbSPetr Machata static int nh_valid_dump_nhid(struct nlattr *attr, u32 *nh_id_p, 34168a1bbabbSPetr Machata struct netlink_ext_ack *extack) 34178a1bbabbSPetr Machata { 34188a1bbabbSPetr Machata u32 idx; 34198a1bbabbSPetr Machata 34208a1bbabbSPetr Machata if (attr) { 34218a1bbabbSPetr Machata idx = nla_get_u32(attr); 34228a1bbabbSPetr Machata if (!idx) { 34238a1bbabbSPetr Machata NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 34248a1bbabbSPetr Machata return -EINVAL; 34258a1bbabbSPetr Machata } 34268a1bbabbSPetr Machata *nh_id_p = idx; 34278a1bbabbSPetr Machata } else { 34288a1bbabbSPetr Machata *nh_id_p = 0; 34298a1bbabbSPetr Machata } 34308a1bbabbSPetr Machata 34318a1bbabbSPetr Machata return 0; 34328a1bbabbSPetr Machata } 34338a1bbabbSPetr Machata 34348a1bbabbSPetr Machata static int nh_valid_dump_bucket_req(const struct nlmsghdr *nlh, 34358a1bbabbSPetr Machata struct nh_dump_filter *filter, 34368a1bbabbSPetr Machata struct netlink_callback *cb) 34378a1bbabbSPetr Machata { 34388a1bbabbSPetr Machata struct nlattr *res_tb[ARRAY_SIZE(rtm_nh_res_bucket_policy_dump)]; 34392118f939SPetr Machata struct nlattr *tb[NHA_MAX + 1]; 34408a1bbabbSPetr Machata int err; 34418a1bbabbSPetr Machata 34422118f939SPetr Machata err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, NHA_MAX, 34438a1bbabbSPetr Machata rtm_nh_policy_dump_bucket, NULL); 34448a1bbabbSPetr Machata if (err < 0) 34458a1bbabbSPetr Machata return err; 34468a1bbabbSPetr Machata 34478a1bbabbSPetr Machata err = nh_valid_dump_nhid(tb[NHA_ID], &filter->nh_id, cb->extack); 34488a1bbabbSPetr Machata if (err) 34498a1bbabbSPetr Machata return err; 34508a1bbabbSPetr Machata 34518a1bbabbSPetr Machata if (tb[NHA_RES_BUCKET]) { 34528a1bbabbSPetr Machata size_t max = ARRAY_SIZE(rtm_nh_res_bucket_policy_dump) - 1; 34538a1bbabbSPetr Machata 34548a1bbabbSPetr Machata err = nla_parse_nested(res_tb, max, 34558a1bbabbSPetr Machata tb[NHA_RES_BUCKET], 34568a1bbabbSPetr Machata rtm_nh_res_bucket_policy_dump, 34578a1bbabbSPetr Machata cb->extack); 34588a1bbabbSPetr Machata if (err < 0) 34598a1bbabbSPetr Machata return err; 34608a1bbabbSPetr Machata 34618a1bbabbSPetr Machata err = nh_valid_dump_nhid(res_tb[NHA_RES_BUCKET_NH_ID], 34628a1bbabbSPetr Machata &filter->res_bucket_nh_id, 34638a1bbabbSPetr Machata cb->extack); 34648a1bbabbSPetr Machata if (err) 34658a1bbabbSPetr Machata return err; 34668a1bbabbSPetr Machata } 34678a1bbabbSPetr Machata 34688a1bbabbSPetr Machata return __nh_valid_dump_req(nlh, tb, filter, cb->extack); 34698a1bbabbSPetr Machata } 34708a1bbabbSPetr Machata 34718a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx { 34728a1bbabbSPetr Machata struct rtm_dump_nh_ctx nh; 34738a1bbabbSPetr Machata u16 bucket_index; 34748a1bbabbSPetr Machata }; 34758a1bbabbSPetr Machata 34768a1bbabbSPetr Machata static struct rtm_dump_res_bucket_ctx * 34778a1bbabbSPetr Machata rtm_dump_res_bucket_ctx(struct netlink_callback *cb) 34788a1bbabbSPetr Machata { 34798a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx *ctx = (void *)cb->ctx; 34808a1bbabbSPetr Machata 34818a1bbabbSPetr Machata BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); 34828a1bbabbSPetr Machata return ctx; 34838a1bbabbSPetr Machata } 34848a1bbabbSPetr Machata 34858a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data { 34868a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx *ctx; 34878a1bbabbSPetr Machata struct nh_dump_filter filter; 34888a1bbabbSPetr Machata }; 34898a1bbabbSPetr Machata 34908a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket_nh(struct sk_buff *skb, 34918a1bbabbSPetr Machata struct netlink_callback *cb, 34928a1bbabbSPetr Machata struct nexthop *nh, 34938a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data *dd) 34948a1bbabbSPetr Machata { 34958a1bbabbSPetr Machata u32 portid = NETLINK_CB(cb->skb).portid; 34968a1bbabbSPetr Machata struct nhmsg *nhm = nlmsg_data(cb->nlh); 34978a1bbabbSPetr Machata struct nh_res_table *res_table; 34988a1bbabbSPetr Machata struct nh_group *nhg; 34998a1bbabbSPetr Machata u16 bucket_index; 35008a1bbabbSPetr Machata int err; 35018a1bbabbSPetr Machata 35028a1bbabbSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 35038a1bbabbSPetr Machata res_table = rtnl_dereference(nhg->res_table); 35048a1bbabbSPetr Machata for (bucket_index = dd->ctx->bucket_index; 35058a1bbabbSPetr Machata bucket_index < res_table->num_nh_buckets; 35068a1bbabbSPetr Machata bucket_index++) { 35078a1bbabbSPetr Machata struct nh_res_bucket *bucket; 35088a1bbabbSPetr Machata struct nh_grp_entry *nhge; 35098a1bbabbSPetr Machata 35108a1bbabbSPetr Machata bucket = &res_table->nh_buckets[bucket_index]; 35118a1bbabbSPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 35128a1bbabbSPetr Machata if (nh_dump_filtered(nhge->nh, &dd->filter, nhm->nh_family)) 35138a1bbabbSPetr Machata continue; 35148a1bbabbSPetr Machata 35158a1bbabbSPetr Machata if (dd->filter.res_bucket_nh_id && 35168a1bbabbSPetr Machata dd->filter.res_bucket_nh_id != nhge->nh->id) 35178a1bbabbSPetr Machata continue; 35188a1bbabbSPetr Machata 3519f10d3d9dSIdo Schimmel dd->ctx->bucket_index = bucket_index; 35208a1bbabbSPetr Machata err = nh_fill_res_bucket(skb, nh, bucket, bucket_index, 35218a1bbabbSPetr Machata RTM_NEWNEXTHOPBUCKET, portid, 35228a1bbabbSPetr Machata cb->nlh->nlmsg_seq, NLM_F_MULTI, 35238a1bbabbSPetr Machata cb->extack); 3524f10d3d9dSIdo Schimmel if (err) 3525f10d3d9dSIdo Schimmel return err; 35268a1bbabbSPetr Machata } 35278a1bbabbSPetr Machata 3528f10d3d9dSIdo Schimmel dd->ctx->bucket_index = 0; 35298a1bbabbSPetr Machata 3530f10d3d9dSIdo Schimmel return 0; 35318a1bbabbSPetr Machata } 35328a1bbabbSPetr Machata 35338a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket_cb(struct sk_buff *skb, 35348a1bbabbSPetr Machata struct netlink_callback *cb, 35358a1bbabbSPetr Machata struct nexthop *nh, void *data) 35368a1bbabbSPetr Machata { 35378a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data *dd = data; 35388a1bbabbSPetr Machata struct nh_group *nhg; 35398a1bbabbSPetr Machata 35408a1bbabbSPetr Machata if (!nh->is_group) 35418a1bbabbSPetr Machata return 0; 35428a1bbabbSPetr Machata 35438a1bbabbSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 35448a1bbabbSPetr Machata if (!nhg->resilient) 35458a1bbabbSPetr Machata return 0; 35468a1bbabbSPetr Machata 35478a1bbabbSPetr Machata return rtm_dump_nexthop_bucket_nh(skb, cb, nh, dd); 35488a1bbabbSPetr Machata } 35498a1bbabbSPetr Machata 35508a1bbabbSPetr Machata /* rtnl */ 35518a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket(struct sk_buff *skb, 35528a1bbabbSPetr Machata struct netlink_callback *cb) 35538a1bbabbSPetr Machata { 35548a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx *ctx = rtm_dump_res_bucket_ctx(cb); 35558a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data dd = { .ctx = ctx }; 35568a1bbabbSPetr Machata struct net *net = sock_net(skb->sk); 35578a1bbabbSPetr Machata struct nexthop *nh; 35588a1bbabbSPetr Machata int err; 35598a1bbabbSPetr Machata 35608a1bbabbSPetr Machata err = nh_valid_dump_bucket_req(cb->nlh, &dd.filter, cb); 35618a1bbabbSPetr Machata if (err) 35628a1bbabbSPetr Machata return err; 35638a1bbabbSPetr Machata 35648a1bbabbSPetr Machata if (dd.filter.nh_id) { 35658a1bbabbSPetr Machata nh = nexthop_find_group_resilient(net, dd.filter.nh_id, 35668a1bbabbSPetr Machata cb->extack); 35678a1bbabbSPetr Machata if (IS_ERR(nh)) 35688a1bbabbSPetr Machata return PTR_ERR(nh); 35698a1bbabbSPetr Machata err = rtm_dump_nexthop_bucket_nh(skb, cb, nh, &dd); 35708a1bbabbSPetr Machata } else { 35718a1bbabbSPetr Machata struct rb_root *root = &net->nexthop.rb_root; 35728a1bbabbSPetr Machata 35738a1bbabbSPetr Machata err = rtm_dump_walk_nexthops(skb, cb, root, &ctx->nh, 35748a1bbabbSPetr Machata &rtm_dump_nexthop_bucket_cb, &dd); 35758a1bbabbSPetr Machata } 35768a1bbabbSPetr Machata 35778a1bbabbSPetr Machata if (err < 0) { 35788a1bbabbSPetr Machata if (likely(skb->len)) 35798743aeffSIdo Schimmel err = skb->len; 35808a1bbabbSPetr Machata } 35818a1bbabbSPetr Machata 35828a1bbabbSPetr Machata cb->seq = net->nexthop.seq; 35838a1bbabbSPetr Machata nl_dump_check_consistent(cb, nlmsg_hdr(skb)); 35848a1bbabbSPetr Machata return err; 35858a1bbabbSPetr Machata } 35868a1bbabbSPetr Machata 3587187d4c6bSPetr Machata static int nh_valid_get_bucket_req_res_bucket(struct nlattr *res, 3588187d4c6bSPetr Machata u16 *bucket_index, 3589187d4c6bSPetr Machata struct netlink_ext_ack *extack) 3590187d4c6bSPetr Machata { 3591187d4c6bSPetr Machata struct nlattr *tb[ARRAY_SIZE(rtm_nh_res_bucket_policy_get)]; 3592187d4c6bSPetr Machata int err; 3593187d4c6bSPetr Machata 3594187d4c6bSPetr Machata err = nla_parse_nested(tb, ARRAY_SIZE(rtm_nh_res_bucket_policy_get) - 1, 3595187d4c6bSPetr Machata res, rtm_nh_res_bucket_policy_get, extack); 3596187d4c6bSPetr Machata if (err < 0) 3597187d4c6bSPetr Machata return err; 3598187d4c6bSPetr Machata 3599187d4c6bSPetr Machata if (!tb[NHA_RES_BUCKET_INDEX]) { 3600187d4c6bSPetr Machata NL_SET_ERR_MSG(extack, "Bucket index is missing"); 3601187d4c6bSPetr Machata return -EINVAL; 3602187d4c6bSPetr Machata } 3603187d4c6bSPetr Machata 3604187d4c6bSPetr Machata *bucket_index = nla_get_u16(tb[NHA_RES_BUCKET_INDEX]); 3605187d4c6bSPetr Machata return 0; 3606187d4c6bSPetr Machata } 3607187d4c6bSPetr Machata 3608187d4c6bSPetr Machata static int nh_valid_get_bucket_req(const struct nlmsghdr *nlh, 3609187d4c6bSPetr Machata u32 *id, u16 *bucket_index, 3610187d4c6bSPetr Machata struct netlink_ext_ack *extack) 3611187d4c6bSPetr Machata { 36122118f939SPetr Machata struct nlattr *tb[NHA_MAX + 1]; 3613a207eab1SPetr Machata u32 op_flags; 3614187d4c6bSPetr Machata int err; 3615187d4c6bSPetr Machata 36162118f939SPetr Machata err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, NHA_MAX, 3617187d4c6bSPetr Machata rtm_nh_policy_get_bucket, extack); 3618187d4c6bSPetr Machata if (err < 0) 3619187d4c6bSPetr Machata return err; 3620187d4c6bSPetr Machata 3621a207eab1SPetr Machata err = nh_valid_get_del_req(nlh, tb, id, &op_flags, extack); 3622187d4c6bSPetr Machata if (err) 3623187d4c6bSPetr Machata return err; 3624187d4c6bSPetr Machata 3625187d4c6bSPetr Machata if (!tb[NHA_RES_BUCKET]) { 3626187d4c6bSPetr Machata NL_SET_ERR_MSG(extack, "Bucket information is missing"); 3627187d4c6bSPetr Machata return -EINVAL; 3628187d4c6bSPetr Machata } 3629187d4c6bSPetr Machata 3630187d4c6bSPetr Machata err = nh_valid_get_bucket_req_res_bucket(tb[NHA_RES_BUCKET], 3631187d4c6bSPetr Machata bucket_index, extack); 3632187d4c6bSPetr Machata if (err) 3633187d4c6bSPetr Machata return err; 3634187d4c6bSPetr Machata 3635187d4c6bSPetr Machata return 0; 3636187d4c6bSPetr Machata } 3637187d4c6bSPetr Machata 3638187d4c6bSPetr Machata /* rtnl */ 3639187d4c6bSPetr Machata static int rtm_get_nexthop_bucket(struct sk_buff *in_skb, struct nlmsghdr *nlh, 3640187d4c6bSPetr Machata struct netlink_ext_ack *extack) 3641187d4c6bSPetr Machata { 3642187d4c6bSPetr Machata struct net *net = sock_net(in_skb->sk); 3643187d4c6bSPetr Machata struct nh_res_table *res_table; 3644187d4c6bSPetr Machata struct sk_buff *skb = NULL; 3645187d4c6bSPetr Machata struct nh_group *nhg; 3646187d4c6bSPetr Machata struct nexthop *nh; 3647187d4c6bSPetr Machata u16 bucket_index; 3648187d4c6bSPetr Machata int err; 3649187d4c6bSPetr Machata u32 id; 3650187d4c6bSPetr Machata 3651187d4c6bSPetr Machata err = nh_valid_get_bucket_req(nlh, &id, &bucket_index, extack); 3652187d4c6bSPetr Machata if (err) 3653187d4c6bSPetr Machata return err; 3654187d4c6bSPetr Machata 3655187d4c6bSPetr Machata nh = nexthop_find_group_resilient(net, id, extack); 3656187d4c6bSPetr Machata if (IS_ERR(nh)) 3657187d4c6bSPetr Machata return PTR_ERR(nh); 3658187d4c6bSPetr Machata 3659187d4c6bSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 3660187d4c6bSPetr Machata res_table = rtnl_dereference(nhg->res_table); 3661187d4c6bSPetr Machata if (bucket_index >= res_table->num_nh_buckets) { 3662187d4c6bSPetr Machata NL_SET_ERR_MSG(extack, "Bucket index out of bounds"); 3663187d4c6bSPetr Machata return -ENOENT; 3664187d4c6bSPetr Machata } 3665187d4c6bSPetr Machata 3666187d4c6bSPetr Machata skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 3667187d4c6bSPetr Machata if (!skb) 3668187d4c6bSPetr Machata return -ENOBUFS; 3669187d4c6bSPetr Machata 3670187d4c6bSPetr Machata err = nh_fill_res_bucket(skb, nh, &res_table->nh_buckets[bucket_index], 3671187d4c6bSPetr Machata bucket_index, RTM_NEWNEXTHOPBUCKET, 3672187d4c6bSPetr Machata NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, 3673187d4c6bSPetr Machata 0, extack); 3674187d4c6bSPetr Machata if (err < 0) { 3675187d4c6bSPetr Machata WARN_ON(err == -EMSGSIZE); 3676187d4c6bSPetr Machata goto errout_free; 3677187d4c6bSPetr Machata } 3678187d4c6bSPetr Machata 3679187d4c6bSPetr Machata return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); 3680187d4c6bSPetr Machata 3681187d4c6bSPetr Machata errout_free: 3682187d4c6bSPetr Machata kfree_skb(skb); 3683187d4c6bSPetr Machata return err; 3684187d4c6bSPetr Machata } 3685187d4c6bSPetr Machata 3686597cfe4fSDavid Ahern static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu) 3687597cfe4fSDavid Ahern { 3688597cfe4fSDavid Ahern unsigned int hash = nh_dev_hashfn(dev->ifindex); 3689597cfe4fSDavid Ahern struct net *net = dev_net(dev); 3690597cfe4fSDavid Ahern struct hlist_head *head = &net->nexthop.devhash[hash]; 3691597cfe4fSDavid Ahern struct hlist_node *n; 3692597cfe4fSDavid Ahern struct nh_info *nhi; 3693597cfe4fSDavid Ahern 3694597cfe4fSDavid Ahern hlist_for_each_entry_safe(nhi, n, head, dev_hash) { 3695597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev == dev) { 3696597cfe4fSDavid Ahern if (nhi->family == AF_INET) 3697597cfe4fSDavid Ahern fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu, 3698597cfe4fSDavid Ahern orig_mtu); 3699597cfe4fSDavid Ahern } 3700597cfe4fSDavid Ahern } 3701597cfe4fSDavid Ahern } 3702597cfe4fSDavid Ahern 3703597cfe4fSDavid Ahern /* rtnl */ 3704597cfe4fSDavid Ahern static int nh_netdev_event(struct notifier_block *this, 3705597cfe4fSDavid Ahern unsigned long event, void *ptr) 3706597cfe4fSDavid Ahern { 3707597cfe4fSDavid Ahern struct net_device *dev = netdev_notifier_info_to_dev(ptr); 3708597cfe4fSDavid Ahern struct netdev_notifier_info_ext *info_ext; 3709597cfe4fSDavid Ahern 3710597cfe4fSDavid Ahern switch (event) { 3711597cfe4fSDavid Ahern case NETDEV_DOWN: 3712597cfe4fSDavid Ahern case NETDEV_UNREGISTER: 371376c03bf8SIdo Schimmel nexthop_flush_dev(dev, event); 3714597cfe4fSDavid Ahern break; 3715597cfe4fSDavid Ahern case NETDEV_CHANGE: 3716597cfe4fSDavid Ahern if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP))) 371776c03bf8SIdo Schimmel nexthop_flush_dev(dev, event); 3718597cfe4fSDavid Ahern break; 3719597cfe4fSDavid Ahern case NETDEV_CHANGEMTU: 3720597cfe4fSDavid Ahern info_ext = ptr; 3721597cfe4fSDavid Ahern nexthop_sync_mtu(dev, info_ext->ext.mtu); 3722597cfe4fSDavid Ahern rt_cache_flush(dev_net(dev)); 3723597cfe4fSDavid Ahern break; 3724597cfe4fSDavid Ahern } 3725597cfe4fSDavid Ahern return NOTIFY_DONE; 3726597cfe4fSDavid Ahern } 3727597cfe4fSDavid Ahern 3728597cfe4fSDavid Ahern static struct notifier_block nh_netdev_notifier = { 3729597cfe4fSDavid Ahern .notifier_call = nh_netdev_event, 3730597cfe4fSDavid Ahern }; 3731597cfe4fSDavid Ahern 3732975ff7f3SIdo Schimmel static int nexthops_dump(struct net *net, struct notifier_block *nb, 37333106a084SIdo Schimmel enum nexthop_event_type event_type, 3734975ff7f3SIdo Schimmel struct netlink_ext_ack *extack) 3735975ff7f3SIdo Schimmel { 3736975ff7f3SIdo Schimmel struct rb_root *root = &net->nexthop.rb_root; 3737975ff7f3SIdo Schimmel struct rb_node *node; 3738975ff7f3SIdo Schimmel int err = 0; 3739975ff7f3SIdo Schimmel 3740975ff7f3SIdo Schimmel for (node = rb_first(root); node; node = rb_next(node)) { 3741975ff7f3SIdo Schimmel struct nexthop *nh; 3742975ff7f3SIdo Schimmel 3743975ff7f3SIdo Schimmel nh = rb_entry(node, struct nexthop, rb_node); 37443106a084SIdo Schimmel err = call_nexthop_notifier(nb, net, event_type, nh, extack); 3745975ff7f3SIdo Schimmel if (err) 3746975ff7f3SIdo Schimmel break; 3747975ff7f3SIdo Schimmel } 3748975ff7f3SIdo Schimmel 3749975ff7f3SIdo Schimmel return err; 3750975ff7f3SIdo Schimmel } 3751975ff7f3SIdo Schimmel 3752ce7e9c8aSIdo Schimmel int register_nexthop_notifier(struct net *net, struct notifier_block *nb, 3753ce7e9c8aSIdo Schimmel struct netlink_ext_ack *extack) 37548590ceedSRoopa Prabhu { 3755975ff7f3SIdo Schimmel int err; 3756975ff7f3SIdo Schimmel 3757975ff7f3SIdo Schimmel rtnl_lock(); 37583106a084SIdo Schimmel err = nexthops_dump(net, nb, NEXTHOP_EVENT_REPLACE, extack); 3759975ff7f3SIdo Schimmel if (err) 3760975ff7f3SIdo Schimmel goto unlock; 3761975ff7f3SIdo Schimmel err = blocking_notifier_chain_register(&net->nexthop.notifier_chain, 376280690ec6SIdo Schimmel nb); 3763975ff7f3SIdo Schimmel unlock: 3764975ff7f3SIdo Schimmel rtnl_unlock(); 3765975ff7f3SIdo Schimmel return err; 37668590ceedSRoopa Prabhu } 37678590ceedSRoopa Prabhu EXPORT_SYMBOL(register_nexthop_notifier); 37688590ceedSRoopa Prabhu 376970f16ea2SEric Dumazet int __unregister_nexthop_notifier(struct net *net, struct notifier_block *nb) 377070f16ea2SEric Dumazet { 377170f16ea2SEric Dumazet int err; 377270f16ea2SEric Dumazet 377370f16ea2SEric Dumazet err = blocking_notifier_chain_unregister(&net->nexthop.notifier_chain, 377470f16ea2SEric Dumazet nb); 377570f16ea2SEric Dumazet if (!err) 377670f16ea2SEric Dumazet nexthops_dump(net, nb, NEXTHOP_EVENT_DEL, NULL); 377770f16ea2SEric Dumazet return err; 377870f16ea2SEric Dumazet } 377970f16ea2SEric Dumazet EXPORT_SYMBOL(__unregister_nexthop_notifier); 378070f16ea2SEric Dumazet 37818590ceedSRoopa Prabhu int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb) 37828590ceedSRoopa Prabhu { 37833106a084SIdo Schimmel int err; 37843106a084SIdo Schimmel 37853106a084SIdo Schimmel rtnl_lock(); 378670f16ea2SEric Dumazet err = __unregister_nexthop_notifier(net, nb); 37873106a084SIdo Schimmel rtnl_unlock(); 37883106a084SIdo Schimmel return err; 37898590ceedSRoopa Prabhu } 37908590ceedSRoopa Prabhu EXPORT_SYMBOL(unregister_nexthop_notifier); 37918590ceedSRoopa Prabhu 3792e95f2592SIdo Schimmel void nexthop_set_hw_flags(struct net *net, u32 id, bool offload, bool trap) 3793e95f2592SIdo Schimmel { 3794e95f2592SIdo Schimmel struct nexthop *nexthop; 3795e95f2592SIdo Schimmel 3796e95f2592SIdo Schimmel rcu_read_lock(); 3797e95f2592SIdo Schimmel 3798e95f2592SIdo Schimmel nexthop = nexthop_find_by_id(net, id); 3799e95f2592SIdo Schimmel if (!nexthop) 3800e95f2592SIdo Schimmel goto out; 3801e95f2592SIdo Schimmel 3802e95f2592SIdo Schimmel nexthop->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP); 3803e95f2592SIdo Schimmel if (offload) 3804e95f2592SIdo Schimmel nexthop->nh_flags |= RTNH_F_OFFLOAD; 3805e95f2592SIdo Schimmel if (trap) 3806e95f2592SIdo Schimmel nexthop->nh_flags |= RTNH_F_TRAP; 3807e95f2592SIdo Schimmel 3808e95f2592SIdo Schimmel out: 3809e95f2592SIdo Schimmel rcu_read_unlock(); 3810e95f2592SIdo Schimmel } 3811e95f2592SIdo Schimmel EXPORT_SYMBOL(nexthop_set_hw_flags); 3812e95f2592SIdo Schimmel 381356ad5ba3SIdo Schimmel void nexthop_bucket_set_hw_flags(struct net *net, u32 id, u16 bucket_index, 381456ad5ba3SIdo Schimmel bool offload, bool trap) 381556ad5ba3SIdo Schimmel { 381656ad5ba3SIdo Schimmel struct nh_res_table *res_table; 381756ad5ba3SIdo Schimmel struct nh_res_bucket *bucket; 381856ad5ba3SIdo Schimmel struct nexthop *nexthop; 381956ad5ba3SIdo Schimmel struct nh_group *nhg; 382056ad5ba3SIdo Schimmel 382156ad5ba3SIdo Schimmel rcu_read_lock(); 382256ad5ba3SIdo Schimmel 382356ad5ba3SIdo Schimmel nexthop = nexthop_find_by_id(net, id); 382456ad5ba3SIdo Schimmel if (!nexthop || !nexthop->is_group) 382556ad5ba3SIdo Schimmel goto out; 382656ad5ba3SIdo Schimmel 382756ad5ba3SIdo Schimmel nhg = rcu_dereference(nexthop->nh_grp); 382856ad5ba3SIdo Schimmel if (!nhg->resilient) 382956ad5ba3SIdo Schimmel goto out; 383056ad5ba3SIdo Schimmel 383156ad5ba3SIdo Schimmel if (bucket_index >= nhg->res_table->num_nh_buckets) 383256ad5ba3SIdo Schimmel goto out; 383356ad5ba3SIdo Schimmel 383456ad5ba3SIdo Schimmel res_table = rcu_dereference(nhg->res_table); 383556ad5ba3SIdo Schimmel bucket = &res_table->nh_buckets[bucket_index]; 383656ad5ba3SIdo Schimmel bucket->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP); 383756ad5ba3SIdo Schimmel if (offload) 383856ad5ba3SIdo Schimmel bucket->nh_flags |= RTNH_F_OFFLOAD; 383956ad5ba3SIdo Schimmel if (trap) 384056ad5ba3SIdo Schimmel bucket->nh_flags |= RTNH_F_TRAP; 384156ad5ba3SIdo Schimmel 384256ad5ba3SIdo Schimmel out: 384356ad5ba3SIdo Schimmel rcu_read_unlock(); 384456ad5ba3SIdo Schimmel } 384556ad5ba3SIdo Schimmel EXPORT_SYMBOL(nexthop_bucket_set_hw_flags); 384656ad5ba3SIdo Schimmel 3847cfc15c1dSIdo Schimmel void nexthop_res_grp_activity_update(struct net *net, u32 id, u16 num_buckets, 3848cfc15c1dSIdo Schimmel unsigned long *activity) 3849cfc15c1dSIdo Schimmel { 3850cfc15c1dSIdo Schimmel struct nh_res_table *res_table; 3851cfc15c1dSIdo Schimmel struct nexthop *nexthop; 3852cfc15c1dSIdo Schimmel struct nh_group *nhg; 3853cfc15c1dSIdo Schimmel u16 i; 3854cfc15c1dSIdo Schimmel 3855cfc15c1dSIdo Schimmel rcu_read_lock(); 3856cfc15c1dSIdo Schimmel 3857cfc15c1dSIdo Schimmel nexthop = nexthop_find_by_id(net, id); 3858cfc15c1dSIdo Schimmel if (!nexthop || !nexthop->is_group) 3859cfc15c1dSIdo Schimmel goto out; 3860cfc15c1dSIdo Schimmel 3861cfc15c1dSIdo Schimmel nhg = rcu_dereference(nexthop->nh_grp); 3862cfc15c1dSIdo Schimmel if (!nhg->resilient) 3863cfc15c1dSIdo Schimmel goto out; 3864cfc15c1dSIdo Schimmel 3865cfc15c1dSIdo Schimmel /* Instead of silently ignoring some buckets, demand that the sizes 3866cfc15c1dSIdo Schimmel * be the same. 3867cfc15c1dSIdo Schimmel */ 3868cfc15c1dSIdo Schimmel res_table = rcu_dereference(nhg->res_table); 3869cfc15c1dSIdo Schimmel if (num_buckets != res_table->num_nh_buckets) 3870cfc15c1dSIdo Schimmel goto out; 3871cfc15c1dSIdo Schimmel 3872cfc15c1dSIdo Schimmel for (i = 0; i < num_buckets; i++) { 3873cfc15c1dSIdo Schimmel if (test_bit(i, activity)) 3874cfc15c1dSIdo Schimmel nh_res_bucket_set_busy(&res_table->nh_buckets[i]); 3875cfc15c1dSIdo Schimmel } 3876cfc15c1dSIdo Schimmel 3877cfc15c1dSIdo Schimmel out: 3878cfc15c1dSIdo Schimmel rcu_read_unlock(); 3879cfc15c1dSIdo Schimmel } 3880cfc15c1dSIdo Schimmel EXPORT_SYMBOL(nexthop_res_grp_activity_update); 3881cfc15c1dSIdo Schimmel 3882a7ec2512SEric Dumazet static void __net_exit nexthop_net_exit_batch_rtnl(struct list_head *net_list, 3883a7ec2512SEric Dumazet struct list_head *dev_to_kill) 3884ab84be7eSDavid Ahern { 3885fea7b201SEric Dumazet struct net *net; 3886fea7b201SEric Dumazet 3887a7ec2512SEric Dumazet ASSERT_RTNL(); 3888a7ec2512SEric Dumazet list_for_each_entry(net, net_list, exit_list) 3889ab84be7eSDavid Ahern flush_all_nexthops(net); 3890ab84be7eSDavid Ahern } 3891a7ec2512SEric Dumazet 3892a7ec2512SEric Dumazet static void __net_exit nexthop_net_exit(struct net *net) 3893a7ec2512SEric Dumazet { 3894a7ec2512SEric Dumazet kfree(net->nexthop.devhash); 3895a7ec2512SEric Dumazet net->nexthop.devhash = NULL; 3896fea7b201SEric Dumazet } 3897ab84be7eSDavid Ahern 3898ab84be7eSDavid Ahern static int __net_init nexthop_net_init(struct net *net) 3899ab84be7eSDavid Ahern { 3900597cfe4fSDavid Ahern size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE; 3901597cfe4fSDavid Ahern 3902ab84be7eSDavid Ahern net->nexthop.rb_root = RB_ROOT; 3903597cfe4fSDavid Ahern net->nexthop.devhash = kzalloc(sz, GFP_KERNEL); 3904597cfe4fSDavid Ahern if (!net->nexthop.devhash) 3905597cfe4fSDavid Ahern return -ENOMEM; 390680690ec6SIdo Schimmel BLOCKING_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain); 3907ab84be7eSDavid Ahern 3908ab84be7eSDavid Ahern return 0; 3909ab84be7eSDavid Ahern } 3910ab84be7eSDavid Ahern 3911ab84be7eSDavid Ahern static struct pernet_operations nexthop_net_ops = { 3912ab84be7eSDavid Ahern .init = nexthop_net_init, 3913a7ec2512SEric Dumazet .exit = nexthop_net_exit, 3914a7ec2512SEric Dumazet .exit_batch_rtnl = nexthop_net_exit_batch_rtnl, 3915ab84be7eSDavid Ahern }; 3916ab84be7eSDavid Ahern 3917ab84be7eSDavid Ahern static int __init nexthop_init(void) 3918ab84be7eSDavid Ahern { 3919ab84be7eSDavid Ahern register_pernet_subsys(&nexthop_net_ops); 3920ab84be7eSDavid Ahern 3921597cfe4fSDavid Ahern register_netdevice_notifier(&nh_netdev_notifier); 3922597cfe4fSDavid Ahern 3923ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 3924ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0); 3925ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop, 3926ab84be7eSDavid Ahern rtm_dump_nexthop, 0); 3927ab84be7eSDavid Ahern 3928ab84be7eSDavid Ahern rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 3929ab84be7eSDavid Ahern rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0); 3930ab84be7eSDavid Ahern 3931ab84be7eSDavid Ahern rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 3932ab84be7eSDavid Ahern rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0); 3933ab84be7eSDavid Ahern 3934187d4c6bSPetr Machata rtnl_register(PF_UNSPEC, RTM_GETNEXTHOPBUCKET, rtm_get_nexthop_bucket, 39358a1bbabbSPetr Machata rtm_dump_nexthop_bucket, 0); 39368a1bbabbSPetr Machata 3937ab84be7eSDavid Ahern return 0; 3938ab84be7eSDavid Ahern } 3939ab84be7eSDavid Ahern subsys_initcall(nexthop_init); 3940