blob: ec1282858cb72eb4ea6020b508d54f77be252280 [file] [log] [blame]
David Ahernab84be72019-05-24 14:43:04 -07001// SPDX-License-Identifier: GPL-2.0
2/* Generic nexthop implementation
3 *
4 * Copyright (c) 2017-19 Cumulus Networks
5 * Copyright (c) 2017-19 David Ahern <dsa@cumulusnetworks.com>
6 */
7
8#include <linux/nexthop.h>
9#include <linux/rtnetlink.h>
10#include <linux/slab.h>
David Ahern430a0492019-05-24 14:43:08 -070011#include <net/arp.h>
David Ahern53010f92019-05-24 14:43:06 -070012#include <net/ipv6_stubs.h>
David Ahernb513bd02019-05-24 14:43:07 -070013#include <net/lwtunnel.h>
David Ahern430a0492019-05-24 14:43:08 -070014#include <net/ndisc.h>
David Ahernab84be72019-05-24 14:43:04 -070015#include <net/nexthop.h>
David Ahern597cfe4f2019-05-24 14:43:05 -070016#include <net/route.h>
David Ahernab84be72019-05-24 14:43:04 -070017#include <net/sock.h>
18
David Ahern430a0492019-05-24 14:43:08 -070019static void remove_nexthop(struct net *net, struct nexthop *nh,
20 struct nl_info *nlinfo);
21
David Ahern597cfe4f2019-05-24 14:43:05 -070022#define NH_DEV_HASHBITS 8
23#define NH_DEV_HASHSIZE (1U << NH_DEV_HASHBITS)
24
David Ahernab84be72019-05-24 14:43:04 -070025static const struct nla_policy rtm_nh_policy[NHA_MAX + 1] = {
David Ahernab84be72019-05-24 14:43:04 -070026 [NHA_ID] = { .type = NLA_U32 },
27 [NHA_GROUP] = { .type = NLA_BINARY },
28 [NHA_GROUP_TYPE] = { .type = NLA_U16 },
29 [NHA_BLACKHOLE] = { .type = NLA_FLAG },
30 [NHA_OIF] = { .type = NLA_U32 },
31 [NHA_GATEWAY] = { .type = NLA_BINARY },
32 [NHA_ENCAP_TYPE] = { .type = NLA_U16 },
33 [NHA_ENCAP] = { .type = NLA_NESTED },
34 [NHA_GROUPS] = { .type = NLA_FLAG },
35 [NHA_MASTER] = { .type = NLA_U32 },
Roopa Prabhu38428d62020-05-21 22:26:13 -070036 [NHA_FDB] = { .type = NLA_FLAG },
David Ahernab84be72019-05-24 14:43:04 -070037};
38
Roopa Prabhu8590ceed2020-05-21 22:26:15 -070039static int call_nexthop_notifiers(struct net *net,
Nathan Chancellord8e79f12020-05-27 01:00:20 -070040 enum nexthop_event_type event_type,
Roopa Prabhu8590ceed2020-05-21 22:26:15 -070041 struct nexthop *nh)
42{
43 int err;
44
45 err = atomic_notifier_call_chain(&net->nexthop.notifier_chain,
46 event_type, nh);
47 return notifier_to_errno(err);
48}
49
David Ahern597cfe4f2019-05-24 14:43:05 -070050static unsigned int nh_dev_hashfn(unsigned int val)
51{
52 unsigned int mask = NH_DEV_HASHSIZE - 1;
53
54 return (val ^
55 (val >> NH_DEV_HASHBITS) ^
56 (val >> (NH_DEV_HASHBITS * 2))) & mask;
57}
58
59static void nexthop_devhash_add(struct net *net, struct nh_info *nhi)
60{
61 struct net_device *dev = nhi->fib_nhc.nhc_dev;
62 struct hlist_head *head;
63 unsigned int hash;
64
65 WARN_ON(!dev);
66
67 hash = nh_dev_hashfn(dev->ifindex);
68 head = &net->nexthop.devhash[hash];
69 hlist_add_head(&nhi->dev_hash, head);
70}
71
David Ahern430a0492019-05-24 14:43:08 -070072static void nexthop_free_mpath(struct nexthop *nh)
David Ahernab84be72019-05-24 14:43:04 -070073{
David Ahern430a0492019-05-24 14:43:08 -070074 struct nh_group *nhg;
75 int i;
76
77 nhg = rcu_dereference_raw(nh->nh_grp);
78 for (i = 0; i < nhg->num_nh; ++i)
79 WARN_ON(nhg->nh_entries[i].nh);
80
81 kfree(nhg);
82}
83
84static void nexthop_free_single(struct nexthop *nh)
85{
David Ahernab84be72019-05-24 14:43:04 -070086 struct nh_info *nhi;
87
88 nhi = rcu_dereference_raw(nh->nh_info);
David Ahern597cfe4f2019-05-24 14:43:05 -070089 switch (nhi->family) {
90 case AF_INET:
91 fib_nh_release(nh->net, &nhi->fib_nh);
92 break;
David Ahern53010f92019-05-24 14:43:06 -070093 case AF_INET6:
94 ipv6_stub->fib6_nh_release(&nhi->fib6_nh);
95 break;
David Ahern597cfe4f2019-05-24 14:43:05 -070096 }
David Ahernab84be72019-05-24 14:43:04 -070097 kfree(nhi);
David Ahern430a0492019-05-24 14:43:08 -070098}
99
100void nexthop_free_rcu(struct rcu_head *head)
101{
102 struct nexthop *nh = container_of(head, struct nexthop, rcu);
103
104 if (nh->is_group)
105 nexthop_free_mpath(nh);
106 else
107 nexthop_free_single(nh);
David Ahernab84be72019-05-24 14:43:04 -0700108
109 kfree(nh);
110}
111EXPORT_SYMBOL_GPL(nexthop_free_rcu);
112
113static struct nexthop *nexthop_alloc(void)
114{
115 struct nexthop *nh;
116
117 nh = kzalloc(sizeof(struct nexthop), GFP_KERNEL);
David Ahern430a0492019-05-24 14:43:08 -0700118 if (nh) {
David Ahern4c7e8082019-06-03 20:19:51 -0700119 INIT_LIST_HEAD(&nh->fi_list);
David Ahernf88d8ea2019-06-03 20:19:52 -0700120 INIT_LIST_HEAD(&nh->f6i_list);
David Ahern430a0492019-05-24 14:43:08 -0700121 INIT_LIST_HEAD(&nh->grp_list);
Roopa Prabhu38428d62020-05-21 22:26:13 -0700122 INIT_LIST_HEAD(&nh->fdb_list);
David Ahern430a0492019-05-24 14:43:08 -0700123 }
David Ahernab84be72019-05-24 14:43:04 -0700124 return nh;
125}
126
David Ahern430a0492019-05-24 14:43:08 -0700127static struct nh_group *nexthop_grp_alloc(u16 num_nh)
128{
129 size_t sz = offsetof(struct nexthop, nh_grp)
130 + sizeof(struct nh_group)
131 + sizeof(struct nh_grp_entry) * num_nh;
132 struct nh_group *nhg;
133
134 nhg = kzalloc(sz, GFP_KERNEL);
135 if (nhg)
136 nhg->num_nh = num_nh;
137
138 return nhg;
139}
140
David Ahernab84be72019-05-24 14:43:04 -0700141static void nh_base_seq_inc(struct net *net)
142{
143 while (++net->nexthop.seq == 0)
144 ;
145}
146
147/* no reference taken; rcu lock or rtnl must be held */
148struct nexthop *nexthop_find_by_id(struct net *net, u32 id)
149{
150 struct rb_node **pp, *parent = NULL, *next;
151
152 pp = &net->nexthop.rb_root.rb_node;
153 while (1) {
154 struct nexthop *nh;
155
156 next = rcu_dereference_raw(*pp);
157 if (!next)
158 break;
159 parent = next;
160
161 nh = rb_entry(parent, struct nexthop, rb_node);
162 if (id < nh->id)
163 pp = &next->rb_left;
164 else if (id > nh->id)
165 pp = &next->rb_right;
166 else
167 return nh;
168 }
169 return NULL;
170}
171EXPORT_SYMBOL_GPL(nexthop_find_by_id);
172
173/* used for auto id allocation; called with rtnl held */
174static u32 nh_find_unused_id(struct net *net)
175{
176 u32 id_start = net->nexthop.last_id_allocated;
177
178 while (1) {
179 net->nexthop.last_id_allocated++;
180 if (net->nexthop.last_id_allocated == id_start)
181 break;
182
183 if (!nexthop_find_by_id(net, net->nexthop.last_id_allocated))
184 return net->nexthop.last_id_allocated;
185 }
186 return 0;
187}
188
David Ahern430a0492019-05-24 14:43:08 -0700189static int nla_put_nh_group(struct sk_buff *skb, struct nh_group *nhg)
190{
191 struct nexthop_grp *p;
192 size_t len = nhg->num_nh * sizeof(*p);
193 struct nlattr *nla;
194 u16 group_type = 0;
195 int i;
196
197 if (nhg->mpath)
198 group_type = NEXTHOP_GRP_TYPE_MPATH;
199
200 if (nla_put_u16(skb, NHA_GROUP_TYPE, group_type))
201 goto nla_put_failure;
202
203 nla = nla_reserve(skb, NHA_GROUP, len);
204 if (!nla)
205 goto nla_put_failure;
206
207 p = nla_data(nla);
208 for (i = 0; i < nhg->num_nh; ++i) {
209 p->id = nhg->nh_entries[i].nh->id;
210 p->weight = nhg->nh_entries[i].weight - 1;
211 p += 1;
212 }
213
214 return 0;
215
216nla_put_failure:
217 return -EMSGSIZE;
218}
219
David Ahernab84be72019-05-24 14:43:04 -0700220static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh,
221 int event, u32 portid, u32 seq, unsigned int nlflags)
222{
David Ahern53010f92019-05-24 14:43:06 -0700223 struct fib6_nh *fib6_nh;
David Ahern597cfe4f2019-05-24 14:43:05 -0700224 struct fib_nh *fib_nh;
David Ahernab84be72019-05-24 14:43:04 -0700225 struct nlmsghdr *nlh;
226 struct nh_info *nhi;
227 struct nhmsg *nhm;
228
229 nlh = nlmsg_put(skb, portid, seq, event, sizeof(*nhm), nlflags);
230 if (!nlh)
231 return -EMSGSIZE;
232
233 nhm = nlmsg_data(nlh);
234 nhm->nh_family = AF_UNSPEC;
235 nhm->nh_flags = nh->nh_flags;
236 nhm->nh_protocol = nh->protocol;
237 nhm->nh_scope = 0;
238 nhm->resvd = 0;
239
240 if (nla_put_u32(skb, NHA_ID, nh->id))
241 goto nla_put_failure;
242
Roopa Prabhu38428d62020-05-21 22:26:13 -0700243 if (nh->is_fdb_nh && nla_put_flag(skb, NHA_FDB))
244 goto nla_put_failure;
245
David Ahern430a0492019-05-24 14:43:08 -0700246 if (nh->is_group) {
247 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
248
249 if (nla_put_nh_group(skb, nhg))
250 goto nla_put_failure;
251 goto out;
252 }
253
David Ahernab84be72019-05-24 14:43:04 -0700254 nhi = rtnl_dereference(nh->nh_info);
255 nhm->nh_family = nhi->family;
256 if (nhi->reject_nh) {
257 if (nla_put_flag(skb, NHA_BLACKHOLE))
258 goto nla_put_failure;
259 goto out;
Roopa Prabhu38428d62020-05-21 22:26:13 -0700260 } else if (!nh->is_fdb_nh) {
David Ahern597cfe4f2019-05-24 14:43:05 -0700261 const struct net_device *dev;
262
263 dev = nhi->fib_nhc.nhc_dev;
264 if (dev && nla_put_u32(skb, NHA_OIF, dev->ifindex))
265 goto nla_put_failure;
266 }
267
268 nhm->nh_scope = nhi->fib_nhc.nhc_scope;
269 switch (nhi->family) {
270 case AF_INET:
271 fib_nh = &nhi->fib_nh;
272 if (fib_nh->fib_nh_gw_family &&
273 nla_put_u32(skb, NHA_GATEWAY, fib_nh->fib_nh_gw4))
274 goto nla_put_failure;
275 break;
David Ahern53010f92019-05-24 14:43:06 -0700276
277 case AF_INET6:
278 fib6_nh = &nhi->fib6_nh;
279 if (fib6_nh->fib_nh_gw_family &&
280 nla_put_in6_addr(skb, NHA_GATEWAY, &fib6_nh->fib_nh_gw6))
281 goto nla_put_failure;
282 break;
David Ahernab84be72019-05-24 14:43:04 -0700283 }
284
David Ahernb513bd02019-05-24 14:43:07 -0700285 if (nhi->fib_nhc.nhc_lwtstate &&
286 lwtunnel_fill_encap(skb, nhi->fib_nhc.nhc_lwtstate,
287 NHA_ENCAP, NHA_ENCAP_TYPE) < 0)
288 goto nla_put_failure;
289
David Ahernab84be72019-05-24 14:43:04 -0700290out:
291 nlmsg_end(skb, nlh);
292 return 0;
293
294nla_put_failure:
Stephen Worleyd69100b2020-05-19 21:57:12 -0400295 nlmsg_cancel(skb, nlh);
David Ahernab84be72019-05-24 14:43:04 -0700296 return -EMSGSIZE;
297}
298
David Ahern430a0492019-05-24 14:43:08 -0700299static size_t nh_nlmsg_size_grp(struct nexthop *nh)
300{
301 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
302 size_t sz = sizeof(struct nexthop_grp) * nhg->num_nh;
303
304 return nla_total_size(sz) +
305 nla_total_size(2); /* NHA_GROUP_TYPE */
306}
307
308static size_t nh_nlmsg_size_single(struct nexthop *nh)
David Ahernab84be72019-05-24 14:43:04 -0700309{
David Ahern597cfe4f2019-05-24 14:43:05 -0700310 struct nh_info *nhi = rtnl_dereference(nh->nh_info);
David Ahern430a0492019-05-24 14:43:08 -0700311 size_t sz;
David Ahernab84be72019-05-24 14:43:04 -0700312
313 /* covers NHA_BLACKHOLE since NHA_OIF and BLACKHOLE
314 * are mutually exclusive
315 */
David Ahern430a0492019-05-24 14:43:08 -0700316 sz = nla_total_size(4); /* NHA_OIF */
David Ahernab84be72019-05-24 14:43:04 -0700317
David Ahern597cfe4f2019-05-24 14:43:05 -0700318 switch (nhi->family) {
319 case AF_INET:
320 if (nhi->fib_nh.fib_nh_gw_family)
321 sz += nla_total_size(4); /* NHA_GATEWAY */
322 break;
David Ahern53010f92019-05-24 14:43:06 -0700323
324 case AF_INET6:
325 /* NHA_GATEWAY */
326 if (nhi->fib6_nh.fib_nh_gw_family)
327 sz += nla_total_size(sizeof(const struct in6_addr));
328 break;
David Ahern597cfe4f2019-05-24 14:43:05 -0700329 }
330
David Ahernb513bd02019-05-24 14:43:07 -0700331 if (nhi->fib_nhc.nhc_lwtstate) {
332 sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate);
333 sz += nla_total_size(2); /* NHA_ENCAP_TYPE */
334 }
335
David Ahernab84be72019-05-24 14:43:04 -0700336 return sz;
337}
338
David Ahern430a0492019-05-24 14:43:08 -0700339static size_t nh_nlmsg_size(struct nexthop *nh)
340{
Stephen Worleyf9e95552020-01-24 16:53:27 -0500341 size_t sz = NLMSG_ALIGN(sizeof(struct nhmsg));
342
343 sz += nla_total_size(4); /* NHA_ID */
David Ahern430a0492019-05-24 14:43:08 -0700344
345 if (nh->is_group)
346 sz += nh_nlmsg_size_grp(nh);
347 else
348 sz += nh_nlmsg_size_single(nh);
349
350 return sz;
351}
352
David Ahernab84be72019-05-24 14:43:04 -0700353static void nexthop_notify(int event, struct nexthop *nh, struct nl_info *info)
354{
355 unsigned int nlflags = info->nlh ? info->nlh->nlmsg_flags : 0;
356 u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
357 struct sk_buff *skb;
358 int err = -ENOBUFS;
359
360 skb = nlmsg_new(nh_nlmsg_size(nh), gfp_any());
361 if (!skb)
362 goto errout;
363
364 err = nh_fill_node(skb, nh, event, info->portid, seq, nlflags);
365 if (err < 0) {
366 /* -EMSGSIZE implies BUG in nh_nlmsg_size() */
367 WARN_ON(err == -EMSGSIZE);
368 kfree_skb(skb);
369 goto errout;
370 }
371
372 rtnl_notify(skb, info->nl_net, info->portid, RTNLGRP_NEXTHOP,
373 info->nlh, gfp_any());
374 return;
375errout:
376 if (err < 0)
377 rtnl_set_sk_err(info->nl_net, RTNLGRP_NEXTHOP, err);
378}
379
David Ahern430a0492019-05-24 14:43:08 -0700380static bool valid_group_nh(struct nexthop *nh, unsigned int npaths,
381 struct netlink_ext_ack *extack)
David Ahern597cfe4f2019-05-24 14:43:05 -0700382{
David Ahern430a0492019-05-24 14:43:08 -0700383 if (nh->is_group) {
384 struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
David Ahern597cfe4f2019-05-24 14:43:05 -0700385
David Ahern430a0492019-05-24 14:43:08 -0700386 /* nested multipath (group within a group) is not
387 * supported
388 */
389 if (nhg->mpath) {
390 NL_SET_ERR_MSG(extack,
391 "Multipath group can not be a nexthop within a group");
392 return false;
393 }
394 } else {
395 struct nh_info *nhi = rtnl_dereference(nh->nh_info);
396
397 if (nhi->reject_nh && npaths > 1) {
398 NL_SET_ERR_MSG(extack,
399 "Blackhole nexthop can not be used in a group with more than 1 path");
400 return false;
401 }
402 }
403
404 return true;
405}
406
Roopa Prabhu38428d62020-05-21 22:26:13 -0700407static int nh_check_attr_fdb_group(struct nexthop *nh, u8 *nh_family,
408 struct netlink_ext_ack *extack)
409{
410 struct nh_info *nhi;
411
412 if (!nh->is_fdb_nh) {
413 NL_SET_ERR_MSG(extack, "FDB nexthop group can only have fdb nexthops");
414 return -EINVAL;
415 }
416
417 nhi = rtnl_dereference(nh->nh_info);
418 if (*nh_family == AF_UNSPEC) {
419 *nh_family = nhi->family;
420 } else if (*nh_family != nhi->family) {
421 NL_SET_ERR_MSG(extack, "FDB nexthop group cannot have mixed family nexthops");
422 return -EINVAL;
423 }
424
425 return 0;
426}
427
David Ahern430a0492019-05-24 14:43:08 -0700428static int nh_check_attr_group(struct net *net, struct nlattr *tb[],
429 struct netlink_ext_ack *extack)
430{
431 unsigned int len = nla_len(tb[NHA_GROUP]);
Roopa Prabhu38428d62020-05-21 22:26:13 -0700432 u8 nh_family = AF_UNSPEC;
David Ahern430a0492019-05-24 14:43:08 -0700433 struct nexthop_grp *nhg;
434 unsigned int i, j;
Roopa Prabhu38428d62020-05-21 22:26:13 -0700435 u8 nhg_fdb = 0;
David Ahern430a0492019-05-24 14:43:08 -0700436
437 if (len & (sizeof(struct nexthop_grp) - 1)) {
438 NL_SET_ERR_MSG(extack,
439 "Invalid length for nexthop group attribute");
440 return -EINVAL;
441 }
442
443 /* convert len to number of nexthop ids */
444 len /= sizeof(*nhg);
445
446 nhg = nla_data(tb[NHA_GROUP]);
447 for (i = 0; i < len; ++i) {
448 if (nhg[i].resvd1 || nhg[i].resvd2) {
449 NL_SET_ERR_MSG(extack, "Reserved fields in nexthop_grp must be 0");
450 return -EINVAL;
451 }
452 if (nhg[i].weight > 254) {
453 NL_SET_ERR_MSG(extack, "Invalid value for weight");
454 return -EINVAL;
455 }
456 for (j = i + 1; j < len; ++j) {
457 if (nhg[i].id == nhg[j].id) {
458 NL_SET_ERR_MSG(extack, "Nexthop id can not be used twice in a group");
459 return -EINVAL;
460 }
461 }
462 }
463
Roopa Prabhu38428d62020-05-21 22:26:13 -0700464 if (tb[NHA_FDB])
465 nhg_fdb = 1;
David Ahern430a0492019-05-24 14:43:08 -0700466 nhg = nla_data(tb[NHA_GROUP]);
467 for (i = 0; i < len; ++i) {
468 struct nexthop *nh;
469
470 nh = nexthop_find_by_id(net, nhg[i].id);
471 if (!nh) {
472 NL_SET_ERR_MSG(extack, "Invalid nexthop id");
473 return -EINVAL;
474 }
475 if (!valid_group_nh(nh, len, extack))
476 return -EINVAL;
Roopa Prabhu38428d62020-05-21 22:26:13 -0700477
478 if (nhg_fdb && nh_check_attr_fdb_group(nh, &nh_family, extack))
479 return -EINVAL;
480
481 if (!nhg_fdb && nh->is_fdb_nh) {
482 NL_SET_ERR_MSG(extack, "Non FDB nexthop group cannot have fdb nexthops");
483 return -EINVAL;
484 }
David Ahern430a0492019-05-24 14:43:08 -0700485 }
David Ahern84be69b2020-05-17 11:26:32 -0600486 for (i = NHA_GROUP_TYPE + 1; i < __NHA_MAX; ++i) {
David Ahern430a0492019-05-24 14:43:08 -0700487 if (!tb[i])
488 continue;
Roopa Prabhu38428d62020-05-21 22:26:13 -0700489 if (tb[NHA_FDB])
490 continue;
David Ahern430a0492019-05-24 14:43:08 -0700491 NL_SET_ERR_MSG(extack,
492 "No other attributes can be set in nexthop groups");
493 return -EINVAL;
494 }
495
496 return 0;
497}
498
499static bool ipv6_good_nh(const struct fib6_nh *nh)
500{
501 int state = NUD_REACHABLE;
502 struct neighbour *n;
503
504 rcu_read_lock_bh();
505
506 n = __ipv6_neigh_lookup_noref_stub(nh->fib_nh_dev, &nh->fib_nh_gw6);
507 if (n)
508 state = n->nud_state;
509
510 rcu_read_unlock_bh();
511
512 return !!(state & NUD_VALID);
513}
514
515static bool ipv4_good_nh(const struct fib_nh *nh)
516{
517 int state = NUD_REACHABLE;
518 struct neighbour *n;
519
520 rcu_read_lock_bh();
521
522 n = __ipv4_neigh_lookup_noref(nh->fib_nh_dev,
523 (__force u32)nh->fib_nh_gw4);
524 if (n)
525 state = n->nud_state;
526
527 rcu_read_unlock_bh();
528
529 return !!(state & NUD_VALID);
530}
531
532struct nexthop *nexthop_select_path(struct nexthop *nh, int hash)
533{
534 struct nexthop *rc = NULL;
535 struct nh_group *nhg;
536 int i;
537
538 if (!nh->is_group)
539 return nh;
540
541 nhg = rcu_dereference(nh->nh_grp);
542 for (i = 0; i < nhg->num_nh; ++i) {
543 struct nh_grp_entry *nhge = &nhg->nh_entries[i];
544 struct nh_info *nhi;
545
546 if (hash > atomic_read(&nhge->upper_bound))
547 continue;
548
Roopa Prabhu38428d62020-05-21 22:26:13 -0700549 if (nhge->nh->is_fdb_nh)
550 return nhge->nh;
551
David Ahern430a0492019-05-24 14:43:08 -0700552 /* nexthops always check if it is good and does
553 * not rely on a sysctl for this behavior
554 */
555 nhi = rcu_dereference(nhge->nh->nh_info);
556 switch (nhi->family) {
557 case AF_INET:
558 if (ipv4_good_nh(&nhi->fib_nh))
559 return nhge->nh;
560 break;
561 case AF_INET6:
562 if (ipv6_good_nh(&nhi->fib6_nh))
563 return nhge->nh;
564 break;
565 }
566
567 if (!rc)
568 rc = nhge->nh;
569 }
570
571 return rc;
572}
573EXPORT_SYMBOL_GPL(nexthop_select_path);
574
David Ahernf88c9aa2019-06-08 14:53:22 -0700575int nexthop_for_each_fib6_nh(struct nexthop *nh,
576 int (*cb)(struct fib6_nh *nh, void *arg),
577 void *arg)
578{
579 struct nh_info *nhi;
580 int err;
581
582 if (nh->is_group) {
583 struct nh_group *nhg;
584 int i;
585
586 nhg = rcu_dereference_rtnl(nh->nh_grp);
587 for (i = 0; i < nhg->num_nh; i++) {
588 struct nh_grp_entry *nhge = &nhg->nh_entries[i];
589
590 nhi = rcu_dereference_rtnl(nhge->nh->nh_info);
591 err = cb(&nhi->fib6_nh, arg);
592 if (err)
593 return err;
594 }
595 } else {
596 nhi = rcu_dereference_rtnl(nh->nh_info);
597 err = cb(&nhi->fib6_nh, arg);
598 if (err)
599 return err;
600 }
601
602 return 0;
603}
604EXPORT_SYMBOL_GPL(nexthop_for_each_fib6_nh);
605
David Ahern7bf47962019-06-08 14:53:35 -0700606static int check_src_addr(const struct in6_addr *saddr,
607 struct netlink_ext_ack *extack)
608{
609 if (!ipv6_addr_any(saddr)) {
610 NL_SET_ERR_MSG(extack, "IPv6 routes using source address can not use nexthop objects");
611 return -EINVAL;
612 }
613 return 0;
614}
615
David Ahernf88d8ea2019-06-03 20:19:52 -0700616int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
617 struct netlink_ext_ack *extack)
618{
619 struct nh_info *nhi;
620
Roopa Prabhu38428d62020-05-21 22:26:13 -0700621 if (nh->is_fdb_nh) {
622 NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
623 return -EINVAL;
624 }
625
David Ahernf88d8ea2019-06-03 20:19:52 -0700626 /* fib6_src is unique to a fib6_info and limits the ability to cache
627 * routes in fib6_nh within a nexthop that is potentially shared
628 * across multiple fib entries. If the config wants to use source
629 * routing it can not use nexthop objects. mlxsw also does not allow
630 * fib6_src on routes.
631 */
David Ahern7bf47962019-06-08 14:53:35 -0700632 if (cfg && check_src_addr(&cfg->fc_src, extack) < 0)
David Ahernf88d8ea2019-06-03 20:19:52 -0700633 return -EINVAL;
David Ahernf88d8ea2019-06-03 20:19:52 -0700634
635 if (nh->is_group) {
636 struct nh_group *nhg;
637
638 nhg = rtnl_dereference(nh->nh_grp);
639 if (nhg->has_v4)
640 goto no_v4_nh;
641 } else {
642 nhi = rtnl_dereference(nh->nh_info);
643 if (nhi->family == AF_INET)
644 goto no_v4_nh;
645 }
646
647 return 0;
648no_v4_nh:
649 NL_SET_ERR_MSG(extack, "IPv6 routes can not use an IPv4 nexthop");
650 return -EINVAL;
651}
652EXPORT_SYMBOL_GPL(fib6_check_nexthop);
653
David Ahern7bf47962019-06-08 14:53:35 -0700654/* if existing nexthop has ipv6 routes linked to it, need
655 * to verify this new spec works with ipv6
656 */
657static int fib6_check_nh_list(struct nexthop *old, struct nexthop *new,
658 struct netlink_ext_ack *extack)
659{
660 struct fib6_info *f6i;
661
662 if (list_empty(&old->f6i_list))
663 return 0;
664
665 list_for_each_entry(f6i, &old->f6i_list, nh_list) {
666 if (check_src_addr(&f6i->fib6_src.addr, extack) < 0)
667 return -EINVAL;
668 }
669
670 return fib6_check_nexthop(new, NULL, extack);
671}
672
David Ahern4c7e8082019-06-03 20:19:51 -0700673static int nexthop_check_scope(struct nexthop *nh, u8 scope,
674 struct netlink_ext_ack *extack)
675{
676 struct nh_info *nhi;
677
678 nhi = rtnl_dereference(nh->nh_info);
679 if (scope == RT_SCOPE_HOST && nhi->fib_nhc.nhc_gw_family) {
680 NL_SET_ERR_MSG(extack,
681 "Route with host scope can not have a gateway");
682 return -EINVAL;
683 }
684
685 if (nhi->fib_nhc.nhc_flags & RTNH_F_ONLINK && scope >= RT_SCOPE_LINK) {
686 NL_SET_ERR_MSG(extack, "Scope mismatch with nexthop");
687 return -EINVAL;
688 }
689
690 return 0;
691}
692
693/* Invoked by fib add code to verify nexthop by id is ok with
694 * config for prefix; parts of fib_check_nh not done when nexthop
695 * object is used.
696 */
697int fib_check_nexthop(struct nexthop *nh, u8 scope,
698 struct netlink_ext_ack *extack)
699{
700 int err = 0;
701
Roopa Prabhu38428d62020-05-21 22:26:13 -0700702 if (nh->is_fdb_nh) {
703 NL_SET_ERR_MSG(extack, "Route cannot point to a fdb nexthop");
704 err = -EINVAL;
705 goto out;
706 }
707
David Ahern4c7e8082019-06-03 20:19:51 -0700708 if (nh->is_group) {
709 struct nh_group *nhg;
710
711 if (scope == RT_SCOPE_HOST) {
712 NL_SET_ERR_MSG(extack, "Route with host scope can not have multiple nexthops");
713 err = -EINVAL;
714 goto out;
715 }
716
717 nhg = rtnl_dereference(nh->nh_grp);
718 /* all nexthops in a group have the same scope */
719 err = nexthop_check_scope(nhg->nh_entries[0].nh, scope, extack);
720 } else {
721 err = nexthop_check_scope(nh, scope, extack);
722 }
723out:
724 return err;
725}
726
David Ahern7bf47962019-06-08 14:53:35 -0700727static int fib_check_nh_list(struct nexthop *old, struct nexthop *new,
728 struct netlink_ext_ack *extack)
729{
730 struct fib_info *fi;
731
732 list_for_each_entry(fi, &old->fi_list, nh_list) {
733 int err;
734
735 err = fib_check_nexthop(new, fi->fib_scope, extack);
736 if (err)
737 return err;
738 }
739 return 0;
740}
741
David Ahern430a0492019-05-24 14:43:08 -0700742static void nh_group_rebalance(struct nh_group *nhg)
743{
744 int total = 0;
745 int w = 0;
746 int i;
747
748 for (i = 0; i < nhg->num_nh; ++i)
749 total += nhg->nh_entries[i].weight;
750
751 for (i = 0; i < nhg->num_nh; ++i) {
752 struct nh_grp_entry *nhge = &nhg->nh_entries[i];
753 int upper_bound;
754
755 w += nhge->weight;
756 upper_bound = DIV_ROUND_CLOSEST_ULL((u64)w << 31, total) - 1;
757 atomic_set(&nhge->upper_bound, upper_bound);
758 }
759}
760
761static void remove_nh_grp_entry(struct nh_grp_entry *nhge,
762 struct nh_group *nhg,
763 struct nl_info *nlinfo)
764{
765 struct nexthop *nh = nhge->nh;
766 struct nh_grp_entry *nhges;
767 bool found = false;
768 int i;
769
770 WARN_ON(!nh);
771
772 nhges = nhg->nh_entries;
773 for (i = 0; i < nhg->num_nh; ++i) {
774 if (found) {
775 nhges[i-1].nh = nhges[i].nh;
776 nhges[i-1].weight = nhges[i].weight;
777 list_del(&nhges[i].nh_list);
778 list_add(&nhges[i-1].nh_list, &nhges[i-1].nh->grp_list);
779 } else if (nhg->nh_entries[i].nh == nh) {
780 found = true;
781 }
782 }
783
784 if (WARN_ON(!found))
785 return;
786
787 nhg->num_nh--;
788 nhg->nh_entries[nhg->num_nh].nh = NULL;
789
790 nh_group_rebalance(nhg);
791
792 nexthop_put(nh);
793
794 if (nlinfo)
795 nexthop_notify(RTM_NEWNEXTHOP, nhge->nh_parent, nlinfo);
796}
797
798static void remove_nexthop_from_groups(struct net *net, struct nexthop *nh,
799 struct nl_info *nlinfo)
800{
801 struct nh_grp_entry *nhge, *tmp;
802
803 list_for_each_entry_safe(nhge, tmp, &nh->grp_list, nh_list) {
804 struct nh_group *nhg;
805
806 list_del(&nhge->nh_list);
807 nhg = rtnl_dereference(nhge->nh_parent->nh_grp);
808 remove_nh_grp_entry(nhge, nhg, nlinfo);
809
810 /* if this group has no more entries then remove it */
811 if (!nhg->num_nh)
812 remove_nexthop(net, nhge->nh_parent, nlinfo);
813 }
814}
815
816static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
817{
818 struct nh_group *nhg = rcu_dereference_rtnl(nh->nh_grp);
819 int i, num_nh = nhg->num_nh;
820
821 for (i = 0; i < num_nh; ++i) {
822 struct nh_grp_entry *nhge = &nhg->nh_entries[i];
823
824 if (WARN_ON(!nhge->nh))
825 continue;
826
827 list_del(&nhge->nh_list);
828 nexthop_put(nhge->nh);
829 nhge->nh = NULL;
830 nhg->num_nh--;
831 }
832}
833
David Ahern7bf47962019-06-08 14:53:35 -0700834/* not called for nexthop replace */
David Ahern4c7e8082019-06-03 20:19:51 -0700835static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
836{
David Ahernf88d8ea2019-06-03 20:19:52 -0700837 struct fib6_info *f6i, *tmp;
David Ahern4c7e8082019-06-03 20:19:51 -0700838 bool do_flush = false;
839 struct fib_info *fi;
840
Roopa Prabhu8590ceed2020-05-21 22:26:15 -0700841 call_nexthop_notifiers(net, NEXTHOP_EVENT_DEL, nh);
842
David Ahern4c7e8082019-06-03 20:19:51 -0700843 list_for_each_entry(fi, &nh->fi_list, nh_list) {
844 fi->fib_flags |= RTNH_F_DEAD;
845 do_flush = true;
846 }
847 if (do_flush)
848 fib_flush(net);
David Ahernf88d8ea2019-06-03 20:19:52 -0700849
850 /* ip6_del_rt removes the entry from this list hence the _safe */
851 list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
852 /* __ip6_del_rt does a release, so do a hold here */
853 fib6_info_hold(f6i);
Roopa Prabhu4f801162020-04-27 13:56:46 -0700854 ipv6_stub->ip6_del_rt(net, f6i,
855 !net->ipv4.sysctl_nexthop_compat_mode);
David Ahernf88d8ea2019-06-03 20:19:52 -0700856 }
David Ahern4c7e8082019-06-03 20:19:51 -0700857}
858
David Ahern430a0492019-05-24 14:43:08 -0700859static void __remove_nexthop(struct net *net, struct nexthop *nh,
860 struct nl_info *nlinfo)
861{
David Ahern4c7e8082019-06-03 20:19:51 -0700862 __remove_nexthop_fib(net, nh);
863
David Ahern430a0492019-05-24 14:43:08 -0700864 if (nh->is_group) {
865 remove_nexthop_group(nh, nlinfo);
866 } else {
867 struct nh_info *nhi;
868
869 nhi = rtnl_dereference(nh->nh_info);
870 if (nhi->fib_nhc.nhc_dev)
871 hlist_del(&nhi->dev_hash);
872
873 remove_nexthop_from_groups(net, nh, nlinfo);
874 }
David Ahern597cfe4f2019-05-24 14:43:05 -0700875}
876
David Ahernab84be72019-05-24 14:43:04 -0700877static void remove_nexthop(struct net *net, struct nexthop *nh,
David Ahern430a0492019-05-24 14:43:08 -0700878 struct nl_info *nlinfo)
David Ahernab84be72019-05-24 14:43:04 -0700879{
880 /* remove from the tree */
881 rb_erase(&nh->rb_node, &net->nexthop.rb_root);
882
883 if (nlinfo)
884 nexthop_notify(RTM_DELNEXTHOP, nh, nlinfo);
885
David Ahern430a0492019-05-24 14:43:08 -0700886 __remove_nexthop(net, nh, nlinfo);
David Ahernab84be72019-05-24 14:43:04 -0700887 nh_base_seq_inc(net);
888
889 nexthop_put(nh);
890}
891
David Ahern7bf47962019-06-08 14:53:35 -0700892/* if any FIB entries reference this nexthop, any dst entries
893 * need to be regenerated
894 */
895static void nh_rt_cache_flush(struct net *net, struct nexthop *nh)
896{
897 struct fib6_info *f6i;
898
899 if (!list_empty(&nh->fi_list))
900 rt_cache_flush(net);
901
902 list_for_each_entry(f6i, &nh->f6i_list, nh_list)
903 ipv6_stub->fib6_update_sernum(net, f6i);
904}
905
906static int replace_nexthop_grp(struct net *net, struct nexthop *old,
907 struct nexthop *new,
908 struct netlink_ext_ack *extack)
909{
910 struct nh_group *oldg, *newg;
911 int i;
912
913 if (!new->is_group) {
914 NL_SET_ERR_MSG(extack, "Can not replace a nexthop group with a nexthop.");
915 return -EINVAL;
916 }
917
918 oldg = rtnl_dereference(old->nh_grp);
919 newg = rtnl_dereference(new->nh_grp);
920
921 /* update parents - used by nexthop code for cleanup */
922 for (i = 0; i < newg->num_nh; i++)
923 newg->nh_entries[i].nh_parent = old;
924
925 rcu_assign_pointer(old->nh_grp, newg);
926
927 for (i = 0; i < oldg->num_nh; i++)
928 oldg->nh_entries[i].nh_parent = new;
929
930 rcu_assign_pointer(new->nh_grp, oldg);
931
932 return 0;
933}
934
935static int replace_nexthop_single(struct net *net, struct nexthop *old,
936 struct nexthop *new,
937 struct netlink_ext_ack *extack)
938{
939 struct nh_info *oldi, *newi;
940
941 if (new->is_group) {
942 NL_SET_ERR_MSG(extack, "Can not replace a nexthop with a nexthop group.");
943 return -EINVAL;
944 }
945
946 oldi = rtnl_dereference(old->nh_info);
947 newi = rtnl_dereference(new->nh_info);
948
949 newi->nh_parent = old;
950 oldi->nh_parent = new;
951
952 old->protocol = new->protocol;
953 old->nh_flags = new->nh_flags;
954
955 rcu_assign_pointer(old->nh_info, newi);
956 rcu_assign_pointer(new->nh_info, oldi);
957
958 return 0;
959}
960
961static void __nexthop_replace_notify(struct net *net, struct nexthop *nh,
962 struct nl_info *info)
963{
964 struct fib6_info *f6i;
965
966 if (!list_empty(&nh->fi_list)) {
967 struct fib_info *fi;
968
969 /* expectation is a few fib_info per nexthop and then
970 * a lot of routes per fib_info. So mark the fib_info
971 * and then walk the fib tables once
972 */
973 list_for_each_entry(fi, &nh->fi_list, nh_list)
974 fi->nh_updated = true;
975
976 fib_info_notify_update(net, info);
977
978 list_for_each_entry(fi, &nh->fi_list, nh_list)
979 fi->nh_updated = false;
980 }
981
982 list_for_each_entry(f6i, &nh->f6i_list, nh_list)
983 ipv6_stub->fib6_rt_update(net, f6i, info);
984}
985
986/* send RTM_NEWROUTE with REPLACE flag set for all FIB entries
987 * linked to this nexthop and for all groups that the nexthop
988 * is a member of
989 */
990static void nexthop_replace_notify(struct net *net, struct nexthop *nh,
991 struct nl_info *info)
992{
993 struct nh_grp_entry *nhge;
994
995 __nexthop_replace_notify(net, nh, info);
996
997 list_for_each_entry(nhge, &nh->grp_list, nh_list)
998 __nexthop_replace_notify(net, nhge->nh_parent, info);
999}
1000
David Ahernab84be72019-05-24 14:43:04 -07001001static int replace_nexthop(struct net *net, struct nexthop *old,
1002 struct nexthop *new, struct netlink_ext_ack *extack)
1003{
David Ahern7bf47962019-06-08 14:53:35 -07001004 bool new_is_reject = false;
1005 struct nh_grp_entry *nhge;
1006 int err;
1007
1008 /* check that existing FIB entries are ok with the
1009 * new nexthop definition
1010 */
1011 err = fib_check_nh_list(old, new, extack);
1012 if (err)
1013 return err;
1014
1015 err = fib6_check_nh_list(old, new, extack);
1016 if (err)
1017 return err;
1018
1019 if (!new->is_group) {
1020 struct nh_info *nhi = rtnl_dereference(new->nh_info);
1021
1022 new_is_reject = nhi->reject_nh;
1023 }
1024
1025 list_for_each_entry(nhge, &old->grp_list, nh_list) {
1026 /* if new nexthop is a blackhole, any groups using this
1027 * nexthop cannot have more than 1 path
1028 */
1029 if (new_is_reject &&
1030 nexthop_num_path(nhge->nh_parent) > 1) {
1031 NL_SET_ERR_MSG(extack, "Blackhole nexthop can not be a member of a group with more than one path");
1032 return -EINVAL;
1033 }
1034
1035 err = fib_check_nh_list(nhge->nh_parent, new, extack);
1036 if (err)
1037 return err;
1038
1039 err = fib6_check_nh_list(nhge->nh_parent, new, extack);
1040 if (err)
1041 return err;
1042 }
1043
1044 if (old->is_group)
1045 err = replace_nexthop_grp(net, old, new, extack);
1046 else
1047 err = replace_nexthop_single(net, old, new, extack);
1048
1049 if (!err) {
1050 nh_rt_cache_flush(net, old);
1051
1052 __remove_nexthop(net, new, NULL);
1053 nexthop_put(new);
1054 }
1055
1056 return err;
David Ahernab84be72019-05-24 14:43:04 -07001057}
1058
1059/* called with rtnl_lock held */
1060static int insert_nexthop(struct net *net, struct nexthop *new_nh,
1061 struct nh_config *cfg, struct netlink_ext_ack *extack)
1062{
1063 struct rb_node **pp, *parent = NULL, *next;
1064 struct rb_root *root = &net->nexthop.rb_root;
1065 bool replace = !!(cfg->nlflags & NLM_F_REPLACE);
1066 bool create = !!(cfg->nlflags & NLM_F_CREATE);
1067 u32 new_id = new_nh->id;
David Ahern7bf47962019-06-08 14:53:35 -07001068 int replace_notify = 0;
David Ahernab84be72019-05-24 14:43:04 -07001069 int rc = -EEXIST;
1070
1071 pp = &root->rb_node;
1072 while (1) {
1073 struct nexthop *nh;
1074
1075 next = rtnl_dereference(*pp);
1076 if (!next)
1077 break;
1078
1079 parent = next;
1080
1081 nh = rb_entry(parent, struct nexthop, rb_node);
1082 if (new_id < nh->id) {
1083 pp = &next->rb_left;
1084 } else if (new_id > nh->id) {
1085 pp = &next->rb_right;
1086 } else if (replace) {
1087 rc = replace_nexthop(net, nh, new_nh, extack);
David Ahern7bf47962019-06-08 14:53:35 -07001088 if (!rc) {
David Ahernab84be72019-05-24 14:43:04 -07001089 new_nh = nh; /* send notification with old nh */
David Ahern7bf47962019-06-08 14:53:35 -07001090 replace_notify = 1;
1091 }
David Ahernab84be72019-05-24 14:43:04 -07001092 goto out;
1093 } else {
1094 /* id already exists and not a replace */
1095 goto out;
1096 }
1097 }
1098
1099 if (replace && !create) {
1100 NL_SET_ERR_MSG(extack, "Replace specified without create and no entry exists");
1101 rc = -ENOENT;
1102 goto out;
1103 }
1104
1105 rb_link_node_rcu(&new_nh->rb_node, parent, pp);
1106 rb_insert_color(&new_nh->rb_node, root);
1107 rc = 0;
1108out:
1109 if (!rc) {
1110 nh_base_seq_inc(net);
1111 nexthop_notify(RTM_NEWNEXTHOP, new_nh, &cfg->nlinfo);
Roopa Prabhu4f801162020-04-27 13:56:46 -07001112 if (replace_notify && net->ipv4.sysctl_nexthop_compat_mode)
David Ahern7bf47962019-06-08 14:53:35 -07001113 nexthop_replace_notify(net, new_nh, &cfg->nlinfo);
David Ahernab84be72019-05-24 14:43:04 -07001114 }
1115
1116 return rc;
1117}
1118
David Ahern597cfe4f2019-05-24 14:43:05 -07001119/* rtnl */
1120/* remove all nexthops tied to a device being deleted */
1121static void nexthop_flush_dev(struct net_device *dev)
1122{
1123 unsigned int hash = nh_dev_hashfn(dev->ifindex);
1124 struct net *net = dev_net(dev);
1125 struct hlist_head *head = &net->nexthop.devhash[hash];
1126 struct hlist_node *n;
1127 struct nh_info *nhi;
1128
1129 hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
1130 if (nhi->fib_nhc.nhc_dev != dev)
1131 continue;
1132
David Ahern430a0492019-05-24 14:43:08 -07001133 remove_nexthop(net, nhi->nh_parent, NULL);
David Ahern597cfe4f2019-05-24 14:43:05 -07001134 }
1135}
1136
David Ahernab84be72019-05-24 14:43:04 -07001137/* rtnl; called when net namespace is deleted */
1138static void flush_all_nexthops(struct net *net)
1139{
1140 struct rb_root *root = &net->nexthop.rb_root;
1141 struct rb_node *node;
1142 struct nexthop *nh;
1143
1144 while ((node = rb_first(root))) {
1145 nh = rb_entry(node, struct nexthop, rb_node);
David Ahern430a0492019-05-24 14:43:08 -07001146 remove_nexthop(net, nh, NULL);
David Ahernab84be72019-05-24 14:43:04 -07001147 cond_resched();
1148 }
1149}
1150
David Ahern430a0492019-05-24 14:43:08 -07001151static struct nexthop *nexthop_create_group(struct net *net,
1152 struct nh_config *cfg)
1153{
1154 struct nlattr *grps_attr = cfg->nh_grp;
1155 struct nexthop_grp *entry = nla_data(grps_attr);
1156 struct nh_group *nhg;
1157 struct nexthop *nh;
1158 int i;
1159
1160 nh = nexthop_alloc();
1161 if (!nh)
1162 return ERR_PTR(-ENOMEM);
1163
1164 nh->is_group = 1;
1165
1166 nhg = nexthop_grp_alloc(nla_len(grps_attr) / sizeof(*entry));
1167 if (!nhg) {
1168 kfree(nh);
1169 return ERR_PTR(-ENOMEM);
1170 }
1171
1172 for (i = 0; i < nhg->num_nh; ++i) {
1173 struct nexthop *nhe;
1174 struct nh_info *nhi;
1175
1176 nhe = nexthop_find_by_id(net, entry[i].id);
1177 if (!nexthop_get(nhe))
1178 goto out_no_nh;
1179
1180 nhi = rtnl_dereference(nhe->nh_info);
1181 if (nhi->family == AF_INET)
1182 nhg->has_v4 = true;
1183
1184 nhg->nh_entries[i].nh = nhe;
1185 nhg->nh_entries[i].weight = entry[i].weight + 1;
1186 list_add(&nhg->nh_entries[i].nh_list, &nhe->grp_list);
1187 nhg->nh_entries[i].nh_parent = nh;
1188 }
1189
1190 if (cfg->nh_grp_type == NEXTHOP_GRP_TYPE_MPATH) {
1191 nhg->mpath = 1;
1192 nh_group_rebalance(nhg);
1193 }
1194
Roopa Prabhu38428d62020-05-21 22:26:13 -07001195 if (cfg->nh_fdb)
1196 nh->is_fdb_nh = 1;
1197
David Ahern430a0492019-05-24 14:43:08 -07001198 rcu_assign_pointer(nh->nh_grp, nhg);
1199
1200 return nh;
1201
1202out_no_nh:
1203 for (; i >= 0; --i)
1204 nexthop_put(nhg->nh_entries[i].nh);
1205
1206 kfree(nhg);
1207 kfree(nh);
1208
1209 return ERR_PTR(-ENOENT);
1210}
1211
David Ahern597cfe4f2019-05-24 14:43:05 -07001212static int nh_create_ipv4(struct net *net, struct nexthop *nh,
1213 struct nh_info *nhi, struct nh_config *cfg,
1214 struct netlink_ext_ack *extack)
1215{
1216 struct fib_nh *fib_nh = &nhi->fib_nh;
1217 struct fib_config fib_cfg = {
1218 .fc_oif = cfg->nh_ifindex,
1219 .fc_gw4 = cfg->gw.ipv4,
1220 .fc_gw_family = cfg->gw.ipv4 ? AF_INET : 0,
1221 .fc_flags = cfg->nh_flags,
David Ahernb513bd02019-05-24 14:43:07 -07001222 .fc_encap = cfg->nh_encap,
1223 .fc_encap_type = cfg->nh_encap_type,
David Ahern597cfe4f2019-05-24 14:43:05 -07001224 };
Roopa Prabhu38428d62020-05-21 22:26:13 -07001225 u32 tb_id = (cfg->dev ? l3mdev_fib_table(cfg->dev) : RT_TABLE_MAIN);
Colin Ian Kingc76c9922019-08-22 13:53:40 +01001226 int err;
David Ahern597cfe4f2019-05-24 14:43:05 -07001227
1228 err = fib_nh_init(net, fib_nh, &fib_cfg, 1, extack);
1229 if (err) {
1230 fib_nh_release(net, fib_nh);
1231 goto out;
1232 }
1233
Roopa Prabhu38428d62020-05-21 22:26:13 -07001234 if (nh->is_fdb_nh)
1235 goto out;
1236
David Ahern597cfe4f2019-05-24 14:43:05 -07001237 /* sets nh_dev if successful */
1238 err = fib_check_nh(net, fib_nh, tb_id, 0, extack);
1239 if (!err) {
1240 nh->nh_flags = fib_nh->fib_nh_flags;
David Aherndcb1ecb2019-06-03 20:19:50 -07001241 fib_info_update_nhc_saddr(net, &fib_nh->nh_common,
1242 fib_nh->fib_nh_scope);
David Ahern597cfe4f2019-05-24 14:43:05 -07001243 } else {
1244 fib_nh_release(net, fib_nh);
1245 }
1246out:
1247 return err;
1248}
1249
David Ahern53010f92019-05-24 14:43:06 -07001250static int nh_create_ipv6(struct net *net, struct nexthop *nh,
1251 struct nh_info *nhi, struct nh_config *cfg,
1252 struct netlink_ext_ack *extack)
1253{
1254 struct fib6_nh *fib6_nh = &nhi->fib6_nh;
1255 struct fib6_config fib6_cfg = {
1256 .fc_table = l3mdev_fib_table(cfg->dev),
1257 .fc_ifindex = cfg->nh_ifindex,
1258 .fc_gateway = cfg->gw.ipv6,
1259 .fc_flags = cfg->nh_flags,
David Ahernb513bd02019-05-24 14:43:07 -07001260 .fc_encap = cfg->nh_encap,
1261 .fc_encap_type = cfg->nh_encap_type,
Roopa Prabhu38428d62020-05-21 22:26:13 -07001262 .fc_is_fdb = cfg->nh_fdb,
David Ahern53010f92019-05-24 14:43:06 -07001263 };
Colin Ian King6f43e522019-05-30 16:57:54 +01001264 int err;
David Ahern53010f92019-05-24 14:43:06 -07001265
1266 if (!ipv6_addr_any(&cfg->gw.ipv6))
1267 fib6_cfg.fc_flags |= RTF_GATEWAY;
1268
1269 /* sets nh_dev if successful */
1270 err = ipv6_stub->fib6_nh_init(net, fib6_nh, &fib6_cfg, GFP_KERNEL,
1271 extack);
1272 if (err)
1273 ipv6_stub->fib6_nh_release(fib6_nh);
1274 else
1275 nh->nh_flags = fib6_nh->fib_nh_flags;
1276
1277 return err;
1278}
1279
David Ahernab84be72019-05-24 14:43:04 -07001280static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg,
1281 struct netlink_ext_ack *extack)
1282{
1283 struct nh_info *nhi;
1284 struct nexthop *nh;
1285 int err = 0;
1286
1287 nh = nexthop_alloc();
1288 if (!nh)
1289 return ERR_PTR(-ENOMEM);
1290
1291 nhi = kzalloc(sizeof(*nhi), GFP_KERNEL);
1292 if (!nhi) {
1293 kfree(nh);
1294 return ERR_PTR(-ENOMEM);
1295 }
1296
1297 nh->nh_flags = cfg->nh_flags;
1298 nh->net = net;
1299
1300 nhi->nh_parent = nh;
1301 nhi->family = cfg->nh_family;
1302 nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK;
1303
Roopa Prabhu38428d62020-05-21 22:26:13 -07001304 if (cfg->nh_fdb)
1305 nh->is_fdb_nh = 1;
1306
David Ahernab84be72019-05-24 14:43:04 -07001307 if (cfg->nh_blackhole) {
1308 nhi->reject_nh = 1;
1309 cfg->nh_ifindex = net->loopback_dev->ifindex;
1310 }
1311
David Ahern597cfe4f2019-05-24 14:43:05 -07001312 switch (cfg->nh_family) {
1313 case AF_INET:
1314 err = nh_create_ipv4(net, nh, nhi, cfg, extack);
1315 break;
David Ahern53010f92019-05-24 14:43:06 -07001316 case AF_INET6:
1317 err = nh_create_ipv6(net, nh, nhi, cfg, extack);
1318 break;
David Ahern597cfe4f2019-05-24 14:43:05 -07001319 }
1320
David Ahernab84be72019-05-24 14:43:04 -07001321 if (err) {
1322 kfree(nhi);
1323 kfree(nh);
1324 return ERR_PTR(err);
1325 }
1326
David Ahern597cfe4f2019-05-24 14:43:05 -07001327 /* add the entry to the device based hash */
Roopa Prabhu38428d62020-05-21 22:26:13 -07001328 if (!nh->is_fdb_nh)
1329 nexthop_devhash_add(net, nhi);
David Ahern597cfe4f2019-05-24 14:43:05 -07001330
David Ahernab84be72019-05-24 14:43:04 -07001331 rcu_assign_pointer(nh->nh_info, nhi);
1332
1333 return nh;
1334}
1335
1336/* called with rtnl lock held */
1337static struct nexthop *nexthop_add(struct net *net, struct nh_config *cfg,
1338 struct netlink_ext_ack *extack)
1339{
1340 struct nexthop *nh;
1341 int err;
1342
1343 if (cfg->nlflags & NLM_F_REPLACE && !cfg->nh_id) {
1344 NL_SET_ERR_MSG(extack, "Replace requires nexthop id");
1345 return ERR_PTR(-EINVAL);
1346 }
1347
1348 if (!cfg->nh_id) {
1349 cfg->nh_id = nh_find_unused_id(net);
1350 if (!cfg->nh_id) {
1351 NL_SET_ERR_MSG(extack, "No unused id");
1352 return ERR_PTR(-EINVAL);
1353 }
1354 }
1355
David Ahern430a0492019-05-24 14:43:08 -07001356 if (cfg->nh_grp)
1357 nh = nexthop_create_group(net, cfg);
1358 else
1359 nh = nexthop_create(net, cfg, extack);
1360
David Ahernab84be72019-05-24 14:43:04 -07001361 if (IS_ERR(nh))
1362 return nh;
1363
1364 refcount_set(&nh->refcnt, 1);
1365 nh->id = cfg->nh_id;
1366 nh->protocol = cfg->nh_protocol;
1367 nh->net = net;
1368
1369 err = insert_nexthop(net, nh, cfg, extack);
1370 if (err) {
David Ahern430a0492019-05-24 14:43:08 -07001371 __remove_nexthop(net, nh, NULL);
David Ahernab84be72019-05-24 14:43:04 -07001372 nexthop_put(nh);
1373 nh = ERR_PTR(err);
1374 }
1375
1376 return nh;
1377}
1378
1379static int rtm_to_nh_config(struct net *net, struct sk_buff *skb,
1380 struct nlmsghdr *nlh, struct nh_config *cfg,
1381 struct netlink_ext_ack *extack)
1382{
1383 struct nhmsg *nhm = nlmsg_data(nlh);
1384 struct nlattr *tb[NHA_MAX + 1];
1385 int err;
1386
1387 err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1388 extack);
1389 if (err < 0)
1390 return err;
1391
1392 err = -EINVAL;
1393 if (nhm->resvd || nhm->nh_scope) {
1394 NL_SET_ERR_MSG(extack, "Invalid values in ancillary header");
1395 goto out;
1396 }
1397 if (nhm->nh_flags & ~NEXTHOP_VALID_USER_FLAGS) {
1398 NL_SET_ERR_MSG(extack, "Invalid nexthop flags in ancillary header");
1399 goto out;
1400 }
1401
1402 switch (nhm->nh_family) {
David Ahern597cfe4f2019-05-24 14:43:05 -07001403 case AF_INET:
David Ahern53010f92019-05-24 14:43:06 -07001404 case AF_INET6:
David Ahern597cfe4f2019-05-24 14:43:05 -07001405 break;
David Ahern430a0492019-05-24 14:43:08 -07001406 case AF_UNSPEC:
1407 if (tb[NHA_GROUP])
1408 break;
Joe Perchesa8eceea2020-03-12 15:50:22 -07001409 fallthrough;
David Ahernab84be72019-05-24 14:43:04 -07001410 default:
1411 NL_SET_ERR_MSG(extack, "Invalid address family");
1412 goto out;
1413 }
1414
1415 if (tb[NHA_GROUPS] || tb[NHA_MASTER]) {
1416 NL_SET_ERR_MSG(extack, "Invalid attributes in request");
1417 goto out;
1418 }
1419
1420 memset(cfg, 0, sizeof(*cfg));
1421 cfg->nlflags = nlh->nlmsg_flags;
1422 cfg->nlinfo.portid = NETLINK_CB(skb).portid;
1423 cfg->nlinfo.nlh = nlh;
1424 cfg->nlinfo.nl_net = net;
1425
1426 cfg->nh_family = nhm->nh_family;
1427 cfg->nh_protocol = nhm->nh_protocol;
1428 cfg->nh_flags = nhm->nh_flags;
1429
1430 if (tb[NHA_ID])
1431 cfg->nh_id = nla_get_u32(tb[NHA_ID]);
1432
Roopa Prabhu38428d62020-05-21 22:26:13 -07001433 if (tb[NHA_FDB]) {
1434 if (tb[NHA_OIF] || tb[NHA_BLACKHOLE] ||
1435 tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE]) {
1436 NL_SET_ERR_MSG(extack, "Fdb attribute can not be used with encap, oif or blackhole");
1437 goto out;
1438 }
1439 if (nhm->nh_flags) {
1440 NL_SET_ERR_MSG(extack, "Unsupported nexthop flags in ancillary header");
1441 goto out;
1442 }
1443 cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]);
1444 }
1445
David Ahern430a0492019-05-24 14:43:08 -07001446 if (tb[NHA_GROUP]) {
1447 if (nhm->nh_family != AF_UNSPEC) {
1448 NL_SET_ERR_MSG(extack, "Invalid family for group");
1449 goto out;
1450 }
1451 cfg->nh_grp = tb[NHA_GROUP];
1452
1453 cfg->nh_grp_type = NEXTHOP_GRP_TYPE_MPATH;
1454 if (tb[NHA_GROUP_TYPE])
1455 cfg->nh_grp_type = nla_get_u16(tb[NHA_GROUP_TYPE]);
1456
1457 if (cfg->nh_grp_type > NEXTHOP_GRP_TYPE_MAX) {
1458 NL_SET_ERR_MSG(extack, "Invalid group type");
1459 goto out;
1460 }
1461 err = nh_check_attr_group(net, tb, extack);
1462
1463 /* no other attributes should be set */
1464 goto out;
1465 }
1466
David Ahernab84be72019-05-24 14:43:04 -07001467 if (tb[NHA_BLACKHOLE]) {
David Ahernb513bd02019-05-24 14:43:07 -07001468 if (tb[NHA_GATEWAY] || tb[NHA_OIF] ||
Roopa Prabhu38428d62020-05-21 22:26:13 -07001469 tb[NHA_ENCAP] || tb[NHA_ENCAP_TYPE] || tb[NHA_FDB]) {
1470 NL_SET_ERR_MSG(extack, "Blackhole attribute can not be used with gateway, oif, encap or fdb");
David Ahernab84be72019-05-24 14:43:04 -07001471 goto out;
1472 }
1473
1474 cfg->nh_blackhole = 1;
1475 err = 0;
1476 goto out;
1477 }
1478
Roopa Prabhu38428d62020-05-21 22:26:13 -07001479 if (!cfg->nh_fdb && !tb[NHA_OIF]) {
1480 NL_SET_ERR_MSG(extack, "Device attribute required for non-blackhole and non-fdb nexthops");
David Ahernab84be72019-05-24 14:43:04 -07001481 goto out;
1482 }
1483
Roopa Prabhu38428d62020-05-21 22:26:13 -07001484 if (!cfg->nh_fdb && tb[NHA_OIF]) {
1485 cfg->nh_ifindex = nla_get_u32(tb[NHA_OIF]);
1486 if (cfg->nh_ifindex)
1487 cfg->dev = __dev_get_by_index(net, cfg->nh_ifindex);
David Ahernab84be72019-05-24 14:43:04 -07001488
Roopa Prabhu38428d62020-05-21 22:26:13 -07001489 if (!cfg->dev) {
1490 NL_SET_ERR_MSG(extack, "Invalid device index");
1491 goto out;
1492 } else if (!(cfg->dev->flags & IFF_UP)) {
1493 NL_SET_ERR_MSG(extack, "Nexthop device is not up");
1494 err = -ENETDOWN;
1495 goto out;
1496 } else if (!netif_carrier_ok(cfg->dev)) {
1497 NL_SET_ERR_MSG(extack, "Carrier for nexthop device is down");
1498 err = -ENETDOWN;
1499 goto out;
1500 }
David Ahernab84be72019-05-24 14:43:04 -07001501 }
1502
David Ahern597cfe4f2019-05-24 14:43:05 -07001503 err = -EINVAL;
1504 if (tb[NHA_GATEWAY]) {
1505 struct nlattr *gwa = tb[NHA_GATEWAY];
1506
1507 switch (cfg->nh_family) {
1508 case AF_INET:
1509 if (nla_len(gwa) != sizeof(u32)) {
1510 NL_SET_ERR_MSG(extack, "Invalid gateway");
1511 goto out;
1512 }
1513 cfg->gw.ipv4 = nla_get_be32(gwa);
1514 break;
David Ahern53010f92019-05-24 14:43:06 -07001515 case AF_INET6:
1516 if (nla_len(gwa) != sizeof(struct in6_addr)) {
1517 NL_SET_ERR_MSG(extack, "Invalid gateway");
1518 goto out;
1519 }
1520 cfg->gw.ipv6 = nla_get_in6_addr(gwa);
1521 break;
David Ahern597cfe4f2019-05-24 14:43:05 -07001522 default:
1523 NL_SET_ERR_MSG(extack,
1524 "Unknown address family for gateway");
1525 goto out;
1526 }
1527 } else {
1528 /* device only nexthop (no gateway) */
1529 if (cfg->nh_flags & RTNH_F_ONLINK) {
1530 NL_SET_ERR_MSG(extack,
1531 "ONLINK flag can not be set for nexthop without a gateway");
1532 goto out;
1533 }
1534 }
1535
David Ahernb513bd02019-05-24 14:43:07 -07001536 if (tb[NHA_ENCAP]) {
1537 cfg->nh_encap = tb[NHA_ENCAP];
1538
1539 if (!tb[NHA_ENCAP_TYPE]) {
1540 NL_SET_ERR_MSG(extack, "LWT encapsulation type is missing");
1541 goto out;
1542 }
1543
1544 cfg->nh_encap_type = nla_get_u16(tb[NHA_ENCAP_TYPE]);
1545 err = lwtunnel_valid_encap_type(cfg->nh_encap_type, extack);
1546 if (err < 0)
1547 goto out;
1548
1549 } else if (tb[NHA_ENCAP_TYPE]) {
1550 NL_SET_ERR_MSG(extack, "LWT encapsulation attribute is missing");
1551 goto out;
1552 }
1553
1554
David Ahernab84be72019-05-24 14:43:04 -07001555 err = 0;
1556out:
1557 return err;
1558}
1559
1560/* rtnl */
1561static int rtm_new_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1562 struct netlink_ext_ack *extack)
1563{
1564 struct net *net = sock_net(skb->sk);
1565 struct nh_config cfg;
1566 struct nexthop *nh;
1567 int err;
1568
1569 err = rtm_to_nh_config(net, skb, nlh, &cfg, extack);
1570 if (!err) {
1571 nh = nexthop_add(net, &cfg, extack);
1572 if (IS_ERR(nh))
1573 err = PTR_ERR(nh);
1574 }
1575
1576 return err;
1577}
1578
1579static int nh_valid_get_del_req(struct nlmsghdr *nlh, u32 *id,
1580 struct netlink_ext_ack *extack)
1581{
1582 struct nhmsg *nhm = nlmsg_data(nlh);
1583 struct nlattr *tb[NHA_MAX + 1];
1584 int err, i;
1585
1586 err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1587 extack);
1588 if (err < 0)
1589 return err;
1590
1591 err = -EINVAL;
1592 for (i = 0; i < __NHA_MAX; ++i) {
1593 if (!tb[i])
1594 continue;
1595
1596 switch (i) {
1597 case NHA_ID:
1598 break;
1599 default:
1600 NL_SET_ERR_MSG_ATTR(extack, tb[i],
1601 "Unexpected attribute in request");
1602 goto out;
1603 }
1604 }
1605 if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
1606 NL_SET_ERR_MSG(extack, "Invalid values in header");
1607 goto out;
1608 }
1609
1610 if (!tb[NHA_ID]) {
1611 NL_SET_ERR_MSG(extack, "Nexthop id is missing");
1612 goto out;
1613 }
1614
1615 *id = nla_get_u32(tb[NHA_ID]);
1616 if (!(*id))
1617 NL_SET_ERR_MSG(extack, "Invalid nexthop id");
1618 else
1619 err = 0;
1620out:
1621 return err;
1622}
1623
1624/* rtnl */
1625static int rtm_del_nexthop(struct sk_buff *skb, struct nlmsghdr *nlh,
1626 struct netlink_ext_ack *extack)
1627{
1628 struct net *net = sock_net(skb->sk);
1629 struct nl_info nlinfo = {
1630 .nlh = nlh,
1631 .nl_net = net,
1632 .portid = NETLINK_CB(skb).portid,
1633 };
1634 struct nexthop *nh;
1635 int err;
1636 u32 id;
1637
1638 err = nh_valid_get_del_req(nlh, &id, extack);
1639 if (err)
1640 return err;
1641
1642 nh = nexthop_find_by_id(net, id);
1643 if (!nh)
1644 return -ENOENT;
1645
David Ahern430a0492019-05-24 14:43:08 -07001646 remove_nexthop(net, nh, &nlinfo);
David Ahernab84be72019-05-24 14:43:04 -07001647
1648 return 0;
1649}
1650
1651/* rtnl */
1652static int rtm_get_nexthop(struct sk_buff *in_skb, struct nlmsghdr *nlh,
1653 struct netlink_ext_ack *extack)
1654{
1655 struct net *net = sock_net(in_skb->sk);
1656 struct sk_buff *skb = NULL;
1657 struct nexthop *nh;
1658 int err;
1659 u32 id;
1660
1661 err = nh_valid_get_del_req(nlh, &id, extack);
1662 if (err)
1663 return err;
1664
1665 err = -ENOBUFS;
1666 skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
1667 if (!skb)
1668 goto out;
1669
1670 err = -ENOENT;
1671 nh = nexthop_find_by_id(net, id);
1672 if (!nh)
1673 goto errout_free;
1674
1675 err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP, NETLINK_CB(in_skb).portid,
1676 nlh->nlmsg_seq, 0);
1677 if (err < 0) {
1678 WARN_ON(err == -EMSGSIZE);
1679 goto errout_free;
1680 }
1681
1682 err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
1683out:
1684 return err;
1685errout_free:
1686 kfree_skb(skb);
1687 goto out;
1688}
1689
David Ahern430a0492019-05-24 14:43:08 -07001690static bool nh_dump_filtered(struct nexthop *nh, int dev_idx, int master_idx,
1691 bool group_filter, u8 family)
David Ahernab84be72019-05-24 14:43:04 -07001692{
1693 const struct net_device *dev;
1694 const struct nh_info *nhi;
1695
David Ahern430a0492019-05-24 14:43:08 -07001696 if (group_filter && !nh->is_group)
1697 return true;
1698
David Ahernab84be72019-05-24 14:43:04 -07001699 if (!dev_idx && !master_idx && !family)
1700 return false;
1701
David Ahern430a0492019-05-24 14:43:08 -07001702 if (nh->is_group)
1703 return true;
1704
David Ahernab84be72019-05-24 14:43:04 -07001705 nhi = rtnl_dereference(nh->nh_info);
1706 if (family && nhi->family != family)
1707 return true;
1708
1709 dev = nhi->fib_nhc.nhc_dev;
1710 if (dev_idx && (!dev || dev->ifindex != dev_idx))
1711 return true;
1712
1713 if (master_idx) {
1714 struct net_device *master;
1715
1716 if (!dev)
1717 return true;
1718
1719 master = netdev_master_upper_dev_get((struct net_device *)dev);
1720 if (!master || master->ifindex != master_idx)
1721 return true;
1722 }
1723
1724 return false;
1725}
1726
David Ahern430a0492019-05-24 14:43:08 -07001727static int nh_valid_dump_req(const struct nlmsghdr *nlh, int *dev_idx,
1728 int *master_idx, bool *group_filter,
Roopa Prabhu38428d62020-05-21 22:26:13 -07001729 bool *fdb_filter, struct netlink_callback *cb)
David Ahernab84be72019-05-24 14:43:04 -07001730{
1731 struct netlink_ext_ack *extack = cb->extack;
1732 struct nlattr *tb[NHA_MAX + 1];
1733 struct nhmsg *nhm;
1734 int err, i;
1735 u32 idx;
1736
1737 err = nlmsg_parse(nlh, sizeof(*nhm), tb, NHA_MAX, rtm_nh_policy,
1738 NULL);
1739 if (err < 0)
1740 return err;
1741
1742 for (i = 0; i <= NHA_MAX; ++i) {
1743 if (!tb[i])
1744 continue;
1745
1746 switch (i) {
1747 case NHA_OIF:
1748 idx = nla_get_u32(tb[i]);
1749 if (idx > INT_MAX) {
1750 NL_SET_ERR_MSG(extack, "Invalid device index");
1751 return -EINVAL;
1752 }
1753 *dev_idx = idx;
1754 break;
1755 case NHA_MASTER:
1756 idx = nla_get_u32(tb[i]);
1757 if (idx > INT_MAX) {
1758 NL_SET_ERR_MSG(extack, "Invalid master device index");
1759 return -EINVAL;
1760 }
1761 *master_idx = idx;
1762 break;
David Ahern430a0492019-05-24 14:43:08 -07001763 case NHA_GROUPS:
1764 *group_filter = true;
1765 break;
Roopa Prabhu38428d62020-05-21 22:26:13 -07001766 case NHA_FDB:
1767 *fdb_filter = true;
1768 break;
David Ahernab84be72019-05-24 14:43:04 -07001769 default:
1770 NL_SET_ERR_MSG(extack, "Unsupported attribute in dump request");
1771 return -EINVAL;
1772 }
1773 }
1774
1775 nhm = nlmsg_data(nlh);
1776 if (nhm->nh_protocol || nhm->resvd || nhm->nh_scope || nhm->nh_flags) {
1777 NL_SET_ERR_MSG(extack, "Invalid values in header for nexthop dump request");
1778 return -EINVAL;
1779 }
1780
1781 return 0;
1782}
1783
1784/* rtnl */
1785static int rtm_dump_nexthop(struct sk_buff *skb, struct netlink_callback *cb)
1786{
Roopa Prabhu38428d62020-05-21 22:26:13 -07001787 bool group_filter = false, fdb_filter = false;
David Ahernab84be72019-05-24 14:43:04 -07001788 struct nhmsg *nhm = nlmsg_data(cb->nlh);
1789 int dev_filter_idx = 0, master_idx = 0;
1790 struct net *net = sock_net(skb->sk);
1791 struct rb_root *root = &net->nexthop.rb_root;
1792 struct rb_node *node;
1793 int idx = 0, s_idx;
1794 int err;
1795
David Ahern430a0492019-05-24 14:43:08 -07001796 err = nh_valid_dump_req(cb->nlh, &dev_filter_idx, &master_idx,
Roopa Prabhu38428d62020-05-21 22:26:13 -07001797 &group_filter, &fdb_filter, cb);
David Ahernab84be72019-05-24 14:43:04 -07001798 if (err < 0)
1799 return err;
1800
1801 s_idx = cb->args[0];
1802 for (node = rb_first(root); node; node = rb_next(node)) {
1803 struct nexthop *nh;
1804
1805 if (idx < s_idx)
1806 goto cont;
1807
1808 nh = rb_entry(node, struct nexthop, rb_node);
1809 if (nh_dump_filtered(nh, dev_filter_idx, master_idx,
David Ahern430a0492019-05-24 14:43:08 -07001810 group_filter, nhm->nh_family))
David Ahernab84be72019-05-24 14:43:04 -07001811 goto cont;
1812
1813 err = nh_fill_node(skb, nh, RTM_NEWNEXTHOP,
1814 NETLINK_CB(cb->skb).portid,
1815 cb->nlh->nlmsg_seq, NLM_F_MULTI);
1816 if (err < 0) {
1817 if (likely(skb->len))
1818 goto out;
1819
1820 goto out_err;
1821 }
1822cont:
1823 idx++;
1824 }
1825
1826out:
1827 err = skb->len;
1828out_err:
1829 cb->args[0] = idx;
1830 cb->seq = net->nexthop.seq;
1831 nl_dump_check_consistent(cb, nlmsg_hdr(skb));
1832
1833 return err;
1834}
1835
David Ahern597cfe4f2019-05-24 14:43:05 -07001836static void nexthop_sync_mtu(struct net_device *dev, u32 orig_mtu)
1837{
1838 unsigned int hash = nh_dev_hashfn(dev->ifindex);
1839 struct net *net = dev_net(dev);
1840 struct hlist_head *head = &net->nexthop.devhash[hash];
1841 struct hlist_node *n;
1842 struct nh_info *nhi;
1843
1844 hlist_for_each_entry_safe(nhi, n, head, dev_hash) {
1845 if (nhi->fib_nhc.nhc_dev == dev) {
1846 if (nhi->family == AF_INET)
1847 fib_nhc_update_mtu(&nhi->fib_nhc, dev->mtu,
1848 orig_mtu);
1849 }
1850 }
1851}
1852
1853/* rtnl */
1854static int nh_netdev_event(struct notifier_block *this,
1855 unsigned long event, void *ptr)
1856{
1857 struct net_device *dev = netdev_notifier_info_to_dev(ptr);
1858 struct netdev_notifier_info_ext *info_ext;
1859
1860 switch (event) {
1861 case NETDEV_DOWN:
1862 case NETDEV_UNREGISTER:
1863 nexthop_flush_dev(dev);
1864 break;
1865 case NETDEV_CHANGE:
1866 if (!(dev_get_flags(dev) & (IFF_RUNNING | IFF_LOWER_UP)))
1867 nexthop_flush_dev(dev);
1868 break;
1869 case NETDEV_CHANGEMTU:
1870 info_ext = ptr;
1871 nexthop_sync_mtu(dev, info_ext->ext.mtu);
1872 rt_cache_flush(dev_net(dev));
1873 break;
1874 }
1875 return NOTIFY_DONE;
1876}
1877
1878static struct notifier_block nh_netdev_notifier = {
1879 .notifier_call = nh_netdev_event,
1880};
1881
Roopa Prabhu8590ceed2020-05-21 22:26:15 -07001882int register_nexthop_notifier(struct net *net, struct notifier_block *nb)
1883{
1884 return atomic_notifier_chain_register(&net->nexthop.notifier_chain, nb);
1885}
1886EXPORT_SYMBOL(register_nexthop_notifier);
1887
1888int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb)
1889{
1890 return atomic_notifier_chain_unregister(&net->nexthop.notifier_chain,
1891 nb);
1892}
1893EXPORT_SYMBOL(unregister_nexthop_notifier);
1894
David Ahernab84be72019-05-24 14:43:04 -07001895static void __net_exit nexthop_net_exit(struct net *net)
1896{
1897 rtnl_lock();
1898 flush_all_nexthops(net);
1899 rtnl_unlock();
David Ahern597cfe4f2019-05-24 14:43:05 -07001900 kfree(net->nexthop.devhash);
David Ahernab84be72019-05-24 14:43:04 -07001901}
1902
1903static int __net_init nexthop_net_init(struct net *net)
1904{
David Ahern597cfe4f2019-05-24 14:43:05 -07001905 size_t sz = sizeof(struct hlist_head) * NH_DEV_HASHSIZE;
1906
David Ahernab84be72019-05-24 14:43:04 -07001907 net->nexthop.rb_root = RB_ROOT;
David Ahern597cfe4f2019-05-24 14:43:05 -07001908 net->nexthop.devhash = kzalloc(sz, GFP_KERNEL);
1909 if (!net->nexthop.devhash)
1910 return -ENOMEM;
Roopa Prabhu8590ceed2020-05-21 22:26:15 -07001911 ATOMIC_INIT_NOTIFIER_HEAD(&net->nexthop.notifier_chain);
David Ahernab84be72019-05-24 14:43:04 -07001912
1913 return 0;
1914}
1915
1916static struct pernet_operations nexthop_net_ops = {
1917 .init = nexthop_net_init,
1918 .exit = nexthop_net_exit,
1919};
1920
1921static int __init nexthop_init(void)
1922{
1923 register_pernet_subsys(&nexthop_net_ops);
1924
David Ahern597cfe4f2019-05-24 14:43:05 -07001925 register_netdevice_notifier(&nh_netdev_notifier);
1926
David Ahernab84be72019-05-24 14:43:04 -07001927 rtnl_register(PF_UNSPEC, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
1928 rtnl_register(PF_UNSPEC, RTM_DELNEXTHOP, rtm_del_nexthop, NULL, 0);
1929 rtnl_register(PF_UNSPEC, RTM_GETNEXTHOP, rtm_get_nexthop,
1930 rtm_dump_nexthop, 0);
1931
1932 rtnl_register(PF_INET, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
1933 rtnl_register(PF_INET, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
1934
1935 rtnl_register(PF_INET6, RTM_NEWNEXTHOP, rtm_new_nexthop, NULL, 0);
1936 rtnl_register(PF_INET6, RTM_GETNEXTHOP, NULL, rtm_dump_nexthop, 0);
1937
1938 return 0;
1939}
1940subsys_initcall(nexthop_init);