| 1 | |
| 2 | * Copyright (c) 2018 Cumulus Networks. All rights reserved.
|
| 3 | * Copyright (c) 2018 David Ahern <[email protected]>
|
| 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;
|
| 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;
|
| 60 | struct mutex nh_lock;
|
| 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;
|
| 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;
|
| 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;
|
| 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(&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(&entry->num, 1, 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(&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(&data->ipv4.rules, add, extack);
|
| 218 | break;
|
| 219 | case AF_INET6:
|
| 220 | err = nsim_fib_rule_account(&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(&entry->num, 1, entry->max))
|
| 233 | err = -ENOSPC;
|
| 234 | } else {
|
| 235 | atomic64_dec_if_positive(&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(&fib_rt->list, &data->fib_rt_list);
|
| 251 | }
|
| 252 |
|
| 253 | static void nsim_fib_rt_fini(struct nsim_fib_rt *fib_rt)
|
| 254 | {
|
| 255 | list_del(&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(fib_rt_ht, &key, 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_obj(*fib4_rt);
|
| 281 | if (!fib4_rt)
|
| 282 | return NULL;
|
| 283 |
|
| 284 | nsim_fib_rt_init(data, &fib4_rt->common, &fen_info->dst, sizeof(u32),
|
| 285 | fen_info->dst_len, AF_INET, fen_info->tb_id);
|
| 286 |
|
| 287 | fib4_rt->fi = fen_info->fi;
|
| 288 | fib_info_hold(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(fib4_rt->fi);
|
| 298 | nsim_fib_rt_fini(&fib4_rt->common);
|
| 299 | kfree(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, &fen_info->dst, sizeof(u32),
|
| 309 | fen_info->dst_len, AF_INET,
|
| 310 | 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);
|
| 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);
|
| 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(data->devlink);
|
| 360 | int err;
|
| 361 |
|
| 362 | err = rhashtable_insert_fast(&data->fib_rt_ht,
|
| 363 | &fib4_rt->common.ht_node,
|
| 364 | nsim_fib_rt_ht_params);
|
| 365 | if (err)
|
| 366 | goto err_fib_dismiss;
|
| 367 |
|
| 368 |
|
| 369 | msleep(1);
|
| 370 | nsim_fib4_rt_hw_flags_set(net, fib4_rt, true);
|
| 371 |
|
| 372 | return 0;
|
| 373 |
|
| 374 | err_fib_dismiss:
|
| 375 | |
| 376 | * context when FIB_EVENT_ENTRY_REPLACE was triggered.
|
| 377 |
|
| 378 | nsim_fib_account(&data->ipv4.fib, 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(data->devlink);
|
| 387 | int err;
|
| 388 |
|
| 389 | |
| 390 | * was increased when FIB_EVENT_ENTRY_REPLACE was triggered.
|
| 391 |
|
| 392 | err = nsim_fib_account(&data->ipv4.fib, false);
|
| 393 | if (err)
|
| 394 | return err;
|
| 395 | err = rhashtable_replace_fast(&data->fib_rt_ht,
|
| 396 | &fib4_rt_old->common.ht_node,
|
| 397 | &fib4_rt->common.ht_node,
|
| 398 | nsim_fib_rt_ht_params);
|
| 399 | if (err)
|
| 400 | return err;
|
| 401 |
|
| 402 | msleep(1);
|
| 403 | nsim_fib4_rt_hw_flags_set(net, fib4_rt, true);
|
| 404 |
|
| 405 | nsim_fib4_rt_hw_flags_set(net, fib4_rt_old, false);
|
| 406 | nsim_fib4_rt_destroy(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 | |
| 419 | * value to true. Simulate hardware programming latency and then
|
| 420 | * fail.
|
| 421 |
|
| 422 | msleep(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(&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(&data->fib_rt_ht, fen_info);
|
| 448 | if (!fib4_rt)
|
| 449 | return;
|
| 450 |
|
| 451 | rhashtable_remove_fast(&data->fib_rt_ht, &fib4_rt->common.ht_node,
|
| 452 | 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(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_obj(*fib6_rt_nh);
|
| 501 | if (!fib6_rt_nh)
|
| 502 | return -ENOMEM;
|
| 503 |
|
| 504 | fib6_info_hold(rt);
|
| 505 | fib6_rt_nh->rt = rt;
|
| 506 | list_add_tail(&fib6_rt_nh->list, &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(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(&fib6_rt_nh->list);
|
| 534 | nsim_rt6_release(fib6_rt_nh->rt);
|
| 535 | kfree(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_obj(*fib6_rt);
|
| 548 | if (!fib6_rt)
|
| 549 | return ERR_PTR(-ENOMEM);
|
| 550 |
|
| 551 | nsim_fib_rt_init(data, &fib6_rt->common, &rt->fib6_dst.addr,
|
| 552 | sizeof(rt->fib6_dst.addr), rt->fib6_dst.plen, AF_INET6,
|
| 553 | rt->fib6_table->tb6_id);
|
| 554 |
|
| 555 | |
| 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(&fib6_rt->nh_list);
|
| 560 |
|
| 561 | for (i = 0; i < nrt6; i++) {
|
| 562 | err = nsim_fib6_rt_nh_add(fib6_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_arr[i]);
|
| 572 | }
|
| 573 | nsim_fib_rt_fini(&fib6_rt->common);
|
| 574 | kfree(fib6_rt);
|
| 575 | return ERR_PTR(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, iter->rt);
|
| 584 | WARN_ON_ONCE(!list_empty(&fib6_rt->nh_list));
|
| 585 | nsim_fib_rt_fini(&fib6_rt->common);
|
| 586 | kfree(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, &rt->fib6_dst.addr,
|
| 595 | sizeof(rt->fib6_dst.addr),
|
| 596 | rt->fib6_dst.plen, AF_INET6,
|
| 597 | 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 | |
| 613 | * value to true. Simulate hardware programming latency and then
|
| 614 | * fail.
|
| 615 |
|
| 616 | msleep(1);
|
| 617 | return -EINVAL;
|
| 618 | }
|
| 619 |
|
| 620 | fib6_rt = nsim_fib6_rt_lookup(&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, 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, 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(data->devlink);
|
| 649 | int i;
|
| 650 |
|
| 651 | for (i = 0; i < nrt6; i++)
|
| 652 | fib6_info_hw_flags_set(net, rt_arr[i], false, false, 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(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, fib6_rt_nh->rt, false, trap, 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(&data->fib_rt_ht,
|
| 687 | &fib6_rt->common.ht_node,
|
| 688 | nsim_fib_rt_ht_params);
|
| 689 |
|
| 690 | if (err)
|
| 691 | goto err_fib_dismiss;
|
| 692 |
|
| 693 | msleep(1);
|
| 694 | nsim_fib6_rt_hw_flags_set(data, fib6_rt, true);
|
| 695 |
|
| 696 | return 0;
|
| 697 |
|
| 698 | err_fib_dismiss:
|
| 699 | |
| 700 | * context when FIB_EVENT_ENTRY_REPLACE was triggered.
|
| 701 |
|
| 702 | nsim_fib_account(&data->ipv6.fib, 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 | |
| 713 | * was increased when FIB_EVENT_ENTRY_REPLACE was triggered.
|
| 714 |
|
| 715 | err = nsim_fib_account(&data->ipv6.fib, false);
|
| 716 | if (err)
|
| 717 | return err;
|
| 718 |
|
| 719 | err = rhashtable_replace_fast(&data->fib_rt_ht,
|
| 720 | &fib6_rt_old->common.ht_node,
|
| 721 | &fib6_rt->common.ht_node,
|
| 722 | nsim_fib_rt_ht_params);
|
| 723 |
|
| 724 | if (err)
|
| 725 | return err;
|
| 726 |
|
| 727 | msleep(1);
|
| 728 | nsim_fib6_rt_hw_flags_set(data, fib6_rt, true);
|
| 729 |
|
| 730 | nsim_fib6_rt_hw_flags_set(data, fib6_rt_old, false);
|
| 731 | nsim_fib6_rt_destroy(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 | |
| 745 | * value to true. Simulate hardware programming latency and then
|
| 746 | * fail.
|
| 747 |
|
| 748 | msleep(1);
|
| 749 | return -EINVAL;
|
| 750 | }
|
| 751 |
|
| 752 | fib6_rt = nsim_fib6_rt_create(data, fib6_event->rt_arr,
|
| 753 | fib6_event->nrt6);
|
| 754 | if (IS_ERR(fib6_rt))
|
| 755 | return PTR_ERR(fib6_rt);
|
| 756 |
|
| 757 | fib6_rt_old = nsim_fib6_rt_lookup(&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 | |
| 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(&data->fib_rt_ht, rt);
|
| 782 | if (!fib6_rt)
|
| 783 | return;
|
| 784 |
|
| 785 | |
| 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, fib6_event->rt_arr[i]);
|
| 791 | return;
|
| 792 | }
|
| 793 |
|
| 794 | rhashtable_remove_fast(&data->fib_rt_ht, &fib6_rt->common.ht_node,
|
| 795 | 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 = kzalloc_objs(struct fib6_info *, nrt6, 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(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(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(fib6_event->rt_arr[i]);
|
| 842 | kfree(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, fib6_event->rt_arr,
|
| 876 | 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(fib_event->data, &fib_event->fen_info,
|
| 885 | fib_event->event);
|
| 886 | fib_info_put(fib_event->fen_info.fi);
|
| 887 | break;
|
| 888 | case AF_INET6:
|
| 889 | nsim_fib6_event(fib_event->data, &fib_event->fib6_event,
|
| 890 | fib_event->event);
|
| 891 | nsim_fib6_event_fini(&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(&data->ipv4.fib, 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(&data->ipv4.fib, false);
|
| 924 | break;
|
| 925 | }
|
| 926 |
|
| 927 | |
| 928 | * freed while event is queued. Release it afterwards.
|
| 929 |
|
| 930 | fib_info_hold(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(&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(&data->ipv6.fib, 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(&data->ipv6.fib, false);
|
| 967 | break;
|
| 968 | }
|
| 969 |
|
| 970 | return 0;
|
| 971 |
|
| 972 | err_fib6_event_fini:
|
| 973 | nsim_fib6_event_fini(&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 | |
| 986 | * 'RTNL_FAMILY_IPMR' and should ignore them.
|
| 987 |
|
| 988 | return NOTIFY_DONE;
|
| 989 |
|
| 990 | fib_event = kzalloc_obj(*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 |
|
| 1011 | spin_lock_bh(&data->fib_event_queue_lock);
|
| 1012 | list_add_tail(&fib_event->list, &data->fib_event_queue);
|
| 1013 | spin_unlock_bh(&data->fib_event_queue_lock);
|
| 1014 | schedule_work(&data->fib_event_work);
|
| 1015 |
|
| 1016 | return NOTIFY_DONE;
|
| 1017 |
|
| 1018 | err_fib_prepare_event:
|
| 1019 | kfree(fib_event);
|
| 1020 | err_fib_event_alloc:
|
| 1021 | if (event == FIB_EVENT_ENTRY_DEL)
|
| 1022 | schedule_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 | 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(devlink_net(devlink), fib4_rt, false);
|
| 1057 | nsim_fib_account(&data->ipv4.fib, 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, false);
|
| 1068 | nsim_fib_account(&data->ipv6.fib, 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 |
|
| 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 |
|
| 1097 | flush_work(&data->fib_event_work);
|
| 1098 |
|
| 1099 | |
| 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(&data->fib_rt_ht, &fib_rt->ht_node,
|
| 1104 | nsim_fib_rt_ht_params);
|
| 1105 | nsim_fib_rt_free(fib_rt, data);
|
| 1106 | }
|
| 1107 |
|
| 1108 | atomic64_set(&data->ipv4.rules.num, 0ULL);
|
| 1109 | atomic64_set(&data->ipv6.rules.num, 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_obj(*nexthop);
|
| 1120 | if (!nexthop)
|
| 1121 | return ERR_PTR(-ENOMEM);
|
| 1122 |
|
| 1123 | nexthop->id = info->id;
|
| 1124 |
|
| 1125 | |
| 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(nexthop);
|
| 1144 | return ERR_PTR(-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(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(&data->nexthops.num, 1,
|
| 1164 | 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(occ, &data->nexthops.num);
|
| 1173 | }
|
| 1174 |
|
| 1175 | return err;
|
| 1176 |
|
| 1177 | err_num_decrease:
|
| 1178 | atomic64_sub(i, &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, nexthop->id, 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, nexthop->id, i, 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(data->devlink);
|
| 1203 | int err;
|
| 1204 |
|
| 1205 | err = nsim_nexthop_account(data, nexthop->occ, true, extack);
|
| 1206 | if (err)
|
| 1207 | return err;
|
| 1208 |
|
| 1209 | err = rhashtable_insert_fast(&data->nexthop_ht, &nexthop->ht_node,
|
| 1210 | 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, true);
|
| 1217 |
|
| 1218 | return 0;
|
| 1219 |
|
| 1220 | err_nexthop_dismiss:
|
| 1221 | nsim_nexthop_account(data, nexthop->occ, 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(data->devlink);
|
| 1231 | int err;
|
| 1232 |
|
| 1233 | err = nsim_nexthop_account(data, nexthop->occ, true, extack);
|
| 1234 | if (err)
|
| 1235 | return err;
|
| 1236 |
|
| 1237 | err = rhashtable_replace_fast(&data->nexthop_ht,
|
| 1238 | &nexthop_old->ht_node, &nexthop->ht_node,
|
| 1239 | 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, true);
|
| 1246 | nsim_nexthop_account(data, nexthop_old->occ, false, extack);
|
| 1247 | nsim_nexthop_destroy(nexthop_old);
|
| 1248 |
|
| 1249 | return 0;
|
| 1250 |
|
| 1251 | err_nexthop_dismiss:
|
| 1252 | nsim_nexthop_account(data, nexthop->occ, 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(nexthop))
|
| 1264 | return PTR_ERR(nexthop);
|
| 1265 |
|
| 1266 | nexthop_old = rhashtable_lookup_fast(&data->nexthop_ht, &info->id,
|
| 1267 | nsim_nexthop_ht_params);
|
| 1268 | if (!nexthop_old)
|
| 1269 | err = nsim_nexthop_add(data, nexthop, info->extack);
|
| 1270 | else
|
| 1271 | err = nsim_nexthop_replace(data, nexthop, nexthop_old,
|
| 1272 | 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(&data->nexthop_ht, &info->id,
|
| 1286 | nsim_nexthop_ht_params);
|
| 1287 | if (!nexthop)
|
| 1288 | return;
|
| 1289 |
|
| 1290 | rhashtable_remove_fast(&data->nexthop_ht, &nexthop->ht_node,
|
| 1291 | nsim_nexthop_ht_params);
|
| 1292 | nsim_nexthop_account(data, nexthop->occ, false, 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(info->net, info->id,
|
| 1316 | info->nh_res_bucket->bucket_index,
|
| 1317 | false, 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(&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(data->devlink);
|
| 1359 | nsim_nexthop_hw_flags_set(net, nexthop, false);
|
| 1360 | nsim_nexthop_account(data, nexthop->occ, 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(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) - 1)
|
| 1381 | return -EINVAL;
|
| 1382 | if (copy_from_user(buf, user_buf, size))
|
| 1383 | return -EFAULT;
|
| 1384 | buf[size] = 0;
|
| 1385 |
|
| 1386 | if (sscanf(buf, "%u %hu", &nhid, &bucket_index) != 2)
|
| 1387 | return -EINVAL;
|
| 1388 |
|
| 1389 | rtnl_lock();
|
| 1390 |
|
| 1391 | nexthop = rhashtable_lookup_fast(&data->nexthop_ht, &nhid,
|
| 1392 | nsim_nexthop_ht_params);
|
| 1393 | if (!nexthop || !nexthop->is_resilient ||
|
| 1394 | bucket_index >= nexthop->occ) {
|
| 1395 | err = -EINVAL;
|
| 1396 | goto out;
|
| 1397 | }
|
| 1398 |
|
| 1399 | activity = bitmap_zalloc(nexthop->occ, GFP_KERNEL);
|
| 1400 | if (!activity) {
|
| 1401 | err = -ENOMEM;
|
| 1402 | goto out;
|
| 1403 | }
|
| 1404 |
|
| 1405 | bitmap_set(activity, bucket_index, 1);
|
| 1406 | nexthop_res_grp_activity_update(net, nhid, nexthop->occ, activity);
|
| 1407 | bitmap_free(activity);
|
| 1408 |
|
| 1409 | out:
|
| 1410 | rtnl_unlock();
|
| 1411 |
|
| 1412 | *ppos = size;
|
| 1413 | return err ?: size;
|
| 1414 | }
|
| 1415 |
|
| 1416 | static const struct file_operations nsim_nexthop_bucket_activity_fops = {
|
| 1417 | .open = simple_open,
|
| 1418 | .write = nsim_nexthop_bucket_activity_write,
|
| 1419 | .owner = THIS_MODULE,
|
| 1420 | };
|
| 1421 |
|
| 1422 | static u64 nsim_fib_ipv4_resource_occ_get(void *priv)
|
| 1423 | {
|
| 1424 | struct nsim_fib_data *data = priv;
|
| 1425 |
|
| 1426 | return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false);
|
| 1427 | }
|
| 1428 |
|
| 1429 | static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv)
|
| 1430 | {
|
| 1431 | struct nsim_fib_data *data = priv;
|
| 1432 |
|
| 1433 | return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false);
|
| 1434 | }
|
| 1435 |
|
| 1436 | static u64 nsim_fib_ipv6_resource_occ_get(void *priv)
|
| 1437 | {
|
| 1438 | struct nsim_fib_data *data = priv;
|
| 1439 |
|
| 1440 | return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false);
|
| 1441 | }
|
| 1442 |
|
| 1443 | static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv)
|
| 1444 | {
|
| 1445 | struct nsim_fib_data *data = priv;
|
| 1446 |
|
| 1447 | return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false);
|
| 1448 | }
|
| 1449 |
|
| 1450 | static u64 nsim_fib_nexthops_res_occ_get(void *priv)
|
| 1451 | {
|
| 1452 | struct nsim_fib_data *data = priv;
|
| 1453 |
|
| 1454 | return nsim_fib_get_val(data, NSIM_RESOURCE_NEXTHOPS, false);
|
| 1455 | }
|
| 1456 |
|
| 1457 | static void nsim_fib_set_max_all(struct nsim_fib_data *data,
|
| 1458 | struct devlink *devlink)
|
| 1459 | {
|
| 1460 | static const enum nsim_resource_id res_ids[] = {
|
| 1461 | NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
|
| 1462 | NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES,
|
| 1463 | NSIM_RESOURCE_NEXTHOPS,
|
| 1464 | };
|
| 1465 | int i;
|
| 1466 |
|
| 1467 | for (i = 0; i < ARRAY_SIZE(res_ids); i++) {
|
| 1468 | int err;
|
| 1469 | u64 val;
|
| 1470 |
|
| 1471 | err = devl_resource_size_get(devlink, res_ids[i], &val);
|
| 1472 | if (err)
|
| 1473 | val = (u64) -1;
|
| 1474 | nsim_fib_set_max(data, res_ids[i], val);
|
| 1475 | }
|
| 1476 | }
|
| 1477 |
|
| 1478 | static void nsim_fib_event_work(struct work_struct *work)
|
| 1479 | {
|
| 1480 | struct nsim_fib_data *data = container_of(work, struct nsim_fib_data,
|
| 1481 | fib_event_work);
|
| 1482 | struct nsim_fib_event *fib_event, *next_fib_event;
|
| 1483 |
|
| 1484 | LIST_HEAD(fib_event_queue);
|
| 1485 |
|
| 1486 | spin_lock_bh(&data->fib_event_queue_lock);
|
| 1487 | list_splice_init(&data->fib_event_queue, &fib_event_queue);
|
| 1488 | spin_unlock_bh(&data->fib_event_queue_lock);
|
| 1489 |
|
| 1490 | mutex_lock(&data->fib_lock);
|
| 1491 | list_for_each_entry_safe(fib_event, next_fib_event, &fib_event_queue,
|
| 1492 | list) {
|
| 1493 | nsim_fib_event(fib_event);
|
| 1494 | list_del(&fib_event->list);
|
| 1495 | kfree(fib_event);
|
| 1496 | cond_resched();
|
| 1497 | }
|
| 1498 | mutex_unlock(&data->fib_lock);
|
| 1499 | }
|
| 1500 |
|
| 1501 | static void nsim_fib_flush_work(struct work_struct *work)
|
| 1502 | {
|
| 1503 | struct nsim_fib_data *data = container_of(work, struct nsim_fib_data,
|
| 1504 | fib_flush_work);
|
| 1505 | struct nsim_fib_rt *fib_rt, *fib_rt_tmp;
|
| 1506 |
|
| 1507 |
|
| 1508 | flush_work(&data->fib_event_work);
|
| 1509 |
|
| 1510 | mutex_lock(&data->fib_lock);
|
| 1511 | list_for_each_entry_safe(fib_rt, fib_rt_tmp, &data->fib_rt_list, list) {
|
| 1512 | rhashtable_remove_fast(&data->fib_rt_ht, &fib_rt->ht_node,
|
| 1513 | nsim_fib_rt_ht_params);
|
| 1514 | nsim_fib_rt_free(fib_rt, data);
|
| 1515 | }
|
| 1516 | mutex_unlock(&data->fib_lock);
|
| 1517 | }
|
| 1518 |
|
| 1519 | static int
|
| 1520 | nsim_fib_debugfs_init(struct nsim_fib_data *data, struct nsim_dev *nsim_dev)
|
| 1521 | {
|
| 1522 | data->ddir = debugfs_create_dir("fib", nsim_dev->ddir);
|
| 1523 | if (IS_ERR(data->ddir))
|
| 1524 | return PTR_ERR(data->ddir);
|
| 1525 |
|
| 1526 | data->fail_route_offload = false;
|
| 1527 | debugfs_create_bool("fail_route_offload", 0600, data->ddir,
|
| 1528 | &data->fail_route_offload);
|
| 1529 |
|
| 1530 | data->fail_res_nexthop_group_replace = false;
|
| 1531 | debugfs_create_bool("fail_res_nexthop_group_replace", 0600, data->ddir,
|
| 1532 | &data->fail_res_nexthop_group_replace);
|
| 1533 |
|
| 1534 | data->fail_nexthop_bucket_replace = false;
|
| 1535 | debugfs_create_bool("fail_nexthop_bucket_replace", 0600, data->ddir,
|
| 1536 | &data->fail_nexthop_bucket_replace);
|
| 1537 |
|
| 1538 | debugfs_create_file("nexthop_bucket_activity", 0200, data->ddir,
|
| 1539 | data, &nsim_nexthop_bucket_activity_fops);
|
| 1540 |
|
| 1541 | data->fail_route_delete = false;
|
| 1542 | debugfs_create_bool("fail_route_delete", 0600, data->ddir,
|
| 1543 | &data->fail_route_delete);
|
| 1544 | return 0;
|
| 1545 | }
|
| 1546 |
|
| 1547 | static void nsim_fib_debugfs_exit(struct nsim_fib_data *data)
|
| 1548 | {
|
| 1549 | debugfs_remove_recursive(data->ddir);
|
| 1550 | }
|
| 1551 |
|
| 1552 | struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
|
| 1553 | struct netlink_ext_ack *extack)
|
| 1554 | {
|
| 1555 | struct nsim_fib_data *data;
|
| 1556 | struct nsim_dev *nsim_dev;
|
| 1557 | int err;
|
| 1558 |
|
| 1559 | data = kzalloc_obj(*data);
|
| 1560 | if (!data)
|
| 1561 | return ERR_PTR(-ENOMEM);
|
| 1562 | data->devlink = devlink;
|
| 1563 |
|
| 1564 | nsim_dev = devlink_priv(devlink);
|
| 1565 |
|
| 1566 | mutex_init(&data->nh_lock);
|
| 1567 | err = rhashtable_init(&data->nexthop_ht, &nsim_nexthop_ht_params);
|
| 1568 | if (err)
|
| 1569 | goto err_nh_lock_destroy;
|
| 1570 |
|
| 1571 | mutex_init(&data->fib_lock);
|
| 1572 | INIT_LIST_HEAD(&data->fib_rt_list);
|
| 1573 | err = rhashtable_init(&data->fib_rt_ht, &nsim_fib_rt_ht_params);
|
| 1574 | if (err)
|
| 1575 | goto err_rhashtable_nexthop_destroy;
|
| 1576 |
|
| 1577 | INIT_WORK(&data->fib_event_work, nsim_fib_event_work);
|
| 1578 | INIT_WORK(&data->fib_flush_work, nsim_fib_flush_work);
|
| 1579 | INIT_LIST_HEAD(&data->fib_event_queue);
|
| 1580 | spin_lock_init(&data->fib_event_queue_lock);
|
| 1581 |
|
| 1582 | nsim_fib_set_max_all(data, devlink);
|
| 1583 |
|
| 1584 | data->nexthop_nb.notifier_call = nsim_nexthop_event_nb;
|
| 1585 | err = register_nexthop_notifier(devlink_net(devlink), &data->nexthop_nb,
|
| 1586 | extack);
|
| 1587 | if (err) {
|
| 1588 | pr_err("Failed to register nexthop notifier\n");
|
| 1589 | goto err_rhashtable_fib_destroy;
|
| 1590 | }
|
| 1591 |
|
| 1592 | data->fib_nb.notifier_call = nsim_fib_event_nb;
|
| 1593 | err = register_fib_notifier(devlink_net(devlink), &data->fib_nb,
|
| 1594 | nsim_fib_dump_inconsistent, extack);
|
| 1595 | if (err) {
|
| 1596 | pr_err("Failed to register fib notifier\n");
|
| 1597 | goto err_nexthop_nb_unregister;
|
| 1598 | }
|
| 1599 |
|
| 1600 | err = nsim_fib_debugfs_init(data, nsim_dev);
|
| 1601 | if (err)
|
| 1602 | goto err_fib_notifier_unregister;
|
| 1603 |
|
| 1604 | devl_resource_occ_get_register(devlink,
|
| 1605 | NSIM_RESOURCE_IPV4_FIB,
|
| 1606 | nsim_fib_ipv4_resource_occ_get,
|
| 1607 | data);
|
| 1608 | devl_resource_occ_get_register(devlink,
|
| 1609 | NSIM_RESOURCE_IPV4_FIB_RULES,
|
| 1610 | nsim_fib_ipv4_rules_res_occ_get,
|
| 1611 | data);
|
| 1612 | devl_resource_occ_get_register(devlink,
|
| 1613 | NSIM_RESOURCE_IPV6_FIB,
|
| 1614 | nsim_fib_ipv6_resource_occ_get,
|
| 1615 | data);
|
| 1616 | devl_resource_occ_get_register(devlink,
|
| 1617 | NSIM_RESOURCE_IPV6_FIB_RULES,
|
| 1618 | nsim_fib_ipv6_rules_res_occ_get,
|
| 1619 | data);
|
| 1620 | devl_resource_occ_get_register(devlink,
|
| 1621 | NSIM_RESOURCE_NEXTHOPS,
|
| 1622 | nsim_fib_nexthops_res_occ_get,
|
| 1623 | data);
|
| 1624 | return data;
|
| 1625 |
|
| 1626 | err_fib_notifier_unregister:
|
| 1627 | unregister_fib_notifier(devlink_net(devlink), &data->fib_nb);
|
| 1628 | err_nexthop_nb_unregister:
|
| 1629 | unregister_nexthop_notifier(devlink_net(devlink), &data->nexthop_nb);
|
| 1630 | err_rhashtable_fib_destroy:
|
| 1631 | cancel_work_sync(&data->fib_flush_work);
|
| 1632 | flush_work(&data->fib_event_work);
|
| 1633 | rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free,
|
| 1634 | data);
|
| 1635 | err_rhashtable_nexthop_destroy:
|
| 1636 | rhashtable_free_and_destroy(&data->nexthop_ht, nsim_nexthop_free,
|
| 1637 | data);
|
| 1638 | mutex_destroy(&data->fib_lock);
|
| 1639 | err_nh_lock_destroy:
|
| 1640 | mutex_destroy(&data->nh_lock);
|
| 1641 | kfree(data);
|
| 1642 | return ERR_PTR(err);
|
| 1643 | }
|
| 1644 |
|
| 1645 | void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data)
|
| 1646 | {
|
| 1647 | devl_resource_occ_get_unregister(devlink,
|
| 1648 | NSIM_RESOURCE_NEXTHOPS);
|
| 1649 | devl_resource_occ_get_unregister(devlink,
|
| 1650 | NSIM_RESOURCE_IPV6_FIB_RULES);
|
| 1651 | devl_resource_occ_get_unregister(devlink,
|
| 1652 | NSIM_RESOURCE_IPV6_FIB);
|
| 1653 | devl_resource_occ_get_unregister(devlink,
|
| 1654 | NSIM_RESOURCE_IPV4_FIB_RULES);
|
| 1655 | devl_resource_occ_get_unregister(devlink,
|
| 1656 | NSIM_RESOURCE_IPV4_FIB);
|
| 1657 | nsim_fib_debugfs_exit(data);
|
| 1658 | unregister_fib_notifier(devlink_net(devlink), &data->fib_nb);
|
| 1659 | unregister_nexthop_notifier(devlink_net(devlink), &data->nexthop_nb);
|
| 1660 | cancel_work_sync(&data->fib_flush_work);
|
| 1661 | flush_work(&data->fib_event_work);
|
| 1662 | rhashtable_free_and_destroy(&data->fib_rt_ht, nsim_fib_rt_free,
|
| 1663 | data);
|
| 1664 | rhashtable_free_and_destroy(&data->nexthop_ht, nsim_nexthop_free,
|
| 1665 | data);
|
| 1666 | WARN_ON_ONCE(!list_empty(&data->fib_event_queue));
|
| 1667 | WARN_ON_ONCE(!list_empty(&data->fib_rt_list));
|
| 1668 | mutex_destroy(&data->fib_lock);
|
| 1669 | mutex_destroy(&data->nh_lock);
|
| 1670 | kfree(data);
|
| 1671 | }
|