2776 lines · 63.6 KB · C
RawDownload
/* *	IP multicast routing support for mrouted 3.6/3.8 * *		(c) 1995 Alan Cox, <alan@lxorguk.ukuu.org.uk> *	  Linux Consultancy and Custom Driver Development * *	This program is free software; you can redistribute it and/or *	modify it under the terms of the GNU General Public License *	as published by the Free Software Foundation; either version *	2 of the License, or (at your option) any later version. * *	Fixes: *	Michael Chastain	:	Incorrect size of copying. *	Alan Cox		:	Added the cache manager code *	Alan Cox		:	Fixed the clone/copy bug and device race. *	Mike McLagan		:	Routing by source *	Malcolm Beattie		:	Buffer handling fixes. *	Alexey Kuznetsov	:	Double buffer free and other fixes. *	SVR Anand		:	Fixed several multicast bugs and problems. *	Alexey Kuznetsov	:	Status, optimisations and more. *	Brad Parker		:	Better behaviour on mrouted upcall *					overflow. *      Carlos Picoto           :       PIMv1 Support *	Pavlin Ivanov Radoslavov:	PIMv2 Registers must checksum only PIM header *					Relax this requirement to work with older peers. * */ #include <asm/uaccess.h>#include <linux/types.h>#include <linux/capability.h>#include <linux/errno.h>#include <linux/timer.h>#include <linux/mm.h>#include <linux/kernel.h>#include <linux/fcntl.h>#include <linux/stat.h>#include <linux/socket.h>#include <linux/in.h>#include <linux/inet.h>#include <linux/netdevice.h>#include <linux/inetdevice.h>#include <linux/igmp.h>#include <linux/proc_fs.h>#include <linux/seq_file.h>#include <linux/mroute.h>#include <linux/init.h>#include <linux/if_ether.h>#include <linux/slab.h>#include <net/net_namespace.h>#include <net/ip.h>#include <net/protocol.h>#include <linux/skbuff.h>#include <net/route.h>#include <net/sock.h>#include <net/icmp.h>#include <net/udp.h>#include <net/raw.h>#include <linux/notifier.h>#include <linux/if_arp.h>#include <linux/netfilter_ipv4.h>#include <linux/compat.h>#include <linux/export.h>#include <net/ip_tunnels.h>#include <net/checksum.h>#include <net/netlink.h>#include <net/fib_rules.h>#include <linux/netconf.h> #if defined(CONFIG_IP_PIMSM_V1) || defined(CONFIG_IP_PIMSM_V2)#define CONFIG_IP_PIMSM	1#endif struct mr_table {	struct list_head	list;#ifdef CONFIG_NET_NS	struct net		*net;#endif	u32			id;	struct sock __rcu	*mroute_sk;	struct timer_list	ipmr_expire_timer;	struct list_head	mfc_unres_queue;	struct list_head	mfc_cache_array[MFC_LINES];	struct vif_device	vif_table[MAXVIFS];	int			maxvif;	atomic_t		cache_resolve_queue_len;	bool			mroute_do_assert;	bool			mroute_do_pim;#if defined(CONFIG_IP_PIMSM_V1) || defined(CONFIG_IP_PIMSM_V2)	int			mroute_reg_vif_num;#endif}; struct ipmr_rule {	struct fib_rule		common;}; struct ipmr_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_RWLOCK(mrt_lock); /* *	Multicast router control variables */ #define VIF_EXISTS(_mrt, _idx) ((_mrt)->vif_table[_idx].dev != NULL) /* 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 *ipmr_new_table(struct net *net, u32 id);static void ipmr_free_table(struct mr_table *mrt); static void ip_mr_forward(struct net *net, struct mr_table *mrt,			  struct sk_buff *skb, struct mfc_cache *cache,			  int local);static int ipmr_cache_report(struct mr_table *mrt,			     struct sk_buff *pkt, vifi_t vifi, int assert);static int __ipmr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb,			      struct mfc_cache *c, struct rtmsg *rtm);static void mroute_netlink_event(struct mr_table *mrt, struct mfc_cache *mfc,				 int cmd);static void mroute_clean_tables(struct mr_table *mrt);static void ipmr_expire_process(unsigned long arg); #ifdef CONFIG_IP_MROUTE_MULTIPLE_TABLES#define ipmr_for_each_table(mrt, net) \	list_for_each_entry_rcu(mrt, &net->ipv4.mr_tables, list) static struct mr_table *ipmr_get_table(struct net *net, u32 id){	struct mr_table *mrt; 	ipmr_for_each_table(mrt, net) {		if (mrt->id == id)			return mrt;	}	return NULL;} static int ipmr_fib_lookup(struct net *net, struct flowi4 *flp4,			   struct mr_table **mrt){	int err;	struct ipmr_result res;	struct fib_lookup_arg arg = {		.result = &res,		.flags = FIB_LOOKUP_NOREF,	}; 	err = fib_rules_lookup(net->ipv4.mr_rules_ops,			       flowi4_to_flowi(flp4), 0, &arg);	if (err < 0)		return err;	*mrt = res.mrt;	return 0;} static int ipmr_rule_action(struct fib_rule *rule, struct flowi *flp,			    int flags, struct fib_lookup_arg *arg){	struct ipmr_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;	} 	mrt = ipmr_get_table(rule->fr_net, rule->table);	if (mrt == NULL)		return -EAGAIN;	res->mrt = mrt;	return 0;} static int ipmr_rule_match(struct fib_rule *rule, struct flowi *fl, int flags){	return 1;} static const struct nla_policy ipmr_rule_policy[FRA_MAX + 1] = {	FRA_GENERIC_POLICY,}; static int ipmr_rule_configure(struct fib_rule *rule, struct sk_buff *skb,			       struct fib_rule_hdr *frh, struct nlattr **tb){	return 0;} static int ipmr_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh,			     struct nlattr **tb){	return 1;} static int ipmr_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 ipmr_rules_ops_template = {	.family		= RTNL_FAMILY_IPMR,	.rule_size	= sizeof(struct ipmr_rule),	.addr_size	= sizeof(u32),	.action		= ipmr_rule_action,	.match		= ipmr_rule_match,	.configure	= ipmr_rule_configure,	.compare	= ipmr_rule_compare,	.default_pref	= fib_default_rule_pref,	.fill		= ipmr_rule_fill,	.nlgroup	= RTNLGRP_IPV4_RULE,	.policy		= ipmr_rule_policy,	.owner		= THIS_MODULE,}; static int __net_init ipmr_rules_init(struct net *net){	struct fib_rules_ops *ops;	struct mr_table *mrt;	int err; 	ops = fib_rules_register(&ipmr_rules_ops_template, net);	if (IS_ERR(ops))		return PTR_ERR(ops); 	INIT_LIST_HEAD(&net->ipv4.mr_tables); 	mrt = ipmr_new_table(net, RT_TABLE_DEFAULT);	if (mrt == NULL) {		err = -ENOMEM;		goto err1;	} 	err = fib_default_rule_add(ops, 0x7fff, RT_TABLE_DEFAULT, 0);	if (err < 0)		goto err2; 	net->ipv4.mr_rules_ops = ops;	return 0; err2:	kfree(mrt);err1:	fib_rules_unregister(ops);	return err;} static void __net_exit ipmr_rules_exit(struct net *net){	struct mr_table *mrt, *next; 	list_for_each_entry_safe(mrt, next, &net->ipv4.mr_tables, list) {		list_del(&mrt->list);		ipmr_free_table(mrt);	}	fib_rules_unregister(net->ipv4.mr_rules_ops);}#else#define ipmr_for_each_table(mrt, net) \	for (mrt = net->ipv4.mrt; mrt; mrt = NULL) static struct mr_table *ipmr_get_table(struct net *net, u32 id){	return net->ipv4.mrt;} static int ipmr_fib_lookup(struct net *net, struct flowi4 *flp4,			   struct mr_table **mrt){	*mrt = net->ipv4.mrt;	return 0;} static int __net_init ipmr_rules_init(struct net *net){	net->ipv4.mrt = ipmr_new_table(net, RT_TABLE_DEFAULT);	return net->ipv4.mrt ? 0 : -ENOMEM;} static void __net_exit ipmr_rules_exit(struct net *net){	ipmr_free_table(net->ipv4.mrt);}#endif static struct mr_table *ipmr_new_table(struct net *net, u32 id){	struct mr_table *mrt;	unsigned int i; 	mrt = ipmr_get_table(net, id);	if (mrt != NULL)		return mrt; 	mrt = kzalloc(sizeof(*mrt), GFP_KERNEL);	if (mrt == NULL)		return NULL;	write_pnet(&mrt->net, net);	mrt->id = id; 	/* Forwarding cache */	for (i = 0; i < MFC_LINES; i++)		INIT_LIST_HEAD(&mrt->mfc_cache_array[i]); 	INIT_LIST_HEAD(&mrt->mfc_unres_queue); 	setup_timer(&mrt->ipmr_expire_timer, ipmr_expire_process,		    (unsigned long)mrt); #ifdef CONFIG_IP_PIMSM	mrt->mroute_reg_vif_num = -1;#endif#ifdef CONFIG_IP_MROUTE_MULTIPLE_TABLES	list_add_tail_rcu(&mrt->list, &net->ipv4.mr_tables);#endif	return mrt;} static void ipmr_free_table(struct mr_table *mrt){	del_timer_sync(&mrt->ipmr_expire_timer);	mroute_clean_tables(mrt);	kfree(mrt);} /* Service routines creating virtual interfaces: DVMRP tunnels and PIMREG */ static void ipmr_del_tunnel(struct net_device *dev, struct vifctl *v){	struct net *net = dev_net(dev); 	dev_close(dev); 	dev = __dev_get_by_name(net, "tunl0");	if (dev) {		const struct net_device_ops *ops = dev->netdev_ops;		struct ifreq ifr;		struct ip_tunnel_parm p; 		memset(&p, 0, sizeof(p));		p.iph.daddr = v->vifc_rmt_addr.s_addr;		p.iph.saddr = v->vifc_lcl_addr.s_addr;		p.iph.version = 4;		p.iph.ihl = 5;		p.iph.protocol = IPPROTO_IPIP;		sprintf(p.name, "dvmrp%d", v->vifc_vifi);		ifr.ifr_ifru.ifru_data = (__force void __user *)&p; 		if (ops->ndo_do_ioctl) {			mm_segment_t oldfs = get_fs(); 			set_fs(KERNEL_DS);			ops->ndo_do_ioctl(dev, &ifr, SIOCDELTUNNEL);			set_fs(oldfs);		}	}} staticstruct net_device *ipmr_new_tunnel(struct net *net, struct vifctl *v){	struct net_device  *dev; 	dev = __dev_get_by_name(net, "tunl0"); 	if (dev) {		const struct net_device_ops *ops = dev->netdev_ops;		int err;		struct ifreq ifr;		struct ip_tunnel_parm p;		struct in_device  *in_dev; 		memset(&p, 0, sizeof(p));		p.iph.daddr = v->vifc_rmt_addr.s_addr;		p.iph.saddr = v->vifc_lcl_addr.s_addr;		p.iph.version = 4;		p.iph.ihl = 5;		p.iph.protocol = IPPROTO_IPIP;		sprintf(p.name, "dvmrp%d", v->vifc_vifi);		ifr.ifr_ifru.ifru_data = (__force void __user *)&p; 		if (ops->ndo_do_ioctl) {			mm_segment_t oldfs = get_fs(); 			set_fs(KERNEL_DS);			err = ops->ndo_do_ioctl(dev, &ifr, SIOCADDTUNNEL);			set_fs(oldfs);		} else {			err = -EOPNOTSUPP;		}		dev = NULL; 		if (err == 0 &&		    (dev = __dev_get_by_name(net, p.name)) != NULL) {			dev->flags |= IFF_MULTICAST; 			in_dev = __in_dev_get_rtnl(dev);			if (in_dev == NULL)				goto failure; 			ipv4_devconf_setall(in_dev);			IPV4_DEVCONF(in_dev->cnf, RP_FILTER) = 0; 			if (dev_open(dev))				goto failure;			dev_hold(dev);		}	}	return dev; failure:	/* allow the register to be completed before unregistering. */	rtnl_unlock();	rtnl_lock(); 	unregister_netdevice(dev);	return NULL;} #ifdef CONFIG_IP_PIMSM static netdev_tx_t reg_vif_xmit(struct sk_buff *skb, struct net_device *dev){	struct net *net = dev_net(dev);	struct mr_table *mrt;	struct flowi4 fl4 = {		.flowi4_oif	= dev->ifindex,		.flowi4_iif	= skb->skb_iif,		.flowi4_mark	= skb->mark,	};	int err; 	err = ipmr_fib_lookup(net, &fl4, &mrt);	if (err < 0) {		kfree_skb(skb);		return err;	} 	read_lock(&mrt_lock);	dev->stats.tx_bytes += skb->len;	dev->stats.tx_packets++;	ipmr_cache_report(mrt, skb, mrt->mroute_reg_vif_num, IGMPMSG_WHOLEPKT);	read_unlock(&mrt_lock);	kfree_skb(skb);	return NETDEV_TX_OK;} static const struct net_device_ops reg_vif_netdev_ops = {	.ndo_start_xmit	= reg_vif_xmit,}; static void reg_vif_setup(struct net_device *dev){	dev->type		= ARPHRD_PIMREG;	dev->mtu		= ETH_DATA_LEN - sizeof(struct iphdr) - 8;	dev->flags		= IFF_NOARP;	dev->netdev_ops		= &reg_vif_netdev_ops,	dev->destructor		= free_netdev;	dev->features		|= NETIF_F_NETNS_LOCAL;} static struct net_device *ipmr_reg_vif(struct net *net, struct mr_table *mrt){	struct net_device *dev;	struct in_device *in_dev;	char name[IFNAMSIZ]; 	if (mrt->id == RT_TABLE_DEFAULT)		sprintf(name, "pimreg");	else		sprintf(name, "pimreg%u", mrt->id);

Lines 1–500 of 2776.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t