From: Neil Horman <nhorman@redhat.com> Date: Tue, 26 May 2009 06:54:23 -0400 Subject: [net] undo vlan promiscuity count when unregistered Message-id: 20090526105423.GA725@hmsreliant.think-freely.org O-Subject: [RHEL 5.5] PATCH: properly undo vlan promiscuity count when device is unregistered (bz481283) Bugzilla: 481283 RH-Acked-by: Thomas Graf <tgraf@redhat.com> RH-Acked-by: David Miller <davem@redhat.com> Hey Currently our vlans leak promiscuity counts when unregistered. Vlans do various things to the underlying real devices promiscuity setings when the vlan itself changes mac address, or its own promiscuity, but it never tracks or undoes them when the device is unregistered. This results in underlying physical devices having strange promiscuity counts and not being able to clear the IFF_PROMISC flag on the interface. Upstream has solved this with a re-write of how unicast and multicast address lists are handled, but the change is an ABI breaker. Flavio and I developed this patch instead to fix the problem locally in the vlan device driver. Validated by internal testing. Fixes bz 481283 Neil diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h index f2f0ba8..2d18f2c 100644 --- a/include/linux/if_vlan.h +++ b/include/linux/if_vlan.h @@ -112,6 +112,7 @@ struct vlan_dev_info { */ int old_allmulti; /* similar to above. */ int old_promiscuity; /* similar to above. */ + int promisc_count; /* our delta on real_devs promisc */ struct net_device *real_dev; /* the underlying device/interface */ struct proc_dir_entry *dent; /* Holds the proc data */ unsigned long cnt_inc_headroom_on_tx; /* How many times did we have to grow the skb on TX. */ diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c index 18fcb9f..7440493 100644 --- a/net/8021q/vlan.c +++ b/net/8021q/vlan.c @@ -275,6 +275,8 @@ static int unregister_vlan_dev(struct net_device *real_dev, static int unregister_vlan_device(const char *vlan_IF_name) { struct net_device *dev = NULL; + struct net_device *real_dev; + int undo_count; int ret; @@ -284,9 +286,15 @@ static int unregister_vlan_device(const char *vlan_IF_name) if (dev->priv_flags & IFF_802_1Q_VLAN) { rtnl_lock(); - ret = unregister_vlan_dev(VLAN_DEV_INFO(dev)->real_dev, + real_dev = VLAN_DEV_INFO(dev)->real_dev; + ret = unregister_vlan_dev(real_dev, VLAN_DEV_INFO(dev)->vlan_id); + undo_count = VLAN_DEV_INFO(dev)->promisc_count * -1; + dev_set_promiscuity(real_dev, undo_count); + if (!(real_dev->flags & IFF_PROMISC)) + real_dev->gflags &= ~IFF_PROMISC; + dev_put(dev); unregister_netdevice(dev); diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index 3d0c784..15b5719 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -680,18 +680,22 @@ int vlan_dev_set_mac_address(struct net_device *dev, void *addr_struct_p) if (memcmp(VLAN_DEV_INFO(dev)->real_dev->dev_addr, dev->dev_addr, dev->addr_len) != 0) { - if (!(VLAN_DEV_INFO(dev)->real_dev->flags & IFF_PROMISC)) { - int flgs = VLAN_DEV_INFO(dev)->real_dev->flags; - - /* Increment our in-use promiscuity counter */ - dev_set_promiscuity(VLAN_DEV_INFO(dev)->real_dev, 1); + int flgs = VLAN_DEV_INFO(dev)->real_dev->flags; + int gflgs = VLAN_DEV_INFO(dev)->real_dev->gflags; + if (!(flgs & IFF_PROMISC)) { /* Make PROMISC visible to the user. */ flgs |= IFF_PROMISC; printk("VLAN (%s): Setting underlying device (%s) to promiscious mode.\n", dev->name, VLAN_DEV_INFO(dev)->real_dev->name); dev_change_flags(VLAN_DEV_INFO(dev)->real_dev, flgs); + if (!(gflgs & IFF_PROMISC)) + VLAN_DEV_INFO(dev)->promisc_count++; } + + /* Increment our in-use promiscuity counter */ + dev_set_promiscuity(VLAN_DEV_INFO(dev)->real_dev, 1); + VLAN_DEV_INFO(dev)->promisc_count++; } else { printk("VLAN (%s): Underlying device (%s) has same MAC, not checking promiscious mode.\n", dev->name, VLAN_DEV_INFO(dev)->real_dev->name); @@ -843,6 +847,7 @@ void vlan_dev_set_multicast_list(struct net_device *vlan_dev) vlan_dev->name, inc); dev_set_promiscuity(real_dev, inc); /* found in dev.c */ VLAN_DEV_INFO(vlan_dev)->old_promiscuity = vlan_dev->promiscuity; + VLAN_DEV_INFO(vlan_dev)->promisc_count += inc; } inc = vlan_dev->allmulti - VLAN_DEV_INFO(vlan_dev)->old_allmulti;