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/bitmap.h> |
18 | #include <linux/in6.h> |
19 | #include <linux/kernel.h> |
20 | #include <linux/list.h> |
21 | #include <linux/rhashtable.h> |
22 | #include <linux/spinlock_types.h> |
23 | #include <linux/types.h> |
24 | #include <net/fib_notifier.h> |
25 | #include <net/inet_dscp.h> |
26 | #include <net/ip_fib.h> |
27 | #include <net/ip6_fib.h> |
28 | #include <net/fib_rules.h> |
29 | #include <net/net_namespace.h> |
30 | #include <net/nexthop.h> |
31 | #include <linux/debugfs.h> |
32 | |
33 | #include "netdevsim.h" |
34 | |
35 | struct nsim_fib_entry { |
36 | u64 max; |
37 | atomic64_t num; |
38 | }; |
39 | |
40 | struct nsim_per_fib_data { |
41 | struct nsim_fib_entry fib; |
42 | struct nsim_fib_entry rules; |
43 | }; |
44 | |
45 | struct nsim_fib_data { |
46 | struct notifier_block fib_nb; |
47 | struct nsim_per_fib_data ipv4; |
48 | struct nsim_per_fib_data ipv6; |
49 | struct nsim_fib_entry nexthops; |
50 | struct rhashtable fib_rt_ht; |
51 | struct list_head fib_rt_list; |
52 | struct mutex fib_lock; /* Protects FIB HT and list */ |
53 | struct notifier_block nexthop_nb; |
54 | struct rhashtable nexthop_ht; |
55 | struct devlink *devlink; |
56 | struct work_struct fib_event_work; |
57 | struct work_struct fib_flush_work; |
58 | struct list_head fib_event_queue; |
59 | spinlock_t fib_event_queue_lock; /* Protects fib event queue list */ |
60 | struct mutex nh_lock; /* Protects NH HT */ |
61 | struct dentry *ddir; |
62 | bool fail_route_offload; |
63 | bool fail_res_nexthop_group_replace; |
64 | bool fail_nexthop_bucket_replace; |
65 | bool fail_route_delete; |
66 | }; |
67 | |
68 | struct nsim_fib_rt_key { |
69 | unsigned char addr[sizeof(struct in6_addr)]; |
70 | unsigned char prefix_len; |
71 | int family; |
72 | u32 tb_id; |
73 | }; |
74 | |
75 | struct nsim_fib_rt { |
76 | struct nsim_fib_rt_key key; |
77 | struct rhash_head ht_node; |
78 | struct list_head list; /* Member of fib_rt_list */ |
79 | }; |
80 | |
81 | struct nsim_fib4_rt { |
82 | struct nsim_fib_rt common; |
83 | struct fib_info *fi; |
84 | dscp_t dscp; |
85 | u8 type; |
86 | }; |
87 | |
88 | struct nsim_fib6_rt { |
89 | struct nsim_fib_rt common; |
90 | struct list_head nh_list; |
91 | unsigned int nhs; |
92 | }; |
93 | |
94 | struct nsim_fib6_rt_nh { |
95 | struct list_head list; /* Member of nh_list */ |
96 | struct fib6_info *rt; |
97 | }; |
98 | |
99 | struct nsim_fib6_event { |
100 | struct fib6_info **rt_arr; |
101 | unsigned int nrt6; |
102 | }; |
103 | |
104 | struct nsim_fib_event { |
105 | struct list_head list; /* node in fib queue */ |
106 | union { |
107 | struct fib_entry_notifier_info fen_info; |
108 | struct nsim_fib6_event fib6_event; |
109 | }; |
110 | struct nsim_fib_data *data; |
111 | unsigned long event; |
112 | int family; |
113 | }; |
114 | |
115 | static const struct rhashtable_params nsim_fib_rt_ht_params = { |
116 | .key_offset = offsetof(struct nsim_fib_rt, key), |
117 | .head_offset = offsetof(struct nsim_fib_rt, ht_node), |
118 | .key_len = sizeof(struct nsim_fib_rt_key), |
119 | .automatic_shrinking = true, |
120 | }; |
121 | |
122 | struct nsim_nexthop { |
123 | struct rhash_head ht_node; |
124 | u64 occ; |
125 | u32 id; |
126 | bool is_resilient; |
127 | }; |
128 | |
129 | static const struct rhashtable_params nsim_nexthop_ht_params = { |
130 | .key_offset = offsetof(struct nsim_nexthop, id), |
131 | .head_offset = offsetof(struct nsim_nexthop, ht_node), |
132 | .key_len = sizeof(u32), |
133 | .automatic_shrinking = true, |
134 | }; |
135 | |
136 | u64 nsim_fib_get_val(struct nsim_fib_data *fib_data, |
137 | enum nsim_resource_id res_id, bool max) |
138 | { |
139 | struct nsim_fib_entry *entry; |
140 | |
141 | switch (res_id) { |
142 | case NSIM_RESOURCE_IPV4_FIB: |
143 | entry = &fib_data->ipv4.fib; |
144 | break; |
145 | case NSIM_RESOURCE_IPV4_FIB_RULES: |
146 | entry = &fib_data->ipv4.rules; |
147 | break; |
148 | case NSIM_RESOURCE_IPV6_FIB: |
149 | entry = &fib_data->ipv6.fib; |
150 | break; |
151 | case NSIM_RESOURCE_IPV6_FIB_RULES: |
152 | entry = &fib_data->ipv6.rules; |
153 | break; |
154 | case NSIM_RESOURCE_NEXTHOPS: |
155 | entry = &fib_data->nexthops; |
156 | break; |
157 | default: |
158 | return 0; |
159 | } |
160 | |
161 | return max ? entry->max : atomic64_read(v: &entry->num); |
162 | } |
163 | |
164 | static void nsim_fib_set_max(struct nsim_fib_data *fib_data, |
165 | enum nsim_resource_id res_id, u64 val) |
166 | { |
167 | struct nsim_fib_entry *entry; |
168 | |
169 | switch (res_id) { |
170 | case NSIM_RESOURCE_IPV4_FIB: |
171 | entry = &fib_data->ipv4.fib; |
172 | break; |
173 | case NSIM_RESOURCE_IPV4_FIB_RULES: |
174 | entry = &fib_data->ipv4.rules; |
175 | break; |
176 | case NSIM_RESOURCE_IPV6_FIB: |
177 | entry = &fib_data->ipv6.fib; |
178 | break; |
179 | case NSIM_RESOURCE_IPV6_FIB_RULES: |
180 | entry = &fib_data->ipv6.rules; |
181 | break; |
182 | case NSIM_RESOURCE_NEXTHOPS: |
183 | entry = &fib_data->nexthops; |
184 | break; |
185 | default: |
186 | WARN_ON(1); |
187 | return; |
188 | } |
189 | entry->max = val; |
190 | } |
191 | |
192 | static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add, |
193 | struct netlink_ext_ack *extack) |
194 | { |
195 | int err = 0; |
196 | |
197 | if (add) { |
198 | if (!atomic64_add_unless(v: &entry->num, a: 1, u: entry->max)) { |
199 | err = -ENOSPC; |
200 | NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries" ); |
201 | } |
202 | } else { |
203 | atomic64_dec_if_positive(v: &entry->num); |
204 | } |
205 | |
206 | return err; |
207 | } |
208 | |
209 | static int nsim_fib_rule_event(struct nsim_fib_data *data, |
210 | struct fib_notifier_info *info, bool add) |
211 | { |
212 | struct netlink_ext_ack *extack = info->extack; |
213 | int err = 0; |
214 | |
215 | switch (info->family) { |
216 | case AF_INET: |
217 | err = nsim_fib_rule_account(entry: &data->ipv4.rules, add, extack); |
218 | break; |
219 | case AF_INET6: |
220 | err = nsim_fib_rule_account(entry: &data->ipv6.rules, add, extack); |
221 | break; |
222 | } |
223 | |
224 | return err; |
225 | } |
226 | |
227 | static int nsim_fib_account(struct nsim_fib_entry *entry, bool add) |
228 | { |
229 | int err = 0; |
230 | |
231 | if (add) { |
232 | if (!atomic64_add_unless(v: &entry->num, a: 1, u: entry->max)) |
233 | err = -ENOSPC; |
234 | } else { |
235 | atomic64_dec_if_positive(v: &entry->num); |
236 | } |
237 | |
238 | return err; |
239 | } |
240 | |
241 | static void nsim_fib_rt_init(struct nsim_fib_data *data, |
242 | struct nsim_fib_rt *fib_rt, const void *addr, |
243 | size_t addr_len, unsigned int prefix_len, |
244 | int family, u32 tb_id) |
245 | { |
246 | memcpy(fib_rt->key.addr, addr, addr_len); |
247 | fib_rt->key.prefix_len = prefix_len; |
248 | fib_rt->key.family = family; |
249 | fib_rt->key.tb_id = tb_id; |
250 | list_add(new: &fib_rt->list, head: &data->fib_rt_list); |
251 | } |
252 | |
253 | static void nsim_fib_rt_fini(struct nsim_fib_rt *fib_rt) |
254 | { |
255 | list_del(entry: &fib_rt->list); |
256 | } |
257 | |
258 | static struct nsim_fib_rt *nsim_fib_rt_lookup(struct rhashtable *fib_rt_ht, |
259 | const void *addr, size_t addr_len, |
260 | unsigned int prefix_len, |
261 | int family, u32 tb_id) |
262 | { |
263 | struct nsim_fib_rt_key key; |
264 | |
265 | memset(&key, 0, sizeof(key)); |
266 | memcpy(key.addr, addr, addr_len); |
267 | key.prefix_len = prefix_len; |
268 | key.family = family; |
269 | key.tb_id = tb_id; |
270 | |
271 | return rhashtable_lookup_fast(ht: fib_rt_ht, key: &key, params: nsim_fib_rt_ht_params); |
272 | } |
273 | |
274 | static struct nsim_fib4_rt * |
275 | nsim_fib4_rt_create(struct nsim_fib_data *data, |
276 | struct fib_entry_notifier_info *fen_info) |
277 | { |
278 | struct nsim_fib4_rt *fib4_rt; |
279 | |
280 | fib4_rt = kzalloc(size: sizeof(*fib4_rt), GFP_KERNEL); |
281 | if (!fib4_rt) |
282 | return NULL; |
283 | |
284 | nsim_fib_rt_init(data, fib_rt: &fib4_rt->common, addr: &fen_info->dst, addr_len: sizeof(u32), |
285 | prefix_len: fen_info->dst_len, AF_INET, tb_id: fen_info->tb_id); |
286 | |
287 | fib4_rt->fi = fen_info->fi; |
288 | fib_info_hold(fi: fib4_rt->fi); |
289 | fib4_rt->dscp = fen_info->dscp; |
290 | fib4_rt->type = fen_info->type; |
291 | |
292 | return fib4_rt; |
293 | } |
294 | |
295 | static void nsim_fib4_rt_destroy(struct nsim_fib4_rt *fib4_rt) |
296 | { |
297 | fib_info_put(fi: fib4_rt->fi); |
298 | nsim_fib_rt_fini(fib_rt: &fib4_rt->common); |
299 | kfree(objp: fib4_rt); |
300 | } |
301 | |
302 | static struct nsim_fib4_rt * |
303 | nsim_fib4_rt_lookup(struct rhashtable *fib_rt_ht, |
304 | const struct fib_entry_notifier_info *fen_info) |
305 | { |
306 | struct nsim_fib_rt *fib_rt; |
307 | |
308 | fib_rt = nsim_fib_rt_lookup(fib_rt_ht, addr: &fen_info->dst, addr_len: sizeof(u32), |
309 | prefix_len: fen_info->dst_len, AF_INET, |
310 | tb_id: fen_info->tb_id); |
311 | if (!fib_rt) |
312 | return NULL; |
313 | |
314 | return container_of(fib_rt, struct nsim_fib4_rt, common); |
315 | } |
316 | |
317 | static void |
318 | nsim_fib4_rt_offload_failed_flag_set(struct net *net, |
319 | struct fib_entry_notifier_info *fen_info) |
320 | { |
321 | u32 *p_dst = (u32 *)&fen_info->dst; |
322 | struct fib_rt_info fri; |
323 | |
324 | fri.fi = fen_info->fi; |
325 | fri.tb_id = fen_info->tb_id; |
326 | fri.dst = cpu_to_be32(*p_dst); |
327 | fri.dst_len = fen_info->dst_len; |
328 | fri.dscp = fen_info->dscp; |
329 | fri.type = fen_info->type; |
330 | fri.offload = false; |
331 | fri.trap = false; |
332 | fri.offload_failed = true; |
333 | fib_alias_hw_flags_set(net, fri: &fri); |
334 | } |
335 | |
336 | static void nsim_fib4_rt_hw_flags_set(struct net *net, |
337 | const struct nsim_fib4_rt *fib4_rt, |
338 | bool trap) |
339 | { |
340 | u32 *p_dst = (u32 *) fib4_rt->common.key.addr; |
341 | int dst_len = fib4_rt->common.key.prefix_len; |
342 | struct fib_rt_info fri; |
343 | |
344 | fri.fi = fib4_rt->fi; |
345 | fri.tb_id = fib4_rt->common.key.tb_id; |
346 | fri.dst = cpu_to_be32(*p_dst); |
347 | fri.dst_len = dst_len; |
348 | fri.dscp = fib4_rt->dscp; |
349 | fri.type = fib4_rt->type; |
350 | fri.offload = false; |
351 | fri.trap = trap; |
352 | fri.offload_failed = false; |
353 | fib_alias_hw_flags_set(net, fri: &fri); |
354 | } |
355 | |
356 | static int nsim_fib4_rt_add(struct nsim_fib_data *data, |
357 | struct nsim_fib4_rt *fib4_rt) |
358 | { |
359 | struct net *net = devlink_net(devlink: data->devlink); |
360 | int err; |
361 | |
362 | err = rhashtable_insert_fast(ht: &data->fib_rt_ht, |
363 | obj: &fib4_rt->common.ht_node, |
364 | params: nsim_fib_rt_ht_params); |
365 | if (err) |
366 | goto err_fib_dismiss; |
367 | |
368 | /* Simulate hardware programming latency. */ |
369 | msleep(msecs: 1); |
370 | nsim_fib4_rt_hw_flags_set(net, fib4_rt, trap: true); |
371 | |
372 | return 0; |
373 | |
374 | err_fib_dismiss: |
375 | /* Drop the accounting that was increased from the notification |
376 | * context when FIB_EVENT_ENTRY_REPLACE was triggered. |
377 | */ |
378 | nsim_fib_account(entry: &data->ipv4.fib, add: false); |
379 | return err; |
380 | } |
381 | |
382 | static int nsim_fib4_rt_replace(struct nsim_fib_data *data, |
383 | struct nsim_fib4_rt *fib4_rt, |
384 | struct nsim_fib4_rt *fib4_rt_old) |
385 | { |
386 | struct net *net = devlink_net(devlink: data->devlink); |
387 | int err; |
388 | |
389 | /* We are replacing a route, so need to remove the accounting which |
390 | * was increased when FIB_EVENT_ENTRY_REPLACE was triggered. |
391 | */ |
392 | err = nsim_fib_account(entry: &data->ipv4.fib, add: false); |
393 | if (err) |
394 | return err; |
395 | err = rhashtable_replace_fast(ht: &data->fib_rt_ht, |
396 | obj_old: &fib4_rt_old->common.ht_node, |
397 | obj_new: &fib4_rt->common.ht_node, |
398 | params: nsim_fib_rt_ht_params); |
399 | if (err) |
400 | return err; |
401 | |
402 | msleep(msecs: 1); |
403 | nsim_fib4_rt_hw_flags_set(net, fib4_rt, trap: true); |
404 | |
405 | nsim_fib4_rt_hw_flags_set(net, fib4_rt: fib4_rt_old, trap: false); |
406 | nsim_fib4_rt_destroy(fib4_rt: fib4_rt_old); |
407 | |
408 | return 0; |
409 | } |
410 | |
411 | static int nsim_fib4_rt_insert(struct nsim_fib_data *data, |
412 | struct fib_entry_notifier_info *fen_info) |
413 | { |
414 | struct nsim_fib4_rt *fib4_rt, *fib4_rt_old; |
415 | int err; |
416 | |
417 | if (data->fail_route_offload) { |
418 | /* For testing purposes, user set debugfs fail_route_offload |
419 | * value to true. Simulate hardware programming latency and then |
420 | * fail. |
421 | */ |
422 | msleep(msecs: 1); |
423 | return -EINVAL; |
424 | } |
425 | |
426 | fib4_rt = nsim_fib4_rt_create(data, fen_info); |
427 | if (!fib4_rt) |
428 | return -ENOMEM; |
429 | |
430 | fib4_rt_old = nsim_fib4_rt_lookup(fib_rt_ht: &data->fib_rt_ht, fen_info); |
431 | if (!fib4_rt_old) |
432 | err = nsim_fib4_rt_add(data, fib4_rt); |
433 | else |
434 | err = nsim_fib4_rt_replace(data, fib4_rt, fib4_rt_old); |
435 | |
436 | if (err) |
437 | nsim_fib4_rt_destroy(fib4_rt); |
438 | |
439 | return err; |
440 | } |
441 | |
442 | static void nsim_fib4_rt_remove(struct nsim_fib_data *data, |
443 | const struct fib_entry_notifier_info *fen_info) |
444 | { |
445 | struct nsim_fib4_rt *fib4_rt; |
446 | |
447 | fib4_rt = nsim_fib4_rt_lookup(fib_rt_ht: &data->fib_rt_ht, fen_info); |
448 | if (!fib4_rt) |
449 | return; |
450 | |
451 | rhashtable_remove_fast(ht: &data->fib_rt_ht, obj: &fib4_rt->common.ht_node, |
452 | params: nsim_fib_rt_ht_params); |
453 | nsim_fib4_rt_destroy(fib4_rt); |
454 | } |
455 | |
456 | static int nsim_fib4_event(struct nsim_fib_data *data, |
457 | struct fib_entry_notifier_info *fen_info, |
458 | unsigned long event) |
459 | { |
460 | int err = 0; |
461 | |
462 | switch (event) { |
463 | case FIB_EVENT_ENTRY_REPLACE: |
464 | err = nsim_fib4_rt_insert(data, fen_info); |
465 | if (err) { |
466 | struct net *net = devlink_net(devlink: data->devlink); |
467 | |
468 | nsim_fib4_rt_offload_failed_flag_set(net, fen_info); |
469 | } |
470 | break; |
471 | case FIB_EVENT_ENTRY_DEL: |
472 | nsim_fib4_rt_remove(data, fen_info); |
473 | break; |
474 | default: |
475 | break; |
476 | } |
477 | |
478 | return err; |
479 | } |
480 | |
481 | static struct nsim_fib6_rt_nh * |
482 | nsim_fib6_rt_nh_find(const struct nsim_fib6_rt *fib6_rt, |
483 | const struct fib6_info *rt) |
484 | { |
485 | struct nsim_fib6_rt_nh *fib6_rt_nh; |
486 | |
487 | list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list) { |
488 | if (fib6_rt_nh->rt == rt) |
489 | return fib6_rt_nh; |
490 | } |
491 | |
492 | return NULL; |
493 | } |
494 | |
495 | static int nsim_fib6_rt_nh_add(struct nsim_fib6_rt *fib6_rt, |
496 | struct fib6_info *rt) |
497 | { |
498 | struct nsim_fib6_rt_nh *fib6_rt_nh; |
499 | |
500 | fib6_rt_nh = kzalloc(size: sizeof(*fib6_rt_nh), GFP_KERNEL); |
501 | if (!fib6_rt_nh) |
502 | return -ENOMEM; |
503 | |
504 | fib6_info_hold(f6i: rt); |
505 | fib6_rt_nh->rt = rt; |
506 | list_add_tail(new: &fib6_rt_nh->list, head: &fib6_rt->nh_list); |
507 | fib6_rt->nhs++; |
508 | |
509 | return 0; |
510 | } |
511 | |
512 | #if IS_ENABLED(CONFIG_IPV6) |
513 | static void nsim_rt6_release(struct fib6_info *rt) |
514 | { |
515 | fib6_info_release(f6i: rt); |
516 | } |
517 | #else |
518 | static void nsim_rt6_release(struct fib6_info *rt) |
519 | { |
520 | } |
521 | #endif |
522 | |
523 | static void nsim_fib6_rt_nh_del(struct nsim_fib6_rt *fib6_rt, |
524 | const struct fib6_info *rt) |
525 | { |
526 | struct nsim_fib6_rt_nh *fib6_rt_nh; |
527 | |
528 | fib6_rt_nh = nsim_fib6_rt_nh_find(fib6_rt, rt); |
529 | if (!fib6_rt_nh) |
530 | return; |
531 | |
532 | fib6_rt->nhs--; |
533 | list_del(entry: &fib6_rt_nh->list); |
534 | nsim_rt6_release(rt: fib6_rt_nh->rt); |
535 | kfree(objp: fib6_rt_nh); |
536 | } |
537 | |
538 | static struct nsim_fib6_rt * |
539 | nsim_fib6_rt_create(struct nsim_fib_data *data, |
540 | struct fib6_info **rt_arr, unsigned int nrt6) |
541 | { |
542 | struct fib6_info *rt = rt_arr[0]; |
543 | struct nsim_fib6_rt *fib6_rt; |
544 | int i = 0; |
545 | int err; |
546 | |
547 | fib6_rt = kzalloc(size: sizeof(*fib6_rt), GFP_KERNEL); |
548 | if (!fib6_rt) |
549 | return ERR_PTR(error: -ENOMEM); |
550 | |
551 | nsim_fib_rt_init(data, fib_rt: &fib6_rt->common, addr: &rt->fib6_dst.addr, |
552 | addr_len: sizeof(rt->fib6_dst.addr), prefix_len: rt->fib6_dst.plen, AF_INET6, |
553 | tb_id: rt->fib6_table->tb6_id); |
554 | |
555 | /* We consider a multipath IPv6 route as one entry, but it can be made |
556 | * up from several fib6_info structs (one for each nexthop), so we |
557 | * add them all to the same list under the entry. |
558 | */ |
559 | INIT_LIST_HEAD(list: &fib6_rt->nh_list); |
560 | |
561 | for (i = 0; i < nrt6; i++) { |
562 | err = nsim_fib6_rt_nh_add(fib6_rt, rt: rt_arr[i]); |
563 | if (err) |
564 | goto err_fib6_rt_nh_del; |
565 | } |
566 | |
567 | return fib6_rt; |
568 | |
569 | err_fib6_rt_nh_del: |
570 | for (i--; i >= 0; i--) { |
571 | nsim_fib6_rt_nh_del(fib6_rt, rt: rt_arr[i]); |
572 | } |
573 | nsim_fib_rt_fini(fib_rt: &fib6_rt->common); |
574 | kfree(objp: fib6_rt); |
575 | return ERR_PTR(error: err); |
576 | } |
577 | |
578 | static void nsim_fib6_rt_destroy(struct nsim_fib6_rt *fib6_rt) |
579 | { |
580 | struct nsim_fib6_rt_nh *iter, *tmp; |
581 | |
582 | list_for_each_entry_safe(iter, tmp, &fib6_rt->nh_list, list) |
583 | nsim_fib6_rt_nh_del(fib6_rt, rt: iter->rt); |
584 | WARN_ON_ONCE(!list_empty(&fib6_rt->nh_list)); |
585 | nsim_fib_rt_fini(fib_rt: &fib6_rt->common); |
586 | kfree(objp: fib6_rt); |
587 | } |
588 | |
589 | static struct nsim_fib6_rt * |
590 | nsim_fib6_rt_lookup(struct rhashtable *fib_rt_ht, const struct fib6_info *rt) |
591 | { |
592 | struct nsim_fib_rt *fib_rt; |
593 | |
594 | fib_rt = nsim_fib_rt_lookup(fib_rt_ht, addr: &rt->fib6_dst.addr, |
595 | addr_len: sizeof(rt->fib6_dst.addr), |
596 | prefix_len: rt->fib6_dst.plen, AF_INET6, |
597 | tb_id: rt->fib6_table->tb6_id); |
598 | if (!fib_rt) |
599 | return NULL; |
600 | |
601 | return container_of(fib_rt, struct nsim_fib6_rt, common); |
602 | } |
603 | |
604 | static int nsim_fib6_rt_append(struct nsim_fib_data *data, |
605 | struct nsim_fib6_event *fib6_event) |
606 | { |
607 | struct fib6_info *rt = fib6_event->rt_arr[0]; |
608 | struct nsim_fib6_rt *fib6_rt; |
609 | int i, err; |
610 | |
611 | if (data->fail_route_offload) { |
612 | /* For testing purposes, user set debugfs fail_route_offload |
613 | * value to true. Simulate hardware programming latency and then |
614 | * fail. |
615 | */ |
616 | msleep(msecs: 1); |
617 | return -EINVAL; |
618 | } |
619 | |
620 | fib6_rt = nsim_fib6_rt_lookup(fib_rt_ht: &data->fib_rt_ht, rt); |
621 | if (!fib6_rt) |
622 | return -EINVAL; |
623 | |
624 | for (i = 0; i < fib6_event->nrt6; i++) { |
625 | err = nsim_fib6_rt_nh_add(fib6_rt, rt: fib6_event->rt_arr[i]); |
626 | if (err) |
627 | goto err_fib6_rt_nh_del; |
628 | |
629 | WRITE_ONCE(fib6_event->rt_arr[i]->trap, true); |
630 | } |
631 | |
632 | return 0; |
633 | |
634 | err_fib6_rt_nh_del: |
635 | for (i--; i >= 0; i--) { |
636 | WRITE_ONCE(fib6_event->rt_arr[i]->trap, false); |
637 | nsim_fib6_rt_nh_del(fib6_rt, rt: fib6_event->rt_arr[i]); |
638 | } |
639 | return err; |
640 | } |
641 | |
642 | #if IS_ENABLED(CONFIG_IPV6) |
643 | static void nsim_fib6_rt_offload_failed_flag_set(struct nsim_fib_data *data, |
644 | struct fib6_info **rt_arr, |
645 | unsigned int nrt6) |
646 | |
647 | { |
648 | struct net *net = devlink_net(devlink: data->devlink); |
649 | int i; |
650 | |
651 | for (i = 0; i < nrt6; i++) |
652 | fib6_info_hw_flags_set(net, f6i: rt_arr[i], offload: false, trap: false, offload_failed: true); |
653 | } |
654 | #else |
655 | static void nsim_fib6_rt_offload_failed_flag_set(struct nsim_fib_data *data, |
656 | struct fib6_info **rt_arr, |
657 | unsigned int nrt6) |
658 | { |
659 | } |
660 | #endif |
661 | |
662 | #if IS_ENABLED(CONFIG_IPV6) |
663 | static void nsim_fib6_rt_hw_flags_set(struct nsim_fib_data *data, |
664 | const struct nsim_fib6_rt *fib6_rt, |
665 | bool trap) |
666 | { |
667 | struct net *net = devlink_net(devlink: data->devlink); |
668 | struct nsim_fib6_rt_nh *fib6_rt_nh; |
669 | |
670 | list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list) |
671 | fib6_info_hw_flags_set(net, f6i: fib6_rt_nh->rt, offload: false, trap, offload_failed: false); |
672 | } |
673 | #else |
674 | static void nsim_fib6_rt_hw_flags_set(struct nsim_fib_data *data, |
675 | const struct nsim_fib6_rt *fib6_rt, |
676 | bool trap) |
677 | { |
678 | } |
679 | #endif |
680 | |
681 | static int nsim_fib6_rt_add(struct nsim_fib_data *data, |
682 | struct nsim_fib6_rt *fib6_rt) |
683 | { |
684 | int err; |
685 | |
686 | err = rhashtable_insert_fast(ht: &data->fib_rt_ht, |
687 | obj: &fib6_rt->common.ht_node, |
688 | params: nsim_fib_rt_ht_params); |
689 | |
690 | if (err) |
691 | goto err_fib_dismiss; |
692 | |
693 | msleep(msecs: 1); |
694 | nsim_fib6_rt_hw_flags_set(data, fib6_rt, trap: true); |
695 | |
696 | return 0; |
697 | |
698 | err_fib_dismiss: |
699 | /* Drop the accounting that was increased from the notification |
700 | * context when FIB_EVENT_ENTRY_REPLACE was triggered. |
701 | */ |
702 | nsim_fib_account(entry: &data->ipv6.fib, add: false); |
703 | return err; |
704 | } |
705 | |
706 | static int nsim_fib6_rt_replace(struct nsim_fib_data *data, |
707 | struct nsim_fib6_rt *fib6_rt, |
708 | struct nsim_fib6_rt *fib6_rt_old) |
709 | { |
710 | int err; |
711 | |
712 | /* We are replacing a route, so need to remove the accounting which |
713 | * was increased when FIB_EVENT_ENTRY_REPLACE was triggered. |
714 | */ |
715 | err = nsim_fib_account(entry: &data->ipv6.fib, add: false); |
716 | if (err) |
717 | return err; |
718 | |
719 | err = rhashtable_replace_fast(ht: &data->fib_rt_ht, |
720 | obj_old: &fib6_rt_old->common.ht_node, |
721 | obj_new: &fib6_rt->common.ht_node, |
722 | params: nsim_fib_rt_ht_params); |
723 | |
724 | if (err) |
725 | return err; |
726 | |
727 | msleep(msecs: 1); |
728 | nsim_fib6_rt_hw_flags_set(data, fib6_rt, trap: true); |
729 | |
730 | nsim_fib6_rt_hw_flags_set(data, fib6_rt: fib6_rt_old, trap: false); |
731 | nsim_fib6_rt_destroy(fib6_rt: fib6_rt_old); |
732 | |
733 | return 0; |
734 | } |
735 | |
736 | static int nsim_fib6_rt_insert(struct nsim_fib_data *data, |
737 | struct nsim_fib6_event *fib6_event) |
738 | { |
739 | struct fib6_info *rt = fib6_event->rt_arr[0]; |
740 | struct nsim_fib6_rt *fib6_rt, *fib6_rt_old; |
741 | int err; |
742 | |
743 | if (data->fail_route_offload) { |
744 | /* For testing purposes, user set debugfs fail_route_offload |
745 | * value to true. Simulate hardware programming latency and then |
746 | * fail. |
747 | */ |
748 | msleep(msecs: 1); |
749 | return -EINVAL; |
750 | } |
751 | |
752 | fib6_rt = nsim_fib6_rt_create(data, rt_arr: fib6_event->rt_arr, |
753 | nrt6: fib6_event->nrt6); |
754 | if (IS_ERR(ptr: fib6_rt)) |
755 | return PTR_ERR(ptr: fib6_rt); |
756 | |
757 | fib6_rt_old = nsim_fib6_rt_lookup(fib_rt_ht: &data->fib_rt_ht, rt); |
758 | if (!fib6_rt_old) |
759 | err = nsim_fib6_rt_add(data, fib6_rt); |
760 | else |
761 | err = nsim_fib6_rt_replace(data, fib6_rt, fib6_rt_old); |
762 | |
763 | if (err) |
764 | nsim_fib6_rt_destroy(fib6_rt); |
765 | |
766 | return err; |
767 | } |
768 | |
769 | static void nsim_fib6_rt_remove(struct nsim_fib_data *data, |
770 | struct nsim_fib6_event *fib6_event) |
771 | { |
772 | struct fib6_info *rt = fib6_event->rt_arr[0]; |
773 | struct nsim_fib6_rt *fib6_rt; |
774 | int i; |
775 | |
776 | /* Multipath routes are first added to the FIB trie and only then |
777 | * notified. If we vetoed the addition, we will get a delete |
778 | * notification for a route we do not have. Therefore, do not warn if |
779 | * route was not found. |
780 | */ |
781 | fib6_rt = nsim_fib6_rt_lookup(fib_rt_ht: &data->fib_rt_ht, rt); |
782 | if (!fib6_rt) |
783 | return; |
784 | |
785 | /* If not all the nexthops are deleted, then only reduce the nexthop |
786 | * group. |
787 | */ |
788 | if (fib6_event->nrt6 != fib6_rt->nhs) { |
789 | for (i = 0; i < fib6_event->nrt6; i++) |
790 | nsim_fib6_rt_nh_del(fib6_rt, rt: fib6_event->rt_arr[i]); |
791 | return; |
792 | } |
793 | |
794 | rhashtable_remove_fast(ht: &data->fib_rt_ht, obj: &fib6_rt->common.ht_node, |
795 | params: nsim_fib_rt_ht_params); |
796 | nsim_fib6_rt_destroy(fib6_rt); |
797 | } |
798 | |
799 | static int nsim_fib6_event_init(struct nsim_fib6_event *fib6_event, |
800 | struct fib6_entry_notifier_info *fen6_info) |
801 | { |
802 | struct fib6_info *rt = fen6_info->rt; |
803 | struct fib6_info **rt_arr; |
804 | struct fib6_info *iter; |
805 | unsigned int nrt6; |
806 | int i = 0; |
807 | |
808 | nrt6 = fen6_info->nsiblings + 1; |
809 | |
810 | rt_arr = kcalloc(n: nrt6, size: sizeof(struct fib6_info *), GFP_ATOMIC); |
811 | if (!rt_arr) |
812 | return -ENOMEM; |
813 | |
814 | fib6_event->rt_arr = rt_arr; |
815 | fib6_event->nrt6 = nrt6; |
816 | |
817 | rt_arr[0] = rt; |
818 | fib6_info_hold(f6i: rt); |
819 | |
820 | if (!fen6_info->nsiblings) |
821 | return 0; |
822 | |
823 | list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) { |
824 | if (i == fen6_info->nsiblings) |
825 | break; |
826 | |
827 | rt_arr[i + 1] = iter; |
828 | fib6_info_hold(f6i: iter); |
829 | i++; |
830 | } |
831 | WARN_ON_ONCE(i != fen6_info->nsiblings); |
832 | |
833 | return 0; |
834 | } |
835 | |
836 | static void nsim_fib6_event_fini(struct nsim_fib6_event *fib6_event) |
837 | { |
838 | int i; |
839 | |
840 | for (i = 0; i < fib6_event->nrt6; i++) |
841 | nsim_rt6_release(rt: fib6_event->rt_arr[i]); |
842 | kfree(objp: fib6_event->rt_arr); |
843 | } |
844 | |
845 | static int nsim_fib6_event(struct nsim_fib_data *data, |
846 | struct nsim_fib6_event *fib6_event, |
847 | unsigned long event) |
848 | { |
849 | int err; |
850 | |
851 | if (fib6_event->rt_arr[0]->fib6_src.plen) |
852 | return 0; |
853 | |
854 | switch (event) { |
855 | case FIB_EVENT_ENTRY_REPLACE: |
856 | err = nsim_fib6_rt_insert(data, fib6_event); |
857 | if (err) |
858 | goto err_rt_offload_failed_flag_set; |
859 | break; |
860 | case FIB_EVENT_ENTRY_APPEND: |
861 | err = nsim_fib6_rt_append(data, fib6_event); |
862 | if (err) |
863 | goto err_rt_offload_failed_flag_set; |
864 | break; |
865 | case FIB_EVENT_ENTRY_DEL: |
866 | nsim_fib6_rt_remove(data, fib6_event); |
867 | break; |
868 | default: |
869 | break; |
870 | } |
871 | |
872 | return 0; |
873 | |
874 | err_rt_offload_failed_flag_set: |
875 | nsim_fib6_rt_offload_failed_flag_set(data, rt_arr: fib6_event->rt_arr, |
876 | nrt6: fib6_event->nrt6); |
877 | return err; |
878 | } |
879 | |
880 | static void nsim_fib_event(struct nsim_fib_event *fib_event) |
881 | { |
882 | switch (fib_event->family) { |
883 | case AF_INET: |
884 | nsim_fib4_event(data: fib_event->data, fen_info: &fib_event->fen_info, |
885 | event: fib_event->event); |
886 | fib_info_put(fi: fib_event->fen_info.fi); |
887 | break; |
888 | case AF_INET6: |
889 | nsim_fib6_event(data: fib_event->data, fib6_event: &fib_event->fib6_event, |
890 | event: fib_event->event); |
891 | nsim_fib6_event_fini(fib6_event: &fib_event->fib6_event); |
892 | break; |
893 | } |
894 | } |
895 | |
896 | static int nsim_fib4_prepare_event(struct fib_notifier_info *info, |
897 | struct nsim_fib_event *fib_event, |
898 | unsigned long event) |
899 | { |
900 | struct nsim_fib_data *data = fib_event->data; |
901 | struct fib_entry_notifier_info *fen_info; |
902 | struct netlink_ext_ack *extack; |
903 | int err = 0; |
904 | |
905 | fen_info = container_of(info, struct fib_entry_notifier_info, |
906 | info); |
907 | fib_event->fen_info = *fen_info; |
908 | extack = info->extack; |
909 | |
910 | switch (event) { |
911 | case FIB_EVENT_ENTRY_REPLACE: |
912 | err = nsim_fib_account(entry: &data->ipv4.fib, add: true); |
913 | if (err) { |
914 | NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries" ); |
915 | return err; |
916 | } |
917 | break; |
918 | case FIB_EVENT_ENTRY_DEL: |
919 | if (data->fail_route_delete) { |
920 | NL_SET_ERR_MSG_MOD(extack, "Failed to process route deletion" ); |
921 | return -EINVAL; |
922 | } |
923 | nsim_fib_account(entry: &data->ipv4.fib, add: false); |
924 | break; |
925 | } |
926 | |
927 | /* Take reference on fib_info to prevent it from being |
928 | * freed while event is queued. Release it afterwards. |
929 | */ |
930 | fib_info_hold(fi: fib_event->fen_info.fi); |
931 | |
932 | return 0; |
933 | } |
934 | |
935 | static int nsim_fib6_prepare_event(struct fib_notifier_info *info, |
936 | struct nsim_fib_event *fib_event, |
937 | unsigned long event) |
938 | { |
939 | struct nsim_fib_data *data = fib_event->data; |
940 | struct fib6_entry_notifier_info *fen6_info; |
941 | struct netlink_ext_ack *extack; |
942 | int err = 0; |
943 | |
944 | fen6_info = container_of(info, struct fib6_entry_notifier_info, |
945 | info); |
946 | |
947 | err = nsim_fib6_event_init(fib6_event: &fib_event->fib6_event, fen6_info); |
948 | if (err) |
949 | return err; |
950 | |
951 | extack = info->extack; |
952 | switch (event) { |
953 | case FIB_EVENT_ENTRY_REPLACE: |
954 | err = nsim_fib_account(entry: &data->ipv6.fib, add: true); |
955 | if (err) { |
956 | NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries" ); |
957 | goto err_fib6_event_fini; |
958 | } |
959 | break; |
960 | case FIB_EVENT_ENTRY_DEL: |
961 | if (data->fail_route_delete) { |
962 | err = -EINVAL; |
963 | NL_SET_ERR_MSG_MOD(extack, "Failed to process route deletion" ); |
964 | goto err_fib6_event_fini; |
965 | } |
966 | nsim_fib_account(entry: &data->ipv6.fib, add: false); |
967 | break; |
968 | } |
969 | |
970 | return 0; |
971 | |
972 | err_fib6_event_fini: |
973 | nsim_fib6_event_fini(fib6_event: &fib_event->fib6_event); |
974 | return err; |
975 | } |
976 | |
977 | static int nsim_fib_event_schedule_work(struct nsim_fib_data *data, |
978 | struct fib_notifier_info *info, |
979 | unsigned long event) |
980 | { |
981 | struct nsim_fib_event *fib_event; |
982 | int err; |
983 | |
984 | if (info->family != AF_INET && info->family != AF_INET6) |
985 | /* netdevsim does not support 'RTNL_FAMILY_IP6MR' and |
986 | * 'RTNL_FAMILY_IPMR' and should ignore them. |
987 | */ |
988 | return NOTIFY_DONE; |
989 | |
990 | fib_event = kzalloc(size: sizeof(*fib_event), GFP_ATOMIC); |
991 | if (!fib_event) |
992 | goto err_fib_event_alloc; |
993 | |
994 | fib_event->data = data; |
995 | fib_event->event = event; |
996 | fib_event->family = info->family; |
997 | |
998 | switch (info->family) { |
999 | case AF_INET: |
1000 | err = nsim_fib4_prepare_event(info, fib_event, event); |
1001 | break; |
1002 | case AF_INET6: |
1003 | err = nsim_fib6_prepare_event(info, fib_event, event); |
1004 | break; |
1005 | } |
1006 | |
1007 | if (err) |
1008 | goto err_fib_prepare_event; |
1009 | |
1010 | /* Enqueue the event and trigger the work */ |
1011 | spin_lock_bh(lock: &data->fib_event_queue_lock); |
1012 | list_add_tail(new: &fib_event->list, head: &data->fib_event_queue); |
1013 | spin_unlock_bh(lock: &data->fib_event_queue_lock); |
1014 | schedule_work(work: &data->fib_event_work); |
1015 | |
1016 | return NOTIFY_DONE; |
1017 | |
1018 | err_fib_prepare_event: |
1019 | kfree(objp: fib_event); |
1020 | err_fib_event_alloc: |
1021 | if (event == FIB_EVENT_ENTRY_DEL) |
1022 | schedule_work(work: &data->fib_flush_work); |
1023 | return NOTIFY_BAD; |
1024 | } |
1025 | |
1026 | static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event, |
1027 | void *ptr) |
1028 | { |
1029 | struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, |
1030 | fib_nb); |
1031 | struct fib_notifier_info *info = ptr; |
1032 | int err; |
1033 | |
1034 | switch (event) { |
1035 | case FIB_EVENT_RULE_ADD: |
1036 | case FIB_EVENT_RULE_DEL: |
1037 | err = nsim_fib_rule_event(data, info, |
1038 | add: event == FIB_EVENT_RULE_ADD); |
1039 | return notifier_from_errno(err); |
1040 | case FIB_EVENT_ENTRY_REPLACE: |
1041 | case FIB_EVENT_ENTRY_APPEND: |
1042 | case FIB_EVENT_ENTRY_DEL: |
1043 | return nsim_fib_event_schedule_work(data, info, event); |
1044 | } |
1045 | |
1046 | return NOTIFY_DONE; |
1047 | } |
1048 | |
1049 | static void nsim_fib4_rt_free(struct nsim_fib_rt *fib_rt, |
1050 | struct nsim_fib_data *data) |
1051 | { |
1052 | struct devlink *devlink = data->devlink; |
1053 | struct nsim_fib4_rt *fib4_rt; |
1054 | |
1055 | fib4_rt = container_of(fib_rt, struct nsim_fib4_rt, common); |
1056 | nsim_fib4_rt_hw_flags_set(net: devlink_net(devlink), fib4_rt, trap: false); |
1057 | nsim_fib_account(entry: &data->ipv4.fib, add: false); |
1058 | nsim_fib4_rt_destroy(fib4_rt); |
1059 | } |
1060 | |
1061 | static void nsim_fib6_rt_free(struct nsim_fib_rt *fib_rt, |
1062 | struct nsim_fib_data *data) |
1063 | { |
1064 | struct nsim_fib6_rt *fib6_rt; |
1065 | |
1066 | fib6_rt = container_of(fib_rt, struct nsim_fib6_rt, common); |
1067 | nsim_fib6_rt_hw_flags_set(data, fib6_rt, trap: false); |
1068 | nsim_fib_account(entry: &data->ipv6.fib, add: false); |
1069 | nsim_fib6_rt_destroy(fib6_rt); |
1070 | } |
1071 | |
1072 | static void nsim_fib_rt_free(void *ptr, void *arg) |
1073 | { |
1074 | struct nsim_fib_rt *fib_rt = ptr; |
1075 | struct nsim_fib_data *data = arg; |
1076 | |
1077 | switch (fib_rt->key.family) { |
1078 | case AF_INET: |
1079 | nsim_fib4_rt_free(fib_rt, data); |
1080 | break; |
1081 | case AF_INET6: |
1082 | nsim_fib6_rt_free(fib_rt, data); |
1083 | break; |
1084 | default: |
1085 | WARN_ON_ONCE(1); |
1086 | } |
1087 | } |
1088 | |
1089 | /* inconsistent dump, trying again */ |
1090 | static void nsim_fib_dump_inconsistent(struct notifier_block *nb) |
1091 | { |
1092 | struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, |
1093 | fib_nb); |
1094 | struct nsim_fib_rt *fib_rt, *fib_rt_tmp; |
1095 | |
1096 | /* Flush the work to make sure there is no race with notifications. */ |
1097 | flush_work(work: &data->fib_event_work); |
1098 | |
1099 | /* The notifier block is still not registered, so we do not need to |
1100 | * take any locks here. |
1101 | */ |
1102 | list_for_each_entry_safe(fib_rt, fib_rt_tmp, &data->fib_rt_list, list) { |
1103 | rhashtable_remove_fast(ht: &data->fib_rt_ht, obj: &fib_rt->ht_node, |
1104 | params: nsim_fib_rt_ht_params); |
1105 | nsim_fib_rt_free(ptr: fib_rt, arg: data); |
1106 | } |
1107 | |
1108 | atomic64_set(v: &data->ipv4.rules.num, i: 0ULL); |
1109 | atomic64_set(v: &data->ipv6.rules.num, i: 0ULL); |
1110 | } |
1111 | |
1112 | static struct nsim_nexthop *nsim_nexthop_create(struct nsim_fib_data *data, |
1113 | struct nh_notifier_info *info) |
1114 | { |
1115 | struct nsim_nexthop *nexthop; |
1116 | u64 occ = 0; |
1117 | int i; |
1118 | |
1119 | nexthop = kzalloc(size: sizeof(*nexthop), GFP_KERNEL); |
1120 | if (!nexthop) |
1121 | return ERR_PTR(error: -ENOMEM); |
1122 | |
1123 | nexthop->id = info->id; |
1124 | |
1125 | /* Determine the number of nexthop entries the new nexthop will |
1126 | * occupy. |
1127 | */ |
1128 | |
1129 | switch (info->type) { |
1130 | case NH_NOTIFIER_INFO_TYPE_SINGLE: |
1131 | occ = 1; |
1132 | break; |
1133 | case NH_NOTIFIER_INFO_TYPE_GRP: |
1134 | for (i = 0; i < info->nh_grp->num_nh; i++) |
1135 | occ += info->nh_grp->nh_entries[i].weight; |
1136 | break; |
1137 | case NH_NOTIFIER_INFO_TYPE_RES_TABLE: |
1138 | occ = info->nh_res_table->num_nh_buckets; |
1139 | nexthop->is_resilient = true; |
1140 | break; |
1141 | default: |
1142 | NL_SET_ERR_MSG_MOD(info->extack, "Unsupported nexthop type" ); |
1143 | kfree(objp: nexthop); |
1144 | return ERR_PTR(error: -EOPNOTSUPP); |
1145 | } |
1146 | |
1147 | nexthop->occ = occ; |
1148 | return nexthop; |
1149 | } |
1150 | |
1151 | static void nsim_nexthop_destroy(struct nsim_nexthop *nexthop) |
1152 | { |
1153 | kfree(objp: nexthop); |
1154 | } |
1155 | |
1156 | static int nsim_nexthop_account(struct nsim_fib_data *data, u64 occ, |
1157 | bool add, struct netlink_ext_ack *extack) |
1158 | { |
1159 | int i, err = 0; |
1160 | |
1161 | if (add) { |
1162 | for (i = 0; i < occ; i++) |
1163 | if (!atomic64_add_unless(v: &data->nexthops.num, a: 1, |
1164 | u: data->nexthops.max)) { |
1165 | err = -ENOSPC; |
1166 | NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported nexthops" ); |
1167 | goto err_num_decrease; |
1168 | } |
1169 | } else { |
1170 | if (WARN_ON(occ > atomic64_read(&data->nexthops.num))) |
1171 | return -EINVAL; |
1172 | atomic64_sub(i: occ, v: &data->nexthops.num); |
1173 | } |
1174 | |
1175 | return err; |
1176 | |
1177 | err_num_decrease: |
1178 | atomic64_sub(i, v: &data->nexthops.num); |
1179 | return err; |
1180 | |
1181 | } |
1182 | |
1183 | static void nsim_nexthop_hw_flags_set(struct net *net, |
1184 | const struct nsim_nexthop *nexthop, |
1185 | bool trap) |
1186 | { |
1187 | int i; |
1188 | |
1189 | nexthop_set_hw_flags(net, id: nexthop->id, offload: false, trap); |
1190 | |
1191 | if (!nexthop->is_resilient) |
1192 | return; |
1193 | |
1194 | for (i = 0; i < nexthop->occ; i++) |
1195 | nexthop_bucket_set_hw_flags(net, id: nexthop->id, bucket_index: i, offload: false, trap); |
1196 | } |
1197 | |
1198 | static int nsim_nexthop_add(struct nsim_fib_data *data, |
1199 | struct nsim_nexthop *nexthop, |
1200 | struct netlink_ext_ack *extack) |
1201 | { |
1202 | struct net *net = devlink_net(devlink: data->devlink); |
1203 | int err; |
1204 | |
1205 | err = nsim_nexthop_account(data, occ: nexthop->occ, add: true, extack); |
1206 | if (err) |
1207 | return err; |
1208 | |
1209 | err = rhashtable_insert_fast(ht: &data->nexthop_ht, obj: &nexthop->ht_node, |
1210 | params: nsim_nexthop_ht_params); |
1211 | if (err) { |
1212 | NL_SET_ERR_MSG_MOD(extack, "Failed to insert nexthop" ); |
1213 | goto err_nexthop_dismiss; |
1214 | } |
1215 | |
1216 | nsim_nexthop_hw_flags_set(net, nexthop, trap: true); |
1217 | |
1218 | return 0; |
1219 | |
1220 | err_nexthop_dismiss: |
1221 | nsim_nexthop_account(data, occ: nexthop->occ, add: false, extack); |
1222 | return err; |
1223 | } |
1224 | |
1225 | static int nsim_nexthop_replace(struct nsim_fib_data *data, |
1226 | struct nsim_nexthop *nexthop, |
1227 | struct nsim_nexthop *nexthop_old, |
1228 | struct netlink_ext_ack *extack) |
1229 | { |
1230 | struct net *net = devlink_net(devlink: data->devlink); |
1231 | int err; |
1232 | |
1233 | err = nsim_nexthop_account(data, occ: nexthop->occ, add: true, extack); |
1234 | if (err) |
1235 | return err; |
1236 | |
1237 | err = rhashtable_replace_fast(ht: &data->nexthop_ht, |
1238 | obj_old: &nexthop_old->ht_node, obj_new: &nexthop->ht_node, |
1239 | params: nsim_nexthop_ht_params); |
1240 | if (err) { |
1241 | NL_SET_ERR_MSG_MOD(extack, "Failed to replace nexthop" ); |
1242 | goto err_nexthop_dismiss; |
1243 | } |
1244 | |
1245 | nsim_nexthop_hw_flags_set(net, nexthop, trap: true); |
1246 | nsim_nexthop_account(data, occ: nexthop_old->occ, add: false, extack); |
1247 | nsim_nexthop_destroy(nexthop: nexthop_old); |
1248 | |
1249 | return 0; |
1250 | |
1251 | err_nexthop_dismiss: |
1252 | nsim_nexthop_account(data, occ: nexthop->occ, add: false, extack); |
1253 | return err; |
1254 | } |
1255 | |
1256 | static int nsim_nexthop_insert(struct nsim_fib_data *data, |
1257 | struct nh_notifier_info *info) |
1258 | { |
1259 | struct nsim_nexthop *nexthop, *nexthop_old; |
1260 | int err; |
1261 | |
1262 | nexthop = nsim_nexthop_create(data, info); |
1263 | if (IS_ERR(ptr: nexthop)) |
1264 | return PTR_ERR(ptr: nexthop); |
1265 | |
1266 | nexthop_old = rhashtable_lookup_fast(ht: &data->nexthop_ht, key: &info->id, |
1267 | params: nsim_nexthop_ht_params); |
1268 | if (!nexthop_old) |
1269 | err = nsim_nexthop_add(data, nexthop, extack: info->extack); |
1270 | else |
1271 | err = nsim_nexthop_replace(data, nexthop, nexthop_old, |
1272 | extack: info->extack); |
1273 | |
1274 | if (err) |
1275 | nsim_nexthop_destroy(nexthop); |
1276 | |
1277 | return err; |
1278 | } |
1279 | |
1280 | static void nsim_nexthop_remove(struct nsim_fib_data *data, |
1281 | struct nh_notifier_info *info) |
1282 | { |
1283 | struct nsim_nexthop *nexthop; |
1284 | |
1285 | nexthop = rhashtable_lookup_fast(ht: &data->nexthop_ht, key: &info->id, |
1286 | params: nsim_nexthop_ht_params); |
1287 | if (!nexthop) |
1288 | return; |
1289 | |
1290 | rhashtable_remove_fast(ht: &data->nexthop_ht, obj: &nexthop->ht_node, |
1291 | params: nsim_nexthop_ht_params); |
1292 | nsim_nexthop_account(data, occ: nexthop->occ, add: false, extack: info->extack); |
1293 | nsim_nexthop_destroy(nexthop); |
1294 | } |
1295 | |
1296 | static int nsim_nexthop_res_table_pre_replace(struct nsim_fib_data *data, |
1297 | struct nh_notifier_info *info) |
1298 | { |
1299 | if (data->fail_res_nexthop_group_replace) { |
1300 | NL_SET_ERR_MSG_MOD(info->extack, "Failed to replace a resilient nexthop group" ); |
1301 | return -EINVAL; |
1302 | } |
1303 | |
1304 | return 0; |
1305 | } |
1306 | |
1307 | static int nsim_nexthop_bucket_replace(struct nsim_fib_data *data, |
1308 | struct nh_notifier_info *info) |
1309 | { |
1310 | if (data->fail_nexthop_bucket_replace) { |
1311 | NL_SET_ERR_MSG_MOD(info->extack, "Failed to replace nexthop bucket" ); |
1312 | return -EINVAL; |
1313 | } |
1314 | |
1315 | nexthop_bucket_set_hw_flags(net: info->net, id: info->id, |
1316 | bucket_index: info->nh_res_bucket->bucket_index, |
1317 | offload: false, trap: true); |
1318 | |
1319 | return 0; |
1320 | } |
1321 | |
1322 | static int nsim_nexthop_event_nb(struct notifier_block *nb, unsigned long event, |
1323 | void *ptr) |
1324 | { |
1325 | struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data, |
1326 | nexthop_nb); |
1327 | struct nh_notifier_info *info = ptr; |
1328 | int err = 0; |
1329 | |
1330 | mutex_lock(&data->nh_lock); |
1331 | switch (event) { |
1332 | case NEXTHOP_EVENT_REPLACE: |
1333 | err = nsim_nexthop_insert(data, info); |
1334 | break; |
1335 | case NEXTHOP_EVENT_DEL: |
1336 | nsim_nexthop_remove(data, info); |
1337 | break; |
1338 | case NEXTHOP_EVENT_RES_TABLE_PRE_REPLACE: |
1339 | err = nsim_nexthop_res_table_pre_replace(data, info); |
1340 | break; |
1341 | case NEXTHOP_EVENT_BUCKET_REPLACE: |
1342 | err = nsim_nexthop_bucket_replace(data, info); |
1343 | break; |
1344 | default: |
1345 | break; |
1346 | } |
1347 | |
1348 | mutex_unlock(lock: &data->nh_lock); |
1349 | return notifier_from_errno(err); |
1350 | } |
1351 | |
1352 | static void nsim_nexthop_free(void *ptr, void *arg) |
1353 | { |
1354 | struct nsim_nexthop *nexthop = ptr; |
1355 | struct nsim_fib_data *data = arg; |
1356 | struct net *net; |
1357 | |
1358 | net = devlink_net(devlink: data->devlink); |
1359 | nsim_nexthop_hw_flags_set(net, nexthop, trap: false); |
1360 | nsim_nexthop_account(data, occ: nexthop->occ, add: false, NULL); |
1361 | nsim_nexthop_destroy(nexthop); |
1362 | } |
1363 | |
1364 | static ssize_t nsim_nexthop_bucket_activity_write(struct file *file, |
1365 | const char __user *user_buf, |
1366 | size_t size, loff_t *ppos) |
1367 | { |
1368 | struct nsim_fib_data *data = file->private_data; |
1369 | struct net *net = devlink_net(devlink: data->devlink); |
1370 | struct nsim_nexthop *nexthop; |
1371 | unsigned long *activity; |
1372 | loff_t pos = *ppos; |
1373 | u16 bucket_index; |
1374 | char buf[128]; |
1375 | int err = 0; |
1376 | u32 nhid; |
1377 | |
1378 | if (pos != 0) |
1379 | return -EINVAL; |
1380 | if (size > sizeof(buf)) |
1381 | return -EINVAL; |
1382 | if (copy_from_user(to: buf, from: user_buf, n: size)) |
1383 | return -EFAULT; |
1384 | if (sscanf(buf, "%u %hu" , &nhid, &bucket_index) != 2) |
1385 | return -EINVAL; |
1386 | |
1387 | rtnl_lock(); |
1388 | |
1389 | nexthop = rhashtable_lookup_fast(ht: &data->nexthop_ht, key: &nhid, |
1390 | params: nsim_nexthop_ht_params); |
1391 | if (!nexthop || !nexthop->is_resilient || |
1392 | bucket_index >= nexthop->occ) { |
1393 | err = -EINVAL; |
1394 | goto out; |
1395 | } |
1396 | |
1397 | activity = bitmap_zalloc(nbits: nexthop->occ, GFP_KERNEL); |
1398 | if (!activity) { |
1399 | err = -ENOMEM; |
1400 | goto out; |
1401 | } |
1402 | |
1403 | bitmap_set(map: activity, start: bucket_index, nbits: 1); |
1404 | nexthop_res_grp_activity_update(net, id: nhid, num_buckets: nexthop->occ, activity); |
1405 | bitmap_free(bitmap: activity); |
1406 | |
1407 | out: |
1408 | rtnl_unlock(); |
1409 | |
1410 | *ppos = size; |
1411 | return err ?: size; |
1412 | } |
1413 | |
1414 | static const struct file_operations nsim_nexthop_bucket_activity_fops = { |
1415 | .open = simple_open, |
1416 | .write = nsim_nexthop_bucket_activity_write, |
1417 | .llseek = no_llseek, |
1418 | .owner = THIS_MODULE, |
1419 | }; |
1420 | |
1421 | static u64 nsim_fib_ipv4_resource_occ_get(void *priv) |
1422 | { |
1423 | struct nsim_fib_data *data = priv; |
1424 | |
1425 | return nsim_fib_get_val(fib_data: data, res_id: NSIM_RESOURCE_IPV4_FIB, max: false); |
1426 | } |
1427 | |
1428 | static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv) |
1429 | { |
1430 | struct nsim_fib_data *data = priv; |
1431 | |
1432 | return nsim_fib_get_val(fib_data: data, res_id: NSIM_RESOURCE_IPV4_FIB_RULES, max: false); |
1433 | } |
1434 | |
1435 | static u64 nsim_fib_ipv6_resource_occ_get(void *priv) |
1436 | { |
1437 | struct nsim_fib_data *data = priv; |
1438 | |
1439 | return nsim_fib_get_val(fib_data: data, res_id: NSIM_RESOURCE_IPV6_FIB, max: false); |
1440 | } |
1441 | |
1442 | static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv) |
1443 | { |
1444 | struct nsim_fib_data *data = priv; |
1445 | |
1446 | return nsim_fib_get_val(fib_data: data, res_id: NSIM_RESOURCE_IPV6_FIB_RULES, max: false); |
1447 | } |
1448 | |
1449 | static u64 nsim_fib_nexthops_res_occ_get(void *priv) |
1450 | { |
1451 | struct nsim_fib_data *data = priv; |
1452 | |
1453 | return nsim_fib_get_val(fib_data: data, res_id: NSIM_RESOURCE_NEXTHOPS, max: false); |
1454 | } |
1455 | |
1456 | static void nsim_fib_set_max_all(struct nsim_fib_data *data, |
1457 | struct devlink *devlink) |
1458 | { |
1459 | static const enum nsim_resource_id res_ids[] = { |
1460 | NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES, |
1461 | NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES, |
1462 | NSIM_RESOURCE_NEXTHOPS, |
1463 | }; |
1464 | int i; |
1465 | |
1466 | for (i = 0; i < ARRAY_SIZE(res_ids); i++) { |
1467 | int err; |
1468 | u64 val; |
1469 | |
1470 | err = devl_resource_size_get(devlink, resource_id: res_ids[i], p_resource_size: &val); |
1471 | if (err) |
1472 | val = (u64) -1; |
1473 | nsim_fib_set_max(fib_data: data, res_id: res_ids[i], val); |
1474 | } |
1475 | } |
1476 | |
1477 | static void nsim_fib_event_work(struct work_struct *work) |
1478 | { |
1479 | struct nsim_fib_data *data = container_of(work, struct nsim_fib_data, |
1480 | fib_event_work); |
1481 | struct nsim_fib_event *fib_event, *next_fib_event; |
1482 | |
1483 | LIST_HEAD(fib_event_queue); |
1484 | |
1485 | spin_lock_bh(lock: &data->fib_event_queue_lock); |
1486 | list_splice_init(list: &data->fib_event_queue, head: &fib_event_queue); |
1487 | spin_unlock_bh(lock: &data->fib_event_queue_lock); |
1488 | |
1489 | mutex_lock(&data->fib_lock); |
1490 | list_for_each_entry_safe(fib_event, next_fib_event, &fib_event_queue, |
1491 | list) { |
1492 | nsim_fib_event(fib_event); |
1493 | list_del(entry: &fib_event->list); |
1494 | kfree(objp: fib_event); |
1495 | cond_resched(); |
1496 | } |
1497 | mutex_unlock(lock: &data->fib_lock); |
1498 | } |
1499 | |
1500 | static void nsim_fib_flush_work(struct work_struct *work) |
1501 | { |
1502 | struct nsim_fib_data *data = container_of(work, struct nsim_fib_data, |
1503 | fib_flush_work); |
1504 | struct nsim_fib_rt *fib_rt, *fib_rt_tmp; |
1505 | |
1506 | /* Process pending work. */ |
1507 | flush_work(work: &data->fib_event_work); |
1508 | |
1509 | mutex_lock(&data->fib_lock); |
1510 | list_for_each_entry_safe(fib_rt, fib_rt_tmp, &data->fib_rt_list, list) { |
1511 | rhashtable_remove_fast(ht: &data->fib_rt_ht, obj: &fib_rt->ht_node, |
1512 | params: nsim_fib_rt_ht_params); |
1513 | nsim_fib_rt_free(ptr: fib_rt, arg: data); |
1514 | } |
1515 | mutex_unlock(lock: &data->fib_lock); |
1516 | } |
1517 | |
1518 | static int |
1519 | nsim_fib_debugfs_init(struct nsim_fib_data *data, struct nsim_dev *nsim_dev) |
1520 | { |
1521 | data->ddir = debugfs_create_dir(name: "fib" , parent: nsim_dev->ddir); |
1522 | if (IS_ERR(ptr: data->ddir)) |
1523 | return PTR_ERR(ptr: data->ddir); |
1524 | |
1525 | data->fail_route_offload = false; |
1526 | debugfs_create_bool(name: "fail_route_offload" , mode: 0600, parent: data->ddir, |
1527 | value: &data->fail_route_offload); |
1528 | |
1529 | data->fail_res_nexthop_group_replace = false; |
1530 | debugfs_create_bool(name: "fail_res_nexthop_group_replace" , mode: 0600, parent: data->ddir, |
1531 | value: &data->fail_res_nexthop_group_replace); |
1532 | |
1533 | data->fail_nexthop_bucket_replace = false; |
1534 | debugfs_create_bool(name: "fail_nexthop_bucket_replace" , mode: 0600, parent: data->ddir, |
1535 | value: &data->fail_nexthop_bucket_replace); |
1536 | |
1537 | debugfs_create_file(name: "nexthop_bucket_activity" , mode: 0200, parent: data->ddir, |
1538 | data, fops: &nsim_nexthop_bucket_activity_fops); |
1539 | |
1540 | data->fail_route_delete = false; |
1541 | debugfs_create_bool(name: "fail_route_delete" , mode: 0600, parent: data->ddir, |
1542 | value: &data->fail_route_delete); |
1543 | return 0; |
1544 | } |
1545 | |
1546 | static void nsim_fib_debugfs_exit(struct nsim_fib_data *data) |
1547 | { |
1548 | debugfs_remove_recursive(dentry: data->ddir); |
1549 | } |
1550 | |
1551 | struct nsim_fib_data *nsim_fib_create(struct devlink *devlink, |
1552 | struct netlink_ext_ack *extack) |
1553 | { |
1554 | struct nsim_fib_data *data; |
1555 | struct nsim_dev *nsim_dev; |
1556 | int err; |
1557 | |
1558 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
1559 | if (!data) |
1560 | return ERR_PTR(error: -ENOMEM); |
1561 | data->devlink = devlink; |
1562 | |
1563 | nsim_dev = devlink_priv(devlink); |
1564 | err = nsim_fib_debugfs_init(data, nsim_dev); |
1565 | if (err) |
1566 | goto err_data_free; |
1567 | |
1568 | mutex_init(&data->nh_lock); |
1569 | err = rhashtable_init(ht: &data->nexthop_ht, params: &nsim_nexthop_ht_params); |
1570 | if (err) |
1571 | goto err_debugfs_exit; |
1572 | |
1573 | mutex_init(&data->fib_lock); |
1574 | INIT_LIST_HEAD(list: &data->fib_rt_list); |
1575 | err = rhashtable_init(ht: &data->fib_rt_ht, params: &nsim_fib_rt_ht_params); |
1576 | if (err) |
1577 | goto err_rhashtable_nexthop_destroy; |
1578 | |
1579 | INIT_WORK(&data->fib_event_work, nsim_fib_event_work); |
1580 | INIT_WORK(&data->fib_flush_work, nsim_fib_flush_work); |
1581 | INIT_LIST_HEAD(list: &data->fib_event_queue); |
1582 | spin_lock_init(&data->fib_event_queue_lock); |
1583 | |
1584 | nsim_fib_set_max_all(data, devlink); |
1585 | |
1586 | data->nexthop_nb.notifier_call = nsim_nexthop_event_nb; |
1587 | err = register_nexthop_notifier(net: devlink_net(devlink), nb: &data->nexthop_nb, |
1588 | extack); |
1589 | if (err) { |
1590 | pr_err("Failed to register nexthop notifier\n" ); |
1591 | goto err_rhashtable_fib_destroy; |
1592 | } |
1593 | |
1594 | data->fib_nb.notifier_call = nsim_fib_event_nb; |
1595 | err = register_fib_notifier(net: devlink_net(devlink), nb: &data->fib_nb, |
1596 | cb: nsim_fib_dump_inconsistent, extack); |
1597 | if (err) { |
1598 | pr_err("Failed to register fib notifier\n" ); |
1599 | goto err_nexthop_nb_unregister; |
1600 | } |
1601 | |
1602 | devl_resource_occ_get_register(devlink, |
1603 | resource_id: NSIM_RESOURCE_IPV4_FIB, |
1604 | occ_get: nsim_fib_ipv4_resource_occ_get, |
1605 | occ_get_priv: data); |
1606 | devl_resource_occ_get_register(devlink, |
1607 | resource_id: NSIM_RESOURCE_IPV4_FIB_RULES, |
1608 | occ_get: nsim_fib_ipv4_rules_res_occ_get, |
1609 | occ_get_priv: data); |
1610 | devl_resource_occ_get_register(devlink, |
1611 | resource_id: NSIM_RESOURCE_IPV6_FIB, |
1612 | occ_get: nsim_fib_ipv6_resource_occ_get, |
1613 | occ_get_priv: data); |
1614 | devl_resource_occ_get_register(devlink, |
1615 | resource_id: NSIM_RESOURCE_IPV6_FIB_RULES, |
1616 | occ_get: nsim_fib_ipv6_rules_res_occ_get, |
1617 | occ_get_priv: data); |
1618 | devl_resource_occ_get_register(devlink, |
1619 | resource_id: NSIM_RESOURCE_NEXTHOPS, |
1620 | occ_get: nsim_fib_nexthops_res_occ_get, |
1621 | occ_get_priv: data); |
1622 | return data; |
1623 | |
1624 | err_nexthop_nb_unregister: |
1625 | unregister_nexthop_notifier(net: devlink_net(devlink), nb: &data->nexthop_nb); |
1626 | err_rhashtable_fib_destroy: |
1627 | cancel_work_sync(work: &data->fib_flush_work); |
1628 | flush_work(work: &data->fib_event_work); |
1629 | rhashtable_free_and_destroy(ht: &data->fib_rt_ht, free_fn: nsim_fib_rt_free, |
1630 | arg: data); |
1631 | err_rhashtable_nexthop_destroy: |
1632 | rhashtable_free_and_destroy(ht: &data->nexthop_ht, free_fn: nsim_nexthop_free, |
1633 | arg: data); |
1634 | mutex_destroy(lock: &data->fib_lock); |
1635 | err_debugfs_exit: |
1636 | mutex_destroy(lock: &data->nh_lock); |
1637 | nsim_fib_debugfs_exit(data); |
1638 | err_data_free: |
1639 | kfree(objp: data); |
1640 | return ERR_PTR(error: err); |
1641 | } |
1642 | |
1643 | void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data) |
1644 | { |
1645 | devl_resource_occ_get_unregister(devlink, |
1646 | resource_id: NSIM_RESOURCE_NEXTHOPS); |
1647 | devl_resource_occ_get_unregister(devlink, |
1648 | resource_id: NSIM_RESOURCE_IPV6_FIB_RULES); |
1649 | devl_resource_occ_get_unregister(devlink, |
1650 | resource_id: NSIM_RESOURCE_IPV6_FIB); |
1651 | devl_resource_occ_get_unregister(devlink, |
1652 | resource_id: NSIM_RESOURCE_IPV4_FIB_RULES); |
1653 | devl_resource_occ_get_unregister(devlink, |
1654 | resource_id: NSIM_RESOURCE_IPV4_FIB); |
1655 | unregister_fib_notifier(net: devlink_net(devlink), nb: &data->fib_nb); |
1656 | unregister_nexthop_notifier(net: devlink_net(devlink), nb: &data->nexthop_nb); |
1657 | cancel_work_sync(work: &data->fib_flush_work); |
1658 | flush_work(work: &data->fib_event_work); |
1659 | rhashtable_free_and_destroy(ht: &data->fib_rt_ht, free_fn: nsim_fib_rt_free, |
1660 | arg: data); |
1661 | rhashtable_free_and_destroy(ht: &data->nexthop_ht, free_fn: nsim_nexthop_free, |
1662 | arg: data); |
1663 | WARN_ON_ONCE(!list_empty(&data->fib_event_queue)); |
1664 | WARN_ON_ONCE(!list_empty(&data->fib_rt_list)); |
1665 | mutex_destroy(lock: &data->fib_lock); |
1666 | mutex_destroy(lock: &data->nh_lock); |
1667 | nsim_fib_debugfs_exit(data); |
1668 | kfree(objp: data); |
1669 | } |
1670 | |