957 lines · 22.9 KB · C
RawDownload
/* * 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 <linux/in6.h>#include <linux/kernel.h>#include <linux/list.h>#include <linux/rhashtable.h>#include <linux/spinlock_types.h>#include <linux/types.h>#include <net/fib_notifier.h>#include <net/ip_fib.h>#include <net/ip6_fib.h>#include <net/fib_rules.h>#include <net/net_namespace.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;	struct rhashtable fib_rt_ht;	struct list_head fib_rt_list;	spinlock_t fib_lock;	/* Protects hashtable, list and accounting */	struct devlink *devlink;}; struct nsim_fib_rt_key {	unsigned char addr[sizeof(struct in6_addr)];	unsigned char prefix_len;	int family;	u32 tb_id;}; struct nsim_fib_rt {	struct nsim_fib_rt_key key;	struct rhash_head ht_node;	struct list_head list;	/* Member of fib_rt_list */}; struct nsim_fib4_rt {	struct nsim_fib_rt common;	struct fib_info *fi;	u8 tos;	u8 type;}; struct nsim_fib6_rt {	struct nsim_fib_rt common;	struct list_head nh_list;	unsigned int nhs;}; struct nsim_fib6_rt_nh {	struct list_head list;	/* Member of nh_list */	struct fib6_info *rt;}; static const struct rhashtable_params nsim_fib_rt_ht_params = {	.key_offset = offsetof(struct nsim_fib_rt, key),	.head_offset = offsetof(struct nsim_fib_rt, ht_node),	.key_len = sizeof(struct nsim_fib_rt_key),	.automatic_shrinking = true,}; 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;} static void nsim_fib_set_max(struct nsim_fib_data *fib_data,			     enum nsim_resource_id res_id, u64 val){	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:		WARN_ON(1);		return;	}	entry->max = val;} 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 void nsim_fib_rt_init(struct nsim_fib_data *data,			     struct nsim_fib_rt *fib_rt, const void *addr,			     size_t addr_len, unsigned int prefix_len,			     int family, u32 tb_id){	memcpy(fib_rt->key.addr, addr, addr_len);	fib_rt->key.prefix_len = prefix_len;	fib_rt->key.family = family;	fib_rt->key.tb_id = tb_id;	list_add(&fib_rt->list, &data->fib_rt_list);} static void nsim_fib_rt_fini(struct nsim_fib_rt *fib_rt){	list_del(&fib_rt->list);} static struct nsim_fib_rt *nsim_fib_rt_lookup(struct rhashtable *fib_rt_ht,					      const void *addr, size_t addr_len,					      unsigned int prefix_len,					      int family, u32 tb_id){	struct nsim_fib_rt_key key; 	memset(&key, 0, sizeof(key));	memcpy(key.addr, addr, addr_len);	key.prefix_len = prefix_len;	key.family = family;	key.tb_id = tb_id; 	return rhashtable_lookup_fast(fib_rt_ht, &key, nsim_fib_rt_ht_params);} static struct nsim_fib4_rt *nsim_fib4_rt_create(struct nsim_fib_data *data,		    struct fib_entry_notifier_info *fen_info){	struct nsim_fib4_rt *fib4_rt; 	fib4_rt = kzalloc(sizeof(*fib4_rt), GFP_ATOMIC);	if (!fib4_rt)		return NULL; 	nsim_fib_rt_init(data, &fib4_rt->common, &fen_info->dst, sizeof(u32),			 fen_info->dst_len, AF_INET, fen_info->tb_id); 	fib4_rt->fi = fen_info->fi;	fib_info_hold(fib4_rt->fi);	fib4_rt->tos = fen_info->tos;	fib4_rt->type = fen_info->type; 	return fib4_rt;} static void nsim_fib4_rt_destroy(struct nsim_fib4_rt *fib4_rt){	fib_info_put(fib4_rt->fi);	nsim_fib_rt_fini(&fib4_rt->common);	kfree(fib4_rt);} static struct nsim_fib4_rt *nsim_fib4_rt_lookup(struct rhashtable *fib_rt_ht,		    const struct fib_entry_notifier_info *fen_info){	struct nsim_fib_rt *fib_rt; 	fib_rt = nsim_fib_rt_lookup(fib_rt_ht, &fen_info->dst, sizeof(u32),				    fen_info->dst_len, AF_INET,				    fen_info->tb_id);	if (!fib_rt)		return NULL; 	return container_of(fib_rt, struct nsim_fib4_rt, common);} static void nsim_fib4_rt_hw_flags_set(struct net *net,				      const struct nsim_fib4_rt *fib4_rt,				      bool trap){	u32 *p_dst = (u32 *) fib4_rt->common.key.addr;	int dst_len = fib4_rt->common.key.prefix_len;	struct fib_rt_info fri; 	fri.fi = fib4_rt->fi;	fri.tb_id = fib4_rt->common.key.tb_id;	fri.dst = cpu_to_be32(*p_dst);	fri.dst_len = dst_len;	fri.tos = fib4_rt->tos;	fri.type = fib4_rt->type;	fri.offload = false;	fri.trap = trap;	fib_alias_hw_flags_set(net, &fri);} static int nsim_fib4_rt_add(struct nsim_fib_data *data,			    struct nsim_fib4_rt *fib4_rt,			    struct netlink_ext_ack *extack){	struct net *net = devlink_net(data->devlink);	int err; 	err = nsim_fib_account(&data->ipv4.fib, true, extack);	if (err)		return err; 	err = rhashtable_insert_fast(&data->fib_rt_ht,				     &fib4_rt->common.ht_node,				     nsim_fib_rt_ht_params);	if (err) {		NL_SET_ERR_MSG_MOD(extack, "Failed to insert IPv4 route");		goto err_fib_dismiss;	} 	nsim_fib4_rt_hw_flags_set(net, fib4_rt, true); 	return 0; err_fib_dismiss:	nsim_fib_account(&data->ipv4.fib, false, extack);	return err;} static int nsim_fib4_rt_replace(struct nsim_fib_data *data,				struct nsim_fib4_rt *fib4_rt,				struct nsim_fib4_rt *fib4_rt_old,				struct netlink_ext_ack *extack){	struct net *net = devlink_net(data->devlink);	int err; 	/* We are replacing a route, so no need to change the accounting. */	err = rhashtable_replace_fast(&data->fib_rt_ht,				      &fib4_rt_old->common.ht_node,				      &fib4_rt->common.ht_node,				      nsim_fib_rt_ht_params);	if (err) {		NL_SET_ERR_MSG_MOD(extack, "Failed to replace IPv4 route");		return err;	} 	nsim_fib4_rt_hw_flags_set(net, fib4_rt, true); 	nsim_fib4_rt_hw_flags_set(net, fib4_rt_old, false);	nsim_fib4_rt_destroy(fib4_rt_old); 	return 0;} static int nsim_fib4_rt_insert(struct nsim_fib_data *data,			       struct fib_entry_notifier_info *fen_info){	struct netlink_ext_ack *extack = fen_info->info.extack;	struct nsim_fib4_rt *fib4_rt, *fib4_rt_old;	int err; 	fib4_rt = nsim_fib4_rt_create(data, fen_info);	if (!fib4_rt)		return -ENOMEM; 	fib4_rt_old = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info);	if (!fib4_rt_old)		err = nsim_fib4_rt_add(data, fib4_rt, extack);	else		err = nsim_fib4_rt_replace(data, fib4_rt, fib4_rt_old, extack); 	if (err)		nsim_fib4_rt_destroy(fib4_rt); 	return err;} static void nsim_fib4_rt_remove(struct nsim_fib_data *data,				const struct fib_entry_notifier_info *fen_info){	struct netlink_ext_ack *extack = fen_info->info.extack;	struct nsim_fib4_rt *fib4_rt; 	fib4_rt = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info);	if (WARN_ON_ONCE(!fib4_rt))		return; 	rhashtable_remove_fast(&data->fib_rt_ht, &fib4_rt->common.ht_node,			       nsim_fib_rt_ht_params);	nsim_fib_account(&data->ipv4.fib, false, extack);	nsim_fib4_rt_destroy(fib4_rt);} static int nsim_fib4_event(struct nsim_fib_data *data,			   struct fib_notifier_info *info,			   unsigned long event){	struct fib_entry_notifier_info *fen_info;	int err = 0; 	fen_info = container_of(info, struct fib_entry_notifier_info, info); 	if (fen_info->fi->nh) {		NL_SET_ERR_MSG_MOD(info->extack, "IPv4 route with nexthop objects is not supported");		return 0;	} 	switch (event) {	case FIB_EVENT_ENTRY_REPLACE:		err = nsim_fib4_rt_insert(data, fen_info);		break;	case FIB_EVENT_ENTRY_DEL:		nsim_fib4_rt_remove(data, fen_info);		break;	default:		break;	} 	return err;} static struct nsim_fib6_rt_nh *nsim_fib6_rt_nh_find(const struct nsim_fib6_rt *fib6_rt,		     const struct fib6_info *rt){	struct nsim_fib6_rt_nh *fib6_rt_nh; 	list_for_each_entry(fib6_rt_nh, &fib6_rt->nh_list, list) {		if (fib6_rt_nh->rt == rt)			return fib6_rt_nh;	} 	return NULL;} static int nsim_fib6_rt_nh_add(struct nsim_fib6_rt *fib6_rt,			       struct fib6_info *rt){	struct nsim_fib6_rt_nh *fib6_rt_nh; 	fib6_rt_nh = kzalloc(sizeof(*fib6_rt_nh), GFP_ATOMIC);	if (!fib6_rt_nh)		return -ENOMEM; 	fib6_info_hold(rt);	fib6_rt_nh->rt = rt;	list_add_tail(&fib6_rt_nh->list, &fib6_rt->nh_list);	fib6_rt->nhs++; 	return 0;} static void nsim_fib6_rt_nh_del(struct nsim_fib6_rt *fib6_rt,				const struct fib6_info *rt){	struct nsim_fib6_rt_nh *fib6_rt_nh; 	fib6_rt_nh = nsim_fib6_rt_nh_find(fib6_rt, rt);	if (WARN_ON_ONCE(!fib6_rt_nh))		return; 	fib6_rt->nhs--;	list_del(&fib6_rt_nh->list);#if IS_ENABLED(CONFIG_IPV6)	fib6_info_release(fib6_rt_nh->rt);#endif	kfree(fib6_rt_nh);} static struct nsim_fib6_rt *nsim_fib6_rt_create(struct nsim_fib_data *data,		    struct fib6_entry_notifier_info *fen6_info){	struct fib6_info *iter, *rt = fen6_info->rt;	struct nsim_fib6_rt *fib6_rt;	int i = 0;	int err; 	fib6_rt = kzalloc(sizeof(*fib6_rt), GFP_ATOMIC);	if (!fib6_rt)		return ERR_PTR(-ENOMEM); 	nsim_fib_rt_init(data, &fib6_rt->common, &rt->fib6_dst.addr,			 sizeof(rt->fib6_dst.addr), rt->fib6_dst.plen, AF_INET6,			 rt->fib6_table->tb6_id); 	/* We consider a multipath IPv6 route as one entry, but it can be made	 * up from several fib6_info structs (one for each nexthop), so we	 * add them all to the same list under the entry.	 */	INIT_LIST_HEAD(&fib6_rt->nh_list); 	err = nsim_fib6_rt_nh_add(fib6_rt, rt);	if (err)		goto err_fib_rt_fini; 	if (!fen6_info->nsiblings)		return fib6_rt; 	list_for_each_entry(iter, &rt->fib6_siblings, fib6_siblings) {		if (i == fen6_info->nsiblings)			break; 		err = nsim_fib6_rt_nh_add(fib6_rt, iter);		if (err)			goto err_fib6_rt_nh_del;		i++;	}	WARN_ON_ONCE(i != fen6_info->nsiblings); 	return fib6_rt;

Lines 1–500 of 957.