/* * 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);}
/*
* 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);
}