1669 lines · 40.6 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/bitmap.h>#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/inet_dscp.h>#include <net/ip_fib.h>#include <net/ip6_fib.h>#include <net/fib_rules.h>#include <net/net_namespace.h>#include <net/nexthop.h>#include <linux/debugfs.h> #include "netdevsim.h" struct nsim_fib_entry {	u64 max;	atomic64_t 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 nsim_fib_entry nexthops;	struct rhashtable fib_rt_ht;	struct list_head fib_rt_list;	struct mutex fib_lock; /* Protects FIB HT and list */	struct notifier_block nexthop_nb;	struct rhashtable nexthop_ht;	struct devlink *devlink;	struct work_struct fib_event_work;	struct work_struct fib_flush_work;	struct list_head fib_event_queue;	spinlock_t fib_event_queue_lock; /* Protects fib event queue list */	struct mutex nh_lock; /* Protects NH HT */	struct dentry *ddir;	bool fail_route_offload;	bool fail_res_nexthop_group_replace;	bool fail_nexthop_bucket_replace;	bool fail_route_delete;}; 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;	dscp_t dscp;	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;}; struct nsim_fib6_event {	struct fib6_info **rt_arr;	unsigned int nrt6;}; struct nsim_fib_event {	struct list_head list; /* node in fib queue */	union {		struct fib_entry_notifier_info fen_info;		struct nsim_fib6_event fib6_event;	};	struct nsim_fib_data *data;	unsigned long event;	int family;}; 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,}; struct nsim_nexthop {	struct rhash_head ht_node;	u64 occ;	u32 id;	bool is_resilient;}; static const struct rhashtable_params nsim_nexthop_ht_params = {	.key_offset = offsetof(struct nsim_nexthop, id),	.head_offset = offsetof(struct nsim_nexthop, ht_node),	.key_len = sizeof(u32),	.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;	case NSIM_RESOURCE_NEXTHOPS:		entry = &fib_data->nexthops;		break;	default:		return 0;	} 	return max ? entry->max : atomic64_read(&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;	case NSIM_RESOURCE_NEXTHOPS:		entry = &fib_data->nexthops;		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 (!atomic64_add_unless(&entry->num, 1, entry->max)) {			err = -ENOSPC;			NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");		}	} else {		atomic64_dec_if_positive(&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){	int err = 0; 	if (add) {		if (!atomic64_add_unless(&entry->num, 1, entry->max))			err = -ENOSPC;	} else {		atomic64_dec_if_positive(&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_KERNEL);	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->dscp = fen_info->dscp;	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 voidnsim_fib4_rt_offload_failed_flag_set(struct net *net,				     struct fib_entry_notifier_info *fen_info){	u32 *p_dst = (u32 *)&fen_info->dst;	struct fib_rt_info fri; 	fri.fi = fen_info->fi;	fri.tb_id = fen_info->tb_id;	fri.dst = cpu_to_be32(*p_dst);	fri.dst_len = fen_info->dst_len;	fri.dscp = fen_info->dscp;	fri.type = fen_info->type;	fri.offload = false;	fri.trap = false;	fri.offload_failed = true;	fib_alias_hw_flags_set(net, &fri);} 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.dscp = fib4_rt->dscp;	fri.type = fib4_rt->type;	fri.offload = false;	fri.trap = trap;	fri.offload_failed = false;	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 net *net = devlink_net(data->devlink);	int err; 	err = rhashtable_insert_fast(&data->fib_rt_ht,				     &fib4_rt->common.ht_node,				     nsim_fib_rt_ht_params);	if (err)		goto err_fib_dismiss; 	/* Simulate hardware programming latency. */	msleep(1);	nsim_fib4_rt_hw_flags_set(net, fib4_rt, true); 	return 0; err_fib_dismiss:	/* Drop the accounting that was increased from the notification	 * context when FIB_EVENT_ENTRY_REPLACE was triggered.	 */	nsim_fib_account(&data->ipv4.fib, false);	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 net *net = devlink_net(data->devlink);	int err; 	/* We are replacing a route, so need to remove the accounting which	 * was increased when FIB_EVENT_ENTRY_REPLACE was triggered.	 */	err = nsim_fib_account(&data->ipv4.fib, false);	if (err)		return err;	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)		return err; 	msleep(1);	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 nsim_fib4_rt *fib4_rt, *fib4_rt_old;	int err; 	if (data->fail_route_offload) {		/* For testing purposes, user set debugfs fail_route_offload		 * value to true. Simulate hardware programming latency and then		 * fail.		 */		msleep(1);		return -EINVAL;	} 	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);	else		err = nsim_fib4_rt_replace(data, fib4_rt, fib4_rt_old); 	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 nsim_fib4_rt *fib4_rt; 	fib4_rt = nsim_fib4_rt_lookup(&data->fib_rt_ht, fen_info);	if (!fib4_rt)		return; 	rhashtable_remove_fast(&data->fib_rt_ht, &fib4_rt->common.ht_node,			       nsim_fib_rt_ht_params);	nsim_fib4_rt_destroy(fib4_rt);} static int nsim_fib4_event(struct nsim_fib_data *data,			   struct fib_entry_notifier_info *fen_info,			   unsigned long event){	int err = 0; 	switch (event) {	case FIB_EVENT_ENTRY_REPLACE:		err = nsim_fib4_rt_insert(data, fen_info);		if (err) {			struct net *net = devlink_net(data->devlink); 			nsim_fib4_rt_offload_failed_flag_set(net, 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_KERNEL);

Lines 1–500 of 1669.