247 lines
5.4 KiB
C
247 lines
5.4 KiB
C
/*
|
|
* Copyright (c) 2018 Cumulus Networks. All rights reserved.
|
|
* Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
|
|
*
|
|
* This software is licensed under the GNU General License Version 2,
|
|
* June 1991 as shown in the file COPYING in the top-level directory of this
|
|
* source tree.
|
|
*
|
|
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
|
|
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
|
|
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
|
|
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
*/
|
|
|
|
#include <net/fib_notifier.h>
|
|
#include <net/ip_fib.h>
|
|
#include <net/ip6_fib.h>
|
|
#include <net/fib_rules.h>
|
|
|
|
#include "netdevsim.h"
|
|
|
|
struct nsim_fib_entry {
|
|
u64 max;
|
|
u64 num;
|
|
};
|
|
|
|
struct nsim_per_fib_data {
|
|
struct nsim_fib_entry fib;
|
|
struct nsim_fib_entry rules;
|
|
};
|
|
|
|
struct nsim_fib_data {
|
|
struct notifier_block fib_nb;
|
|
struct nsim_per_fib_data ipv4;
|
|
struct nsim_per_fib_data ipv6;
|
|
};
|
|
|
|
u64 nsim_fib_get_val(struct nsim_fib_data *fib_data,
|
|
enum nsim_resource_id res_id, bool max)
|
|
{
|
|
struct nsim_fib_entry *entry;
|
|
|
|
switch (res_id) {
|
|
case NSIM_RESOURCE_IPV4_FIB:
|
|
entry = &fib_data->ipv4.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV4_FIB_RULES:
|
|
entry = &fib_data->ipv4.rules;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB:
|
|
entry = &fib_data->ipv6.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB_RULES:
|
|
entry = &fib_data->ipv6.rules;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return max ? entry->max : entry->num;
|
|
}
|
|
|
|
int nsim_fib_set_max(struct nsim_fib_data *fib_data,
|
|
enum nsim_resource_id res_id, u64 val,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct nsim_fib_entry *entry;
|
|
int err = 0;
|
|
|
|
switch (res_id) {
|
|
case NSIM_RESOURCE_IPV4_FIB:
|
|
entry = &fib_data->ipv4.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV4_FIB_RULES:
|
|
entry = &fib_data->ipv4.rules;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB:
|
|
entry = &fib_data->ipv6.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB_RULES:
|
|
entry = &fib_data->ipv6.rules;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
/* not allowing a new max to be less than curren occupancy
|
|
* --> no means of evicting entries
|
|
*/
|
|
if (val < entry->num) {
|
|
NL_SET_ERR_MSG_MOD(extack, "New size is less than current occupancy");
|
|
err = -EINVAL;
|
|
} else {
|
|
entry->max = val;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
int err = 0;
|
|
|
|
if (add) {
|
|
if (entry->num < entry->max) {
|
|
entry->num++;
|
|
} else {
|
|
err = -ENOSPC;
|
|
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");
|
|
}
|
|
} else {
|
|
entry->num--;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_rule_event(struct nsim_fib_data *data,
|
|
struct fib_notifier_info *info, bool add)
|
|
{
|
|
struct netlink_ext_ack *extack = info->extack;
|
|
int err = 0;
|
|
|
|
switch (info->family) {
|
|
case AF_INET:
|
|
err = nsim_fib_rule_account(&data->ipv4.rules, add, extack);
|
|
break;
|
|
case AF_INET6:
|
|
err = nsim_fib_rule_account(&data->ipv6.rules, add, extack);
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_account(struct nsim_fib_entry *entry, bool add,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
int err = 0;
|
|
|
|
if (add) {
|
|
if (entry->num < entry->max) {
|
|
entry->num++;
|
|
} else {
|
|
err = -ENOSPC;
|
|
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries");
|
|
}
|
|
} else {
|
|
entry->num--;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_event(struct nsim_fib_data *data,
|
|
struct fib_notifier_info *info, bool add)
|
|
{
|
|
struct netlink_ext_ack *extack = info->extack;
|
|
int err = 0;
|
|
|
|
switch (info->family) {
|
|
case AF_INET:
|
|
err = nsim_fib_account(&data->ipv4.fib, add, extack);
|
|
break;
|
|
case AF_INET6:
|
|
err = nsim_fib_account(&data->ipv6.fib, add, extack);
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
|
|
void *ptr)
|
|
{
|
|
struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
|
|
fib_nb);
|
|
struct fib_notifier_info *info = ptr;
|
|
int err = 0;
|
|
|
|
switch (event) {
|
|
case FIB_EVENT_RULE_ADD: /* fall through */
|
|
case FIB_EVENT_RULE_DEL:
|
|
err = nsim_fib_rule_event(data, info,
|
|
event == FIB_EVENT_RULE_ADD);
|
|
break;
|
|
|
|
case FIB_EVENT_ENTRY_ADD: /* fall through */
|
|
case FIB_EVENT_ENTRY_DEL:
|
|
err = nsim_fib_event(data, info,
|
|
event == FIB_EVENT_ENTRY_ADD);
|
|
break;
|
|
}
|
|
|
|
return notifier_from_errno(err);
|
|
}
|
|
|
|
/* inconsistent dump, trying again */
|
|
static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
|
|
{
|
|
struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
|
|
fib_nb);
|
|
|
|
data->ipv4.fib.num = 0ULL;
|
|
data->ipv4.rules.num = 0ULL;
|
|
data->ipv6.fib.num = 0ULL;
|
|
data->ipv6.rules.num = 0ULL;
|
|
}
|
|
|
|
struct nsim_fib_data *nsim_fib_create(void)
|
|
{
|
|
struct nsim_fib_data *data;
|
|
int err;
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
data->ipv4.fib.max = (u64)-1;
|
|
data->ipv4.rules.max = (u64)-1;
|
|
|
|
data->ipv6.fib.max = (u64)-1;
|
|
data->ipv6.rules.max = (u64)-1;
|
|
|
|
data->fib_nb.notifier_call = nsim_fib_event_nb;
|
|
err = register_fib_notifier(&data->fib_nb, nsim_fib_dump_inconsistent);
|
|
if (err) {
|
|
pr_err("Failed to register fib notifier\n");
|
|
goto err_out;
|
|
}
|
|
|
|
return data;
|
|
|
|
err_out:
|
|
kfree(data);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
void nsim_fib_destroy(struct nsim_fib_data *data)
|
|
{
|
|
unregister_fib_notifier(&data->fib_nb);
|
|
kfree(data);
|
|
}
|