diff options
Diffstat (limited to 'net/core/dev_mcast.c')
| -rw-r--r-- | net/core/dev_mcast.c | 299 | 
1 files changed, 299 insertions, 0 deletions
diff --git a/net/core/dev_mcast.c b/net/core/dev_mcast.c new file mode 100644 index 00000000000..db098ff3cd6 --- /dev/null +++ b/net/core/dev_mcast.c @@ -0,0 +1,299 @@ +/* + *	Linux NET3:	Multicast List maintenance.  + * + *	Authors: + *		Tim Kordas <tjk@nostromo.eeap.cwru.edu>  + *		Richard Underwood <richard@wuzz.demon.co.uk> + * + *	Stir fried together from the IP multicast and CAP patches above + *		Alan Cox <Alan.Cox@linux.org>	 + * + *	Fixes: + *		Alan Cox	:	Update the device on a real delete + *					rather than any time but... + *		Alan Cox	:	IFF_ALLMULTI support. + *		Alan Cox	: 	New format set_multicast_list() calls. + *		Gleb Natapov    :       Remove dev_mc_lock. + * + *	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. + */ + +#include <linux/config.h>  +#include <linux/module.h>  +#include <asm/uaccess.h> +#include <asm/system.h> +#include <linux/bitops.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/socket.h> +#include <linux/sockios.h> +#include <linux/in.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/if_ether.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/init.h> +#include <net/ip.h> +#include <net/route.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <net/arp.h> + + +/* + *	Device multicast list maintenance.  + * + *	This is used both by IP and by the user level maintenance functions.  + *	Unlike BSD we maintain a usage count on a given multicast address so  + *	that a casual user application can add/delete multicasts used by  + *	protocols without doing damage to the protocols when it deletes the + *	entries. It also helps IP as it tracks overlapping maps. + * + *	Device mc lists are changed by bh at least if IPv6 is enabled, + *	so that it must be bh protected. + * + *	We block accesses to device mc filters with dev->xmit_lock. + */ + +/* + *	Update the multicast list into the physical NIC controller. + */ +  +static void __dev_mc_upload(struct net_device *dev) +{ +	/* Don't do anything till we up the interface +	 * [dev_open will call this function so the list will +	 * stay sane] +	 */ + +	if (!(dev->flags&IFF_UP)) +		return; + +	/* +	 *	Devices with no set multicast or which have been +	 *	detached don't get set. +	 */ + +	if (dev->set_multicast_list == NULL || +	    !netif_device_present(dev)) +		return; + +	dev->set_multicast_list(dev); +} + +void dev_mc_upload(struct net_device *dev) +{ +	spin_lock_bh(&dev->xmit_lock); +	__dev_mc_upload(dev); +	spin_unlock_bh(&dev->xmit_lock); +} + +/* + *	Delete a device level multicast + */ +  +int dev_mc_delete(struct net_device *dev, void *addr, int alen, int glbl) +{ +	int err = 0; +	struct dev_mc_list *dmi, **dmip; + +	spin_lock_bh(&dev->xmit_lock); + +	for (dmip = &dev->mc_list; (dmi = *dmip) != NULL; dmip = &dmi->next) { +		/* +		 *	Find the entry we want to delete. The device could +		 *	have variable length entries so check these too. +		 */ +		if (memcmp(dmi->dmi_addr, addr, dmi->dmi_addrlen) == 0 && +		    alen == dmi->dmi_addrlen) { +			if (glbl) { +				int old_glbl = dmi->dmi_gusers; +				dmi->dmi_gusers = 0; +				if (old_glbl == 0) +					break; +			} +			if (--dmi->dmi_users) +				goto done; + +			/* +			 *	Last user. So delete the entry. +			 */ +			*dmip = dmi->next; +			dev->mc_count--; + +			kfree(dmi); + +			/* +			 *	We have altered the list, so the card +			 *	loaded filter is now wrong. Fix it +			 */ +			__dev_mc_upload(dev); +			 +			spin_unlock_bh(&dev->xmit_lock); +			return 0; +		} +	} +	err = -ENOENT; +done: +	spin_unlock_bh(&dev->xmit_lock); +	return err; +} + +/* + *	Add a device level multicast + */ +  +int dev_mc_add(struct net_device *dev, void *addr, int alen, int glbl) +{ +	int err = 0; +	struct dev_mc_list *dmi, *dmi1; + +	dmi1 = (struct dev_mc_list *)kmalloc(sizeof(*dmi), GFP_ATOMIC); + +	spin_lock_bh(&dev->xmit_lock); +	for (dmi = dev->mc_list; dmi != NULL; dmi = dmi->next) { +		if (memcmp(dmi->dmi_addr, addr, dmi->dmi_addrlen) == 0 && +		    dmi->dmi_addrlen == alen) { +			if (glbl) { +				int old_glbl = dmi->dmi_gusers; +				dmi->dmi_gusers = 1; +				if (old_glbl) +					goto done; +			} +			dmi->dmi_users++; +			goto done; +		} +	} + +	if ((dmi = dmi1) == NULL) { +		spin_unlock_bh(&dev->xmit_lock); +		return -ENOMEM; +	} +	memcpy(dmi->dmi_addr, addr, alen); +	dmi->dmi_addrlen = alen; +	dmi->next = dev->mc_list; +	dmi->dmi_users = 1; +	dmi->dmi_gusers = glbl ? 1 : 0; +	dev->mc_list = dmi; +	dev->mc_count++; + +	__dev_mc_upload(dev); +	 +	spin_unlock_bh(&dev->xmit_lock); +	return 0; + +done: +	spin_unlock_bh(&dev->xmit_lock); +	if (dmi1) +		kfree(dmi1); +	return err; +} + +/* + *	Discard multicast list when a device is downed + */ + +void dev_mc_discard(struct net_device *dev) +{ +	spin_lock_bh(&dev->xmit_lock); +	 +	while (dev->mc_list != NULL) { +		struct dev_mc_list *tmp = dev->mc_list; +		dev->mc_list = tmp->next; +		if (tmp->dmi_users > tmp->dmi_gusers) +			printk("dev_mc_discard: multicast leakage! dmi_users=%d\n", tmp->dmi_users); +		kfree(tmp); +	} +	dev->mc_count = 0; + +	spin_unlock_bh(&dev->xmit_lock); +} + +#ifdef CONFIG_PROC_FS +static void *dev_mc_seq_start(struct seq_file *seq, loff_t *pos) +{ +	struct net_device *dev; +	loff_t off = 0; + +	read_lock(&dev_base_lock); +	for (dev = dev_base; dev; dev = dev->next) { +		if (off++ == *pos)  +			return dev; +	} +	return NULL; +} + +static void *dev_mc_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ +	struct net_device *dev = v; +	++*pos; +	return dev->next; +} + +static void dev_mc_seq_stop(struct seq_file *seq, void *v) +{ +	read_unlock(&dev_base_lock); +} + + +static int dev_mc_seq_show(struct seq_file *seq, void *v) +{ +	struct dev_mc_list *m; +	struct net_device *dev = v; + +	spin_lock_bh(&dev->xmit_lock); +	for (m = dev->mc_list; m; m = m->next) { +		int i; + +		seq_printf(seq, "%-4d %-15s %-5d %-5d ", dev->ifindex, +			   dev->name, m->dmi_users, m->dmi_gusers); + +		for (i = 0; i < m->dmi_addrlen; i++) +			seq_printf(seq, "%02x", m->dmi_addr[i]); + +		seq_putc(seq, '\n'); +	} +	spin_unlock_bh(&dev->xmit_lock); +	return 0; +} + +static struct seq_operations dev_mc_seq_ops = { +	.start = dev_mc_seq_start, +	.next  = dev_mc_seq_next, +	.stop  = dev_mc_seq_stop, +	.show  = dev_mc_seq_show, +}; + +static int dev_mc_seq_open(struct inode *inode, struct file *file) +{ +	return seq_open(file, &dev_mc_seq_ops); +} + +static struct file_operations dev_mc_seq_fops = { +	.owner	 = THIS_MODULE, +	.open    = dev_mc_seq_open, +	.read    = seq_read, +	.llseek  = seq_lseek, +	.release = seq_release, +}; + +#endif + +void __init dev_mcast_init(void) +{ +	proc_net_fops_create("dev_mcast", 0, &dev_mc_seq_fops); +} + +EXPORT_SYMBOL(dev_mc_add); +EXPORT_SYMBOL(dev_mc_delete); +EXPORT_SYMBOL(dev_mc_upload);  |