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 295072ae00SIdo Schimmel #define NHA_OP_FLAGS_DUMP_ALL (NHA_OP_FLAG_DUMP_STATS | \ 305072ae00SIdo Schimmel NHA_OP_FLAG_DUMP_HW_STATS) 3195fedd76SIdo Schimmel 32643d0878SPetr Machata static const struct nla_policy rtm_nh_policy_new[] = { 33ab84be7eSDavid Ahern [NHA_ID] = { .type = NLA_U32 }, 34ab84be7eSDavid Ahern [NHA_GROUP] = { .type = NLA_BINARY }, 35ab84be7eSDavid Ahern [NHA_GROUP_TYPE] = { .type = NLA_U16 }, 36ab84be7eSDavid Ahern [NHA_BLACKHOLE] = { .type = NLA_FLAG }, 37ab84be7eSDavid Ahern [NHA_OIF] = { .type = NLA_U32 }, 38ab84be7eSDavid Ahern [NHA_GATEWAY] = { .type = NLA_BINARY }, 39ab84be7eSDavid Ahern [NHA_ENCAP_TYPE] = { .type = NLA_U16 }, 40ab84be7eSDavid Ahern [NHA_ENCAP] = { .type = NLA_NESTED }, 4138428d68SRoopa Prabhu [NHA_FDB] = { .type = NLA_FLAG }, 42a2601e2bSPetr Machata [NHA_RES_GROUP] = { .type = NLA_NESTED }, 43746c19a5SIdo Schimmel [NHA_HW_STATS_ENABLE] = NLA_POLICY_MAX(NLA_U32, true), 44ab84be7eSDavid Ahern }; 45ab84be7eSDavid Ahern 4660f5ad5eSPetr Machata static const struct nla_policy rtm_nh_policy_get[] = { 4760f5ad5eSPetr Machata [NHA_ID] = { .type = NLA_U32 }, 4895fedd76SIdo Schimmel [NHA_OP_FLAGS] = NLA_POLICY_MASK(NLA_U32, 4995fedd76SIdo Schimmel NHA_OP_FLAGS_DUMP_ALL), 5060f5ad5eSPetr Machata }; 5160f5ad5eSPetr Machata 522118f939SPetr Machata static const struct nla_policy rtm_nh_policy_del[] = { 532118f939SPetr Machata [NHA_ID] = { .type = NLA_U32 }, 542118f939SPetr Machata }; 552118f939SPetr Machata 5644551bffSPetr Machata static const struct nla_policy rtm_nh_policy_dump[] = { 5744551bffSPetr Machata [NHA_OIF] = { .type = NLA_U32 }, 5844551bffSPetr Machata [NHA_GROUPS] = { .type = NLA_FLAG }, 5944551bffSPetr Machata [NHA_MASTER] = { .type = NLA_U32 }, 6044551bffSPetr Machata [NHA_FDB] = { .type = NLA_FLAG }, 6195fedd76SIdo Schimmel [NHA_OP_FLAGS] = NLA_POLICY_MASK(NLA_U32, 6295fedd76SIdo Schimmel NHA_OP_FLAGS_DUMP_ALL), 6344551bffSPetr Machata }; 6444551bffSPetr Machata 65a2601e2bSPetr Machata static const struct nla_policy rtm_nh_res_policy_new[] = { 66a2601e2bSPetr Machata [NHA_RES_GROUP_BUCKETS] = { .type = NLA_U16 }, 67a2601e2bSPetr Machata [NHA_RES_GROUP_IDLE_TIMER] = { .type = NLA_U32 }, 68a2601e2bSPetr Machata [NHA_RES_GROUP_UNBALANCED_TIMER] = { .type = NLA_U32 }, 69a2601e2bSPetr Machata }; 70a2601e2bSPetr Machata 718a1bbabbSPetr Machata static const struct nla_policy rtm_nh_policy_dump_bucket[] = { 728a1bbabbSPetr Machata [NHA_ID] = { .type = NLA_U32 }, 738a1bbabbSPetr Machata [NHA_OIF] = { .type = NLA_U32 }, 748a1bbabbSPetr Machata [NHA_MASTER] = { .type = NLA_U32 }, 758a1bbabbSPetr Machata [NHA_RES_BUCKET] = { .type = NLA_NESTED }, 768a1bbabbSPetr Machata }; 778a1bbabbSPetr Machata 788a1bbabbSPetr Machata static const struct nla_policy rtm_nh_res_bucket_policy_dump[] = { 798a1bbabbSPetr Machata [NHA_RES_BUCKET_NH_ID] = { .type = NLA_U32 }, 808a1bbabbSPetr Machata }; 818a1bbabbSPetr Machata 82187d4c6bSPetr Machata static const struct nla_policy rtm_nh_policy_get_bucket[] = { 83187d4c6bSPetr Machata [NHA_ID] = { .type = NLA_U32 }, 84187d4c6bSPetr Machata [NHA_RES_BUCKET] = { .type = NLA_NESTED }, 85187d4c6bSPetr Machata }; 86187d4c6bSPetr Machata 87187d4c6bSPetr Machata static const struct nla_policy rtm_nh_res_bucket_policy_get[] = { 88187d4c6bSPetr Machata [NHA_RES_BUCKET_INDEX] = { .type = NLA_U16 }, 89187d4c6bSPetr Machata }; 90187d4c6bSPetr Machata 915ca474f2SIdo Schimmel static bool nexthop_notifiers_is_empty(struct net *net) 925ca474f2SIdo Schimmel { 935ca474f2SIdo Schimmel return !net->nexthop.notifier_chain.head; 945ca474f2SIdo Schimmel } 955ca474f2SIdo Schimmel 965ca474f2SIdo Schimmel static void 975ca474f2SIdo Schimmel __nh_notifier_single_info_init(struct nh_notifier_single_info *nh_info, 9896a85625SPetr Machata const struct nh_info *nhi) 995ca474f2SIdo Schimmel { 1005ca474f2SIdo Schimmel nh_info->dev = nhi->fib_nhc.nhc_dev; 1015ca474f2SIdo Schimmel nh_info->gw_family = nhi->fib_nhc.nhc_gw_family; 1025ca474f2SIdo Schimmel if (nh_info->gw_family == AF_INET) 1035ca474f2SIdo Schimmel nh_info->ipv4 = nhi->fib_nhc.nhc_gw.ipv4; 1045ca474f2SIdo Schimmel else if (nh_info->gw_family == AF_INET6) 1055ca474f2SIdo Schimmel nh_info->ipv6 = nhi->fib_nhc.nhc_gw.ipv6; 1065ca474f2SIdo Schimmel 107e99eb57eSPetr Machata nh_info->id = nhi->nh_parent->id; 1085ca474f2SIdo Schimmel nh_info->is_reject = nhi->reject_nh; 1095ca474f2SIdo Schimmel nh_info->is_fdb = nhi->fdb_nh; 1105ca474f2SIdo Schimmel nh_info->has_encap = !!nhi->fib_nhc.nhc_lwtstate; 1115ca474f2SIdo Schimmel } 1125ca474f2SIdo Schimmel 1135ca474f2SIdo Schimmel static int nh_notifier_single_info_init(struct nh_notifier_info *info, 1145ca474f2SIdo Schimmel const struct nexthop *nh) 1155ca474f2SIdo Schimmel { 11696a85625SPetr Machata struct nh_info *nhi = rtnl_dereference(nh->nh_info); 11796a85625SPetr Machata 11809ad6becSIdo Schimmel info->type = NH_NOTIFIER_INFO_TYPE_SINGLE; 1195ca474f2SIdo Schimmel info->nh = kzalloc(sizeof(*info->nh), GFP_KERNEL); 1205ca474f2SIdo Schimmel if (!info->nh) 1215ca474f2SIdo Schimmel return -ENOMEM; 1225ca474f2SIdo Schimmel 12396a85625SPetr Machata __nh_notifier_single_info_init(info->nh, nhi); 1245ca474f2SIdo Schimmel 1255ca474f2SIdo Schimmel return 0; 1265ca474f2SIdo Schimmel } 1275ca474f2SIdo Schimmel 1285ca474f2SIdo Schimmel static void nh_notifier_single_info_fini(struct nh_notifier_info *info) 1295ca474f2SIdo Schimmel { 1305ca474f2SIdo Schimmel kfree(info->nh); 1315ca474f2SIdo Schimmel } 1325ca474f2SIdo Schimmel 133de1d1ee3SPetr Machata static int nh_notifier_mpath_info_init(struct nh_notifier_info *info, 134da230501SPetr Machata struct nh_group *nhg) 1355ca474f2SIdo Schimmel { 1365ca474f2SIdo Schimmel u16 num_nh = nhg->num_nh; 1375ca474f2SIdo Schimmel int i; 1385ca474f2SIdo Schimmel 13909ad6becSIdo Schimmel info->type = NH_NOTIFIER_INFO_TYPE_GRP; 1405ca474f2SIdo Schimmel info->nh_grp = kzalloc(struct_size(info->nh_grp, nh_entries, num_nh), 1415ca474f2SIdo Schimmel GFP_KERNEL); 1425ca474f2SIdo Schimmel if (!info->nh_grp) 1435ca474f2SIdo Schimmel return -ENOMEM; 1445ca474f2SIdo Schimmel 1455ca474f2SIdo Schimmel info->nh_grp->num_nh = num_nh; 1465ca474f2SIdo Schimmel info->nh_grp->is_fdb = nhg->fdb_nh; 1475877786fSIdo Schimmel info->nh_grp->hw_stats = nhg->hw_stats; 1485ca474f2SIdo Schimmel 1495ca474f2SIdo Schimmel for (i = 0; i < num_nh; i++) { 1505ca474f2SIdo Schimmel struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 15196a85625SPetr Machata struct nh_info *nhi; 1525ca474f2SIdo Schimmel 15396a85625SPetr Machata nhi = rtnl_dereference(nhge->nh->nh_info); 1545ca474f2SIdo Schimmel info->nh_grp->nh_entries[i].weight = nhge->weight; 1555ca474f2SIdo Schimmel __nh_notifier_single_info_init(&info->nh_grp->nh_entries[i].nh, 15696a85625SPetr Machata nhi); 1575ca474f2SIdo Schimmel } 1585ca474f2SIdo Schimmel 1595ca474f2SIdo Schimmel return 0; 1605ca474f2SIdo Schimmel } 1615ca474f2SIdo Schimmel 1627c37c7e0SPetr Machata static int nh_notifier_res_table_info_init(struct nh_notifier_info *info, 1637c37c7e0SPetr Machata struct nh_group *nhg) 1647c37c7e0SPetr Machata { 1657c37c7e0SPetr Machata struct nh_res_table *res_table = rtnl_dereference(nhg->res_table); 1667c37c7e0SPetr Machata u16 num_nh_buckets = res_table->num_nh_buckets; 1677c37c7e0SPetr Machata unsigned long size; 1687c37c7e0SPetr Machata u16 i; 1697c37c7e0SPetr Machata 1707c37c7e0SPetr Machata info->type = NH_NOTIFIER_INFO_TYPE_RES_TABLE; 1717c37c7e0SPetr Machata size = struct_size(info->nh_res_table, nhs, num_nh_buckets); 1727c37c7e0SPetr Machata info->nh_res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO | 1737c37c7e0SPetr Machata __GFP_NOWARN); 1747c37c7e0SPetr Machata if (!info->nh_res_table) 1757c37c7e0SPetr Machata return -ENOMEM; 1767c37c7e0SPetr Machata 1777c37c7e0SPetr Machata info->nh_res_table->num_nh_buckets = num_nh_buckets; 1785877786fSIdo Schimmel info->nh_res_table->hw_stats = nhg->hw_stats; 1797c37c7e0SPetr Machata 1807c37c7e0SPetr Machata for (i = 0; i < num_nh_buckets; i++) { 1817c37c7e0SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 1827c37c7e0SPetr Machata struct nh_grp_entry *nhge; 1837c37c7e0SPetr Machata struct nh_info *nhi; 1847c37c7e0SPetr Machata 1857c37c7e0SPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 1867c37c7e0SPetr Machata nhi = rtnl_dereference(nhge->nh->nh_info); 1877c37c7e0SPetr Machata __nh_notifier_single_info_init(&info->nh_res_table->nhs[i], 1887c37c7e0SPetr Machata nhi); 1897c37c7e0SPetr Machata } 1907c37c7e0SPetr Machata 1917c37c7e0SPetr Machata return 0; 1927c37c7e0SPetr Machata } 1937c37c7e0SPetr Machata 194da230501SPetr Machata static int nh_notifier_grp_info_init(struct nh_notifier_info *info, 195da230501SPetr Machata const struct nexthop *nh) 1965ca474f2SIdo Schimmel { 197da230501SPetr Machata struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 198da230501SPetr Machata 199de1d1ee3SPetr Machata if (nhg->hash_threshold) 200de1d1ee3SPetr Machata return nh_notifier_mpath_info_init(info, nhg); 2017c37c7e0SPetr Machata else if (nhg->resilient) 2027c37c7e0SPetr Machata return nh_notifier_res_table_info_init(info, nhg); 203da230501SPetr Machata return -EINVAL; 204da230501SPetr Machata } 205da230501SPetr Machata 206da230501SPetr Machata static void nh_notifier_grp_info_fini(struct nh_notifier_info *info, 207da230501SPetr Machata const struct nexthop *nh) 208da230501SPetr Machata { 209da230501SPetr Machata struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 210da230501SPetr Machata 211de1d1ee3SPetr Machata if (nhg->hash_threshold) 2125ca474f2SIdo Schimmel kfree(info->nh_grp); 2137c37c7e0SPetr Machata else if (nhg->resilient) 2147c37c7e0SPetr Machata vfree(info->nh_res_table); 2155ca474f2SIdo Schimmel } 2165ca474f2SIdo Schimmel 2175ca474f2SIdo Schimmel static int nh_notifier_info_init(struct nh_notifier_info *info, 2185ca474f2SIdo Schimmel const struct nexthop *nh) 2195ca474f2SIdo Schimmel { 2205ca474f2SIdo Schimmel info->id = nh->id; 2215ca474f2SIdo Schimmel 22209ad6becSIdo Schimmel if (nh->is_group) 2235ca474f2SIdo Schimmel return nh_notifier_grp_info_init(info, nh); 2245ca474f2SIdo Schimmel else 2255ca474f2SIdo Schimmel return nh_notifier_single_info_init(info, nh); 2265ca474f2SIdo Schimmel } 2275ca474f2SIdo Schimmel 22809ad6becSIdo Schimmel static void nh_notifier_info_fini(struct nh_notifier_info *info, 22909ad6becSIdo Schimmel const struct nexthop *nh) 2305ca474f2SIdo Schimmel { 23109ad6becSIdo Schimmel if (nh->is_group) 232da230501SPetr Machata nh_notifier_grp_info_fini(info, nh); 2335ca474f2SIdo Schimmel else 2345ca474f2SIdo Schimmel nh_notifier_single_info_fini(info); 2355ca474f2SIdo Schimmel } 2365ca474f2SIdo Schimmel 2378590ceedSRoopa Prabhu static int call_nexthop_notifiers(struct net *net, 238d8e79f1dSNathan Chancellor enum nexthop_event_type event_type, 2393578d53dSIdo Schimmel struct nexthop *nh, 2403578d53dSIdo Schimmel struct netlink_ext_ack *extack) 2418590ceedSRoopa Prabhu { 2425ca474f2SIdo Schimmel struct nh_notifier_info info = { 2435ca474f2SIdo Schimmel .net = net, 2445ca474f2SIdo Schimmel .extack = extack, 2455ca474f2SIdo Schimmel }; 2468590ceedSRoopa Prabhu int err; 2478590ceedSRoopa Prabhu 2485ca474f2SIdo Schimmel ASSERT_RTNL(); 2495ca474f2SIdo Schimmel 2505ca474f2SIdo Schimmel if (nexthop_notifiers_is_empty(net)) 2515ca474f2SIdo Schimmel return 0; 2525ca474f2SIdo Schimmel 2535ca474f2SIdo Schimmel err = nh_notifier_info_init(&info, nh); 2545ca474f2SIdo Schimmel if (err) { 2555ca474f2SIdo Schimmel NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info"); 2565ca474f2SIdo Schimmel return err; 2575ca474f2SIdo Schimmel } 2585ca474f2SIdo Schimmel 25980690ec6SIdo Schimmel err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 2601ec69d18SIdo Schimmel event_type, &info); 26109ad6becSIdo Schimmel nh_notifier_info_fini(&info, nh); 2625ca474f2SIdo Schimmel 2638590ceedSRoopa Prabhu return notifier_to_errno(err); 2648590ceedSRoopa Prabhu } 2658590ceedSRoopa Prabhu 2667c37c7e0SPetr Machata static int 2677c37c7e0SPetr Machata nh_notifier_res_bucket_idle_timer_get(const struct nh_notifier_info *info, 2687c37c7e0SPetr Machata bool force, unsigned int *p_idle_timer_ms) 2697c37c7e0SPetr Machata { 2707c37c7e0SPetr Machata struct nh_res_table *res_table; 2717c37c7e0SPetr Machata struct nh_group *nhg; 2727c37c7e0SPetr Machata struct nexthop *nh; 2737c37c7e0SPetr Machata int err = 0; 2747c37c7e0SPetr Machata 2757c37c7e0SPetr Machata /* When 'force' is false, nexthop bucket replacement is performed 2767c37c7e0SPetr Machata * because the bucket was deemed to be idle. In this case, capable 2777c37c7e0SPetr Machata * listeners can choose to perform an atomic replacement: The bucket is 2787c37c7e0SPetr Machata * only replaced if it is inactive. However, if the idle timer interval 2797c37c7e0SPetr Machata * is smaller than the interval in which a listener is querying 2807c37c7e0SPetr Machata * buckets' activity from the device, then atomic replacement should 2817c37c7e0SPetr Machata * not be tried. Pass the idle timer value to listeners, so that they 2827c37c7e0SPetr Machata * could determine which type of replacement to perform. 2837c37c7e0SPetr Machata */ 2847c37c7e0SPetr Machata if (force) { 2857c37c7e0SPetr Machata *p_idle_timer_ms = 0; 2867c37c7e0SPetr Machata return 0; 2877c37c7e0SPetr Machata } 2887c37c7e0SPetr Machata 2897c37c7e0SPetr Machata rcu_read_lock(); 2907c37c7e0SPetr Machata 2917c37c7e0SPetr Machata nh = nexthop_find_by_id(info->net, info->id); 2927c37c7e0SPetr Machata if (!nh) { 2937c37c7e0SPetr Machata err = -EINVAL; 2947c37c7e0SPetr Machata goto out; 2957c37c7e0SPetr Machata } 2967c37c7e0SPetr Machata 2977c37c7e0SPetr Machata nhg = rcu_dereference(nh->nh_grp); 2987c37c7e0SPetr Machata res_table = rcu_dereference(nhg->res_table); 2997c37c7e0SPetr Machata *p_idle_timer_ms = jiffies_to_msecs(res_table->idle_timer); 3007c37c7e0SPetr Machata 3017c37c7e0SPetr Machata out: 3027c37c7e0SPetr Machata rcu_read_unlock(); 3037c37c7e0SPetr Machata 3047c37c7e0SPetr Machata return err; 3057c37c7e0SPetr Machata } 3067c37c7e0SPetr Machata 3077c37c7e0SPetr Machata static int nh_notifier_res_bucket_info_init(struct nh_notifier_info *info, 3087c37c7e0SPetr Machata u16 bucket_index, bool force, 3097c37c7e0SPetr Machata struct nh_info *oldi, 3107c37c7e0SPetr Machata struct nh_info *newi) 3117c37c7e0SPetr Machata { 3127c37c7e0SPetr Machata unsigned int idle_timer_ms; 3137c37c7e0SPetr Machata int err; 3147c37c7e0SPetr Machata 3157c37c7e0SPetr Machata err = nh_notifier_res_bucket_idle_timer_get(info, force, 3167c37c7e0SPetr Machata &idle_timer_ms); 3177c37c7e0SPetr Machata if (err) 3187c37c7e0SPetr Machata return err; 3197c37c7e0SPetr Machata 3207c37c7e0SPetr Machata info->type = NH_NOTIFIER_INFO_TYPE_RES_BUCKET; 3217c37c7e0SPetr Machata info->nh_res_bucket = kzalloc(sizeof(*info->nh_res_bucket), 3227c37c7e0SPetr Machata GFP_KERNEL); 3237c37c7e0SPetr Machata if (!info->nh_res_bucket) 3247c37c7e0SPetr Machata return -ENOMEM; 3257c37c7e0SPetr Machata 3267c37c7e0SPetr Machata info->nh_res_bucket->bucket_index = bucket_index; 3277c37c7e0SPetr Machata info->nh_res_bucket->idle_timer_ms = idle_timer_ms; 3287c37c7e0SPetr Machata info->nh_res_bucket->force = force; 3297c37c7e0SPetr Machata __nh_notifier_single_info_init(&info->nh_res_bucket->old_nh, oldi); 3307c37c7e0SPetr Machata __nh_notifier_single_info_init(&info->nh_res_bucket->new_nh, newi); 3317c37c7e0SPetr Machata return 0; 3327c37c7e0SPetr Machata } 3337c37c7e0SPetr Machata 3347c37c7e0SPetr Machata static void nh_notifier_res_bucket_info_fini(struct nh_notifier_info *info) 3357c37c7e0SPetr Machata { 3367c37c7e0SPetr Machata kfree(info->nh_res_bucket); 3377c37c7e0SPetr Machata } 3387c37c7e0SPetr Machata 3397c37c7e0SPetr Machata static int __call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id, 3407c37c7e0SPetr Machata u16 bucket_index, bool force, 3417c37c7e0SPetr Machata struct nh_info *oldi, 3427c37c7e0SPetr Machata struct nh_info *newi, 3437c37c7e0SPetr Machata struct netlink_ext_ack *extack) 3447c37c7e0SPetr Machata { 3457c37c7e0SPetr Machata struct nh_notifier_info info = { 3467c37c7e0SPetr Machata .net = net, 3477c37c7e0SPetr Machata .extack = extack, 3487c37c7e0SPetr Machata .id = nhg_id, 3497c37c7e0SPetr Machata }; 3507c37c7e0SPetr Machata int err; 3517c37c7e0SPetr Machata 3527c37c7e0SPetr Machata if (nexthop_notifiers_is_empty(net)) 3537c37c7e0SPetr Machata return 0; 3547c37c7e0SPetr Machata 3557c37c7e0SPetr Machata err = nh_notifier_res_bucket_info_init(&info, bucket_index, force, 3567c37c7e0SPetr Machata oldi, newi); 3577c37c7e0SPetr Machata if (err) 3587c37c7e0SPetr Machata return err; 3597c37c7e0SPetr Machata 3607c37c7e0SPetr Machata err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 3617c37c7e0SPetr Machata NEXTHOP_EVENT_BUCKET_REPLACE, &info); 3627c37c7e0SPetr Machata nh_notifier_res_bucket_info_fini(&info); 3637c37c7e0SPetr Machata 3647c37c7e0SPetr Machata return notifier_to_errno(err); 3657c37c7e0SPetr Machata } 3667c37c7e0SPetr Machata 367283a72a5SPetr Machata /* There are three users of RES_TABLE, and NHs etc. referenced from there: 368283a72a5SPetr Machata * 369283a72a5SPetr Machata * 1) a collection of callbacks for NH maintenance. This operates under 370283a72a5SPetr Machata * RTNL, 371283a72a5SPetr Machata * 2) the delayed work that gradually balances the resilient table, 372283a72a5SPetr Machata * 3) and nexthop_select_path(), operating under RCU. 373283a72a5SPetr Machata * 374283a72a5SPetr Machata * Both the delayed work and the RTNL block are writers, and need to 375283a72a5SPetr Machata * maintain mutual exclusion. Since there are only two and well-known 376283a72a5SPetr Machata * writers for each table, the RTNL code can make sure it has exclusive 377283a72a5SPetr Machata * access thus: 378283a72a5SPetr Machata * 379283a72a5SPetr Machata * - Have the DW operate without locking; 380283a72a5SPetr Machata * - synchronously cancel the DW; 381283a72a5SPetr Machata * - do the writing; 382283a72a5SPetr Machata * - if the write was not actually a delete, call upkeep, which schedules 383283a72a5SPetr Machata * DW again if necessary. 384283a72a5SPetr Machata * 385283a72a5SPetr Machata * The functions that are always called from the RTNL context use 386283a72a5SPetr Machata * rtnl_dereference(). The functions that can also be called from the DW do 387283a72a5SPetr Machata * a raw dereference and rely on the above mutual exclusion scheme. 388283a72a5SPetr Machata */ 389283a72a5SPetr Machata #define nh_res_dereference(p) (rcu_dereference_raw(p)) 390283a72a5SPetr Machata 3917c37c7e0SPetr Machata static int call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id, 3927c37c7e0SPetr Machata u16 bucket_index, bool force, 3937c37c7e0SPetr Machata struct nexthop *old_nh, 3947c37c7e0SPetr Machata struct nexthop *new_nh, 3957c37c7e0SPetr Machata struct netlink_ext_ack *extack) 3967c37c7e0SPetr Machata { 3977c37c7e0SPetr Machata struct nh_info *oldi = nh_res_dereference(old_nh->nh_info); 3987c37c7e0SPetr Machata struct nh_info *newi = nh_res_dereference(new_nh->nh_info); 3997c37c7e0SPetr Machata 4007c37c7e0SPetr Machata return __call_nexthop_res_bucket_notifiers(net, nhg_id, bucket_index, 4017c37c7e0SPetr Machata force, oldi, newi, extack); 4027c37c7e0SPetr Machata } 4037c37c7e0SPetr Machata 4047c37c7e0SPetr Machata static int call_nexthop_res_table_notifiers(struct net *net, struct nexthop *nh, 4057c37c7e0SPetr Machata struct netlink_ext_ack *extack) 4067c37c7e0SPetr Machata { 4077c37c7e0SPetr Machata struct nh_notifier_info info = { 4087c37c7e0SPetr Machata .net = net, 4097c37c7e0SPetr Machata .extack = extack, 4102d32c493SPetr Machata .id = nh->id, 4117c37c7e0SPetr Machata }; 4127c37c7e0SPetr Machata struct nh_group *nhg; 4137c37c7e0SPetr Machata int err; 4147c37c7e0SPetr Machata 4157c37c7e0SPetr Machata ASSERT_RTNL(); 4167c37c7e0SPetr Machata 4177c37c7e0SPetr Machata if (nexthop_notifiers_is_empty(net)) 4187c37c7e0SPetr Machata return 0; 4197c37c7e0SPetr Machata 4207c37c7e0SPetr Machata /* At this point, the nexthop buckets are still not populated. Only 4217c37c7e0SPetr Machata * emit a notification with the logical nexthops, so that a listener 4227c37c7e0SPetr Machata * could potentially veto it in case of unsupported configuration. 4237c37c7e0SPetr Machata */ 4247c37c7e0SPetr Machata nhg = rtnl_dereference(nh->nh_grp); 425de1d1ee3SPetr Machata err = nh_notifier_mpath_info_init(&info, nhg); 4267c37c7e0SPetr Machata if (err) { 4277c37c7e0SPetr Machata NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info"); 4287c37c7e0SPetr Machata return err; 4297c37c7e0SPetr Machata } 4307c37c7e0SPetr Machata 4317c37c7e0SPetr Machata err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 4327c37c7e0SPetr Machata NEXTHOP_EVENT_RES_TABLE_PRE_REPLACE, 4337c37c7e0SPetr Machata &info); 4347c37c7e0SPetr Machata kfree(info.nh_grp); 4357c37c7e0SPetr Machata 4367c37c7e0SPetr Machata return notifier_to_errno(err); 4377c37c7e0SPetr Machata } 4387c37c7e0SPetr Machata 439975ff7f3SIdo Schimmel static int call_nexthop_notifier(struct notifier_block *nb, struct net *net, 440975ff7f3SIdo Schimmel enum nexthop_event_type event_type, 441975ff7f3SIdo Schimmel struct nexthop *nh, 442975ff7f3SIdo Schimmel struct netlink_ext_ack *extack) 443975ff7f3SIdo Schimmel { 444975ff7f3SIdo Schimmel struct nh_notifier_info info = { 445975ff7f3SIdo Schimmel .net = net, 446975ff7f3SIdo Schimmel .extack = extack, 447975ff7f3SIdo Schimmel }; 448975ff7f3SIdo Schimmel int err; 449975ff7f3SIdo Schimmel 450975ff7f3SIdo Schimmel err = nh_notifier_info_init(&info, nh); 451975ff7f3SIdo Schimmel if (err) 452975ff7f3SIdo Schimmel return err; 453975ff7f3SIdo Schimmel 454975ff7f3SIdo Schimmel err = nb->notifier_call(nb, event_type, &info); 45509ad6becSIdo Schimmel nh_notifier_info_fini(&info, nh); 456975ff7f3SIdo Schimmel 457975ff7f3SIdo Schimmel return notifier_to_errno(err); 458975ff7f3SIdo Schimmel } 459975ff7f3SIdo Schimmel 460597cfe4fSDavid Ahern static unsigned int nh_dev_hashfn(unsigned int val) 461597cfe4fSDavid Ahern { 462597cfe4fSDavid Ahern unsigned int mask = NH_DEV_HASHSIZE - 1; 463597cfe4fSDavid Ahern 464597cfe4fSDavid Ahern return (val ^ 465597cfe4fSDavid Ahern (val >> NH_DEV_HASHBITS) ^ 466597cfe4fSDavid Ahern (val >> (NH_DEV_HASHBITS * 2))) & mask; 467597cfe4fSDavid Ahern } 468597cfe4fSDavid Ahern 469597cfe4fSDavid Ahern static void nexthop_devhash_add(struct net *net, struct nh_info *nhi) 470597cfe4fSDavid Ahern { 471597cfe4fSDavid Ahern struct net_device *dev = nhi->fib_nhc.nhc_dev; 472597cfe4fSDavid Ahern struct hlist_head *head; 473597cfe4fSDavid Ahern unsigned int hash; 474597cfe4fSDavid Ahern 475597cfe4fSDavid Ahern WARN_ON(!dev); 476597cfe4fSDavid Ahern 477597cfe4fSDavid Ahern hash = nh_dev_hashfn(dev->ifindex); 478597cfe4fSDavid Ahern head = &net->nexthop.devhash[hash]; 479597cfe4fSDavid Ahern hlist_add_head(&nhi->dev_hash, head); 480597cfe4fSDavid Ahern } 481597cfe4fSDavid Ahern 4825d1f0f09SDavid Ahern static void nexthop_free_group(struct nexthop *nh) 483ab84be7eSDavid Ahern { 484430a0491SDavid Ahern struct nh_group *nhg; 485430a0491SDavid Ahern int i; 486430a0491SDavid Ahern 487430a0491SDavid Ahern nhg = rcu_dereference_raw(nh->nh_grp); 48890f33bffSNikolay Aleksandrov for (i = 0; i < nhg->num_nh; ++i) { 48990f33bffSNikolay Aleksandrov struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 490430a0491SDavid Ahern 49190f33bffSNikolay Aleksandrov WARN_ON(!list_empty(&nhge->nh_list)); 492f4676ea7SIdo Schimmel free_percpu(nhge->stats); 49390f33bffSNikolay Aleksandrov nexthop_put(nhge->nh); 49490f33bffSNikolay Aleksandrov } 49590f33bffSNikolay Aleksandrov 49690f33bffSNikolay Aleksandrov WARN_ON(nhg->spare == nhg); 49790f33bffSNikolay Aleksandrov 498283a72a5SPetr Machata if (nhg->resilient) 499283a72a5SPetr Machata vfree(rcu_dereference_raw(nhg->res_table)); 500283a72a5SPetr Machata 50190f33bffSNikolay Aleksandrov kfree(nhg->spare); 502430a0491SDavid Ahern kfree(nhg); 503430a0491SDavid Ahern } 504430a0491SDavid Ahern 505430a0491SDavid Ahern static void nexthop_free_single(struct nexthop *nh) 506430a0491SDavid Ahern { 507ab84be7eSDavid Ahern struct nh_info *nhi; 508ab84be7eSDavid Ahern 509ab84be7eSDavid Ahern nhi = rcu_dereference_raw(nh->nh_info); 510597cfe4fSDavid Ahern switch (nhi->family) { 511597cfe4fSDavid Ahern case AF_INET: 512597cfe4fSDavid Ahern fib_nh_release(nh->net, &nhi->fib_nh); 513597cfe4fSDavid Ahern break; 51453010f99SDavid Ahern case AF_INET6: 51553010f99SDavid Ahern ipv6_stub->fib6_nh_release(&nhi->fib6_nh); 51653010f99SDavid Ahern break; 517597cfe4fSDavid Ahern } 518ab84be7eSDavid Ahern kfree(nhi); 519430a0491SDavid Ahern } 520430a0491SDavid Ahern 521430a0491SDavid Ahern void nexthop_free_rcu(struct rcu_head *head) 522430a0491SDavid Ahern { 523430a0491SDavid Ahern struct nexthop *nh = container_of(head, struct nexthop, rcu); 524430a0491SDavid Ahern 525430a0491SDavid Ahern if (nh->is_group) 5265d1f0f09SDavid Ahern nexthop_free_group(nh); 527430a0491SDavid Ahern else 528430a0491SDavid Ahern nexthop_free_single(nh); 529ab84be7eSDavid Ahern 530ab84be7eSDavid Ahern kfree(nh); 531ab84be7eSDavid Ahern } 532ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_free_rcu); 533ab84be7eSDavid Ahern 534ab84be7eSDavid Ahern static struct nexthop *nexthop_alloc(void) 535ab84be7eSDavid Ahern { 536ab84be7eSDavid Ahern struct nexthop *nh; 537ab84be7eSDavid Ahern 538ab84be7eSDavid Ahern nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL); 539430a0491SDavid Ahern if (nh) { 5404c7e8084SDavid Ahern INIT_LIST_HEAD(&nh->fi_list); 541f88d8ea6SDavid Ahern INIT_LIST_HEAD(&nh->f6i_list); 542430a0491SDavid Ahern INIT_LIST_HEAD(&nh->grp_list); 54338428d68SRoopa Prabhu INIT_LIST_HEAD(&nh->fdb_list); 544430a0491SDavid Ahern } 545ab84be7eSDavid Ahern return nh; 546ab84be7eSDavid Ahern } 547ab84be7eSDavid Ahern 548430a0491SDavid Ahern static struct nh_group *nexthop_grp_alloc(u16 num_nh) 549430a0491SDavid Ahern { 550430a0491SDavid Ahern struct nh_group *nhg; 551430a0491SDavid Ahern 552d7d49dc7SIdo Schimmel nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL); 553430a0491SDavid Ahern if (nhg) 554430a0491SDavid Ahern nhg->num_nh = num_nh; 555430a0491SDavid Ahern 556430a0491SDavid Ahern return nhg; 557430a0491SDavid Ahern } 558430a0491SDavid Ahern 559283a72a5SPetr Machata static void nh_res_table_upkeep_dw(struct work_struct *work); 560283a72a5SPetr Machata 561283a72a5SPetr Machata static struct nh_res_table * 562283a72a5SPetr Machata nexthop_res_table_alloc(struct net *net, u32 nhg_id, struct nh_config *cfg) 563283a72a5SPetr Machata { 564283a72a5SPetr Machata const u16 num_nh_buckets = cfg->nh_grp_res_num_buckets; 565283a72a5SPetr Machata struct nh_res_table *res_table; 566283a72a5SPetr Machata unsigned long size; 567283a72a5SPetr Machata 568283a72a5SPetr Machata size = struct_size(res_table, nh_buckets, num_nh_buckets); 569283a72a5SPetr Machata res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN); 570283a72a5SPetr Machata if (!res_table) 571283a72a5SPetr Machata return NULL; 572283a72a5SPetr Machata 573283a72a5SPetr Machata res_table->net = net; 574283a72a5SPetr Machata res_table->nhg_id = nhg_id; 575283a72a5SPetr Machata INIT_DELAYED_WORK(&res_table->upkeep_dw, &nh_res_table_upkeep_dw); 576283a72a5SPetr Machata INIT_LIST_HEAD(&res_table->uw_nh_entries); 577283a72a5SPetr Machata res_table->idle_timer = cfg->nh_grp_res_idle_timer; 578283a72a5SPetr Machata res_table->unbalanced_timer = cfg->nh_grp_res_unbalanced_timer; 579283a72a5SPetr Machata res_table->num_nh_buckets = num_nh_buckets; 580283a72a5SPetr Machata return res_table; 581283a72a5SPetr Machata } 582283a72a5SPetr Machata 583ab84be7eSDavid Ahern static void nh_base_seq_inc(struct net *net) 584ab84be7eSDavid Ahern { 585ab84be7eSDavid Ahern while (++net->nexthop.seq == 0) 586ab84be7eSDavid Ahern ; 587ab84be7eSDavid Ahern } 588ab84be7eSDavid Ahern 589ab84be7eSDavid Ahern /* no reference taken; rcu lock or rtnl must be held */ 590ab84be7eSDavid Ahern struct nexthop *nexthop_find_by_id(struct net *net, u32 id) 591ab84be7eSDavid Ahern { 592ab84be7eSDavid Ahern struct rb_node **pp, *parent = NULL, *next; 593ab84be7eSDavid Ahern 594ab84be7eSDavid Ahern pp = &net->nexthop.rb_root.rb_node; 595ab84be7eSDavid Ahern while (1) { 596ab84be7eSDavid Ahern struct nexthop *nh; 597ab84be7eSDavid Ahern 598ab84be7eSDavid Ahern next = rcu_dereference_raw(*pp); 599ab84be7eSDavid Ahern if (!next) 600ab84be7eSDavid Ahern break; 601ab84be7eSDavid Ahern parent = next; 602ab84be7eSDavid Ahern 603ab84be7eSDavid Ahern nh = rb_entry(parent, struct nexthop, rb_node); 604ab84be7eSDavid Ahern if (id < nh->id) 605ab84be7eSDavid Ahern pp = &next->rb_left; 606ab84be7eSDavid Ahern else if (id > nh->id) 607ab84be7eSDavid Ahern pp = &next->rb_right; 608ab84be7eSDavid Ahern else 609ab84be7eSDavid Ahern return nh; 610ab84be7eSDavid Ahern } 611ab84be7eSDavid Ahern return NULL; 612ab84be7eSDavid Ahern } 613ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_find_by_id); 614ab84be7eSDavid Ahern 615ab84be7eSDavid Ahern /* used for auto id allocation; called with rtnl held */ 616ab84be7eSDavid Ahern static u32 nh_find_unused_id(struct net *net) 617ab84be7eSDavid Ahern { 618ab84be7eSDavid Ahern u32 id_start = net->nexthop.last_id_allocated; 619ab84be7eSDavid Ahern 620ab84be7eSDavid Ahern while (1) { 621ab84be7eSDavid Ahern net->nexthop.last_id_allocated++; 622ab84be7eSDavid Ahern if (net->nexthop.last_id_allocated == id_start) 623ab84be7eSDavid Ahern break; 624ab84be7eSDavid Ahern 625ab84be7eSDavid Ahern if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated)) 626ab84be7eSDavid Ahern return net->nexthop.last_id_allocated; 627ab84be7eSDavid Ahern } 628ab84be7eSDavid Ahern return 0; 629ab84be7eSDavid Ahern } 630ab84be7eSDavid Ahern 631283a72a5SPetr Machata static void nh_res_time_set_deadline(unsigned long next_time, 632283a72a5SPetr Machata unsigned long *deadline) 633283a72a5SPetr Machata { 634283a72a5SPetr Machata if (time_before(next_time, *deadline)) 635283a72a5SPetr Machata *deadline = next_time; 636283a72a5SPetr Machata } 637283a72a5SPetr Machata 638a2601e2bSPetr Machata static clock_t nh_res_table_unbalanced_time(struct nh_res_table *res_table) 639a2601e2bSPetr Machata { 640a2601e2bSPetr Machata if (list_empty(&res_table->uw_nh_entries)) 641a2601e2bSPetr Machata return 0; 642a2601e2bSPetr Machata return jiffies_delta_to_clock_t(jiffies - res_table->unbalanced_since); 643a2601e2bSPetr Machata } 644a2601e2bSPetr Machata 645a2601e2bSPetr Machata static int nla_put_nh_group_res(struct sk_buff *skb, struct nh_group *nhg) 646a2601e2bSPetr Machata { 647a2601e2bSPetr Machata struct nh_res_table *res_table = rtnl_dereference(nhg->res_table); 648a2601e2bSPetr Machata struct nlattr *nest; 649a2601e2bSPetr Machata 650a2601e2bSPetr Machata nest = nla_nest_start(skb, NHA_RES_GROUP); 651a2601e2bSPetr Machata if (!nest) 652a2601e2bSPetr Machata return -EMSGSIZE; 653a2601e2bSPetr Machata 654a2601e2bSPetr Machata if (nla_put_u16(skb, NHA_RES_GROUP_BUCKETS, 655a2601e2bSPetr Machata res_table->num_nh_buckets) || 656a2601e2bSPetr Machata nla_put_u32(skb, NHA_RES_GROUP_IDLE_TIMER, 657a2601e2bSPetr Machata jiffies_to_clock_t(res_table->idle_timer)) || 658a2601e2bSPetr Machata nla_put_u32(skb, NHA_RES_GROUP_UNBALANCED_TIMER, 659a2601e2bSPetr Machata jiffies_to_clock_t(res_table->unbalanced_timer)) || 660a2601e2bSPetr Machata nla_put_u64_64bit(skb, NHA_RES_GROUP_UNBALANCED_TIME, 661a2601e2bSPetr Machata nh_res_table_unbalanced_time(res_table), 662a2601e2bSPetr Machata NHA_RES_GROUP_PAD)) 663a2601e2bSPetr Machata goto nla_put_failure; 664a2601e2bSPetr Machata 665a2601e2bSPetr Machata nla_nest_end(skb, nest); 666a2601e2bSPetr Machata return 0; 667a2601e2bSPetr Machata 668a2601e2bSPetr Machata nla_put_failure: 669a2601e2bSPetr Machata nla_nest_cancel(skb, nest); 670a2601e2bSPetr Machata return -EMSGSIZE; 671a2601e2bSPetr Machata } 672a2601e2bSPetr Machata 673f4676ea7SIdo Schimmel static void nh_grp_entry_stats_inc(struct nh_grp_entry *nhge) 674f4676ea7SIdo Schimmel { 675f4676ea7SIdo Schimmel struct nh_grp_entry_stats *cpu_stats; 676f4676ea7SIdo Schimmel 677e006858fSIdo Schimmel cpu_stats = get_cpu_ptr(nhge->stats); 678f4676ea7SIdo Schimmel u64_stats_update_begin(&cpu_stats->syncp); 679f4676ea7SIdo Schimmel u64_stats_inc(&cpu_stats->packets); 680f4676ea7SIdo Schimmel u64_stats_update_end(&cpu_stats->syncp); 681e006858fSIdo Schimmel put_cpu_ptr(cpu_stats); 682f4676ea7SIdo Schimmel } 683f4676ea7SIdo Schimmel 68495fedd76SIdo Schimmel static void nh_grp_entry_stats_read(struct nh_grp_entry *nhge, 68595fedd76SIdo Schimmel u64 *ret_packets) 686430a0491SDavid Ahern { 68795fedd76SIdo Schimmel int i; 68895fedd76SIdo Schimmel 68995fedd76SIdo Schimmel *ret_packets = 0; 69095fedd76SIdo Schimmel 69195fedd76SIdo Schimmel for_each_possible_cpu(i) { 69295fedd76SIdo Schimmel struct nh_grp_entry_stats *cpu_stats; 69395fedd76SIdo Schimmel unsigned int start; 69495fedd76SIdo Schimmel u64 packets; 69595fedd76SIdo Schimmel 69695fedd76SIdo Schimmel cpu_stats = per_cpu_ptr(nhge->stats, i); 69795fedd76SIdo Schimmel do { 69895fedd76SIdo Schimmel start = u64_stats_fetch_begin(&cpu_stats->syncp); 69995fedd76SIdo Schimmel packets = u64_stats_read(&cpu_stats->packets); 70095fedd76SIdo Schimmel } while (u64_stats_fetch_retry(&cpu_stats->syncp, start)); 70195fedd76SIdo Schimmel 70295fedd76SIdo Schimmel *ret_packets += packets; 70395fedd76SIdo Schimmel } 70495fedd76SIdo Schimmel } 70595fedd76SIdo Schimmel 7065072ae00SIdo Schimmel static int nh_notifier_grp_hw_stats_init(struct nh_notifier_info *info, 7075072ae00SIdo Schimmel const struct nexthop *nh) 7085072ae00SIdo Schimmel { 7095072ae00SIdo Schimmel struct nh_group *nhg; 7105072ae00SIdo Schimmel int i; 7115072ae00SIdo Schimmel 7125072ae00SIdo Schimmel ASSERT_RTNL(); 7135072ae00SIdo Schimmel nhg = rtnl_dereference(nh->nh_grp); 7145072ae00SIdo Schimmel 7155072ae00SIdo Schimmel info->id = nh->id; 7165072ae00SIdo Schimmel info->type = NH_NOTIFIER_INFO_TYPE_GRP_HW_STATS; 7175072ae00SIdo Schimmel info->nh_grp_hw_stats = kzalloc(struct_size(info->nh_grp_hw_stats, 7185072ae00SIdo Schimmel stats, nhg->num_nh), 7195072ae00SIdo Schimmel GFP_KERNEL); 7205072ae00SIdo Schimmel if (!info->nh_grp_hw_stats) 7215072ae00SIdo Schimmel return -ENOMEM; 7225072ae00SIdo Schimmel 7235072ae00SIdo Schimmel info->nh_grp_hw_stats->num_nh = nhg->num_nh; 7245072ae00SIdo Schimmel for (i = 0; i < nhg->num_nh; i++) { 7255072ae00SIdo Schimmel struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 7265072ae00SIdo Schimmel 7275072ae00SIdo Schimmel info->nh_grp_hw_stats->stats[i].id = nhge->nh->id; 7285072ae00SIdo Schimmel } 7295072ae00SIdo Schimmel 7305072ae00SIdo Schimmel return 0; 7315072ae00SIdo Schimmel } 7325072ae00SIdo Schimmel 7335072ae00SIdo Schimmel static void nh_notifier_grp_hw_stats_fini(struct nh_notifier_info *info) 7345072ae00SIdo Schimmel { 7355072ae00SIdo Schimmel kfree(info->nh_grp_hw_stats); 7365072ae00SIdo Schimmel } 7375072ae00SIdo Schimmel 7385072ae00SIdo Schimmel void nh_grp_hw_stats_report_delta(struct nh_notifier_grp_hw_stats_info *info, 7395072ae00SIdo Schimmel unsigned int nh_idx, 7405072ae00SIdo Schimmel u64 delta_packets) 7415072ae00SIdo Schimmel { 7425072ae00SIdo Schimmel info->hw_stats_used = true; 7435072ae00SIdo Schimmel info->stats[nh_idx].packets += delta_packets; 7445072ae00SIdo Schimmel } 7455072ae00SIdo Schimmel EXPORT_SYMBOL(nh_grp_hw_stats_report_delta); 7465072ae00SIdo Schimmel 7475072ae00SIdo Schimmel static void nh_grp_hw_stats_apply_update(struct nexthop *nh, 7485072ae00SIdo Schimmel struct nh_notifier_info *info) 7495072ae00SIdo Schimmel { 7505072ae00SIdo Schimmel struct nh_group *nhg; 7515072ae00SIdo Schimmel int i; 7525072ae00SIdo Schimmel 7535072ae00SIdo Schimmel ASSERT_RTNL(); 7545072ae00SIdo Schimmel nhg = rtnl_dereference(nh->nh_grp); 7555072ae00SIdo Schimmel 7565072ae00SIdo Schimmel for (i = 0; i < nhg->num_nh; i++) { 7575072ae00SIdo Schimmel struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 7585072ae00SIdo Schimmel 7595072ae00SIdo Schimmel nhge->packets_hw += info->nh_grp_hw_stats->stats[i].packets; 7605072ae00SIdo Schimmel } 7615072ae00SIdo Schimmel } 7625072ae00SIdo Schimmel 7635072ae00SIdo Schimmel static int nh_grp_hw_stats_update(struct nexthop *nh, bool *hw_stats_used) 7645072ae00SIdo Schimmel { 7655072ae00SIdo Schimmel struct nh_notifier_info info = { 7665072ae00SIdo Schimmel .net = nh->net, 7675072ae00SIdo Schimmel }; 7685072ae00SIdo Schimmel struct net *net = nh->net; 7695072ae00SIdo Schimmel int err; 7705072ae00SIdo Schimmel 7719145e224SDan Carpenter if (nexthop_notifiers_is_empty(net)) { 7729145e224SDan Carpenter *hw_stats_used = false; 7735072ae00SIdo Schimmel return 0; 7749145e224SDan Carpenter } 7755072ae00SIdo Schimmel 7765072ae00SIdo Schimmel err = nh_notifier_grp_hw_stats_init(&info, nh); 7775072ae00SIdo Schimmel if (err) 7785072ae00SIdo Schimmel return err; 7795072ae00SIdo Schimmel 7805072ae00SIdo Schimmel err = blocking_notifier_call_chain(&net->nexthop.notifier_chain, 7815072ae00SIdo Schimmel NEXTHOP_EVENT_HW_STATS_REPORT_DELTA, 7825072ae00SIdo Schimmel &info); 7835072ae00SIdo Schimmel 7845072ae00SIdo Schimmel /* Cache whatever we got, even if there was an error, otherwise the 7855072ae00SIdo Schimmel * successful stats retrievals would get lost. 7865072ae00SIdo Schimmel */ 7875072ae00SIdo Schimmel nh_grp_hw_stats_apply_update(nh, &info); 7885072ae00SIdo Schimmel *hw_stats_used = info.nh_grp_hw_stats->hw_stats_used; 7895072ae00SIdo Schimmel 7905072ae00SIdo Schimmel nh_notifier_grp_hw_stats_fini(&info); 7915072ae00SIdo Schimmel return notifier_to_errno(err); 7925072ae00SIdo Schimmel } 7935072ae00SIdo Schimmel 79495fedd76SIdo Schimmel static int nla_put_nh_group_stats_entry(struct sk_buff *skb, 7955072ae00SIdo Schimmel struct nh_grp_entry *nhge, 7965072ae00SIdo Schimmel u32 op_flags) 79795fedd76SIdo Schimmel { 79895fedd76SIdo Schimmel struct nlattr *nest; 79995fedd76SIdo Schimmel u64 packets; 80095fedd76SIdo Schimmel 80195fedd76SIdo Schimmel nh_grp_entry_stats_read(nhge, &packets); 80295fedd76SIdo Schimmel 80395fedd76SIdo Schimmel nest = nla_nest_start(skb, NHA_GROUP_STATS_ENTRY); 80495fedd76SIdo Schimmel if (!nest) 80595fedd76SIdo Schimmel return -EMSGSIZE; 80695fedd76SIdo Schimmel 80795fedd76SIdo Schimmel if (nla_put_u32(skb, NHA_GROUP_STATS_ENTRY_ID, nhge->nh->id) || 8085072ae00SIdo Schimmel nla_put_uint(skb, NHA_GROUP_STATS_ENTRY_PACKETS, 8095072ae00SIdo Schimmel packets + nhge->packets_hw)) 8105072ae00SIdo Schimmel goto nla_put_failure; 8115072ae00SIdo Schimmel 8125072ae00SIdo Schimmel if (op_flags & NHA_OP_FLAG_DUMP_HW_STATS && 8135072ae00SIdo Schimmel nla_put_uint(skb, NHA_GROUP_STATS_ENTRY_PACKETS_HW, 8145072ae00SIdo Schimmel nhge->packets_hw)) 81595fedd76SIdo Schimmel goto nla_put_failure; 81695fedd76SIdo Schimmel 81795fedd76SIdo Schimmel nla_nest_end(skb, nest); 81895fedd76SIdo Schimmel return 0; 81995fedd76SIdo Schimmel 82095fedd76SIdo Schimmel nla_put_failure: 82195fedd76SIdo Schimmel nla_nest_cancel(skb, nest); 82295fedd76SIdo Schimmel return -EMSGSIZE; 82395fedd76SIdo Schimmel } 82495fedd76SIdo Schimmel 8255072ae00SIdo Schimmel static int nla_put_nh_group_stats(struct sk_buff *skb, struct nexthop *nh, 8265072ae00SIdo Schimmel u32 op_flags) 82795fedd76SIdo Schimmel { 82895fedd76SIdo Schimmel struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 82995fedd76SIdo Schimmel struct nlattr *nest; 8305072ae00SIdo Schimmel bool hw_stats_used; 8315072ae00SIdo Schimmel int err; 83295fedd76SIdo Schimmel int i; 83395fedd76SIdo Schimmel 8345072ae00SIdo Schimmel if (nla_put_u32(skb, NHA_HW_STATS_ENABLE, nhg->hw_stats)) 8355072ae00SIdo Schimmel goto err_out; 8365072ae00SIdo Schimmel 8375072ae00SIdo Schimmel if (op_flags & NHA_OP_FLAG_DUMP_HW_STATS && 8385072ae00SIdo Schimmel nhg->hw_stats) { 8395072ae00SIdo Schimmel err = nh_grp_hw_stats_update(nh, &hw_stats_used); 8405072ae00SIdo Schimmel if (err) 8415072ae00SIdo Schimmel goto out; 8425072ae00SIdo Schimmel 8435072ae00SIdo Schimmel if (nla_put_u32(skb, NHA_HW_STATS_USED, hw_stats_used)) 8445072ae00SIdo Schimmel goto err_out; 8455072ae00SIdo Schimmel } 8465072ae00SIdo Schimmel 84795fedd76SIdo Schimmel nest = nla_nest_start(skb, NHA_GROUP_STATS); 84895fedd76SIdo Schimmel if (!nest) 8495072ae00SIdo Schimmel goto err_out; 85095fedd76SIdo Schimmel 85195fedd76SIdo Schimmel for (i = 0; i < nhg->num_nh; i++) 8525072ae00SIdo Schimmel if (nla_put_nh_group_stats_entry(skb, &nhg->nh_entries[i], 8535072ae00SIdo Schimmel op_flags)) 85495fedd76SIdo Schimmel goto cancel_out; 85595fedd76SIdo Schimmel 85695fedd76SIdo Schimmel nla_nest_end(skb, nest); 85795fedd76SIdo Schimmel return 0; 85895fedd76SIdo Schimmel 85995fedd76SIdo Schimmel cancel_out: 86095fedd76SIdo Schimmel nla_nest_cancel(skb, nest); 8615072ae00SIdo Schimmel err_out: 8625072ae00SIdo Schimmel err = -EMSGSIZE; 8635072ae00SIdo Schimmel out: 8645072ae00SIdo Schimmel return err; 86595fedd76SIdo Schimmel } 86695fedd76SIdo Schimmel 86795fedd76SIdo Schimmel static int nla_put_nh_group(struct sk_buff *skb, struct nexthop *nh, 86895fedd76SIdo Schimmel u32 op_flags) 86995fedd76SIdo Schimmel { 87095fedd76SIdo Schimmel struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 871430a0491SDavid Ahern struct nexthop_grp *p; 872430a0491SDavid Ahern size_t len = nhg->num_nh * sizeof(*p); 873430a0491SDavid Ahern struct nlattr *nla; 874430a0491SDavid Ahern u16 group_type = 0; 875430a0491SDavid Ahern int i; 876430a0491SDavid Ahern 877de1d1ee3SPetr Machata if (nhg->hash_threshold) 878430a0491SDavid Ahern group_type = NEXTHOP_GRP_TYPE_MPATH; 879a2601e2bSPetr Machata else if (nhg->resilient) 880a2601e2bSPetr Machata group_type = NEXTHOP_GRP_TYPE_RES; 881430a0491SDavid Ahern 882430a0491SDavid Ahern if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type)) 883430a0491SDavid Ahern goto nla_put_failure; 884430a0491SDavid Ahern 885430a0491SDavid Ahern nla = nla_reserve(skb, NHA_GROUP, len); 886430a0491SDavid Ahern if (!nla) 887430a0491SDavid Ahern goto nla_put_failure; 888430a0491SDavid Ahern 889430a0491SDavid Ahern p = nla_data(nla); 890430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 891*6d745cd0SPetr Machata *p++ = (struct nexthop_grp) { 892*6d745cd0SPetr Machata .id = nhg->nh_entries[i].nh->id, 893*6d745cd0SPetr Machata .weight = nhg->nh_entries[i].weight - 1, 894*6d745cd0SPetr Machata }; 895430a0491SDavid Ahern } 896430a0491SDavid Ahern 897a2601e2bSPetr Machata if (nhg->resilient && nla_put_nh_group_res(skb, nhg)) 898a2601e2bSPetr Machata goto nla_put_failure; 899a2601e2bSPetr Machata 90095fedd76SIdo Schimmel if (op_flags & NHA_OP_FLAG_DUMP_STATS && 901746c19a5SIdo Schimmel (nla_put_u32(skb, NHA_HW_STATS_ENABLE, nhg->hw_stats) || 9025072ae00SIdo Schimmel nla_put_nh_group_stats(skb, nh, op_flags))) 90395fedd76SIdo Schimmel goto nla_put_failure; 90495fedd76SIdo Schimmel 905430a0491SDavid Ahern return 0; 906430a0491SDavid Ahern 907430a0491SDavid Ahern nla_put_failure: 908430a0491SDavid Ahern return -EMSGSIZE; 909430a0491SDavid Ahern } 910430a0491SDavid Ahern 911ab84be7eSDavid Ahern static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh, 91295fedd76SIdo Schimmel int event, u32 portid, u32 seq, unsigned int nlflags, 91395fedd76SIdo Schimmel u32 op_flags) 914ab84be7eSDavid Ahern { 91553010f99SDavid Ahern struct fib6_nh *fib6_nh; 916597cfe4fSDavid Ahern struct fib_nh *fib_nh; 917ab84be7eSDavid Ahern struct nlmsghdr *nlh; 918ab84be7eSDavid Ahern struct nh_info *nhi; 919ab84be7eSDavid Ahern struct nhmsg *nhm; 920ab84be7eSDavid Ahern 921ab84be7eSDavid Ahern nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags); 922ab84be7eSDavid Ahern if (!nlh) 923ab84be7eSDavid Ahern return -EMSGSIZE; 924ab84be7eSDavid Ahern 925ab84be7eSDavid Ahern nhm = nlmsg_data(nlh); 926ab84be7eSDavid Ahern nhm->nh_family = AF_UNSPEC; 927ab84be7eSDavid Ahern nhm->nh_flags = nh->nh_flags; 928ab84be7eSDavid Ahern nhm->nh_protocol = nh->protocol; 929ab84be7eSDavid Ahern nhm->nh_scope = 0; 930ab84be7eSDavid Ahern nhm->resvd = 0; 931ab84be7eSDavid Ahern 932ab84be7eSDavid Ahern if (nla_put_u32(skb, NHA_ID, nh->id)) 933ab84be7eSDavid Ahern goto nla_put_failure; 934ab84be7eSDavid Ahern 935430a0491SDavid Ahern if (nh->is_group) { 936430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 937430a0491SDavid Ahern 938ce9ac056SDavid Ahern if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB)) 939ce9ac056SDavid Ahern goto nla_put_failure; 94095fedd76SIdo Schimmel if (nla_put_nh_group(skb, nh, op_flags)) 941430a0491SDavid Ahern goto nla_put_failure; 942430a0491SDavid Ahern goto out; 943430a0491SDavid Ahern } 944430a0491SDavid Ahern 945ab84be7eSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 946ab84be7eSDavid Ahern nhm->nh_family = nhi->family; 947ab84be7eSDavid Ahern if (nhi->reject_nh) { 948ab84be7eSDavid Ahern if (nla_put_flag(skb, NHA_BLACKHOLE)) 949ab84be7eSDavid Ahern goto nla_put_failure; 950ab84be7eSDavid Ahern goto out; 951ce9ac056SDavid Ahern } else if (nhi->fdb_nh) { 952ce9ac056SDavid Ahern if (nla_put_flag(skb, NHA_FDB)) 953ce9ac056SDavid Ahern goto nla_put_failure; 954ce9ac056SDavid Ahern } else { 955597cfe4fSDavid Ahern const struct net_device *dev; 956597cfe4fSDavid Ahern 957597cfe4fSDavid Ahern dev = nhi->fib_nhc.nhc_dev; 958597cfe4fSDavid Ahern if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex)) 959597cfe4fSDavid Ahern goto nla_put_failure; 960597cfe4fSDavid Ahern } 961597cfe4fSDavid Ahern 962597cfe4fSDavid Ahern nhm->nh_scope = nhi->fib_nhc.nhc_scope; 963597cfe4fSDavid Ahern switch (nhi->family) { 964597cfe4fSDavid Ahern case AF_INET: 965597cfe4fSDavid Ahern fib_nh = &nhi->fib_nh; 966597cfe4fSDavid Ahern if (fib_nh->fib_nh_gw_family && 96733d80996SIdo Schimmel nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4)) 968597cfe4fSDavid Ahern goto nla_put_failure; 969597cfe4fSDavid Ahern break; 97053010f99SDavid Ahern 97153010f99SDavid Ahern case AF_INET6: 97253010f99SDavid Ahern fib6_nh = &nhi->fib6_nh; 97353010f99SDavid Ahern if (fib6_nh->fib_nh_gw_family && 97453010f99SDavid Ahern nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6)) 97553010f99SDavid Ahern goto nla_put_failure; 97653010f99SDavid Ahern break; 977ab84be7eSDavid Ahern } 978ab84be7eSDavid Ahern 979b513bd03SDavid Ahern if (nhi->fib_nhc.nhc_lwtstate && 980b513bd03SDavid Ahern lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate, 981b513bd03SDavid Ahern NHA_ENCAP, NHA_ENCAP_TYPE) < 0) 982b513bd03SDavid Ahern goto nla_put_failure; 983b513bd03SDavid Ahern 984ab84be7eSDavid Ahern out: 985ab84be7eSDavid Ahern nlmsg_end(skb, nlh); 986ab84be7eSDavid Ahern return 0; 987ab84be7eSDavid Ahern 988ab84be7eSDavid Ahern nla_put_failure: 989d69100b8SStephen Worley nlmsg_cancel(skb, nlh); 990ab84be7eSDavid Ahern return -EMSGSIZE; 991ab84be7eSDavid Ahern } 992ab84be7eSDavid Ahern 993a2601e2bSPetr Machata static size_t nh_nlmsg_size_grp_res(struct nh_group *nhg) 994a2601e2bSPetr Machata { 995a2601e2bSPetr Machata return nla_total_size(0) + /* NHA_RES_GROUP */ 996a2601e2bSPetr Machata nla_total_size(2) + /* NHA_RES_GROUP_BUCKETS */ 997a2601e2bSPetr Machata nla_total_size(4) + /* NHA_RES_GROUP_IDLE_TIMER */ 998a2601e2bSPetr Machata nla_total_size(4) + /* NHA_RES_GROUP_UNBALANCED_TIMER */ 999a2601e2bSPetr Machata nla_total_size_64bit(8);/* NHA_RES_GROUP_UNBALANCED_TIME */ 1000a2601e2bSPetr Machata } 1001a2601e2bSPetr Machata 1002430a0491SDavid Ahern static size_t nh_nlmsg_size_grp(struct nexthop *nh) 1003430a0491SDavid Ahern { 1004430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 1005430a0491SDavid Ahern size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh; 1006a2601e2bSPetr Machata size_t tot = nla_total_size(sz) + 1007430a0491SDavid Ahern nla_total_size(2); /* NHA_GROUP_TYPE */ 1008a2601e2bSPetr Machata 1009a2601e2bSPetr Machata if (nhg->resilient) 1010a2601e2bSPetr Machata tot += nh_nlmsg_size_grp_res(nhg); 1011a2601e2bSPetr Machata 1012a2601e2bSPetr Machata return tot; 1013430a0491SDavid Ahern } 1014430a0491SDavid Ahern 1015430a0491SDavid Ahern static size_t nh_nlmsg_size_single(struct nexthop *nh) 1016ab84be7eSDavid Ahern { 1017597cfe4fSDavid Ahern struct nh_info *nhi = rtnl_dereference(nh->nh_info); 1018430a0491SDavid Ahern size_t sz; 1019ab84be7eSDavid Ahern 1020ab84be7eSDavid Ahern /* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE 1021ab84be7eSDavid Ahern * are mutually exclusive 1022ab84be7eSDavid Ahern */ 1023430a0491SDavid Ahern sz = nla_total_size(4); /* NHA_OIF */ 1024ab84be7eSDavid Ahern 1025597cfe4fSDavid Ahern switch (nhi->family) { 1026597cfe4fSDavid Ahern case AF_INET: 1027597cfe4fSDavid Ahern if (nhi->fib_nh.fib_nh_gw_family) 1028597cfe4fSDavid Ahern sz += nla_total_size(4); /* NHA_GATEWAY */ 1029597cfe4fSDavid Ahern break; 103053010f99SDavid Ahern 103153010f99SDavid Ahern case AF_INET6: 103253010f99SDavid Ahern /* NHA_GATEWAY */ 103353010f99SDavid Ahern if (nhi->fib6_nh.fib_nh_gw_family) 103453010f99SDavid Ahern sz += nla_total_size(sizeof(const struct in6_addr)); 103553010f99SDavid Ahern break; 1036597cfe4fSDavid Ahern } 1037597cfe4fSDavid Ahern 1038b513bd03SDavid Ahern if (nhi->fib_nhc.nhc_lwtstate) { 1039b513bd03SDavid Ahern sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate); 1040b513bd03SDavid Ahern sz += nla_total_size(2); /* NHA_ENCAP_TYPE */ 1041b513bd03SDavid Ahern } 1042b513bd03SDavid Ahern 1043ab84be7eSDavid Ahern return sz; 1044ab84be7eSDavid Ahern } 1045ab84be7eSDavid Ahern 1046430a0491SDavid Ahern static size_t nh_nlmsg_size(struct nexthop *nh) 1047430a0491SDavid Ahern { 1048f9e95555SStephen Worley size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg)); 1049f9e95555SStephen Worley 1050f9e95555SStephen Worley sz += nla_total_size(4); /* NHA_ID */ 1051430a0491SDavid Ahern 1052430a0491SDavid Ahern if (nh->is_group) 1053430a0491SDavid Ahern sz += nh_nlmsg_size_grp(nh); 1054430a0491SDavid Ahern else 1055430a0491SDavid Ahern sz += nh_nlmsg_size_single(nh); 1056430a0491SDavid Ahern 1057430a0491SDavid Ahern return sz; 1058430a0491SDavid Ahern } 1059430a0491SDavid Ahern 1060ab84be7eSDavid Ahern static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info) 1061ab84be7eSDavid Ahern { 1062ab84be7eSDavid Ahern unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0; 1063ab84be7eSDavid Ahern u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0; 1064ab84be7eSDavid Ahern struct sk_buff *skb; 1065ab84be7eSDavid Ahern int err = -ENOBUFS; 1066ab84be7eSDavid Ahern 1067ab84be7eSDavid Ahern skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any()); 1068ab84be7eSDavid Ahern if (!skb) 1069ab84be7eSDavid Ahern goto errout; 1070ab84be7eSDavid Ahern 107195fedd76SIdo Schimmel err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags, 0); 1072ab84be7eSDavid Ahern if (err < 0) { 1073ab84be7eSDavid Ahern /* -EMSGSIZE implies BUG in nh_nlmsg_size() */ 1074ab84be7eSDavid Ahern WARN_ON(err == -EMSGSIZE); 1075ab84be7eSDavid Ahern kfree_skb(skb); 1076ab84be7eSDavid Ahern goto errout; 1077ab84be7eSDavid Ahern } 1078ab84be7eSDavid Ahern 1079ab84be7eSDavid Ahern rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP, 1080ab84be7eSDavid Ahern info->nlh, gfp_any()); 1081ab84be7eSDavid Ahern return; 1082ab84be7eSDavid Ahern errout: 1083ab84be7eSDavid Ahern if (err < 0) 1084ab84be7eSDavid Ahern rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err); 1085ab84be7eSDavid Ahern } 1086ab84be7eSDavid Ahern 1087283a72a5SPetr Machata static unsigned long nh_res_bucket_used_time(const struct nh_res_bucket *bucket) 1088283a72a5SPetr Machata { 1089283a72a5SPetr Machata return (unsigned long)atomic_long_read(&bucket->used_time); 1090283a72a5SPetr Machata } 1091283a72a5SPetr Machata 1092283a72a5SPetr Machata static unsigned long 1093283a72a5SPetr Machata nh_res_bucket_idle_point(const struct nh_res_table *res_table, 1094283a72a5SPetr Machata const struct nh_res_bucket *bucket, 1095283a72a5SPetr Machata unsigned long now) 1096283a72a5SPetr Machata { 1097283a72a5SPetr Machata unsigned long time = nh_res_bucket_used_time(bucket); 1098283a72a5SPetr Machata 1099283a72a5SPetr Machata /* Bucket was not used since it was migrated. The idle time is now. */ 1100283a72a5SPetr Machata if (time == bucket->migrated_time) 1101283a72a5SPetr Machata return now; 1102283a72a5SPetr Machata 1103283a72a5SPetr Machata return time + res_table->idle_timer; 1104283a72a5SPetr Machata } 1105283a72a5SPetr Machata 1106283a72a5SPetr Machata static unsigned long 1107283a72a5SPetr Machata nh_res_table_unb_point(const struct nh_res_table *res_table) 1108283a72a5SPetr Machata { 1109283a72a5SPetr Machata return res_table->unbalanced_since + res_table->unbalanced_timer; 1110283a72a5SPetr Machata } 1111283a72a5SPetr Machata 1112283a72a5SPetr Machata static void nh_res_bucket_set_idle(const struct nh_res_table *res_table, 1113283a72a5SPetr Machata struct nh_res_bucket *bucket) 1114283a72a5SPetr Machata { 1115283a72a5SPetr Machata unsigned long now = jiffies; 1116283a72a5SPetr Machata 1117283a72a5SPetr Machata atomic_long_set(&bucket->used_time, (long)now); 1118283a72a5SPetr Machata bucket->migrated_time = now; 1119283a72a5SPetr Machata } 1120283a72a5SPetr Machata 1121283a72a5SPetr Machata static void nh_res_bucket_set_busy(struct nh_res_bucket *bucket) 1122283a72a5SPetr Machata { 1123283a72a5SPetr Machata atomic_long_set(&bucket->used_time, (long)jiffies); 1124283a72a5SPetr Machata } 1125283a72a5SPetr Machata 11268a1bbabbSPetr Machata static clock_t nh_res_bucket_idle_time(const struct nh_res_bucket *bucket) 11278a1bbabbSPetr Machata { 11288a1bbabbSPetr Machata unsigned long used_time = nh_res_bucket_used_time(bucket); 11298a1bbabbSPetr Machata 11308a1bbabbSPetr Machata return jiffies_delta_to_clock_t(jiffies - used_time); 11318a1bbabbSPetr Machata } 11328a1bbabbSPetr Machata 11338a1bbabbSPetr Machata static int nh_fill_res_bucket(struct sk_buff *skb, struct nexthop *nh, 11348a1bbabbSPetr Machata struct nh_res_bucket *bucket, u16 bucket_index, 11358a1bbabbSPetr Machata int event, u32 portid, u32 seq, 11368a1bbabbSPetr Machata unsigned int nlflags, 11378a1bbabbSPetr Machata struct netlink_ext_ack *extack) 11388a1bbabbSPetr Machata { 11398a1bbabbSPetr Machata struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry); 11408a1bbabbSPetr Machata struct nlmsghdr *nlh; 11418a1bbabbSPetr Machata struct nlattr *nest; 11428a1bbabbSPetr Machata struct nhmsg *nhm; 11438a1bbabbSPetr Machata 11448a1bbabbSPetr Machata nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags); 11458a1bbabbSPetr Machata if (!nlh) 11468a1bbabbSPetr Machata return -EMSGSIZE; 11478a1bbabbSPetr Machata 11488a1bbabbSPetr Machata nhm = nlmsg_data(nlh); 11498a1bbabbSPetr Machata nhm->nh_family = AF_UNSPEC; 11508a1bbabbSPetr Machata nhm->nh_flags = bucket->nh_flags; 11518a1bbabbSPetr Machata nhm->nh_protocol = nh->protocol; 11528a1bbabbSPetr Machata nhm->nh_scope = 0; 11538a1bbabbSPetr Machata nhm->resvd = 0; 11548a1bbabbSPetr Machata 11558a1bbabbSPetr Machata if (nla_put_u32(skb, NHA_ID, nh->id)) 11568a1bbabbSPetr Machata goto nla_put_failure; 11578a1bbabbSPetr Machata 11588a1bbabbSPetr Machata nest = nla_nest_start(skb, NHA_RES_BUCKET); 11598a1bbabbSPetr Machata if (!nest) 11608a1bbabbSPetr Machata goto nla_put_failure; 11618a1bbabbSPetr Machata 11628a1bbabbSPetr Machata if (nla_put_u16(skb, NHA_RES_BUCKET_INDEX, bucket_index) || 11638a1bbabbSPetr Machata nla_put_u32(skb, NHA_RES_BUCKET_NH_ID, nhge->nh->id) || 11648a1bbabbSPetr Machata nla_put_u64_64bit(skb, NHA_RES_BUCKET_IDLE_TIME, 11658a1bbabbSPetr Machata nh_res_bucket_idle_time(bucket), 11668a1bbabbSPetr Machata NHA_RES_BUCKET_PAD)) 11678a1bbabbSPetr Machata goto nla_put_failure_nest; 11688a1bbabbSPetr Machata 11698a1bbabbSPetr Machata nla_nest_end(skb, nest); 11708a1bbabbSPetr Machata nlmsg_end(skb, nlh); 11718a1bbabbSPetr Machata return 0; 11728a1bbabbSPetr Machata 11738a1bbabbSPetr Machata nla_put_failure_nest: 11748a1bbabbSPetr Machata nla_nest_cancel(skb, nest); 11758a1bbabbSPetr Machata nla_put_failure: 11768a1bbabbSPetr Machata nlmsg_cancel(skb, nlh); 11778a1bbabbSPetr Machata return -EMSGSIZE; 11788a1bbabbSPetr Machata } 11798a1bbabbSPetr Machata 11800b4818aaSPetr Machata static void nexthop_bucket_notify(struct nh_res_table *res_table, 11810b4818aaSPetr Machata u16 bucket_index) 11820b4818aaSPetr Machata { 11830b4818aaSPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index]; 11840b4818aaSPetr Machata struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry); 11850b4818aaSPetr Machata struct nexthop *nh = nhge->nh_parent; 11860b4818aaSPetr Machata struct sk_buff *skb; 11870b4818aaSPetr Machata int err = -ENOBUFS; 11880b4818aaSPetr Machata 11890b4818aaSPetr Machata skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 11900b4818aaSPetr Machata if (!skb) 11910b4818aaSPetr Machata goto errout; 11920b4818aaSPetr Machata 11930b4818aaSPetr Machata err = nh_fill_res_bucket(skb, nh, bucket, bucket_index, 11940b4818aaSPetr Machata RTM_NEWNEXTHOPBUCKET, 0, 0, NLM_F_REPLACE, 11950b4818aaSPetr Machata NULL); 11960b4818aaSPetr Machata if (err < 0) { 11970b4818aaSPetr Machata kfree_skb(skb); 11980b4818aaSPetr Machata goto errout; 11990b4818aaSPetr Machata } 12000b4818aaSPetr Machata 12010b4818aaSPetr Machata rtnl_notify(skb, nh->net, 0, RTNLGRP_NEXTHOP, NULL, GFP_KERNEL); 12020b4818aaSPetr Machata return; 12030b4818aaSPetr Machata errout: 12040b4818aaSPetr Machata if (err < 0) 12050b4818aaSPetr Machata rtnl_set_sk_err(nh->net, RTNLGRP_NEXTHOP, err); 12060b4818aaSPetr Machata } 12070b4818aaSPetr Machata 1208430a0491SDavid Ahern static bool valid_group_nh(struct nexthop *nh, unsigned int npaths, 1209ce9ac056SDavid Ahern bool *is_fdb, struct netlink_ext_ack *extack) 1210597cfe4fSDavid Ahern { 1211430a0491SDavid Ahern if (nh->is_group) { 1212430a0491SDavid Ahern struct nh_group *nhg = rtnl_dereference(nh->nh_grp); 1213430a0491SDavid Ahern 1214283a72a5SPetr Machata /* Nesting groups within groups is not supported. */ 1215de1d1ee3SPetr Machata if (nhg->hash_threshold) { 1216430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1217de1d1ee3SPetr Machata "Hash-threshold group can not be a nexthop within a group"); 1218430a0491SDavid Ahern return false; 1219430a0491SDavid Ahern } 1220283a72a5SPetr Machata if (nhg->resilient) { 1221283a72a5SPetr Machata NL_SET_ERR_MSG(extack, 1222283a72a5SPetr Machata "Resilient group can not be a nexthop within a group"); 1223283a72a5SPetr Machata return false; 1224283a72a5SPetr Machata } 1225ce9ac056SDavid Ahern *is_fdb = nhg->fdb_nh; 1226430a0491SDavid Ahern } else { 1227430a0491SDavid Ahern struct nh_info *nhi = rtnl_dereference(nh->nh_info); 1228430a0491SDavid Ahern 1229430a0491SDavid Ahern if (nhi->reject_nh && npaths > 1) { 1230430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1231430a0491SDavid Ahern "Blackhole nexthop can not be used in a group with more than 1 path"); 1232430a0491SDavid Ahern return false; 1233430a0491SDavid Ahern } 1234ce9ac056SDavid Ahern *is_fdb = nhi->fdb_nh; 1235430a0491SDavid Ahern } 1236430a0491SDavid Ahern 1237430a0491SDavid Ahern return true; 1238430a0491SDavid Ahern } 1239430a0491SDavid Ahern 124038428d68SRoopa Prabhu static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family, 124138428d68SRoopa Prabhu struct netlink_ext_ack *extack) 124238428d68SRoopa Prabhu { 124338428d68SRoopa Prabhu struct nh_info *nhi; 124438428d68SRoopa Prabhu 1245ce9ac056SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 1246ce9ac056SDavid Ahern 1247ce9ac056SDavid Ahern if (!nhi->fdb_nh) { 124838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops"); 124938428d68SRoopa Prabhu return -EINVAL; 125038428d68SRoopa Prabhu } 125138428d68SRoopa Prabhu 125238428d68SRoopa Prabhu if (*nh_family == AF_UNSPEC) { 125338428d68SRoopa Prabhu *nh_family = nhi->family; 125438428d68SRoopa Prabhu } else if (*nh_family != nhi->family) { 125538428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops"); 125638428d68SRoopa Prabhu return -EINVAL; 125738428d68SRoopa Prabhu } 125838428d68SRoopa Prabhu 125938428d68SRoopa Prabhu return 0; 126038428d68SRoopa Prabhu } 126138428d68SRoopa Prabhu 1262643d0878SPetr Machata static int nh_check_attr_group(struct net *net, 1263643d0878SPetr Machata struct nlattr *tb[], size_t tb_size, 1264a2601e2bSPetr Machata u16 nh_grp_type, struct netlink_ext_ack *extack) 1265430a0491SDavid Ahern { 1266430a0491SDavid Ahern unsigned int len = nla_len(tb[NHA_GROUP]); 126738428d68SRoopa Prabhu u8 nh_family = AF_UNSPEC; 1268430a0491SDavid Ahern struct nexthop_grp *nhg; 1269430a0491SDavid Ahern unsigned int i, j; 127038428d68SRoopa Prabhu u8 nhg_fdb = 0; 1271430a0491SDavid Ahern 1272eeaac363SNikolay Aleksandrov if (!len || len & (sizeof(struct nexthop_grp) - 1)) { 1273430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1274430a0491SDavid Ahern "Invalid length for nexthop group attribute"); 1275430a0491SDavid Ahern return -EINVAL; 1276430a0491SDavid Ahern } 1277430a0491SDavid Ahern 1278430a0491SDavid Ahern /* convert len to number of nexthop ids */ 1279430a0491SDavid Ahern len /= sizeof(*nhg); 1280430a0491SDavid Ahern 1281430a0491SDavid Ahern nhg = nla_data(tb[NHA_GROUP]); 1282430a0491SDavid Ahern for (i = 0; i < len; ++i) { 1283430a0491SDavid Ahern if (nhg[i].resvd1 || nhg[i].resvd2) { 1284430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0"); 1285430a0491SDavid Ahern return -EINVAL; 1286430a0491SDavid Ahern } 1287430a0491SDavid Ahern if (nhg[i].weight > 254) { 1288430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid value for weight"); 1289430a0491SDavid Ahern return -EINVAL; 1290430a0491SDavid Ahern } 1291430a0491SDavid Ahern for (j = i + 1; j < len; ++j) { 1292430a0491SDavid Ahern if (nhg[i].id == nhg[j].id) { 1293430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group"); 1294430a0491SDavid Ahern return -EINVAL; 1295430a0491SDavid Ahern } 1296430a0491SDavid Ahern } 1297430a0491SDavid Ahern } 1298430a0491SDavid Ahern 129938428d68SRoopa Prabhu if (tb[NHA_FDB]) 130038428d68SRoopa Prabhu nhg_fdb = 1; 1301430a0491SDavid Ahern nhg = nla_data(tb[NHA_GROUP]); 1302430a0491SDavid Ahern for (i = 0; i < len; ++i) { 1303430a0491SDavid Ahern struct nexthop *nh; 1304ce9ac056SDavid Ahern bool is_fdb_nh; 1305430a0491SDavid Ahern 1306430a0491SDavid Ahern nh = nexthop_find_by_id(net, nhg[i].id); 1307430a0491SDavid Ahern if (!nh) { 1308430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 1309430a0491SDavid Ahern return -EINVAL; 1310430a0491SDavid Ahern } 1311ce9ac056SDavid Ahern if (!valid_group_nh(nh, len, &is_fdb_nh, extack)) 1312430a0491SDavid Ahern return -EINVAL; 131338428d68SRoopa Prabhu 131438428d68SRoopa Prabhu if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack)) 131538428d68SRoopa Prabhu return -EINVAL; 131638428d68SRoopa Prabhu 1317ce9ac056SDavid Ahern if (!nhg_fdb && is_fdb_nh) { 131838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops"); 131938428d68SRoopa Prabhu return -EINVAL; 132038428d68SRoopa Prabhu } 1321430a0491SDavid Ahern } 1322643d0878SPetr Machata for (i = NHA_GROUP_TYPE + 1; i < tb_size; ++i) { 1323430a0491SDavid Ahern if (!tb[i]) 1324430a0491SDavid Ahern continue; 1325a2601e2bSPetr Machata switch (i) { 1326746c19a5SIdo Schimmel case NHA_HW_STATS_ENABLE: 1327a2601e2bSPetr Machata case NHA_FDB: 132838428d68SRoopa Prabhu continue; 1329a2601e2bSPetr Machata case NHA_RES_GROUP: 1330a2601e2bSPetr Machata if (nh_grp_type == NEXTHOP_GRP_TYPE_RES) 1331a2601e2bSPetr Machata continue; 1332a2601e2bSPetr Machata break; 1333a2601e2bSPetr Machata } 1334430a0491SDavid Ahern NL_SET_ERR_MSG(extack, 1335430a0491SDavid Ahern "No other attributes can be set in nexthop groups"); 1336430a0491SDavid Ahern return -EINVAL; 1337430a0491SDavid Ahern } 1338430a0491SDavid Ahern 1339430a0491SDavid Ahern return 0; 1340430a0491SDavid Ahern } 1341430a0491SDavid Ahern 1342430a0491SDavid Ahern static bool ipv6_good_nh(const struct fib6_nh *nh) 1343430a0491SDavid Ahern { 1344430a0491SDavid Ahern int state = NUD_REACHABLE; 1345430a0491SDavid Ahern struct neighbour *n; 1346430a0491SDavid Ahern 134709eed119SEric Dumazet rcu_read_lock(); 1348430a0491SDavid Ahern 1349430a0491SDavid Ahern n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6); 1350430a0491SDavid Ahern if (n) 1351b071af52SEric Dumazet state = READ_ONCE(n->nud_state); 1352430a0491SDavid Ahern 135309eed119SEric Dumazet rcu_read_unlock(); 1354430a0491SDavid Ahern 1355430a0491SDavid Ahern return !!(state & NUD_VALID); 1356430a0491SDavid Ahern } 1357430a0491SDavid Ahern 1358430a0491SDavid Ahern static bool ipv4_good_nh(const struct fib_nh *nh) 1359430a0491SDavid Ahern { 1360430a0491SDavid Ahern int state = NUD_REACHABLE; 1361430a0491SDavid Ahern struct neighbour *n; 1362430a0491SDavid Ahern 136309eed119SEric Dumazet rcu_read_lock(); 1364430a0491SDavid Ahern 1365430a0491SDavid Ahern n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev, 1366430a0491SDavid Ahern (__force u32)nh->fib_nh_gw4); 1367430a0491SDavid Ahern if (n) 1368b071af52SEric Dumazet state = READ_ONCE(n->nud_state); 1369430a0491SDavid Ahern 137009eed119SEric Dumazet rcu_read_unlock(); 1371430a0491SDavid Ahern 1372430a0491SDavid Ahern return !!(state & NUD_VALID); 1373430a0491SDavid Ahern } 1374430a0491SDavid Ahern 13754bb5239bSBenjamin Poirier static bool nexthop_is_good_nh(const struct nexthop *nh) 13764bb5239bSBenjamin Poirier { 13774bb5239bSBenjamin Poirier struct nh_info *nhi = rcu_dereference(nh->nh_info); 13784bb5239bSBenjamin Poirier 13794bb5239bSBenjamin Poirier switch (nhi->family) { 13804bb5239bSBenjamin Poirier case AF_INET: 13814bb5239bSBenjamin Poirier return ipv4_good_nh(&nhi->fib_nh); 13824bb5239bSBenjamin Poirier case AF_INET6: 13834bb5239bSBenjamin Poirier return ipv6_good_nh(&nhi->fib6_nh); 13844bb5239bSBenjamin Poirier } 13854bb5239bSBenjamin Poirier 13864bb5239bSBenjamin Poirier return false; 13874bb5239bSBenjamin Poirier } 13884bb5239bSBenjamin Poirier 1389eedd47a6SBenjamin Poirier static struct nexthop *nexthop_select_path_fdb(struct nh_group *nhg, int hash) 1390eedd47a6SBenjamin Poirier { 1391eedd47a6SBenjamin Poirier int i; 1392eedd47a6SBenjamin Poirier 1393eedd47a6SBenjamin Poirier for (i = 0; i < nhg->num_nh; i++) { 1394eedd47a6SBenjamin Poirier struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1395eedd47a6SBenjamin Poirier 1396eedd47a6SBenjamin Poirier if (hash > atomic_read(&nhge->hthr.upper_bound)) 1397eedd47a6SBenjamin Poirier continue; 1398eedd47a6SBenjamin Poirier 1399f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge); 1400eedd47a6SBenjamin Poirier return nhge->nh; 1401eedd47a6SBenjamin Poirier } 1402eedd47a6SBenjamin Poirier 1403eedd47a6SBenjamin Poirier WARN_ON_ONCE(1); 1404eedd47a6SBenjamin Poirier return NULL; 1405eedd47a6SBenjamin Poirier } 1406eedd47a6SBenjamin Poirier 1407de1d1ee3SPetr Machata static struct nexthop *nexthop_select_path_hthr(struct nh_group *nhg, int hash) 1408430a0491SDavid Ahern { 1409f4676ea7SIdo Schimmel struct nh_grp_entry *nhge0 = NULL; 1410430a0491SDavid Ahern int i; 1411430a0491SDavid Ahern 1412eedd47a6SBenjamin Poirier if (nhg->fdb_nh) 1413eedd47a6SBenjamin Poirier return nexthop_select_path_fdb(nhg, hash); 1414eedd47a6SBenjamin Poirier 1415430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 1416430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1417430a0491SDavid Ahern 1418430a0491SDavid Ahern /* nexthops always check if it is good and does 1419430a0491SDavid Ahern * not rely on a sysctl for this behavior 1420430a0491SDavid Ahern */ 142175f5f04cSBenjamin Poirier if (!nexthop_is_good_nh(nhge->nh)) 142275f5f04cSBenjamin Poirier continue; 1423430a0491SDavid Ahern 1424f4676ea7SIdo Schimmel if (!nhge0) 1425f4676ea7SIdo Schimmel nhge0 = nhge; 142675f5f04cSBenjamin Poirier 142775f5f04cSBenjamin Poirier if (hash > atomic_read(&nhge->hthr.upper_bound)) 142875f5f04cSBenjamin Poirier continue; 142975f5f04cSBenjamin Poirier 1430f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge); 143175f5f04cSBenjamin Poirier return nhge->nh; 1432430a0491SDavid Ahern } 1433430a0491SDavid Ahern 1434f4676ea7SIdo Schimmel if (!nhge0) 1435f4676ea7SIdo Schimmel nhge0 = &nhg->nh_entries[0]; 1436f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge0); 1437f4676ea7SIdo Schimmel return nhge0->nh; 1438430a0491SDavid Ahern } 143979bc55e3SPetr Machata 1440283a72a5SPetr Machata static struct nexthop *nexthop_select_path_res(struct nh_group *nhg, int hash) 1441283a72a5SPetr Machata { 1442283a72a5SPetr Machata struct nh_res_table *res_table = rcu_dereference(nhg->res_table); 1443283a72a5SPetr Machata u16 bucket_index = hash % res_table->num_nh_buckets; 1444283a72a5SPetr Machata struct nh_res_bucket *bucket; 1445283a72a5SPetr Machata struct nh_grp_entry *nhge; 1446283a72a5SPetr Machata 1447283a72a5SPetr Machata /* nexthop_select_path() is expected to return a non-NULL value, so 1448283a72a5SPetr Machata * skip protocol validation and just hand out whatever there is. 1449283a72a5SPetr Machata */ 1450283a72a5SPetr Machata bucket = &res_table->nh_buckets[bucket_index]; 1451283a72a5SPetr Machata nh_res_bucket_set_busy(bucket); 1452283a72a5SPetr Machata nhge = rcu_dereference(bucket->nh_entry); 1453f4676ea7SIdo Schimmel nh_grp_entry_stats_inc(nhge); 1454283a72a5SPetr Machata return nhge->nh; 1455283a72a5SPetr Machata } 1456283a72a5SPetr Machata 145779bc55e3SPetr Machata struct nexthop *nexthop_select_path(struct nexthop *nh, int hash) 145879bc55e3SPetr Machata { 145979bc55e3SPetr Machata struct nh_group *nhg; 146079bc55e3SPetr Machata 146179bc55e3SPetr Machata if (!nh->is_group) 146279bc55e3SPetr Machata return nh; 146379bc55e3SPetr Machata 146479bc55e3SPetr Machata nhg = rcu_dereference(nh->nh_grp); 1465de1d1ee3SPetr Machata if (nhg->hash_threshold) 1466de1d1ee3SPetr Machata return nexthop_select_path_hthr(nhg, hash); 1467283a72a5SPetr Machata else if (nhg->resilient) 1468283a72a5SPetr Machata return nexthop_select_path_res(nhg, hash); 146979bc55e3SPetr Machata 147079bc55e3SPetr Machata /* Unreachable. */ 147179bc55e3SPetr Machata return NULL; 147279bc55e3SPetr Machata } 1473430a0491SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_select_path); 1474430a0491SDavid Ahern 1475f88c9aa1SDavid Ahern int nexthop_for_each_fib6_nh(struct nexthop *nh, 1476f88c9aa1SDavid Ahern int (*cb)(struct fib6_nh *nh, void *arg), 1477f88c9aa1SDavid Ahern void *arg) 1478f88c9aa1SDavid Ahern { 1479f88c9aa1SDavid Ahern struct nh_info *nhi; 1480f88c9aa1SDavid Ahern int err; 1481f88c9aa1SDavid Ahern 1482f88c9aa1SDavid Ahern if (nh->is_group) { 1483f88c9aa1SDavid Ahern struct nh_group *nhg; 1484f88c9aa1SDavid Ahern int i; 1485f88c9aa1SDavid Ahern 1486f88c9aa1SDavid Ahern nhg = rcu_dereference_rtnl(nh->nh_grp); 1487f88c9aa1SDavid Ahern for (i = 0; i < nhg->num_nh; i++) { 1488f88c9aa1SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1489f88c9aa1SDavid Ahern 1490f88c9aa1SDavid Ahern nhi = rcu_dereference_rtnl(nhge->nh->nh_info); 1491f88c9aa1SDavid Ahern err = cb(&nhi->fib6_nh, arg); 1492f88c9aa1SDavid Ahern if (err) 1493f88c9aa1SDavid Ahern return err; 1494f88c9aa1SDavid Ahern } 1495f88c9aa1SDavid Ahern } else { 1496f88c9aa1SDavid Ahern nhi = rcu_dereference_rtnl(nh->nh_info); 1497f88c9aa1SDavid Ahern err = cb(&nhi->fib6_nh, arg); 1498f88c9aa1SDavid Ahern if (err) 1499f88c9aa1SDavid Ahern return err; 1500f88c9aa1SDavid Ahern } 1501f88c9aa1SDavid Ahern 1502f88c9aa1SDavid Ahern return 0; 1503f88c9aa1SDavid Ahern } 1504f88c9aa1SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh); 1505f88c9aa1SDavid Ahern 15067bf4796dSDavid Ahern static int check_src_addr(const struct in6_addr *saddr, 15077bf4796dSDavid Ahern struct netlink_ext_ack *extack) 15087bf4796dSDavid Ahern { 15097bf4796dSDavid Ahern if (!ipv6_addr_any(saddr)) { 15107bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects"); 15117bf4796dSDavid Ahern return -EINVAL; 15127bf4796dSDavid Ahern } 15137bf4796dSDavid Ahern return 0; 15147bf4796dSDavid Ahern } 15157bf4796dSDavid Ahern 1516f88d8ea6SDavid Ahern int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg, 1517f88d8ea6SDavid Ahern struct netlink_ext_ack *extack) 1518f88d8ea6SDavid Ahern { 1519f88d8ea6SDavid Ahern struct nh_info *nhi; 1520ce9ac056SDavid Ahern bool is_fdb_nh; 152138428d68SRoopa Prabhu 1522f88d8ea6SDavid Ahern /* fib6_src is unique to a fib6_info and limits the ability to cache 1523f88d8ea6SDavid Ahern * routes in fib6_nh within a nexthop that is potentially shared 1524f88d8ea6SDavid Ahern * across multiple fib entries. If the config wants to use source 1525f88d8ea6SDavid Ahern * routing it can not use nexthop objects. mlxsw also does not allow 1526f88d8ea6SDavid Ahern * fib6_src on routes. 1527f88d8ea6SDavid Ahern */ 15287bf4796dSDavid Ahern if (cfg && check_src_addr(&cfg->fc_src, extack) < 0) 1529f88d8ea6SDavid Ahern return -EINVAL; 1530f88d8ea6SDavid Ahern 1531f88d8ea6SDavid Ahern if (nh->is_group) { 1532f88d8ea6SDavid Ahern struct nh_group *nhg; 1533f88d8ea6SDavid Ahern 1534f88d8ea6SDavid Ahern nhg = rtnl_dereference(nh->nh_grp); 1535f88d8ea6SDavid Ahern if (nhg->has_v4) 1536f88d8ea6SDavid Ahern goto no_v4_nh; 1537ce9ac056SDavid Ahern is_fdb_nh = nhg->fdb_nh; 1538f88d8ea6SDavid Ahern } else { 1539f88d8ea6SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 1540f88d8ea6SDavid Ahern if (nhi->family == AF_INET) 1541f88d8ea6SDavid Ahern goto no_v4_nh; 1542ce9ac056SDavid Ahern is_fdb_nh = nhi->fdb_nh; 1543ce9ac056SDavid Ahern } 1544ce9ac056SDavid Ahern 1545ce9ac056SDavid Ahern if (is_fdb_nh) { 1546ce9ac056SDavid Ahern NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 1547ce9ac056SDavid Ahern return -EINVAL; 1548f88d8ea6SDavid Ahern } 1549f88d8ea6SDavid Ahern 1550f88d8ea6SDavid Ahern return 0; 1551f88d8ea6SDavid Ahern no_v4_nh: 1552f88d8ea6SDavid Ahern NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop"); 1553f88d8ea6SDavid Ahern return -EINVAL; 1554f88d8ea6SDavid Ahern } 1555f88d8ea6SDavid Ahern EXPORT_SYMBOL_GPL(fib6_check_nexthop); 1556f88d8ea6SDavid Ahern 15577bf4796dSDavid Ahern /* if existing nexthop has ipv6 routes linked to it, need 15587bf4796dSDavid Ahern * to verify this new spec works with ipv6 15597bf4796dSDavid Ahern */ 15607bf4796dSDavid Ahern static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new, 15617bf4796dSDavid Ahern struct netlink_ext_ack *extack) 15627bf4796dSDavid Ahern { 15637bf4796dSDavid Ahern struct fib6_info *f6i; 15647bf4796dSDavid Ahern 15657bf4796dSDavid Ahern if (list_empty(&old->f6i_list)) 15667bf4796dSDavid Ahern return 0; 15677bf4796dSDavid Ahern 15687bf4796dSDavid Ahern list_for_each_entry(f6i, &old->f6i_list, nh_list) { 15697bf4796dSDavid Ahern if (check_src_addr(&f6i->fib6_src.addr, extack) < 0) 15707bf4796dSDavid Ahern return -EINVAL; 15717bf4796dSDavid Ahern } 15727bf4796dSDavid Ahern 15737bf4796dSDavid Ahern return fib6_check_nexthop(new, NULL, extack); 15747bf4796dSDavid Ahern } 15757bf4796dSDavid Ahern 1576ce9ac056SDavid Ahern static int nexthop_check_scope(struct nh_info *nhi, u8 scope, 15774c7e8084SDavid Ahern struct netlink_ext_ack *extack) 15784c7e8084SDavid Ahern { 15794c7e8084SDavid Ahern if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) { 15804c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, 15814c7e8084SDavid Ahern "Route with host scope can not have a gateway"); 15824c7e8084SDavid Ahern return -EINVAL; 15834c7e8084SDavid Ahern } 15844c7e8084SDavid Ahern 15854c7e8084SDavid Ahern if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) { 15864c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop"); 15874c7e8084SDavid Ahern return -EINVAL; 15884c7e8084SDavid Ahern } 15894c7e8084SDavid Ahern 15904c7e8084SDavid Ahern return 0; 15914c7e8084SDavid Ahern } 15924c7e8084SDavid Ahern 15934c7e8084SDavid Ahern /* Invoked by fib add code to verify nexthop by id is ok with 15944c7e8084SDavid Ahern * config for prefix; parts of fib_check_nh not done when nexthop 15954c7e8084SDavid Ahern * object is used. 15964c7e8084SDavid Ahern */ 15974c7e8084SDavid Ahern int fib_check_nexthop(struct nexthop *nh, u8 scope, 15984c7e8084SDavid Ahern struct netlink_ext_ack *extack) 15994c7e8084SDavid Ahern { 1600ce9ac056SDavid Ahern struct nh_info *nhi; 16014c7e8084SDavid Ahern int err = 0; 16024c7e8084SDavid Ahern 1603ce9ac056SDavid Ahern if (nh->is_group) { 1604ce9ac056SDavid Ahern struct nh_group *nhg; 1605ce9ac056SDavid Ahern 1606ce9ac056SDavid Ahern nhg = rtnl_dereference(nh->nh_grp); 1607ce9ac056SDavid Ahern if (nhg->fdb_nh) { 160838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 160938428d68SRoopa Prabhu err = -EINVAL; 161038428d68SRoopa Prabhu goto out; 161138428d68SRoopa Prabhu } 161238428d68SRoopa Prabhu 16134c7e8084SDavid Ahern if (scope == RT_SCOPE_HOST) { 16144c7e8084SDavid Ahern NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops"); 16154c7e8084SDavid Ahern err = -EINVAL; 16164c7e8084SDavid Ahern goto out; 16174c7e8084SDavid Ahern } 16184c7e8084SDavid Ahern 16194c7e8084SDavid Ahern /* all nexthops in a group have the same scope */ 1620ce9ac056SDavid Ahern nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info); 1621ce9ac056SDavid Ahern err = nexthop_check_scope(nhi, scope, extack); 16224c7e8084SDavid Ahern } else { 1623ce9ac056SDavid Ahern nhi = rtnl_dereference(nh->nh_info); 1624ce9ac056SDavid Ahern if (nhi->fdb_nh) { 1625ce9ac056SDavid Ahern NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop"); 1626ce9ac056SDavid Ahern err = -EINVAL; 1627ce9ac056SDavid Ahern goto out; 16284c7e8084SDavid Ahern } 1629ce9ac056SDavid Ahern err = nexthop_check_scope(nhi, scope, extack); 1630ce9ac056SDavid Ahern } 1631ce9ac056SDavid Ahern 16324c7e8084SDavid Ahern out: 16334c7e8084SDavid Ahern return err; 16344c7e8084SDavid Ahern } 16354c7e8084SDavid Ahern 16367bf4796dSDavid Ahern static int fib_check_nh_list(struct nexthop *old, struct nexthop *new, 16377bf4796dSDavid Ahern struct netlink_ext_ack *extack) 16387bf4796dSDavid Ahern { 16397bf4796dSDavid Ahern struct fib_info *fi; 16407bf4796dSDavid Ahern 16417bf4796dSDavid Ahern list_for_each_entry(fi, &old->fi_list, nh_list) { 16427bf4796dSDavid Ahern int err; 16437bf4796dSDavid Ahern 16447bf4796dSDavid Ahern err = fib_check_nexthop(new, fi->fib_scope, extack); 16457bf4796dSDavid Ahern if (err) 16467bf4796dSDavid Ahern return err; 16477bf4796dSDavid Ahern } 16487bf4796dSDavid Ahern return 0; 16497bf4796dSDavid Ahern } 16507bf4796dSDavid Ahern 1651283a72a5SPetr Machata static bool nh_res_nhge_is_balanced(const struct nh_grp_entry *nhge) 1652283a72a5SPetr Machata { 1653283a72a5SPetr Machata return nhge->res.count_buckets == nhge->res.wants_buckets; 1654283a72a5SPetr Machata } 1655283a72a5SPetr Machata 1656283a72a5SPetr Machata static bool nh_res_nhge_is_ow(const struct nh_grp_entry *nhge) 1657283a72a5SPetr Machata { 1658283a72a5SPetr Machata return nhge->res.count_buckets > nhge->res.wants_buckets; 1659283a72a5SPetr Machata } 1660283a72a5SPetr Machata 1661283a72a5SPetr Machata static bool nh_res_nhge_is_uw(const struct nh_grp_entry *nhge) 1662283a72a5SPetr Machata { 1663283a72a5SPetr Machata return nhge->res.count_buckets < nhge->res.wants_buckets; 1664283a72a5SPetr Machata } 1665283a72a5SPetr Machata 1666283a72a5SPetr Machata static bool nh_res_table_is_balanced(const struct nh_res_table *res_table) 1667283a72a5SPetr Machata { 1668283a72a5SPetr Machata return list_empty(&res_table->uw_nh_entries); 1669283a72a5SPetr Machata } 1670283a72a5SPetr Machata 1671283a72a5SPetr Machata static void nh_res_bucket_unset_nh(struct nh_res_bucket *bucket) 1672283a72a5SPetr Machata { 1673283a72a5SPetr Machata struct nh_grp_entry *nhge; 1674283a72a5SPetr Machata 1675283a72a5SPetr Machata if (bucket->occupied) { 1676283a72a5SPetr Machata nhge = nh_res_dereference(bucket->nh_entry); 1677283a72a5SPetr Machata nhge->res.count_buckets--; 1678283a72a5SPetr Machata bucket->occupied = false; 1679283a72a5SPetr Machata } 1680283a72a5SPetr Machata } 1681283a72a5SPetr Machata 1682283a72a5SPetr Machata static void nh_res_bucket_set_nh(struct nh_res_bucket *bucket, 1683283a72a5SPetr Machata struct nh_grp_entry *nhge) 1684283a72a5SPetr Machata { 1685283a72a5SPetr Machata nh_res_bucket_unset_nh(bucket); 1686283a72a5SPetr Machata 1687283a72a5SPetr Machata bucket->occupied = true; 1688283a72a5SPetr Machata rcu_assign_pointer(bucket->nh_entry, nhge); 1689283a72a5SPetr Machata nhge->res.count_buckets++; 1690283a72a5SPetr Machata } 1691283a72a5SPetr Machata 1692283a72a5SPetr Machata static bool nh_res_bucket_should_migrate(struct nh_res_table *res_table, 1693283a72a5SPetr Machata struct nh_res_bucket *bucket, 1694283a72a5SPetr Machata unsigned long *deadline, bool *force) 1695283a72a5SPetr Machata { 1696283a72a5SPetr Machata unsigned long now = jiffies; 1697283a72a5SPetr Machata struct nh_grp_entry *nhge; 1698283a72a5SPetr Machata unsigned long idle_point; 1699283a72a5SPetr Machata 1700283a72a5SPetr Machata if (!bucket->occupied) { 1701283a72a5SPetr Machata /* The bucket is not occupied, its NHGE pointer is either 1702283a72a5SPetr Machata * NULL or obsolete. We _have to_ migrate: set force. 1703283a72a5SPetr Machata */ 1704283a72a5SPetr Machata *force = true; 1705283a72a5SPetr Machata return true; 1706283a72a5SPetr Machata } 1707283a72a5SPetr Machata 1708283a72a5SPetr Machata nhge = nh_res_dereference(bucket->nh_entry); 1709283a72a5SPetr Machata 1710283a72a5SPetr Machata /* If the bucket is populated by an underweight or balanced 1711283a72a5SPetr Machata * nexthop, do not migrate. 1712283a72a5SPetr Machata */ 1713283a72a5SPetr Machata if (!nh_res_nhge_is_ow(nhge)) 1714283a72a5SPetr Machata return false; 1715283a72a5SPetr Machata 1716283a72a5SPetr Machata /* At this point we know that the bucket is populated with an 1717283a72a5SPetr Machata * overweight nexthop. It needs to be migrated to a new nexthop if 1718283a72a5SPetr Machata * the idle timer of unbalanced timer expired. 1719283a72a5SPetr Machata */ 1720283a72a5SPetr Machata 1721283a72a5SPetr Machata idle_point = nh_res_bucket_idle_point(res_table, bucket, now); 1722283a72a5SPetr Machata if (time_after_eq(now, idle_point)) { 1723283a72a5SPetr Machata /* The bucket is idle. We _can_ migrate: unset force. */ 1724283a72a5SPetr Machata *force = false; 1725283a72a5SPetr Machata return true; 1726283a72a5SPetr Machata } 1727283a72a5SPetr Machata 1728283a72a5SPetr Machata /* Unbalanced timer of 0 means "never force". */ 1729283a72a5SPetr Machata if (res_table->unbalanced_timer) { 1730283a72a5SPetr Machata unsigned long unb_point; 1731283a72a5SPetr Machata 1732283a72a5SPetr Machata unb_point = nh_res_table_unb_point(res_table); 1733283a72a5SPetr Machata if (time_after(now, unb_point)) { 1734283a72a5SPetr Machata /* The bucket is not idle, but the unbalanced timer 1735283a72a5SPetr Machata * expired. We _can_ migrate, but set force anyway, 1736283a72a5SPetr Machata * so that drivers know to ignore activity reports 1737283a72a5SPetr Machata * from the HW. 1738283a72a5SPetr Machata */ 1739283a72a5SPetr Machata *force = true; 1740283a72a5SPetr Machata return true; 1741283a72a5SPetr Machata } 1742283a72a5SPetr Machata 1743283a72a5SPetr Machata nh_res_time_set_deadline(unb_point, deadline); 1744283a72a5SPetr Machata } 1745283a72a5SPetr Machata 1746283a72a5SPetr Machata nh_res_time_set_deadline(idle_point, deadline); 1747283a72a5SPetr Machata return false; 1748283a72a5SPetr Machata } 1749283a72a5SPetr Machata 1750283a72a5SPetr Machata static bool nh_res_bucket_migrate(struct nh_res_table *res_table, 17510b4818aaSPetr Machata u16 bucket_index, bool notify, 17520b4818aaSPetr Machata bool notify_nl, bool force) 1753283a72a5SPetr Machata { 1754283a72a5SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index]; 1755283a72a5SPetr Machata struct nh_grp_entry *new_nhge; 17567c37c7e0SPetr Machata struct netlink_ext_ack extack; 17577c37c7e0SPetr Machata int err; 1758283a72a5SPetr Machata 1759283a72a5SPetr Machata new_nhge = list_first_entry_or_null(&res_table->uw_nh_entries, 1760283a72a5SPetr Machata struct nh_grp_entry, 1761283a72a5SPetr Machata res.uw_nh_entry); 1762283a72a5SPetr Machata if (WARN_ON_ONCE(!new_nhge)) 1763283a72a5SPetr Machata /* If this function is called, "bucket" is either not 1764283a72a5SPetr Machata * occupied, or it belongs to a next hop that is 1765283a72a5SPetr Machata * overweight. In either case, there ought to be a 1766283a72a5SPetr Machata * corresponding underweight next hop. 1767283a72a5SPetr Machata */ 1768283a72a5SPetr Machata return false; 1769283a72a5SPetr Machata 17707c37c7e0SPetr Machata if (notify) { 17717c37c7e0SPetr Machata struct nh_grp_entry *old_nhge; 17727c37c7e0SPetr Machata 17737c37c7e0SPetr Machata old_nhge = nh_res_dereference(bucket->nh_entry); 17747c37c7e0SPetr Machata err = call_nexthop_res_bucket_notifiers(res_table->net, 17757c37c7e0SPetr Machata res_table->nhg_id, 17767c37c7e0SPetr Machata bucket_index, force, 17777c37c7e0SPetr Machata old_nhge->nh, 17787c37c7e0SPetr Machata new_nhge->nh, &extack); 17797c37c7e0SPetr Machata if (err) { 17807c37c7e0SPetr Machata pr_err_ratelimited("%s\n", extack._msg); 17817c37c7e0SPetr Machata if (!force) 17827c37c7e0SPetr Machata return false; 17837c37c7e0SPetr Machata /* It is not possible to veto a forced replacement, so 17847c37c7e0SPetr Machata * just clear the hardware flags from the nexthop 17857c37c7e0SPetr Machata * bucket to indicate to user space that this bucket is 17867c37c7e0SPetr Machata * not correctly populated in hardware. 17877c37c7e0SPetr Machata */ 17887c37c7e0SPetr Machata bucket->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP); 17897c37c7e0SPetr Machata } 17907c37c7e0SPetr Machata } 17917c37c7e0SPetr Machata 1792283a72a5SPetr Machata nh_res_bucket_set_nh(bucket, new_nhge); 1793283a72a5SPetr Machata nh_res_bucket_set_idle(res_table, bucket); 1794283a72a5SPetr Machata 17950b4818aaSPetr Machata if (notify_nl) 17960b4818aaSPetr Machata nexthop_bucket_notify(res_table, bucket_index); 17970b4818aaSPetr Machata 1798283a72a5SPetr Machata if (nh_res_nhge_is_balanced(new_nhge)) 1799283a72a5SPetr Machata list_del(&new_nhge->res.uw_nh_entry); 1800283a72a5SPetr Machata return true; 1801283a72a5SPetr Machata } 1802283a72a5SPetr Machata 1803283a72a5SPetr Machata #define NH_RES_UPKEEP_DW_MINIMUM_INTERVAL (HZ / 2) 1804283a72a5SPetr Machata 18050b4818aaSPetr Machata static void nh_res_table_upkeep(struct nh_res_table *res_table, 18060b4818aaSPetr Machata bool notify, bool notify_nl) 1807283a72a5SPetr Machata { 1808283a72a5SPetr Machata unsigned long now = jiffies; 1809283a72a5SPetr Machata unsigned long deadline; 1810283a72a5SPetr Machata u16 i; 1811283a72a5SPetr Machata 1812283a72a5SPetr Machata /* Deadline is the next time that upkeep should be run. It is the 1813283a72a5SPetr Machata * earliest time at which one of the buckets might be migrated. 1814283a72a5SPetr Machata * Start at the most pessimistic estimate: either unbalanced_timer 1815283a72a5SPetr Machata * from now, or if there is none, idle_timer from now. For each 1816283a72a5SPetr Machata * encountered time point, call nh_res_time_set_deadline() to 1817283a72a5SPetr Machata * refine the estimate. 1818283a72a5SPetr Machata */ 1819283a72a5SPetr Machata if (res_table->unbalanced_timer) 1820283a72a5SPetr Machata deadline = now + res_table->unbalanced_timer; 1821283a72a5SPetr Machata else 1822283a72a5SPetr Machata deadline = now + res_table->idle_timer; 1823283a72a5SPetr Machata 1824283a72a5SPetr Machata for (i = 0; i < res_table->num_nh_buckets; i++) { 1825283a72a5SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 1826283a72a5SPetr Machata bool force; 1827283a72a5SPetr Machata 1828283a72a5SPetr Machata if (nh_res_bucket_should_migrate(res_table, bucket, 1829283a72a5SPetr Machata &deadline, &force)) { 18307c37c7e0SPetr Machata if (!nh_res_bucket_migrate(res_table, i, notify, 18310b4818aaSPetr Machata notify_nl, force)) { 1832283a72a5SPetr Machata unsigned long idle_point; 1833283a72a5SPetr Machata 1834283a72a5SPetr Machata /* A driver can override the migration 1835283a72a5SPetr Machata * decision if the HW reports that the 1836283a72a5SPetr Machata * bucket is actually not idle. Therefore 1837283a72a5SPetr Machata * remark the bucket as busy again and 1838283a72a5SPetr Machata * update the deadline. 1839283a72a5SPetr Machata */ 1840283a72a5SPetr Machata nh_res_bucket_set_busy(bucket); 1841283a72a5SPetr Machata idle_point = nh_res_bucket_idle_point(res_table, 1842283a72a5SPetr Machata bucket, 1843283a72a5SPetr Machata now); 1844283a72a5SPetr Machata nh_res_time_set_deadline(idle_point, &deadline); 1845283a72a5SPetr Machata } 1846283a72a5SPetr Machata } 1847283a72a5SPetr Machata } 1848283a72a5SPetr Machata 1849283a72a5SPetr Machata /* If the group is still unbalanced, schedule the next upkeep to 1850283a72a5SPetr Machata * either the deadline computed above, or the minimum deadline, 1851283a72a5SPetr Machata * whichever comes later. 1852283a72a5SPetr Machata */ 1853283a72a5SPetr Machata if (!nh_res_table_is_balanced(res_table)) { 1854283a72a5SPetr Machata unsigned long now = jiffies; 1855283a72a5SPetr Machata unsigned long min_deadline; 1856283a72a5SPetr Machata 1857283a72a5SPetr Machata min_deadline = now + NH_RES_UPKEEP_DW_MINIMUM_INTERVAL; 1858283a72a5SPetr Machata if (time_before(deadline, min_deadline)) 1859283a72a5SPetr Machata deadline = min_deadline; 1860283a72a5SPetr Machata 1861283a72a5SPetr Machata queue_delayed_work(system_power_efficient_wq, 1862283a72a5SPetr Machata &res_table->upkeep_dw, deadline - now); 1863283a72a5SPetr Machata } 1864283a72a5SPetr Machata } 1865283a72a5SPetr Machata 1866283a72a5SPetr Machata static void nh_res_table_upkeep_dw(struct work_struct *work) 1867283a72a5SPetr Machata { 1868283a72a5SPetr Machata struct delayed_work *dw = to_delayed_work(work); 1869283a72a5SPetr Machata struct nh_res_table *res_table; 1870283a72a5SPetr Machata 1871283a72a5SPetr Machata res_table = container_of(dw, struct nh_res_table, upkeep_dw); 18720b4818aaSPetr Machata nh_res_table_upkeep(res_table, true, true); 1873283a72a5SPetr Machata } 1874283a72a5SPetr Machata 1875283a72a5SPetr Machata static void nh_res_table_cancel_upkeep(struct nh_res_table *res_table) 1876283a72a5SPetr Machata { 1877283a72a5SPetr Machata cancel_delayed_work_sync(&res_table->upkeep_dw); 1878283a72a5SPetr Machata } 1879283a72a5SPetr Machata 1880283a72a5SPetr Machata static void nh_res_group_rebalance(struct nh_group *nhg, 1881283a72a5SPetr Machata struct nh_res_table *res_table) 1882283a72a5SPetr Machata { 1883283a72a5SPetr Machata int prev_upper_bound = 0; 1884283a72a5SPetr Machata int total = 0; 1885283a72a5SPetr Machata int w = 0; 1886283a72a5SPetr Machata int i; 1887283a72a5SPetr Machata 1888283a72a5SPetr Machata INIT_LIST_HEAD(&res_table->uw_nh_entries); 1889283a72a5SPetr Machata 1890283a72a5SPetr Machata for (i = 0; i < nhg->num_nh; ++i) 1891283a72a5SPetr Machata total += nhg->nh_entries[i].weight; 1892283a72a5SPetr Machata 1893283a72a5SPetr Machata for (i = 0; i < nhg->num_nh; ++i) { 1894283a72a5SPetr Machata struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1895283a72a5SPetr Machata int upper_bound; 1896283a72a5SPetr Machata 1897283a72a5SPetr Machata w += nhge->weight; 1898283a72a5SPetr Machata upper_bound = DIV_ROUND_CLOSEST(res_table->num_nh_buckets * w, 1899283a72a5SPetr Machata total); 1900283a72a5SPetr Machata nhge->res.wants_buckets = upper_bound - prev_upper_bound; 1901283a72a5SPetr Machata prev_upper_bound = upper_bound; 1902283a72a5SPetr Machata 1903283a72a5SPetr Machata if (nh_res_nhge_is_uw(nhge)) { 1904283a72a5SPetr Machata if (list_empty(&res_table->uw_nh_entries)) 1905283a72a5SPetr Machata res_table->unbalanced_since = jiffies; 1906283a72a5SPetr Machata list_add(&nhge->res.uw_nh_entry, 1907283a72a5SPetr Machata &res_table->uw_nh_entries); 1908283a72a5SPetr Machata } 1909283a72a5SPetr Machata } 1910283a72a5SPetr Machata } 1911283a72a5SPetr Machata 1912283a72a5SPetr Machata /* Migrate buckets in res_table so that they reference NHGE's from NHG with 1913283a72a5SPetr Machata * the right NH ID. Set those buckets that do not have a corresponding NHGE 1914283a72a5SPetr Machata * entry in NHG as not occupied. 1915283a72a5SPetr Machata */ 1916283a72a5SPetr Machata static void nh_res_table_migrate_buckets(struct nh_res_table *res_table, 1917283a72a5SPetr Machata struct nh_group *nhg) 1918283a72a5SPetr Machata { 1919283a72a5SPetr Machata u16 i; 1920283a72a5SPetr Machata 1921283a72a5SPetr Machata for (i = 0; i < res_table->num_nh_buckets; i++) { 1922283a72a5SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 1923283a72a5SPetr Machata u32 id = rtnl_dereference(bucket->nh_entry)->nh->id; 1924283a72a5SPetr Machata bool found = false; 1925283a72a5SPetr Machata int j; 1926283a72a5SPetr Machata 1927283a72a5SPetr Machata for (j = 0; j < nhg->num_nh; j++) { 1928283a72a5SPetr Machata struct nh_grp_entry *nhge = &nhg->nh_entries[j]; 1929283a72a5SPetr Machata 1930283a72a5SPetr Machata if (nhge->nh->id == id) { 1931283a72a5SPetr Machata nh_res_bucket_set_nh(bucket, nhge); 1932283a72a5SPetr Machata found = true; 1933283a72a5SPetr Machata break; 1934283a72a5SPetr Machata } 1935283a72a5SPetr Machata } 1936283a72a5SPetr Machata 1937283a72a5SPetr Machata if (!found) 1938283a72a5SPetr Machata nh_res_bucket_unset_nh(bucket); 1939283a72a5SPetr Machata } 1940283a72a5SPetr Machata } 1941283a72a5SPetr Machata 1942283a72a5SPetr Machata static void replace_nexthop_grp_res(struct nh_group *oldg, 1943283a72a5SPetr Machata struct nh_group *newg) 1944283a72a5SPetr Machata { 1945283a72a5SPetr Machata /* For NH group replacement, the new NHG might only have a stub 1946283a72a5SPetr Machata * hash table with 0 buckets, because the number of buckets was not 1947283a72a5SPetr Machata * specified. For NH removal, oldg and newg both reference the same 1948283a72a5SPetr Machata * res_table. So in any case, in the following, we want to work 1949283a72a5SPetr Machata * with oldg->res_table. 1950283a72a5SPetr Machata */ 1951283a72a5SPetr Machata struct nh_res_table *old_res_table = rtnl_dereference(oldg->res_table); 1952283a72a5SPetr Machata unsigned long prev_unbalanced_since = old_res_table->unbalanced_since; 1953283a72a5SPetr Machata bool prev_has_uw = !list_empty(&old_res_table->uw_nh_entries); 1954283a72a5SPetr Machata 1955283a72a5SPetr Machata nh_res_table_cancel_upkeep(old_res_table); 1956283a72a5SPetr Machata nh_res_table_migrate_buckets(old_res_table, newg); 1957283a72a5SPetr Machata nh_res_group_rebalance(newg, old_res_table); 1958283a72a5SPetr Machata if (prev_has_uw && !list_empty(&old_res_table->uw_nh_entries)) 1959283a72a5SPetr Machata old_res_table->unbalanced_since = prev_unbalanced_since; 19600b4818aaSPetr Machata nh_res_table_upkeep(old_res_table, true, false); 1961283a72a5SPetr Machata } 1962283a72a5SPetr Machata 1963de1d1ee3SPetr Machata static void nh_hthr_group_rebalance(struct nh_group *nhg) 1964430a0491SDavid Ahern { 1965430a0491SDavid Ahern int total = 0; 1966430a0491SDavid Ahern int w = 0; 1967430a0491SDavid Ahern int i; 1968430a0491SDavid Ahern 1969430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) 1970430a0491SDavid Ahern total += nhg->nh_entries[i].weight; 1971430a0491SDavid Ahern 1972430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 1973430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 1974430a0491SDavid Ahern int upper_bound; 1975430a0491SDavid Ahern 1976430a0491SDavid Ahern w += nhge->weight; 1977430a0491SDavid Ahern upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1; 1978de1d1ee3SPetr Machata atomic_set(&nhge->hthr.upper_bound, upper_bound); 1979430a0491SDavid Ahern } 1980430a0491SDavid Ahern } 1981430a0491SDavid Ahern 1982ac21753aSDavid Ahern static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge, 1983430a0491SDavid Ahern struct nl_info *nlinfo) 1984430a0491SDavid Ahern { 198590f33bffSNikolay Aleksandrov struct nh_grp_entry *nhges, *new_nhges; 1986ac21753aSDavid Ahern struct nexthop *nhp = nhge->nh_parent; 1987833a1065SIdo Schimmel struct netlink_ext_ack extack; 1988430a0491SDavid Ahern struct nexthop *nh = nhge->nh; 198990f33bffSNikolay Aleksandrov struct nh_group *nhg, *newg; 1990833a1065SIdo Schimmel int i, j, err; 1991430a0491SDavid Ahern 1992430a0491SDavid Ahern WARN_ON(!nh); 1993430a0491SDavid Ahern 1994ac21753aSDavid Ahern nhg = rtnl_dereference(nhp->nh_grp); 199590f33bffSNikolay Aleksandrov newg = nhg->spare; 1996430a0491SDavid Ahern 199790f33bffSNikolay Aleksandrov /* last entry, keep it visible and remove the parent */ 199890f33bffSNikolay Aleksandrov if (nhg->num_nh == 1) { 199990f33bffSNikolay Aleksandrov remove_nexthop(net, nhp, nlinfo); 2000430a0491SDavid Ahern return; 200190f33bffSNikolay Aleksandrov } 2002430a0491SDavid Ahern 2003863b2558SIdo Schimmel newg->has_v4 = false; 200490e1a9e2SPetr Machata newg->is_multipath = nhg->is_multipath; 2005de1d1ee3SPetr Machata newg->hash_threshold = nhg->hash_threshold; 2006283a72a5SPetr Machata newg->resilient = nhg->resilient; 2007ce9ac056SDavid Ahern newg->fdb_nh = nhg->fdb_nh; 200890f33bffSNikolay Aleksandrov newg->num_nh = nhg->num_nh; 2009430a0491SDavid Ahern 201090f33bffSNikolay Aleksandrov /* copy old entries to new except the one getting removed */ 201190f33bffSNikolay Aleksandrov nhges = nhg->nh_entries; 201290f33bffSNikolay Aleksandrov new_nhges = newg->nh_entries; 201390f33bffSNikolay Aleksandrov for (i = 0, j = 0; i < nhg->num_nh; ++i) { 2014863b2558SIdo Schimmel struct nh_info *nhi; 2015863b2558SIdo Schimmel 201690f33bffSNikolay Aleksandrov /* current nexthop getting removed */ 201790f33bffSNikolay Aleksandrov if (nhg->nh_entries[i].nh == nh) { 201890f33bffSNikolay Aleksandrov newg->num_nh--; 201990f33bffSNikolay Aleksandrov continue; 202090f33bffSNikolay Aleksandrov } 2021430a0491SDavid Ahern 2022863b2558SIdo Schimmel nhi = rtnl_dereference(nhges[i].nh->nh_info); 2023863b2558SIdo Schimmel if (nhi->family == AF_INET) 2024863b2558SIdo Schimmel newg->has_v4 = true; 2025863b2558SIdo Schimmel 202690f33bffSNikolay Aleksandrov list_del(&nhges[i].nh_list); 2027f4676ea7SIdo Schimmel new_nhges[j].stats = nhges[i].stats; 202890f33bffSNikolay Aleksandrov new_nhges[j].nh_parent = nhges[i].nh_parent; 202990f33bffSNikolay Aleksandrov new_nhges[j].nh = nhges[i].nh; 203090f33bffSNikolay Aleksandrov new_nhges[j].weight = nhges[i].weight; 203190f33bffSNikolay Aleksandrov list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list); 203290f33bffSNikolay Aleksandrov j++; 203390f33bffSNikolay Aleksandrov } 203490f33bffSNikolay Aleksandrov 2035de1d1ee3SPetr Machata if (newg->hash_threshold) 2036de1d1ee3SPetr Machata nh_hthr_group_rebalance(newg); 2037283a72a5SPetr Machata else if (newg->resilient) 2038283a72a5SPetr Machata replace_nexthop_grp_res(nhg, newg); 2039283a72a5SPetr Machata 204090f33bffSNikolay Aleksandrov rcu_assign_pointer(nhp->nh_grp, newg); 204190f33bffSNikolay Aleksandrov 204290f33bffSNikolay Aleksandrov list_del(&nhge->nh_list); 2043f4676ea7SIdo Schimmel free_percpu(nhge->stats); 204490f33bffSNikolay Aleksandrov nexthop_put(nhge->nh); 2045430a0491SDavid Ahern 20467c37c7e0SPetr Machata /* Removal of a NH from a resilient group is notified through 20477c37c7e0SPetr Machata * bucket notifications. 20487c37c7e0SPetr Machata */ 2049de1d1ee3SPetr Machata if (newg->hash_threshold) { 20507c37c7e0SPetr Machata err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp, 20517c37c7e0SPetr Machata &extack); 2052833a1065SIdo Schimmel if (err) 2053833a1065SIdo Schimmel pr_err("%s\n", extack._msg); 20547c37c7e0SPetr Machata } 2055833a1065SIdo Schimmel 2056430a0491SDavid Ahern if (nlinfo) 2057ac21753aSDavid Ahern nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo); 2058430a0491SDavid Ahern } 2059430a0491SDavid Ahern 2060430a0491SDavid Ahern static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh, 2061430a0491SDavid Ahern struct nl_info *nlinfo) 2062430a0491SDavid Ahern { 2063430a0491SDavid Ahern struct nh_grp_entry *nhge, *tmp; 2064430a0491SDavid Ahern 2065ac21753aSDavid Ahern list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list) 2066ac21753aSDavid Ahern remove_nh_grp_entry(net, nhge, nlinfo); 2067430a0491SDavid Ahern 206890f33bffSNikolay Aleksandrov /* make sure all see the newly published array before releasing rtnl */ 2069df6afe2fSIdo Schimmel synchronize_net(); 2070430a0491SDavid Ahern } 2071430a0491SDavid Ahern 2072430a0491SDavid Ahern static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo) 2073430a0491SDavid Ahern { 2074430a0491SDavid Ahern struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp); 2075283a72a5SPetr Machata struct nh_res_table *res_table; 2076430a0491SDavid Ahern int i, num_nh = nhg->num_nh; 2077430a0491SDavid Ahern 2078430a0491SDavid Ahern for (i = 0; i < num_nh; ++i) { 2079430a0491SDavid Ahern struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 2080430a0491SDavid Ahern 2081430a0491SDavid Ahern if (WARN_ON(!nhge->nh)) 2082430a0491SDavid Ahern continue; 2083430a0491SDavid Ahern 208490f33bffSNikolay Aleksandrov list_del_init(&nhge->nh_list); 2085430a0491SDavid Ahern } 2086283a72a5SPetr Machata 2087283a72a5SPetr Machata if (nhg->resilient) { 2088283a72a5SPetr Machata res_table = rtnl_dereference(nhg->res_table); 2089283a72a5SPetr Machata nh_res_table_cancel_upkeep(res_table); 2090283a72a5SPetr Machata } 2091430a0491SDavid Ahern } 2092430a0491SDavid Ahern 20937bf4796dSDavid Ahern /* not called for nexthop replace */ 20944c7e8084SDavid Ahern static void __remove_nexthop_fib(struct net *net, struct nexthop *nh) 20954c7e8084SDavid Ahern { 2096f88d8ea6SDavid Ahern struct fib6_info *f6i, *tmp; 20974c7e8084SDavid Ahern bool do_flush = false; 20984c7e8084SDavid Ahern struct fib_info *fi; 20994c7e8084SDavid Ahern 21004c7e8084SDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) { 21014c7e8084SDavid Ahern fi->fib_flags |= RTNH_F_DEAD; 21024c7e8084SDavid Ahern do_flush = true; 21034c7e8084SDavid Ahern } 21044c7e8084SDavid Ahern if (do_flush) 21054c7e8084SDavid Ahern fib_flush(net); 2106f88d8ea6SDavid Ahern 2107f88d8ea6SDavid Ahern /* ip6_del_rt removes the entry from this list hence the _safe */ 2108f88d8ea6SDavid Ahern list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) { 2109f88d8ea6SDavid Ahern /* __ip6_del_rt does a release, so do a hold here */ 2110f88d8ea6SDavid Ahern fib6_info_hold(f6i); 21114f80116dSRoopa Prabhu ipv6_stub->ip6_del_rt(net, f6i, 2112bdf00bf2SKuniyuki Iwashima !READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode)); 2113f88d8ea6SDavid Ahern } 21144c7e8084SDavid Ahern } 21154c7e8084SDavid Ahern 2116430a0491SDavid Ahern static void __remove_nexthop(struct net *net, struct nexthop *nh, 2117430a0491SDavid Ahern struct nl_info *nlinfo) 2118430a0491SDavid Ahern { 21194c7e8084SDavid Ahern __remove_nexthop_fib(net, nh); 21204c7e8084SDavid Ahern 2121430a0491SDavid Ahern if (nh->is_group) { 2122430a0491SDavid Ahern remove_nexthop_group(nh, nlinfo); 2123430a0491SDavid Ahern } else { 2124597cfe4fSDavid Ahern struct nh_info *nhi; 2125597cfe4fSDavid Ahern 2126597cfe4fSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 2127597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev) 2128597cfe4fSDavid Ahern hlist_del(&nhi->dev_hash); 2129430a0491SDavid Ahern 2130430a0491SDavid Ahern remove_nexthop_from_groups(net, nh, nlinfo); 2131430a0491SDavid Ahern } 2132597cfe4fSDavid Ahern } 2133597cfe4fSDavid Ahern 2134ab84be7eSDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh, 2135430a0491SDavid Ahern struct nl_info *nlinfo) 2136ab84be7eSDavid Ahern { 21373578d53dSIdo Schimmel call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh, NULL); 21380695564bSIdo Schimmel 2139ab84be7eSDavid Ahern /* remove from the tree */ 2140ab84be7eSDavid Ahern rb_erase(&nh->rb_node, &net->nexthop.rb_root); 2141ab84be7eSDavid Ahern 2142ab84be7eSDavid Ahern if (nlinfo) 2143ab84be7eSDavid Ahern nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo); 2144ab84be7eSDavid Ahern 2145430a0491SDavid Ahern __remove_nexthop(net, nh, nlinfo); 2146ab84be7eSDavid Ahern nh_base_seq_inc(net); 2147ab84be7eSDavid Ahern 2148ab84be7eSDavid Ahern nexthop_put(nh); 2149ab84be7eSDavid Ahern } 2150ab84be7eSDavid Ahern 21517bf4796dSDavid Ahern /* if any FIB entries reference this nexthop, any dst entries 21527bf4796dSDavid Ahern * need to be regenerated 21537bf4796dSDavid Ahern */ 21541005f19bSNikolay Aleksandrov static void nh_rt_cache_flush(struct net *net, struct nexthop *nh, 21551005f19bSNikolay Aleksandrov struct nexthop *replaced_nh) 21567bf4796dSDavid Ahern { 21577bf4796dSDavid Ahern struct fib6_info *f6i; 21581005f19bSNikolay Aleksandrov struct nh_group *nhg; 21591005f19bSNikolay Aleksandrov int i; 21607bf4796dSDavid Ahern 21617bf4796dSDavid Ahern if (!list_empty(&nh->fi_list)) 21627bf4796dSDavid Ahern rt_cache_flush(net); 21637bf4796dSDavid Ahern 21647bf4796dSDavid Ahern list_for_each_entry(f6i, &nh->f6i_list, nh_list) 21657bf4796dSDavid Ahern ipv6_stub->fib6_update_sernum(net, f6i); 21661005f19bSNikolay Aleksandrov 21671005f19bSNikolay Aleksandrov /* if an IPv6 group was replaced, we have to release all old 21681005f19bSNikolay Aleksandrov * dsts to make sure all refcounts are released 21691005f19bSNikolay Aleksandrov */ 21701005f19bSNikolay Aleksandrov if (!replaced_nh->is_group) 21711005f19bSNikolay Aleksandrov return; 21721005f19bSNikolay Aleksandrov 21731005f19bSNikolay Aleksandrov nhg = rtnl_dereference(replaced_nh->nh_grp); 21741005f19bSNikolay Aleksandrov for (i = 0; i < nhg->num_nh; i++) { 21751005f19bSNikolay Aleksandrov struct nh_grp_entry *nhge = &nhg->nh_entries[i]; 21761005f19bSNikolay Aleksandrov struct nh_info *nhi = rtnl_dereference(nhge->nh->nh_info); 21771005f19bSNikolay Aleksandrov 21781005f19bSNikolay Aleksandrov if (nhi->family == AF_INET6) 21791005f19bSNikolay Aleksandrov ipv6_stub->fib6_nh_release_dsts(&nhi->fib6_nh); 21801005f19bSNikolay Aleksandrov } 21817bf4796dSDavid Ahern } 21827bf4796dSDavid Ahern 21837bf4796dSDavid Ahern static int replace_nexthop_grp(struct net *net, struct nexthop *old, 2184597f48e4SPetr Machata struct nexthop *new, const struct nh_config *cfg, 21857bf4796dSDavid Ahern struct netlink_ext_ack *extack) 21867bf4796dSDavid Ahern { 2187283a72a5SPetr Machata struct nh_res_table *tmp_table = NULL; 2188283a72a5SPetr Machata struct nh_res_table *new_res_table; 2189283a72a5SPetr Machata struct nh_res_table *old_res_table; 21907bf4796dSDavid Ahern struct nh_group *oldg, *newg; 2191d144cc5fSIdo Schimmel int i, err; 21927bf4796dSDavid Ahern 21937bf4796dSDavid Ahern if (!new->is_group) { 21947bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop."); 21957bf4796dSDavid Ahern return -EINVAL; 21967bf4796dSDavid Ahern } 21977bf4796dSDavid Ahern 21987bf4796dSDavid Ahern oldg = rtnl_dereference(old->nh_grp); 21997bf4796dSDavid Ahern newg = rtnl_dereference(new->nh_grp); 22007bf4796dSDavid Ahern 2201de1d1ee3SPetr Machata if (newg->hash_threshold != oldg->hash_threshold) { 2202283a72a5SPetr Machata NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with one of a different type."); 2203283a72a5SPetr Machata return -EINVAL; 2204283a72a5SPetr Machata } 2205283a72a5SPetr Machata 2206de1d1ee3SPetr Machata if (newg->hash_threshold) { 2207283a72a5SPetr Machata err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, 2208283a72a5SPetr Machata extack); 2209283a72a5SPetr Machata if (err) 2210283a72a5SPetr Machata return err; 2211283a72a5SPetr Machata } else if (newg->resilient) { 2212283a72a5SPetr Machata new_res_table = rtnl_dereference(newg->res_table); 2213283a72a5SPetr Machata old_res_table = rtnl_dereference(oldg->res_table); 2214283a72a5SPetr Machata 2215283a72a5SPetr Machata /* Accept if num_nh_buckets was not given, but if it was 2216283a72a5SPetr Machata * given, demand that the value be correct. 2217283a72a5SPetr Machata */ 2218283a72a5SPetr Machata if (cfg->nh_grp_res_has_num_buckets && 2219283a72a5SPetr Machata cfg->nh_grp_res_num_buckets != 2220283a72a5SPetr Machata old_res_table->num_nh_buckets) { 2221283a72a5SPetr Machata NL_SET_ERR_MSG(extack, "Can not change number of buckets of a resilient nexthop group."); 2222283a72a5SPetr Machata return -EINVAL; 2223283a72a5SPetr Machata } 2224283a72a5SPetr Machata 22257c37c7e0SPetr Machata /* Emit a pre-replace notification so that listeners could veto 22267c37c7e0SPetr Machata * a potentially unsupported configuration. Otherwise, 22277c37c7e0SPetr Machata * individual bucket replacement notifications would need to be 22287c37c7e0SPetr Machata * vetoed, which is something that should only happen if the 22297c37c7e0SPetr Machata * bucket is currently active. 22307c37c7e0SPetr Machata */ 22317c37c7e0SPetr Machata err = call_nexthop_res_table_notifiers(net, new, extack); 22327c37c7e0SPetr Machata if (err) 22337c37c7e0SPetr Machata return err; 22347c37c7e0SPetr Machata 2235283a72a5SPetr Machata if (cfg->nh_grp_res_has_idle_timer) 2236283a72a5SPetr Machata old_res_table->idle_timer = cfg->nh_grp_res_idle_timer; 2237283a72a5SPetr Machata if (cfg->nh_grp_res_has_unbalanced_timer) 2238283a72a5SPetr Machata old_res_table->unbalanced_timer = 2239283a72a5SPetr Machata cfg->nh_grp_res_unbalanced_timer; 2240283a72a5SPetr Machata 2241283a72a5SPetr Machata replace_nexthop_grp_res(oldg, newg); 2242283a72a5SPetr Machata 2243283a72a5SPetr Machata tmp_table = new_res_table; 2244283a72a5SPetr Machata rcu_assign_pointer(newg->res_table, old_res_table); 2245283a72a5SPetr Machata rcu_assign_pointer(newg->spare->res_table, old_res_table); 2246283a72a5SPetr Machata } 2247283a72a5SPetr Machata 22487bf4796dSDavid Ahern /* update parents - used by nexthop code for cleanup */ 22497bf4796dSDavid Ahern for (i = 0; i < newg->num_nh; i++) 22507bf4796dSDavid Ahern newg->nh_entries[i].nh_parent = old; 22517bf4796dSDavid Ahern 22527bf4796dSDavid Ahern rcu_assign_pointer(old->nh_grp, newg); 22537bf4796dSDavid Ahern 2254563f23b0SIdo Schimmel /* Make sure concurrent readers are not using 'oldg' anymore. */ 2255563f23b0SIdo Schimmel synchronize_net(); 22567709efa6SNikolay Aleksandrov 22577709efa6SNikolay Aleksandrov if (newg->resilient) { 2258283a72a5SPetr Machata rcu_assign_pointer(oldg->res_table, tmp_table); 2259283a72a5SPetr Machata rcu_assign_pointer(oldg->spare->res_table, tmp_table); 2260283a72a5SPetr Machata } 2261283a72a5SPetr Machata 22627bf4796dSDavid Ahern for (i = 0; i < oldg->num_nh; i++) 22637bf4796dSDavid Ahern oldg->nh_entries[i].nh_parent = new; 22647bf4796dSDavid Ahern 22657bf4796dSDavid Ahern rcu_assign_pointer(new->nh_grp, oldg); 22667bf4796dSDavid Ahern 22677bf4796dSDavid Ahern return 0; 22687bf4796dSDavid Ahern } 22697bf4796dSDavid Ahern 2270885a3b15SIdo Schimmel static void nh_group_v4_update(struct nh_group *nhg) 2271885a3b15SIdo Schimmel { 2272885a3b15SIdo Schimmel struct nh_grp_entry *nhges; 2273885a3b15SIdo Schimmel bool has_v4 = false; 2274885a3b15SIdo Schimmel int i; 2275885a3b15SIdo Schimmel 2276885a3b15SIdo Schimmel nhges = nhg->nh_entries; 2277885a3b15SIdo Schimmel for (i = 0; i < nhg->num_nh; i++) { 2278885a3b15SIdo Schimmel struct nh_info *nhi; 2279885a3b15SIdo Schimmel 2280885a3b15SIdo Schimmel nhi = rtnl_dereference(nhges[i].nh->nh_info); 2281885a3b15SIdo Schimmel if (nhi->family == AF_INET) 2282885a3b15SIdo Schimmel has_v4 = true; 2283885a3b15SIdo Schimmel } 2284885a3b15SIdo Schimmel nhg->has_v4 = has_v4; 2285885a3b15SIdo Schimmel } 2286885a3b15SIdo Schimmel 22877c37c7e0SPetr Machata static int replace_nexthop_single_notify_res(struct net *net, 22887c37c7e0SPetr Machata struct nh_res_table *res_table, 22897c37c7e0SPetr Machata struct nexthop *old, 22907c37c7e0SPetr Machata struct nh_info *oldi, 22917c37c7e0SPetr Machata struct nh_info *newi, 22927c37c7e0SPetr Machata struct netlink_ext_ack *extack) 22937c37c7e0SPetr Machata { 22947c37c7e0SPetr Machata u32 nhg_id = res_table->nhg_id; 22957c37c7e0SPetr Machata int err; 22967c37c7e0SPetr Machata u16 i; 22977c37c7e0SPetr Machata 22987c37c7e0SPetr Machata for (i = 0; i < res_table->num_nh_buckets; i++) { 22997c37c7e0SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 23007c37c7e0SPetr Machata struct nh_grp_entry *nhge; 23017c37c7e0SPetr Machata 23027c37c7e0SPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 23037c37c7e0SPetr Machata if (nhge->nh == old) { 23047c37c7e0SPetr Machata err = __call_nexthop_res_bucket_notifiers(net, nhg_id, 23057c37c7e0SPetr Machata i, true, 23067c37c7e0SPetr Machata oldi, newi, 23077c37c7e0SPetr Machata extack); 23087c37c7e0SPetr Machata if (err) 23097c37c7e0SPetr Machata goto err_notify; 23107c37c7e0SPetr Machata } 23117c37c7e0SPetr Machata } 23127c37c7e0SPetr Machata 23137c37c7e0SPetr Machata return 0; 23147c37c7e0SPetr Machata 23157c37c7e0SPetr Machata err_notify: 23167c37c7e0SPetr Machata while (i-- > 0) { 23177c37c7e0SPetr Machata struct nh_res_bucket *bucket = &res_table->nh_buckets[i]; 23187c37c7e0SPetr Machata struct nh_grp_entry *nhge; 23197c37c7e0SPetr Machata 23207c37c7e0SPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 23217c37c7e0SPetr Machata if (nhge->nh == old) 23227c37c7e0SPetr Machata __call_nexthop_res_bucket_notifiers(net, nhg_id, i, 23237c37c7e0SPetr Machata true, newi, oldi, 23247c37c7e0SPetr Machata extack); 23257c37c7e0SPetr Machata } 23267c37c7e0SPetr Machata return err; 23277c37c7e0SPetr Machata } 23287c37c7e0SPetr Machata 23297c37c7e0SPetr Machata static int replace_nexthop_single_notify(struct net *net, 23307c37c7e0SPetr Machata struct nexthop *group_nh, 23317c37c7e0SPetr Machata struct nexthop *old, 23327c37c7e0SPetr Machata struct nh_info *oldi, 23337c37c7e0SPetr Machata struct nh_info *newi, 23347c37c7e0SPetr Machata struct netlink_ext_ack *extack) 23357c37c7e0SPetr Machata { 23367c37c7e0SPetr Machata struct nh_group *nhg = rtnl_dereference(group_nh->nh_grp); 23377c37c7e0SPetr Machata struct nh_res_table *res_table; 23387c37c7e0SPetr Machata 2339de1d1ee3SPetr Machata if (nhg->hash_threshold) { 23407c37c7e0SPetr Machata return call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, 23417c37c7e0SPetr Machata group_nh, extack); 23427c37c7e0SPetr Machata } else if (nhg->resilient) { 23437c37c7e0SPetr Machata res_table = rtnl_dereference(nhg->res_table); 23447c37c7e0SPetr Machata return replace_nexthop_single_notify_res(net, res_table, 23457c37c7e0SPetr Machata old, oldi, newi, 23467c37c7e0SPetr Machata extack); 23477c37c7e0SPetr Machata } 23487c37c7e0SPetr Machata 23497c37c7e0SPetr Machata return -EINVAL; 23507c37c7e0SPetr Machata } 23517c37c7e0SPetr Machata 23527bf4796dSDavid Ahern static int replace_nexthop_single(struct net *net, struct nexthop *old, 23537bf4796dSDavid Ahern struct nexthop *new, 23547bf4796dSDavid Ahern struct netlink_ext_ack *extack) 23557bf4796dSDavid Ahern { 2356f17bc33dSIdo Schimmel u8 old_protocol, old_nh_flags; 23577bf4796dSDavid Ahern struct nh_info *oldi, *newi; 2358f17bc33dSIdo Schimmel struct nh_grp_entry *nhge; 23598c09c9f9SIdo Schimmel int err; 23607bf4796dSDavid Ahern 23617bf4796dSDavid Ahern if (new->is_group) { 23627bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group."); 23637bf4796dSDavid Ahern return -EINVAL; 23647bf4796dSDavid Ahern } 23657bf4796dSDavid Ahern 23668c09c9f9SIdo Schimmel err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack); 23678c09c9f9SIdo Schimmel if (err) 23688c09c9f9SIdo Schimmel return err; 23698c09c9f9SIdo Schimmel 23708c09c9f9SIdo Schimmel /* Hardware flags were set on 'old' as 'new' is not in the red-black 23718c09c9f9SIdo Schimmel * tree. Therefore, inherit the flags from 'old' to 'new'. 23728c09c9f9SIdo Schimmel */ 23738c09c9f9SIdo Schimmel new->nh_flags |= old->nh_flags & (RTNH_F_OFFLOAD | RTNH_F_TRAP); 23748c09c9f9SIdo Schimmel 23757bf4796dSDavid Ahern oldi = rtnl_dereference(old->nh_info); 23767bf4796dSDavid Ahern newi = rtnl_dereference(new->nh_info); 23777bf4796dSDavid Ahern 23787bf4796dSDavid Ahern newi->nh_parent = old; 23797bf4796dSDavid Ahern oldi->nh_parent = new; 23807bf4796dSDavid Ahern 2381f17bc33dSIdo Schimmel old_protocol = old->protocol; 2382f17bc33dSIdo Schimmel old_nh_flags = old->nh_flags; 2383f17bc33dSIdo Schimmel 23847bf4796dSDavid Ahern old->protocol = new->protocol; 23857bf4796dSDavid Ahern old->nh_flags = new->nh_flags; 23867bf4796dSDavid Ahern 23877bf4796dSDavid Ahern rcu_assign_pointer(old->nh_info, newi); 23887bf4796dSDavid Ahern rcu_assign_pointer(new->nh_info, oldi); 23897bf4796dSDavid Ahern 2390f17bc33dSIdo Schimmel /* Send a replace notification for all the groups using the nexthop. */ 2391f17bc33dSIdo Schimmel list_for_each_entry(nhge, &old->grp_list, nh_list) { 2392f17bc33dSIdo Schimmel struct nexthop *nhp = nhge->nh_parent; 2393f17bc33dSIdo Schimmel 23947c37c7e0SPetr Machata err = replace_nexthop_single_notify(net, nhp, old, oldi, newi, 2395f17bc33dSIdo Schimmel extack); 2396f17bc33dSIdo Schimmel if (err) 2397f17bc33dSIdo Schimmel goto err_notify; 2398f17bc33dSIdo Schimmel } 2399f17bc33dSIdo Schimmel 2400885a3b15SIdo Schimmel /* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially 2401885a3b15SIdo Schimmel * update IPv4 indication in all the groups using the nexthop. 2402885a3b15SIdo Schimmel */ 2403885a3b15SIdo Schimmel if (oldi->family == AF_INET && newi->family == AF_INET6) { 2404885a3b15SIdo Schimmel list_for_each_entry(nhge, &old->grp_list, nh_list) { 2405885a3b15SIdo Schimmel struct nexthop *nhp = nhge->nh_parent; 2406885a3b15SIdo Schimmel struct nh_group *nhg; 2407885a3b15SIdo Schimmel 2408885a3b15SIdo Schimmel nhg = rtnl_dereference(nhp->nh_grp); 2409885a3b15SIdo Schimmel nh_group_v4_update(nhg); 2410885a3b15SIdo Schimmel } 2411885a3b15SIdo Schimmel } 2412885a3b15SIdo Schimmel 24137bf4796dSDavid Ahern return 0; 2414f17bc33dSIdo Schimmel 2415f17bc33dSIdo Schimmel err_notify: 2416f17bc33dSIdo Schimmel rcu_assign_pointer(new->nh_info, newi); 2417f17bc33dSIdo Schimmel rcu_assign_pointer(old->nh_info, oldi); 2418f17bc33dSIdo Schimmel old->nh_flags = old_nh_flags; 2419f17bc33dSIdo Schimmel old->protocol = old_protocol; 2420f17bc33dSIdo Schimmel oldi->nh_parent = old; 2421f17bc33dSIdo Schimmel newi->nh_parent = new; 2422f17bc33dSIdo Schimmel list_for_each_entry_continue_reverse(nhge, &old->grp_list, nh_list) { 2423f17bc33dSIdo Schimmel struct nexthop *nhp = nhge->nh_parent; 2424f17bc33dSIdo Schimmel 24257c37c7e0SPetr Machata replace_nexthop_single_notify(net, nhp, old, newi, oldi, NULL); 2426f17bc33dSIdo Schimmel } 2427f17bc33dSIdo Schimmel call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, old, extack); 2428f17bc33dSIdo Schimmel return err; 24297bf4796dSDavid Ahern } 24307bf4796dSDavid Ahern 24317bf4796dSDavid Ahern static void __nexthop_replace_notify(struct net *net, struct nexthop *nh, 24327bf4796dSDavid Ahern struct nl_info *info) 24337bf4796dSDavid Ahern { 24347bf4796dSDavid Ahern struct fib6_info *f6i; 24357bf4796dSDavid Ahern 24367bf4796dSDavid Ahern if (!list_empty(&nh->fi_list)) { 24377bf4796dSDavid Ahern struct fib_info *fi; 24387bf4796dSDavid Ahern 24397bf4796dSDavid Ahern /* expectation is a few fib_info per nexthop and then 24407bf4796dSDavid Ahern * a lot of routes per fib_info. So mark the fib_info 24417bf4796dSDavid Ahern * and then walk the fib tables once 24427bf4796dSDavid Ahern */ 24437bf4796dSDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) 24447bf4796dSDavid Ahern fi->nh_updated = true; 24457bf4796dSDavid Ahern 24467bf4796dSDavid Ahern fib_info_notify_update(net, info); 24477bf4796dSDavid Ahern 24487bf4796dSDavid Ahern list_for_each_entry(fi, &nh->fi_list, nh_list) 24497bf4796dSDavid Ahern fi->nh_updated = false; 24507bf4796dSDavid Ahern } 24517bf4796dSDavid Ahern 24527bf4796dSDavid Ahern list_for_each_entry(f6i, &nh->f6i_list, nh_list) 24537bf4796dSDavid Ahern ipv6_stub->fib6_rt_update(net, f6i, info); 24547bf4796dSDavid Ahern } 24557bf4796dSDavid Ahern 24567bf4796dSDavid Ahern /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries 24577bf4796dSDavid Ahern * linked to this nexthop and for all groups that the nexthop 24587bf4796dSDavid Ahern * is a member of 24597bf4796dSDavid Ahern */ 24607bf4796dSDavid Ahern static void nexthop_replace_notify(struct net *net, struct nexthop *nh, 24617bf4796dSDavid Ahern struct nl_info *info) 24627bf4796dSDavid Ahern { 24637bf4796dSDavid Ahern struct nh_grp_entry *nhge; 24647bf4796dSDavid Ahern 24657bf4796dSDavid Ahern __nexthop_replace_notify(net, nh, info); 24667bf4796dSDavid Ahern 24677bf4796dSDavid Ahern list_for_each_entry(nhge, &nh->grp_list, nh_list) 24687bf4796dSDavid Ahern __nexthop_replace_notify(net, nhge->nh_parent, info); 24697bf4796dSDavid Ahern } 24707bf4796dSDavid Ahern 2471ab84be7eSDavid Ahern static int replace_nexthop(struct net *net, struct nexthop *old, 2472597f48e4SPetr Machata struct nexthop *new, const struct nh_config *cfg, 2473597f48e4SPetr Machata struct netlink_ext_ack *extack) 2474ab84be7eSDavid Ahern { 24757bf4796dSDavid Ahern bool new_is_reject = false; 24767bf4796dSDavid Ahern struct nh_grp_entry *nhge; 24777bf4796dSDavid Ahern int err; 24787bf4796dSDavid Ahern 24797bf4796dSDavid Ahern /* check that existing FIB entries are ok with the 24807bf4796dSDavid Ahern * new nexthop definition 24817bf4796dSDavid Ahern */ 24827bf4796dSDavid Ahern err = fib_check_nh_list(old, new, extack); 24837bf4796dSDavid Ahern if (err) 24847bf4796dSDavid Ahern return err; 24857bf4796dSDavid Ahern 24867bf4796dSDavid Ahern err = fib6_check_nh_list(old, new, extack); 24877bf4796dSDavid Ahern if (err) 24887bf4796dSDavid Ahern return err; 24897bf4796dSDavid Ahern 24907bf4796dSDavid Ahern if (!new->is_group) { 24917bf4796dSDavid Ahern struct nh_info *nhi = rtnl_dereference(new->nh_info); 24927bf4796dSDavid Ahern 24937bf4796dSDavid Ahern new_is_reject = nhi->reject_nh; 24947bf4796dSDavid Ahern } 24957bf4796dSDavid Ahern 24967bf4796dSDavid Ahern list_for_each_entry(nhge, &old->grp_list, nh_list) { 24977bf4796dSDavid Ahern /* if new nexthop is a blackhole, any groups using this 24987bf4796dSDavid Ahern * nexthop cannot have more than 1 path 24997bf4796dSDavid Ahern */ 25007bf4796dSDavid Ahern if (new_is_reject && 25017bf4796dSDavid Ahern nexthop_num_path(nhge->nh_parent) > 1) { 25027bf4796dSDavid Ahern NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path"); 25037bf4796dSDavid Ahern return -EINVAL; 25047bf4796dSDavid Ahern } 25057bf4796dSDavid Ahern 25067bf4796dSDavid Ahern err = fib_check_nh_list(nhge->nh_parent, new, extack); 25077bf4796dSDavid Ahern if (err) 25087bf4796dSDavid Ahern return err; 25097bf4796dSDavid Ahern 25107bf4796dSDavid Ahern err = fib6_check_nh_list(nhge->nh_parent, new, extack); 25117bf4796dSDavid Ahern if (err) 25127bf4796dSDavid Ahern return err; 25137bf4796dSDavid Ahern } 25147bf4796dSDavid Ahern 25157bf4796dSDavid Ahern if (old->is_group) 2516597f48e4SPetr Machata err = replace_nexthop_grp(net, old, new, cfg, extack); 25177bf4796dSDavid Ahern else 25187bf4796dSDavid Ahern err = replace_nexthop_single(net, old, new, extack); 25197bf4796dSDavid Ahern 25207bf4796dSDavid Ahern if (!err) { 25211005f19bSNikolay Aleksandrov nh_rt_cache_flush(net, old, new); 25227bf4796dSDavid Ahern 25237bf4796dSDavid Ahern __remove_nexthop(net, new, NULL); 25247bf4796dSDavid Ahern nexthop_put(new); 25257bf4796dSDavid Ahern } 25267bf4796dSDavid Ahern 25277bf4796dSDavid Ahern return err; 2528ab84be7eSDavid Ahern } 2529ab84be7eSDavid Ahern 2530ab84be7eSDavid Ahern /* called with rtnl_lock held */ 2531ab84be7eSDavid Ahern static int insert_nexthop(struct net *net, struct nexthop *new_nh, 2532ab84be7eSDavid Ahern struct nh_config *cfg, struct netlink_ext_ack *extack) 2533ab84be7eSDavid Ahern { 2534ab84be7eSDavid Ahern struct rb_node **pp, *parent = NULL, *next; 2535ab84be7eSDavid Ahern struct rb_root *root = &net->nexthop.rb_root; 2536ab84be7eSDavid Ahern bool replace = !!(cfg->nlflags & NLM_F_REPLACE); 2537ab84be7eSDavid Ahern bool create = !!(cfg->nlflags & NLM_F_CREATE); 2538ab84be7eSDavid Ahern u32 new_id = new_nh->id; 25397bf4796dSDavid Ahern int replace_notify = 0; 2540ab84be7eSDavid Ahern int rc = -EEXIST; 2541ab84be7eSDavid Ahern 2542ab84be7eSDavid Ahern pp = &root->rb_node; 2543ab84be7eSDavid Ahern while (1) { 2544ab84be7eSDavid Ahern struct nexthop *nh; 2545ab84be7eSDavid Ahern 2546233c6378SIdo Schimmel next = *pp; 2547ab84be7eSDavid Ahern if (!next) 2548ab84be7eSDavid Ahern break; 2549ab84be7eSDavid Ahern 2550ab84be7eSDavid Ahern parent = next; 2551ab84be7eSDavid Ahern 2552ab84be7eSDavid Ahern nh = rb_entry(parent, struct nexthop, rb_node); 2553ab84be7eSDavid Ahern if (new_id < nh->id) { 2554ab84be7eSDavid Ahern pp = &next->rb_left; 2555ab84be7eSDavid Ahern } else if (new_id > nh->id) { 2556ab84be7eSDavid Ahern pp = &next->rb_right; 2557ab84be7eSDavid Ahern } else if (replace) { 2558597f48e4SPetr Machata rc = replace_nexthop(net, nh, new_nh, cfg, extack); 25597bf4796dSDavid Ahern if (!rc) { 2560ab84be7eSDavid Ahern new_nh = nh; /* send notification with old nh */ 25617bf4796dSDavid Ahern replace_notify = 1; 25627bf4796dSDavid Ahern } 2563ab84be7eSDavid Ahern goto out; 2564ab84be7eSDavid Ahern } else { 2565ab84be7eSDavid Ahern /* id already exists and not a replace */ 2566ab84be7eSDavid Ahern goto out; 2567ab84be7eSDavid Ahern } 2568ab84be7eSDavid Ahern } 2569ab84be7eSDavid Ahern 2570ab84be7eSDavid Ahern if (replace && !create) { 2571ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists"); 2572ab84be7eSDavid Ahern rc = -ENOENT; 2573ab84be7eSDavid Ahern goto out; 2574ab84be7eSDavid Ahern } 2575ab84be7eSDavid Ahern 2576283a72a5SPetr Machata if (new_nh->is_group) { 2577283a72a5SPetr Machata struct nh_group *nhg = rtnl_dereference(new_nh->nh_grp); 2578283a72a5SPetr Machata struct nh_res_table *res_table; 2579283a72a5SPetr Machata 2580283a72a5SPetr Machata if (nhg->resilient) { 2581283a72a5SPetr Machata res_table = rtnl_dereference(nhg->res_table); 2582283a72a5SPetr Machata 2583283a72a5SPetr Machata /* Not passing the number of buckets is OK when 2584283a72a5SPetr Machata * replacing, but not when creating a new group. 2585283a72a5SPetr Machata */ 2586283a72a5SPetr Machata if (!cfg->nh_grp_res_has_num_buckets) { 2587283a72a5SPetr Machata NL_SET_ERR_MSG(extack, "Number of buckets not specified for nexthop group insertion"); 2588283a72a5SPetr Machata rc = -EINVAL; 2589283a72a5SPetr Machata goto out; 2590283a72a5SPetr Machata } 2591283a72a5SPetr Machata 2592283a72a5SPetr Machata nh_res_group_rebalance(nhg, res_table); 25937c37c7e0SPetr Machata 25947c37c7e0SPetr Machata /* Do not send bucket notifications, we do full 25957c37c7e0SPetr Machata * notification below. 25967c37c7e0SPetr Machata */ 25970b4818aaSPetr Machata nh_res_table_upkeep(res_table, false, false); 2598283a72a5SPetr Machata } 2599283a72a5SPetr Machata } 2600283a72a5SPetr Machata 2601ab84be7eSDavid Ahern rb_link_node_rcu(&new_nh->rb_node, parent, pp); 2602ab84be7eSDavid Ahern rb_insert_color(&new_nh->rb_node, root); 2603732d167bSIdo Schimmel 2604de1d1ee3SPetr Machata /* The initial insertion is a full notification for hash-threshold as 2605de1d1ee3SPetr Machata * well as resilient groups. 26067c37c7e0SPetr Machata */ 2607732d167bSIdo Schimmel rc = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new_nh, extack); 2608732d167bSIdo Schimmel if (rc) 2609732d167bSIdo Schimmel rb_erase(&new_nh->rb_node, &net->nexthop.rb_root); 2610732d167bSIdo Schimmel 2611ab84be7eSDavid Ahern out: 2612ab84be7eSDavid Ahern if (!rc) { 2613ab84be7eSDavid Ahern nh_base_seq_inc(net); 2614ab84be7eSDavid Ahern nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo); 2615bdf00bf2SKuniyuki Iwashima if (replace_notify && 2616bdf00bf2SKuniyuki Iwashima READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode)) 26177bf4796dSDavid Ahern nexthop_replace_notify(net, new_nh, &cfg->nlinfo); 2618ab84be7eSDavid Ahern } 2619ab84be7eSDavid Ahern 2620ab84be7eSDavid Ahern return rc; 2621ab84be7eSDavid Ahern } 2622ab84be7eSDavid Ahern 2623597cfe4fSDavid Ahern /* rtnl */ 2624597cfe4fSDavid Ahern /* remove all nexthops tied to a device being deleted */ 262576c03bf8SIdo Schimmel static void nexthop_flush_dev(struct net_device *dev, unsigned long event) 2626597cfe4fSDavid Ahern { 2627597cfe4fSDavid Ahern unsigned int hash = nh_dev_hashfn(dev->ifindex); 2628597cfe4fSDavid Ahern struct net *net = dev_net(dev); 2629597cfe4fSDavid Ahern struct hlist_head *head = &net->nexthop.devhash[hash]; 2630597cfe4fSDavid Ahern struct hlist_node *n; 2631597cfe4fSDavid Ahern struct nh_info *nhi; 2632597cfe4fSDavid Ahern 2633597cfe4fSDavid Ahern hlist_for_each_entry_safe(nhi, n, head, dev_hash) { 2634597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev != dev) 2635597cfe4fSDavid Ahern continue; 2636597cfe4fSDavid Ahern 263776c03bf8SIdo Schimmel if (nhi->reject_nh && 263876c03bf8SIdo Schimmel (event == NETDEV_DOWN || event == NETDEV_CHANGE)) 263976c03bf8SIdo Schimmel continue; 264076c03bf8SIdo Schimmel 2641430a0491SDavid Ahern remove_nexthop(net, nhi->nh_parent, NULL); 2642597cfe4fSDavid Ahern } 2643597cfe4fSDavid Ahern } 2644597cfe4fSDavid Ahern 2645ab84be7eSDavid Ahern /* rtnl; called when net namespace is deleted */ 2646ab84be7eSDavid Ahern static void flush_all_nexthops(struct net *net) 2647ab84be7eSDavid Ahern { 2648ab84be7eSDavid Ahern struct rb_root *root = &net->nexthop.rb_root; 2649ab84be7eSDavid Ahern struct rb_node *node; 2650ab84be7eSDavid Ahern struct nexthop *nh; 2651ab84be7eSDavid Ahern 2652ab84be7eSDavid Ahern while ((node = rb_first(root))) { 2653ab84be7eSDavid Ahern nh = rb_entry(node, struct nexthop, rb_node); 2654430a0491SDavid Ahern remove_nexthop(net, nh, NULL); 2655ab84be7eSDavid Ahern cond_resched(); 2656ab84be7eSDavid Ahern } 2657ab84be7eSDavid Ahern } 2658ab84be7eSDavid Ahern 2659430a0491SDavid Ahern static struct nexthop *nexthop_create_group(struct net *net, 2660430a0491SDavid Ahern struct nh_config *cfg) 2661430a0491SDavid Ahern { 2662430a0491SDavid Ahern struct nlattr *grps_attr = cfg->nh_grp; 2663430a0491SDavid Ahern struct nexthop_grp *entry = nla_data(grps_attr); 266490f33bffSNikolay Aleksandrov u16 num_nh = nla_len(grps_attr) / sizeof(*entry); 2665430a0491SDavid Ahern struct nh_group *nhg; 2666430a0491SDavid Ahern struct nexthop *nh; 2667283a72a5SPetr Machata int err; 2668430a0491SDavid Ahern int i; 2669430a0491SDavid Ahern 2670eeaac363SNikolay Aleksandrov if (WARN_ON(!num_nh)) 2671eeaac363SNikolay Aleksandrov return ERR_PTR(-EINVAL); 2672eeaac363SNikolay Aleksandrov 2673430a0491SDavid Ahern nh = nexthop_alloc(); 2674430a0491SDavid Ahern if (!nh) 2675430a0491SDavid Ahern return ERR_PTR(-ENOMEM); 2676430a0491SDavid Ahern 2677430a0491SDavid Ahern nh->is_group = 1; 2678430a0491SDavid Ahern 267990f33bffSNikolay Aleksandrov nhg = nexthop_grp_alloc(num_nh); 2680430a0491SDavid Ahern if (!nhg) { 2681430a0491SDavid Ahern kfree(nh); 2682430a0491SDavid Ahern return ERR_PTR(-ENOMEM); 2683430a0491SDavid Ahern } 2684430a0491SDavid Ahern 268590f33bffSNikolay Aleksandrov /* spare group used for removals */ 268690f33bffSNikolay Aleksandrov nhg->spare = nexthop_grp_alloc(num_nh); 2687dafe2078SPatrick Eigensatz if (!nhg->spare) { 268890f33bffSNikolay Aleksandrov kfree(nhg); 268990f33bffSNikolay Aleksandrov kfree(nh); 2690dafe2078SPatrick Eigensatz return ERR_PTR(-ENOMEM); 269190f33bffSNikolay Aleksandrov } 269290f33bffSNikolay Aleksandrov nhg->spare->spare = nhg; 269390f33bffSNikolay Aleksandrov 2694430a0491SDavid Ahern for (i = 0; i < nhg->num_nh; ++i) { 2695430a0491SDavid Ahern struct nexthop *nhe; 2696430a0491SDavid Ahern struct nh_info *nhi; 2697430a0491SDavid Ahern 2698430a0491SDavid Ahern nhe = nexthop_find_by_id(net, entry[i].id); 2699283a72a5SPetr Machata if (!nexthop_get(nhe)) { 2700283a72a5SPetr Machata err = -ENOENT; 2701430a0491SDavid Ahern goto out_no_nh; 2702283a72a5SPetr Machata } 2703430a0491SDavid Ahern 2704430a0491SDavid Ahern nhi = rtnl_dereference(nhe->nh_info); 2705430a0491SDavid Ahern if (nhi->family == AF_INET) 2706430a0491SDavid Ahern nhg->has_v4 = true; 2707430a0491SDavid Ahern 2708f4676ea7SIdo Schimmel nhg->nh_entries[i].stats = 2709f4676ea7SIdo Schimmel netdev_alloc_pcpu_stats(struct nh_grp_entry_stats); 2710f4676ea7SIdo Schimmel if (!nhg->nh_entries[i].stats) { 2711f4676ea7SIdo Schimmel err = -ENOMEM; 2712f4676ea7SIdo Schimmel nexthop_put(nhe); 2713f4676ea7SIdo Schimmel goto out_no_nh; 2714f4676ea7SIdo Schimmel } 2715430a0491SDavid Ahern nhg->nh_entries[i].nh = nhe; 2716430a0491SDavid Ahern nhg->nh_entries[i].weight = entry[i].weight + 1; 2717430a0491SDavid Ahern list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list); 2718430a0491SDavid Ahern nhg->nh_entries[i].nh_parent = nh; 2719430a0491SDavid Ahern } 2720430a0491SDavid Ahern 272190e1a9e2SPetr Machata if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) { 2722de1d1ee3SPetr Machata nhg->hash_threshold = 1; 272390e1a9e2SPetr Machata nhg->is_multipath = true; 2724710ec562SIdo Schimmel } else if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_RES) { 2725283a72a5SPetr Machata struct nh_res_table *res_table; 2726283a72a5SPetr Machata 2727283a72a5SPetr Machata res_table = nexthop_res_table_alloc(net, cfg->nh_id, cfg); 2728283a72a5SPetr Machata if (!res_table) { 2729283a72a5SPetr Machata err = -ENOMEM; 2730710ec562SIdo Schimmel goto out_no_nh; 273190e1a9e2SPetr Machata } 2732720ccd9aSPetr Machata 2733283a72a5SPetr Machata rcu_assign_pointer(nhg->spare->res_table, res_table); 2734283a72a5SPetr Machata rcu_assign_pointer(nhg->res_table, res_table); 2735283a72a5SPetr Machata nhg->resilient = true; 2736283a72a5SPetr Machata nhg->is_multipath = true; 2737283a72a5SPetr Machata } 2738283a72a5SPetr Machata 2739de1d1ee3SPetr Machata WARN_ON_ONCE(nhg->hash_threshold + nhg->resilient != 1); 2740720ccd9aSPetr Machata 2741de1d1ee3SPetr Machata if (nhg->hash_threshold) 2742de1d1ee3SPetr Machata nh_hthr_group_rebalance(nhg); 2743430a0491SDavid Ahern 274438428d68SRoopa Prabhu if (cfg->nh_fdb) 2745ce9ac056SDavid Ahern nhg->fdb_nh = 1; 274638428d68SRoopa Prabhu 2747746c19a5SIdo Schimmel if (cfg->nh_hw_stats) 2748746c19a5SIdo Schimmel nhg->hw_stats = true; 2749746c19a5SIdo Schimmel 2750430a0491SDavid Ahern rcu_assign_pointer(nh->nh_grp, nhg); 2751430a0491SDavid Ahern 2752430a0491SDavid Ahern return nh; 2753430a0491SDavid Ahern 2754430a0491SDavid Ahern out_no_nh: 27557b01e53eSIdo Schimmel for (i--; i >= 0; --i) { 27567b01e53eSIdo Schimmel list_del(&nhg->nh_entries[i].nh_list); 2757f4676ea7SIdo Schimmel free_percpu(nhg->nh_entries[i].stats); 2758430a0491SDavid Ahern nexthop_put(nhg->nh_entries[i].nh); 27597b01e53eSIdo Schimmel } 2760430a0491SDavid Ahern 276190f33bffSNikolay Aleksandrov kfree(nhg->spare); 2762430a0491SDavid Ahern kfree(nhg); 2763430a0491SDavid Ahern kfree(nh); 2764430a0491SDavid Ahern 2765283a72a5SPetr Machata return ERR_PTR(err); 2766430a0491SDavid Ahern } 2767430a0491SDavid Ahern 2768597cfe4fSDavid Ahern static int nh_create_ipv4(struct net *net, struct nexthop *nh, 2769597cfe4fSDavid Ahern struct nh_info *nhi, struct nh_config *cfg, 2770597cfe4fSDavid Ahern struct netlink_ext_ack *extack) 2771597cfe4fSDavid Ahern { 2772597cfe4fSDavid Ahern struct fib_nh *fib_nh = &nhi->fib_nh; 2773597cfe4fSDavid Ahern struct fib_config fib_cfg = { 2774597cfe4fSDavid Ahern .fc_oif = cfg->nh_ifindex, 2775597cfe4fSDavid Ahern .fc_gw4 = cfg->gw.ipv4, 2776597cfe4fSDavid Ahern .fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0, 2777597cfe4fSDavid Ahern .fc_flags = cfg->nh_flags, 27789aca491eSRyoga Saito .fc_nlinfo = cfg->nlinfo, 2779b513bd03SDavid Ahern .fc_encap = cfg->nh_encap, 2780b513bd03SDavid Ahern .fc_encap_type = cfg->nh_encap_type, 2781597cfe4fSDavid Ahern }; 278238428d68SRoopa Prabhu u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN); 2783c76c9925SColin Ian King int err; 2784597cfe4fSDavid Ahern 2785597cfe4fSDavid Ahern err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack); 2786597cfe4fSDavid Ahern if (err) { 2787597cfe4fSDavid Ahern fib_nh_release(net, fib_nh); 2788597cfe4fSDavid Ahern goto out; 2789597cfe4fSDavid Ahern } 2790597cfe4fSDavid Ahern 2791ce9ac056SDavid Ahern if (nhi->fdb_nh) 279238428d68SRoopa Prabhu goto out; 279338428d68SRoopa Prabhu 2794597cfe4fSDavid Ahern /* sets nh_dev if successful */ 2795597cfe4fSDavid Ahern err = fib_check_nh(net, fib_nh, tb_id, 0, extack); 2796597cfe4fSDavid Ahern if (!err) { 2797597cfe4fSDavid Ahern nh->nh_flags = fib_nh->fib_nh_flags; 2798dcb1ecb5SDavid Ahern fib_info_update_nhc_saddr(net, &fib_nh->nh_common, 2799bac0f937SNicolas Dichtel !fib_nh->fib_nh_scope ? 0 : fib_nh->fib_nh_scope - 1); 2800597cfe4fSDavid Ahern } else { 2801597cfe4fSDavid Ahern fib_nh_release(net, fib_nh); 2802597cfe4fSDavid Ahern } 2803597cfe4fSDavid Ahern out: 2804597cfe4fSDavid Ahern return err; 2805597cfe4fSDavid Ahern } 2806597cfe4fSDavid Ahern 280753010f99SDavid Ahern static int nh_create_ipv6(struct net *net, struct nexthop *nh, 280853010f99SDavid Ahern struct nh_info *nhi, struct nh_config *cfg, 280953010f99SDavid Ahern struct netlink_ext_ack *extack) 281053010f99SDavid Ahern { 281153010f99SDavid Ahern struct fib6_nh *fib6_nh = &nhi->fib6_nh; 281253010f99SDavid Ahern struct fib6_config fib6_cfg = { 281353010f99SDavid Ahern .fc_table = l3mdev_fib_table(cfg->dev), 281453010f99SDavid Ahern .fc_ifindex = cfg->nh_ifindex, 281553010f99SDavid Ahern .fc_gateway = cfg->gw.ipv6, 281653010f99SDavid Ahern .fc_flags = cfg->nh_flags, 28179aca491eSRyoga Saito .fc_nlinfo = cfg->nlinfo, 2818b513bd03SDavid Ahern .fc_encap = cfg->nh_encap, 2819b513bd03SDavid Ahern .fc_encap_type = cfg->nh_encap_type, 282038428d68SRoopa Prabhu .fc_is_fdb = cfg->nh_fdb, 282153010f99SDavid Ahern }; 28226f43e525SColin Ian King int err; 282353010f99SDavid Ahern 282453010f99SDavid Ahern if (!ipv6_addr_any(&cfg->gw.ipv6)) 282553010f99SDavid Ahern fib6_cfg.fc_flags |= RTF_GATEWAY; 282653010f99SDavid Ahern 282753010f99SDavid Ahern /* sets nh_dev if successful */ 282853010f99SDavid Ahern err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL, 282953010f99SDavid Ahern extack); 28301c743127SNikolay Aleksandrov if (err) { 28311c743127SNikolay Aleksandrov /* IPv6 is not enabled, don't call fib6_nh_release */ 28321c743127SNikolay Aleksandrov if (err == -EAFNOSUPPORT) 28331c743127SNikolay Aleksandrov goto out; 283453010f99SDavid Ahern ipv6_stub->fib6_nh_release(fib6_nh); 28351c743127SNikolay Aleksandrov } else { 283653010f99SDavid Ahern nh->nh_flags = fib6_nh->fib_nh_flags; 28371c743127SNikolay Aleksandrov } 28381c743127SNikolay Aleksandrov out: 283953010f99SDavid Ahern return err; 284053010f99SDavid Ahern } 284153010f99SDavid Ahern 2842ab84be7eSDavid Ahern static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg, 2843ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 2844ab84be7eSDavid Ahern { 2845ab84be7eSDavid Ahern struct nh_info *nhi; 2846ab84be7eSDavid Ahern struct nexthop *nh; 2847ab84be7eSDavid Ahern int err = 0; 2848ab84be7eSDavid Ahern 2849ab84be7eSDavid Ahern nh = nexthop_alloc(); 2850ab84be7eSDavid Ahern if (!nh) 2851ab84be7eSDavid Ahern return ERR_PTR(-ENOMEM); 2852ab84be7eSDavid Ahern 2853ab84be7eSDavid Ahern nhi = kzalloc(sizeof(*nhi), GFP_KERNEL); 2854ab84be7eSDavid Ahern if (!nhi) { 2855ab84be7eSDavid Ahern kfree(nh); 2856ab84be7eSDavid Ahern return ERR_PTR(-ENOMEM); 2857ab84be7eSDavid Ahern } 2858ab84be7eSDavid Ahern 2859ab84be7eSDavid Ahern nh->nh_flags = cfg->nh_flags; 2860ab84be7eSDavid Ahern nh->net = net; 2861ab84be7eSDavid Ahern 2862ab84be7eSDavid Ahern nhi->nh_parent = nh; 2863ab84be7eSDavid Ahern nhi->family = cfg->nh_family; 2864ab84be7eSDavid Ahern nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK; 2865ab84be7eSDavid Ahern 286638428d68SRoopa Prabhu if (cfg->nh_fdb) 2867ce9ac056SDavid Ahern nhi->fdb_nh = 1; 286838428d68SRoopa Prabhu 2869ab84be7eSDavid Ahern if (cfg->nh_blackhole) { 2870ab84be7eSDavid Ahern nhi->reject_nh = 1; 2871ab84be7eSDavid Ahern cfg->nh_ifindex = net->loopback_dev->ifindex; 2872ab84be7eSDavid Ahern } 2873ab84be7eSDavid Ahern 2874597cfe4fSDavid Ahern switch (cfg->nh_family) { 2875597cfe4fSDavid Ahern case AF_INET: 2876597cfe4fSDavid Ahern err = nh_create_ipv4(net, nh, nhi, cfg, extack); 2877597cfe4fSDavid Ahern break; 287853010f99SDavid Ahern case AF_INET6: 287953010f99SDavid Ahern err = nh_create_ipv6(net, nh, nhi, cfg, extack); 288053010f99SDavid Ahern break; 2881597cfe4fSDavid Ahern } 2882597cfe4fSDavid Ahern 2883ab84be7eSDavid Ahern if (err) { 2884ab84be7eSDavid Ahern kfree(nhi); 2885ab84be7eSDavid Ahern kfree(nh); 2886ab84be7eSDavid Ahern return ERR_PTR(err); 2887ab84be7eSDavid Ahern } 2888ab84be7eSDavid Ahern 2889597cfe4fSDavid Ahern /* add the entry to the device based hash */ 2890ce9ac056SDavid Ahern if (!nhi->fdb_nh) 2891597cfe4fSDavid Ahern nexthop_devhash_add(net, nhi); 2892597cfe4fSDavid Ahern 2893ab84be7eSDavid Ahern rcu_assign_pointer(nh->nh_info, nhi); 2894ab84be7eSDavid Ahern 2895ab84be7eSDavid Ahern return nh; 2896ab84be7eSDavid Ahern } 2897ab84be7eSDavid Ahern 2898ab84be7eSDavid Ahern /* called with rtnl lock held */ 2899ab84be7eSDavid Ahern static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg, 2900ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 2901ab84be7eSDavid Ahern { 2902ab84be7eSDavid Ahern struct nexthop *nh; 2903ab84be7eSDavid Ahern int err; 2904ab84be7eSDavid Ahern 2905ab84be7eSDavid Ahern if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) { 2906ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Replace requires nexthop id"); 2907ab84be7eSDavid Ahern return ERR_PTR(-EINVAL); 2908ab84be7eSDavid Ahern } 2909ab84be7eSDavid Ahern 2910ab84be7eSDavid Ahern if (!cfg->nh_id) { 2911ab84be7eSDavid Ahern cfg->nh_id = nh_find_unused_id(net); 2912ab84be7eSDavid Ahern if (!cfg->nh_id) { 2913ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "No unused id"); 2914ab84be7eSDavid Ahern return ERR_PTR(-EINVAL); 2915ab84be7eSDavid Ahern } 2916ab84be7eSDavid Ahern } 2917ab84be7eSDavid Ahern 2918430a0491SDavid Ahern if (cfg->nh_grp) 2919430a0491SDavid Ahern nh = nexthop_create_group(net, cfg); 2920430a0491SDavid Ahern else 2921ab84be7eSDavid Ahern nh = nexthop_create(net, cfg, extack); 2922430a0491SDavid Ahern 2923ab84be7eSDavid Ahern if (IS_ERR(nh)) 2924ab84be7eSDavid Ahern return nh; 2925ab84be7eSDavid Ahern 2926ab84be7eSDavid Ahern refcount_set(&nh->refcnt, 1); 2927ab84be7eSDavid Ahern nh->id = cfg->nh_id; 2928ab84be7eSDavid Ahern nh->protocol = cfg->nh_protocol; 2929ab84be7eSDavid Ahern nh->net = net; 2930ab84be7eSDavid Ahern 2931ab84be7eSDavid Ahern err = insert_nexthop(net, nh, cfg, extack); 2932ab84be7eSDavid Ahern if (err) { 2933430a0491SDavid Ahern __remove_nexthop(net, nh, NULL); 2934ab84be7eSDavid Ahern nexthop_put(nh); 2935ab84be7eSDavid Ahern nh = ERR_PTR(err); 2936ab84be7eSDavid Ahern } 2937ab84be7eSDavid Ahern 2938ab84be7eSDavid Ahern return nh; 2939ab84be7eSDavid Ahern } 2940ab84be7eSDavid Ahern 2941a2601e2bSPetr Machata static int rtm_nh_get_timer(struct nlattr *attr, unsigned long fallback, 2942a2601e2bSPetr Machata unsigned long *timer_p, bool *has_p, 2943a2601e2bSPetr Machata struct netlink_ext_ack *extack) 2944a2601e2bSPetr Machata { 2945a2601e2bSPetr Machata unsigned long timer; 2946a2601e2bSPetr Machata u32 value; 2947a2601e2bSPetr Machata 2948a2601e2bSPetr Machata if (!attr) { 2949a2601e2bSPetr Machata *timer_p = fallback; 2950a2601e2bSPetr Machata *has_p = false; 2951a2601e2bSPetr Machata return 0; 2952a2601e2bSPetr Machata } 2953a2601e2bSPetr Machata 2954a2601e2bSPetr Machata value = nla_get_u32(attr); 2955a2601e2bSPetr Machata timer = clock_t_to_jiffies(value); 2956a2601e2bSPetr Machata if (timer == ~0UL) { 2957a2601e2bSPetr Machata NL_SET_ERR_MSG(extack, "Timer value too large"); 2958a2601e2bSPetr Machata return -EINVAL; 2959a2601e2bSPetr Machata } 2960a2601e2bSPetr Machata 2961a2601e2bSPetr Machata *timer_p = timer; 2962a2601e2bSPetr Machata *has_p = true; 2963a2601e2bSPetr Machata return 0; 2964a2601e2bSPetr Machata } 2965a2601e2bSPetr Machata 2966a2601e2bSPetr Machata static int rtm_to_nh_config_grp_res(struct nlattr *res, struct nh_config *cfg, 2967a2601e2bSPetr Machata struct netlink_ext_ack *extack) 2968a2601e2bSPetr Machata { 2969a2601e2bSPetr Machata struct nlattr *tb[ARRAY_SIZE(rtm_nh_res_policy_new)] = {}; 2970a2601e2bSPetr Machata int err; 2971a2601e2bSPetr Machata 2972a2601e2bSPetr Machata if (res) { 2973a2601e2bSPetr Machata err = nla_parse_nested(tb, 2974a2601e2bSPetr Machata ARRAY_SIZE(rtm_nh_res_policy_new) - 1, 2975a2601e2bSPetr Machata res, rtm_nh_res_policy_new, extack); 2976a2601e2bSPetr Machata if (err < 0) 2977a2601e2bSPetr Machata return err; 2978a2601e2bSPetr Machata } 2979a2601e2bSPetr Machata 2980a2601e2bSPetr Machata if (tb[NHA_RES_GROUP_BUCKETS]) { 2981a2601e2bSPetr Machata cfg->nh_grp_res_num_buckets = 2982a2601e2bSPetr Machata nla_get_u16(tb[NHA_RES_GROUP_BUCKETS]); 2983a2601e2bSPetr Machata cfg->nh_grp_res_has_num_buckets = true; 2984a2601e2bSPetr Machata if (!cfg->nh_grp_res_num_buckets) { 2985a2601e2bSPetr Machata NL_SET_ERR_MSG(extack, "Number of buckets needs to be non-0"); 2986a2601e2bSPetr Machata return -EINVAL; 2987a2601e2bSPetr Machata } 2988a2601e2bSPetr Machata } 2989a2601e2bSPetr Machata 2990a2601e2bSPetr Machata err = rtm_nh_get_timer(tb[NHA_RES_GROUP_IDLE_TIMER], 2991a2601e2bSPetr Machata NH_RES_DEFAULT_IDLE_TIMER, 2992a2601e2bSPetr Machata &cfg->nh_grp_res_idle_timer, 2993a2601e2bSPetr Machata &cfg->nh_grp_res_has_idle_timer, 2994a2601e2bSPetr Machata extack); 2995a2601e2bSPetr Machata if (err) 2996a2601e2bSPetr Machata return err; 2997a2601e2bSPetr Machata 2998a2601e2bSPetr Machata return rtm_nh_get_timer(tb[NHA_RES_GROUP_UNBALANCED_TIMER], 2999a2601e2bSPetr Machata NH_RES_DEFAULT_UNBALANCED_TIMER, 3000a2601e2bSPetr Machata &cfg->nh_grp_res_unbalanced_timer, 3001a2601e2bSPetr Machata &cfg->nh_grp_res_has_unbalanced_timer, 3002a2601e2bSPetr Machata extack); 3003a2601e2bSPetr Machata } 3004a2601e2bSPetr Machata 3005ab84be7eSDavid Ahern static int rtm_to_nh_config(struct net *net, struct sk_buff *skb, 3006ab84be7eSDavid Ahern struct nlmsghdr *nlh, struct nh_config *cfg, 3007ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3008ab84be7eSDavid Ahern { 3009ab84be7eSDavid Ahern struct nhmsg *nhm = nlmsg_data(nlh); 3010643d0878SPetr Machata struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_new)]; 3011ab84be7eSDavid Ahern int err; 3012ab84be7eSDavid Ahern 3013643d0878SPetr Machata err = nlmsg_parse(nlh, sizeof(*nhm), tb, 3014643d0878SPetr Machata ARRAY_SIZE(rtm_nh_policy_new) - 1, 3015643d0878SPetr Machata rtm_nh_policy_new, extack); 3016ab84be7eSDavid Ahern if (err < 0) 3017ab84be7eSDavid Ahern return err; 3018ab84be7eSDavid Ahern 3019ab84be7eSDavid Ahern err = -EINVAL; 3020ab84be7eSDavid Ahern if (nhm->resvd || nhm->nh_scope) { 3021ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid values in ancillary header"); 3022ab84be7eSDavid Ahern goto out; 3023ab84be7eSDavid Ahern } 3024ab84be7eSDavid Ahern if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) { 3025ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header"); 3026ab84be7eSDavid Ahern goto out; 3027ab84be7eSDavid Ahern } 3028ab84be7eSDavid Ahern 3029ab84be7eSDavid Ahern switch (nhm->nh_family) { 3030597cfe4fSDavid Ahern case AF_INET: 303153010f99SDavid Ahern case AF_INET6: 3032597cfe4fSDavid Ahern break; 3033430a0491SDavid Ahern case AF_UNSPEC: 3034430a0491SDavid Ahern if (tb[NHA_GROUP]) 3035430a0491SDavid Ahern break; 3036a8eceea8SJoe Perches fallthrough; 3037ab84be7eSDavid Ahern default: 3038ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid address family"); 3039ab84be7eSDavid Ahern goto out; 3040ab84be7eSDavid Ahern } 3041ab84be7eSDavid Ahern 3042ab84be7eSDavid Ahern memset(cfg, 0, sizeof(*cfg)); 3043ab84be7eSDavid Ahern cfg->nlflags = nlh->nlmsg_flags; 3044ab84be7eSDavid Ahern cfg->nlinfo.portid = NETLINK_CB(skb).portid; 3045ab84be7eSDavid Ahern cfg->nlinfo.nlh = nlh; 3046ab84be7eSDavid Ahern cfg->nlinfo.nl_net = net; 3047ab84be7eSDavid Ahern 3048ab84be7eSDavid Ahern cfg->nh_family = nhm->nh_family; 3049ab84be7eSDavid Ahern cfg->nh_protocol = nhm->nh_protocol; 3050ab84be7eSDavid Ahern cfg->nh_flags = nhm->nh_flags; 3051ab84be7eSDavid Ahern 3052ab84be7eSDavid Ahern if (tb[NHA_ID]) 3053ab84be7eSDavid Ahern cfg->nh_id = nla_get_u32(tb[NHA_ID]); 3054ab84be7eSDavid Ahern 305538428d68SRoopa Prabhu if (tb[NHA_FDB]) { 305638428d68SRoopa Prabhu if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] || 305738428d68SRoopa Prabhu tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE]) { 305838428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole"); 305938428d68SRoopa Prabhu goto out; 306038428d68SRoopa Prabhu } 306138428d68SRoopa Prabhu if (nhm->nh_flags) { 306238428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header"); 306338428d68SRoopa Prabhu goto out; 306438428d68SRoopa Prabhu } 306538428d68SRoopa Prabhu cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]); 306638428d68SRoopa Prabhu } 306738428d68SRoopa Prabhu 3068430a0491SDavid Ahern if (tb[NHA_GROUP]) { 3069430a0491SDavid Ahern if (nhm->nh_family != AF_UNSPEC) { 3070430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid family for group"); 3071430a0491SDavid Ahern goto out; 3072430a0491SDavid Ahern } 3073430a0491SDavid Ahern cfg->nh_grp = tb[NHA_GROUP]; 3074430a0491SDavid Ahern 3075430a0491SDavid Ahern cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH; 3076430a0491SDavid Ahern if (tb[NHA_GROUP_TYPE]) 3077430a0491SDavid Ahern cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]); 3078430a0491SDavid Ahern 3079430a0491SDavid Ahern if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) { 3080430a0491SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid group type"); 3081430a0491SDavid Ahern goto out; 3082430a0491SDavid Ahern } 3083a2601e2bSPetr Machata err = nh_check_attr_group(net, tb, ARRAY_SIZE(tb), 3084a2601e2bSPetr Machata cfg->nh_grp_type, extack); 3085a2601e2bSPetr Machata if (err) 3086a2601e2bSPetr Machata goto out; 3087a2601e2bSPetr Machata 3088a2601e2bSPetr Machata if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_RES) 3089a2601e2bSPetr Machata err = rtm_to_nh_config_grp_res(tb[NHA_RES_GROUP], 3090a2601e2bSPetr Machata cfg, extack); 3091430a0491SDavid Ahern 3092746c19a5SIdo Schimmel if (tb[NHA_HW_STATS_ENABLE]) 3093746c19a5SIdo Schimmel cfg->nh_hw_stats = nla_get_u32(tb[NHA_HW_STATS_ENABLE]); 3094746c19a5SIdo Schimmel 3095430a0491SDavid Ahern /* no other attributes should be set */ 3096430a0491SDavid Ahern goto out; 3097430a0491SDavid Ahern } 3098430a0491SDavid Ahern 3099ab84be7eSDavid Ahern if (tb[NHA_BLACKHOLE]) { 3100b513bd03SDavid Ahern if (tb[NHA_GATEWAY] || tb[NHA_OIF] || 310138428d68SRoopa Prabhu tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) { 310238428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb"); 3103ab84be7eSDavid Ahern goto out; 3104ab84be7eSDavid Ahern } 3105ab84be7eSDavid Ahern 3106ab84be7eSDavid Ahern cfg->nh_blackhole = 1; 3107ab84be7eSDavid Ahern err = 0; 3108ab84be7eSDavid Ahern goto out; 3109ab84be7eSDavid Ahern } 3110ab84be7eSDavid Ahern 311138428d68SRoopa Prabhu if (!cfg->nh_fdb && !tb[NHA_OIF]) { 311238428d68SRoopa Prabhu NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops"); 3113ab84be7eSDavid Ahern goto out; 3114ab84be7eSDavid Ahern } 3115ab84be7eSDavid Ahern 311638428d68SRoopa Prabhu if (!cfg->nh_fdb && tb[NHA_OIF]) { 3117ab84be7eSDavid Ahern cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]); 3118ab84be7eSDavid Ahern if (cfg->nh_ifindex) 3119ab84be7eSDavid Ahern cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex); 3120ab84be7eSDavid Ahern 3121ab84be7eSDavid Ahern if (!cfg->dev) { 3122ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid device index"); 3123ab84be7eSDavid Ahern goto out; 3124ab84be7eSDavid Ahern } else if (!(cfg->dev->flags & IFF_UP)) { 3125ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Nexthop device is not up"); 3126ab84be7eSDavid Ahern err = -ENETDOWN; 3127ab84be7eSDavid Ahern goto out; 3128ab84be7eSDavid Ahern } else if (!netif_carrier_ok(cfg->dev)) { 3129ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down"); 3130ab84be7eSDavid Ahern err = -ENETDOWN; 3131ab84be7eSDavid Ahern goto out; 3132ab84be7eSDavid Ahern } 313338428d68SRoopa Prabhu } 3134ab84be7eSDavid Ahern 3135597cfe4fSDavid Ahern err = -EINVAL; 3136597cfe4fSDavid Ahern if (tb[NHA_GATEWAY]) { 3137597cfe4fSDavid Ahern struct nlattr *gwa = tb[NHA_GATEWAY]; 3138597cfe4fSDavid Ahern 3139597cfe4fSDavid Ahern switch (cfg->nh_family) { 3140597cfe4fSDavid Ahern case AF_INET: 3141597cfe4fSDavid Ahern if (nla_len(gwa) != sizeof(u32)) { 3142597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid gateway"); 3143597cfe4fSDavid Ahern goto out; 3144597cfe4fSDavid Ahern } 3145597cfe4fSDavid Ahern cfg->gw.ipv4 = nla_get_be32(gwa); 3146597cfe4fSDavid Ahern break; 314753010f99SDavid Ahern case AF_INET6: 314853010f99SDavid Ahern if (nla_len(gwa) != sizeof(struct in6_addr)) { 314953010f99SDavid Ahern NL_SET_ERR_MSG(extack, "Invalid gateway"); 315053010f99SDavid Ahern goto out; 315153010f99SDavid Ahern } 315253010f99SDavid Ahern cfg->gw.ipv6 = nla_get_in6_addr(gwa); 315353010f99SDavid Ahern break; 3154597cfe4fSDavid Ahern default: 3155597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, 3156597cfe4fSDavid Ahern "Unknown address family for gateway"); 3157597cfe4fSDavid Ahern goto out; 3158597cfe4fSDavid Ahern } 3159597cfe4fSDavid Ahern } else { 3160597cfe4fSDavid Ahern /* device only nexthop (no gateway) */ 3161597cfe4fSDavid Ahern if (cfg->nh_flags & RTNH_F_ONLINK) { 3162597cfe4fSDavid Ahern NL_SET_ERR_MSG(extack, 3163597cfe4fSDavid Ahern "ONLINK flag can not be set for nexthop without a gateway"); 3164597cfe4fSDavid Ahern goto out; 3165597cfe4fSDavid Ahern } 3166597cfe4fSDavid Ahern } 3167597cfe4fSDavid Ahern 3168b513bd03SDavid Ahern if (tb[NHA_ENCAP]) { 3169b513bd03SDavid Ahern cfg->nh_encap = tb[NHA_ENCAP]; 3170b513bd03SDavid Ahern 3171b513bd03SDavid Ahern if (!tb[NHA_ENCAP_TYPE]) { 3172b513bd03SDavid Ahern NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing"); 3173b513bd03SDavid Ahern goto out; 3174b513bd03SDavid Ahern } 3175b513bd03SDavid Ahern 3176b513bd03SDavid Ahern cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]); 3177b513bd03SDavid Ahern err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack); 3178b513bd03SDavid Ahern if (err < 0) 3179b513bd03SDavid Ahern goto out; 3180b513bd03SDavid Ahern 3181b513bd03SDavid Ahern } else if (tb[NHA_ENCAP_TYPE]) { 3182b513bd03SDavid Ahern NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing"); 3183b513bd03SDavid Ahern goto out; 3184b513bd03SDavid Ahern } 3185b513bd03SDavid Ahern 3186746c19a5SIdo Schimmel if (tb[NHA_HW_STATS_ENABLE]) { 3187746c19a5SIdo Schimmel NL_SET_ERR_MSG(extack, "Cannot enable nexthop hardware statistics for non-group nexthops"); 3188746c19a5SIdo Schimmel goto out; 3189746c19a5SIdo Schimmel } 3190b513bd03SDavid Ahern 3191ab84be7eSDavid Ahern err = 0; 3192ab84be7eSDavid Ahern out: 3193ab84be7eSDavid Ahern return err; 3194ab84be7eSDavid Ahern } 3195ab84be7eSDavid Ahern 3196ab84be7eSDavid Ahern /* rtnl */ 3197ab84be7eSDavid Ahern static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh, 3198ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3199ab84be7eSDavid Ahern { 3200ab84be7eSDavid Ahern struct net *net = sock_net(skb->sk); 3201ab84be7eSDavid Ahern struct nh_config cfg; 3202ab84be7eSDavid Ahern struct nexthop *nh; 3203ab84be7eSDavid Ahern int err; 3204ab84be7eSDavid Ahern 3205ab84be7eSDavid Ahern err = rtm_to_nh_config(net, skb, nlh, &cfg, extack); 3206ab84be7eSDavid Ahern if (!err) { 3207ab84be7eSDavid Ahern nh = nexthop_add(net, &cfg, extack); 3208ab84be7eSDavid Ahern if (IS_ERR(nh)) 3209ab84be7eSDavid Ahern err = PTR_ERR(nh); 3210ab84be7eSDavid Ahern } 3211ab84be7eSDavid Ahern 3212ab84be7eSDavid Ahern return err; 3213ab84be7eSDavid Ahern } 3214ab84be7eSDavid Ahern 32152118f939SPetr Machata static int nh_valid_get_del_req(const struct nlmsghdr *nlh, 3216a207eab1SPetr Machata struct nlattr **tb, u32 *id, u32 *op_flags, 3217ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3218ab84be7eSDavid Ahern { 3219ab84be7eSDavid Ahern struct nhmsg *nhm = nlmsg_data(nlh); 32200bccf8edSPetr Machata 32210bccf8edSPetr Machata if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) { 32220bccf8edSPetr Machata NL_SET_ERR_MSG(extack, "Invalid values in header"); 32230bccf8edSPetr Machata return -EINVAL; 32240bccf8edSPetr Machata } 32250bccf8edSPetr Machata 32260bccf8edSPetr Machata if (!tb[NHA_ID]) { 32270bccf8edSPetr Machata NL_SET_ERR_MSG(extack, "Nexthop id is missing"); 32280bccf8edSPetr Machata return -EINVAL; 32290bccf8edSPetr Machata } 32300bccf8edSPetr Machata 32310bccf8edSPetr Machata *id = nla_get_u32(tb[NHA_ID]); 32320bccf8edSPetr Machata if (!(*id)) { 32330bccf8edSPetr Machata NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 32340bccf8edSPetr Machata return -EINVAL; 32350bccf8edSPetr Machata } 32360bccf8edSPetr Machata 3237dc5e0141SIdo Schimmel if (op_flags) { 3238a207eab1SPetr Machata if (tb[NHA_OP_FLAGS]) 3239a207eab1SPetr Machata *op_flags = nla_get_u32(tb[NHA_OP_FLAGS]); 3240a207eab1SPetr Machata else 3241a207eab1SPetr Machata *op_flags = 0; 3242dc5e0141SIdo Schimmel } 3243a207eab1SPetr Machata 32440bccf8edSPetr Machata return 0; 32450bccf8edSPetr Machata } 32460bccf8edSPetr Machata 3247ab84be7eSDavid Ahern /* rtnl */ 3248ab84be7eSDavid Ahern static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh, 3249ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3250ab84be7eSDavid Ahern { 3251d8a21070SIdo Schimmel struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_del)]; 3252ab84be7eSDavid Ahern struct net *net = sock_net(skb->sk); 3253ab84be7eSDavid Ahern struct nl_info nlinfo = { 3254ab84be7eSDavid Ahern .nlh = nlh, 3255ab84be7eSDavid Ahern .nl_net = net, 3256ab84be7eSDavid Ahern .portid = NETLINK_CB(skb).portid, 3257ab84be7eSDavid Ahern }; 3258ab84be7eSDavid Ahern struct nexthop *nh; 3259ab84be7eSDavid Ahern int err; 3260ab84be7eSDavid Ahern u32 id; 3261ab84be7eSDavid Ahern 3262d8a21070SIdo Schimmel err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, 3263d8a21070SIdo Schimmel ARRAY_SIZE(rtm_nh_policy_del) - 1, rtm_nh_policy_del, 3264d8a21070SIdo Schimmel extack); 32652118f939SPetr Machata if (err < 0) 32662118f939SPetr Machata return err; 32672118f939SPetr Machata 3268dc5e0141SIdo Schimmel err = nh_valid_get_del_req(nlh, tb, &id, NULL, extack); 3269ab84be7eSDavid Ahern if (err) 3270ab84be7eSDavid Ahern return err; 3271ab84be7eSDavid Ahern 3272ab84be7eSDavid Ahern nh = nexthop_find_by_id(net, id); 3273ab84be7eSDavid Ahern if (!nh) 3274ab84be7eSDavid Ahern return -ENOENT; 3275ab84be7eSDavid Ahern 3276430a0491SDavid Ahern remove_nexthop(net, nh, &nlinfo); 3277ab84be7eSDavid Ahern 3278ab84be7eSDavid Ahern return 0; 3279ab84be7eSDavid Ahern } 3280ab84be7eSDavid Ahern 3281ab84be7eSDavid Ahern /* rtnl */ 3282ab84be7eSDavid Ahern static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh, 3283ab84be7eSDavid Ahern struct netlink_ext_ack *extack) 3284ab84be7eSDavid Ahern { 3285d8a21070SIdo Schimmel struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_get)]; 3286ab84be7eSDavid Ahern struct net *net = sock_net(in_skb->sk); 3287ab84be7eSDavid Ahern struct sk_buff *skb = NULL; 3288ab84be7eSDavid Ahern struct nexthop *nh; 3289a207eab1SPetr Machata u32 op_flags; 3290ab84be7eSDavid Ahern int err; 3291ab84be7eSDavid Ahern u32 id; 3292ab84be7eSDavid Ahern 3293d8a21070SIdo Schimmel err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, 3294d8a21070SIdo Schimmel ARRAY_SIZE(rtm_nh_policy_get) - 1, rtm_nh_policy_get, 3295d8a21070SIdo Schimmel extack); 32962118f939SPetr Machata if (err < 0) 32972118f939SPetr Machata return err; 32982118f939SPetr Machata 3299a207eab1SPetr Machata err = nh_valid_get_del_req(nlh, tb, &id, &op_flags, extack); 3300ab84be7eSDavid Ahern if (err) 3301ab84be7eSDavid Ahern return err; 3302ab84be7eSDavid Ahern 3303ab84be7eSDavid Ahern err = -ENOBUFS; 3304ab84be7eSDavid Ahern skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 3305ab84be7eSDavid Ahern if (!skb) 3306ab84be7eSDavid Ahern goto out; 3307ab84be7eSDavid Ahern 3308ab84be7eSDavid Ahern err = -ENOENT; 3309ab84be7eSDavid Ahern nh = nexthop_find_by_id(net, id); 3310ab84be7eSDavid Ahern if (!nh) 3311ab84be7eSDavid Ahern goto errout_free; 3312ab84be7eSDavid Ahern 3313ab84be7eSDavid Ahern err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid, 331495fedd76SIdo Schimmel nlh->nlmsg_seq, 0, op_flags); 3315ab84be7eSDavid Ahern if (err < 0) { 3316ab84be7eSDavid Ahern WARN_ON(err == -EMSGSIZE); 3317ab84be7eSDavid Ahern goto errout_free; 3318ab84be7eSDavid Ahern } 3319ab84be7eSDavid Ahern 3320ab84be7eSDavid Ahern err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); 3321ab84be7eSDavid Ahern out: 3322ab84be7eSDavid Ahern return err; 3323ab84be7eSDavid Ahern errout_free: 3324ab84be7eSDavid Ahern kfree_skb(skb); 3325ab84be7eSDavid Ahern goto out; 3326ab84be7eSDavid Ahern } 3327ab84be7eSDavid Ahern 332856450ec6SPetr Machata struct nh_dump_filter { 33298a1bbabbSPetr Machata u32 nh_id; 333056450ec6SPetr Machata int dev_idx; 333156450ec6SPetr Machata int master_idx; 333256450ec6SPetr Machata bool group_filter; 333356450ec6SPetr Machata bool fdb_filter; 33348a1bbabbSPetr Machata u32 res_bucket_nh_id; 3335a207eab1SPetr Machata u32 op_flags; 333656450ec6SPetr Machata }; 333756450ec6SPetr Machata 333856450ec6SPetr Machata static bool nh_dump_filtered(struct nexthop *nh, 333956450ec6SPetr Machata struct nh_dump_filter *filter, u8 family) 3340ab84be7eSDavid Ahern { 3341ab84be7eSDavid Ahern const struct net_device *dev; 3342ab84be7eSDavid Ahern const struct nh_info *nhi; 3343ab84be7eSDavid Ahern 334456450ec6SPetr Machata if (filter->group_filter && !nh->is_group) 3345430a0491SDavid Ahern return true; 3346430a0491SDavid Ahern 334756450ec6SPetr Machata if (!filter->dev_idx && !filter->master_idx && !family) 3348ab84be7eSDavid Ahern return false; 3349ab84be7eSDavid Ahern 3350430a0491SDavid Ahern if (nh->is_group) 3351430a0491SDavid Ahern return true; 3352430a0491SDavid Ahern 3353ab84be7eSDavid Ahern nhi = rtnl_dereference(nh->nh_info); 3354ab84be7eSDavid Ahern if (family && nhi->family != family) 3355ab84be7eSDavid Ahern return true; 3356ab84be7eSDavid Ahern 3357ab84be7eSDavid Ahern dev = nhi->fib_nhc.nhc_dev; 335856450ec6SPetr Machata if (filter->dev_idx && (!dev || dev->ifindex != filter->dev_idx)) 3359ab84be7eSDavid Ahern return true; 3360ab84be7eSDavid Ahern 336156450ec6SPetr Machata if (filter->master_idx) { 3362ab84be7eSDavid Ahern struct net_device *master; 3363ab84be7eSDavid Ahern 3364ab84be7eSDavid Ahern if (!dev) 3365ab84be7eSDavid Ahern return true; 3366ab84be7eSDavid Ahern 3367ab84be7eSDavid Ahern master = netdev_master_upper_dev_get((struct net_device *)dev); 336856450ec6SPetr Machata if (!master || master->ifindex != filter->master_idx) 3369ab84be7eSDavid Ahern return true; 3370ab84be7eSDavid Ahern } 3371ab84be7eSDavid Ahern 3372ab84be7eSDavid Ahern return false; 3373ab84be7eSDavid Ahern } 3374ab84be7eSDavid Ahern 3375b9ebea12SPetr Machata static int __nh_valid_dump_req(const struct nlmsghdr *nlh, struct nlattr **tb, 337656450ec6SPetr Machata struct nh_dump_filter *filter, 3377b9ebea12SPetr Machata struct netlink_ext_ack *extack) 3378ab84be7eSDavid Ahern { 3379ab84be7eSDavid Ahern struct nhmsg *nhm; 3380ab84be7eSDavid Ahern u32 idx; 3381ab84be7eSDavid Ahern 338244551bffSPetr Machata if (tb[NHA_OIF]) { 338344551bffSPetr Machata idx = nla_get_u32(tb[NHA_OIF]); 3384ab84be7eSDavid Ahern if (idx > INT_MAX) { 3385ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid device index"); 3386ab84be7eSDavid Ahern return -EINVAL; 3387ab84be7eSDavid Ahern } 338856450ec6SPetr Machata filter->dev_idx = idx; 338944551bffSPetr Machata } 339044551bffSPetr Machata if (tb[NHA_MASTER]) { 339144551bffSPetr Machata idx = nla_get_u32(tb[NHA_MASTER]); 3392ab84be7eSDavid Ahern if (idx > INT_MAX) { 3393ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid master device index"); 3394ab84be7eSDavid Ahern return -EINVAL; 3395ab84be7eSDavid Ahern } 339656450ec6SPetr Machata filter->master_idx = idx; 3397ab84be7eSDavid Ahern } 339856450ec6SPetr Machata filter->group_filter = nla_get_flag(tb[NHA_GROUPS]); 339956450ec6SPetr Machata filter->fdb_filter = nla_get_flag(tb[NHA_FDB]); 3400ab84be7eSDavid Ahern 3401ab84be7eSDavid Ahern nhm = nlmsg_data(nlh); 3402ab84be7eSDavid Ahern if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) { 3403ab84be7eSDavid Ahern NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request"); 3404ab84be7eSDavid Ahern return -EINVAL; 3405ab84be7eSDavid Ahern } 3406ab84be7eSDavid Ahern 3407ab84be7eSDavid Ahern return 0; 3408ab84be7eSDavid Ahern } 3409ab84be7eSDavid Ahern 3410b9ebea12SPetr Machata static int nh_valid_dump_req(const struct nlmsghdr *nlh, 3411b9ebea12SPetr Machata struct nh_dump_filter *filter, 3412b9ebea12SPetr Machata struct netlink_callback *cb) 3413b9ebea12SPetr Machata { 3414d8a21070SIdo Schimmel struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_dump)]; 3415b9ebea12SPetr Machata int err; 3416b9ebea12SPetr Machata 3417d8a21070SIdo Schimmel err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, 3418d8a21070SIdo Schimmel ARRAY_SIZE(rtm_nh_policy_dump) - 1, 3419b9ebea12SPetr Machata rtm_nh_policy_dump, cb->extack); 3420b9ebea12SPetr Machata if (err < 0) 3421b9ebea12SPetr Machata return err; 3422b9ebea12SPetr Machata 3423262a68aaSIdo Schimmel if (tb[NHA_OP_FLAGS]) 3424262a68aaSIdo Schimmel filter->op_flags = nla_get_u32(tb[NHA_OP_FLAGS]); 3425262a68aaSIdo Schimmel else 3426262a68aaSIdo Schimmel filter->op_flags = 0; 3427262a68aaSIdo Schimmel 3428b9ebea12SPetr Machata return __nh_valid_dump_req(nlh, tb, filter, cb->extack); 3429b9ebea12SPetr Machata } 3430b9ebea12SPetr Machata 3431a6fbbaa6SPetr Machata struct rtm_dump_nh_ctx { 3432a6fbbaa6SPetr Machata u32 idx; 3433a6fbbaa6SPetr Machata }; 3434a6fbbaa6SPetr Machata 3435a6fbbaa6SPetr Machata static struct rtm_dump_nh_ctx * 3436a6fbbaa6SPetr Machata rtm_dump_nh_ctx(struct netlink_callback *cb) 3437a6fbbaa6SPetr Machata { 3438a6fbbaa6SPetr Machata struct rtm_dump_nh_ctx *ctx = (void *)cb->ctx; 3439a6fbbaa6SPetr Machata 3440a6fbbaa6SPetr Machata BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); 3441a6fbbaa6SPetr Machata return ctx; 3442a6fbbaa6SPetr Machata } 3443a6fbbaa6SPetr Machata 3444cbee1807SPetr Machata static int rtm_dump_walk_nexthops(struct sk_buff *skb, 3445cbee1807SPetr Machata struct netlink_callback *cb, 3446cbee1807SPetr Machata struct rb_root *root, 3447cbee1807SPetr Machata struct rtm_dump_nh_ctx *ctx, 3448e948217dSPetr Machata int (*nh_cb)(struct sk_buff *skb, 3449e948217dSPetr Machata struct netlink_callback *cb, 3450e948217dSPetr Machata struct nexthop *nh, void *data), 3451e948217dSPetr Machata void *data) 3452ab84be7eSDavid Ahern { 3453ab84be7eSDavid Ahern struct rb_node *node; 34549e46fb65SIdo Schimmel int s_idx; 3455ab84be7eSDavid Ahern int err; 3456ab84be7eSDavid Ahern 3457a6fbbaa6SPetr Machata s_idx = ctx->idx; 3458ab84be7eSDavid Ahern for (node = rb_first(root); node; node = rb_next(node)) { 3459ab84be7eSDavid Ahern struct nexthop *nh; 3460ab84be7eSDavid Ahern 3461ab84be7eSDavid Ahern nh = rb_entry(node, struct nexthop, rb_node); 34629e46fb65SIdo Schimmel if (nh->id < s_idx) 34639e46fb65SIdo Schimmel continue; 34649e46fb65SIdo Schimmel 34659e46fb65SIdo Schimmel ctx->idx = nh->id; 3466e948217dSPetr Machata err = nh_cb(skb, cb, nh, data); 3467e948217dSPetr Machata if (err) 3468cbee1807SPetr Machata return err; 3469cbee1807SPetr Machata } 3470cbee1807SPetr Machata 3471cbee1807SPetr Machata return 0; 3472cbee1807SPetr Machata } 3473cbee1807SPetr Machata 3474e948217dSPetr Machata static int rtm_dump_nexthop_cb(struct sk_buff *skb, struct netlink_callback *cb, 3475e948217dSPetr Machata struct nexthop *nh, void *data) 3476e948217dSPetr Machata { 3477e948217dSPetr Machata struct nhmsg *nhm = nlmsg_data(cb->nlh); 3478e948217dSPetr Machata struct nh_dump_filter *filter = data; 3479e948217dSPetr Machata 3480e948217dSPetr Machata if (nh_dump_filtered(nh, filter, nhm->nh_family)) 3481e948217dSPetr Machata return 0; 3482e948217dSPetr Machata 3483e948217dSPetr Machata return nh_fill_node(skb, nh, RTM_NEWNEXTHOP, 3484e948217dSPetr Machata NETLINK_CB(cb->skb).portid, 348595fedd76SIdo Schimmel cb->nlh->nlmsg_seq, NLM_F_MULTI, filter->op_flags); 3486e948217dSPetr Machata } 3487e948217dSPetr Machata 3488cbee1807SPetr Machata /* rtnl */ 3489cbee1807SPetr Machata static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb) 3490cbee1807SPetr Machata { 3491cbee1807SPetr Machata struct rtm_dump_nh_ctx *ctx = rtm_dump_nh_ctx(cb); 3492cbee1807SPetr Machata struct net *net = sock_net(skb->sk); 3493cbee1807SPetr Machata struct rb_root *root = &net->nexthop.rb_root; 3494cbee1807SPetr Machata struct nh_dump_filter filter = {}; 3495cbee1807SPetr Machata int err; 3496cbee1807SPetr Machata 3497cbee1807SPetr Machata err = nh_valid_dump_req(cb->nlh, &filter, cb); 3498cbee1807SPetr Machata if (err < 0) 3499cbee1807SPetr Machata return err; 3500cbee1807SPetr Machata 3501e948217dSPetr Machata err = rtm_dump_walk_nexthops(skb, cb, root, ctx, 3502e948217dSPetr Machata &rtm_dump_nexthop_cb, &filter); 3503ab84be7eSDavid Ahern 3504ab84be7eSDavid Ahern cb->seq = net->nexthop.seq; 3505ab84be7eSDavid Ahern nl_dump_check_consistent(cb, nlmsg_hdr(skb)); 3506ab84be7eSDavid Ahern return err; 3507ab84be7eSDavid Ahern } 3508ab84be7eSDavid Ahern 35098a1bbabbSPetr Machata static struct nexthop * 35108a1bbabbSPetr Machata nexthop_find_group_resilient(struct net *net, u32 id, 35118a1bbabbSPetr Machata struct netlink_ext_ack *extack) 35128a1bbabbSPetr Machata { 35138a1bbabbSPetr Machata struct nh_group *nhg; 35148a1bbabbSPetr Machata struct nexthop *nh; 35158a1bbabbSPetr Machata 35168a1bbabbSPetr Machata nh = nexthop_find_by_id(net, id); 35178a1bbabbSPetr Machata if (!nh) 35188a1bbabbSPetr Machata return ERR_PTR(-ENOENT); 35198a1bbabbSPetr Machata 35208a1bbabbSPetr Machata if (!nh->is_group) { 35218a1bbabbSPetr Machata NL_SET_ERR_MSG(extack, "Not a nexthop group"); 35228a1bbabbSPetr Machata return ERR_PTR(-EINVAL); 35238a1bbabbSPetr Machata } 35248a1bbabbSPetr Machata 35258a1bbabbSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 35268a1bbabbSPetr Machata if (!nhg->resilient) { 35278a1bbabbSPetr Machata NL_SET_ERR_MSG(extack, "Nexthop group not of type resilient"); 35288a1bbabbSPetr Machata return ERR_PTR(-EINVAL); 35298a1bbabbSPetr Machata } 35308a1bbabbSPetr Machata 35318a1bbabbSPetr Machata return nh; 35328a1bbabbSPetr Machata } 35338a1bbabbSPetr Machata 35348a1bbabbSPetr Machata static int nh_valid_dump_nhid(struct nlattr *attr, u32 *nh_id_p, 35358a1bbabbSPetr Machata struct netlink_ext_ack *extack) 35368a1bbabbSPetr Machata { 35378a1bbabbSPetr Machata u32 idx; 35388a1bbabbSPetr Machata 35398a1bbabbSPetr Machata if (attr) { 35408a1bbabbSPetr Machata idx = nla_get_u32(attr); 35418a1bbabbSPetr Machata if (!idx) { 35428a1bbabbSPetr Machata NL_SET_ERR_MSG(extack, "Invalid nexthop id"); 35438a1bbabbSPetr Machata return -EINVAL; 35448a1bbabbSPetr Machata } 35458a1bbabbSPetr Machata *nh_id_p = idx; 35468a1bbabbSPetr Machata } else { 35478a1bbabbSPetr Machata *nh_id_p = 0; 35488a1bbabbSPetr Machata } 35498a1bbabbSPetr Machata 35508a1bbabbSPetr Machata return 0; 35518a1bbabbSPetr Machata } 35528a1bbabbSPetr Machata 35538a1bbabbSPetr Machata static int nh_valid_dump_bucket_req(const struct nlmsghdr *nlh, 35548a1bbabbSPetr Machata struct nh_dump_filter *filter, 35558a1bbabbSPetr Machata struct netlink_callback *cb) 35568a1bbabbSPetr Machata { 35578a1bbabbSPetr Machata struct nlattr *res_tb[ARRAY_SIZE(rtm_nh_res_bucket_policy_dump)]; 3558d8a21070SIdo Schimmel struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_dump_bucket)]; 35598a1bbabbSPetr Machata int err; 35608a1bbabbSPetr Machata 3561d8a21070SIdo Schimmel err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, 3562d8a21070SIdo Schimmel ARRAY_SIZE(rtm_nh_policy_dump_bucket) - 1, 35638a1bbabbSPetr Machata rtm_nh_policy_dump_bucket, NULL); 35648a1bbabbSPetr Machata if (err < 0) 35658a1bbabbSPetr Machata return err; 35668a1bbabbSPetr Machata 35678a1bbabbSPetr Machata err = nh_valid_dump_nhid(tb[NHA_ID], &filter->nh_id, cb->extack); 35688a1bbabbSPetr Machata if (err) 35698a1bbabbSPetr Machata return err; 35708a1bbabbSPetr Machata 35718a1bbabbSPetr Machata if (tb[NHA_RES_BUCKET]) { 35728a1bbabbSPetr Machata size_t max = ARRAY_SIZE(rtm_nh_res_bucket_policy_dump) - 1; 35738a1bbabbSPetr Machata 35748a1bbabbSPetr Machata err = nla_parse_nested(res_tb, max, 35758a1bbabbSPetr Machata tb[NHA_RES_BUCKET], 35768a1bbabbSPetr Machata rtm_nh_res_bucket_policy_dump, 35778a1bbabbSPetr Machata cb->extack); 35788a1bbabbSPetr Machata if (err < 0) 35798a1bbabbSPetr Machata return err; 35808a1bbabbSPetr Machata 35818a1bbabbSPetr Machata err = nh_valid_dump_nhid(res_tb[NHA_RES_BUCKET_NH_ID], 35828a1bbabbSPetr Machata &filter->res_bucket_nh_id, 35838a1bbabbSPetr Machata cb->extack); 35848a1bbabbSPetr Machata if (err) 35858a1bbabbSPetr Machata return err; 35868a1bbabbSPetr Machata } 35878a1bbabbSPetr Machata 35888a1bbabbSPetr Machata return __nh_valid_dump_req(nlh, tb, filter, cb->extack); 35898a1bbabbSPetr Machata } 35908a1bbabbSPetr Machata 35918a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx { 35928a1bbabbSPetr Machata struct rtm_dump_nh_ctx nh; 35938a1bbabbSPetr Machata u16 bucket_index; 35948a1bbabbSPetr Machata }; 35958a1bbabbSPetr Machata 35968a1bbabbSPetr Machata static struct rtm_dump_res_bucket_ctx * 35978a1bbabbSPetr Machata rtm_dump_res_bucket_ctx(struct netlink_callback *cb) 35988a1bbabbSPetr Machata { 35998a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx *ctx = (void *)cb->ctx; 36008a1bbabbSPetr Machata 36018a1bbabbSPetr Machata BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); 36028a1bbabbSPetr Machata return ctx; 36038a1bbabbSPetr Machata } 36048a1bbabbSPetr Machata 36058a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data { 36068a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx *ctx; 36078a1bbabbSPetr Machata struct nh_dump_filter filter; 36088a1bbabbSPetr Machata }; 36098a1bbabbSPetr Machata 36108a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket_nh(struct sk_buff *skb, 36118a1bbabbSPetr Machata struct netlink_callback *cb, 36128a1bbabbSPetr Machata struct nexthop *nh, 36138a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data *dd) 36148a1bbabbSPetr Machata { 36158a1bbabbSPetr Machata u32 portid = NETLINK_CB(cb->skb).portid; 36168a1bbabbSPetr Machata struct nhmsg *nhm = nlmsg_data(cb->nlh); 36178a1bbabbSPetr Machata struct nh_res_table *res_table; 36188a1bbabbSPetr Machata struct nh_group *nhg; 36198a1bbabbSPetr Machata u16 bucket_index; 36208a1bbabbSPetr Machata int err; 36218a1bbabbSPetr Machata 36228a1bbabbSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 36238a1bbabbSPetr Machata res_table = rtnl_dereference(nhg->res_table); 36248a1bbabbSPetr Machata for (bucket_index = dd->ctx->bucket_index; 36258a1bbabbSPetr Machata bucket_index < res_table->num_nh_buckets; 36268a1bbabbSPetr Machata bucket_index++) { 36278a1bbabbSPetr Machata struct nh_res_bucket *bucket; 36288a1bbabbSPetr Machata struct nh_grp_entry *nhge; 36298a1bbabbSPetr Machata 36308a1bbabbSPetr Machata bucket = &res_table->nh_buckets[bucket_index]; 36318a1bbabbSPetr Machata nhge = rtnl_dereference(bucket->nh_entry); 36328a1bbabbSPetr Machata if (nh_dump_filtered(nhge->nh, &dd->filter, nhm->nh_family)) 36338a1bbabbSPetr Machata continue; 36348a1bbabbSPetr Machata 36358a1bbabbSPetr Machata if (dd->filter.res_bucket_nh_id && 36368a1bbabbSPetr Machata dd->filter.res_bucket_nh_id != nhge->nh->id) 36378a1bbabbSPetr Machata continue; 36388a1bbabbSPetr Machata 3639f10d3d9dSIdo Schimmel dd->ctx->bucket_index = bucket_index; 36408a1bbabbSPetr Machata err = nh_fill_res_bucket(skb, nh, bucket, bucket_index, 36418a1bbabbSPetr Machata RTM_NEWNEXTHOPBUCKET, portid, 36428a1bbabbSPetr Machata cb->nlh->nlmsg_seq, NLM_F_MULTI, 36438a1bbabbSPetr Machata cb->extack); 3644f10d3d9dSIdo Schimmel if (err) 3645f10d3d9dSIdo Schimmel return err; 36468a1bbabbSPetr Machata } 36478a1bbabbSPetr Machata 3648f10d3d9dSIdo Schimmel dd->ctx->bucket_index = 0; 36498a1bbabbSPetr Machata 3650f10d3d9dSIdo Schimmel return 0; 36518a1bbabbSPetr Machata } 36528a1bbabbSPetr Machata 36538a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket_cb(struct sk_buff *skb, 36548a1bbabbSPetr Machata struct netlink_callback *cb, 36558a1bbabbSPetr Machata struct nexthop *nh, void *data) 36568a1bbabbSPetr Machata { 36578a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data *dd = data; 36588a1bbabbSPetr Machata struct nh_group *nhg; 36598a1bbabbSPetr Machata 36608a1bbabbSPetr Machata if (!nh->is_group) 36618a1bbabbSPetr Machata return 0; 36628a1bbabbSPetr Machata 36638a1bbabbSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 36648a1bbabbSPetr Machata if (!nhg->resilient) 36658a1bbabbSPetr Machata return 0; 36668a1bbabbSPetr Machata 36678a1bbabbSPetr Machata return rtm_dump_nexthop_bucket_nh(skb, cb, nh, dd); 36688a1bbabbSPetr Machata } 36698a1bbabbSPetr Machata 36708a1bbabbSPetr Machata /* rtnl */ 36718a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket(struct sk_buff *skb, 36728a1bbabbSPetr Machata struct netlink_callback *cb) 36738a1bbabbSPetr Machata { 36748a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx *ctx = rtm_dump_res_bucket_ctx(cb); 36758a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data dd = { .ctx = ctx }; 36768a1bbabbSPetr Machata struct net *net = sock_net(skb->sk); 36778a1bbabbSPetr Machata struct nexthop *nh; 36788a1bbabbSPetr Machata int err; 36798a1bbabbSPetr Machata 36808a1bbabbSPetr Machata err = nh_valid_dump_bucket_req(cb->nlh, &dd.filter, cb); 36818a1bbabbSPetr Machata if (err) 36828a1bbabbSPetr Machata return err; 36838a1bbabbSPetr Machata 36848a1bbabbSPetr Machata if (dd.filter.nh_id) { 36858a1bbabbSPetr Machata nh = nexthop_find_group_resilient(net, dd.filter.nh_id, 36868a1bbabbSPetr Machata cb->extack); 36878a1bbabbSPetr Machata if (IS_ERR(nh)) 36888a1bbabbSPetr Machata return PTR_ERR(nh); 36898a1bbabbSPetr Machata err = rtm_dump_nexthop_bucket_nh(skb, cb, nh, &dd); 36908a1bbabbSPetr Machata } else { 36918a1bbabbSPetr Machata struct rb_root *root = &net->nexthop.rb_root; 36928a1bbabbSPetr Machata 36938a1bbabbSPetr Machata err = rtm_dump_walk_nexthops(skb, cb, root, &ctx->nh, 36948a1bbabbSPetr Machata &rtm_dump_nexthop_bucket_cb, &dd); 36958a1bbabbSPetr Machata } 36968a1bbabbSPetr Machata 36978a1bbabbSPetr Machata cb->seq = net->nexthop.seq; 36988a1bbabbSPetr Machata nl_dump_check_consistent(cb, nlmsg_hdr(skb)); 36998a1bbabbSPetr Machata return err; 37008a1bbabbSPetr Machata } 37018a1bbabbSPetr Machata 3702187d4c6bSPetr Machata static int nh_valid_get_bucket_req_res_bucket(struct nlattr *res, 3703187d4c6bSPetr Machata u16 *bucket_index, 3704187d4c6bSPetr Machata struct netlink_ext_ack *extack) 3705187d4c6bSPetr Machata { 3706187d4c6bSPetr Machata struct nlattr *tb[ARRAY_SIZE(rtm_nh_res_bucket_policy_get)]; 3707187d4c6bSPetr Machata int err; 3708187d4c6bSPetr Machata 3709187d4c6bSPetr Machata err = nla_parse_nested(tb, ARRAY_SIZE(rtm_nh_res_bucket_policy_get) - 1, 3710187d4c6bSPetr Machata res, rtm_nh_res_bucket_policy_get, extack); 3711187d4c6bSPetr Machata if (err < 0) 3712187d4c6bSPetr Machata return err; 3713187d4c6bSPetr Machata 3714187d4c6bSPetr Machata if (!tb[NHA_RES_BUCKET_INDEX]) { 3715187d4c6bSPetr Machata NL_SET_ERR_MSG(extack, "Bucket index is missing"); 3716187d4c6bSPetr Machata return -EINVAL; 3717187d4c6bSPetr Machata } 3718187d4c6bSPetr Machata 3719187d4c6bSPetr Machata *bucket_index = nla_get_u16(tb[NHA_RES_BUCKET_INDEX]); 3720187d4c6bSPetr Machata return 0; 3721187d4c6bSPetr Machata } 3722187d4c6bSPetr Machata 3723187d4c6bSPetr Machata static int nh_valid_get_bucket_req(const struct nlmsghdr *nlh, 3724187d4c6bSPetr Machata u32 *id, u16 *bucket_index, 3725187d4c6bSPetr Machata struct netlink_ext_ack *extack) 3726187d4c6bSPetr Machata { 3727d8a21070SIdo Schimmel struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_get_bucket)]; 3728187d4c6bSPetr Machata int err; 3729187d4c6bSPetr Machata 3730d8a21070SIdo Schimmel err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb, 3731d8a21070SIdo Schimmel ARRAY_SIZE(rtm_nh_policy_get_bucket) - 1, 3732187d4c6bSPetr Machata rtm_nh_policy_get_bucket, extack); 3733187d4c6bSPetr Machata if (err < 0) 3734187d4c6bSPetr Machata return err; 3735187d4c6bSPetr Machata 3736dc5e0141SIdo Schimmel err = nh_valid_get_del_req(nlh, tb, id, NULL, extack); 3737187d4c6bSPetr Machata if (err) 3738187d4c6bSPetr Machata return err; 3739187d4c6bSPetr Machata 3740187d4c6bSPetr Machata if (!tb[NHA_RES_BUCKET]) { 3741187d4c6bSPetr Machata NL_SET_ERR_MSG(extack, "Bucket information is missing"); 3742187d4c6bSPetr Machata return -EINVAL; 3743187d4c6bSPetr Machata } 3744187d4c6bSPetr Machata 3745187d4c6bSPetr Machata err = nh_valid_get_bucket_req_res_bucket(tb[NHA_RES_BUCKET], 3746187d4c6bSPetr Machata bucket_index, extack); 3747187d4c6bSPetr Machata if (err) 3748187d4c6bSPetr Machata return err; 3749187d4c6bSPetr Machata 3750187d4c6bSPetr Machata return 0; 3751187d4c6bSPetr Machata } 3752187d4c6bSPetr Machata 3753187d4c6bSPetr Machata /* rtnl */ 3754187d4c6bSPetr Machata static int rtm_get_nexthop_bucket(struct sk_buff *in_skb, struct nlmsghdr *nlh, 3755187d4c6bSPetr Machata struct netlink_ext_ack *extack) 3756187d4c6bSPetr Machata { 3757187d4c6bSPetr Machata struct net *net = sock_net(in_skb->sk); 3758187d4c6bSPetr Machata struct nh_res_table *res_table; 3759187d4c6bSPetr Machata struct sk_buff *skb = NULL; 3760187d4c6bSPetr Machata struct nh_group *nhg; 3761187d4c6bSPetr Machata struct nexthop *nh; 3762187d4c6bSPetr Machata u16 bucket_index; 3763187d4c6bSPetr Machata int err; 3764187d4c6bSPetr Machata u32 id; 3765187d4c6bSPetr Machata 3766187d4c6bSPetr Machata err = nh_valid_get_bucket_req(nlh, &id, &bucket_index, extack); 3767187d4c6bSPetr Machata if (err) 3768187d4c6bSPetr Machata return err; 3769187d4c6bSPetr Machata 3770187d4c6bSPetr Machata nh = nexthop_find_group_resilient(net, id, extack); 3771187d4c6bSPetr Machata if (IS_ERR(nh)) 3772187d4c6bSPetr Machata return PTR_ERR(nh); 3773187d4c6bSPetr Machata 3774187d4c6bSPetr Machata nhg = rtnl_dereference(nh->nh_grp); 3775187d4c6bSPetr Machata res_table = rtnl_dereference(nhg->res_table); 3776187d4c6bSPetr Machata if (bucket_index >= res_table->num_nh_buckets) { 3777187d4c6bSPetr Machata NL_SET_ERR_MSG(extack, "Bucket index out of bounds"); 3778187d4c6bSPetr Machata return -ENOENT; 3779187d4c6bSPetr Machata } 3780187d4c6bSPetr Machata 3781187d4c6bSPetr Machata skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 3782187d4c6bSPetr Machata if (!skb) 3783187d4c6bSPetr Machata return -ENOBUFS; 3784187d4c6bSPetr Machata 3785187d4c6bSPetr Machata err = nh_fill_res_bucket(skb, nh, &res_table->nh_buckets[bucket_index], 3786187d4c6bSPetr Machata bucket_index, RTM_NEWNEXTHOPBUCKET, 3787187d4c6bSPetr Machata NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, 3788187d4c6bSPetr Machata 0, extack); 3789187d4c6bSPetr Machata if (err < 0) { 3790187d4c6bSPetr Machata WARN_ON(err == -EMSGSIZE); 3791187d4c6bSPetr Machata goto errout_free; 3792187d4c6bSPetr Machata } 3793187d4c6bSPetr Machata 3794187d4c6bSPetr Machata return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); 3795187d4c6bSPetr Machata 3796187d4c6bSPetr Machata errout_free: 3797187d4c6bSPetr Machata kfree_skb(skb); 3798187d4c6bSPetr Machata return err; 3799187d4c6bSPetr Machata } 3800187d4c6bSPetr Machata 3801597cfe4fSDavid Ahern static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu) 3802597cfe4fSDavid Ahern { 3803597cfe4fSDavid Ahern unsigned int hash = nh_dev_hashfn(dev->ifindex); 3804597cfe4fSDavid Ahern struct net *net = dev_net(dev); 3805597cfe4fSDavid Ahern struct hlist_head *head = &net->nexthop.devhash[hash]; 3806597cfe4fSDavid Ahern struct hlist_node *n; 3807597cfe4fSDavid Ahern struct nh_info *nhi; 3808597cfe4fSDavid Ahern 3809597cfe4fSDavid Ahern hlist_for_each_entry_safe(nhi, n, head, dev_hash) { 3810597cfe4fSDavid Ahern if (nhi->fib_nhc.nhc_dev == dev) { 3811597cfe4fSDavid Ahern if (nhi->family == AF_INET) 3812597cfe4fSDavid Ahern fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu, 3813597cfe4fSDavid Ahern orig_mtu); 3814597cfe4fSDavid Ahern } 3815597cfe4fSDavid Ahern } 3816597cfe4fSDavid Ahern } 3817597cfe4fSDavid Ahern 3818597cfe4fSDavid Ahern /* rtnl */ 3819597cfe4fSDavid Ahern static int nh_netdev_event(struct notifier_block *this, 3820597cfe4fSDavid Ahern unsigned long event, void *ptr) 3821597cfe4fSDavid Ahern { 3822597cfe4fSDavid Ahern struct net_device *dev = netdev_notifier_info_to_dev(ptr); 3823597cfe4fSDavid Ahern struct netdev_notifier_info_ext *info_ext; 3824597cfe4fSDavid Ahern 3825597cfe4fSDavid Ahern switch (event) { 3826597cfe4fSDavid Ahern case NETDEV_DOWN: 3827597cfe4fSDavid Ahern case NETDEV_UNREGISTER: 382876c03bf8SIdo Schimmel nexthop_flush_dev(dev, event); 3829597cfe4fSDavid Ahern break; 3830597cfe4fSDavid Ahern case NETDEV_CHANGE: 3831597cfe4fSDavid Ahern if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP))) 383276c03bf8SIdo Schimmel nexthop_flush_dev(dev, event); 3833597cfe4fSDavid Ahern break; 3834597cfe4fSDavid Ahern case NETDEV_CHANGEMTU: 3835597cfe4fSDavid Ahern info_ext = ptr; 3836597cfe4fSDavid Ahern nexthop_sync_mtu(dev, info_ext->ext.mtu); 3837597cfe4fSDavid Ahern rt_cache_flush(dev_net(dev)); 3838597cfe4fSDavid Ahern break; 3839597cfe4fSDavid Ahern } 3840597cfe4fSDavid Ahern return NOTIFY_DONE; 3841597cfe4fSDavid Ahern } 3842597cfe4fSDavid Ahern 3843597cfe4fSDavid Ahern static struct notifier_block nh_netdev_notifier = { 3844597cfe4fSDavid Ahern .notifier_call = nh_netdev_event, 3845597cfe4fSDavid Ahern }; 3846597cfe4fSDavid Ahern 3847975ff7f3SIdo Schimmel static int nexthops_dump(struct net *net, struct notifier_block *nb, 38483106a084SIdo Schimmel enum nexthop_event_type event_type, 3849975ff7f3SIdo Schimmel struct netlink_ext_ack *extack) 3850975ff7f3SIdo Schimmel { 3851975ff7f3SIdo Schimmel struct rb_root *root = &net->nexthop.rb_root; 3852975ff7f3SIdo Schimmel struct rb_node *node; 3853975ff7f3SIdo Schimmel int err = 0; 3854975ff7f3SIdo Schimmel 3855975ff7f3SIdo Schimmel for (node = rb_first(root); node; node = rb_next(node)) { 3856975ff7f3SIdo Schimmel struct nexthop *nh; 3857975ff7f3SIdo Schimmel 3858975ff7f3SIdo Schimmel nh = rb_entry(node, struct nexthop, rb_node); 38593106a084SIdo Schimmel err = call_nexthop_notifier(nb, net, event_type, nh, extack); 3860975ff7f3SIdo Schimmel if (err) 3861975ff7f3SIdo Schimmel break; 3862975ff7f3SIdo Schimmel } 3863975ff7f3SIdo Schimmel 3864975ff7f3SIdo Schimmel return err; 3865975ff7f3SIdo Schimmel } 3866975ff7f3SIdo Schimmel 3867ce7e9c8aSIdo Schimmel int register_nexthop_notifier(struct net *net, struct notifier_block *nb, 3868ce7e9c8aSIdo Schimmel struct netlink_ext_ack *extack) 38698590ceedSRoopa Prabhu { 3870975ff7f3SIdo Schimmel int err; 3871975ff7f3SIdo Schimmel 3872975ff7f3SIdo Schimmel rtnl_lock(); 38733106a084SIdo Schimmel err = nexthops_dump(net, nb, NEXTHOP_EVENT_REPLACE, extack); 3874975ff7f3SIdo Schimmel if (err) 3875975ff7f3SIdo Schimmel goto unlock; 3876975ff7f3SIdo Schimmel err = blocking_notifier_chain_register(&net->nexthop.notifier_chain, 387780690ec6SIdo Schimmel nb); 3878975ff7f3SIdo Schimmel unlock: 3879975ff7f3SIdo Schimmel rtnl_unlock(); 3880975ff7f3SIdo Schimmel return err; 38818590ceedSRoopa Prabhu } 38828590ceedSRoopa Prabhu EXPORT_SYMBOL(register_nexthop_notifier); 38838590ceedSRoopa Prabhu 388470f16ea2SEric Dumazet int __unregister_nexthop_notifier(struct net *net, struct notifier_block *nb) 388570f16ea2SEric Dumazet { 388670f16ea2SEric Dumazet int err; 388770f16ea2SEric Dumazet 388870f16ea2SEric Dumazet err = blocking_notifier_chain_unregister(&net->nexthop.notifier_chain, 388970f16ea2SEric Dumazet nb); 389070f16ea2SEric Dumazet if (!err) 389170f16ea2SEric Dumazet nexthops_dump(net, nb, NEXTHOP_EVENT_DEL, NULL); 389270f16ea2SEric Dumazet return err; 389370f16ea2SEric Dumazet } 389470f16ea2SEric Dumazet EXPORT_SYMBOL(__unregister_nexthop_notifier); 389570f16ea2SEric Dumazet 38968590ceedSRoopa Prabhu int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb) 38978590ceedSRoopa Prabhu { 38983106a084SIdo Schimmel int err; 38993106a084SIdo Schimmel 39003106a084SIdo Schimmel rtnl_lock(); 390170f16ea2SEric Dumazet err = __unregister_nexthop_notifier(net, nb); 39023106a084SIdo Schimmel rtnl_unlock(); 39033106a084SIdo Schimmel return err; 39048590ceedSRoopa Prabhu } 39058590ceedSRoopa Prabhu EXPORT_SYMBOL(unregister_nexthop_notifier); 39068590ceedSRoopa Prabhu 3907e95f2592SIdo Schimmel void nexthop_set_hw_flags(struct net *net, u32 id, bool offload, bool trap) 3908e95f2592SIdo Schimmel { 3909e95f2592SIdo Schimmel struct nexthop *nexthop; 3910e95f2592SIdo Schimmel 3911e95f2592SIdo Schimmel rcu_read_lock(); 3912e95f2592SIdo Schimmel 3913e95f2592SIdo Schimmel nexthop = nexthop_find_by_id(net, id); 3914e95f2592SIdo Schimmel if (!nexthop) 3915e95f2592SIdo Schimmel goto out; 3916e95f2592SIdo Schimmel 3917e95f2592SIdo Schimmel nexthop->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP); 3918e95f2592SIdo Schimmel if (offload) 3919e95f2592SIdo Schimmel nexthop->nh_flags |= RTNH_F_OFFLOAD; 3920e95f2592SIdo Schimmel if (trap) 3921e95f2592SIdo Schimmel nexthop->nh_flags |= RTNH_F_TRAP; 3922e95f2592SIdo Schimmel 3923e95f2592SIdo Schimmel out: 3924e95f2592SIdo Schimmel rcu_read_unlock(); 3925e95f2592SIdo Schimmel } 3926e95f2592SIdo Schimmel EXPORT_SYMBOL(nexthop_set_hw_flags); 3927e95f2592SIdo Schimmel 392856ad5ba3SIdo Schimmel void nexthop_bucket_set_hw_flags(struct net *net, u32 id, u16 bucket_index, 392956ad5ba3SIdo Schimmel bool offload, bool trap) 393056ad5ba3SIdo Schimmel { 393156ad5ba3SIdo Schimmel struct nh_res_table *res_table; 393256ad5ba3SIdo Schimmel struct nh_res_bucket *bucket; 393356ad5ba3SIdo Schimmel struct nexthop *nexthop; 393456ad5ba3SIdo Schimmel struct nh_group *nhg; 393556ad5ba3SIdo Schimmel 393656ad5ba3SIdo Schimmel rcu_read_lock(); 393756ad5ba3SIdo Schimmel 393856ad5ba3SIdo Schimmel nexthop = nexthop_find_by_id(net, id); 393956ad5ba3SIdo Schimmel if (!nexthop || !nexthop->is_group) 394056ad5ba3SIdo Schimmel goto out; 394156ad5ba3SIdo Schimmel 394256ad5ba3SIdo Schimmel nhg = rcu_dereference(nexthop->nh_grp); 394356ad5ba3SIdo Schimmel if (!nhg->resilient) 394456ad5ba3SIdo Schimmel goto out; 394556ad5ba3SIdo Schimmel 394656ad5ba3SIdo Schimmel if (bucket_index >= nhg->res_table->num_nh_buckets) 394756ad5ba3SIdo Schimmel goto out; 394856ad5ba3SIdo Schimmel 394956ad5ba3SIdo Schimmel res_table = rcu_dereference(nhg->res_table); 395056ad5ba3SIdo Schimmel bucket = &res_table->nh_buckets[bucket_index]; 395156ad5ba3SIdo Schimmel bucket->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP); 395256ad5ba3SIdo Schimmel if (offload) 395356ad5ba3SIdo Schimmel bucket->nh_flags |= RTNH_F_OFFLOAD; 395456ad5ba3SIdo Schimmel if (trap) 395556ad5ba3SIdo Schimmel bucket->nh_flags |= RTNH_F_TRAP; 395656ad5ba3SIdo Schimmel 395756ad5ba3SIdo Schimmel out: 395856ad5ba3SIdo Schimmel rcu_read_unlock(); 395956ad5ba3SIdo Schimmel } 396056ad5ba3SIdo Schimmel EXPORT_SYMBOL(nexthop_bucket_set_hw_flags); 396156ad5ba3SIdo Schimmel 3962cfc15c1dSIdo Schimmel void nexthop_res_grp_activity_update(struct net *net, u32 id, u16 num_buckets, 3963cfc15c1dSIdo Schimmel unsigned long *activity) 3964cfc15c1dSIdo Schimmel { 3965cfc15c1dSIdo Schimmel struct nh_res_table *res_table; 3966cfc15c1dSIdo Schimmel struct nexthop *nexthop; 3967cfc15c1dSIdo Schimmel struct nh_group *nhg; 3968cfc15c1dSIdo Schimmel u16 i; 3969cfc15c1dSIdo Schimmel 3970cfc15c1dSIdo Schimmel rcu_read_lock(); 3971cfc15c1dSIdo Schimmel 3972cfc15c1dSIdo Schimmel nexthop = nexthop_find_by_id(net, id); 3973cfc15c1dSIdo Schimmel if (!nexthop || !nexthop->is_group) 3974cfc15c1dSIdo Schimmel goto out; 3975cfc15c1dSIdo Schimmel 3976cfc15c1dSIdo Schimmel nhg = rcu_dereference(nexthop->nh_grp); 3977cfc15c1dSIdo Schimmel if (!nhg->resilient) 3978cfc15c1dSIdo Schimmel goto out; 3979cfc15c1dSIdo Schimmel 3980cfc15c1dSIdo Schimmel /* Instead of silently ignoring some buckets, demand that the sizes 3981cfc15c1dSIdo Schimmel * be the same. 3982cfc15c1dSIdo Schimmel */ 3983cfc15c1dSIdo Schimmel res_table = rcu_dereference(nhg->res_table); 3984cfc15c1dSIdo Schimmel if (num_buckets != res_table->num_nh_buckets) 3985cfc15c1dSIdo Schimmel goto out; 3986cfc15c1dSIdo Schimmel 3987cfc15c1dSIdo Schimmel for (i = 0; i < num_buckets; i++) { 3988cfc15c1dSIdo Schimmel if (test_bit(i, activity)) 3989cfc15c1dSIdo Schimmel nh_res_bucket_set_busy(&res_table->nh_buckets[i]); 3990cfc15c1dSIdo Schimmel } 3991cfc15c1dSIdo Schimmel 3992cfc15c1dSIdo Schimmel out: 3993cfc15c1dSIdo Schimmel rcu_read_unlock(); 3994cfc15c1dSIdo Schimmel } 3995cfc15c1dSIdo Schimmel EXPORT_SYMBOL(nexthop_res_grp_activity_update); 3996cfc15c1dSIdo Schimmel 3997a7ec2512SEric Dumazet static void __net_exit nexthop_net_exit_batch_rtnl(struct list_head *net_list, 3998a7ec2512SEric Dumazet struct list_head *dev_to_kill) 3999ab84be7eSDavid Ahern { 4000fea7b201SEric Dumazet struct net *net; 4001fea7b201SEric Dumazet 4002a7ec2512SEric Dumazet ASSERT_RTNL(); 4003a7ec2512SEric Dumazet list_for_each_entry(net, net_list, exit_list) 4004ab84be7eSDavid Ahern flush_all_nexthops(net); 4005ab84be7eSDavid Ahern } 4006a7ec2512SEric Dumazet 4007a7ec2512SEric Dumazet static void __net_exit nexthop_net_exit(struct net *net) 4008a7ec2512SEric Dumazet { 4009a7ec2512SEric Dumazet kfree(net->nexthop.devhash); 4010a7ec2512SEric Dumazet net->nexthop.devhash = NULL; 4011fea7b201SEric Dumazet } 4012ab84be7eSDavid Ahern 4013ab84be7eSDavid Ahern static int __net_init nexthop_net_init(struct net *net) 4014ab84be7eSDavid Ahern { 4015597cfe4fSDavid Ahern size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE; 4016597cfe4fSDavid Ahern 4017ab84be7eSDavid Ahern net->nexthop.rb_root = RB_ROOT; 4018597cfe4fSDavid Ahern net->nexthop.devhash = kzalloc(sz, GFP_KERNEL); 4019597cfe4fSDavid Ahern if (!net->nexthop.devhash) 4020597cfe4fSDavid Ahern return -ENOMEM; 402180690ec6SIdo Schimmel BLOCKING_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain); 4022ab84be7eSDavid Ahern 4023ab84be7eSDavid Ahern return 0; 4024ab84be7eSDavid Ahern } 4025ab84be7eSDavid Ahern 4026ab84be7eSDavid Ahern static struct pernet_operations nexthop_net_ops = { 4027ab84be7eSDavid Ahern .init = nexthop_net_init, 4028a7ec2512SEric Dumazet .exit = nexthop_net_exit, 4029a7ec2512SEric Dumazet .exit_batch_rtnl = nexthop_net_exit_batch_rtnl, 4030ab84be7eSDavid Ahern }; 4031ab84be7eSDavid Ahern 4032ab84be7eSDavid Ahern static int __init nexthop_init(void) 4033ab84be7eSDavid Ahern { 4034ab84be7eSDavid Ahern register_pernet_subsys(&nexthop_net_ops); 4035ab84be7eSDavid Ahern 4036597cfe4fSDavid Ahern register_netdevice_notifier(&nh_netdev_notifier); 4037597cfe4fSDavid Ahern 4038ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 4039ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0); 4040ab84be7eSDavid Ahern rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop, 4041ab84be7eSDavid Ahern rtm_dump_nexthop, 0); 4042ab84be7eSDavid Ahern 4043ab84be7eSDavid Ahern rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 4044ab84be7eSDavid Ahern rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0); 4045ab84be7eSDavid Ahern 4046ab84be7eSDavid Ahern rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0); 4047ab84be7eSDavid Ahern rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0); 4048ab84be7eSDavid Ahern 4049187d4c6bSPetr Machata rtnl_register(PF_UNSPEC, RTM_GETNEXTHOPBUCKET, rtm_get_nexthop_bucket, 40508a1bbabbSPetr Machata rtm_dump_nexthop_bucket, 0); 40518a1bbabbSPetr Machata 4052ab84be7eSDavid Ahern return 0; 4053ab84be7eSDavid Ahern } 4054ab84be7eSDavid Ahern subsys_initcall(nexthop_init); 4055