]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blame - drivers/net/netdevsim/fib.c
Merge tag 'for-5.7-rc1-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave...
[mirror_ubuntu-jammy-kernel.git] / drivers / net / netdevsim / fib.c
CommitLineData
37923ed6
DA
1/*
2 * Copyright (c) 2018 Cumulus Networks. All rights reserved.
3 * Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
4 *
5 * This software is licensed under the GNU General License Version 2,
6 * June 1991 as shown in the file COPYING in the top-level directory of this
7 * source tree.
8 *
9 * THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
10 * WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
11 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12 * FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
13 * OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
14 * THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
15 */
16
48bb9eb4
IS
17#include <linux/in6.h>
18#include <linux/kernel.h>
19#include <linux/list.h>
20#include <linux/rhashtable.h>
21#include <linux/spinlock_types.h>
22#include <linux/types.h>
37923ed6
DA
23#include <net/fib_notifier.h>
24#include <net/ip_fib.h>
25#include <net/ip6_fib.h>
26#include <net/fib_rules.h>
a5facc4c 27#include <net/net_namespace.h>
37923ed6
DA
28
29#include "netdevsim.h"
30
31struct nsim_fib_entry {
32 u64 max;
33 u64 num;
34};
35
36struct nsim_per_fib_data {
37 struct nsim_fib_entry fib;
38 struct nsim_fib_entry rules;
39};
40
41struct nsim_fib_data {
a5facc4c 42 struct notifier_block fib_nb;
37923ed6
DA
43 struct nsim_per_fib_data ipv4;
44 struct nsim_per_fib_data ipv6;
48bb9eb4
IS
45 struct rhashtable fib_rt_ht;
46 struct list_head fib_rt_list;
47 spinlock_t fib_lock; /* Protects hashtable, list and accounting */
48 struct devlink *devlink;
49};
50
51struct nsim_fib_rt_key {
52 unsigned char addr[sizeof(struct in6_addr)];
53 unsigned char prefix_len;
54 int family;
55 u32 tb_id;
56};
57
58struct nsim_fib_rt {
59 struct nsim_fib_rt_key key;
60 struct rhash_head ht_node;
61 struct list_head list; /* Member of fib_rt_list */
62};
63
64struct nsim_fib4_rt {
65 struct nsim_fib_rt common;
66 struct fib_info *fi;
67 u8 tos;
68 u8 type;
69};
70
71struct nsim_fib6_rt {
72 struct nsim_fib_rt common;
73 struct list_head nh_list;
74 unsigned int nhs;
75};
76
77struct nsim_fib6_rt_nh {
78 struct list_head list; /* Member of nh_list */
79 struct fib6_info *rt;
80};
81
82static const struct rhashtable_params nsim_fib_rt_ht_params = {
83 .key_offset = offsetof(struct nsim_fib_rt, key),
84 .head_offset = offsetof(struct nsim_fib_rt, ht_node),
85 .key_len = sizeof(struct nsim_fib_rt_key),
86 .automatic_shrinking = true,
37923ed6
DA
87};
88
a5facc4c
JP
89u64 nsim_fib_get_val(struct nsim_fib_data *fib_data,
90 enum nsim_resource_id res_id, bool max)
37923ed6 91{
37923ed6
DA
92 struct nsim_fib_entry *entry;
93
94 switch (res_id) {
95 case NSIM_RESOURCE_IPV4_FIB:
96 entry = &fib_data->ipv4.fib;
97 break;
98 case NSIM_RESOURCE_IPV4_FIB_RULES:
99 entry = &fib_data->ipv4.rules;
100 break;
101 case NSIM_RESOURCE_IPV6_FIB:
102 entry = &fib_data->ipv6.fib;
103 break;
104 case NSIM_RESOURCE_IPV6_FIB_RULES:
105 entry = &fib_data->ipv6.rules;
106 break;
107 default:
108 return 0;
109 }
110
111 return max ? entry->max : entry->num;
112}
113
75ba029f
JP
114static void nsim_fib_set_max(struct nsim_fib_data *fib_data,
115 enum nsim_resource_id res_id, u64 val)
37923ed6 116{
37923ed6 117 struct nsim_fib_entry *entry;
37923ed6
DA
118
119 switch (res_id) {
120 case NSIM_RESOURCE_IPV4_FIB:
121 entry = &fib_data->ipv4.fib;
122 break;
123 case NSIM_RESOURCE_IPV4_FIB_RULES:
124 entry = &fib_data->ipv4.rules;
125 break;
126 case NSIM_RESOURCE_IPV6_FIB:
127 entry = &fib_data->ipv6.fib;
128 break;
129 case NSIM_RESOURCE_IPV6_FIB_RULES:
130 entry = &fib_data->ipv6.rules;
131 break;
132 default:
75ba029f
JP
133 WARN_ON(1);
134 return;
7fa76d77 135 }
75ba029f 136 entry->max = val;
37923ed6
DA
137}
138
139static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add,
140 struct netlink_ext_ack *extack)
141{
142 int err = 0;
143
144 if (add) {
145 if (entry->num < entry->max) {
146 entry->num++;
147 } else {
148 err = -ENOSPC;
149 NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");
150 }
151 } else {
152 entry->num--;
153 }
154
155 return err;
156}
157
a5facc4c
JP
158static int nsim_fib_rule_event(struct nsim_fib_data *data,
159 struct fib_notifier_info *info, bool add)
37923ed6 160{
37923ed6
DA
161 struct netlink_ext_ack *extack = info->extack;
162 int err = 0;
163
164 switch (info->family) {
165 case AF_INET:
166 err = nsim_fib_rule_account(&data->ipv4.rules, add, extack);
167 break;
168 case AF_INET6:
169 err = nsim_fib_rule_account(&data->ipv6.rules, add, extack);
170 break;
171 }
172
173 return err;
174}
175
176static int nsim_fib_account(struct nsim_fib_entry *entry, bool add,
177 struct netlink_ext_ack *extack)
178{
179 int err = 0;
180
181 if (add) {
182 if (entry->num < entry->max) {
183 entry->num++;
184 } else {
185 err = -ENOSPC;
186 NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries");
187 }
188 } else {
189 entry->num--;
190 }
191
192 return err;
193}
194
48bb9eb4
IS
195static void nsim_fib_rt_init(struct nsim_fib_data *data,
196 struct nsim_fib_rt *fib_rt, const void *addr,
197 size_t addr_len, unsigned int prefix_len,
198 int family, u32 tb_id)
199{
200 memcpy(fib_rt->key.addr, addr, addr_len);
201 fib_rt->key.prefix_len = prefix_len;
202 fib_rt->key.family = family;
203 fib_rt->key.tb_id = tb_id;
204 list_add(&fib_rt->list, &data->fib_rt_list);
205}
206
207static void nsim_fib_rt_fini(struct nsim_fib_rt *fib_rt)
208{
209 list_del(&fib_rt->list);
210}
211
212static struct nsim_fib_rt *nsim_fib_rt_lookup(struct rhashtable *fib_rt_ht,
213 const void *addr, size_t addr_len,
214 unsigned int prefix_len,
215 int family, u32 tb_id)
216{
217 struct nsim_fib_rt_key key;
218
219 memset(&key, 0, sizeof(key));
220 memcpy(key.addr, addr, addr_len);
221 key.prefix_len = prefix_len;
222 key.family = family;
223 key.tb_id = tb_id;
224
225 return rhashtable_lookup_fast(fib_rt_ht, &key, nsim_fib_rt_ht_params);
226}
227
228static struct nsim_fib4_rt *
229nsim_fib4_rt_create(struct nsim_fib_data *data,
230 struct fib_entry_notifier_info *fen_info)
231{
232 struct nsim_fib4_rt *fib4_rt;
233
234 fib4_rt = kzalloc(sizeof(*fib4_rt), GFP_ATOMIC);
235 if (!fib4_rt)
236 return NULL;
237
238 nsim_fib_rt_init(data, &fib4_rt->common, &fen_info->dst, sizeof(u32),
239 fen_info->dst_len, AF_INET, fen_info->tb_id);
240
241 fib4_rt->fi = fen_info->fi;
242 fib_info_hold(fib4_rt->fi);
243 fib4_rt->tos = fen_info->tos;
244 fib4_rt->type = fen_info->type;
245
246 return fib4_rt;
247}
248
249static void nsim_fib4_rt_destroy(struct nsim_fib4_rt *fib4_rt)
250{
251 fib_info_put(fib4_rt->fi);
252 nsim_fib_rt_fini(&fib4_rt->common);
253 kfree(fib4_rt);
254}
255
256static struct nsim_fib4_rt *
257nsim_fib4_rt_lookup(struct rhashtable *fib_rt_ht,
258 const struct fib_entry_notifier_info *fen_info)
259{
260 struct nsim_fib_rt *fib_rt;
261
262 fib_rt = nsim_fib_rt_lookup(fib_rt_ht, &fen_info->dst, sizeof(u32),
263 fen_info->dst_len, AF_INET,
264 fen_info->tb_id);
265 if (!fib_rt)
266 return NULL;
267
268 return container_of(fib_rt, struct nsim_fib4_rt, common);
269}
270
271static void nsim_fib4_rt_hw_flags_set(struct net *net,
272 const struct nsim_fib4_rt *fib4_rt,
273 bool trap)
274{
275 u32 *p_dst = (u32 *) fib4_rt->common.key.addr;
276 int dst_len = fib4_rt->common.key.prefix_len;
277 struct fib_rt_info fri;
278
279 fri.fi = fib4_rt->fi;
280 fri.tb_id = fib4_rt->common.key.tb_id;
281 fri.dst = cpu_to_be32(*p_dst);
282 fri.dst_len = dst_len;
283 fri.tos = fib4_rt->tos;
284 fri.type = fib4_rt->type;
285 fri.offload = false;
286 fri.trap = trap;
287 fib_alias_hw_flags_set(net, &fri);
288}
289
290static int nsim_fib4_rt_add(struct nsim_fib_data *data,
291 struct nsim_fib4_rt *fib4_rt,
292 struct netlink_ext_ack *extack)
293{
294 struct net *net = devlink_net(data->devlink);
295 int err;
296
297 err = nsim_fib_account(&data->ipv4.fib, true, extack);
298 if (err)
299 return err;
300
301 err = rhashtable_insert_fast(&data->fib_rt_ht,
302 &fib4_rt->common.ht_node,
303 nsim_fib_rt_ht_params);
304 if (err) {
305 NL_SET_ERR_MSG_MOD(extack, "Failed to insert IPv4 route");
306 goto err_fib_dismiss;
307 }
308
309 nsim_fib4_rt_hw_flags_set(net, fib4_rt, true);
310
311 return 0;
312
313err_fib_dismiss:
314 nsim_fib_account(&data->ipv4.fib, false, extack);
315 return err;
316}
317
318static int nsim_fib4_rt_replace(struct nsim_fib_data *data,
319 struct nsim_fib4_rt *fib4_rt,
320 struct nsim_fib4_rt *fib4_rt_old,
321 struct netlink_ext_ack *extack)
322{
323 struct net *net = devlink_net(data->devlink);
324 int err;
325
326 /* We are replacing a route, so no need to change the accounting. */
327 err = rhashtable_replace_fast(&data->fib_rt_ht,
328 &fib4_rt_old->common.ht_node,
329 &fib4_rt->common.ht_node,
330 nsim_fib_rt_ht_params);
331 if (err) {
332 NL_SET_ERR_MSG_MOD(extack, "Failed to replace IPv4 route");
333 return err;
334 }
335
336 nsim_fib4_rt_hw_flags_set(net, fib4_rt, true);
337
338 nsim_fib4_rt_hw_flags_set(net, fib4_rt_old, false);
339 nsim_fib4_rt_destroy(fib4_rt_old);
340
341 return 0;
342}
343
344static int nsim_fib4_rt_insert(struct nsim_fib_data *data,
345 struct fib_entry_notifier_info *fen_info)
346{
347 struct netlink_ext_ack *extack = fen_info->info.extack;
348 struct nsim_fib4_rt *fib4_rt, *fib4_rt_old;
349 int err;
350
351 fib4_rt = nsim_fib4_rt_create(data, fen_info);
352 if (!fib4_rt)
353 return -ENOMEM;
354
355 fib4_rt_old = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info);
356 if (!fib4_rt_old)
357 err = nsim_fib4_rt_add(data, fib4_rt, extack);
358 else
359 err = nsim_fib4_rt_replace(data, fib4_rt, fib4_rt_old, extack);
360
361 if (err)
362 nsim_fib4_rt_destroy(fib4_rt);
363
364 return err;
365}
366
367static void nsim_fib4_rt_remove(struct nsim_fib_data *data,
368 const struct fib_entry_notifier_info *fen_info)
369{
370 struct netlink_ext_ack *extack = fen_info->info.extack;
371 struct nsim_fib4_rt *fib4_rt;
372
373 fib4_rt = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info);
374 if (WARN_ON_ONCE(!fib4_rt))
375 return;
376
377 rhashtable_remove_fast(&data->fib_rt_ht, &fib4_rt->common.ht_node,
378 nsim_fib_rt_ht_params);
379 nsim_fib_account(&data->ipv4.fib, false, extack);
380 nsim_fib4_rt_destroy(fib4_rt);
381}
382
383static int nsim_fib4_event(struct nsim_fib_data *data,
384 struct fib_notifier_info *info,
385 unsigned long event)
386{
387 struct fib_entry_notifier_info *fen_info;
388 int err = 0;
389
390 fen_info = container_of(info, struct fib_entry_notifier_info, info);
391
392 if (fen_info->fi->nh) {
393 NL_SET_ERR_MSG_MOD(info->extack, "IPv4 route with nexthop objects is not supported");
394 return 0;
395 }
396
397 switch (event) {
398 case FIB_EVENT_ENTRY_REPLACE:
399 err = nsim_fib4_rt_insert(data, fen_info);
400 break;
401 case FIB_EVENT_ENTRY_DEL:
402 nsim_fib4_rt_remove(data, fen_info);
403 break;
404 default:
405 break;
406 }
407
408 return err;
409}
410
411static struct nsim_fib6_rt_nh *
412nsim_fib6_rt_nh_find(const struct nsim_fib6_rt *fib6_rt,
413 const struct fib6_info *rt)
414{
415 struct nsim_fib6_rt_nh *fib6_rt_nh;
416
417 list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list) {
418 if (fib6_rt_nh->rt == rt)
419 return fib6_rt_nh;
420 }
421
422 return NULL;
423}
424
425static int nsim_fib6_rt_nh_add(struct nsim_fib6_rt *fib6_rt,
426 struct fib6_info *rt)
427{
428 struct nsim_fib6_rt_nh *fib6_rt_nh;
429
430 fib6_rt_nh = kzalloc(sizeof(*fib6_rt_nh), GFP_ATOMIC);
431 if (!fib6_rt_nh)
432 return -ENOMEM;
433
434 fib6_info_hold(rt);
435 fib6_rt_nh->rt = rt;
436 list_add_tail(&fib6_rt_nh->list, &fib6_rt->nh_list);
437 fib6_rt->nhs++;
438
439 return 0;
440}
441
442static void nsim_fib6_rt_nh_del(struct nsim_fib6_rt *fib6_rt,
443 const struct fib6_info *rt)
444{
445 struct nsim_fib6_rt_nh *fib6_rt_nh;
446
447 fib6_rt_nh = nsim_fib6_rt_nh_find(fib6_rt, rt);
448 if (WARN_ON_ONCE(!fib6_rt_nh))
449 return;
450
451 fib6_rt->nhs--;
452 list_del(&fib6_rt_nh->list);
453#if IS_ENABLED(CONFIG_IPV6)
454 fib6_info_release(fib6_rt_nh->rt);
455#endif
456 kfree(fib6_rt_nh);
457}
458
459static struct nsim_fib6_rt *
460nsim_fib6_rt_create(struct nsim_fib_data *data,
461 struct fib6_entry_notifier_info *fen6_info)
462{
463 struct fib6_info *iter, *rt = fen6_info->rt;
464 struct nsim_fib6_rt *fib6_rt;
465 int i = 0;
466 int err;
467
468 fib6_rt = kzalloc(sizeof(*fib6_rt), GFP_ATOMIC);
469 if (!fib6_rt)
41cdc741 470 return ERR_PTR(-ENOMEM);
48bb9eb4
IS
471
472 nsim_fib_rt_init(data, &fib6_rt->common, &rt->fib6_dst.addr,
473 sizeof(rt->fib6_dst.addr), rt->fib6_dst.plen, AF_INET6,
474 rt->fib6_table->tb6_id);
475
476 /* We consider a multipath IPv6 route as one entry, but it can be made
477 * up from several fib6_info structs (one for each nexthop), so we
478 * add them all to the same list under the entry.
479 */
480 INIT_LIST_HEAD(&fib6_rt->nh_list);
481
482 err = nsim_fib6_rt_nh_add(fib6_rt, rt);
483 if (err)
484 goto err_fib_rt_fini;
485
486 if (!fen6_info->nsiblings)
487 return fib6_rt;
488
489 list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) {
490 if (i == fen6_info->nsiblings)
491 break;
492
493 err = nsim_fib6_rt_nh_add(fib6_rt, iter);
494 if (err)
495 goto err_fib6_rt_nh_del;
496 i++;
497 }
498 WARN_ON_ONCE(i != fen6_info->nsiblings);
499
500 return fib6_rt;
501
502err_fib6_rt_nh_del:
503 list_for_each_entry_continue_reverse(iter, &rt->fib6_siblings,
504 fib6_siblings)
505 nsim_fib6_rt_nh_del(fib6_rt, iter);
506 nsim_fib6_rt_nh_del(fib6_rt, rt);
507err_fib_rt_fini:
508 nsim_fib_rt_fini(&fib6_rt->common);
509 kfree(fib6_rt);
510 return ERR_PTR(err);
511}
512
513static void nsim_fib6_rt_destroy(struct nsim_fib6_rt *fib6_rt)
514{
515 struct nsim_fib6_rt_nh *iter, *tmp;
516
517 list_for_each_entry_safe(iter, tmp, &fib6_rt->nh_list, list)
518 nsim_fib6_rt_nh_del(fib6_rt, iter->rt);
519 WARN_ON_ONCE(!list_empty(&fib6_rt->nh_list));
520 nsim_fib_rt_fini(&fib6_rt->common);
521 kfree(fib6_rt);
522}
523
524static struct nsim_fib6_rt *
525nsim_fib6_rt_lookup(struct rhashtable *fib_rt_ht, const struct fib6_info *rt)
526{
527 struct nsim_fib_rt *fib_rt;
528
529 fib_rt = nsim_fib_rt_lookup(fib_rt_ht, &rt->fib6_dst.addr,
530 sizeof(rt->fib6_dst.addr),
531 rt->fib6_dst.plen, AF_INET6,
532 rt->fib6_table->tb6_id);
533 if (!fib_rt)
534 return NULL;
535
536 return container_of(fib_rt, struct nsim_fib6_rt, common);
537}
538
539static int nsim_fib6_rt_append(struct nsim_fib_data *data,
540 struct fib6_entry_notifier_info *fen6_info)
541{
542 struct fib6_info *iter, *rt = fen6_info->rt;
543 struct nsim_fib6_rt *fib6_rt;
544 int i = 0;
545 int err;
546
547 fib6_rt = nsim_fib6_rt_lookup(&data->fib_rt_ht, rt);
548 if (WARN_ON_ONCE(!fib6_rt))
549 return -EINVAL;
550
551 err = nsim_fib6_rt_nh_add(fib6_rt, rt);
552 if (err)
553 return err;
554 rt->trap = true;
555
556 if (!fen6_info->nsiblings)
557 return 0;
558
559 list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) {
560 if (i == fen6_info->nsiblings)
561 break;
562
563 err = nsim_fib6_rt_nh_add(fib6_rt, iter);
564 if (err)
565 goto err_fib6_rt_nh_del;
566 iter->trap = true;
567 i++;
568 }
569 WARN_ON_ONCE(i != fen6_info->nsiblings);
570
571 return 0;
572
573err_fib6_rt_nh_del:
574 list_for_each_entry_continue_reverse(iter, &rt->fib6_siblings,
575 fib6_siblings) {
576 iter->trap = false;
577 nsim_fib6_rt_nh_del(fib6_rt, iter);
578 }
579 rt->trap = false;
580 nsim_fib6_rt_nh_del(fib6_rt, rt);
581 return err;
582}
583
584static void nsim_fib6_rt_hw_flags_set(const struct nsim_fib6_rt *fib6_rt,
585 bool trap)
586{
587 struct nsim_fib6_rt_nh *fib6_rt_nh;
588
589 list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list)
590 fib6_info_hw_flags_set(fib6_rt_nh->rt, false, trap);
591}
592
593static int nsim_fib6_rt_add(struct nsim_fib_data *data,
594 struct nsim_fib6_rt *fib6_rt,
595 struct netlink_ext_ack *extack)
596{
597 int err;
598
599 err = nsim_fib_account(&data->ipv6.fib, true, extack);
600 if (err)
601 return err;
602
603 err = rhashtable_insert_fast(&data->fib_rt_ht,
604 &fib6_rt->common.ht_node,
605 nsim_fib_rt_ht_params);
606 if (err) {
607 NL_SET_ERR_MSG_MOD(extack, "Failed to insert IPv6 route");
608 goto err_fib_dismiss;
609 }
610
611 nsim_fib6_rt_hw_flags_set(fib6_rt, true);
612
613 return 0;
614
615err_fib_dismiss:
616 nsim_fib_account(&data->ipv6.fib, false, extack);
617 return err;
618}
619
620static int nsim_fib6_rt_replace(struct nsim_fib_data *data,
621 struct nsim_fib6_rt *fib6_rt,
622 struct nsim_fib6_rt *fib6_rt_old,
623 struct netlink_ext_ack *extack)
624{
625 int err;
626
627 /* We are replacing a route, so no need to change the accounting. */
628 err = rhashtable_replace_fast(&data->fib_rt_ht,
629 &fib6_rt_old->common.ht_node,
630 &fib6_rt->common.ht_node,
631 nsim_fib_rt_ht_params);
632 if (err) {
633 NL_SET_ERR_MSG_MOD(extack, "Failed to replace IPv6 route");
634 return err;
635 }
636
637 nsim_fib6_rt_hw_flags_set(fib6_rt, true);
638
639 nsim_fib6_rt_hw_flags_set(fib6_rt_old, false);
640 nsim_fib6_rt_destroy(fib6_rt_old);
641
642 return 0;
643}
644
645static int nsim_fib6_rt_insert(struct nsim_fib_data *data,
646 struct fib6_entry_notifier_info *fen6_info)
647{
648 struct netlink_ext_ack *extack = fen6_info->info.extack;
649 struct nsim_fib6_rt *fib6_rt, *fib6_rt_old;
650 int err;
651
652 fib6_rt = nsim_fib6_rt_create(data, fen6_info);
41cdc741
ED
653 if (IS_ERR(fib6_rt))
654 return PTR_ERR(fib6_rt);
48bb9eb4
IS
655
656 fib6_rt_old = nsim_fib6_rt_lookup(&data->fib_rt_ht, fen6_info->rt);
657 if (!fib6_rt_old)
658 err = nsim_fib6_rt_add(data, fib6_rt, extack);
659 else
660 err = nsim_fib6_rt_replace(data, fib6_rt, fib6_rt_old, extack);
661
662 if (err)
663 nsim_fib6_rt_destroy(fib6_rt);
664
665 return err;
666}
667
668static void
669nsim_fib6_rt_remove(struct nsim_fib_data *data,
670 const struct fib6_entry_notifier_info *fen6_info)
671{
672 struct netlink_ext_ack *extack = fen6_info->info.extack;
673 struct nsim_fib6_rt *fib6_rt;
674
675 /* Multipath routes are first added to the FIB trie and only then
676 * notified. If we vetoed the addition, we will get a delete
677 * notification for a route we do not have. Therefore, do not warn if
678 * route was not found.
679 */
680 fib6_rt = nsim_fib6_rt_lookup(&data->fib_rt_ht, fen6_info->rt);
681 if (!fib6_rt)
682 return;
683
684 /* If not all the nexthops are deleted, then only reduce the nexthop
685 * group.
686 */
687 if (fen6_info->nsiblings + 1 != fib6_rt->nhs) {
688 nsim_fib6_rt_nh_del(fib6_rt, fen6_info->rt);
689 return;
690 }
691
692 rhashtable_remove_fast(&data->fib_rt_ht, &fib6_rt->common.ht_node,
693 nsim_fib_rt_ht_params);
694 nsim_fib_account(&data->ipv6.fib, false, extack);
695 nsim_fib6_rt_destroy(fib6_rt);
696}
697
698static int nsim_fib6_event(struct nsim_fib_data *data,
699 struct fib_notifier_info *info,
700 unsigned long event)
701{
702 struct fib6_entry_notifier_info *fen6_info;
703 int err = 0;
704
705 fen6_info = container_of(info, struct fib6_entry_notifier_info, info);
706
707 if (fen6_info->rt->nh) {
708 NL_SET_ERR_MSG_MOD(info->extack, "IPv6 route with nexthop objects is not supported");
709 return 0;
710 }
711
712 if (fen6_info->rt->fib6_src.plen) {
713 NL_SET_ERR_MSG_MOD(info->extack, "IPv6 source-specific route is not supported");
714 return 0;
715 }
716
717 switch (event) {
718 case FIB_EVENT_ENTRY_REPLACE:
719 err = nsim_fib6_rt_insert(data, fen6_info);
720 break;
721 case FIB_EVENT_ENTRY_APPEND:
722 err = nsim_fib6_rt_append(data, fen6_info);
723 break;
724 case FIB_EVENT_ENTRY_DEL:
725 nsim_fib6_rt_remove(data, fen6_info);
726 break;
727 default:
728 break;
729 }
730
731 return err;
732}
733
a5facc4c 734static int nsim_fib_event(struct nsim_fib_data *data,
48bb9eb4 735 struct fib_notifier_info *info, unsigned long event)
37923ed6 736{
37923ed6
DA
737 int err = 0;
738
739 switch (info->family) {
740 case AF_INET:
48bb9eb4 741 err = nsim_fib4_event(data, info, event);
37923ed6
DA
742 break;
743 case AF_INET6:
48bb9eb4 744 err = nsim_fib6_event(data, info, event);
37923ed6
DA
745 break;
746 }
747
748 return err;
749}
750
751static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
752 void *ptr)
753{
a5facc4c
JP
754 struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
755 fib_nb);
37923ed6
DA
756 struct fib_notifier_info *info = ptr;
757 int err = 0;
758
48bb9eb4
IS
759 /* IPv6 routes can be added via RAs from softIRQ. */
760 spin_lock_bh(&data->fib_lock);
761
37923ed6
DA
762 switch (event) {
763 case FIB_EVENT_RULE_ADD: /* fall through */
764 case FIB_EVENT_RULE_DEL:
a5facc4c
JP
765 err = nsim_fib_rule_event(data, info,
766 event == FIB_EVENT_RULE_ADD);
37923ed6
DA
767 break;
768
446f7391 769 case FIB_EVENT_ENTRY_REPLACE: /* fall through */
48bb9eb4 770 case FIB_EVENT_ENTRY_APPEND: /* fall through */
37923ed6 771 case FIB_EVENT_ENTRY_DEL:
48bb9eb4 772 err = nsim_fib_event(data, info, event);
37923ed6
DA
773 break;
774 }
775
48bb9eb4
IS
776 spin_unlock_bh(&data->fib_lock);
777
37923ed6
DA
778 return notifier_from_errno(err);
779}
780
48bb9eb4
IS
781static void nsim_fib4_rt_free(struct nsim_fib_rt *fib_rt,
782 struct nsim_fib_data *data)
783{
784 struct devlink *devlink = data->devlink;
785 struct nsim_fib4_rt *fib4_rt;
786
787 fib4_rt = container_of(fib_rt, struct nsim_fib4_rt, common);
788 nsim_fib4_rt_hw_flags_set(devlink_net(devlink), fib4_rt, false);
789 nsim_fib_account(&data->ipv4.fib, false, NULL);
790 nsim_fib4_rt_destroy(fib4_rt);
791}
792
793static void nsim_fib6_rt_free(struct nsim_fib_rt *fib_rt,
794 struct nsim_fib_data *data)
795{
796 struct nsim_fib6_rt *fib6_rt;
797
798 fib6_rt = container_of(fib_rt, struct nsim_fib6_rt, common);
799 nsim_fib6_rt_hw_flags_set(fib6_rt, false);
800 nsim_fib_account(&data->ipv6.fib, false, NULL);
801 nsim_fib6_rt_destroy(fib6_rt);
802}
803
804static void nsim_fib_rt_free(void *ptr, void *arg)
805{
806 struct nsim_fib_rt *fib_rt = ptr;
807 struct nsim_fib_data *data = arg;
808
809 switch (fib_rt->key.family) {
810 case AF_INET:
811 nsim_fib4_rt_free(fib_rt, data);
812 break;
813 case AF_INET6:
814 nsim_fib6_rt_free(fib_rt, data);
815 break;
816 default:
817 WARN_ON_ONCE(1);
818 }
819}
820
37923ed6
DA
821/* inconsistent dump, trying again */
822static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
823{
a5facc4c
JP
824 struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
825 fib_nb);
48bb9eb4
IS
826 struct nsim_fib_rt *fib_rt, *fib_rt_tmp;
827
828 /* The notifier block is still not registered, so we do not need to
829 * take any locks here.
830 */
831 list_for_each_entry_safe(fib_rt, fib_rt_tmp, &data->fib_rt_list, list) {
832 rhashtable_remove_fast(&data->fib_rt_ht, &fib_rt->ht_node,
833 nsim_fib_rt_ht_params);
834 nsim_fib_rt_free(fib_rt, data);
835 }
59c84b9f 836
a5facc4c 837 data->ipv4.rules.num = 0ULL;
a5facc4c
JP
838 data->ipv6.rules.num = 0ULL;
839}
59c84b9f 840
a5facc4c
JP
841static u64 nsim_fib_ipv4_resource_occ_get(void *priv)
842{
843 struct nsim_fib_data *data = priv;
37923ed6 844
a5facc4c 845 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false);
37923ed6
DA
846}
847
a5facc4c 848static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv)
59c84b9f 849{
a5facc4c 850 struct nsim_fib_data *data = priv;
37923ed6 851
a5facc4c
JP
852 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false);
853}
37923ed6 854
a5facc4c
JP
855static u64 nsim_fib_ipv6_resource_occ_get(void *priv)
856{
857 struct nsim_fib_data *data = priv;
37923ed6 858
a5facc4c 859 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false);
59c84b9f 860}
37923ed6 861
a5facc4c 862static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv)
59c84b9f 863{
a5facc4c
JP
864 struct nsim_fib_data *data = priv;
865
866 return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false);
5fc49422
JP
867}
868
75ba029f
JP
869static void nsim_fib_set_max_all(struct nsim_fib_data *data,
870 struct devlink *devlink)
871{
872 enum nsim_resource_id res_ids[] = {
873 NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
874 NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
875 };
876 int i;
877
878 for (i = 0; i < ARRAY_SIZE(res_ids); i++) {
879 int err;
880 u64 val;
881
882 err = devlink_resource_size_get(devlink, res_ids[i], &val);
883 if (err)
884 val = (u64) -1;
885 nsim_fib_set_max(data, res_ids[i], val);
886 }
887}
888
889struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
890 struct netlink_ext_ack *extack)
5fc49422 891{
a5facc4c 892 struct nsim_fib_data *data;
59c84b9f
DA
893 int err;
894
a5facc4c
JP
895 data = kzalloc(sizeof(*data), GFP_KERNEL);
896 if (!data)
897 return ERR_PTR(-ENOMEM);
48bb9eb4
IS
898 data->devlink = devlink;
899
900 spin_lock_init(&data->fib_lock);
901 INIT_LIST_HEAD(&data->fib_rt_list);
902 err = rhashtable_init(&data->fib_rt_ht, &nsim_fib_rt_ht_params);
903 if (err)
904 goto err_data_free;
59c84b9f 905
75ba029f 906 nsim_fib_set_max_all(data, devlink);
a5facc4c
JP
907
908 data->fib_nb.notifier_call = nsim_fib_event_nb;
4f174bbc 909 err = register_fib_notifier(devlink_net(devlink), &data->fib_nb,
75ba029f 910 nsim_fib_dump_inconsistent, extack);
a5facc4c 911 if (err) {
59c84b9f 912 pr_err("Failed to register fib notifier\n");
48bb9eb4 913 goto err_rhashtable_destroy;
59c84b9f
DA
914 }
915
a5facc4c
JP
916 devlink_resource_occ_get_register(devlink,
917 NSIM_RESOURCE_IPV4_FIB,
918 nsim_fib_ipv4_resource_occ_get,
919 data);
920 devlink_resource_occ_get_register(devlink,
921 NSIM_RESOURCE_IPV4_FIB_RULES,
922 nsim_fib_ipv4_rules_res_occ_get,
923 data);
924 devlink_resource_occ_get_register(devlink,
925 NSIM_RESOURCE_IPV6_FIB,
926 nsim_fib_ipv6_resource_occ_get,
927 data);
928 devlink_resource_occ_get_register(devlink,
929 NSIM_RESOURCE_IPV6_FIB_RULES,
930 nsim_fib_ipv6_rules_res_occ_get,
931 data);
932 return data;
933
48bb9eb4
IS
934err_rhashtable_destroy:
935 rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free,
936 data);
937err_data_free:
a5facc4c
JP
938 kfree(data);
939 return ERR_PTR(err);
940}
941
942void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data)
943{
944 devlink_resource_occ_get_unregister(devlink,
945 NSIM_RESOURCE_IPV6_FIB_RULES);
946 devlink_resource_occ_get_unregister(devlink,
947 NSIM_RESOURCE_IPV6_FIB);
948 devlink_resource_occ_get_unregister(devlink,
949 NSIM_RESOURCE_IPV4_FIB_RULES);
950 devlink_resource_occ_get_unregister(devlink,
951 NSIM_RESOURCE_IPV4_FIB);
4f174bbc 952 unregister_fib_notifier(devlink_net(devlink), &data->fib_nb);
48bb9eb4
IS
953 rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free,
954 data);
955 WARN_ON_ONCE(!list_empty(&data->fib_rt_list));
a5facc4c 956 kfree(data);
37923ed6 957}