xref: /openbmc/linux/drivers/net/netdevsim/fib.c (revision 6c33a6f4)
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 
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>
23 #include <net/fib_notifier.h>
24 #include <net/ip_fib.h>
25 #include <net/ip6_fib.h>
26 #include <net/fib_rules.h>
27 #include <net/net_namespace.h>
28 
29 #include "netdevsim.h"
30 
31 struct nsim_fib_entry {
32 	u64 max;
33 	u64 num;
34 };
35 
36 struct nsim_per_fib_data {
37 	struct nsim_fib_entry fib;
38 	struct nsim_fib_entry rules;
39 };
40 
41 struct nsim_fib_data {
42 	struct notifier_block fib_nb;
43 	struct nsim_per_fib_data ipv4;
44 	struct nsim_per_fib_data ipv6;
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 
51 struct 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 
58 struct 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 
64 struct nsim_fib4_rt {
65 	struct nsim_fib_rt common;
66 	struct fib_info *fi;
67 	u8 tos;
68 	u8 type;
69 };
70 
71 struct nsim_fib6_rt {
72 	struct nsim_fib_rt common;
73 	struct list_head nh_list;
74 	unsigned int nhs;
75 };
76 
77 struct nsim_fib6_rt_nh {
78 	struct list_head list;	/* Member of nh_list */
79 	struct fib6_info *rt;
80 };
81 
82 static 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,
87 };
88 
89 u64 nsim_fib_get_val(struct nsim_fib_data *fib_data,
90 		     enum nsim_resource_id res_id, bool max)
91 {
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 
114 static void nsim_fib_set_max(struct nsim_fib_data *fib_data,
115 			     enum nsim_resource_id res_id, u64 val)
116 {
117 	struct nsim_fib_entry *entry;
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:
133 		WARN_ON(1);
134 		return;
135 	}
136 	entry->max = val;
137 }
138 
139 static 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 
158 static int nsim_fib_rule_event(struct nsim_fib_data *data,
159 			       struct fib_notifier_info *info, bool add)
160 {
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 
176 static 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 
195 static 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 
207 static void nsim_fib_rt_fini(struct nsim_fib_rt *fib_rt)
208 {
209 	list_del(&fib_rt->list);
210 }
211 
212 static 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 
228 static struct nsim_fib4_rt *
229 nsim_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 
249 static 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 
256 static struct nsim_fib4_rt *
257 nsim_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 
271 static 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 
290 static 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 
313 err_fib_dismiss:
314 	nsim_fib_account(&data->ipv4.fib, false, extack);
315 	return err;
316 }
317 
318 static 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 
344 static 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 
367 static 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 
383 static 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 
411 static struct nsim_fib6_rt_nh *
412 nsim_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 
425 static 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 
442 static 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 
459 static struct nsim_fib6_rt *
460 nsim_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)
470 		return ERR_PTR(-ENOMEM);
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 
502 err_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);
507 err_fib_rt_fini:
508 	nsim_fib_rt_fini(&fib6_rt->common);
509 	kfree(fib6_rt);
510 	return ERR_PTR(err);
511 }
512 
513 static 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 
524 static struct nsim_fib6_rt *
525 nsim_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 
539 static 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 
573 err_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 
584 static 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 
593 static 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 
615 err_fib_dismiss:
616 	nsim_fib_account(&data->ipv6.fib, false, extack);
617 	return err;
618 }
619 
620 static 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 
645 static 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);
653 	if (IS_ERR(fib6_rt))
654 		return PTR_ERR(fib6_rt);
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 
668 static void
669 nsim_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 
698 static 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 
734 static int nsim_fib_event(struct nsim_fib_data *data,
735 			  struct fib_notifier_info *info, unsigned long event)
736 {
737 	int err = 0;
738 
739 	switch (info->family) {
740 	case AF_INET:
741 		err = nsim_fib4_event(data, info, event);
742 		break;
743 	case AF_INET6:
744 		err = nsim_fib6_event(data, info, event);
745 		break;
746 	}
747 
748 	return err;
749 }
750 
751 static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
752 			     void *ptr)
753 {
754 	struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
755 						  fib_nb);
756 	struct fib_notifier_info *info = ptr;
757 	int err = 0;
758 
759 	/* IPv6 routes can be added via RAs from softIRQ. */
760 	spin_lock_bh(&data->fib_lock);
761 
762 	switch (event) {
763 	case FIB_EVENT_RULE_ADD: /* fall through */
764 	case FIB_EVENT_RULE_DEL:
765 		err = nsim_fib_rule_event(data, info,
766 					  event == FIB_EVENT_RULE_ADD);
767 		break;
768 
769 	case FIB_EVENT_ENTRY_REPLACE:  /* fall through */
770 	case FIB_EVENT_ENTRY_APPEND:  /* fall through */
771 	case FIB_EVENT_ENTRY_DEL:
772 		err = nsim_fib_event(data, info, event);
773 		break;
774 	}
775 
776 	spin_unlock_bh(&data->fib_lock);
777 
778 	return notifier_from_errno(err);
779 }
780 
781 static 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 
793 static 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 
804 static 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 
821 /* inconsistent dump, trying again */
822 static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
823 {
824 	struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
825 						  fib_nb);
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 	}
836 
837 	data->ipv4.rules.num = 0ULL;
838 	data->ipv6.rules.num = 0ULL;
839 }
840 
841 static u64 nsim_fib_ipv4_resource_occ_get(void *priv)
842 {
843 	struct nsim_fib_data *data = priv;
844 
845 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false);
846 }
847 
848 static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv)
849 {
850 	struct nsim_fib_data *data = priv;
851 
852 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false);
853 }
854 
855 static u64 nsim_fib_ipv6_resource_occ_get(void *priv)
856 {
857 	struct nsim_fib_data *data = priv;
858 
859 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false);
860 }
861 
862 static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv)
863 {
864 	struct nsim_fib_data *data = priv;
865 
866 	return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false);
867 }
868 
869 static 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 
889 struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
890 				      struct netlink_ext_ack *extack)
891 {
892 	struct nsim_fib_data *data;
893 	int err;
894 
895 	data = kzalloc(sizeof(*data), GFP_KERNEL);
896 	if (!data)
897 		return ERR_PTR(-ENOMEM);
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;
905 
906 	nsim_fib_set_max_all(data, devlink);
907 
908 	data->fib_nb.notifier_call = nsim_fib_event_nb;
909 	err = register_fib_notifier(devlink_net(devlink), &data->fib_nb,
910 				    nsim_fib_dump_inconsistent, extack);
911 	if (err) {
912 		pr_err("Failed to register fib notifier\n");
913 		goto err_rhashtable_destroy;
914 	}
915 
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 
934 err_rhashtable_destroy:
935 	rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free,
936 				    data);
937 err_data_free:
938 	kfree(data);
939 	return ERR_PTR(err);
940 }
941 
942 void 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);
952 	unregister_fib_notifier(devlink_net(devlink), &data->fib_nb);
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));
956 	kfree(data);
957 }
958