xref: /linux/net/ipv4/nexthop.c (revision b6459415b384cb829f0b2a4268f211c789f6cf0b)
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>
11*b6459415SJakub 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 
29643d0878SPetr Machata static const struct nla_policy rtm_nh_policy_new[] = {
30ab84be7eSDavid Ahern 	[NHA_ID]		= { .type = NLA_U32 },
31ab84be7eSDavid Ahern 	[NHA_GROUP]		= { .type = NLA_BINARY },
32ab84be7eSDavid Ahern 	[NHA_GROUP_TYPE]	= { .type = NLA_U16 },
33ab84be7eSDavid Ahern 	[NHA_BLACKHOLE]		= { .type = NLA_FLAG },
34ab84be7eSDavid Ahern 	[NHA_OIF]		= { .type = NLA_U32 },
35ab84be7eSDavid Ahern 	[NHA_GATEWAY]		= { .type = NLA_BINARY },
36ab84be7eSDavid Ahern 	[NHA_ENCAP_TYPE]	= { .type = NLA_U16 },
37ab84be7eSDavid Ahern 	[NHA_ENCAP]		= { .type = NLA_NESTED },
3838428d68SRoopa Prabhu 	[NHA_FDB]		= { .type = NLA_FLAG },
39a2601e2bSPetr Machata 	[NHA_RES_GROUP]		= { .type = NLA_NESTED },
40ab84be7eSDavid Ahern };
41ab84be7eSDavid Ahern 
4260f5ad5eSPetr Machata static const struct nla_policy rtm_nh_policy_get[] = {
4360f5ad5eSPetr Machata 	[NHA_ID]		= { .type = NLA_U32 },
4460f5ad5eSPetr Machata };
4560f5ad5eSPetr Machata 
4644551bffSPetr Machata static const struct nla_policy rtm_nh_policy_dump[] = {
4744551bffSPetr Machata 	[NHA_OIF]		= { .type = NLA_U32 },
4844551bffSPetr Machata 	[NHA_GROUPS]		= { .type = NLA_FLAG },
4944551bffSPetr Machata 	[NHA_MASTER]		= { .type = NLA_U32 },
5044551bffSPetr Machata 	[NHA_FDB]		= { .type = NLA_FLAG },
5144551bffSPetr Machata };
5244551bffSPetr Machata 
53a2601e2bSPetr Machata static const struct nla_policy rtm_nh_res_policy_new[] = {
54a2601e2bSPetr Machata 	[NHA_RES_GROUP_BUCKETS]			= { .type = NLA_U16 },
55a2601e2bSPetr Machata 	[NHA_RES_GROUP_IDLE_TIMER]		= { .type = NLA_U32 },
56a2601e2bSPetr Machata 	[NHA_RES_GROUP_UNBALANCED_TIMER]	= { .type = NLA_U32 },
57a2601e2bSPetr Machata };
58a2601e2bSPetr Machata 
598a1bbabbSPetr Machata static const struct nla_policy rtm_nh_policy_dump_bucket[] = {
608a1bbabbSPetr Machata 	[NHA_ID]		= { .type = NLA_U32 },
618a1bbabbSPetr Machata 	[NHA_OIF]		= { .type = NLA_U32 },
628a1bbabbSPetr Machata 	[NHA_MASTER]		= { .type = NLA_U32 },
638a1bbabbSPetr Machata 	[NHA_RES_BUCKET]	= { .type = NLA_NESTED },
648a1bbabbSPetr Machata };
658a1bbabbSPetr Machata 
668a1bbabbSPetr Machata static const struct nla_policy rtm_nh_res_bucket_policy_dump[] = {
678a1bbabbSPetr Machata 	[NHA_RES_BUCKET_NH_ID]	= { .type = NLA_U32 },
688a1bbabbSPetr Machata };
698a1bbabbSPetr Machata 
70187d4c6bSPetr Machata static const struct nla_policy rtm_nh_policy_get_bucket[] = {
71187d4c6bSPetr Machata 	[NHA_ID]		= { .type = NLA_U32 },
72187d4c6bSPetr Machata 	[NHA_RES_BUCKET]	= { .type = NLA_NESTED },
73187d4c6bSPetr Machata };
74187d4c6bSPetr Machata 
75187d4c6bSPetr Machata static const struct nla_policy rtm_nh_res_bucket_policy_get[] = {
76187d4c6bSPetr Machata 	[NHA_RES_BUCKET_INDEX]	= { .type = NLA_U16 },
77187d4c6bSPetr Machata };
78187d4c6bSPetr Machata 
795ca474f2SIdo Schimmel static bool nexthop_notifiers_is_empty(struct net *net)
805ca474f2SIdo Schimmel {
815ca474f2SIdo Schimmel 	return !net->nexthop.notifier_chain.head;
825ca474f2SIdo Schimmel }
835ca474f2SIdo Schimmel 
845ca474f2SIdo Schimmel static void
855ca474f2SIdo Schimmel __nh_notifier_single_info_init(struct nh_notifier_single_info *nh_info,
8696a85625SPetr Machata 			       const struct nh_info *nhi)
875ca474f2SIdo Schimmel {
885ca474f2SIdo Schimmel 	nh_info->dev = nhi->fib_nhc.nhc_dev;
895ca474f2SIdo Schimmel 	nh_info->gw_family = nhi->fib_nhc.nhc_gw_family;
905ca474f2SIdo Schimmel 	if (nh_info->gw_family == AF_INET)
915ca474f2SIdo Schimmel 		nh_info->ipv4 = nhi->fib_nhc.nhc_gw.ipv4;
925ca474f2SIdo Schimmel 	else if (nh_info->gw_family == AF_INET6)
935ca474f2SIdo Schimmel 		nh_info->ipv6 = nhi->fib_nhc.nhc_gw.ipv6;
945ca474f2SIdo Schimmel 
955ca474f2SIdo Schimmel 	nh_info->is_reject = nhi->reject_nh;
965ca474f2SIdo Schimmel 	nh_info->is_fdb = nhi->fdb_nh;
975ca474f2SIdo Schimmel 	nh_info->has_encap = !!nhi->fib_nhc.nhc_lwtstate;
985ca474f2SIdo Schimmel }
995ca474f2SIdo Schimmel 
1005ca474f2SIdo Schimmel static int nh_notifier_single_info_init(struct nh_notifier_info *info,
1015ca474f2SIdo Schimmel 					const struct nexthop *nh)
1025ca474f2SIdo Schimmel {
10396a85625SPetr Machata 	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
10496a85625SPetr Machata 
10509ad6becSIdo Schimmel 	info->type = NH_NOTIFIER_INFO_TYPE_SINGLE;
1065ca474f2SIdo Schimmel 	info->nh = kzalloc(sizeof(*info->nh), GFP_KERNEL);
1075ca474f2SIdo Schimmel 	if (!info->nh)
1085ca474f2SIdo Schimmel 		return -ENOMEM;
1095ca474f2SIdo Schimmel 
11096a85625SPetr Machata 	__nh_notifier_single_info_init(info->nh, nhi);
1115ca474f2SIdo Schimmel 
1125ca474f2SIdo Schimmel 	return 0;
1135ca474f2SIdo Schimmel }
1145ca474f2SIdo Schimmel 
1155ca474f2SIdo Schimmel static void nh_notifier_single_info_fini(struct nh_notifier_info *info)
1165ca474f2SIdo Schimmel {
1175ca474f2SIdo Schimmel 	kfree(info->nh);
1185ca474f2SIdo Schimmel }
1195ca474f2SIdo Schimmel 
120de1d1ee3SPetr Machata static int nh_notifier_mpath_info_init(struct nh_notifier_info *info,
121da230501SPetr Machata 				       struct nh_group *nhg)
1225ca474f2SIdo Schimmel {
1235ca474f2SIdo Schimmel 	u16 num_nh = nhg->num_nh;
1245ca474f2SIdo Schimmel 	int i;
1255ca474f2SIdo Schimmel 
12609ad6becSIdo Schimmel 	info->type = NH_NOTIFIER_INFO_TYPE_GRP;
1275ca474f2SIdo Schimmel 	info->nh_grp = kzalloc(struct_size(info->nh_grp, nh_entries, num_nh),
1285ca474f2SIdo Schimmel 			       GFP_KERNEL);
1295ca474f2SIdo Schimmel 	if (!info->nh_grp)
1305ca474f2SIdo Schimmel 		return -ENOMEM;
1315ca474f2SIdo Schimmel 
1325ca474f2SIdo Schimmel 	info->nh_grp->num_nh = num_nh;
1335ca474f2SIdo Schimmel 	info->nh_grp->is_fdb = nhg->fdb_nh;
1345ca474f2SIdo Schimmel 
1355ca474f2SIdo Schimmel 	for (i = 0; i < num_nh; i++) {
1365ca474f2SIdo Schimmel 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
13796a85625SPetr Machata 		struct nh_info *nhi;
1385ca474f2SIdo Schimmel 
13996a85625SPetr Machata 		nhi = rtnl_dereference(nhge->nh->nh_info);
1405ca474f2SIdo Schimmel 		info->nh_grp->nh_entries[i].id = nhge->nh->id;
1415ca474f2SIdo Schimmel 		info->nh_grp->nh_entries[i].weight = nhge->weight;
1425ca474f2SIdo Schimmel 		__nh_notifier_single_info_init(&info->nh_grp->nh_entries[i].nh,
14396a85625SPetr Machata 					       nhi);
1445ca474f2SIdo Schimmel 	}
1455ca474f2SIdo Schimmel 
1465ca474f2SIdo Schimmel 	return 0;
1475ca474f2SIdo Schimmel }
1485ca474f2SIdo Schimmel 
1497c37c7e0SPetr Machata static int nh_notifier_res_table_info_init(struct nh_notifier_info *info,
1507c37c7e0SPetr Machata 					   struct nh_group *nhg)
1517c37c7e0SPetr Machata {
1527c37c7e0SPetr Machata 	struct nh_res_table *res_table = rtnl_dereference(nhg->res_table);
1537c37c7e0SPetr Machata 	u16 num_nh_buckets = res_table->num_nh_buckets;
1547c37c7e0SPetr Machata 	unsigned long size;
1557c37c7e0SPetr Machata 	u16 i;
1567c37c7e0SPetr Machata 
1577c37c7e0SPetr Machata 	info->type = NH_NOTIFIER_INFO_TYPE_RES_TABLE;
1587c37c7e0SPetr Machata 	size = struct_size(info->nh_res_table, nhs, num_nh_buckets);
1597c37c7e0SPetr Machata 	info->nh_res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO |
1607c37c7e0SPetr Machata 				       __GFP_NOWARN);
1617c37c7e0SPetr Machata 	if (!info->nh_res_table)
1627c37c7e0SPetr Machata 		return -ENOMEM;
1637c37c7e0SPetr Machata 
1647c37c7e0SPetr Machata 	info->nh_res_table->num_nh_buckets = num_nh_buckets;
1657c37c7e0SPetr Machata 
1667c37c7e0SPetr Machata 	for (i = 0; i < num_nh_buckets; i++) {
1677c37c7e0SPetr Machata 		struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
1687c37c7e0SPetr Machata 		struct nh_grp_entry *nhge;
1697c37c7e0SPetr Machata 		struct nh_info *nhi;
1707c37c7e0SPetr Machata 
1717c37c7e0SPetr Machata 		nhge = rtnl_dereference(bucket->nh_entry);
1727c37c7e0SPetr Machata 		nhi = rtnl_dereference(nhge->nh->nh_info);
1737c37c7e0SPetr Machata 		__nh_notifier_single_info_init(&info->nh_res_table->nhs[i],
1747c37c7e0SPetr Machata 					       nhi);
1757c37c7e0SPetr Machata 	}
1767c37c7e0SPetr Machata 
1777c37c7e0SPetr Machata 	return 0;
1787c37c7e0SPetr Machata }
1797c37c7e0SPetr Machata 
180da230501SPetr Machata static int nh_notifier_grp_info_init(struct nh_notifier_info *info,
181da230501SPetr Machata 				     const struct nexthop *nh)
1825ca474f2SIdo Schimmel {
183da230501SPetr Machata 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
184da230501SPetr Machata 
185de1d1ee3SPetr Machata 	if (nhg->hash_threshold)
186de1d1ee3SPetr Machata 		return nh_notifier_mpath_info_init(info, nhg);
1877c37c7e0SPetr Machata 	else if (nhg->resilient)
1887c37c7e0SPetr Machata 		return nh_notifier_res_table_info_init(info, nhg);
189da230501SPetr Machata 	return -EINVAL;
190da230501SPetr Machata }
191da230501SPetr Machata 
192da230501SPetr Machata static void nh_notifier_grp_info_fini(struct nh_notifier_info *info,
193da230501SPetr Machata 				      const struct nexthop *nh)
194da230501SPetr Machata {
195da230501SPetr Machata 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
196da230501SPetr Machata 
197de1d1ee3SPetr Machata 	if (nhg->hash_threshold)
1985ca474f2SIdo Schimmel 		kfree(info->nh_grp);
1997c37c7e0SPetr Machata 	else if (nhg->resilient)
2007c37c7e0SPetr Machata 		vfree(info->nh_res_table);
2015ca474f2SIdo Schimmel }
2025ca474f2SIdo Schimmel 
2035ca474f2SIdo Schimmel static int nh_notifier_info_init(struct nh_notifier_info *info,
2045ca474f2SIdo Schimmel 				 const struct nexthop *nh)
2055ca474f2SIdo Schimmel {
2065ca474f2SIdo Schimmel 	info->id = nh->id;
2075ca474f2SIdo Schimmel 
20809ad6becSIdo Schimmel 	if (nh->is_group)
2095ca474f2SIdo Schimmel 		return nh_notifier_grp_info_init(info, nh);
2105ca474f2SIdo Schimmel 	else
2115ca474f2SIdo Schimmel 		return nh_notifier_single_info_init(info, nh);
2125ca474f2SIdo Schimmel }
2135ca474f2SIdo Schimmel 
21409ad6becSIdo Schimmel static void nh_notifier_info_fini(struct nh_notifier_info *info,
21509ad6becSIdo Schimmel 				  const struct nexthop *nh)
2165ca474f2SIdo Schimmel {
21709ad6becSIdo Schimmel 	if (nh->is_group)
218da230501SPetr Machata 		nh_notifier_grp_info_fini(info, nh);
2195ca474f2SIdo Schimmel 	else
2205ca474f2SIdo Schimmel 		nh_notifier_single_info_fini(info);
2215ca474f2SIdo Schimmel }
2225ca474f2SIdo Schimmel 
2238590ceedSRoopa Prabhu static int call_nexthop_notifiers(struct net *net,
224d8e79f1dSNathan Chancellor 				  enum nexthop_event_type event_type,
2253578d53dSIdo Schimmel 				  struct nexthop *nh,
2263578d53dSIdo Schimmel 				  struct netlink_ext_ack *extack)
2278590ceedSRoopa Prabhu {
2285ca474f2SIdo Schimmel 	struct nh_notifier_info info = {
2295ca474f2SIdo Schimmel 		.net = net,
2305ca474f2SIdo Schimmel 		.extack = extack,
2315ca474f2SIdo Schimmel 	};
2328590ceedSRoopa Prabhu 	int err;
2338590ceedSRoopa Prabhu 
2345ca474f2SIdo Schimmel 	ASSERT_RTNL();
2355ca474f2SIdo Schimmel 
2365ca474f2SIdo Schimmel 	if (nexthop_notifiers_is_empty(net))
2375ca474f2SIdo Schimmel 		return 0;
2385ca474f2SIdo Schimmel 
2395ca474f2SIdo Schimmel 	err = nh_notifier_info_init(&info, nh);
2405ca474f2SIdo Schimmel 	if (err) {
2415ca474f2SIdo Schimmel 		NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info");
2425ca474f2SIdo Schimmel 		return err;
2435ca474f2SIdo Schimmel 	}
2445ca474f2SIdo Schimmel 
24580690ec6SIdo Schimmel 	err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
2461ec69d18SIdo Schimmel 					   event_type, &info);
24709ad6becSIdo Schimmel 	nh_notifier_info_fini(&info, nh);
2485ca474f2SIdo Schimmel 
2498590ceedSRoopa Prabhu 	return notifier_to_errno(err);
2508590ceedSRoopa Prabhu }
2518590ceedSRoopa Prabhu 
2527c37c7e0SPetr Machata static int
2537c37c7e0SPetr Machata nh_notifier_res_bucket_idle_timer_get(const struct nh_notifier_info *info,
2547c37c7e0SPetr Machata 				      bool force, unsigned int *p_idle_timer_ms)
2557c37c7e0SPetr Machata {
2567c37c7e0SPetr Machata 	struct nh_res_table *res_table;
2577c37c7e0SPetr Machata 	struct nh_group *nhg;
2587c37c7e0SPetr Machata 	struct nexthop *nh;
2597c37c7e0SPetr Machata 	int err = 0;
2607c37c7e0SPetr Machata 
2617c37c7e0SPetr Machata 	/* When 'force' is false, nexthop bucket replacement is performed
2627c37c7e0SPetr Machata 	 * because the bucket was deemed to be idle. In this case, capable
2637c37c7e0SPetr Machata 	 * listeners can choose to perform an atomic replacement: The bucket is
2647c37c7e0SPetr Machata 	 * only replaced if it is inactive. However, if the idle timer interval
2657c37c7e0SPetr Machata 	 * is smaller than the interval in which a listener is querying
2667c37c7e0SPetr Machata 	 * buckets' activity from the device, then atomic replacement should
2677c37c7e0SPetr Machata 	 * not be tried. Pass the idle timer value to listeners, so that they
2687c37c7e0SPetr Machata 	 * could determine which type of replacement to perform.
2697c37c7e0SPetr Machata 	 */
2707c37c7e0SPetr Machata 	if (force) {
2717c37c7e0SPetr Machata 		*p_idle_timer_ms = 0;
2727c37c7e0SPetr Machata 		return 0;
2737c37c7e0SPetr Machata 	}
2747c37c7e0SPetr Machata 
2757c37c7e0SPetr Machata 	rcu_read_lock();
2767c37c7e0SPetr Machata 
2777c37c7e0SPetr Machata 	nh = nexthop_find_by_id(info->net, info->id);
2787c37c7e0SPetr Machata 	if (!nh) {
2797c37c7e0SPetr Machata 		err = -EINVAL;
2807c37c7e0SPetr Machata 		goto out;
2817c37c7e0SPetr Machata 	}
2827c37c7e0SPetr Machata 
2837c37c7e0SPetr Machata 	nhg = rcu_dereference(nh->nh_grp);
2847c37c7e0SPetr Machata 	res_table = rcu_dereference(nhg->res_table);
2857c37c7e0SPetr Machata 	*p_idle_timer_ms = jiffies_to_msecs(res_table->idle_timer);
2867c37c7e0SPetr Machata 
2877c37c7e0SPetr Machata out:
2887c37c7e0SPetr Machata 	rcu_read_unlock();
2897c37c7e0SPetr Machata 
2907c37c7e0SPetr Machata 	return err;
2917c37c7e0SPetr Machata }
2927c37c7e0SPetr Machata 
2937c37c7e0SPetr Machata static int nh_notifier_res_bucket_info_init(struct nh_notifier_info *info,
2947c37c7e0SPetr Machata 					    u16 bucket_index, bool force,
2957c37c7e0SPetr Machata 					    struct nh_info *oldi,
2967c37c7e0SPetr Machata 					    struct nh_info *newi)
2977c37c7e0SPetr Machata {
2987c37c7e0SPetr Machata 	unsigned int idle_timer_ms;
2997c37c7e0SPetr Machata 	int err;
3007c37c7e0SPetr Machata 
3017c37c7e0SPetr Machata 	err = nh_notifier_res_bucket_idle_timer_get(info, force,
3027c37c7e0SPetr Machata 						    &idle_timer_ms);
3037c37c7e0SPetr Machata 	if (err)
3047c37c7e0SPetr Machata 		return err;
3057c37c7e0SPetr Machata 
3067c37c7e0SPetr Machata 	info->type = NH_NOTIFIER_INFO_TYPE_RES_BUCKET;
3077c37c7e0SPetr Machata 	info->nh_res_bucket = kzalloc(sizeof(*info->nh_res_bucket),
3087c37c7e0SPetr Machata 				      GFP_KERNEL);
3097c37c7e0SPetr Machata 	if (!info->nh_res_bucket)
3107c37c7e0SPetr Machata 		return -ENOMEM;
3117c37c7e0SPetr Machata 
3127c37c7e0SPetr Machata 	info->nh_res_bucket->bucket_index = bucket_index;
3137c37c7e0SPetr Machata 	info->nh_res_bucket->idle_timer_ms = idle_timer_ms;
3147c37c7e0SPetr Machata 	info->nh_res_bucket->force = force;
3157c37c7e0SPetr Machata 	__nh_notifier_single_info_init(&info->nh_res_bucket->old_nh, oldi);
3167c37c7e0SPetr Machata 	__nh_notifier_single_info_init(&info->nh_res_bucket->new_nh, newi);
3177c37c7e0SPetr Machata 	return 0;
3187c37c7e0SPetr Machata }
3197c37c7e0SPetr Machata 
3207c37c7e0SPetr Machata static void nh_notifier_res_bucket_info_fini(struct nh_notifier_info *info)
3217c37c7e0SPetr Machata {
3227c37c7e0SPetr Machata 	kfree(info->nh_res_bucket);
3237c37c7e0SPetr Machata }
3247c37c7e0SPetr Machata 
3257c37c7e0SPetr Machata static int __call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id,
3267c37c7e0SPetr Machata 					       u16 bucket_index, bool force,
3277c37c7e0SPetr Machata 					       struct nh_info *oldi,
3287c37c7e0SPetr Machata 					       struct nh_info *newi,
3297c37c7e0SPetr Machata 					       struct netlink_ext_ack *extack)
3307c37c7e0SPetr Machata {
3317c37c7e0SPetr Machata 	struct nh_notifier_info info = {
3327c37c7e0SPetr Machata 		.net = net,
3337c37c7e0SPetr Machata 		.extack = extack,
3347c37c7e0SPetr Machata 		.id = nhg_id,
3357c37c7e0SPetr Machata 	};
3367c37c7e0SPetr Machata 	int err;
3377c37c7e0SPetr Machata 
3387c37c7e0SPetr Machata 	if (nexthop_notifiers_is_empty(net))
3397c37c7e0SPetr Machata 		return 0;
3407c37c7e0SPetr Machata 
3417c37c7e0SPetr Machata 	err = nh_notifier_res_bucket_info_init(&info, bucket_index, force,
3427c37c7e0SPetr Machata 					       oldi, newi);
3437c37c7e0SPetr Machata 	if (err)
3447c37c7e0SPetr Machata 		return err;
3457c37c7e0SPetr Machata 
3467c37c7e0SPetr Machata 	err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
3477c37c7e0SPetr Machata 					   NEXTHOP_EVENT_BUCKET_REPLACE, &info);
3487c37c7e0SPetr Machata 	nh_notifier_res_bucket_info_fini(&info);
3497c37c7e0SPetr Machata 
3507c37c7e0SPetr Machata 	return notifier_to_errno(err);
3517c37c7e0SPetr Machata }
3527c37c7e0SPetr Machata 
353283a72a5SPetr Machata /* There are three users of RES_TABLE, and NHs etc. referenced from there:
354283a72a5SPetr Machata  *
355283a72a5SPetr Machata  * 1) a collection of callbacks for NH maintenance. This operates under
356283a72a5SPetr Machata  *    RTNL,
357283a72a5SPetr Machata  * 2) the delayed work that gradually balances the resilient table,
358283a72a5SPetr Machata  * 3) and nexthop_select_path(), operating under RCU.
359283a72a5SPetr Machata  *
360283a72a5SPetr Machata  * Both the delayed work and the RTNL block are writers, and need to
361283a72a5SPetr Machata  * maintain mutual exclusion. Since there are only two and well-known
362283a72a5SPetr Machata  * writers for each table, the RTNL code can make sure it has exclusive
363283a72a5SPetr Machata  * access thus:
364283a72a5SPetr Machata  *
365283a72a5SPetr Machata  * - Have the DW operate without locking;
366283a72a5SPetr Machata  * - synchronously cancel the DW;
367283a72a5SPetr Machata  * - do the writing;
368283a72a5SPetr Machata  * - if the write was not actually a delete, call upkeep, which schedules
369283a72a5SPetr Machata  *   DW again if necessary.
370283a72a5SPetr Machata  *
371283a72a5SPetr Machata  * The functions that are always called from the RTNL context use
372283a72a5SPetr Machata  * rtnl_dereference(). The functions that can also be called from the DW do
373283a72a5SPetr Machata  * a raw dereference and rely on the above mutual exclusion scheme.
374283a72a5SPetr Machata  */
375283a72a5SPetr Machata #define nh_res_dereference(p) (rcu_dereference_raw(p))
376283a72a5SPetr Machata 
3777c37c7e0SPetr Machata static int call_nexthop_res_bucket_notifiers(struct net *net, u32 nhg_id,
3787c37c7e0SPetr Machata 					     u16 bucket_index, bool force,
3797c37c7e0SPetr Machata 					     struct nexthop *old_nh,
3807c37c7e0SPetr Machata 					     struct nexthop *new_nh,
3817c37c7e0SPetr Machata 					     struct netlink_ext_ack *extack)
3827c37c7e0SPetr Machata {
3837c37c7e0SPetr Machata 	struct nh_info *oldi = nh_res_dereference(old_nh->nh_info);
3847c37c7e0SPetr Machata 	struct nh_info *newi = nh_res_dereference(new_nh->nh_info);
3857c37c7e0SPetr Machata 
3867c37c7e0SPetr Machata 	return __call_nexthop_res_bucket_notifiers(net, nhg_id, bucket_index,
3877c37c7e0SPetr Machata 						   force, oldi, newi, extack);
3887c37c7e0SPetr Machata }
3897c37c7e0SPetr Machata 
3907c37c7e0SPetr Machata static int call_nexthop_res_table_notifiers(struct net *net, struct nexthop *nh,
3917c37c7e0SPetr Machata 					    struct netlink_ext_ack *extack)
3927c37c7e0SPetr Machata {
3937c37c7e0SPetr Machata 	struct nh_notifier_info info = {
3947c37c7e0SPetr Machata 		.net = net,
3957c37c7e0SPetr Machata 		.extack = extack,
3967c37c7e0SPetr Machata 	};
3977c37c7e0SPetr Machata 	struct nh_group *nhg;
3987c37c7e0SPetr Machata 	int err;
3997c37c7e0SPetr Machata 
4007c37c7e0SPetr Machata 	ASSERT_RTNL();
4017c37c7e0SPetr Machata 
4027c37c7e0SPetr Machata 	if (nexthop_notifiers_is_empty(net))
4037c37c7e0SPetr Machata 		return 0;
4047c37c7e0SPetr Machata 
4057c37c7e0SPetr Machata 	/* At this point, the nexthop buckets are still not populated. Only
4067c37c7e0SPetr Machata 	 * emit a notification with the logical nexthops, so that a listener
4077c37c7e0SPetr Machata 	 * could potentially veto it in case of unsupported configuration.
4087c37c7e0SPetr Machata 	 */
4097c37c7e0SPetr Machata 	nhg = rtnl_dereference(nh->nh_grp);
410de1d1ee3SPetr Machata 	err = nh_notifier_mpath_info_init(&info, nhg);
4117c37c7e0SPetr Machata 	if (err) {
4127c37c7e0SPetr Machata 		NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info");
4137c37c7e0SPetr Machata 		return err;
4147c37c7e0SPetr Machata 	}
4157c37c7e0SPetr Machata 
4167c37c7e0SPetr Machata 	err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
4177c37c7e0SPetr Machata 					   NEXTHOP_EVENT_RES_TABLE_PRE_REPLACE,
4187c37c7e0SPetr Machata 					   &info);
4197c37c7e0SPetr Machata 	kfree(info.nh_grp);
4207c37c7e0SPetr Machata 
4217c37c7e0SPetr Machata 	return notifier_to_errno(err);
4227c37c7e0SPetr Machata }
4237c37c7e0SPetr Machata 
424975ff7f3SIdo Schimmel static int call_nexthop_notifier(struct notifier_block *nb, struct net *net,
425975ff7f3SIdo Schimmel 				 enum nexthop_event_type event_type,
426975ff7f3SIdo Schimmel 				 struct nexthop *nh,
427975ff7f3SIdo Schimmel 				 struct netlink_ext_ack *extack)
428975ff7f3SIdo Schimmel {
429975ff7f3SIdo Schimmel 	struct nh_notifier_info info = {
430975ff7f3SIdo Schimmel 		.net = net,
431975ff7f3SIdo Schimmel 		.extack = extack,
432975ff7f3SIdo Schimmel 	};
433975ff7f3SIdo Schimmel 	int err;
434975ff7f3SIdo Schimmel 
435975ff7f3SIdo Schimmel 	err = nh_notifier_info_init(&info, nh);
436975ff7f3SIdo Schimmel 	if (err)
437975ff7f3SIdo Schimmel 		return err;
438975ff7f3SIdo Schimmel 
439975ff7f3SIdo Schimmel 	err = nb->notifier_call(nb, event_type, &info);
44009ad6becSIdo Schimmel 	nh_notifier_info_fini(&info, nh);
441975ff7f3SIdo Schimmel 
442975ff7f3SIdo Schimmel 	return notifier_to_errno(err);
443975ff7f3SIdo Schimmel }
444975ff7f3SIdo Schimmel 
445597cfe4fSDavid Ahern static unsigned int nh_dev_hashfn(unsigned int val)
446597cfe4fSDavid Ahern {
447597cfe4fSDavid Ahern 	unsigned int mask = NH_DEV_HASHSIZE - 1;
448597cfe4fSDavid Ahern 
449597cfe4fSDavid Ahern 	return (val ^
450597cfe4fSDavid Ahern 		(val >> NH_DEV_HASHBITS) ^
451597cfe4fSDavid Ahern 		(val >> (NH_DEV_HASHBITS * 2))) & mask;
452597cfe4fSDavid Ahern }
453597cfe4fSDavid Ahern 
454597cfe4fSDavid Ahern static void nexthop_devhash_add(struct net *net, struct nh_info *nhi)
455597cfe4fSDavid Ahern {
456597cfe4fSDavid Ahern 	struct net_device *dev = nhi->fib_nhc.nhc_dev;
457597cfe4fSDavid Ahern 	struct hlist_head *head;
458597cfe4fSDavid Ahern 	unsigned int hash;
459597cfe4fSDavid Ahern 
460597cfe4fSDavid Ahern 	WARN_ON(!dev);
461597cfe4fSDavid Ahern 
462597cfe4fSDavid Ahern 	hash = nh_dev_hashfn(dev->ifindex);
463597cfe4fSDavid Ahern 	head = &net->nexthop.devhash[hash];
464597cfe4fSDavid Ahern 	hlist_add_head(&nhi->dev_hash, head);
465597cfe4fSDavid Ahern }
466597cfe4fSDavid Ahern 
4675d1f0f09SDavid Ahern static void nexthop_free_group(struct nexthop *nh)
468ab84be7eSDavid Ahern {
469430a0491SDavid Ahern 	struct nh_group *nhg;
470430a0491SDavid Ahern 	int i;
471430a0491SDavid Ahern 
472430a0491SDavid Ahern 	nhg = rcu_dereference_raw(nh->nh_grp);
47390f33bffSNikolay Aleksandrov 	for (i = 0; i < nhg->num_nh; ++i) {
47490f33bffSNikolay Aleksandrov 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
475430a0491SDavid Ahern 
47690f33bffSNikolay Aleksandrov 		WARN_ON(!list_empty(&nhge->nh_list));
47790f33bffSNikolay Aleksandrov 		nexthop_put(nhge->nh);
47890f33bffSNikolay Aleksandrov 	}
47990f33bffSNikolay Aleksandrov 
48090f33bffSNikolay Aleksandrov 	WARN_ON(nhg->spare == nhg);
48190f33bffSNikolay Aleksandrov 
482283a72a5SPetr Machata 	if (nhg->resilient)
483283a72a5SPetr Machata 		vfree(rcu_dereference_raw(nhg->res_table));
484283a72a5SPetr Machata 
48590f33bffSNikolay Aleksandrov 	kfree(nhg->spare);
486430a0491SDavid Ahern 	kfree(nhg);
487430a0491SDavid Ahern }
488430a0491SDavid Ahern 
489430a0491SDavid Ahern static void nexthop_free_single(struct nexthop *nh)
490430a0491SDavid Ahern {
491ab84be7eSDavid Ahern 	struct nh_info *nhi;
492ab84be7eSDavid Ahern 
493ab84be7eSDavid Ahern 	nhi = rcu_dereference_raw(nh->nh_info);
494597cfe4fSDavid Ahern 	switch (nhi->family) {
495597cfe4fSDavid Ahern 	case AF_INET:
496597cfe4fSDavid Ahern 		fib_nh_release(nh->net, &nhi->fib_nh);
497597cfe4fSDavid Ahern 		break;
49853010f99SDavid Ahern 	case AF_INET6:
49953010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(&nhi->fib6_nh);
50053010f99SDavid Ahern 		break;
501597cfe4fSDavid Ahern 	}
502ab84be7eSDavid Ahern 	kfree(nhi);
503430a0491SDavid Ahern }
504430a0491SDavid Ahern 
505430a0491SDavid Ahern void nexthop_free_rcu(struct rcu_head *head)
506430a0491SDavid Ahern {
507430a0491SDavid Ahern 	struct nexthop *nh = container_of(head, struct nexthop, rcu);
508430a0491SDavid Ahern 
509430a0491SDavid Ahern 	if (nh->is_group)
5105d1f0f09SDavid Ahern 		nexthop_free_group(nh);
511430a0491SDavid Ahern 	else
512430a0491SDavid Ahern 		nexthop_free_single(nh);
513ab84be7eSDavid Ahern 
514ab84be7eSDavid Ahern 	kfree(nh);
515ab84be7eSDavid Ahern }
516ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_free_rcu);
517ab84be7eSDavid Ahern 
518ab84be7eSDavid Ahern static struct nexthop *nexthop_alloc(void)
519ab84be7eSDavid Ahern {
520ab84be7eSDavid Ahern 	struct nexthop *nh;
521ab84be7eSDavid Ahern 
522ab84be7eSDavid Ahern 	nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
523430a0491SDavid Ahern 	if (nh) {
5244c7e8084SDavid Ahern 		INIT_LIST_HEAD(&nh->fi_list);
525f88d8ea6SDavid Ahern 		INIT_LIST_HEAD(&nh->f6i_list);
526430a0491SDavid Ahern 		INIT_LIST_HEAD(&nh->grp_list);
52738428d68SRoopa Prabhu 		INIT_LIST_HEAD(&nh->fdb_list);
528430a0491SDavid Ahern 	}
529ab84be7eSDavid Ahern 	return nh;
530ab84be7eSDavid Ahern }
531ab84be7eSDavid Ahern 
532430a0491SDavid Ahern static struct nh_group *nexthop_grp_alloc(u16 num_nh)
533430a0491SDavid Ahern {
534430a0491SDavid Ahern 	struct nh_group *nhg;
535430a0491SDavid Ahern 
536d7d49dc7SIdo Schimmel 	nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL);
537430a0491SDavid Ahern 	if (nhg)
538430a0491SDavid Ahern 		nhg->num_nh = num_nh;
539430a0491SDavid Ahern 
540430a0491SDavid Ahern 	return nhg;
541430a0491SDavid Ahern }
542430a0491SDavid Ahern 
543283a72a5SPetr Machata static void nh_res_table_upkeep_dw(struct work_struct *work);
544283a72a5SPetr Machata 
545283a72a5SPetr Machata static struct nh_res_table *
546283a72a5SPetr Machata nexthop_res_table_alloc(struct net *net, u32 nhg_id, struct nh_config *cfg)
547283a72a5SPetr Machata {
548283a72a5SPetr Machata 	const u16 num_nh_buckets = cfg->nh_grp_res_num_buckets;
549283a72a5SPetr Machata 	struct nh_res_table *res_table;
550283a72a5SPetr Machata 	unsigned long size;
551283a72a5SPetr Machata 
552283a72a5SPetr Machata 	size = struct_size(res_table, nh_buckets, num_nh_buckets);
553283a72a5SPetr Machata 	res_table = __vmalloc(size, GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN);
554283a72a5SPetr Machata 	if (!res_table)
555283a72a5SPetr Machata 		return NULL;
556283a72a5SPetr Machata 
557283a72a5SPetr Machata 	res_table->net = net;
558283a72a5SPetr Machata 	res_table->nhg_id = nhg_id;
559283a72a5SPetr Machata 	INIT_DELAYED_WORK(&res_table->upkeep_dw, &nh_res_table_upkeep_dw);
560283a72a5SPetr Machata 	INIT_LIST_HEAD(&res_table->uw_nh_entries);
561283a72a5SPetr Machata 	res_table->idle_timer = cfg->nh_grp_res_idle_timer;
562283a72a5SPetr Machata 	res_table->unbalanced_timer = cfg->nh_grp_res_unbalanced_timer;
563283a72a5SPetr Machata 	res_table->num_nh_buckets = num_nh_buckets;
564283a72a5SPetr Machata 	return res_table;
565283a72a5SPetr Machata }
566283a72a5SPetr Machata 
567ab84be7eSDavid Ahern static void nh_base_seq_inc(struct net *net)
568ab84be7eSDavid Ahern {
569ab84be7eSDavid Ahern 	while (++net->nexthop.seq == 0)
570ab84be7eSDavid Ahern 		;
571ab84be7eSDavid Ahern }
572ab84be7eSDavid Ahern 
573ab84be7eSDavid Ahern /* no reference taken; rcu lock or rtnl must be held */
574ab84be7eSDavid Ahern struct nexthop *nexthop_find_by_id(struct net *net, u32 id)
575ab84be7eSDavid Ahern {
576ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
577ab84be7eSDavid Ahern 
578ab84be7eSDavid Ahern 	pp = &net->nexthop.rb_root.rb_node;
579ab84be7eSDavid Ahern 	while (1) {
580ab84be7eSDavid Ahern 		struct nexthop *nh;
581ab84be7eSDavid Ahern 
582ab84be7eSDavid Ahern 		next = rcu_dereference_raw(*pp);
583ab84be7eSDavid Ahern 		if (!next)
584ab84be7eSDavid Ahern 			break;
585ab84be7eSDavid Ahern 		parent = next;
586ab84be7eSDavid Ahern 
587ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
588ab84be7eSDavid Ahern 		if (id < nh->id)
589ab84be7eSDavid Ahern 			pp = &next->rb_left;
590ab84be7eSDavid Ahern 		else if (id > nh->id)
591ab84be7eSDavid Ahern 			pp = &next->rb_right;
592ab84be7eSDavid Ahern 		else
593ab84be7eSDavid Ahern 			return nh;
594ab84be7eSDavid Ahern 	}
595ab84be7eSDavid Ahern 	return NULL;
596ab84be7eSDavid Ahern }
597ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_find_by_id);
598ab84be7eSDavid Ahern 
599ab84be7eSDavid Ahern /* used for auto id allocation; called with rtnl held */
600ab84be7eSDavid Ahern static u32 nh_find_unused_id(struct net *net)
601ab84be7eSDavid Ahern {
602ab84be7eSDavid Ahern 	u32 id_start = net->nexthop.last_id_allocated;
603ab84be7eSDavid Ahern 
604ab84be7eSDavid Ahern 	while (1) {
605ab84be7eSDavid Ahern 		net->nexthop.last_id_allocated++;
606ab84be7eSDavid Ahern 		if (net->nexthop.last_id_allocated == id_start)
607ab84be7eSDavid Ahern 			break;
608ab84be7eSDavid Ahern 
609ab84be7eSDavid Ahern 		if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated))
610ab84be7eSDavid Ahern 			return net->nexthop.last_id_allocated;
611ab84be7eSDavid Ahern 	}
612ab84be7eSDavid Ahern 	return 0;
613ab84be7eSDavid Ahern }
614ab84be7eSDavid Ahern 
615283a72a5SPetr Machata static void nh_res_time_set_deadline(unsigned long next_time,
616283a72a5SPetr Machata 				     unsigned long *deadline)
617283a72a5SPetr Machata {
618283a72a5SPetr Machata 	if (time_before(next_time, *deadline))
619283a72a5SPetr Machata 		*deadline = next_time;
620283a72a5SPetr Machata }
621283a72a5SPetr Machata 
622a2601e2bSPetr Machata static clock_t nh_res_table_unbalanced_time(struct nh_res_table *res_table)
623a2601e2bSPetr Machata {
624a2601e2bSPetr Machata 	if (list_empty(&res_table->uw_nh_entries))
625a2601e2bSPetr Machata 		return 0;
626a2601e2bSPetr Machata 	return jiffies_delta_to_clock_t(jiffies - res_table->unbalanced_since);
627a2601e2bSPetr Machata }
628a2601e2bSPetr Machata 
629a2601e2bSPetr Machata static int nla_put_nh_group_res(struct sk_buff *skb, struct nh_group *nhg)
630a2601e2bSPetr Machata {
631a2601e2bSPetr Machata 	struct nh_res_table *res_table = rtnl_dereference(nhg->res_table);
632a2601e2bSPetr Machata 	struct nlattr *nest;
633a2601e2bSPetr Machata 
634a2601e2bSPetr Machata 	nest = nla_nest_start(skb, NHA_RES_GROUP);
635a2601e2bSPetr Machata 	if (!nest)
636a2601e2bSPetr Machata 		return -EMSGSIZE;
637a2601e2bSPetr Machata 
638a2601e2bSPetr Machata 	if (nla_put_u16(skb, NHA_RES_GROUP_BUCKETS,
639a2601e2bSPetr Machata 			res_table->num_nh_buckets) ||
640a2601e2bSPetr Machata 	    nla_put_u32(skb, NHA_RES_GROUP_IDLE_TIMER,
641a2601e2bSPetr Machata 			jiffies_to_clock_t(res_table->idle_timer)) ||
642a2601e2bSPetr Machata 	    nla_put_u32(skb, NHA_RES_GROUP_UNBALANCED_TIMER,
643a2601e2bSPetr Machata 			jiffies_to_clock_t(res_table->unbalanced_timer)) ||
644a2601e2bSPetr Machata 	    nla_put_u64_64bit(skb, NHA_RES_GROUP_UNBALANCED_TIME,
645a2601e2bSPetr Machata 			      nh_res_table_unbalanced_time(res_table),
646a2601e2bSPetr Machata 			      NHA_RES_GROUP_PAD))
647a2601e2bSPetr Machata 		goto nla_put_failure;
648a2601e2bSPetr Machata 
649a2601e2bSPetr Machata 	nla_nest_end(skb, nest);
650a2601e2bSPetr Machata 	return 0;
651a2601e2bSPetr Machata 
652a2601e2bSPetr Machata nla_put_failure:
653a2601e2bSPetr Machata 	nla_nest_cancel(skb, nest);
654a2601e2bSPetr Machata 	return -EMSGSIZE;
655a2601e2bSPetr Machata }
656a2601e2bSPetr Machata 
657430a0491SDavid Ahern static int nla_put_nh_group(struct sk_buff *skb, struct nh_group *nhg)
658430a0491SDavid Ahern {
659430a0491SDavid Ahern 	struct nexthop_grp *p;
660430a0491SDavid Ahern 	size_t len = nhg->num_nh * sizeof(*p);
661430a0491SDavid Ahern 	struct nlattr *nla;
662430a0491SDavid Ahern 	u16 group_type = 0;
663430a0491SDavid Ahern 	int i;
664430a0491SDavid Ahern 
665de1d1ee3SPetr Machata 	if (nhg->hash_threshold)
666430a0491SDavid Ahern 		group_type = NEXTHOP_GRP_TYPE_MPATH;
667a2601e2bSPetr Machata 	else if (nhg->resilient)
668a2601e2bSPetr Machata 		group_type = NEXTHOP_GRP_TYPE_RES;
669430a0491SDavid Ahern 
670430a0491SDavid Ahern 	if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type))
671430a0491SDavid Ahern 		goto nla_put_failure;
672430a0491SDavid Ahern 
673430a0491SDavid Ahern 	nla = nla_reserve(skb, NHA_GROUP, len);
674430a0491SDavid Ahern 	if (!nla)
675430a0491SDavid Ahern 		goto nla_put_failure;
676430a0491SDavid Ahern 
677430a0491SDavid Ahern 	p = nla_data(nla);
678430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
679430a0491SDavid Ahern 		p->id = nhg->nh_entries[i].nh->id;
680430a0491SDavid Ahern 		p->weight = nhg->nh_entries[i].weight - 1;
681430a0491SDavid Ahern 		p += 1;
682430a0491SDavid Ahern 	}
683430a0491SDavid Ahern 
684a2601e2bSPetr Machata 	if (nhg->resilient && nla_put_nh_group_res(skb, nhg))
685a2601e2bSPetr Machata 		goto nla_put_failure;
686a2601e2bSPetr Machata 
687430a0491SDavid Ahern 	return 0;
688430a0491SDavid Ahern 
689430a0491SDavid Ahern nla_put_failure:
690430a0491SDavid Ahern 	return -EMSGSIZE;
691430a0491SDavid Ahern }
692430a0491SDavid Ahern 
693ab84be7eSDavid Ahern static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
694ab84be7eSDavid Ahern 			int event, u32 portid, u32 seq, unsigned int nlflags)
695ab84be7eSDavid Ahern {
69653010f99SDavid Ahern 	struct fib6_nh *fib6_nh;
697597cfe4fSDavid Ahern 	struct fib_nh *fib_nh;
698ab84be7eSDavid Ahern 	struct nlmsghdr *nlh;
699ab84be7eSDavid Ahern 	struct nh_info *nhi;
700ab84be7eSDavid Ahern 	struct nhmsg *nhm;
701ab84be7eSDavid Ahern 
702ab84be7eSDavid Ahern 	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
703ab84be7eSDavid Ahern 	if (!nlh)
704ab84be7eSDavid Ahern 		return -EMSGSIZE;
705ab84be7eSDavid Ahern 
706ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
707ab84be7eSDavid Ahern 	nhm->nh_family = AF_UNSPEC;
708ab84be7eSDavid Ahern 	nhm->nh_flags = nh->nh_flags;
709ab84be7eSDavid Ahern 	nhm->nh_protocol = nh->protocol;
710ab84be7eSDavid Ahern 	nhm->nh_scope = 0;
711ab84be7eSDavid Ahern 	nhm->resvd = 0;
712ab84be7eSDavid Ahern 
713ab84be7eSDavid Ahern 	if (nla_put_u32(skb, NHA_ID, nh->id))
714ab84be7eSDavid Ahern 		goto nla_put_failure;
715ab84be7eSDavid Ahern 
716430a0491SDavid Ahern 	if (nh->is_group) {
717430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
718430a0491SDavid Ahern 
719ce9ac056SDavid Ahern 		if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB))
720ce9ac056SDavid Ahern 			goto nla_put_failure;
721430a0491SDavid Ahern 		if (nla_put_nh_group(skb, nhg))
722430a0491SDavid Ahern 			goto nla_put_failure;
723430a0491SDavid Ahern 		goto out;
724430a0491SDavid Ahern 	}
725430a0491SDavid Ahern 
726ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
727ab84be7eSDavid Ahern 	nhm->nh_family = nhi->family;
728ab84be7eSDavid Ahern 	if (nhi->reject_nh) {
729ab84be7eSDavid Ahern 		if (nla_put_flag(skb, NHA_BLACKHOLE))
730ab84be7eSDavid Ahern 			goto nla_put_failure;
731ab84be7eSDavid Ahern 		goto out;
732ce9ac056SDavid Ahern 	} else if (nhi->fdb_nh) {
733ce9ac056SDavid Ahern 		if (nla_put_flag(skb, NHA_FDB))
734ce9ac056SDavid Ahern 			goto nla_put_failure;
735ce9ac056SDavid Ahern 	} else {
736597cfe4fSDavid Ahern 		const struct net_device *dev;
737597cfe4fSDavid Ahern 
738597cfe4fSDavid Ahern 		dev = nhi->fib_nhc.nhc_dev;
739597cfe4fSDavid Ahern 		if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex))
740597cfe4fSDavid Ahern 			goto nla_put_failure;
741597cfe4fSDavid Ahern 	}
742597cfe4fSDavid Ahern 
743597cfe4fSDavid Ahern 	nhm->nh_scope = nhi->fib_nhc.nhc_scope;
744597cfe4fSDavid Ahern 	switch (nhi->family) {
745597cfe4fSDavid Ahern 	case AF_INET:
746597cfe4fSDavid Ahern 		fib_nh = &nhi->fib_nh;
747597cfe4fSDavid Ahern 		if (fib_nh->fib_nh_gw_family &&
74833d80996SIdo Schimmel 		    nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4))
749597cfe4fSDavid Ahern 			goto nla_put_failure;
750597cfe4fSDavid Ahern 		break;
75153010f99SDavid Ahern 
75253010f99SDavid Ahern 	case AF_INET6:
75353010f99SDavid Ahern 		fib6_nh = &nhi->fib6_nh;
75453010f99SDavid Ahern 		if (fib6_nh->fib_nh_gw_family &&
75553010f99SDavid Ahern 		    nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6))
75653010f99SDavid Ahern 			goto nla_put_failure;
75753010f99SDavid Ahern 		break;
758ab84be7eSDavid Ahern 	}
759ab84be7eSDavid Ahern 
760b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate &&
761b513bd03SDavid Ahern 	    lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate,
762b513bd03SDavid Ahern 				NHA_ENCAP, NHA_ENCAP_TYPE) < 0)
763b513bd03SDavid Ahern 		goto nla_put_failure;
764b513bd03SDavid Ahern 
765ab84be7eSDavid Ahern out:
766ab84be7eSDavid Ahern 	nlmsg_end(skb, nlh);
767ab84be7eSDavid Ahern 	return 0;
768ab84be7eSDavid Ahern 
769ab84be7eSDavid Ahern nla_put_failure:
770d69100b8SStephen Worley 	nlmsg_cancel(skb, nlh);
771ab84be7eSDavid Ahern 	return -EMSGSIZE;
772ab84be7eSDavid Ahern }
773ab84be7eSDavid Ahern 
774a2601e2bSPetr Machata static size_t nh_nlmsg_size_grp_res(struct nh_group *nhg)
775a2601e2bSPetr Machata {
776a2601e2bSPetr Machata 	return nla_total_size(0) +	/* NHA_RES_GROUP */
777a2601e2bSPetr Machata 		nla_total_size(2) +	/* NHA_RES_GROUP_BUCKETS */
778a2601e2bSPetr Machata 		nla_total_size(4) +	/* NHA_RES_GROUP_IDLE_TIMER */
779a2601e2bSPetr Machata 		nla_total_size(4) +	/* NHA_RES_GROUP_UNBALANCED_TIMER */
780a2601e2bSPetr Machata 		nla_total_size_64bit(8);/* NHA_RES_GROUP_UNBALANCED_TIME */
781a2601e2bSPetr Machata }
782a2601e2bSPetr Machata 
783430a0491SDavid Ahern static size_t nh_nlmsg_size_grp(struct nexthop *nh)
784430a0491SDavid Ahern {
785430a0491SDavid Ahern 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
786430a0491SDavid Ahern 	size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh;
787a2601e2bSPetr Machata 	size_t tot = nla_total_size(sz) +
788430a0491SDavid Ahern 		nla_total_size(2); /* NHA_GROUP_TYPE */
789a2601e2bSPetr Machata 
790a2601e2bSPetr Machata 	if (nhg->resilient)
791a2601e2bSPetr Machata 		tot += nh_nlmsg_size_grp_res(nhg);
792a2601e2bSPetr Machata 
793a2601e2bSPetr Machata 	return tot;
794430a0491SDavid Ahern }
795430a0491SDavid Ahern 
796430a0491SDavid Ahern static size_t nh_nlmsg_size_single(struct nexthop *nh)
797ab84be7eSDavid Ahern {
798597cfe4fSDavid Ahern 	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
799430a0491SDavid Ahern 	size_t sz;
800ab84be7eSDavid Ahern 
801ab84be7eSDavid Ahern 	/* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE
802ab84be7eSDavid Ahern 	 * are mutually exclusive
803ab84be7eSDavid Ahern 	 */
804430a0491SDavid Ahern 	sz = nla_total_size(4);  /* NHA_OIF */
805ab84be7eSDavid Ahern 
806597cfe4fSDavid Ahern 	switch (nhi->family) {
807597cfe4fSDavid Ahern 	case AF_INET:
808597cfe4fSDavid Ahern 		if (nhi->fib_nh.fib_nh_gw_family)
809597cfe4fSDavid Ahern 			sz += nla_total_size(4);  /* NHA_GATEWAY */
810597cfe4fSDavid Ahern 		break;
81153010f99SDavid Ahern 
81253010f99SDavid Ahern 	case AF_INET6:
81353010f99SDavid Ahern 		/* NHA_GATEWAY */
81453010f99SDavid Ahern 		if (nhi->fib6_nh.fib_nh_gw_family)
81553010f99SDavid Ahern 			sz += nla_total_size(sizeof(const struct in6_addr));
81653010f99SDavid Ahern 		break;
817597cfe4fSDavid Ahern 	}
818597cfe4fSDavid Ahern 
819b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate) {
820b513bd03SDavid Ahern 		sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
821b513bd03SDavid Ahern 		sz += nla_total_size(2);  /* NHA_ENCAP_TYPE */
822b513bd03SDavid Ahern 	}
823b513bd03SDavid Ahern 
824ab84be7eSDavid Ahern 	return sz;
825ab84be7eSDavid Ahern }
826ab84be7eSDavid Ahern 
827430a0491SDavid Ahern static size_t nh_nlmsg_size(struct nexthop *nh)
828430a0491SDavid Ahern {
829f9e95555SStephen Worley 	size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg));
830f9e95555SStephen Worley 
831f9e95555SStephen Worley 	sz += nla_total_size(4); /* NHA_ID */
832430a0491SDavid Ahern 
833430a0491SDavid Ahern 	if (nh->is_group)
834430a0491SDavid Ahern 		sz += nh_nlmsg_size_grp(nh);
835430a0491SDavid Ahern 	else
836430a0491SDavid Ahern 		sz += nh_nlmsg_size_single(nh);
837430a0491SDavid Ahern 
838430a0491SDavid Ahern 	return sz;
839430a0491SDavid Ahern }
840430a0491SDavid Ahern 
841ab84be7eSDavid Ahern static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info)
842ab84be7eSDavid Ahern {
843ab84be7eSDavid Ahern 	unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0;
844ab84be7eSDavid Ahern 	u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
845ab84be7eSDavid Ahern 	struct sk_buff *skb;
846ab84be7eSDavid Ahern 	int err = -ENOBUFS;
847ab84be7eSDavid Ahern 
848ab84be7eSDavid Ahern 	skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any());
849ab84be7eSDavid Ahern 	if (!skb)
850ab84be7eSDavid Ahern 		goto errout;
851ab84be7eSDavid Ahern 
852ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags);
853ab84be7eSDavid Ahern 	if (err < 0) {
854ab84be7eSDavid Ahern 		/* -EMSGSIZE implies BUG in nh_nlmsg_size() */
855ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
856ab84be7eSDavid Ahern 		kfree_skb(skb);
857ab84be7eSDavid Ahern 		goto errout;
858ab84be7eSDavid Ahern 	}
859ab84be7eSDavid Ahern 
860ab84be7eSDavid Ahern 	rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP,
861ab84be7eSDavid Ahern 		    info->nlh, gfp_any());
862ab84be7eSDavid Ahern 	return;
863ab84be7eSDavid Ahern errout:
864ab84be7eSDavid Ahern 	if (err < 0)
865ab84be7eSDavid Ahern 		rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err);
866ab84be7eSDavid Ahern }
867ab84be7eSDavid Ahern 
868283a72a5SPetr Machata static unsigned long nh_res_bucket_used_time(const struct nh_res_bucket *bucket)
869283a72a5SPetr Machata {
870283a72a5SPetr Machata 	return (unsigned long)atomic_long_read(&bucket->used_time);
871283a72a5SPetr Machata }
872283a72a5SPetr Machata 
873283a72a5SPetr Machata static unsigned long
874283a72a5SPetr Machata nh_res_bucket_idle_point(const struct nh_res_table *res_table,
875283a72a5SPetr Machata 			 const struct nh_res_bucket *bucket,
876283a72a5SPetr Machata 			 unsigned long now)
877283a72a5SPetr Machata {
878283a72a5SPetr Machata 	unsigned long time = nh_res_bucket_used_time(bucket);
879283a72a5SPetr Machata 
880283a72a5SPetr Machata 	/* Bucket was not used since it was migrated. The idle time is now. */
881283a72a5SPetr Machata 	if (time == bucket->migrated_time)
882283a72a5SPetr Machata 		return now;
883283a72a5SPetr Machata 
884283a72a5SPetr Machata 	return time + res_table->idle_timer;
885283a72a5SPetr Machata }
886283a72a5SPetr Machata 
887283a72a5SPetr Machata static unsigned long
888283a72a5SPetr Machata nh_res_table_unb_point(const struct nh_res_table *res_table)
889283a72a5SPetr Machata {
890283a72a5SPetr Machata 	return res_table->unbalanced_since + res_table->unbalanced_timer;
891283a72a5SPetr Machata }
892283a72a5SPetr Machata 
893283a72a5SPetr Machata static void nh_res_bucket_set_idle(const struct nh_res_table *res_table,
894283a72a5SPetr Machata 				   struct nh_res_bucket *bucket)
895283a72a5SPetr Machata {
896283a72a5SPetr Machata 	unsigned long now = jiffies;
897283a72a5SPetr Machata 
898283a72a5SPetr Machata 	atomic_long_set(&bucket->used_time, (long)now);
899283a72a5SPetr Machata 	bucket->migrated_time = now;
900283a72a5SPetr Machata }
901283a72a5SPetr Machata 
902283a72a5SPetr Machata static void nh_res_bucket_set_busy(struct nh_res_bucket *bucket)
903283a72a5SPetr Machata {
904283a72a5SPetr Machata 	atomic_long_set(&bucket->used_time, (long)jiffies);
905283a72a5SPetr Machata }
906283a72a5SPetr Machata 
9078a1bbabbSPetr Machata static clock_t nh_res_bucket_idle_time(const struct nh_res_bucket *bucket)
9088a1bbabbSPetr Machata {
9098a1bbabbSPetr Machata 	unsigned long used_time = nh_res_bucket_used_time(bucket);
9108a1bbabbSPetr Machata 
9118a1bbabbSPetr Machata 	return jiffies_delta_to_clock_t(jiffies - used_time);
9128a1bbabbSPetr Machata }
9138a1bbabbSPetr Machata 
9148a1bbabbSPetr Machata static int nh_fill_res_bucket(struct sk_buff *skb, struct nexthop *nh,
9158a1bbabbSPetr Machata 			      struct nh_res_bucket *bucket, u16 bucket_index,
9168a1bbabbSPetr Machata 			      int event, u32 portid, u32 seq,
9178a1bbabbSPetr Machata 			      unsigned int nlflags,
9188a1bbabbSPetr Machata 			      struct netlink_ext_ack *extack)
9198a1bbabbSPetr Machata {
9208a1bbabbSPetr Machata 	struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry);
9218a1bbabbSPetr Machata 	struct nlmsghdr *nlh;
9228a1bbabbSPetr Machata 	struct nlattr *nest;
9238a1bbabbSPetr Machata 	struct nhmsg *nhm;
9248a1bbabbSPetr Machata 
9258a1bbabbSPetr Machata 	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
9268a1bbabbSPetr Machata 	if (!nlh)
9278a1bbabbSPetr Machata 		return -EMSGSIZE;
9288a1bbabbSPetr Machata 
9298a1bbabbSPetr Machata 	nhm = nlmsg_data(nlh);
9308a1bbabbSPetr Machata 	nhm->nh_family = AF_UNSPEC;
9318a1bbabbSPetr Machata 	nhm->nh_flags = bucket->nh_flags;
9328a1bbabbSPetr Machata 	nhm->nh_protocol = nh->protocol;
9338a1bbabbSPetr Machata 	nhm->nh_scope = 0;
9348a1bbabbSPetr Machata 	nhm->resvd = 0;
9358a1bbabbSPetr Machata 
9368a1bbabbSPetr Machata 	if (nla_put_u32(skb, NHA_ID, nh->id))
9378a1bbabbSPetr Machata 		goto nla_put_failure;
9388a1bbabbSPetr Machata 
9398a1bbabbSPetr Machata 	nest = nla_nest_start(skb, NHA_RES_BUCKET);
9408a1bbabbSPetr Machata 	if (!nest)
9418a1bbabbSPetr Machata 		goto nla_put_failure;
9428a1bbabbSPetr Machata 
9438a1bbabbSPetr Machata 	if (nla_put_u16(skb, NHA_RES_BUCKET_INDEX, bucket_index) ||
9448a1bbabbSPetr Machata 	    nla_put_u32(skb, NHA_RES_BUCKET_NH_ID, nhge->nh->id) ||
9458a1bbabbSPetr Machata 	    nla_put_u64_64bit(skb, NHA_RES_BUCKET_IDLE_TIME,
9468a1bbabbSPetr Machata 			      nh_res_bucket_idle_time(bucket),
9478a1bbabbSPetr Machata 			      NHA_RES_BUCKET_PAD))
9488a1bbabbSPetr Machata 		goto nla_put_failure_nest;
9498a1bbabbSPetr Machata 
9508a1bbabbSPetr Machata 	nla_nest_end(skb, nest);
9518a1bbabbSPetr Machata 	nlmsg_end(skb, nlh);
9528a1bbabbSPetr Machata 	return 0;
9538a1bbabbSPetr Machata 
9548a1bbabbSPetr Machata nla_put_failure_nest:
9558a1bbabbSPetr Machata 	nla_nest_cancel(skb, nest);
9568a1bbabbSPetr Machata nla_put_failure:
9578a1bbabbSPetr Machata 	nlmsg_cancel(skb, nlh);
9588a1bbabbSPetr Machata 	return -EMSGSIZE;
9598a1bbabbSPetr Machata }
9608a1bbabbSPetr Machata 
9610b4818aaSPetr Machata static void nexthop_bucket_notify(struct nh_res_table *res_table,
9620b4818aaSPetr Machata 				  u16 bucket_index)
9630b4818aaSPetr Machata {
9640b4818aaSPetr Machata 	struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index];
9650b4818aaSPetr Machata 	struct nh_grp_entry *nhge = nh_res_dereference(bucket->nh_entry);
9660b4818aaSPetr Machata 	struct nexthop *nh = nhge->nh_parent;
9670b4818aaSPetr Machata 	struct sk_buff *skb;
9680b4818aaSPetr Machata 	int err = -ENOBUFS;
9690b4818aaSPetr Machata 
9700b4818aaSPetr Machata 	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
9710b4818aaSPetr Machata 	if (!skb)
9720b4818aaSPetr Machata 		goto errout;
9730b4818aaSPetr Machata 
9740b4818aaSPetr Machata 	err = nh_fill_res_bucket(skb, nh, bucket, bucket_index,
9750b4818aaSPetr Machata 				 RTM_NEWNEXTHOPBUCKET, 0, 0, NLM_F_REPLACE,
9760b4818aaSPetr Machata 				 NULL);
9770b4818aaSPetr Machata 	if (err < 0) {
9780b4818aaSPetr Machata 		kfree_skb(skb);
9790b4818aaSPetr Machata 		goto errout;
9800b4818aaSPetr Machata 	}
9810b4818aaSPetr Machata 
9820b4818aaSPetr Machata 	rtnl_notify(skb, nh->net, 0, RTNLGRP_NEXTHOP, NULL, GFP_KERNEL);
9830b4818aaSPetr Machata 	return;
9840b4818aaSPetr Machata errout:
9850b4818aaSPetr Machata 	if (err < 0)
9860b4818aaSPetr Machata 		rtnl_set_sk_err(nh->net, RTNLGRP_NEXTHOP, err);
9870b4818aaSPetr Machata }
9880b4818aaSPetr Machata 
989430a0491SDavid Ahern static bool valid_group_nh(struct nexthop *nh, unsigned int npaths,
990ce9ac056SDavid Ahern 			   bool *is_fdb, struct netlink_ext_ack *extack)
991597cfe4fSDavid Ahern {
992430a0491SDavid Ahern 	if (nh->is_group) {
993430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
994430a0491SDavid Ahern 
995283a72a5SPetr Machata 		/* Nesting groups within groups is not supported. */
996de1d1ee3SPetr Machata 		if (nhg->hash_threshold) {
997430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
998de1d1ee3SPetr Machata 				       "Hash-threshold group can not be a nexthop within a group");
999430a0491SDavid Ahern 			return false;
1000430a0491SDavid Ahern 		}
1001283a72a5SPetr Machata 		if (nhg->resilient) {
1002283a72a5SPetr Machata 			NL_SET_ERR_MSG(extack,
1003283a72a5SPetr Machata 				       "Resilient group can not be a nexthop within a group");
1004283a72a5SPetr Machata 			return false;
1005283a72a5SPetr Machata 		}
1006ce9ac056SDavid Ahern 		*is_fdb = nhg->fdb_nh;
1007430a0491SDavid Ahern 	} else {
1008430a0491SDavid Ahern 		struct nh_info *nhi = rtnl_dereference(nh->nh_info);
1009430a0491SDavid Ahern 
1010430a0491SDavid Ahern 		if (nhi->reject_nh && npaths > 1) {
1011430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
1012430a0491SDavid Ahern 				       "Blackhole nexthop can not be used in a group with more than 1 path");
1013430a0491SDavid Ahern 			return false;
1014430a0491SDavid Ahern 		}
1015ce9ac056SDavid Ahern 		*is_fdb = nhi->fdb_nh;
1016430a0491SDavid Ahern 	}
1017430a0491SDavid Ahern 
1018430a0491SDavid Ahern 	return true;
1019430a0491SDavid Ahern }
1020430a0491SDavid Ahern 
102138428d68SRoopa Prabhu static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family,
102238428d68SRoopa Prabhu 				   struct netlink_ext_ack *extack)
102338428d68SRoopa Prabhu {
102438428d68SRoopa Prabhu 	struct nh_info *nhi;
102538428d68SRoopa Prabhu 
1026ce9ac056SDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
1027ce9ac056SDavid Ahern 
1028ce9ac056SDavid Ahern 	if (!nhi->fdb_nh) {
102938428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops");
103038428d68SRoopa Prabhu 		return -EINVAL;
103138428d68SRoopa Prabhu 	}
103238428d68SRoopa Prabhu 
103338428d68SRoopa Prabhu 	if (*nh_family == AF_UNSPEC) {
103438428d68SRoopa Prabhu 		*nh_family = nhi->family;
103538428d68SRoopa Prabhu 	} else if (*nh_family != nhi->family) {
103638428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops");
103738428d68SRoopa Prabhu 		return -EINVAL;
103838428d68SRoopa Prabhu 	}
103938428d68SRoopa Prabhu 
104038428d68SRoopa Prabhu 	return 0;
104138428d68SRoopa Prabhu }
104238428d68SRoopa Prabhu 
1043643d0878SPetr Machata static int nh_check_attr_group(struct net *net,
1044643d0878SPetr Machata 			       struct nlattr *tb[], size_t tb_size,
1045a2601e2bSPetr Machata 			       u16 nh_grp_type, struct netlink_ext_ack *extack)
1046430a0491SDavid Ahern {
1047430a0491SDavid Ahern 	unsigned int len = nla_len(tb[NHA_GROUP]);
104838428d68SRoopa Prabhu 	u8 nh_family = AF_UNSPEC;
1049430a0491SDavid Ahern 	struct nexthop_grp *nhg;
1050430a0491SDavid Ahern 	unsigned int i, j;
105138428d68SRoopa Prabhu 	u8 nhg_fdb = 0;
1052430a0491SDavid Ahern 
1053eeaac363SNikolay Aleksandrov 	if (!len || len & (sizeof(struct nexthop_grp) - 1)) {
1054430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
1055430a0491SDavid Ahern 			       "Invalid length for nexthop group attribute");
1056430a0491SDavid Ahern 		return -EINVAL;
1057430a0491SDavid Ahern 	}
1058430a0491SDavid Ahern 
1059430a0491SDavid Ahern 	/* convert len to number of nexthop ids */
1060430a0491SDavid Ahern 	len /= sizeof(*nhg);
1061430a0491SDavid Ahern 
1062430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
1063430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
1064430a0491SDavid Ahern 		if (nhg[i].resvd1 || nhg[i].resvd2) {
1065430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0");
1066430a0491SDavid Ahern 			return -EINVAL;
1067430a0491SDavid Ahern 		}
1068430a0491SDavid Ahern 		if (nhg[i].weight > 254) {
1069430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid value for weight");
1070430a0491SDavid Ahern 			return -EINVAL;
1071430a0491SDavid Ahern 		}
1072430a0491SDavid Ahern 		for (j = i + 1; j < len; ++j) {
1073430a0491SDavid Ahern 			if (nhg[i].id == nhg[j].id) {
1074430a0491SDavid Ahern 				NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group");
1075430a0491SDavid Ahern 				return -EINVAL;
1076430a0491SDavid Ahern 			}
1077430a0491SDavid Ahern 		}
1078430a0491SDavid Ahern 	}
1079430a0491SDavid Ahern 
108038428d68SRoopa Prabhu 	if (tb[NHA_FDB])
108138428d68SRoopa Prabhu 		nhg_fdb = 1;
1082430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
1083430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
1084430a0491SDavid Ahern 		struct nexthop *nh;
1085ce9ac056SDavid Ahern 		bool is_fdb_nh;
1086430a0491SDavid Ahern 
1087430a0491SDavid Ahern 		nh = nexthop_find_by_id(net, nhg[i].id);
1088430a0491SDavid Ahern 		if (!nh) {
1089430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid nexthop id");
1090430a0491SDavid Ahern 			return -EINVAL;
1091430a0491SDavid Ahern 		}
1092ce9ac056SDavid Ahern 		if (!valid_group_nh(nh, len, &is_fdb_nh, extack))
1093430a0491SDavid Ahern 			return -EINVAL;
109438428d68SRoopa Prabhu 
109538428d68SRoopa Prabhu 		if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack))
109638428d68SRoopa Prabhu 			return -EINVAL;
109738428d68SRoopa Prabhu 
1098ce9ac056SDavid Ahern 		if (!nhg_fdb && is_fdb_nh) {
109938428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops");
110038428d68SRoopa Prabhu 			return -EINVAL;
110138428d68SRoopa Prabhu 		}
1102430a0491SDavid Ahern 	}
1103643d0878SPetr Machata 	for (i = NHA_GROUP_TYPE + 1; i < tb_size; ++i) {
1104430a0491SDavid Ahern 		if (!tb[i])
1105430a0491SDavid Ahern 			continue;
1106a2601e2bSPetr Machata 		switch (i) {
1107a2601e2bSPetr Machata 		case NHA_FDB:
110838428d68SRoopa Prabhu 			continue;
1109a2601e2bSPetr Machata 		case NHA_RES_GROUP:
1110a2601e2bSPetr Machata 			if (nh_grp_type == NEXTHOP_GRP_TYPE_RES)
1111a2601e2bSPetr Machata 				continue;
1112a2601e2bSPetr Machata 			break;
1113a2601e2bSPetr Machata 		}
1114430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
1115430a0491SDavid Ahern 			       "No other attributes can be set in nexthop groups");
1116430a0491SDavid Ahern 		return -EINVAL;
1117430a0491SDavid Ahern 	}
1118430a0491SDavid Ahern 
1119430a0491SDavid Ahern 	return 0;
1120430a0491SDavid Ahern }
1121430a0491SDavid Ahern 
1122430a0491SDavid Ahern static bool ipv6_good_nh(const struct fib6_nh *nh)
1123430a0491SDavid Ahern {
1124430a0491SDavid Ahern 	int state = NUD_REACHABLE;
1125430a0491SDavid Ahern 	struct neighbour *n;
1126430a0491SDavid Ahern 
1127430a0491SDavid Ahern 	rcu_read_lock_bh();
1128430a0491SDavid Ahern 
1129430a0491SDavid Ahern 	n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
1130430a0491SDavid Ahern 	if (n)
1131430a0491SDavid Ahern 		state = n->nud_state;
1132430a0491SDavid Ahern 
1133430a0491SDavid Ahern 	rcu_read_unlock_bh();
1134430a0491SDavid Ahern 
1135430a0491SDavid Ahern 	return !!(state & NUD_VALID);
1136430a0491SDavid Ahern }
1137430a0491SDavid Ahern 
1138430a0491SDavid Ahern static bool ipv4_good_nh(const struct fib_nh *nh)
1139430a0491SDavid Ahern {
1140430a0491SDavid Ahern 	int state = NUD_REACHABLE;
1141430a0491SDavid Ahern 	struct neighbour *n;
1142430a0491SDavid Ahern 
1143430a0491SDavid Ahern 	rcu_read_lock_bh();
1144430a0491SDavid Ahern 
1145430a0491SDavid Ahern 	n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
1146430a0491SDavid Ahern 				      (__force u32)nh->fib_nh_gw4);
1147430a0491SDavid Ahern 	if (n)
1148430a0491SDavid Ahern 		state = n->nud_state;
1149430a0491SDavid Ahern 
1150430a0491SDavid Ahern 	rcu_read_unlock_bh();
1151430a0491SDavid Ahern 
1152430a0491SDavid Ahern 	return !!(state & NUD_VALID);
1153430a0491SDavid Ahern }
1154430a0491SDavid Ahern 
1155de1d1ee3SPetr Machata static struct nexthop *nexthop_select_path_hthr(struct nh_group *nhg, int hash)
1156430a0491SDavid Ahern {
1157430a0491SDavid Ahern 	struct nexthop *rc = NULL;
1158430a0491SDavid Ahern 	int i;
1159430a0491SDavid Ahern 
1160430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
1161430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
1162430a0491SDavid Ahern 		struct nh_info *nhi;
1163430a0491SDavid Ahern 
1164de1d1ee3SPetr Machata 		if (hash > atomic_read(&nhge->hthr.upper_bound))
1165430a0491SDavid Ahern 			continue;
1166430a0491SDavid Ahern 
1167ce9ac056SDavid Ahern 		nhi = rcu_dereference(nhge->nh->nh_info);
1168ce9ac056SDavid Ahern 		if (nhi->fdb_nh)
116938428d68SRoopa Prabhu 			return nhge->nh;
117038428d68SRoopa Prabhu 
1171430a0491SDavid Ahern 		/* nexthops always check if it is good and does
1172430a0491SDavid Ahern 		 * not rely on a sysctl for this behavior
1173430a0491SDavid Ahern 		 */
1174430a0491SDavid Ahern 		switch (nhi->family) {
1175430a0491SDavid Ahern 		case AF_INET:
1176430a0491SDavid Ahern 			if (ipv4_good_nh(&nhi->fib_nh))
1177430a0491SDavid Ahern 				return nhge->nh;
1178430a0491SDavid Ahern 			break;
1179430a0491SDavid Ahern 		case AF_INET6:
1180430a0491SDavid Ahern 			if (ipv6_good_nh(&nhi->fib6_nh))
1181430a0491SDavid Ahern 				return nhge->nh;
1182430a0491SDavid Ahern 			break;
1183430a0491SDavid Ahern 		}
1184430a0491SDavid Ahern 
1185430a0491SDavid Ahern 		if (!rc)
1186430a0491SDavid Ahern 			rc = nhge->nh;
1187430a0491SDavid Ahern 	}
1188430a0491SDavid Ahern 
1189430a0491SDavid Ahern 	return rc;
1190430a0491SDavid Ahern }
119179bc55e3SPetr Machata 
1192283a72a5SPetr Machata static struct nexthop *nexthop_select_path_res(struct nh_group *nhg, int hash)
1193283a72a5SPetr Machata {
1194283a72a5SPetr Machata 	struct nh_res_table *res_table = rcu_dereference(nhg->res_table);
1195283a72a5SPetr Machata 	u16 bucket_index = hash % res_table->num_nh_buckets;
1196283a72a5SPetr Machata 	struct nh_res_bucket *bucket;
1197283a72a5SPetr Machata 	struct nh_grp_entry *nhge;
1198283a72a5SPetr Machata 
1199283a72a5SPetr Machata 	/* nexthop_select_path() is expected to return a non-NULL value, so
1200283a72a5SPetr Machata 	 * skip protocol validation and just hand out whatever there is.
1201283a72a5SPetr Machata 	 */
1202283a72a5SPetr Machata 	bucket = &res_table->nh_buckets[bucket_index];
1203283a72a5SPetr Machata 	nh_res_bucket_set_busy(bucket);
1204283a72a5SPetr Machata 	nhge = rcu_dereference(bucket->nh_entry);
1205283a72a5SPetr Machata 	return nhge->nh;
1206283a72a5SPetr Machata }
1207283a72a5SPetr Machata 
120879bc55e3SPetr Machata struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
120979bc55e3SPetr Machata {
121079bc55e3SPetr Machata 	struct nh_group *nhg;
121179bc55e3SPetr Machata 
121279bc55e3SPetr Machata 	if (!nh->is_group)
121379bc55e3SPetr Machata 		return nh;
121479bc55e3SPetr Machata 
121579bc55e3SPetr Machata 	nhg = rcu_dereference(nh->nh_grp);
1216de1d1ee3SPetr Machata 	if (nhg->hash_threshold)
1217de1d1ee3SPetr Machata 		return nexthop_select_path_hthr(nhg, hash);
1218283a72a5SPetr Machata 	else if (nhg->resilient)
1219283a72a5SPetr Machata 		return nexthop_select_path_res(nhg, hash);
122079bc55e3SPetr Machata 
122179bc55e3SPetr Machata 	/* Unreachable. */
122279bc55e3SPetr Machata 	return NULL;
122379bc55e3SPetr Machata }
1224430a0491SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_select_path);
1225430a0491SDavid Ahern 
1226f88c9aa1SDavid Ahern int nexthop_for_each_fib6_nh(struct nexthop *nh,
1227f88c9aa1SDavid Ahern 			     int (*cb)(struct fib6_nh *nh, void *arg),
1228f88c9aa1SDavid Ahern 			     void *arg)
1229f88c9aa1SDavid Ahern {
1230f88c9aa1SDavid Ahern 	struct nh_info *nhi;
1231f88c9aa1SDavid Ahern 	int err;
1232f88c9aa1SDavid Ahern 
1233f88c9aa1SDavid Ahern 	if (nh->is_group) {
1234f88c9aa1SDavid Ahern 		struct nh_group *nhg;
1235f88c9aa1SDavid Ahern 		int i;
1236f88c9aa1SDavid Ahern 
1237f88c9aa1SDavid Ahern 		nhg = rcu_dereference_rtnl(nh->nh_grp);
1238f88c9aa1SDavid Ahern 		for (i = 0; i < nhg->num_nh; i++) {
1239f88c9aa1SDavid Ahern 			struct nh_grp_entry *nhge = &nhg->nh_entries[i];
1240f88c9aa1SDavid Ahern 
1241f88c9aa1SDavid Ahern 			nhi = rcu_dereference_rtnl(nhge->nh->nh_info);
1242f88c9aa1SDavid Ahern 			err = cb(&nhi->fib6_nh, arg);
1243f88c9aa1SDavid Ahern 			if (err)
1244f88c9aa1SDavid Ahern 				return err;
1245f88c9aa1SDavid Ahern 		}
1246f88c9aa1SDavid Ahern 	} else {
1247f88c9aa1SDavid Ahern 		nhi = rcu_dereference_rtnl(nh->nh_info);
1248f88c9aa1SDavid Ahern 		err = cb(&nhi->fib6_nh, arg);
1249f88c9aa1SDavid Ahern 		if (err)
1250f88c9aa1SDavid Ahern 			return err;
1251f88c9aa1SDavid Ahern 	}
1252f88c9aa1SDavid Ahern 
1253f88c9aa1SDavid Ahern 	return 0;
1254f88c9aa1SDavid Ahern }
1255f88c9aa1SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh);
1256f88c9aa1SDavid Ahern 
12577bf4796dSDavid Ahern static int check_src_addr(const struct in6_addr *saddr,
12587bf4796dSDavid Ahern 			  struct netlink_ext_ack *extack)
12597bf4796dSDavid Ahern {
12607bf4796dSDavid Ahern 	if (!ipv6_addr_any(saddr)) {
12617bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
12627bf4796dSDavid Ahern 		return -EINVAL;
12637bf4796dSDavid Ahern 	}
12647bf4796dSDavid Ahern 	return 0;
12657bf4796dSDavid Ahern }
12667bf4796dSDavid Ahern 
1267f88d8ea6SDavid Ahern int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
1268f88d8ea6SDavid Ahern 		       struct netlink_ext_ack *extack)
1269f88d8ea6SDavid Ahern {
1270f88d8ea6SDavid Ahern 	struct nh_info *nhi;
1271ce9ac056SDavid Ahern 	bool is_fdb_nh;
127238428d68SRoopa Prabhu 
1273f88d8ea6SDavid Ahern 	/* fib6_src is unique to a fib6_info and limits the ability to cache
1274f88d8ea6SDavid Ahern 	 * routes in fib6_nh within a nexthop that is potentially shared
1275f88d8ea6SDavid Ahern 	 * across multiple fib entries. If the config wants to use source
1276f88d8ea6SDavid Ahern 	 * routing it can not use nexthop objects. mlxsw also does not allow
1277f88d8ea6SDavid Ahern 	 * fib6_src on routes.
1278f88d8ea6SDavid Ahern 	 */
12797bf4796dSDavid Ahern 	if (cfg && check_src_addr(&cfg->fc_src, extack) < 0)
1280f88d8ea6SDavid Ahern 		return -EINVAL;
1281f88d8ea6SDavid Ahern 
1282f88d8ea6SDavid Ahern 	if (nh->is_group) {
1283f88d8ea6SDavid Ahern 		struct nh_group *nhg;
1284f88d8ea6SDavid Ahern 
1285f88d8ea6SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
1286f88d8ea6SDavid Ahern 		if (nhg->has_v4)
1287f88d8ea6SDavid Ahern 			goto no_v4_nh;
1288ce9ac056SDavid Ahern 		is_fdb_nh = nhg->fdb_nh;
1289f88d8ea6SDavid Ahern 	} else {
1290f88d8ea6SDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
1291f88d8ea6SDavid Ahern 		if (nhi->family == AF_INET)
1292f88d8ea6SDavid Ahern 			goto no_v4_nh;
1293ce9ac056SDavid Ahern 		is_fdb_nh = nhi->fdb_nh;
1294ce9ac056SDavid Ahern 	}
1295ce9ac056SDavid Ahern 
1296ce9ac056SDavid Ahern 	if (is_fdb_nh) {
1297ce9ac056SDavid Ahern 		NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
1298ce9ac056SDavid Ahern 		return -EINVAL;
1299f88d8ea6SDavid Ahern 	}
1300f88d8ea6SDavid Ahern 
1301f88d8ea6SDavid Ahern 	return 0;
1302f88d8ea6SDavid Ahern no_v4_nh:
1303f88d8ea6SDavid Ahern 	NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
1304f88d8ea6SDavid Ahern 	return -EINVAL;
1305f88d8ea6SDavid Ahern }
1306f88d8ea6SDavid Ahern EXPORT_SYMBOL_GPL(fib6_check_nexthop);
1307f88d8ea6SDavid Ahern 
13087bf4796dSDavid Ahern /* if existing nexthop has ipv6 routes linked to it, need
13097bf4796dSDavid Ahern  * to verify this new spec works with ipv6
13107bf4796dSDavid Ahern  */
13117bf4796dSDavid Ahern static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new,
13127bf4796dSDavid Ahern 			      struct netlink_ext_ack *extack)
13137bf4796dSDavid Ahern {
13147bf4796dSDavid Ahern 	struct fib6_info *f6i;
13157bf4796dSDavid Ahern 
13167bf4796dSDavid Ahern 	if (list_empty(&old->f6i_list))
13177bf4796dSDavid Ahern 		return 0;
13187bf4796dSDavid Ahern 
13197bf4796dSDavid Ahern 	list_for_each_entry(f6i, &old->f6i_list, nh_list) {
13207bf4796dSDavid Ahern 		if (check_src_addr(&f6i->fib6_src.addr, extack) < 0)
13217bf4796dSDavid Ahern 			return -EINVAL;
13227bf4796dSDavid Ahern 	}
13237bf4796dSDavid Ahern 
13247bf4796dSDavid Ahern 	return fib6_check_nexthop(new, NULL, extack);
13257bf4796dSDavid Ahern }
13267bf4796dSDavid Ahern 
1327ce9ac056SDavid Ahern static int nexthop_check_scope(struct nh_info *nhi, u8 scope,
13284c7e8084SDavid Ahern 			       struct netlink_ext_ack *extack)
13294c7e8084SDavid Ahern {
13304c7e8084SDavid Ahern 	if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) {
13314c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack,
13324c7e8084SDavid Ahern 			       "Route with host scope can not have a gateway");
13334c7e8084SDavid Ahern 		return -EINVAL;
13344c7e8084SDavid Ahern 	}
13354c7e8084SDavid Ahern 
13364c7e8084SDavid Ahern 	if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) {
13374c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop");
13384c7e8084SDavid Ahern 		return -EINVAL;
13394c7e8084SDavid Ahern 	}
13404c7e8084SDavid Ahern 
13414c7e8084SDavid Ahern 	return 0;
13424c7e8084SDavid Ahern }
13434c7e8084SDavid Ahern 
13444c7e8084SDavid Ahern /* Invoked by fib add code to verify nexthop by id is ok with
13454c7e8084SDavid Ahern  * config for prefix; parts of fib_check_nh not done when nexthop
13464c7e8084SDavid Ahern  * object is used.
13474c7e8084SDavid Ahern  */
13484c7e8084SDavid Ahern int fib_check_nexthop(struct nexthop *nh, u8 scope,
13494c7e8084SDavid Ahern 		      struct netlink_ext_ack *extack)
13504c7e8084SDavid Ahern {
1351ce9ac056SDavid Ahern 	struct nh_info *nhi;
13524c7e8084SDavid Ahern 	int err = 0;
13534c7e8084SDavid Ahern 
1354ce9ac056SDavid Ahern 	if (nh->is_group) {
1355ce9ac056SDavid Ahern 		struct nh_group *nhg;
1356ce9ac056SDavid Ahern 
1357ce9ac056SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
1358ce9ac056SDavid Ahern 		if (nhg->fdb_nh) {
135938428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
136038428d68SRoopa Prabhu 			err = -EINVAL;
136138428d68SRoopa Prabhu 			goto out;
136238428d68SRoopa Prabhu 		}
136338428d68SRoopa Prabhu 
13644c7e8084SDavid Ahern 		if (scope == RT_SCOPE_HOST) {
13654c7e8084SDavid Ahern 			NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops");
13664c7e8084SDavid Ahern 			err = -EINVAL;
13674c7e8084SDavid Ahern 			goto out;
13684c7e8084SDavid Ahern 		}
13694c7e8084SDavid Ahern 
13704c7e8084SDavid Ahern 		/* all nexthops in a group have the same scope */
1371ce9ac056SDavid Ahern 		nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info);
1372ce9ac056SDavid Ahern 		err = nexthop_check_scope(nhi, scope, extack);
13734c7e8084SDavid Ahern 	} else {
1374ce9ac056SDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
1375ce9ac056SDavid Ahern 		if (nhi->fdb_nh) {
1376ce9ac056SDavid Ahern 			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
1377ce9ac056SDavid Ahern 			err = -EINVAL;
1378ce9ac056SDavid Ahern 			goto out;
13794c7e8084SDavid Ahern 		}
1380ce9ac056SDavid Ahern 		err = nexthop_check_scope(nhi, scope, extack);
1381ce9ac056SDavid Ahern 	}
1382ce9ac056SDavid Ahern 
13834c7e8084SDavid Ahern out:
13844c7e8084SDavid Ahern 	return err;
13854c7e8084SDavid Ahern }
13864c7e8084SDavid Ahern 
13877bf4796dSDavid Ahern static int fib_check_nh_list(struct nexthop *old, struct nexthop *new,
13887bf4796dSDavid Ahern 			     struct netlink_ext_ack *extack)
13897bf4796dSDavid Ahern {
13907bf4796dSDavid Ahern 	struct fib_info *fi;
13917bf4796dSDavid Ahern 
13927bf4796dSDavid Ahern 	list_for_each_entry(fi, &old->fi_list, nh_list) {
13937bf4796dSDavid Ahern 		int err;
13947bf4796dSDavid Ahern 
13957bf4796dSDavid Ahern 		err = fib_check_nexthop(new, fi->fib_scope, extack);
13967bf4796dSDavid Ahern 		if (err)
13977bf4796dSDavid Ahern 			return err;
13987bf4796dSDavid Ahern 	}
13997bf4796dSDavid Ahern 	return 0;
14007bf4796dSDavid Ahern }
14017bf4796dSDavid Ahern 
1402283a72a5SPetr Machata static bool nh_res_nhge_is_balanced(const struct nh_grp_entry *nhge)
1403283a72a5SPetr Machata {
1404283a72a5SPetr Machata 	return nhge->res.count_buckets == nhge->res.wants_buckets;
1405283a72a5SPetr Machata }
1406283a72a5SPetr Machata 
1407283a72a5SPetr Machata static bool nh_res_nhge_is_ow(const struct nh_grp_entry *nhge)
1408283a72a5SPetr Machata {
1409283a72a5SPetr Machata 	return nhge->res.count_buckets > nhge->res.wants_buckets;
1410283a72a5SPetr Machata }
1411283a72a5SPetr Machata 
1412283a72a5SPetr Machata static bool nh_res_nhge_is_uw(const struct nh_grp_entry *nhge)
1413283a72a5SPetr Machata {
1414283a72a5SPetr Machata 	return nhge->res.count_buckets < nhge->res.wants_buckets;
1415283a72a5SPetr Machata }
1416283a72a5SPetr Machata 
1417283a72a5SPetr Machata static bool nh_res_table_is_balanced(const struct nh_res_table *res_table)
1418283a72a5SPetr Machata {
1419283a72a5SPetr Machata 	return list_empty(&res_table->uw_nh_entries);
1420283a72a5SPetr Machata }
1421283a72a5SPetr Machata 
1422283a72a5SPetr Machata static void nh_res_bucket_unset_nh(struct nh_res_bucket *bucket)
1423283a72a5SPetr Machata {
1424283a72a5SPetr Machata 	struct nh_grp_entry *nhge;
1425283a72a5SPetr Machata 
1426283a72a5SPetr Machata 	if (bucket->occupied) {
1427283a72a5SPetr Machata 		nhge = nh_res_dereference(bucket->nh_entry);
1428283a72a5SPetr Machata 		nhge->res.count_buckets--;
1429283a72a5SPetr Machata 		bucket->occupied = false;
1430283a72a5SPetr Machata 	}
1431283a72a5SPetr Machata }
1432283a72a5SPetr Machata 
1433283a72a5SPetr Machata static void nh_res_bucket_set_nh(struct nh_res_bucket *bucket,
1434283a72a5SPetr Machata 				 struct nh_grp_entry *nhge)
1435283a72a5SPetr Machata {
1436283a72a5SPetr Machata 	nh_res_bucket_unset_nh(bucket);
1437283a72a5SPetr Machata 
1438283a72a5SPetr Machata 	bucket->occupied = true;
1439283a72a5SPetr Machata 	rcu_assign_pointer(bucket->nh_entry, nhge);
1440283a72a5SPetr Machata 	nhge->res.count_buckets++;
1441283a72a5SPetr Machata }
1442283a72a5SPetr Machata 
1443283a72a5SPetr Machata static bool nh_res_bucket_should_migrate(struct nh_res_table *res_table,
1444283a72a5SPetr Machata 					 struct nh_res_bucket *bucket,
1445283a72a5SPetr Machata 					 unsigned long *deadline, bool *force)
1446283a72a5SPetr Machata {
1447283a72a5SPetr Machata 	unsigned long now = jiffies;
1448283a72a5SPetr Machata 	struct nh_grp_entry *nhge;
1449283a72a5SPetr Machata 	unsigned long idle_point;
1450283a72a5SPetr Machata 
1451283a72a5SPetr Machata 	if (!bucket->occupied) {
1452283a72a5SPetr Machata 		/* The bucket is not occupied, its NHGE pointer is either
1453283a72a5SPetr Machata 		 * NULL or obsolete. We _have to_ migrate: set force.
1454283a72a5SPetr Machata 		 */
1455283a72a5SPetr Machata 		*force = true;
1456283a72a5SPetr Machata 		return true;
1457283a72a5SPetr Machata 	}
1458283a72a5SPetr Machata 
1459283a72a5SPetr Machata 	nhge = nh_res_dereference(bucket->nh_entry);
1460283a72a5SPetr Machata 
1461283a72a5SPetr Machata 	/* If the bucket is populated by an underweight or balanced
1462283a72a5SPetr Machata 	 * nexthop, do not migrate.
1463283a72a5SPetr Machata 	 */
1464283a72a5SPetr Machata 	if (!nh_res_nhge_is_ow(nhge))
1465283a72a5SPetr Machata 		return false;
1466283a72a5SPetr Machata 
1467283a72a5SPetr Machata 	/* At this point we know that the bucket is populated with an
1468283a72a5SPetr Machata 	 * overweight nexthop. It needs to be migrated to a new nexthop if
1469283a72a5SPetr Machata 	 * the idle timer of unbalanced timer expired.
1470283a72a5SPetr Machata 	 */
1471283a72a5SPetr Machata 
1472283a72a5SPetr Machata 	idle_point = nh_res_bucket_idle_point(res_table, bucket, now);
1473283a72a5SPetr Machata 	if (time_after_eq(now, idle_point)) {
1474283a72a5SPetr Machata 		/* The bucket is idle. We _can_ migrate: unset force. */
1475283a72a5SPetr Machata 		*force = false;
1476283a72a5SPetr Machata 		return true;
1477283a72a5SPetr Machata 	}
1478283a72a5SPetr Machata 
1479283a72a5SPetr Machata 	/* Unbalanced timer of 0 means "never force". */
1480283a72a5SPetr Machata 	if (res_table->unbalanced_timer) {
1481283a72a5SPetr Machata 		unsigned long unb_point;
1482283a72a5SPetr Machata 
1483283a72a5SPetr Machata 		unb_point = nh_res_table_unb_point(res_table);
1484283a72a5SPetr Machata 		if (time_after(now, unb_point)) {
1485283a72a5SPetr Machata 			/* The bucket is not idle, but the unbalanced timer
1486283a72a5SPetr Machata 			 * expired. We _can_ migrate, but set force anyway,
1487283a72a5SPetr Machata 			 * so that drivers know to ignore activity reports
1488283a72a5SPetr Machata 			 * from the HW.
1489283a72a5SPetr Machata 			 */
1490283a72a5SPetr Machata 			*force = true;
1491283a72a5SPetr Machata 			return true;
1492283a72a5SPetr Machata 		}
1493283a72a5SPetr Machata 
1494283a72a5SPetr Machata 		nh_res_time_set_deadline(unb_point, deadline);
1495283a72a5SPetr Machata 	}
1496283a72a5SPetr Machata 
1497283a72a5SPetr Machata 	nh_res_time_set_deadline(idle_point, deadline);
1498283a72a5SPetr Machata 	return false;
1499283a72a5SPetr Machata }
1500283a72a5SPetr Machata 
1501283a72a5SPetr Machata static bool nh_res_bucket_migrate(struct nh_res_table *res_table,
15020b4818aaSPetr Machata 				  u16 bucket_index, bool notify,
15030b4818aaSPetr Machata 				  bool notify_nl, bool force)
1504283a72a5SPetr Machata {
1505283a72a5SPetr Machata 	struct nh_res_bucket *bucket = &res_table->nh_buckets[bucket_index];
1506283a72a5SPetr Machata 	struct nh_grp_entry *new_nhge;
15077c37c7e0SPetr Machata 	struct netlink_ext_ack extack;
15087c37c7e0SPetr Machata 	int err;
1509283a72a5SPetr Machata 
1510283a72a5SPetr Machata 	new_nhge = list_first_entry_or_null(&res_table->uw_nh_entries,
1511283a72a5SPetr Machata 					    struct nh_grp_entry,
1512283a72a5SPetr Machata 					    res.uw_nh_entry);
1513283a72a5SPetr Machata 	if (WARN_ON_ONCE(!new_nhge))
1514283a72a5SPetr Machata 		/* If this function is called, "bucket" is either not
1515283a72a5SPetr Machata 		 * occupied, or it belongs to a next hop that is
1516283a72a5SPetr Machata 		 * overweight. In either case, there ought to be a
1517283a72a5SPetr Machata 		 * corresponding underweight next hop.
1518283a72a5SPetr Machata 		 */
1519283a72a5SPetr Machata 		return false;
1520283a72a5SPetr Machata 
15217c37c7e0SPetr Machata 	if (notify) {
15227c37c7e0SPetr Machata 		struct nh_grp_entry *old_nhge;
15237c37c7e0SPetr Machata 
15247c37c7e0SPetr Machata 		old_nhge = nh_res_dereference(bucket->nh_entry);
15257c37c7e0SPetr Machata 		err = call_nexthop_res_bucket_notifiers(res_table->net,
15267c37c7e0SPetr Machata 							res_table->nhg_id,
15277c37c7e0SPetr Machata 							bucket_index, force,
15287c37c7e0SPetr Machata 							old_nhge->nh,
15297c37c7e0SPetr Machata 							new_nhge->nh, &extack);
15307c37c7e0SPetr Machata 		if (err) {
15317c37c7e0SPetr Machata 			pr_err_ratelimited("%s\n", extack._msg);
15327c37c7e0SPetr Machata 			if (!force)
15337c37c7e0SPetr Machata 				return false;
15347c37c7e0SPetr Machata 			/* It is not possible to veto a forced replacement, so
15357c37c7e0SPetr Machata 			 * just clear the hardware flags from the nexthop
15367c37c7e0SPetr Machata 			 * bucket to indicate to user space that this bucket is
15377c37c7e0SPetr Machata 			 * not correctly populated in hardware.
15387c37c7e0SPetr Machata 			 */
15397c37c7e0SPetr Machata 			bucket->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP);
15407c37c7e0SPetr Machata 		}
15417c37c7e0SPetr Machata 	}
15427c37c7e0SPetr Machata 
1543283a72a5SPetr Machata 	nh_res_bucket_set_nh(bucket, new_nhge);
1544283a72a5SPetr Machata 	nh_res_bucket_set_idle(res_table, bucket);
1545283a72a5SPetr Machata 
15460b4818aaSPetr Machata 	if (notify_nl)
15470b4818aaSPetr Machata 		nexthop_bucket_notify(res_table, bucket_index);
15480b4818aaSPetr Machata 
1549283a72a5SPetr Machata 	if (nh_res_nhge_is_balanced(new_nhge))
1550283a72a5SPetr Machata 		list_del(&new_nhge->res.uw_nh_entry);
1551283a72a5SPetr Machata 	return true;
1552283a72a5SPetr Machata }
1553283a72a5SPetr Machata 
1554283a72a5SPetr Machata #define NH_RES_UPKEEP_DW_MINIMUM_INTERVAL (HZ / 2)
1555283a72a5SPetr Machata 
15560b4818aaSPetr Machata static void nh_res_table_upkeep(struct nh_res_table *res_table,
15570b4818aaSPetr Machata 				bool notify, bool notify_nl)
1558283a72a5SPetr Machata {
1559283a72a5SPetr Machata 	unsigned long now = jiffies;
1560283a72a5SPetr Machata 	unsigned long deadline;
1561283a72a5SPetr Machata 	u16 i;
1562283a72a5SPetr Machata 
1563283a72a5SPetr Machata 	/* Deadline is the next time that upkeep should be run. It is the
1564283a72a5SPetr Machata 	 * earliest time at which one of the buckets might be migrated.
1565283a72a5SPetr Machata 	 * Start at the most pessimistic estimate: either unbalanced_timer
1566283a72a5SPetr Machata 	 * from now, or if there is none, idle_timer from now. For each
1567283a72a5SPetr Machata 	 * encountered time point, call nh_res_time_set_deadline() to
1568283a72a5SPetr Machata 	 * refine the estimate.
1569283a72a5SPetr Machata 	 */
1570283a72a5SPetr Machata 	if (res_table->unbalanced_timer)
1571283a72a5SPetr Machata 		deadline = now + res_table->unbalanced_timer;
1572283a72a5SPetr Machata 	else
1573283a72a5SPetr Machata 		deadline = now + res_table->idle_timer;
1574283a72a5SPetr Machata 
1575283a72a5SPetr Machata 	for (i = 0; i < res_table->num_nh_buckets; i++) {
1576283a72a5SPetr Machata 		struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
1577283a72a5SPetr Machata 		bool force;
1578283a72a5SPetr Machata 
1579283a72a5SPetr Machata 		if (nh_res_bucket_should_migrate(res_table, bucket,
1580283a72a5SPetr Machata 						 &deadline, &force)) {
15817c37c7e0SPetr Machata 			if (!nh_res_bucket_migrate(res_table, i, notify,
15820b4818aaSPetr Machata 						   notify_nl, force)) {
1583283a72a5SPetr Machata 				unsigned long idle_point;
1584283a72a5SPetr Machata 
1585283a72a5SPetr Machata 				/* A driver can override the migration
1586283a72a5SPetr Machata 				 * decision if the HW reports that the
1587283a72a5SPetr Machata 				 * bucket is actually not idle. Therefore
1588283a72a5SPetr Machata 				 * remark the bucket as busy again and
1589283a72a5SPetr Machata 				 * update the deadline.
1590283a72a5SPetr Machata 				 */
1591283a72a5SPetr Machata 				nh_res_bucket_set_busy(bucket);
1592283a72a5SPetr Machata 				idle_point = nh_res_bucket_idle_point(res_table,
1593283a72a5SPetr Machata 								      bucket,
1594283a72a5SPetr Machata 								      now);
1595283a72a5SPetr Machata 				nh_res_time_set_deadline(idle_point, &deadline);
1596283a72a5SPetr Machata 			}
1597283a72a5SPetr Machata 		}
1598283a72a5SPetr Machata 	}
1599283a72a5SPetr Machata 
1600283a72a5SPetr Machata 	/* If the group is still unbalanced, schedule the next upkeep to
1601283a72a5SPetr Machata 	 * either the deadline computed above, or the minimum deadline,
1602283a72a5SPetr Machata 	 * whichever comes later.
1603283a72a5SPetr Machata 	 */
1604283a72a5SPetr Machata 	if (!nh_res_table_is_balanced(res_table)) {
1605283a72a5SPetr Machata 		unsigned long now = jiffies;
1606283a72a5SPetr Machata 		unsigned long min_deadline;
1607283a72a5SPetr Machata 
1608283a72a5SPetr Machata 		min_deadline = now + NH_RES_UPKEEP_DW_MINIMUM_INTERVAL;
1609283a72a5SPetr Machata 		if (time_before(deadline, min_deadline))
1610283a72a5SPetr Machata 			deadline = min_deadline;
1611283a72a5SPetr Machata 
1612283a72a5SPetr Machata 		queue_delayed_work(system_power_efficient_wq,
1613283a72a5SPetr Machata 				   &res_table->upkeep_dw, deadline - now);
1614283a72a5SPetr Machata 	}
1615283a72a5SPetr Machata }
1616283a72a5SPetr Machata 
1617283a72a5SPetr Machata static void nh_res_table_upkeep_dw(struct work_struct *work)
1618283a72a5SPetr Machata {
1619283a72a5SPetr Machata 	struct delayed_work *dw = to_delayed_work(work);
1620283a72a5SPetr Machata 	struct nh_res_table *res_table;
1621283a72a5SPetr Machata 
1622283a72a5SPetr Machata 	res_table = container_of(dw, struct nh_res_table, upkeep_dw);
16230b4818aaSPetr Machata 	nh_res_table_upkeep(res_table, true, true);
1624283a72a5SPetr Machata }
1625283a72a5SPetr Machata 
1626283a72a5SPetr Machata static void nh_res_table_cancel_upkeep(struct nh_res_table *res_table)
1627283a72a5SPetr Machata {
1628283a72a5SPetr Machata 	cancel_delayed_work_sync(&res_table->upkeep_dw);
1629283a72a5SPetr Machata }
1630283a72a5SPetr Machata 
1631283a72a5SPetr Machata static void nh_res_group_rebalance(struct nh_group *nhg,
1632283a72a5SPetr Machata 				   struct nh_res_table *res_table)
1633283a72a5SPetr Machata {
1634283a72a5SPetr Machata 	int prev_upper_bound = 0;
1635283a72a5SPetr Machata 	int total = 0;
1636283a72a5SPetr Machata 	int w = 0;
1637283a72a5SPetr Machata 	int i;
1638283a72a5SPetr Machata 
1639283a72a5SPetr Machata 	INIT_LIST_HEAD(&res_table->uw_nh_entries);
1640283a72a5SPetr Machata 
1641283a72a5SPetr Machata 	for (i = 0; i < nhg->num_nh; ++i)
1642283a72a5SPetr Machata 		total += nhg->nh_entries[i].weight;
1643283a72a5SPetr Machata 
1644283a72a5SPetr Machata 	for (i = 0; i < nhg->num_nh; ++i) {
1645283a72a5SPetr Machata 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
1646283a72a5SPetr Machata 		int upper_bound;
1647283a72a5SPetr Machata 
1648283a72a5SPetr Machata 		w += nhge->weight;
1649283a72a5SPetr Machata 		upper_bound = DIV_ROUND_CLOSEST(res_table->num_nh_buckets * w,
1650283a72a5SPetr Machata 						total);
1651283a72a5SPetr Machata 		nhge->res.wants_buckets = upper_bound - prev_upper_bound;
1652283a72a5SPetr Machata 		prev_upper_bound = upper_bound;
1653283a72a5SPetr Machata 
1654283a72a5SPetr Machata 		if (nh_res_nhge_is_uw(nhge)) {
1655283a72a5SPetr Machata 			if (list_empty(&res_table->uw_nh_entries))
1656283a72a5SPetr Machata 				res_table->unbalanced_since = jiffies;
1657283a72a5SPetr Machata 			list_add(&nhge->res.uw_nh_entry,
1658283a72a5SPetr Machata 				 &res_table->uw_nh_entries);
1659283a72a5SPetr Machata 		}
1660283a72a5SPetr Machata 	}
1661283a72a5SPetr Machata }
1662283a72a5SPetr Machata 
1663283a72a5SPetr Machata /* Migrate buckets in res_table so that they reference NHGE's from NHG with
1664283a72a5SPetr Machata  * the right NH ID. Set those buckets that do not have a corresponding NHGE
1665283a72a5SPetr Machata  * entry in NHG as not occupied.
1666283a72a5SPetr Machata  */
1667283a72a5SPetr Machata static void nh_res_table_migrate_buckets(struct nh_res_table *res_table,
1668283a72a5SPetr Machata 					 struct nh_group *nhg)
1669283a72a5SPetr Machata {
1670283a72a5SPetr Machata 	u16 i;
1671283a72a5SPetr Machata 
1672283a72a5SPetr Machata 	for (i = 0; i < res_table->num_nh_buckets; i++) {
1673283a72a5SPetr Machata 		struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
1674283a72a5SPetr Machata 		u32 id = rtnl_dereference(bucket->nh_entry)->nh->id;
1675283a72a5SPetr Machata 		bool found = false;
1676283a72a5SPetr Machata 		int j;
1677283a72a5SPetr Machata 
1678283a72a5SPetr Machata 		for (j = 0; j < nhg->num_nh; j++) {
1679283a72a5SPetr Machata 			struct nh_grp_entry *nhge = &nhg->nh_entries[j];
1680283a72a5SPetr Machata 
1681283a72a5SPetr Machata 			if (nhge->nh->id == id) {
1682283a72a5SPetr Machata 				nh_res_bucket_set_nh(bucket, nhge);
1683283a72a5SPetr Machata 				found = true;
1684283a72a5SPetr Machata 				break;
1685283a72a5SPetr Machata 			}
1686283a72a5SPetr Machata 		}
1687283a72a5SPetr Machata 
1688283a72a5SPetr Machata 		if (!found)
1689283a72a5SPetr Machata 			nh_res_bucket_unset_nh(bucket);
1690283a72a5SPetr Machata 	}
1691283a72a5SPetr Machata }
1692283a72a5SPetr Machata 
1693283a72a5SPetr Machata static void replace_nexthop_grp_res(struct nh_group *oldg,
1694283a72a5SPetr Machata 				    struct nh_group *newg)
1695283a72a5SPetr Machata {
1696283a72a5SPetr Machata 	/* For NH group replacement, the new NHG might only have a stub
1697283a72a5SPetr Machata 	 * hash table with 0 buckets, because the number of buckets was not
1698283a72a5SPetr Machata 	 * specified. For NH removal, oldg and newg both reference the same
1699283a72a5SPetr Machata 	 * res_table. So in any case, in the following, we want to work
1700283a72a5SPetr Machata 	 * with oldg->res_table.
1701283a72a5SPetr Machata 	 */
1702283a72a5SPetr Machata 	struct nh_res_table *old_res_table = rtnl_dereference(oldg->res_table);
1703283a72a5SPetr Machata 	unsigned long prev_unbalanced_since = old_res_table->unbalanced_since;
1704283a72a5SPetr Machata 	bool prev_has_uw = !list_empty(&old_res_table->uw_nh_entries);
1705283a72a5SPetr Machata 
1706283a72a5SPetr Machata 	nh_res_table_cancel_upkeep(old_res_table);
1707283a72a5SPetr Machata 	nh_res_table_migrate_buckets(old_res_table, newg);
1708283a72a5SPetr Machata 	nh_res_group_rebalance(newg, old_res_table);
1709283a72a5SPetr Machata 	if (prev_has_uw && !list_empty(&old_res_table->uw_nh_entries))
1710283a72a5SPetr Machata 		old_res_table->unbalanced_since = prev_unbalanced_since;
17110b4818aaSPetr Machata 	nh_res_table_upkeep(old_res_table, true, false);
1712283a72a5SPetr Machata }
1713283a72a5SPetr Machata 
1714de1d1ee3SPetr Machata static void nh_hthr_group_rebalance(struct nh_group *nhg)
1715430a0491SDavid Ahern {
1716430a0491SDavid Ahern 	int total = 0;
1717430a0491SDavid Ahern 	int w = 0;
1718430a0491SDavid Ahern 	int i;
1719430a0491SDavid Ahern 
1720430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i)
1721430a0491SDavid Ahern 		total += nhg->nh_entries[i].weight;
1722430a0491SDavid Ahern 
1723430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
1724430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
1725430a0491SDavid Ahern 		int upper_bound;
1726430a0491SDavid Ahern 
1727430a0491SDavid Ahern 		w += nhge->weight;
1728430a0491SDavid Ahern 		upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1;
1729de1d1ee3SPetr Machata 		atomic_set(&nhge->hthr.upper_bound, upper_bound);
1730430a0491SDavid Ahern 	}
1731430a0491SDavid Ahern }
1732430a0491SDavid Ahern 
1733ac21753aSDavid Ahern static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge,
1734430a0491SDavid Ahern 				struct nl_info *nlinfo)
1735430a0491SDavid Ahern {
173690f33bffSNikolay Aleksandrov 	struct nh_grp_entry *nhges, *new_nhges;
1737ac21753aSDavid Ahern 	struct nexthop *nhp = nhge->nh_parent;
1738833a1065SIdo Schimmel 	struct netlink_ext_ack extack;
1739430a0491SDavid Ahern 	struct nexthop *nh = nhge->nh;
174090f33bffSNikolay Aleksandrov 	struct nh_group *nhg, *newg;
1741833a1065SIdo Schimmel 	int i, j, err;
1742430a0491SDavid Ahern 
1743430a0491SDavid Ahern 	WARN_ON(!nh);
1744430a0491SDavid Ahern 
1745ac21753aSDavid Ahern 	nhg = rtnl_dereference(nhp->nh_grp);
174690f33bffSNikolay Aleksandrov 	newg = nhg->spare;
1747430a0491SDavid Ahern 
174890f33bffSNikolay Aleksandrov 	/* last entry, keep it visible and remove the parent */
174990f33bffSNikolay Aleksandrov 	if (nhg->num_nh == 1) {
175090f33bffSNikolay Aleksandrov 		remove_nexthop(net, nhp, nlinfo);
1751430a0491SDavid Ahern 		return;
175290f33bffSNikolay Aleksandrov 	}
1753430a0491SDavid Ahern 
1754863b2558SIdo Schimmel 	newg->has_v4 = false;
175590e1a9e2SPetr Machata 	newg->is_multipath = nhg->is_multipath;
1756de1d1ee3SPetr Machata 	newg->hash_threshold = nhg->hash_threshold;
1757283a72a5SPetr Machata 	newg->resilient = nhg->resilient;
1758ce9ac056SDavid Ahern 	newg->fdb_nh = nhg->fdb_nh;
175990f33bffSNikolay Aleksandrov 	newg->num_nh = nhg->num_nh;
1760430a0491SDavid Ahern 
176190f33bffSNikolay Aleksandrov 	/* copy old entries to new except the one getting removed */
176290f33bffSNikolay Aleksandrov 	nhges = nhg->nh_entries;
176390f33bffSNikolay Aleksandrov 	new_nhges = newg->nh_entries;
176490f33bffSNikolay Aleksandrov 	for (i = 0, j = 0; i < nhg->num_nh; ++i) {
1765863b2558SIdo Schimmel 		struct nh_info *nhi;
1766863b2558SIdo Schimmel 
176790f33bffSNikolay Aleksandrov 		/* current nexthop getting removed */
176890f33bffSNikolay Aleksandrov 		if (nhg->nh_entries[i].nh == nh) {
176990f33bffSNikolay Aleksandrov 			newg->num_nh--;
177090f33bffSNikolay Aleksandrov 			continue;
177190f33bffSNikolay Aleksandrov 		}
1772430a0491SDavid Ahern 
1773863b2558SIdo Schimmel 		nhi = rtnl_dereference(nhges[i].nh->nh_info);
1774863b2558SIdo Schimmel 		if (nhi->family == AF_INET)
1775863b2558SIdo Schimmel 			newg->has_v4 = true;
1776863b2558SIdo Schimmel 
177790f33bffSNikolay Aleksandrov 		list_del(&nhges[i].nh_list);
177890f33bffSNikolay Aleksandrov 		new_nhges[j].nh_parent = nhges[i].nh_parent;
177990f33bffSNikolay Aleksandrov 		new_nhges[j].nh = nhges[i].nh;
178090f33bffSNikolay Aleksandrov 		new_nhges[j].weight = nhges[i].weight;
178190f33bffSNikolay Aleksandrov 		list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list);
178290f33bffSNikolay Aleksandrov 		j++;
178390f33bffSNikolay Aleksandrov 	}
178490f33bffSNikolay Aleksandrov 
1785de1d1ee3SPetr Machata 	if (newg->hash_threshold)
1786de1d1ee3SPetr Machata 		nh_hthr_group_rebalance(newg);
1787283a72a5SPetr Machata 	else if (newg->resilient)
1788283a72a5SPetr Machata 		replace_nexthop_grp_res(nhg, newg);
1789283a72a5SPetr Machata 
179090f33bffSNikolay Aleksandrov 	rcu_assign_pointer(nhp->nh_grp, newg);
179190f33bffSNikolay Aleksandrov 
179290f33bffSNikolay Aleksandrov 	list_del(&nhge->nh_list);
179390f33bffSNikolay Aleksandrov 	nexthop_put(nhge->nh);
1794430a0491SDavid Ahern 
17957c37c7e0SPetr Machata 	/* Removal of a NH from a resilient group is notified through
17967c37c7e0SPetr Machata 	 * bucket notifications.
17977c37c7e0SPetr Machata 	 */
1798de1d1ee3SPetr Machata 	if (newg->hash_threshold) {
17997c37c7e0SPetr Machata 		err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp,
18007c37c7e0SPetr Machata 					     &extack);
1801833a1065SIdo Schimmel 		if (err)
1802833a1065SIdo Schimmel 			pr_err("%s\n", extack._msg);
18037c37c7e0SPetr Machata 	}
1804833a1065SIdo Schimmel 
1805430a0491SDavid Ahern 	if (nlinfo)
1806ac21753aSDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo);
1807430a0491SDavid Ahern }
1808430a0491SDavid Ahern 
1809430a0491SDavid Ahern static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
1810430a0491SDavid Ahern 				       struct nl_info *nlinfo)
1811430a0491SDavid Ahern {
1812430a0491SDavid Ahern 	struct nh_grp_entry *nhge, *tmp;
1813430a0491SDavid Ahern 
1814ac21753aSDavid Ahern 	list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list)
1815ac21753aSDavid Ahern 		remove_nh_grp_entry(net, nhge, nlinfo);
1816430a0491SDavid Ahern 
181790f33bffSNikolay Aleksandrov 	/* make sure all see the newly published array before releasing rtnl */
1818df6afe2fSIdo Schimmel 	synchronize_net();
1819430a0491SDavid Ahern }
1820430a0491SDavid Ahern 
1821430a0491SDavid Ahern static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
1822430a0491SDavid Ahern {
1823430a0491SDavid Ahern 	struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp);
1824283a72a5SPetr Machata 	struct nh_res_table *res_table;
1825430a0491SDavid Ahern 	int i, num_nh = nhg->num_nh;
1826430a0491SDavid Ahern 
1827430a0491SDavid Ahern 	for (i = 0; i < num_nh; ++i) {
1828430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
1829430a0491SDavid Ahern 
1830430a0491SDavid Ahern 		if (WARN_ON(!nhge->nh))
1831430a0491SDavid Ahern 			continue;
1832430a0491SDavid Ahern 
183390f33bffSNikolay Aleksandrov 		list_del_init(&nhge->nh_list);
1834430a0491SDavid Ahern 	}
1835283a72a5SPetr Machata 
1836283a72a5SPetr Machata 	if (nhg->resilient) {
1837283a72a5SPetr Machata 		res_table = rtnl_dereference(nhg->res_table);
1838283a72a5SPetr Machata 		nh_res_table_cancel_upkeep(res_table);
1839283a72a5SPetr Machata 	}
1840430a0491SDavid Ahern }
1841430a0491SDavid Ahern 
18427bf4796dSDavid Ahern /* not called for nexthop replace */
18434c7e8084SDavid Ahern static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
18444c7e8084SDavid Ahern {
1845f88d8ea6SDavid Ahern 	struct fib6_info *f6i, *tmp;
18464c7e8084SDavid Ahern 	bool do_flush = false;
18474c7e8084SDavid Ahern 	struct fib_info *fi;
18484c7e8084SDavid Ahern 
18494c7e8084SDavid Ahern 	list_for_each_entry(fi, &nh->fi_list, nh_list) {
18504c7e8084SDavid Ahern 		fi->fib_flags |= RTNH_F_DEAD;
18514c7e8084SDavid Ahern 		do_flush = true;
18524c7e8084SDavid Ahern 	}
18534c7e8084SDavid Ahern 	if (do_flush)
18544c7e8084SDavid Ahern 		fib_flush(net);
1855f88d8ea6SDavid Ahern 
1856f88d8ea6SDavid Ahern 	/* ip6_del_rt removes the entry from this list hence the _safe */
1857f88d8ea6SDavid Ahern 	list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
1858f88d8ea6SDavid Ahern 		/* __ip6_del_rt does a release, so do a hold here */
1859f88d8ea6SDavid Ahern 		fib6_info_hold(f6i);
18604f80116dSRoopa Prabhu 		ipv6_stub->ip6_del_rt(net, f6i,
18614f80116dSRoopa Prabhu 				      !net->ipv4.sysctl_nexthop_compat_mode);
1862f88d8ea6SDavid Ahern 	}
18634c7e8084SDavid Ahern }
18644c7e8084SDavid Ahern 
1865430a0491SDavid Ahern static void __remove_nexthop(struct net *net, struct nexthop *nh,
1866430a0491SDavid Ahern 			     struct nl_info *nlinfo)
1867430a0491SDavid Ahern {
18684c7e8084SDavid Ahern 	__remove_nexthop_fib(net, nh);
18694c7e8084SDavid Ahern 
1870430a0491SDavid Ahern 	if (nh->is_group) {
1871430a0491SDavid Ahern 		remove_nexthop_group(nh, nlinfo);
1872430a0491SDavid Ahern 	} else {
1873597cfe4fSDavid Ahern 		struct nh_info *nhi;
1874597cfe4fSDavid Ahern 
1875597cfe4fSDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
1876597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev)
1877597cfe4fSDavid Ahern 			hlist_del(&nhi->dev_hash);
1878430a0491SDavid Ahern 
1879430a0491SDavid Ahern 		remove_nexthop_from_groups(net, nh, nlinfo);
1880430a0491SDavid Ahern 	}
1881597cfe4fSDavid Ahern }
1882597cfe4fSDavid Ahern 
1883ab84be7eSDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh,
1884430a0491SDavid Ahern 			   struct nl_info *nlinfo)
1885ab84be7eSDavid Ahern {
18863578d53dSIdo Schimmel 	call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh, NULL);
18870695564bSIdo Schimmel 
1888ab84be7eSDavid Ahern 	/* remove from the tree */
1889ab84be7eSDavid Ahern 	rb_erase(&nh->rb_node, &net->nexthop.rb_root);
1890ab84be7eSDavid Ahern 
1891ab84be7eSDavid Ahern 	if (nlinfo)
1892ab84be7eSDavid Ahern 		nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo);
1893ab84be7eSDavid Ahern 
1894430a0491SDavid Ahern 	__remove_nexthop(net, nh, nlinfo);
1895ab84be7eSDavid Ahern 	nh_base_seq_inc(net);
1896ab84be7eSDavid Ahern 
1897ab84be7eSDavid Ahern 	nexthop_put(nh);
1898ab84be7eSDavid Ahern }
1899ab84be7eSDavid Ahern 
19007bf4796dSDavid Ahern /* if any FIB entries reference this nexthop, any dst entries
19017bf4796dSDavid Ahern  * need to be regenerated
19027bf4796dSDavid Ahern  */
19031005f19bSNikolay Aleksandrov static void nh_rt_cache_flush(struct net *net, struct nexthop *nh,
19041005f19bSNikolay Aleksandrov 			      struct nexthop *replaced_nh)
19057bf4796dSDavid Ahern {
19067bf4796dSDavid Ahern 	struct fib6_info *f6i;
19071005f19bSNikolay Aleksandrov 	struct nh_group *nhg;
19081005f19bSNikolay Aleksandrov 	int i;
19097bf4796dSDavid Ahern 
19107bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list))
19117bf4796dSDavid Ahern 		rt_cache_flush(net);
19127bf4796dSDavid Ahern 
19137bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
19147bf4796dSDavid Ahern 		ipv6_stub->fib6_update_sernum(net, f6i);
19151005f19bSNikolay Aleksandrov 
19161005f19bSNikolay Aleksandrov 	/* if an IPv6 group was replaced, we have to release all old
19171005f19bSNikolay Aleksandrov 	 * dsts to make sure all refcounts are released
19181005f19bSNikolay Aleksandrov 	 */
19191005f19bSNikolay Aleksandrov 	if (!replaced_nh->is_group)
19201005f19bSNikolay Aleksandrov 		return;
19211005f19bSNikolay Aleksandrov 
19221005f19bSNikolay Aleksandrov 	nhg = rtnl_dereference(replaced_nh->nh_grp);
19231005f19bSNikolay Aleksandrov 	for (i = 0; i < nhg->num_nh; i++) {
19241005f19bSNikolay Aleksandrov 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
19251005f19bSNikolay Aleksandrov 		struct nh_info *nhi = rtnl_dereference(nhge->nh->nh_info);
19261005f19bSNikolay Aleksandrov 
19271005f19bSNikolay Aleksandrov 		if (nhi->family == AF_INET6)
19281005f19bSNikolay Aleksandrov 			ipv6_stub->fib6_nh_release_dsts(&nhi->fib6_nh);
19291005f19bSNikolay Aleksandrov 	}
19307bf4796dSDavid Ahern }
19317bf4796dSDavid Ahern 
19327bf4796dSDavid Ahern static int replace_nexthop_grp(struct net *net, struct nexthop *old,
1933597f48e4SPetr Machata 			       struct nexthop *new, const struct nh_config *cfg,
19347bf4796dSDavid Ahern 			       struct netlink_ext_ack *extack)
19357bf4796dSDavid Ahern {
1936283a72a5SPetr Machata 	struct nh_res_table *tmp_table = NULL;
1937283a72a5SPetr Machata 	struct nh_res_table *new_res_table;
1938283a72a5SPetr Machata 	struct nh_res_table *old_res_table;
19397bf4796dSDavid Ahern 	struct nh_group *oldg, *newg;
1940d144cc5fSIdo Schimmel 	int i, err;
19417bf4796dSDavid Ahern 
19427bf4796dSDavid Ahern 	if (!new->is_group) {
19437bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop.");
19447bf4796dSDavid Ahern 		return -EINVAL;
19457bf4796dSDavid Ahern 	}
19467bf4796dSDavid Ahern 
19477bf4796dSDavid Ahern 	oldg = rtnl_dereference(old->nh_grp);
19487bf4796dSDavid Ahern 	newg = rtnl_dereference(new->nh_grp);
19497bf4796dSDavid Ahern 
1950de1d1ee3SPetr Machata 	if (newg->hash_threshold != oldg->hash_threshold) {
1951283a72a5SPetr Machata 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with one of a different type.");
1952283a72a5SPetr Machata 		return -EINVAL;
1953283a72a5SPetr Machata 	}
1954283a72a5SPetr Machata 
1955de1d1ee3SPetr Machata 	if (newg->hash_threshold) {
1956283a72a5SPetr Machata 		err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new,
1957283a72a5SPetr Machata 					     extack);
1958283a72a5SPetr Machata 		if (err)
1959283a72a5SPetr Machata 			return err;
1960283a72a5SPetr Machata 	} else if (newg->resilient) {
1961283a72a5SPetr Machata 		new_res_table = rtnl_dereference(newg->res_table);
1962283a72a5SPetr Machata 		old_res_table = rtnl_dereference(oldg->res_table);
1963283a72a5SPetr Machata 
1964283a72a5SPetr Machata 		/* Accept if num_nh_buckets was not given, but if it was
1965283a72a5SPetr Machata 		 * given, demand that the value be correct.
1966283a72a5SPetr Machata 		 */
1967283a72a5SPetr Machata 		if (cfg->nh_grp_res_has_num_buckets &&
1968283a72a5SPetr Machata 		    cfg->nh_grp_res_num_buckets !=
1969283a72a5SPetr Machata 		    old_res_table->num_nh_buckets) {
1970283a72a5SPetr Machata 			NL_SET_ERR_MSG(extack, "Can not change number of buckets of a resilient nexthop group.");
1971283a72a5SPetr Machata 			return -EINVAL;
1972283a72a5SPetr Machata 		}
1973283a72a5SPetr Machata 
19747c37c7e0SPetr Machata 		/* Emit a pre-replace notification so that listeners could veto
19757c37c7e0SPetr Machata 		 * a potentially unsupported configuration. Otherwise,
19767c37c7e0SPetr Machata 		 * individual bucket replacement notifications would need to be
19777c37c7e0SPetr Machata 		 * vetoed, which is something that should only happen if the
19787c37c7e0SPetr Machata 		 * bucket is currently active.
19797c37c7e0SPetr Machata 		 */
19807c37c7e0SPetr Machata 		err = call_nexthop_res_table_notifiers(net, new, extack);
19817c37c7e0SPetr Machata 		if (err)
19827c37c7e0SPetr Machata 			return err;
19837c37c7e0SPetr Machata 
1984283a72a5SPetr Machata 		if (cfg->nh_grp_res_has_idle_timer)
1985283a72a5SPetr Machata 			old_res_table->idle_timer = cfg->nh_grp_res_idle_timer;
1986283a72a5SPetr Machata 		if (cfg->nh_grp_res_has_unbalanced_timer)
1987283a72a5SPetr Machata 			old_res_table->unbalanced_timer =
1988283a72a5SPetr Machata 				cfg->nh_grp_res_unbalanced_timer;
1989283a72a5SPetr Machata 
1990283a72a5SPetr Machata 		replace_nexthop_grp_res(oldg, newg);
1991283a72a5SPetr Machata 
1992283a72a5SPetr Machata 		tmp_table = new_res_table;
1993283a72a5SPetr Machata 		rcu_assign_pointer(newg->res_table, old_res_table);
1994283a72a5SPetr Machata 		rcu_assign_pointer(newg->spare->res_table, old_res_table);
1995283a72a5SPetr Machata 	}
1996283a72a5SPetr Machata 
19977bf4796dSDavid Ahern 	/* update parents - used by nexthop code for cleanup */
19987bf4796dSDavid Ahern 	for (i = 0; i < newg->num_nh; i++)
19997bf4796dSDavid Ahern 		newg->nh_entries[i].nh_parent = old;
20007bf4796dSDavid Ahern 
20017bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_grp, newg);
20027bf4796dSDavid Ahern 
2003563f23b0SIdo Schimmel 	/* Make sure concurrent readers are not using 'oldg' anymore. */
2004563f23b0SIdo Schimmel 	synchronize_net();
20057709efa6SNikolay Aleksandrov 
20067709efa6SNikolay Aleksandrov 	if (newg->resilient) {
2007283a72a5SPetr Machata 		rcu_assign_pointer(oldg->res_table, tmp_table);
2008283a72a5SPetr Machata 		rcu_assign_pointer(oldg->spare->res_table, tmp_table);
2009283a72a5SPetr Machata 	}
2010283a72a5SPetr Machata 
20117bf4796dSDavid Ahern 	for (i = 0; i < oldg->num_nh; i++)
20127bf4796dSDavid Ahern 		oldg->nh_entries[i].nh_parent = new;
20137bf4796dSDavid Ahern 
20147bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_grp, oldg);
20157bf4796dSDavid Ahern 
20167bf4796dSDavid Ahern 	return 0;
20177bf4796dSDavid Ahern }
20187bf4796dSDavid Ahern 
2019885a3b15SIdo Schimmel static void nh_group_v4_update(struct nh_group *nhg)
2020885a3b15SIdo Schimmel {
2021885a3b15SIdo Schimmel 	struct nh_grp_entry *nhges;
2022885a3b15SIdo Schimmel 	bool has_v4 = false;
2023885a3b15SIdo Schimmel 	int i;
2024885a3b15SIdo Schimmel 
2025885a3b15SIdo Schimmel 	nhges = nhg->nh_entries;
2026885a3b15SIdo Schimmel 	for (i = 0; i < nhg->num_nh; i++) {
2027885a3b15SIdo Schimmel 		struct nh_info *nhi;
2028885a3b15SIdo Schimmel 
2029885a3b15SIdo Schimmel 		nhi = rtnl_dereference(nhges[i].nh->nh_info);
2030885a3b15SIdo Schimmel 		if (nhi->family == AF_INET)
2031885a3b15SIdo Schimmel 			has_v4 = true;
2032885a3b15SIdo Schimmel 	}
2033885a3b15SIdo Schimmel 	nhg->has_v4 = has_v4;
2034885a3b15SIdo Schimmel }
2035885a3b15SIdo Schimmel 
20367c37c7e0SPetr Machata static int replace_nexthop_single_notify_res(struct net *net,
20377c37c7e0SPetr Machata 					     struct nh_res_table *res_table,
20387c37c7e0SPetr Machata 					     struct nexthop *old,
20397c37c7e0SPetr Machata 					     struct nh_info *oldi,
20407c37c7e0SPetr Machata 					     struct nh_info *newi,
20417c37c7e0SPetr Machata 					     struct netlink_ext_ack *extack)
20427c37c7e0SPetr Machata {
20437c37c7e0SPetr Machata 	u32 nhg_id = res_table->nhg_id;
20447c37c7e0SPetr Machata 	int err;
20457c37c7e0SPetr Machata 	u16 i;
20467c37c7e0SPetr Machata 
20477c37c7e0SPetr Machata 	for (i = 0; i < res_table->num_nh_buckets; i++) {
20487c37c7e0SPetr Machata 		struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
20497c37c7e0SPetr Machata 		struct nh_grp_entry *nhge;
20507c37c7e0SPetr Machata 
20517c37c7e0SPetr Machata 		nhge = rtnl_dereference(bucket->nh_entry);
20527c37c7e0SPetr Machata 		if (nhge->nh == old) {
20537c37c7e0SPetr Machata 			err = __call_nexthop_res_bucket_notifiers(net, nhg_id,
20547c37c7e0SPetr Machata 								  i, true,
20557c37c7e0SPetr Machata 								  oldi, newi,
20567c37c7e0SPetr Machata 								  extack);
20577c37c7e0SPetr Machata 			if (err)
20587c37c7e0SPetr Machata 				goto err_notify;
20597c37c7e0SPetr Machata 		}
20607c37c7e0SPetr Machata 	}
20617c37c7e0SPetr Machata 
20627c37c7e0SPetr Machata 	return 0;
20637c37c7e0SPetr Machata 
20647c37c7e0SPetr Machata err_notify:
20657c37c7e0SPetr Machata 	while (i-- > 0) {
20667c37c7e0SPetr Machata 		struct nh_res_bucket *bucket = &res_table->nh_buckets[i];
20677c37c7e0SPetr Machata 		struct nh_grp_entry *nhge;
20687c37c7e0SPetr Machata 
20697c37c7e0SPetr Machata 		nhge = rtnl_dereference(bucket->nh_entry);
20707c37c7e0SPetr Machata 		if (nhge->nh == old)
20717c37c7e0SPetr Machata 			__call_nexthop_res_bucket_notifiers(net, nhg_id, i,
20727c37c7e0SPetr Machata 							    true, newi, oldi,
20737c37c7e0SPetr Machata 							    extack);
20747c37c7e0SPetr Machata 	}
20757c37c7e0SPetr Machata 	return err;
20767c37c7e0SPetr Machata }
20777c37c7e0SPetr Machata 
20787c37c7e0SPetr Machata static int replace_nexthop_single_notify(struct net *net,
20797c37c7e0SPetr Machata 					 struct nexthop *group_nh,
20807c37c7e0SPetr Machata 					 struct nexthop *old,
20817c37c7e0SPetr Machata 					 struct nh_info *oldi,
20827c37c7e0SPetr Machata 					 struct nh_info *newi,
20837c37c7e0SPetr Machata 					 struct netlink_ext_ack *extack)
20847c37c7e0SPetr Machata {
20857c37c7e0SPetr Machata 	struct nh_group *nhg = rtnl_dereference(group_nh->nh_grp);
20867c37c7e0SPetr Machata 	struct nh_res_table *res_table;
20877c37c7e0SPetr Machata 
2088de1d1ee3SPetr Machata 	if (nhg->hash_threshold) {
20897c37c7e0SPetr Machata 		return call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE,
20907c37c7e0SPetr Machata 					      group_nh, extack);
20917c37c7e0SPetr Machata 	} else if (nhg->resilient) {
20927c37c7e0SPetr Machata 		res_table = rtnl_dereference(nhg->res_table);
20937c37c7e0SPetr Machata 		return replace_nexthop_single_notify_res(net, res_table,
20947c37c7e0SPetr Machata 							 old, oldi, newi,
20957c37c7e0SPetr Machata 							 extack);
20967c37c7e0SPetr Machata 	}
20977c37c7e0SPetr Machata 
20987c37c7e0SPetr Machata 	return -EINVAL;
20997c37c7e0SPetr Machata }
21007c37c7e0SPetr Machata 
21017bf4796dSDavid Ahern static int replace_nexthop_single(struct net *net, struct nexthop *old,
21027bf4796dSDavid Ahern 				  struct nexthop *new,
21037bf4796dSDavid Ahern 				  struct netlink_ext_ack *extack)
21047bf4796dSDavid Ahern {
2105f17bc33dSIdo Schimmel 	u8 old_protocol, old_nh_flags;
21067bf4796dSDavid Ahern 	struct nh_info *oldi, *newi;
2107f17bc33dSIdo Schimmel 	struct nh_grp_entry *nhge;
21088c09c9f9SIdo Schimmel 	int err;
21097bf4796dSDavid Ahern 
21107bf4796dSDavid Ahern 	if (new->is_group) {
21117bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group.");
21127bf4796dSDavid Ahern 		return -EINVAL;
21137bf4796dSDavid Ahern 	}
21147bf4796dSDavid Ahern 
21158c09c9f9SIdo Schimmel 	err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack);
21168c09c9f9SIdo Schimmel 	if (err)
21178c09c9f9SIdo Schimmel 		return err;
21188c09c9f9SIdo Schimmel 
21198c09c9f9SIdo Schimmel 	/* Hardware flags were set on 'old' as 'new' is not in the red-black
21208c09c9f9SIdo Schimmel 	 * tree. Therefore, inherit the flags from 'old' to 'new'.
21218c09c9f9SIdo Schimmel 	 */
21228c09c9f9SIdo Schimmel 	new->nh_flags |= old->nh_flags & (RTNH_F_OFFLOAD | RTNH_F_TRAP);
21238c09c9f9SIdo Schimmel 
21247bf4796dSDavid Ahern 	oldi = rtnl_dereference(old->nh_info);
21257bf4796dSDavid Ahern 	newi = rtnl_dereference(new->nh_info);
21267bf4796dSDavid Ahern 
21277bf4796dSDavid Ahern 	newi->nh_parent = old;
21287bf4796dSDavid Ahern 	oldi->nh_parent = new;
21297bf4796dSDavid Ahern 
2130f17bc33dSIdo Schimmel 	old_protocol = old->protocol;
2131f17bc33dSIdo Schimmel 	old_nh_flags = old->nh_flags;
2132f17bc33dSIdo Schimmel 
21337bf4796dSDavid Ahern 	old->protocol = new->protocol;
21347bf4796dSDavid Ahern 	old->nh_flags = new->nh_flags;
21357bf4796dSDavid Ahern 
21367bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_info, newi);
21377bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_info, oldi);
21387bf4796dSDavid Ahern 
2139f17bc33dSIdo Schimmel 	/* Send a replace notification for all the groups using the nexthop. */
2140f17bc33dSIdo Schimmel 	list_for_each_entry(nhge, &old->grp_list, nh_list) {
2141f17bc33dSIdo Schimmel 		struct nexthop *nhp = nhge->nh_parent;
2142f17bc33dSIdo Schimmel 
21437c37c7e0SPetr Machata 		err = replace_nexthop_single_notify(net, nhp, old, oldi, newi,
2144f17bc33dSIdo Schimmel 						    extack);
2145f17bc33dSIdo Schimmel 		if (err)
2146f17bc33dSIdo Schimmel 			goto err_notify;
2147f17bc33dSIdo Schimmel 	}
2148f17bc33dSIdo Schimmel 
2149885a3b15SIdo Schimmel 	/* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially
2150885a3b15SIdo Schimmel 	 * update IPv4 indication in all the groups using the nexthop.
2151885a3b15SIdo Schimmel 	 */
2152885a3b15SIdo Schimmel 	if (oldi->family == AF_INET && newi->family == AF_INET6) {
2153885a3b15SIdo Schimmel 		list_for_each_entry(nhge, &old->grp_list, nh_list) {
2154885a3b15SIdo Schimmel 			struct nexthop *nhp = nhge->nh_parent;
2155885a3b15SIdo Schimmel 			struct nh_group *nhg;
2156885a3b15SIdo Schimmel 
2157885a3b15SIdo Schimmel 			nhg = rtnl_dereference(nhp->nh_grp);
2158885a3b15SIdo Schimmel 			nh_group_v4_update(nhg);
2159885a3b15SIdo Schimmel 		}
2160885a3b15SIdo Schimmel 	}
2161885a3b15SIdo Schimmel 
21627bf4796dSDavid Ahern 	return 0;
2163f17bc33dSIdo Schimmel 
2164f17bc33dSIdo Schimmel err_notify:
2165f17bc33dSIdo Schimmel 	rcu_assign_pointer(new->nh_info, newi);
2166f17bc33dSIdo Schimmel 	rcu_assign_pointer(old->nh_info, oldi);
2167f17bc33dSIdo Schimmel 	old->nh_flags = old_nh_flags;
2168f17bc33dSIdo Schimmel 	old->protocol = old_protocol;
2169f17bc33dSIdo Schimmel 	oldi->nh_parent = old;
2170f17bc33dSIdo Schimmel 	newi->nh_parent = new;
2171f17bc33dSIdo Schimmel 	list_for_each_entry_continue_reverse(nhge, &old->grp_list, nh_list) {
2172f17bc33dSIdo Schimmel 		struct nexthop *nhp = nhge->nh_parent;
2173f17bc33dSIdo Schimmel 
21747c37c7e0SPetr Machata 		replace_nexthop_single_notify(net, nhp, old, newi, oldi, NULL);
2175f17bc33dSIdo Schimmel 	}
2176f17bc33dSIdo Schimmel 	call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, old, extack);
2177f17bc33dSIdo Schimmel 	return err;
21787bf4796dSDavid Ahern }
21797bf4796dSDavid Ahern 
21807bf4796dSDavid Ahern static void __nexthop_replace_notify(struct net *net, struct nexthop *nh,
21817bf4796dSDavid Ahern 				     struct nl_info *info)
21827bf4796dSDavid Ahern {
21837bf4796dSDavid Ahern 	struct fib6_info *f6i;
21847bf4796dSDavid Ahern 
21857bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list)) {
21867bf4796dSDavid Ahern 		struct fib_info *fi;
21877bf4796dSDavid Ahern 
21887bf4796dSDavid Ahern 		/* expectation is a few fib_info per nexthop and then
21897bf4796dSDavid Ahern 		 * a lot of routes per fib_info. So mark the fib_info
21907bf4796dSDavid Ahern 		 * and then walk the fib tables once
21917bf4796dSDavid Ahern 		 */
21927bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
21937bf4796dSDavid Ahern 			fi->nh_updated = true;
21947bf4796dSDavid Ahern 
21957bf4796dSDavid Ahern 		fib_info_notify_update(net, info);
21967bf4796dSDavid Ahern 
21977bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
21987bf4796dSDavid Ahern 			fi->nh_updated = false;
21997bf4796dSDavid Ahern 	}
22007bf4796dSDavid Ahern 
22017bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
22027bf4796dSDavid Ahern 		ipv6_stub->fib6_rt_update(net, f6i, info);
22037bf4796dSDavid Ahern }
22047bf4796dSDavid Ahern 
22057bf4796dSDavid Ahern /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries
22067bf4796dSDavid Ahern  * linked to this nexthop and for all groups that the nexthop
22077bf4796dSDavid Ahern  * is a member of
22087bf4796dSDavid Ahern  */
22097bf4796dSDavid Ahern static void nexthop_replace_notify(struct net *net, struct nexthop *nh,
22107bf4796dSDavid Ahern 				   struct nl_info *info)
22117bf4796dSDavid Ahern {
22127bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
22137bf4796dSDavid Ahern 
22147bf4796dSDavid Ahern 	__nexthop_replace_notify(net, nh, info);
22157bf4796dSDavid Ahern 
22167bf4796dSDavid Ahern 	list_for_each_entry(nhge, &nh->grp_list, nh_list)
22177bf4796dSDavid Ahern 		__nexthop_replace_notify(net, nhge->nh_parent, info);
22187bf4796dSDavid Ahern }
22197bf4796dSDavid Ahern 
2220ab84be7eSDavid Ahern static int replace_nexthop(struct net *net, struct nexthop *old,
2221597f48e4SPetr Machata 			   struct nexthop *new, const struct nh_config *cfg,
2222597f48e4SPetr Machata 			   struct netlink_ext_ack *extack)
2223ab84be7eSDavid Ahern {
22247bf4796dSDavid Ahern 	bool new_is_reject = false;
22257bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
22267bf4796dSDavid Ahern 	int err;
22277bf4796dSDavid Ahern 
22287bf4796dSDavid Ahern 	/* check that existing FIB entries are ok with the
22297bf4796dSDavid Ahern 	 * new nexthop definition
22307bf4796dSDavid Ahern 	 */
22317bf4796dSDavid Ahern 	err = fib_check_nh_list(old, new, extack);
22327bf4796dSDavid Ahern 	if (err)
22337bf4796dSDavid Ahern 		return err;
22347bf4796dSDavid Ahern 
22357bf4796dSDavid Ahern 	err = fib6_check_nh_list(old, new, extack);
22367bf4796dSDavid Ahern 	if (err)
22377bf4796dSDavid Ahern 		return err;
22387bf4796dSDavid Ahern 
22397bf4796dSDavid Ahern 	if (!new->is_group) {
22407bf4796dSDavid Ahern 		struct nh_info *nhi = rtnl_dereference(new->nh_info);
22417bf4796dSDavid Ahern 
22427bf4796dSDavid Ahern 		new_is_reject = nhi->reject_nh;
22437bf4796dSDavid Ahern 	}
22447bf4796dSDavid Ahern 
22457bf4796dSDavid Ahern 	list_for_each_entry(nhge, &old->grp_list, nh_list) {
22467bf4796dSDavid Ahern 		/* if new nexthop is a blackhole, any groups using this
22477bf4796dSDavid Ahern 		 * nexthop cannot have more than 1 path
22487bf4796dSDavid Ahern 		 */
22497bf4796dSDavid Ahern 		if (new_is_reject &&
22507bf4796dSDavid Ahern 		    nexthop_num_path(nhge->nh_parent) > 1) {
22517bf4796dSDavid Ahern 			NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path");
22527bf4796dSDavid Ahern 			return -EINVAL;
22537bf4796dSDavid Ahern 		}
22547bf4796dSDavid Ahern 
22557bf4796dSDavid Ahern 		err = fib_check_nh_list(nhge->nh_parent, new, extack);
22567bf4796dSDavid Ahern 		if (err)
22577bf4796dSDavid Ahern 			return err;
22587bf4796dSDavid Ahern 
22597bf4796dSDavid Ahern 		err = fib6_check_nh_list(nhge->nh_parent, new, extack);
22607bf4796dSDavid Ahern 		if (err)
22617bf4796dSDavid Ahern 			return err;
22627bf4796dSDavid Ahern 	}
22637bf4796dSDavid Ahern 
22647bf4796dSDavid Ahern 	if (old->is_group)
2265597f48e4SPetr Machata 		err = replace_nexthop_grp(net, old, new, cfg, extack);
22667bf4796dSDavid Ahern 	else
22677bf4796dSDavid Ahern 		err = replace_nexthop_single(net, old, new, extack);
22687bf4796dSDavid Ahern 
22697bf4796dSDavid Ahern 	if (!err) {
22701005f19bSNikolay Aleksandrov 		nh_rt_cache_flush(net, old, new);
22717bf4796dSDavid Ahern 
22727bf4796dSDavid Ahern 		__remove_nexthop(net, new, NULL);
22737bf4796dSDavid Ahern 		nexthop_put(new);
22747bf4796dSDavid Ahern 	}
22757bf4796dSDavid Ahern 
22767bf4796dSDavid Ahern 	return err;
2277ab84be7eSDavid Ahern }
2278ab84be7eSDavid Ahern 
2279ab84be7eSDavid Ahern /* called with rtnl_lock held */
2280ab84be7eSDavid Ahern static int insert_nexthop(struct net *net, struct nexthop *new_nh,
2281ab84be7eSDavid Ahern 			  struct nh_config *cfg, struct netlink_ext_ack *extack)
2282ab84be7eSDavid Ahern {
2283ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
2284ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
2285ab84be7eSDavid Ahern 	bool replace = !!(cfg->nlflags & NLM_F_REPLACE);
2286ab84be7eSDavid Ahern 	bool create = !!(cfg->nlflags & NLM_F_CREATE);
2287ab84be7eSDavid Ahern 	u32 new_id = new_nh->id;
22887bf4796dSDavid Ahern 	int replace_notify = 0;
2289ab84be7eSDavid Ahern 	int rc = -EEXIST;
2290ab84be7eSDavid Ahern 
2291ab84be7eSDavid Ahern 	pp = &root->rb_node;
2292ab84be7eSDavid Ahern 	while (1) {
2293ab84be7eSDavid Ahern 		struct nexthop *nh;
2294ab84be7eSDavid Ahern 
2295233c6378SIdo Schimmel 		next = *pp;
2296ab84be7eSDavid Ahern 		if (!next)
2297ab84be7eSDavid Ahern 			break;
2298ab84be7eSDavid Ahern 
2299ab84be7eSDavid Ahern 		parent = next;
2300ab84be7eSDavid Ahern 
2301ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
2302ab84be7eSDavid Ahern 		if (new_id < nh->id) {
2303ab84be7eSDavid Ahern 			pp = &next->rb_left;
2304ab84be7eSDavid Ahern 		} else if (new_id > nh->id) {
2305ab84be7eSDavid Ahern 			pp = &next->rb_right;
2306ab84be7eSDavid Ahern 		} else if (replace) {
2307597f48e4SPetr Machata 			rc = replace_nexthop(net, nh, new_nh, cfg, extack);
23087bf4796dSDavid Ahern 			if (!rc) {
2309ab84be7eSDavid Ahern 				new_nh = nh; /* send notification with old nh */
23107bf4796dSDavid Ahern 				replace_notify = 1;
23117bf4796dSDavid Ahern 			}
2312ab84be7eSDavid Ahern 			goto out;
2313ab84be7eSDavid Ahern 		} else {
2314ab84be7eSDavid Ahern 			/* id already exists and not a replace */
2315ab84be7eSDavid Ahern 			goto out;
2316ab84be7eSDavid Ahern 		}
2317ab84be7eSDavid Ahern 	}
2318ab84be7eSDavid Ahern 
2319ab84be7eSDavid Ahern 	if (replace && !create) {
2320ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists");
2321ab84be7eSDavid Ahern 		rc = -ENOENT;
2322ab84be7eSDavid Ahern 		goto out;
2323ab84be7eSDavid Ahern 	}
2324ab84be7eSDavid Ahern 
2325283a72a5SPetr Machata 	if (new_nh->is_group) {
2326283a72a5SPetr Machata 		struct nh_group *nhg = rtnl_dereference(new_nh->nh_grp);
2327283a72a5SPetr Machata 		struct nh_res_table *res_table;
2328283a72a5SPetr Machata 
2329283a72a5SPetr Machata 		if (nhg->resilient) {
2330283a72a5SPetr Machata 			res_table = rtnl_dereference(nhg->res_table);
2331283a72a5SPetr Machata 
2332283a72a5SPetr Machata 			/* Not passing the number of buckets is OK when
2333283a72a5SPetr Machata 			 * replacing, but not when creating a new group.
2334283a72a5SPetr Machata 			 */
2335283a72a5SPetr Machata 			if (!cfg->nh_grp_res_has_num_buckets) {
2336283a72a5SPetr Machata 				NL_SET_ERR_MSG(extack, "Number of buckets not specified for nexthop group insertion");
2337283a72a5SPetr Machata 				rc = -EINVAL;
2338283a72a5SPetr Machata 				goto out;
2339283a72a5SPetr Machata 			}
2340283a72a5SPetr Machata 
2341283a72a5SPetr Machata 			nh_res_group_rebalance(nhg, res_table);
23427c37c7e0SPetr Machata 
23437c37c7e0SPetr Machata 			/* Do not send bucket notifications, we do full
23447c37c7e0SPetr Machata 			 * notification below.
23457c37c7e0SPetr Machata 			 */
23460b4818aaSPetr Machata 			nh_res_table_upkeep(res_table, false, false);
2347283a72a5SPetr Machata 		}
2348283a72a5SPetr Machata 	}
2349283a72a5SPetr Machata 
2350ab84be7eSDavid Ahern 	rb_link_node_rcu(&new_nh->rb_node, parent, pp);
2351ab84be7eSDavid Ahern 	rb_insert_color(&new_nh->rb_node, root);
2352732d167bSIdo Schimmel 
2353de1d1ee3SPetr Machata 	/* The initial insertion is a full notification for hash-threshold as
2354de1d1ee3SPetr Machata 	 * well as resilient groups.
23557c37c7e0SPetr Machata 	 */
2356732d167bSIdo Schimmel 	rc = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new_nh, extack);
2357732d167bSIdo Schimmel 	if (rc)
2358732d167bSIdo Schimmel 		rb_erase(&new_nh->rb_node, &net->nexthop.rb_root);
2359732d167bSIdo Schimmel 
2360ab84be7eSDavid Ahern out:
2361ab84be7eSDavid Ahern 	if (!rc) {
2362ab84be7eSDavid Ahern 		nh_base_seq_inc(net);
2363ab84be7eSDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo);
23644f80116dSRoopa Prabhu 		if (replace_notify && net->ipv4.sysctl_nexthop_compat_mode)
23657bf4796dSDavid Ahern 			nexthop_replace_notify(net, new_nh, &cfg->nlinfo);
2366ab84be7eSDavid Ahern 	}
2367ab84be7eSDavid Ahern 
2368ab84be7eSDavid Ahern 	return rc;
2369ab84be7eSDavid Ahern }
2370ab84be7eSDavid Ahern 
2371597cfe4fSDavid Ahern /* rtnl */
2372597cfe4fSDavid Ahern /* remove all nexthops tied to a device being deleted */
237376c03bf8SIdo Schimmel static void nexthop_flush_dev(struct net_device *dev, unsigned long event)
2374597cfe4fSDavid Ahern {
2375597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
2376597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
2377597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
2378597cfe4fSDavid Ahern 	struct hlist_node *n;
2379597cfe4fSDavid Ahern 	struct nh_info *nhi;
2380597cfe4fSDavid Ahern 
2381597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
2382597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev != dev)
2383597cfe4fSDavid Ahern 			continue;
2384597cfe4fSDavid Ahern 
238576c03bf8SIdo Schimmel 		if (nhi->reject_nh &&
238676c03bf8SIdo Schimmel 		    (event == NETDEV_DOWN || event == NETDEV_CHANGE))
238776c03bf8SIdo Schimmel 			continue;
238876c03bf8SIdo Schimmel 
2389430a0491SDavid Ahern 		remove_nexthop(net, nhi->nh_parent, NULL);
2390597cfe4fSDavid Ahern 	}
2391597cfe4fSDavid Ahern }
2392597cfe4fSDavid Ahern 
2393ab84be7eSDavid Ahern /* rtnl; called when net namespace is deleted */
2394ab84be7eSDavid Ahern static void flush_all_nexthops(struct net *net)
2395ab84be7eSDavid Ahern {
2396ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
2397ab84be7eSDavid Ahern 	struct rb_node *node;
2398ab84be7eSDavid Ahern 	struct nexthop *nh;
2399ab84be7eSDavid Ahern 
2400ab84be7eSDavid Ahern 	while ((node = rb_first(root))) {
2401ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
2402430a0491SDavid Ahern 		remove_nexthop(net, nh, NULL);
2403ab84be7eSDavid Ahern 		cond_resched();
2404ab84be7eSDavid Ahern 	}
2405ab84be7eSDavid Ahern }
2406ab84be7eSDavid Ahern 
2407430a0491SDavid Ahern static struct nexthop *nexthop_create_group(struct net *net,
2408430a0491SDavid Ahern 					    struct nh_config *cfg)
2409430a0491SDavid Ahern {
2410430a0491SDavid Ahern 	struct nlattr *grps_attr = cfg->nh_grp;
2411430a0491SDavid Ahern 	struct nexthop_grp *entry = nla_data(grps_attr);
241290f33bffSNikolay Aleksandrov 	u16 num_nh = nla_len(grps_attr) / sizeof(*entry);
2413430a0491SDavid Ahern 	struct nh_group *nhg;
2414430a0491SDavid Ahern 	struct nexthop *nh;
2415283a72a5SPetr Machata 	int err;
2416430a0491SDavid Ahern 	int i;
2417430a0491SDavid Ahern 
2418eeaac363SNikolay Aleksandrov 	if (WARN_ON(!num_nh))
2419eeaac363SNikolay Aleksandrov 		return ERR_PTR(-EINVAL);
2420eeaac363SNikolay Aleksandrov 
2421430a0491SDavid Ahern 	nh = nexthop_alloc();
2422430a0491SDavid Ahern 	if (!nh)
2423430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
2424430a0491SDavid Ahern 
2425430a0491SDavid Ahern 	nh->is_group = 1;
2426430a0491SDavid Ahern 
242790f33bffSNikolay Aleksandrov 	nhg = nexthop_grp_alloc(num_nh);
2428430a0491SDavid Ahern 	if (!nhg) {
2429430a0491SDavid Ahern 		kfree(nh);
2430430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
2431430a0491SDavid Ahern 	}
2432430a0491SDavid Ahern 
243390f33bffSNikolay Aleksandrov 	/* spare group used for removals */
243490f33bffSNikolay Aleksandrov 	nhg->spare = nexthop_grp_alloc(num_nh);
2435dafe2078SPatrick Eigensatz 	if (!nhg->spare) {
243690f33bffSNikolay Aleksandrov 		kfree(nhg);
243790f33bffSNikolay Aleksandrov 		kfree(nh);
2438dafe2078SPatrick Eigensatz 		return ERR_PTR(-ENOMEM);
243990f33bffSNikolay Aleksandrov 	}
244090f33bffSNikolay Aleksandrov 	nhg->spare->spare = nhg;
244190f33bffSNikolay Aleksandrov 
2442430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
2443430a0491SDavid Ahern 		struct nexthop *nhe;
2444430a0491SDavid Ahern 		struct nh_info *nhi;
2445430a0491SDavid Ahern 
2446430a0491SDavid Ahern 		nhe = nexthop_find_by_id(net, entry[i].id);
2447283a72a5SPetr Machata 		if (!nexthop_get(nhe)) {
2448283a72a5SPetr Machata 			err = -ENOENT;
2449430a0491SDavid Ahern 			goto out_no_nh;
2450283a72a5SPetr Machata 		}
2451430a0491SDavid Ahern 
2452430a0491SDavid Ahern 		nhi = rtnl_dereference(nhe->nh_info);
2453430a0491SDavid Ahern 		if (nhi->family == AF_INET)
2454430a0491SDavid Ahern 			nhg->has_v4 = true;
2455430a0491SDavid Ahern 
2456430a0491SDavid Ahern 		nhg->nh_entries[i].nh = nhe;
2457430a0491SDavid Ahern 		nhg->nh_entries[i].weight = entry[i].weight + 1;
2458430a0491SDavid Ahern 		list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list);
2459430a0491SDavid Ahern 		nhg->nh_entries[i].nh_parent = nh;
2460430a0491SDavid Ahern 	}
2461430a0491SDavid Ahern 
246290e1a9e2SPetr Machata 	if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) {
2463de1d1ee3SPetr Machata 		nhg->hash_threshold = 1;
246490e1a9e2SPetr Machata 		nhg->is_multipath = true;
2465710ec562SIdo Schimmel 	} else if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_RES) {
2466283a72a5SPetr Machata 		struct nh_res_table *res_table;
2467283a72a5SPetr Machata 
2468283a72a5SPetr Machata 		res_table = nexthop_res_table_alloc(net, cfg->nh_id, cfg);
2469283a72a5SPetr Machata 		if (!res_table) {
2470283a72a5SPetr Machata 			err = -ENOMEM;
2471710ec562SIdo Schimmel 			goto out_no_nh;
247290e1a9e2SPetr Machata 		}
2473720ccd9aSPetr Machata 
2474283a72a5SPetr Machata 		rcu_assign_pointer(nhg->spare->res_table, res_table);
2475283a72a5SPetr Machata 		rcu_assign_pointer(nhg->res_table, res_table);
2476283a72a5SPetr Machata 		nhg->resilient = true;
2477283a72a5SPetr Machata 		nhg->is_multipath = true;
2478283a72a5SPetr Machata 	}
2479283a72a5SPetr Machata 
2480de1d1ee3SPetr Machata 	WARN_ON_ONCE(nhg->hash_threshold + nhg->resilient != 1);
2481720ccd9aSPetr Machata 
2482de1d1ee3SPetr Machata 	if (nhg->hash_threshold)
2483de1d1ee3SPetr Machata 		nh_hthr_group_rebalance(nhg);
2484430a0491SDavid Ahern 
248538428d68SRoopa Prabhu 	if (cfg->nh_fdb)
2486ce9ac056SDavid Ahern 		nhg->fdb_nh = 1;
248738428d68SRoopa Prabhu 
2488430a0491SDavid Ahern 	rcu_assign_pointer(nh->nh_grp, nhg);
2489430a0491SDavid Ahern 
2490430a0491SDavid Ahern 	return nh;
2491430a0491SDavid Ahern 
2492430a0491SDavid Ahern out_no_nh:
24937b01e53eSIdo Schimmel 	for (i--; i >= 0; --i) {
24947b01e53eSIdo Schimmel 		list_del(&nhg->nh_entries[i].nh_list);
2495430a0491SDavid Ahern 		nexthop_put(nhg->nh_entries[i].nh);
24967b01e53eSIdo Schimmel 	}
2497430a0491SDavid Ahern 
249890f33bffSNikolay Aleksandrov 	kfree(nhg->spare);
2499430a0491SDavid Ahern 	kfree(nhg);
2500430a0491SDavid Ahern 	kfree(nh);
2501430a0491SDavid Ahern 
2502283a72a5SPetr Machata 	return ERR_PTR(err);
2503430a0491SDavid Ahern }
2504430a0491SDavid Ahern 
2505597cfe4fSDavid Ahern static int nh_create_ipv4(struct net *net, struct nexthop *nh,
2506597cfe4fSDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
2507597cfe4fSDavid Ahern 			  struct netlink_ext_ack *extack)
2508597cfe4fSDavid Ahern {
2509597cfe4fSDavid Ahern 	struct fib_nh *fib_nh = &nhi->fib_nh;
2510597cfe4fSDavid Ahern 	struct fib_config fib_cfg = {
2511597cfe4fSDavid Ahern 		.fc_oif   = cfg->nh_ifindex,
2512597cfe4fSDavid Ahern 		.fc_gw4   = cfg->gw.ipv4,
2513597cfe4fSDavid Ahern 		.fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0,
2514597cfe4fSDavid Ahern 		.fc_flags = cfg->nh_flags,
25159aca491eSRyoga Saito 		.fc_nlinfo = cfg->nlinfo,
2516b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
2517b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
2518597cfe4fSDavid Ahern 	};
251938428d68SRoopa Prabhu 	u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN);
2520c76c9925SColin Ian King 	int err;
2521597cfe4fSDavid Ahern 
2522597cfe4fSDavid Ahern 	err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack);
2523597cfe4fSDavid Ahern 	if (err) {
2524597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
2525597cfe4fSDavid Ahern 		goto out;
2526597cfe4fSDavid Ahern 	}
2527597cfe4fSDavid Ahern 
2528ce9ac056SDavid Ahern 	if (nhi->fdb_nh)
252938428d68SRoopa Prabhu 		goto out;
253038428d68SRoopa Prabhu 
2531597cfe4fSDavid Ahern 	/* sets nh_dev if successful */
2532597cfe4fSDavid Ahern 	err = fib_check_nh(net, fib_nh, tb_id, 0, extack);
2533597cfe4fSDavid Ahern 	if (!err) {
2534597cfe4fSDavid Ahern 		nh->nh_flags = fib_nh->fib_nh_flags;
2535dcb1ecb5SDavid Ahern 		fib_info_update_nhc_saddr(net, &fib_nh->nh_common,
2536dcb1ecb5SDavid Ahern 					  fib_nh->fib_nh_scope);
2537597cfe4fSDavid Ahern 	} else {
2538597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
2539597cfe4fSDavid Ahern 	}
2540597cfe4fSDavid Ahern out:
2541597cfe4fSDavid Ahern 	return err;
2542597cfe4fSDavid Ahern }
2543597cfe4fSDavid Ahern 
254453010f99SDavid Ahern static int nh_create_ipv6(struct net *net,  struct nexthop *nh,
254553010f99SDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
254653010f99SDavid Ahern 			  struct netlink_ext_ack *extack)
254753010f99SDavid Ahern {
254853010f99SDavid Ahern 	struct fib6_nh *fib6_nh = &nhi->fib6_nh;
254953010f99SDavid Ahern 	struct fib6_config fib6_cfg = {
255053010f99SDavid Ahern 		.fc_table = l3mdev_fib_table(cfg->dev),
255153010f99SDavid Ahern 		.fc_ifindex = cfg->nh_ifindex,
255253010f99SDavid Ahern 		.fc_gateway = cfg->gw.ipv6,
255353010f99SDavid Ahern 		.fc_flags = cfg->nh_flags,
25549aca491eSRyoga Saito 		.fc_nlinfo = cfg->nlinfo,
2555b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
2556b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
255738428d68SRoopa Prabhu 		.fc_is_fdb = cfg->nh_fdb,
255853010f99SDavid Ahern 	};
25596f43e525SColin Ian King 	int err;
256053010f99SDavid Ahern 
256153010f99SDavid Ahern 	if (!ipv6_addr_any(&cfg->gw.ipv6))
256253010f99SDavid Ahern 		fib6_cfg.fc_flags |= RTF_GATEWAY;
256353010f99SDavid Ahern 
256453010f99SDavid Ahern 	/* sets nh_dev if successful */
256553010f99SDavid Ahern 	err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL,
256653010f99SDavid Ahern 				      extack);
25671c743127SNikolay Aleksandrov 	if (err) {
25681c743127SNikolay Aleksandrov 		/* IPv6 is not enabled, don't call fib6_nh_release */
25691c743127SNikolay Aleksandrov 		if (err == -EAFNOSUPPORT)
25701c743127SNikolay Aleksandrov 			goto out;
257153010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(fib6_nh);
25721c743127SNikolay Aleksandrov 	} else {
257353010f99SDavid Ahern 		nh->nh_flags = fib6_nh->fib_nh_flags;
25741c743127SNikolay Aleksandrov 	}
25751c743127SNikolay Aleksandrov out:
257653010f99SDavid Ahern 	return err;
257753010f99SDavid Ahern }
257853010f99SDavid Ahern 
2579ab84be7eSDavid Ahern static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg,
2580ab84be7eSDavid Ahern 				      struct netlink_ext_ack *extack)
2581ab84be7eSDavid Ahern {
2582ab84be7eSDavid Ahern 	struct nh_info *nhi;
2583ab84be7eSDavid Ahern 	struct nexthop *nh;
2584ab84be7eSDavid Ahern 	int err = 0;
2585ab84be7eSDavid Ahern 
2586ab84be7eSDavid Ahern 	nh = nexthop_alloc();
2587ab84be7eSDavid Ahern 	if (!nh)
2588ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
2589ab84be7eSDavid Ahern 
2590ab84be7eSDavid Ahern 	nhi = kzalloc(sizeof(*nhi), GFP_KERNEL);
2591ab84be7eSDavid Ahern 	if (!nhi) {
2592ab84be7eSDavid Ahern 		kfree(nh);
2593ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
2594ab84be7eSDavid Ahern 	}
2595ab84be7eSDavid Ahern 
2596ab84be7eSDavid Ahern 	nh->nh_flags = cfg->nh_flags;
2597ab84be7eSDavid Ahern 	nh->net = net;
2598ab84be7eSDavid Ahern 
2599ab84be7eSDavid Ahern 	nhi->nh_parent = nh;
2600ab84be7eSDavid Ahern 	nhi->family = cfg->nh_family;
2601ab84be7eSDavid Ahern 	nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK;
2602ab84be7eSDavid Ahern 
260338428d68SRoopa Prabhu 	if (cfg->nh_fdb)
2604ce9ac056SDavid Ahern 		nhi->fdb_nh = 1;
260538428d68SRoopa Prabhu 
2606ab84be7eSDavid Ahern 	if (cfg->nh_blackhole) {
2607ab84be7eSDavid Ahern 		nhi->reject_nh = 1;
2608ab84be7eSDavid Ahern 		cfg->nh_ifindex = net->loopback_dev->ifindex;
2609ab84be7eSDavid Ahern 	}
2610ab84be7eSDavid Ahern 
2611597cfe4fSDavid Ahern 	switch (cfg->nh_family) {
2612597cfe4fSDavid Ahern 	case AF_INET:
2613597cfe4fSDavid Ahern 		err = nh_create_ipv4(net, nh, nhi, cfg, extack);
2614597cfe4fSDavid Ahern 		break;
261553010f99SDavid Ahern 	case AF_INET6:
261653010f99SDavid Ahern 		err = nh_create_ipv6(net, nh, nhi, cfg, extack);
261753010f99SDavid Ahern 		break;
2618597cfe4fSDavid Ahern 	}
2619597cfe4fSDavid Ahern 
2620ab84be7eSDavid Ahern 	if (err) {
2621ab84be7eSDavid Ahern 		kfree(nhi);
2622ab84be7eSDavid Ahern 		kfree(nh);
2623ab84be7eSDavid Ahern 		return ERR_PTR(err);
2624ab84be7eSDavid Ahern 	}
2625ab84be7eSDavid Ahern 
2626597cfe4fSDavid Ahern 	/* add the entry to the device based hash */
2627ce9ac056SDavid Ahern 	if (!nhi->fdb_nh)
2628597cfe4fSDavid Ahern 		nexthop_devhash_add(net, nhi);
2629597cfe4fSDavid Ahern 
2630ab84be7eSDavid Ahern 	rcu_assign_pointer(nh->nh_info, nhi);
2631ab84be7eSDavid Ahern 
2632ab84be7eSDavid Ahern 	return nh;
2633ab84be7eSDavid Ahern }
2634ab84be7eSDavid Ahern 
2635ab84be7eSDavid Ahern /* called with rtnl lock held */
2636ab84be7eSDavid Ahern static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg,
2637ab84be7eSDavid Ahern 				   struct netlink_ext_ack *extack)
2638ab84be7eSDavid Ahern {
2639ab84be7eSDavid Ahern 	struct nexthop *nh;
2640ab84be7eSDavid Ahern 	int err;
2641ab84be7eSDavid Ahern 
2642ab84be7eSDavid Ahern 	if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) {
2643ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace requires nexthop id");
2644ab84be7eSDavid Ahern 		return ERR_PTR(-EINVAL);
2645ab84be7eSDavid Ahern 	}
2646ab84be7eSDavid Ahern 
2647ab84be7eSDavid Ahern 	if (!cfg->nh_id) {
2648ab84be7eSDavid Ahern 		cfg->nh_id = nh_find_unused_id(net);
2649ab84be7eSDavid Ahern 		if (!cfg->nh_id) {
2650ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "No unused id");
2651ab84be7eSDavid Ahern 			return ERR_PTR(-EINVAL);
2652ab84be7eSDavid Ahern 		}
2653ab84be7eSDavid Ahern 	}
2654ab84be7eSDavid Ahern 
2655430a0491SDavid Ahern 	if (cfg->nh_grp)
2656430a0491SDavid Ahern 		nh = nexthop_create_group(net, cfg);
2657430a0491SDavid Ahern 	else
2658ab84be7eSDavid Ahern 		nh = nexthop_create(net, cfg, extack);
2659430a0491SDavid Ahern 
2660ab84be7eSDavid Ahern 	if (IS_ERR(nh))
2661ab84be7eSDavid Ahern 		return nh;
2662ab84be7eSDavid Ahern 
2663ab84be7eSDavid Ahern 	refcount_set(&nh->refcnt, 1);
2664ab84be7eSDavid Ahern 	nh->id = cfg->nh_id;
2665ab84be7eSDavid Ahern 	nh->protocol = cfg->nh_protocol;
2666ab84be7eSDavid Ahern 	nh->net = net;
2667ab84be7eSDavid Ahern 
2668ab84be7eSDavid Ahern 	err = insert_nexthop(net, nh, cfg, extack);
2669ab84be7eSDavid Ahern 	if (err) {
2670430a0491SDavid Ahern 		__remove_nexthop(net, nh, NULL);
2671ab84be7eSDavid Ahern 		nexthop_put(nh);
2672ab84be7eSDavid Ahern 		nh = ERR_PTR(err);
2673ab84be7eSDavid Ahern 	}
2674ab84be7eSDavid Ahern 
2675ab84be7eSDavid Ahern 	return nh;
2676ab84be7eSDavid Ahern }
2677ab84be7eSDavid Ahern 
2678a2601e2bSPetr Machata static int rtm_nh_get_timer(struct nlattr *attr, unsigned long fallback,
2679a2601e2bSPetr Machata 			    unsigned long *timer_p, bool *has_p,
2680a2601e2bSPetr Machata 			    struct netlink_ext_ack *extack)
2681a2601e2bSPetr Machata {
2682a2601e2bSPetr Machata 	unsigned long timer;
2683a2601e2bSPetr Machata 	u32 value;
2684a2601e2bSPetr Machata 
2685a2601e2bSPetr Machata 	if (!attr) {
2686a2601e2bSPetr Machata 		*timer_p = fallback;
2687a2601e2bSPetr Machata 		*has_p = false;
2688a2601e2bSPetr Machata 		return 0;
2689a2601e2bSPetr Machata 	}
2690a2601e2bSPetr Machata 
2691a2601e2bSPetr Machata 	value = nla_get_u32(attr);
2692a2601e2bSPetr Machata 	timer = clock_t_to_jiffies(value);
2693a2601e2bSPetr Machata 	if (timer == ~0UL) {
2694a2601e2bSPetr Machata 		NL_SET_ERR_MSG(extack, "Timer value too large");
2695a2601e2bSPetr Machata 		return -EINVAL;
2696a2601e2bSPetr Machata 	}
2697a2601e2bSPetr Machata 
2698a2601e2bSPetr Machata 	*timer_p = timer;
2699a2601e2bSPetr Machata 	*has_p = true;
2700a2601e2bSPetr Machata 	return 0;
2701a2601e2bSPetr Machata }
2702a2601e2bSPetr Machata 
2703a2601e2bSPetr Machata static int rtm_to_nh_config_grp_res(struct nlattr *res, struct nh_config *cfg,
2704a2601e2bSPetr Machata 				    struct netlink_ext_ack *extack)
2705a2601e2bSPetr Machata {
2706a2601e2bSPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_res_policy_new)] = {};
2707a2601e2bSPetr Machata 	int err;
2708a2601e2bSPetr Machata 
2709a2601e2bSPetr Machata 	if (res) {
2710a2601e2bSPetr Machata 		err = nla_parse_nested(tb,
2711a2601e2bSPetr Machata 				       ARRAY_SIZE(rtm_nh_res_policy_new) - 1,
2712a2601e2bSPetr Machata 				       res, rtm_nh_res_policy_new, extack);
2713a2601e2bSPetr Machata 		if (err < 0)
2714a2601e2bSPetr Machata 			return err;
2715a2601e2bSPetr Machata 	}
2716a2601e2bSPetr Machata 
2717a2601e2bSPetr Machata 	if (tb[NHA_RES_GROUP_BUCKETS]) {
2718a2601e2bSPetr Machata 		cfg->nh_grp_res_num_buckets =
2719a2601e2bSPetr Machata 			nla_get_u16(tb[NHA_RES_GROUP_BUCKETS]);
2720a2601e2bSPetr Machata 		cfg->nh_grp_res_has_num_buckets = true;
2721a2601e2bSPetr Machata 		if (!cfg->nh_grp_res_num_buckets) {
2722a2601e2bSPetr Machata 			NL_SET_ERR_MSG(extack, "Number of buckets needs to be non-0");
2723a2601e2bSPetr Machata 			return -EINVAL;
2724a2601e2bSPetr Machata 		}
2725a2601e2bSPetr Machata 	}
2726a2601e2bSPetr Machata 
2727a2601e2bSPetr Machata 	err = rtm_nh_get_timer(tb[NHA_RES_GROUP_IDLE_TIMER],
2728a2601e2bSPetr Machata 			       NH_RES_DEFAULT_IDLE_TIMER,
2729a2601e2bSPetr Machata 			       &cfg->nh_grp_res_idle_timer,
2730a2601e2bSPetr Machata 			       &cfg->nh_grp_res_has_idle_timer,
2731a2601e2bSPetr Machata 			       extack);
2732a2601e2bSPetr Machata 	if (err)
2733a2601e2bSPetr Machata 		return err;
2734a2601e2bSPetr Machata 
2735a2601e2bSPetr Machata 	return rtm_nh_get_timer(tb[NHA_RES_GROUP_UNBALANCED_TIMER],
2736a2601e2bSPetr Machata 				NH_RES_DEFAULT_UNBALANCED_TIMER,
2737a2601e2bSPetr Machata 				&cfg->nh_grp_res_unbalanced_timer,
2738a2601e2bSPetr Machata 				&cfg->nh_grp_res_has_unbalanced_timer,
2739a2601e2bSPetr Machata 				extack);
2740a2601e2bSPetr Machata }
2741a2601e2bSPetr Machata 
2742ab84be7eSDavid Ahern static int rtm_to_nh_config(struct net *net, struct sk_buff *skb,
2743ab84be7eSDavid Ahern 			    struct nlmsghdr *nlh, struct nh_config *cfg,
2744ab84be7eSDavid Ahern 			    struct netlink_ext_ack *extack)
2745ab84be7eSDavid Ahern {
2746ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
2747643d0878SPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_new)];
2748ab84be7eSDavid Ahern 	int err;
2749ab84be7eSDavid Ahern 
2750643d0878SPetr Machata 	err = nlmsg_parse(nlh, sizeof(*nhm), tb,
2751643d0878SPetr Machata 			  ARRAY_SIZE(rtm_nh_policy_new) - 1,
2752643d0878SPetr Machata 			  rtm_nh_policy_new, extack);
2753ab84be7eSDavid Ahern 	if (err < 0)
2754ab84be7eSDavid Ahern 		return err;
2755ab84be7eSDavid Ahern 
2756ab84be7eSDavid Ahern 	err = -EINVAL;
2757ab84be7eSDavid Ahern 	if (nhm->resvd || nhm->nh_scope) {
2758ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in ancillary header");
2759ab84be7eSDavid Ahern 		goto out;
2760ab84be7eSDavid Ahern 	}
2761ab84be7eSDavid Ahern 	if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) {
2762ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header");
2763ab84be7eSDavid Ahern 		goto out;
2764ab84be7eSDavid Ahern 	}
2765ab84be7eSDavid Ahern 
2766ab84be7eSDavid Ahern 	switch (nhm->nh_family) {
2767597cfe4fSDavid Ahern 	case AF_INET:
276853010f99SDavid Ahern 	case AF_INET6:
2769597cfe4fSDavid Ahern 		break;
2770430a0491SDavid Ahern 	case AF_UNSPEC:
2771430a0491SDavid Ahern 		if (tb[NHA_GROUP])
2772430a0491SDavid Ahern 			break;
2773a8eceea8SJoe Perches 		fallthrough;
2774ab84be7eSDavid Ahern 	default:
2775ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid address family");
2776ab84be7eSDavid Ahern 		goto out;
2777ab84be7eSDavid Ahern 	}
2778ab84be7eSDavid Ahern 
2779ab84be7eSDavid Ahern 	memset(cfg, 0, sizeof(*cfg));
2780ab84be7eSDavid Ahern 	cfg->nlflags = nlh->nlmsg_flags;
2781ab84be7eSDavid Ahern 	cfg->nlinfo.portid = NETLINK_CB(skb).portid;
2782ab84be7eSDavid Ahern 	cfg->nlinfo.nlh = nlh;
2783ab84be7eSDavid Ahern 	cfg->nlinfo.nl_net = net;
2784ab84be7eSDavid Ahern 
2785ab84be7eSDavid Ahern 	cfg->nh_family = nhm->nh_family;
2786ab84be7eSDavid Ahern 	cfg->nh_protocol = nhm->nh_protocol;
2787ab84be7eSDavid Ahern 	cfg->nh_flags = nhm->nh_flags;
2788ab84be7eSDavid Ahern 
2789ab84be7eSDavid Ahern 	if (tb[NHA_ID])
2790ab84be7eSDavid Ahern 		cfg->nh_id = nla_get_u32(tb[NHA_ID]);
2791ab84be7eSDavid Ahern 
279238428d68SRoopa Prabhu 	if (tb[NHA_FDB]) {
279338428d68SRoopa Prabhu 		if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] ||
279438428d68SRoopa Prabhu 		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE]) {
279538428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole");
279638428d68SRoopa Prabhu 			goto out;
279738428d68SRoopa Prabhu 		}
279838428d68SRoopa Prabhu 		if (nhm->nh_flags) {
279938428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header");
280038428d68SRoopa Prabhu 			goto out;
280138428d68SRoopa Prabhu 		}
280238428d68SRoopa Prabhu 		cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]);
280338428d68SRoopa Prabhu 	}
280438428d68SRoopa Prabhu 
2805430a0491SDavid Ahern 	if (tb[NHA_GROUP]) {
2806430a0491SDavid Ahern 		if (nhm->nh_family != AF_UNSPEC) {
2807430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid family for group");
2808430a0491SDavid Ahern 			goto out;
2809430a0491SDavid Ahern 		}
2810430a0491SDavid Ahern 		cfg->nh_grp = tb[NHA_GROUP];
2811430a0491SDavid Ahern 
2812430a0491SDavid Ahern 		cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH;
2813430a0491SDavid Ahern 		if (tb[NHA_GROUP_TYPE])
2814430a0491SDavid Ahern 			cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]);
2815430a0491SDavid Ahern 
2816430a0491SDavid Ahern 		if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) {
2817430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid group type");
2818430a0491SDavid Ahern 			goto out;
2819430a0491SDavid Ahern 		}
2820a2601e2bSPetr Machata 		err = nh_check_attr_group(net, tb, ARRAY_SIZE(tb),
2821a2601e2bSPetr Machata 					  cfg->nh_grp_type, extack);
2822a2601e2bSPetr Machata 		if (err)
2823a2601e2bSPetr Machata 			goto out;
2824a2601e2bSPetr Machata 
2825a2601e2bSPetr Machata 		if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_RES)
2826a2601e2bSPetr Machata 			err = rtm_to_nh_config_grp_res(tb[NHA_RES_GROUP],
2827a2601e2bSPetr Machata 						       cfg, extack);
2828430a0491SDavid Ahern 
2829430a0491SDavid Ahern 		/* no other attributes should be set */
2830430a0491SDavid Ahern 		goto out;
2831430a0491SDavid Ahern 	}
2832430a0491SDavid Ahern 
2833ab84be7eSDavid Ahern 	if (tb[NHA_BLACKHOLE]) {
2834b513bd03SDavid Ahern 		if (tb[NHA_GATEWAY] || tb[NHA_OIF] ||
283538428d68SRoopa Prabhu 		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) {
283638428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb");
2837ab84be7eSDavid Ahern 			goto out;
2838ab84be7eSDavid Ahern 		}
2839ab84be7eSDavid Ahern 
2840ab84be7eSDavid Ahern 		cfg->nh_blackhole = 1;
2841ab84be7eSDavid Ahern 		err = 0;
2842ab84be7eSDavid Ahern 		goto out;
2843ab84be7eSDavid Ahern 	}
2844ab84be7eSDavid Ahern 
284538428d68SRoopa Prabhu 	if (!cfg->nh_fdb && !tb[NHA_OIF]) {
284638428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops");
2847ab84be7eSDavid Ahern 		goto out;
2848ab84be7eSDavid Ahern 	}
2849ab84be7eSDavid Ahern 
285038428d68SRoopa Prabhu 	if (!cfg->nh_fdb && tb[NHA_OIF]) {
2851ab84be7eSDavid Ahern 		cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]);
2852ab84be7eSDavid Ahern 		if (cfg->nh_ifindex)
2853ab84be7eSDavid Ahern 			cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex);
2854ab84be7eSDavid Ahern 
2855ab84be7eSDavid Ahern 		if (!cfg->dev) {
2856ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid device index");
2857ab84be7eSDavid Ahern 			goto out;
2858ab84be7eSDavid Ahern 		} else if (!(cfg->dev->flags & IFF_UP)) {
2859ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Nexthop device is not up");
2860ab84be7eSDavid Ahern 			err = -ENETDOWN;
2861ab84be7eSDavid Ahern 			goto out;
2862ab84be7eSDavid Ahern 		} else if (!netif_carrier_ok(cfg->dev)) {
2863ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down");
2864ab84be7eSDavid Ahern 			err = -ENETDOWN;
2865ab84be7eSDavid Ahern 			goto out;
2866ab84be7eSDavid Ahern 		}
286738428d68SRoopa Prabhu 	}
2868ab84be7eSDavid Ahern 
2869597cfe4fSDavid Ahern 	err = -EINVAL;
2870597cfe4fSDavid Ahern 	if (tb[NHA_GATEWAY]) {
2871597cfe4fSDavid Ahern 		struct nlattr *gwa = tb[NHA_GATEWAY];
2872597cfe4fSDavid Ahern 
2873597cfe4fSDavid Ahern 		switch (cfg->nh_family) {
2874597cfe4fSDavid Ahern 		case AF_INET:
2875597cfe4fSDavid Ahern 			if (nla_len(gwa) != sizeof(u32)) {
2876597cfe4fSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
2877597cfe4fSDavid Ahern 				goto out;
2878597cfe4fSDavid Ahern 			}
2879597cfe4fSDavid Ahern 			cfg->gw.ipv4 = nla_get_be32(gwa);
2880597cfe4fSDavid Ahern 			break;
288153010f99SDavid Ahern 		case AF_INET6:
288253010f99SDavid Ahern 			if (nla_len(gwa) != sizeof(struct in6_addr)) {
288353010f99SDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
288453010f99SDavid Ahern 				goto out;
288553010f99SDavid Ahern 			}
288653010f99SDavid Ahern 			cfg->gw.ipv6 = nla_get_in6_addr(gwa);
288753010f99SDavid Ahern 			break;
2888597cfe4fSDavid Ahern 		default:
2889597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
2890597cfe4fSDavid Ahern 				       "Unknown address family for gateway");
2891597cfe4fSDavid Ahern 			goto out;
2892597cfe4fSDavid Ahern 		}
2893597cfe4fSDavid Ahern 	} else {
2894597cfe4fSDavid Ahern 		/* device only nexthop (no gateway) */
2895597cfe4fSDavid Ahern 		if (cfg->nh_flags & RTNH_F_ONLINK) {
2896597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
2897597cfe4fSDavid Ahern 				       "ONLINK flag can not be set for nexthop without a gateway");
2898597cfe4fSDavid Ahern 			goto out;
2899597cfe4fSDavid Ahern 		}
2900597cfe4fSDavid Ahern 	}
2901597cfe4fSDavid Ahern 
2902b513bd03SDavid Ahern 	if (tb[NHA_ENCAP]) {
2903b513bd03SDavid Ahern 		cfg->nh_encap = tb[NHA_ENCAP];
2904b513bd03SDavid Ahern 
2905b513bd03SDavid Ahern 		if (!tb[NHA_ENCAP_TYPE]) {
2906b513bd03SDavid Ahern 			NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing");
2907b513bd03SDavid Ahern 			goto out;
2908b513bd03SDavid Ahern 		}
2909b513bd03SDavid Ahern 
2910b513bd03SDavid Ahern 		cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]);
2911b513bd03SDavid Ahern 		err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack);
2912b513bd03SDavid Ahern 		if (err < 0)
2913b513bd03SDavid Ahern 			goto out;
2914b513bd03SDavid Ahern 
2915b513bd03SDavid Ahern 	} else if (tb[NHA_ENCAP_TYPE]) {
2916b513bd03SDavid Ahern 		NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing");
2917b513bd03SDavid Ahern 		goto out;
2918b513bd03SDavid Ahern 	}
2919b513bd03SDavid Ahern 
2920b513bd03SDavid Ahern 
2921ab84be7eSDavid Ahern 	err = 0;
2922ab84be7eSDavid Ahern out:
2923ab84be7eSDavid Ahern 	return err;
2924ab84be7eSDavid Ahern }
2925ab84be7eSDavid Ahern 
2926ab84be7eSDavid Ahern /* rtnl */
2927ab84be7eSDavid Ahern static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
2928ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
2929ab84be7eSDavid Ahern {
2930ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
2931ab84be7eSDavid Ahern 	struct nh_config cfg;
2932ab84be7eSDavid Ahern 	struct nexthop *nh;
2933ab84be7eSDavid Ahern 	int err;
2934ab84be7eSDavid Ahern 
2935ab84be7eSDavid Ahern 	err = rtm_to_nh_config(net, skb, nlh, &cfg, extack);
2936ab84be7eSDavid Ahern 	if (!err) {
2937ab84be7eSDavid Ahern 		nh = nexthop_add(net, &cfg, extack);
2938ab84be7eSDavid Ahern 		if (IS_ERR(nh))
2939ab84be7eSDavid Ahern 			err = PTR_ERR(nh);
2940ab84be7eSDavid Ahern 	}
2941ab84be7eSDavid Ahern 
2942ab84be7eSDavid Ahern 	return err;
2943ab84be7eSDavid Ahern }
2944ab84be7eSDavid Ahern 
29450bccf8edSPetr Machata static int __nh_valid_get_del_req(const struct nlmsghdr *nlh,
29460bccf8edSPetr Machata 				  struct nlattr **tb, u32 *id,
2947ab84be7eSDavid Ahern 				  struct netlink_ext_ack *extack)
2948ab84be7eSDavid Ahern {
2949ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
29500bccf8edSPetr Machata 
29510bccf8edSPetr Machata 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
29520bccf8edSPetr Machata 		NL_SET_ERR_MSG(extack, "Invalid values in header");
29530bccf8edSPetr Machata 		return -EINVAL;
29540bccf8edSPetr Machata 	}
29550bccf8edSPetr Machata 
29560bccf8edSPetr Machata 	if (!tb[NHA_ID]) {
29570bccf8edSPetr Machata 		NL_SET_ERR_MSG(extack, "Nexthop id is missing");
29580bccf8edSPetr Machata 		return -EINVAL;
29590bccf8edSPetr Machata 	}
29600bccf8edSPetr Machata 
29610bccf8edSPetr Machata 	*id = nla_get_u32(tb[NHA_ID]);
29620bccf8edSPetr Machata 	if (!(*id)) {
29630bccf8edSPetr Machata 		NL_SET_ERR_MSG(extack, "Invalid nexthop id");
29640bccf8edSPetr Machata 		return -EINVAL;
29650bccf8edSPetr Machata 	}
29660bccf8edSPetr Machata 
29670bccf8edSPetr Machata 	return 0;
29680bccf8edSPetr Machata }
29690bccf8edSPetr Machata 
29700bccf8edSPetr Machata static int nh_valid_get_del_req(const struct nlmsghdr *nlh, u32 *id,
29710bccf8edSPetr Machata 				struct netlink_ext_ack *extack)
29720bccf8edSPetr Machata {
297360f5ad5eSPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_get)];
297460f5ad5eSPetr Machata 	int err;
2975ab84be7eSDavid Ahern 
29760bccf8edSPetr Machata 	err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb,
297760f5ad5eSPetr Machata 			  ARRAY_SIZE(rtm_nh_policy_get) - 1,
297860f5ad5eSPetr Machata 			  rtm_nh_policy_get, extack);
2979ab84be7eSDavid Ahern 	if (err < 0)
2980ab84be7eSDavid Ahern 		return err;
2981ab84be7eSDavid Ahern 
29820bccf8edSPetr Machata 	return __nh_valid_get_del_req(nlh, tb, id, extack);
2983ab84be7eSDavid Ahern }
2984ab84be7eSDavid Ahern 
2985ab84be7eSDavid Ahern /* rtnl */
2986ab84be7eSDavid Ahern static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
2987ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
2988ab84be7eSDavid Ahern {
2989ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
2990ab84be7eSDavid Ahern 	struct nl_info nlinfo = {
2991ab84be7eSDavid Ahern 		.nlh = nlh,
2992ab84be7eSDavid Ahern 		.nl_net = net,
2993ab84be7eSDavid Ahern 		.portid = NETLINK_CB(skb).portid,
2994ab84be7eSDavid Ahern 	};
2995ab84be7eSDavid Ahern 	struct nexthop *nh;
2996ab84be7eSDavid Ahern 	int err;
2997ab84be7eSDavid Ahern 	u32 id;
2998ab84be7eSDavid Ahern 
2999ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
3000ab84be7eSDavid Ahern 	if (err)
3001ab84be7eSDavid Ahern 		return err;
3002ab84be7eSDavid Ahern 
3003ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
3004ab84be7eSDavid Ahern 	if (!nh)
3005ab84be7eSDavid Ahern 		return -ENOENT;
3006ab84be7eSDavid Ahern 
3007430a0491SDavid Ahern 	remove_nexthop(net, nh, &nlinfo);
3008ab84be7eSDavid Ahern 
3009ab84be7eSDavid Ahern 	return 0;
3010ab84be7eSDavid Ahern }
3011ab84be7eSDavid Ahern 
3012ab84be7eSDavid Ahern /* rtnl */
3013ab84be7eSDavid Ahern static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh,
3014ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
3015ab84be7eSDavid Ahern {
3016ab84be7eSDavid Ahern 	struct net *net = sock_net(in_skb->sk);
3017ab84be7eSDavid Ahern 	struct sk_buff *skb = NULL;
3018ab84be7eSDavid Ahern 	struct nexthop *nh;
3019ab84be7eSDavid Ahern 	int err;
3020ab84be7eSDavid Ahern 	u32 id;
3021ab84be7eSDavid Ahern 
3022ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
3023ab84be7eSDavid Ahern 	if (err)
3024ab84be7eSDavid Ahern 		return err;
3025ab84be7eSDavid Ahern 
3026ab84be7eSDavid Ahern 	err = -ENOBUFS;
3027ab84be7eSDavid Ahern 	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
3028ab84be7eSDavid Ahern 	if (!skb)
3029ab84be7eSDavid Ahern 		goto out;
3030ab84be7eSDavid Ahern 
3031ab84be7eSDavid Ahern 	err = -ENOENT;
3032ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
3033ab84be7eSDavid Ahern 	if (!nh)
3034ab84be7eSDavid Ahern 		goto errout_free;
3035ab84be7eSDavid Ahern 
3036ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid,
3037ab84be7eSDavid Ahern 			   nlh->nlmsg_seq, 0);
3038ab84be7eSDavid Ahern 	if (err < 0) {
3039ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
3040ab84be7eSDavid Ahern 		goto errout_free;
3041ab84be7eSDavid Ahern 	}
3042ab84be7eSDavid Ahern 
3043ab84be7eSDavid Ahern 	err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
3044ab84be7eSDavid Ahern out:
3045ab84be7eSDavid Ahern 	return err;
3046ab84be7eSDavid Ahern errout_free:
3047ab84be7eSDavid Ahern 	kfree_skb(skb);
3048ab84be7eSDavid Ahern 	goto out;
3049ab84be7eSDavid Ahern }
3050ab84be7eSDavid Ahern 
305156450ec6SPetr Machata struct nh_dump_filter {
30528a1bbabbSPetr Machata 	u32 nh_id;
305356450ec6SPetr Machata 	int dev_idx;
305456450ec6SPetr Machata 	int master_idx;
305556450ec6SPetr Machata 	bool group_filter;
305656450ec6SPetr Machata 	bool fdb_filter;
30578a1bbabbSPetr Machata 	u32 res_bucket_nh_id;
305856450ec6SPetr Machata };
305956450ec6SPetr Machata 
306056450ec6SPetr Machata static bool nh_dump_filtered(struct nexthop *nh,
306156450ec6SPetr Machata 			     struct nh_dump_filter *filter, u8 family)
3062ab84be7eSDavid Ahern {
3063ab84be7eSDavid Ahern 	const struct net_device *dev;
3064ab84be7eSDavid Ahern 	const struct nh_info *nhi;
3065ab84be7eSDavid Ahern 
306656450ec6SPetr Machata 	if (filter->group_filter && !nh->is_group)
3067430a0491SDavid Ahern 		return true;
3068430a0491SDavid Ahern 
306956450ec6SPetr Machata 	if (!filter->dev_idx && !filter->master_idx && !family)
3070ab84be7eSDavid Ahern 		return false;
3071ab84be7eSDavid Ahern 
3072430a0491SDavid Ahern 	if (nh->is_group)
3073430a0491SDavid Ahern 		return true;
3074430a0491SDavid Ahern 
3075ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
3076ab84be7eSDavid Ahern 	if (family && nhi->family != family)
3077ab84be7eSDavid Ahern 		return true;
3078ab84be7eSDavid Ahern 
3079ab84be7eSDavid Ahern 	dev = nhi->fib_nhc.nhc_dev;
308056450ec6SPetr Machata 	if (filter->dev_idx && (!dev || dev->ifindex != filter->dev_idx))
3081ab84be7eSDavid Ahern 		return true;
3082ab84be7eSDavid Ahern 
308356450ec6SPetr Machata 	if (filter->master_idx) {
3084ab84be7eSDavid Ahern 		struct net_device *master;
3085ab84be7eSDavid Ahern 
3086ab84be7eSDavid Ahern 		if (!dev)
3087ab84be7eSDavid Ahern 			return true;
3088ab84be7eSDavid Ahern 
3089ab84be7eSDavid Ahern 		master = netdev_master_upper_dev_get((struct net_device *)dev);
309056450ec6SPetr Machata 		if (!master || master->ifindex != filter->master_idx)
3091ab84be7eSDavid Ahern 			return true;
3092ab84be7eSDavid Ahern 	}
3093ab84be7eSDavid Ahern 
3094ab84be7eSDavid Ahern 	return false;
3095ab84be7eSDavid Ahern }
3096ab84be7eSDavid Ahern 
3097b9ebea12SPetr Machata static int __nh_valid_dump_req(const struct nlmsghdr *nlh, struct nlattr **tb,
309856450ec6SPetr Machata 			       struct nh_dump_filter *filter,
3099b9ebea12SPetr Machata 			       struct netlink_ext_ack *extack)
3100ab84be7eSDavid Ahern {
3101ab84be7eSDavid Ahern 	struct nhmsg *nhm;
3102ab84be7eSDavid Ahern 	u32 idx;
3103ab84be7eSDavid Ahern 
310444551bffSPetr Machata 	if (tb[NHA_OIF]) {
310544551bffSPetr Machata 		idx = nla_get_u32(tb[NHA_OIF]);
3106ab84be7eSDavid Ahern 		if (idx > INT_MAX) {
3107ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid device index");
3108ab84be7eSDavid Ahern 			return -EINVAL;
3109ab84be7eSDavid Ahern 		}
311056450ec6SPetr Machata 		filter->dev_idx = idx;
311144551bffSPetr Machata 	}
311244551bffSPetr Machata 	if (tb[NHA_MASTER]) {
311344551bffSPetr Machata 		idx = nla_get_u32(tb[NHA_MASTER]);
3114ab84be7eSDavid Ahern 		if (idx > INT_MAX) {
3115ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid master device index");
3116ab84be7eSDavid Ahern 			return -EINVAL;
3117ab84be7eSDavid Ahern 		}
311856450ec6SPetr Machata 		filter->master_idx = idx;
3119ab84be7eSDavid Ahern 	}
312056450ec6SPetr Machata 	filter->group_filter = nla_get_flag(tb[NHA_GROUPS]);
312156450ec6SPetr Machata 	filter->fdb_filter = nla_get_flag(tb[NHA_FDB]);
3122ab84be7eSDavid Ahern 
3123ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
3124ab84be7eSDavid Ahern 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
3125ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request");
3126ab84be7eSDavid Ahern 		return -EINVAL;
3127ab84be7eSDavid Ahern 	}
3128ab84be7eSDavid Ahern 
3129ab84be7eSDavid Ahern 	return 0;
3130ab84be7eSDavid Ahern }
3131ab84be7eSDavid Ahern 
3132b9ebea12SPetr Machata static int nh_valid_dump_req(const struct nlmsghdr *nlh,
3133b9ebea12SPetr Machata 			     struct nh_dump_filter *filter,
3134b9ebea12SPetr Machata 			     struct netlink_callback *cb)
3135b9ebea12SPetr Machata {
3136b9ebea12SPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_dump)];
3137b9ebea12SPetr Machata 	int err;
3138b9ebea12SPetr Machata 
3139b9ebea12SPetr Machata 	err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb,
3140b9ebea12SPetr Machata 			  ARRAY_SIZE(rtm_nh_policy_dump) - 1,
3141b9ebea12SPetr Machata 			  rtm_nh_policy_dump, cb->extack);
3142b9ebea12SPetr Machata 	if (err < 0)
3143b9ebea12SPetr Machata 		return err;
3144b9ebea12SPetr Machata 
3145b9ebea12SPetr Machata 	return __nh_valid_dump_req(nlh, tb, filter, cb->extack);
3146b9ebea12SPetr Machata }
3147b9ebea12SPetr Machata 
3148a6fbbaa6SPetr Machata struct rtm_dump_nh_ctx {
3149a6fbbaa6SPetr Machata 	u32 idx;
3150a6fbbaa6SPetr Machata };
3151a6fbbaa6SPetr Machata 
3152a6fbbaa6SPetr Machata static struct rtm_dump_nh_ctx *
3153a6fbbaa6SPetr Machata rtm_dump_nh_ctx(struct netlink_callback *cb)
3154a6fbbaa6SPetr Machata {
3155a6fbbaa6SPetr Machata 	struct rtm_dump_nh_ctx *ctx = (void *)cb->ctx;
3156a6fbbaa6SPetr Machata 
3157a6fbbaa6SPetr Machata 	BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
3158a6fbbaa6SPetr Machata 	return ctx;
3159a6fbbaa6SPetr Machata }
3160a6fbbaa6SPetr Machata 
3161cbee1807SPetr Machata static int rtm_dump_walk_nexthops(struct sk_buff *skb,
3162cbee1807SPetr Machata 				  struct netlink_callback *cb,
3163cbee1807SPetr Machata 				  struct rb_root *root,
3164cbee1807SPetr Machata 				  struct rtm_dump_nh_ctx *ctx,
3165e948217dSPetr Machata 				  int (*nh_cb)(struct sk_buff *skb,
3166e948217dSPetr Machata 					       struct netlink_callback *cb,
3167e948217dSPetr Machata 					       struct nexthop *nh, void *data),
3168e948217dSPetr Machata 				  void *data)
3169ab84be7eSDavid Ahern {
3170ab84be7eSDavid Ahern 	struct rb_node *node;
31719e46fb65SIdo Schimmel 	int s_idx;
3172ab84be7eSDavid Ahern 	int err;
3173ab84be7eSDavid Ahern 
3174a6fbbaa6SPetr Machata 	s_idx = ctx->idx;
3175ab84be7eSDavid Ahern 	for (node = rb_first(root); node; node = rb_next(node)) {
3176ab84be7eSDavid Ahern 		struct nexthop *nh;
3177ab84be7eSDavid Ahern 
3178ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
31799e46fb65SIdo Schimmel 		if (nh->id < s_idx)
31809e46fb65SIdo Schimmel 			continue;
31819e46fb65SIdo Schimmel 
31829e46fb65SIdo Schimmel 		ctx->idx = nh->id;
3183e948217dSPetr Machata 		err = nh_cb(skb, cb, nh, data);
3184e948217dSPetr Machata 		if (err)
3185cbee1807SPetr Machata 			return err;
3186cbee1807SPetr Machata 	}
3187cbee1807SPetr Machata 
31889e46fb65SIdo Schimmel 	ctx->idx++;
3189cbee1807SPetr Machata 	return 0;
3190cbee1807SPetr Machata }
3191cbee1807SPetr Machata 
3192e948217dSPetr Machata static int rtm_dump_nexthop_cb(struct sk_buff *skb, struct netlink_callback *cb,
3193e948217dSPetr Machata 			       struct nexthop *nh, void *data)
3194e948217dSPetr Machata {
3195e948217dSPetr Machata 	struct nhmsg *nhm = nlmsg_data(cb->nlh);
3196e948217dSPetr Machata 	struct nh_dump_filter *filter = data;
3197e948217dSPetr Machata 
3198e948217dSPetr Machata 	if (nh_dump_filtered(nh, filter, nhm->nh_family))
3199e948217dSPetr Machata 		return 0;
3200e948217dSPetr Machata 
3201e948217dSPetr Machata 	return nh_fill_node(skb, nh, RTM_NEWNEXTHOP,
3202e948217dSPetr Machata 			    NETLINK_CB(cb->skb).portid,
3203e948217dSPetr Machata 			    cb->nlh->nlmsg_seq, NLM_F_MULTI);
3204e948217dSPetr Machata }
3205e948217dSPetr Machata 
3206cbee1807SPetr Machata /* rtnl */
3207cbee1807SPetr Machata static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb)
3208cbee1807SPetr Machata {
3209cbee1807SPetr Machata 	struct rtm_dump_nh_ctx *ctx = rtm_dump_nh_ctx(cb);
3210cbee1807SPetr Machata 	struct net *net = sock_net(skb->sk);
3211cbee1807SPetr Machata 	struct rb_root *root = &net->nexthop.rb_root;
3212cbee1807SPetr Machata 	struct nh_dump_filter filter = {};
3213cbee1807SPetr Machata 	int err;
3214cbee1807SPetr Machata 
3215cbee1807SPetr Machata 	err = nh_valid_dump_req(cb->nlh, &filter, cb);
3216cbee1807SPetr Machata 	if (err < 0)
3217cbee1807SPetr Machata 		return err;
3218cbee1807SPetr Machata 
3219e948217dSPetr Machata 	err = rtm_dump_walk_nexthops(skb, cb, root, ctx,
3220e948217dSPetr Machata 				     &rtm_dump_nexthop_cb, &filter);
3221ab84be7eSDavid Ahern 	if (err < 0) {
3222ab84be7eSDavid Ahern 		if (likely(skb->len))
3223ab84be7eSDavid Ahern 			goto out;
3224ab84be7eSDavid Ahern 		goto out_err;
3225ab84be7eSDavid Ahern 	}
3226ab84be7eSDavid Ahern 
3227ab84be7eSDavid Ahern out:
3228ab84be7eSDavid Ahern 	err = skb->len;
3229ab84be7eSDavid Ahern out_err:
3230ab84be7eSDavid Ahern 	cb->seq = net->nexthop.seq;
3231ab84be7eSDavid Ahern 	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
3232ab84be7eSDavid Ahern 	return err;
3233ab84be7eSDavid Ahern }
3234ab84be7eSDavid Ahern 
32358a1bbabbSPetr Machata static struct nexthop *
32368a1bbabbSPetr Machata nexthop_find_group_resilient(struct net *net, u32 id,
32378a1bbabbSPetr Machata 			     struct netlink_ext_ack *extack)
32388a1bbabbSPetr Machata {
32398a1bbabbSPetr Machata 	struct nh_group *nhg;
32408a1bbabbSPetr Machata 	struct nexthop *nh;
32418a1bbabbSPetr Machata 
32428a1bbabbSPetr Machata 	nh = nexthop_find_by_id(net, id);
32438a1bbabbSPetr Machata 	if (!nh)
32448a1bbabbSPetr Machata 		return ERR_PTR(-ENOENT);
32458a1bbabbSPetr Machata 
32468a1bbabbSPetr Machata 	if (!nh->is_group) {
32478a1bbabbSPetr Machata 		NL_SET_ERR_MSG(extack, "Not a nexthop group");
32488a1bbabbSPetr Machata 		return ERR_PTR(-EINVAL);
32498a1bbabbSPetr Machata 	}
32508a1bbabbSPetr Machata 
32518a1bbabbSPetr Machata 	nhg = rtnl_dereference(nh->nh_grp);
32528a1bbabbSPetr Machata 	if (!nhg->resilient) {
32538a1bbabbSPetr Machata 		NL_SET_ERR_MSG(extack, "Nexthop group not of type resilient");
32548a1bbabbSPetr Machata 		return ERR_PTR(-EINVAL);
32558a1bbabbSPetr Machata 	}
32568a1bbabbSPetr Machata 
32578a1bbabbSPetr Machata 	return nh;
32588a1bbabbSPetr Machata }
32598a1bbabbSPetr Machata 
32608a1bbabbSPetr Machata static int nh_valid_dump_nhid(struct nlattr *attr, u32 *nh_id_p,
32618a1bbabbSPetr Machata 			      struct netlink_ext_ack *extack)
32628a1bbabbSPetr Machata {
32638a1bbabbSPetr Machata 	u32 idx;
32648a1bbabbSPetr Machata 
32658a1bbabbSPetr Machata 	if (attr) {
32668a1bbabbSPetr Machata 		idx = nla_get_u32(attr);
32678a1bbabbSPetr Machata 		if (!idx) {
32688a1bbabbSPetr Machata 			NL_SET_ERR_MSG(extack, "Invalid nexthop id");
32698a1bbabbSPetr Machata 			return -EINVAL;
32708a1bbabbSPetr Machata 		}
32718a1bbabbSPetr Machata 		*nh_id_p = idx;
32728a1bbabbSPetr Machata 	} else {
32738a1bbabbSPetr Machata 		*nh_id_p = 0;
32748a1bbabbSPetr Machata 	}
32758a1bbabbSPetr Machata 
32768a1bbabbSPetr Machata 	return 0;
32778a1bbabbSPetr Machata }
32788a1bbabbSPetr Machata 
32798a1bbabbSPetr Machata static int nh_valid_dump_bucket_req(const struct nlmsghdr *nlh,
32808a1bbabbSPetr Machata 				    struct nh_dump_filter *filter,
32818a1bbabbSPetr Machata 				    struct netlink_callback *cb)
32828a1bbabbSPetr Machata {
32838a1bbabbSPetr Machata 	struct nlattr *res_tb[ARRAY_SIZE(rtm_nh_res_bucket_policy_dump)];
32848a1bbabbSPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_dump_bucket)];
32858a1bbabbSPetr Machata 	int err;
32868a1bbabbSPetr Machata 
32878a1bbabbSPetr Machata 	err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb,
32888a1bbabbSPetr Machata 			  ARRAY_SIZE(rtm_nh_policy_dump_bucket) - 1,
32898a1bbabbSPetr Machata 			  rtm_nh_policy_dump_bucket, NULL);
32908a1bbabbSPetr Machata 	if (err < 0)
32918a1bbabbSPetr Machata 		return err;
32928a1bbabbSPetr Machata 
32938a1bbabbSPetr Machata 	err = nh_valid_dump_nhid(tb[NHA_ID], &filter->nh_id, cb->extack);
32948a1bbabbSPetr Machata 	if (err)
32958a1bbabbSPetr Machata 		return err;
32968a1bbabbSPetr Machata 
32978a1bbabbSPetr Machata 	if (tb[NHA_RES_BUCKET]) {
32988a1bbabbSPetr Machata 		size_t max = ARRAY_SIZE(rtm_nh_res_bucket_policy_dump) - 1;
32998a1bbabbSPetr Machata 
33008a1bbabbSPetr Machata 		err = nla_parse_nested(res_tb, max,
33018a1bbabbSPetr Machata 				       tb[NHA_RES_BUCKET],
33028a1bbabbSPetr Machata 				       rtm_nh_res_bucket_policy_dump,
33038a1bbabbSPetr Machata 				       cb->extack);
33048a1bbabbSPetr Machata 		if (err < 0)
33058a1bbabbSPetr Machata 			return err;
33068a1bbabbSPetr Machata 
33078a1bbabbSPetr Machata 		err = nh_valid_dump_nhid(res_tb[NHA_RES_BUCKET_NH_ID],
33088a1bbabbSPetr Machata 					 &filter->res_bucket_nh_id,
33098a1bbabbSPetr Machata 					 cb->extack);
33108a1bbabbSPetr Machata 		if (err)
33118a1bbabbSPetr Machata 			return err;
33128a1bbabbSPetr Machata 	}
33138a1bbabbSPetr Machata 
33148a1bbabbSPetr Machata 	return __nh_valid_dump_req(nlh, tb, filter, cb->extack);
33158a1bbabbSPetr Machata }
33168a1bbabbSPetr Machata 
33178a1bbabbSPetr Machata struct rtm_dump_res_bucket_ctx {
33188a1bbabbSPetr Machata 	struct rtm_dump_nh_ctx nh;
33198a1bbabbSPetr Machata 	u16 bucket_index;
33208a1bbabbSPetr Machata 	u32 done_nh_idx; /* 1 + the index of the last fully processed NH. */
33218a1bbabbSPetr Machata };
33228a1bbabbSPetr Machata 
33238a1bbabbSPetr Machata static struct rtm_dump_res_bucket_ctx *
33248a1bbabbSPetr Machata rtm_dump_res_bucket_ctx(struct netlink_callback *cb)
33258a1bbabbSPetr Machata {
33268a1bbabbSPetr Machata 	struct rtm_dump_res_bucket_ctx *ctx = (void *)cb->ctx;
33278a1bbabbSPetr Machata 
33288a1bbabbSPetr Machata 	BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
33298a1bbabbSPetr Machata 	return ctx;
33308a1bbabbSPetr Machata }
33318a1bbabbSPetr Machata 
33328a1bbabbSPetr Machata struct rtm_dump_nexthop_bucket_data {
33338a1bbabbSPetr Machata 	struct rtm_dump_res_bucket_ctx *ctx;
33348a1bbabbSPetr Machata 	struct nh_dump_filter filter;
33358a1bbabbSPetr Machata };
33368a1bbabbSPetr Machata 
33378a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket_nh(struct sk_buff *skb,
33388a1bbabbSPetr Machata 				      struct netlink_callback *cb,
33398a1bbabbSPetr Machata 				      struct nexthop *nh,
33408a1bbabbSPetr Machata 				      struct rtm_dump_nexthop_bucket_data *dd)
33418a1bbabbSPetr Machata {
33428a1bbabbSPetr Machata 	u32 portid = NETLINK_CB(cb->skb).portid;
33438a1bbabbSPetr Machata 	struct nhmsg *nhm = nlmsg_data(cb->nlh);
33448a1bbabbSPetr Machata 	struct nh_res_table *res_table;
33458a1bbabbSPetr Machata 	struct nh_group *nhg;
33468a1bbabbSPetr Machata 	u16 bucket_index;
33478a1bbabbSPetr Machata 	int err;
33488a1bbabbSPetr Machata 
33498a1bbabbSPetr Machata 	if (dd->ctx->nh.idx < dd->ctx->done_nh_idx)
33508a1bbabbSPetr Machata 		return 0;
33518a1bbabbSPetr Machata 
33528a1bbabbSPetr Machata 	nhg = rtnl_dereference(nh->nh_grp);
33538a1bbabbSPetr Machata 	res_table = rtnl_dereference(nhg->res_table);
33548a1bbabbSPetr Machata 	for (bucket_index = dd->ctx->bucket_index;
33558a1bbabbSPetr Machata 	     bucket_index < res_table->num_nh_buckets;
33568a1bbabbSPetr Machata 	     bucket_index++) {
33578a1bbabbSPetr Machata 		struct nh_res_bucket *bucket;
33588a1bbabbSPetr Machata 		struct nh_grp_entry *nhge;
33598a1bbabbSPetr Machata 
33608a1bbabbSPetr Machata 		bucket = &res_table->nh_buckets[bucket_index];
33618a1bbabbSPetr Machata 		nhge = rtnl_dereference(bucket->nh_entry);
33628a1bbabbSPetr Machata 		if (nh_dump_filtered(nhge->nh, &dd->filter, nhm->nh_family))
33638a1bbabbSPetr Machata 			continue;
33648a1bbabbSPetr Machata 
33658a1bbabbSPetr Machata 		if (dd->filter.res_bucket_nh_id &&
33668a1bbabbSPetr Machata 		    dd->filter.res_bucket_nh_id != nhge->nh->id)
33678a1bbabbSPetr Machata 			continue;
33688a1bbabbSPetr Machata 
33698a1bbabbSPetr Machata 		err = nh_fill_res_bucket(skb, nh, bucket, bucket_index,
33708a1bbabbSPetr Machata 					 RTM_NEWNEXTHOPBUCKET, portid,
33718a1bbabbSPetr Machata 					 cb->nlh->nlmsg_seq, NLM_F_MULTI,
33728a1bbabbSPetr Machata 					 cb->extack);
33738a1bbabbSPetr Machata 		if (err < 0) {
33748a1bbabbSPetr Machata 			if (likely(skb->len))
33758a1bbabbSPetr Machata 				goto out;
33768a1bbabbSPetr Machata 			goto out_err;
33778a1bbabbSPetr Machata 		}
33788a1bbabbSPetr Machata 	}
33798a1bbabbSPetr Machata 
33808a1bbabbSPetr Machata 	dd->ctx->done_nh_idx = dd->ctx->nh.idx + 1;
33818a1bbabbSPetr Machata 	bucket_index = 0;
33828a1bbabbSPetr Machata 
33838a1bbabbSPetr Machata out:
33848a1bbabbSPetr Machata 	err = skb->len;
33858a1bbabbSPetr Machata out_err:
33868a1bbabbSPetr Machata 	dd->ctx->bucket_index = bucket_index;
33878a1bbabbSPetr Machata 	return err;
33888a1bbabbSPetr Machata }
33898a1bbabbSPetr Machata 
33908a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket_cb(struct sk_buff *skb,
33918a1bbabbSPetr Machata 				      struct netlink_callback *cb,
33928a1bbabbSPetr Machata 				      struct nexthop *nh, void *data)
33938a1bbabbSPetr Machata {
33948a1bbabbSPetr Machata 	struct rtm_dump_nexthop_bucket_data *dd = data;
33958a1bbabbSPetr Machata 	struct nh_group *nhg;
33968a1bbabbSPetr Machata 
33978a1bbabbSPetr Machata 	if (!nh->is_group)
33988a1bbabbSPetr Machata 		return 0;
33998a1bbabbSPetr Machata 
34008a1bbabbSPetr Machata 	nhg = rtnl_dereference(nh->nh_grp);
34018a1bbabbSPetr Machata 	if (!nhg->resilient)
34028a1bbabbSPetr Machata 		return 0;
34038a1bbabbSPetr Machata 
34048a1bbabbSPetr Machata 	return rtm_dump_nexthop_bucket_nh(skb, cb, nh, dd);
34058a1bbabbSPetr Machata }
34068a1bbabbSPetr Machata 
34078a1bbabbSPetr Machata /* rtnl */
34088a1bbabbSPetr Machata static int rtm_dump_nexthop_bucket(struct sk_buff *skb,
34098a1bbabbSPetr Machata 				   struct netlink_callback *cb)
34108a1bbabbSPetr Machata {
34118a1bbabbSPetr Machata 	struct rtm_dump_res_bucket_ctx *ctx = rtm_dump_res_bucket_ctx(cb);
34128a1bbabbSPetr Machata 	struct rtm_dump_nexthop_bucket_data dd = { .ctx = ctx };
34138a1bbabbSPetr Machata 	struct net *net = sock_net(skb->sk);
34148a1bbabbSPetr Machata 	struct nexthop *nh;
34158a1bbabbSPetr Machata 	int err;
34168a1bbabbSPetr Machata 
34178a1bbabbSPetr Machata 	err = nh_valid_dump_bucket_req(cb->nlh, &dd.filter, cb);
34188a1bbabbSPetr Machata 	if (err)
34198a1bbabbSPetr Machata 		return err;
34208a1bbabbSPetr Machata 
34218a1bbabbSPetr Machata 	if (dd.filter.nh_id) {
34228a1bbabbSPetr Machata 		nh = nexthop_find_group_resilient(net, dd.filter.nh_id,
34238a1bbabbSPetr Machata 						  cb->extack);
34248a1bbabbSPetr Machata 		if (IS_ERR(nh))
34258a1bbabbSPetr Machata 			return PTR_ERR(nh);
34268a1bbabbSPetr Machata 		err = rtm_dump_nexthop_bucket_nh(skb, cb, nh, &dd);
34278a1bbabbSPetr Machata 	} else {
34288a1bbabbSPetr Machata 		struct rb_root *root = &net->nexthop.rb_root;
34298a1bbabbSPetr Machata 
34308a1bbabbSPetr Machata 		err = rtm_dump_walk_nexthops(skb, cb, root, &ctx->nh,
34318a1bbabbSPetr Machata 					     &rtm_dump_nexthop_bucket_cb, &dd);
34328a1bbabbSPetr Machata 	}
34338a1bbabbSPetr Machata 
34348a1bbabbSPetr Machata 	if (err < 0) {
34358a1bbabbSPetr Machata 		if (likely(skb->len))
34368a1bbabbSPetr Machata 			goto out;
34378a1bbabbSPetr Machata 		goto out_err;
34388a1bbabbSPetr Machata 	}
34398a1bbabbSPetr Machata 
34408a1bbabbSPetr Machata out:
34418a1bbabbSPetr Machata 	err = skb->len;
34428a1bbabbSPetr Machata out_err:
34438a1bbabbSPetr Machata 	cb->seq = net->nexthop.seq;
34448a1bbabbSPetr Machata 	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
34458a1bbabbSPetr Machata 	return err;
34468a1bbabbSPetr Machata }
34478a1bbabbSPetr Machata 
3448187d4c6bSPetr Machata static int nh_valid_get_bucket_req_res_bucket(struct nlattr *res,
3449187d4c6bSPetr Machata 					      u16 *bucket_index,
3450187d4c6bSPetr Machata 					      struct netlink_ext_ack *extack)
3451187d4c6bSPetr Machata {
3452187d4c6bSPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_res_bucket_policy_get)];
3453187d4c6bSPetr Machata 	int err;
3454187d4c6bSPetr Machata 
3455187d4c6bSPetr Machata 	err = nla_parse_nested(tb, ARRAY_SIZE(rtm_nh_res_bucket_policy_get) - 1,
3456187d4c6bSPetr Machata 			       res, rtm_nh_res_bucket_policy_get, extack);
3457187d4c6bSPetr Machata 	if (err < 0)
3458187d4c6bSPetr Machata 		return err;
3459187d4c6bSPetr Machata 
3460187d4c6bSPetr Machata 	if (!tb[NHA_RES_BUCKET_INDEX]) {
3461187d4c6bSPetr Machata 		NL_SET_ERR_MSG(extack, "Bucket index is missing");
3462187d4c6bSPetr Machata 		return -EINVAL;
3463187d4c6bSPetr Machata 	}
3464187d4c6bSPetr Machata 
3465187d4c6bSPetr Machata 	*bucket_index = nla_get_u16(tb[NHA_RES_BUCKET_INDEX]);
3466187d4c6bSPetr Machata 	return 0;
3467187d4c6bSPetr Machata }
3468187d4c6bSPetr Machata 
3469187d4c6bSPetr Machata static int nh_valid_get_bucket_req(const struct nlmsghdr *nlh,
3470187d4c6bSPetr Machata 				   u32 *id, u16 *bucket_index,
3471187d4c6bSPetr Machata 				   struct netlink_ext_ack *extack)
3472187d4c6bSPetr Machata {
3473187d4c6bSPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_get_bucket)];
3474187d4c6bSPetr Machata 	int err;
3475187d4c6bSPetr Machata 
3476187d4c6bSPetr Machata 	err = nlmsg_parse(nlh, sizeof(struct nhmsg), tb,
3477187d4c6bSPetr Machata 			  ARRAY_SIZE(rtm_nh_policy_get_bucket) - 1,
3478187d4c6bSPetr Machata 			  rtm_nh_policy_get_bucket, extack);
3479187d4c6bSPetr Machata 	if (err < 0)
3480187d4c6bSPetr Machata 		return err;
3481187d4c6bSPetr Machata 
3482187d4c6bSPetr Machata 	err = __nh_valid_get_del_req(nlh, tb, id, extack);
3483187d4c6bSPetr Machata 	if (err)
3484187d4c6bSPetr Machata 		return err;
3485187d4c6bSPetr Machata 
3486187d4c6bSPetr Machata 	if (!tb[NHA_RES_BUCKET]) {
3487187d4c6bSPetr Machata 		NL_SET_ERR_MSG(extack, "Bucket information is missing");
3488187d4c6bSPetr Machata 		return -EINVAL;
3489187d4c6bSPetr Machata 	}
3490187d4c6bSPetr Machata 
3491187d4c6bSPetr Machata 	err = nh_valid_get_bucket_req_res_bucket(tb[NHA_RES_BUCKET],
3492187d4c6bSPetr Machata 						 bucket_index, extack);
3493187d4c6bSPetr Machata 	if (err)
3494187d4c6bSPetr Machata 		return err;
3495187d4c6bSPetr Machata 
3496187d4c6bSPetr Machata 	return 0;
3497187d4c6bSPetr Machata }
3498187d4c6bSPetr Machata 
3499187d4c6bSPetr Machata /* rtnl */
3500187d4c6bSPetr Machata static int rtm_get_nexthop_bucket(struct sk_buff *in_skb, struct nlmsghdr *nlh,
3501187d4c6bSPetr Machata 				  struct netlink_ext_ack *extack)
3502187d4c6bSPetr Machata {
3503187d4c6bSPetr Machata 	struct net *net = sock_net(in_skb->sk);
3504187d4c6bSPetr Machata 	struct nh_res_table *res_table;
3505187d4c6bSPetr Machata 	struct sk_buff *skb = NULL;
3506187d4c6bSPetr Machata 	struct nh_group *nhg;
3507187d4c6bSPetr Machata 	struct nexthop *nh;
3508187d4c6bSPetr Machata 	u16 bucket_index;
3509187d4c6bSPetr Machata 	int err;
3510187d4c6bSPetr Machata 	u32 id;
3511187d4c6bSPetr Machata 
3512187d4c6bSPetr Machata 	err = nh_valid_get_bucket_req(nlh, &id, &bucket_index, extack);
3513187d4c6bSPetr Machata 	if (err)
3514187d4c6bSPetr Machata 		return err;
3515187d4c6bSPetr Machata 
3516187d4c6bSPetr Machata 	nh = nexthop_find_group_resilient(net, id, extack);
3517187d4c6bSPetr Machata 	if (IS_ERR(nh))
3518187d4c6bSPetr Machata 		return PTR_ERR(nh);
3519187d4c6bSPetr Machata 
3520187d4c6bSPetr Machata 	nhg = rtnl_dereference(nh->nh_grp);
3521187d4c6bSPetr Machata 	res_table = rtnl_dereference(nhg->res_table);
3522187d4c6bSPetr Machata 	if (bucket_index >= res_table->num_nh_buckets) {
3523187d4c6bSPetr Machata 		NL_SET_ERR_MSG(extack, "Bucket index out of bounds");
3524187d4c6bSPetr Machata 		return -ENOENT;
3525187d4c6bSPetr Machata 	}
3526187d4c6bSPetr Machata 
3527187d4c6bSPetr Machata 	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
3528187d4c6bSPetr Machata 	if (!skb)
3529187d4c6bSPetr Machata 		return -ENOBUFS;
3530187d4c6bSPetr Machata 
3531187d4c6bSPetr Machata 	err = nh_fill_res_bucket(skb, nh, &res_table->nh_buckets[bucket_index],
3532187d4c6bSPetr Machata 				 bucket_index, RTM_NEWNEXTHOPBUCKET,
3533187d4c6bSPetr Machata 				 NETLINK_CB(in_skb).portid, nlh->nlmsg_seq,
3534187d4c6bSPetr Machata 				 0, extack);
3535187d4c6bSPetr Machata 	if (err < 0) {
3536187d4c6bSPetr Machata 		WARN_ON(err == -EMSGSIZE);
3537187d4c6bSPetr Machata 		goto errout_free;
3538187d4c6bSPetr Machata 	}
3539187d4c6bSPetr Machata 
3540187d4c6bSPetr Machata 	return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
3541187d4c6bSPetr Machata 
3542187d4c6bSPetr Machata errout_free:
3543187d4c6bSPetr Machata 	kfree_skb(skb);
3544187d4c6bSPetr Machata 	return err;
3545187d4c6bSPetr Machata }
3546187d4c6bSPetr Machata 
3547597cfe4fSDavid Ahern static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu)
3548597cfe4fSDavid Ahern {
3549597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
3550597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
3551597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
3552597cfe4fSDavid Ahern 	struct hlist_node *n;
3553597cfe4fSDavid Ahern 	struct nh_info *nhi;
3554597cfe4fSDavid Ahern 
3555597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
3556597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev == dev) {
3557597cfe4fSDavid Ahern 			if (nhi->family == AF_INET)
3558597cfe4fSDavid Ahern 				fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu,
3559597cfe4fSDavid Ahern 						   orig_mtu);
3560597cfe4fSDavid Ahern 		}
3561597cfe4fSDavid Ahern 	}
3562597cfe4fSDavid Ahern }
3563597cfe4fSDavid Ahern 
3564597cfe4fSDavid Ahern /* rtnl */
3565597cfe4fSDavid Ahern static int nh_netdev_event(struct notifier_block *this,
3566597cfe4fSDavid Ahern 			   unsigned long event, void *ptr)
3567597cfe4fSDavid Ahern {
3568597cfe4fSDavid Ahern 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
3569597cfe4fSDavid Ahern 	struct netdev_notifier_info_ext *info_ext;
3570597cfe4fSDavid Ahern 
3571597cfe4fSDavid Ahern 	switch (event) {
3572597cfe4fSDavid Ahern 	case NETDEV_DOWN:
3573597cfe4fSDavid Ahern 	case NETDEV_UNREGISTER:
357476c03bf8SIdo Schimmel 		nexthop_flush_dev(dev, event);
3575597cfe4fSDavid Ahern 		break;
3576597cfe4fSDavid Ahern 	case NETDEV_CHANGE:
3577597cfe4fSDavid Ahern 		if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP)))
357876c03bf8SIdo Schimmel 			nexthop_flush_dev(dev, event);
3579597cfe4fSDavid Ahern 		break;
3580597cfe4fSDavid Ahern 	case NETDEV_CHANGEMTU:
3581597cfe4fSDavid Ahern 		info_ext = ptr;
3582597cfe4fSDavid Ahern 		nexthop_sync_mtu(dev, info_ext->ext.mtu);
3583597cfe4fSDavid Ahern 		rt_cache_flush(dev_net(dev));
3584597cfe4fSDavid Ahern 		break;
3585597cfe4fSDavid Ahern 	}
3586597cfe4fSDavid Ahern 	return NOTIFY_DONE;
3587597cfe4fSDavid Ahern }
3588597cfe4fSDavid Ahern 
3589597cfe4fSDavid Ahern static struct notifier_block nh_netdev_notifier = {
3590597cfe4fSDavid Ahern 	.notifier_call = nh_netdev_event,
3591597cfe4fSDavid Ahern };
3592597cfe4fSDavid Ahern 
3593975ff7f3SIdo Schimmel static int nexthops_dump(struct net *net, struct notifier_block *nb,
35943106a084SIdo Schimmel 			 enum nexthop_event_type event_type,
3595975ff7f3SIdo Schimmel 			 struct netlink_ext_ack *extack)
3596975ff7f3SIdo Schimmel {
3597975ff7f3SIdo Schimmel 	struct rb_root *root = &net->nexthop.rb_root;
3598975ff7f3SIdo Schimmel 	struct rb_node *node;
3599975ff7f3SIdo Schimmel 	int err = 0;
3600975ff7f3SIdo Schimmel 
3601975ff7f3SIdo Schimmel 	for (node = rb_first(root); node; node = rb_next(node)) {
3602975ff7f3SIdo Schimmel 		struct nexthop *nh;
3603975ff7f3SIdo Schimmel 
3604975ff7f3SIdo Schimmel 		nh = rb_entry(node, struct nexthop, rb_node);
36053106a084SIdo Schimmel 		err = call_nexthop_notifier(nb, net, event_type, nh, extack);
3606975ff7f3SIdo Schimmel 		if (err)
3607975ff7f3SIdo Schimmel 			break;
3608975ff7f3SIdo Schimmel 	}
3609975ff7f3SIdo Schimmel 
3610975ff7f3SIdo Schimmel 	return err;
3611975ff7f3SIdo Schimmel }
3612975ff7f3SIdo Schimmel 
3613ce7e9c8aSIdo Schimmel int register_nexthop_notifier(struct net *net, struct notifier_block *nb,
3614ce7e9c8aSIdo Schimmel 			      struct netlink_ext_ack *extack)
36158590ceedSRoopa Prabhu {
3616975ff7f3SIdo Schimmel 	int err;
3617975ff7f3SIdo Schimmel 
3618975ff7f3SIdo Schimmel 	rtnl_lock();
36193106a084SIdo Schimmel 	err = nexthops_dump(net, nb, NEXTHOP_EVENT_REPLACE, extack);
3620975ff7f3SIdo Schimmel 	if (err)
3621975ff7f3SIdo Schimmel 		goto unlock;
3622975ff7f3SIdo Schimmel 	err = blocking_notifier_chain_register(&net->nexthop.notifier_chain,
362380690ec6SIdo Schimmel 					       nb);
3624975ff7f3SIdo Schimmel unlock:
3625975ff7f3SIdo Schimmel 	rtnl_unlock();
3626975ff7f3SIdo Schimmel 	return err;
36278590ceedSRoopa Prabhu }
36288590ceedSRoopa Prabhu EXPORT_SYMBOL(register_nexthop_notifier);
36298590ceedSRoopa Prabhu 
36308590ceedSRoopa Prabhu int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb)
36318590ceedSRoopa Prabhu {
36323106a084SIdo Schimmel 	int err;
36333106a084SIdo Schimmel 
36343106a084SIdo Schimmel 	rtnl_lock();
36353106a084SIdo Schimmel 	err = blocking_notifier_chain_unregister(&net->nexthop.notifier_chain,
36368590ceedSRoopa Prabhu 						 nb);
36373106a084SIdo Schimmel 	if (err)
36383106a084SIdo Schimmel 		goto unlock;
36393106a084SIdo Schimmel 	nexthops_dump(net, nb, NEXTHOP_EVENT_DEL, NULL);
36403106a084SIdo Schimmel unlock:
36413106a084SIdo Schimmel 	rtnl_unlock();
36423106a084SIdo Schimmel 	return err;
36438590ceedSRoopa Prabhu }
36448590ceedSRoopa Prabhu EXPORT_SYMBOL(unregister_nexthop_notifier);
36458590ceedSRoopa Prabhu 
3646e95f2592SIdo Schimmel void nexthop_set_hw_flags(struct net *net, u32 id, bool offload, bool trap)
3647e95f2592SIdo Schimmel {
3648e95f2592SIdo Schimmel 	struct nexthop *nexthop;
3649e95f2592SIdo Schimmel 
3650e95f2592SIdo Schimmel 	rcu_read_lock();
3651e95f2592SIdo Schimmel 
3652e95f2592SIdo Schimmel 	nexthop = nexthop_find_by_id(net, id);
3653e95f2592SIdo Schimmel 	if (!nexthop)
3654e95f2592SIdo Schimmel 		goto out;
3655e95f2592SIdo Schimmel 
3656e95f2592SIdo Schimmel 	nexthop->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP);
3657e95f2592SIdo Schimmel 	if (offload)
3658e95f2592SIdo Schimmel 		nexthop->nh_flags |= RTNH_F_OFFLOAD;
3659e95f2592SIdo Schimmel 	if (trap)
3660e95f2592SIdo Schimmel 		nexthop->nh_flags |= RTNH_F_TRAP;
3661e95f2592SIdo Schimmel 
3662e95f2592SIdo Schimmel out:
3663e95f2592SIdo Schimmel 	rcu_read_unlock();
3664e95f2592SIdo Schimmel }
3665e95f2592SIdo Schimmel EXPORT_SYMBOL(nexthop_set_hw_flags);
3666e95f2592SIdo Schimmel 
366756ad5ba3SIdo Schimmel void nexthop_bucket_set_hw_flags(struct net *net, u32 id, u16 bucket_index,
366856ad5ba3SIdo Schimmel 				 bool offload, bool trap)
366956ad5ba3SIdo Schimmel {
367056ad5ba3SIdo Schimmel 	struct nh_res_table *res_table;
367156ad5ba3SIdo Schimmel 	struct nh_res_bucket *bucket;
367256ad5ba3SIdo Schimmel 	struct nexthop *nexthop;
367356ad5ba3SIdo Schimmel 	struct nh_group *nhg;
367456ad5ba3SIdo Schimmel 
367556ad5ba3SIdo Schimmel 	rcu_read_lock();
367656ad5ba3SIdo Schimmel 
367756ad5ba3SIdo Schimmel 	nexthop = nexthop_find_by_id(net, id);
367856ad5ba3SIdo Schimmel 	if (!nexthop || !nexthop->is_group)
367956ad5ba3SIdo Schimmel 		goto out;
368056ad5ba3SIdo Schimmel 
368156ad5ba3SIdo Schimmel 	nhg = rcu_dereference(nexthop->nh_grp);
368256ad5ba3SIdo Schimmel 	if (!nhg->resilient)
368356ad5ba3SIdo Schimmel 		goto out;
368456ad5ba3SIdo Schimmel 
368556ad5ba3SIdo Schimmel 	if (bucket_index >= nhg->res_table->num_nh_buckets)
368656ad5ba3SIdo Schimmel 		goto out;
368756ad5ba3SIdo Schimmel 
368856ad5ba3SIdo Schimmel 	res_table = rcu_dereference(nhg->res_table);
368956ad5ba3SIdo Schimmel 	bucket = &res_table->nh_buckets[bucket_index];
369056ad5ba3SIdo Schimmel 	bucket->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP);
369156ad5ba3SIdo Schimmel 	if (offload)
369256ad5ba3SIdo Schimmel 		bucket->nh_flags |= RTNH_F_OFFLOAD;
369356ad5ba3SIdo Schimmel 	if (trap)
369456ad5ba3SIdo Schimmel 		bucket->nh_flags |= RTNH_F_TRAP;
369556ad5ba3SIdo Schimmel 
369656ad5ba3SIdo Schimmel out:
369756ad5ba3SIdo Schimmel 	rcu_read_unlock();
369856ad5ba3SIdo Schimmel }
369956ad5ba3SIdo Schimmel EXPORT_SYMBOL(nexthop_bucket_set_hw_flags);
370056ad5ba3SIdo Schimmel 
3701cfc15c1dSIdo Schimmel void nexthop_res_grp_activity_update(struct net *net, u32 id, u16 num_buckets,
3702cfc15c1dSIdo Schimmel 				     unsigned long *activity)
3703cfc15c1dSIdo Schimmel {
3704cfc15c1dSIdo Schimmel 	struct nh_res_table *res_table;
3705cfc15c1dSIdo Schimmel 	struct nexthop *nexthop;
3706cfc15c1dSIdo Schimmel 	struct nh_group *nhg;
3707cfc15c1dSIdo Schimmel 	u16 i;
3708cfc15c1dSIdo Schimmel 
3709cfc15c1dSIdo Schimmel 	rcu_read_lock();
3710cfc15c1dSIdo Schimmel 
3711cfc15c1dSIdo Schimmel 	nexthop = nexthop_find_by_id(net, id);
3712cfc15c1dSIdo Schimmel 	if (!nexthop || !nexthop->is_group)
3713cfc15c1dSIdo Schimmel 		goto out;
3714cfc15c1dSIdo Schimmel 
3715cfc15c1dSIdo Schimmel 	nhg = rcu_dereference(nexthop->nh_grp);
3716cfc15c1dSIdo Schimmel 	if (!nhg->resilient)
3717cfc15c1dSIdo Schimmel 		goto out;
3718cfc15c1dSIdo Schimmel 
3719cfc15c1dSIdo Schimmel 	/* Instead of silently ignoring some buckets, demand that the sizes
3720cfc15c1dSIdo Schimmel 	 * be the same.
3721cfc15c1dSIdo Schimmel 	 */
3722cfc15c1dSIdo Schimmel 	res_table = rcu_dereference(nhg->res_table);
3723cfc15c1dSIdo Schimmel 	if (num_buckets != res_table->num_nh_buckets)
3724cfc15c1dSIdo Schimmel 		goto out;
3725cfc15c1dSIdo Schimmel 
3726cfc15c1dSIdo Schimmel 	for (i = 0; i < num_buckets; i++) {
3727cfc15c1dSIdo Schimmel 		if (test_bit(i, activity))
3728cfc15c1dSIdo Schimmel 			nh_res_bucket_set_busy(&res_table->nh_buckets[i]);
3729cfc15c1dSIdo Schimmel 	}
3730cfc15c1dSIdo Schimmel 
3731cfc15c1dSIdo Schimmel out:
3732cfc15c1dSIdo Schimmel 	rcu_read_unlock();
3733cfc15c1dSIdo Schimmel }
3734cfc15c1dSIdo Schimmel EXPORT_SYMBOL(nexthop_res_grp_activity_update);
3735cfc15c1dSIdo Schimmel 
3736ab84be7eSDavid Ahern static void __net_exit nexthop_net_exit(struct net *net)
3737ab84be7eSDavid Ahern {
3738ab84be7eSDavid Ahern 	rtnl_lock();
3739ab84be7eSDavid Ahern 	flush_all_nexthops(net);
3740ab84be7eSDavid Ahern 	rtnl_unlock();
3741597cfe4fSDavid Ahern 	kfree(net->nexthop.devhash);
3742ab84be7eSDavid Ahern }
3743ab84be7eSDavid Ahern 
3744ab84be7eSDavid Ahern static int __net_init nexthop_net_init(struct net *net)
3745ab84be7eSDavid Ahern {
3746597cfe4fSDavid Ahern 	size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE;
3747597cfe4fSDavid Ahern 
3748ab84be7eSDavid Ahern 	net->nexthop.rb_root = RB_ROOT;
3749597cfe4fSDavid Ahern 	net->nexthop.devhash = kzalloc(sz, GFP_KERNEL);
3750597cfe4fSDavid Ahern 	if (!net->nexthop.devhash)
3751597cfe4fSDavid Ahern 		return -ENOMEM;
375280690ec6SIdo Schimmel 	BLOCKING_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain);
3753ab84be7eSDavid Ahern 
3754ab84be7eSDavid Ahern 	return 0;
3755ab84be7eSDavid Ahern }
3756ab84be7eSDavid Ahern 
3757ab84be7eSDavid Ahern static struct pernet_operations nexthop_net_ops = {
3758ab84be7eSDavid Ahern 	.init = nexthop_net_init,
3759ab84be7eSDavid Ahern 	.exit = nexthop_net_exit,
3760ab84be7eSDavid Ahern };
3761ab84be7eSDavid Ahern 
3762ab84be7eSDavid Ahern static int __init nexthop_init(void)
3763ab84be7eSDavid Ahern {
3764ab84be7eSDavid Ahern 	register_pernet_subsys(&nexthop_net_ops);
3765ab84be7eSDavid Ahern 
3766597cfe4fSDavid Ahern 	register_netdevice_notifier(&nh_netdev_notifier);
3767597cfe4fSDavid Ahern 
3768ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
3769ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0);
3770ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop,
3771ab84be7eSDavid Ahern 		      rtm_dump_nexthop, 0);
3772ab84be7eSDavid Ahern 
3773ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
3774ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
3775ab84be7eSDavid Ahern 
3776ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
3777ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
3778ab84be7eSDavid Ahern 
3779187d4c6bSPetr Machata 	rtnl_register(PF_UNSPEC, RTM_GETNEXTHOPBUCKET, rtm_get_nexthop_bucket,
37808a1bbabbSPetr Machata 		      rtm_dump_nexthop_bucket, 0);
37818a1bbabbSPetr Machata 
3782ab84be7eSDavid Ahern 	return 0;
3783ab84be7eSDavid Ahern }
3784ab84be7eSDavid Ahern subsys_initcall(nexthop_init);
3785