From ac6ecad34952822e7f85fae561fdb9c9229d5f4a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 4 Apr 2003 01:06:13 -0800 Subject: kobject: cause /sbin/hotplug to be called when kobjects are added and removed This only happens if a kobject belongs to a subsystem that has specified a set of hotplug operations. Based on work done by Kevin Fleming --- include/linux/kobject.h | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/kobject.h b/include/linux/kobject.h index e9cfa0dc3d24..8c84e7bce42e 100644 --- a/include/linux/kobject.h +++ b/include/linux/kobject.h @@ -57,12 +57,24 @@ struct kobj_type { * of object; multiple ksets can belong to one subsystem. All * ksets of a subsystem share the subsystem's lock. * + * Each kset can support hotplugging; if it does, it will be given + * the opportunity to filter out specific kobjects from being + * reported, as well as to add its own "data" elements to the + * environment being passed to the hotplug helper. */ +struct kset_hotplug_ops { + int (*filter)(struct kset *kset, struct kobject *kobj); + char *(*name)(struct kset *kset, struct kobject *kobj); + int (*hotplug)(struct kset *kset, struct kobject *kobj, char **envp, + int num_envp, char *buffer, int buffer_size); +}; + struct kset { struct subsystem * subsys; struct kobj_type * ktype; struct list_head list; struct kobject kobj; + struct kset_hotplug_ops * hotplug_ops; }; @@ -86,6 +98,13 @@ static inline void kset_put(struct kset * k) kobject_put(&k->kobj); } +static inline struct kobj_type * get_ktype(struct kobject * k) +{ + if (k->kset && k->kset->ktype) + return k->kset->ktype; + else + return k->ktype; +} extern struct kobject * kset_find_obj(struct kset *, const char *); @@ -95,11 +114,12 @@ struct subsystem { struct rw_semaphore rwsem; }; -#define decl_subsys(_name,_type) \ +#define decl_subsys(_name,_type,_hotplug_ops) \ struct subsystem _name##_subsys = { \ .kset = { \ .kobj = { .name = __stringify(_name) }, \ .ktype = _type, \ + .hotplug_ops =_hotplug_ops, \ } \ } -- cgit v1.2.3 From affdf6c288da0ba719d2e4f9aab50eea5a621669 Mon Sep 17 00:00:00 2001 From: Gerd Knorr Date: Mon, 7 Apr 2003 12:48:29 -0700 Subject: [PATCH] i2c: add i2c_clientname() This patch just adds a #define and a inline function to hide the "i2c_client->name" => "i2c_client->dev.name" move introduced by the recent i2c updates. That makes it easier to build i2c drivers on both 2.4 and 2.5 kernels. --- include/linux/i2c.h | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'include/linux') diff --git a/include/linux/i2c.h b/include/linux/i2c.h index bc10de846363..ddf1ee79389c 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -182,6 +182,13 @@ static inline void i2c_set_clientdata (struct i2c_client *dev, void *data) return dev_set_drvdata (&dev->dev, data); } +#define I2C_DEVNAME(str) .dev = { .name = str } + +static inline char *i2c_clientname(struct i2c_client *c) +{ + return c->dev.name; +} + /* * The following structs are for those who like to implement new bus drivers: * i2c_algorithm is the interface to a class of hardware solutions which can -- cgit v1.2.3 From 7481987d0b9bae116de5f5b388910c44df784eff Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 9 Apr 2003 00:36:21 -0700 Subject: i2c: clean up i2c-dev.c's formatting, DEBUG, and ioctl mess --- drivers/i2c/i2c-dev.c | 97 +++++++++++++++++++++++-------------------------- include/linux/i2c-dev.h | 6 +-- include/linux/i2c.h | 6 +-- 3 files changed, 52 insertions(+), 57 deletions(-) (limited to 'include/linux') diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index bf80b0060621..ac939bd90b5d 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -77,7 +77,7 @@ static struct i2c_adapter *i2cdev_adaps[I2CDEV_ADAPS_MAX]; static struct i2c_driver i2cdev_driver = { .owner = THIS_MODULE, - .name = "i2c-dev dummy driver", + .name = "dev driver", .id = I2C_DRIVERID_I2CDEV, .flags = I2C_DF_DUMMY, .attach_adapter = i2cdev_attach_adapter, @@ -100,10 +100,6 @@ static ssize_t i2cdev_read (struct file *file, char *buf, size_t count, char *tmp; int ret; -#ifdef DEBUG - struct inode *inode = file->f_dentry->d_inode; -#endif /* DEBUG */ - struct i2c_client *client = (struct i2c_client *)file->private_data; if (count > 8192) @@ -114,10 +110,8 @@ static ssize_t i2cdev_read (struct file *file, char *buf, size_t count, if (tmp==NULL) return -ENOMEM; -#ifdef DEBUG - printk(KERN_DEBUG "i2c-dev.o: i2c-%d reading %d bytes.\n",minor(inode->i_rdev), - count); -#endif + pr_debug("i2c-dev.o: i2c-%d reading %d bytes.\n", + minor(file->f_dentry->d_inode->i_rdev), count); ret = i2c_master_recv(client,tmp,count); if (ret >= 0) @@ -133,10 +127,6 @@ static ssize_t i2cdev_write (struct file *file, const char *buf, size_t count, char *tmp; struct i2c_client *client = (struct i2c_client *)file->private_data; -#ifdef DEBUG - struct inode *inode = file->f_dentry->d_inode; -#endif /* DEBUG */ - if (count > 8192) count = 8192; @@ -149,10 +139,9 @@ static ssize_t i2cdev_write (struct file *file, const char *buf, size_t count, return -EFAULT; } -#ifdef DEBUG - printk(KERN_DEBUG "i2c-dev.o: i2c-%d writing %d bytes.\n",minor(inode->i_rdev), - count); -#endif + pr_debug("i2c-dev.o: i2c-%d writing %d bytes.\n", + minor(file->f_dentry->d_inode->i_rdev), count); + ret = i2c_master_send(client,tmp,count); kfree(tmp); return ret; @@ -169,10 +158,8 @@ int i2cdev_ioctl (struct inode *inode, struct file *file, unsigned int cmd, int i,datasize,res; unsigned long funcs; -#ifdef DEBUG - printk(KERN_DEBUG "i2c-dev.o: i2c-%d ioctl, cmd: 0x%x, arg: %lx.\n", - minor(inode->i_rdev),cmd, arg); -#endif /* DEBUG */ + pr_debug("i2c-dev.o: i2c-%d ioctl, cmd: 0x%x, arg: %lx.\n", + minor(inode->i_rdev),cmd, arg); switch ( cmd ) { case I2C_SLAVE: @@ -207,6 +194,11 @@ int i2cdev_ioctl (struct inode *inode, struct file *file, unsigned int cmd, sizeof(rdwr_arg))) return -EFAULT; + /* Put an arbritrary limit on the number of messages that can + * be sent at once */ + if (rdwr_arg.nmsgs > 42) + return -EINVAL; + rdwr_pa = (struct i2c_msg *) kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), GFP_KERNEL); @@ -214,38 +206,43 @@ int i2cdev_ioctl (struct inode *inode, struct file *file, unsigned int cmd, if (rdwr_pa == NULL) return -ENOMEM; res = 0; - for( i=0; i 8192) { + res = -EINVAL; + break; + } rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL); - if(rdwr_pa[i].buf == NULL) - { + if(rdwr_pa[i].buf == NULL) { res = -ENOMEM; break; } if(copy_from_user(rdwr_pa[i].buf, rdwr_arg.msgs[i].buf, - rdwr_pa[i].len)) - { - kfree(rdwr_pa[i].buf); + rdwr_pa[i].len)) { res = -EFAULT; break; } } - if (!res) - { + if (res < 0) { + int j; + for (j = 0; j < i; ++j) + kfree(rdwr_pa[j].buf); + kfree(rdwr_pa); + return res; + } + if (!res) { res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs); } - while(i-- > 0) - { + while(i-- > 0) { if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) { if(copy_to_user( @@ -274,20 +271,18 @@ int i2cdev_ioctl (struct inode *inode, struct file *file, unsigned int cmd, (data_arg.size != I2C_SMBUS_BLOCK_DATA) && (data_arg.size != I2C_SMBUS_I2C_BLOCK_DATA) && (data_arg.size != I2C_SMBUS_BLOCK_PROC_CALL)) { -#ifdef DEBUG - printk(KERN_DEBUG "i2c-dev.o: size out of range (%x) in ioctl I2C_SMBUS.\n", - data_arg.size); -#endif + dev_dbg(&client->dev, + "size out of range (%x) in ioctl I2C_SMBUS.\n", + data_arg.size); return -EINVAL; } /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1, so the check is valid if size==I2C_SMBUS_QUICK too. */ if ((data_arg.read_write != I2C_SMBUS_READ) && (data_arg.read_write != I2C_SMBUS_WRITE)) { -#ifdef DEBUG - printk(KERN_DEBUG "i2c-dev.o: read_write out of range (%x) in ioctl I2C_SMBUS.\n", - data_arg.read_write); -#endif + dev_dbg(&client->dev, + "read_write out of range (%x) in ioctl I2C_SMBUS.\n", + data_arg.read_write); return -EINVAL; } @@ -298,15 +293,14 @@ int i2cdev_ioctl (struct inode *inode, struct file *file, unsigned int cmd, (data_arg.read_write == I2C_SMBUS_WRITE))) /* These are special: we do not use data */ return i2c_smbus_xfer(client->adapter, client->addr, - client->flags, - data_arg.read_write, - data_arg.command, - data_arg.size, NULL); + client->flags, + data_arg.read_write, + data_arg.command, + data_arg.size, NULL); if (data_arg.data == NULL) { -#ifdef DEBUG - printk(KERN_DEBUG "i2c-dev.o: data is NULL pointer in ioctl I2C_SMBUS.\n"); -#endif + dev_dbg(&client->dev, + "data is NULL pointer in ioctl I2C_SMBUS.\n"); return -EINVAL; } @@ -365,7 +359,7 @@ static int i2cdev_open(struct inode *inode, struct file *file) return 0; - out_kfree: +out_kfree: kfree(client); return -ENODEV; } @@ -428,7 +422,8 @@ int __init i2c_dev_init(void) { int res; - printk(KERN_INFO "i2c-dev.o: i2c /dev entries driver module version %s (%s)\n", I2C_VERSION, I2C_DATE); + printk(KERN_INFO "i2c /dev entries driver module version %s (%s)\n", + I2C_VERSION, I2C_DATE); if (register_chrdev(I2C_MAJOR,"i2c",&i2cdev_fops)) { printk(KERN_ERR "i2c-dev.o: unable to get major %d for i2c bus\n", diff --git a/include/linux/i2c-dev.h b/include/linux/i2c-dev.h index 3803c36293a8..dd2e43c66905 100644 --- a/include/linux/i2c-dev.h +++ b/include/linux/i2c-dev.h @@ -31,16 +31,16 @@ /* This is the structure as used in the I2C_SMBUS ioctl call */ struct i2c_smbus_ioctl_data { - char read_write; + __u8 read_write; __u8 command; - int size; + __u32 size; union i2c_smbus_data *data; }; /* This is the structure as used in the I2C_RDWR ioctl call */ struct i2c_rdwr_ioctl_data { struct i2c_msg *msgs; /* pointers to i2c_msgs */ - int nmsgs; /* number of i2c_msgs */ + __u32 nmsgs; /* number of i2c_msgs */ }; #endif /* _LINUX_I2C_DEV_H */ diff --git a/include/linux/i2c.h b/include/linux/i2c.h index ddf1ee79389c..12befd6bd7e0 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -367,15 +367,15 @@ extern int i2c_check_functionality (struct i2c_adapter *adap, u32 func); */ struct i2c_msg { __u16 addr; /* slave address */ - unsigned short flags; + __u16 flags; #define I2C_M_TEN 0x10 /* we have a ten bit chip address */ #define I2C_M_RD 0x01 #define I2C_M_NOSTART 0x4000 #define I2C_M_REV_DIR_ADDR 0x2000 #define I2C_M_IGNORE_NAK 0x1000 #define I2C_M_NO_RD_ACK 0x0800 - short len; /* msg length */ - char *buf; /* pointer to msg data */ + __u16 len; /* msg length */ + __u8 *buf; /* pointer to msg data */ }; /* To determine what functionality is present */ -- cgit v1.2.3 From d1ebfcd6e608b71bb13cb245544dafa6d0c05674 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 9 Apr 2003 11:01:10 -0700 Subject: [BRIDGE]: Fix several locking bugs, plus cleanups. --- include/linux/if_bridge.h | 2 +- net/bridge/br.c | 13 ++++--------- net/bridge/br_if.c | 23 ++++++++++++++++++----- net/bridge/br_ioctl.c | 34 ++++++++++++++++++---------------- net/bridge/br_notify.c | 33 +++++++++++++++++---------------- net/core/dev.c | 9 +++++---- net/netsyms.c | 2 +- net/socket.c | 20 ++++++++++++++++++-- 8 files changed, 82 insertions(+), 54 deletions(-) (limited to 'include/linux') diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h index 77b3c632f118..099368acb7ed 100644 --- a/include/linux/if_bridge.h +++ b/include/linux/if_bridge.h @@ -101,7 +101,7 @@ struct __fdb_entry struct net_bridge; struct net_bridge_port; -extern int (*br_ioctl_hook)(unsigned long arg); +extern void brioctl_set(int (*ioctl_hook)(unsigned long)); extern int (*br_handle_frame_hook)(struct sk_buff *skb); extern int (*br_should_route_hook)(struct sk_buff **pskb); diff --git a/net/bridge/br.c b/net/bridge/br.c index 23b44c9c9b82..35b3045cf6b0 100644 --- a/net/bridge/br.c +++ b/net/bridge/br.c @@ -49,8 +49,9 @@ static int __init br_init(void) if (br_netfilter_init()) return 1; #endif + brioctl_set(br_ioctl_deviceless_stub); br_handle_frame_hook = br_handle_frame; - br_ioctl_hook = br_ioctl_deviceless_stub; + #if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) br_fdb_get_hook = br_fdb_get; br_fdb_put_hook = br_fdb_put; @@ -60,24 +61,18 @@ static int __init br_init(void) return 0; } -static void __br_clear_ioctl_hook(void) -{ - br_ioctl_hook = NULL; -} - static void __exit br_deinit(void) { #ifdef CONFIG_NETFILTER br_netfilter_fini(); #endif unregister_netdevice_notifier(&br_device_notifier); - br_call_ioctl_atomic(__br_clear_ioctl_hook); - br_write_lock_bh(BR_NETPROTO_LOCK); + brioctl_set(NULL); br_handle_frame_hook = NULL; - br_write_unlock_bh(BR_NETPROTO_LOCK); #if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) + /* FIX ME. move into hook structure with ref count */ br_fdb_get_hook = NULL; br_fdb_put_hook = NULL; #endif diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c index 39427429bf56..f3b70d7fda57 100644 --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c @@ -23,6 +23,7 @@ #include "br_private.h" static struct net_bridge *bridge_list; +static spinlock_t bridge_lock = SPIN_LOCK_UNLOCKED; static int br_initial_port_cost(struct net_device *dev) { @@ -69,6 +70,7 @@ static int __br_del_if(struct net_bridge *br, struct net_device *dev) return 0; } +/* called with bridge_lock */ static struct net_bridge **__find_br(char *name) { struct net_bridge **b; @@ -188,8 +190,10 @@ int br_add_bridge(char *name) return -EEXIST; } + spin_lock(&bridge_lock); br->next = bridge_list; bridge_list = br; + spin_unlock(&bridge_lock); br_inc_use_count(); register_netdev(&br->dev); @@ -202,17 +206,22 @@ int br_del_bridge(char *name) struct net_bridge **b; struct net_bridge *br; - if ((b = __find_br(name)) == NULL) + spin_lock(&bridge_lock); + if ((b = __find_br(name)) == NULL) { + spin_unlock(&bridge_lock); return -ENXIO; + } br = *b; - - if (br->dev.flags & IFF_UP) + if (br->dev.flags & IFF_UP) { + spin_unlock(&bridge_lock); return -EBUSY; - - del_ifs(br); + } *b = br->next; + spin_unlock(&bridge_lock); + + del_ifs(br); unregister_netdev(&br->dev); kfree(br); @@ -272,6 +281,7 @@ int br_get_bridge_ifindices(int *indices, int num) struct net_bridge *br; int i; + spin_lock(&bridge_lock); br = bridge_list; for (i=0;idev.ifindex; br = br->next; } + spin_unlock(&bridge_lock); return i; } @@ -289,9 +300,11 @@ void br_get_port_ifindices(struct net_bridge *br, int *ifindices) { struct net_bridge_port *p; + read_lock(&br->lock); p = br->port_list; while (p != NULL) { ifindices[p->port_no] = p->dev->ifindex; p = p->next; } + read_unlock(&br->lock); } diff --git a/net/bridge/br_ioctl.c b/net/bridge/br_ioctl.c index fb353e586103..f9f18146e1db 100644 --- a/net/bridge/br_ioctl.c +++ b/net/bridge/br_ioctl.c @@ -53,6 +53,7 @@ static int br_ioctl_device(struct net_bridge *br, { struct __bridge_info b; + read_lock(&br->lock); memset(&b, 0, sizeof(struct __bridge_info)); memcpy(&b.designated_root, &br->designated_root, 8); memcpy(&b.bridge_id, &br->bridge_id, 8); @@ -73,6 +74,7 @@ static int br_ioctl_device(struct net_bridge *br, b.tcn_timer_value = br_timer_get_residue(&br->tcn_timer); b.topology_change_timer_value = br_timer_get_residue(&br->topology_change_timer); b.gc_timer_value = br_timer_get_residue(&br->gc_timer); + read_unlock(&br->lock); if (copy_to_user((void *)arg0, &b, sizeof(b))) return -EFAULT; @@ -96,21 +98,27 @@ static int br_ioctl_device(struct net_bridge *br, } case BRCTL_SET_BRIDGE_FORWARD_DELAY: + write_lock(&br->lock); br->bridge_forward_delay = arg0; if (br_is_root_bridge(br)) br->forward_delay = arg0; + write_unlock(&br->lock); return 0; case BRCTL_SET_BRIDGE_HELLO_TIME: + write_lock(&br->lock); br->bridge_hello_time = arg0; if (br_is_root_bridge(br)) br->hello_time = arg0; + write_unlock(&br->lock); return 0; case BRCTL_SET_BRIDGE_MAX_AGE: + write_lock(&br->lock); br->bridge_max_age = arg0; if (br_is_root_bridge(br)) br->max_age = arg0; + write_unlock(&br->lock); return 0; case BRCTL_SET_AGEING_TIME: @@ -126,6 +134,7 @@ static int br_ioctl_device(struct net_bridge *br, struct __port_info p; struct net_bridge_port *pt; + read_lock(&br->lock); if ((pt = br_get_port(br, arg1)) == NULL) return -EINVAL; @@ -143,6 +152,8 @@ static int br_ioctl_device(struct net_bridge *br, p.forward_delay_timer_value = br_timer_get_residue(&pt->forward_delay_timer); p.hold_timer_value = br_timer_get_residue(&pt->hold_timer); + read_unlock(&br->lock); + if (copy_to_user((void *)arg0, &p, sizeof(p))) return -EFAULT; @@ -154,16 +165,20 @@ static int br_ioctl_device(struct net_bridge *br, return 0; case BRCTL_SET_BRIDGE_PRIORITY: + write_lock(&br->lock); br_stp_set_bridge_priority(br, arg0); + write_unlock(&br->lock); return 0; case BRCTL_SET_PORT_PRIORITY: { struct net_bridge_port *p; + write_lock(&br->lock); if ((p = br_get_port(br, arg0)) == NULL) return -EINVAL; br_stp_set_port_priority(p, arg1); + write_unlock(&br->lock); return 0; } @@ -171,9 +186,11 @@ static int br_ioctl_device(struct net_bridge *br, { struct net_bridge_port *p; + write_lock(&br->lock); if ((p = br_get_port(br, arg0)) == NULL) return -EINVAL; br_stp_set_path_cost(p, arg1); + write_unlock(&br->lock); return 0; } @@ -230,11 +247,9 @@ static int br_ioctl_deviceless(unsigned int cmd, return -EOPNOTSUPP; } -static DECLARE_MUTEX(ioctl_mutex); int br_ioctl_deviceless_stub(unsigned long arg) { - int err; unsigned long i[3]; if (!capable(CAP_NET_ADMIN)) @@ -243,11 +258,7 @@ int br_ioctl_deviceless_stub(unsigned long arg) if (copy_from_user(i, (void *)arg, 3*sizeof(unsigned long))) return -EFAULT; - down(&ioctl_mutex); - err = br_ioctl_deviceless(i[0], i[1], i[2]); - up(&ioctl_mutex); - - return err; + return br_ioctl_deviceless(i[0], i[1], i[2]); } int br_ioctl(struct net_bridge *br, unsigned int cmd, unsigned long arg0, unsigned long arg1, unsigned long arg2) @@ -257,18 +268,9 @@ int br_ioctl(struct net_bridge *br, unsigned int cmd, unsigned long arg0, unsign if (!capable(CAP_NET_ADMIN)) return -EPERM; - down(&ioctl_mutex); err = br_ioctl_deviceless(cmd, arg0, arg1); if (err == -EOPNOTSUPP) err = br_ioctl_device(br, cmd, arg0, arg1, arg2); - up(&ioctl_mutex); return err; } - -void br_call_ioctl_atomic(void (*fn)(void)) -{ - down(&ioctl_mutex); - fn(); - up(&ioctl_mutex); -} diff --git a/net/bridge/br_notify.c b/net/bridge/br_notify.c index 6ed309b755bb..f63df779b8cf 100644 --- a/net/bridge/br_notify.c +++ b/net/bridge/br_notify.c @@ -21,15 +21,14 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v struct notifier_block br_device_notifier = { - br_device_event, - NULL, - 0 + .notifier_call = br_device_event }; static int br_device_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct net_device *dev; struct net_bridge_port *p; + struct net_bridge *br; dev = ptr; p = dev->br_port; @@ -37,13 +36,15 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v if (p == NULL) return NOTIFY_DONE; - switch (event) + br = p->br; + + switch (event) { case NETDEV_CHANGEADDR: - read_lock(&p->br->lock); + write_lock_bh(&br->lock); br_fdb_changeaddr(p, dev->dev_addr); - br_stp_recalculate_bridge_id(p->br); - read_unlock(&p->br->lock); + br_stp_recalculate_bridge_id(br); + write_unlock_bh(&br->lock); break; case NETDEV_GOING_DOWN: @@ -51,23 +52,23 @@ static int br_device_event(struct notifier_block *unused, unsigned long event, v break; case NETDEV_DOWN: - if (p->br->dev.flags & IFF_UP) { - read_lock(&p->br->lock); - br_stp_disable_port(dev->br_port); - read_unlock(&p->br->lock); + if (br->dev.flags & IFF_UP) { + write_lock_bh(&br->lock); + br_stp_disable_port(p); + write_unlock_bh(&br->lock); } break; case NETDEV_UP: - if (p->br->dev.flags & IFF_UP) { - read_lock(&p->br->lock); - br_stp_enable_port(dev->br_port); - read_unlock(&p->br->lock); + if (!(br->dev.flags & IFF_UP)) { + write_lock_bh(&br->lock); + br_stp_enable_port(p); + write_unlock_bh(&br->lock); } break; case NETDEV_UNREGISTER: - br_del_if(dev->br_port->br, dev); + br_del_if(br, dev); break; } diff --git a/net/core/dev.c b/net/core/dev.c index 17e32722281b..a8ef547eb08d 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1433,9 +1433,8 @@ static void net_tx_action(struct softirq_action *h) } } -#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) +#if defined(CONFIG_BRIDGE) || defined (CONFIG_BRIDGE_MODULE) int (*br_handle_frame_hook)(struct sk_buff *skb) = NULL; -#endif static __inline__ int handle_bridge(struct sk_buff *skb, struct packet_type *pt_prev) @@ -1454,6 +1453,7 @@ static __inline__ int handle_bridge(struct sk_buff *skb, return ret; } +#endif #ifdef CONFIG_NET_DIVERT static inline int handle_diverter(struct sk_buff *skb) @@ -1510,12 +1510,13 @@ int netif_receive_skb(struct sk_buff *skb) #endif /* CONFIG_NET_DIVERT */ #if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) - if (skb->dev->br_port && br_handle_frame_hook) { + if (skb->dev->br_port) { int ret; ret = handle_bridge(skb, pt_prev); - if (br_handle_frame_hook(skb) == 0) + if (br_handle_frame_hook(skb) == 0) return ret; + pt_prev = NULL; } #endif diff --git a/net/netsyms.c b/net/netsyms.c index b678bb9df90d..b55b5fed2c81 100644 --- a/net/netsyms.c +++ b/net/netsyms.c @@ -234,8 +234,8 @@ EXPORT_SYMBOL(scm_detach_fds); #if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) EXPORT_SYMBOL(br_handle_frame_hook); +EXPORT_SYMBOL(brioctl_set); #endif -EXPORT_SYMBOL(br_ioctl_hook); #ifdef CONFIG_NET_DIVERT EXPORT_SYMBOL(alloc_divert_blk); diff --git a/net/socket.c b/net/socket.c index 0df13a726ce5..0d549c4602b5 100644 --- a/net/socket.c +++ b/net/socket.c @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -712,7 +713,18 @@ static ssize_t sock_writev(struct file *file, const struct iovec *vector, file, vector, count, tot_len); } -int (*br_ioctl_hook)(unsigned long arg); + +static DECLARE_MUTEX(br_ioctl_mutex); +static int (*br_ioctl_hook)(unsigned long arg) = NULL; + +void brioctl_set(int (*hook)(unsigned long)) +{ + down(&br_ioctl_mutex); + br_ioctl_hook = hook; + up(&br_ioctl_mutex); +} + + int (*vlan_ioctl_hook)(unsigned long arg); #ifdef CONFIG_DLCI @@ -759,12 +771,16 @@ static int sock_ioctl(struct inode *inode, struct file *file, unsigned int cmd, case SIOCGIFBR: case SIOCSIFBR: err = -ENOPKG; + #ifdef CONFIG_KMOD if (!br_ioctl_hook) request_module("bridge"); #endif - if (br_ioctl_hook) + + down(&br_ioctl_mutex); + if (br_ioctl_hook) err = br_ioctl_hook(arg); + up(&br_ioctl_mutex); break; case SIOCGIFVLAN: case SIOCSIFVLAN: -- cgit v1.2.3 From c96fa28d87f7178ca865074db478311d5b57fab2 Mon Sep 17 00:00:00 2001 From: David Stevens Date: Thu, 10 Apr 2003 09:35:56 -0700 Subject: [IPV4]: IGMPv3 support, with help from Vinay Kulkarni --- include/linux/igmp.h | 98 +++- include/linux/in.h | 26 + include/linux/inetdevice.h | 10 + include/net/ip.h | 1 + net/ipv4/igmp.c | 1363 ++++++++++++++++++++++++++++++++++++++++++-- net/ipv4/ip_output.c | 1 + net/ipv4/ip_sockglue.c | 76 ++- net/ipv4/route.c | 5 +- net/ipv4/udp.c | 2 + 9 files changed, 1542 insertions(+), 40 deletions(-) (limited to 'include/linux') diff --git a/include/linux/igmp.h b/include/linux/igmp.h index fd28c22a5036..2cc2472d2eb2 100644 --- a/include/linux/igmp.h +++ b/include/linux/igmp.h @@ -32,13 +32,60 @@ struct igmphdr __u32 group; }; +/* V3 group record types [grec_type] */ +#define IGMPV3_MODE_IS_INCLUDE 1 +#define IGMPV3_MODE_IS_EXCLUDE 2 +#define IGMPV3_CHANGE_TO_INCLUDE 3 +#define IGMPV3_CHANGE_TO_EXCLUDE 4 +#define IGMPV3_ALLOW_NEW_SOURCES 5 +#define IGMPV3_BLOCK_OLD_SOURCES 6 + +struct igmpv3_grec { + __u8 grec_type; + __u8 grec_auxwords; + __u16 grec_nsrcs; + __u32 grec_mca; + __u32 grec_src[0]; +}; + +struct igmpv3_report { + __u8 type; + __u8 resv1; + __u16 csum; + __u16 resv2; + __u16 ngrec; + struct igmpv3_grec grec[0]; +}; + +struct igmpv3_query { + __u8 type; + __u8 code; + __u16 csum; + __u32 group; +#if defined(__LITTLE_ENDIAN_BITFIELD) + __u8 qrv:3, + suppress:1, + resv:4; +#elif defined(__BIG_ENDIAN_BITFIELD) + __u8 resv:4, + suppress:1, + qrv:3; +#else +#error "Please fix " +#endif + __u8 qqic; + __u16 nsrcs; + __u32 srcs[0]; +}; + #define IGMP_HOST_MEMBERSHIP_QUERY 0x11 /* From RFC1112 */ #define IGMP_HOST_MEMBERSHIP_REPORT 0x12 /* Ditto */ #define IGMP_DVMRP 0x13 /* DVMRP routing */ #define IGMP_PIM 0x14 /* PIM routing */ #define IGMP_TRACE 0x15 -#define IGMP_HOST_NEW_MEMBERSHIP_REPORT 0x16 /* New version of 0x11 */ +#define IGMPV2_HOST_MEMBERSHIP_REPORT 0x16 /* V2 version of 0x11 */ #define IGMP_HOST_LEAVE_MESSAGE 0x17 +#define IGMPV3_HOST_MEMBERSHIP_REPORT 0x22 /* V3 version of 0x11 */ #define IGMP_MTRACE_RESP 0x1e #define IGMP_MTRACE 0x1f @@ -68,6 +115,7 @@ struct igmphdr #define IGMP_ALL_HOSTS htonl(0xE0000001L) #define IGMP_ALL_ROUTER htonl(0xE0000002L) +#define IGMPV3_ALL_MCR htonl(0xE0000016L) #define IGMP_LOCAL_GROUP htonl(0xE0000000L) #define IGMP_LOCAL_GROUP_MASK htonl(0xFFFFFF00L) @@ -79,6 +127,18 @@ struct igmphdr #include #include +struct ip_sf_socklist +{ + unsigned int sl_max; + unsigned int sl_count; + __u32 sl_addr[0]; +}; + +#define IP_SFLSIZE(count) (sizeof(struct ip_sf_socklist) + \ + (count) * sizeof(__u32)) + +#define IP_SFBLOCK 10 /* allocate this many at once */ + /* ip_mc_socklist is real list now. Speed is not argument; this list never used in fast path code */ @@ -88,12 +148,28 @@ struct ip_mc_socklist struct ip_mc_socklist *next; int count; struct ip_mreqn multi; + unsigned int sfmode; /* MCAST_{INCLUDE,EXCLUDE} */ + struct ip_sf_socklist *sflist; +}; + +struct ip_sf_list +{ + struct ip_sf_list *sf_next; + __u32 sf_inaddr; + unsigned long sf_count[2]; /* include/exclude counts */ + unsigned char sf_gsresp; /* include in g & s response? */ + unsigned char sf_oldin; /* change state */ + unsigned char sf_crcount; /* retrans. left to send */ }; struct ip_mc_list { struct in_device *interface; unsigned long multiaddr; + struct ip_sf_list *sources; + struct ip_sf_list *tomb; + unsigned int sfmode; + unsigned long sfcount[2]; struct ip_mc_list *next; struct timer_list timer; int users; @@ -103,13 +179,31 @@ struct ip_mc_list char reporter; char unsolicit_count; char loaded; + unsigned char gsquery; /* check source marks? */ + unsigned char crcount; }; -extern int ip_check_mc(struct in_device *dev, u32 mc_addr); +/* V3 exponential field decoding */ +#define IGMPV3_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value)) +#define IGMPV3_EXP(thresh, nbmant, nbexp, value) \ + ((value) < (thresh) ? (value) : \ + ((IGMPV3_MASK(value, nbmant) | (1<<(nbmant+nbexp))) << \ + (IGMPV3_MASK((value) >> (nbmant), nbexp) + (nbexp)))) + +#define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value) +#define IGMPV3_MRC(value) IGMPV3_EXP(0x8000, 12, 3, value) + +extern int ip_check_mc(struct in_device *dev, u32 mc_addr, u32 src_addr, u16 proto); extern int igmp_rcv(struct sk_buff *); extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr); extern int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr); extern void ip_mc_drop_socket(struct sock *sk); +extern int ip_mc_source(int add, int omode, struct sock *sk, + struct ip_mreq_source *mreqs); +extern int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf); +extern int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf, + struct ip_msfilter *optval, int *optlen); +extern int ip_mc_sf_allow(struct sock *sk, u32 local, u32 rmt, int dif); extern void ip_mr_init(void); extern void ip_mc_init_dev(struct in_device *); extern void ip_mc_destroy_dev(struct in_device *); diff --git a/include/linux/in.h b/include/linux/in.h index 24814adc8643..bd2ec6c01f4f 100644 --- a/include/linux/in.h +++ b/include/linux/in.h @@ -85,6 +85,14 @@ struct in_addr { #define IP_MULTICAST_LOOP 34 #define IP_ADD_MEMBERSHIP 35 #define IP_DROP_MEMBERSHIP 36 +#define IP_UNBLOCK_SOURCE 37 +#define IP_BLOCK_SOURCE 38 +#define IP_ADD_SOURCE_MEMBERSHIP 39 +#define IP_DROP_SOURCE_MEMBERSHIP 40 +#define IP_MSFILTER 41 + +#define MCAST_EXCLUDE 0 +#define MCAST_INCLUDE 1 /* These need to appear somewhere around here */ #define IP_DEFAULT_MULTICAST_TTL 1 @@ -105,6 +113,24 @@ struct ip_mreqn int imr_ifindex; /* Interface index */ }; +struct ip_mreq_source { + __u32 imr_multiaddr; + __u32 imr_interface; + __u32 imr_sourceaddr; +}; + +struct ip_msfilter { + __u32 imsf_multiaddr; + __u32 imsf_interface; + __u32 imsf_fmode; + __u32 imsf_numsrc; + __u32 imsf_slist[1]; +}; + +#define IP_MSFILTER_SIZE(numsrc) \ + (sizeof(struct ip_msfilter) - sizeof(__u32) \ + + (numsrc) * sizeof(__u32)) + struct in_pktinfo { int ipi_ifindex; diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h index c88a595b6177..18913dfaddb1 100644 --- a/include/linux/inetdevice.h +++ b/include/linux/inetdevice.h @@ -34,7 +34,17 @@ struct in_device int dead; struct in_ifaddr *ifa_list; /* IP ifaddr chain */ struct ip_mc_list *mc_list; /* IP multicast filter chain */ + rwlock_t mc_lock; /* for mc_tomb */ + struct ip_mc_list *mc_tomb; unsigned long mr_v1_seen; + unsigned long mr_v2_seen; + unsigned long mr_maxdelay; + unsigned char mr_qrv; + unsigned char mr_gq_running; + unsigned char mr_ifc_count; + struct timer_list mr_gq_timer; /* general query timer */ + struct timer_list mr_ifc_timer; /* interface change timer */ + struct neigh_parms *arp_parms; struct ipv4_devconf cnf; }; diff --git a/include/net/ip.h b/include/net/ip.h index 1f5be0ecf807..da0dab10f4f0 100644 --- a/include/net/ip.h +++ b/include/net/ip.h @@ -79,6 +79,7 @@ extern rwlock_t ip_ra_lock; extern void ip_mc_dropsocket(struct sock *); extern void ip_mc_dropdevice(struct net_device *dev); extern int ip_mc_procinfo(char *, char **, off_t, int); +extern int ip_mcf_procinfo(char *, char **, off_t, int); /* * Functions provided by ip.c diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index 05600080cf7b..2a6a536fdc39 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -68,6 +68,8 @@ * Alan Cox: Forget to enable FDDI support earlier. * Alexey Kuznetsov: Fixed leaving groups on device down. * Alexey Kuznetsov: Accordance to igmp-v2-06 draft. + * David L Stevens: IGMPv3 support, with help from + * Vinay Kulkarni */ @@ -107,6 +109,7 @@ /* Parameter names and values are taken from igmp-v2-06 draft */ #define IGMP_V1_Router_Present_Timeout (400*HZ) +#define IGMP_V2_Router_Present_Timeout (400*HZ) #define IGMP_Unsolicited_Report_Interval (10*HZ) #define IGMP_Query_Response_Interval (10*HZ) #define IGMP_Unsolicited_Report_Count 2 @@ -121,10 +124,22 @@ * contradict to specs provided this delay is small enough. */ -#define IGMP_V1_SEEN(in_dev) ((in_dev)->mr_v1_seen && (long)(jiffies - (in_dev)->mr_v1_seen) < 0) +#define IGMP_V1_SEEN(in_dev) ((in_dev)->mr_v1_seen && \ + time_before(jiffies, (in_dev)->mr_v1_seen)) +#define IGMP_V2_SEEN(in_dev) ((in_dev)->mr_v2_seen && \ + time_before(jiffies, (in_dev)->mr_v2_seen)) #endif +static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im); +static void igmpv3_del_delrec(struct in_device *in_dev, __u32 multiaddr); +static void igmpv3_clear_delrec(struct in_device *in_dev); +static int sf_setstate(struct ip_mc_list *pmc); +static void sf_markstate(struct ip_mc_list *pmc); +static void ip_mc_clear_src(struct ip_mc_list *pmc); +int ip_mc_add_src(struct in_device *in_dev, __u32 *pmca, int sfmode, + int sfcount, __u32 *psfsrc, int delta); + static void ip_ma_put(struct ip_mc_list *im) { if (atomic_dec_and_test(&im->refcnt)) { @@ -160,6 +175,23 @@ static void igmp_start_timer(struct ip_mc_list *im, int max_delay) atomic_inc(&im->refcnt); } +static void igmp_gq_start_timer(struct in_device *in_dev) +{ + int tv = net_random() % in_dev->mr_maxdelay; + + in_dev->mr_gq_running = 1; + if (!mod_timer(&in_dev->mr_gq_timer, jiffies+tv+2)) + atomic_inc(&in_dev->refcnt); +} + +static void igmp_ifc_start_timer(struct in_device *in_dev, int delay) +{ + int tv = net_random() % delay; + + if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2)) + atomic_inc(&in_dev->refcnt); +} + static void igmp_mod_timer(struct ip_mc_list *im, int max_delay) { spin_lock_bh(&im->lock); @@ -184,20 +216,396 @@ static void igmp_mod_timer(struct ip_mc_list *im, int max_delay) #define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4) -static int igmp_send_report(struct net_device *dev, u32 group, int type) + +static int is_in(struct ip_mc_list *pmc, struct ip_sf_list *psf, int type, + int gdeleted, int sdeleted) +{ + switch (type) { + case IGMPV3_MODE_IS_INCLUDE: + case IGMPV3_MODE_IS_EXCLUDE: + if (gdeleted || sdeleted) + return 0; + return !(pmc->gsquery && !psf->sf_gsresp); + case IGMPV3_CHANGE_TO_INCLUDE: + if (gdeleted || sdeleted) + return 0; + return psf->sf_count[MCAST_INCLUDE] != 0; + case IGMPV3_CHANGE_TO_EXCLUDE: + if (gdeleted || sdeleted) + return 0; + if (pmc->sfcount[MCAST_EXCLUDE] == 0 || + psf->sf_count[MCAST_INCLUDE]) + return 0; + return pmc->sfcount[MCAST_EXCLUDE] == + psf->sf_count[MCAST_EXCLUDE]; + case IGMPV3_ALLOW_NEW_SOURCES: + if (gdeleted || !psf->sf_crcount) + return 0; + return (pmc->sfmode == MCAST_INCLUDE) ^ sdeleted; + case IGMPV3_BLOCK_OLD_SOURCES: + if (pmc->sfmode == MCAST_INCLUDE) + return gdeleted || (psf->sf_crcount && sdeleted); + return psf->sf_crcount && !gdeleted && !sdeleted; + } + return 0; +} + +static int +igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted) +{ + struct ip_sf_list *psf; + int scount = 0; + + for (psf=pmc->sources; psf; psf=psf->sf_next) { + if (!is_in(pmc, psf, type, gdeleted, sdeleted)) + continue; + scount++; + } + return scount; +} + +static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size) +{ + struct sk_buff *skb; + struct rtable *rt; + struct iphdr *pip; + struct igmpv3_report *pig; + + skb = alloc_skb(size + dev->hard_header_len + 15, GFP_ATOMIC); + if (skb == NULL) + return 0; + + { + struct flowi fl = { .oif = dev->ifindex, + .nl_u = { .ip4_u = { + .daddr = IGMPV3_ALL_MCR } }, + .proto = IPPROTO_IGMP }; + if (ip_route_output_key(&rt, &fl)) + return 0; + } + if (rt->rt_src == 0) { + ip_rt_put(rt); + return 0; + } + + skb->dst = &rt->u.dst; + skb->dev = dev; + + skb_reserve(skb, (dev->hard_header_len+15)&~15); + + skb->nh.iph = pip =(struct iphdr *)skb_put(skb, sizeof(struct iphdr)+4); + + pip->version = 4; + pip->ihl = (sizeof(struct iphdr)+4)>>2; + pip->tos = 0xc0; + pip->frag_off = htons(IP_DF); + pip->ttl = 1; + pip->daddr = rt->rt_dst; + pip->saddr = rt->rt_src; + pip->protocol = IPPROTO_IGMP; + pip->tot_len = 0; /* filled in later */ + ip_select_ident(pip, &rt->u.dst, NULL); + ((u8*)&pip[1])[0] = IPOPT_RA; + ((u8*)&pip[1])[1] = 4; + ((u8*)&pip[1])[2] = 0; + ((u8*)&pip[1])[3] = 0; + + pig =(struct igmpv3_report *)skb_put(skb, sizeof(*pig)); + skb->h.igmph = (struct igmphdr *)pig; + pig->type = IGMPV3_HOST_MEMBERSHIP_REPORT; + pig->resv1 = 0; + pig->csum = 0; + pig->resv2 = 0; + pig->ngrec = 0; + return skb; +} + +static int igmpv3_sendpack(struct sk_buff *skb) +{ + struct iphdr *pip = skb->nh.iph; + struct igmphdr *pig = skb->h.igmph; + int iplen, igmplen; + + iplen = skb->tail - (unsigned char *)skb->nh.iph; + pip->tot_len = htons(iplen); + ip_send_check(pip); + + igmplen = skb->tail - (unsigned char *)skb->h.igmph; + pig->csum = ip_compute_csum((void *)skb->h.igmph, igmplen); + + return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, skb->dev, + dst_output); +} + +static int grec_size(struct ip_mc_list *pmc, int type, int gdel, int sdel) +{ + return sizeof(struct igmpv3_grec) + 4*igmp_scount(pmc,type,gdel,sdel); +} + +static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc, + int type, struct igmpv3_grec **ppgr) +{ + struct net_device *dev = pmc->interface->dev; + struct igmpv3_report *pih; + struct igmpv3_grec *pgr; + + if (!skb) + skb = igmpv3_newpack(dev, dev->mtu); + if (!skb) + return 0; + pgr = (struct igmpv3_grec *)skb_put(skb, sizeof(struct igmpv3_grec)); + pgr->grec_type = type; + pgr->grec_auxwords = 0; + pgr->grec_nsrcs = 0; + pgr->grec_mca = pmc->multiaddr; + pih = (struct igmpv3_report *)skb->h.igmph; + pih->ngrec = htons(ntohs(pih->ngrec)+1); + *ppgr = pgr; + return skb; +} + +#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? (skb)->dev->mtu - (skb)->len : \ + skb_tailroom(skb)) : 0) + +static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, + int type, int gdeleted, int sdeleted) +{ + struct net_device *dev = pmc->interface->dev; + struct igmpv3_report *pih; + struct igmpv3_grec *pgr = 0; + struct ip_sf_list *psf, *psf_next, *psf_prev, *psf_list; + int scount, first, isquery, truncate; + + if (pmc->multiaddr == IGMP_ALL_HOSTS) + return skb; + + isquery = type == IGMPV3_MODE_IS_INCLUDE || + type == IGMPV3_MODE_IS_EXCLUDE; + truncate = type == IGMPV3_MODE_IS_EXCLUDE || + type == IGMPV3_CHANGE_TO_EXCLUDE; + + psf_list = sdeleted ? pmc->tomb : pmc->sources; + + if (!psf_list) { + if (type == IGMPV3_ALLOW_NEW_SOURCES || + type == IGMPV3_BLOCK_OLD_SOURCES) + return skb; + if (pmc->crcount || isquery) + skb = add_grhead(skb, pmc, type, &pgr); + return skb; + } + pih = skb ? (struct igmpv3_report *)skb->h.igmph : 0; + + /* EX and TO_EX get a fresh packet, if needed */ + if (truncate) { + if (pih && pih->ngrec && + AVAILABLE(skb) < grec_size(pmc, type, gdeleted, sdeleted)) { + if (skb) + igmpv3_sendpack(skb); + skb = igmpv3_newpack(dev, dev->mtu); + } + } + first = 1; + scount = 0; + psf_prev = 0; + for (psf=psf_list; psf; psf=psf_next) { + u32 *psrc; + + psf_next = psf->sf_next; + + if (!is_in(pmc, psf, type, gdeleted, sdeleted)) { + psf_prev = psf; + continue; + } + + /* clear marks on query responses */ + if (isquery) + psf->sf_gsresp = 0; + + if (AVAILABLE(skb) < sizeof(u32) + + first*sizeof(struct igmpv3_grec)) { + if (truncate && !first) + break; /* truncate these */ + if (pgr) + pgr->grec_nsrcs = htons(scount); + if (skb) + igmpv3_sendpack(skb); + skb = igmpv3_newpack(dev, dev->mtu); + first = 1; + scount = 0; + } + if (first) { + skb = add_grhead(skb, pmc, type, &pgr); + first = 0; + } + psrc = (u32 *)skb_put(skb, sizeof(u32)); + *psrc = psf->sf_inaddr; + scount++; + if ((type == IGMPV3_ALLOW_NEW_SOURCES || + type == IGMPV3_BLOCK_OLD_SOURCES) && psf->sf_crcount) { + psf->sf_crcount--; + if ((sdeleted || gdeleted) && psf->sf_crcount == 0) { + if (psf_prev) + psf_prev->sf_next = psf->sf_next; + else + pmc->tomb = psf->sf_next; + kfree(psf); + continue; + } + } + psf_prev = psf; + } + if (pgr) + pgr->grec_nsrcs = htons(scount); + + if (isquery) + pmc->gsquery = 0; /* clear query state on report */ + return skb; +} + +static int igmpv3_send_report(struct in_device *in_dev, struct ip_mc_list *pmc) +{ + struct sk_buff *skb = 0; + int type; + + if (!pmc) { + read_lock(&in_dev->lock); + for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) { + if (pmc->multiaddr == IGMP_ALL_HOSTS) + continue; + spin_lock_bh(&pmc->lock); + if (pmc->sfcount[MCAST_EXCLUDE]) + type = IGMPV3_MODE_IS_EXCLUDE; + else + type = IGMPV3_MODE_IS_INCLUDE; + skb = add_grec(skb, pmc, type, 0, 0); + spin_unlock_bh(&pmc->lock); + } + read_unlock(&in_dev->lock); + } else { + spin_lock_bh(&pmc->lock); + if (pmc->sfcount[MCAST_EXCLUDE]) + type = IGMPV3_MODE_IS_EXCLUDE; + else + type = IGMPV3_MODE_IS_INCLUDE; + skb = add_grec(skb, pmc, type, 0, 0); + spin_unlock_bh(&pmc->lock); + } + if (!skb) + return 0; + return igmpv3_sendpack(skb); +} + +/* + * remove zero-count source records from a source filter list + */ +static void igmpv3_clear_zeros(struct ip_sf_list **ppsf) +{ + struct ip_sf_list *psf_prev, *psf_next, *psf; + + psf_prev = 0; + for (psf=*ppsf; psf; psf = psf_next) { + psf_next = psf->sf_next; + if (psf->sf_crcount == 0) { + if (psf_prev) + psf_prev->sf_next = psf->sf_next; + else + *ppsf = psf->sf_next; + kfree(psf); + } else + psf_prev = psf; + } +} + +static void igmpv3_send_cr(struct in_device *in_dev) +{ + struct ip_mc_list *pmc, *pmc_prev, *pmc_next; + struct sk_buff *skb = 0; + int type, dtype; + + read_lock(&in_dev->lock); + write_lock_bh(&in_dev->mc_lock); + + /* deleted MCA's */ + pmc_prev = 0; + for (pmc=in_dev->mc_tomb; pmc; pmc=pmc_next) { + pmc_next = pmc->next; + if (pmc->sfmode == MCAST_INCLUDE) { + type = IGMPV3_BLOCK_OLD_SOURCES; + dtype = IGMPV3_BLOCK_OLD_SOURCES; + skb = add_grec(skb, pmc, type, 1, 0); + skb = add_grec(skb, pmc, dtype, 1, 1); + } + if (pmc->crcount) { + pmc->crcount--; + if (pmc->sfmode == MCAST_EXCLUDE) { + type = IGMPV3_CHANGE_TO_INCLUDE; + skb = add_grec(skb, pmc, type, 1, 0); + } + if (pmc->crcount == 0) { + igmpv3_clear_zeros(&pmc->tomb); + igmpv3_clear_zeros(&pmc->sources); + } + } + if (pmc->crcount == 0 && !pmc->tomb && !pmc->sources) { + if (pmc_prev) + pmc_prev->next = pmc_next; + else + in_dev->mc_tomb = pmc_next; + in_dev_put(pmc->interface); + kfree(pmc); + } else + pmc_prev = pmc; + } + write_unlock_bh(&in_dev->mc_lock); + + /* change recs */ + for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) { + spin_lock_bh(&pmc->lock); + if (pmc->sfcount[MCAST_EXCLUDE]) { + type = IGMPV3_BLOCK_OLD_SOURCES; + dtype = IGMPV3_ALLOW_NEW_SOURCES; + } else { + type = IGMPV3_ALLOW_NEW_SOURCES; + dtype = IGMPV3_BLOCK_OLD_SOURCES; + } + skb = add_grec(skb, pmc, type, 0, 0); + skb = add_grec(skb, pmc, dtype, 0, 1); /* deleted sources */ + + /* filter mode changes */ + if (pmc->crcount) { + pmc->crcount--; + if (pmc->sfmode == MCAST_EXCLUDE) + type = IGMPV3_CHANGE_TO_EXCLUDE; + else + type = IGMPV3_CHANGE_TO_INCLUDE; + skb = add_grec(skb, pmc, type, 0, 0); + } + spin_unlock_bh(&pmc->lock); + } + read_unlock(&in_dev->lock); + if (!skb) + return; + (void) igmpv3_sendpack(skb); +} + +static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc, + int type) { struct sk_buff *skb; struct iphdr *iph; struct igmphdr *ih; struct rtable *rt; + struct net_device *dev = in_dev->dev; + u32 group = pmc ? pmc->multiaddr : 0; u32 dst; - /* According to IGMPv2 specs, LEAVE messages are - * sent to all-routers group. - */ - dst = group; - if (type == IGMP_HOST_LEAVE_MESSAGE) + if (type == IGMPV3_HOST_MEMBERSHIP_REPORT) + return igmpv3_send_report(in_dev, pmc); + else if (type == IGMP_HOST_LEAVE_MESSAGE) dst = IGMP_ALL_ROUTER; + else + dst = group; { struct flowi fl = { .oif = dev->ifindex, @@ -225,7 +633,7 @@ static int igmp_send_report(struct net_device *dev, u32 group, int type) iph->version = 4; iph->ihl = (sizeof(struct iphdr)+4)>>2; - iph->tos = 0; + iph->tos = 0xc0; iph->frag_off = htons(IP_DF); iph->ttl = 1; iph->daddr = dst; @@ -250,6 +658,34 @@ static int igmp_send_report(struct net_device *dev, u32 group, int type) dst_output); } +static void igmp_gq_timer_expire(unsigned long data) +{ + struct in_device *in_dev = (struct in_device *)data; + + in_dev->mr_gq_running = 0; + igmpv3_send_report(in_dev, 0); +} + +static void igmp_ifc_timer_expire(unsigned long data) +{ + struct in_device *in_dev = (struct in_device *)data; + + igmpv3_send_cr(in_dev); + if (in_dev->mr_ifc_count) { + in_dev->mr_ifc_count--; + igmp_ifc_start_timer(in_dev, IGMP_Unsolicited_Report_Interval); + } +} + +static void igmp_ifc_event(struct in_device *in_dev) +{ + if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev)) + return; + in_dev->mr_ifc_count = in_dev->mr_qrv ? in_dev->mr_qrv : + IGMP_Unsolicited_Report_Count; + igmp_ifc_start_timer(in_dev, 1); +} + static void igmp_timer_expire(unsigned long data) { @@ -267,13 +703,33 @@ static void igmp_timer_expire(unsigned long data) spin_unlock(&im->lock); if (IGMP_V1_SEEN(in_dev)) - igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT); + igmp_send_report(in_dev, im, IGMP_HOST_MEMBERSHIP_REPORT); + else if (IGMP_V2_SEEN(in_dev)) + igmp_send_report(in_dev, im, IGMPV2_HOST_MEMBERSHIP_REPORT); else - igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_NEW_MEMBERSHIP_REPORT); + igmp_send_report(in_dev, im, IGMPV3_HOST_MEMBERSHIP_REPORT); ip_ma_put(im); } +static void igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __u32 *srcs) +{ + struct ip_sf_list *psf; + int i, scount; + + scount = 0; + for (psf=pmc->sources; psf; psf=psf->sf_next) { + if (scount == nsrcs) + break; + for (i=0; isf_inaddr) { + psf->sf_gsresp = 1; + scount++; + break; + } + } +} + static void igmp_heard_report(struct in_device *in_dev, u32 group) { struct ip_mc_list *im; @@ -293,20 +749,46 @@ static void igmp_heard_report(struct in_device *in_dev, u32 group) read_unlock(&in_dev->lock); } -static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_time, - u32 group) +static void igmp_heard_query(struct in_device *in_dev, struct igmphdr *ih, + int len) { + struct igmpv3_query *ih3 = (struct igmpv3_query *)ih; struct ip_mc_list *im; + u32 group = ih->group; int max_delay; + int mark = 0; - max_delay = max_resp_time*(HZ/IGMP_TIMER_SCALE); - if (max_resp_time == 0) { - /* Alas, old v1 router presents here. */ - - max_delay = IGMP_Query_Response_Interval; - in_dev->mr_v1_seen = jiffies + IGMP_V1_Router_Present_Timeout; - group = 0; + if (len == 8) { + if (ih->code == 0) { + /* Alas, old v1 router presents here. */ + + max_delay = IGMP_Query_Response_Interval; + in_dev->mr_v1_seen = jiffies + + IGMP_V1_Router_Present_Timeout; + group = 0; + } else { + /* v2 router present */ + max_delay = ih->code*(HZ/IGMP_TIMER_SCALE); + in_dev->mr_v2_seen = jiffies + + IGMP_V2_Router_Present_Timeout; + } + igmpv3_clear_delrec(in_dev); + } else if (len < 12) { + return; /* ignore bogus packet; freed by caller */ + } else { /* v3 */ + max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE); + in_dev->mr_maxdelay = max_delay; + if (ih3->qrv) + in_dev->mr_qrv = ih3->qrv; + if (!group) { /* general query */ + if (ih3->nsrcs) + return; /* no sources allowed */ + igmp_gq_start_timer(in_dev); + return; + } + /* mark sources to include, if group & source-specific */ + mark = ih3->nsrcs != 0; } /* @@ -325,6 +807,14 @@ static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_ti continue; if (im->multiaddr == IGMP_ALL_HOSTS) continue; + spin_lock_bh(&im->lock); + if (im->tm_running) + im->gsquery = im->gsquery && mark; + else + im->gsquery = mark; + if (im->gsquery) + igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs); + spin_unlock_bh(&im->lock); igmp_mod_timer(im, max_delay); } read_unlock(&in_dev->lock); @@ -358,10 +848,11 @@ int igmp_rcv(struct sk_buff *skb) switch (ih->type) { case IGMP_HOST_MEMBERSHIP_QUERY: - igmp_heard_query(in_dev, ih->code, ih->group); + igmp_heard_query(in_dev, ih, len); break; case IGMP_HOST_MEMBERSHIP_REPORT: - case IGMP_HOST_NEW_MEMBERSHIP_REPORT: + case IGMPV2_HOST_MEMBERSHIP_REPORT: + case IGMPV3_HOST_MEMBERSHIP_REPORT: /* Is it our report looped back? */ if (((struct rtable*)skb->dst)->fl.iif == 0) break; @@ -422,15 +913,103 @@ static void ip_mc_filter_del(struct in_device *in_dev, u32 addr) dev_mc_delete(dev,buf,dev->addr_len,0); } +/* + * deleted ip_mc_list manipulation + */ +static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im) +{ + struct ip_mc_list *pmc; + + /* this is an "ip_mc_list" for convenience; only the fields below + * are actually used. In particular, the refcnt and users are not + * used for management of the delete list. Using the same structure + * for deleted items allows change reports to use common code with + * non-deleted or query-response MCA's. + */ + pmc = (struct ip_mc_list *)kmalloc(sizeof(*pmc), GFP_KERNEL); + if (!pmc) + return; + memset(pmc, 0, sizeof(*pmc)); + spin_lock_bh(&im->lock); + pmc->interface = im->interface; + in_dev_hold(in_dev); + pmc->multiaddr = im->multiaddr; + pmc->crcount = in_dev->mr_qrv ? in_dev->mr_qrv : + IGMP_Unsolicited_Report_Count; + pmc->sfmode = im->sfmode; + if (pmc->sfmode == MCAST_INCLUDE) { + struct ip_sf_list *psf; + + pmc->tomb = im->tomb; + pmc->sources = im->sources; + im->tomb = im->sources = 0; + for (psf=pmc->sources; psf; psf=psf->sf_next) + psf->sf_crcount = pmc->crcount; + } + spin_unlock_bh(&im->lock); + + write_lock_bh(&in_dev->mc_lock); + pmc->next = in_dev->mc_tomb; + in_dev->mc_tomb = pmc; + write_unlock_bh(&in_dev->mc_lock); +} + +static void igmpv3_del_delrec(struct in_device *in_dev, __u32 multiaddr) +{ + struct ip_mc_list *pmc, *pmc_prev; + struct ip_sf_list *psf, *psf_next; + + write_lock_bh(&in_dev->mc_lock); + pmc_prev = 0; + for (pmc=in_dev->mc_tomb; pmc; pmc=pmc->next) { + if (pmc->multiaddr == multiaddr) + break; + pmc_prev = pmc; + } + if (pmc) { + if (pmc_prev) + pmc_prev->next = pmc->next; + else + in_dev->mc_tomb = pmc->next; + } + write_unlock_bh(&in_dev->mc_lock); + if (pmc) { + for (psf=pmc->tomb; psf; psf=psf_next) { + psf_next = psf->sf_next; + kfree(psf); + } + in_dev_put(pmc->interface); + kfree(pmc); + } +} + +static void igmpv3_clear_delrec(struct in_device *in_dev) +{ + struct ip_mc_list *pmc, *nextpmc; + + write_lock_bh(&in_dev->mc_lock); + pmc = in_dev->mc_tomb; + in_dev->mc_tomb = 0; + write_unlock_bh(&in_dev->mc_lock); + + for (; pmc; pmc = nextpmc) { + nextpmc = pmc->next; + ip_mc_clear_src(pmc); + in_dev_put(pmc->interface); + kfree(pmc); + } +} + static void igmp_group_dropped(struct ip_mc_list *im) { #ifdef CONFIG_IP_MULTICAST int reporter; + struct in_device *in_dev = im->interface; #endif if (im->loaded) { im->loaded = 0; - ip_mc_filter_del(im->interface, im->multiaddr); + ip_mc_filter_del(in_dev, im->multiaddr); } #ifdef CONFIG_IP_MULTICAST @@ -440,25 +1019,46 @@ static void igmp_group_dropped(struct ip_mc_list *im) reporter = im->reporter; igmp_stop_timer(im); - if (reporter && !IGMP_V1_SEEN(im->interface)) - igmp_send_report(im->interface->dev, im->multiaddr, IGMP_HOST_LEAVE_MESSAGE); + if (IGMP_V1_SEEN(in_dev)) + goto done; + if (IGMP_V2_SEEN(in_dev)) { + if (reporter) + igmp_send_report(in_dev, im, IGMP_HOST_LEAVE_MESSAGE); + goto done; + } + /* IGMPv3 */ + igmpv3_add_delrec(in_dev, im); + + igmp_ifc_event(in_dev); +done: + ip_mc_clear_src(im); #endif } static void igmp_group_added(struct ip_mc_list *im) { + struct in_device *in_dev = im->interface; + if (im->loaded == 0) { im->loaded = 1; - ip_mc_filter_add(im->interface, im->multiaddr); + ip_mc_filter_add(in_dev, im->multiaddr); } #ifdef CONFIG_IP_MULTICAST if (im->multiaddr == IGMP_ALL_HOSTS) return; - spin_lock_bh(&im->lock); - igmp_start_timer(im, IGMP_Initial_Report_Delay); - spin_unlock_bh(&im->lock); + if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev)) { + spin_lock_bh(&im->lock); + igmp_start_timer(im, IGMP_Initial_Report_Delay); + spin_unlock_bh(&im->lock); + return; + } + /* else, v3 */ + + im->crcount = in_dev->mr_qrv ? in_dev->mr_qrv : + IGMP_Unsolicited_Report_Count; + igmp_ifc_event(in_dev); #endif } @@ -481,6 +1081,7 @@ void ip_mc_inc_group(struct in_device *in_dev, u32 addr) for (im=in_dev->mc_list; im; im=im->next) { if (im->multiaddr == addr) { im->users++; + ip_mc_add_src(in_dev, &addr, MCAST_EXCLUDE, 0, 0, 0); goto out; } } @@ -493,6 +1094,13 @@ void ip_mc_inc_group(struct in_device *in_dev, u32 addr) im->interface=in_dev; in_dev_hold(in_dev); im->multiaddr=addr; + /* initial mode is (EX, empty) */ + im->sfmode = MCAST_EXCLUDE; + im->sfcount[MCAST_INCLUDE] = 0; + im->sfcount[MCAST_EXCLUDE] = 1; + im->sources = 0; + im->tomb = 0; + im->crcount = 0; atomic_set(&im->refcnt, 1); spin_lock_init(&im->lock); #ifdef CONFIG_IP_MULTICAST @@ -502,12 +1110,14 @@ void ip_mc_inc_group(struct in_device *in_dev, u32 addr) im->timer.function=&igmp_timer_expire; im->unsolicit_count = IGMP_Unsolicited_Report_Count; im->reporter = 0; + im->gsquery = 0; #endif im->loaded = 0; write_lock_bh(&in_dev->lock); im->next=in_dev->mc_list; in_dev->mc_list=im; write_unlock_bh(&in_dev->lock); + igmpv3_del_delrec(in_dev, im->multiaddr); igmp_group_added(im); if (in_dev->dev->flags & IFF_UP) ip_rt_multicast_event(in_dev); @@ -552,9 +1162,18 @@ void ip_mc_down(struct in_device *in_dev) ASSERT_RTNL(); + in_dev->mr_ifc_count = 0; + if (del_timer(&in_dev->mr_ifc_timer)) + atomic_dec(&in_dev->refcnt); + in_dev->mr_gq_running = 0; + if (del_timer(&in_dev->mr_gq_timer)) + atomic_dec(&in_dev->refcnt); + for (i=in_dev->mc_list; i; i=i->next) igmp_group_dropped(i); + igmpv3_clear_delrec(in_dev); + ip_mc_dec_group(in_dev, IGMP_ALL_HOSTS); } @@ -566,6 +1185,18 @@ void ip_mc_up(struct in_device *in_dev) ASSERT_RTNL(); + in_dev->mc_lock = RW_LOCK_UNLOCKED; + in_dev->mr_gq_running = 0; + init_timer(&in_dev->mr_gq_timer); + in_dev->mr_gq_timer.data=(unsigned long) in_dev; + in_dev->mr_gq_timer.function=&igmp_gq_timer_expire; + in_dev->mc_tomb = 0; + in_dev->mr_ifc_count = 0; + init_timer(&in_dev->mr_ifc_timer); + in_dev->mr_ifc_timer.data=(unsigned long) in_dev; + in_dev->mr_ifc_timer.function=&igmp_ifc_timer_expire; + in_dev->mr_qrv = IGMP_Unsolicited_Report_Count; + ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS); for (i=in_dev->mc_list; i; i=i->next) @@ -626,6 +1257,257 @@ static struct in_device * ip_mc_find_dev(struct ip_mreqn *imr) */ int sysctl_igmp_max_memberships = IP_MAX_MEMBERSHIPS; + +static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode, + __u32 *psfsrc) +{ + struct ip_sf_list *psf, *psf_prev; + int rv = 0; + + psf_prev = 0; + for (psf=pmc->sources; psf; psf=psf->sf_next) { + if (psf->sf_inaddr == *psfsrc) + break; + psf_prev = psf; + } + if (!psf || psf->sf_count[sfmode] == 0) { + /* source filter not found, or count wrong => bug */ + return -ESRCH; + } + psf->sf_count[sfmode]--; + if (psf->sf_count[sfmode] == 0) { + ip_rt_multicast_event(pmc->interface); + } + if (!psf->sf_count[MCAST_INCLUDE] && !psf->sf_count[MCAST_EXCLUDE]) { + struct in_device *in_dev = pmc->interface; + + /* no more filters for this source */ + if (psf_prev) + psf_prev->sf_next = psf->sf_next; + else + pmc->sources = psf->sf_next; + if (psf->sf_oldin && + !IGMP_V1_SEEN(in_dev) && !IGMP_V2_SEEN(in_dev)) { + psf->sf_crcount = in_dev->mr_qrv ? in_dev->mr_qrv : + IGMP_Unsolicited_Report_Count; + psf->sf_next = pmc->tomb; + pmc->tomb = psf; + rv = 1; + } else + kfree(psf); + } + return rv; +} + +int ip_mc_del_src(struct in_device *in_dev, __u32 *pmca, int sfmode, + int sfcount, __u32 *psfsrc, int delta) +{ + struct ip_mc_list *pmc; + int changerec = 0; + int i, err; + + if (!in_dev) + return -ENODEV; + read_lock(&in_dev->lock); + for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) { + if (*pmca == pmc->multiaddr) + break; + } + if (!pmc) { + /* MCA not found?? bug */ + read_unlock(&in_dev->lock); + return -ESRCH; + } + spin_lock_bh(&pmc->lock); + read_unlock(&in_dev->lock); + sf_markstate(pmc); + if (!delta) { + if (!pmc->sfcount[sfmode]) + return -EINVAL; + pmc->sfcount[sfmode]--; + } + err = 0; + for (i=0; i 0; + if (!err && rv < 0) + err = rv; + } + if (pmc->sfmode == MCAST_EXCLUDE && + pmc->sfcount[MCAST_EXCLUDE] == 0 && + pmc->sfcount[MCAST_INCLUDE]) { + struct ip_sf_list *psf; + + /* filter mode change */ + pmc->sfmode = MCAST_INCLUDE; + pmc->crcount = in_dev->mr_qrv ? in_dev->mr_qrv : + IGMP_Unsolicited_Report_Count; + in_dev->mr_ifc_count = pmc->crcount; + for (psf=pmc->sources; psf; psf = psf->sf_next) + psf->sf_crcount = 0; + igmp_ifc_event(pmc->interface); + } else if (sf_setstate(pmc) || changerec) + igmp_ifc_event(pmc->interface); + spin_unlock_bh(&pmc->lock); + return err; +} + +/* + * Add multicast single-source filter to the interface list + */ +static int ip_mc_add1_src(struct ip_mc_list *pmc, int sfmode, + __u32 *psfsrc, int delta) +{ + struct ip_sf_list *psf, *psf_prev; + + psf_prev = 0; + for (psf=pmc->sources; psf; psf=psf->sf_next) { + if (psf->sf_inaddr == *psfsrc) + break; + psf_prev = psf; + } + if (!psf) { + psf = (struct ip_sf_list *)kmalloc(sizeof(*psf), GFP_ATOMIC); + if (!psf) + return -ENOBUFS; + memset(psf, 0, sizeof(*psf)); + psf->sf_inaddr = *psfsrc; + if (psf_prev) { + psf_prev->sf_next = psf; + } else + pmc->sources = psf; + } + psf->sf_count[sfmode]++; + if (psf->sf_count[sfmode] == 1) { + ip_rt_multicast_event(pmc->interface); + } + return 0; +} + +static void sf_markstate(struct ip_mc_list *pmc) +{ + struct ip_sf_list *psf; + int mca_xcount = pmc->sfcount[MCAST_EXCLUDE]; + + for (psf=pmc->sources; psf; psf=psf->sf_next) + if (pmc->sfcount[MCAST_EXCLUDE]) { + psf->sf_oldin = mca_xcount == + psf->sf_count[MCAST_EXCLUDE] && + !psf->sf_count[MCAST_INCLUDE]; + } else + psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0; +} + +static int sf_setstate(struct ip_mc_list *pmc) +{ + struct ip_sf_list *psf; + int mca_xcount = pmc->sfcount[MCAST_EXCLUDE]; + int qrv = pmc->interface->mr_qrv; + int new_in, rv; + + rv = 0; + for (psf=pmc->sources; psf; psf=psf->sf_next) { + if (pmc->sfcount[MCAST_EXCLUDE]) { + new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] && + !psf->sf_count[MCAST_INCLUDE]; + } else + new_in = psf->sf_count[MCAST_INCLUDE] != 0; + if (new_in != psf->sf_oldin) { + psf->sf_crcount = qrv; + rv++; + } + } + return rv; +} + +/* + * Add multicast source filter list to the interface list + */ +int ip_mc_add_src(struct in_device *in_dev, __u32 *pmca, int sfmode, + int sfcount, __u32 *psfsrc, int delta) +{ + struct ip_mc_list *pmc; + int isexclude; + int i, err; + + if (!in_dev) + return -ENODEV; + read_lock(&in_dev->lock); + for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) { + if (*pmca == pmc->multiaddr) + break; + } + if (!pmc) { + /* MCA not found?? bug */ + read_unlock(&in_dev->lock); + return -ESRCH; + } + spin_lock_bh(&pmc->lock); + read_unlock(&in_dev->lock); + + sf_markstate(pmc); + isexclude = pmc->sfmode == MCAST_EXCLUDE; + if (!delta) + pmc->sfcount[sfmode]++; + err = 0; + for (i=0; isfcount[sfmode]--; + for (j=0; jsfcount[MCAST_EXCLUDE] != 0)) { + struct in_device *in_dev = pmc->interface; + struct ip_sf_list *psf; + + /* filter mode change */ + if (pmc->sfcount[MCAST_EXCLUDE]) + pmc->sfmode = MCAST_EXCLUDE; + else if (pmc->sfcount[MCAST_INCLUDE]) + pmc->sfmode = MCAST_INCLUDE; + /* else no filters; keep old mode for reports */ + + pmc->crcount = in_dev->mr_qrv ? in_dev->mr_qrv : + IGMP_Unsolicited_Report_Count; + in_dev->mr_ifc_count = pmc->crcount; + for (psf=pmc->sources; psf; psf = psf->sf_next) + psf->sf_crcount = 0; + igmp_ifc_event(in_dev); + } else if (sf_setstate(pmc)) + igmp_ifc_event(in_dev); + spin_unlock_bh(&pmc->lock); + return err; +} + +static void ip_mc_clear_src(struct ip_mc_list *pmc) +{ + struct ip_sf_list *psf, *nextpsf; + + for (psf=pmc->tomb; psf; psf=nextpsf) { + nextpsf = psf->sf_next; + kfree(psf); + } + pmc->tomb = 0; + for (psf=pmc->sources; psf; psf=nextpsf) { + nextpsf = psf->sf_next; + kfree(psf); + } + pmc->sources = 0; + pmc->sfmode = MCAST_EXCLUDE; + pmc->sfcount[MCAST_EXCLUDE] = 0; + pmc->sfcount[MCAST_EXCLUDE] = 1; +} + + +/* + * Join a multicast group + */ int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr) { int err; @@ -674,6 +1556,8 @@ int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr) memcpy(&iml->multi, imr, sizeof(*imr)); iml->next = inet->mc_list; iml->count = 1; + iml->sflist = NULL; + iml->sfmode = MCAST_EXCLUDE; inet->mc_list = iml; ip_mc_inc_group(in_dev, addr); iml = NULL; @@ -686,6 +1570,24 @@ done: return err; } +int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml, + struct in_device *in_dev) +{ + int err; + + if (iml->sflist == 0) { + /* any-source empty exclude case */ + return ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr, + iml->sfmode, 0, 0, 0); + } + err = ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr, + iml->sfmode, iml->sflist->sl_count, + iml->sflist->sl_addr, 0); + sock_kfree_s(sk, iml->sflist, IP_SFLSIZE(iml->sflist->sl_max)); + iml->sflist = 0; + return err; +} + /* * Ask a socket to leave a group. */ @@ -701,14 +1603,19 @@ int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr) iml->multi.imr_address.s_addr==imr->imr_address.s_addr && (!imr->imr_ifindex || iml->multi.imr_ifindex==imr->imr_ifindex)) { struct in_device *in_dev; + + in_dev = inetdev_by_index(iml->multi.imr_ifindex); + if (in_dev) + (void) ip_mc_leave_src(sk, iml, in_dev); if (--iml->count) { rtnl_unlock(); + if (in_dev) + in_dev_put(in_dev); return 0; } *imlp = iml->next; - in_dev = inetdev_by_index(iml->multi.imr_ifindex); if (in_dev) { ip_mc_dec_group(in_dev, imr->imr_multiaddr.s_addr); in_dev_put(in_dev); @@ -722,6 +1629,283 @@ int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr) return -EADDRNOTAVAIL; } +int ip_mc_source(int add, int omode, struct sock *sk, struct + ip_mreq_source *mreqs) +{ + int err; + struct ip_mreqn imr; + u32 addr = mreqs->imr_multiaddr; + struct ip_mc_socklist *pmc; + struct in_device *in_dev; + struct inet_opt *inet = inet_sk(sk); + struct ip_sf_socklist *psl; + int i, j, rv; + + if (!MULTICAST(addr)) + return -EINVAL; + + rtnl_shlock(); + + imr.imr_multiaddr.s_addr = mreqs->imr_multiaddr; + imr.imr_address.s_addr = mreqs->imr_interface; + imr.imr_ifindex = 0; + in_dev = ip_mc_find_dev(&imr); + + if (!in_dev) { + err = -ENODEV; + goto done; + } + err = -EADDRNOTAVAIL; + + for (pmc=inet->mc_list; pmc; pmc=pmc->next) { + if (memcmp(&pmc->multi, mreqs, 2*sizeof(__u32)) == 0) + break; + } + if (!pmc) /* must have a prior join */ + goto done; + /* if a source filter was set, must be the same mode as before */ + if (pmc->sflist) { + if (pmc->sfmode != omode) + goto done; + } else if (pmc->sfmode != omode) { + /* allow mode switches for empty-set filters */ + ip_mc_del_src(in_dev, &mreqs->imr_multiaddr, pmc->sfmode, 0, + 0, 0); + pmc->sfmode = omode; + ip_mc_add_src(in_dev, &mreqs->imr_multiaddr, pmc->sfmode, 0, + 0, 0); + } + + psl = pmc->sflist; + if (!add) { + if (!psl) + goto done; + rv = !0; + for (i=0; isl_count; i++) { + rv = memcmp(&psl->sl_addr, &mreqs->imr_multiaddr, + sizeof(__u32)); + if (rv >= 0) + break; + } + if (!rv) /* source not found */ + goto done; + + /* update the interface filter */ + ip_mc_del_src(in_dev, &mreqs->imr_multiaddr, omode, 1, + &mreqs->imr_sourceaddr, 1); + + for (j=i+1; jsl_count; j++) + psl->sl_addr[j-1] = psl->sl_addr[j]; + psl->sl_count--; + err = 0; + goto done; + } + /* else, add a new source to the filter */ + + if (!psl || psl->sl_count == psl->sl_max) { + struct ip_sf_socklist *newpsl; + int count = IP_SFBLOCK; + + if (psl) + count += psl->sl_max; + newpsl = (struct ip_sf_socklist *)sock_kmalloc(sk, + IP_SFLSIZE(count), GFP_KERNEL); + if (!newpsl) { + err = -ENOBUFS; + goto done; + } + newpsl->sl_max = count; + newpsl->sl_count = count - IP_SFBLOCK; + if (psl) { + for (i=0; isl_count; i++) + newpsl->sl_addr[i] = psl->sl_addr[i]; + sock_kfree_s(sk, psl, IP_SFLSIZE(psl->sl_max)); + } + pmc->sflist = psl = newpsl; + } + rv = 1; /* > 0 for insert logic below if sl_count is 0 */ + for (i=0; isl_count; i++) { + rv = memcmp(&psl->sl_addr, &mreqs->imr_multiaddr, + sizeof(__u32)); + if (rv >= 0) + break; + } + if (rv == 0) /* address already there is an error */ + goto done; + for (j=psl->sl_count-1; j>=i; j--) + psl->sl_addr[j+1] = psl->sl_addr[j]; + psl->sl_addr[i] = mreqs->imr_sourceaddr; + psl->sl_count++; + err = 0; + /* update the interface list */ + ip_mc_add_src(in_dev, &mreqs->imr_multiaddr, omode, 1, + &mreqs->imr_sourceaddr, 1); +done: + rtnl_shunlock(); + return err; +} + +int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf) +{ + int err; + struct ip_mreqn imr; + u32 addr = msf->imsf_multiaddr; + struct ip_mc_socklist *pmc; + struct in_device *in_dev; + struct inet_opt *inet = inet_sk(sk); + struct ip_sf_socklist *newpsl, *psl; + + if (!MULTICAST(addr)) + return -EINVAL; + if (msf->imsf_fmode != MCAST_INCLUDE && + msf->imsf_fmode != MCAST_EXCLUDE) + return -EINVAL; + + rtnl_shlock(); + + imr.imr_multiaddr.s_addr = msf->imsf_multiaddr; + imr.imr_address.s_addr = msf->imsf_interface; + imr.imr_ifindex = 0; + in_dev = ip_mc_find_dev(&imr); + + if (!in_dev) { + err = -ENODEV; + goto done; + } + err = -EADDRNOTAVAIL; + + for (pmc=inet->mc_list; pmc; pmc=pmc->next) { + if (memcmp(&pmc->multi, &imr, sizeof(imr)) == 0) + break; + } + if (!pmc) /* must have a prior join */ + goto done; + if (msf->imsf_numsrc) { + newpsl = (struct ip_sf_socklist *)sock_kmalloc(sk, + IP_SFLSIZE(msf->imsf_numsrc), GFP_KERNEL); + if (!newpsl) { + err = -ENOBUFS; + goto done; + } + newpsl->sl_max = newpsl->sl_count = msf->imsf_numsrc; + memcpy(newpsl->sl_addr, msf->imsf_slist, + msf->imsf_numsrc * sizeof(msf->imsf_slist[0])); + err = ip_mc_add_src(in_dev, &msf->imsf_multiaddr, + msf->imsf_fmode, newpsl->sl_count, newpsl->sl_addr, 0); + if (err) { + sock_kfree_s(sk, newpsl, IP_SFLSIZE(newpsl->sl_max)); + goto done; + } + } else + newpsl = 0; + psl = pmc->sflist; + if (psl) { + (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, + psl->sl_count, psl->sl_addr, 0); + sock_kfree_s(sk, psl, IP_SFLSIZE(psl->sl_max)); + } else + (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, + 0, 0, 0); + pmc->sflist = newpsl; + pmc->sfmode = msf->imsf_fmode; +done: + rtnl_shunlock(); + return err; +} + +int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf, + struct ip_msfilter *optval, int *optlen) +{ + int err, len, count, copycount; + struct ip_mreqn imr; + u32 addr = msf->imsf_multiaddr; + struct ip_mc_socklist *pmc; + struct in_device *in_dev; + struct inet_opt *inet = inet_sk(sk); + struct ip_sf_socklist *psl; + + if (!MULTICAST(addr)) + return -EINVAL; + if (msf->imsf_fmode != MCAST_INCLUDE && + msf->imsf_fmode != MCAST_EXCLUDE) + return -EINVAL; + + rtnl_shlock(); + + imr.imr_multiaddr.s_addr = msf->imsf_multiaddr; + imr.imr_address.s_addr = msf->imsf_interface; + imr.imr_ifindex = 0; + in_dev = ip_mc_find_dev(&imr); + + if (!in_dev) { + err = -ENODEV; + goto done; + } + err = -EADDRNOTAVAIL; + + for (pmc=inet->mc_list; pmc; pmc=pmc->next) { + if (memcmp(&pmc->multi, &imr, sizeof(imr)) == 0) + break; + } + if (!pmc) /* must have a prior join */ + goto done; + msf->imsf_fmode = pmc->sfmode; + psl = pmc->sflist; + rtnl_shunlock(); + if (!psl) { + len = 0; + count = 0; + } else { + count = psl->sl_count; + } + copycount = count < msf->imsf_numsrc ? count : msf->imsf_numsrc; + len = copycount * sizeof(psl->sl_addr[0]); + msf->imsf_numsrc = count; + if (put_user(IP_MSFILTER_SIZE(copycount), optlen) || + copy_to_user((void *)optval, msf, IP_MSFILTER_SIZE(0))) { + return -EFAULT; + } + if (len && + copy_to_user((void *)&optval->imsf_slist[0], psl->sl_addr, len)) + return -EFAULT; + return 0; +done: + rtnl_shunlock(); + return err; +} + +/* + * check if a multicast source filter allows delivery for a given + */ +int ip_mc_sf_allow(struct sock *sk, u32 loc_addr, u32 rmt_addr, int dif) +{ + struct inet_opt *inet = inet_sk(sk); + struct ip_mc_socklist *pmc; + struct ip_sf_socklist *psl; + int i; + + for (pmc=inet->mc_list; pmc; pmc=pmc->next) { + if (pmc->multi.imr_multiaddr.s_addr == loc_addr && + pmc->multi.imr_ifindex == dif) + break; + } + if (!pmc) + return 0; + psl = pmc->sflist; + if (!psl) + return pmc->sfmode == MCAST_EXCLUDE; + + for (i=0; isl_count; i++) { + if (psl->sl_addr[i] == rmt_addr) + break; + } + if (pmc->sfmode == MCAST_INCLUDE && i < psl->sl_count) + return 1; + if (pmc->sfmode == MCAST_EXCLUDE && i >= psl->sl_count) + return 1; + return 0; +} + /* * A socket is closing. */ @@ -740,6 +1924,7 @@ void ip_mc_drop_socket(struct sock *sk) inet->mc_list = iml->next; if ((in_dev = inetdev_by_index(iml->multi.imr_ifindex)) != NULL) { + (void) ip_mc_leave_src(sk, iml, in_dev); ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr); in_dev_put(in_dev); } @@ -749,19 +1934,33 @@ void ip_mc_drop_socket(struct sock *sk) rtnl_unlock(); } -int ip_check_mc(struct in_device *in_dev, u32 mc_addr) +int ip_check_mc(struct in_device *in_dev, u32 mc_addr, u32 src_addr, u16 proto) { struct ip_mc_list *im; + struct ip_sf_list *psf; + int rv = 0; read_lock(&in_dev->lock); for (im=in_dev->mc_list; im; im=im->next) { - if (im->multiaddr == mc_addr) { - read_unlock(&in_dev->lock); - return 1; + if (im->multiaddr == mc_addr) + break; + } + if (im && proto == IPPROTO_IGMP) { + rv = 1; + } else if (im) { + for (psf=im->sources; psf; psf=psf->sf_next) { + if (psf->sf_inaddr == src_addr) + break; } + if (psf) + rv = psf->sf_count[MCAST_INCLUDE] || + psf->sf_count[MCAST_EXCLUDE] != + im->sfcount[MCAST_EXCLUDE]; + else + rv = im->sfcount[MCAST_EXCLUDE] != 0; } read_unlock(&in_dev->lock); - return 0; + return rv; } @@ -822,5 +2021,101 @@ done: len=0; return len; } + +int ip_mcf_procinfo(char *buffer, char **start, off_t offset, int length) +{ + off_t pos=0, begin=0; + int len=0; + int first = 1; + struct net_device *dev; + + read_lock(&dev_base_lock); + for(dev=dev_base; dev; dev=dev->next) { + struct in_device *in_dev = in_dev_get(dev); + struct ip_mc_list *imc; + + if (in_dev == NULL) + continue; + + read_lock(&in_dev->lock); + + for (imc=in_dev->mc_list; imc; imc=imc->next) { + struct ip_sf_list *psf; + unsigned long icount, xcount; + + spin_lock_bh(&imc->lock); + icount = imc->sfcount[MCAST_INCLUDE]; + xcount = imc->sfcount[MCAST_EXCLUDE]; + for (psf=imc->sources; psf; psf=psf->sf_next) { + if (first) { + len += sprintf(buffer+len, "%3s %6s " + "%10s %10s %6s %6s\n", "Idx", + "Device", "MCA", "SRC", "INC", + "EXC"); + first = 0; + } + len += sprintf(buffer+len, "%3d %6.6s 0x%08x " + "0x%08x %6lu %6lu\n", dev->ifindex, + dev->name, ntohl(imc->multiaddr), + ntohl(psf->sf_inaddr), + psf->sf_count[MCAST_INCLUDE], + psf->sf_count[MCAST_EXCLUDE]); + pos=begin+len; + if(posoffset+length) { + spin_unlock_bh(&imc->lock); + read_unlock(&in_dev->lock); + in_dev_put(in_dev); + goto done; + } + icount -= psf->sf_count[MCAST_INCLUDE]; + xcount -= psf->sf_count[MCAST_EXCLUDE]; + } + if (icount > 0 || xcount > 0) { + if (first) { + len += sprintf(buffer+len, "%3s %6s " + "%10s %10s %6s %6s\n", "Idx", + "Device", "MCA", "SRC", "INC", + "EXC"); + first = 0; + } + len += sprintf(buffer+len, "%3d %6.6s 0x%08x " + "%10s %6lu %6lu\n", dev->ifindex, + dev->name, ntohl(imc->multiaddr), + "NONE", icount, xcount); + pos=begin+len; + if(posoffset+length) { + spin_unlock_bh(&imc->lock); + read_unlock(&in_dev->lock); + in_dev_put(in_dev); + goto done; + } + } + spin_unlock_bh(&imc->lock); + } + read_unlock(&in_dev->lock); + in_dev_put(in_dev); + } +done: + read_unlock(&dev_base_lock); + + *start=buffer+(offset-begin); + len-=(offset-begin); + if(len>length) + len=length; + if(len<0) + len=0; + return len; +} + #endif diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index f5dbd274ec40..9a4db884dc1f 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -1312,5 +1312,6 @@ void __init ip_init(void) #ifdef CONFIG_IP_MULTICAST proc_net_create("igmp", 0, ip_mc_procinfo); + proc_net_create("mcfilter", 0, ip_mcf_procinfo); #endif } diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c index 0eadd005f4f3..a8a561411dea 100644 --- a/net/ipv4/ip_sockglue.c +++ b/net/ipv4/ip_sockglue.c @@ -610,9 +610,67 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char *optval, int opt } if (optname == IP_ADD_MEMBERSHIP) - err = ip_mc_join_group(sk,&mreq); + err = ip_mc_join_group(sk, &mreq); else - err = ip_mc_leave_group(sk,&mreq); + err = ip_mc_leave_group(sk, &mreq); + break; + } + case IP_MSFILTER: + { + struct ip_msfilter *msf; + + if (optlen < IP_MSFILTER_SIZE(0)) + goto e_inval; + msf = (struct ip_msfilter *)kmalloc(optlen, GFP_KERNEL); + if (msf == 0) { + err = -ENOBUFS; + break; + } + err = -EFAULT; + if (copy_from_user(msf, optval, optlen)) { + kfree(msf); + break; + } + err = ip_mc_msfilter(sk, msf); + kfree(msf); + break; + } + case IP_BLOCK_SOURCE: + case IP_UNBLOCK_SOURCE: + case IP_ADD_SOURCE_MEMBERSHIP: + case IP_DROP_SOURCE_MEMBERSHIP: + { + struct ip_mreq_source mreqs; + int omode, add; + + if (optlen != sizeof(struct ip_mreq_source)) + goto e_inval; + if (copy_from_user(&mreqs, optval, sizeof(mreqs))) { + err = -EFAULT; + break; + } + if (optname == IP_BLOCK_SOURCE) { + omode = MCAST_EXCLUDE; + add = 1; + } else if (optname == IP_UNBLOCK_SOURCE) { + omode = MCAST_EXCLUDE; + add = 0; + } else if (optname == IP_ADD_SOURCE_MEMBERSHIP) { + struct ip_mreqn mreq; + + mreq.imr_multiaddr.s_addr = mreqs.imr_multiaddr; + mreq.imr_address.s_addr = mreqs.imr_interface; + mreq.imr_ifindex = 0; + err = ip_mc_join_group(sk, &mreq); + if (err) + break; + omode = MCAST_INCLUDE; + add = 1; + } else /*IP_DROP_SOURCE_MEMBERSHIP */ { + omode = MCAST_INCLUDE; + add = 0; + } + err = ip_mc_source(add, omode, sk, &mreqs); break; } case IP_ROUTER_ALERT: @@ -763,6 +821,20 @@ int ip_getsockopt(struct sock *sk, int level, int optname, char *optval, int *op return -EFAULT; return 0; } + case IP_MSFILTER: + { + struct ip_msfilter msf; + int err; + + if (len < IP_MSFILTER_SIZE(0)) + return -EINVAL; + if (copy_from_user(&msf, optval, IP_MSFILTER_SIZE(0))) + return -EFAULT; + err = ip_mc_msfget(sk, &msf, + (struct ip_msfilter *)optval, optlen); + release_sock(sk); + return err; + } case IP_PKTOPTIONS: { struct msghdr msg; diff --git a/net/ipv4/route.c b/net/ipv4/route.c index a915ce6d5889..a20ea074b300 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1790,7 +1790,8 @@ int ip_route_input(struct sk_buff *skb, u32 daddr, u32 saddr, read_lock(&inetdev_lock); if ((in_dev = __in_dev_get(dev)) != NULL) { - int our = ip_check_mc(in_dev, daddr); + int our = ip_check_mc(in_dev, daddr, saddr, + skb->nh.iph->protocol); if (our #ifdef CONFIG_IP_MROUTE || (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev)) @@ -2020,7 +2021,7 @@ make_route: } } else if (res.type == RTN_MULTICAST) { flags |= RTCF_MULTICAST|RTCF_LOCAL; - if (!ip_check_mc(in_dev, oldflp->fl4_dst)) + if (!ip_check_mc(in_dev, oldflp->fl4_dst, oldflp->fl4_src, oldflp->proto)) flags &= ~RTCF_LOCAL; /* If multicast route do not exist use default one, but do not gateway in this case. diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 0d3c90f7b8fd..71f5a0df5296 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -298,6 +298,8 @@ static inline struct sock *udp_v4_mcast_next(struct sock *sk, ipv6_only_sock(s) || (s->bound_dev_if && s->bound_dev_if != dif)) continue; + if (!ip_mc_sf_allow(sk, loc_addr, rmt_addr, dif)) + continue; break; } return s; -- cgit v1.2.3