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