18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/* Generic nexthop implementation
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (c) 2017-19 Cumulus Networks
58c2ecf20Sopenharmony_ci * Copyright (c) 2017-19 David Ahern <dsa@cumulusnetworks.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/nexthop.h>
98c2ecf20Sopenharmony_ci#include <linux/rtnetlink.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <net/arp.h>
128c2ecf20Sopenharmony_ci#include <net/ipv6_stubs.h>
138c2ecf20Sopenharmony_ci#include <net/lwtunnel.h>
148c2ecf20Sopenharmony_ci#include <net/ndisc.h>
158c2ecf20Sopenharmony_ci#include <net/nexthop.h>
168c2ecf20Sopenharmony_ci#include <net/route.h>
178c2ecf20Sopenharmony_ci#include <net/sock.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic void remove_nexthop(struct net *net, struct nexthop *nh,
208c2ecf20Sopenharmony_ci			   struct nl_info *nlinfo);
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define NH_DEV_HASHBITS  8
238c2ecf20Sopenharmony_ci#define NH_DEV_HASHSIZE (1U << NH_DEV_HASHBITS)
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic const struct nla_policy rtm_nh_policy[NHA_MAX + 1] = {
268c2ecf20Sopenharmony_ci	[NHA_ID]		= { .type = NLA_U32 },
278c2ecf20Sopenharmony_ci	[NHA_GROUP]		= { .type = NLA_BINARY },
288c2ecf20Sopenharmony_ci	[NHA_GROUP_TYPE]	= { .type = NLA_U16 },
298c2ecf20Sopenharmony_ci	[NHA_BLACKHOLE]		= { .type = NLA_FLAG },
308c2ecf20Sopenharmony_ci	[NHA_OIF]		= { .type = NLA_U32 },
318c2ecf20Sopenharmony_ci	[NHA_GATEWAY]		= { .type = NLA_BINARY },
328c2ecf20Sopenharmony_ci	[NHA_ENCAP_TYPE]	= { .type = NLA_U16 },
338c2ecf20Sopenharmony_ci	[NHA_ENCAP]		= { .type = NLA_NESTED },
348c2ecf20Sopenharmony_ci	[NHA_GROUPS]		= { .type = NLA_FLAG },
358c2ecf20Sopenharmony_ci	[NHA_MASTER]		= { .type = NLA_U32 },
368c2ecf20Sopenharmony_ci	[NHA_FDB]		= { .type = NLA_FLAG },
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic int call_nexthop_notifiers(struct net *net,
408c2ecf20Sopenharmony_ci				  enum nexthop_event_type event_type,
418c2ecf20Sopenharmony_ci				  struct nexthop *nh)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	int err;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
468c2ecf20Sopenharmony_ci					   event_type, nh);
478c2ecf20Sopenharmony_ci	return notifier_to_errno(err);
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic unsigned int nh_dev_hashfn(unsigned int val)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	unsigned int mask = NH_DEV_HASHSIZE - 1;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	return (val ^
558c2ecf20Sopenharmony_ci		(val >> NH_DEV_HASHBITS) ^
568c2ecf20Sopenharmony_ci		(val >> (NH_DEV_HASHBITS * 2))) & mask;
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic void nexthop_devhash_add(struct net *net, struct nh_info *nhi)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	struct net_device *dev = nhi->fib_nhc.nhc_dev;
628c2ecf20Sopenharmony_ci	struct hlist_head *head;
638c2ecf20Sopenharmony_ci	unsigned int hash;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	WARN_ON(!dev);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	hash = nh_dev_hashfn(dev->ifindex);
688c2ecf20Sopenharmony_ci	head = &net->nexthop.devhash[hash];
698c2ecf20Sopenharmony_ci	hlist_add_head(&nhi->dev_hash, head);
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic void nexthop_free_mpath(struct nexthop *nh)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	struct nh_group *nhg;
758c2ecf20Sopenharmony_ci	int i;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	nhg = rcu_dereference_raw(nh->nh_grp);
788c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; ++i) {
798c2ecf20Sopenharmony_ci		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci		WARN_ON(!list_empty(&nhge->nh_list));
828c2ecf20Sopenharmony_ci		nexthop_put(nhge->nh);
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	WARN_ON(nhg->spare == nhg);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	kfree(nhg->spare);
888c2ecf20Sopenharmony_ci	kfree(nhg);
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic void nexthop_free_single(struct nexthop *nh)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	struct nh_info *nhi;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	nhi = rcu_dereference_raw(nh->nh_info);
968c2ecf20Sopenharmony_ci	switch (nhi->family) {
978c2ecf20Sopenharmony_ci	case AF_INET:
988c2ecf20Sopenharmony_ci		fib_nh_release(nh->net, &nhi->fib_nh);
998c2ecf20Sopenharmony_ci		break;
1008c2ecf20Sopenharmony_ci	case AF_INET6:
1018c2ecf20Sopenharmony_ci		ipv6_stub->fib6_nh_release(&nhi->fib6_nh);
1028c2ecf20Sopenharmony_ci		break;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci	kfree(nhi);
1058c2ecf20Sopenharmony_ci}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_civoid nexthop_free_rcu(struct rcu_head *head)
1088c2ecf20Sopenharmony_ci{
1098c2ecf20Sopenharmony_ci	struct nexthop *nh = container_of(head, struct nexthop, rcu);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (nh->is_group)
1128c2ecf20Sopenharmony_ci		nexthop_free_mpath(nh);
1138c2ecf20Sopenharmony_ci	else
1148c2ecf20Sopenharmony_ci		nexthop_free_single(nh);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	kfree(nh);
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nexthop_free_rcu);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic struct nexthop *nexthop_alloc(void)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	struct nexthop *nh;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
1258c2ecf20Sopenharmony_ci	if (nh) {
1268c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&nh->fi_list);
1278c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&nh->f6i_list);
1288c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&nh->grp_list);
1298c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&nh->fdb_list);
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci	return nh;
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic struct nh_group *nexthop_grp_alloc(u16 num_nh)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	struct nh_group *nhg;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL);
1398c2ecf20Sopenharmony_ci	if (nhg)
1408c2ecf20Sopenharmony_ci		nhg->num_nh = num_nh;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return nhg;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic void nh_base_seq_inc(struct net *net)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	while (++net->nexthop.seq == 0)
1488c2ecf20Sopenharmony_ci		;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci/* no reference taken; rcu lock or rtnl must be held */
1528c2ecf20Sopenharmony_cistruct nexthop *nexthop_find_by_id(struct net *net, u32 id)
1538c2ecf20Sopenharmony_ci{
1548c2ecf20Sopenharmony_ci	struct rb_node **pp, *parent = NULL, *next;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	pp = &net->nexthop.rb_root.rb_node;
1578c2ecf20Sopenharmony_ci	while (1) {
1588c2ecf20Sopenharmony_ci		struct nexthop *nh;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci		next = rcu_dereference_raw(*pp);
1618c2ecf20Sopenharmony_ci		if (!next)
1628c2ecf20Sopenharmony_ci			break;
1638c2ecf20Sopenharmony_ci		parent = next;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		nh = rb_entry(parent, struct nexthop, rb_node);
1668c2ecf20Sopenharmony_ci		if (id < nh->id)
1678c2ecf20Sopenharmony_ci			pp = &next->rb_left;
1688c2ecf20Sopenharmony_ci		else if (id > nh->id)
1698c2ecf20Sopenharmony_ci			pp = &next->rb_right;
1708c2ecf20Sopenharmony_ci		else
1718c2ecf20Sopenharmony_ci			return nh;
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci	return NULL;
1748c2ecf20Sopenharmony_ci}
1758c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nexthop_find_by_id);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci/* used for auto id allocation; called with rtnl held */
1788c2ecf20Sopenharmony_cistatic u32 nh_find_unused_id(struct net *net)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	u32 id_start = net->nexthop.last_id_allocated;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	while (1) {
1838c2ecf20Sopenharmony_ci		net->nexthop.last_id_allocated++;
1848c2ecf20Sopenharmony_ci		if (net->nexthop.last_id_allocated == id_start)
1858c2ecf20Sopenharmony_ci			break;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci		if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated))
1888c2ecf20Sopenharmony_ci			return net->nexthop.last_id_allocated;
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci	return 0;
1918c2ecf20Sopenharmony_ci}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_cistatic int nla_put_nh_group(struct sk_buff *skb, struct nh_group *nhg)
1948c2ecf20Sopenharmony_ci{
1958c2ecf20Sopenharmony_ci	struct nexthop_grp *p;
1968c2ecf20Sopenharmony_ci	size_t len = nhg->num_nh * sizeof(*p);
1978c2ecf20Sopenharmony_ci	struct nlattr *nla;
1988c2ecf20Sopenharmony_ci	u16 group_type = 0;
1998c2ecf20Sopenharmony_ci	int i;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	if (nhg->mpath)
2028c2ecf20Sopenharmony_ci		group_type = NEXTHOP_GRP_TYPE_MPATH;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type))
2058c2ecf20Sopenharmony_ci		goto nla_put_failure;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	nla = nla_reserve(skb, NHA_GROUP, len);
2088c2ecf20Sopenharmony_ci	if (!nla)
2098c2ecf20Sopenharmony_ci		goto nla_put_failure;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	p = nla_data(nla);
2128c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; ++i) {
2138c2ecf20Sopenharmony_ci		*p++ = (struct nexthop_grp) {
2148c2ecf20Sopenharmony_ci			.id = nhg->nh_entries[i].nh->id,
2158c2ecf20Sopenharmony_ci			.weight = nhg->nh_entries[i].weight - 1,
2168c2ecf20Sopenharmony_ci		};
2178c2ecf20Sopenharmony_ci	}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	return 0;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_cinla_put_failure:
2228c2ecf20Sopenharmony_ci	return -EMSGSIZE;
2238c2ecf20Sopenharmony_ci}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cistatic int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
2268c2ecf20Sopenharmony_ci			int event, u32 portid, u32 seq, unsigned int nlflags)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	struct fib6_nh *fib6_nh;
2298c2ecf20Sopenharmony_ci	struct fib_nh *fib_nh;
2308c2ecf20Sopenharmony_ci	struct nlmsghdr *nlh;
2318c2ecf20Sopenharmony_ci	struct nh_info *nhi;
2328c2ecf20Sopenharmony_ci	struct nhmsg *nhm;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
2358c2ecf20Sopenharmony_ci	if (!nlh)
2368c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	nhm = nlmsg_data(nlh);
2398c2ecf20Sopenharmony_ci	nhm->nh_family = AF_UNSPEC;
2408c2ecf20Sopenharmony_ci	nhm->nh_flags = nh->nh_flags;
2418c2ecf20Sopenharmony_ci	nhm->nh_protocol = nh->protocol;
2428c2ecf20Sopenharmony_ci	nhm->nh_scope = 0;
2438c2ecf20Sopenharmony_ci	nhm->resvd = 0;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	if (nla_put_u32(skb, NHA_ID, nh->id))
2468c2ecf20Sopenharmony_ci		goto nla_put_failure;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	if (nh->is_group) {
2498c2ecf20Sopenharmony_ci		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci		if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB))
2528c2ecf20Sopenharmony_ci			goto nla_put_failure;
2538c2ecf20Sopenharmony_ci		if (nla_put_nh_group(skb, nhg))
2548c2ecf20Sopenharmony_ci			goto nla_put_failure;
2558c2ecf20Sopenharmony_ci		goto out;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	nhi = rtnl_dereference(nh->nh_info);
2598c2ecf20Sopenharmony_ci	nhm->nh_family = nhi->family;
2608c2ecf20Sopenharmony_ci	if (nhi->reject_nh) {
2618c2ecf20Sopenharmony_ci		if (nla_put_flag(skb, NHA_BLACKHOLE))
2628c2ecf20Sopenharmony_ci			goto nla_put_failure;
2638c2ecf20Sopenharmony_ci		goto out;
2648c2ecf20Sopenharmony_ci	} else if (nhi->fdb_nh) {
2658c2ecf20Sopenharmony_ci		if (nla_put_flag(skb, NHA_FDB))
2668c2ecf20Sopenharmony_ci			goto nla_put_failure;
2678c2ecf20Sopenharmony_ci	} else {
2688c2ecf20Sopenharmony_ci		const struct net_device *dev;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci		dev = nhi->fib_nhc.nhc_dev;
2718c2ecf20Sopenharmony_ci		if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex))
2728c2ecf20Sopenharmony_ci			goto nla_put_failure;
2738c2ecf20Sopenharmony_ci	}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	nhm->nh_scope = nhi->fib_nhc.nhc_scope;
2768c2ecf20Sopenharmony_ci	switch (nhi->family) {
2778c2ecf20Sopenharmony_ci	case AF_INET:
2788c2ecf20Sopenharmony_ci		fib_nh = &nhi->fib_nh;
2798c2ecf20Sopenharmony_ci		if (fib_nh->fib_nh_gw_family &&
2808c2ecf20Sopenharmony_ci		    nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4))
2818c2ecf20Sopenharmony_ci			goto nla_put_failure;
2828c2ecf20Sopenharmony_ci		break;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	case AF_INET6:
2858c2ecf20Sopenharmony_ci		fib6_nh = &nhi->fib6_nh;
2868c2ecf20Sopenharmony_ci		if (fib6_nh->fib_nh_gw_family &&
2878c2ecf20Sopenharmony_ci		    nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6))
2888c2ecf20Sopenharmony_ci			goto nla_put_failure;
2898c2ecf20Sopenharmony_ci		break;
2908c2ecf20Sopenharmony_ci	}
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	if (nhi->fib_nhc.nhc_lwtstate &&
2938c2ecf20Sopenharmony_ci	    lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate,
2948c2ecf20Sopenharmony_ci				NHA_ENCAP, NHA_ENCAP_TYPE) < 0)
2958c2ecf20Sopenharmony_ci		goto nla_put_failure;
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ciout:
2988c2ecf20Sopenharmony_ci	nlmsg_end(skb, nlh);
2998c2ecf20Sopenharmony_ci	return 0;
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_cinla_put_failure:
3028c2ecf20Sopenharmony_ci	nlmsg_cancel(skb, nlh);
3038c2ecf20Sopenharmony_ci	return -EMSGSIZE;
3048c2ecf20Sopenharmony_ci}
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_cistatic size_t nh_nlmsg_size_grp(struct nexthop *nh)
3078c2ecf20Sopenharmony_ci{
3088c2ecf20Sopenharmony_ci	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
3098c2ecf20Sopenharmony_ci	size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh;
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	return nla_total_size(sz) +
3128c2ecf20Sopenharmony_ci	       nla_total_size(2);  /* NHA_GROUP_TYPE */
3138c2ecf20Sopenharmony_ci}
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_cistatic size_t nh_nlmsg_size_single(struct nexthop *nh)
3168c2ecf20Sopenharmony_ci{
3178c2ecf20Sopenharmony_ci	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
3188c2ecf20Sopenharmony_ci	size_t sz;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	/* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE
3218c2ecf20Sopenharmony_ci	 * are mutually exclusive
3228c2ecf20Sopenharmony_ci	 */
3238c2ecf20Sopenharmony_ci	sz = nla_total_size(4);  /* NHA_OIF */
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	switch (nhi->family) {
3268c2ecf20Sopenharmony_ci	case AF_INET:
3278c2ecf20Sopenharmony_ci		if (nhi->fib_nh.fib_nh_gw_family)
3288c2ecf20Sopenharmony_ci			sz += nla_total_size(4);  /* NHA_GATEWAY */
3298c2ecf20Sopenharmony_ci		break;
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	case AF_INET6:
3328c2ecf20Sopenharmony_ci		/* NHA_GATEWAY */
3338c2ecf20Sopenharmony_ci		if (nhi->fib6_nh.fib_nh_gw_family)
3348c2ecf20Sopenharmony_ci			sz += nla_total_size(sizeof(const struct in6_addr));
3358c2ecf20Sopenharmony_ci		break;
3368c2ecf20Sopenharmony_ci	}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	if (nhi->fib_nhc.nhc_lwtstate) {
3398c2ecf20Sopenharmony_ci		sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
3408c2ecf20Sopenharmony_ci		sz += nla_total_size(2);  /* NHA_ENCAP_TYPE */
3418c2ecf20Sopenharmony_ci	}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	return sz;
3448c2ecf20Sopenharmony_ci}
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_cistatic size_t nh_nlmsg_size(struct nexthop *nh)
3478c2ecf20Sopenharmony_ci{
3488c2ecf20Sopenharmony_ci	size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg));
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	sz += nla_total_size(4); /* NHA_ID */
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	if (nh->is_group)
3538c2ecf20Sopenharmony_ci		sz += nh_nlmsg_size_grp(nh);
3548c2ecf20Sopenharmony_ci	else
3558c2ecf20Sopenharmony_ci		sz += nh_nlmsg_size_single(nh);
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	return sz;
3588c2ecf20Sopenharmony_ci}
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_cistatic void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info)
3618c2ecf20Sopenharmony_ci{
3628c2ecf20Sopenharmony_ci	unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0;
3638c2ecf20Sopenharmony_ci	u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
3648c2ecf20Sopenharmony_ci	struct sk_buff *skb;
3658c2ecf20Sopenharmony_ci	int err = -ENOBUFS;
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci	skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any());
3688c2ecf20Sopenharmony_ci	if (!skb)
3698c2ecf20Sopenharmony_ci		goto errout;
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags);
3728c2ecf20Sopenharmony_ci	if (err < 0) {
3738c2ecf20Sopenharmony_ci		/* -EMSGSIZE implies BUG in nh_nlmsg_size() */
3748c2ecf20Sopenharmony_ci		WARN_ON(err == -EMSGSIZE);
3758c2ecf20Sopenharmony_ci		kfree_skb(skb);
3768c2ecf20Sopenharmony_ci		goto errout;
3778c2ecf20Sopenharmony_ci	}
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP,
3808c2ecf20Sopenharmony_ci		    info->nlh, gfp_any());
3818c2ecf20Sopenharmony_ci	return;
3828c2ecf20Sopenharmony_cierrout:
3838c2ecf20Sopenharmony_ci	if (err < 0)
3848c2ecf20Sopenharmony_ci		rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err);
3858c2ecf20Sopenharmony_ci}
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_cistatic bool valid_group_nh(struct nexthop *nh, unsigned int npaths,
3888c2ecf20Sopenharmony_ci			   bool *is_fdb, struct netlink_ext_ack *extack)
3898c2ecf20Sopenharmony_ci{
3908c2ecf20Sopenharmony_ci	if (nh->is_group) {
3918c2ecf20Sopenharmony_ci		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci		/* nested multipath (group within a group) is not
3948c2ecf20Sopenharmony_ci		 * supported
3958c2ecf20Sopenharmony_ci		 */
3968c2ecf20Sopenharmony_ci		if (nhg->mpath) {
3978c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack,
3988c2ecf20Sopenharmony_ci				       "Multipath group can not be a nexthop within a group");
3998c2ecf20Sopenharmony_ci			return false;
4008c2ecf20Sopenharmony_ci		}
4018c2ecf20Sopenharmony_ci		*is_fdb = nhg->fdb_nh;
4028c2ecf20Sopenharmony_ci	} else {
4038c2ecf20Sopenharmony_ci		struct nh_info *nhi = rtnl_dereference(nh->nh_info);
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci		if (nhi->reject_nh && npaths > 1) {
4068c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack,
4078c2ecf20Sopenharmony_ci				       "Blackhole nexthop can not be used in a group with more than 1 path");
4088c2ecf20Sopenharmony_ci			return false;
4098c2ecf20Sopenharmony_ci		}
4108c2ecf20Sopenharmony_ci		*is_fdb = nhi->fdb_nh;
4118c2ecf20Sopenharmony_ci	}
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	return true;
4148c2ecf20Sopenharmony_ci}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_cistatic int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family,
4178c2ecf20Sopenharmony_ci				   struct netlink_ext_ack *extack)
4188c2ecf20Sopenharmony_ci{
4198c2ecf20Sopenharmony_ci	struct nh_info *nhi;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	nhi = rtnl_dereference(nh->nh_info);
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	if (!nhi->fdb_nh) {
4248c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops");
4258c2ecf20Sopenharmony_ci		return -EINVAL;
4268c2ecf20Sopenharmony_ci	}
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	if (*nh_family == AF_UNSPEC) {
4298c2ecf20Sopenharmony_ci		*nh_family = nhi->family;
4308c2ecf20Sopenharmony_ci	} else if (*nh_family != nhi->family) {
4318c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops");
4328c2ecf20Sopenharmony_ci		return -EINVAL;
4338c2ecf20Sopenharmony_ci	}
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	return 0;
4368c2ecf20Sopenharmony_ci}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_cistatic int nh_check_attr_group(struct net *net, struct nlattr *tb[],
4398c2ecf20Sopenharmony_ci			       struct netlink_ext_ack *extack)
4408c2ecf20Sopenharmony_ci{
4418c2ecf20Sopenharmony_ci	unsigned int len = nla_len(tb[NHA_GROUP]);
4428c2ecf20Sopenharmony_ci	u8 nh_family = AF_UNSPEC;
4438c2ecf20Sopenharmony_ci	struct nexthop_grp *nhg;
4448c2ecf20Sopenharmony_ci	unsigned int i, j;
4458c2ecf20Sopenharmony_ci	u8 nhg_fdb = 0;
4468c2ecf20Sopenharmony_ci
4478c2ecf20Sopenharmony_ci	if (!len || len & (sizeof(struct nexthop_grp) - 1)) {
4488c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack,
4498c2ecf20Sopenharmony_ci			       "Invalid length for nexthop group attribute");
4508c2ecf20Sopenharmony_ci		return -EINVAL;
4518c2ecf20Sopenharmony_ci	}
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	/* convert len to number of nexthop ids */
4548c2ecf20Sopenharmony_ci	len /= sizeof(*nhg);
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	nhg = nla_data(tb[NHA_GROUP]);
4578c2ecf20Sopenharmony_ci	for (i = 0; i < len; ++i) {
4588c2ecf20Sopenharmony_ci		if (nhg[i].resvd1 || nhg[i].resvd2) {
4598c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0");
4608c2ecf20Sopenharmony_ci			return -EINVAL;
4618c2ecf20Sopenharmony_ci		}
4628c2ecf20Sopenharmony_ci		if (nhg[i].weight > 254) {
4638c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid value for weight");
4648c2ecf20Sopenharmony_ci			return -EINVAL;
4658c2ecf20Sopenharmony_ci		}
4668c2ecf20Sopenharmony_ci		for (j = i + 1; j < len; ++j) {
4678c2ecf20Sopenharmony_ci			if (nhg[i].id == nhg[j].id) {
4688c2ecf20Sopenharmony_ci				NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group");
4698c2ecf20Sopenharmony_ci				return -EINVAL;
4708c2ecf20Sopenharmony_ci			}
4718c2ecf20Sopenharmony_ci		}
4728c2ecf20Sopenharmony_ci	}
4738c2ecf20Sopenharmony_ci
4748c2ecf20Sopenharmony_ci	if (tb[NHA_FDB])
4758c2ecf20Sopenharmony_ci		nhg_fdb = 1;
4768c2ecf20Sopenharmony_ci	nhg = nla_data(tb[NHA_GROUP]);
4778c2ecf20Sopenharmony_ci	for (i = 0; i < len; ++i) {
4788c2ecf20Sopenharmony_ci		struct nexthop *nh;
4798c2ecf20Sopenharmony_ci		bool is_fdb_nh;
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci		nh = nexthop_find_by_id(net, nhg[i].id);
4828c2ecf20Sopenharmony_ci		if (!nh) {
4838c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid nexthop id");
4848c2ecf20Sopenharmony_ci			return -EINVAL;
4858c2ecf20Sopenharmony_ci		}
4868c2ecf20Sopenharmony_ci		if (!valid_group_nh(nh, len, &is_fdb_nh, extack))
4878c2ecf20Sopenharmony_ci			return -EINVAL;
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci		if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack))
4908c2ecf20Sopenharmony_ci			return -EINVAL;
4918c2ecf20Sopenharmony_ci
4928c2ecf20Sopenharmony_ci		if (!nhg_fdb && is_fdb_nh) {
4938c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops");
4948c2ecf20Sopenharmony_ci			return -EINVAL;
4958c2ecf20Sopenharmony_ci		}
4968c2ecf20Sopenharmony_ci	}
4978c2ecf20Sopenharmony_ci	for (i = NHA_GROUP_TYPE + 1; i < __NHA_MAX; ++i) {
4988c2ecf20Sopenharmony_ci		if (!tb[i])
4998c2ecf20Sopenharmony_ci			continue;
5008c2ecf20Sopenharmony_ci		if (i == NHA_FDB)
5018c2ecf20Sopenharmony_ci			continue;
5028c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack,
5038c2ecf20Sopenharmony_ci			       "No other attributes can be set in nexthop groups");
5048c2ecf20Sopenharmony_ci		return -EINVAL;
5058c2ecf20Sopenharmony_ci	}
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci	return 0;
5088c2ecf20Sopenharmony_ci}
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_cistatic bool ipv6_good_nh(const struct fib6_nh *nh)
5118c2ecf20Sopenharmony_ci{
5128c2ecf20Sopenharmony_ci	int state = NUD_REACHABLE;
5138c2ecf20Sopenharmony_ci	struct neighbour *n;
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	rcu_read_lock_bh();
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci	n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
5188c2ecf20Sopenharmony_ci	if (n)
5198c2ecf20Sopenharmony_ci		state = n->nud_state;
5208c2ecf20Sopenharmony_ci
5218c2ecf20Sopenharmony_ci	rcu_read_unlock_bh();
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci	return !!(state & NUD_VALID);
5248c2ecf20Sopenharmony_ci}
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_cistatic bool ipv4_good_nh(const struct fib_nh *nh)
5278c2ecf20Sopenharmony_ci{
5288c2ecf20Sopenharmony_ci	int state = NUD_REACHABLE;
5298c2ecf20Sopenharmony_ci	struct neighbour *n;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	rcu_read_lock_bh();
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ci	n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
5348c2ecf20Sopenharmony_ci				      (__force u32)nh->fib_nh_gw4);
5358c2ecf20Sopenharmony_ci	if (n)
5368c2ecf20Sopenharmony_ci		state = n->nud_state;
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci	rcu_read_unlock_bh();
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_ci	return !!(state & NUD_VALID);
5418c2ecf20Sopenharmony_ci}
5428c2ecf20Sopenharmony_ci
5438c2ecf20Sopenharmony_cistruct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
5448c2ecf20Sopenharmony_ci{
5458c2ecf20Sopenharmony_ci	struct nexthop *rc = NULL;
5468c2ecf20Sopenharmony_ci	struct nh_group *nhg;
5478c2ecf20Sopenharmony_ci	int i;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	if (!nh->is_group)
5508c2ecf20Sopenharmony_ci		return nh;
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	nhg = rcu_dereference(nh->nh_grp);
5538c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; ++i) {
5548c2ecf20Sopenharmony_ci		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
5558c2ecf20Sopenharmony_ci		struct nh_info *nhi;
5568c2ecf20Sopenharmony_ci
5578c2ecf20Sopenharmony_ci		if (hash > atomic_read(&nhge->upper_bound))
5588c2ecf20Sopenharmony_ci			continue;
5598c2ecf20Sopenharmony_ci
5608c2ecf20Sopenharmony_ci		nhi = rcu_dereference(nhge->nh->nh_info);
5618c2ecf20Sopenharmony_ci		if (nhi->fdb_nh)
5628c2ecf20Sopenharmony_ci			return nhge->nh;
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci		/* nexthops always check if it is good and does
5658c2ecf20Sopenharmony_ci		 * not rely on a sysctl for this behavior
5668c2ecf20Sopenharmony_ci		 */
5678c2ecf20Sopenharmony_ci		switch (nhi->family) {
5688c2ecf20Sopenharmony_ci		case AF_INET:
5698c2ecf20Sopenharmony_ci			if (ipv4_good_nh(&nhi->fib_nh))
5708c2ecf20Sopenharmony_ci				return nhge->nh;
5718c2ecf20Sopenharmony_ci			break;
5728c2ecf20Sopenharmony_ci		case AF_INET6:
5738c2ecf20Sopenharmony_ci			if (ipv6_good_nh(&nhi->fib6_nh))
5748c2ecf20Sopenharmony_ci				return nhge->nh;
5758c2ecf20Sopenharmony_ci			break;
5768c2ecf20Sopenharmony_ci		}
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci		if (!rc)
5798c2ecf20Sopenharmony_ci			rc = nhge->nh;
5808c2ecf20Sopenharmony_ci	}
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_ci	return rc;
5838c2ecf20Sopenharmony_ci}
5848c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nexthop_select_path);
5858c2ecf20Sopenharmony_ci
5868c2ecf20Sopenharmony_ciint nexthop_for_each_fib6_nh(struct nexthop *nh,
5878c2ecf20Sopenharmony_ci			     int (*cb)(struct fib6_nh *nh, void *arg),
5888c2ecf20Sopenharmony_ci			     void *arg)
5898c2ecf20Sopenharmony_ci{
5908c2ecf20Sopenharmony_ci	struct nh_info *nhi;
5918c2ecf20Sopenharmony_ci	int err;
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci	if (nh->is_group) {
5948c2ecf20Sopenharmony_ci		struct nh_group *nhg;
5958c2ecf20Sopenharmony_ci		int i;
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci		nhg = rcu_dereference_rtnl(nh->nh_grp);
5988c2ecf20Sopenharmony_ci		for (i = 0; i < nhg->num_nh; i++) {
5998c2ecf20Sopenharmony_ci			struct nh_grp_entry *nhge = &nhg->nh_entries[i];
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci			nhi = rcu_dereference_rtnl(nhge->nh->nh_info);
6028c2ecf20Sopenharmony_ci			err = cb(&nhi->fib6_nh, arg);
6038c2ecf20Sopenharmony_ci			if (err)
6048c2ecf20Sopenharmony_ci				return err;
6058c2ecf20Sopenharmony_ci		}
6068c2ecf20Sopenharmony_ci	} else {
6078c2ecf20Sopenharmony_ci		nhi = rcu_dereference_rtnl(nh->nh_info);
6088c2ecf20Sopenharmony_ci		err = cb(&nhi->fib6_nh, arg);
6098c2ecf20Sopenharmony_ci		if (err)
6108c2ecf20Sopenharmony_ci			return err;
6118c2ecf20Sopenharmony_ci	}
6128c2ecf20Sopenharmony_ci
6138c2ecf20Sopenharmony_ci	return 0;
6148c2ecf20Sopenharmony_ci}
6158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh);
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_cistatic int check_src_addr(const struct in6_addr *saddr,
6188c2ecf20Sopenharmony_ci			  struct netlink_ext_ack *extack)
6198c2ecf20Sopenharmony_ci{
6208c2ecf20Sopenharmony_ci	if (!ipv6_addr_any(saddr)) {
6218c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
6228c2ecf20Sopenharmony_ci		return -EINVAL;
6238c2ecf20Sopenharmony_ci	}
6248c2ecf20Sopenharmony_ci	return 0;
6258c2ecf20Sopenharmony_ci}
6268c2ecf20Sopenharmony_ci
6278c2ecf20Sopenharmony_ciint fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
6288c2ecf20Sopenharmony_ci		       struct netlink_ext_ack *extack)
6298c2ecf20Sopenharmony_ci{
6308c2ecf20Sopenharmony_ci	struct nh_info *nhi;
6318c2ecf20Sopenharmony_ci	bool is_fdb_nh;
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_ci	/* fib6_src is unique to a fib6_info and limits the ability to cache
6348c2ecf20Sopenharmony_ci	 * routes in fib6_nh within a nexthop that is potentially shared
6358c2ecf20Sopenharmony_ci	 * across multiple fib entries. If the config wants to use source
6368c2ecf20Sopenharmony_ci	 * routing it can not use nexthop objects. mlxsw also does not allow
6378c2ecf20Sopenharmony_ci	 * fib6_src on routes.
6388c2ecf20Sopenharmony_ci	 */
6398c2ecf20Sopenharmony_ci	if (cfg && check_src_addr(&cfg->fc_src, extack) < 0)
6408c2ecf20Sopenharmony_ci		return -EINVAL;
6418c2ecf20Sopenharmony_ci
6428c2ecf20Sopenharmony_ci	if (nh->is_group) {
6438c2ecf20Sopenharmony_ci		struct nh_group *nhg;
6448c2ecf20Sopenharmony_ci
6458c2ecf20Sopenharmony_ci		nhg = rtnl_dereference(nh->nh_grp);
6468c2ecf20Sopenharmony_ci		if (nhg->has_v4)
6478c2ecf20Sopenharmony_ci			goto no_v4_nh;
6488c2ecf20Sopenharmony_ci		is_fdb_nh = nhg->fdb_nh;
6498c2ecf20Sopenharmony_ci	} else {
6508c2ecf20Sopenharmony_ci		nhi = rtnl_dereference(nh->nh_info);
6518c2ecf20Sopenharmony_ci		if (nhi->family == AF_INET)
6528c2ecf20Sopenharmony_ci			goto no_v4_nh;
6538c2ecf20Sopenharmony_ci		is_fdb_nh = nhi->fdb_nh;
6548c2ecf20Sopenharmony_ci	}
6558c2ecf20Sopenharmony_ci
6568c2ecf20Sopenharmony_ci	if (is_fdb_nh) {
6578c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
6588c2ecf20Sopenharmony_ci		return -EINVAL;
6598c2ecf20Sopenharmony_ci	}
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ci	return 0;
6628c2ecf20Sopenharmony_cino_v4_nh:
6638c2ecf20Sopenharmony_ci	NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
6648c2ecf20Sopenharmony_ci	return -EINVAL;
6658c2ecf20Sopenharmony_ci}
6668c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(fib6_check_nexthop);
6678c2ecf20Sopenharmony_ci
6688c2ecf20Sopenharmony_ci/* if existing nexthop has ipv6 routes linked to it, need
6698c2ecf20Sopenharmony_ci * to verify this new spec works with ipv6
6708c2ecf20Sopenharmony_ci */
6718c2ecf20Sopenharmony_cistatic int fib6_check_nh_list(struct nexthop *old, struct nexthop *new,
6728c2ecf20Sopenharmony_ci			      struct netlink_ext_ack *extack)
6738c2ecf20Sopenharmony_ci{
6748c2ecf20Sopenharmony_ci	struct fib6_info *f6i;
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci	if (list_empty(&old->f6i_list))
6778c2ecf20Sopenharmony_ci		return 0;
6788c2ecf20Sopenharmony_ci
6798c2ecf20Sopenharmony_ci	list_for_each_entry(f6i, &old->f6i_list, nh_list) {
6808c2ecf20Sopenharmony_ci		if (check_src_addr(&f6i->fib6_src.addr, extack) < 0)
6818c2ecf20Sopenharmony_ci			return -EINVAL;
6828c2ecf20Sopenharmony_ci	}
6838c2ecf20Sopenharmony_ci
6848c2ecf20Sopenharmony_ci	return fib6_check_nexthop(new, NULL, extack);
6858c2ecf20Sopenharmony_ci}
6868c2ecf20Sopenharmony_ci
6878c2ecf20Sopenharmony_cistatic int nexthop_check_scope(struct nh_info *nhi, u8 scope,
6888c2ecf20Sopenharmony_ci			       struct netlink_ext_ack *extack)
6898c2ecf20Sopenharmony_ci{
6908c2ecf20Sopenharmony_ci	if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) {
6918c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack,
6928c2ecf20Sopenharmony_ci			       "Route with host scope can not have a gateway");
6938c2ecf20Sopenharmony_ci		return -EINVAL;
6948c2ecf20Sopenharmony_ci	}
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci	if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) {
6978c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop");
6988c2ecf20Sopenharmony_ci		return -EINVAL;
6998c2ecf20Sopenharmony_ci	}
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_ci	return 0;
7028c2ecf20Sopenharmony_ci}
7038c2ecf20Sopenharmony_ci
7048c2ecf20Sopenharmony_ci/* Invoked by fib add code to verify nexthop by id is ok with
7058c2ecf20Sopenharmony_ci * config for prefix; parts of fib_check_nh not done when nexthop
7068c2ecf20Sopenharmony_ci * object is used.
7078c2ecf20Sopenharmony_ci */
7088c2ecf20Sopenharmony_ciint fib_check_nexthop(struct nexthop *nh, u8 scope,
7098c2ecf20Sopenharmony_ci		      struct netlink_ext_ack *extack)
7108c2ecf20Sopenharmony_ci{
7118c2ecf20Sopenharmony_ci	struct nh_info *nhi;
7128c2ecf20Sopenharmony_ci	int err = 0;
7138c2ecf20Sopenharmony_ci
7148c2ecf20Sopenharmony_ci	if (nh->is_group) {
7158c2ecf20Sopenharmony_ci		struct nh_group *nhg;
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci		nhg = rtnl_dereference(nh->nh_grp);
7188c2ecf20Sopenharmony_ci		if (nhg->fdb_nh) {
7198c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
7208c2ecf20Sopenharmony_ci			err = -EINVAL;
7218c2ecf20Sopenharmony_ci			goto out;
7228c2ecf20Sopenharmony_ci		}
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci		if (scope == RT_SCOPE_HOST) {
7258c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops");
7268c2ecf20Sopenharmony_ci			err = -EINVAL;
7278c2ecf20Sopenharmony_ci			goto out;
7288c2ecf20Sopenharmony_ci		}
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci		/* all nexthops in a group have the same scope */
7318c2ecf20Sopenharmony_ci		nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info);
7328c2ecf20Sopenharmony_ci		err = nexthop_check_scope(nhi, scope, extack);
7338c2ecf20Sopenharmony_ci	} else {
7348c2ecf20Sopenharmony_ci		nhi = rtnl_dereference(nh->nh_info);
7358c2ecf20Sopenharmony_ci		if (nhi->fdb_nh) {
7368c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
7378c2ecf20Sopenharmony_ci			err = -EINVAL;
7388c2ecf20Sopenharmony_ci			goto out;
7398c2ecf20Sopenharmony_ci		}
7408c2ecf20Sopenharmony_ci		err = nexthop_check_scope(nhi, scope, extack);
7418c2ecf20Sopenharmony_ci	}
7428c2ecf20Sopenharmony_ci
7438c2ecf20Sopenharmony_ciout:
7448c2ecf20Sopenharmony_ci	return err;
7458c2ecf20Sopenharmony_ci}
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_cistatic int fib_check_nh_list(struct nexthop *old, struct nexthop *new,
7488c2ecf20Sopenharmony_ci			     struct netlink_ext_ack *extack)
7498c2ecf20Sopenharmony_ci{
7508c2ecf20Sopenharmony_ci	struct fib_info *fi;
7518c2ecf20Sopenharmony_ci
7528c2ecf20Sopenharmony_ci	list_for_each_entry(fi, &old->fi_list, nh_list) {
7538c2ecf20Sopenharmony_ci		int err;
7548c2ecf20Sopenharmony_ci
7558c2ecf20Sopenharmony_ci		err = fib_check_nexthop(new, fi->fib_scope, extack);
7568c2ecf20Sopenharmony_ci		if (err)
7578c2ecf20Sopenharmony_ci			return err;
7588c2ecf20Sopenharmony_ci	}
7598c2ecf20Sopenharmony_ci	return 0;
7608c2ecf20Sopenharmony_ci}
7618c2ecf20Sopenharmony_ci
7628c2ecf20Sopenharmony_cistatic void nh_group_rebalance(struct nh_group *nhg)
7638c2ecf20Sopenharmony_ci{
7648c2ecf20Sopenharmony_ci	int total = 0;
7658c2ecf20Sopenharmony_ci	int w = 0;
7668c2ecf20Sopenharmony_ci	int i;
7678c2ecf20Sopenharmony_ci
7688c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; ++i)
7698c2ecf20Sopenharmony_ci		total += nhg->nh_entries[i].weight;
7708c2ecf20Sopenharmony_ci
7718c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; ++i) {
7728c2ecf20Sopenharmony_ci		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
7738c2ecf20Sopenharmony_ci		int upper_bound;
7748c2ecf20Sopenharmony_ci
7758c2ecf20Sopenharmony_ci		w += nhge->weight;
7768c2ecf20Sopenharmony_ci		upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1;
7778c2ecf20Sopenharmony_ci		atomic_set(&nhge->upper_bound, upper_bound);
7788c2ecf20Sopenharmony_ci	}
7798c2ecf20Sopenharmony_ci}
7808c2ecf20Sopenharmony_ci
7818c2ecf20Sopenharmony_cistatic void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge,
7828c2ecf20Sopenharmony_ci				struct nl_info *nlinfo)
7838c2ecf20Sopenharmony_ci{
7848c2ecf20Sopenharmony_ci	struct nh_grp_entry *nhges, *new_nhges;
7858c2ecf20Sopenharmony_ci	struct nexthop *nhp = nhge->nh_parent;
7868c2ecf20Sopenharmony_ci	struct nexthop *nh = nhge->nh;
7878c2ecf20Sopenharmony_ci	struct nh_group *nhg, *newg;
7888c2ecf20Sopenharmony_ci	int i, j;
7898c2ecf20Sopenharmony_ci
7908c2ecf20Sopenharmony_ci	WARN_ON(!nh);
7918c2ecf20Sopenharmony_ci
7928c2ecf20Sopenharmony_ci	nhg = rtnl_dereference(nhp->nh_grp);
7938c2ecf20Sopenharmony_ci	newg = nhg->spare;
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_ci	/* last entry, keep it visible and remove the parent */
7968c2ecf20Sopenharmony_ci	if (nhg->num_nh == 1) {
7978c2ecf20Sopenharmony_ci		remove_nexthop(net, nhp, nlinfo);
7988c2ecf20Sopenharmony_ci		return;
7998c2ecf20Sopenharmony_ci	}
8008c2ecf20Sopenharmony_ci
8018c2ecf20Sopenharmony_ci	newg->has_v4 = false;
8028c2ecf20Sopenharmony_ci	newg->mpath = nhg->mpath;
8038c2ecf20Sopenharmony_ci	newg->fdb_nh = nhg->fdb_nh;
8048c2ecf20Sopenharmony_ci	newg->num_nh = nhg->num_nh;
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_ci	/* copy old entries to new except the one getting removed */
8078c2ecf20Sopenharmony_ci	nhges = nhg->nh_entries;
8088c2ecf20Sopenharmony_ci	new_nhges = newg->nh_entries;
8098c2ecf20Sopenharmony_ci	for (i = 0, j = 0; i < nhg->num_nh; ++i) {
8108c2ecf20Sopenharmony_ci		struct nh_info *nhi;
8118c2ecf20Sopenharmony_ci
8128c2ecf20Sopenharmony_ci		/* current nexthop getting removed */
8138c2ecf20Sopenharmony_ci		if (nhg->nh_entries[i].nh == nh) {
8148c2ecf20Sopenharmony_ci			newg->num_nh--;
8158c2ecf20Sopenharmony_ci			continue;
8168c2ecf20Sopenharmony_ci		}
8178c2ecf20Sopenharmony_ci
8188c2ecf20Sopenharmony_ci		nhi = rtnl_dereference(nhges[i].nh->nh_info);
8198c2ecf20Sopenharmony_ci		if (nhi->family == AF_INET)
8208c2ecf20Sopenharmony_ci			newg->has_v4 = true;
8218c2ecf20Sopenharmony_ci
8228c2ecf20Sopenharmony_ci		list_del(&nhges[i].nh_list);
8238c2ecf20Sopenharmony_ci		new_nhges[j].nh_parent = nhges[i].nh_parent;
8248c2ecf20Sopenharmony_ci		new_nhges[j].nh = nhges[i].nh;
8258c2ecf20Sopenharmony_ci		new_nhges[j].weight = nhges[i].weight;
8268c2ecf20Sopenharmony_ci		list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list);
8278c2ecf20Sopenharmony_ci		j++;
8288c2ecf20Sopenharmony_ci	}
8298c2ecf20Sopenharmony_ci
8308c2ecf20Sopenharmony_ci	nh_group_rebalance(newg);
8318c2ecf20Sopenharmony_ci	rcu_assign_pointer(nhp->nh_grp, newg);
8328c2ecf20Sopenharmony_ci
8338c2ecf20Sopenharmony_ci	list_del(&nhge->nh_list);
8348c2ecf20Sopenharmony_ci	nexthop_put(nhge->nh);
8358c2ecf20Sopenharmony_ci
8368c2ecf20Sopenharmony_ci	if (nlinfo)
8378c2ecf20Sopenharmony_ci		nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo);
8388c2ecf20Sopenharmony_ci}
8398c2ecf20Sopenharmony_ci
8408c2ecf20Sopenharmony_cistatic void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
8418c2ecf20Sopenharmony_ci				       struct nl_info *nlinfo)
8428c2ecf20Sopenharmony_ci{
8438c2ecf20Sopenharmony_ci	struct nh_grp_entry *nhge, *tmp;
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_ci	list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list)
8468c2ecf20Sopenharmony_ci		remove_nh_grp_entry(net, nhge, nlinfo);
8478c2ecf20Sopenharmony_ci
8488c2ecf20Sopenharmony_ci	/* make sure all see the newly published array before releasing rtnl */
8498c2ecf20Sopenharmony_ci	synchronize_net();
8508c2ecf20Sopenharmony_ci}
8518c2ecf20Sopenharmony_ci
8528c2ecf20Sopenharmony_cistatic void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
8538c2ecf20Sopenharmony_ci{
8548c2ecf20Sopenharmony_ci	struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp);
8558c2ecf20Sopenharmony_ci	int i, num_nh = nhg->num_nh;
8568c2ecf20Sopenharmony_ci
8578c2ecf20Sopenharmony_ci	for (i = 0; i < num_nh; ++i) {
8588c2ecf20Sopenharmony_ci		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci		if (WARN_ON(!nhge->nh))
8618c2ecf20Sopenharmony_ci			continue;
8628c2ecf20Sopenharmony_ci
8638c2ecf20Sopenharmony_ci		list_del_init(&nhge->nh_list);
8648c2ecf20Sopenharmony_ci	}
8658c2ecf20Sopenharmony_ci}
8668c2ecf20Sopenharmony_ci
8678c2ecf20Sopenharmony_ci/* not called for nexthop replace */
8688c2ecf20Sopenharmony_cistatic void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
8698c2ecf20Sopenharmony_ci{
8708c2ecf20Sopenharmony_ci	struct fib6_info *f6i, *tmp;
8718c2ecf20Sopenharmony_ci	bool do_flush = false;
8728c2ecf20Sopenharmony_ci	struct fib_info *fi;
8738c2ecf20Sopenharmony_ci
8748c2ecf20Sopenharmony_ci	list_for_each_entry(fi, &nh->fi_list, nh_list) {
8758c2ecf20Sopenharmony_ci		fi->fib_flags |= RTNH_F_DEAD;
8768c2ecf20Sopenharmony_ci		do_flush = true;
8778c2ecf20Sopenharmony_ci	}
8788c2ecf20Sopenharmony_ci	if (do_flush)
8798c2ecf20Sopenharmony_ci		fib_flush(net);
8808c2ecf20Sopenharmony_ci
8818c2ecf20Sopenharmony_ci	/* ip6_del_rt removes the entry from this list hence the _safe */
8828c2ecf20Sopenharmony_ci	list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
8838c2ecf20Sopenharmony_ci		/* __ip6_del_rt does a release, so do a hold here */
8848c2ecf20Sopenharmony_ci		fib6_info_hold(f6i);
8858c2ecf20Sopenharmony_ci		ipv6_stub->ip6_del_rt(net, f6i,
8868c2ecf20Sopenharmony_ci				      !READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode));
8878c2ecf20Sopenharmony_ci	}
8888c2ecf20Sopenharmony_ci}
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_cistatic void __remove_nexthop(struct net *net, struct nexthop *nh,
8918c2ecf20Sopenharmony_ci			     struct nl_info *nlinfo)
8928c2ecf20Sopenharmony_ci{
8938c2ecf20Sopenharmony_ci	__remove_nexthop_fib(net, nh);
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_ci	if (nh->is_group) {
8968c2ecf20Sopenharmony_ci		remove_nexthop_group(nh, nlinfo);
8978c2ecf20Sopenharmony_ci	} else {
8988c2ecf20Sopenharmony_ci		struct nh_info *nhi;
8998c2ecf20Sopenharmony_ci
9008c2ecf20Sopenharmony_ci		nhi = rtnl_dereference(nh->nh_info);
9018c2ecf20Sopenharmony_ci		if (nhi->fib_nhc.nhc_dev)
9028c2ecf20Sopenharmony_ci			hlist_del(&nhi->dev_hash);
9038c2ecf20Sopenharmony_ci
9048c2ecf20Sopenharmony_ci		remove_nexthop_from_groups(net, nh, nlinfo);
9058c2ecf20Sopenharmony_ci	}
9068c2ecf20Sopenharmony_ci}
9078c2ecf20Sopenharmony_ci
9088c2ecf20Sopenharmony_cistatic void remove_nexthop(struct net *net, struct nexthop *nh,
9098c2ecf20Sopenharmony_ci			   struct nl_info *nlinfo)
9108c2ecf20Sopenharmony_ci{
9118c2ecf20Sopenharmony_ci	call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh);
9128c2ecf20Sopenharmony_ci
9138c2ecf20Sopenharmony_ci	/* remove from the tree */
9148c2ecf20Sopenharmony_ci	rb_erase(&nh->rb_node, &net->nexthop.rb_root);
9158c2ecf20Sopenharmony_ci
9168c2ecf20Sopenharmony_ci	if (nlinfo)
9178c2ecf20Sopenharmony_ci		nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo);
9188c2ecf20Sopenharmony_ci
9198c2ecf20Sopenharmony_ci	__remove_nexthop(net, nh, nlinfo);
9208c2ecf20Sopenharmony_ci	nh_base_seq_inc(net);
9218c2ecf20Sopenharmony_ci
9228c2ecf20Sopenharmony_ci	nexthop_put(nh);
9238c2ecf20Sopenharmony_ci}
9248c2ecf20Sopenharmony_ci
9258c2ecf20Sopenharmony_ci/* if any FIB entries reference this nexthop, any dst entries
9268c2ecf20Sopenharmony_ci * need to be regenerated
9278c2ecf20Sopenharmony_ci */
9288c2ecf20Sopenharmony_cistatic void nh_rt_cache_flush(struct net *net, struct nexthop *nh,
9298c2ecf20Sopenharmony_ci			      struct nexthop *replaced_nh)
9308c2ecf20Sopenharmony_ci{
9318c2ecf20Sopenharmony_ci	struct fib6_info *f6i;
9328c2ecf20Sopenharmony_ci	struct nh_group *nhg;
9338c2ecf20Sopenharmony_ci	int i;
9348c2ecf20Sopenharmony_ci
9358c2ecf20Sopenharmony_ci	if (!list_empty(&nh->fi_list))
9368c2ecf20Sopenharmony_ci		rt_cache_flush(net);
9378c2ecf20Sopenharmony_ci
9388c2ecf20Sopenharmony_ci	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
9398c2ecf20Sopenharmony_ci		ipv6_stub->fib6_update_sernum(net, f6i);
9408c2ecf20Sopenharmony_ci
9418c2ecf20Sopenharmony_ci	/* if an IPv6 group was replaced, we have to release all old
9428c2ecf20Sopenharmony_ci	 * dsts to make sure all refcounts are released
9438c2ecf20Sopenharmony_ci	 */
9448c2ecf20Sopenharmony_ci	if (!replaced_nh->is_group)
9458c2ecf20Sopenharmony_ci		return;
9468c2ecf20Sopenharmony_ci
9478c2ecf20Sopenharmony_ci	/* new dsts must use only the new nexthop group */
9488c2ecf20Sopenharmony_ci	synchronize_net();
9498c2ecf20Sopenharmony_ci
9508c2ecf20Sopenharmony_ci	nhg = rtnl_dereference(replaced_nh->nh_grp);
9518c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; i++) {
9528c2ecf20Sopenharmony_ci		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
9538c2ecf20Sopenharmony_ci		struct nh_info *nhi = rtnl_dereference(nhge->nh->nh_info);
9548c2ecf20Sopenharmony_ci
9558c2ecf20Sopenharmony_ci		if (nhi->family == AF_INET6)
9568c2ecf20Sopenharmony_ci			ipv6_stub->fib6_nh_release_dsts(&nhi->fib6_nh);
9578c2ecf20Sopenharmony_ci	}
9588c2ecf20Sopenharmony_ci}
9598c2ecf20Sopenharmony_ci
9608c2ecf20Sopenharmony_cistatic int replace_nexthop_grp(struct net *net, struct nexthop *old,
9618c2ecf20Sopenharmony_ci			       struct nexthop *new,
9628c2ecf20Sopenharmony_ci			       struct netlink_ext_ack *extack)
9638c2ecf20Sopenharmony_ci{
9648c2ecf20Sopenharmony_ci	struct nh_group *oldg, *newg;
9658c2ecf20Sopenharmony_ci	int i;
9668c2ecf20Sopenharmony_ci
9678c2ecf20Sopenharmony_ci	if (!new->is_group) {
9688c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop.");
9698c2ecf20Sopenharmony_ci		return -EINVAL;
9708c2ecf20Sopenharmony_ci	}
9718c2ecf20Sopenharmony_ci
9728c2ecf20Sopenharmony_ci	oldg = rtnl_dereference(old->nh_grp);
9738c2ecf20Sopenharmony_ci	newg = rtnl_dereference(new->nh_grp);
9748c2ecf20Sopenharmony_ci
9758c2ecf20Sopenharmony_ci	/* update parents - used by nexthop code for cleanup */
9768c2ecf20Sopenharmony_ci	for (i = 0; i < newg->num_nh; i++)
9778c2ecf20Sopenharmony_ci		newg->nh_entries[i].nh_parent = old;
9788c2ecf20Sopenharmony_ci
9798c2ecf20Sopenharmony_ci	rcu_assign_pointer(old->nh_grp, newg);
9808c2ecf20Sopenharmony_ci
9818c2ecf20Sopenharmony_ci	for (i = 0; i < oldg->num_nh; i++)
9828c2ecf20Sopenharmony_ci		oldg->nh_entries[i].nh_parent = new;
9838c2ecf20Sopenharmony_ci
9848c2ecf20Sopenharmony_ci	rcu_assign_pointer(new->nh_grp, oldg);
9858c2ecf20Sopenharmony_ci
9868c2ecf20Sopenharmony_ci	return 0;
9878c2ecf20Sopenharmony_ci}
9888c2ecf20Sopenharmony_ci
9898c2ecf20Sopenharmony_cistatic void nh_group_v4_update(struct nh_group *nhg)
9908c2ecf20Sopenharmony_ci{
9918c2ecf20Sopenharmony_ci	struct nh_grp_entry *nhges;
9928c2ecf20Sopenharmony_ci	bool has_v4 = false;
9938c2ecf20Sopenharmony_ci	int i;
9948c2ecf20Sopenharmony_ci
9958c2ecf20Sopenharmony_ci	nhges = nhg->nh_entries;
9968c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; i++) {
9978c2ecf20Sopenharmony_ci		struct nh_info *nhi;
9988c2ecf20Sopenharmony_ci
9998c2ecf20Sopenharmony_ci		nhi = rtnl_dereference(nhges[i].nh->nh_info);
10008c2ecf20Sopenharmony_ci		if (nhi->family == AF_INET)
10018c2ecf20Sopenharmony_ci			has_v4 = true;
10028c2ecf20Sopenharmony_ci	}
10038c2ecf20Sopenharmony_ci	nhg->has_v4 = has_v4;
10048c2ecf20Sopenharmony_ci}
10058c2ecf20Sopenharmony_ci
10068c2ecf20Sopenharmony_cistatic int replace_nexthop_single(struct net *net, struct nexthop *old,
10078c2ecf20Sopenharmony_ci				  struct nexthop *new,
10088c2ecf20Sopenharmony_ci				  struct netlink_ext_ack *extack)
10098c2ecf20Sopenharmony_ci{
10108c2ecf20Sopenharmony_ci	struct nh_info *oldi, *newi;
10118c2ecf20Sopenharmony_ci
10128c2ecf20Sopenharmony_ci	if (new->is_group) {
10138c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group.");
10148c2ecf20Sopenharmony_ci		return -EINVAL;
10158c2ecf20Sopenharmony_ci	}
10168c2ecf20Sopenharmony_ci
10178c2ecf20Sopenharmony_ci	oldi = rtnl_dereference(old->nh_info);
10188c2ecf20Sopenharmony_ci	newi = rtnl_dereference(new->nh_info);
10198c2ecf20Sopenharmony_ci
10208c2ecf20Sopenharmony_ci	newi->nh_parent = old;
10218c2ecf20Sopenharmony_ci	oldi->nh_parent = new;
10228c2ecf20Sopenharmony_ci
10238c2ecf20Sopenharmony_ci	old->protocol = new->protocol;
10248c2ecf20Sopenharmony_ci	old->nh_flags = new->nh_flags;
10258c2ecf20Sopenharmony_ci
10268c2ecf20Sopenharmony_ci	rcu_assign_pointer(old->nh_info, newi);
10278c2ecf20Sopenharmony_ci	rcu_assign_pointer(new->nh_info, oldi);
10288c2ecf20Sopenharmony_ci
10298c2ecf20Sopenharmony_ci	/* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially
10308c2ecf20Sopenharmony_ci	 * update IPv4 indication in all the groups using the nexthop.
10318c2ecf20Sopenharmony_ci	 */
10328c2ecf20Sopenharmony_ci	if (oldi->family == AF_INET && newi->family == AF_INET6) {
10338c2ecf20Sopenharmony_ci		struct nh_grp_entry *nhge;
10348c2ecf20Sopenharmony_ci
10358c2ecf20Sopenharmony_ci		list_for_each_entry(nhge, &old->grp_list, nh_list) {
10368c2ecf20Sopenharmony_ci			struct nexthop *nhp = nhge->nh_parent;
10378c2ecf20Sopenharmony_ci			struct nh_group *nhg;
10388c2ecf20Sopenharmony_ci
10398c2ecf20Sopenharmony_ci			nhg = rtnl_dereference(nhp->nh_grp);
10408c2ecf20Sopenharmony_ci			nh_group_v4_update(nhg);
10418c2ecf20Sopenharmony_ci		}
10428c2ecf20Sopenharmony_ci	}
10438c2ecf20Sopenharmony_ci
10448c2ecf20Sopenharmony_ci	return 0;
10458c2ecf20Sopenharmony_ci}
10468c2ecf20Sopenharmony_ci
10478c2ecf20Sopenharmony_cistatic void __nexthop_replace_notify(struct net *net, struct nexthop *nh,
10488c2ecf20Sopenharmony_ci				     struct nl_info *info)
10498c2ecf20Sopenharmony_ci{
10508c2ecf20Sopenharmony_ci	struct fib6_info *f6i;
10518c2ecf20Sopenharmony_ci
10528c2ecf20Sopenharmony_ci	if (!list_empty(&nh->fi_list)) {
10538c2ecf20Sopenharmony_ci		struct fib_info *fi;
10548c2ecf20Sopenharmony_ci
10558c2ecf20Sopenharmony_ci		/* expectation is a few fib_info per nexthop and then
10568c2ecf20Sopenharmony_ci		 * a lot of routes per fib_info. So mark the fib_info
10578c2ecf20Sopenharmony_ci		 * and then walk the fib tables once
10588c2ecf20Sopenharmony_ci		 */
10598c2ecf20Sopenharmony_ci		list_for_each_entry(fi, &nh->fi_list, nh_list)
10608c2ecf20Sopenharmony_ci			fi->nh_updated = true;
10618c2ecf20Sopenharmony_ci
10628c2ecf20Sopenharmony_ci		fib_info_notify_update(net, info);
10638c2ecf20Sopenharmony_ci
10648c2ecf20Sopenharmony_ci		list_for_each_entry(fi, &nh->fi_list, nh_list)
10658c2ecf20Sopenharmony_ci			fi->nh_updated = false;
10668c2ecf20Sopenharmony_ci	}
10678c2ecf20Sopenharmony_ci
10688c2ecf20Sopenharmony_ci	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
10698c2ecf20Sopenharmony_ci		ipv6_stub->fib6_rt_update(net, f6i, info);
10708c2ecf20Sopenharmony_ci}
10718c2ecf20Sopenharmony_ci
10728c2ecf20Sopenharmony_ci/* send RTM_NEWROUTE with REPLACE flag set for all FIB entries
10738c2ecf20Sopenharmony_ci * linked to this nexthop and for all groups that the nexthop
10748c2ecf20Sopenharmony_ci * is a member of
10758c2ecf20Sopenharmony_ci */
10768c2ecf20Sopenharmony_cistatic void nexthop_replace_notify(struct net *net, struct nexthop *nh,
10778c2ecf20Sopenharmony_ci				   struct nl_info *info)
10788c2ecf20Sopenharmony_ci{
10798c2ecf20Sopenharmony_ci	struct nh_grp_entry *nhge;
10808c2ecf20Sopenharmony_ci
10818c2ecf20Sopenharmony_ci	__nexthop_replace_notify(net, nh, info);
10828c2ecf20Sopenharmony_ci
10838c2ecf20Sopenharmony_ci	list_for_each_entry(nhge, &nh->grp_list, nh_list)
10848c2ecf20Sopenharmony_ci		__nexthop_replace_notify(net, nhge->nh_parent, info);
10858c2ecf20Sopenharmony_ci}
10868c2ecf20Sopenharmony_ci
10878c2ecf20Sopenharmony_cistatic int replace_nexthop(struct net *net, struct nexthop *old,
10888c2ecf20Sopenharmony_ci			   struct nexthop *new, struct netlink_ext_ack *extack)
10898c2ecf20Sopenharmony_ci{
10908c2ecf20Sopenharmony_ci	bool new_is_reject = false;
10918c2ecf20Sopenharmony_ci	struct nh_grp_entry *nhge;
10928c2ecf20Sopenharmony_ci	int err;
10938c2ecf20Sopenharmony_ci
10948c2ecf20Sopenharmony_ci	/* check that existing FIB entries are ok with the
10958c2ecf20Sopenharmony_ci	 * new nexthop definition
10968c2ecf20Sopenharmony_ci	 */
10978c2ecf20Sopenharmony_ci	err = fib_check_nh_list(old, new, extack);
10988c2ecf20Sopenharmony_ci	if (err)
10998c2ecf20Sopenharmony_ci		return err;
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_ci	err = fib6_check_nh_list(old, new, extack);
11028c2ecf20Sopenharmony_ci	if (err)
11038c2ecf20Sopenharmony_ci		return err;
11048c2ecf20Sopenharmony_ci
11058c2ecf20Sopenharmony_ci	if (!new->is_group) {
11068c2ecf20Sopenharmony_ci		struct nh_info *nhi = rtnl_dereference(new->nh_info);
11078c2ecf20Sopenharmony_ci
11088c2ecf20Sopenharmony_ci		new_is_reject = nhi->reject_nh;
11098c2ecf20Sopenharmony_ci	}
11108c2ecf20Sopenharmony_ci
11118c2ecf20Sopenharmony_ci	list_for_each_entry(nhge, &old->grp_list, nh_list) {
11128c2ecf20Sopenharmony_ci		/* if new nexthop is a blackhole, any groups using this
11138c2ecf20Sopenharmony_ci		 * nexthop cannot have more than 1 path
11148c2ecf20Sopenharmony_ci		 */
11158c2ecf20Sopenharmony_ci		if (new_is_reject &&
11168c2ecf20Sopenharmony_ci		    nexthop_num_path(nhge->nh_parent) > 1) {
11178c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path");
11188c2ecf20Sopenharmony_ci			return -EINVAL;
11198c2ecf20Sopenharmony_ci		}
11208c2ecf20Sopenharmony_ci
11218c2ecf20Sopenharmony_ci		err = fib_check_nh_list(nhge->nh_parent, new, extack);
11228c2ecf20Sopenharmony_ci		if (err)
11238c2ecf20Sopenharmony_ci			return err;
11248c2ecf20Sopenharmony_ci
11258c2ecf20Sopenharmony_ci		err = fib6_check_nh_list(nhge->nh_parent, new, extack);
11268c2ecf20Sopenharmony_ci		if (err)
11278c2ecf20Sopenharmony_ci			return err;
11288c2ecf20Sopenharmony_ci	}
11298c2ecf20Sopenharmony_ci
11308c2ecf20Sopenharmony_ci	if (old->is_group)
11318c2ecf20Sopenharmony_ci		err = replace_nexthop_grp(net, old, new, extack);
11328c2ecf20Sopenharmony_ci	else
11338c2ecf20Sopenharmony_ci		err = replace_nexthop_single(net, old, new, extack);
11348c2ecf20Sopenharmony_ci
11358c2ecf20Sopenharmony_ci	if (!err) {
11368c2ecf20Sopenharmony_ci		nh_rt_cache_flush(net, old, new);
11378c2ecf20Sopenharmony_ci
11388c2ecf20Sopenharmony_ci		__remove_nexthop(net, new, NULL);
11398c2ecf20Sopenharmony_ci		nexthop_put(new);
11408c2ecf20Sopenharmony_ci	}
11418c2ecf20Sopenharmony_ci
11428c2ecf20Sopenharmony_ci	return err;
11438c2ecf20Sopenharmony_ci}
11448c2ecf20Sopenharmony_ci
11458c2ecf20Sopenharmony_ci/* called with rtnl_lock held */
11468c2ecf20Sopenharmony_cistatic int insert_nexthop(struct net *net, struct nexthop *new_nh,
11478c2ecf20Sopenharmony_ci			  struct nh_config *cfg, struct netlink_ext_ack *extack)
11488c2ecf20Sopenharmony_ci{
11498c2ecf20Sopenharmony_ci	struct rb_node **pp, *parent = NULL, *next;
11508c2ecf20Sopenharmony_ci	struct rb_root *root = &net->nexthop.rb_root;
11518c2ecf20Sopenharmony_ci	bool replace = !!(cfg->nlflags & NLM_F_REPLACE);
11528c2ecf20Sopenharmony_ci	bool create = !!(cfg->nlflags & NLM_F_CREATE);
11538c2ecf20Sopenharmony_ci	u32 new_id = new_nh->id;
11548c2ecf20Sopenharmony_ci	int replace_notify = 0;
11558c2ecf20Sopenharmony_ci	int rc = -EEXIST;
11568c2ecf20Sopenharmony_ci
11578c2ecf20Sopenharmony_ci	pp = &root->rb_node;
11588c2ecf20Sopenharmony_ci	while (1) {
11598c2ecf20Sopenharmony_ci		struct nexthop *nh;
11608c2ecf20Sopenharmony_ci
11618c2ecf20Sopenharmony_ci		next = *pp;
11628c2ecf20Sopenharmony_ci		if (!next)
11638c2ecf20Sopenharmony_ci			break;
11648c2ecf20Sopenharmony_ci
11658c2ecf20Sopenharmony_ci		parent = next;
11668c2ecf20Sopenharmony_ci
11678c2ecf20Sopenharmony_ci		nh = rb_entry(parent, struct nexthop, rb_node);
11688c2ecf20Sopenharmony_ci		if (new_id < nh->id) {
11698c2ecf20Sopenharmony_ci			pp = &next->rb_left;
11708c2ecf20Sopenharmony_ci		} else if (new_id > nh->id) {
11718c2ecf20Sopenharmony_ci			pp = &next->rb_right;
11728c2ecf20Sopenharmony_ci		} else if (replace) {
11738c2ecf20Sopenharmony_ci			rc = replace_nexthop(net, nh, new_nh, extack);
11748c2ecf20Sopenharmony_ci			if (!rc) {
11758c2ecf20Sopenharmony_ci				new_nh = nh; /* send notification with old nh */
11768c2ecf20Sopenharmony_ci				replace_notify = 1;
11778c2ecf20Sopenharmony_ci			}
11788c2ecf20Sopenharmony_ci			goto out;
11798c2ecf20Sopenharmony_ci		} else {
11808c2ecf20Sopenharmony_ci			/* id already exists and not a replace */
11818c2ecf20Sopenharmony_ci			goto out;
11828c2ecf20Sopenharmony_ci		}
11838c2ecf20Sopenharmony_ci	}
11848c2ecf20Sopenharmony_ci
11858c2ecf20Sopenharmony_ci	if (replace && !create) {
11868c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists");
11878c2ecf20Sopenharmony_ci		rc = -ENOENT;
11888c2ecf20Sopenharmony_ci		goto out;
11898c2ecf20Sopenharmony_ci	}
11908c2ecf20Sopenharmony_ci
11918c2ecf20Sopenharmony_ci	rb_link_node_rcu(&new_nh->rb_node, parent, pp);
11928c2ecf20Sopenharmony_ci	rb_insert_color(&new_nh->rb_node, root);
11938c2ecf20Sopenharmony_ci	rc = 0;
11948c2ecf20Sopenharmony_ciout:
11958c2ecf20Sopenharmony_ci	if (!rc) {
11968c2ecf20Sopenharmony_ci		nh_base_seq_inc(net);
11978c2ecf20Sopenharmony_ci		nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo);
11988c2ecf20Sopenharmony_ci		if (replace_notify &&
11998c2ecf20Sopenharmony_ci		    READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode))
12008c2ecf20Sopenharmony_ci			nexthop_replace_notify(net, new_nh, &cfg->nlinfo);
12018c2ecf20Sopenharmony_ci	}
12028c2ecf20Sopenharmony_ci
12038c2ecf20Sopenharmony_ci	return rc;
12048c2ecf20Sopenharmony_ci}
12058c2ecf20Sopenharmony_ci
12068c2ecf20Sopenharmony_ci/* rtnl */
12078c2ecf20Sopenharmony_ci/* remove all nexthops tied to a device being deleted */
12088c2ecf20Sopenharmony_cistatic void nexthop_flush_dev(struct net_device *dev, unsigned long event)
12098c2ecf20Sopenharmony_ci{
12108c2ecf20Sopenharmony_ci	unsigned int hash = nh_dev_hashfn(dev->ifindex);
12118c2ecf20Sopenharmony_ci	struct net *net = dev_net(dev);
12128c2ecf20Sopenharmony_ci	struct hlist_head *head = &net->nexthop.devhash[hash];
12138c2ecf20Sopenharmony_ci	struct hlist_node *n;
12148c2ecf20Sopenharmony_ci	struct nh_info *nhi;
12158c2ecf20Sopenharmony_ci
12168c2ecf20Sopenharmony_ci	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
12178c2ecf20Sopenharmony_ci		if (nhi->fib_nhc.nhc_dev != dev)
12188c2ecf20Sopenharmony_ci			continue;
12198c2ecf20Sopenharmony_ci
12208c2ecf20Sopenharmony_ci		if (nhi->reject_nh &&
12218c2ecf20Sopenharmony_ci		    (event == NETDEV_DOWN || event == NETDEV_CHANGE))
12228c2ecf20Sopenharmony_ci			continue;
12238c2ecf20Sopenharmony_ci
12248c2ecf20Sopenharmony_ci		remove_nexthop(net, nhi->nh_parent, NULL);
12258c2ecf20Sopenharmony_ci	}
12268c2ecf20Sopenharmony_ci}
12278c2ecf20Sopenharmony_ci
12288c2ecf20Sopenharmony_ci/* rtnl; called when net namespace is deleted */
12298c2ecf20Sopenharmony_cistatic void flush_all_nexthops(struct net *net)
12308c2ecf20Sopenharmony_ci{
12318c2ecf20Sopenharmony_ci	struct rb_root *root = &net->nexthop.rb_root;
12328c2ecf20Sopenharmony_ci	struct rb_node *node;
12338c2ecf20Sopenharmony_ci	struct nexthop *nh;
12348c2ecf20Sopenharmony_ci
12358c2ecf20Sopenharmony_ci	while ((node = rb_first(root))) {
12368c2ecf20Sopenharmony_ci		nh = rb_entry(node, struct nexthop, rb_node);
12378c2ecf20Sopenharmony_ci		remove_nexthop(net, nh, NULL);
12388c2ecf20Sopenharmony_ci		cond_resched();
12398c2ecf20Sopenharmony_ci	}
12408c2ecf20Sopenharmony_ci}
12418c2ecf20Sopenharmony_ci
12428c2ecf20Sopenharmony_cistatic struct nexthop *nexthop_create_group(struct net *net,
12438c2ecf20Sopenharmony_ci					    struct nh_config *cfg)
12448c2ecf20Sopenharmony_ci{
12458c2ecf20Sopenharmony_ci	struct nlattr *grps_attr = cfg->nh_grp;
12468c2ecf20Sopenharmony_ci	struct nexthop_grp *entry = nla_data(grps_attr);
12478c2ecf20Sopenharmony_ci	u16 num_nh = nla_len(grps_attr) / sizeof(*entry);
12488c2ecf20Sopenharmony_ci	struct nh_group *nhg;
12498c2ecf20Sopenharmony_ci	struct nexthop *nh;
12508c2ecf20Sopenharmony_ci	int i;
12518c2ecf20Sopenharmony_ci
12528c2ecf20Sopenharmony_ci	if (WARN_ON(!num_nh))
12538c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
12548c2ecf20Sopenharmony_ci
12558c2ecf20Sopenharmony_ci	nh = nexthop_alloc();
12568c2ecf20Sopenharmony_ci	if (!nh)
12578c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
12588c2ecf20Sopenharmony_ci
12598c2ecf20Sopenharmony_ci	nh->is_group = 1;
12608c2ecf20Sopenharmony_ci
12618c2ecf20Sopenharmony_ci	nhg = nexthop_grp_alloc(num_nh);
12628c2ecf20Sopenharmony_ci	if (!nhg) {
12638c2ecf20Sopenharmony_ci		kfree(nh);
12648c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
12658c2ecf20Sopenharmony_ci	}
12668c2ecf20Sopenharmony_ci
12678c2ecf20Sopenharmony_ci	/* spare group used for removals */
12688c2ecf20Sopenharmony_ci	nhg->spare = nexthop_grp_alloc(num_nh);
12698c2ecf20Sopenharmony_ci	if (!nhg->spare) {
12708c2ecf20Sopenharmony_ci		kfree(nhg);
12718c2ecf20Sopenharmony_ci		kfree(nh);
12728c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
12738c2ecf20Sopenharmony_ci	}
12748c2ecf20Sopenharmony_ci	nhg->spare->spare = nhg;
12758c2ecf20Sopenharmony_ci
12768c2ecf20Sopenharmony_ci	for (i = 0; i < nhg->num_nh; ++i) {
12778c2ecf20Sopenharmony_ci		struct nexthop *nhe;
12788c2ecf20Sopenharmony_ci		struct nh_info *nhi;
12798c2ecf20Sopenharmony_ci
12808c2ecf20Sopenharmony_ci		nhe = nexthop_find_by_id(net, entry[i].id);
12818c2ecf20Sopenharmony_ci		if (!nexthop_get(nhe))
12828c2ecf20Sopenharmony_ci			goto out_no_nh;
12838c2ecf20Sopenharmony_ci
12848c2ecf20Sopenharmony_ci		nhi = rtnl_dereference(nhe->nh_info);
12858c2ecf20Sopenharmony_ci		if (nhi->family == AF_INET)
12868c2ecf20Sopenharmony_ci			nhg->has_v4 = true;
12878c2ecf20Sopenharmony_ci
12888c2ecf20Sopenharmony_ci		nhg->nh_entries[i].nh = nhe;
12898c2ecf20Sopenharmony_ci		nhg->nh_entries[i].weight = entry[i].weight + 1;
12908c2ecf20Sopenharmony_ci		list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list);
12918c2ecf20Sopenharmony_ci		nhg->nh_entries[i].nh_parent = nh;
12928c2ecf20Sopenharmony_ci	}
12938c2ecf20Sopenharmony_ci
12948c2ecf20Sopenharmony_ci	if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) {
12958c2ecf20Sopenharmony_ci		nhg->mpath = 1;
12968c2ecf20Sopenharmony_ci		nh_group_rebalance(nhg);
12978c2ecf20Sopenharmony_ci	}
12988c2ecf20Sopenharmony_ci
12998c2ecf20Sopenharmony_ci	if (cfg->nh_fdb)
13008c2ecf20Sopenharmony_ci		nhg->fdb_nh = 1;
13018c2ecf20Sopenharmony_ci
13028c2ecf20Sopenharmony_ci	rcu_assign_pointer(nh->nh_grp, nhg);
13038c2ecf20Sopenharmony_ci
13048c2ecf20Sopenharmony_ci	return nh;
13058c2ecf20Sopenharmony_ci
13068c2ecf20Sopenharmony_ciout_no_nh:
13078c2ecf20Sopenharmony_ci	for (i--; i >= 0; --i) {
13088c2ecf20Sopenharmony_ci		list_del(&nhg->nh_entries[i].nh_list);
13098c2ecf20Sopenharmony_ci		nexthop_put(nhg->nh_entries[i].nh);
13108c2ecf20Sopenharmony_ci	}
13118c2ecf20Sopenharmony_ci
13128c2ecf20Sopenharmony_ci	kfree(nhg->spare);
13138c2ecf20Sopenharmony_ci	kfree(nhg);
13148c2ecf20Sopenharmony_ci	kfree(nh);
13158c2ecf20Sopenharmony_ci
13168c2ecf20Sopenharmony_ci	return ERR_PTR(-ENOENT);
13178c2ecf20Sopenharmony_ci}
13188c2ecf20Sopenharmony_ci
13198c2ecf20Sopenharmony_cistatic int nh_create_ipv4(struct net *net, struct nexthop *nh,
13208c2ecf20Sopenharmony_ci			  struct nh_info *nhi, struct nh_config *cfg,
13218c2ecf20Sopenharmony_ci			  struct netlink_ext_ack *extack)
13228c2ecf20Sopenharmony_ci{
13238c2ecf20Sopenharmony_ci	struct fib_nh *fib_nh = &nhi->fib_nh;
13248c2ecf20Sopenharmony_ci	struct fib_config fib_cfg = {
13258c2ecf20Sopenharmony_ci		.fc_oif   = cfg->nh_ifindex,
13268c2ecf20Sopenharmony_ci		.fc_gw4   = cfg->gw.ipv4,
13278c2ecf20Sopenharmony_ci		.fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0,
13288c2ecf20Sopenharmony_ci		.fc_flags = cfg->nh_flags,
13298c2ecf20Sopenharmony_ci		.fc_nlinfo = cfg->nlinfo,
13308c2ecf20Sopenharmony_ci		.fc_encap = cfg->nh_encap,
13318c2ecf20Sopenharmony_ci		.fc_encap_type = cfg->nh_encap_type,
13328c2ecf20Sopenharmony_ci	};
13338c2ecf20Sopenharmony_ci	u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN);
13348c2ecf20Sopenharmony_ci	int err;
13358c2ecf20Sopenharmony_ci
13368c2ecf20Sopenharmony_ci	err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack);
13378c2ecf20Sopenharmony_ci	if (err) {
13388c2ecf20Sopenharmony_ci		fib_nh_release(net, fib_nh);
13398c2ecf20Sopenharmony_ci		goto out;
13408c2ecf20Sopenharmony_ci	}
13418c2ecf20Sopenharmony_ci
13428c2ecf20Sopenharmony_ci	if (nhi->fdb_nh)
13438c2ecf20Sopenharmony_ci		goto out;
13448c2ecf20Sopenharmony_ci
13458c2ecf20Sopenharmony_ci	/* sets nh_dev if successful */
13468c2ecf20Sopenharmony_ci	err = fib_check_nh(net, fib_nh, tb_id, 0, extack);
13478c2ecf20Sopenharmony_ci	if (!err) {
13488c2ecf20Sopenharmony_ci		nh->nh_flags = fib_nh->fib_nh_flags;
13498c2ecf20Sopenharmony_ci		fib_info_update_nhc_saddr(net, &fib_nh->nh_common,
13508c2ecf20Sopenharmony_ci					  !fib_nh->fib_nh_scope ? 0 : fib_nh->fib_nh_scope - 1);
13518c2ecf20Sopenharmony_ci	} else {
13528c2ecf20Sopenharmony_ci		fib_nh_release(net, fib_nh);
13538c2ecf20Sopenharmony_ci	}
13548c2ecf20Sopenharmony_ciout:
13558c2ecf20Sopenharmony_ci	return err;
13568c2ecf20Sopenharmony_ci}
13578c2ecf20Sopenharmony_ci
13588c2ecf20Sopenharmony_cistatic int nh_create_ipv6(struct net *net,  struct nexthop *nh,
13598c2ecf20Sopenharmony_ci			  struct nh_info *nhi, struct nh_config *cfg,
13608c2ecf20Sopenharmony_ci			  struct netlink_ext_ack *extack)
13618c2ecf20Sopenharmony_ci{
13628c2ecf20Sopenharmony_ci	struct fib6_nh *fib6_nh = &nhi->fib6_nh;
13638c2ecf20Sopenharmony_ci	struct fib6_config fib6_cfg = {
13648c2ecf20Sopenharmony_ci		.fc_table = l3mdev_fib_table(cfg->dev),
13658c2ecf20Sopenharmony_ci		.fc_ifindex = cfg->nh_ifindex,
13668c2ecf20Sopenharmony_ci		.fc_gateway = cfg->gw.ipv6,
13678c2ecf20Sopenharmony_ci		.fc_flags = cfg->nh_flags,
13688c2ecf20Sopenharmony_ci		.fc_nlinfo = cfg->nlinfo,
13698c2ecf20Sopenharmony_ci		.fc_encap = cfg->nh_encap,
13708c2ecf20Sopenharmony_ci		.fc_encap_type = cfg->nh_encap_type,
13718c2ecf20Sopenharmony_ci		.fc_is_fdb = cfg->nh_fdb,
13728c2ecf20Sopenharmony_ci	};
13738c2ecf20Sopenharmony_ci	int err;
13748c2ecf20Sopenharmony_ci
13758c2ecf20Sopenharmony_ci	if (!ipv6_addr_any(&cfg->gw.ipv6))
13768c2ecf20Sopenharmony_ci		fib6_cfg.fc_flags |= RTF_GATEWAY;
13778c2ecf20Sopenharmony_ci
13788c2ecf20Sopenharmony_ci	/* sets nh_dev if successful */
13798c2ecf20Sopenharmony_ci	err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL,
13808c2ecf20Sopenharmony_ci				      extack);
13818c2ecf20Sopenharmony_ci	if (err) {
13828c2ecf20Sopenharmony_ci		/* IPv6 is not enabled, don't call fib6_nh_release */
13838c2ecf20Sopenharmony_ci		if (err == -EAFNOSUPPORT)
13848c2ecf20Sopenharmony_ci			goto out;
13858c2ecf20Sopenharmony_ci		ipv6_stub->fib6_nh_release(fib6_nh);
13868c2ecf20Sopenharmony_ci	} else {
13878c2ecf20Sopenharmony_ci		nh->nh_flags = fib6_nh->fib_nh_flags;
13888c2ecf20Sopenharmony_ci	}
13898c2ecf20Sopenharmony_ciout:
13908c2ecf20Sopenharmony_ci	return err;
13918c2ecf20Sopenharmony_ci}
13928c2ecf20Sopenharmony_ci
13938c2ecf20Sopenharmony_cistatic struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg,
13948c2ecf20Sopenharmony_ci				      struct netlink_ext_ack *extack)
13958c2ecf20Sopenharmony_ci{
13968c2ecf20Sopenharmony_ci	struct nh_info *nhi;
13978c2ecf20Sopenharmony_ci	struct nexthop *nh;
13988c2ecf20Sopenharmony_ci	int err = 0;
13998c2ecf20Sopenharmony_ci
14008c2ecf20Sopenharmony_ci	nh = nexthop_alloc();
14018c2ecf20Sopenharmony_ci	if (!nh)
14028c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
14038c2ecf20Sopenharmony_ci
14048c2ecf20Sopenharmony_ci	nhi = kzalloc(sizeof(*nhi), GFP_KERNEL);
14058c2ecf20Sopenharmony_ci	if (!nhi) {
14068c2ecf20Sopenharmony_ci		kfree(nh);
14078c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
14088c2ecf20Sopenharmony_ci	}
14098c2ecf20Sopenharmony_ci
14108c2ecf20Sopenharmony_ci	nh->nh_flags = cfg->nh_flags;
14118c2ecf20Sopenharmony_ci	nh->net = net;
14128c2ecf20Sopenharmony_ci
14138c2ecf20Sopenharmony_ci	nhi->nh_parent = nh;
14148c2ecf20Sopenharmony_ci	nhi->family = cfg->nh_family;
14158c2ecf20Sopenharmony_ci	nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK;
14168c2ecf20Sopenharmony_ci
14178c2ecf20Sopenharmony_ci	if (cfg->nh_fdb)
14188c2ecf20Sopenharmony_ci		nhi->fdb_nh = 1;
14198c2ecf20Sopenharmony_ci
14208c2ecf20Sopenharmony_ci	if (cfg->nh_blackhole) {
14218c2ecf20Sopenharmony_ci		nhi->reject_nh = 1;
14228c2ecf20Sopenharmony_ci		cfg->nh_ifindex = net->loopback_dev->ifindex;
14238c2ecf20Sopenharmony_ci	}
14248c2ecf20Sopenharmony_ci
14258c2ecf20Sopenharmony_ci	switch (cfg->nh_family) {
14268c2ecf20Sopenharmony_ci	case AF_INET:
14278c2ecf20Sopenharmony_ci		err = nh_create_ipv4(net, nh, nhi, cfg, extack);
14288c2ecf20Sopenharmony_ci		break;
14298c2ecf20Sopenharmony_ci	case AF_INET6:
14308c2ecf20Sopenharmony_ci		err = nh_create_ipv6(net, nh, nhi, cfg, extack);
14318c2ecf20Sopenharmony_ci		break;
14328c2ecf20Sopenharmony_ci	}
14338c2ecf20Sopenharmony_ci
14348c2ecf20Sopenharmony_ci	if (err) {
14358c2ecf20Sopenharmony_ci		kfree(nhi);
14368c2ecf20Sopenharmony_ci		kfree(nh);
14378c2ecf20Sopenharmony_ci		return ERR_PTR(err);
14388c2ecf20Sopenharmony_ci	}
14398c2ecf20Sopenharmony_ci
14408c2ecf20Sopenharmony_ci	/* add the entry to the device based hash */
14418c2ecf20Sopenharmony_ci	if (!nhi->fdb_nh)
14428c2ecf20Sopenharmony_ci		nexthop_devhash_add(net, nhi);
14438c2ecf20Sopenharmony_ci
14448c2ecf20Sopenharmony_ci	rcu_assign_pointer(nh->nh_info, nhi);
14458c2ecf20Sopenharmony_ci
14468c2ecf20Sopenharmony_ci	return nh;
14478c2ecf20Sopenharmony_ci}
14488c2ecf20Sopenharmony_ci
14498c2ecf20Sopenharmony_ci/* called with rtnl lock held */
14508c2ecf20Sopenharmony_cistatic struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg,
14518c2ecf20Sopenharmony_ci				   struct netlink_ext_ack *extack)
14528c2ecf20Sopenharmony_ci{
14538c2ecf20Sopenharmony_ci	struct nexthop *nh;
14548c2ecf20Sopenharmony_ci	int err;
14558c2ecf20Sopenharmony_ci
14568c2ecf20Sopenharmony_ci	if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) {
14578c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Replace requires nexthop id");
14588c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
14598c2ecf20Sopenharmony_ci	}
14608c2ecf20Sopenharmony_ci
14618c2ecf20Sopenharmony_ci	if (!cfg->nh_id) {
14628c2ecf20Sopenharmony_ci		cfg->nh_id = nh_find_unused_id(net);
14638c2ecf20Sopenharmony_ci		if (!cfg->nh_id) {
14648c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "No unused id");
14658c2ecf20Sopenharmony_ci			return ERR_PTR(-EINVAL);
14668c2ecf20Sopenharmony_ci		}
14678c2ecf20Sopenharmony_ci	}
14688c2ecf20Sopenharmony_ci
14698c2ecf20Sopenharmony_ci	if (cfg->nh_grp)
14708c2ecf20Sopenharmony_ci		nh = nexthop_create_group(net, cfg);
14718c2ecf20Sopenharmony_ci	else
14728c2ecf20Sopenharmony_ci		nh = nexthop_create(net, cfg, extack);
14738c2ecf20Sopenharmony_ci
14748c2ecf20Sopenharmony_ci	if (IS_ERR(nh))
14758c2ecf20Sopenharmony_ci		return nh;
14768c2ecf20Sopenharmony_ci
14778c2ecf20Sopenharmony_ci	refcount_set(&nh->refcnt, 1);
14788c2ecf20Sopenharmony_ci	nh->id = cfg->nh_id;
14798c2ecf20Sopenharmony_ci	nh->protocol = cfg->nh_protocol;
14808c2ecf20Sopenharmony_ci	nh->net = net;
14818c2ecf20Sopenharmony_ci
14828c2ecf20Sopenharmony_ci	err = insert_nexthop(net, nh, cfg, extack);
14838c2ecf20Sopenharmony_ci	if (err) {
14848c2ecf20Sopenharmony_ci		__remove_nexthop(net, nh, NULL);
14858c2ecf20Sopenharmony_ci		nexthop_put(nh);
14868c2ecf20Sopenharmony_ci		nh = ERR_PTR(err);
14878c2ecf20Sopenharmony_ci	}
14888c2ecf20Sopenharmony_ci
14898c2ecf20Sopenharmony_ci	return nh;
14908c2ecf20Sopenharmony_ci}
14918c2ecf20Sopenharmony_ci
14928c2ecf20Sopenharmony_cistatic int rtm_to_nh_config(struct net *net, struct sk_buff *skb,
14938c2ecf20Sopenharmony_ci			    struct nlmsghdr *nlh, struct nh_config *cfg,
14948c2ecf20Sopenharmony_ci			    struct netlink_ext_ack *extack)
14958c2ecf20Sopenharmony_ci{
14968c2ecf20Sopenharmony_ci	struct nhmsg *nhm = nlmsg_data(nlh);
14978c2ecf20Sopenharmony_ci	struct nlattr *tb[NHA_MAX + 1];
14988c2ecf20Sopenharmony_ci	int err;
14998c2ecf20Sopenharmony_ci
15008c2ecf20Sopenharmony_ci	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
15018c2ecf20Sopenharmony_ci			  extack);
15028c2ecf20Sopenharmony_ci	if (err < 0)
15038c2ecf20Sopenharmony_ci		return err;
15048c2ecf20Sopenharmony_ci
15058c2ecf20Sopenharmony_ci	err = -EINVAL;
15068c2ecf20Sopenharmony_ci	if (nhm->resvd || nhm->nh_scope) {
15078c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid values in ancillary header");
15088c2ecf20Sopenharmony_ci		goto out;
15098c2ecf20Sopenharmony_ci	}
15108c2ecf20Sopenharmony_ci	if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) {
15118c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header");
15128c2ecf20Sopenharmony_ci		goto out;
15138c2ecf20Sopenharmony_ci	}
15148c2ecf20Sopenharmony_ci
15158c2ecf20Sopenharmony_ci	switch (nhm->nh_family) {
15168c2ecf20Sopenharmony_ci	case AF_INET:
15178c2ecf20Sopenharmony_ci	case AF_INET6:
15188c2ecf20Sopenharmony_ci		break;
15198c2ecf20Sopenharmony_ci	case AF_UNSPEC:
15208c2ecf20Sopenharmony_ci		if (tb[NHA_GROUP])
15218c2ecf20Sopenharmony_ci			break;
15228c2ecf20Sopenharmony_ci		fallthrough;
15238c2ecf20Sopenharmony_ci	default:
15248c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid address family");
15258c2ecf20Sopenharmony_ci		goto out;
15268c2ecf20Sopenharmony_ci	}
15278c2ecf20Sopenharmony_ci
15288c2ecf20Sopenharmony_ci	if (tb[NHA_GROUPS] || tb[NHA_MASTER]) {
15298c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid attributes in request");
15308c2ecf20Sopenharmony_ci		goto out;
15318c2ecf20Sopenharmony_ci	}
15328c2ecf20Sopenharmony_ci
15338c2ecf20Sopenharmony_ci	memset(cfg, 0, sizeof(*cfg));
15348c2ecf20Sopenharmony_ci	cfg->nlflags = nlh->nlmsg_flags;
15358c2ecf20Sopenharmony_ci	cfg->nlinfo.portid = NETLINK_CB(skb).portid;
15368c2ecf20Sopenharmony_ci	cfg->nlinfo.nlh = nlh;
15378c2ecf20Sopenharmony_ci	cfg->nlinfo.nl_net = net;
15388c2ecf20Sopenharmony_ci
15398c2ecf20Sopenharmony_ci	cfg->nh_family = nhm->nh_family;
15408c2ecf20Sopenharmony_ci	cfg->nh_protocol = nhm->nh_protocol;
15418c2ecf20Sopenharmony_ci	cfg->nh_flags = nhm->nh_flags;
15428c2ecf20Sopenharmony_ci
15438c2ecf20Sopenharmony_ci	if (tb[NHA_ID])
15448c2ecf20Sopenharmony_ci		cfg->nh_id = nla_get_u32(tb[NHA_ID]);
15458c2ecf20Sopenharmony_ci
15468c2ecf20Sopenharmony_ci	if (tb[NHA_FDB]) {
15478c2ecf20Sopenharmony_ci		if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] ||
15488c2ecf20Sopenharmony_ci		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE]) {
15498c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole");
15508c2ecf20Sopenharmony_ci			goto out;
15518c2ecf20Sopenharmony_ci		}
15528c2ecf20Sopenharmony_ci		if (nhm->nh_flags) {
15538c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header");
15548c2ecf20Sopenharmony_ci			goto out;
15558c2ecf20Sopenharmony_ci		}
15568c2ecf20Sopenharmony_ci		cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]);
15578c2ecf20Sopenharmony_ci	}
15588c2ecf20Sopenharmony_ci
15598c2ecf20Sopenharmony_ci	if (tb[NHA_GROUP]) {
15608c2ecf20Sopenharmony_ci		if (nhm->nh_family != AF_UNSPEC) {
15618c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid family for group");
15628c2ecf20Sopenharmony_ci			goto out;
15638c2ecf20Sopenharmony_ci		}
15648c2ecf20Sopenharmony_ci		cfg->nh_grp = tb[NHA_GROUP];
15658c2ecf20Sopenharmony_ci
15668c2ecf20Sopenharmony_ci		cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH;
15678c2ecf20Sopenharmony_ci		if (tb[NHA_GROUP_TYPE])
15688c2ecf20Sopenharmony_ci			cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]);
15698c2ecf20Sopenharmony_ci
15708c2ecf20Sopenharmony_ci		if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) {
15718c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid group type");
15728c2ecf20Sopenharmony_ci			goto out;
15738c2ecf20Sopenharmony_ci		}
15748c2ecf20Sopenharmony_ci		err = nh_check_attr_group(net, tb, extack);
15758c2ecf20Sopenharmony_ci
15768c2ecf20Sopenharmony_ci		/* no other attributes should be set */
15778c2ecf20Sopenharmony_ci		goto out;
15788c2ecf20Sopenharmony_ci	}
15798c2ecf20Sopenharmony_ci
15808c2ecf20Sopenharmony_ci	if (tb[NHA_BLACKHOLE]) {
15818c2ecf20Sopenharmony_ci		if (tb[NHA_GATEWAY] || tb[NHA_OIF] ||
15828c2ecf20Sopenharmony_ci		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) {
15838c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb");
15848c2ecf20Sopenharmony_ci			goto out;
15858c2ecf20Sopenharmony_ci		}
15868c2ecf20Sopenharmony_ci
15878c2ecf20Sopenharmony_ci		cfg->nh_blackhole = 1;
15888c2ecf20Sopenharmony_ci		err = 0;
15898c2ecf20Sopenharmony_ci		goto out;
15908c2ecf20Sopenharmony_ci	}
15918c2ecf20Sopenharmony_ci
15928c2ecf20Sopenharmony_ci	if (!cfg->nh_fdb && !tb[NHA_OIF]) {
15938c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops");
15948c2ecf20Sopenharmony_ci		goto out;
15958c2ecf20Sopenharmony_ci	}
15968c2ecf20Sopenharmony_ci
15978c2ecf20Sopenharmony_ci	if (!cfg->nh_fdb && tb[NHA_OIF]) {
15988c2ecf20Sopenharmony_ci		cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]);
15998c2ecf20Sopenharmony_ci		if (cfg->nh_ifindex)
16008c2ecf20Sopenharmony_ci			cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex);
16018c2ecf20Sopenharmony_ci
16028c2ecf20Sopenharmony_ci		if (!cfg->dev) {
16038c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Invalid device index");
16048c2ecf20Sopenharmony_ci			goto out;
16058c2ecf20Sopenharmony_ci		} else if (!(cfg->dev->flags & IFF_UP)) {
16068c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Nexthop device is not up");
16078c2ecf20Sopenharmony_ci			err = -ENETDOWN;
16088c2ecf20Sopenharmony_ci			goto out;
16098c2ecf20Sopenharmony_ci		} else if (!netif_carrier_ok(cfg->dev)) {
16108c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down");
16118c2ecf20Sopenharmony_ci			err = -ENETDOWN;
16128c2ecf20Sopenharmony_ci			goto out;
16138c2ecf20Sopenharmony_ci		}
16148c2ecf20Sopenharmony_ci	}
16158c2ecf20Sopenharmony_ci
16168c2ecf20Sopenharmony_ci	err = -EINVAL;
16178c2ecf20Sopenharmony_ci	if (tb[NHA_GATEWAY]) {
16188c2ecf20Sopenharmony_ci		struct nlattr *gwa = tb[NHA_GATEWAY];
16198c2ecf20Sopenharmony_ci
16208c2ecf20Sopenharmony_ci		switch (cfg->nh_family) {
16218c2ecf20Sopenharmony_ci		case AF_INET:
16228c2ecf20Sopenharmony_ci			if (nla_len(gwa) != sizeof(u32)) {
16238c2ecf20Sopenharmony_ci				NL_SET_ERR_MSG(extack, "Invalid gateway");
16248c2ecf20Sopenharmony_ci				goto out;
16258c2ecf20Sopenharmony_ci			}
16268c2ecf20Sopenharmony_ci			cfg->gw.ipv4 = nla_get_be32(gwa);
16278c2ecf20Sopenharmony_ci			break;
16288c2ecf20Sopenharmony_ci		case AF_INET6:
16298c2ecf20Sopenharmony_ci			if (nla_len(gwa) != sizeof(struct in6_addr)) {
16308c2ecf20Sopenharmony_ci				NL_SET_ERR_MSG(extack, "Invalid gateway");
16318c2ecf20Sopenharmony_ci				goto out;
16328c2ecf20Sopenharmony_ci			}
16338c2ecf20Sopenharmony_ci			cfg->gw.ipv6 = nla_get_in6_addr(gwa);
16348c2ecf20Sopenharmony_ci			break;
16358c2ecf20Sopenharmony_ci		default:
16368c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack,
16378c2ecf20Sopenharmony_ci				       "Unknown address family for gateway");
16388c2ecf20Sopenharmony_ci			goto out;
16398c2ecf20Sopenharmony_ci		}
16408c2ecf20Sopenharmony_ci	} else {
16418c2ecf20Sopenharmony_ci		/* device only nexthop (no gateway) */
16428c2ecf20Sopenharmony_ci		if (cfg->nh_flags & RTNH_F_ONLINK) {
16438c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack,
16448c2ecf20Sopenharmony_ci				       "ONLINK flag can not be set for nexthop without a gateway");
16458c2ecf20Sopenharmony_ci			goto out;
16468c2ecf20Sopenharmony_ci		}
16478c2ecf20Sopenharmony_ci	}
16488c2ecf20Sopenharmony_ci
16498c2ecf20Sopenharmony_ci	if (tb[NHA_ENCAP]) {
16508c2ecf20Sopenharmony_ci		cfg->nh_encap = tb[NHA_ENCAP];
16518c2ecf20Sopenharmony_ci
16528c2ecf20Sopenharmony_ci		if (!tb[NHA_ENCAP_TYPE]) {
16538c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing");
16548c2ecf20Sopenharmony_ci			goto out;
16558c2ecf20Sopenharmony_ci		}
16568c2ecf20Sopenharmony_ci
16578c2ecf20Sopenharmony_ci		cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]);
16588c2ecf20Sopenharmony_ci		err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack);
16598c2ecf20Sopenharmony_ci		if (err < 0)
16608c2ecf20Sopenharmony_ci			goto out;
16618c2ecf20Sopenharmony_ci
16628c2ecf20Sopenharmony_ci	} else if (tb[NHA_ENCAP_TYPE]) {
16638c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing");
16648c2ecf20Sopenharmony_ci		goto out;
16658c2ecf20Sopenharmony_ci	}
16668c2ecf20Sopenharmony_ci
16678c2ecf20Sopenharmony_ci
16688c2ecf20Sopenharmony_ci	err = 0;
16698c2ecf20Sopenharmony_ciout:
16708c2ecf20Sopenharmony_ci	return err;
16718c2ecf20Sopenharmony_ci}
16728c2ecf20Sopenharmony_ci
16738c2ecf20Sopenharmony_ci/* rtnl */
16748c2ecf20Sopenharmony_cistatic int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
16758c2ecf20Sopenharmony_ci			   struct netlink_ext_ack *extack)
16768c2ecf20Sopenharmony_ci{
16778c2ecf20Sopenharmony_ci	struct net *net = sock_net(skb->sk);
16788c2ecf20Sopenharmony_ci	struct nh_config cfg;
16798c2ecf20Sopenharmony_ci	struct nexthop *nh;
16808c2ecf20Sopenharmony_ci	int err;
16818c2ecf20Sopenharmony_ci
16828c2ecf20Sopenharmony_ci	err = rtm_to_nh_config(net, skb, nlh, &cfg, extack);
16838c2ecf20Sopenharmony_ci	if (!err) {
16848c2ecf20Sopenharmony_ci		nh = nexthop_add(net, &cfg, extack);
16858c2ecf20Sopenharmony_ci		if (IS_ERR(nh))
16868c2ecf20Sopenharmony_ci			err = PTR_ERR(nh);
16878c2ecf20Sopenharmony_ci	}
16888c2ecf20Sopenharmony_ci
16898c2ecf20Sopenharmony_ci	return err;
16908c2ecf20Sopenharmony_ci}
16918c2ecf20Sopenharmony_ci
16928c2ecf20Sopenharmony_cistatic int nh_valid_get_del_req(struct nlmsghdr *nlh, u32 *id,
16938c2ecf20Sopenharmony_ci				struct netlink_ext_ack *extack)
16948c2ecf20Sopenharmony_ci{
16958c2ecf20Sopenharmony_ci	struct nhmsg *nhm = nlmsg_data(nlh);
16968c2ecf20Sopenharmony_ci	struct nlattr *tb[NHA_MAX + 1];
16978c2ecf20Sopenharmony_ci	int err, i;
16988c2ecf20Sopenharmony_ci
16998c2ecf20Sopenharmony_ci	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
17008c2ecf20Sopenharmony_ci			  extack);
17018c2ecf20Sopenharmony_ci	if (err < 0)
17028c2ecf20Sopenharmony_ci		return err;
17038c2ecf20Sopenharmony_ci
17048c2ecf20Sopenharmony_ci	err = -EINVAL;
17058c2ecf20Sopenharmony_ci	for (i = 0; i < __NHA_MAX; ++i) {
17068c2ecf20Sopenharmony_ci		if (!tb[i])
17078c2ecf20Sopenharmony_ci			continue;
17088c2ecf20Sopenharmony_ci
17098c2ecf20Sopenharmony_ci		switch (i) {
17108c2ecf20Sopenharmony_ci		case NHA_ID:
17118c2ecf20Sopenharmony_ci			break;
17128c2ecf20Sopenharmony_ci		default:
17138c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG_ATTR(extack, tb[i],
17148c2ecf20Sopenharmony_ci					    "Unexpected attribute in request");
17158c2ecf20Sopenharmony_ci			goto out;
17168c2ecf20Sopenharmony_ci		}
17178c2ecf20Sopenharmony_ci	}
17188c2ecf20Sopenharmony_ci	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
17198c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid values in header");
17208c2ecf20Sopenharmony_ci		goto out;
17218c2ecf20Sopenharmony_ci	}
17228c2ecf20Sopenharmony_ci
17238c2ecf20Sopenharmony_ci	if (!tb[NHA_ID]) {
17248c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Nexthop id is missing");
17258c2ecf20Sopenharmony_ci		goto out;
17268c2ecf20Sopenharmony_ci	}
17278c2ecf20Sopenharmony_ci
17288c2ecf20Sopenharmony_ci	*id = nla_get_u32(tb[NHA_ID]);
17298c2ecf20Sopenharmony_ci	if (!(*id))
17308c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid nexthop id");
17318c2ecf20Sopenharmony_ci	else
17328c2ecf20Sopenharmony_ci		err = 0;
17338c2ecf20Sopenharmony_ciout:
17348c2ecf20Sopenharmony_ci	return err;
17358c2ecf20Sopenharmony_ci}
17368c2ecf20Sopenharmony_ci
17378c2ecf20Sopenharmony_ci/* rtnl */
17388c2ecf20Sopenharmony_cistatic int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
17398c2ecf20Sopenharmony_ci			   struct netlink_ext_ack *extack)
17408c2ecf20Sopenharmony_ci{
17418c2ecf20Sopenharmony_ci	struct net *net = sock_net(skb->sk);
17428c2ecf20Sopenharmony_ci	struct nl_info nlinfo = {
17438c2ecf20Sopenharmony_ci		.nlh = nlh,
17448c2ecf20Sopenharmony_ci		.nl_net = net,
17458c2ecf20Sopenharmony_ci		.portid = NETLINK_CB(skb).portid,
17468c2ecf20Sopenharmony_ci	};
17478c2ecf20Sopenharmony_ci	struct nexthop *nh;
17488c2ecf20Sopenharmony_ci	int err;
17498c2ecf20Sopenharmony_ci	u32 id;
17508c2ecf20Sopenharmony_ci
17518c2ecf20Sopenharmony_ci	err = nh_valid_get_del_req(nlh, &id, extack);
17528c2ecf20Sopenharmony_ci	if (err)
17538c2ecf20Sopenharmony_ci		return err;
17548c2ecf20Sopenharmony_ci
17558c2ecf20Sopenharmony_ci	nh = nexthop_find_by_id(net, id);
17568c2ecf20Sopenharmony_ci	if (!nh)
17578c2ecf20Sopenharmony_ci		return -ENOENT;
17588c2ecf20Sopenharmony_ci
17598c2ecf20Sopenharmony_ci	remove_nexthop(net, nh, &nlinfo);
17608c2ecf20Sopenharmony_ci
17618c2ecf20Sopenharmony_ci	return 0;
17628c2ecf20Sopenharmony_ci}
17638c2ecf20Sopenharmony_ci
17648c2ecf20Sopenharmony_ci/* rtnl */
17658c2ecf20Sopenharmony_cistatic int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh,
17668c2ecf20Sopenharmony_ci			   struct netlink_ext_ack *extack)
17678c2ecf20Sopenharmony_ci{
17688c2ecf20Sopenharmony_ci	struct net *net = sock_net(in_skb->sk);
17698c2ecf20Sopenharmony_ci	struct sk_buff *skb = NULL;
17708c2ecf20Sopenharmony_ci	struct nexthop *nh;
17718c2ecf20Sopenharmony_ci	int err;
17728c2ecf20Sopenharmony_ci	u32 id;
17738c2ecf20Sopenharmony_ci
17748c2ecf20Sopenharmony_ci	err = nh_valid_get_del_req(nlh, &id, extack);
17758c2ecf20Sopenharmony_ci	if (err)
17768c2ecf20Sopenharmony_ci		return err;
17778c2ecf20Sopenharmony_ci
17788c2ecf20Sopenharmony_ci	err = -ENOBUFS;
17798c2ecf20Sopenharmony_ci	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
17808c2ecf20Sopenharmony_ci	if (!skb)
17818c2ecf20Sopenharmony_ci		goto out;
17828c2ecf20Sopenharmony_ci
17838c2ecf20Sopenharmony_ci	err = -ENOENT;
17848c2ecf20Sopenharmony_ci	nh = nexthop_find_by_id(net, id);
17858c2ecf20Sopenharmony_ci	if (!nh)
17868c2ecf20Sopenharmony_ci		goto errout_free;
17878c2ecf20Sopenharmony_ci
17888c2ecf20Sopenharmony_ci	err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid,
17898c2ecf20Sopenharmony_ci			   nlh->nlmsg_seq, 0);
17908c2ecf20Sopenharmony_ci	if (err < 0) {
17918c2ecf20Sopenharmony_ci		WARN_ON(err == -EMSGSIZE);
17928c2ecf20Sopenharmony_ci		goto errout_free;
17938c2ecf20Sopenharmony_ci	}
17948c2ecf20Sopenharmony_ci
17958c2ecf20Sopenharmony_ci	err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
17968c2ecf20Sopenharmony_ciout:
17978c2ecf20Sopenharmony_ci	return err;
17988c2ecf20Sopenharmony_cierrout_free:
17998c2ecf20Sopenharmony_ci	kfree_skb(skb);
18008c2ecf20Sopenharmony_ci	goto out;
18018c2ecf20Sopenharmony_ci}
18028c2ecf20Sopenharmony_ci
18038c2ecf20Sopenharmony_cistatic bool nh_dump_filtered(struct nexthop *nh, int dev_idx, int master_idx,
18048c2ecf20Sopenharmony_ci			     bool group_filter, u8 family)
18058c2ecf20Sopenharmony_ci{
18068c2ecf20Sopenharmony_ci	const struct net_device *dev;
18078c2ecf20Sopenharmony_ci	const struct nh_info *nhi;
18088c2ecf20Sopenharmony_ci
18098c2ecf20Sopenharmony_ci	if (group_filter && !nh->is_group)
18108c2ecf20Sopenharmony_ci		return true;
18118c2ecf20Sopenharmony_ci
18128c2ecf20Sopenharmony_ci	if (!dev_idx && !master_idx && !family)
18138c2ecf20Sopenharmony_ci		return false;
18148c2ecf20Sopenharmony_ci
18158c2ecf20Sopenharmony_ci	if (nh->is_group)
18168c2ecf20Sopenharmony_ci		return true;
18178c2ecf20Sopenharmony_ci
18188c2ecf20Sopenharmony_ci	nhi = rtnl_dereference(nh->nh_info);
18198c2ecf20Sopenharmony_ci	if (family && nhi->family != family)
18208c2ecf20Sopenharmony_ci		return true;
18218c2ecf20Sopenharmony_ci
18228c2ecf20Sopenharmony_ci	dev = nhi->fib_nhc.nhc_dev;
18238c2ecf20Sopenharmony_ci	if (dev_idx && (!dev || dev->ifindex != dev_idx))
18248c2ecf20Sopenharmony_ci		return true;
18258c2ecf20Sopenharmony_ci
18268c2ecf20Sopenharmony_ci	if (master_idx) {
18278c2ecf20Sopenharmony_ci		struct net_device *master;
18288c2ecf20Sopenharmony_ci
18298c2ecf20Sopenharmony_ci		if (!dev)
18308c2ecf20Sopenharmony_ci			return true;
18318c2ecf20Sopenharmony_ci
18328c2ecf20Sopenharmony_ci		master = netdev_master_upper_dev_get((struct net_device *)dev);
18338c2ecf20Sopenharmony_ci		if (!master || master->ifindex != master_idx)
18348c2ecf20Sopenharmony_ci			return true;
18358c2ecf20Sopenharmony_ci	}
18368c2ecf20Sopenharmony_ci
18378c2ecf20Sopenharmony_ci	return false;
18388c2ecf20Sopenharmony_ci}
18398c2ecf20Sopenharmony_ci
18408c2ecf20Sopenharmony_cistatic int nh_valid_dump_req(const struct nlmsghdr *nlh, int *dev_idx,
18418c2ecf20Sopenharmony_ci			     int *master_idx, bool *group_filter,
18428c2ecf20Sopenharmony_ci			     bool *fdb_filter, struct netlink_callback *cb)
18438c2ecf20Sopenharmony_ci{
18448c2ecf20Sopenharmony_ci	struct netlink_ext_ack *extack = cb->extack;
18458c2ecf20Sopenharmony_ci	struct nlattr *tb[NHA_MAX + 1];
18468c2ecf20Sopenharmony_ci	struct nhmsg *nhm;
18478c2ecf20Sopenharmony_ci	int err, i;
18488c2ecf20Sopenharmony_ci	u32 idx;
18498c2ecf20Sopenharmony_ci
18508c2ecf20Sopenharmony_ci	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
18518c2ecf20Sopenharmony_ci			  NULL);
18528c2ecf20Sopenharmony_ci	if (err < 0)
18538c2ecf20Sopenharmony_ci		return err;
18548c2ecf20Sopenharmony_ci
18558c2ecf20Sopenharmony_ci	for (i = 0; i <= NHA_MAX; ++i) {
18568c2ecf20Sopenharmony_ci		if (!tb[i])
18578c2ecf20Sopenharmony_ci			continue;
18588c2ecf20Sopenharmony_ci
18598c2ecf20Sopenharmony_ci		switch (i) {
18608c2ecf20Sopenharmony_ci		case NHA_OIF:
18618c2ecf20Sopenharmony_ci			idx = nla_get_u32(tb[i]);
18628c2ecf20Sopenharmony_ci			if (idx > INT_MAX) {
18638c2ecf20Sopenharmony_ci				NL_SET_ERR_MSG(extack, "Invalid device index");
18648c2ecf20Sopenharmony_ci				return -EINVAL;
18658c2ecf20Sopenharmony_ci			}
18668c2ecf20Sopenharmony_ci			*dev_idx = idx;
18678c2ecf20Sopenharmony_ci			break;
18688c2ecf20Sopenharmony_ci		case NHA_MASTER:
18698c2ecf20Sopenharmony_ci			idx = nla_get_u32(tb[i]);
18708c2ecf20Sopenharmony_ci			if (idx > INT_MAX) {
18718c2ecf20Sopenharmony_ci				NL_SET_ERR_MSG(extack, "Invalid master device index");
18728c2ecf20Sopenharmony_ci				return -EINVAL;
18738c2ecf20Sopenharmony_ci			}
18748c2ecf20Sopenharmony_ci			*master_idx = idx;
18758c2ecf20Sopenharmony_ci			break;
18768c2ecf20Sopenharmony_ci		case NHA_GROUPS:
18778c2ecf20Sopenharmony_ci			*group_filter = true;
18788c2ecf20Sopenharmony_ci			break;
18798c2ecf20Sopenharmony_ci		case NHA_FDB:
18808c2ecf20Sopenharmony_ci			*fdb_filter = true;
18818c2ecf20Sopenharmony_ci			break;
18828c2ecf20Sopenharmony_ci		default:
18838c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
18848c2ecf20Sopenharmony_ci			return -EINVAL;
18858c2ecf20Sopenharmony_ci		}
18868c2ecf20Sopenharmony_ci	}
18878c2ecf20Sopenharmony_ci
18888c2ecf20Sopenharmony_ci	nhm = nlmsg_data(nlh);
18898c2ecf20Sopenharmony_ci	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
18908c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request");
18918c2ecf20Sopenharmony_ci		return -EINVAL;
18928c2ecf20Sopenharmony_ci	}
18938c2ecf20Sopenharmony_ci
18948c2ecf20Sopenharmony_ci	return 0;
18958c2ecf20Sopenharmony_ci}
18968c2ecf20Sopenharmony_ci
18978c2ecf20Sopenharmony_ci/* rtnl */
18988c2ecf20Sopenharmony_cistatic int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb)
18998c2ecf20Sopenharmony_ci{
19008c2ecf20Sopenharmony_ci	bool group_filter = false, fdb_filter = false;
19018c2ecf20Sopenharmony_ci	struct nhmsg *nhm = nlmsg_data(cb->nlh);
19028c2ecf20Sopenharmony_ci	int dev_filter_idx = 0, master_idx = 0;
19038c2ecf20Sopenharmony_ci	struct net *net = sock_net(skb->sk);
19048c2ecf20Sopenharmony_ci	struct rb_root *root = &net->nexthop.rb_root;
19058c2ecf20Sopenharmony_ci	struct rb_node *node;
19068c2ecf20Sopenharmony_ci	int idx = 0, s_idx;
19078c2ecf20Sopenharmony_ci	int err;
19088c2ecf20Sopenharmony_ci
19098c2ecf20Sopenharmony_ci	err = nh_valid_dump_req(cb->nlh, &dev_filter_idx, &master_idx,
19108c2ecf20Sopenharmony_ci				&group_filter, &fdb_filter, cb);
19118c2ecf20Sopenharmony_ci	if (err < 0)
19128c2ecf20Sopenharmony_ci		return err;
19138c2ecf20Sopenharmony_ci
19148c2ecf20Sopenharmony_ci	s_idx = cb->args[0];
19158c2ecf20Sopenharmony_ci	for (node = rb_first(root); node; node = rb_next(node)) {
19168c2ecf20Sopenharmony_ci		struct nexthop *nh;
19178c2ecf20Sopenharmony_ci
19188c2ecf20Sopenharmony_ci		if (idx < s_idx)
19198c2ecf20Sopenharmony_ci			goto cont;
19208c2ecf20Sopenharmony_ci
19218c2ecf20Sopenharmony_ci		nh = rb_entry(node, struct nexthop, rb_node);
19228c2ecf20Sopenharmony_ci		if (nh_dump_filtered(nh, dev_filter_idx, master_idx,
19238c2ecf20Sopenharmony_ci				     group_filter, nhm->nh_family))
19248c2ecf20Sopenharmony_ci			goto cont;
19258c2ecf20Sopenharmony_ci
19268c2ecf20Sopenharmony_ci		err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP,
19278c2ecf20Sopenharmony_ci				   NETLINK_CB(cb->skb).portid,
19288c2ecf20Sopenharmony_ci				   cb->nlh->nlmsg_seq, NLM_F_MULTI);
19298c2ecf20Sopenharmony_ci		if (err < 0) {
19308c2ecf20Sopenharmony_ci			if (likely(skb->len))
19318c2ecf20Sopenharmony_ci				goto out;
19328c2ecf20Sopenharmony_ci
19338c2ecf20Sopenharmony_ci			goto out_err;
19348c2ecf20Sopenharmony_ci		}
19358c2ecf20Sopenharmony_cicont:
19368c2ecf20Sopenharmony_ci		idx++;
19378c2ecf20Sopenharmony_ci	}
19388c2ecf20Sopenharmony_ci
19398c2ecf20Sopenharmony_ciout:
19408c2ecf20Sopenharmony_ci	err = skb->len;
19418c2ecf20Sopenharmony_ciout_err:
19428c2ecf20Sopenharmony_ci	cb->args[0] = idx;
19438c2ecf20Sopenharmony_ci	cb->seq = net->nexthop.seq;
19448c2ecf20Sopenharmony_ci	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
19458c2ecf20Sopenharmony_ci
19468c2ecf20Sopenharmony_ci	return err;
19478c2ecf20Sopenharmony_ci}
19488c2ecf20Sopenharmony_ci
19498c2ecf20Sopenharmony_cistatic void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu)
19508c2ecf20Sopenharmony_ci{
19518c2ecf20Sopenharmony_ci	unsigned int hash = nh_dev_hashfn(dev->ifindex);
19528c2ecf20Sopenharmony_ci	struct net *net = dev_net(dev);
19538c2ecf20Sopenharmony_ci	struct hlist_head *head = &net->nexthop.devhash[hash];
19548c2ecf20Sopenharmony_ci	struct hlist_node *n;
19558c2ecf20Sopenharmony_ci	struct nh_info *nhi;
19568c2ecf20Sopenharmony_ci
19578c2ecf20Sopenharmony_ci	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
19588c2ecf20Sopenharmony_ci		if (nhi->fib_nhc.nhc_dev == dev) {
19598c2ecf20Sopenharmony_ci			if (nhi->family == AF_INET)
19608c2ecf20Sopenharmony_ci				fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu,
19618c2ecf20Sopenharmony_ci						   orig_mtu);
19628c2ecf20Sopenharmony_ci		}
19638c2ecf20Sopenharmony_ci	}
19648c2ecf20Sopenharmony_ci}
19658c2ecf20Sopenharmony_ci
19668c2ecf20Sopenharmony_ci/* rtnl */
19678c2ecf20Sopenharmony_cistatic int nh_netdev_event(struct notifier_block *this,
19688c2ecf20Sopenharmony_ci			   unsigned long event, void *ptr)
19698c2ecf20Sopenharmony_ci{
19708c2ecf20Sopenharmony_ci	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
19718c2ecf20Sopenharmony_ci	struct netdev_notifier_info_ext *info_ext;
19728c2ecf20Sopenharmony_ci
19738c2ecf20Sopenharmony_ci	switch (event) {
19748c2ecf20Sopenharmony_ci	case NETDEV_DOWN:
19758c2ecf20Sopenharmony_ci	case NETDEV_UNREGISTER:
19768c2ecf20Sopenharmony_ci		nexthop_flush_dev(dev, event);
19778c2ecf20Sopenharmony_ci		break;
19788c2ecf20Sopenharmony_ci	case NETDEV_CHANGE:
19798c2ecf20Sopenharmony_ci		if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP)))
19808c2ecf20Sopenharmony_ci			nexthop_flush_dev(dev, event);
19818c2ecf20Sopenharmony_ci		break;
19828c2ecf20Sopenharmony_ci	case NETDEV_CHANGEMTU:
19838c2ecf20Sopenharmony_ci		info_ext = ptr;
19848c2ecf20Sopenharmony_ci		nexthop_sync_mtu(dev, info_ext->ext.mtu);
19858c2ecf20Sopenharmony_ci		rt_cache_flush(dev_net(dev));
19868c2ecf20Sopenharmony_ci		break;
19878c2ecf20Sopenharmony_ci	}
19888c2ecf20Sopenharmony_ci	return NOTIFY_DONE;
19898c2ecf20Sopenharmony_ci}
19908c2ecf20Sopenharmony_ci
19918c2ecf20Sopenharmony_cistatic struct notifier_block nh_netdev_notifier = {
19928c2ecf20Sopenharmony_ci	.notifier_call = nh_netdev_event,
19938c2ecf20Sopenharmony_ci};
19948c2ecf20Sopenharmony_ci
19958c2ecf20Sopenharmony_ciint register_nexthop_notifier(struct net *net, struct notifier_block *nb)
19968c2ecf20Sopenharmony_ci{
19978c2ecf20Sopenharmony_ci	return blocking_notifier_chain_register(&net->nexthop.notifier_chain,
19988c2ecf20Sopenharmony_ci						nb);
19998c2ecf20Sopenharmony_ci}
20008c2ecf20Sopenharmony_ciEXPORT_SYMBOL(register_nexthop_notifier);
20018c2ecf20Sopenharmony_ci
20028c2ecf20Sopenharmony_ciint unregister_nexthop_notifier(struct net *net, struct notifier_block *nb)
20038c2ecf20Sopenharmony_ci{
20048c2ecf20Sopenharmony_ci	return blocking_notifier_chain_unregister(&net->nexthop.notifier_chain,
20058c2ecf20Sopenharmony_ci						  nb);
20068c2ecf20Sopenharmony_ci}
20078c2ecf20Sopenharmony_ciEXPORT_SYMBOL(unregister_nexthop_notifier);
20088c2ecf20Sopenharmony_ci
20098c2ecf20Sopenharmony_cistatic void __net_exit nexthop_net_exit(struct net *net)
20108c2ecf20Sopenharmony_ci{
20118c2ecf20Sopenharmony_ci	rtnl_lock();
20128c2ecf20Sopenharmony_ci	flush_all_nexthops(net);
20138c2ecf20Sopenharmony_ci	rtnl_unlock();
20148c2ecf20Sopenharmony_ci	kfree(net->nexthop.devhash);
20158c2ecf20Sopenharmony_ci}
20168c2ecf20Sopenharmony_ci
20178c2ecf20Sopenharmony_cistatic int __net_init nexthop_net_init(struct net *net)
20188c2ecf20Sopenharmony_ci{
20198c2ecf20Sopenharmony_ci	size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE;
20208c2ecf20Sopenharmony_ci
20218c2ecf20Sopenharmony_ci	net->nexthop.rb_root = RB_ROOT;
20228c2ecf20Sopenharmony_ci	net->nexthop.devhash = kzalloc(sz, GFP_KERNEL);
20238c2ecf20Sopenharmony_ci	if (!net->nexthop.devhash)
20248c2ecf20Sopenharmony_ci		return -ENOMEM;
20258c2ecf20Sopenharmony_ci	BLOCKING_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain);
20268c2ecf20Sopenharmony_ci
20278c2ecf20Sopenharmony_ci	return 0;
20288c2ecf20Sopenharmony_ci}
20298c2ecf20Sopenharmony_ci
20308c2ecf20Sopenharmony_cistatic struct pernet_operations nexthop_net_ops = {
20318c2ecf20Sopenharmony_ci	.init = nexthop_net_init,
20328c2ecf20Sopenharmony_ci	.exit = nexthop_net_exit,
20338c2ecf20Sopenharmony_ci};
20348c2ecf20Sopenharmony_ci
20358c2ecf20Sopenharmony_cistatic int __init nexthop_init(void)
20368c2ecf20Sopenharmony_ci{
20378c2ecf20Sopenharmony_ci	register_pernet_subsys(&nexthop_net_ops);
20388c2ecf20Sopenharmony_ci
20398c2ecf20Sopenharmony_ci	register_netdevice_notifier(&nh_netdev_notifier);
20408c2ecf20Sopenharmony_ci
20418c2ecf20Sopenharmony_ci	rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
20428c2ecf20Sopenharmony_ci	rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0);
20438c2ecf20Sopenharmony_ci	rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop,
20448c2ecf20Sopenharmony_ci		      rtm_dump_nexthop, 0);
20458c2ecf20Sopenharmony_ci
20468c2ecf20Sopenharmony_ci	rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
20478c2ecf20Sopenharmony_ci	rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
20488c2ecf20Sopenharmony_ci
20498c2ecf20Sopenharmony_ci	rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
20508c2ecf20Sopenharmony_ci	rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
20518c2ecf20Sopenharmony_ci
20528c2ecf20Sopenharmony_ci	return 0;
20538c2ecf20Sopenharmony_ci}
20548c2ecf20Sopenharmony_cisubsys_initcall(nexthop_init);
2055