xref: /linux/net/ipv4/nexthop.c (revision 60f5ad5e19c0996df7ca4ce7ef5fd4596cb13f01)
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 
39*60f5ad5eSPetr Machata static const struct nla_policy rtm_nh_policy_get[] = {
40*60f5ad5eSPetr Machata 	[NHA_ID]		= { .type = NLA_U32 },
41*60f5ad5eSPetr Machata };
42*60f5ad5eSPetr Machata 
435ca474f2SIdo Schimmel static bool nexthop_notifiers_is_empty(struct net *net)
445ca474f2SIdo Schimmel {
455ca474f2SIdo Schimmel 	return !net->nexthop.notifier_chain.head;
465ca474f2SIdo Schimmel }
475ca474f2SIdo Schimmel 
485ca474f2SIdo Schimmel static void
495ca474f2SIdo Schimmel __nh_notifier_single_info_init(struct nh_notifier_single_info *nh_info,
505ca474f2SIdo Schimmel 			       const struct nexthop *nh)
515ca474f2SIdo Schimmel {
525ca474f2SIdo Schimmel 	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
535ca474f2SIdo Schimmel 
545ca474f2SIdo Schimmel 	nh_info->dev = nhi->fib_nhc.nhc_dev;
555ca474f2SIdo Schimmel 	nh_info->gw_family = nhi->fib_nhc.nhc_gw_family;
565ca474f2SIdo Schimmel 	if (nh_info->gw_family == AF_INET)
575ca474f2SIdo Schimmel 		nh_info->ipv4 = nhi->fib_nhc.nhc_gw.ipv4;
585ca474f2SIdo Schimmel 	else if (nh_info->gw_family == AF_INET6)
595ca474f2SIdo Schimmel 		nh_info->ipv6 = nhi->fib_nhc.nhc_gw.ipv6;
605ca474f2SIdo Schimmel 
615ca474f2SIdo Schimmel 	nh_info->is_reject = nhi->reject_nh;
625ca474f2SIdo Schimmel 	nh_info->is_fdb = nhi->fdb_nh;
635ca474f2SIdo Schimmel 	nh_info->has_encap = !!nhi->fib_nhc.nhc_lwtstate;
645ca474f2SIdo Schimmel }
655ca474f2SIdo Schimmel 
665ca474f2SIdo Schimmel static int nh_notifier_single_info_init(struct nh_notifier_info *info,
675ca474f2SIdo Schimmel 					const struct nexthop *nh)
685ca474f2SIdo Schimmel {
695ca474f2SIdo Schimmel 	info->nh = kzalloc(sizeof(*info->nh), GFP_KERNEL);
705ca474f2SIdo Schimmel 	if (!info->nh)
715ca474f2SIdo Schimmel 		return -ENOMEM;
725ca474f2SIdo Schimmel 
735ca474f2SIdo Schimmel 	__nh_notifier_single_info_init(info->nh, nh);
745ca474f2SIdo Schimmel 
755ca474f2SIdo Schimmel 	return 0;
765ca474f2SIdo Schimmel }
775ca474f2SIdo Schimmel 
785ca474f2SIdo Schimmel static void nh_notifier_single_info_fini(struct nh_notifier_info *info)
795ca474f2SIdo Schimmel {
805ca474f2SIdo Schimmel 	kfree(info->nh);
815ca474f2SIdo Schimmel }
825ca474f2SIdo Schimmel 
835ca474f2SIdo Schimmel static int nh_notifier_grp_info_init(struct nh_notifier_info *info,
845ca474f2SIdo Schimmel 				     const struct nexthop *nh)
855ca474f2SIdo Schimmel {
865ca474f2SIdo Schimmel 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
875ca474f2SIdo Schimmel 	u16 num_nh = nhg->num_nh;
885ca474f2SIdo Schimmel 	int i;
895ca474f2SIdo Schimmel 
905ca474f2SIdo Schimmel 	info->nh_grp = kzalloc(struct_size(info->nh_grp, nh_entries, num_nh),
915ca474f2SIdo Schimmel 			       GFP_KERNEL);
925ca474f2SIdo Schimmel 	if (!info->nh_grp)
935ca474f2SIdo Schimmel 		return -ENOMEM;
945ca474f2SIdo Schimmel 
955ca474f2SIdo Schimmel 	info->nh_grp->num_nh = num_nh;
965ca474f2SIdo Schimmel 	info->nh_grp->is_fdb = nhg->fdb_nh;
975ca474f2SIdo Schimmel 
985ca474f2SIdo Schimmel 	for (i = 0; i < num_nh; i++) {
995ca474f2SIdo Schimmel 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
1005ca474f2SIdo Schimmel 
1015ca474f2SIdo Schimmel 		info->nh_grp->nh_entries[i].id = nhge->nh->id;
1025ca474f2SIdo Schimmel 		info->nh_grp->nh_entries[i].weight = nhge->weight;
1035ca474f2SIdo Schimmel 		__nh_notifier_single_info_init(&info->nh_grp->nh_entries[i].nh,
1045ca474f2SIdo Schimmel 					       nhge->nh);
1055ca474f2SIdo Schimmel 	}
1065ca474f2SIdo Schimmel 
1075ca474f2SIdo Schimmel 	return 0;
1085ca474f2SIdo Schimmel }
1095ca474f2SIdo Schimmel 
1105ca474f2SIdo Schimmel static void nh_notifier_grp_info_fini(struct nh_notifier_info *info)
1115ca474f2SIdo Schimmel {
1125ca474f2SIdo Schimmel 	kfree(info->nh_grp);
1135ca474f2SIdo Schimmel }
1145ca474f2SIdo Schimmel 
1155ca474f2SIdo Schimmel static int nh_notifier_info_init(struct nh_notifier_info *info,
1165ca474f2SIdo Schimmel 				 const struct nexthop *nh)
1175ca474f2SIdo Schimmel {
1185ca474f2SIdo Schimmel 	info->id = nh->id;
1195ca474f2SIdo Schimmel 	info->is_grp = nh->is_group;
1205ca474f2SIdo Schimmel 
1215ca474f2SIdo Schimmel 	if (info->is_grp)
1225ca474f2SIdo Schimmel 		return nh_notifier_grp_info_init(info, nh);
1235ca474f2SIdo Schimmel 	else
1245ca474f2SIdo Schimmel 		return nh_notifier_single_info_init(info, nh);
1255ca474f2SIdo Schimmel }
1265ca474f2SIdo Schimmel 
1275ca474f2SIdo Schimmel static void nh_notifier_info_fini(struct nh_notifier_info *info)
1285ca474f2SIdo Schimmel {
1295ca474f2SIdo Schimmel 	if (info->is_grp)
1305ca474f2SIdo Schimmel 		nh_notifier_grp_info_fini(info);
1315ca474f2SIdo Schimmel 	else
1325ca474f2SIdo Schimmel 		nh_notifier_single_info_fini(info);
1335ca474f2SIdo Schimmel }
1345ca474f2SIdo Schimmel 
1358590ceedSRoopa Prabhu static int call_nexthop_notifiers(struct net *net,
136d8e79f1dSNathan Chancellor 				  enum nexthop_event_type event_type,
1373578d53dSIdo Schimmel 				  struct nexthop *nh,
1383578d53dSIdo Schimmel 				  struct netlink_ext_ack *extack)
1398590ceedSRoopa Prabhu {
1405ca474f2SIdo Schimmel 	struct nh_notifier_info info = {
1415ca474f2SIdo Schimmel 		.net = net,
1425ca474f2SIdo Schimmel 		.extack = extack,
1435ca474f2SIdo Schimmel 	};
1448590ceedSRoopa Prabhu 	int err;
1458590ceedSRoopa Prabhu 
1465ca474f2SIdo Schimmel 	ASSERT_RTNL();
1475ca474f2SIdo Schimmel 
1485ca474f2SIdo Schimmel 	if (nexthop_notifiers_is_empty(net))
1495ca474f2SIdo Schimmel 		return 0;
1505ca474f2SIdo Schimmel 
1515ca474f2SIdo Schimmel 	err = nh_notifier_info_init(&info, nh);
1525ca474f2SIdo Schimmel 	if (err) {
1535ca474f2SIdo Schimmel 		NL_SET_ERR_MSG(extack, "Failed to initialize nexthop notifier info");
1545ca474f2SIdo Schimmel 		return err;
1555ca474f2SIdo Schimmel 	}
1565ca474f2SIdo Schimmel 
15780690ec6SIdo Schimmel 	err = blocking_notifier_call_chain(&net->nexthop.notifier_chain,
1581ec69d18SIdo Schimmel 					   event_type, &info);
1595ca474f2SIdo Schimmel 	nh_notifier_info_fini(&info);
1605ca474f2SIdo Schimmel 
1618590ceedSRoopa Prabhu 	return notifier_to_errno(err);
1628590ceedSRoopa Prabhu }
1638590ceedSRoopa Prabhu 
164975ff7f3SIdo Schimmel static int call_nexthop_notifier(struct notifier_block *nb, struct net *net,
165975ff7f3SIdo Schimmel 				 enum nexthop_event_type event_type,
166975ff7f3SIdo Schimmel 				 struct nexthop *nh,
167975ff7f3SIdo Schimmel 				 struct netlink_ext_ack *extack)
168975ff7f3SIdo Schimmel {
169975ff7f3SIdo Schimmel 	struct nh_notifier_info info = {
170975ff7f3SIdo Schimmel 		.net = net,
171975ff7f3SIdo Schimmel 		.extack = extack,
172975ff7f3SIdo Schimmel 	};
173975ff7f3SIdo Schimmel 	int err;
174975ff7f3SIdo Schimmel 
175975ff7f3SIdo Schimmel 	err = nh_notifier_info_init(&info, nh);
176975ff7f3SIdo Schimmel 	if (err)
177975ff7f3SIdo Schimmel 		return err;
178975ff7f3SIdo Schimmel 
179975ff7f3SIdo Schimmel 	err = nb->notifier_call(nb, event_type, &info);
180975ff7f3SIdo Schimmel 	nh_notifier_info_fini(&info);
181975ff7f3SIdo Schimmel 
182975ff7f3SIdo Schimmel 	return notifier_to_errno(err);
183975ff7f3SIdo Schimmel }
184975ff7f3SIdo Schimmel 
185597cfe4fSDavid Ahern static unsigned int nh_dev_hashfn(unsigned int val)
186597cfe4fSDavid Ahern {
187597cfe4fSDavid Ahern 	unsigned int mask = NH_DEV_HASHSIZE - 1;
188597cfe4fSDavid Ahern 
189597cfe4fSDavid Ahern 	return (val ^
190597cfe4fSDavid Ahern 		(val >> NH_DEV_HASHBITS) ^
191597cfe4fSDavid Ahern 		(val >> (NH_DEV_HASHBITS * 2))) & mask;
192597cfe4fSDavid Ahern }
193597cfe4fSDavid Ahern 
194597cfe4fSDavid Ahern static void nexthop_devhash_add(struct net *net, struct nh_info *nhi)
195597cfe4fSDavid Ahern {
196597cfe4fSDavid Ahern 	struct net_device *dev = nhi->fib_nhc.nhc_dev;
197597cfe4fSDavid Ahern 	struct hlist_head *head;
198597cfe4fSDavid Ahern 	unsigned int hash;
199597cfe4fSDavid Ahern 
200597cfe4fSDavid Ahern 	WARN_ON(!dev);
201597cfe4fSDavid Ahern 
202597cfe4fSDavid Ahern 	hash = nh_dev_hashfn(dev->ifindex);
203597cfe4fSDavid Ahern 	head = &net->nexthop.devhash[hash];
204597cfe4fSDavid Ahern 	hlist_add_head(&nhi->dev_hash, head);
205597cfe4fSDavid Ahern }
206597cfe4fSDavid Ahern 
207430a0491SDavid Ahern static void nexthop_free_mpath(struct nexthop *nh)
208ab84be7eSDavid Ahern {
209430a0491SDavid Ahern 	struct nh_group *nhg;
210430a0491SDavid Ahern 	int i;
211430a0491SDavid Ahern 
212430a0491SDavid Ahern 	nhg = rcu_dereference_raw(nh->nh_grp);
21390f33bffSNikolay Aleksandrov 	for (i = 0; i < nhg->num_nh; ++i) {
21490f33bffSNikolay Aleksandrov 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
215430a0491SDavid Ahern 
21690f33bffSNikolay Aleksandrov 		WARN_ON(!list_empty(&nhge->nh_list));
21790f33bffSNikolay Aleksandrov 		nexthop_put(nhge->nh);
21890f33bffSNikolay Aleksandrov 	}
21990f33bffSNikolay Aleksandrov 
22090f33bffSNikolay Aleksandrov 	WARN_ON(nhg->spare == nhg);
22190f33bffSNikolay Aleksandrov 
22290f33bffSNikolay Aleksandrov 	kfree(nhg->spare);
223430a0491SDavid Ahern 	kfree(nhg);
224430a0491SDavid Ahern }
225430a0491SDavid Ahern 
226430a0491SDavid Ahern static void nexthop_free_single(struct nexthop *nh)
227430a0491SDavid Ahern {
228ab84be7eSDavid Ahern 	struct nh_info *nhi;
229ab84be7eSDavid Ahern 
230ab84be7eSDavid Ahern 	nhi = rcu_dereference_raw(nh->nh_info);
231597cfe4fSDavid Ahern 	switch (nhi->family) {
232597cfe4fSDavid Ahern 	case AF_INET:
233597cfe4fSDavid Ahern 		fib_nh_release(nh->net, &nhi->fib_nh);
234597cfe4fSDavid Ahern 		break;
23553010f99SDavid Ahern 	case AF_INET6:
23653010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(&nhi->fib6_nh);
23753010f99SDavid Ahern 		break;
238597cfe4fSDavid Ahern 	}
239ab84be7eSDavid Ahern 	kfree(nhi);
240430a0491SDavid Ahern }
241430a0491SDavid Ahern 
242430a0491SDavid Ahern void nexthop_free_rcu(struct rcu_head *head)
243430a0491SDavid Ahern {
244430a0491SDavid Ahern 	struct nexthop *nh = container_of(head, struct nexthop, rcu);
245430a0491SDavid Ahern 
246430a0491SDavid Ahern 	if (nh->is_group)
247430a0491SDavid Ahern 		nexthop_free_mpath(nh);
248430a0491SDavid Ahern 	else
249430a0491SDavid Ahern 		nexthop_free_single(nh);
250ab84be7eSDavid Ahern 
251ab84be7eSDavid Ahern 	kfree(nh);
252ab84be7eSDavid Ahern }
253ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_free_rcu);
254ab84be7eSDavid Ahern 
255ab84be7eSDavid Ahern static struct nexthop *nexthop_alloc(void)
256ab84be7eSDavid Ahern {
257ab84be7eSDavid Ahern 	struct nexthop *nh;
258ab84be7eSDavid Ahern 
259ab84be7eSDavid Ahern 	nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
260430a0491SDavid Ahern 	if (nh) {
2614c7e8084SDavid Ahern 		INIT_LIST_HEAD(&nh->fi_list);
262f88d8ea6SDavid Ahern 		INIT_LIST_HEAD(&nh->f6i_list);
263430a0491SDavid Ahern 		INIT_LIST_HEAD(&nh->grp_list);
26438428d68SRoopa Prabhu 		INIT_LIST_HEAD(&nh->fdb_list);
265430a0491SDavid Ahern 	}
266ab84be7eSDavid Ahern 	return nh;
267ab84be7eSDavid Ahern }
268ab84be7eSDavid Ahern 
269430a0491SDavid Ahern static struct nh_group *nexthop_grp_alloc(u16 num_nh)
270430a0491SDavid Ahern {
271430a0491SDavid Ahern 	struct nh_group *nhg;
272430a0491SDavid Ahern 
273d7d49dc7SIdo Schimmel 	nhg = kzalloc(struct_size(nhg, nh_entries, num_nh), GFP_KERNEL);
274430a0491SDavid Ahern 	if (nhg)
275430a0491SDavid Ahern 		nhg->num_nh = num_nh;
276430a0491SDavid Ahern 
277430a0491SDavid Ahern 	return nhg;
278430a0491SDavid Ahern }
279430a0491SDavid Ahern 
280ab84be7eSDavid Ahern static void nh_base_seq_inc(struct net *net)
281ab84be7eSDavid Ahern {
282ab84be7eSDavid Ahern 	while (++net->nexthop.seq == 0)
283ab84be7eSDavid Ahern 		;
284ab84be7eSDavid Ahern }
285ab84be7eSDavid Ahern 
286ab84be7eSDavid Ahern /* no reference taken; rcu lock or rtnl must be held */
287ab84be7eSDavid Ahern struct nexthop *nexthop_find_by_id(struct net *net, u32 id)
288ab84be7eSDavid Ahern {
289ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
290ab84be7eSDavid Ahern 
291ab84be7eSDavid Ahern 	pp = &net->nexthop.rb_root.rb_node;
292ab84be7eSDavid Ahern 	while (1) {
293ab84be7eSDavid Ahern 		struct nexthop *nh;
294ab84be7eSDavid Ahern 
295ab84be7eSDavid Ahern 		next = rcu_dereference_raw(*pp);
296ab84be7eSDavid Ahern 		if (!next)
297ab84be7eSDavid Ahern 			break;
298ab84be7eSDavid Ahern 		parent = next;
299ab84be7eSDavid Ahern 
300ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
301ab84be7eSDavid Ahern 		if (id < nh->id)
302ab84be7eSDavid Ahern 			pp = &next->rb_left;
303ab84be7eSDavid Ahern 		else if (id > nh->id)
304ab84be7eSDavid Ahern 			pp = &next->rb_right;
305ab84be7eSDavid Ahern 		else
306ab84be7eSDavid Ahern 			return nh;
307ab84be7eSDavid Ahern 	}
308ab84be7eSDavid Ahern 	return NULL;
309ab84be7eSDavid Ahern }
310ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_find_by_id);
311ab84be7eSDavid Ahern 
312ab84be7eSDavid Ahern /* used for auto id allocation; called with rtnl held */
313ab84be7eSDavid Ahern static u32 nh_find_unused_id(struct net *net)
314ab84be7eSDavid Ahern {
315ab84be7eSDavid Ahern 	u32 id_start = net->nexthop.last_id_allocated;
316ab84be7eSDavid Ahern 
317ab84be7eSDavid Ahern 	while (1) {
318ab84be7eSDavid Ahern 		net->nexthop.last_id_allocated++;
319ab84be7eSDavid Ahern 		if (net->nexthop.last_id_allocated == id_start)
320ab84be7eSDavid Ahern 			break;
321ab84be7eSDavid Ahern 
322ab84be7eSDavid Ahern 		if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated))
323ab84be7eSDavid Ahern 			return net->nexthop.last_id_allocated;
324ab84be7eSDavid Ahern 	}
325ab84be7eSDavid Ahern 	return 0;
326ab84be7eSDavid Ahern }
327ab84be7eSDavid Ahern 
328430a0491SDavid Ahern static int nla_put_nh_group(struct sk_buff *skb, struct nh_group *nhg)
329430a0491SDavid Ahern {
330430a0491SDavid Ahern 	struct nexthop_grp *p;
331430a0491SDavid Ahern 	size_t len = nhg->num_nh * sizeof(*p);
332430a0491SDavid Ahern 	struct nlattr *nla;
333430a0491SDavid Ahern 	u16 group_type = 0;
334430a0491SDavid Ahern 	int i;
335430a0491SDavid Ahern 
336430a0491SDavid Ahern 	if (nhg->mpath)
337430a0491SDavid Ahern 		group_type = NEXTHOP_GRP_TYPE_MPATH;
338430a0491SDavid Ahern 
339430a0491SDavid Ahern 	if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type))
340430a0491SDavid Ahern 		goto nla_put_failure;
341430a0491SDavid Ahern 
342430a0491SDavid Ahern 	nla = nla_reserve(skb, NHA_GROUP, len);
343430a0491SDavid Ahern 	if (!nla)
344430a0491SDavid Ahern 		goto nla_put_failure;
345430a0491SDavid Ahern 
346430a0491SDavid Ahern 	p = nla_data(nla);
347430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
348430a0491SDavid Ahern 		p->id = nhg->nh_entries[i].nh->id;
349430a0491SDavid Ahern 		p->weight = nhg->nh_entries[i].weight - 1;
350430a0491SDavid Ahern 		p += 1;
351430a0491SDavid Ahern 	}
352430a0491SDavid Ahern 
353430a0491SDavid Ahern 	return 0;
354430a0491SDavid Ahern 
355430a0491SDavid Ahern nla_put_failure:
356430a0491SDavid Ahern 	return -EMSGSIZE;
357430a0491SDavid Ahern }
358430a0491SDavid Ahern 
359ab84be7eSDavid Ahern static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
360ab84be7eSDavid Ahern 			int event, u32 portid, u32 seq, unsigned int nlflags)
361ab84be7eSDavid Ahern {
36253010f99SDavid Ahern 	struct fib6_nh *fib6_nh;
363597cfe4fSDavid Ahern 	struct fib_nh *fib_nh;
364ab84be7eSDavid Ahern 	struct nlmsghdr *nlh;
365ab84be7eSDavid Ahern 	struct nh_info *nhi;
366ab84be7eSDavid Ahern 	struct nhmsg *nhm;
367ab84be7eSDavid Ahern 
368ab84be7eSDavid Ahern 	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
369ab84be7eSDavid Ahern 	if (!nlh)
370ab84be7eSDavid Ahern 		return -EMSGSIZE;
371ab84be7eSDavid Ahern 
372ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
373ab84be7eSDavid Ahern 	nhm->nh_family = AF_UNSPEC;
374ab84be7eSDavid Ahern 	nhm->nh_flags = nh->nh_flags;
375ab84be7eSDavid Ahern 	nhm->nh_protocol = nh->protocol;
376ab84be7eSDavid Ahern 	nhm->nh_scope = 0;
377ab84be7eSDavid Ahern 	nhm->resvd = 0;
378ab84be7eSDavid Ahern 
379ab84be7eSDavid Ahern 	if (nla_put_u32(skb, NHA_ID, nh->id))
380ab84be7eSDavid Ahern 		goto nla_put_failure;
381ab84be7eSDavid Ahern 
382430a0491SDavid Ahern 	if (nh->is_group) {
383430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
384430a0491SDavid Ahern 
385ce9ac056SDavid Ahern 		if (nhg->fdb_nh && nla_put_flag(skb, NHA_FDB))
386ce9ac056SDavid Ahern 			goto nla_put_failure;
387430a0491SDavid Ahern 		if (nla_put_nh_group(skb, nhg))
388430a0491SDavid Ahern 			goto nla_put_failure;
389430a0491SDavid Ahern 		goto out;
390430a0491SDavid Ahern 	}
391430a0491SDavid Ahern 
392ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
393ab84be7eSDavid Ahern 	nhm->nh_family = nhi->family;
394ab84be7eSDavid Ahern 	if (nhi->reject_nh) {
395ab84be7eSDavid Ahern 		if (nla_put_flag(skb, NHA_BLACKHOLE))
396ab84be7eSDavid Ahern 			goto nla_put_failure;
397ab84be7eSDavid Ahern 		goto out;
398ce9ac056SDavid Ahern 	} else if (nhi->fdb_nh) {
399ce9ac056SDavid Ahern 		if (nla_put_flag(skb, NHA_FDB))
400ce9ac056SDavid Ahern 			goto nla_put_failure;
401ce9ac056SDavid Ahern 	} else {
402597cfe4fSDavid Ahern 		const struct net_device *dev;
403597cfe4fSDavid Ahern 
404597cfe4fSDavid Ahern 		dev = nhi->fib_nhc.nhc_dev;
405597cfe4fSDavid Ahern 		if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex))
406597cfe4fSDavid Ahern 			goto nla_put_failure;
407597cfe4fSDavid Ahern 	}
408597cfe4fSDavid Ahern 
409597cfe4fSDavid Ahern 	nhm->nh_scope = nhi->fib_nhc.nhc_scope;
410597cfe4fSDavid Ahern 	switch (nhi->family) {
411597cfe4fSDavid Ahern 	case AF_INET:
412597cfe4fSDavid Ahern 		fib_nh = &nhi->fib_nh;
413597cfe4fSDavid Ahern 		if (fib_nh->fib_nh_gw_family &&
41433d80996SIdo Schimmel 		    nla_put_be32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4))
415597cfe4fSDavid Ahern 			goto nla_put_failure;
416597cfe4fSDavid Ahern 		break;
41753010f99SDavid Ahern 
41853010f99SDavid Ahern 	case AF_INET6:
41953010f99SDavid Ahern 		fib6_nh = &nhi->fib6_nh;
42053010f99SDavid Ahern 		if (fib6_nh->fib_nh_gw_family &&
42153010f99SDavid Ahern 		    nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6))
42253010f99SDavid Ahern 			goto nla_put_failure;
42353010f99SDavid Ahern 		break;
424ab84be7eSDavid Ahern 	}
425ab84be7eSDavid Ahern 
426b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate &&
427b513bd03SDavid Ahern 	    lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate,
428b513bd03SDavid Ahern 				NHA_ENCAP, NHA_ENCAP_TYPE) < 0)
429b513bd03SDavid Ahern 		goto nla_put_failure;
430b513bd03SDavid Ahern 
431ab84be7eSDavid Ahern out:
432ab84be7eSDavid Ahern 	nlmsg_end(skb, nlh);
433ab84be7eSDavid Ahern 	return 0;
434ab84be7eSDavid Ahern 
435ab84be7eSDavid Ahern nla_put_failure:
436d69100b8SStephen Worley 	nlmsg_cancel(skb, nlh);
437ab84be7eSDavid Ahern 	return -EMSGSIZE;
438ab84be7eSDavid Ahern }
439ab84be7eSDavid Ahern 
440430a0491SDavid Ahern static size_t nh_nlmsg_size_grp(struct nexthop *nh)
441430a0491SDavid Ahern {
442430a0491SDavid Ahern 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
443430a0491SDavid Ahern 	size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh;
444430a0491SDavid Ahern 
445430a0491SDavid Ahern 	return nla_total_size(sz) +
446430a0491SDavid Ahern 	       nla_total_size(2);  /* NHA_GROUP_TYPE */
447430a0491SDavid Ahern }
448430a0491SDavid Ahern 
449430a0491SDavid Ahern static size_t nh_nlmsg_size_single(struct nexthop *nh)
450ab84be7eSDavid Ahern {
451597cfe4fSDavid Ahern 	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
452430a0491SDavid Ahern 	size_t sz;
453ab84be7eSDavid Ahern 
454ab84be7eSDavid Ahern 	/* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE
455ab84be7eSDavid Ahern 	 * are mutually exclusive
456ab84be7eSDavid Ahern 	 */
457430a0491SDavid Ahern 	sz = nla_total_size(4);  /* NHA_OIF */
458ab84be7eSDavid Ahern 
459597cfe4fSDavid Ahern 	switch (nhi->family) {
460597cfe4fSDavid Ahern 	case AF_INET:
461597cfe4fSDavid Ahern 		if (nhi->fib_nh.fib_nh_gw_family)
462597cfe4fSDavid Ahern 			sz += nla_total_size(4);  /* NHA_GATEWAY */
463597cfe4fSDavid Ahern 		break;
46453010f99SDavid Ahern 
46553010f99SDavid Ahern 	case AF_INET6:
46653010f99SDavid Ahern 		/* NHA_GATEWAY */
46753010f99SDavid Ahern 		if (nhi->fib6_nh.fib_nh_gw_family)
46853010f99SDavid Ahern 			sz += nla_total_size(sizeof(const struct in6_addr));
46953010f99SDavid Ahern 		break;
470597cfe4fSDavid Ahern 	}
471597cfe4fSDavid Ahern 
472b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate) {
473b513bd03SDavid Ahern 		sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
474b513bd03SDavid Ahern 		sz += nla_total_size(2);  /* NHA_ENCAP_TYPE */
475b513bd03SDavid Ahern 	}
476b513bd03SDavid Ahern 
477ab84be7eSDavid Ahern 	return sz;
478ab84be7eSDavid Ahern }
479ab84be7eSDavid Ahern 
480430a0491SDavid Ahern static size_t nh_nlmsg_size(struct nexthop *nh)
481430a0491SDavid Ahern {
482f9e95555SStephen Worley 	size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg));
483f9e95555SStephen Worley 
484f9e95555SStephen Worley 	sz += nla_total_size(4); /* NHA_ID */
485430a0491SDavid Ahern 
486430a0491SDavid Ahern 	if (nh->is_group)
487430a0491SDavid Ahern 		sz += nh_nlmsg_size_grp(nh);
488430a0491SDavid Ahern 	else
489430a0491SDavid Ahern 		sz += nh_nlmsg_size_single(nh);
490430a0491SDavid Ahern 
491430a0491SDavid Ahern 	return sz;
492430a0491SDavid Ahern }
493430a0491SDavid Ahern 
494ab84be7eSDavid Ahern static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info)
495ab84be7eSDavid Ahern {
496ab84be7eSDavid Ahern 	unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0;
497ab84be7eSDavid Ahern 	u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
498ab84be7eSDavid Ahern 	struct sk_buff *skb;
499ab84be7eSDavid Ahern 	int err = -ENOBUFS;
500ab84be7eSDavid Ahern 
501ab84be7eSDavid Ahern 	skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any());
502ab84be7eSDavid Ahern 	if (!skb)
503ab84be7eSDavid Ahern 		goto errout;
504ab84be7eSDavid Ahern 
505ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags);
506ab84be7eSDavid Ahern 	if (err < 0) {
507ab84be7eSDavid Ahern 		/* -EMSGSIZE implies BUG in nh_nlmsg_size() */
508ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
509ab84be7eSDavid Ahern 		kfree_skb(skb);
510ab84be7eSDavid Ahern 		goto errout;
511ab84be7eSDavid Ahern 	}
512ab84be7eSDavid Ahern 
513ab84be7eSDavid Ahern 	rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP,
514ab84be7eSDavid Ahern 		    info->nlh, gfp_any());
515ab84be7eSDavid Ahern 	return;
516ab84be7eSDavid Ahern errout:
517ab84be7eSDavid Ahern 	if (err < 0)
518ab84be7eSDavid Ahern 		rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err);
519ab84be7eSDavid Ahern }
520ab84be7eSDavid Ahern 
521430a0491SDavid Ahern static bool valid_group_nh(struct nexthop *nh, unsigned int npaths,
522ce9ac056SDavid Ahern 			   bool *is_fdb, struct netlink_ext_ack *extack)
523597cfe4fSDavid Ahern {
524430a0491SDavid Ahern 	if (nh->is_group) {
525430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
526430a0491SDavid Ahern 
527430a0491SDavid Ahern 		/* nested multipath (group within a group) is not
528430a0491SDavid Ahern 		 * supported
529430a0491SDavid Ahern 		 */
530430a0491SDavid Ahern 		if (nhg->mpath) {
531430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
532430a0491SDavid Ahern 				       "Multipath group can not be a nexthop within a group");
533430a0491SDavid Ahern 			return false;
534430a0491SDavid Ahern 		}
535ce9ac056SDavid Ahern 		*is_fdb = nhg->fdb_nh;
536430a0491SDavid Ahern 	} else {
537430a0491SDavid Ahern 		struct nh_info *nhi = rtnl_dereference(nh->nh_info);
538430a0491SDavid Ahern 
539430a0491SDavid Ahern 		if (nhi->reject_nh && npaths > 1) {
540430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
541430a0491SDavid Ahern 				       "Blackhole nexthop can not be used in a group with more than 1 path");
542430a0491SDavid Ahern 			return false;
543430a0491SDavid Ahern 		}
544ce9ac056SDavid Ahern 		*is_fdb = nhi->fdb_nh;
545430a0491SDavid Ahern 	}
546430a0491SDavid Ahern 
547430a0491SDavid Ahern 	return true;
548430a0491SDavid Ahern }
549430a0491SDavid Ahern 
55038428d68SRoopa Prabhu static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family,
55138428d68SRoopa Prabhu 				   struct netlink_ext_ack *extack)
55238428d68SRoopa Prabhu {
55338428d68SRoopa Prabhu 	struct nh_info *nhi;
55438428d68SRoopa Prabhu 
555ce9ac056SDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
556ce9ac056SDavid Ahern 
557ce9ac056SDavid Ahern 	if (!nhi->fdb_nh) {
55838428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops");
55938428d68SRoopa Prabhu 		return -EINVAL;
56038428d68SRoopa Prabhu 	}
56138428d68SRoopa Prabhu 
56238428d68SRoopa Prabhu 	if (*nh_family == AF_UNSPEC) {
56338428d68SRoopa Prabhu 		*nh_family = nhi->family;
56438428d68SRoopa Prabhu 	} else if (*nh_family != nhi->family) {
56538428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops");
56638428d68SRoopa Prabhu 		return -EINVAL;
56738428d68SRoopa Prabhu 	}
56838428d68SRoopa Prabhu 
56938428d68SRoopa Prabhu 	return 0;
57038428d68SRoopa Prabhu }
57138428d68SRoopa Prabhu 
572430a0491SDavid Ahern static int nh_check_attr_group(struct net *net, struct nlattr *tb[],
573430a0491SDavid Ahern 			       struct netlink_ext_ack *extack)
574430a0491SDavid Ahern {
575430a0491SDavid Ahern 	unsigned int len = nla_len(tb[NHA_GROUP]);
57638428d68SRoopa Prabhu 	u8 nh_family = AF_UNSPEC;
577430a0491SDavid Ahern 	struct nexthop_grp *nhg;
578430a0491SDavid Ahern 	unsigned int i, j;
57938428d68SRoopa Prabhu 	u8 nhg_fdb = 0;
580430a0491SDavid Ahern 
581eeaac363SNikolay Aleksandrov 	if (!len || len & (sizeof(struct nexthop_grp) - 1)) {
582430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
583430a0491SDavid Ahern 			       "Invalid length for nexthop group attribute");
584430a0491SDavid Ahern 		return -EINVAL;
585430a0491SDavid Ahern 	}
586430a0491SDavid Ahern 
587430a0491SDavid Ahern 	/* convert len to number of nexthop ids */
588430a0491SDavid Ahern 	len /= sizeof(*nhg);
589430a0491SDavid Ahern 
590430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
591430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
592430a0491SDavid Ahern 		if (nhg[i].resvd1 || nhg[i].resvd2) {
593430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0");
594430a0491SDavid Ahern 			return -EINVAL;
595430a0491SDavid Ahern 		}
596430a0491SDavid Ahern 		if (nhg[i].weight > 254) {
597430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid value for weight");
598430a0491SDavid Ahern 			return -EINVAL;
599430a0491SDavid Ahern 		}
600430a0491SDavid Ahern 		for (j = i + 1; j < len; ++j) {
601430a0491SDavid Ahern 			if (nhg[i].id == nhg[j].id) {
602430a0491SDavid Ahern 				NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group");
603430a0491SDavid Ahern 				return -EINVAL;
604430a0491SDavid Ahern 			}
605430a0491SDavid Ahern 		}
606430a0491SDavid Ahern 	}
607430a0491SDavid Ahern 
60838428d68SRoopa Prabhu 	if (tb[NHA_FDB])
60938428d68SRoopa Prabhu 		nhg_fdb = 1;
610430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
611430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
612430a0491SDavid Ahern 		struct nexthop *nh;
613ce9ac056SDavid Ahern 		bool is_fdb_nh;
614430a0491SDavid Ahern 
615430a0491SDavid Ahern 		nh = nexthop_find_by_id(net, nhg[i].id);
616430a0491SDavid Ahern 		if (!nh) {
617430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid nexthop id");
618430a0491SDavid Ahern 			return -EINVAL;
619430a0491SDavid Ahern 		}
620ce9ac056SDavid Ahern 		if (!valid_group_nh(nh, len, &is_fdb_nh, extack))
621430a0491SDavid Ahern 			return -EINVAL;
62238428d68SRoopa Prabhu 
62338428d68SRoopa Prabhu 		if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack))
62438428d68SRoopa Prabhu 			return -EINVAL;
62538428d68SRoopa Prabhu 
626ce9ac056SDavid Ahern 		if (!nhg_fdb && is_fdb_nh) {
62738428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops");
62838428d68SRoopa Prabhu 			return -EINVAL;
62938428d68SRoopa Prabhu 		}
630430a0491SDavid Ahern 	}
63184be69b8SDavid Ahern 	for (i = NHA_GROUP_TYPE + 1; i < __NHA_MAX; ++i) {
632430a0491SDavid Ahern 		if (!tb[i])
633430a0491SDavid Ahern 			continue;
634b19218b2SPetr Machata 		if (i == NHA_FDB)
63538428d68SRoopa Prabhu 			continue;
636430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
637430a0491SDavid Ahern 			       "No other attributes can be set in nexthop groups");
638430a0491SDavid Ahern 		return -EINVAL;
639430a0491SDavid Ahern 	}
640430a0491SDavid Ahern 
641430a0491SDavid Ahern 	return 0;
642430a0491SDavid Ahern }
643430a0491SDavid Ahern 
644430a0491SDavid Ahern static bool ipv6_good_nh(const struct fib6_nh *nh)
645430a0491SDavid Ahern {
646430a0491SDavid Ahern 	int state = NUD_REACHABLE;
647430a0491SDavid Ahern 	struct neighbour *n;
648430a0491SDavid Ahern 
649430a0491SDavid Ahern 	rcu_read_lock_bh();
650430a0491SDavid Ahern 
651430a0491SDavid Ahern 	n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
652430a0491SDavid Ahern 	if (n)
653430a0491SDavid Ahern 		state = n->nud_state;
654430a0491SDavid Ahern 
655430a0491SDavid Ahern 	rcu_read_unlock_bh();
656430a0491SDavid Ahern 
657430a0491SDavid Ahern 	return !!(state & NUD_VALID);
658430a0491SDavid Ahern }
659430a0491SDavid Ahern 
660430a0491SDavid Ahern static bool ipv4_good_nh(const struct fib_nh *nh)
661430a0491SDavid Ahern {
662430a0491SDavid Ahern 	int state = NUD_REACHABLE;
663430a0491SDavid Ahern 	struct neighbour *n;
664430a0491SDavid Ahern 
665430a0491SDavid Ahern 	rcu_read_lock_bh();
666430a0491SDavid Ahern 
667430a0491SDavid Ahern 	n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
668430a0491SDavid Ahern 				      (__force u32)nh->fib_nh_gw4);
669430a0491SDavid Ahern 	if (n)
670430a0491SDavid Ahern 		state = n->nud_state;
671430a0491SDavid Ahern 
672430a0491SDavid Ahern 	rcu_read_unlock_bh();
673430a0491SDavid Ahern 
674430a0491SDavid Ahern 	return !!(state & NUD_VALID);
675430a0491SDavid Ahern }
676430a0491SDavid Ahern 
677430a0491SDavid Ahern struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
678430a0491SDavid Ahern {
679430a0491SDavid Ahern 	struct nexthop *rc = NULL;
680430a0491SDavid Ahern 	struct nh_group *nhg;
681430a0491SDavid Ahern 	int i;
682430a0491SDavid Ahern 
683430a0491SDavid Ahern 	if (!nh->is_group)
684430a0491SDavid Ahern 		return nh;
685430a0491SDavid Ahern 
686430a0491SDavid Ahern 	nhg = rcu_dereference(nh->nh_grp);
687430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
688430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
689430a0491SDavid Ahern 		struct nh_info *nhi;
690430a0491SDavid Ahern 
691430a0491SDavid Ahern 		if (hash > atomic_read(&nhge->upper_bound))
692430a0491SDavid Ahern 			continue;
693430a0491SDavid Ahern 
694ce9ac056SDavid Ahern 		nhi = rcu_dereference(nhge->nh->nh_info);
695ce9ac056SDavid Ahern 		if (nhi->fdb_nh)
69638428d68SRoopa Prabhu 			return nhge->nh;
69738428d68SRoopa Prabhu 
698430a0491SDavid Ahern 		/* nexthops always check if it is good and does
699430a0491SDavid Ahern 		 * not rely on a sysctl for this behavior
700430a0491SDavid Ahern 		 */
701430a0491SDavid Ahern 		switch (nhi->family) {
702430a0491SDavid Ahern 		case AF_INET:
703430a0491SDavid Ahern 			if (ipv4_good_nh(&nhi->fib_nh))
704430a0491SDavid Ahern 				return nhge->nh;
705430a0491SDavid Ahern 			break;
706430a0491SDavid Ahern 		case AF_INET6:
707430a0491SDavid Ahern 			if (ipv6_good_nh(&nhi->fib6_nh))
708430a0491SDavid Ahern 				return nhge->nh;
709430a0491SDavid Ahern 			break;
710430a0491SDavid Ahern 		}
711430a0491SDavid Ahern 
712430a0491SDavid Ahern 		if (!rc)
713430a0491SDavid Ahern 			rc = nhge->nh;
714430a0491SDavid Ahern 	}
715430a0491SDavid Ahern 
716430a0491SDavid Ahern 	return rc;
717430a0491SDavid Ahern }
718430a0491SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_select_path);
719430a0491SDavid Ahern 
720f88c9aa1SDavid Ahern int nexthop_for_each_fib6_nh(struct nexthop *nh,
721f88c9aa1SDavid Ahern 			     int (*cb)(struct fib6_nh *nh, void *arg),
722f88c9aa1SDavid Ahern 			     void *arg)
723f88c9aa1SDavid Ahern {
724f88c9aa1SDavid Ahern 	struct nh_info *nhi;
725f88c9aa1SDavid Ahern 	int err;
726f88c9aa1SDavid Ahern 
727f88c9aa1SDavid Ahern 	if (nh->is_group) {
728f88c9aa1SDavid Ahern 		struct nh_group *nhg;
729f88c9aa1SDavid Ahern 		int i;
730f88c9aa1SDavid Ahern 
731f88c9aa1SDavid Ahern 		nhg = rcu_dereference_rtnl(nh->nh_grp);
732f88c9aa1SDavid Ahern 		for (i = 0; i < nhg->num_nh; i++) {
733f88c9aa1SDavid Ahern 			struct nh_grp_entry *nhge = &nhg->nh_entries[i];
734f88c9aa1SDavid Ahern 
735f88c9aa1SDavid Ahern 			nhi = rcu_dereference_rtnl(nhge->nh->nh_info);
736f88c9aa1SDavid Ahern 			err = cb(&nhi->fib6_nh, arg);
737f88c9aa1SDavid Ahern 			if (err)
738f88c9aa1SDavid Ahern 				return err;
739f88c9aa1SDavid Ahern 		}
740f88c9aa1SDavid Ahern 	} else {
741f88c9aa1SDavid Ahern 		nhi = rcu_dereference_rtnl(nh->nh_info);
742f88c9aa1SDavid Ahern 		err = cb(&nhi->fib6_nh, arg);
743f88c9aa1SDavid Ahern 		if (err)
744f88c9aa1SDavid Ahern 			return err;
745f88c9aa1SDavid Ahern 	}
746f88c9aa1SDavid Ahern 
747f88c9aa1SDavid Ahern 	return 0;
748f88c9aa1SDavid Ahern }
749f88c9aa1SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh);
750f88c9aa1SDavid Ahern 
7517bf4796dSDavid Ahern static int check_src_addr(const struct in6_addr *saddr,
7527bf4796dSDavid Ahern 			  struct netlink_ext_ack *extack)
7537bf4796dSDavid Ahern {
7547bf4796dSDavid Ahern 	if (!ipv6_addr_any(saddr)) {
7557bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
7567bf4796dSDavid Ahern 		return -EINVAL;
7577bf4796dSDavid Ahern 	}
7587bf4796dSDavid Ahern 	return 0;
7597bf4796dSDavid Ahern }
7607bf4796dSDavid Ahern 
761f88d8ea6SDavid Ahern int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
762f88d8ea6SDavid Ahern 		       struct netlink_ext_ack *extack)
763f88d8ea6SDavid Ahern {
764f88d8ea6SDavid Ahern 	struct nh_info *nhi;
765ce9ac056SDavid Ahern 	bool is_fdb_nh;
76638428d68SRoopa Prabhu 
767f88d8ea6SDavid Ahern 	/* fib6_src is unique to a fib6_info and limits the ability to cache
768f88d8ea6SDavid Ahern 	 * routes in fib6_nh within a nexthop that is potentially shared
769f88d8ea6SDavid Ahern 	 * across multiple fib entries. If the config wants to use source
770f88d8ea6SDavid Ahern 	 * routing it can not use nexthop objects. mlxsw also does not allow
771f88d8ea6SDavid Ahern 	 * fib6_src on routes.
772f88d8ea6SDavid Ahern 	 */
7737bf4796dSDavid Ahern 	if (cfg && check_src_addr(&cfg->fc_src, extack) < 0)
774f88d8ea6SDavid Ahern 		return -EINVAL;
775f88d8ea6SDavid Ahern 
776f88d8ea6SDavid Ahern 	if (nh->is_group) {
777f88d8ea6SDavid Ahern 		struct nh_group *nhg;
778f88d8ea6SDavid Ahern 
779f88d8ea6SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
780f88d8ea6SDavid Ahern 		if (nhg->has_v4)
781f88d8ea6SDavid Ahern 			goto no_v4_nh;
782ce9ac056SDavid Ahern 		is_fdb_nh = nhg->fdb_nh;
783f88d8ea6SDavid Ahern 	} else {
784f88d8ea6SDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
785f88d8ea6SDavid Ahern 		if (nhi->family == AF_INET)
786f88d8ea6SDavid Ahern 			goto no_v4_nh;
787ce9ac056SDavid Ahern 		is_fdb_nh = nhi->fdb_nh;
788ce9ac056SDavid Ahern 	}
789ce9ac056SDavid Ahern 
790ce9ac056SDavid Ahern 	if (is_fdb_nh) {
791ce9ac056SDavid Ahern 		NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
792ce9ac056SDavid Ahern 		return -EINVAL;
793f88d8ea6SDavid Ahern 	}
794f88d8ea6SDavid Ahern 
795f88d8ea6SDavid Ahern 	return 0;
796f88d8ea6SDavid Ahern no_v4_nh:
797f88d8ea6SDavid Ahern 	NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
798f88d8ea6SDavid Ahern 	return -EINVAL;
799f88d8ea6SDavid Ahern }
800f88d8ea6SDavid Ahern EXPORT_SYMBOL_GPL(fib6_check_nexthop);
801f88d8ea6SDavid Ahern 
8027bf4796dSDavid Ahern /* if existing nexthop has ipv6 routes linked to it, need
8037bf4796dSDavid Ahern  * to verify this new spec works with ipv6
8047bf4796dSDavid Ahern  */
8057bf4796dSDavid Ahern static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new,
8067bf4796dSDavid Ahern 			      struct netlink_ext_ack *extack)
8077bf4796dSDavid Ahern {
8087bf4796dSDavid Ahern 	struct fib6_info *f6i;
8097bf4796dSDavid Ahern 
8107bf4796dSDavid Ahern 	if (list_empty(&old->f6i_list))
8117bf4796dSDavid Ahern 		return 0;
8127bf4796dSDavid Ahern 
8137bf4796dSDavid Ahern 	list_for_each_entry(f6i, &old->f6i_list, nh_list) {
8147bf4796dSDavid Ahern 		if (check_src_addr(&f6i->fib6_src.addr, extack) < 0)
8157bf4796dSDavid Ahern 			return -EINVAL;
8167bf4796dSDavid Ahern 	}
8177bf4796dSDavid Ahern 
8187bf4796dSDavid Ahern 	return fib6_check_nexthop(new, NULL, extack);
8197bf4796dSDavid Ahern }
8207bf4796dSDavid Ahern 
821ce9ac056SDavid Ahern static int nexthop_check_scope(struct nh_info *nhi, u8 scope,
8224c7e8084SDavid Ahern 			       struct netlink_ext_ack *extack)
8234c7e8084SDavid Ahern {
8244c7e8084SDavid Ahern 	if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) {
8254c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack,
8264c7e8084SDavid Ahern 			       "Route with host scope can not have a gateway");
8274c7e8084SDavid Ahern 		return -EINVAL;
8284c7e8084SDavid Ahern 	}
8294c7e8084SDavid Ahern 
8304c7e8084SDavid Ahern 	if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) {
8314c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop");
8324c7e8084SDavid Ahern 		return -EINVAL;
8334c7e8084SDavid Ahern 	}
8344c7e8084SDavid Ahern 
8354c7e8084SDavid Ahern 	return 0;
8364c7e8084SDavid Ahern }
8374c7e8084SDavid Ahern 
8384c7e8084SDavid Ahern /* Invoked by fib add code to verify nexthop by id is ok with
8394c7e8084SDavid Ahern  * config for prefix; parts of fib_check_nh not done when nexthop
8404c7e8084SDavid Ahern  * object is used.
8414c7e8084SDavid Ahern  */
8424c7e8084SDavid Ahern int fib_check_nexthop(struct nexthop *nh, u8 scope,
8434c7e8084SDavid Ahern 		      struct netlink_ext_ack *extack)
8444c7e8084SDavid Ahern {
845ce9ac056SDavid Ahern 	struct nh_info *nhi;
8464c7e8084SDavid Ahern 	int err = 0;
8474c7e8084SDavid Ahern 
848ce9ac056SDavid Ahern 	if (nh->is_group) {
849ce9ac056SDavid Ahern 		struct nh_group *nhg;
850ce9ac056SDavid Ahern 
851ce9ac056SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
852ce9ac056SDavid Ahern 		if (nhg->fdb_nh) {
85338428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
85438428d68SRoopa Prabhu 			err = -EINVAL;
85538428d68SRoopa Prabhu 			goto out;
85638428d68SRoopa Prabhu 		}
85738428d68SRoopa Prabhu 
8584c7e8084SDavid Ahern 		if (scope == RT_SCOPE_HOST) {
8594c7e8084SDavid Ahern 			NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops");
8604c7e8084SDavid Ahern 			err = -EINVAL;
8614c7e8084SDavid Ahern 			goto out;
8624c7e8084SDavid Ahern 		}
8634c7e8084SDavid Ahern 
8644c7e8084SDavid Ahern 		/* all nexthops in a group have the same scope */
865ce9ac056SDavid Ahern 		nhi = rtnl_dereference(nhg->nh_entries[0].nh->nh_info);
866ce9ac056SDavid Ahern 		err = nexthop_check_scope(nhi, scope, extack);
8674c7e8084SDavid Ahern 	} else {
868ce9ac056SDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
869ce9ac056SDavid Ahern 		if (nhi->fdb_nh) {
870ce9ac056SDavid Ahern 			NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
871ce9ac056SDavid Ahern 			err = -EINVAL;
872ce9ac056SDavid Ahern 			goto out;
8734c7e8084SDavid Ahern 		}
874ce9ac056SDavid Ahern 		err = nexthop_check_scope(nhi, scope, extack);
875ce9ac056SDavid Ahern 	}
876ce9ac056SDavid Ahern 
8774c7e8084SDavid Ahern out:
8784c7e8084SDavid Ahern 	return err;
8794c7e8084SDavid Ahern }
8804c7e8084SDavid Ahern 
8817bf4796dSDavid Ahern static int fib_check_nh_list(struct nexthop *old, struct nexthop *new,
8827bf4796dSDavid Ahern 			     struct netlink_ext_ack *extack)
8837bf4796dSDavid Ahern {
8847bf4796dSDavid Ahern 	struct fib_info *fi;
8857bf4796dSDavid Ahern 
8867bf4796dSDavid Ahern 	list_for_each_entry(fi, &old->fi_list, nh_list) {
8877bf4796dSDavid Ahern 		int err;
8887bf4796dSDavid Ahern 
8897bf4796dSDavid Ahern 		err = fib_check_nexthop(new, fi->fib_scope, extack);
8907bf4796dSDavid Ahern 		if (err)
8917bf4796dSDavid Ahern 			return err;
8927bf4796dSDavid Ahern 	}
8937bf4796dSDavid Ahern 	return 0;
8947bf4796dSDavid Ahern }
8957bf4796dSDavid Ahern 
896430a0491SDavid Ahern static void nh_group_rebalance(struct nh_group *nhg)
897430a0491SDavid Ahern {
898430a0491SDavid Ahern 	int total = 0;
899430a0491SDavid Ahern 	int w = 0;
900430a0491SDavid Ahern 	int i;
901430a0491SDavid Ahern 
902430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i)
903430a0491SDavid Ahern 		total += nhg->nh_entries[i].weight;
904430a0491SDavid Ahern 
905430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
906430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
907430a0491SDavid Ahern 		int upper_bound;
908430a0491SDavid Ahern 
909430a0491SDavid Ahern 		w += nhge->weight;
910430a0491SDavid Ahern 		upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1;
911430a0491SDavid Ahern 		atomic_set(&nhge->upper_bound, upper_bound);
912430a0491SDavid Ahern 	}
913430a0491SDavid Ahern }
914430a0491SDavid Ahern 
915ac21753aSDavid Ahern static void remove_nh_grp_entry(struct net *net, struct nh_grp_entry *nhge,
916430a0491SDavid Ahern 				struct nl_info *nlinfo)
917430a0491SDavid Ahern {
91890f33bffSNikolay Aleksandrov 	struct nh_grp_entry *nhges, *new_nhges;
919ac21753aSDavid Ahern 	struct nexthop *nhp = nhge->nh_parent;
920833a1065SIdo Schimmel 	struct netlink_ext_ack extack;
921430a0491SDavid Ahern 	struct nexthop *nh = nhge->nh;
92290f33bffSNikolay Aleksandrov 	struct nh_group *nhg, *newg;
923833a1065SIdo Schimmel 	int i, j, err;
924430a0491SDavid Ahern 
925430a0491SDavid Ahern 	WARN_ON(!nh);
926430a0491SDavid Ahern 
927ac21753aSDavid Ahern 	nhg = rtnl_dereference(nhp->nh_grp);
92890f33bffSNikolay Aleksandrov 	newg = nhg->spare;
929430a0491SDavid Ahern 
93090f33bffSNikolay Aleksandrov 	/* last entry, keep it visible and remove the parent */
93190f33bffSNikolay Aleksandrov 	if (nhg->num_nh == 1) {
93290f33bffSNikolay Aleksandrov 		remove_nexthop(net, nhp, nlinfo);
933430a0491SDavid Ahern 		return;
93490f33bffSNikolay Aleksandrov 	}
935430a0491SDavid Ahern 
936863b2558SIdo Schimmel 	newg->has_v4 = false;
93790f33bffSNikolay Aleksandrov 	newg->mpath = nhg->mpath;
938ce9ac056SDavid Ahern 	newg->fdb_nh = nhg->fdb_nh;
93990f33bffSNikolay Aleksandrov 	newg->num_nh = nhg->num_nh;
940430a0491SDavid Ahern 
94190f33bffSNikolay Aleksandrov 	/* copy old entries to new except the one getting removed */
94290f33bffSNikolay Aleksandrov 	nhges = nhg->nh_entries;
94390f33bffSNikolay Aleksandrov 	new_nhges = newg->nh_entries;
94490f33bffSNikolay Aleksandrov 	for (i = 0, j = 0; i < nhg->num_nh; ++i) {
945863b2558SIdo Schimmel 		struct nh_info *nhi;
946863b2558SIdo Schimmel 
94790f33bffSNikolay Aleksandrov 		/* current nexthop getting removed */
94890f33bffSNikolay Aleksandrov 		if (nhg->nh_entries[i].nh == nh) {
94990f33bffSNikolay Aleksandrov 			newg->num_nh--;
95090f33bffSNikolay Aleksandrov 			continue;
95190f33bffSNikolay Aleksandrov 		}
952430a0491SDavid Ahern 
953863b2558SIdo Schimmel 		nhi = rtnl_dereference(nhges[i].nh->nh_info);
954863b2558SIdo Schimmel 		if (nhi->family == AF_INET)
955863b2558SIdo Schimmel 			newg->has_v4 = true;
956863b2558SIdo Schimmel 
95790f33bffSNikolay Aleksandrov 		list_del(&nhges[i].nh_list);
95890f33bffSNikolay Aleksandrov 		new_nhges[j].nh_parent = nhges[i].nh_parent;
95990f33bffSNikolay Aleksandrov 		new_nhges[j].nh = nhges[i].nh;
96090f33bffSNikolay Aleksandrov 		new_nhges[j].weight = nhges[i].weight;
96190f33bffSNikolay Aleksandrov 		list_add(&new_nhges[j].nh_list, &new_nhges[j].nh->grp_list);
96290f33bffSNikolay Aleksandrov 		j++;
96390f33bffSNikolay Aleksandrov 	}
96490f33bffSNikolay Aleksandrov 
96590f33bffSNikolay Aleksandrov 	nh_group_rebalance(newg);
96690f33bffSNikolay Aleksandrov 	rcu_assign_pointer(nhp->nh_grp, newg);
96790f33bffSNikolay Aleksandrov 
96890f33bffSNikolay Aleksandrov 	list_del(&nhge->nh_list);
96990f33bffSNikolay Aleksandrov 	nexthop_put(nhge->nh);
970430a0491SDavid Ahern 
971833a1065SIdo Schimmel 	err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp, &extack);
972833a1065SIdo Schimmel 	if (err)
973833a1065SIdo Schimmel 		pr_err("%s\n", extack._msg);
974833a1065SIdo Schimmel 
975430a0491SDavid Ahern 	if (nlinfo)
976ac21753aSDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, nhp, nlinfo);
977430a0491SDavid Ahern }
978430a0491SDavid Ahern 
979430a0491SDavid Ahern static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
980430a0491SDavid Ahern 				       struct nl_info *nlinfo)
981430a0491SDavid Ahern {
982430a0491SDavid Ahern 	struct nh_grp_entry *nhge, *tmp;
983430a0491SDavid Ahern 
984ac21753aSDavid Ahern 	list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list)
985ac21753aSDavid Ahern 		remove_nh_grp_entry(net, nhge, nlinfo);
986430a0491SDavid Ahern 
98790f33bffSNikolay Aleksandrov 	/* make sure all see the newly published array before releasing rtnl */
988df6afe2fSIdo Schimmel 	synchronize_net();
989430a0491SDavid Ahern }
990430a0491SDavid Ahern 
991430a0491SDavid Ahern static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
992430a0491SDavid Ahern {
993430a0491SDavid Ahern 	struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp);
994430a0491SDavid Ahern 	int i, num_nh = nhg->num_nh;
995430a0491SDavid Ahern 
996430a0491SDavid Ahern 	for (i = 0; i < num_nh; ++i) {
997430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
998430a0491SDavid Ahern 
999430a0491SDavid Ahern 		if (WARN_ON(!nhge->nh))
1000430a0491SDavid Ahern 			continue;
1001430a0491SDavid Ahern 
100290f33bffSNikolay Aleksandrov 		list_del_init(&nhge->nh_list);
1003430a0491SDavid Ahern 	}
1004430a0491SDavid Ahern }
1005430a0491SDavid Ahern 
10067bf4796dSDavid Ahern /* not called for nexthop replace */
10074c7e8084SDavid Ahern static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
10084c7e8084SDavid Ahern {
1009f88d8ea6SDavid Ahern 	struct fib6_info *f6i, *tmp;
10104c7e8084SDavid Ahern 	bool do_flush = false;
10114c7e8084SDavid Ahern 	struct fib_info *fi;
10124c7e8084SDavid Ahern 
10134c7e8084SDavid Ahern 	list_for_each_entry(fi, &nh->fi_list, nh_list) {
10144c7e8084SDavid Ahern 		fi->fib_flags |= RTNH_F_DEAD;
10154c7e8084SDavid Ahern 		do_flush = true;
10164c7e8084SDavid Ahern 	}
10174c7e8084SDavid Ahern 	if (do_flush)
10184c7e8084SDavid Ahern 		fib_flush(net);
1019f88d8ea6SDavid Ahern 
1020f88d8ea6SDavid Ahern 	/* ip6_del_rt removes the entry from this list hence the _safe */
1021f88d8ea6SDavid Ahern 	list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
1022f88d8ea6SDavid Ahern 		/* __ip6_del_rt does a release, so do a hold here */
1023f88d8ea6SDavid Ahern 		fib6_info_hold(f6i);
10244f80116dSRoopa Prabhu 		ipv6_stub->ip6_del_rt(net, f6i,
10254f80116dSRoopa Prabhu 				      !net->ipv4.sysctl_nexthop_compat_mode);
1026f88d8ea6SDavid Ahern 	}
10274c7e8084SDavid Ahern }
10284c7e8084SDavid Ahern 
1029430a0491SDavid Ahern static void __remove_nexthop(struct net *net, struct nexthop *nh,
1030430a0491SDavid Ahern 			     struct nl_info *nlinfo)
1031430a0491SDavid Ahern {
10324c7e8084SDavid Ahern 	__remove_nexthop_fib(net, nh);
10334c7e8084SDavid Ahern 
1034430a0491SDavid Ahern 	if (nh->is_group) {
1035430a0491SDavid Ahern 		remove_nexthop_group(nh, nlinfo);
1036430a0491SDavid Ahern 	} else {
1037597cfe4fSDavid Ahern 		struct nh_info *nhi;
1038597cfe4fSDavid Ahern 
1039597cfe4fSDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
1040597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev)
1041597cfe4fSDavid Ahern 			hlist_del(&nhi->dev_hash);
1042430a0491SDavid Ahern 
1043430a0491SDavid Ahern 		remove_nexthop_from_groups(net, nh, nlinfo);
1044430a0491SDavid Ahern 	}
1045597cfe4fSDavid Ahern }
1046597cfe4fSDavid Ahern 
1047ab84be7eSDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh,
1048430a0491SDavid Ahern 			   struct nl_info *nlinfo)
1049ab84be7eSDavid Ahern {
10503578d53dSIdo Schimmel 	call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh, NULL);
10510695564bSIdo Schimmel 
1052ab84be7eSDavid Ahern 	/* remove from the tree */
1053ab84be7eSDavid Ahern 	rb_erase(&nh->rb_node, &net->nexthop.rb_root);
1054ab84be7eSDavid Ahern 
1055ab84be7eSDavid Ahern 	if (nlinfo)
1056ab84be7eSDavid Ahern 		nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo);
1057ab84be7eSDavid Ahern 
1058430a0491SDavid Ahern 	__remove_nexthop(net, nh, nlinfo);
1059ab84be7eSDavid Ahern 	nh_base_seq_inc(net);
1060ab84be7eSDavid Ahern 
1061ab84be7eSDavid Ahern 	nexthop_put(nh);
1062ab84be7eSDavid Ahern }
1063ab84be7eSDavid Ahern 
10647bf4796dSDavid Ahern /* if any FIB entries reference this nexthop, any dst entries
10657bf4796dSDavid Ahern  * need to be regenerated
10667bf4796dSDavid Ahern  */
10677bf4796dSDavid Ahern static void nh_rt_cache_flush(struct net *net, struct nexthop *nh)
10687bf4796dSDavid Ahern {
10697bf4796dSDavid Ahern 	struct fib6_info *f6i;
10707bf4796dSDavid Ahern 
10717bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list))
10727bf4796dSDavid Ahern 		rt_cache_flush(net);
10737bf4796dSDavid Ahern 
10747bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
10757bf4796dSDavid Ahern 		ipv6_stub->fib6_update_sernum(net, f6i);
10767bf4796dSDavid Ahern }
10777bf4796dSDavid Ahern 
10787bf4796dSDavid Ahern static int replace_nexthop_grp(struct net *net, struct nexthop *old,
10797bf4796dSDavid Ahern 			       struct nexthop *new,
10807bf4796dSDavid Ahern 			       struct netlink_ext_ack *extack)
10817bf4796dSDavid Ahern {
10827bf4796dSDavid Ahern 	struct nh_group *oldg, *newg;
1083d144cc5fSIdo Schimmel 	int i, err;
10847bf4796dSDavid Ahern 
10857bf4796dSDavid Ahern 	if (!new->is_group) {
10867bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop.");
10877bf4796dSDavid Ahern 		return -EINVAL;
10887bf4796dSDavid Ahern 	}
10897bf4796dSDavid Ahern 
1090d144cc5fSIdo Schimmel 	err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack);
1091d144cc5fSIdo Schimmel 	if (err)
1092d144cc5fSIdo Schimmel 		return err;
1093d144cc5fSIdo Schimmel 
10947bf4796dSDavid Ahern 	oldg = rtnl_dereference(old->nh_grp);
10957bf4796dSDavid Ahern 	newg = rtnl_dereference(new->nh_grp);
10967bf4796dSDavid Ahern 
10977bf4796dSDavid Ahern 	/* update parents - used by nexthop code for cleanup */
10987bf4796dSDavid Ahern 	for (i = 0; i < newg->num_nh; i++)
10997bf4796dSDavid Ahern 		newg->nh_entries[i].nh_parent = old;
11007bf4796dSDavid Ahern 
11017bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_grp, newg);
11027bf4796dSDavid Ahern 
11037bf4796dSDavid Ahern 	for (i = 0; i < oldg->num_nh; i++)
11047bf4796dSDavid Ahern 		oldg->nh_entries[i].nh_parent = new;
11057bf4796dSDavid Ahern 
11067bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_grp, oldg);
11077bf4796dSDavid Ahern 
11087bf4796dSDavid Ahern 	return 0;
11097bf4796dSDavid Ahern }
11107bf4796dSDavid Ahern 
1111885a3b15SIdo Schimmel static void nh_group_v4_update(struct nh_group *nhg)
1112885a3b15SIdo Schimmel {
1113885a3b15SIdo Schimmel 	struct nh_grp_entry *nhges;
1114885a3b15SIdo Schimmel 	bool has_v4 = false;
1115885a3b15SIdo Schimmel 	int i;
1116885a3b15SIdo Schimmel 
1117885a3b15SIdo Schimmel 	nhges = nhg->nh_entries;
1118885a3b15SIdo Schimmel 	for (i = 0; i < nhg->num_nh; i++) {
1119885a3b15SIdo Schimmel 		struct nh_info *nhi;
1120885a3b15SIdo Schimmel 
1121885a3b15SIdo Schimmel 		nhi = rtnl_dereference(nhges[i].nh->nh_info);
1122885a3b15SIdo Schimmel 		if (nhi->family == AF_INET)
1123885a3b15SIdo Schimmel 			has_v4 = true;
1124885a3b15SIdo Schimmel 	}
1125885a3b15SIdo Schimmel 	nhg->has_v4 = has_v4;
1126885a3b15SIdo Schimmel }
1127885a3b15SIdo Schimmel 
11287bf4796dSDavid Ahern static int replace_nexthop_single(struct net *net, struct nexthop *old,
11297bf4796dSDavid Ahern 				  struct nexthop *new,
11307bf4796dSDavid Ahern 				  struct netlink_ext_ack *extack)
11317bf4796dSDavid Ahern {
1132f17bc33dSIdo Schimmel 	u8 old_protocol, old_nh_flags;
11337bf4796dSDavid Ahern 	struct nh_info *oldi, *newi;
1134f17bc33dSIdo Schimmel 	struct nh_grp_entry *nhge;
11358c09c9f9SIdo Schimmel 	int err;
11367bf4796dSDavid Ahern 
11377bf4796dSDavid Ahern 	if (new->is_group) {
11387bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group.");
11397bf4796dSDavid Ahern 		return -EINVAL;
11407bf4796dSDavid Ahern 	}
11417bf4796dSDavid Ahern 
11428c09c9f9SIdo Schimmel 	err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new, extack);
11438c09c9f9SIdo Schimmel 	if (err)
11448c09c9f9SIdo Schimmel 		return err;
11458c09c9f9SIdo Schimmel 
11468c09c9f9SIdo Schimmel 	/* Hardware flags were set on 'old' as 'new' is not in the red-black
11478c09c9f9SIdo Schimmel 	 * tree. Therefore, inherit the flags from 'old' to 'new'.
11488c09c9f9SIdo Schimmel 	 */
11498c09c9f9SIdo Schimmel 	new->nh_flags |= old->nh_flags & (RTNH_F_OFFLOAD | RTNH_F_TRAP);
11508c09c9f9SIdo Schimmel 
11517bf4796dSDavid Ahern 	oldi = rtnl_dereference(old->nh_info);
11527bf4796dSDavid Ahern 	newi = rtnl_dereference(new->nh_info);
11537bf4796dSDavid Ahern 
11547bf4796dSDavid Ahern 	newi->nh_parent = old;
11557bf4796dSDavid Ahern 	oldi->nh_parent = new;
11567bf4796dSDavid Ahern 
1157f17bc33dSIdo Schimmel 	old_protocol = old->protocol;
1158f17bc33dSIdo Schimmel 	old_nh_flags = old->nh_flags;
1159f17bc33dSIdo Schimmel 
11607bf4796dSDavid Ahern 	old->protocol = new->protocol;
11617bf4796dSDavid Ahern 	old->nh_flags = new->nh_flags;
11627bf4796dSDavid Ahern 
11637bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_info, newi);
11647bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_info, oldi);
11657bf4796dSDavid Ahern 
1166f17bc33dSIdo Schimmel 	/* Send a replace notification for all the groups using the nexthop. */
1167f17bc33dSIdo Schimmel 	list_for_each_entry(nhge, &old->grp_list, nh_list) {
1168f17bc33dSIdo Schimmel 		struct nexthop *nhp = nhge->nh_parent;
1169f17bc33dSIdo Schimmel 
1170f17bc33dSIdo Schimmel 		err = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp,
1171f17bc33dSIdo Schimmel 					     extack);
1172f17bc33dSIdo Schimmel 		if (err)
1173f17bc33dSIdo Schimmel 			goto err_notify;
1174f17bc33dSIdo Schimmel 	}
1175f17bc33dSIdo Schimmel 
1176885a3b15SIdo Schimmel 	/* When replacing an IPv4 nexthop with an IPv6 nexthop, potentially
1177885a3b15SIdo Schimmel 	 * update IPv4 indication in all the groups using the nexthop.
1178885a3b15SIdo Schimmel 	 */
1179885a3b15SIdo Schimmel 	if (oldi->family == AF_INET && newi->family == AF_INET6) {
1180885a3b15SIdo Schimmel 		list_for_each_entry(nhge, &old->grp_list, nh_list) {
1181885a3b15SIdo Schimmel 			struct nexthop *nhp = nhge->nh_parent;
1182885a3b15SIdo Schimmel 			struct nh_group *nhg;
1183885a3b15SIdo Schimmel 
1184885a3b15SIdo Schimmel 			nhg = rtnl_dereference(nhp->nh_grp);
1185885a3b15SIdo Schimmel 			nh_group_v4_update(nhg);
1186885a3b15SIdo Schimmel 		}
1187885a3b15SIdo Schimmel 	}
1188885a3b15SIdo Schimmel 
11897bf4796dSDavid Ahern 	return 0;
1190f17bc33dSIdo Schimmel 
1191f17bc33dSIdo Schimmel err_notify:
1192f17bc33dSIdo Schimmel 	rcu_assign_pointer(new->nh_info, newi);
1193f17bc33dSIdo Schimmel 	rcu_assign_pointer(old->nh_info, oldi);
1194f17bc33dSIdo Schimmel 	old->nh_flags = old_nh_flags;
1195f17bc33dSIdo Schimmel 	old->protocol = old_protocol;
1196f17bc33dSIdo Schimmel 	oldi->nh_parent = old;
1197f17bc33dSIdo Schimmel 	newi->nh_parent = new;
1198f17bc33dSIdo Schimmel 	list_for_each_entry_continue_reverse(nhge, &old->grp_list, nh_list) {
1199f17bc33dSIdo Schimmel 		struct nexthop *nhp = nhge->nh_parent;
1200f17bc33dSIdo Schimmel 
1201f17bc33dSIdo Schimmel 		call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, nhp, extack);
1202f17bc33dSIdo Schimmel 	}
1203f17bc33dSIdo Schimmel 	call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, old, extack);
1204f17bc33dSIdo Schimmel 	return err;
12057bf4796dSDavid Ahern }
12067bf4796dSDavid Ahern 
12077bf4796dSDavid Ahern static void __nexthop_replace_notify(struct net *net, struct nexthop *nh,
12087bf4796dSDavid Ahern 				     struct nl_info *info)
12097bf4796dSDavid Ahern {
12107bf4796dSDavid Ahern 	struct fib6_info *f6i;
12117bf4796dSDavid Ahern 
12127bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list)) {
12137bf4796dSDavid Ahern 		struct fib_info *fi;
12147bf4796dSDavid Ahern 
12157bf4796dSDavid Ahern 		/* expectation is a few fib_info per nexthop and then
12167bf4796dSDavid Ahern 		 * a lot of routes per fib_info. So mark the fib_info
12177bf4796dSDavid Ahern 		 * and then walk the fib tables once
12187bf4796dSDavid Ahern 		 */
12197bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
12207bf4796dSDavid Ahern 			fi->nh_updated = true;
12217bf4796dSDavid Ahern 
12227bf4796dSDavid Ahern 		fib_info_notify_update(net, info);
12237bf4796dSDavid Ahern 
12247bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
12257bf4796dSDavid Ahern 			fi->nh_updated = false;
12267bf4796dSDavid Ahern 	}
12277bf4796dSDavid Ahern 
12287bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
12297bf4796dSDavid Ahern 		ipv6_stub->fib6_rt_update(net, f6i, info);
12307bf4796dSDavid Ahern }
12317bf4796dSDavid Ahern 
12327bf4796dSDavid Ahern /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries
12337bf4796dSDavid Ahern  * linked to this nexthop and for all groups that the nexthop
12347bf4796dSDavid Ahern  * is a member of
12357bf4796dSDavid Ahern  */
12367bf4796dSDavid Ahern static void nexthop_replace_notify(struct net *net, struct nexthop *nh,
12377bf4796dSDavid Ahern 				   struct nl_info *info)
12387bf4796dSDavid Ahern {
12397bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
12407bf4796dSDavid Ahern 
12417bf4796dSDavid Ahern 	__nexthop_replace_notify(net, nh, info);
12427bf4796dSDavid Ahern 
12437bf4796dSDavid Ahern 	list_for_each_entry(nhge, &nh->grp_list, nh_list)
12447bf4796dSDavid Ahern 		__nexthop_replace_notify(net, nhge->nh_parent, info);
12457bf4796dSDavid Ahern }
12467bf4796dSDavid Ahern 
1247ab84be7eSDavid Ahern static int replace_nexthop(struct net *net, struct nexthop *old,
1248ab84be7eSDavid Ahern 			   struct nexthop *new, struct netlink_ext_ack *extack)
1249ab84be7eSDavid Ahern {
12507bf4796dSDavid Ahern 	bool new_is_reject = false;
12517bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
12527bf4796dSDavid Ahern 	int err;
12537bf4796dSDavid Ahern 
12547bf4796dSDavid Ahern 	/* check that existing FIB entries are ok with the
12557bf4796dSDavid Ahern 	 * new nexthop definition
12567bf4796dSDavid Ahern 	 */
12577bf4796dSDavid Ahern 	err = fib_check_nh_list(old, new, extack);
12587bf4796dSDavid Ahern 	if (err)
12597bf4796dSDavid Ahern 		return err;
12607bf4796dSDavid Ahern 
12617bf4796dSDavid Ahern 	err = fib6_check_nh_list(old, new, extack);
12627bf4796dSDavid Ahern 	if (err)
12637bf4796dSDavid Ahern 		return err;
12647bf4796dSDavid Ahern 
12657bf4796dSDavid Ahern 	if (!new->is_group) {
12667bf4796dSDavid Ahern 		struct nh_info *nhi = rtnl_dereference(new->nh_info);
12677bf4796dSDavid Ahern 
12687bf4796dSDavid Ahern 		new_is_reject = nhi->reject_nh;
12697bf4796dSDavid Ahern 	}
12707bf4796dSDavid Ahern 
12717bf4796dSDavid Ahern 	list_for_each_entry(nhge, &old->grp_list, nh_list) {
12727bf4796dSDavid Ahern 		/* if new nexthop is a blackhole, any groups using this
12737bf4796dSDavid Ahern 		 * nexthop cannot have more than 1 path
12747bf4796dSDavid Ahern 		 */
12757bf4796dSDavid Ahern 		if (new_is_reject &&
12767bf4796dSDavid Ahern 		    nexthop_num_path(nhge->nh_parent) > 1) {
12777bf4796dSDavid Ahern 			NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path");
12787bf4796dSDavid Ahern 			return -EINVAL;
12797bf4796dSDavid Ahern 		}
12807bf4796dSDavid Ahern 
12817bf4796dSDavid Ahern 		err = fib_check_nh_list(nhge->nh_parent, new, extack);
12827bf4796dSDavid Ahern 		if (err)
12837bf4796dSDavid Ahern 			return err;
12847bf4796dSDavid Ahern 
12857bf4796dSDavid Ahern 		err = fib6_check_nh_list(nhge->nh_parent, new, extack);
12867bf4796dSDavid Ahern 		if (err)
12877bf4796dSDavid Ahern 			return err;
12887bf4796dSDavid Ahern 	}
12897bf4796dSDavid Ahern 
12907bf4796dSDavid Ahern 	if (old->is_group)
12917bf4796dSDavid Ahern 		err = replace_nexthop_grp(net, old, new, extack);
12927bf4796dSDavid Ahern 	else
12937bf4796dSDavid Ahern 		err = replace_nexthop_single(net, old, new, extack);
12947bf4796dSDavid Ahern 
12957bf4796dSDavid Ahern 	if (!err) {
12967bf4796dSDavid Ahern 		nh_rt_cache_flush(net, old);
12977bf4796dSDavid Ahern 
12987bf4796dSDavid Ahern 		__remove_nexthop(net, new, NULL);
12997bf4796dSDavid Ahern 		nexthop_put(new);
13007bf4796dSDavid Ahern 	}
13017bf4796dSDavid Ahern 
13027bf4796dSDavid Ahern 	return err;
1303ab84be7eSDavid Ahern }
1304ab84be7eSDavid Ahern 
1305ab84be7eSDavid Ahern /* called with rtnl_lock held */
1306ab84be7eSDavid Ahern static int insert_nexthop(struct net *net, struct nexthop *new_nh,
1307ab84be7eSDavid Ahern 			  struct nh_config *cfg, struct netlink_ext_ack *extack)
1308ab84be7eSDavid Ahern {
1309ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
1310ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
1311ab84be7eSDavid Ahern 	bool replace = !!(cfg->nlflags & NLM_F_REPLACE);
1312ab84be7eSDavid Ahern 	bool create = !!(cfg->nlflags & NLM_F_CREATE);
1313ab84be7eSDavid Ahern 	u32 new_id = new_nh->id;
13147bf4796dSDavid Ahern 	int replace_notify = 0;
1315ab84be7eSDavid Ahern 	int rc = -EEXIST;
1316ab84be7eSDavid Ahern 
1317ab84be7eSDavid Ahern 	pp = &root->rb_node;
1318ab84be7eSDavid Ahern 	while (1) {
1319ab84be7eSDavid Ahern 		struct nexthop *nh;
1320ab84be7eSDavid Ahern 
1321233c6378SIdo Schimmel 		next = *pp;
1322ab84be7eSDavid Ahern 		if (!next)
1323ab84be7eSDavid Ahern 			break;
1324ab84be7eSDavid Ahern 
1325ab84be7eSDavid Ahern 		parent = next;
1326ab84be7eSDavid Ahern 
1327ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
1328ab84be7eSDavid Ahern 		if (new_id < nh->id) {
1329ab84be7eSDavid Ahern 			pp = &next->rb_left;
1330ab84be7eSDavid Ahern 		} else if (new_id > nh->id) {
1331ab84be7eSDavid Ahern 			pp = &next->rb_right;
1332ab84be7eSDavid Ahern 		} else if (replace) {
1333ab84be7eSDavid Ahern 			rc = replace_nexthop(net, nh, new_nh, extack);
13347bf4796dSDavid Ahern 			if (!rc) {
1335ab84be7eSDavid Ahern 				new_nh = nh; /* send notification with old nh */
13367bf4796dSDavid Ahern 				replace_notify = 1;
13377bf4796dSDavid Ahern 			}
1338ab84be7eSDavid Ahern 			goto out;
1339ab84be7eSDavid Ahern 		} else {
1340ab84be7eSDavid Ahern 			/* id already exists and not a replace */
1341ab84be7eSDavid Ahern 			goto out;
1342ab84be7eSDavid Ahern 		}
1343ab84be7eSDavid Ahern 	}
1344ab84be7eSDavid Ahern 
1345ab84be7eSDavid Ahern 	if (replace && !create) {
1346ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists");
1347ab84be7eSDavid Ahern 		rc = -ENOENT;
1348ab84be7eSDavid Ahern 		goto out;
1349ab84be7eSDavid Ahern 	}
1350ab84be7eSDavid Ahern 
1351ab84be7eSDavid Ahern 	rb_link_node_rcu(&new_nh->rb_node, parent, pp);
1352ab84be7eSDavid Ahern 	rb_insert_color(&new_nh->rb_node, root);
1353732d167bSIdo Schimmel 
1354732d167bSIdo Schimmel 	rc = call_nexthop_notifiers(net, NEXTHOP_EVENT_REPLACE, new_nh, extack);
1355732d167bSIdo Schimmel 	if (rc)
1356732d167bSIdo Schimmel 		rb_erase(&new_nh->rb_node, &net->nexthop.rb_root);
1357732d167bSIdo Schimmel 
1358ab84be7eSDavid Ahern out:
1359ab84be7eSDavid Ahern 	if (!rc) {
1360ab84be7eSDavid Ahern 		nh_base_seq_inc(net);
1361ab84be7eSDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo);
13624f80116dSRoopa Prabhu 		if (replace_notify && net->ipv4.sysctl_nexthop_compat_mode)
13637bf4796dSDavid Ahern 			nexthop_replace_notify(net, new_nh, &cfg->nlinfo);
1364ab84be7eSDavid Ahern 	}
1365ab84be7eSDavid Ahern 
1366ab84be7eSDavid Ahern 	return rc;
1367ab84be7eSDavid Ahern }
1368ab84be7eSDavid Ahern 
1369597cfe4fSDavid Ahern /* rtnl */
1370597cfe4fSDavid Ahern /* remove all nexthops tied to a device being deleted */
1371597cfe4fSDavid Ahern static void nexthop_flush_dev(struct net_device *dev)
1372597cfe4fSDavid Ahern {
1373597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
1374597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
1375597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
1376597cfe4fSDavid Ahern 	struct hlist_node *n;
1377597cfe4fSDavid Ahern 	struct nh_info *nhi;
1378597cfe4fSDavid Ahern 
1379597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
1380597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev != dev)
1381597cfe4fSDavid Ahern 			continue;
1382597cfe4fSDavid Ahern 
1383430a0491SDavid Ahern 		remove_nexthop(net, nhi->nh_parent, NULL);
1384597cfe4fSDavid Ahern 	}
1385597cfe4fSDavid Ahern }
1386597cfe4fSDavid Ahern 
1387ab84be7eSDavid Ahern /* rtnl; called when net namespace is deleted */
1388ab84be7eSDavid Ahern static void flush_all_nexthops(struct net *net)
1389ab84be7eSDavid Ahern {
1390ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
1391ab84be7eSDavid Ahern 	struct rb_node *node;
1392ab84be7eSDavid Ahern 	struct nexthop *nh;
1393ab84be7eSDavid Ahern 
1394ab84be7eSDavid Ahern 	while ((node = rb_first(root))) {
1395ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
1396430a0491SDavid Ahern 		remove_nexthop(net, nh, NULL);
1397ab84be7eSDavid Ahern 		cond_resched();
1398ab84be7eSDavid Ahern 	}
1399ab84be7eSDavid Ahern }
1400ab84be7eSDavid Ahern 
1401430a0491SDavid Ahern static struct nexthop *nexthop_create_group(struct net *net,
1402430a0491SDavid Ahern 					    struct nh_config *cfg)
1403430a0491SDavid Ahern {
1404430a0491SDavid Ahern 	struct nlattr *grps_attr = cfg->nh_grp;
1405430a0491SDavid Ahern 	struct nexthop_grp *entry = nla_data(grps_attr);
140690f33bffSNikolay Aleksandrov 	u16 num_nh = nla_len(grps_attr) / sizeof(*entry);
1407430a0491SDavid Ahern 	struct nh_group *nhg;
1408430a0491SDavid Ahern 	struct nexthop *nh;
1409430a0491SDavid Ahern 	int i;
1410430a0491SDavid Ahern 
1411eeaac363SNikolay Aleksandrov 	if (WARN_ON(!num_nh))
1412eeaac363SNikolay Aleksandrov 		return ERR_PTR(-EINVAL);
1413eeaac363SNikolay Aleksandrov 
1414430a0491SDavid Ahern 	nh = nexthop_alloc();
1415430a0491SDavid Ahern 	if (!nh)
1416430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
1417430a0491SDavid Ahern 
1418430a0491SDavid Ahern 	nh->is_group = 1;
1419430a0491SDavid Ahern 
142090f33bffSNikolay Aleksandrov 	nhg = nexthop_grp_alloc(num_nh);
1421430a0491SDavid Ahern 	if (!nhg) {
1422430a0491SDavid Ahern 		kfree(nh);
1423430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
1424430a0491SDavid Ahern 	}
1425430a0491SDavid Ahern 
142690f33bffSNikolay Aleksandrov 	/* spare group used for removals */
142790f33bffSNikolay Aleksandrov 	nhg->spare = nexthop_grp_alloc(num_nh);
1428dafe2078SPatrick Eigensatz 	if (!nhg->spare) {
142990f33bffSNikolay Aleksandrov 		kfree(nhg);
143090f33bffSNikolay Aleksandrov 		kfree(nh);
1431dafe2078SPatrick Eigensatz 		return ERR_PTR(-ENOMEM);
143290f33bffSNikolay Aleksandrov 	}
143390f33bffSNikolay Aleksandrov 	nhg->spare->spare = nhg;
143490f33bffSNikolay Aleksandrov 
1435430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
1436430a0491SDavid Ahern 		struct nexthop *nhe;
1437430a0491SDavid Ahern 		struct nh_info *nhi;
1438430a0491SDavid Ahern 
1439430a0491SDavid Ahern 		nhe = nexthop_find_by_id(net, entry[i].id);
1440430a0491SDavid Ahern 		if (!nexthop_get(nhe))
1441430a0491SDavid Ahern 			goto out_no_nh;
1442430a0491SDavid Ahern 
1443430a0491SDavid Ahern 		nhi = rtnl_dereference(nhe->nh_info);
1444430a0491SDavid Ahern 		if (nhi->family == AF_INET)
1445430a0491SDavid Ahern 			nhg->has_v4 = true;
1446430a0491SDavid Ahern 
1447430a0491SDavid Ahern 		nhg->nh_entries[i].nh = nhe;
1448430a0491SDavid Ahern 		nhg->nh_entries[i].weight = entry[i].weight + 1;
1449430a0491SDavid Ahern 		list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list);
1450430a0491SDavid Ahern 		nhg->nh_entries[i].nh_parent = nh;
1451430a0491SDavid Ahern 	}
1452430a0491SDavid Ahern 
1453430a0491SDavid Ahern 	if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) {
1454430a0491SDavid Ahern 		nhg->mpath = 1;
1455430a0491SDavid Ahern 		nh_group_rebalance(nhg);
1456430a0491SDavid Ahern 	}
1457430a0491SDavid Ahern 
145838428d68SRoopa Prabhu 	if (cfg->nh_fdb)
1459ce9ac056SDavid Ahern 		nhg->fdb_nh = 1;
146038428d68SRoopa Prabhu 
1461430a0491SDavid Ahern 	rcu_assign_pointer(nh->nh_grp, nhg);
1462430a0491SDavid Ahern 
1463430a0491SDavid Ahern 	return nh;
1464430a0491SDavid Ahern 
1465430a0491SDavid Ahern out_no_nh:
14667b01e53eSIdo Schimmel 	for (i--; i >= 0; --i) {
14677b01e53eSIdo Schimmel 		list_del(&nhg->nh_entries[i].nh_list);
1468430a0491SDavid Ahern 		nexthop_put(nhg->nh_entries[i].nh);
14697b01e53eSIdo Schimmel 	}
1470430a0491SDavid Ahern 
147190f33bffSNikolay Aleksandrov 	kfree(nhg->spare);
1472430a0491SDavid Ahern 	kfree(nhg);
1473430a0491SDavid Ahern 	kfree(nh);
1474430a0491SDavid Ahern 
1475430a0491SDavid Ahern 	return ERR_PTR(-ENOENT);
1476430a0491SDavid Ahern }
1477430a0491SDavid Ahern 
1478597cfe4fSDavid Ahern static int nh_create_ipv4(struct net *net, struct nexthop *nh,
1479597cfe4fSDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
1480597cfe4fSDavid Ahern 			  struct netlink_ext_ack *extack)
1481597cfe4fSDavid Ahern {
1482597cfe4fSDavid Ahern 	struct fib_nh *fib_nh = &nhi->fib_nh;
1483597cfe4fSDavid Ahern 	struct fib_config fib_cfg = {
1484597cfe4fSDavid Ahern 		.fc_oif   = cfg->nh_ifindex,
1485597cfe4fSDavid Ahern 		.fc_gw4   = cfg->gw.ipv4,
1486597cfe4fSDavid Ahern 		.fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0,
1487597cfe4fSDavid Ahern 		.fc_flags = cfg->nh_flags,
1488b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
1489b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
1490597cfe4fSDavid Ahern 	};
149138428d68SRoopa Prabhu 	u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN);
1492c76c9925SColin Ian King 	int err;
1493597cfe4fSDavid Ahern 
1494597cfe4fSDavid Ahern 	err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack);
1495597cfe4fSDavid Ahern 	if (err) {
1496597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
1497597cfe4fSDavid Ahern 		goto out;
1498597cfe4fSDavid Ahern 	}
1499597cfe4fSDavid Ahern 
1500ce9ac056SDavid Ahern 	if (nhi->fdb_nh)
150138428d68SRoopa Prabhu 		goto out;
150238428d68SRoopa Prabhu 
1503597cfe4fSDavid Ahern 	/* sets nh_dev if successful */
1504597cfe4fSDavid Ahern 	err = fib_check_nh(net, fib_nh, tb_id, 0, extack);
1505597cfe4fSDavid Ahern 	if (!err) {
1506597cfe4fSDavid Ahern 		nh->nh_flags = fib_nh->fib_nh_flags;
1507dcb1ecb5SDavid Ahern 		fib_info_update_nhc_saddr(net, &fib_nh->nh_common,
1508dcb1ecb5SDavid Ahern 					  fib_nh->fib_nh_scope);
1509597cfe4fSDavid Ahern 	} else {
1510597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
1511597cfe4fSDavid Ahern 	}
1512597cfe4fSDavid Ahern out:
1513597cfe4fSDavid Ahern 	return err;
1514597cfe4fSDavid Ahern }
1515597cfe4fSDavid Ahern 
151653010f99SDavid Ahern static int nh_create_ipv6(struct net *net,  struct nexthop *nh,
151753010f99SDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
151853010f99SDavid Ahern 			  struct netlink_ext_ack *extack)
151953010f99SDavid Ahern {
152053010f99SDavid Ahern 	struct fib6_nh *fib6_nh = &nhi->fib6_nh;
152153010f99SDavid Ahern 	struct fib6_config fib6_cfg = {
152253010f99SDavid Ahern 		.fc_table = l3mdev_fib_table(cfg->dev),
152353010f99SDavid Ahern 		.fc_ifindex = cfg->nh_ifindex,
152453010f99SDavid Ahern 		.fc_gateway = cfg->gw.ipv6,
152553010f99SDavid Ahern 		.fc_flags = cfg->nh_flags,
1526b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
1527b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
152838428d68SRoopa Prabhu 		.fc_is_fdb = cfg->nh_fdb,
152953010f99SDavid Ahern 	};
15306f43e525SColin Ian King 	int err;
153153010f99SDavid Ahern 
153253010f99SDavid Ahern 	if (!ipv6_addr_any(&cfg->gw.ipv6))
153353010f99SDavid Ahern 		fib6_cfg.fc_flags |= RTF_GATEWAY;
153453010f99SDavid Ahern 
153553010f99SDavid Ahern 	/* sets nh_dev if successful */
153653010f99SDavid Ahern 	err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL,
153753010f99SDavid Ahern 				      extack);
153853010f99SDavid Ahern 	if (err)
153953010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(fib6_nh);
154053010f99SDavid Ahern 	else
154153010f99SDavid Ahern 		nh->nh_flags = fib6_nh->fib_nh_flags;
154253010f99SDavid Ahern 
154353010f99SDavid Ahern 	return err;
154453010f99SDavid Ahern }
154553010f99SDavid Ahern 
1546ab84be7eSDavid Ahern static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg,
1547ab84be7eSDavid Ahern 				      struct netlink_ext_ack *extack)
1548ab84be7eSDavid Ahern {
1549ab84be7eSDavid Ahern 	struct nh_info *nhi;
1550ab84be7eSDavid Ahern 	struct nexthop *nh;
1551ab84be7eSDavid Ahern 	int err = 0;
1552ab84be7eSDavid Ahern 
1553ab84be7eSDavid Ahern 	nh = nexthop_alloc();
1554ab84be7eSDavid Ahern 	if (!nh)
1555ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
1556ab84be7eSDavid Ahern 
1557ab84be7eSDavid Ahern 	nhi = kzalloc(sizeof(*nhi), GFP_KERNEL);
1558ab84be7eSDavid Ahern 	if (!nhi) {
1559ab84be7eSDavid Ahern 		kfree(nh);
1560ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
1561ab84be7eSDavid Ahern 	}
1562ab84be7eSDavid Ahern 
1563ab84be7eSDavid Ahern 	nh->nh_flags = cfg->nh_flags;
1564ab84be7eSDavid Ahern 	nh->net = net;
1565ab84be7eSDavid Ahern 
1566ab84be7eSDavid Ahern 	nhi->nh_parent = nh;
1567ab84be7eSDavid Ahern 	nhi->family = cfg->nh_family;
1568ab84be7eSDavid Ahern 	nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK;
1569ab84be7eSDavid Ahern 
157038428d68SRoopa Prabhu 	if (cfg->nh_fdb)
1571ce9ac056SDavid Ahern 		nhi->fdb_nh = 1;
157238428d68SRoopa Prabhu 
1573ab84be7eSDavid Ahern 	if (cfg->nh_blackhole) {
1574ab84be7eSDavid Ahern 		nhi->reject_nh = 1;
1575ab84be7eSDavid Ahern 		cfg->nh_ifindex = net->loopback_dev->ifindex;
1576ab84be7eSDavid Ahern 	}
1577ab84be7eSDavid Ahern 
1578597cfe4fSDavid Ahern 	switch (cfg->nh_family) {
1579597cfe4fSDavid Ahern 	case AF_INET:
1580597cfe4fSDavid Ahern 		err = nh_create_ipv4(net, nh, nhi, cfg, extack);
1581597cfe4fSDavid Ahern 		break;
158253010f99SDavid Ahern 	case AF_INET6:
158353010f99SDavid Ahern 		err = nh_create_ipv6(net, nh, nhi, cfg, extack);
158453010f99SDavid Ahern 		break;
1585597cfe4fSDavid Ahern 	}
1586597cfe4fSDavid Ahern 
1587ab84be7eSDavid Ahern 	if (err) {
1588ab84be7eSDavid Ahern 		kfree(nhi);
1589ab84be7eSDavid Ahern 		kfree(nh);
1590ab84be7eSDavid Ahern 		return ERR_PTR(err);
1591ab84be7eSDavid Ahern 	}
1592ab84be7eSDavid Ahern 
1593597cfe4fSDavid Ahern 	/* add the entry to the device based hash */
1594ce9ac056SDavid Ahern 	if (!nhi->fdb_nh)
1595597cfe4fSDavid Ahern 		nexthop_devhash_add(net, nhi);
1596597cfe4fSDavid Ahern 
1597ab84be7eSDavid Ahern 	rcu_assign_pointer(nh->nh_info, nhi);
1598ab84be7eSDavid Ahern 
1599ab84be7eSDavid Ahern 	return nh;
1600ab84be7eSDavid Ahern }
1601ab84be7eSDavid Ahern 
1602ab84be7eSDavid Ahern /* called with rtnl lock held */
1603ab84be7eSDavid Ahern static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg,
1604ab84be7eSDavid Ahern 				   struct netlink_ext_ack *extack)
1605ab84be7eSDavid Ahern {
1606ab84be7eSDavid Ahern 	struct nexthop *nh;
1607ab84be7eSDavid Ahern 	int err;
1608ab84be7eSDavid Ahern 
1609ab84be7eSDavid Ahern 	if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) {
1610ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace requires nexthop id");
1611ab84be7eSDavid Ahern 		return ERR_PTR(-EINVAL);
1612ab84be7eSDavid Ahern 	}
1613ab84be7eSDavid Ahern 
1614ab84be7eSDavid Ahern 	if (!cfg->nh_id) {
1615ab84be7eSDavid Ahern 		cfg->nh_id = nh_find_unused_id(net);
1616ab84be7eSDavid Ahern 		if (!cfg->nh_id) {
1617ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "No unused id");
1618ab84be7eSDavid Ahern 			return ERR_PTR(-EINVAL);
1619ab84be7eSDavid Ahern 		}
1620ab84be7eSDavid Ahern 	}
1621ab84be7eSDavid Ahern 
1622430a0491SDavid Ahern 	if (cfg->nh_grp)
1623430a0491SDavid Ahern 		nh = nexthop_create_group(net, cfg);
1624430a0491SDavid Ahern 	else
1625ab84be7eSDavid Ahern 		nh = nexthop_create(net, cfg, extack);
1626430a0491SDavid Ahern 
1627ab84be7eSDavid Ahern 	if (IS_ERR(nh))
1628ab84be7eSDavid Ahern 		return nh;
1629ab84be7eSDavid Ahern 
1630ab84be7eSDavid Ahern 	refcount_set(&nh->refcnt, 1);
1631ab84be7eSDavid Ahern 	nh->id = cfg->nh_id;
1632ab84be7eSDavid Ahern 	nh->protocol = cfg->nh_protocol;
1633ab84be7eSDavid Ahern 	nh->net = net;
1634ab84be7eSDavid Ahern 
1635ab84be7eSDavid Ahern 	err = insert_nexthop(net, nh, cfg, extack);
1636ab84be7eSDavid Ahern 	if (err) {
1637430a0491SDavid Ahern 		__remove_nexthop(net, nh, NULL);
1638ab84be7eSDavid Ahern 		nexthop_put(nh);
1639ab84be7eSDavid Ahern 		nh = ERR_PTR(err);
1640ab84be7eSDavid Ahern 	}
1641ab84be7eSDavid Ahern 
1642ab84be7eSDavid Ahern 	return nh;
1643ab84be7eSDavid Ahern }
1644ab84be7eSDavid Ahern 
1645ab84be7eSDavid Ahern static int rtm_to_nh_config(struct net *net, struct sk_buff *skb,
1646ab84be7eSDavid Ahern 			    struct nlmsghdr *nlh, struct nh_config *cfg,
1647ab84be7eSDavid Ahern 			    struct netlink_ext_ack *extack)
1648ab84be7eSDavid Ahern {
1649ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
1650ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1651ab84be7eSDavid Ahern 	int err;
1652ab84be7eSDavid Ahern 
1653ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1654ab84be7eSDavid Ahern 			  extack);
1655ab84be7eSDavid Ahern 	if (err < 0)
1656ab84be7eSDavid Ahern 		return err;
1657ab84be7eSDavid Ahern 
1658ab84be7eSDavid Ahern 	err = -EINVAL;
1659ab84be7eSDavid Ahern 	if (nhm->resvd || nhm->nh_scope) {
1660ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in ancillary header");
1661ab84be7eSDavid Ahern 		goto out;
1662ab84be7eSDavid Ahern 	}
1663ab84be7eSDavid Ahern 	if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) {
1664ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header");
1665ab84be7eSDavid Ahern 		goto out;
1666ab84be7eSDavid Ahern 	}
1667ab84be7eSDavid Ahern 
1668ab84be7eSDavid Ahern 	switch (nhm->nh_family) {
1669597cfe4fSDavid Ahern 	case AF_INET:
167053010f99SDavid Ahern 	case AF_INET6:
1671597cfe4fSDavid Ahern 		break;
1672430a0491SDavid Ahern 	case AF_UNSPEC:
1673430a0491SDavid Ahern 		if (tb[NHA_GROUP])
1674430a0491SDavid Ahern 			break;
1675a8eceea8SJoe Perches 		fallthrough;
1676ab84be7eSDavid Ahern 	default:
1677ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid address family");
1678ab84be7eSDavid Ahern 		goto out;
1679ab84be7eSDavid Ahern 	}
1680ab84be7eSDavid Ahern 
1681ab84be7eSDavid Ahern 	if (tb[NHA_GROUPS] || tb[NHA_MASTER]) {
1682ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid attributes in request");
1683ab84be7eSDavid Ahern 		goto out;
1684ab84be7eSDavid Ahern 	}
1685ab84be7eSDavid Ahern 
1686ab84be7eSDavid Ahern 	memset(cfg, 0, sizeof(*cfg));
1687ab84be7eSDavid Ahern 	cfg->nlflags = nlh->nlmsg_flags;
1688ab84be7eSDavid Ahern 	cfg->nlinfo.portid = NETLINK_CB(skb).portid;
1689ab84be7eSDavid Ahern 	cfg->nlinfo.nlh = nlh;
1690ab84be7eSDavid Ahern 	cfg->nlinfo.nl_net = net;
1691ab84be7eSDavid Ahern 
1692ab84be7eSDavid Ahern 	cfg->nh_family = nhm->nh_family;
1693ab84be7eSDavid Ahern 	cfg->nh_protocol = nhm->nh_protocol;
1694ab84be7eSDavid Ahern 	cfg->nh_flags = nhm->nh_flags;
1695ab84be7eSDavid Ahern 
1696ab84be7eSDavid Ahern 	if (tb[NHA_ID])
1697ab84be7eSDavid Ahern 		cfg->nh_id = nla_get_u32(tb[NHA_ID]);
1698ab84be7eSDavid Ahern 
169938428d68SRoopa Prabhu 	if (tb[NHA_FDB]) {
170038428d68SRoopa Prabhu 		if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] ||
170138428d68SRoopa Prabhu 		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE]) {
170238428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole");
170338428d68SRoopa Prabhu 			goto out;
170438428d68SRoopa Prabhu 		}
170538428d68SRoopa Prabhu 		if (nhm->nh_flags) {
170638428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header");
170738428d68SRoopa Prabhu 			goto out;
170838428d68SRoopa Prabhu 		}
170938428d68SRoopa Prabhu 		cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]);
171038428d68SRoopa Prabhu 	}
171138428d68SRoopa Prabhu 
1712430a0491SDavid Ahern 	if (tb[NHA_GROUP]) {
1713430a0491SDavid Ahern 		if (nhm->nh_family != AF_UNSPEC) {
1714430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid family for group");
1715430a0491SDavid Ahern 			goto out;
1716430a0491SDavid Ahern 		}
1717430a0491SDavid Ahern 		cfg->nh_grp = tb[NHA_GROUP];
1718430a0491SDavid Ahern 
1719430a0491SDavid Ahern 		cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH;
1720430a0491SDavid Ahern 		if (tb[NHA_GROUP_TYPE])
1721430a0491SDavid Ahern 			cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]);
1722430a0491SDavid Ahern 
1723430a0491SDavid Ahern 		if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) {
1724430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid group type");
1725430a0491SDavid Ahern 			goto out;
1726430a0491SDavid Ahern 		}
1727430a0491SDavid Ahern 		err = nh_check_attr_group(net, tb, extack);
1728430a0491SDavid Ahern 
1729430a0491SDavid Ahern 		/* no other attributes should be set */
1730430a0491SDavid Ahern 		goto out;
1731430a0491SDavid Ahern 	}
1732430a0491SDavid Ahern 
1733ab84be7eSDavid Ahern 	if (tb[NHA_BLACKHOLE]) {
1734b513bd03SDavid Ahern 		if (tb[NHA_GATEWAY] || tb[NHA_OIF] ||
173538428d68SRoopa Prabhu 		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) {
173638428d68SRoopa Prabhu 			NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb");
1737ab84be7eSDavid Ahern 			goto out;
1738ab84be7eSDavid Ahern 		}
1739ab84be7eSDavid Ahern 
1740ab84be7eSDavid Ahern 		cfg->nh_blackhole = 1;
1741ab84be7eSDavid Ahern 		err = 0;
1742ab84be7eSDavid Ahern 		goto out;
1743ab84be7eSDavid Ahern 	}
1744ab84be7eSDavid Ahern 
174538428d68SRoopa Prabhu 	if (!cfg->nh_fdb && !tb[NHA_OIF]) {
174638428d68SRoopa Prabhu 		NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops");
1747ab84be7eSDavid Ahern 		goto out;
1748ab84be7eSDavid Ahern 	}
1749ab84be7eSDavid Ahern 
175038428d68SRoopa Prabhu 	if (!cfg->nh_fdb && tb[NHA_OIF]) {
1751ab84be7eSDavid Ahern 		cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]);
1752ab84be7eSDavid Ahern 		if (cfg->nh_ifindex)
1753ab84be7eSDavid Ahern 			cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex);
1754ab84be7eSDavid Ahern 
1755ab84be7eSDavid Ahern 		if (!cfg->dev) {
1756ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid device index");
1757ab84be7eSDavid Ahern 			goto out;
1758ab84be7eSDavid Ahern 		} else if (!(cfg->dev->flags & IFF_UP)) {
1759ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Nexthop device is not up");
1760ab84be7eSDavid Ahern 			err = -ENETDOWN;
1761ab84be7eSDavid Ahern 			goto out;
1762ab84be7eSDavid Ahern 		} else if (!netif_carrier_ok(cfg->dev)) {
1763ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down");
1764ab84be7eSDavid Ahern 			err = -ENETDOWN;
1765ab84be7eSDavid Ahern 			goto out;
1766ab84be7eSDavid Ahern 		}
176738428d68SRoopa Prabhu 	}
1768ab84be7eSDavid Ahern 
1769597cfe4fSDavid Ahern 	err = -EINVAL;
1770597cfe4fSDavid Ahern 	if (tb[NHA_GATEWAY]) {
1771597cfe4fSDavid Ahern 		struct nlattr *gwa = tb[NHA_GATEWAY];
1772597cfe4fSDavid Ahern 
1773597cfe4fSDavid Ahern 		switch (cfg->nh_family) {
1774597cfe4fSDavid Ahern 		case AF_INET:
1775597cfe4fSDavid Ahern 			if (nla_len(gwa) != sizeof(u32)) {
1776597cfe4fSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
1777597cfe4fSDavid Ahern 				goto out;
1778597cfe4fSDavid Ahern 			}
1779597cfe4fSDavid Ahern 			cfg->gw.ipv4 = nla_get_be32(gwa);
1780597cfe4fSDavid Ahern 			break;
178153010f99SDavid Ahern 		case AF_INET6:
178253010f99SDavid Ahern 			if (nla_len(gwa) != sizeof(struct in6_addr)) {
178353010f99SDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
178453010f99SDavid Ahern 				goto out;
178553010f99SDavid Ahern 			}
178653010f99SDavid Ahern 			cfg->gw.ipv6 = nla_get_in6_addr(gwa);
178753010f99SDavid Ahern 			break;
1788597cfe4fSDavid Ahern 		default:
1789597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
1790597cfe4fSDavid Ahern 				       "Unknown address family for gateway");
1791597cfe4fSDavid Ahern 			goto out;
1792597cfe4fSDavid Ahern 		}
1793597cfe4fSDavid Ahern 	} else {
1794597cfe4fSDavid Ahern 		/* device only nexthop (no gateway) */
1795597cfe4fSDavid Ahern 		if (cfg->nh_flags & RTNH_F_ONLINK) {
1796597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
1797597cfe4fSDavid Ahern 				       "ONLINK flag can not be set for nexthop without a gateway");
1798597cfe4fSDavid Ahern 			goto out;
1799597cfe4fSDavid Ahern 		}
1800597cfe4fSDavid Ahern 	}
1801597cfe4fSDavid Ahern 
1802b513bd03SDavid Ahern 	if (tb[NHA_ENCAP]) {
1803b513bd03SDavid Ahern 		cfg->nh_encap = tb[NHA_ENCAP];
1804b513bd03SDavid Ahern 
1805b513bd03SDavid Ahern 		if (!tb[NHA_ENCAP_TYPE]) {
1806b513bd03SDavid Ahern 			NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing");
1807b513bd03SDavid Ahern 			goto out;
1808b513bd03SDavid Ahern 		}
1809b513bd03SDavid Ahern 
1810b513bd03SDavid Ahern 		cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]);
1811b513bd03SDavid Ahern 		err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack);
1812b513bd03SDavid Ahern 		if (err < 0)
1813b513bd03SDavid Ahern 			goto out;
1814b513bd03SDavid Ahern 
1815b513bd03SDavid Ahern 	} else if (tb[NHA_ENCAP_TYPE]) {
1816b513bd03SDavid Ahern 		NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing");
1817b513bd03SDavid Ahern 		goto out;
1818b513bd03SDavid Ahern 	}
1819b513bd03SDavid Ahern 
1820b513bd03SDavid Ahern 
1821ab84be7eSDavid Ahern 	err = 0;
1822ab84be7eSDavid Ahern out:
1823ab84be7eSDavid Ahern 	return err;
1824ab84be7eSDavid Ahern }
1825ab84be7eSDavid Ahern 
1826ab84be7eSDavid Ahern /* rtnl */
1827ab84be7eSDavid Ahern static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1828ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1829ab84be7eSDavid Ahern {
1830ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
1831ab84be7eSDavid Ahern 	struct nh_config cfg;
1832ab84be7eSDavid Ahern 	struct nexthop *nh;
1833ab84be7eSDavid Ahern 	int err;
1834ab84be7eSDavid Ahern 
1835ab84be7eSDavid Ahern 	err = rtm_to_nh_config(net, skb, nlh, &cfg, extack);
1836ab84be7eSDavid Ahern 	if (!err) {
1837ab84be7eSDavid Ahern 		nh = nexthop_add(net, &cfg, extack);
1838ab84be7eSDavid Ahern 		if (IS_ERR(nh))
1839ab84be7eSDavid Ahern 			err = PTR_ERR(nh);
1840ab84be7eSDavid Ahern 	}
1841ab84be7eSDavid Ahern 
1842ab84be7eSDavid Ahern 	return err;
1843ab84be7eSDavid Ahern }
1844ab84be7eSDavid Ahern 
1845ab84be7eSDavid Ahern static int nh_valid_get_del_req(struct nlmsghdr *nlh, u32 *id,
1846ab84be7eSDavid Ahern 				struct netlink_ext_ack *extack)
1847ab84be7eSDavid Ahern {
1848ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
1849*60f5ad5eSPetr Machata 	struct nlattr *tb[ARRAY_SIZE(rtm_nh_policy_get)];
1850*60f5ad5eSPetr Machata 	int err;
1851ab84be7eSDavid Ahern 
1852*60f5ad5eSPetr Machata 	err = nlmsg_parse(nlh, sizeof(*nhm), tb,
1853*60f5ad5eSPetr Machata 			  ARRAY_SIZE(rtm_nh_policy_get) - 1,
1854*60f5ad5eSPetr Machata 			  rtm_nh_policy_get, extack);
1855ab84be7eSDavid Ahern 	if (err < 0)
1856ab84be7eSDavid Ahern 		return err;
1857ab84be7eSDavid Ahern 
1858ab84be7eSDavid Ahern 	err = -EINVAL;
1859ab84be7eSDavid Ahern 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
1860ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in header");
1861ab84be7eSDavid Ahern 		goto out;
1862ab84be7eSDavid Ahern 	}
1863ab84be7eSDavid Ahern 
1864ab84be7eSDavid Ahern 	if (!tb[NHA_ID]) {
1865ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Nexthop id is missing");
1866ab84be7eSDavid Ahern 		goto out;
1867ab84be7eSDavid Ahern 	}
1868ab84be7eSDavid Ahern 
1869ab84be7eSDavid Ahern 	*id = nla_get_u32(tb[NHA_ID]);
1870ab84be7eSDavid Ahern 	if (!(*id))
1871ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid nexthop id");
1872ab84be7eSDavid Ahern 	else
1873ab84be7eSDavid Ahern 		err = 0;
1874ab84be7eSDavid Ahern out:
1875ab84be7eSDavid Ahern 	return err;
1876ab84be7eSDavid Ahern }
1877ab84be7eSDavid Ahern 
1878ab84be7eSDavid Ahern /* rtnl */
1879ab84be7eSDavid Ahern static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1880ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1881ab84be7eSDavid Ahern {
1882ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
1883ab84be7eSDavid Ahern 	struct nl_info nlinfo = {
1884ab84be7eSDavid Ahern 		.nlh = nlh,
1885ab84be7eSDavid Ahern 		.nl_net = net,
1886ab84be7eSDavid Ahern 		.portid = NETLINK_CB(skb).portid,
1887ab84be7eSDavid Ahern 	};
1888ab84be7eSDavid Ahern 	struct nexthop *nh;
1889ab84be7eSDavid Ahern 	int err;
1890ab84be7eSDavid Ahern 	u32 id;
1891ab84be7eSDavid Ahern 
1892ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
1893ab84be7eSDavid Ahern 	if (err)
1894ab84be7eSDavid Ahern 		return err;
1895ab84be7eSDavid Ahern 
1896ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
1897ab84be7eSDavid Ahern 	if (!nh)
1898ab84be7eSDavid Ahern 		return -ENOENT;
1899ab84be7eSDavid Ahern 
1900430a0491SDavid Ahern 	remove_nexthop(net, nh, &nlinfo);
1901ab84be7eSDavid Ahern 
1902ab84be7eSDavid Ahern 	return 0;
1903ab84be7eSDavid Ahern }
1904ab84be7eSDavid Ahern 
1905ab84be7eSDavid Ahern /* rtnl */
1906ab84be7eSDavid Ahern static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh,
1907ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1908ab84be7eSDavid Ahern {
1909ab84be7eSDavid Ahern 	struct net *net = sock_net(in_skb->sk);
1910ab84be7eSDavid Ahern 	struct sk_buff *skb = NULL;
1911ab84be7eSDavid Ahern 	struct nexthop *nh;
1912ab84be7eSDavid Ahern 	int err;
1913ab84be7eSDavid Ahern 	u32 id;
1914ab84be7eSDavid Ahern 
1915ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
1916ab84be7eSDavid Ahern 	if (err)
1917ab84be7eSDavid Ahern 		return err;
1918ab84be7eSDavid Ahern 
1919ab84be7eSDavid Ahern 	err = -ENOBUFS;
1920ab84be7eSDavid Ahern 	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
1921ab84be7eSDavid Ahern 	if (!skb)
1922ab84be7eSDavid Ahern 		goto out;
1923ab84be7eSDavid Ahern 
1924ab84be7eSDavid Ahern 	err = -ENOENT;
1925ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
1926ab84be7eSDavid Ahern 	if (!nh)
1927ab84be7eSDavid Ahern 		goto errout_free;
1928ab84be7eSDavid Ahern 
1929ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid,
1930ab84be7eSDavid Ahern 			   nlh->nlmsg_seq, 0);
1931ab84be7eSDavid Ahern 	if (err < 0) {
1932ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
1933ab84be7eSDavid Ahern 		goto errout_free;
1934ab84be7eSDavid Ahern 	}
1935ab84be7eSDavid Ahern 
1936ab84be7eSDavid Ahern 	err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
1937ab84be7eSDavid Ahern out:
1938ab84be7eSDavid Ahern 	return err;
1939ab84be7eSDavid Ahern errout_free:
1940ab84be7eSDavid Ahern 	kfree_skb(skb);
1941ab84be7eSDavid Ahern 	goto out;
1942ab84be7eSDavid Ahern }
1943ab84be7eSDavid Ahern 
1944430a0491SDavid Ahern static bool nh_dump_filtered(struct nexthop *nh, int dev_idx, int master_idx,
1945430a0491SDavid Ahern 			     bool group_filter, u8 family)
1946ab84be7eSDavid Ahern {
1947ab84be7eSDavid Ahern 	const struct net_device *dev;
1948ab84be7eSDavid Ahern 	const struct nh_info *nhi;
1949ab84be7eSDavid Ahern 
1950430a0491SDavid Ahern 	if (group_filter && !nh->is_group)
1951430a0491SDavid Ahern 		return true;
1952430a0491SDavid Ahern 
1953ab84be7eSDavid Ahern 	if (!dev_idx && !master_idx && !family)
1954ab84be7eSDavid Ahern 		return false;
1955ab84be7eSDavid Ahern 
1956430a0491SDavid Ahern 	if (nh->is_group)
1957430a0491SDavid Ahern 		return true;
1958430a0491SDavid Ahern 
1959ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
1960ab84be7eSDavid Ahern 	if (family && nhi->family != family)
1961ab84be7eSDavid Ahern 		return true;
1962ab84be7eSDavid Ahern 
1963ab84be7eSDavid Ahern 	dev = nhi->fib_nhc.nhc_dev;
1964ab84be7eSDavid Ahern 	if (dev_idx && (!dev || dev->ifindex != dev_idx))
1965ab84be7eSDavid Ahern 		return true;
1966ab84be7eSDavid Ahern 
1967ab84be7eSDavid Ahern 	if (master_idx) {
1968ab84be7eSDavid Ahern 		struct net_device *master;
1969ab84be7eSDavid Ahern 
1970ab84be7eSDavid Ahern 		if (!dev)
1971ab84be7eSDavid Ahern 			return true;
1972ab84be7eSDavid Ahern 
1973ab84be7eSDavid Ahern 		master = netdev_master_upper_dev_get((struct net_device *)dev);
1974ab84be7eSDavid Ahern 		if (!master || master->ifindex != master_idx)
1975ab84be7eSDavid Ahern 			return true;
1976ab84be7eSDavid Ahern 	}
1977ab84be7eSDavid Ahern 
1978ab84be7eSDavid Ahern 	return false;
1979ab84be7eSDavid Ahern }
1980ab84be7eSDavid Ahern 
1981430a0491SDavid Ahern static int nh_valid_dump_req(const struct nlmsghdr *nlh, int *dev_idx,
1982430a0491SDavid Ahern 			     int *master_idx, bool *group_filter,
198338428d68SRoopa Prabhu 			     bool *fdb_filter, struct netlink_callback *cb)
1984ab84be7eSDavid Ahern {
1985ab84be7eSDavid Ahern 	struct netlink_ext_ack *extack = cb->extack;
1986ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1987ab84be7eSDavid Ahern 	struct nhmsg *nhm;
1988ab84be7eSDavid Ahern 	int err, i;
1989ab84be7eSDavid Ahern 	u32 idx;
1990ab84be7eSDavid Ahern 
1991ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1992ab84be7eSDavid Ahern 			  NULL);
1993ab84be7eSDavid Ahern 	if (err < 0)
1994ab84be7eSDavid Ahern 		return err;
1995ab84be7eSDavid Ahern 
1996ab84be7eSDavid Ahern 	for (i = 0; i <= NHA_MAX; ++i) {
1997ab84be7eSDavid Ahern 		if (!tb[i])
1998ab84be7eSDavid Ahern 			continue;
1999ab84be7eSDavid Ahern 
2000ab84be7eSDavid Ahern 		switch (i) {
2001ab84be7eSDavid Ahern 		case NHA_OIF:
2002ab84be7eSDavid Ahern 			idx = nla_get_u32(tb[i]);
2003ab84be7eSDavid Ahern 			if (idx > INT_MAX) {
2004ab84be7eSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid device index");
2005ab84be7eSDavid Ahern 				return -EINVAL;
2006ab84be7eSDavid Ahern 			}
2007ab84be7eSDavid Ahern 			*dev_idx = idx;
2008ab84be7eSDavid Ahern 			break;
2009ab84be7eSDavid Ahern 		case NHA_MASTER:
2010ab84be7eSDavid Ahern 			idx = nla_get_u32(tb[i]);
2011ab84be7eSDavid Ahern 			if (idx > INT_MAX) {
2012ab84be7eSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid master device index");
2013ab84be7eSDavid Ahern 				return -EINVAL;
2014ab84be7eSDavid Ahern 			}
2015ab84be7eSDavid Ahern 			*master_idx = idx;
2016ab84be7eSDavid Ahern 			break;
2017430a0491SDavid Ahern 		case NHA_GROUPS:
2018430a0491SDavid Ahern 			*group_filter = true;
2019430a0491SDavid Ahern 			break;
202038428d68SRoopa Prabhu 		case NHA_FDB:
202138428d68SRoopa Prabhu 			*fdb_filter = true;
202238428d68SRoopa Prabhu 			break;
2023ab84be7eSDavid Ahern 		default:
2024ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
2025ab84be7eSDavid Ahern 			return -EINVAL;
2026ab84be7eSDavid Ahern 		}
2027ab84be7eSDavid Ahern 	}
2028ab84be7eSDavid Ahern 
2029ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
2030ab84be7eSDavid Ahern 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
2031ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request");
2032ab84be7eSDavid Ahern 		return -EINVAL;
2033ab84be7eSDavid Ahern 	}
2034ab84be7eSDavid Ahern 
2035ab84be7eSDavid Ahern 	return 0;
2036ab84be7eSDavid Ahern }
2037ab84be7eSDavid Ahern 
2038ab84be7eSDavid Ahern /* rtnl */
2039ab84be7eSDavid Ahern static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb)
2040ab84be7eSDavid Ahern {
204138428d68SRoopa Prabhu 	bool group_filter = false, fdb_filter = false;
2042ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(cb->nlh);
2043ab84be7eSDavid Ahern 	int dev_filter_idx = 0, master_idx = 0;
2044ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
2045ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
2046ab84be7eSDavid Ahern 	struct rb_node *node;
2047ab84be7eSDavid Ahern 	int idx = 0, s_idx;
2048ab84be7eSDavid Ahern 	int err;
2049ab84be7eSDavid Ahern 
2050430a0491SDavid Ahern 	err = nh_valid_dump_req(cb->nlh, &dev_filter_idx, &master_idx,
205138428d68SRoopa Prabhu 				&group_filter, &fdb_filter, cb);
2052ab84be7eSDavid Ahern 	if (err < 0)
2053ab84be7eSDavid Ahern 		return err;
2054ab84be7eSDavid Ahern 
2055ab84be7eSDavid Ahern 	s_idx = cb->args[0];
2056ab84be7eSDavid Ahern 	for (node = rb_first(root); node; node = rb_next(node)) {
2057ab84be7eSDavid Ahern 		struct nexthop *nh;
2058ab84be7eSDavid Ahern 
2059ab84be7eSDavid Ahern 		if (idx < s_idx)
2060ab84be7eSDavid Ahern 			goto cont;
2061ab84be7eSDavid Ahern 
2062ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
2063ab84be7eSDavid Ahern 		if (nh_dump_filtered(nh, dev_filter_idx, master_idx,
2064430a0491SDavid Ahern 				     group_filter, nhm->nh_family))
2065ab84be7eSDavid Ahern 			goto cont;
2066ab84be7eSDavid Ahern 
2067ab84be7eSDavid Ahern 		err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP,
2068ab84be7eSDavid Ahern 				   NETLINK_CB(cb->skb).portid,
2069ab84be7eSDavid Ahern 				   cb->nlh->nlmsg_seq, NLM_F_MULTI);
2070ab84be7eSDavid Ahern 		if (err < 0) {
2071ab84be7eSDavid Ahern 			if (likely(skb->len))
2072ab84be7eSDavid Ahern 				goto out;
2073ab84be7eSDavid Ahern 
2074ab84be7eSDavid Ahern 			goto out_err;
2075ab84be7eSDavid Ahern 		}
2076ab84be7eSDavid Ahern cont:
2077ab84be7eSDavid Ahern 		idx++;
2078ab84be7eSDavid Ahern 	}
2079ab84be7eSDavid Ahern 
2080ab84be7eSDavid Ahern out:
2081ab84be7eSDavid Ahern 	err = skb->len;
2082ab84be7eSDavid Ahern out_err:
2083ab84be7eSDavid Ahern 	cb->args[0] = idx;
2084ab84be7eSDavid Ahern 	cb->seq = net->nexthop.seq;
2085ab84be7eSDavid Ahern 	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
2086ab84be7eSDavid Ahern 
2087ab84be7eSDavid Ahern 	return err;
2088ab84be7eSDavid Ahern }
2089ab84be7eSDavid Ahern 
2090597cfe4fSDavid Ahern static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu)
2091597cfe4fSDavid Ahern {
2092597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
2093597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
2094597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
2095597cfe4fSDavid Ahern 	struct hlist_node *n;
2096597cfe4fSDavid Ahern 	struct nh_info *nhi;
2097597cfe4fSDavid Ahern 
2098597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
2099597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev == dev) {
2100597cfe4fSDavid Ahern 			if (nhi->family == AF_INET)
2101597cfe4fSDavid Ahern 				fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu,
2102597cfe4fSDavid Ahern 						   orig_mtu);
2103597cfe4fSDavid Ahern 		}
2104597cfe4fSDavid Ahern 	}
2105597cfe4fSDavid Ahern }
2106597cfe4fSDavid Ahern 
2107597cfe4fSDavid Ahern /* rtnl */
2108597cfe4fSDavid Ahern static int nh_netdev_event(struct notifier_block *this,
2109597cfe4fSDavid Ahern 			   unsigned long event, void *ptr)
2110597cfe4fSDavid Ahern {
2111597cfe4fSDavid Ahern 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
2112597cfe4fSDavid Ahern 	struct netdev_notifier_info_ext *info_ext;
2113597cfe4fSDavid Ahern 
2114597cfe4fSDavid Ahern 	switch (event) {
2115597cfe4fSDavid Ahern 	case NETDEV_DOWN:
2116597cfe4fSDavid Ahern 	case NETDEV_UNREGISTER:
2117597cfe4fSDavid Ahern 		nexthop_flush_dev(dev);
2118597cfe4fSDavid Ahern 		break;
2119597cfe4fSDavid Ahern 	case NETDEV_CHANGE:
2120597cfe4fSDavid Ahern 		if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP)))
2121597cfe4fSDavid Ahern 			nexthop_flush_dev(dev);
2122597cfe4fSDavid Ahern 		break;
2123597cfe4fSDavid Ahern 	case NETDEV_CHANGEMTU:
2124597cfe4fSDavid Ahern 		info_ext = ptr;
2125597cfe4fSDavid Ahern 		nexthop_sync_mtu(dev, info_ext->ext.mtu);
2126597cfe4fSDavid Ahern 		rt_cache_flush(dev_net(dev));
2127597cfe4fSDavid Ahern 		break;
2128597cfe4fSDavid Ahern 	}
2129597cfe4fSDavid Ahern 	return NOTIFY_DONE;
2130597cfe4fSDavid Ahern }
2131597cfe4fSDavid Ahern 
2132597cfe4fSDavid Ahern static struct notifier_block nh_netdev_notifier = {
2133597cfe4fSDavid Ahern 	.notifier_call = nh_netdev_event,
2134597cfe4fSDavid Ahern };
2135597cfe4fSDavid Ahern 
2136975ff7f3SIdo Schimmel static int nexthops_dump(struct net *net, struct notifier_block *nb,
2137975ff7f3SIdo Schimmel 			 struct netlink_ext_ack *extack)
2138975ff7f3SIdo Schimmel {
2139975ff7f3SIdo Schimmel 	struct rb_root *root = &net->nexthop.rb_root;
2140975ff7f3SIdo Schimmel 	struct rb_node *node;
2141975ff7f3SIdo Schimmel 	int err = 0;
2142975ff7f3SIdo Schimmel 
2143975ff7f3SIdo Schimmel 	for (node = rb_first(root); node; node = rb_next(node)) {
2144975ff7f3SIdo Schimmel 		struct nexthop *nh;
2145975ff7f3SIdo Schimmel 
2146975ff7f3SIdo Schimmel 		nh = rb_entry(node, struct nexthop, rb_node);
2147975ff7f3SIdo Schimmel 		err = call_nexthop_notifier(nb, net, NEXTHOP_EVENT_REPLACE, nh,
2148975ff7f3SIdo Schimmel 					    extack);
2149975ff7f3SIdo Schimmel 		if (err)
2150975ff7f3SIdo Schimmel 			break;
2151975ff7f3SIdo Schimmel 	}
2152975ff7f3SIdo Schimmel 
2153975ff7f3SIdo Schimmel 	return err;
2154975ff7f3SIdo Schimmel }
2155975ff7f3SIdo Schimmel 
2156ce7e9c8aSIdo Schimmel int register_nexthop_notifier(struct net *net, struct notifier_block *nb,
2157ce7e9c8aSIdo Schimmel 			      struct netlink_ext_ack *extack)
21588590ceedSRoopa Prabhu {
2159975ff7f3SIdo Schimmel 	int err;
2160975ff7f3SIdo Schimmel 
2161975ff7f3SIdo Schimmel 	rtnl_lock();
2162975ff7f3SIdo Schimmel 	err = nexthops_dump(net, nb, extack);
2163975ff7f3SIdo Schimmel 	if (err)
2164975ff7f3SIdo Schimmel 		goto unlock;
2165975ff7f3SIdo Schimmel 	err = blocking_notifier_chain_register(&net->nexthop.notifier_chain,
216680690ec6SIdo Schimmel 					       nb);
2167975ff7f3SIdo Schimmel unlock:
2168975ff7f3SIdo Schimmel 	rtnl_unlock();
2169975ff7f3SIdo Schimmel 	return err;
21708590ceedSRoopa Prabhu }
21718590ceedSRoopa Prabhu EXPORT_SYMBOL(register_nexthop_notifier);
21728590ceedSRoopa Prabhu 
21738590ceedSRoopa Prabhu int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb)
21748590ceedSRoopa Prabhu {
217580690ec6SIdo Schimmel 	return blocking_notifier_chain_unregister(&net->nexthop.notifier_chain,
21768590ceedSRoopa Prabhu 						  nb);
21778590ceedSRoopa Prabhu }
21788590ceedSRoopa Prabhu EXPORT_SYMBOL(unregister_nexthop_notifier);
21798590ceedSRoopa Prabhu 
2180e95f2592SIdo Schimmel void nexthop_set_hw_flags(struct net *net, u32 id, bool offload, bool trap)
2181e95f2592SIdo Schimmel {
2182e95f2592SIdo Schimmel 	struct nexthop *nexthop;
2183e95f2592SIdo Schimmel 
2184e95f2592SIdo Schimmel 	rcu_read_lock();
2185e95f2592SIdo Schimmel 
2186e95f2592SIdo Schimmel 	nexthop = nexthop_find_by_id(net, id);
2187e95f2592SIdo Schimmel 	if (!nexthop)
2188e95f2592SIdo Schimmel 		goto out;
2189e95f2592SIdo Schimmel 
2190e95f2592SIdo Schimmel 	nexthop->nh_flags &= ~(RTNH_F_OFFLOAD | RTNH_F_TRAP);
2191e95f2592SIdo Schimmel 	if (offload)
2192e95f2592SIdo Schimmel 		nexthop->nh_flags |= RTNH_F_OFFLOAD;
2193e95f2592SIdo Schimmel 	if (trap)
2194e95f2592SIdo Schimmel 		nexthop->nh_flags |= RTNH_F_TRAP;
2195e95f2592SIdo Schimmel 
2196e95f2592SIdo Schimmel out:
2197e95f2592SIdo Schimmel 	rcu_read_unlock();
2198e95f2592SIdo Schimmel }
2199e95f2592SIdo Schimmel EXPORT_SYMBOL(nexthop_set_hw_flags);
2200e95f2592SIdo Schimmel 
2201ab84be7eSDavid Ahern static void __net_exit nexthop_net_exit(struct net *net)
2202ab84be7eSDavid Ahern {
2203ab84be7eSDavid Ahern 	rtnl_lock();
2204ab84be7eSDavid Ahern 	flush_all_nexthops(net);
2205ab84be7eSDavid Ahern 	rtnl_unlock();
2206597cfe4fSDavid Ahern 	kfree(net->nexthop.devhash);
2207ab84be7eSDavid Ahern }
2208ab84be7eSDavid Ahern 
2209ab84be7eSDavid Ahern static int __net_init nexthop_net_init(struct net *net)
2210ab84be7eSDavid Ahern {
2211597cfe4fSDavid Ahern 	size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE;
2212597cfe4fSDavid Ahern 
2213ab84be7eSDavid Ahern 	net->nexthop.rb_root = RB_ROOT;
2214597cfe4fSDavid Ahern 	net->nexthop.devhash = kzalloc(sz, GFP_KERNEL);
2215597cfe4fSDavid Ahern 	if (!net->nexthop.devhash)
2216597cfe4fSDavid Ahern 		return -ENOMEM;
221780690ec6SIdo Schimmel 	BLOCKING_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain);
2218ab84be7eSDavid Ahern 
2219ab84be7eSDavid Ahern 	return 0;
2220ab84be7eSDavid Ahern }
2221ab84be7eSDavid Ahern 
2222ab84be7eSDavid Ahern static struct pernet_operations nexthop_net_ops = {
2223ab84be7eSDavid Ahern 	.init = nexthop_net_init,
2224ab84be7eSDavid Ahern 	.exit = nexthop_net_exit,
2225ab84be7eSDavid Ahern };
2226ab84be7eSDavid Ahern 
2227ab84be7eSDavid Ahern static int __init nexthop_init(void)
2228ab84be7eSDavid Ahern {
2229ab84be7eSDavid Ahern 	register_pernet_subsys(&nexthop_net_ops);
2230ab84be7eSDavid Ahern 
2231597cfe4fSDavid Ahern 	register_netdevice_notifier(&nh_netdev_notifier);
2232597cfe4fSDavid Ahern 
2233ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
2234ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0);
2235ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop,
2236ab84be7eSDavid Ahern 		      rtm_dump_nexthop, 0);
2237ab84be7eSDavid Ahern 
2238ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
2239ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
2240ab84be7eSDavid Ahern 
2241ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
2242ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
2243ab84be7eSDavid Ahern 
2244ab84be7eSDavid Ahern 	return 0;
2245ab84be7eSDavid Ahern }
2246ab84be7eSDavid Ahern subsys_initcall(nexthop_init);
2247