2832 lines · 67.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *	Linux IPv6 multicast routing support for BSD pim6sd *	Based on net/ipv4/ipmr.c. * *	(c) 2004 Mickael Hoerdt, <hoerdt@clarinet.u-strasbg.fr> *		LSIIT Laboratory, Strasbourg, France *	(c) 2004 Jean-Philippe Andriot, <jean-philippe.andriot@6WIND.com> *		6WIND, Paris, France *	Copyright (C)2007,2008 USAGI/WIDE Project *		YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> */ #include <linux/uaccess.h>#include <linux/types.h>#include <linux/sched.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/kernel.h>#include <linux/fcntl.h>#include <linux/stat.h>#include <linux/socket.h>#include <linux/inet.h>#include <linux/netdevice.h>#include <linux/inetdevice.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/init.h>#include <linux/compat.h>#include <linux/rhashtable.h>#include <net/protocol.h>#include <linux/skbuff.h>#include <net/raw.h>#include <linux/notifier.h>#include <linux/if_arp.h>#include <net/checksum.h>#include <net/netlink.h>#include <net/fib_rules.h> #include <net/ipv6.h>#include <net/ip6_route.h>#include <linux/mroute6.h>#include <linux/pim.h>#include <net/addrconf.h>#include <linux/netfilter_ipv6.h>#include <linux/export.h>#include <net/ip6_checksum.h>#include <linux/netconf.h>#include <net/ip_tunnels.h> #include <linux/nospec.h> struct ip6mr_rule {	struct fib_rule		common;}; struct ip6mr_result {	struct mr_table	*mrt;}; /* Big lock, protecting vif table, mrt cache and mroute socket state.   Note that the changes are semaphored via rtnl_lock. */ static DEFINE_SPINLOCK(mrt_lock); static struct net_device *vif_dev_read(const struct vif_device *vif){	return rcu_dereference(vif->dev);} /* Multicast router control variables */ /* Special spinlock for queue of unresolved entries */static DEFINE_SPINLOCK(mfc_unres_lock); /* We return to original Alan's scheme. Hash table of resolved   entries is changed only in process context and protected   with weak lock mrt_lock. Queue of unresolved entries is protected   with strong spinlock mfc_unres_lock.    In this case data path is free of exclusive locks at all. */ static struct kmem_cache *mrt_cachep __read_mostly; static struct mr_table *ip6mr_new_table(struct net *net, u32 id);static void ip6mr_free_table(struct mr_table *mrt,			     struct list_head *dev_kill_list); static void ip6_mr_forward(struct net *net, struct mr_table *mrt,			   struct net_device *dev, struct sk_buff *skb,			   struct mfc6_cache *cache);static int ip6mr_cache_report(const struct mr_table *mrt, struct sk_buff *pkt,			      mifi_t mifi, int assert);static void mr6_netlink_event(struct mr_table *mrt, struct mfc6_cache *mfc,			      int cmd);static void mrt6msg_netlink_event(const struct mr_table *mrt, struct sk_buff *pkt);static int ip6mr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,			      struct netlink_ext_ack *extack);static int ip6mr_rtm_dumproute(struct sk_buff *skb,			       struct netlink_callback *cb);static void mroute_clean_tables(struct mr_table *mrt, int flags,				struct list_head *dev_kill_list);static void ipmr_expire_process(struct timer_list *t); #ifdef CONFIG_IPV6_MROUTE_MULTIPLE_TABLES#define ip6mr_for_each_table(mrt, net) \	list_for_each_entry_rcu(mrt, &net->ipv6.mr6_tables, list, \				lockdep_rtnl_is_held() || \				list_empty(&net->ipv6.mr6_tables)) static struct mr_table *ip6mr_mr_table_iter(struct net *net,					    struct mr_table *mrt){	struct mr_table *ret; 	if (!mrt)		ret = list_entry_rcu(net->ipv6.mr6_tables.next,				     struct mr_table, list);	else		ret = list_entry_rcu(mrt->list.next,				     struct mr_table, list); 	if (&ret->list == &net->ipv6.mr6_tables)		return NULL;	return ret;} static struct mr_table *__ip6mr_get_table(struct net *net, u32 id){	struct mr_table *mrt; 	ip6mr_for_each_table(mrt, net) {		if (mrt->id == id)			return mrt;	}	return NULL;} static int ip6mr_fib_lookup(struct net *net, struct flowi6 *flp6,			    struct mr_table **mrt){	int err;	struct ip6mr_result res;	struct fib_lookup_arg arg = {		.result = &res,		.flags = FIB_LOOKUP_NOREF,	}; 	/* update flow if oif or iif point to device enslaved to l3mdev */	l3mdev_update_flow(net, flowi6_to_flowi(flp6)); 	err = fib_rules_lookup(net->ipv6.mr6_rules_ops,			       flowi6_to_flowi(flp6), 0, &arg);	if (err < 0)		return err;	*mrt = res.mrt;	return 0;} static int ip6mr_rule_action(struct fib_rule *rule, struct flowi *flp,			     int flags, struct fib_lookup_arg *arg){	struct ip6mr_result *res = arg->result;	struct mr_table *mrt; 	switch (rule->action) {	case FR_ACT_TO_TBL:		break;	case FR_ACT_UNREACHABLE:		return -ENETUNREACH;	case FR_ACT_PROHIBIT:		return -EACCES;	case FR_ACT_BLACKHOLE:	default:		return -EINVAL;	} 	arg->table = fib_rule_get_table(rule, arg); 	mrt = __ip6mr_get_table(rule->fr_net, arg->table);	if (!mrt)		return -EAGAIN;	res->mrt = mrt;	return 0;} static int ip6mr_rule_match(struct fib_rule *rule, struct flowi *flp, int flags){	return 1;} static int ip6mr_rule_configure(struct fib_rule *rule, struct sk_buff *skb,				struct fib_rule_hdr *frh, struct nlattr **tb,				struct netlink_ext_ack *extack){	return 0;} static int ip6mr_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh,			      struct nlattr **tb){	return 1;} static int ip6mr_rule_fill(struct fib_rule *rule, struct sk_buff *skb,			   struct fib_rule_hdr *frh){	frh->dst_len = 0;	frh->src_len = 0;	frh->tos     = 0;	return 0;} static const struct fib_rules_ops __net_initconst ip6mr_rules_ops_template = {	.family		= RTNL_FAMILY_IP6MR,	.rule_size	= sizeof(struct ip6mr_rule),	.addr_size	= sizeof(struct in6_addr),	.action		= ip6mr_rule_action,	.match		= ip6mr_rule_match,	.configure	= ip6mr_rule_configure,	.compare	= ip6mr_rule_compare,	.fill		= ip6mr_rule_fill,	.nlgroup	= RTNLGRP_IPV6_RULE,	.owner		= THIS_MODULE,}; static int __net_init ip6mr_rules_init(struct net *net){	struct fib_rules_ops *ops;	LIST_HEAD(dev_kill_list);	struct mr_table *mrt;	int err; 	ops = fib_rules_register(&ip6mr_rules_ops_template, net);	if (IS_ERR(ops))		return PTR_ERR(ops); 	INIT_LIST_HEAD(&net->ipv6.mr6_tables); 	mrt = ip6mr_new_table(net, RT6_TABLE_DFLT);	if (IS_ERR(mrt)) {		err = PTR_ERR(mrt);		goto err1;	} 	err = fib_default_rule_add(ops, 0x7fff, RT6_TABLE_DFLT);	if (err < 0)		goto err2; 	net->ipv6.mr6_rules_ops = ops;	return 0; err2:	ip6mr_free_table(mrt, &dev_kill_list);err1:	fib_rules_unregister(ops);	return err;} static void __net_exit ip6mr_rules_exit(struct net *net){	fib_rules_unregister(net->ipv6.mr6_rules_ops);} static void __net_exit ip6mr_rules_exit_rtnl(struct net *net,					     struct list_head *dev_kill_list){	struct mr_table *mrt, *next; 	list_for_each_entry_safe(mrt, next, &net->ipv6.mr6_tables, list) {		list_del_rcu(&mrt->list);		ip6mr_free_table(mrt, dev_kill_list);	}} static int ip6mr_rules_dump(struct net *net, struct notifier_block *nb,			    struct netlink_ext_ack *extack){	return fib_rules_dump(net, nb, RTNL_FAMILY_IP6MR, extack);} static unsigned int ip6mr_rules_seq_read(const struct net *net){	return fib_rules_seq_read(net, RTNL_FAMILY_IP6MR);} bool ip6mr_rule_default(const struct fib_rule *rule){	return fib_rule_matchall(rule) && rule->action == FR_ACT_TO_TBL &&	       rule->table == RT6_TABLE_DFLT && !rule->l3mdev;}EXPORT_SYMBOL(ip6mr_rule_default);#elsestatic struct mr_table *ip6mr_mr_table_iter(struct net *net,					    struct mr_table *mrt){	if (!mrt)		return rcu_dereference(net->ipv6.mrt6);	return NULL;} static struct mr_table *__ip6mr_get_table(struct net *net, u32 id){	return rcu_dereference_check(net->ipv6.mrt6,				     lockdep_rtnl_is_held() ||				     !rcu_access_pointer(net->ipv6.mrt6));} #define ip6mr_for_each_table(mrt, net)				\	for (mrt = __ip6mr_get_table(net, 0); mrt; mrt = NULL) static int ip6mr_fib_lookup(struct net *net, struct flowi6 *flp6,			    struct mr_table **mrt){	*mrt = rcu_dereference(net->ipv6.mrt6);	if (!*mrt)		return -EAGAIN;	return 0;} static int __net_init ip6mr_rules_init(struct net *net){	struct mr_table *mrt; 	mrt = ip6mr_new_table(net, RT6_TABLE_DFLT);	if (IS_ERR(mrt))		return PTR_ERR(mrt); 	rcu_assign_pointer(net->ipv6.mrt6, mrt);	return 0;} static void __net_exit ip6mr_rules_exit(struct net *net){} static void __net_exit ip6mr_rules_exit_rtnl(struct net *net,					     struct list_head *dev_kill_list){	struct mr_table *mrt = rcu_dereference_protected(net->ipv6.mrt6, 1); 	RCU_INIT_POINTER(net->ipv6.mrt6, NULL);	ip6mr_free_table(mrt, dev_kill_list);} static int ip6mr_rules_dump(struct net *net, struct notifier_block *nb,			    struct netlink_ext_ack *extack){	return 0;} static unsigned int ip6mr_rules_seq_read(const struct net *net){	return 0;}#endif static struct mr_table *ip6mr_get_table(struct net *net, u32 id){	struct mr_table *mrt; 	rcu_read_lock();	mrt = __ip6mr_get_table(net, id);	rcu_read_unlock(); 	return mrt;} static int ip6mr_hash_cmp(struct rhashtable_compare_arg *arg,			  const void *ptr){	const struct mfc6_cache_cmp_arg *cmparg = arg->key;	struct mfc6_cache *c = (struct mfc6_cache *)ptr; 	return !ipv6_addr_equal(&c->mf6c_mcastgrp, &cmparg->mf6c_mcastgrp) ||	       !ipv6_addr_equal(&c->mf6c_origin, &cmparg->mf6c_origin);} static const struct rhashtable_params ip6mr_rht_params = {	.head_offset = offsetof(struct mr_mfc, mnode),	.key_offset = offsetof(struct mfc6_cache, cmparg),	.key_len = sizeof(struct mfc6_cache_cmp_arg),	.nelem_hint = 3,	.obj_cmpfn = ip6mr_hash_cmp,	.automatic_shrinking = true,}; static void ip6mr_new_table_set(struct mr_table *mrt,				struct net *net){#ifdef CONFIG_IPV6_MROUTE_MULTIPLE_TABLES	list_add_tail_rcu(&mrt->list, &net->ipv6.mr6_tables);#endif} static struct mfc6_cache_cmp_arg ip6mr_mr_table_ops_cmparg_any = {	.mf6c_origin = IN6ADDR_ANY_INIT,	.mf6c_mcastgrp = IN6ADDR_ANY_INIT,}; static struct mr_table_ops ip6mr_mr_table_ops = {	.rht_params = &ip6mr_rht_params,	.cmparg_any = &ip6mr_mr_table_ops_cmparg_any,}; static struct mr_table *ip6mr_new_table(struct net *net, u32 id){	struct mr_table *mrt; 	mrt = __ip6mr_get_table(net, id);	if (mrt)		return mrt; 	return mr_table_alloc(net, id, &ip6mr_mr_table_ops,			      ipmr_expire_process, ip6mr_new_table_set);} static void ip6mr_free_table(struct mr_table *mrt,			     struct list_head *dev_kill_list){	struct net *net = read_pnet(&mrt->net);	LIST_HEAD(ip6mr_dev_kill_list); 	WARN_ON_ONCE(!mr_can_free_table(net)); 	timer_shutdown_sync(&mrt->ipmr_expire_timer);	mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MIFS_STATIC |			    MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC,			    &ip6mr_dev_kill_list); 	mr_table_free(mrt); 	WARN_ON_ONCE(!net_initialized(net) && !list_empty(&ip6mr_dev_kill_list));	list_splice(&ip6mr_dev_kill_list, dev_kill_list);} #ifdef CONFIG_PROC_FS/* The /proc interfaces to multicast routing * /proc/ip6_mr_cache /proc/ip6_mr_vif */ static void *ip6mr_vif_seq_start(struct seq_file *seq, loff_t *pos)	__acquires(RCU){	struct mr_vif_iter *iter = seq->private;	struct net *net = seq_file_net(seq);	struct mr_table *mrt; 	rcu_read_lock();	mrt = __ip6mr_get_table(net, RT6_TABLE_DFLT);	if (!mrt) {		rcu_read_unlock();		return ERR_PTR(-ENOENT);	} 	iter->mrt = mrt; 	return mr_vif_seq_start(seq, pos);} static void ip6mr_vif_seq_stop(struct seq_file *seq, void *v)	__releases(RCU){	rcu_read_unlock();} static int ip6mr_vif_seq_show(struct seq_file *seq, void *v){	struct mr_vif_iter *iter = seq->private;	struct mr_table *mrt = iter->mrt; 	if (v == SEQ_START_TOKEN) {		seq_puts(seq,			 "Interface      BytesIn  PktsIn  BytesOut PktsOut Flags\n");	} else {		const struct vif_device *vif = v;		const struct net_device *vif_dev;		const char *name; 		vif_dev = vif_dev_read(vif);		name = vif_dev ? vif_dev->name : "none"; 		seq_printf(seq,			   "%2td %-10s %8ld %7ld  %8ld %7ld %05X\n",			   vif - mrt->vif_table,			   name, vif->bytes_in, vif->pkt_in,			   vif->bytes_out, vif->pkt_out,			   vif->flags);	}	return 0;} static const struct seq_operations ip6mr_vif_seq_ops = {	.start = ip6mr_vif_seq_start,	.next  = mr_vif_seq_next,	.stop  = ip6mr_vif_seq_stop,	.show  = ip6mr_vif_seq_show,};

Lines 1–500 of 2832.