xref: /linux/net/ipv4/nexthop.c (revision 7bf4796dd09984ad1612877a82d0d139c70ae27f)
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_UNSPEC]		= { .strict_start_type = NHA_UNSPEC + 1 },
27ab84be7eSDavid Ahern 	[NHA_ID]		= { .type = NLA_U32 },
28ab84be7eSDavid Ahern 	[NHA_GROUP]		= { .type = NLA_BINARY },
29ab84be7eSDavid Ahern 	[NHA_GROUP_TYPE]	= { .type = NLA_U16 },
30ab84be7eSDavid Ahern 	[NHA_BLACKHOLE]		= { .type = NLA_FLAG },
31ab84be7eSDavid Ahern 	[NHA_OIF]		= { .type = NLA_U32 },
32ab84be7eSDavid Ahern 	[NHA_GATEWAY]		= { .type = NLA_BINARY },
33ab84be7eSDavid Ahern 	[NHA_ENCAP_TYPE]	= { .type = NLA_U16 },
34ab84be7eSDavid Ahern 	[NHA_ENCAP]		= { .type = NLA_NESTED },
35ab84be7eSDavid Ahern 	[NHA_GROUPS]		= { .type = NLA_FLAG },
36ab84be7eSDavid Ahern 	[NHA_MASTER]		= { .type = NLA_U32 },
37ab84be7eSDavid Ahern };
38ab84be7eSDavid Ahern 
39597cfe4fSDavid Ahern static unsigned int nh_dev_hashfn(unsigned int val)
40597cfe4fSDavid Ahern {
41597cfe4fSDavid Ahern 	unsigned int mask = NH_DEV_HASHSIZE - 1;
42597cfe4fSDavid Ahern 
43597cfe4fSDavid Ahern 	return (val ^
44597cfe4fSDavid Ahern 		(val >> NH_DEV_HASHBITS) ^
45597cfe4fSDavid Ahern 		(val >> (NH_DEV_HASHBITS * 2))) & mask;
46597cfe4fSDavid Ahern }
47597cfe4fSDavid Ahern 
48597cfe4fSDavid Ahern static void nexthop_devhash_add(struct net *net, struct nh_info *nhi)
49597cfe4fSDavid Ahern {
50597cfe4fSDavid Ahern 	struct net_device *dev = nhi->fib_nhc.nhc_dev;
51597cfe4fSDavid Ahern 	struct hlist_head *head;
52597cfe4fSDavid Ahern 	unsigned int hash;
53597cfe4fSDavid Ahern 
54597cfe4fSDavid Ahern 	WARN_ON(!dev);
55597cfe4fSDavid Ahern 
56597cfe4fSDavid Ahern 	hash = nh_dev_hashfn(dev->ifindex);
57597cfe4fSDavid Ahern 	head = &net->nexthop.devhash[hash];
58597cfe4fSDavid Ahern 	hlist_add_head(&nhi->dev_hash, head);
59597cfe4fSDavid Ahern }
60597cfe4fSDavid Ahern 
61430a0491SDavid Ahern static void nexthop_free_mpath(struct nexthop *nh)
62ab84be7eSDavid Ahern {
63430a0491SDavid Ahern 	struct nh_group *nhg;
64430a0491SDavid Ahern 	int i;
65430a0491SDavid Ahern 
66430a0491SDavid Ahern 	nhg = rcu_dereference_raw(nh->nh_grp);
67430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i)
68430a0491SDavid Ahern 		WARN_ON(nhg->nh_entries[i].nh);
69430a0491SDavid Ahern 
70430a0491SDavid Ahern 	kfree(nhg);
71430a0491SDavid Ahern }
72430a0491SDavid Ahern 
73430a0491SDavid Ahern static void nexthop_free_single(struct nexthop *nh)
74430a0491SDavid Ahern {
75ab84be7eSDavid Ahern 	struct nh_info *nhi;
76ab84be7eSDavid Ahern 
77ab84be7eSDavid Ahern 	nhi = rcu_dereference_raw(nh->nh_info);
78597cfe4fSDavid Ahern 	switch (nhi->family) {
79597cfe4fSDavid Ahern 	case AF_INET:
80597cfe4fSDavid Ahern 		fib_nh_release(nh->net, &nhi->fib_nh);
81597cfe4fSDavid Ahern 		break;
8253010f99SDavid Ahern 	case AF_INET6:
8353010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(&nhi->fib6_nh);
8453010f99SDavid Ahern 		break;
85597cfe4fSDavid Ahern 	}
86ab84be7eSDavid Ahern 	kfree(nhi);
87430a0491SDavid Ahern }
88430a0491SDavid Ahern 
89430a0491SDavid Ahern void nexthop_free_rcu(struct rcu_head *head)
90430a0491SDavid Ahern {
91430a0491SDavid Ahern 	struct nexthop *nh = container_of(head, struct nexthop, rcu);
92430a0491SDavid Ahern 
93430a0491SDavid Ahern 	if (nh->is_group)
94430a0491SDavid Ahern 		nexthop_free_mpath(nh);
95430a0491SDavid Ahern 	else
96430a0491SDavid Ahern 		nexthop_free_single(nh);
97ab84be7eSDavid Ahern 
98ab84be7eSDavid Ahern 	kfree(nh);
99ab84be7eSDavid Ahern }
100ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_free_rcu);
101ab84be7eSDavid Ahern 
102ab84be7eSDavid Ahern static struct nexthop *nexthop_alloc(void)
103ab84be7eSDavid Ahern {
104ab84be7eSDavid Ahern 	struct nexthop *nh;
105ab84be7eSDavid Ahern 
106ab84be7eSDavid Ahern 	nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
107430a0491SDavid Ahern 	if (nh) {
1084c7e8084SDavid Ahern 		INIT_LIST_HEAD(&nh->fi_list);
109f88d8ea6SDavid Ahern 		INIT_LIST_HEAD(&nh->f6i_list);
110430a0491SDavid Ahern 		INIT_LIST_HEAD(&nh->grp_list);
111430a0491SDavid Ahern 	}
112ab84be7eSDavid Ahern 	return nh;
113ab84be7eSDavid Ahern }
114ab84be7eSDavid Ahern 
115430a0491SDavid Ahern static struct nh_group *nexthop_grp_alloc(u16 num_nh)
116430a0491SDavid Ahern {
117430a0491SDavid Ahern 	size_t sz = offsetof(struct nexthop, nh_grp)
118430a0491SDavid Ahern 		    + sizeof(struct nh_group)
119430a0491SDavid Ahern 		    + sizeof(struct nh_grp_entry) * num_nh;
120430a0491SDavid Ahern 	struct nh_group *nhg;
121430a0491SDavid Ahern 
122430a0491SDavid Ahern 	nhg = kzalloc(sz, GFP_KERNEL);
123430a0491SDavid Ahern 	if (nhg)
124430a0491SDavid Ahern 		nhg->num_nh = num_nh;
125430a0491SDavid Ahern 
126430a0491SDavid Ahern 	return nhg;
127430a0491SDavid Ahern }
128430a0491SDavid Ahern 
129ab84be7eSDavid Ahern static void nh_base_seq_inc(struct net *net)
130ab84be7eSDavid Ahern {
131ab84be7eSDavid Ahern 	while (++net->nexthop.seq == 0)
132ab84be7eSDavid Ahern 		;
133ab84be7eSDavid Ahern }
134ab84be7eSDavid Ahern 
135ab84be7eSDavid Ahern /* no reference taken; rcu lock or rtnl must be held */
136ab84be7eSDavid Ahern struct nexthop *nexthop_find_by_id(struct net *net, u32 id)
137ab84be7eSDavid Ahern {
138ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
139ab84be7eSDavid Ahern 
140ab84be7eSDavid Ahern 	pp = &net->nexthop.rb_root.rb_node;
141ab84be7eSDavid Ahern 	while (1) {
142ab84be7eSDavid Ahern 		struct nexthop *nh;
143ab84be7eSDavid Ahern 
144ab84be7eSDavid Ahern 		next = rcu_dereference_raw(*pp);
145ab84be7eSDavid Ahern 		if (!next)
146ab84be7eSDavid Ahern 			break;
147ab84be7eSDavid Ahern 		parent = next;
148ab84be7eSDavid Ahern 
149ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
150ab84be7eSDavid Ahern 		if (id < nh->id)
151ab84be7eSDavid Ahern 			pp = &next->rb_left;
152ab84be7eSDavid Ahern 		else if (id > nh->id)
153ab84be7eSDavid Ahern 			pp = &next->rb_right;
154ab84be7eSDavid Ahern 		else
155ab84be7eSDavid Ahern 			return nh;
156ab84be7eSDavid Ahern 	}
157ab84be7eSDavid Ahern 	return NULL;
158ab84be7eSDavid Ahern }
159ab84be7eSDavid Ahern EXPORT_SYMBOL_GPL(nexthop_find_by_id);
160ab84be7eSDavid Ahern 
161ab84be7eSDavid Ahern /* used for auto id allocation; called with rtnl held */
162ab84be7eSDavid Ahern static u32 nh_find_unused_id(struct net *net)
163ab84be7eSDavid Ahern {
164ab84be7eSDavid Ahern 	u32 id_start = net->nexthop.last_id_allocated;
165ab84be7eSDavid Ahern 
166ab84be7eSDavid Ahern 	while (1) {
167ab84be7eSDavid Ahern 		net->nexthop.last_id_allocated++;
168ab84be7eSDavid Ahern 		if (net->nexthop.last_id_allocated == id_start)
169ab84be7eSDavid Ahern 			break;
170ab84be7eSDavid Ahern 
171ab84be7eSDavid Ahern 		if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated))
172ab84be7eSDavid Ahern 			return net->nexthop.last_id_allocated;
173ab84be7eSDavid Ahern 	}
174ab84be7eSDavid Ahern 	return 0;
175ab84be7eSDavid Ahern }
176ab84be7eSDavid Ahern 
177430a0491SDavid Ahern static int nla_put_nh_group(struct sk_buff *skb, struct nh_group *nhg)
178430a0491SDavid Ahern {
179430a0491SDavid Ahern 	struct nexthop_grp *p;
180430a0491SDavid Ahern 	size_t len = nhg->num_nh * sizeof(*p);
181430a0491SDavid Ahern 	struct nlattr *nla;
182430a0491SDavid Ahern 	u16 group_type = 0;
183430a0491SDavid Ahern 	int i;
184430a0491SDavid Ahern 
185430a0491SDavid Ahern 	if (nhg->mpath)
186430a0491SDavid Ahern 		group_type = NEXTHOP_GRP_TYPE_MPATH;
187430a0491SDavid Ahern 
188430a0491SDavid Ahern 	if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type))
189430a0491SDavid Ahern 		goto nla_put_failure;
190430a0491SDavid Ahern 
191430a0491SDavid Ahern 	nla = nla_reserve(skb, NHA_GROUP, len);
192430a0491SDavid Ahern 	if (!nla)
193430a0491SDavid Ahern 		goto nla_put_failure;
194430a0491SDavid Ahern 
195430a0491SDavid Ahern 	p = nla_data(nla);
196430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
197430a0491SDavid Ahern 		p->id = nhg->nh_entries[i].nh->id;
198430a0491SDavid Ahern 		p->weight = nhg->nh_entries[i].weight - 1;
199430a0491SDavid Ahern 		p += 1;
200430a0491SDavid Ahern 	}
201430a0491SDavid Ahern 
202430a0491SDavid Ahern 	return 0;
203430a0491SDavid Ahern 
204430a0491SDavid Ahern nla_put_failure:
205430a0491SDavid Ahern 	return -EMSGSIZE;
206430a0491SDavid Ahern }
207430a0491SDavid Ahern 
208ab84be7eSDavid Ahern static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
209ab84be7eSDavid Ahern 			int event, u32 portid, u32 seq, unsigned int nlflags)
210ab84be7eSDavid Ahern {
21153010f99SDavid Ahern 	struct fib6_nh *fib6_nh;
212597cfe4fSDavid Ahern 	struct fib_nh *fib_nh;
213ab84be7eSDavid Ahern 	struct nlmsghdr *nlh;
214ab84be7eSDavid Ahern 	struct nh_info *nhi;
215ab84be7eSDavid Ahern 	struct nhmsg *nhm;
216ab84be7eSDavid Ahern 
217ab84be7eSDavid Ahern 	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
218ab84be7eSDavid Ahern 	if (!nlh)
219ab84be7eSDavid Ahern 		return -EMSGSIZE;
220ab84be7eSDavid Ahern 
221ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
222ab84be7eSDavid Ahern 	nhm->nh_family = AF_UNSPEC;
223ab84be7eSDavid Ahern 	nhm->nh_flags = nh->nh_flags;
224ab84be7eSDavid Ahern 	nhm->nh_protocol = nh->protocol;
225ab84be7eSDavid Ahern 	nhm->nh_scope = 0;
226ab84be7eSDavid Ahern 	nhm->resvd = 0;
227ab84be7eSDavid Ahern 
228ab84be7eSDavid Ahern 	if (nla_put_u32(skb, NHA_ID, nh->id))
229ab84be7eSDavid Ahern 		goto nla_put_failure;
230ab84be7eSDavid Ahern 
231430a0491SDavid Ahern 	if (nh->is_group) {
232430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
233430a0491SDavid Ahern 
234430a0491SDavid Ahern 		if (nla_put_nh_group(skb, nhg))
235430a0491SDavid Ahern 			goto nla_put_failure;
236430a0491SDavid Ahern 		goto out;
237430a0491SDavid Ahern 	}
238430a0491SDavid Ahern 
239ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
240ab84be7eSDavid Ahern 	nhm->nh_family = nhi->family;
241ab84be7eSDavid Ahern 	if (nhi->reject_nh) {
242ab84be7eSDavid Ahern 		if (nla_put_flag(skb, NHA_BLACKHOLE))
243ab84be7eSDavid Ahern 			goto nla_put_failure;
244ab84be7eSDavid Ahern 		goto out;
245597cfe4fSDavid Ahern 	} else {
246597cfe4fSDavid Ahern 		const struct net_device *dev;
247597cfe4fSDavid Ahern 
248597cfe4fSDavid Ahern 		dev = nhi->fib_nhc.nhc_dev;
249597cfe4fSDavid Ahern 		if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex))
250597cfe4fSDavid Ahern 			goto nla_put_failure;
251597cfe4fSDavid Ahern 	}
252597cfe4fSDavid Ahern 
253597cfe4fSDavid Ahern 	nhm->nh_scope = nhi->fib_nhc.nhc_scope;
254597cfe4fSDavid Ahern 	switch (nhi->family) {
255597cfe4fSDavid Ahern 	case AF_INET:
256597cfe4fSDavid Ahern 		fib_nh = &nhi->fib_nh;
257597cfe4fSDavid Ahern 		if (fib_nh->fib_nh_gw_family &&
258597cfe4fSDavid Ahern 		    nla_put_u32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4))
259597cfe4fSDavid Ahern 			goto nla_put_failure;
260597cfe4fSDavid Ahern 		break;
26153010f99SDavid Ahern 
26253010f99SDavid Ahern 	case AF_INET6:
26353010f99SDavid Ahern 		fib6_nh = &nhi->fib6_nh;
26453010f99SDavid Ahern 		if (fib6_nh->fib_nh_gw_family &&
26553010f99SDavid Ahern 		    nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6))
26653010f99SDavid Ahern 			goto nla_put_failure;
26753010f99SDavid Ahern 		break;
268ab84be7eSDavid Ahern 	}
269ab84be7eSDavid Ahern 
270b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate &&
271b513bd03SDavid Ahern 	    lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate,
272b513bd03SDavid Ahern 				NHA_ENCAP, NHA_ENCAP_TYPE) < 0)
273b513bd03SDavid Ahern 		goto nla_put_failure;
274b513bd03SDavid Ahern 
275ab84be7eSDavid Ahern out:
276ab84be7eSDavid Ahern 	nlmsg_end(skb, nlh);
277ab84be7eSDavid Ahern 	return 0;
278ab84be7eSDavid Ahern 
279ab84be7eSDavid Ahern nla_put_failure:
280ab84be7eSDavid Ahern 	return -EMSGSIZE;
281ab84be7eSDavid Ahern }
282ab84be7eSDavid Ahern 
283430a0491SDavid Ahern static size_t nh_nlmsg_size_grp(struct nexthop *nh)
284430a0491SDavid Ahern {
285430a0491SDavid Ahern 	struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
286430a0491SDavid Ahern 	size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh;
287430a0491SDavid Ahern 
288430a0491SDavid Ahern 	return nla_total_size(sz) +
289430a0491SDavid Ahern 	       nla_total_size(2);  /* NHA_GROUP_TYPE */
290430a0491SDavid Ahern }
291430a0491SDavid Ahern 
292430a0491SDavid Ahern static size_t nh_nlmsg_size_single(struct nexthop *nh)
293ab84be7eSDavid Ahern {
294597cfe4fSDavid Ahern 	struct nh_info *nhi = rtnl_dereference(nh->nh_info);
295430a0491SDavid Ahern 	size_t sz;
296ab84be7eSDavid Ahern 
297ab84be7eSDavid Ahern 	/* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE
298ab84be7eSDavid Ahern 	 * are mutually exclusive
299ab84be7eSDavid Ahern 	 */
300430a0491SDavid Ahern 	sz = nla_total_size(4);  /* NHA_OIF */
301ab84be7eSDavid Ahern 
302597cfe4fSDavid Ahern 	switch (nhi->family) {
303597cfe4fSDavid Ahern 	case AF_INET:
304597cfe4fSDavid Ahern 		if (nhi->fib_nh.fib_nh_gw_family)
305597cfe4fSDavid Ahern 			sz += nla_total_size(4);  /* NHA_GATEWAY */
306597cfe4fSDavid Ahern 		break;
30753010f99SDavid Ahern 
30853010f99SDavid Ahern 	case AF_INET6:
30953010f99SDavid Ahern 		/* NHA_GATEWAY */
31053010f99SDavid Ahern 		if (nhi->fib6_nh.fib_nh_gw_family)
31153010f99SDavid Ahern 			sz += nla_total_size(sizeof(const struct in6_addr));
31253010f99SDavid Ahern 		break;
313597cfe4fSDavid Ahern 	}
314597cfe4fSDavid Ahern 
315b513bd03SDavid Ahern 	if (nhi->fib_nhc.nhc_lwtstate) {
316b513bd03SDavid Ahern 		sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
317b513bd03SDavid Ahern 		sz += nla_total_size(2);  /* NHA_ENCAP_TYPE */
318b513bd03SDavid Ahern 	}
319b513bd03SDavid Ahern 
320ab84be7eSDavid Ahern 	return sz;
321ab84be7eSDavid Ahern }
322ab84be7eSDavid Ahern 
323430a0491SDavid Ahern static size_t nh_nlmsg_size(struct nexthop *nh)
324430a0491SDavid Ahern {
325430a0491SDavid Ahern 	size_t sz = nla_total_size(4);    /* NHA_ID */
326430a0491SDavid Ahern 
327430a0491SDavid Ahern 	if (nh->is_group)
328430a0491SDavid Ahern 		sz += nh_nlmsg_size_grp(nh);
329430a0491SDavid Ahern 	else
330430a0491SDavid Ahern 		sz += nh_nlmsg_size_single(nh);
331430a0491SDavid Ahern 
332430a0491SDavid Ahern 	return sz;
333430a0491SDavid Ahern }
334430a0491SDavid Ahern 
335ab84be7eSDavid Ahern static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info)
336ab84be7eSDavid Ahern {
337ab84be7eSDavid Ahern 	unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0;
338ab84be7eSDavid Ahern 	u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
339ab84be7eSDavid Ahern 	struct sk_buff *skb;
340ab84be7eSDavid Ahern 	int err = -ENOBUFS;
341ab84be7eSDavid Ahern 
342ab84be7eSDavid Ahern 	skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any());
343ab84be7eSDavid Ahern 	if (!skb)
344ab84be7eSDavid Ahern 		goto errout;
345ab84be7eSDavid Ahern 
346ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags);
347ab84be7eSDavid Ahern 	if (err < 0) {
348ab84be7eSDavid Ahern 		/* -EMSGSIZE implies BUG in nh_nlmsg_size() */
349ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
350ab84be7eSDavid Ahern 		kfree_skb(skb);
351ab84be7eSDavid Ahern 		goto errout;
352ab84be7eSDavid Ahern 	}
353ab84be7eSDavid Ahern 
354ab84be7eSDavid Ahern 	rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP,
355ab84be7eSDavid Ahern 		    info->nlh, gfp_any());
356ab84be7eSDavid Ahern 	return;
357ab84be7eSDavid Ahern errout:
358ab84be7eSDavid Ahern 	if (err < 0)
359ab84be7eSDavid Ahern 		rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err);
360ab84be7eSDavid Ahern }
361ab84be7eSDavid Ahern 
362430a0491SDavid Ahern static bool valid_group_nh(struct nexthop *nh, unsigned int npaths,
363430a0491SDavid Ahern 			   struct netlink_ext_ack *extack)
364597cfe4fSDavid Ahern {
365430a0491SDavid Ahern 	if (nh->is_group) {
366430a0491SDavid Ahern 		struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
367430a0491SDavid Ahern 
368430a0491SDavid Ahern 		/* nested multipath (group within a group) is not
369430a0491SDavid Ahern 		 * supported
370430a0491SDavid Ahern 		 */
371430a0491SDavid Ahern 		if (nhg->mpath) {
372430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
373430a0491SDavid Ahern 				       "Multipath group can not be a nexthop within a group");
374430a0491SDavid Ahern 			return false;
375430a0491SDavid Ahern 		}
376430a0491SDavid Ahern 	} else {
377430a0491SDavid Ahern 		struct nh_info *nhi = rtnl_dereference(nh->nh_info);
378430a0491SDavid Ahern 
379430a0491SDavid Ahern 		if (nhi->reject_nh && npaths > 1) {
380430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack,
381430a0491SDavid Ahern 				       "Blackhole nexthop can not be used in a group with more than 1 path");
382430a0491SDavid Ahern 			return false;
383430a0491SDavid Ahern 		}
384430a0491SDavid Ahern 	}
385430a0491SDavid Ahern 
386430a0491SDavid Ahern 	return true;
387430a0491SDavid Ahern }
388430a0491SDavid Ahern 
389430a0491SDavid Ahern static int nh_check_attr_group(struct net *net, struct nlattr *tb[],
390430a0491SDavid Ahern 			       struct netlink_ext_ack *extack)
391430a0491SDavid Ahern {
392430a0491SDavid Ahern 	unsigned int len = nla_len(tb[NHA_GROUP]);
393430a0491SDavid Ahern 	struct nexthop_grp *nhg;
394430a0491SDavid Ahern 	unsigned int i, j;
395430a0491SDavid Ahern 
396430a0491SDavid Ahern 	if (len & (sizeof(struct nexthop_grp) - 1)) {
397430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
398430a0491SDavid Ahern 			       "Invalid length for nexthop group attribute");
399430a0491SDavid Ahern 		return -EINVAL;
400430a0491SDavid Ahern 	}
401430a0491SDavid Ahern 
402430a0491SDavid Ahern 	/* convert len to number of nexthop ids */
403430a0491SDavid Ahern 	len /= sizeof(*nhg);
404430a0491SDavid Ahern 
405430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
406430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
407430a0491SDavid Ahern 		if (nhg[i].resvd1 || nhg[i].resvd2) {
408430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0");
409430a0491SDavid Ahern 			return -EINVAL;
410430a0491SDavid Ahern 		}
411430a0491SDavid Ahern 		if (nhg[i].weight > 254) {
412430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid value for weight");
413430a0491SDavid Ahern 			return -EINVAL;
414430a0491SDavid Ahern 		}
415430a0491SDavid Ahern 		for (j = i + 1; j < len; ++j) {
416430a0491SDavid Ahern 			if (nhg[i].id == nhg[j].id) {
417430a0491SDavid Ahern 				NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group");
418430a0491SDavid Ahern 				return -EINVAL;
419430a0491SDavid Ahern 			}
420430a0491SDavid Ahern 		}
421430a0491SDavid Ahern 	}
422430a0491SDavid Ahern 
423430a0491SDavid Ahern 	nhg = nla_data(tb[NHA_GROUP]);
424430a0491SDavid Ahern 	for (i = 0; i < len; ++i) {
425430a0491SDavid Ahern 		struct nexthop *nh;
426430a0491SDavid Ahern 
427430a0491SDavid Ahern 		nh = nexthop_find_by_id(net, nhg[i].id);
428430a0491SDavid Ahern 		if (!nh) {
429430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid nexthop id");
430430a0491SDavid Ahern 			return -EINVAL;
431430a0491SDavid Ahern 		}
432430a0491SDavid Ahern 		if (!valid_group_nh(nh, len, extack))
433430a0491SDavid Ahern 			return -EINVAL;
434430a0491SDavid Ahern 	}
435430a0491SDavid Ahern 	for (i = NHA_GROUP + 1; i < __NHA_MAX; ++i) {
436430a0491SDavid Ahern 		if (!tb[i])
437430a0491SDavid Ahern 			continue;
438430a0491SDavid Ahern 
439430a0491SDavid Ahern 		NL_SET_ERR_MSG(extack,
440430a0491SDavid Ahern 			       "No other attributes can be set in nexthop groups");
441430a0491SDavid Ahern 		return -EINVAL;
442430a0491SDavid Ahern 	}
443430a0491SDavid Ahern 
444430a0491SDavid Ahern 	return 0;
445430a0491SDavid Ahern }
446430a0491SDavid Ahern 
447430a0491SDavid Ahern static bool ipv6_good_nh(const struct fib6_nh *nh)
448430a0491SDavid Ahern {
449430a0491SDavid Ahern 	int state = NUD_REACHABLE;
450430a0491SDavid Ahern 	struct neighbour *n;
451430a0491SDavid Ahern 
452430a0491SDavid Ahern 	rcu_read_lock_bh();
453430a0491SDavid Ahern 
454430a0491SDavid Ahern 	n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
455430a0491SDavid Ahern 	if (n)
456430a0491SDavid Ahern 		state = n->nud_state;
457430a0491SDavid Ahern 
458430a0491SDavid Ahern 	rcu_read_unlock_bh();
459430a0491SDavid Ahern 
460430a0491SDavid Ahern 	return !!(state & NUD_VALID);
461430a0491SDavid Ahern }
462430a0491SDavid Ahern 
463430a0491SDavid Ahern static bool ipv4_good_nh(const struct fib_nh *nh)
464430a0491SDavid Ahern {
465430a0491SDavid Ahern 	int state = NUD_REACHABLE;
466430a0491SDavid Ahern 	struct neighbour *n;
467430a0491SDavid Ahern 
468430a0491SDavid Ahern 	rcu_read_lock_bh();
469430a0491SDavid Ahern 
470430a0491SDavid Ahern 	n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
471430a0491SDavid Ahern 				      (__force u32)nh->fib_nh_gw4);
472430a0491SDavid Ahern 	if (n)
473430a0491SDavid Ahern 		state = n->nud_state;
474430a0491SDavid Ahern 
475430a0491SDavid Ahern 	rcu_read_unlock_bh();
476430a0491SDavid Ahern 
477430a0491SDavid Ahern 	return !!(state & NUD_VALID);
478430a0491SDavid Ahern }
479430a0491SDavid Ahern 
480430a0491SDavid Ahern struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
481430a0491SDavid Ahern {
482430a0491SDavid Ahern 	struct nexthop *rc = NULL;
483430a0491SDavid Ahern 	struct nh_group *nhg;
484430a0491SDavid Ahern 	int i;
485430a0491SDavid Ahern 
486430a0491SDavid Ahern 	if (!nh->is_group)
487430a0491SDavid Ahern 		return nh;
488430a0491SDavid Ahern 
489430a0491SDavid Ahern 	nhg = rcu_dereference(nh->nh_grp);
490430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
491430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
492430a0491SDavid Ahern 		struct nh_info *nhi;
493430a0491SDavid Ahern 
494430a0491SDavid Ahern 		if (hash > atomic_read(&nhge->upper_bound))
495430a0491SDavid Ahern 			continue;
496430a0491SDavid Ahern 
497430a0491SDavid Ahern 		/* nexthops always check if it is good and does
498430a0491SDavid Ahern 		 * not rely on a sysctl for this behavior
499430a0491SDavid Ahern 		 */
500430a0491SDavid Ahern 		nhi = rcu_dereference(nhge->nh->nh_info);
501430a0491SDavid Ahern 		switch (nhi->family) {
502430a0491SDavid Ahern 		case AF_INET:
503430a0491SDavid Ahern 			if (ipv4_good_nh(&nhi->fib_nh))
504430a0491SDavid Ahern 				return nhge->nh;
505430a0491SDavid Ahern 			break;
506430a0491SDavid Ahern 		case AF_INET6:
507430a0491SDavid Ahern 			if (ipv6_good_nh(&nhi->fib6_nh))
508430a0491SDavid Ahern 				return nhge->nh;
509430a0491SDavid Ahern 			break;
510430a0491SDavid Ahern 		}
511430a0491SDavid Ahern 
512430a0491SDavid Ahern 		if (!rc)
513430a0491SDavid Ahern 			rc = nhge->nh;
514430a0491SDavid Ahern 	}
515430a0491SDavid Ahern 
516430a0491SDavid Ahern 	return rc;
517430a0491SDavid Ahern }
518430a0491SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_select_path);
519430a0491SDavid Ahern 
520f88c9aa1SDavid Ahern int nexthop_for_each_fib6_nh(struct nexthop *nh,
521f88c9aa1SDavid Ahern 			     int (*cb)(struct fib6_nh *nh, void *arg),
522f88c9aa1SDavid Ahern 			     void *arg)
523f88c9aa1SDavid Ahern {
524f88c9aa1SDavid Ahern 	struct nh_info *nhi;
525f88c9aa1SDavid Ahern 	int err;
526f88c9aa1SDavid Ahern 
527f88c9aa1SDavid Ahern 	if (nh->is_group) {
528f88c9aa1SDavid Ahern 		struct nh_group *nhg;
529f88c9aa1SDavid Ahern 		int i;
530f88c9aa1SDavid Ahern 
531f88c9aa1SDavid Ahern 		nhg = rcu_dereference_rtnl(nh->nh_grp);
532f88c9aa1SDavid Ahern 		for (i = 0; i < nhg->num_nh; i++) {
533f88c9aa1SDavid Ahern 			struct nh_grp_entry *nhge = &nhg->nh_entries[i];
534f88c9aa1SDavid Ahern 
535f88c9aa1SDavid Ahern 			nhi = rcu_dereference_rtnl(nhge->nh->nh_info);
536f88c9aa1SDavid Ahern 			err = cb(&nhi->fib6_nh, arg);
537f88c9aa1SDavid Ahern 			if (err)
538f88c9aa1SDavid Ahern 				return err;
539f88c9aa1SDavid Ahern 		}
540f88c9aa1SDavid Ahern 	} else {
541f88c9aa1SDavid Ahern 		nhi = rcu_dereference_rtnl(nh->nh_info);
542f88c9aa1SDavid Ahern 		err = cb(&nhi->fib6_nh, arg);
543f88c9aa1SDavid Ahern 		if (err)
544f88c9aa1SDavid Ahern 			return err;
545f88c9aa1SDavid Ahern 	}
546f88c9aa1SDavid Ahern 
547f88c9aa1SDavid Ahern 	return 0;
548f88c9aa1SDavid Ahern }
549f88c9aa1SDavid Ahern EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh);
550f88c9aa1SDavid Ahern 
551*7bf4796dSDavid Ahern static int check_src_addr(const struct in6_addr *saddr,
552*7bf4796dSDavid Ahern 			  struct netlink_ext_ack *extack)
553*7bf4796dSDavid Ahern {
554*7bf4796dSDavid Ahern 	if (!ipv6_addr_any(saddr)) {
555*7bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
556*7bf4796dSDavid Ahern 		return -EINVAL;
557*7bf4796dSDavid Ahern 	}
558*7bf4796dSDavid Ahern 	return 0;
559*7bf4796dSDavid Ahern }
560*7bf4796dSDavid Ahern 
561f88d8ea6SDavid Ahern int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
562f88d8ea6SDavid Ahern 		       struct netlink_ext_ack *extack)
563f88d8ea6SDavid Ahern {
564f88d8ea6SDavid Ahern 	struct nh_info *nhi;
565f88d8ea6SDavid Ahern 
566f88d8ea6SDavid Ahern 	/* fib6_src is unique to a fib6_info and limits the ability to cache
567f88d8ea6SDavid Ahern 	 * routes in fib6_nh within a nexthop that is potentially shared
568f88d8ea6SDavid Ahern 	 * across multiple fib entries. If the config wants to use source
569f88d8ea6SDavid Ahern 	 * routing it can not use nexthop objects. mlxsw also does not allow
570f88d8ea6SDavid Ahern 	 * fib6_src on routes.
571f88d8ea6SDavid Ahern 	 */
572*7bf4796dSDavid Ahern 	if (cfg && check_src_addr(&cfg->fc_src, extack) < 0)
573f88d8ea6SDavid Ahern 		return -EINVAL;
574f88d8ea6SDavid Ahern 
575f88d8ea6SDavid Ahern 	if (nh->is_group) {
576f88d8ea6SDavid Ahern 		struct nh_group *nhg;
577f88d8ea6SDavid Ahern 
578f88d8ea6SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
579f88d8ea6SDavid Ahern 		if (nhg->has_v4)
580f88d8ea6SDavid Ahern 			goto no_v4_nh;
581f88d8ea6SDavid Ahern 	} else {
582f88d8ea6SDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
583f88d8ea6SDavid Ahern 		if (nhi->family == AF_INET)
584f88d8ea6SDavid Ahern 			goto no_v4_nh;
585f88d8ea6SDavid Ahern 	}
586f88d8ea6SDavid Ahern 
587f88d8ea6SDavid Ahern 	return 0;
588f88d8ea6SDavid Ahern no_v4_nh:
589f88d8ea6SDavid Ahern 	NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
590f88d8ea6SDavid Ahern 	return -EINVAL;
591f88d8ea6SDavid Ahern }
592f88d8ea6SDavid Ahern EXPORT_SYMBOL_GPL(fib6_check_nexthop);
593f88d8ea6SDavid Ahern 
594*7bf4796dSDavid Ahern /* if existing nexthop has ipv6 routes linked to it, need
595*7bf4796dSDavid Ahern  * to verify this new spec works with ipv6
596*7bf4796dSDavid Ahern  */
597*7bf4796dSDavid Ahern static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new,
598*7bf4796dSDavid Ahern 			      struct netlink_ext_ack *extack)
599*7bf4796dSDavid Ahern {
600*7bf4796dSDavid Ahern 	struct fib6_info *f6i;
601*7bf4796dSDavid Ahern 
602*7bf4796dSDavid Ahern 	if (list_empty(&old->f6i_list))
603*7bf4796dSDavid Ahern 		return 0;
604*7bf4796dSDavid Ahern 
605*7bf4796dSDavid Ahern 	list_for_each_entry(f6i, &old->f6i_list, nh_list) {
606*7bf4796dSDavid Ahern 		if (check_src_addr(&f6i->fib6_src.addr, extack) < 0)
607*7bf4796dSDavid Ahern 			return -EINVAL;
608*7bf4796dSDavid Ahern 	}
609*7bf4796dSDavid Ahern 
610*7bf4796dSDavid Ahern 	return fib6_check_nexthop(new, NULL, extack);
611*7bf4796dSDavid Ahern }
612*7bf4796dSDavid Ahern 
6134c7e8084SDavid Ahern static int nexthop_check_scope(struct nexthop *nh, u8 scope,
6144c7e8084SDavid Ahern 			       struct netlink_ext_ack *extack)
6154c7e8084SDavid Ahern {
6164c7e8084SDavid Ahern 	struct nh_info *nhi;
6174c7e8084SDavid Ahern 
6184c7e8084SDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
6194c7e8084SDavid Ahern 	if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) {
6204c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack,
6214c7e8084SDavid Ahern 			       "Route with host scope can not have a gateway");
6224c7e8084SDavid Ahern 		return -EINVAL;
6234c7e8084SDavid Ahern 	}
6244c7e8084SDavid Ahern 
6254c7e8084SDavid Ahern 	if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) {
6264c7e8084SDavid Ahern 		NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop");
6274c7e8084SDavid Ahern 		return -EINVAL;
6284c7e8084SDavid Ahern 	}
6294c7e8084SDavid Ahern 
6304c7e8084SDavid Ahern 	return 0;
6314c7e8084SDavid Ahern }
6324c7e8084SDavid Ahern 
6334c7e8084SDavid Ahern /* Invoked by fib add code to verify nexthop by id is ok with
6344c7e8084SDavid Ahern  * config for prefix; parts of fib_check_nh not done when nexthop
6354c7e8084SDavid Ahern  * object is used.
6364c7e8084SDavid Ahern  */
6374c7e8084SDavid Ahern int fib_check_nexthop(struct nexthop *nh, u8 scope,
6384c7e8084SDavid Ahern 		      struct netlink_ext_ack *extack)
6394c7e8084SDavid Ahern {
6404c7e8084SDavid Ahern 	int err = 0;
6414c7e8084SDavid Ahern 
6424c7e8084SDavid Ahern 	if (nh->is_group) {
6434c7e8084SDavid Ahern 		struct nh_group *nhg;
6444c7e8084SDavid Ahern 
6454c7e8084SDavid Ahern 		if (scope == RT_SCOPE_HOST) {
6464c7e8084SDavid Ahern 			NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops");
6474c7e8084SDavid Ahern 			err = -EINVAL;
6484c7e8084SDavid Ahern 			goto out;
6494c7e8084SDavid Ahern 		}
6504c7e8084SDavid Ahern 
6514c7e8084SDavid Ahern 		nhg = rtnl_dereference(nh->nh_grp);
6524c7e8084SDavid Ahern 		/* all nexthops in a group have the same scope */
6534c7e8084SDavid Ahern 		err = nexthop_check_scope(nhg->nh_entries[0].nh, scope, extack);
6544c7e8084SDavid Ahern 	} else {
6554c7e8084SDavid Ahern 		err = nexthop_check_scope(nh, scope, extack);
6564c7e8084SDavid Ahern 	}
6574c7e8084SDavid Ahern out:
6584c7e8084SDavid Ahern 	return err;
6594c7e8084SDavid Ahern }
6604c7e8084SDavid Ahern 
661*7bf4796dSDavid Ahern static int fib_check_nh_list(struct nexthop *old, struct nexthop *new,
662*7bf4796dSDavid Ahern 			     struct netlink_ext_ack *extack)
663*7bf4796dSDavid Ahern {
664*7bf4796dSDavid Ahern 	struct fib_info *fi;
665*7bf4796dSDavid Ahern 
666*7bf4796dSDavid Ahern 	list_for_each_entry(fi, &old->fi_list, nh_list) {
667*7bf4796dSDavid Ahern 		int err;
668*7bf4796dSDavid Ahern 
669*7bf4796dSDavid Ahern 		err = fib_check_nexthop(new, fi->fib_scope, extack);
670*7bf4796dSDavid Ahern 		if (err)
671*7bf4796dSDavid Ahern 			return err;
672*7bf4796dSDavid Ahern 	}
673*7bf4796dSDavid Ahern 	return 0;
674*7bf4796dSDavid Ahern }
675*7bf4796dSDavid Ahern 
676430a0491SDavid Ahern static void nh_group_rebalance(struct nh_group *nhg)
677430a0491SDavid Ahern {
678430a0491SDavid Ahern 	int total = 0;
679430a0491SDavid Ahern 	int w = 0;
680430a0491SDavid Ahern 	int i;
681430a0491SDavid Ahern 
682430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i)
683430a0491SDavid Ahern 		total += nhg->nh_entries[i].weight;
684430a0491SDavid Ahern 
685430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
686430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
687430a0491SDavid Ahern 		int upper_bound;
688430a0491SDavid Ahern 
689430a0491SDavid Ahern 		w += nhge->weight;
690430a0491SDavid Ahern 		upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1;
691430a0491SDavid Ahern 		atomic_set(&nhge->upper_bound, upper_bound);
692430a0491SDavid Ahern 	}
693430a0491SDavid Ahern }
694430a0491SDavid Ahern 
695430a0491SDavid Ahern static void remove_nh_grp_entry(struct nh_grp_entry *nhge,
696430a0491SDavid Ahern 				struct nh_group *nhg,
697430a0491SDavid Ahern 				struct nl_info *nlinfo)
698430a0491SDavid Ahern {
699430a0491SDavid Ahern 	struct nexthop *nh = nhge->nh;
700430a0491SDavid Ahern 	struct nh_grp_entry *nhges;
701430a0491SDavid Ahern 	bool found = false;
702430a0491SDavid Ahern 	int i;
703430a0491SDavid Ahern 
704430a0491SDavid Ahern 	WARN_ON(!nh);
705430a0491SDavid Ahern 
706430a0491SDavid Ahern 	nhges = nhg->nh_entries;
707430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
708430a0491SDavid Ahern 		if (found) {
709430a0491SDavid Ahern 			nhges[i-1].nh = nhges[i].nh;
710430a0491SDavid Ahern 			nhges[i-1].weight = nhges[i].weight;
711430a0491SDavid Ahern 			list_del(&nhges[i].nh_list);
712430a0491SDavid Ahern 			list_add(&nhges[i-1].nh_list, &nhges[i-1].nh->grp_list);
713430a0491SDavid Ahern 		} else if (nhg->nh_entries[i].nh == nh) {
714430a0491SDavid Ahern 			found = true;
715430a0491SDavid Ahern 		}
716430a0491SDavid Ahern 	}
717430a0491SDavid Ahern 
718430a0491SDavid Ahern 	if (WARN_ON(!found))
719430a0491SDavid Ahern 		return;
720430a0491SDavid Ahern 
721430a0491SDavid Ahern 	nhg->num_nh--;
722430a0491SDavid Ahern 	nhg->nh_entries[nhg->num_nh].nh = NULL;
723430a0491SDavid Ahern 
724430a0491SDavid Ahern 	nh_group_rebalance(nhg);
725430a0491SDavid Ahern 
726430a0491SDavid Ahern 	nexthop_put(nh);
727430a0491SDavid Ahern 
728430a0491SDavid Ahern 	if (nlinfo)
729430a0491SDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, nhge->nh_parent, nlinfo);
730430a0491SDavid Ahern }
731430a0491SDavid Ahern 
732430a0491SDavid Ahern static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
733430a0491SDavid Ahern 				       struct nl_info *nlinfo)
734430a0491SDavid Ahern {
735430a0491SDavid Ahern 	struct nh_grp_entry *nhge, *tmp;
736430a0491SDavid Ahern 
737430a0491SDavid Ahern 	list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list) {
738430a0491SDavid Ahern 		struct nh_group *nhg;
739430a0491SDavid Ahern 
740430a0491SDavid Ahern 		list_del(&nhge->nh_list);
741430a0491SDavid Ahern 		nhg = rtnl_dereference(nhge->nh_parent->nh_grp);
742430a0491SDavid Ahern 		remove_nh_grp_entry(nhge, nhg, nlinfo);
743430a0491SDavid Ahern 
744430a0491SDavid Ahern 		/* if this group has no more entries then remove it */
745430a0491SDavid Ahern 		if (!nhg->num_nh)
746430a0491SDavid Ahern 			remove_nexthop(net, nhge->nh_parent, nlinfo);
747430a0491SDavid Ahern 	}
748430a0491SDavid Ahern }
749430a0491SDavid Ahern 
750430a0491SDavid Ahern static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
751430a0491SDavid Ahern {
752430a0491SDavid Ahern 	struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp);
753430a0491SDavid Ahern 	int i, num_nh = nhg->num_nh;
754430a0491SDavid Ahern 
755430a0491SDavid Ahern 	for (i = 0; i < num_nh; ++i) {
756430a0491SDavid Ahern 		struct nh_grp_entry *nhge = &nhg->nh_entries[i];
757430a0491SDavid Ahern 
758430a0491SDavid Ahern 		if (WARN_ON(!nhge->nh))
759430a0491SDavid Ahern 			continue;
760430a0491SDavid Ahern 
761430a0491SDavid Ahern 		list_del(&nhge->nh_list);
762430a0491SDavid Ahern 		nexthop_put(nhge->nh);
763430a0491SDavid Ahern 		nhge->nh = NULL;
764430a0491SDavid Ahern 		nhg->num_nh--;
765430a0491SDavid Ahern 	}
766430a0491SDavid Ahern }
767430a0491SDavid Ahern 
768*7bf4796dSDavid Ahern /* not called for nexthop replace */
7694c7e8084SDavid Ahern static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
7704c7e8084SDavid Ahern {
771f88d8ea6SDavid Ahern 	struct fib6_info *f6i, *tmp;
7724c7e8084SDavid Ahern 	bool do_flush = false;
7734c7e8084SDavid Ahern 	struct fib_info *fi;
7744c7e8084SDavid Ahern 
7754c7e8084SDavid Ahern 	list_for_each_entry(fi, &nh->fi_list, nh_list) {
7764c7e8084SDavid Ahern 		fi->fib_flags |= RTNH_F_DEAD;
7774c7e8084SDavid Ahern 		do_flush = true;
7784c7e8084SDavid Ahern 	}
7794c7e8084SDavid Ahern 	if (do_flush)
7804c7e8084SDavid Ahern 		fib_flush(net);
781f88d8ea6SDavid Ahern 
782f88d8ea6SDavid Ahern 	/* ip6_del_rt removes the entry from this list hence the _safe */
783f88d8ea6SDavid Ahern 	list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
784f88d8ea6SDavid Ahern 		/* __ip6_del_rt does a release, so do a hold here */
785f88d8ea6SDavid Ahern 		fib6_info_hold(f6i);
786f88d8ea6SDavid Ahern 		ipv6_stub->ip6_del_rt(net, f6i);
787f88d8ea6SDavid Ahern 	}
7884c7e8084SDavid Ahern }
7894c7e8084SDavid Ahern 
790430a0491SDavid Ahern static void __remove_nexthop(struct net *net, struct nexthop *nh,
791430a0491SDavid Ahern 			     struct nl_info *nlinfo)
792430a0491SDavid Ahern {
7934c7e8084SDavid Ahern 	__remove_nexthop_fib(net, nh);
7944c7e8084SDavid Ahern 
795430a0491SDavid Ahern 	if (nh->is_group) {
796430a0491SDavid Ahern 		remove_nexthop_group(nh, nlinfo);
797430a0491SDavid Ahern 	} else {
798597cfe4fSDavid Ahern 		struct nh_info *nhi;
799597cfe4fSDavid Ahern 
800597cfe4fSDavid Ahern 		nhi = rtnl_dereference(nh->nh_info);
801597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev)
802597cfe4fSDavid Ahern 			hlist_del(&nhi->dev_hash);
803430a0491SDavid Ahern 
804430a0491SDavid Ahern 		remove_nexthop_from_groups(net, nh, nlinfo);
805430a0491SDavid Ahern 	}
806597cfe4fSDavid Ahern }
807597cfe4fSDavid Ahern 
808ab84be7eSDavid Ahern static void remove_nexthop(struct net *net, struct nexthop *nh,
809430a0491SDavid Ahern 			   struct nl_info *nlinfo)
810ab84be7eSDavid Ahern {
811ab84be7eSDavid Ahern 	/* remove from the tree */
812ab84be7eSDavid Ahern 	rb_erase(&nh->rb_node, &net->nexthop.rb_root);
813ab84be7eSDavid Ahern 
814ab84be7eSDavid Ahern 	if (nlinfo)
815ab84be7eSDavid Ahern 		nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo);
816ab84be7eSDavid Ahern 
817430a0491SDavid Ahern 	__remove_nexthop(net, nh, nlinfo);
818ab84be7eSDavid Ahern 	nh_base_seq_inc(net);
819ab84be7eSDavid Ahern 
820ab84be7eSDavid Ahern 	nexthop_put(nh);
821ab84be7eSDavid Ahern }
822ab84be7eSDavid Ahern 
823*7bf4796dSDavid Ahern /* if any FIB entries reference this nexthop, any dst entries
824*7bf4796dSDavid Ahern  * need to be regenerated
825*7bf4796dSDavid Ahern  */
826*7bf4796dSDavid Ahern static void nh_rt_cache_flush(struct net *net, struct nexthop *nh)
827*7bf4796dSDavid Ahern {
828*7bf4796dSDavid Ahern 	struct fib6_info *f6i;
829*7bf4796dSDavid Ahern 
830*7bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list))
831*7bf4796dSDavid Ahern 		rt_cache_flush(net);
832*7bf4796dSDavid Ahern 
833*7bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
834*7bf4796dSDavid Ahern 		ipv6_stub->fib6_update_sernum(net, f6i);
835*7bf4796dSDavid Ahern }
836*7bf4796dSDavid Ahern 
837*7bf4796dSDavid Ahern static int replace_nexthop_grp(struct net *net, struct nexthop *old,
838*7bf4796dSDavid Ahern 			       struct nexthop *new,
839*7bf4796dSDavid Ahern 			       struct netlink_ext_ack *extack)
840*7bf4796dSDavid Ahern {
841*7bf4796dSDavid Ahern 	struct nh_group *oldg, *newg;
842*7bf4796dSDavid Ahern 	int i;
843*7bf4796dSDavid Ahern 
844*7bf4796dSDavid Ahern 	if (!new->is_group) {
845*7bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop.");
846*7bf4796dSDavid Ahern 		return -EINVAL;
847*7bf4796dSDavid Ahern 	}
848*7bf4796dSDavid Ahern 
849*7bf4796dSDavid Ahern 	oldg = rtnl_dereference(old->nh_grp);
850*7bf4796dSDavid Ahern 	newg = rtnl_dereference(new->nh_grp);
851*7bf4796dSDavid Ahern 
852*7bf4796dSDavid Ahern 	/* update parents - used by nexthop code for cleanup */
853*7bf4796dSDavid Ahern 	for (i = 0; i < newg->num_nh; i++)
854*7bf4796dSDavid Ahern 		newg->nh_entries[i].nh_parent = old;
855*7bf4796dSDavid Ahern 
856*7bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_grp, newg);
857*7bf4796dSDavid Ahern 
858*7bf4796dSDavid Ahern 	for (i = 0; i < oldg->num_nh; i++)
859*7bf4796dSDavid Ahern 		oldg->nh_entries[i].nh_parent = new;
860*7bf4796dSDavid Ahern 
861*7bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_grp, oldg);
862*7bf4796dSDavid Ahern 
863*7bf4796dSDavid Ahern 	return 0;
864*7bf4796dSDavid Ahern }
865*7bf4796dSDavid Ahern 
866*7bf4796dSDavid Ahern static int replace_nexthop_single(struct net *net, struct nexthop *old,
867*7bf4796dSDavid Ahern 				  struct nexthop *new,
868*7bf4796dSDavid Ahern 				  struct netlink_ext_ack *extack)
869*7bf4796dSDavid Ahern {
870*7bf4796dSDavid Ahern 	struct nh_info *oldi, *newi;
871*7bf4796dSDavid Ahern 
872*7bf4796dSDavid Ahern 	if (new->is_group) {
873*7bf4796dSDavid Ahern 		NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group.");
874*7bf4796dSDavid Ahern 		return -EINVAL;
875*7bf4796dSDavid Ahern 	}
876*7bf4796dSDavid Ahern 
877*7bf4796dSDavid Ahern 	oldi = rtnl_dereference(old->nh_info);
878*7bf4796dSDavid Ahern 	newi = rtnl_dereference(new->nh_info);
879*7bf4796dSDavid Ahern 
880*7bf4796dSDavid Ahern 	newi->nh_parent = old;
881*7bf4796dSDavid Ahern 	oldi->nh_parent = new;
882*7bf4796dSDavid Ahern 
883*7bf4796dSDavid Ahern 	old->protocol = new->protocol;
884*7bf4796dSDavid Ahern 	old->nh_flags = new->nh_flags;
885*7bf4796dSDavid Ahern 
886*7bf4796dSDavid Ahern 	rcu_assign_pointer(old->nh_info, newi);
887*7bf4796dSDavid Ahern 	rcu_assign_pointer(new->nh_info, oldi);
888*7bf4796dSDavid Ahern 
889*7bf4796dSDavid Ahern 	return 0;
890*7bf4796dSDavid Ahern }
891*7bf4796dSDavid Ahern 
892*7bf4796dSDavid Ahern static void __nexthop_replace_notify(struct net *net, struct nexthop *nh,
893*7bf4796dSDavid Ahern 				     struct nl_info *info)
894*7bf4796dSDavid Ahern {
895*7bf4796dSDavid Ahern 	struct fib6_info *f6i;
896*7bf4796dSDavid Ahern 
897*7bf4796dSDavid Ahern 	if (!list_empty(&nh->fi_list)) {
898*7bf4796dSDavid Ahern 		struct fib_info *fi;
899*7bf4796dSDavid Ahern 
900*7bf4796dSDavid Ahern 		/* expectation is a few fib_info per nexthop and then
901*7bf4796dSDavid Ahern 		 * a lot of routes per fib_info. So mark the fib_info
902*7bf4796dSDavid Ahern 		 * and then walk the fib tables once
903*7bf4796dSDavid Ahern 		 */
904*7bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
905*7bf4796dSDavid Ahern 			fi->nh_updated = true;
906*7bf4796dSDavid Ahern 
907*7bf4796dSDavid Ahern 		fib_info_notify_update(net, info);
908*7bf4796dSDavid Ahern 
909*7bf4796dSDavid Ahern 		list_for_each_entry(fi, &nh->fi_list, nh_list)
910*7bf4796dSDavid Ahern 			fi->nh_updated = false;
911*7bf4796dSDavid Ahern 	}
912*7bf4796dSDavid Ahern 
913*7bf4796dSDavid Ahern 	list_for_each_entry(f6i, &nh->f6i_list, nh_list)
914*7bf4796dSDavid Ahern 		ipv6_stub->fib6_rt_update(net, f6i, info);
915*7bf4796dSDavid Ahern }
916*7bf4796dSDavid Ahern 
917*7bf4796dSDavid Ahern /* send RTM_NEWROUTE with REPLACE flag set for all FIB entries
918*7bf4796dSDavid Ahern  * linked to this nexthop and for all groups that the nexthop
919*7bf4796dSDavid Ahern  * is a member of
920*7bf4796dSDavid Ahern  */
921*7bf4796dSDavid Ahern static void nexthop_replace_notify(struct net *net, struct nexthop *nh,
922*7bf4796dSDavid Ahern 				   struct nl_info *info)
923*7bf4796dSDavid Ahern {
924*7bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
925*7bf4796dSDavid Ahern 
926*7bf4796dSDavid Ahern 	__nexthop_replace_notify(net, nh, info);
927*7bf4796dSDavid Ahern 
928*7bf4796dSDavid Ahern 	list_for_each_entry(nhge, &nh->grp_list, nh_list)
929*7bf4796dSDavid Ahern 		__nexthop_replace_notify(net, nhge->nh_parent, info);
930*7bf4796dSDavid Ahern }
931*7bf4796dSDavid Ahern 
932ab84be7eSDavid Ahern static int replace_nexthop(struct net *net, struct nexthop *old,
933ab84be7eSDavid Ahern 			   struct nexthop *new, struct netlink_ext_ack *extack)
934ab84be7eSDavid Ahern {
935*7bf4796dSDavid Ahern 	bool new_is_reject = false;
936*7bf4796dSDavid Ahern 	struct nh_grp_entry *nhge;
937*7bf4796dSDavid Ahern 	int err;
938*7bf4796dSDavid Ahern 
939*7bf4796dSDavid Ahern 	/* check that existing FIB entries are ok with the
940*7bf4796dSDavid Ahern 	 * new nexthop definition
941*7bf4796dSDavid Ahern 	 */
942*7bf4796dSDavid Ahern 	err = fib_check_nh_list(old, new, extack);
943*7bf4796dSDavid Ahern 	if (err)
944*7bf4796dSDavid Ahern 		return err;
945*7bf4796dSDavid Ahern 
946*7bf4796dSDavid Ahern 	err = fib6_check_nh_list(old, new, extack);
947*7bf4796dSDavid Ahern 	if (err)
948*7bf4796dSDavid Ahern 		return err;
949*7bf4796dSDavid Ahern 
950*7bf4796dSDavid Ahern 	if (!new->is_group) {
951*7bf4796dSDavid Ahern 		struct nh_info *nhi = rtnl_dereference(new->nh_info);
952*7bf4796dSDavid Ahern 
953*7bf4796dSDavid Ahern 		new_is_reject = nhi->reject_nh;
954*7bf4796dSDavid Ahern 	}
955*7bf4796dSDavid Ahern 
956*7bf4796dSDavid Ahern 	list_for_each_entry(nhge, &old->grp_list, nh_list) {
957*7bf4796dSDavid Ahern 		/* if new nexthop is a blackhole, any groups using this
958*7bf4796dSDavid Ahern 		 * nexthop cannot have more than 1 path
959*7bf4796dSDavid Ahern 		 */
960*7bf4796dSDavid Ahern 		if (new_is_reject &&
961*7bf4796dSDavid Ahern 		    nexthop_num_path(nhge->nh_parent) > 1) {
962*7bf4796dSDavid Ahern 			NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path");
963*7bf4796dSDavid Ahern 			return -EINVAL;
964*7bf4796dSDavid Ahern 		}
965*7bf4796dSDavid Ahern 
966*7bf4796dSDavid Ahern 		err = fib_check_nh_list(nhge->nh_parent, new, extack);
967*7bf4796dSDavid Ahern 		if (err)
968*7bf4796dSDavid Ahern 			return err;
969*7bf4796dSDavid Ahern 
970*7bf4796dSDavid Ahern 		err = fib6_check_nh_list(nhge->nh_parent, new, extack);
971*7bf4796dSDavid Ahern 		if (err)
972*7bf4796dSDavid Ahern 			return err;
973*7bf4796dSDavid Ahern 	}
974*7bf4796dSDavid Ahern 
975*7bf4796dSDavid Ahern 	if (old->is_group)
976*7bf4796dSDavid Ahern 		err = replace_nexthop_grp(net, old, new, extack);
977*7bf4796dSDavid Ahern 	else
978*7bf4796dSDavid Ahern 		err = replace_nexthop_single(net, old, new, extack);
979*7bf4796dSDavid Ahern 
980*7bf4796dSDavid Ahern 	if (!err) {
981*7bf4796dSDavid Ahern 		nh_rt_cache_flush(net, old);
982*7bf4796dSDavid Ahern 
983*7bf4796dSDavid Ahern 		__remove_nexthop(net, new, NULL);
984*7bf4796dSDavid Ahern 		nexthop_put(new);
985*7bf4796dSDavid Ahern 	}
986*7bf4796dSDavid Ahern 
987*7bf4796dSDavid Ahern 	return err;
988ab84be7eSDavid Ahern }
989ab84be7eSDavid Ahern 
990ab84be7eSDavid Ahern /* called with rtnl_lock held */
991ab84be7eSDavid Ahern static int insert_nexthop(struct net *net, struct nexthop *new_nh,
992ab84be7eSDavid Ahern 			  struct nh_config *cfg, struct netlink_ext_ack *extack)
993ab84be7eSDavid Ahern {
994ab84be7eSDavid Ahern 	struct rb_node **pp, *parent = NULL, *next;
995ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
996ab84be7eSDavid Ahern 	bool replace = !!(cfg->nlflags & NLM_F_REPLACE);
997ab84be7eSDavid Ahern 	bool create = !!(cfg->nlflags & NLM_F_CREATE);
998ab84be7eSDavid Ahern 	u32 new_id = new_nh->id;
999*7bf4796dSDavid Ahern 	int replace_notify = 0;
1000ab84be7eSDavid Ahern 	int rc = -EEXIST;
1001ab84be7eSDavid Ahern 
1002ab84be7eSDavid Ahern 	pp = &root->rb_node;
1003ab84be7eSDavid Ahern 	while (1) {
1004ab84be7eSDavid Ahern 		struct nexthop *nh;
1005ab84be7eSDavid Ahern 
1006ab84be7eSDavid Ahern 		next = rtnl_dereference(*pp);
1007ab84be7eSDavid Ahern 		if (!next)
1008ab84be7eSDavid Ahern 			break;
1009ab84be7eSDavid Ahern 
1010ab84be7eSDavid Ahern 		parent = next;
1011ab84be7eSDavid Ahern 
1012ab84be7eSDavid Ahern 		nh = rb_entry(parent, struct nexthop, rb_node);
1013ab84be7eSDavid Ahern 		if (new_id < nh->id) {
1014ab84be7eSDavid Ahern 			pp = &next->rb_left;
1015ab84be7eSDavid Ahern 		} else if (new_id > nh->id) {
1016ab84be7eSDavid Ahern 			pp = &next->rb_right;
1017ab84be7eSDavid Ahern 		} else if (replace) {
1018ab84be7eSDavid Ahern 			rc = replace_nexthop(net, nh, new_nh, extack);
1019*7bf4796dSDavid Ahern 			if (!rc) {
1020ab84be7eSDavid Ahern 				new_nh = nh; /* send notification with old nh */
1021*7bf4796dSDavid Ahern 				replace_notify = 1;
1022*7bf4796dSDavid Ahern 			}
1023ab84be7eSDavid Ahern 			goto out;
1024ab84be7eSDavid Ahern 		} else {
1025ab84be7eSDavid Ahern 			/* id already exists and not a replace */
1026ab84be7eSDavid Ahern 			goto out;
1027ab84be7eSDavid Ahern 		}
1028ab84be7eSDavid Ahern 	}
1029ab84be7eSDavid Ahern 
1030ab84be7eSDavid Ahern 	if (replace && !create) {
1031ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists");
1032ab84be7eSDavid Ahern 		rc = -ENOENT;
1033ab84be7eSDavid Ahern 		goto out;
1034ab84be7eSDavid Ahern 	}
1035ab84be7eSDavid Ahern 
1036ab84be7eSDavid Ahern 	rb_link_node_rcu(&new_nh->rb_node, parent, pp);
1037ab84be7eSDavid Ahern 	rb_insert_color(&new_nh->rb_node, root);
1038ab84be7eSDavid Ahern 	rc = 0;
1039ab84be7eSDavid Ahern out:
1040ab84be7eSDavid Ahern 	if (!rc) {
1041ab84be7eSDavid Ahern 		nh_base_seq_inc(net);
1042ab84be7eSDavid Ahern 		nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo);
1043*7bf4796dSDavid Ahern 		if (replace_notify)
1044*7bf4796dSDavid Ahern 			nexthop_replace_notify(net, new_nh, &cfg->nlinfo);
1045ab84be7eSDavid Ahern 	}
1046ab84be7eSDavid Ahern 
1047ab84be7eSDavid Ahern 	return rc;
1048ab84be7eSDavid Ahern }
1049ab84be7eSDavid Ahern 
1050597cfe4fSDavid Ahern /* rtnl */
1051597cfe4fSDavid Ahern /* remove all nexthops tied to a device being deleted */
1052597cfe4fSDavid Ahern static void nexthop_flush_dev(struct net_device *dev)
1053597cfe4fSDavid Ahern {
1054597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
1055597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
1056597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
1057597cfe4fSDavid Ahern 	struct hlist_node *n;
1058597cfe4fSDavid Ahern 	struct nh_info *nhi;
1059597cfe4fSDavid Ahern 
1060597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
1061597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev != dev)
1062597cfe4fSDavid Ahern 			continue;
1063597cfe4fSDavid Ahern 
1064430a0491SDavid Ahern 		remove_nexthop(net, nhi->nh_parent, NULL);
1065597cfe4fSDavid Ahern 	}
1066597cfe4fSDavid Ahern }
1067597cfe4fSDavid Ahern 
1068ab84be7eSDavid Ahern /* rtnl; called when net namespace is deleted */
1069ab84be7eSDavid Ahern static void flush_all_nexthops(struct net *net)
1070ab84be7eSDavid Ahern {
1071ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
1072ab84be7eSDavid Ahern 	struct rb_node *node;
1073ab84be7eSDavid Ahern 	struct nexthop *nh;
1074ab84be7eSDavid Ahern 
1075ab84be7eSDavid Ahern 	while ((node = rb_first(root))) {
1076ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
1077430a0491SDavid Ahern 		remove_nexthop(net, nh, NULL);
1078ab84be7eSDavid Ahern 		cond_resched();
1079ab84be7eSDavid Ahern 	}
1080ab84be7eSDavid Ahern }
1081ab84be7eSDavid Ahern 
1082430a0491SDavid Ahern static struct nexthop *nexthop_create_group(struct net *net,
1083430a0491SDavid Ahern 					    struct nh_config *cfg)
1084430a0491SDavid Ahern {
1085430a0491SDavid Ahern 	struct nlattr *grps_attr = cfg->nh_grp;
1086430a0491SDavid Ahern 	struct nexthop_grp *entry = nla_data(grps_attr);
1087430a0491SDavid Ahern 	struct nh_group *nhg;
1088430a0491SDavid Ahern 	struct nexthop *nh;
1089430a0491SDavid Ahern 	int i;
1090430a0491SDavid Ahern 
1091430a0491SDavid Ahern 	nh = nexthop_alloc();
1092430a0491SDavid Ahern 	if (!nh)
1093430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
1094430a0491SDavid Ahern 
1095430a0491SDavid Ahern 	nh->is_group = 1;
1096430a0491SDavid Ahern 
1097430a0491SDavid Ahern 	nhg = nexthop_grp_alloc(nla_len(grps_attr) / sizeof(*entry));
1098430a0491SDavid Ahern 	if (!nhg) {
1099430a0491SDavid Ahern 		kfree(nh);
1100430a0491SDavid Ahern 		return ERR_PTR(-ENOMEM);
1101430a0491SDavid Ahern 	}
1102430a0491SDavid Ahern 
1103430a0491SDavid Ahern 	for (i = 0; i < nhg->num_nh; ++i) {
1104430a0491SDavid Ahern 		struct nexthop *nhe;
1105430a0491SDavid Ahern 		struct nh_info *nhi;
1106430a0491SDavid Ahern 
1107430a0491SDavid Ahern 		nhe = nexthop_find_by_id(net, entry[i].id);
1108430a0491SDavid Ahern 		if (!nexthop_get(nhe))
1109430a0491SDavid Ahern 			goto out_no_nh;
1110430a0491SDavid Ahern 
1111430a0491SDavid Ahern 		nhi = rtnl_dereference(nhe->nh_info);
1112430a0491SDavid Ahern 		if (nhi->family == AF_INET)
1113430a0491SDavid Ahern 			nhg->has_v4 = true;
1114430a0491SDavid Ahern 
1115430a0491SDavid Ahern 		nhg->nh_entries[i].nh = nhe;
1116430a0491SDavid Ahern 		nhg->nh_entries[i].weight = entry[i].weight + 1;
1117430a0491SDavid Ahern 		list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list);
1118430a0491SDavid Ahern 		nhg->nh_entries[i].nh_parent = nh;
1119430a0491SDavid Ahern 	}
1120430a0491SDavid Ahern 
1121430a0491SDavid Ahern 	if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) {
1122430a0491SDavid Ahern 		nhg->mpath = 1;
1123430a0491SDavid Ahern 		nh_group_rebalance(nhg);
1124430a0491SDavid Ahern 	}
1125430a0491SDavid Ahern 
1126430a0491SDavid Ahern 	rcu_assign_pointer(nh->nh_grp, nhg);
1127430a0491SDavid Ahern 
1128430a0491SDavid Ahern 	return nh;
1129430a0491SDavid Ahern 
1130430a0491SDavid Ahern out_no_nh:
1131430a0491SDavid Ahern 	for (; i >= 0; --i)
1132430a0491SDavid Ahern 		nexthop_put(nhg->nh_entries[i].nh);
1133430a0491SDavid Ahern 
1134430a0491SDavid Ahern 	kfree(nhg);
1135430a0491SDavid Ahern 	kfree(nh);
1136430a0491SDavid Ahern 
1137430a0491SDavid Ahern 	return ERR_PTR(-ENOENT);
1138430a0491SDavid Ahern }
1139430a0491SDavid Ahern 
1140597cfe4fSDavid Ahern static int nh_create_ipv4(struct net *net, struct nexthop *nh,
1141597cfe4fSDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
1142597cfe4fSDavid Ahern 			  struct netlink_ext_ack *extack)
1143597cfe4fSDavid Ahern {
1144597cfe4fSDavid Ahern 	struct fib_nh *fib_nh = &nhi->fib_nh;
1145597cfe4fSDavid Ahern 	struct fib_config fib_cfg = {
1146597cfe4fSDavid Ahern 		.fc_oif   = cfg->nh_ifindex,
1147597cfe4fSDavid Ahern 		.fc_gw4   = cfg->gw.ipv4,
1148597cfe4fSDavid Ahern 		.fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0,
1149597cfe4fSDavid Ahern 		.fc_flags = cfg->nh_flags,
1150b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
1151b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
1152597cfe4fSDavid Ahern 	};
1153597cfe4fSDavid Ahern 	u32 tb_id = l3mdev_fib_table(cfg->dev);
1154597cfe4fSDavid Ahern 	int err = -EINVAL;
1155597cfe4fSDavid Ahern 
1156597cfe4fSDavid Ahern 	err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack);
1157597cfe4fSDavid Ahern 	if (err) {
1158597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
1159597cfe4fSDavid Ahern 		goto out;
1160597cfe4fSDavid Ahern 	}
1161597cfe4fSDavid Ahern 
1162597cfe4fSDavid Ahern 	/* sets nh_dev if successful */
1163597cfe4fSDavid Ahern 	err = fib_check_nh(net, fib_nh, tb_id, 0, extack);
1164597cfe4fSDavid Ahern 	if (!err) {
1165597cfe4fSDavid Ahern 		nh->nh_flags = fib_nh->fib_nh_flags;
1166dcb1ecb5SDavid Ahern 		fib_info_update_nhc_saddr(net, &fib_nh->nh_common,
1167dcb1ecb5SDavid Ahern 					  fib_nh->fib_nh_scope);
1168597cfe4fSDavid Ahern 	} else {
1169597cfe4fSDavid Ahern 		fib_nh_release(net, fib_nh);
1170597cfe4fSDavid Ahern 	}
1171597cfe4fSDavid Ahern out:
1172597cfe4fSDavid Ahern 	return err;
1173597cfe4fSDavid Ahern }
1174597cfe4fSDavid Ahern 
117553010f99SDavid Ahern static int nh_create_ipv6(struct net *net,  struct nexthop *nh,
117653010f99SDavid Ahern 			  struct nh_info *nhi, struct nh_config *cfg,
117753010f99SDavid Ahern 			  struct netlink_ext_ack *extack)
117853010f99SDavid Ahern {
117953010f99SDavid Ahern 	struct fib6_nh *fib6_nh = &nhi->fib6_nh;
118053010f99SDavid Ahern 	struct fib6_config fib6_cfg = {
118153010f99SDavid Ahern 		.fc_table = l3mdev_fib_table(cfg->dev),
118253010f99SDavid Ahern 		.fc_ifindex = cfg->nh_ifindex,
118353010f99SDavid Ahern 		.fc_gateway = cfg->gw.ipv6,
118453010f99SDavid Ahern 		.fc_flags = cfg->nh_flags,
1185b513bd03SDavid Ahern 		.fc_encap = cfg->nh_encap,
1186b513bd03SDavid Ahern 		.fc_encap_type = cfg->nh_encap_type,
118753010f99SDavid Ahern 	};
11886f43e525SColin Ian King 	int err;
118953010f99SDavid Ahern 
119053010f99SDavid Ahern 	if (!ipv6_addr_any(&cfg->gw.ipv6))
119153010f99SDavid Ahern 		fib6_cfg.fc_flags |= RTF_GATEWAY;
119253010f99SDavid Ahern 
119353010f99SDavid Ahern 	/* sets nh_dev if successful */
119453010f99SDavid Ahern 	err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL,
119553010f99SDavid Ahern 				      extack);
119653010f99SDavid Ahern 	if (err)
119753010f99SDavid Ahern 		ipv6_stub->fib6_nh_release(fib6_nh);
119853010f99SDavid Ahern 	else
119953010f99SDavid Ahern 		nh->nh_flags = fib6_nh->fib_nh_flags;
120053010f99SDavid Ahern 
120153010f99SDavid Ahern 	return err;
120253010f99SDavid Ahern }
120353010f99SDavid Ahern 
1204ab84be7eSDavid Ahern static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg,
1205ab84be7eSDavid Ahern 				      struct netlink_ext_ack *extack)
1206ab84be7eSDavid Ahern {
1207ab84be7eSDavid Ahern 	struct nh_info *nhi;
1208ab84be7eSDavid Ahern 	struct nexthop *nh;
1209ab84be7eSDavid Ahern 	int err = 0;
1210ab84be7eSDavid Ahern 
1211ab84be7eSDavid Ahern 	nh = nexthop_alloc();
1212ab84be7eSDavid Ahern 	if (!nh)
1213ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
1214ab84be7eSDavid Ahern 
1215ab84be7eSDavid Ahern 	nhi = kzalloc(sizeof(*nhi), GFP_KERNEL);
1216ab84be7eSDavid Ahern 	if (!nhi) {
1217ab84be7eSDavid Ahern 		kfree(nh);
1218ab84be7eSDavid Ahern 		return ERR_PTR(-ENOMEM);
1219ab84be7eSDavid Ahern 	}
1220ab84be7eSDavid Ahern 
1221ab84be7eSDavid Ahern 	nh->nh_flags = cfg->nh_flags;
1222ab84be7eSDavid Ahern 	nh->net = net;
1223ab84be7eSDavid Ahern 
1224ab84be7eSDavid Ahern 	nhi->nh_parent = nh;
1225ab84be7eSDavid Ahern 	nhi->family = cfg->nh_family;
1226ab84be7eSDavid Ahern 	nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK;
1227ab84be7eSDavid Ahern 
1228ab84be7eSDavid Ahern 	if (cfg->nh_blackhole) {
1229ab84be7eSDavid Ahern 		nhi->reject_nh = 1;
1230ab84be7eSDavid Ahern 		cfg->nh_ifindex = net->loopback_dev->ifindex;
1231ab84be7eSDavid Ahern 	}
1232ab84be7eSDavid Ahern 
1233597cfe4fSDavid Ahern 	switch (cfg->nh_family) {
1234597cfe4fSDavid Ahern 	case AF_INET:
1235597cfe4fSDavid Ahern 		err = nh_create_ipv4(net, nh, nhi, cfg, extack);
1236597cfe4fSDavid Ahern 		break;
123753010f99SDavid Ahern 	case AF_INET6:
123853010f99SDavid Ahern 		err = nh_create_ipv6(net, nh, nhi, cfg, extack);
123953010f99SDavid Ahern 		break;
1240597cfe4fSDavid Ahern 	}
1241597cfe4fSDavid Ahern 
1242ab84be7eSDavid Ahern 	if (err) {
1243ab84be7eSDavid Ahern 		kfree(nhi);
1244ab84be7eSDavid Ahern 		kfree(nh);
1245ab84be7eSDavid Ahern 		return ERR_PTR(err);
1246ab84be7eSDavid Ahern 	}
1247ab84be7eSDavid Ahern 
1248597cfe4fSDavid Ahern 	/* add the entry to the device based hash */
1249597cfe4fSDavid Ahern 	nexthop_devhash_add(net, nhi);
1250597cfe4fSDavid Ahern 
1251ab84be7eSDavid Ahern 	rcu_assign_pointer(nh->nh_info, nhi);
1252ab84be7eSDavid Ahern 
1253ab84be7eSDavid Ahern 	return nh;
1254ab84be7eSDavid Ahern }
1255ab84be7eSDavid Ahern 
1256ab84be7eSDavid Ahern /* called with rtnl lock held */
1257ab84be7eSDavid Ahern static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg,
1258ab84be7eSDavid Ahern 				   struct netlink_ext_ack *extack)
1259ab84be7eSDavid Ahern {
1260ab84be7eSDavid Ahern 	struct nexthop *nh;
1261ab84be7eSDavid Ahern 	int err;
1262ab84be7eSDavid Ahern 
1263ab84be7eSDavid Ahern 	if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) {
1264ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Replace requires nexthop id");
1265ab84be7eSDavid Ahern 		return ERR_PTR(-EINVAL);
1266ab84be7eSDavid Ahern 	}
1267ab84be7eSDavid Ahern 
1268ab84be7eSDavid Ahern 	if (!cfg->nh_id) {
1269ab84be7eSDavid Ahern 		cfg->nh_id = nh_find_unused_id(net);
1270ab84be7eSDavid Ahern 		if (!cfg->nh_id) {
1271ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "No unused id");
1272ab84be7eSDavid Ahern 			return ERR_PTR(-EINVAL);
1273ab84be7eSDavid Ahern 		}
1274ab84be7eSDavid Ahern 	}
1275ab84be7eSDavid Ahern 
1276430a0491SDavid Ahern 	if (cfg->nh_grp)
1277430a0491SDavid Ahern 		nh = nexthop_create_group(net, cfg);
1278430a0491SDavid Ahern 	else
1279ab84be7eSDavid Ahern 		nh = nexthop_create(net, cfg, extack);
1280430a0491SDavid Ahern 
1281ab84be7eSDavid Ahern 	if (IS_ERR(nh))
1282ab84be7eSDavid Ahern 		return nh;
1283ab84be7eSDavid Ahern 
1284ab84be7eSDavid Ahern 	refcount_set(&nh->refcnt, 1);
1285ab84be7eSDavid Ahern 	nh->id = cfg->nh_id;
1286ab84be7eSDavid Ahern 	nh->protocol = cfg->nh_protocol;
1287ab84be7eSDavid Ahern 	nh->net = net;
1288ab84be7eSDavid Ahern 
1289ab84be7eSDavid Ahern 	err = insert_nexthop(net, nh, cfg, extack);
1290ab84be7eSDavid Ahern 	if (err) {
1291430a0491SDavid Ahern 		__remove_nexthop(net, nh, NULL);
1292ab84be7eSDavid Ahern 		nexthop_put(nh);
1293ab84be7eSDavid Ahern 		nh = ERR_PTR(err);
1294ab84be7eSDavid Ahern 	}
1295ab84be7eSDavid Ahern 
1296ab84be7eSDavid Ahern 	return nh;
1297ab84be7eSDavid Ahern }
1298ab84be7eSDavid Ahern 
1299ab84be7eSDavid Ahern static int rtm_to_nh_config(struct net *net, struct sk_buff *skb,
1300ab84be7eSDavid Ahern 			    struct nlmsghdr *nlh, struct nh_config *cfg,
1301ab84be7eSDavid Ahern 			    struct netlink_ext_ack *extack)
1302ab84be7eSDavid Ahern {
1303ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
1304ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1305ab84be7eSDavid Ahern 	int err;
1306ab84be7eSDavid Ahern 
1307ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1308ab84be7eSDavid Ahern 			  extack);
1309ab84be7eSDavid Ahern 	if (err < 0)
1310ab84be7eSDavid Ahern 		return err;
1311ab84be7eSDavid Ahern 
1312ab84be7eSDavid Ahern 	err = -EINVAL;
1313ab84be7eSDavid Ahern 	if (nhm->resvd || nhm->nh_scope) {
1314ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in ancillary header");
1315ab84be7eSDavid Ahern 		goto out;
1316ab84be7eSDavid Ahern 	}
1317ab84be7eSDavid Ahern 	if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) {
1318ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header");
1319ab84be7eSDavid Ahern 		goto out;
1320ab84be7eSDavid Ahern 	}
1321ab84be7eSDavid Ahern 
1322ab84be7eSDavid Ahern 	switch (nhm->nh_family) {
1323597cfe4fSDavid Ahern 	case AF_INET:
132453010f99SDavid Ahern 	case AF_INET6:
1325597cfe4fSDavid Ahern 		break;
1326430a0491SDavid Ahern 	case AF_UNSPEC:
1327430a0491SDavid Ahern 		if (tb[NHA_GROUP])
1328430a0491SDavid Ahern 			break;
1329430a0491SDavid Ahern 		/* fallthrough */
1330ab84be7eSDavid Ahern 	default:
1331ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid address family");
1332ab84be7eSDavid Ahern 		goto out;
1333ab84be7eSDavid Ahern 	}
1334ab84be7eSDavid Ahern 
1335ab84be7eSDavid Ahern 	if (tb[NHA_GROUPS] || tb[NHA_MASTER]) {
1336ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid attributes in request");
1337ab84be7eSDavid Ahern 		goto out;
1338ab84be7eSDavid Ahern 	}
1339ab84be7eSDavid Ahern 
1340ab84be7eSDavid Ahern 	memset(cfg, 0, sizeof(*cfg));
1341ab84be7eSDavid Ahern 	cfg->nlflags = nlh->nlmsg_flags;
1342ab84be7eSDavid Ahern 	cfg->nlinfo.portid = NETLINK_CB(skb).portid;
1343ab84be7eSDavid Ahern 	cfg->nlinfo.nlh = nlh;
1344ab84be7eSDavid Ahern 	cfg->nlinfo.nl_net = net;
1345ab84be7eSDavid Ahern 
1346ab84be7eSDavid Ahern 	cfg->nh_family = nhm->nh_family;
1347ab84be7eSDavid Ahern 	cfg->nh_protocol = nhm->nh_protocol;
1348ab84be7eSDavid Ahern 	cfg->nh_flags = nhm->nh_flags;
1349ab84be7eSDavid Ahern 
1350ab84be7eSDavid Ahern 	if (tb[NHA_ID])
1351ab84be7eSDavid Ahern 		cfg->nh_id = nla_get_u32(tb[NHA_ID]);
1352ab84be7eSDavid Ahern 
1353430a0491SDavid Ahern 	if (tb[NHA_GROUP]) {
1354430a0491SDavid Ahern 		if (nhm->nh_family != AF_UNSPEC) {
1355430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid family for group");
1356430a0491SDavid Ahern 			goto out;
1357430a0491SDavid Ahern 		}
1358430a0491SDavid Ahern 		cfg->nh_grp = tb[NHA_GROUP];
1359430a0491SDavid Ahern 
1360430a0491SDavid Ahern 		cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH;
1361430a0491SDavid Ahern 		if (tb[NHA_GROUP_TYPE])
1362430a0491SDavid Ahern 			cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]);
1363430a0491SDavid Ahern 
1364430a0491SDavid Ahern 		if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) {
1365430a0491SDavid Ahern 			NL_SET_ERR_MSG(extack, "Invalid group type");
1366430a0491SDavid Ahern 			goto out;
1367430a0491SDavid Ahern 		}
1368430a0491SDavid Ahern 		err = nh_check_attr_group(net, tb, extack);
1369430a0491SDavid Ahern 
1370430a0491SDavid Ahern 		/* no other attributes should be set */
1371430a0491SDavid Ahern 		goto out;
1372430a0491SDavid Ahern 	}
1373430a0491SDavid Ahern 
1374ab84be7eSDavid Ahern 	if (tb[NHA_BLACKHOLE]) {
1375b513bd03SDavid Ahern 		if (tb[NHA_GATEWAY] || tb[NHA_OIF] ||
1376b513bd03SDavid Ahern 		    tb[NHA_ENCAP]   || tb[NHA_ENCAP_TYPE]) {
1377ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway or oif");
1378ab84be7eSDavid Ahern 			goto out;
1379ab84be7eSDavid Ahern 		}
1380ab84be7eSDavid Ahern 
1381ab84be7eSDavid Ahern 		cfg->nh_blackhole = 1;
1382ab84be7eSDavid Ahern 		err = 0;
1383ab84be7eSDavid Ahern 		goto out;
1384ab84be7eSDavid Ahern 	}
1385ab84be7eSDavid Ahern 
1386ab84be7eSDavid Ahern 	if (!tb[NHA_OIF]) {
1387ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole nexthops");
1388ab84be7eSDavid Ahern 		goto out;
1389ab84be7eSDavid Ahern 	}
1390ab84be7eSDavid Ahern 
1391ab84be7eSDavid Ahern 	cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]);
1392ab84be7eSDavid Ahern 	if (cfg->nh_ifindex)
1393ab84be7eSDavid Ahern 		cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex);
1394ab84be7eSDavid Ahern 
1395ab84be7eSDavid Ahern 	if (!cfg->dev) {
1396ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid device index");
1397ab84be7eSDavid Ahern 		goto out;
1398ab84be7eSDavid Ahern 	} else if (!(cfg->dev->flags & IFF_UP)) {
1399ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Nexthop device is not up");
1400ab84be7eSDavid Ahern 		err = -ENETDOWN;
1401ab84be7eSDavid Ahern 		goto out;
1402ab84be7eSDavid Ahern 	} else if (!netif_carrier_ok(cfg->dev)) {
1403ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down");
1404ab84be7eSDavid Ahern 		err = -ENETDOWN;
1405ab84be7eSDavid Ahern 		goto out;
1406ab84be7eSDavid Ahern 	}
1407ab84be7eSDavid Ahern 
1408597cfe4fSDavid Ahern 	err = -EINVAL;
1409597cfe4fSDavid Ahern 	if (tb[NHA_GATEWAY]) {
1410597cfe4fSDavid Ahern 		struct nlattr *gwa = tb[NHA_GATEWAY];
1411597cfe4fSDavid Ahern 
1412597cfe4fSDavid Ahern 		switch (cfg->nh_family) {
1413597cfe4fSDavid Ahern 		case AF_INET:
1414597cfe4fSDavid Ahern 			if (nla_len(gwa) != sizeof(u32)) {
1415597cfe4fSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
1416597cfe4fSDavid Ahern 				goto out;
1417597cfe4fSDavid Ahern 			}
1418597cfe4fSDavid Ahern 			cfg->gw.ipv4 = nla_get_be32(gwa);
1419597cfe4fSDavid Ahern 			break;
142053010f99SDavid Ahern 		case AF_INET6:
142153010f99SDavid Ahern 			if (nla_len(gwa) != sizeof(struct in6_addr)) {
142253010f99SDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid gateway");
142353010f99SDavid Ahern 				goto out;
142453010f99SDavid Ahern 			}
142553010f99SDavid Ahern 			cfg->gw.ipv6 = nla_get_in6_addr(gwa);
142653010f99SDavid Ahern 			break;
1427597cfe4fSDavid Ahern 		default:
1428597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
1429597cfe4fSDavid Ahern 				       "Unknown address family for gateway");
1430597cfe4fSDavid Ahern 			goto out;
1431597cfe4fSDavid Ahern 		}
1432597cfe4fSDavid Ahern 	} else {
1433597cfe4fSDavid Ahern 		/* device only nexthop (no gateway) */
1434597cfe4fSDavid Ahern 		if (cfg->nh_flags & RTNH_F_ONLINK) {
1435597cfe4fSDavid Ahern 			NL_SET_ERR_MSG(extack,
1436597cfe4fSDavid Ahern 				       "ONLINK flag can not be set for nexthop without a gateway");
1437597cfe4fSDavid Ahern 			goto out;
1438597cfe4fSDavid Ahern 		}
1439597cfe4fSDavid Ahern 	}
1440597cfe4fSDavid Ahern 
1441b513bd03SDavid Ahern 	if (tb[NHA_ENCAP]) {
1442b513bd03SDavid Ahern 		cfg->nh_encap = tb[NHA_ENCAP];
1443b513bd03SDavid Ahern 
1444b513bd03SDavid Ahern 		if (!tb[NHA_ENCAP_TYPE]) {
1445b513bd03SDavid Ahern 			NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing");
1446b513bd03SDavid Ahern 			goto out;
1447b513bd03SDavid Ahern 		}
1448b513bd03SDavid Ahern 
1449b513bd03SDavid Ahern 		cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]);
1450b513bd03SDavid Ahern 		err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack);
1451b513bd03SDavid Ahern 		if (err < 0)
1452b513bd03SDavid Ahern 			goto out;
1453b513bd03SDavid Ahern 
1454b513bd03SDavid Ahern 	} else if (tb[NHA_ENCAP_TYPE]) {
1455b513bd03SDavid Ahern 		NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing");
1456b513bd03SDavid Ahern 		goto out;
1457b513bd03SDavid Ahern 	}
1458b513bd03SDavid Ahern 
1459b513bd03SDavid Ahern 
1460ab84be7eSDavid Ahern 	err = 0;
1461ab84be7eSDavid Ahern out:
1462ab84be7eSDavid Ahern 	return err;
1463ab84be7eSDavid Ahern }
1464ab84be7eSDavid Ahern 
1465ab84be7eSDavid Ahern /* rtnl */
1466ab84be7eSDavid Ahern static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1467ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1468ab84be7eSDavid Ahern {
1469ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
1470ab84be7eSDavid Ahern 	struct nh_config cfg;
1471ab84be7eSDavid Ahern 	struct nexthop *nh;
1472ab84be7eSDavid Ahern 	int err;
1473ab84be7eSDavid Ahern 
1474ab84be7eSDavid Ahern 	err = rtm_to_nh_config(net, skb, nlh, &cfg, extack);
1475ab84be7eSDavid Ahern 	if (!err) {
1476ab84be7eSDavid Ahern 		nh = nexthop_add(net, &cfg, extack);
1477ab84be7eSDavid Ahern 		if (IS_ERR(nh))
1478ab84be7eSDavid Ahern 			err = PTR_ERR(nh);
1479ab84be7eSDavid Ahern 	}
1480ab84be7eSDavid Ahern 
1481ab84be7eSDavid Ahern 	return err;
1482ab84be7eSDavid Ahern }
1483ab84be7eSDavid Ahern 
1484ab84be7eSDavid Ahern static int nh_valid_get_del_req(struct nlmsghdr *nlh, u32 *id,
1485ab84be7eSDavid Ahern 				struct netlink_ext_ack *extack)
1486ab84be7eSDavid Ahern {
1487ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(nlh);
1488ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1489ab84be7eSDavid Ahern 	int err, i;
1490ab84be7eSDavid Ahern 
1491ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1492ab84be7eSDavid Ahern 			  extack);
1493ab84be7eSDavid Ahern 	if (err < 0)
1494ab84be7eSDavid Ahern 		return err;
1495ab84be7eSDavid Ahern 
1496ab84be7eSDavid Ahern 	err = -EINVAL;
1497ab84be7eSDavid Ahern 	for (i = 0; i < __NHA_MAX; ++i) {
1498ab84be7eSDavid Ahern 		if (!tb[i])
1499ab84be7eSDavid Ahern 			continue;
1500ab84be7eSDavid Ahern 
1501ab84be7eSDavid Ahern 		switch (i) {
1502ab84be7eSDavid Ahern 		case NHA_ID:
1503ab84be7eSDavid Ahern 			break;
1504ab84be7eSDavid Ahern 		default:
1505ab84be7eSDavid Ahern 			NL_SET_ERR_MSG_ATTR(extack, tb[i],
1506ab84be7eSDavid Ahern 					    "Unexpected attribute in request");
1507ab84be7eSDavid Ahern 			goto out;
1508ab84be7eSDavid Ahern 		}
1509ab84be7eSDavid Ahern 	}
1510ab84be7eSDavid Ahern 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
1511ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in header");
1512ab84be7eSDavid Ahern 		goto out;
1513ab84be7eSDavid Ahern 	}
1514ab84be7eSDavid Ahern 
1515ab84be7eSDavid Ahern 	if (!tb[NHA_ID]) {
1516ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Nexthop id is missing");
1517ab84be7eSDavid Ahern 		goto out;
1518ab84be7eSDavid Ahern 	}
1519ab84be7eSDavid Ahern 
1520ab84be7eSDavid Ahern 	*id = nla_get_u32(tb[NHA_ID]);
1521ab84be7eSDavid Ahern 	if (!(*id))
1522ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid nexthop id");
1523ab84be7eSDavid Ahern 	else
1524ab84be7eSDavid Ahern 		err = 0;
1525ab84be7eSDavid Ahern out:
1526ab84be7eSDavid Ahern 	return err;
1527ab84be7eSDavid Ahern }
1528ab84be7eSDavid Ahern 
1529ab84be7eSDavid Ahern /* rtnl */
1530ab84be7eSDavid Ahern static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1531ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1532ab84be7eSDavid Ahern {
1533ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
1534ab84be7eSDavid Ahern 	struct nl_info nlinfo = {
1535ab84be7eSDavid Ahern 		.nlh = nlh,
1536ab84be7eSDavid Ahern 		.nl_net = net,
1537ab84be7eSDavid Ahern 		.portid = NETLINK_CB(skb).portid,
1538ab84be7eSDavid Ahern 	};
1539ab84be7eSDavid Ahern 	struct nexthop *nh;
1540ab84be7eSDavid Ahern 	int err;
1541ab84be7eSDavid Ahern 	u32 id;
1542ab84be7eSDavid Ahern 
1543ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
1544ab84be7eSDavid Ahern 	if (err)
1545ab84be7eSDavid Ahern 		return err;
1546ab84be7eSDavid Ahern 
1547ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
1548ab84be7eSDavid Ahern 	if (!nh)
1549ab84be7eSDavid Ahern 		return -ENOENT;
1550ab84be7eSDavid Ahern 
1551430a0491SDavid Ahern 	remove_nexthop(net, nh, &nlinfo);
1552ab84be7eSDavid Ahern 
1553ab84be7eSDavid Ahern 	return 0;
1554ab84be7eSDavid Ahern }
1555ab84be7eSDavid Ahern 
1556ab84be7eSDavid Ahern /* rtnl */
1557ab84be7eSDavid Ahern static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh,
1558ab84be7eSDavid Ahern 			   struct netlink_ext_ack *extack)
1559ab84be7eSDavid Ahern {
1560ab84be7eSDavid Ahern 	struct net *net = sock_net(in_skb->sk);
1561ab84be7eSDavid Ahern 	struct sk_buff *skb = NULL;
1562ab84be7eSDavid Ahern 	struct nexthop *nh;
1563ab84be7eSDavid Ahern 	int err;
1564ab84be7eSDavid Ahern 	u32 id;
1565ab84be7eSDavid Ahern 
1566ab84be7eSDavid Ahern 	err = nh_valid_get_del_req(nlh, &id, extack);
1567ab84be7eSDavid Ahern 	if (err)
1568ab84be7eSDavid Ahern 		return err;
1569ab84be7eSDavid Ahern 
1570ab84be7eSDavid Ahern 	err = -ENOBUFS;
1571ab84be7eSDavid Ahern 	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
1572ab84be7eSDavid Ahern 	if (!skb)
1573ab84be7eSDavid Ahern 		goto out;
1574ab84be7eSDavid Ahern 
1575ab84be7eSDavid Ahern 	err = -ENOENT;
1576ab84be7eSDavid Ahern 	nh = nexthop_find_by_id(net, id);
1577ab84be7eSDavid Ahern 	if (!nh)
1578ab84be7eSDavid Ahern 		goto errout_free;
1579ab84be7eSDavid Ahern 
1580ab84be7eSDavid Ahern 	err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid,
1581ab84be7eSDavid Ahern 			   nlh->nlmsg_seq, 0);
1582ab84be7eSDavid Ahern 	if (err < 0) {
1583ab84be7eSDavid Ahern 		WARN_ON(err == -EMSGSIZE);
1584ab84be7eSDavid Ahern 		goto errout_free;
1585ab84be7eSDavid Ahern 	}
1586ab84be7eSDavid Ahern 
1587ab84be7eSDavid Ahern 	err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
1588ab84be7eSDavid Ahern out:
1589ab84be7eSDavid Ahern 	return err;
1590ab84be7eSDavid Ahern errout_free:
1591ab84be7eSDavid Ahern 	kfree_skb(skb);
1592ab84be7eSDavid Ahern 	goto out;
1593ab84be7eSDavid Ahern }
1594ab84be7eSDavid Ahern 
1595430a0491SDavid Ahern static bool nh_dump_filtered(struct nexthop *nh, int dev_idx, int master_idx,
1596430a0491SDavid Ahern 			     bool group_filter, u8 family)
1597ab84be7eSDavid Ahern {
1598ab84be7eSDavid Ahern 	const struct net_device *dev;
1599ab84be7eSDavid Ahern 	const struct nh_info *nhi;
1600ab84be7eSDavid Ahern 
1601430a0491SDavid Ahern 	if (group_filter && !nh->is_group)
1602430a0491SDavid Ahern 		return true;
1603430a0491SDavid Ahern 
1604ab84be7eSDavid Ahern 	if (!dev_idx && !master_idx && !family)
1605ab84be7eSDavid Ahern 		return false;
1606ab84be7eSDavid Ahern 
1607430a0491SDavid Ahern 	if (nh->is_group)
1608430a0491SDavid Ahern 		return true;
1609430a0491SDavid Ahern 
1610ab84be7eSDavid Ahern 	nhi = rtnl_dereference(nh->nh_info);
1611ab84be7eSDavid Ahern 	if (family && nhi->family != family)
1612ab84be7eSDavid Ahern 		return true;
1613ab84be7eSDavid Ahern 
1614ab84be7eSDavid Ahern 	dev = nhi->fib_nhc.nhc_dev;
1615ab84be7eSDavid Ahern 	if (dev_idx && (!dev || dev->ifindex != dev_idx))
1616ab84be7eSDavid Ahern 		return true;
1617ab84be7eSDavid Ahern 
1618ab84be7eSDavid Ahern 	if (master_idx) {
1619ab84be7eSDavid Ahern 		struct net_device *master;
1620ab84be7eSDavid Ahern 
1621ab84be7eSDavid Ahern 		if (!dev)
1622ab84be7eSDavid Ahern 			return true;
1623ab84be7eSDavid Ahern 
1624ab84be7eSDavid Ahern 		master = netdev_master_upper_dev_get((struct net_device *)dev);
1625ab84be7eSDavid Ahern 		if (!master || master->ifindex != master_idx)
1626ab84be7eSDavid Ahern 			return true;
1627ab84be7eSDavid Ahern 	}
1628ab84be7eSDavid Ahern 
1629ab84be7eSDavid Ahern 	return false;
1630ab84be7eSDavid Ahern }
1631ab84be7eSDavid Ahern 
1632430a0491SDavid Ahern static int nh_valid_dump_req(const struct nlmsghdr *nlh, int *dev_idx,
1633430a0491SDavid Ahern 			     int *master_idx, bool *group_filter,
1634ab84be7eSDavid Ahern 			     struct netlink_callback *cb)
1635ab84be7eSDavid Ahern {
1636ab84be7eSDavid Ahern 	struct netlink_ext_ack *extack = cb->extack;
1637ab84be7eSDavid Ahern 	struct nlattr *tb[NHA_MAX + 1];
1638ab84be7eSDavid Ahern 	struct nhmsg *nhm;
1639ab84be7eSDavid Ahern 	int err, i;
1640ab84be7eSDavid Ahern 	u32 idx;
1641ab84be7eSDavid Ahern 
1642ab84be7eSDavid Ahern 	err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1643ab84be7eSDavid Ahern 			  NULL);
1644ab84be7eSDavid Ahern 	if (err < 0)
1645ab84be7eSDavid Ahern 		return err;
1646ab84be7eSDavid Ahern 
1647ab84be7eSDavid Ahern 	for (i = 0; i <= NHA_MAX; ++i) {
1648ab84be7eSDavid Ahern 		if (!tb[i])
1649ab84be7eSDavid Ahern 			continue;
1650ab84be7eSDavid Ahern 
1651ab84be7eSDavid Ahern 		switch (i) {
1652ab84be7eSDavid Ahern 		case NHA_OIF:
1653ab84be7eSDavid Ahern 			idx = nla_get_u32(tb[i]);
1654ab84be7eSDavid Ahern 			if (idx > INT_MAX) {
1655ab84be7eSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid device index");
1656ab84be7eSDavid Ahern 				return -EINVAL;
1657ab84be7eSDavid Ahern 			}
1658ab84be7eSDavid Ahern 			*dev_idx = idx;
1659ab84be7eSDavid Ahern 			break;
1660ab84be7eSDavid Ahern 		case NHA_MASTER:
1661ab84be7eSDavid Ahern 			idx = nla_get_u32(tb[i]);
1662ab84be7eSDavid Ahern 			if (idx > INT_MAX) {
1663ab84be7eSDavid Ahern 				NL_SET_ERR_MSG(extack, "Invalid master device index");
1664ab84be7eSDavid Ahern 				return -EINVAL;
1665ab84be7eSDavid Ahern 			}
1666ab84be7eSDavid Ahern 			*master_idx = idx;
1667ab84be7eSDavid Ahern 			break;
1668430a0491SDavid Ahern 		case NHA_GROUPS:
1669430a0491SDavid Ahern 			*group_filter = true;
1670430a0491SDavid Ahern 			break;
1671ab84be7eSDavid Ahern 		default:
1672ab84be7eSDavid Ahern 			NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
1673ab84be7eSDavid Ahern 			return -EINVAL;
1674ab84be7eSDavid Ahern 		}
1675ab84be7eSDavid Ahern 	}
1676ab84be7eSDavid Ahern 
1677ab84be7eSDavid Ahern 	nhm = nlmsg_data(nlh);
1678ab84be7eSDavid Ahern 	if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
1679ab84be7eSDavid Ahern 		NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request");
1680ab84be7eSDavid Ahern 		return -EINVAL;
1681ab84be7eSDavid Ahern 	}
1682ab84be7eSDavid Ahern 
1683ab84be7eSDavid Ahern 	return 0;
1684ab84be7eSDavid Ahern }
1685ab84be7eSDavid Ahern 
1686ab84be7eSDavid Ahern /* rtnl */
1687ab84be7eSDavid Ahern static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb)
1688ab84be7eSDavid Ahern {
1689ab84be7eSDavid Ahern 	struct nhmsg *nhm = nlmsg_data(cb->nlh);
1690ab84be7eSDavid Ahern 	int dev_filter_idx = 0, master_idx = 0;
1691ab84be7eSDavid Ahern 	struct net *net = sock_net(skb->sk);
1692ab84be7eSDavid Ahern 	struct rb_root *root = &net->nexthop.rb_root;
1693430a0491SDavid Ahern 	bool group_filter = false;
1694ab84be7eSDavid Ahern 	struct rb_node *node;
1695ab84be7eSDavid Ahern 	int idx = 0, s_idx;
1696ab84be7eSDavid Ahern 	int err;
1697ab84be7eSDavid Ahern 
1698430a0491SDavid Ahern 	err = nh_valid_dump_req(cb->nlh, &dev_filter_idx, &master_idx,
1699430a0491SDavid Ahern 				&group_filter, cb);
1700ab84be7eSDavid Ahern 	if (err < 0)
1701ab84be7eSDavid Ahern 		return err;
1702ab84be7eSDavid Ahern 
1703ab84be7eSDavid Ahern 	s_idx = cb->args[0];
1704ab84be7eSDavid Ahern 	for (node = rb_first(root); node; node = rb_next(node)) {
1705ab84be7eSDavid Ahern 		struct nexthop *nh;
1706ab84be7eSDavid Ahern 
1707ab84be7eSDavid Ahern 		if (idx < s_idx)
1708ab84be7eSDavid Ahern 			goto cont;
1709ab84be7eSDavid Ahern 
1710ab84be7eSDavid Ahern 		nh = rb_entry(node, struct nexthop, rb_node);
1711ab84be7eSDavid Ahern 		if (nh_dump_filtered(nh, dev_filter_idx, master_idx,
1712430a0491SDavid Ahern 				     group_filter, nhm->nh_family))
1713ab84be7eSDavid Ahern 			goto cont;
1714ab84be7eSDavid Ahern 
1715ab84be7eSDavid Ahern 		err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP,
1716ab84be7eSDavid Ahern 				   NETLINK_CB(cb->skb).portid,
1717ab84be7eSDavid Ahern 				   cb->nlh->nlmsg_seq, NLM_F_MULTI);
1718ab84be7eSDavid Ahern 		if (err < 0) {
1719ab84be7eSDavid Ahern 			if (likely(skb->len))
1720ab84be7eSDavid Ahern 				goto out;
1721ab84be7eSDavid Ahern 
1722ab84be7eSDavid Ahern 			goto out_err;
1723ab84be7eSDavid Ahern 		}
1724ab84be7eSDavid Ahern cont:
1725ab84be7eSDavid Ahern 		idx++;
1726ab84be7eSDavid Ahern 	}
1727ab84be7eSDavid Ahern 
1728ab84be7eSDavid Ahern out:
1729ab84be7eSDavid Ahern 	err = skb->len;
1730ab84be7eSDavid Ahern out_err:
1731ab84be7eSDavid Ahern 	cb->args[0] = idx;
1732ab84be7eSDavid Ahern 	cb->seq = net->nexthop.seq;
1733ab84be7eSDavid Ahern 	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
1734ab84be7eSDavid Ahern 
1735ab84be7eSDavid Ahern 	return err;
1736ab84be7eSDavid Ahern }
1737ab84be7eSDavid Ahern 
1738597cfe4fSDavid Ahern static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu)
1739597cfe4fSDavid Ahern {
1740597cfe4fSDavid Ahern 	unsigned int hash = nh_dev_hashfn(dev->ifindex);
1741597cfe4fSDavid Ahern 	struct net *net = dev_net(dev);
1742597cfe4fSDavid Ahern 	struct hlist_head *head = &net->nexthop.devhash[hash];
1743597cfe4fSDavid Ahern 	struct hlist_node *n;
1744597cfe4fSDavid Ahern 	struct nh_info *nhi;
1745597cfe4fSDavid Ahern 
1746597cfe4fSDavid Ahern 	hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
1747597cfe4fSDavid Ahern 		if (nhi->fib_nhc.nhc_dev == dev) {
1748597cfe4fSDavid Ahern 			if (nhi->family == AF_INET)
1749597cfe4fSDavid Ahern 				fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu,
1750597cfe4fSDavid Ahern 						   orig_mtu);
1751597cfe4fSDavid Ahern 		}
1752597cfe4fSDavid Ahern 	}
1753597cfe4fSDavid Ahern }
1754597cfe4fSDavid Ahern 
1755597cfe4fSDavid Ahern /* rtnl */
1756597cfe4fSDavid Ahern static int nh_netdev_event(struct notifier_block *this,
1757597cfe4fSDavid Ahern 			   unsigned long event, void *ptr)
1758597cfe4fSDavid Ahern {
1759597cfe4fSDavid Ahern 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
1760597cfe4fSDavid Ahern 	struct netdev_notifier_info_ext *info_ext;
1761597cfe4fSDavid Ahern 
1762597cfe4fSDavid Ahern 	switch (event) {
1763597cfe4fSDavid Ahern 	case NETDEV_DOWN:
1764597cfe4fSDavid Ahern 	case NETDEV_UNREGISTER:
1765597cfe4fSDavid Ahern 		nexthop_flush_dev(dev);
1766597cfe4fSDavid Ahern 		break;
1767597cfe4fSDavid Ahern 	case NETDEV_CHANGE:
1768597cfe4fSDavid Ahern 		if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP)))
1769597cfe4fSDavid Ahern 			nexthop_flush_dev(dev);
1770597cfe4fSDavid Ahern 		break;
1771597cfe4fSDavid Ahern 	case NETDEV_CHANGEMTU:
1772597cfe4fSDavid Ahern 		info_ext = ptr;
1773597cfe4fSDavid Ahern 		nexthop_sync_mtu(dev, info_ext->ext.mtu);
1774597cfe4fSDavid Ahern 		rt_cache_flush(dev_net(dev));
1775597cfe4fSDavid Ahern 		break;
1776597cfe4fSDavid Ahern 	}
1777597cfe4fSDavid Ahern 	return NOTIFY_DONE;
1778597cfe4fSDavid Ahern }
1779597cfe4fSDavid Ahern 
1780597cfe4fSDavid Ahern static struct notifier_block nh_netdev_notifier = {
1781597cfe4fSDavid Ahern 	.notifier_call = nh_netdev_event,
1782597cfe4fSDavid Ahern };
1783597cfe4fSDavid Ahern 
1784ab84be7eSDavid Ahern static void __net_exit nexthop_net_exit(struct net *net)
1785ab84be7eSDavid Ahern {
1786ab84be7eSDavid Ahern 	rtnl_lock();
1787ab84be7eSDavid Ahern 	flush_all_nexthops(net);
1788ab84be7eSDavid Ahern 	rtnl_unlock();
1789597cfe4fSDavid Ahern 	kfree(net->nexthop.devhash);
1790ab84be7eSDavid Ahern }
1791ab84be7eSDavid Ahern 
1792ab84be7eSDavid Ahern static int __net_init nexthop_net_init(struct net *net)
1793ab84be7eSDavid Ahern {
1794597cfe4fSDavid Ahern 	size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE;
1795597cfe4fSDavid Ahern 
1796ab84be7eSDavid Ahern 	net->nexthop.rb_root = RB_ROOT;
1797597cfe4fSDavid Ahern 	net->nexthop.devhash = kzalloc(sz, GFP_KERNEL);
1798597cfe4fSDavid Ahern 	if (!net->nexthop.devhash)
1799597cfe4fSDavid Ahern 		return -ENOMEM;
1800ab84be7eSDavid Ahern 
1801ab84be7eSDavid Ahern 	return 0;
1802ab84be7eSDavid Ahern }
1803ab84be7eSDavid Ahern 
1804ab84be7eSDavid Ahern static struct pernet_operations nexthop_net_ops = {
1805ab84be7eSDavid Ahern 	.init = nexthop_net_init,
1806ab84be7eSDavid Ahern 	.exit = nexthop_net_exit,
1807ab84be7eSDavid Ahern };
1808ab84be7eSDavid Ahern 
1809ab84be7eSDavid Ahern static int __init nexthop_init(void)
1810ab84be7eSDavid Ahern {
1811ab84be7eSDavid Ahern 	register_pernet_subsys(&nexthop_net_ops);
1812ab84be7eSDavid Ahern 
1813597cfe4fSDavid Ahern 	register_netdevice_notifier(&nh_netdev_notifier);
1814597cfe4fSDavid Ahern 
1815ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
1816ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0);
1817ab84be7eSDavid Ahern 	rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop,
1818ab84be7eSDavid Ahern 		      rtm_dump_nexthop, 0);
1819ab84be7eSDavid Ahern 
1820ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
1821ab84be7eSDavid Ahern 	rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
1822ab84be7eSDavid Ahern 
1823ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
1824ab84be7eSDavid Ahern 	rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
1825ab84be7eSDavid Ahern 
1826ab84be7eSDavid Ahern 	return 0;
1827ab84be7eSDavid Ahern }
1828ab84be7eSDavid Ahern subsys_initcall(nexthop_init);
1829