aboutsummaryrefslogtreecommitdiffstats
path: root/net/8021q
diff options
context:
space:
mode:
authorPatrick McHardy <kaber@trash.net>2008-01-21 03:25:50 -0500
committerDavid S. Miller <davem@davemloft.net>2008-01-28 18:08:31 -0500
commitaf30151709bcace1ca844d4bb8b7e2e392ff81eb (patch)
tree5da346ea833fe8097359613463cd716613359215 /net/8021q
parentacc5efbcd2a023c8801f2bba39971cf93812ce7c (diff)
[VLAN]: Simplify vlan unregistration
Keep track of the number of VLAN devices in a vlan group. This allows to have the caller sense when the group is going to be destroyed and stop using it, which in turn allows to remove the wrapper around unregister_vlan_dev for the NETDEV_UNREGISTER notifier and avoid iterating over all possible VLAN ids whenever a device in unregistered. Also fix what looks like a use-after-free (but is actually safe since we're holding the RTNL), the real_dev reference should not be dropped while we still use it. Signed-off-by: Patrick McHardy <kaber@trash.net> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/8021q')
-rw-r--r--net/8021q/vlan.c76
-rw-r--r--net/8021q/vlan.h2
-rw-r--r--net/8021q/vlan_netlink.c7
3 files changed, 22 insertions, 63 deletions
diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c
index ad34e4a0326a..ac7963854103 100644
--- a/net/8021q/vlan.c
+++ b/net/8021q/vlan.c
@@ -132,33 +132,17 @@ static void vlan_rcu_free(struct rcu_head *rcu)
132 vlan_group_free(container_of(rcu, struct vlan_group, rcu)); 132 vlan_group_free(container_of(rcu, struct vlan_group, rcu));
133} 133}
134 134
135 135void unregister_vlan_dev(struct net_device *dev)
136/* This returns 0 if everything went fine.
137 * It will return 1 if the group was killed as a result.
138 * A negative return indicates failure.
139 *
140 * The RTNL lock must be held.
141 */
142static int unregister_vlan_dev(struct net_device *real_dev,
143 unsigned short vlan_id)
144{ 136{
145 struct net_device *dev; 137 struct vlan_dev_info *vlan = VLAN_DEV_INFO(dev);
146 int real_dev_ifindex = real_dev->ifindex; 138 struct net_device *real_dev = vlan->real_dev;
147 struct vlan_group *grp; 139 struct vlan_group *grp;
148 unsigned int i; 140 unsigned short vlan_id = vlan->vlan_id;
149 int ret;
150
151 if (vlan_id >= VLAN_VID_MASK)
152 return -EINVAL;
153 141
154 ASSERT_RTNL(); 142 ASSERT_RTNL();
155 grp = __vlan_find_group(real_dev_ifindex);
156 if (!grp)
157 return -ENOENT;
158 143
159 dev = vlan_group_get_device(grp, vlan_id); 144 grp = __vlan_find_group(real_dev->ifindex);
160 if (!dev) 145 BUG_ON(!grp);
161 return -ENOENT;
162 146
163 vlan_proc_rem_dev(dev); 147 vlan_proc_rem_dev(dev);
164 148
@@ -169,20 +153,12 @@ static int unregister_vlan_dev(struct net_device *real_dev,
169 real_dev->vlan_rx_kill_vid(real_dev, vlan_id); 153 real_dev->vlan_rx_kill_vid(real_dev, vlan_id);
170 154
171 vlan_group_set_device(grp, vlan_id, NULL); 155 vlan_group_set_device(grp, vlan_id, NULL);
172 synchronize_net(); 156 grp->nr_vlans--;
173 157
174 /* Caller unregisters (and if necessary, puts) VLAN device, but we 158 synchronize_net();
175 * get rid of the reference to real_dev here.
176 */
177 dev_put(real_dev);
178 159
179 /* If the group is now empty, kill off the group. */ 160 /* If the group is now empty, kill off the group. */
180 ret = 0; 161 if (grp->nr_vlans == 0) {
181 for (i = 0; i < VLAN_VID_MASK; i++)
182 if (vlan_group_get_device(grp, i))
183 break;
184
185 if (i == VLAN_VID_MASK) {
186 if (real_dev->features & NETIF_F_HW_VLAN_RX) 162 if (real_dev->features & NETIF_F_HW_VLAN_RX)
187 real_dev->vlan_rx_register(real_dev, NULL); 163 real_dev->vlan_rx_register(real_dev, NULL);
188 164
@@ -190,23 +166,12 @@ static int unregister_vlan_dev(struct net_device *real_dev,
190 166
191 /* Free the group, after all cpu's are done. */ 167 /* Free the group, after all cpu's are done. */
192 call_rcu(&grp->rcu, vlan_rcu_free); 168 call_rcu(&grp->rcu, vlan_rcu_free);
193 ret = 1;
194 } 169 }
195 170
196 return ret; 171 /* Get rid of the vlan's reference to real_dev */
197} 172 dev_put(real_dev);
198
199int unregister_vlan_device(struct net_device *dev)
200{
201 int ret;
202 173
203 ret = unregister_vlan_dev(VLAN_DEV_INFO(dev)->real_dev,
204 VLAN_DEV_INFO(dev)->vlan_id);
205 unregister_netdevice(dev); 174 unregister_netdevice(dev);
206
207 if (ret == 1)
208 ret = 0;
209 return ret;
210} 175}
211 176
212static void vlan_transfer_operstate(const struct net_device *dev, struct net_device *vlandev) 177static void vlan_transfer_operstate(const struct net_device *dev, struct net_device *vlandev)
@@ -291,6 +256,8 @@ int register_vlan_dev(struct net_device *dev)
291 * it into our local structure. 256 * it into our local structure.
292 */ 257 */
293 vlan_group_set_device(grp, vlan_id, dev); 258 vlan_group_set_device(grp, vlan_id, dev);
259 grp->nr_vlans++;
260
294 if (ngrp && real_dev->features & NETIF_F_HW_VLAN_RX) 261 if (ngrp && real_dev->features & NETIF_F_HW_VLAN_RX)
295 real_dev->vlan_rx_register(real_dev, ngrp); 262 real_dev->vlan_rx_register(real_dev, ngrp);
296 if (real_dev->features & NETIF_F_HW_VLAN_FILTER) 263 if (real_dev->features & NETIF_F_HW_VLAN_FILTER)
@@ -479,20 +446,16 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event,
479 case NETDEV_UNREGISTER: 446 case NETDEV_UNREGISTER:
480 /* Delete all VLANs for this dev. */ 447 /* Delete all VLANs for this dev. */
481 for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { 448 for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) {
482 int ret;
483
484 vlandev = vlan_group_get_device(grp, i); 449 vlandev = vlan_group_get_device(grp, i);
485 if (!vlandev) 450 if (!vlandev)
486 continue; 451 continue;
487 452
488 ret = unregister_vlan_dev(dev, 453 /* unregistration of last vlan destroys group, abort
489 VLAN_DEV_INFO(vlandev)->vlan_id); 454 * afterwards */
490 455 if (grp->nr_vlans == 1)
491 unregister_netdevice(vlandev); 456 i = VLAN_GROUP_ARRAY_LEN;
492 457
493 /* Group was destroyed? */ 458 unregister_vlan_dev(vlandev);
494 if (ret == 1)
495 break;
496 } 459 }
497 break; 460 break;
498 } 461 }
@@ -598,7 +561,8 @@ static int vlan_ioctl_handler(struct net *net, void __user *arg)
598 err = -EPERM; 561 err = -EPERM;
599 if (!capable(CAP_NET_ADMIN)) 562 if (!capable(CAP_NET_ADMIN))
600 break; 563 break;
601 err = unregister_vlan_device(dev); 564 unregister_vlan_dev(dev);
565 err = 0;
602 break; 566 break;
603 567
604 case GET_VLAN_REALDEV_NAME_CMD: 568 case GET_VLAN_REALDEV_NAME_CMD:
diff --git a/net/8021q/vlan.h b/net/8021q/vlan.h
index 56378651cc46..0cfdf77b497c 100644
--- a/net/8021q/vlan.h
+++ b/net/8021q/vlan.h
@@ -38,7 +38,7 @@ void vlan_dev_get_vid(const struct net_device *dev, unsigned short *result);
38int vlan_check_real_dev(struct net_device *real_dev, unsigned short vlan_id); 38int vlan_check_real_dev(struct net_device *real_dev, unsigned short vlan_id);
39void vlan_setup(struct net_device *dev); 39void vlan_setup(struct net_device *dev);
40int register_vlan_dev(struct net_device *dev); 40int register_vlan_dev(struct net_device *dev);
41int unregister_vlan_device(struct net_device *dev); 41void unregister_vlan_dev(struct net_device *dev);
42 42
43int vlan_netlink_init(void); 43int vlan_netlink_init(void);
44void vlan_netlink_fini(void); 44void vlan_netlink_fini(void);
diff --git a/net/8021q/vlan_netlink.c b/net/8021q/vlan_netlink.c
index 0996185e2ed5..9ee63583ed27 100644
--- a/net/8021q/vlan_netlink.c
+++ b/net/8021q/vlan_netlink.c
@@ -137,11 +137,6 @@ static int vlan_newlink(struct net_device *dev,
137 return register_vlan_dev(dev); 137 return register_vlan_dev(dev);
138} 138}
139 139
140static void vlan_dellink(struct net_device *dev)
141{
142 unregister_vlan_device(dev);
143}
144
145static inline size_t vlan_qos_map_size(unsigned int n) 140static inline size_t vlan_qos_map_size(unsigned int n)
146{ 141{
147 if (n == 0) 142 if (n == 0)
@@ -226,7 +221,7 @@ struct rtnl_link_ops vlan_link_ops __read_mostly = {
226 .validate = vlan_validate, 221 .validate = vlan_validate,
227 .newlink = vlan_newlink, 222 .newlink = vlan_newlink,
228 .changelink = vlan_changelink, 223 .changelink = vlan_changelink,
229 .dellink = vlan_dellink, 224 .dellink = unregister_vlan_dev,
230 .get_size = vlan_get_size, 225 .get_size = vlan_get_size,
231 .fill_info = vlan_fill_info, 226 .fill_info = vlan_fill_info,
232}; 227};