xref: /linux/net/ipv4/nexthop.c (revision 7b01e53eee6dce7a8a6736e06b99b68cd0cc7a27)
1ab84be7eSDavid Ahern // SPDX-License-Identifier: GPL-2.0
2ab84be7eSDavid Ahern /* Generic nexthop implementation
3ab84be7eSDavid Ahern  *
4ab84be7eSDavid Ahern  * Copyright (c) 2017-19 Cumulus Networks
5ab84be7eSDavid Ahern  * Copyright (c) 2017-19 David Ahern <dsa@cumulusnetworks.com>
6ab84be7eSDavid Ahern  */
7ab84be7eSDavid Ahern 
8ab84be7eSDavid Ahern #include <linux/nexthop.h>
9ab84be7eSDavid Ahern #include <linux/rtnetlink.h>
10ab84be7eSDavid Ahern #include <linux/slab.h>
11430a0491SDavid Ahern #include <net/arp.h>
1253010f99SDavid Ahern #include <net/ipv6_stubs.h>
13b513bd03SDavid Ahern #include <net/lwtunnel.h>
14430a0491SDavid Ahern #include <net/ndisc.h>
15ab84be7eSDavid Ahern #include <net/nexthop.h>
16597cfe4fSDavid Ahern #include <net/route.h>
17ab84be7eSDavid Ahern #include <net/sock.h>
18ab84be7eSDavid Ahern 
19430a0491SDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh,
20430a0491SDavid Ahern 			   struct nl_info *nlinfo);
21430a0491SDavid Ahern 
22597cfe4fSDavid Ahern #define NH_DEV_HASHBITS  8
23597cfe4fSDavid Ahern #define NH_DEV_HASHSIZE (1U << NH_DEV_HASHBITS)
24597cfe4fSDavid Ahern 
25ab84be7eSDavid Ahern static const struct nla_policy rtm_nh_policy[NHA_MAX + 1] = {
26ab84be7eSDavid Ahern 	[NHA_ID]		= { .type = NLA_U32 },
27ab84be7eSDavid Ahern 	[NHA_GROUP]		= { .type = NLA_BINARY },
28ab84be7eSDavid Ahern 	[NHA_GROUP_TYPE]	= { .type = NLA_U16 },
29ab84be7eSDavid Ahern 	[NHA_BLACKHOLE]		= { .type = NLA_FLAG },
30ab84be7eSDavid Ahern 	[NHA_OIF]		= { .type = NLA_U32 },
31ab84be7eSDavid Ahern 	[NHA_GATEWAY]		= { .type = NLA_BINARY },
32ab84be7eSDavid Ahern 	[NHA_ENCAP_TYPE]	= { .type = NLA_U16 },
33ab84be7eSDavid Ahern 	[NHA_ENCAP]		= { .type = NLA_NESTED },
34ab84be7eSDavid Ahern 	[NHA_GROUPS]		= { .type = NLA_FLAG },
35ab84be7eSDavid Ahern 	[NHA_MASTER]		= { .type = NLA_U32 },
3638428d68SRoopa Prabhu 	[NHA_FDB]		= { .type = NLA_FLAG },
37ab84be7eSDavid Ahern };
38ab84be7eSDavid Ahern 
395ca474f2SIdo Schimmel static bool nexthop_notifiers_is_empty(struct net *net)
405ca474f2SIdo Schimmel {
415ca474f2SIdo Schimmel 	return !net->nexthop.notifier_chain.head;
425ca474f2SIdo Schimmel }
435ca474f2SIdo Schimmel 
445ca474f2SIdo Schimmel static void
455ca474f2SIdo Schimmel __nh_notifier_single_info_init(struct nh_notifier_single_info *nh_info,
465ca474f2SIdo Schimmel 			       const struct nexthop *nh)
475ca474f2SIdo Schimmel {
485ca474f2SIdo Schimmel 	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
495ca474f2SIdo Schimmel 
505ca474f2SIdo Schimmel 	nh_info->dev = nhi->fib_nhc.nhc_dev;
515ca474f2SIdo Schimmel 	nh_info->gw_family = nhi->fib_nhc.nhc_gw_family;
525ca474f2SIdo Schimmel 	if (nh_info->gw_family == AF_INET)
535ca474f2SIdo Schimmel 		nh_info->ipv4 = nhi->fib_nhc.nhc_gw.ipv4;
545ca474f2SIdo Schimmel 	else if (nh_info->gw_family == AF_INET6)
555ca474f2SIdo Schimmel 		nh_info->ipv6 = nhi->fib_nhc.nhc_gw.ipv6;
565ca474f2SIdo Schimmel 
575ca474f2SIdo Schimmel 	nh_info->is_reject = nhi->reject_nh;
585ca474f2SIdo Schimmel 	nh_info->is_fdb = nhi->fdb_nh;
595ca474f2SIdo Schimmel 	nh_info->has_encap = !!nhi->fib_nhc.nhc_lwtstate;
605ca474f2SIdo Schimmel }
615ca474f2SIdo Schimmel 
625ca474f2SIdo Schimmel static int nh_notifier_single_info_init(struct nh_notifier_info *info,
635ca474f2SIdo Schimmel 					const struct nexthop *nh)
645ca474f2SIdo Schimmel {
655ca474f2SIdo Schimmel 	info->nh = kzalloc(sizeof(*info->nh), GFP_KERNEL);
665ca474f2SIdo Schimmel 	if (!info->nh)
675ca474f2SIdo Schimmel 		return -ENOMEM;
685ca474f2SIdo Schimmel 
695ca474f2SIdo Schimmel 	__nh_notifier_single_info_init(info->nh, nh);
705ca474f2SIdo Schimmel 
715ca474f2SIdo Schimmel 	return 0;
725ca474f2SIdo Schimmel }
735ca474f2SIdo Schimmel 
745ca474f2SIdo Schimmel static void nh_notifier_single_info_fini(struct nh_notifier_info *info)
755ca474f2SIdo Schimmel {
765ca474f2SIdo Schimmel 	kfree(info->nh);
775ca474f2SIdo Schimmel }
785ca474f2SIdo Schimmel 
795ca474f2SIdo Schimmel static int nh_notifier_grp_info_init(struct nh_notifier_info *info,
805ca474f2SIdo Schimmel 				     const struct nexthop *nh)
815ca474f2SIdo Schimmel {
825ca474f2SIdo Schimmel 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
835ca474f2SIdo Schimmel 	u16 num_nh = nhg->num_nh;
845ca474f2SIdo Schimmel 	int i;
855ca474f2SIdo Schimmel 
865ca474f2SIdo Schimmel 	info->nh_grp = kzalloc(struct_size(info->nh_grp, nh_entries, num_nh),
875ca474f2SIdo Schimmel 			       GFP_KERNEL);
885ca474f2SIdo Schimmel 	if (!info->nh_grp)
895ca474f2SIdo Schimmel 		return -ENOMEM;
905ca474f2SIdo Schimmel 
915ca474f2SIdo Schimmel 	info->nh_grp->num_nh = num_nh;
925ca474f2SIdo Schimmel 	info->nh_grp->is_fdb = nhg->fdb_nh;
935ca474f2SIdo Schimmel 
945ca474f2SIdo Schimmel 	for (i = 0; i < num_nh; i++) {
955ca474f2SIdo Schimmel 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
965ca474f2SIdo Schimmel 
975ca474f2SIdo Schimmel 		info->nh_grp->nh_entries[i].id = nhge->nh->id;
985ca474f2SIdo Schimmel 		info->nh_grp->nh_entries[i].weight = nhge->weight;
995ca474f2SIdo Schimmel 		__nh_notifier_single_info_init(&info->nh_grp->nh_entries[i].nh,
1005ca474f2SIdo Schimmel 					       nhge->nh);
1015ca474f2SIdo Schimmel 	}
1025ca474f2SIdo Schimmel 
1035ca474f2SIdo Schimmel 	return 0;
1045ca474f2SIdo Schimmel }
1055ca474f2SIdo Schimmel 
1065ca474f2SIdo Schimmel static void nh_notifier_grp_info_fini(struct nh_notifier_info *info)
1075ca474f2SIdo Schimmel {
1085ca474f2SIdo Schimmel 	kfree(info->nh_grp);
1095ca474f2SIdo Schimmel }
1105ca474f2SIdo Schimmel 
1115ca474f2SIdo Schimmel static int nh_notifier_info_init(struct nh_notifier_info *info,
1125ca474f2SIdo Schimmel 				 const struct nexthop *nh)
1135ca474f2SIdo Schimmel {
1145ca474f2SIdo Schimmel 	info->id = nh->id;
1155ca474f2SIdo Schimmel 	info->is_grp = nh->is_group;
1165ca474f2SIdo Schimmel 
1175ca474f2SIdo Schimmel 	if (info->is_grp)
1185ca474f2SIdo Schimmel 		return nh_notifier_grp_info_init(info, nh);
1195ca474f2SIdo Schimmel 	else
1205ca474f2SIdo Schimmel 		return nh_notifier_single_info_init(info, nh);
1215ca474f2SIdo Schimmel }
1225ca474f2SIdo Schimmel 
1235ca474f2SIdo Schimmel static void nh_notifier_info_fini(struct nh_notifier_info *info)
1245ca474f2SIdo Schimmel {
1255ca474f2SIdo Schimmel 	if (info->is_grp)
1265ca474f2SIdo Schimmel 		nh_notifier_grp_info_fini(info);
1275ca474f2SIdo Schimmel 	else
1285ca474f2SIdo Schimmel 		nh_notifier_single_info_fini(info);
1295ca474f2SIdo Schimmel }
1305ca474f2SIdo Schimmel 
1318590ceedSRoopa Prabhu static int call_nexthop_notifiers(struct net *net,
132d8e79f1dSNathan Chancellor 				  enum nexthop_event_type event_type,
1333578d53dSIdo Schimmel 				  struct nexthop *nh,
1343578d53dSIdo Schimmel 				  struct netlink_ext_ack *extack)
1358590ceedSRoopa Prabhu {
1365ca474f2SIdo Schimmel 	struct nh_notifier_info info = {
1375ca474f2SIdo Schimmel 		.net = net,
1385ca474f2SIdo Schimmel 		.extack = extack,
1395ca474f2SIdo Schimmel 	};
1408590ceedSRoopa Prabhu 	int err;
1418590ceedSRoopa Prabhu 
1425ca474f2SIdo Schimmel 	ASSERT_RTNL();
1435ca474f2SIdo Schimmel 
1445ca474f2SIdo Schimmel 	if (nexthop_notifiers_is_empty(net))
1455ca474f2SIdo Schimmel 		return 0;
1465ca474f2SIdo Schimmel 
1475ca474f2SIdo Schimmel 	err = nh_notifier_info_init(&info, nh);
1485ca474f2SIdo Schimmel 	if (err) {
1495ca474f2SIdo Schimmel 		NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info");
1505ca474f2SIdo Schimmel 		return err;
1515ca474f2SIdo Schimmel 	}
1525ca474f2SIdo Schimmel 
15380690ec6SIdo Schimmel 	err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
1541ec69d18SIdo Schimmel 					   event_type, &info);
1555ca474f2SIdo Schimmel 	nh_notifier_info_fini(&info);
1565ca474f2SIdo Schimmel 
1578590ceedSRoopa Prabhu 	return notifier_to_errno(err);
1588590ceedSRoopa Prabhu }
1598590ceedSRoopa Prabhu 
160975ff7f3SIdo Schimmel static int call_nexthop_notifier(struct notifier_block *nb, struct net *net,
161975ff7f3SIdo Schimmel 				 enum nexthop_event_type event_type,
162975ff7f3SIdo Schimmel 				 struct nexthop *nh,
163975ff7f3SIdo Schimmel 				 struct netlink_ext_ack *extack)
164975ff7f3SIdo Schimmel {
165975ff7f3SIdo Schimmel 	struct nh_notifier_info info = {
166975ff7f3SIdo Schimmel 		.net = net,
167975ff7f3SIdo Schimmel 		.extack = extack,
168975ff7f3SIdo Schimmel 	};
169975ff7f3SIdo Schimmel 	int err;
170975ff7f3SIdo Schimmel 
171975ff7f3SIdo Schimmel 	err = nh_notifier_info_init(&info, nh);
172975ff7f3SIdo Schimmel 	if (err)
173975ff7f3SIdo Schimmel 		return err;
174975ff7f3SIdo Schimmel 
175975ff7f3SIdo Schimmel 	err = nb->notifier_call(nb, event_type, &info);
176975ff7f3SIdo Schimmel 	nh_notifier_info_fini(&info);
177975ff7f3SIdo Schimmel 
178975ff7f3SIdo Schimmel 	return notifier_to_errno(err);
179975ff7f3SIdo Schimmel }
180975ff7f3SIdo Schimmel 
181597cfe4fSDavid Ahern static unsigned int nh_dev_hashfn(unsigned int val)
182597cfe4fSDavid Ahern {
183597cfe4fSDavid Ahern 	unsigned int mask = NH_DEV_HASHSIZE - 1;
184597cfe4fSDavid Ahern 
185597cfe4fSDavid Ahern 	return (val ^
186597cfe4fSDavid Ahern 		(val >> NH_DEV_HASHBITS) ^
187597cfe4fSDavid Ahern 		(val >> (NH_DEV_HASHBITS * 2))) & mask;
188597cfe4fSDavid Ahern }
189597cfe4fSDavid Ahern 
190597cfe4fSDavid Ahern static void nexthop_devhash_add(struct net *net, struct nh_info *nhi)
191597cfe4fSDavid Ahern {
192597cfe4fSDavid Ahern 	struct net_device *dev = nhi->fib_nhc.nhc_dev;
193597cfe4fSDavid Ahern 	struct hlist_head *head;
194597cfe4fSDavid Ahern 	unsigned int hash;
195597cfe4fSDavid Ahern 
196597cfe4fSDavid Ahern 	WARN_ON(!dev);
197597cfe4fSDavid Ahern 
198597cfe4fSDavid Ahern 	hash = nh_dev_hashfn(dev->ifindex);
199597cfe4fSDavid Ahern 	head = &net->nexthop.devhash[hash];
200597cfe4fSDavid Ahern 	hlist_add_head(&nhi->dev_hash, head);
201597cfe4fSDavid Ahern }
202597cfe4fSDavid Ahern 
203430a0491SDavid Ahern static void nexthop_free_mpath(struct nexthop *nh)
204ab84be7eSDavid Ahern {
205430a0491SDavid Ahern 	struct nh_group *nhg;
206430a0491SDavid Ahern 	int i;
207430a0491SDavid Ahern 
208430a0491SDavid Ahern 	nhg = rcu_dereference_raw(nh->nh_grp);
20990f33bffSNikolay Aleksandrov 	for (i = 0; i < nhg->num_nh; ++i) {
21090f33bffSNikolay Aleksandrov 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
211430a0491SDavid Ahern 
21290f33bffSNikolay Aleksandrov 		WARN_ON(!list_empty(&nhge->nh_list));
21390f33bffSNikolay Aleksandrov 		nexthop_put(nhge->nh);
21490f33bffSNikolay Aleksandrov 	}
21590f33bffSNikolay Aleksandrov 
21690f33bffSNikolay Aleksandrov 	WARN_ON(nhg->spare == nhg);
21790f33bffSNikolay Aleksandrov 
21890f33bffSNikolay Aleksandrov 	kfree(nhg->spare);
219430a0491SDavid Ahern 	kfree(nhg);
220430a0491SDavid Ahern }
221430a0491SDavid Ahern 
222430a0491SDavid Ahern static void nexthop_free_single(struct nexthop *nh)
223430a0491SDavid Ahern {
224ab84be7eSDavid Ahern 	struct nh_info *nhi;
225ab84be7eSDavid Ahern 
226ab84be7eSDavid Ahern 	nhi = rcu_dereference_raw(nh->nh_info);
227597cfe4fSDavid Ahern 	switch (nhi->family) {
228597cfe4fSDavid Ahern 	case AF_INET:
229597cfe4fSDavid Ahern 		fib_nh_release(nh->net, &nhi->fib_nh);
230597cfe4fSDavid Ahern 		break;
23153010f99SDavid Ahern 	case AF_INET6:
23253010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(&nhi->fib6_nh);
23353010f99SDavid Ahern 		break;
234597cfe4fSDavid Ahern 	}
235ab84be7eSDavid Ahern 	kfree(nhi);
236430a0491SDavid Ahern }
237430a0491SDavid Ahern 
238430a0491SDavid Ahern void nexthop_free_rcu(struct rcu_head *head)
239430a0491SDavid Ahern {
240430a0491SDavid Ahern 	struct nexthop *nh = container_of(head, struct nexthop, rcu);
241430a0491SDavid Ahern 
242430a0491SDavid Ahern 	if (nh->is_group)
243430a0491SDavid Ahern 		nexthop_free_mpath(nh);
244430a0491SDavid Ahern 	else
245430a0491SDavid Ahern 		nexthop_free_single(nh);
246ab84be7eSDavid Ahern 
247ab84be7eSDavid Ahern 	kfree(nh);
248ab84be7eSDavid Ahern }
249ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_free_rcu);
250ab84be7eSDavid Ahern 
251ab84be7eSDavid Ahern static struct nexthop *nexthop_alloc(void)
252ab84be7eSDavid Ahern {
253ab84be7eSDavid Ahern 	struct nexthop *nh;
254ab84be7eSDavid Ahern 
255ab84be7eSDavid Ahern 	nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
256430a0491SDavid Ahern 	if (nh) {
2574c7e8084SDavid Ahern 		INIT_LIST_HEAD(&nh->fi_list);
258f88d8ea6SDavid Ahern 		INIT_LIST_HEAD(&nh->f6i_list);
259430a0491SDavid Ahern 		INIT_LIST_HEAD(&nh->grp_list);
26038428d68SRoopa Prabhu 		INIT_LIST_HEAD(&nh->fdb_list);
261430a0491SDavid Ahern 	}
262ab84be7eSDavid Ahern 	return nh;
263ab84be7eSDavid Ahern }
264ab84be7eSDavid Ahern 
265430a0491SDavid Ahern static struct nh_group *nexthop_grp_alloc(u16 num_nh)
266430a0491SDavid Ahern {
267430a0491SDavid Ahern 	struct nh_group *nhg;
268430a0491SDavid Ahern 
269d7d49dc7SIdo Schimmel 	nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL);
270430a0491SDavid Ahern 	if (nhg)
271430a0491SDavid Ahern 		nhg->num_nh = num_nh;
272430a0491SDavid Ahern 
273430a0491SDavid Ahern 	return nhg;
274430a0491SDavid Ahern }
275430a0491SDavid Ahern 
276ab84be7eSDavid Ahern static void nh_base_seq_inc(struct net *net)
277ab84be7eSDavid Ahern {
278ab84be7eSDavid Ahern 	while (++net->nexthop.seq == 0)
279ab84be7eSDavid Ahern 		;
280ab84be7eSDavid Ahern }
281ab84be7eSDavid Ahern 
282ab84be7eSDavid Ahern /* no reference taken; rcu lock or rtnl must be held */
283ab84be7eSDavid Ahern struct nexthop *nexthop_find_by_id(struct net *net, u32 id)
284ab84be7eSDavid Ahern {
285ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
286ab84be7eSDavid Ahern 
287ab84be7eSDavid Ahern 	pp = &net->nexthop.rb_root.rb_node;
288ab84be7eSDavid Ahern 	while (1) {
289ab84be7eSDavid Ahern 		struct nexthop *nh;
290ab84be7eSDavid Ahern 
291ab84be7eSDavid Ahern 		next = rcu_dereference_raw(*pp);
292ab84be7eSDavid Ahern 		if (!next)
293ab84be7eSDavid Ahern 			break;
294ab84be7eSDavid Ahern 		parent = next;
295ab84be7eSDavid Ahern 
296ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
297ab84be7eSDavid Ahern 		if (id < nh->id)
298ab84be7eSDavid Ahern 			pp = &next->rb_left;
299ab84be7eSDavid Ahern 		else if (id > nh->id)
300ab84be7eSDavid Ahern 			pp = &next->rb_right;
301ab84be7eSDavid Ahern 		else
302ab84be7eSDavid Ahern 			return nh;
303ab84be7eSDavid Ahern 	}
304ab84be7eSDavid Ahern 	return NULL;
305ab84be7eSDavid Ahern }
306ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_find_by_id);
307ab84be7eSDavid Ahern 
308ab84be7eSDavid Ahern /* used for auto id allocation; called with rtnl held */
309ab84be7eSDavid Ahern static u32 nh_find_unused_id(struct net *net)
310ab84be7eSDavid Ahern {
311ab84be7eSDavid Ahern 	u32 id_start = net->nexthop.last_id_allocated;
312ab84be7eSDavid Ahern 
313ab84be7eSDavid Ahern 	while (1) {
314ab84be7eSDavid Ahern 		net->nexthop.last_id_allocated++;
315ab84be7eSDavid Ahern 		if (net->nexthop.last_id_allocated == id_start)
316ab84be7eSDavid Ahern 			break;
317ab84be7eSDavid Ahern 
318ab84be7eSDavid Ahern 		if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated))
319ab84be7eSDavid Ahern 			return net->nexthop.last_id_allocated;
320ab84be7eSDavid Ahern 	}
321ab84be7eSDavid Ahern 	return 0;
322ab84be7eSDavid Ahern }
323ab84be7eSDavid Ahern 
324430a0491SDavid Ahern static int nla_put_nh_group(struct sk_buff *skb, struct nh_group *nhg)
325430a0491SDavid Ahern {
326430a0491SDavid Ahern 	struct nexthop_grp *p;
327430a0491SDavid Ahern 	size_t len = nhg->num_nh * sizeof(*p);
328430a0491SDavid Ahern 	struct nlattr *nla;
329430a0491SDavid Ahern 	u16 group_type = 0;
330430a0491SDavid Ahern 	int i;
331430a0491SDavid Ahern 
332430a0491SDavid Ahern 	if (nhg->mpath)
333430a0491SDavid Ahern 		group_type = NEXTHOP_GRP_TYPE_MPATH;
334430a0491SDavid Ahern 
335430a0491SDavid Ahern 	if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type))
336430a0491SDavid Ahern 		goto nla_put_failure;
337430a0491SDavid Ahern 
338430a0491SDavid Ahern 	nla = nla_reserve(skb, NHA_GROUP, len);
339430a0491SDavid Ahern 	if (!nla)
340430a0491SDavid Ahern 		goto nla_put_failure;
341430a0491SDavid Ahern 
342430a0491SDavid Ahern 	p = nla_data(nla);
343430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
344430a0491SDavid Ahern 		p->id = nhg->nh_entries[i].nh->id;
345430a0491SDavid Ahern 		p->weight = nhg->nh_entries[i].weight - 1;
346430a0491SDavid Ahern 		p += 1;
347430a0491SDavid Ahern 	}
348430a0491SDavid Ahern 
349430a0491SDavid Ahern 	return 0;
350430a0491SDavid Ahern 
351430a0491SDavid Ahern nla_put_failure:
352430a0491SDavid Ahern 	return -EMSGSIZE;
353430a0491SDavid Ahern }
354430a0491SDavid Ahern 
355ab84be7eSDavid Ahern static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
356ab84be7eSDavid Ahern 			int event, u32 portid, u32 seq, unsigned int nlflags)
357ab84be7eSDavid Ahern {
35853010f99SDavid Ahern 	struct fib6_nh *fib6_nh;
359597cfe4fSDavid Ahern 	struct fib_nh *fib_nh;
360ab84be7eSDavid Ahern 	struct nlmsghdr *nlh;
361ab84be7eSDavid Ahern 	struct nh_info *nhi;
362ab84be7eSDavid Ahern 	struct nhmsg *nhm;
363ab84be7eSDavid Ahern 
364ab84be7eSDavid Ahern 	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
365ab84be7eSDavid Ahern 	if (!nlh)
366ab84be7eSDavid Ahern 		return -EMSGSIZE;
367ab84be7eSDavid Ahern 
368ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
369ab84be7eSDavid Ahern 	nhm->nh_family = AF_UNSPEC;
370ab84be7eSDavid Ahern 	nhm->nh_flags = nh->nh_flags;
371ab84be7eSDavid Ahern 	nhm->nh_protocol = nh->protocol;
372ab84be7eSDavid Ahern 	nhm->nh_scope = 0;
373ab84be7eSDavid Ahern 	nhm->resvd = 0;
374ab84be7eSDavid Ahern 
375ab84be7eSDavid Ahern 	if (nla_put_u32(skb, NHA_ID, nh->id))
376ab84be7eSDavid Ahern 		goto nla_put_failure;
377ab84be7eSDavid Ahern 
378430a0491SDavid Ahern 	if (nh->is_group) {
379430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
380430a0491SDavid Ahern 
381ce9ac056SDavid Ahern 		if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB))
382ce9ac056SDavid Ahern 			goto nla_put_failure;
383430a0491SDavid Ahern 		if (nla_put_nh_group(skb, nhg))
384430a0491SDavid Ahern 			goto nla_put_failure;
385430a0491SDavid Ahern 		goto out;
386430a0491SDavid Ahern 	}
387430a0491SDavid Ahern 
388ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
389ab84be7eSDavid Ahern 	nhm->nh_family = nhi->family;
390ab84be7eSDavid Ahern 	if (nhi->reject_nh) {
391ab84be7eSDavid Ahern 		if (nla_put_flag(skb, NHA_BLACKHOLE))
392ab84be7eSDavid Ahern 			goto nla_put_failure;
393ab84be7eSDavid Ahern 		goto out;
394ce9ac056SDavid Ahern 	} else if (nhi->fdb_nh) {
395ce9ac056SDavid Ahern 		if (nla_put_flag(skb, NHA_FDB))
396ce9ac056SDavid Ahern 			goto nla_put_failure;
397ce9ac056SDavid Ahern 	} else {
398597cfe4fSDavid Ahern 		const struct net_device *dev;
399597cfe4fSDavid Ahern 
400597cfe4fSDavid Ahern 		dev = nhi->fib_nhc.nhc_dev;
401597cfe4fSDavid Ahern 		if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex))
402597cfe4fSDavid Ahern 			goto nla_put_failure;
403597cfe4fSDavid Ahern 	}
404597cfe4fSDavid Ahern 
405597cfe4fSDavid Ahern 	nhm->nh_scope = nhi->fib_nhc.nhc_scope;
406597cfe4fSDavid Ahern 	switch (nhi->family) {
407597cfe4fSDavid Ahern 	case AF_INET:
408597cfe4fSDavid Ahern 		fib_nh = &nhi->fib_nh;
409597cfe4fSDavid Ahern 		if (fib_nh->fib_nh_gw_family &&
41033d80996SIdo Schimmel 		    nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4))
411597cfe4fSDavid Ahern 			goto nla_put_failure;
412597cfe4fSDavid Ahern 		break;
41353010f99SDavid Ahern 
41453010f99SDavid Ahern 	case AF_INET6:
41553010f99SDavid Ahern 		fib6_nh = &nhi->fib6_nh;
41653010f99SDavid Ahern 		if (fib6_nh->fib_nh_gw_family &&
41753010f99SDavid Ahern 		    nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6))
41853010f99SDavid Ahern 			goto nla_put_failure;
41953010f99SDavid Ahern 		break;
420ab84be7eSDavid Ahern 	}
421ab84be7eSDavid Ahern 
422b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate &&
423b513bd03SDavid Ahern 	    lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate,
424b513bd03SDavid Ahern 				NHA_ENCAP, NHA_ENCAP_TYPE) < 0)
425b513bd03SDavid Ahern 		goto nla_put_failure;
426b513bd03SDavid Ahern 
427ab84be7eSDavid Ahern out:
428ab84be7eSDavid Ahern 	nlmsg_end(skb, nlh);
429ab84be7eSDavid Ahern 	return 0;
430ab84be7eSDavid Ahern 
431ab84be7eSDavid Ahern nla_put_failure:
432d69100b8SStephen Worley 	nlmsg_cancel(skb, nlh);
433ab84be7eSDavid Ahern 	return -EMSGSIZE;
434ab84be7eSDavid Ahern }
435ab84be7eSDavid Ahern 
436430a0491SDavid Ahern static size_t nh_nlmsg_size_grp(struct nexthop *nh)
437430a0491SDavid Ahern {
438430a0491SDavid Ahern 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
439430a0491SDavid Ahern 	size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh;
440430a0491SDavid Ahern 
441430a0491SDavid Ahern 	return nla_total_size(sz) +
442430a0491SDavid Ahern 	       nla_total_size(2);  /* NHA_GROUP_TYPE */
443430a0491SDavid Ahern }
444430a0491SDavid Ahern 
445430a0491SDavid Ahern static size_t nh_nlmsg_size_single(struct nexthop *nh)
446ab84be7eSDavid Ahern {
447597cfe4fSDavid Ahern 	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
448430a0491SDavid Ahern 	size_t sz;
449ab84be7eSDavid Ahern 
450ab84be7eSDavid Ahern 	/* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE
451ab84be7eSDavid Ahern 	 * are mutually exclusive
452ab84be7eSDavid Ahern 	 */
453430a0491SDavid Ahern 	sz = nla_total_size(4);  /* NHA_OIF */
454ab84be7eSDavid Ahern 
455597cfe4fSDavid Ahern 	switch (nhi->family) {
456597cfe4fSDavid Ahern 	case AF_INET:
457597cfe4fSDavid Ahern 		if (nhi->fib_nh.fib_nh_gw_family)
458597cfe4fSDavid Ahern 			sz += nla_total_size(4);  /* NHA_GATEWAY */
459597cfe4fSDavid Ahern 		break;
46053010f99SDavid Ahern 
46153010f99SDavid Ahern 	case AF_INET6:
46253010f99SDavid Ahern 		/* NHA_GATEWAY */
46353010f99SDavid Ahern 		if (nhi->fib6_nh.fib_nh_gw_family)
46453010f99SDavid Ahern 			sz += nla_total_size(sizeof(const struct in6_addr));
46553010f99SDavid Ahern 		break;
466597cfe4fSDavid Ahern 	}
467597cfe4fSDavid Ahern 
468b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate) {
469b513bd03SDavid Ahern 		sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
470b513bd03SDavid Ahern 		sz += nla_total_size(2);  /* NHA_ENCAP_TYPE */
471b513bd03SDavid Ahern 	}
472b513bd03SDavid Ahern 
473ab84be7eSDavid Ahern 	return sz;
474ab84be7eSDavid Ahern }
475ab84be7eSDavid Ahern 
476430a0491SDavid Ahern static size_t nh_nlmsg_size(struct nexthop *nh)
477430a0491SDavid Ahern {
478f9e95555SStephen Worley 	size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg));
479f9e95555SStephen Worley 
480f9e95555SStephen Worley 	sz += nla_total_size(4); /* NHA_ID */
481430a0491SDavid Ahern 
482430a0491SDavid Ahern 	if (nh->is_group)
483430a0491SDavid Ahern 		sz += nh_nlmsg_size_grp(nh);
484430a0491SDavid Ahern 	else
485430a0491SDavid Ahern 		sz += nh_nlmsg_size_single(nh);
486430a0491SDavid Ahern 
487430a0491SDavid Ahern 	return sz;
488430a0491SDavid Ahern }
489430a0491SDavid Ahern 
490ab84be7eSDavid Ahern static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info)
491ab84be7eSDavid Ahern {
492ab84be7eSDavid Ahern 	unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0;
493ab84be7eSDavid Ahern 	u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
494ab84be7eSDavid Ahern 	struct sk_buff *skb;
495ab84be7eSDavid Ahern 	int err = -ENOBUFS;
496ab84be7eSDavid Ahern 
497ab84be7eSDavid Ahern 	skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any());
498ab84be7eSDavid Ahern 	if (!skb)
499ab84be7eSDavid Ahern 		goto errout;
500ab84be7eSDavid Ahern 
501ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags);
502ab84be7eSDavid Ahern 	if (err < 0) {
503ab84be7eSDavid Ahern 		/* -EMSGSIZE implies BUG in nh_nlmsg_size() */
504ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
505ab84be7eSDavid Ahern 		kfree_skb(skb);
506ab84be7eSDavid Ahern 		goto errout;
507ab84be7eSDavid Ahern 	}
508ab84be7eSDavid Ahern 
509ab84be7eSDavid Ahern 	rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP,
510ab84be7eSDavid Ahern 		    info->nlh, gfp_any());
511ab84be7eSDavid Ahern 	return;
512ab84be7eSDavid Ahern errout:
513ab84be7eSDavid Ahern 	if (err < 0)
514ab84be7eSDavid Ahern 		rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err);
515ab84be7eSDavid Ahern }
516ab84be7eSDavid Ahern 
517430a0491SDavid Ahern static bool valid_group_nh(struct nexthop *nh, unsigned int npaths,
518ce9ac056SDavid Ahern 			   bool *is_fdb, struct netlink_ext_ack *extack)
519597cfe4fSDavid Ahern {
520430a0491SDavid Ahern 	if (nh->is_group) {
521430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
522430a0491SDavid Ahern 
523430a0491SDavid Ahern 		/* nested multipath (group within a group) is not
524430a0491SDavid Ahern 		 * supported
525430a0491SDavid Ahern 		 */
526430a0491SDavid Ahern 		if (nhg->mpath) {
527430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
528430a0491SDavid Ahern 				       "Multipath group can not be a nexthop within a group");
529430a0491SDavid Ahern 			return false;
530430a0491SDavid Ahern 		}
531ce9ac056SDavid Ahern 		*is_fdb = nhg->fdb_nh;
532430a0491SDavid Ahern 	} else {
533430a0491SDavid Ahern 		struct nh_info *nhi = rtnl_dereference(nh->nh_info);
534430a0491SDavid Ahern 
535430a0491SDavid Ahern 		if (nhi->reject_nh && npaths > 1) {
536430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
537430a0491SDavid Ahern 				       "Blackhole nexthop can not be used in a group with more than 1 path");
538430a0491SDavid Ahern 			return false;
539430a0491SDavid Ahern 		}
540ce9ac056SDavid Ahern 		*is_fdb = nhi->fdb_nh;
541430a0491SDavid Ahern 	}
542430a0491SDavid Ahern 
543430a0491SDavid Ahern 	return true;
544430a0491SDavid Ahern }
545430a0491SDavid Ahern 
54638428d68SRoopa Prabhu static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family,
54738428d68SRoopa Prabhu 				   struct netlink_ext_ack *extack)
54838428d68SRoopa Prabhu {
54938428d68SRoopa Prabhu 	struct nh_info *nhi;
55038428d68SRoopa Prabhu 
551ce9ac056SDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
552ce9ac056SDavid Ahern 
553ce9ac056SDavid Ahern 	if (!nhi->fdb_nh) {
55438428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops");
55538428d68SRoopa Prabhu 		return -EINVAL;
55638428d68SRoopa Prabhu 	}
55738428d68SRoopa Prabhu 
55838428d68SRoopa Prabhu 	if (*nh_family == AF_UNSPEC) {
55938428d68SRoopa Prabhu 		*nh_family = nhi->family;
56038428d68SRoopa Prabhu 	} else if (*nh_family != nhi->family) {
56138428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops");
56238428d68SRoopa Prabhu 		return -EINVAL;
56338428d68SRoopa Prabhu 	}
56438428d68SRoopa Prabhu 
56538428d68SRoopa Prabhu 	return 0;
56638428d68SRoopa Prabhu }
56738428d68SRoopa Prabhu 
568430a0491SDavid Ahern static int nh_check_attr_group(struct net *net, struct nlattr *tb[],
569430a0491SDavid Ahern 			       struct netlink_ext_ack *extack)
570430a0491SDavid Ahern {
571430a0491SDavid Ahern 	unsigned int len = nla_len(tb[NHA_GROUP]);
57238428d68SRoopa Prabhu 	u8 nh_family = AF_UNSPEC;
573430a0491SDavid Ahern 	struct nexthop_grp *nhg;
574430a0491SDavid Ahern 	unsigned int i, j;
57538428d68SRoopa Prabhu 	u8 nhg_fdb = 0;
576430a0491SDavid Ahern 
577eeaac363SNikolay Aleksandrov 	if (!len || len & (sizeof(struct nexthop_grp) - 1)) {
578430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
579430a0491SDavid Ahern 			       "Invalid length for nexthop group attribute");
580430a0491SDavid Ahern 		return -EINVAL;
581430a0491SDavid Ahern 	}
582430a0491SDavid Ahern 
583430a0491SDavid Ahern 	/* convert len to number of nexthop ids */
584430a0491SDavid Ahern 	len /= sizeof(*nhg);
585430a0491SDavid Ahern 
586430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
587430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
588430a0491SDavid Ahern 		if (nhg[i].resvd1 || nhg[i].resvd2) {
589430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0");
590430a0491SDavid Ahern 			return -EINVAL;
591430a0491SDavid Ahern 		}
592430a0491SDavid Ahern 		if (nhg[i].weight > 254) {
593430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid value for weight");
594430a0491SDavid Ahern 			return -EINVAL;
595430a0491SDavid Ahern 		}
596430a0491SDavid Ahern 		for (j = i + 1; j < len; ++j) {
597430a0491SDavid Ahern 			if (nhg[i].id == nhg[j].id) {
598430a0491SDavid Ahern 				NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group");
599430a0491SDavid Ahern 				return -EINVAL;
600430a0491SDavid Ahern 			}
601430a0491SDavid Ahern 		}
602430a0491SDavid Ahern 	}
603430a0491SDavid Ahern 
60438428d68SRoopa Prabhu 	if (tb[NHA_FDB])
60538428d68SRoopa Prabhu 		nhg_fdb = 1;
606430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
607430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
608430a0491SDavid Ahern 		struct nexthop *nh;
609ce9ac056SDavid Ahern 		bool is_fdb_nh;
610430a0491SDavid Ahern 
611430a0491SDavid Ahern 		nh = nexthop_find_by_id(net, nhg[i].id);
612430a0491SDavid Ahern 		if (!nh) {
613430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid nexthop id");
614430a0491SDavid Ahern 			return -EINVAL;
615430a0491SDavid Ahern 		}
616ce9ac056SDavid Ahern 		if (!valid_group_nh(nh, len, &is_fdb_nh, extack))
617430a0491SDavid Ahern 			return -EINVAL;
61838428d68SRoopa Prabhu 
61938428d68SRoopa Prabhu 		if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack))
62038428d68SRoopa Prabhu 			return -EINVAL;
62138428d68SRoopa Prabhu 
622ce9ac056SDavid Ahern 		if (!nhg_fdb && is_fdb_nh) {
62338428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops");
62438428d68SRoopa Prabhu 			return -EINVAL;
62538428d68SRoopa Prabhu 		}
626430a0491SDavid Ahern 	}
62784be69b8SDavid Ahern 	for (i = NHA_GROUP_TYPE + 1; i < __NHA_MAX; ++i) {
628430a0491SDavid Ahern 		if (!tb[i])
629430a0491SDavid Ahern 			continue;
63038428d68SRoopa Prabhu 		if (tb[NHA_FDB])
63138428d68SRoopa Prabhu 			continue;
632430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
633430a0491SDavid Ahern 			       "No other attributes can be set in nexthop groups");
634430a0491SDavid Ahern 		return -EINVAL;
635430a0491SDavid Ahern 	}
636430a0491SDavid Ahern 
637430a0491SDavid Ahern 	return 0;
638430a0491SDavid Ahern }
639430a0491SDavid Ahern 
640430a0491SDavid Ahern static bool ipv6_good_nh(const struct fib6_nh *nh)
641430a0491SDavid Ahern {
642430a0491SDavid Ahern 	int state = NUD_REACHABLE;
643430a0491SDavid Ahern 	struct neighbour *n;
644430a0491SDavid Ahern 
645430a0491SDavid Ahern 	rcu_read_lock_bh();
646430a0491SDavid Ahern 
647430a0491SDavid Ahern 	n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
648430a0491SDavid Ahern 	if (n)
649430a0491SDavid Ahern 		state = n->nud_state;
650430a0491SDavid Ahern 
651430a0491SDavid Ahern 	rcu_read_unlock_bh();
652430a0491SDavid Ahern 
653430a0491SDavid Ahern 	return !!(state & NUD_VALID);
654430a0491SDavid Ahern }
655430a0491SDavid Ahern 
656430a0491SDavid Ahern static bool ipv4_good_nh(const struct fib_nh *nh)
657430a0491SDavid Ahern {
658430a0491SDavid Ahern 	int state = NUD_REACHABLE;
659430a0491SDavid Ahern 	struct neighbour *n;
660430a0491SDavid Ahern 
661430a0491SDavid Ahern 	rcu_read_lock_bh();
662430a0491SDavid Ahern 
663430a0491SDavid Ahern 	n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
664430a0491SDavid Ahern 				      (__force u32)nh->fib_nh_gw4);
665430a0491SDavid Ahern 	if (n)
666430a0491SDavid Ahern 		state = n->nud_state;
667430a0491SDavid Ahern 
668430a0491SDavid Ahern 	rcu_read_unlock_bh();
669430a0491SDavid Ahern 
670430a0491SDavid Ahern 	return !!(state & NUD_VALID);
671430a0491SDavid Ahern }
672430a0491SDavid Ahern 
673430a0491SDavid Ahern struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
674430a0491SDavid Ahern {
675430a0491SDavid Ahern 	struct nexthop *rc = NULL;
676430a0491SDavid Ahern 	struct nh_group *nhg;
677430a0491SDavid Ahern 	int i;
678430a0491SDavid Ahern 
679430a0491SDavid Ahern 	if (!nh->is_group)
680430a0491SDavid Ahern 		return nh;
681430a0491SDavid Ahern 
682430a0491SDavid Ahern 	nhg = rcu_dereference(nh->nh_grp);
683430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
684430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
685430a0491SDavid Ahern 		struct nh_info *nhi;
686430a0491SDavid Ahern 
687430a0491SDavid Ahern 		if (hash > atomic_read(&nhge->upper_bound))
688430a0491SDavid Ahern 			continue;
689430a0491SDavid Ahern 
690ce9ac056SDavid Ahern 		nhi = rcu_dereference(nhge->nh->nh_info);
691ce9ac056SDavid Ahern 		if (nhi->fdb_nh)
69238428d68SRoopa Prabhu 			return nhge->nh;
69338428d68SRoopa Prabhu 
694430a0491SDavid Ahern 		/* nexthops always check if it is good and does
695430a0491SDavid Ahern 		 * not rely on a sysctl for this behavior
696430a0491SDavid Ahern 		 */
697430a0491SDavid Ahern 		switch (nhi->family) {
698430a0491SDavid Ahern 		case AF_INET:
699430a0491SDavid Ahern 			if (ipv4_good_nh(&nhi->fib_nh))
700430a0491SDavid Ahern 				return nhge->nh;
701430a0491SDavid Ahern 			break;
702430a0491SDavid Ahern 		case AF_INET6:
703430a0491SDavid Ahern 			if (ipv6_good_nh(&nhi->fib6_nh))
704430a0491SDavid Ahern 				return nhge->nh;
705430a0491SDavid Ahern 			break;
706430a0491SDavid Ahern 		}
707430a0491SDavid Ahern 
708430a0491SDavid Ahern 		if (!rc)
709430a0491SDavid Ahern 			rc = nhge->nh;
710430a0491SDavid Ahern 	}
711430a0491SDavid Ahern 
712430a0491SDavid Ahern 	return rc;
713430a0491SDavid Ahern }
714430a0491SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_select_path);
715430a0491SDavid Ahern 
716f88c9aa1SDavid Ahern int nexthop_for_each_fib6_nh(struct nexthop *nh,
717f88c9aa1SDavid Ahern 			     int (*cb)(struct fib6_nh *nh, void *arg),
718f88c9aa1SDavid Ahern 			     void *arg)
719f88c9aa1SDavid Ahern {
720f88c9aa1SDavid Ahern 	struct nh_info *nhi;
721f88c9aa1SDavid Ahern 	int err;
722f88c9aa1SDavid Ahern 
723f88c9aa1SDavid Ahern 	if (nh->is_group) {
724f88c9aa1SDavid Ahern 		struct nh_group *nhg;
725f88c9aa1SDavid Ahern 		int i;
726f88c9aa1SDavid Ahern 
727f88c9aa1SDavid Ahern 		nhg = rcu_dereference_rtnl(nh->nh_grp);
728f88c9aa1SDavid Ahern 		for (i = 0; i < nhg->num_nh; i++) {
729f88c9aa1SDavid Ahern 			struct nh_grp_entry *nhge = &nhg->nh_entries[i];
730f88c9aa1SDavid Ahern 
731f88c9aa1SDavid Ahern 			nhi = rcu_dereference_rtnl(nhge->nh->nh_info);
732f88c9aa1SDavid Ahern 			err = cb(&nhi->fib6_nh, arg);
733f88c9aa1SDavid Ahern 			if (err)
734f88c9aa1SDavid Ahern 				return err;
735f88c9aa1SDavid Ahern 		}
736f88c9aa1SDavid Ahern 	} else {
737f88c9aa1SDavid Ahern 		nhi = rcu_dereference_rtnl(nh->nh_info);
738f88c9aa1SDavid Ahern 		err = cb(&nhi->fib6_nh, arg);
739f88c9aa1SDavid Ahern 		if (err)
740f88c9aa1SDavid Ahern 			return err;
741f88c9aa1SDavid Ahern 	}
742f88c9aa1SDavid Ahern 
743f88c9aa1SDavid Ahern 	return 0;
744f88c9aa1SDavid Ahern }
745f88c9aa1SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh);
746f88c9aa1SDavid Ahern 
7477bf4796dSDavid Ahern static int check_src_addr(const struct in6_addr *saddr,
7487bf4796dSDavid Ahern 			  struct netlink_ext_ack *extack)
7497bf4796dSDavid Ahern {
7507bf4796dSDavid Ahern 	if (!ipv6_addr_any(saddr)) {
7517bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
7527bf4796dSDavid Ahern 		return -EINVAL;
7537bf4796dSDavid Ahern 	}
7547bf4796dSDavid Ahern 	return 0;
7557bf4796dSDavid Ahern }
7567bf4796dSDavid Ahern 
757f88d8ea6SDavid Ahern int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
758f88d8ea6SDavid Ahern 		       struct netlink_ext_ack *extack)
759f88d8ea6SDavid Ahern {
760f88d8ea6SDavid Ahern 	struct nh_info *nhi;
761ce9ac056SDavid Ahern 	bool is_fdb_nh;
76238428d68SRoopa Prabhu 
763f88d8ea6SDavid Ahern 	/* fib6_src is unique to a fib6_info and limits the ability to cache
764f88d8ea6SDavid Ahern 	 * routes in fib6_nh within a nexthop that is potentially shared
765f88d8ea6SDavid Ahern 	 * across multiple fib entries. If the config wants to use source
766f88d8ea6SDavid Ahern 	 * routing it can not use nexthop objects. mlxsw also does not allow
767f88d8ea6SDavid Ahern 	 * fib6_src on routes.
768f88d8ea6SDavid Ahern 	 */
7697bf4796dSDavid Ahern 	if (cfg && check_src_addr(&cfg->fc_src, extack) < 0)
770f88d8ea6SDavid Ahern 		return -EINVAL;
771f88d8ea6SDavid Ahern 
772f88d8ea6SDavid Ahern 	if (nh->is_group) {
773f88d8ea6SDavid Ahern 		struct nh_group *nhg;
774f88d8ea6SDavid Ahern 
775f88d8ea6SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
776f88d8ea6SDavid Ahern 		if (nhg->has_v4)
777f88d8ea6SDavid Ahern 			goto no_v4_nh;
778ce9ac056SDavid Ahern 		is_fdb_nh = nhg->fdb_nh;
779f88d8ea6SDavid Ahern 	} else {
780f88d8ea6SDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
781f88d8ea6SDavid Ahern 		if (nhi->family == AF_INET)
782f88d8ea6SDavid Ahern 			goto no_v4_nh;
783ce9ac056SDavid Ahern 		is_fdb_nh = nhi->fdb_nh;
784ce9ac056SDavid Ahern 	}
785ce9ac056SDavid Ahern 
786ce9ac056SDavid Ahern 	if (is_fdb_nh) {
787ce9ac056SDavid Ahern 		NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
788ce9ac056SDavid Ahern 		return -EINVAL;
789f88d8ea6SDavid Ahern 	}
790f88d8ea6SDavid Ahern 
791f88d8ea6SDavid Ahern 	return 0;
792f88d8ea6SDavid Ahern no_v4_nh:
793f88d8ea6SDavid Ahern 	NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
794f88d8ea6SDavid Ahern 	return -EINVAL;
795f88d8ea6SDavid Ahern }
796f88d8ea6SDavid Ahern EXPORT_SYMBOL_GPL(fib6_check_nexthop);
797f88d8ea6SDavid Ahern 
7987bf4796dSDavid Ahern /* if existing nexthop has ipv6 routes linked to it, need
7997bf4796dSDavid Ahern  * to verify this new spec works with ipv6
8007bf4796dSDavid Ahern  */
8017bf4796dSDavid Ahern static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new,
8027bf4796dSDavid Ahern 			      struct netlink_ext_ack *extack)
8037bf4796dSDavid Ahern {
8047bf4796dSDavid Ahern 	struct fib6_info *f6i;
8057bf4796dSDavid Ahern 
8067bf4796dSDavid Ahern 	if (list_empty(&old->f6i_list))
8077bf4796dSDavid Ahern 		return 0;
8087bf4796dSDavid Ahern 
8097bf4796dSDavid Ahern 	list_for_each_entry(f6i, &old->f6i_list, nh_list) {
8107bf4796dSDavid Ahern 		if (check_src_addr(&f6i->fib6_src.addr, extack) < 0)
8117bf4796dSDavid Ahern 			return -EINVAL;
8127bf4796dSDavid Ahern 	}
8137bf4796dSDavid Ahern 
8147bf4796dSDavid Ahern 	return fib6_check_nexthop(new, NULL, extack);
8157bf4796dSDavid Ahern }
8167bf4796dSDavid Ahern 
817ce9ac056SDavid Ahern static int nexthop_check_scope(struct nh_info *nhi, u8 scope,
8184c7e8084SDavid Ahern 			       struct netlink_ext_ack *extack)
8194c7e8084SDavid Ahern {
8204c7e8084SDavid Ahern 	if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) {
8214c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack,
8224c7e8084SDavid Ahern 			       "Route with host scope can not have a gateway");
8234c7e8084SDavid Ahern 		return -EINVAL;
8244c7e8084SDavid Ahern 	}
8254c7e8084SDavid Ahern 
8264c7e8084SDavid Ahern 	if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) {
8274c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop");
8284c7e8084SDavid Ahern 		return -EINVAL;
8294c7e8084SDavid Ahern 	}
8304c7e8084SDavid Ahern 
8314c7e8084SDavid Ahern 	return 0;
8324c7e8084SDavid Ahern }
8334c7e8084SDavid Ahern 
8344c7e8084SDavid Ahern /* Invoked by fib add code to verify nexthop by id is ok with
8354c7e8084SDavid Ahern  * config for prefix; parts of fib_check_nh not done when nexthop
8364c7e8084SDavid Ahern  * object is used.
8374c7e8084SDavid Ahern  */
8384c7e8084SDavid Ahern int fib_check_nexthop(struct nexthop *nh, u8 scope,
8394c7e8084SDavid Ahern 		      struct netlink_ext_ack *extack)
8404c7e8084SDavid Ahern {
841ce9ac056SDavid Ahern 	struct nh_info *nhi;
8424c7e8084SDavid Ahern 	int err = 0;
8434c7e8084SDavid Ahern 
844ce9ac056SDavid Ahern 	if (nh->is_group) {
845ce9ac056SDavid Ahern 		struct nh_group *nhg;
846ce9ac056SDavid Ahern 
847ce9ac056SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
848ce9ac056SDavid Ahern 		if (nhg->fdb_nh) {
84938428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
85038428d68SRoopa Prabhu 			err = -EINVAL;
85138428d68SRoopa Prabhu 			goto out;
85238428d68SRoopa Prabhu 		}
85338428d68SRoopa Prabhu 
8544c7e8084SDavid Ahern 		if (scope == RT_SCOPE_HOST) {
8554c7e8084SDavid Ahern 			NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops");
8564c7e8084SDavid Ahern 			err = -EINVAL;
8574c7e8084SDavid Ahern 			goto out;
8584c7e8084SDavid Ahern 		}
8594c7e8084SDavid Ahern 
8604c7e8084SDavid Ahern 		/* all nexthops in a group have the same scope */
861ce9ac056SDavid Ahern 		nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info);
862ce9ac056SDavid Ahern 		err = nexthop_check_scope(nhi, scope, extack);
8634c7e8084SDavid Ahern 	} else {
864ce9ac056SDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
865ce9ac056SDavid Ahern 		if (nhi->fdb_nh) {
866ce9ac056SDavid Ahern 			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
867ce9ac056SDavid Ahern 			err = -EINVAL;
868ce9ac056SDavid Ahern 			goto out;
8694c7e8084SDavid Ahern 		}
870ce9ac056SDavid Ahern 		err = nexthop_check_scope(nhi, scope, extack);
871ce9ac056SDavid Ahern 	}
872ce9ac056SDavid Ahern 
8734c7e8084SDavid Ahern out:
8744c7e8084SDavid Ahern 	return err;
8754c7e8084SDavid Ahern }
8764c7e8084SDavid Ahern 
8777bf4796dSDavid Ahern static int fib_check_nh_list(struct nexthop *old, struct nexthop *new,
8787bf4796dSDavid Ahern 			     struct netlink_ext_ack *extack)
8797bf4796dSDavid Ahern {
8807bf4796dSDavid Ahern 	struct fib_info *fi;
8817bf4796dSDavid Ahern 
8827bf4796dSDavid Ahern 	list_for_each_entry(fi, &old->fi_list, nh_list) {
8837bf4796dSDavid Ahern 		int err;
8847bf4796dSDavid Ahern 
8857bf4796dSDavid Ahern 		err = fib_check_nexthop(new, fi->fib_scope, extack);
8867bf4796dSDavid Ahern 		if (err)
8877bf4796dSDavid Ahern 			return err;
8887bf4796dSDavid Ahern 	}
8897bf4796dSDavid Ahern 	return 0;
8907bf4796dSDavid Ahern }
8917bf4796dSDavid Ahern 
892430a0491SDavid Ahern static void nh_group_rebalance(struct nh_group *nhg)
893430a0491SDavid Ahern {
894430a0491SDavid Ahern 	int total = 0;
895430a0491SDavid Ahern 	int w = 0;
896430a0491SDavid Ahern 	int i;
897430a0491SDavid Ahern 
898430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i)
899430a0491SDavid Ahern 		total += nhg->nh_entries[i].weight;
900430a0491SDavid Ahern 
901430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
902430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
903430a0491SDavid Ahern 		int upper_bound;
904430a0491SDavid Ahern 
905430a0491SDavid Ahern 		w += nhge->weight;
906430a0491SDavid Ahern 		upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1;
907430a0491SDavid Ahern 		atomic_set(&nhge->upper_bound, upper_bound);
908430a0491SDavid Ahern 	}
909430a0491SDavid Ahern }
910430a0491SDavid Ahern 
911ac21753aSDavid Ahern static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge,
912430a0491SDavid Ahern 				struct nl_info *nlinfo)
913430a0491SDavid Ahern {
91490f33bffSNikolay Aleksandrov 	struct nh_grp_entry *nhges, *new_nhges;
915ac21753aSDavid Ahern 	struct nexthop *nhp = nhge->nh_parent;
916833a1065SIdo Schimmel 	struct netlink_ext_ack extack;
917430a0491SDavid Ahern 	struct nexthop *nh = nhge->nh;
91890f33bffSNikolay Aleksandrov 	struct nh_group *nhg, *newg;
919833a1065SIdo Schimmel 	int i, j, err;
920430a0491SDavid Ahern 
921430a0491SDavid Ahern 	WARN_ON(!nh);
922430a0491SDavid Ahern 
923ac21753aSDavid Ahern 	nhg = rtnl_dereference(nhp->nh_grp);
92490f33bffSNikolay Aleksandrov 	newg = nhg->spare;
925430a0491SDavid Ahern 
92690f33bffSNikolay Aleksandrov 	/* last entry, keep it visible and remove the parent */
92790f33bffSNikolay Aleksandrov 	if (nhg->num_nh == 1) {
92890f33bffSNikolay Aleksandrov 		remove_nexthop(net, nhp, nlinfo);
929430a0491SDavid Ahern 		return;
93090f33bffSNikolay Aleksandrov 	}
931430a0491SDavid Ahern 
932863b2558SIdo Schimmel 	newg->has_v4 = false;
93390f33bffSNikolay Aleksandrov 	newg->mpath = nhg->mpath;
934ce9ac056SDavid Ahern 	newg->fdb_nh = nhg->fdb_nh;
93590f33bffSNikolay Aleksandrov 	newg->num_nh = nhg->num_nh;
936430a0491SDavid Ahern 
93790f33bffSNikolay Aleksandrov 	/* copy old entries to new except the one getting removed */
93890f33bffSNikolay Aleksandrov 	nhges = nhg->nh_entries;
93990f33bffSNikolay Aleksandrov 	new_nhges = newg->nh_entries;
94090f33bffSNikolay Aleksandrov 	for (i = 0, j = 0; i < nhg->num_nh; ++i) {
941863b2558SIdo Schimmel 		struct nh_info *nhi;
942863b2558SIdo Schimmel 
94390f33bffSNikolay Aleksandrov 		/* current nexthop getting removed */
94490f33bffSNikolay Aleksandrov 		if (nhg->nh_entries[i].nh == nh) {
94590f33bffSNikolay Aleksandrov 			newg->num_nh--;
94690f33bffSNikolay Aleksandrov 			continue;
94790f33bffSNikolay Aleksandrov 		}
948430a0491SDavid Ahern 
949863b2558SIdo Schimmel 		nhi = rtnl_dereference(nhges[i].nh->nh_info);
950863b2558SIdo Schimmel 		if (nhi->family == AF_INET)
951863b2558SIdo Schimmel 			newg->has_v4 = true;
952863b2558SIdo Schimmel 
95390f33bffSNikolay Aleksandrov 		list_del(&nhges[i].nh_list);
95490f33bffSNikolay Aleksandrov 		new_nhges[j].nh_parent = nhges[i].nh_parent;
95590f33bffSNikolay Aleksandrov 		new_nhges[j].nh = nhges[i].nh;
95690f33bffSNikolay Aleksandrov 		new_nhges[j].weight = nhges[i].weight;
95790f33bffSNikolay Aleksandrov 		list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list);
95890f33bffSNikolay Aleksandrov 		j++;
95990f33bffSNikolay Aleksandrov 	}
96090f33bffSNikolay Aleksandrov 
96190f33bffSNikolay Aleksandrov 	nh_group_rebalance(newg);
96290f33bffSNikolay Aleksandrov 	rcu_assign_pointer(nhp->nh_grp, newg);
96390f33bffSNikolay Aleksandrov 
96490f33bffSNikolay Aleksandrov 	list_del(&nhge->nh_list);
96590f33bffSNikolay Aleksandrov 	nexthop_put(nhge->nh);
966430a0491SDavid Ahern 
967833a1065SIdo Schimmel 	err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp, &extack);
968833a1065SIdo Schimmel 	if (err)
969833a1065SIdo Schimmel 		pr_err("%s\n", extack._msg);
970833a1065SIdo Schimmel 
971430a0491SDavid Ahern 	if (nlinfo)
972ac21753aSDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo);
973430a0491SDavid Ahern }
974430a0491SDavid Ahern 
975430a0491SDavid Ahern static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
976430a0491SDavid Ahern 				       struct nl_info *nlinfo)
977430a0491SDavid Ahern {
978430a0491SDavid Ahern 	struct nh_grp_entry *nhge, *tmp;
979430a0491SDavid Ahern 
980ac21753aSDavid Ahern 	list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list)
981ac21753aSDavid Ahern 		remove_nh_grp_entry(net, nhge, nlinfo);
982430a0491SDavid Ahern 
98390f33bffSNikolay Aleksandrov 	/* make sure all see the newly published array before releasing rtnl */
984df6afe2fSIdo Schimmel 	synchronize_net();
985430a0491SDavid Ahern }
986430a0491SDavid Ahern 
987430a0491SDavid Ahern static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
988430a0491SDavid Ahern {
989430a0491SDavid Ahern 	struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp);
990430a0491SDavid Ahern 	int i, num_nh = nhg->num_nh;
991430a0491SDavid Ahern 
992430a0491SDavid Ahern 	for (i = 0; i < num_nh; ++i) {
993430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
994430a0491SDavid Ahern 
995430a0491SDavid Ahern 		if (WARN_ON(!nhge->nh))
996430a0491SDavid Ahern 			continue;
997430a0491SDavid Ahern 
99890f33bffSNikolay Aleksandrov 		list_del_init(&nhge->nh_list);
999430a0491SDavid Ahern 	}
1000430a0491SDavid Ahern }
1001430a0491SDavid Ahern 
10027bf4796dSDavid Ahern /* not called for nexthop replace */
10034c7e8084SDavid Ahern static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
10044c7e8084SDavid Ahern {
1005f88d8ea6SDavid Ahern 	struct fib6_info *f6i, *tmp;
10064c7e8084SDavid Ahern 	bool do_flush = false;
10074c7e8084SDavid Ahern 	struct fib_info *fi;
10084c7e8084SDavid Ahern 
10094c7e8084SDavid Ahern 	list_for_each_entry(fi, &nh->fi_list, nh_list) {
10104c7e8084SDavid Ahern 		fi->fib_flags |= RTNH_F_DEAD;
10114c7e8084SDavid Ahern 		do_flush = true;
10124c7e8084SDavid Ahern 	}
10134c7e8084SDavid Ahern 	if (do_flush)
10144c7e8084SDavid Ahern 		fib_flush(net);
1015f88d8ea6SDavid Ahern 
1016f88d8ea6SDavid Ahern 	/* ip6_del_rt removes the entry from this list hence the _safe */
1017f88d8ea6SDavid Ahern 	list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
1018f88d8ea6SDavid Ahern 		/* __ip6_del_rt does a release, so do a hold here */
1019f88d8ea6SDavid Ahern 		fib6_info_hold(f6i);
10204f80116dSRoopa Prabhu 		ipv6_stub->ip6_del_rt(net, f6i,
10214f80116dSRoopa Prabhu 				      !net->ipv4.sysctl_nexthop_compat_mode);
1022f88d8ea6SDavid Ahern 	}
10234c7e8084SDavid Ahern }
10244c7e8084SDavid Ahern 
1025430a0491SDavid Ahern static void __remove_nexthop(struct net *net, struct nexthop *nh,
1026430a0491SDavid Ahern 			     struct nl_info *nlinfo)
1027430a0491SDavid Ahern {
10284c7e8084SDavid Ahern 	__remove_nexthop_fib(net, nh);
10294c7e8084SDavid Ahern 
1030430a0491SDavid Ahern 	if (nh->is_group) {
1031430a0491SDavid Ahern 		remove_nexthop_group(nh, nlinfo);
1032430a0491SDavid Ahern 	} else {
1033597cfe4fSDavid Ahern 		struct nh_info *nhi;
1034597cfe4fSDavid Ahern 
1035597cfe4fSDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
1036597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev)
1037597cfe4fSDavid Ahern 			hlist_del(&nhi->dev_hash);
1038430a0491SDavid Ahern 
1039430a0491SDavid Ahern 		remove_nexthop_from_groups(net, nh, nlinfo);
1040430a0491SDavid Ahern 	}
1041597cfe4fSDavid Ahern }
1042597cfe4fSDavid Ahern 
1043ab84be7eSDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh,
1044430a0491SDavid Ahern 			   struct nl_info *nlinfo)
1045ab84be7eSDavid Ahern {
10463578d53dSIdo Schimmel 	call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh, NULL);
10470695564bSIdo Schimmel 
1048ab84be7eSDavid Ahern 	/* remove from the tree */
1049ab84be7eSDavid Ahern 	rb_erase(&nh->rb_node, &net->nexthop.rb_root);
1050ab84be7eSDavid Ahern 
1051ab84be7eSDavid Ahern 	if (nlinfo)
1052ab84be7eSDavid Ahern 		nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo);
1053ab84be7eSDavid Ahern 
1054430a0491SDavid Ahern 	__remove_nexthop(net, nh, nlinfo);
1055ab84be7eSDavid Ahern 	nh_base_seq_inc(net);
1056ab84be7eSDavid Ahern 
1057ab84be7eSDavid Ahern 	nexthop_put(nh);
1058ab84be7eSDavid Ahern }
1059ab84be7eSDavid Ahern 
10607bf4796dSDavid Ahern /* if any FIB entries reference this nexthop, any dst entries
10617bf4796dSDavid Ahern  * need to be regenerated
10627bf4796dSDavid Ahern  */
10637bf4796dSDavid Ahern static void nh_rt_cache_flush(struct net *net, struct nexthop *nh)
10647bf4796dSDavid Ahern {
10657bf4796dSDavid Ahern 	struct fib6_info *f6i;
10667bf4796dSDavid Ahern 
10677bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list))
10687bf4796dSDavid Ahern 		rt_cache_flush(net);
10697bf4796dSDavid Ahern 
10707bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
10717bf4796dSDavid Ahern 		ipv6_stub->fib6_update_sernum(net, f6i);
10727bf4796dSDavid Ahern }
10737bf4796dSDavid Ahern 
10747bf4796dSDavid Ahern static int replace_nexthop_grp(struct net *net, struct nexthop *old,
10757bf4796dSDavid Ahern 			       struct nexthop *new,
10767bf4796dSDavid Ahern 			       struct netlink_ext_ack *extack)
10777bf4796dSDavid Ahern {
10787bf4796dSDavid Ahern 	struct nh_group *oldg, *newg;
1079d144cc5fSIdo Schimmel 	int i, err;
10807bf4796dSDavid Ahern 
10817bf4796dSDavid Ahern 	if (!new->is_group) {
10827bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop.");
10837bf4796dSDavid Ahern 		return -EINVAL;
10847bf4796dSDavid Ahern 	}
10857bf4796dSDavid Ahern 
1086d144cc5fSIdo Schimmel 	err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack);
1087d144cc5fSIdo Schimmel 	if (err)
1088d144cc5fSIdo Schimmel 		return err;
1089d144cc5fSIdo Schimmel 
10907bf4796dSDavid Ahern 	oldg = rtnl_dereference(old->nh_grp);
10917bf4796dSDavid Ahern 	newg = rtnl_dereference(new->nh_grp);
10927bf4796dSDavid Ahern 
10937bf4796dSDavid Ahern 	/* update parents - used by nexthop code for cleanup */
10947bf4796dSDavid Ahern 	for (i = 0; i < newg->num_nh; i++)
10957bf4796dSDavid Ahern 		newg->nh_entries[i].nh_parent = old;
10967bf4796dSDavid Ahern 
10977bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_grp, newg);
10987bf4796dSDavid Ahern 
10997bf4796dSDavid Ahern 	for (i = 0; i < oldg->num_nh; i++)
11007bf4796dSDavid Ahern 		oldg->nh_entries[i].nh_parent = new;
11017bf4796dSDavid Ahern 
11027bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_grp, oldg);
11037bf4796dSDavid Ahern 
11047bf4796dSDavid Ahern 	return 0;
11057bf4796dSDavid Ahern }
11067bf4796dSDavid Ahern 
1107885a3b15SIdo Schimmel static void nh_group_v4_update(struct nh_group *nhg)
1108885a3b15SIdo Schimmel {
1109885a3b15SIdo Schimmel 	struct nh_grp_entry *nhges;
1110885a3b15SIdo Schimmel 	bool has_v4 = false;
1111885a3b15SIdo Schimmel 	int i;
1112885a3b15SIdo Schimmel 
1113885a3b15SIdo Schimmel 	nhges = nhg->nh_entries;
1114885a3b15SIdo Schimmel 	for (i = 0; i < nhg->num_nh; i++) {
1115885a3b15SIdo Schimmel 		struct nh_info *nhi;
1116885a3b15SIdo Schimmel 
1117885a3b15SIdo Schimmel 		nhi = rtnl_dereference(nhges[i].nh->nh_info);
1118885a3b15SIdo Schimmel 		if (nhi->family == AF_INET)
1119885a3b15SIdo Schimmel 			has_v4 = true;
1120885a3b15SIdo Schimmel 	}
1121885a3b15SIdo Schimmel 	nhg->has_v4 = has_v4;
1122885a3b15SIdo Schimmel }
1123885a3b15SIdo Schimmel 
11247bf4796dSDavid Ahern static int replace_nexthop_single(struct net *net, struct nexthop *old,
11257bf4796dSDavid Ahern 				  struct nexthop *new,
11267bf4796dSDavid Ahern 				  struct netlink_ext_ack *extack)
11277bf4796dSDavid Ahern {
1128f17bc33dSIdo Schimmel 	u8 old_protocol, old_nh_flags;
11297bf4796dSDavid Ahern 	struct nh_info *oldi, *newi;
1130f17bc33dSIdo Schimmel 	struct nh_grp_entry *nhge;
11318c09c9f9SIdo Schimmel 	int err;
11327bf4796dSDavid Ahern 
11337bf4796dSDavid Ahern 	if (new->is_group) {
11347bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group.");
11357bf4796dSDavid Ahern 		return -EINVAL;
11367bf4796dSDavid Ahern 	}
11377bf4796dSDavid Ahern 
11388c09c9f9SIdo Schimmel 	err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack);
11398c09c9f9SIdo Schimmel 	if (err)
11408c09c9f9SIdo Schimmel 		return err;
11418c09c9f9SIdo Schimmel 
11428c09c9f9SIdo Schimmel 	/* Hardware flags were set on 'old' as 'new' is not in the red-black
11438c09c9f9SIdo Schimmel 	 * tree. Therefore, inherit the flags from 'old' to 'new'.
11448c09c9f9SIdo Schimmel 	 */
11458c09c9f9SIdo Schimmel 	new->nh_flags |= old->nh_flags & (RTNH_F_OFFLOAD | RTNH_F_TRAP);
11468c09c9f9SIdo Schimmel 
11477bf4796dSDavid Ahern 	oldi = rtnl_dereference(old->nh_info);
11487bf4796dSDavid Ahern 	newi = rtnl_dereference(new->nh_info);
11497bf4796dSDavid Ahern 
11507bf4796dSDavid Ahern 	newi->nh_parent = old;
11517bf4796dSDavid Ahern 	oldi->nh_parent = new;
11527bf4796dSDavid Ahern 
1153f17bc33dSIdo Schimmel 	old_protocol = old->protocol;
1154f17bc33dSIdo Schimmel 	old_nh_flags = old->nh_flags;
1155f17bc33dSIdo Schimmel 
11567bf4796dSDavid Ahern 	old->protocol = new->protocol;
11577bf4796dSDavid Ahern 	old->nh_flags = new->nh_flags;
11587bf4796dSDavid Ahern 
11597bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_info, newi);
11607bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_info, oldi);
11617bf4796dSDavid Ahern 
1162f17bc33dSIdo Schimmel 	/* Send a replace notification for all the groups using the nexthop. */
1163f17bc33dSIdo Schimmel 	list_for_each_entry(nhge, &old->grp_list, nh_list) {
1164f17bc33dSIdo Schimmel 		struct nexthop *nhp = nhge->nh_parent;
1165f17bc33dSIdo Schimmel 
1166f17bc33dSIdo Schimmel 		err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp,
1167f17bc33dSIdo Schimmel 					     extack);
1168f17bc33dSIdo Schimmel 		if (err)
1169f17bc33dSIdo Schimmel 			goto err_notify;
1170f17bc33dSIdo Schimmel 	}
1171f17bc33dSIdo Schimmel 
1172885a3b15SIdo Schimmel 	/* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially
1173885a3b15SIdo Schimmel 	 * update IPv4 indication in all the groups using the nexthop.
1174885a3b15SIdo Schimmel 	 */
1175885a3b15SIdo Schimmel 	if (oldi->family == AF_INET && newi->family == AF_INET6) {
1176885a3b15SIdo Schimmel 		list_for_each_entry(nhge, &old->grp_list, nh_list) {
1177885a3b15SIdo Schimmel 			struct nexthop *nhp = nhge->nh_parent;
1178885a3b15SIdo Schimmel 			struct nh_group *nhg;
1179885a3b15SIdo Schimmel 
1180885a3b15SIdo Schimmel 			nhg = rtnl_dereference(nhp->nh_grp);
1181885a3b15SIdo Schimmel 			nh_group_v4_update(nhg);
1182885a3b15SIdo Schimmel 		}
1183885a3b15SIdo Schimmel 	}
1184885a3b15SIdo Schimmel 
11857bf4796dSDavid Ahern 	return 0;
1186f17bc33dSIdo Schimmel 
1187f17bc33dSIdo Schimmel err_notify:
1188f17bc33dSIdo Schimmel 	rcu_assign_pointer(new->nh_info, newi);
1189f17bc33dSIdo Schimmel 	rcu_assign_pointer(old->nh_info, oldi);
1190f17bc33dSIdo Schimmel 	old->nh_flags = old_nh_flags;
1191f17bc33dSIdo Schimmel 	old->protocol = old_protocol;
1192f17bc33dSIdo Schimmel 	oldi->nh_parent = old;
1193f17bc33dSIdo Schimmel 	newi->nh_parent = new;
1194f17bc33dSIdo Schimmel 	list_for_each_entry_continue_reverse(nhge, &old->grp_list, nh_list) {
1195f17bc33dSIdo Schimmel 		struct nexthop *nhp = nhge->nh_parent;
1196f17bc33dSIdo Schimmel 
1197f17bc33dSIdo Schimmel 		call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp, extack);
1198f17bc33dSIdo Schimmel 	}
1199f17bc33dSIdo Schimmel 	call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, old, extack);
1200f17bc33dSIdo Schimmel 	return err;
12017bf4796dSDavid Ahern }
12027bf4796dSDavid Ahern 
12037bf4796dSDavid Ahern static void __nexthop_replace_notify(struct net *net, struct nexthop *nh,
12047bf4796dSDavid Ahern 				     struct nl_info *info)
12057bf4796dSDavid Ahern {
12067bf4796dSDavid Ahern 	struct fib6_info *f6i;
12077bf4796dSDavid Ahern 
12087bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list)) {
12097bf4796dSDavid Ahern 		struct fib_info *fi;
12107bf4796dSDavid Ahern 
12117bf4796dSDavid Ahern 		/* expectation is a few fib_info per nexthop and then
12127bf4796dSDavid Ahern 		 * a lot of routes per fib_info. So mark the fib_info
12137bf4796dSDavid Ahern 		 * and then walk the fib tables once
12147bf4796dSDavid Ahern 		 */
12157bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
12167bf4796dSDavid Ahern 			fi->nh_updated = true;
12177bf4796dSDavid Ahern 
12187bf4796dSDavid Ahern 		fib_info_notify_update(net, info);
12197bf4796dSDavid Ahern 
12207bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
12217bf4796dSDavid Ahern 			fi->nh_updated = false;
12227bf4796dSDavid Ahern 	}
12237bf4796dSDavid Ahern 
12247bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
12257bf4796dSDavid Ahern 		ipv6_stub->fib6_rt_update(net, f6i, info);
12267bf4796dSDavid Ahern }
12277bf4796dSDavid Ahern 
12287bf4796dSDavid Ahern /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries
12297bf4796dSDavid Ahern  * linked to this nexthop and for all groups that the nexthop
12307bf4796dSDavid Ahern  * is a member of
12317bf4796dSDavid Ahern  */
12327bf4796dSDavid Ahern static void nexthop_replace_notify(struct net *net, struct nexthop *nh,
12337bf4796dSDavid Ahern 				   struct nl_info *info)
12347bf4796dSDavid Ahern {
12357bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
12367bf4796dSDavid Ahern 
12377bf4796dSDavid Ahern 	__nexthop_replace_notify(net, nh, info);
12387bf4796dSDavid Ahern 
12397bf4796dSDavid Ahern 	list_for_each_entry(nhge, &nh->grp_list, nh_list)
12407bf4796dSDavid Ahern 		__nexthop_replace_notify(net, nhge->nh_parent, info);
12417bf4796dSDavid Ahern }
12427bf4796dSDavid Ahern 
1243ab84be7eSDavid Ahern static int replace_nexthop(struct net *net, struct nexthop *old,
1244ab84be7eSDavid Ahern 			   struct nexthop *new, struct netlink_ext_ack *extack)
1245ab84be7eSDavid Ahern {
12467bf4796dSDavid Ahern 	bool new_is_reject = false;
12477bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
12487bf4796dSDavid Ahern 	int err;
12497bf4796dSDavid Ahern 
12507bf4796dSDavid Ahern 	/* check that existing FIB entries are ok with the
12517bf4796dSDavid Ahern 	 * new nexthop definition
12527bf4796dSDavid Ahern 	 */
12537bf4796dSDavid Ahern 	err = fib_check_nh_list(old, new, extack);
12547bf4796dSDavid Ahern 	if (err)
12557bf4796dSDavid Ahern 		return err;
12567bf4796dSDavid Ahern 
12577bf4796dSDavid Ahern 	err = fib6_check_nh_list(old, new, extack);
12587bf4796dSDavid Ahern 	if (err)
12597bf4796dSDavid Ahern 		return err;
12607bf4796dSDavid Ahern 
12617bf4796dSDavid Ahern 	if (!new->is_group) {
12627bf4796dSDavid Ahern 		struct nh_info *nhi = rtnl_dereference(new->nh_info);
12637bf4796dSDavid Ahern 
12647bf4796dSDavid Ahern 		new_is_reject = nhi->reject_nh;
12657bf4796dSDavid Ahern 	}
12667bf4796dSDavid Ahern 
12677bf4796dSDavid Ahern 	list_for_each_entry(nhge, &old->grp_list, nh_list) {
12687bf4796dSDavid Ahern 		/* if new nexthop is a blackhole, any groups using this
12697bf4796dSDavid Ahern 		 * nexthop cannot have more than 1 path
12707bf4796dSDavid Ahern 		 */
12717bf4796dSDavid Ahern 		if (new_is_reject &&
12727bf4796dSDavid Ahern 		    nexthop_num_path(nhge->nh_parent) > 1) {
12737bf4796dSDavid Ahern 			NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path");
12747bf4796dSDavid Ahern 			return -EINVAL;
12757bf4796dSDavid Ahern 		}
12767bf4796dSDavid Ahern 
12777bf4796dSDavid Ahern 		err = fib_check_nh_list(nhge->nh_parent, new, extack);
12787bf4796dSDavid Ahern 		if (err)
12797bf4796dSDavid Ahern 			return err;
12807bf4796dSDavid Ahern 
12817bf4796dSDavid Ahern 		err = fib6_check_nh_list(nhge->nh_parent, new, extack);
12827bf4796dSDavid Ahern 		if (err)
12837bf4796dSDavid Ahern 			return err;
12847bf4796dSDavid Ahern 	}
12857bf4796dSDavid Ahern 
12867bf4796dSDavid Ahern 	if (old->is_group)
12877bf4796dSDavid Ahern 		err = replace_nexthop_grp(net, old, new, extack);
12887bf4796dSDavid Ahern 	else
12897bf4796dSDavid Ahern 		err = replace_nexthop_single(net, old, new, extack);
12907bf4796dSDavid Ahern 
12917bf4796dSDavid Ahern 	if (!err) {
12927bf4796dSDavid Ahern 		nh_rt_cache_flush(net, old);
12937bf4796dSDavid Ahern 
12947bf4796dSDavid Ahern 		__remove_nexthop(net, new, NULL);
12957bf4796dSDavid Ahern 		nexthop_put(new);
12967bf4796dSDavid Ahern 	}
12977bf4796dSDavid Ahern 
12987bf4796dSDavid Ahern 	return err;
1299ab84be7eSDavid Ahern }
1300ab84be7eSDavid Ahern 
1301ab84be7eSDavid Ahern /* called with rtnl_lock held */
1302ab84be7eSDavid Ahern static int insert_nexthop(struct net *net, struct nexthop *new_nh,
1303ab84be7eSDavid Ahern 			  struct nh_config *cfg, struct netlink_ext_ack *extack)
1304ab84be7eSDavid Ahern {
1305ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
1306ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
1307ab84be7eSDavid Ahern 	bool replace = !!(cfg->nlflags & NLM_F_REPLACE);
1308ab84be7eSDavid Ahern 	bool create = !!(cfg->nlflags & NLM_F_CREATE);
1309ab84be7eSDavid Ahern 	u32 new_id = new_nh->id;
13107bf4796dSDavid Ahern 	int replace_notify = 0;
1311ab84be7eSDavid Ahern 	int rc = -EEXIST;
1312ab84be7eSDavid Ahern 
1313ab84be7eSDavid Ahern 	pp = &root->rb_node;
1314ab84be7eSDavid Ahern 	while (1) {
1315ab84be7eSDavid Ahern 		struct nexthop *nh;
1316ab84be7eSDavid Ahern 
1317233c6378SIdo Schimmel 		next = *pp;
1318ab84be7eSDavid Ahern 		if (!next)
1319ab84be7eSDavid Ahern 			break;
1320ab84be7eSDavid Ahern 
1321ab84be7eSDavid Ahern 		parent = next;
1322ab84be7eSDavid Ahern 
1323ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
1324ab84be7eSDavid Ahern 		if (new_id < nh->id) {
1325ab84be7eSDavid Ahern 			pp = &next->rb_left;
1326ab84be7eSDavid Ahern 		} else if (new_id > nh->id) {
1327ab84be7eSDavid Ahern 			pp = &next->rb_right;
1328ab84be7eSDavid Ahern 		} else if (replace) {
1329ab84be7eSDavid Ahern 			rc = replace_nexthop(net, nh, new_nh, extack);
13307bf4796dSDavid Ahern 			if (!rc) {
1331ab84be7eSDavid Ahern 				new_nh = nh; /* send notification with old nh */
13327bf4796dSDavid Ahern 				replace_notify = 1;
13337bf4796dSDavid Ahern 			}
1334ab84be7eSDavid Ahern 			goto out;
1335ab84be7eSDavid Ahern 		} else {
1336ab84be7eSDavid Ahern 			/* id already exists and not a replace */
1337ab84be7eSDavid Ahern 			goto out;
1338ab84be7eSDavid Ahern 		}
1339ab84be7eSDavid Ahern 	}
1340ab84be7eSDavid Ahern 
1341ab84be7eSDavid Ahern 	if (replace && !create) {
1342ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists");
1343ab84be7eSDavid Ahern 		rc = -ENOENT;
1344ab84be7eSDavid Ahern 		goto out;
1345ab84be7eSDavid Ahern 	}
1346ab84be7eSDavid Ahern 
1347ab84be7eSDavid Ahern 	rb_link_node_rcu(&new_nh->rb_node, parent, pp);
1348ab84be7eSDavid Ahern 	rb_insert_color(&new_nh->rb_node, root);
1349732d167bSIdo Schimmel 
1350732d167bSIdo Schimmel 	rc = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new_nh, extack);
1351732d167bSIdo Schimmel 	if (rc)
1352732d167bSIdo Schimmel 		rb_erase(&new_nh->rb_node, &net->nexthop.rb_root);
1353732d167bSIdo Schimmel 
1354ab84be7eSDavid Ahern out:
1355ab84be7eSDavid Ahern 	if (!rc) {
1356ab84be7eSDavid Ahern 		nh_base_seq_inc(net);
1357ab84be7eSDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo);
13584f80116dSRoopa Prabhu 		if (replace_notify && net->ipv4.sysctl_nexthop_compat_mode)
13597bf4796dSDavid Ahern 			nexthop_replace_notify(net, new_nh, &cfg->nlinfo);
1360ab84be7eSDavid Ahern 	}
1361ab84be7eSDavid Ahern 
1362ab84be7eSDavid Ahern 	return rc;
1363ab84be7eSDavid Ahern }
1364ab84be7eSDavid Ahern 
1365597cfe4fSDavid Ahern /* rtnl */
1366597cfe4fSDavid Ahern /* remove all nexthops tied to a device being deleted */
1367597cfe4fSDavid Ahern static void nexthop_flush_dev(struct net_device *dev)
1368597cfe4fSDavid Ahern {
1369597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
1370597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
1371597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
1372597cfe4fSDavid Ahern 	struct hlist_node *n;
1373597cfe4fSDavid Ahern 	struct nh_info *nhi;
1374597cfe4fSDavid Ahern 
1375597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
1376597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev != dev)
1377597cfe4fSDavid Ahern 			continue;
1378597cfe4fSDavid Ahern 
1379430a0491SDavid Ahern 		remove_nexthop(net, nhi->nh_parent, NULL);
1380597cfe4fSDavid Ahern 	}
1381597cfe4fSDavid Ahern }
1382597cfe4fSDavid Ahern 
1383ab84be7eSDavid Ahern /* rtnl; called when net namespace is deleted */
1384ab84be7eSDavid Ahern static void flush_all_nexthops(struct net *net)
1385ab84be7eSDavid Ahern {
1386ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
1387ab84be7eSDavid Ahern 	struct rb_node *node;
1388ab84be7eSDavid Ahern 	struct nexthop *nh;
1389ab84be7eSDavid Ahern 
1390ab84be7eSDavid Ahern 	while ((node = rb_first(root))) {
1391ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
1392430a0491SDavid Ahern 		remove_nexthop(net, nh, NULL);
1393ab84be7eSDavid Ahern 		cond_resched();
1394ab84be7eSDavid Ahern 	}
1395ab84be7eSDavid Ahern }
1396ab84be7eSDavid Ahern 
1397430a0491SDavid Ahern static struct nexthop *nexthop_create_group(struct net *net,
1398430a0491SDavid Ahern 					    struct nh_config *cfg)
1399430a0491SDavid Ahern {
1400430a0491SDavid Ahern 	struct nlattr *grps_attr = cfg->nh_grp;
1401430a0491SDavid Ahern 	struct nexthop_grp *entry = nla_data(grps_attr);
140290f33bffSNikolay Aleksandrov 	u16 num_nh = nla_len(grps_attr) / sizeof(*entry);
1403430a0491SDavid Ahern 	struct nh_group *nhg;
1404430a0491SDavid Ahern 	struct nexthop *nh;
1405430a0491SDavid Ahern 	int i;
1406430a0491SDavid Ahern 
1407eeaac363SNikolay Aleksandrov 	if (WARN_ON(!num_nh))
1408eeaac363SNikolay Aleksandrov 		return ERR_PTR(-EINVAL);
1409eeaac363SNikolay Aleksandrov 
1410430a0491SDavid Ahern 	nh = nexthop_alloc();
1411430a0491SDavid Ahern 	if (!nh)
1412430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
1413430a0491SDavid Ahern 
1414430a0491SDavid Ahern 	nh->is_group = 1;
1415430a0491SDavid Ahern 
141690f33bffSNikolay Aleksandrov 	nhg = nexthop_grp_alloc(num_nh);
1417430a0491SDavid Ahern 	if (!nhg) {
1418430a0491SDavid Ahern 		kfree(nh);
1419430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
1420430a0491SDavid Ahern 	}
1421430a0491SDavid Ahern 
142290f33bffSNikolay Aleksandrov 	/* spare group used for removals */
142390f33bffSNikolay Aleksandrov 	nhg->spare = nexthop_grp_alloc(num_nh);
1424dafe2078SPatrick Eigensatz 	if (!nhg->spare) {
142590f33bffSNikolay Aleksandrov 		kfree(nhg);
142690f33bffSNikolay Aleksandrov 		kfree(nh);
1427dafe2078SPatrick Eigensatz 		return ERR_PTR(-ENOMEM);
142890f33bffSNikolay Aleksandrov 	}
142990f33bffSNikolay Aleksandrov 	nhg->spare->spare = nhg;
143090f33bffSNikolay Aleksandrov 
1431430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
1432430a0491SDavid Ahern 		struct nexthop *nhe;
1433430a0491SDavid Ahern 		struct nh_info *nhi;
1434430a0491SDavid Ahern 
1435430a0491SDavid Ahern 		nhe = nexthop_find_by_id(net, entry[i].id);
1436430a0491SDavid Ahern 		if (!nexthop_get(nhe))
1437430a0491SDavid Ahern 			goto out_no_nh;
1438430a0491SDavid Ahern 
1439430a0491SDavid Ahern 		nhi = rtnl_dereference(nhe->nh_info);
1440430a0491SDavid Ahern 		if (nhi->family == AF_INET)
1441430a0491SDavid Ahern 			nhg->has_v4 = true;
1442430a0491SDavid Ahern 
1443430a0491SDavid Ahern 		nhg->nh_entries[i].nh = nhe;
1444430a0491SDavid Ahern 		nhg->nh_entries[i].weight = entry[i].weight + 1;
1445430a0491SDavid Ahern 		list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list);
1446430a0491SDavid Ahern 		nhg->nh_entries[i].nh_parent = nh;
1447430a0491SDavid Ahern 	}
1448430a0491SDavid Ahern 
1449430a0491SDavid Ahern 	if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) {
1450430a0491SDavid Ahern 		nhg->mpath = 1;
1451430a0491SDavid Ahern 		nh_group_rebalance(nhg);
1452430a0491SDavid Ahern 	}
1453430a0491SDavid Ahern 
145438428d68SRoopa Prabhu 	if (cfg->nh_fdb)
1455ce9ac056SDavid Ahern 		nhg->fdb_nh = 1;
145638428d68SRoopa Prabhu 
1457430a0491SDavid Ahern 	rcu_assign_pointer(nh->nh_grp, nhg);
1458430a0491SDavid Ahern 
1459430a0491SDavid Ahern 	return nh;
1460430a0491SDavid Ahern 
1461430a0491SDavid Ahern out_no_nh:
1462*7b01e53eSIdo Schimmel 	for (i--; i >= 0; --i) {
1463*7b01e53eSIdo Schimmel 		list_del(&nhg->nh_entries[i].nh_list);
1464430a0491SDavid Ahern 		nexthop_put(nhg->nh_entries[i].nh);
1465*7b01e53eSIdo Schimmel 	}
1466430a0491SDavid Ahern 
146790f33bffSNikolay Aleksandrov 	kfree(nhg->spare);
1468430a0491SDavid Ahern 	kfree(nhg);
1469430a0491SDavid Ahern 	kfree(nh);
1470430a0491SDavid Ahern 
1471430a0491SDavid Ahern 	return ERR_PTR(-ENOENT);
1472430a0491SDavid Ahern }
1473430a0491SDavid Ahern 
1474597cfe4fSDavid Ahern static int nh_create_ipv4(struct net *net, struct nexthop *nh,
1475597cfe4fSDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
1476597cfe4fSDavid Ahern 			  struct netlink_ext_ack *extack)
1477597cfe4fSDavid Ahern {
1478597cfe4fSDavid Ahern 	struct fib_nh *fib_nh = &nhi->fib_nh;
1479597cfe4fSDavid Ahern 	struct fib_config fib_cfg = {
1480597cfe4fSDavid Ahern 		.fc_oif   = cfg->nh_ifindex,
1481597cfe4fSDavid Ahern 		.fc_gw4   = cfg->gw.ipv4,
1482597cfe4fSDavid Ahern 		.fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0,
1483597cfe4fSDavid Ahern 		.fc_flags = cfg->nh_flags,
1484b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
1485b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
1486597cfe4fSDavid Ahern 	};
148738428d68SRoopa Prabhu 	u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN);
1488c76c9925SColin Ian King 	int err;
1489597cfe4fSDavid Ahern 
1490597cfe4fSDavid Ahern 	err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack);
1491597cfe4fSDavid Ahern 	if (err) {
1492597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
1493597cfe4fSDavid Ahern 		goto out;
1494597cfe4fSDavid Ahern 	}
1495597cfe4fSDavid Ahern 
1496ce9ac056SDavid Ahern 	if (nhi->fdb_nh)
149738428d68SRoopa Prabhu 		goto out;
149838428d68SRoopa Prabhu 
1499597cfe4fSDavid Ahern 	/* sets nh_dev if successful */
1500597cfe4fSDavid Ahern 	err = fib_check_nh(net, fib_nh, tb_id, 0, extack);
1501597cfe4fSDavid Ahern 	if (!err) {
1502597cfe4fSDavid Ahern 		nh->nh_flags = fib_nh->fib_nh_flags;
1503dcb1ecb5SDavid Ahern 		fib_info_update_nhc_saddr(net, &fib_nh->nh_common,
1504dcb1ecb5SDavid Ahern 					  fib_nh->fib_nh_scope);
1505597cfe4fSDavid Ahern 	} else {
1506597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
1507597cfe4fSDavid Ahern 	}
1508597cfe4fSDavid Ahern out:
1509597cfe4fSDavid Ahern 	return err;
1510597cfe4fSDavid Ahern }
1511597cfe4fSDavid Ahern 
151253010f99SDavid Ahern static int nh_create_ipv6(struct net *net,  struct nexthop *nh,
151353010f99SDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
151453010f99SDavid Ahern 			  struct netlink_ext_ack *extack)
151553010f99SDavid Ahern {
151653010f99SDavid Ahern 	struct fib6_nh *fib6_nh = &nhi->fib6_nh;
151753010f99SDavid Ahern 	struct fib6_config fib6_cfg = {
151853010f99SDavid Ahern 		.fc_table = l3mdev_fib_table(cfg->dev),
151953010f99SDavid Ahern 		.fc_ifindex = cfg->nh_ifindex,
152053010f99SDavid Ahern 		.fc_gateway = cfg->gw.ipv6,
152153010f99SDavid Ahern 		.fc_flags = cfg->nh_flags,
1522b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
1523b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
152438428d68SRoopa Prabhu 		.fc_is_fdb = cfg->nh_fdb,
152553010f99SDavid Ahern 	};
15266f43e525SColin Ian King 	int err;
152753010f99SDavid Ahern 
152853010f99SDavid Ahern 	if (!ipv6_addr_any(&cfg->gw.ipv6))
152953010f99SDavid Ahern 		fib6_cfg.fc_flags |= RTF_GATEWAY;
153053010f99SDavid Ahern 
153153010f99SDavid Ahern 	/* sets nh_dev if successful */
153253010f99SDavid Ahern 	err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL,
153353010f99SDavid Ahern 				      extack);
153453010f99SDavid Ahern 	if (err)
153553010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(fib6_nh);
153653010f99SDavid Ahern 	else
153753010f99SDavid Ahern 		nh->nh_flags = fib6_nh->fib_nh_flags;
153853010f99SDavid Ahern 
153953010f99SDavid Ahern 	return err;
154053010f99SDavid Ahern }
154153010f99SDavid Ahern 
1542ab84be7eSDavid Ahern static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg,
1543ab84be7eSDavid Ahern 				      struct netlink_ext_ack *extack)
1544ab84be7eSDavid Ahern {
1545ab84be7eSDavid Ahern 	struct nh_info *nhi;
1546ab84be7eSDavid Ahern 	struct nexthop *nh;
1547ab84be7eSDavid Ahern 	int err = 0;
1548ab84be7eSDavid Ahern 
1549ab84be7eSDavid Ahern 	nh = nexthop_alloc();
1550ab84be7eSDavid Ahern 	if (!nh)
1551ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
1552ab84be7eSDavid Ahern 
1553ab84be7eSDavid Ahern 	nhi = kzalloc(sizeof(*nhi), GFP_KERNEL);
1554ab84be7eSDavid Ahern 	if (!nhi) {
1555ab84be7eSDavid Ahern 		kfree(nh);
1556ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
1557ab84be7eSDavid Ahern 	}
1558ab84be7eSDavid Ahern 
1559ab84be7eSDavid Ahern 	nh->nh_flags = cfg->nh_flags;
1560ab84be7eSDavid Ahern 	nh->net = net;
1561ab84be7eSDavid Ahern 
1562ab84be7eSDavid Ahern 	nhi->nh_parent = nh;
1563ab84be7eSDavid Ahern 	nhi->family = cfg->nh_family;
1564ab84be7eSDavid Ahern 	nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK;
1565ab84be7eSDavid Ahern 
156638428d68SRoopa Prabhu 	if (cfg->nh_fdb)
1567ce9ac056SDavid Ahern 		nhi->fdb_nh = 1;
156838428d68SRoopa Prabhu 
1569ab84be7eSDavid Ahern 	if (cfg->nh_blackhole) {
1570ab84be7eSDavid Ahern 		nhi->reject_nh = 1;
1571ab84be7eSDavid Ahern 		cfg->nh_ifindex = net->loopback_dev->ifindex;
1572ab84be7eSDavid Ahern 	}
1573ab84be7eSDavid Ahern 
1574597cfe4fSDavid Ahern 	switch (cfg->nh_family) {
1575597cfe4fSDavid Ahern 	case AF_INET:
1576597cfe4fSDavid Ahern 		err = nh_create_ipv4(net, nh, nhi, cfg, extack);
1577597cfe4fSDavid Ahern 		break;
157853010f99SDavid Ahern 	case AF_INET6:
157953010f99SDavid Ahern 		err = nh_create_ipv6(net, nh, nhi, cfg, extack);
158053010f99SDavid Ahern 		break;
1581597cfe4fSDavid Ahern 	}
1582597cfe4fSDavid Ahern 
1583ab84be7eSDavid Ahern 	if (err) {
1584ab84be7eSDavid Ahern 		kfree(nhi);
1585ab84be7eSDavid Ahern 		kfree(nh);
1586ab84be7eSDavid Ahern 		return ERR_PTR(err);
1587ab84be7eSDavid Ahern 	}
1588ab84be7eSDavid Ahern 
1589597cfe4fSDavid Ahern 	/* add the entry to the device based hash */
1590ce9ac056SDavid Ahern 	if (!nhi->fdb_nh)
1591597cfe4fSDavid Ahern 		nexthop_devhash_add(net, nhi);
1592597cfe4fSDavid Ahern 
1593ab84be7eSDavid Ahern 	rcu_assign_pointer(nh->nh_info, nhi);
1594ab84be7eSDavid Ahern 
1595ab84be7eSDavid Ahern 	return nh;
1596ab84be7eSDavid Ahern }
1597ab84be7eSDavid Ahern 
1598ab84be7eSDavid Ahern /* called with rtnl lock held */
1599ab84be7eSDavid Ahern static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg,
1600ab84be7eSDavid Ahern 				   struct netlink_ext_ack *extack)
1601ab84be7eSDavid Ahern {
1602ab84be7eSDavid Ahern 	struct nexthop *nh;
1603ab84be7eSDavid Ahern 	int err;
1604ab84be7eSDavid Ahern 
1605ab84be7eSDavid Ahern 	if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) {
1606ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace requires nexthop id");
1607ab84be7eSDavid Ahern 		return ERR_PTR(-EINVAL);
1608ab84be7eSDavid Ahern 	}
1609ab84be7eSDavid Ahern 
1610ab84be7eSDavid Ahern 	if (!cfg->nh_id) {
1611ab84be7eSDavid Ahern 		cfg->nh_id = nh_find_unused_id(net);
1612ab84be7eSDavid Ahern 		if (!cfg->nh_id) {
1613ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "No unused id");
1614ab84be7eSDavid Ahern 			return ERR_PTR(-EINVAL);
1615ab84be7eSDavid Ahern 		}
1616ab84be7eSDavid Ahern 	}
1617ab84be7eSDavid Ahern 
1618430a0491SDavid Ahern 	if (cfg->nh_grp)
1619430a0491SDavid Ahern 		nh = nexthop_create_group(net, cfg);
1620430a0491SDavid Ahern 	else
1621ab84be7eSDavid Ahern 		nh = nexthop_create(net, cfg, extack);
1622430a0491SDavid Ahern 
1623ab84be7eSDavid Ahern 	if (IS_ERR(nh))
1624ab84be7eSDavid Ahern 		return nh;
1625ab84be7eSDavid Ahern 
1626ab84be7eSDavid Ahern 	refcount_set(&nh->refcnt, 1);
1627ab84be7eSDavid Ahern 	nh->id = cfg->nh_id;
1628ab84be7eSDavid Ahern 	nh->protocol = cfg->nh_protocol;
1629ab84be7eSDavid Ahern 	nh->net = net;
1630ab84be7eSDavid Ahern 
1631ab84be7eSDavid Ahern 	err = insert_nexthop(net, nh, cfg, extack);
1632ab84be7eSDavid Ahern 	if (err) {
1633430a0491SDavid Ahern 		__remove_nexthop(net, nh, NULL);
1634ab84be7eSDavid Ahern 		nexthop_put(nh);
1635ab84be7eSDavid Ahern 		nh = ERR_PTR(err);
1636ab84be7eSDavid Ahern 	}
1637ab84be7eSDavid Ahern 
1638ab84be7eSDavid Ahern 	return nh;
1639ab84be7eSDavid Ahern }
1640ab84be7eSDavid Ahern 
1641ab84be7eSDavid Ahern static int rtm_to_nh_config(struct net *net, struct sk_buff *skb,
1642ab84be7eSDavid Ahern 			    struct nlmsghdr *nlh, struct nh_config *cfg,
1643ab84be7eSDavid Ahern 			    struct netlink_ext_ack *extack)
1644ab84be7eSDavid Ahern {
1645ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
1646ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1647ab84be7eSDavid Ahern 	int err;
1648ab84be7eSDavid Ahern 
1649ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1650ab84be7eSDavid Ahern 			  extack);
1651ab84be7eSDavid Ahern 	if (err < 0)
1652ab84be7eSDavid Ahern 		return err;
1653ab84be7eSDavid Ahern 
1654ab84be7eSDavid Ahern 	err = -EINVAL;
1655ab84be7eSDavid Ahern 	if (nhm->resvd || nhm->nh_scope) {
1656ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in ancillary header");
1657ab84be7eSDavid Ahern 		goto out;
1658ab84be7eSDavid Ahern 	}
1659ab84be7eSDavid Ahern 	if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) {
1660ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header");
1661ab84be7eSDavid Ahern 		goto out;
1662ab84be7eSDavid Ahern 	}
1663ab84be7eSDavid Ahern 
1664ab84be7eSDavid Ahern 	switch (nhm->nh_family) {
1665597cfe4fSDavid Ahern 	case AF_INET:
166653010f99SDavid Ahern 	case AF_INET6:
1667597cfe4fSDavid Ahern 		break;
1668430a0491SDavid Ahern 	case AF_UNSPEC:
1669430a0491SDavid Ahern 		if (tb[NHA_GROUP])
1670430a0491SDavid Ahern 			break;
1671a8eceea8SJoe Perches 		fallthrough;
1672ab84be7eSDavid Ahern 	default:
1673ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid address family");
1674ab84be7eSDavid Ahern 		goto out;
1675ab84be7eSDavid Ahern 	}
1676ab84be7eSDavid Ahern 
1677ab84be7eSDavid Ahern 	if (tb[NHA_GROUPS] || tb[NHA_MASTER]) {
1678ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid attributes in request");
1679ab84be7eSDavid Ahern 		goto out;
1680ab84be7eSDavid Ahern 	}
1681ab84be7eSDavid Ahern 
1682ab84be7eSDavid Ahern 	memset(cfg, 0, sizeof(*cfg));
1683ab84be7eSDavid Ahern 	cfg->nlflags = nlh->nlmsg_flags;
1684ab84be7eSDavid Ahern 	cfg->nlinfo.portid = NETLINK_CB(skb).portid;
1685ab84be7eSDavid Ahern 	cfg->nlinfo.nlh = nlh;
1686ab84be7eSDavid Ahern 	cfg->nlinfo.nl_net = net;
1687ab84be7eSDavid Ahern 
1688ab84be7eSDavid Ahern 	cfg->nh_family = nhm->nh_family;
1689ab84be7eSDavid Ahern 	cfg->nh_protocol = nhm->nh_protocol;
1690ab84be7eSDavid Ahern 	cfg->nh_flags = nhm->nh_flags;
1691ab84be7eSDavid Ahern 
1692ab84be7eSDavid Ahern 	if (tb[NHA_ID])
1693ab84be7eSDavid Ahern 		cfg->nh_id = nla_get_u32(tb[NHA_ID]);
1694ab84be7eSDavid Ahern 
169538428d68SRoopa Prabhu 	if (tb[NHA_FDB]) {
169638428d68SRoopa Prabhu 		if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] ||
169738428d68SRoopa Prabhu 		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE]) {
169838428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole");
169938428d68SRoopa Prabhu 			goto out;
170038428d68SRoopa Prabhu 		}
170138428d68SRoopa Prabhu 		if (nhm->nh_flags) {
170238428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header");
170338428d68SRoopa Prabhu 			goto out;
170438428d68SRoopa Prabhu 		}
170538428d68SRoopa Prabhu 		cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]);
170638428d68SRoopa Prabhu 	}
170738428d68SRoopa Prabhu 
1708430a0491SDavid Ahern 	if (tb[NHA_GROUP]) {
1709430a0491SDavid Ahern 		if (nhm->nh_family != AF_UNSPEC) {
1710430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid family for group");
1711430a0491SDavid Ahern 			goto out;
1712430a0491SDavid Ahern 		}
1713430a0491SDavid Ahern 		cfg->nh_grp = tb[NHA_GROUP];
1714430a0491SDavid Ahern 
1715430a0491SDavid Ahern 		cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH;
1716430a0491SDavid Ahern 		if (tb[NHA_GROUP_TYPE])
1717430a0491SDavid Ahern 			cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]);
1718430a0491SDavid Ahern 
1719430a0491SDavid Ahern 		if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) {
1720430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid group type");
1721430a0491SDavid Ahern 			goto out;
1722430a0491SDavid Ahern 		}
1723430a0491SDavid Ahern 		err = nh_check_attr_group(net, tb, extack);
1724430a0491SDavid Ahern 
1725430a0491SDavid Ahern 		/* no other attributes should be set */
1726430a0491SDavid Ahern 		goto out;
1727430a0491SDavid Ahern 	}
1728430a0491SDavid Ahern 
1729ab84be7eSDavid Ahern 	if (tb[NHA_BLACKHOLE]) {
1730b513bd03SDavid Ahern 		if (tb[NHA_GATEWAY] || tb[NHA_OIF] ||
173138428d68SRoopa Prabhu 		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) {
173238428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb");
1733ab84be7eSDavid Ahern 			goto out;
1734ab84be7eSDavid Ahern 		}
1735ab84be7eSDavid Ahern 
1736ab84be7eSDavid Ahern 		cfg->nh_blackhole = 1;
1737ab84be7eSDavid Ahern 		err = 0;
1738ab84be7eSDavid Ahern 		goto out;
1739ab84be7eSDavid Ahern 	}
1740ab84be7eSDavid Ahern 
174138428d68SRoopa Prabhu 	if (!cfg->nh_fdb && !tb[NHA_OIF]) {
174238428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops");
1743ab84be7eSDavid Ahern 		goto out;
1744ab84be7eSDavid Ahern 	}
1745ab84be7eSDavid Ahern 
174638428d68SRoopa Prabhu 	if (!cfg->nh_fdb && tb[NHA_OIF]) {
1747ab84be7eSDavid Ahern 		cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]);
1748ab84be7eSDavid Ahern 		if (cfg->nh_ifindex)
1749ab84be7eSDavid Ahern 			cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex);
1750ab84be7eSDavid Ahern 
1751ab84be7eSDavid Ahern 		if (!cfg->dev) {
1752ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid device index");
1753ab84be7eSDavid Ahern 			goto out;
1754ab84be7eSDavid Ahern 		} else if (!(cfg->dev->flags & IFF_UP)) {
1755ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Nexthop device is not up");
1756ab84be7eSDavid Ahern 			err = -ENETDOWN;
1757ab84be7eSDavid Ahern 			goto out;
1758ab84be7eSDavid Ahern 		} else if (!netif_carrier_ok(cfg->dev)) {
1759ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down");
1760ab84be7eSDavid Ahern 			err = -ENETDOWN;
1761ab84be7eSDavid Ahern 			goto out;
1762ab84be7eSDavid Ahern 		}
176338428d68SRoopa Prabhu 	}
1764ab84be7eSDavid Ahern 
1765597cfe4fSDavid Ahern 	err = -EINVAL;
1766597cfe4fSDavid Ahern 	if (tb[NHA_GATEWAY]) {
1767597cfe4fSDavid Ahern 		struct nlattr *gwa = tb[NHA_GATEWAY];
1768597cfe4fSDavid Ahern 
1769597cfe4fSDavid Ahern 		switch (cfg->nh_family) {
1770597cfe4fSDavid Ahern 		case AF_INET:
1771597cfe4fSDavid Ahern 			if (nla_len(gwa) != sizeof(u32)) {
1772597cfe4fSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
1773597cfe4fSDavid Ahern 				goto out;
1774597cfe4fSDavid Ahern 			}
1775597cfe4fSDavid Ahern 			cfg->gw.ipv4 = nla_get_be32(gwa);
1776597cfe4fSDavid Ahern 			break;
177753010f99SDavid Ahern 		case AF_INET6:
177853010f99SDavid Ahern 			if (nla_len(gwa) != sizeof(struct in6_addr)) {
177953010f99SDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
178053010f99SDavid Ahern 				goto out;
178153010f99SDavid Ahern 			}
178253010f99SDavid Ahern 			cfg->gw.ipv6 = nla_get_in6_addr(gwa);
178353010f99SDavid Ahern 			break;
1784597cfe4fSDavid Ahern 		default:
1785597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
1786597cfe4fSDavid Ahern 				       "Unknown address family for gateway");
1787597cfe4fSDavid Ahern 			goto out;
1788597cfe4fSDavid Ahern 		}
1789597cfe4fSDavid Ahern 	} else {
1790597cfe4fSDavid Ahern 		/* device only nexthop (no gateway) */
1791597cfe4fSDavid Ahern 		if (cfg->nh_flags & RTNH_F_ONLINK) {
1792597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
1793597cfe4fSDavid Ahern 				       "ONLINK flag can not be set for nexthop without a gateway");
1794597cfe4fSDavid Ahern 			goto out;
1795597cfe4fSDavid Ahern 		}
1796597cfe4fSDavid Ahern 	}
1797597cfe4fSDavid Ahern 
1798b513bd03SDavid Ahern 	if (tb[NHA_ENCAP]) {
1799b513bd03SDavid Ahern 		cfg->nh_encap = tb[NHA_ENCAP];
1800b513bd03SDavid Ahern 
1801b513bd03SDavid Ahern 		if (!tb[NHA_ENCAP_TYPE]) {
1802b513bd03SDavid Ahern 			NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing");
1803b513bd03SDavid Ahern 			goto out;
1804b513bd03SDavid Ahern 		}
1805b513bd03SDavid Ahern 
1806b513bd03SDavid Ahern 		cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]);
1807b513bd03SDavid Ahern 		err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack);
1808b513bd03SDavid Ahern 		if (err < 0)
1809b513bd03SDavid Ahern 			goto out;
1810b513bd03SDavid Ahern 
1811b513bd03SDavid Ahern 	} else if (tb[NHA_ENCAP_TYPE]) {
1812b513bd03SDavid Ahern 		NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing");
1813b513bd03SDavid Ahern 		goto out;
1814b513bd03SDavid Ahern 	}
1815b513bd03SDavid Ahern 
1816b513bd03SDavid Ahern 
1817ab84be7eSDavid Ahern 	err = 0;
1818ab84be7eSDavid Ahern out:
1819ab84be7eSDavid Ahern 	return err;
1820ab84be7eSDavid Ahern }
1821ab84be7eSDavid Ahern 
1822ab84be7eSDavid Ahern /* rtnl */
1823ab84be7eSDavid Ahern static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1824ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1825ab84be7eSDavid Ahern {
1826ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
1827ab84be7eSDavid Ahern 	struct nh_config cfg;
1828ab84be7eSDavid Ahern 	struct nexthop *nh;
1829ab84be7eSDavid Ahern 	int err;
1830ab84be7eSDavid Ahern 
1831ab84be7eSDavid Ahern 	err = rtm_to_nh_config(net, skb, nlh, &cfg, extack);
1832ab84be7eSDavid Ahern 	if (!err) {
1833ab84be7eSDavid Ahern 		nh = nexthop_add(net, &cfg, extack);
1834ab84be7eSDavid Ahern 		if (IS_ERR(nh))
1835ab84be7eSDavid Ahern 			err = PTR_ERR(nh);
1836ab84be7eSDavid Ahern 	}
1837ab84be7eSDavid Ahern 
1838ab84be7eSDavid Ahern 	return err;
1839ab84be7eSDavid Ahern }
1840ab84be7eSDavid Ahern 
1841ab84be7eSDavid Ahern static int nh_valid_get_del_req(struct nlmsghdr *nlh, u32 *id,
1842ab84be7eSDavid Ahern 				struct netlink_ext_ack *extack)
1843ab84be7eSDavid Ahern {
1844ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
1845ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1846ab84be7eSDavid Ahern 	int err, i;
1847ab84be7eSDavid Ahern 
1848ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1849ab84be7eSDavid Ahern 			  extack);
1850ab84be7eSDavid Ahern 	if (err < 0)
1851ab84be7eSDavid Ahern 		return err;
1852ab84be7eSDavid Ahern 
1853ab84be7eSDavid Ahern 	err = -EINVAL;
1854ab84be7eSDavid Ahern 	for (i = 0; i < __NHA_MAX; ++i) {
1855ab84be7eSDavid Ahern 		if (!tb[i])
1856ab84be7eSDavid Ahern 			continue;
1857ab84be7eSDavid Ahern 
1858ab84be7eSDavid Ahern 		switch (i) {
1859ab84be7eSDavid Ahern 		case NHA_ID:
1860ab84be7eSDavid Ahern 			break;
1861ab84be7eSDavid Ahern 		default:
1862ab84be7eSDavid Ahern 			NL_SET_ERR_MSG_ATTR(extack, tb[i],
1863ab84be7eSDavid Ahern 					    "Unexpected attribute in request");
1864ab84be7eSDavid Ahern 			goto out;
1865ab84be7eSDavid Ahern 		}
1866ab84be7eSDavid Ahern 	}
1867ab84be7eSDavid Ahern 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
1868ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in header");
1869ab84be7eSDavid Ahern 		goto out;
1870ab84be7eSDavid Ahern 	}
1871ab84be7eSDavid Ahern 
1872ab84be7eSDavid Ahern 	if (!tb[NHA_ID]) {
1873ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Nexthop id is missing");
1874ab84be7eSDavid Ahern 		goto out;
1875ab84be7eSDavid Ahern 	}
1876ab84be7eSDavid Ahern 
1877ab84be7eSDavid Ahern 	*id = nla_get_u32(tb[NHA_ID]);
1878ab84be7eSDavid Ahern 	if (!(*id))
1879ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid nexthop id");
1880ab84be7eSDavid Ahern 	else
1881ab84be7eSDavid Ahern 		err = 0;
1882ab84be7eSDavid Ahern out:
1883ab84be7eSDavid Ahern 	return err;
1884ab84be7eSDavid Ahern }
1885ab84be7eSDavid Ahern 
1886ab84be7eSDavid Ahern /* rtnl */
1887ab84be7eSDavid Ahern static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1888ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1889ab84be7eSDavid Ahern {
1890ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
1891ab84be7eSDavid Ahern 	struct nl_info nlinfo = {
1892ab84be7eSDavid Ahern 		.nlh = nlh,
1893ab84be7eSDavid Ahern 		.nl_net = net,
1894ab84be7eSDavid Ahern 		.portid = NETLINK_CB(skb).portid,
1895ab84be7eSDavid Ahern 	};
1896ab84be7eSDavid Ahern 	struct nexthop *nh;
1897ab84be7eSDavid Ahern 	int err;
1898ab84be7eSDavid Ahern 	u32 id;
1899ab84be7eSDavid Ahern 
1900ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
1901ab84be7eSDavid Ahern 	if (err)
1902ab84be7eSDavid Ahern 		return err;
1903ab84be7eSDavid Ahern 
1904ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
1905ab84be7eSDavid Ahern 	if (!nh)
1906ab84be7eSDavid Ahern 		return -ENOENT;
1907ab84be7eSDavid Ahern 
1908430a0491SDavid Ahern 	remove_nexthop(net, nh, &nlinfo);
1909ab84be7eSDavid Ahern 
1910ab84be7eSDavid Ahern 	return 0;
1911ab84be7eSDavid Ahern }
1912ab84be7eSDavid Ahern 
1913ab84be7eSDavid Ahern /* rtnl */
1914ab84be7eSDavid Ahern static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh,
1915ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1916ab84be7eSDavid Ahern {
1917ab84be7eSDavid Ahern 	struct net *net = sock_net(in_skb->sk);
1918ab84be7eSDavid Ahern 	struct sk_buff *skb = NULL;
1919ab84be7eSDavid Ahern 	struct nexthop *nh;
1920ab84be7eSDavid Ahern 	int err;
1921ab84be7eSDavid Ahern 	u32 id;
1922ab84be7eSDavid Ahern 
1923ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
1924ab84be7eSDavid Ahern 	if (err)
1925ab84be7eSDavid Ahern 		return err;
1926ab84be7eSDavid Ahern 
1927ab84be7eSDavid Ahern 	err = -ENOBUFS;
1928ab84be7eSDavid Ahern 	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
1929ab84be7eSDavid Ahern 	if (!skb)
1930ab84be7eSDavid Ahern 		goto out;
1931ab84be7eSDavid Ahern 
1932ab84be7eSDavid Ahern 	err = -ENOENT;
1933ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
1934ab84be7eSDavid Ahern 	if (!nh)
1935ab84be7eSDavid Ahern 		goto errout_free;
1936ab84be7eSDavid Ahern 
1937ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid,
1938ab84be7eSDavid Ahern 			   nlh->nlmsg_seq, 0);
1939ab84be7eSDavid Ahern 	if (err < 0) {
1940ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
1941ab84be7eSDavid Ahern 		goto errout_free;
1942ab84be7eSDavid Ahern 	}
1943ab84be7eSDavid Ahern 
1944ab84be7eSDavid Ahern 	err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
1945ab84be7eSDavid Ahern out:
1946ab84be7eSDavid Ahern 	return err;
1947ab84be7eSDavid Ahern errout_free:
1948ab84be7eSDavid Ahern 	kfree_skb(skb);
1949ab84be7eSDavid Ahern 	goto out;
1950ab84be7eSDavid Ahern }
1951ab84be7eSDavid Ahern 
1952430a0491SDavid Ahern static bool nh_dump_filtered(struct nexthop *nh, int dev_idx, int master_idx,
1953430a0491SDavid Ahern 			     bool group_filter, u8 family)
1954ab84be7eSDavid Ahern {
1955ab84be7eSDavid Ahern 	const struct net_device *dev;
1956ab84be7eSDavid Ahern 	const struct nh_info *nhi;
1957ab84be7eSDavid Ahern 
1958430a0491SDavid Ahern 	if (group_filter && !nh->is_group)
1959430a0491SDavid Ahern 		return true;
1960430a0491SDavid Ahern 
1961ab84be7eSDavid Ahern 	if (!dev_idx && !master_idx && !family)
1962ab84be7eSDavid Ahern 		return false;
1963ab84be7eSDavid Ahern 
1964430a0491SDavid Ahern 	if (nh->is_group)
1965430a0491SDavid Ahern 		return true;
1966430a0491SDavid Ahern 
1967ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
1968ab84be7eSDavid Ahern 	if (family && nhi->family != family)
1969ab84be7eSDavid Ahern 		return true;
1970ab84be7eSDavid Ahern 
1971ab84be7eSDavid Ahern 	dev = nhi->fib_nhc.nhc_dev;
1972ab84be7eSDavid Ahern 	if (dev_idx && (!dev || dev->ifindex != dev_idx))
1973ab84be7eSDavid Ahern 		return true;
1974ab84be7eSDavid Ahern 
1975ab84be7eSDavid Ahern 	if (master_idx) {
1976ab84be7eSDavid Ahern 		struct net_device *master;
1977ab84be7eSDavid Ahern 
1978ab84be7eSDavid Ahern 		if (!dev)
1979ab84be7eSDavid Ahern 			return true;
1980ab84be7eSDavid Ahern 
1981ab84be7eSDavid Ahern 		master = netdev_master_upper_dev_get((struct net_device *)dev);
1982ab84be7eSDavid Ahern 		if (!master || master->ifindex != master_idx)
1983ab84be7eSDavid Ahern 			return true;
1984ab84be7eSDavid Ahern 	}
1985ab84be7eSDavid Ahern 
1986ab84be7eSDavid Ahern 	return false;
1987ab84be7eSDavid Ahern }
1988ab84be7eSDavid Ahern 
1989430a0491SDavid Ahern static int nh_valid_dump_req(const struct nlmsghdr *nlh, int *dev_idx,
1990430a0491SDavid Ahern 			     int *master_idx, bool *group_filter,
199138428d68SRoopa Prabhu 			     bool *fdb_filter, struct netlink_callback *cb)
1992ab84be7eSDavid Ahern {
1993ab84be7eSDavid Ahern 	struct netlink_ext_ack *extack = cb->extack;
1994ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1995ab84be7eSDavid Ahern 	struct nhmsg *nhm;
1996ab84be7eSDavid Ahern 	int err, i;
1997ab84be7eSDavid Ahern 	u32 idx;
1998ab84be7eSDavid Ahern 
1999ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
2000ab84be7eSDavid Ahern 			  NULL);
2001ab84be7eSDavid Ahern 	if (err < 0)
2002ab84be7eSDavid Ahern 		return err;
2003ab84be7eSDavid Ahern 
2004ab84be7eSDavid Ahern 	for (i = 0; i <= NHA_MAX; ++i) {
2005ab84be7eSDavid Ahern 		if (!tb[i])
2006ab84be7eSDavid Ahern 			continue;
2007ab84be7eSDavid Ahern 
2008ab84be7eSDavid Ahern 		switch (i) {
2009ab84be7eSDavid Ahern 		case NHA_OIF:
2010ab84be7eSDavid Ahern 			idx = nla_get_u32(tb[i]);
2011ab84be7eSDavid Ahern 			if (idx > INT_MAX) {
2012ab84be7eSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid device index");
2013ab84be7eSDavid Ahern 				return -EINVAL;
2014ab84be7eSDavid Ahern 			}
2015ab84be7eSDavid Ahern 			*dev_idx = idx;
2016ab84be7eSDavid Ahern 			break;
2017ab84be7eSDavid Ahern 		case NHA_MASTER:
2018ab84be7eSDavid Ahern 			idx = nla_get_u32(tb[i]);
2019ab84be7eSDavid Ahern 			if (idx > INT_MAX) {
2020ab84be7eSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid master device index");
2021ab84be7eSDavid Ahern 				return -EINVAL;
2022ab84be7eSDavid Ahern 			}
2023ab84be7eSDavid Ahern 			*master_idx = idx;
2024ab84be7eSDavid Ahern 			break;
2025430a0491SDavid Ahern 		case NHA_GROUPS:
2026430a0491SDavid Ahern 			*group_filter = true;
2027430a0491SDavid Ahern 			break;
202838428d68SRoopa Prabhu 		case NHA_FDB:
202938428d68SRoopa Prabhu 			*fdb_filter = true;
203038428d68SRoopa Prabhu 			break;
2031ab84be7eSDavid Ahern 		default:
2032ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
2033ab84be7eSDavid Ahern 			return -EINVAL;
2034ab84be7eSDavid Ahern 		}
2035ab84be7eSDavid Ahern 	}
2036ab84be7eSDavid Ahern 
2037ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
2038ab84be7eSDavid Ahern 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
2039ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request");
2040ab84be7eSDavid Ahern 		return -EINVAL;
2041ab84be7eSDavid Ahern 	}
2042ab84be7eSDavid Ahern 
2043ab84be7eSDavid Ahern 	return 0;
2044ab84be7eSDavid Ahern }
2045ab84be7eSDavid Ahern 
2046ab84be7eSDavid Ahern /* rtnl */
2047ab84be7eSDavid Ahern static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb)
2048ab84be7eSDavid Ahern {
204938428d68SRoopa Prabhu 	bool group_filter = false, fdb_filter = false;
2050ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(cb->nlh);
2051ab84be7eSDavid Ahern 	int dev_filter_idx = 0, master_idx = 0;
2052ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
2053ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
2054ab84be7eSDavid Ahern 	struct rb_node *node;
2055ab84be7eSDavid Ahern 	int idx = 0, s_idx;
2056ab84be7eSDavid Ahern 	int err;
2057ab84be7eSDavid Ahern 
2058430a0491SDavid Ahern 	err = nh_valid_dump_req(cb->nlh, &dev_filter_idx, &master_idx,
205938428d68SRoopa Prabhu 				&group_filter, &fdb_filter, cb);
2060ab84be7eSDavid Ahern 	if (err < 0)
2061ab84be7eSDavid Ahern 		return err;
2062ab84be7eSDavid Ahern 
2063ab84be7eSDavid Ahern 	s_idx = cb->args[0];
2064ab84be7eSDavid Ahern 	for (node = rb_first(root); node; node = rb_next(node)) {
2065ab84be7eSDavid Ahern 		struct nexthop *nh;
2066ab84be7eSDavid Ahern 
2067ab84be7eSDavid Ahern 		if (idx < s_idx)
2068ab84be7eSDavid Ahern 			goto cont;
2069ab84be7eSDavid Ahern 
2070ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
2071ab84be7eSDavid Ahern 		if (nh_dump_filtered(nh, dev_filter_idx, master_idx,
2072430a0491SDavid Ahern 				     group_filter, nhm->nh_family))
2073ab84be7eSDavid Ahern 			goto cont;
2074ab84be7eSDavid Ahern 
2075ab84be7eSDavid Ahern 		err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP,
2076ab84be7eSDavid Ahern 				   NETLINK_CB(cb->skb).portid,
2077ab84be7eSDavid Ahern 				   cb->nlh->nlmsg_seq, NLM_F_MULTI);
2078ab84be7eSDavid Ahern 		if (err < 0) {
2079ab84be7eSDavid Ahern 			if (likely(skb->len))
2080ab84be7eSDavid Ahern 				goto out;
2081ab84be7eSDavid Ahern 
2082ab84be7eSDavid Ahern 			goto out_err;
2083ab84be7eSDavid Ahern 		}
2084ab84be7eSDavid Ahern cont:
2085ab84be7eSDavid Ahern 		idx++;
2086ab84be7eSDavid Ahern 	}
2087ab84be7eSDavid Ahern 
2088ab84be7eSDavid Ahern out:
2089ab84be7eSDavid Ahern 	err = skb->len;
2090ab84be7eSDavid Ahern out_err:
2091ab84be7eSDavid Ahern 	cb->args[0] = idx;
2092ab84be7eSDavid Ahern 	cb->seq = net->nexthop.seq;
2093ab84be7eSDavid Ahern 	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
2094ab84be7eSDavid Ahern 
2095ab84be7eSDavid Ahern 	return err;
2096ab84be7eSDavid Ahern }
2097ab84be7eSDavid Ahern 
2098597cfe4fSDavid Ahern static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu)
2099597cfe4fSDavid Ahern {
2100597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
2101597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
2102597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
2103597cfe4fSDavid Ahern 	struct hlist_node *n;
2104597cfe4fSDavid Ahern 	struct nh_info *nhi;
2105597cfe4fSDavid Ahern 
2106597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
2107597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev == dev) {
2108597cfe4fSDavid Ahern 			if (nhi->family == AF_INET)
2109597cfe4fSDavid Ahern 				fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu,
2110597cfe4fSDavid Ahern 						   orig_mtu);
2111597cfe4fSDavid Ahern 		}
2112597cfe4fSDavid Ahern 	}
2113597cfe4fSDavid Ahern }
2114597cfe4fSDavid Ahern 
2115597cfe4fSDavid Ahern /* rtnl */
2116597cfe4fSDavid Ahern static int nh_netdev_event(struct notifier_block *this,
2117597cfe4fSDavid Ahern 			   unsigned long event, void *ptr)
2118597cfe4fSDavid Ahern {
2119597cfe4fSDavid Ahern 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
2120597cfe4fSDavid Ahern 	struct netdev_notifier_info_ext *info_ext;
2121597cfe4fSDavid Ahern 
2122597cfe4fSDavid Ahern 	switch (event) {
2123597cfe4fSDavid Ahern 	case NETDEV_DOWN:
2124597cfe4fSDavid Ahern 	case NETDEV_UNREGISTER:
2125597cfe4fSDavid Ahern 		nexthop_flush_dev(dev);
2126597cfe4fSDavid Ahern 		break;
2127597cfe4fSDavid Ahern 	case NETDEV_CHANGE:
2128597cfe4fSDavid Ahern 		if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP)))
2129597cfe4fSDavid Ahern 			nexthop_flush_dev(dev);
2130597cfe4fSDavid Ahern 		break;
2131597cfe4fSDavid Ahern 	case NETDEV_CHANGEMTU:
2132597cfe4fSDavid Ahern 		info_ext = ptr;
2133597cfe4fSDavid Ahern 		nexthop_sync_mtu(dev, info_ext->ext.mtu);
2134597cfe4fSDavid Ahern 		rt_cache_flush(dev_net(dev));
2135597cfe4fSDavid Ahern 		break;
2136597cfe4fSDavid Ahern 	}
2137597cfe4fSDavid Ahern 	return NOTIFY_DONE;
2138597cfe4fSDavid Ahern }
2139597cfe4fSDavid Ahern 
2140597cfe4fSDavid Ahern static struct notifier_block nh_netdev_notifier = {
2141597cfe4fSDavid Ahern 	.notifier_call = nh_netdev_event,
2142597cfe4fSDavid Ahern };
2143597cfe4fSDavid Ahern 
2144975ff7f3SIdo Schimmel static int nexthops_dump(struct net *net, struct notifier_block *nb,
2145975ff7f3SIdo Schimmel 			 struct netlink_ext_ack *extack)
2146975ff7f3SIdo Schimmel {
2147975ff7f3SIdo Schimmel 	struct rb_root *root = &net->nexthop.rb_root;
2148975ff7f3SIdo Schimmel 	struct rb_node *node;
2149975ff7f3SIdo Schimmel 	int err = 0;
2150975ff7f3SIdo Schimmel 
2151975ff7f3SIdo Schimmel 	for (node = rb_first(root); node; node = rb_next(node)) {
2152975ff7f3SIdo Schimmel 		struct nexthop *nh;
2153975ff7f3SIdo Schimmel 
2154975ff7f3SIdo Schimmel 		nh = rb_entry(node, struct nexthop, rb_node);
2155975ff7f3SIdo Schimmel 		err = call_nexthop_notifier(nb, net, NEXTHOP_EVENT_REPLACE, nh,
2156975ff7f3SIdo Schimmel 					    extack);
2157975ff7f3SIdo Schimmel 		if (err)
2158975ff7f3SIdo Schimmel 			break;
2159975ff7f3SIdo Schimmel 	}
2160975ff7f3SIdo Schimmel 
2161975ff7f3SIdo Schimmel 	return err;
2162975ff7f3SIdo Schimmel }
2163975ff7f3SIdo Schimmel 
2164ce7e9c8aSIdo Schimmel int register_nexthop_notifier(struct net *net, struct notifier_block *nb,
2165ce7e9c8aSIdo Schimmel 			      struct netlink_ext_ack *extack)
21668590ceedSRoopa Prabhu {
2167975ff7f3SIdo Schimmel 	int err;
2168975ff7f3SIdo Schimmel 
2169975ff7f3SIdo Schimmel 	rtnl_lock();
2170975ff7f3SIdo Schimmel 	err = nexthops_dump(net, nb, extack);
2171975ff7f3SIdo Schimmel 	if (err)
2172975ff7f3SIdo Schimmel 		goto unlock;
2173975ff7f3SIdo Schimmel 	err = blocking_notifier_chain_register(&net->nexthop.notifier_chain,
217480690ec6SIdo Schimmel 					       nb);
2175975ff7f3SIdo Schimmel unlock:
2176975ff7f3SIdo Schimmel 	rtnl_unlock();
2177975ff7f3SIdo Schimmel 	return err;
21788590ceedSRoopa Prabhu }
21798590ceedSRoopa Prabhu EXPORT_SYMBOL(register_nexthop_notifier);
21808590ceedSRoopa Prabhu 
21818590ceedSRoopa Prabhu int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb)
21828590ceedSRoopa Prabhu {
218380690ec6SIdo Schimmel 	return blocking_notifier_chain_unregister(&net->nexthop.notifier_chain,
21848590ceedSRoopa Prabhu 						  nb);
21858590ceedSRoopa Prabhu }
21868590ceedSRoopa Prabhu EXPORT_SYMBOL(unregister_nexthop_notifier);
21878590ceedSRoopa Prabhu 
2188e95f2592SIdo Schimmel void nexthop_set_hw_flags(struct net *net, u32 id, bool offload, bool trap)
2189e95f2592SIdo Schimmel {
2190e95f2592SIdo Schimmel 	struct nexthop *nexthop;
2191e95f2592SIdo Schimmel 
2192e95f2592SIdo Schimmel 	rcu_read_lock();
2193e95f2592SIdo Schimmel 
2194e95f2592SIdo Schimmel 	nexthop = nexthop_find_by_id(net, id);
2195e95f2592SIdo Schimmel 	if (!nexthop)
2196e95f2592SIdo Schimmel 		goto out;
2197e95f2592SIdo Schimmel 
2198e95f2592SIdo Schimmel 	nexthop->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP);
2199e95f2592SIdo Schimmel 	if (offload)
2200e95f2592SIdo Schimmel 		nexthop->nh_flags |= RTNH_F_OFFLOAD;
2201e95f2592SIdo Schimmel 	if (trap)
2202e95f2592SIdo Schimmel 		nexthop->nh_flags |= RTNH_F_TRAP;
2203e95f2592SIdo Schimmel 
2204e95f2592SIdo Schimmel out:
2205e95f2592SIdo Schimmel 	rcu_read_unlock();
2206e95f2592SIdo Schimmel }
2207e95f2592SIdo Schimmel EXPORT_SYMBOL(nexthop_set_hw_flags);
2208e95f2592SIdo Schimmel 
2209ab84be7eSDavid Ahern static void __net_exit nexthop_net_exit(struct net *net)
2210ab84be7eSDavid Ahern {
2211ab84be7eSDavid Ahern 	rtnl_lock();
2212ab84be7eSDavid Ahern 	flush_all_nexthops(net);
2213ab84be7eSDavid Ahern 	rtnl_unlock();
2214597cfe4fSDavid Ahern 	kfree(net->nexthop.devhash);
2215ab84be7eSDavid Ahern }
2216ab84be7eSDavid Ahern 
2217ab84be7eSDavid Ahern static int __net_init nexthop_net_init(struct net *net)
2218ab84be7eSDavid Ahern {
2219597cfe4fSDavid Ahern 	size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE;
2220597cfe4fSDavid Ahern 
2221ab84be7eSDavid Ahern 	net->nexthop.rb_root = RB_ROOT;
2222597cfe4fSDavid Ahern 	net->nexthop.devhash = kzalloc(sz, GFP_KERNEL);
2223597cfe4fSDavid Ahern 	if (!net->nexthop.devhash)
2224597cfe4fSDavid Ahern 		return -ENOMEM;
222580690ec6SIdo Schimmel 	BLOCKING_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain);
2226ab84be7eSDavid Ahern 
2227ab84be7eSDavid Ahern 	return 0;
2228ab84be7eSDavid Ahern }
2229ab84be7eSDavid Ahern 
2230ab84be7eSDavid Ahern static struct pernet_operations nexthop_net_ops = {
2231ab84be7eSDavid Ahern 	.init = nexthop_net_init,
2232ab84be7eSDavid Ahern 	.exit = nexthop_net_exit,
2233ab84be7eSDavid Ahern };
2234ab84be7eSDavid Ahern 
2235ab84be7eSDavid Ahern static int __init nexthop_init(void)
2236ab84be7eSDavid Ahern {
2237ab84be7eSDavid Ahern 	register_pernet_subsys(&nexthop_net_ops);
2238ab84be7eSDavid Ahern 
2239597cfe4fSDavid Ahern 	register_netdevice_notifier(&nh_netdev_notifier);
2240597cfe4fSDavid Ahern 
2241ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
2242ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0);
2243ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop,
2244ab84be7eSDavid Ahern 		      rtm_dump_nexthop, 0);
2245ab84be7eSDavid Ahern 
2246ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
2247ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
2248ab84be7eSDavid Ahern 
2249ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
2250ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
2251ab84be7eSDavid Ahern 
2252ab84be7eSDavid Ahern 	return 0;
2253ab84be7eSDavid Ahern }
2254ab84be7eSDavid Ahern subsys_initcall(nexthop_init);
2255