aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorJohannes Berg <johannes@sipsolutions.net>2009-08-21 08:51:05 -0400
committerJohn W. Linville <linville@tuxdriver.com>2009-08-28 14:40:31 -0400
commit3d54d25515838543e56889aa7e48f40d00719368 (patch)
treeac8e7ab50b5fa9e9be64885f86c99a0b6c71892c /net
parentf7969969f416e593bcc7dc24abf3f9fd6c27136d (diff)
cfg80211: clean up properly on interface type change
When the interface type changes while connected, and the driver does not require the interface to be down for a type change, it is currently possible to get very strange results unless the driver takes special care, which it shouldn't have to. To fix this, take care to disconnect/leave IBSS when changing the interface type -- even if the driver may fail the call. Also process all events that may be pending to avoid running into a situation where an event is reported but only processed after the type has already changed, which would lead to missing events and warnings. A side effect of this is that you will have disconnected or left the IBSS even if the mode change ultimately fails, but since the intention was to change it and thus leave or disconnect, this is not a problem. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net')
-rw-r--r--net/wireless/core.c54
-rw-r--r--net/wireless/core.h4
-rw-r--r--net/wireless/nl80211.c16
-rw-r--r--net/wireless/util.c108
-rw-r--r--net/wireless/wext-compat.c16
5 files changed, 117 insertions, 81 deletions
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 9b157caa74fd..45b2be3274db 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -294,69 +294,17 @@ static void cfg80211_rfkill_sync_work(struct work_struct *work)
294 cfg80211_rfkill_set_block(rdev, rfkill_blocked(rdev->rfkill)); 294 cfg80211_rfkill_set_block(rdev, rfkill_blocked(rdev->rfkill));
295} 295}
296 296
297static void cfg80211_process_events(struct wireless_dev *wdev)
298{
299 struct cfg80211_event *ev;
300 unsigned long flags;
301
302 spin_lock_irqsave(&wdev->event_lock, flags);
303 while (!list_empty(&wdev->event_list)) {
304 ev = list_first_entry(&wdev->event_list,
305 struct cfg80211_event, list);
306 list_del(&ev->list);
307 spin_unlock_irqrestore(&wdev->event_lock, flags);
308
309 wdev_lock(wdev);
310 switch (ev->type) {
311 case EVENT_CONNECT_RESULT:
312 __cfg80211_connect_result(
313 wdev->netdev, is_zero_ether_addr(ev->cr.bssid) ?
314 NULL : ev->cr.bssid,
315 ev->cr.req_ie, ev->cr.req_ie_len,
316 ev->cr.resp_ie, ev->cr.resp_ie_len,
317 ev->cr.status,
318 ev->cr.status == WLAN_STATUS_SUCCESS,
319 NULL);
320 break;
321 case EVENT_ROAMED:
322 __cfg80211_roamed(wdev, ev->rm.bssid,
323 ev->rm.req_ie, ev->rm.req_ie_len,
324 ev->rm.resp_ie, ev->rm.resp_ie_len);
325 break;
326 case EVENT_DISCONNECTED:
327 __cfg80211_disconnected(wdev->netdev,
328 ev->dc.ie, ev->dc.ie_len,
329 ev->dc.reason, true);
330 break;
331 case EVENT_IBSS_JOINED:
332 __cfg80211_ibss_joined(wdev->netdev, ev->ij.bssid);
333 break;
334 }
335 wdev_unlock(wdev);
336
337 kfree(ev);
338
339 spin_lock_irqsave(&wdev->event_lock, flags);
340 }
341 spin_unlock_irqrestore(&wdev->event_lock, flags);
342}
343
344static void cfg80211_event_work(struct work_struct *work) 297static void cfg80211_event_work(struct work_struct *work)
345{ 298{
346 struct cfg80211_registered_device *rdev; 299 struct cfg80211_registered_device *rdev;
347 struct wireless_dev *wdev;
348 300
349 rdev = container_of(work, struct cfg80211_registered_device, 301 rdev = container_of(work, struct cfg80211_registered_device,
350 event_work); 302 event_work);
351 303
352 rtnl_lock(); 304 rtnl_lock();
353 cfg80211_lock_rdev(rdev); 305 cfg80211_lock_rdev(rdev);
354 mutex_lock(&rdev->devlist_mtx);
355 306
356 list_for_each_entry(wdev, &rdev->netdev_list, list) 307 cfg80211_process_rdev_events(rdev);
357 cfg80211_process_events(wdev);
358
359 mutex_unlock(&rdev->devlist_mtx);
360 cfg80211_unlock_rdev(rdev); 308 cfg80211_unlock_rdev(rdev);
361 rtnl_unlock(); 309 rtnl_unlock();
362} 310}
diff --git a/net/wireless/core.h b/net/wireless/core.h
index d262d42cbd5e..2a33d8bc886b 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -372,6 +372,10 @@ void cfg80211_sme_disassoc(struct net_device *dev, int idx);
372void __cfg80211_scan_done(struct work_struct *wk); 372void __cfg80211_scan_done(struct work_struct *wk);
373void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak); 373void ___cfg80211_scan_done(struct cfg80211_registered_device *rdev, bool leak);
374void cfg80211_upload_connect_keys(struct wireless_dev *wdev); 374void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
375int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
376 struct net_device *dev, enum nl80211_iftype ntype,
377 u32 *flags, struct vif_params *params);
378void cfg80211_process_rdev_events(struct cfg80211_registered_device *rdev);
375 379
376struct ieee80211_channel * 380struct ieee80211_channel *
377rdev_fixed_channel(struct cfg80211_registered_device *rdev, 381rdev_fixed_channel(struct cfg80211_registered_device *rdev,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index a8aaadeb6773..71bfc044a939 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -977,12 +977,6 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
977 } 977 }
978 } 978 }
979 979
980 if (!rdev->ops->change_virtual_intf ||
981 !(rdev->wiphy.interface_modes & (1 << ntype))) {
982 err = -EOPNOTSUPP;
983 goto unlock;
984 }
985
986 if (info->attrs[NL80211_ATTR_MESH_ID]) { 980 if (info->attrs[NL80211_ATTR_MESH_ID]) {
987 if (ntype != NL80211_IFTYPE_MESH_POINT) { 981 if (ntype != NL80211_IFTYPE_MESH_POINT) {
988 err = -EINVAL; 982 err = -EINVAL;
@@ -1008,18 +1002,10 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
1008 } 1002 }
1009 1003
1010 if (change) 1004 if (change)
1011 err = rdev->ops->change_virtual_intf(&rdev->wiphy, dev, 1005 err = cfg80211_change_iface(rdev, dev, ntype, flags, &params);
1012 ntype, flags, &params);
1013 else 1006 else
1014 err = 0; 1007 err = 0;
1015 1008
1016 WARN_ON(!err && dev->ieee80211_ptr->iftype != ntype);
1017
1018 if (!err && (ntype != otype)) {
1019 if (otype == NL80211_IFTYPE_ADHOC)
1020 cfg80211_clear_ibss(dev, false);
1021 }
1022
1023 unlock: 1009 unlock:
1024 dev_put(dev); 1010 dev_put(dev);
1025 cfg80211_unlock_rdev(rdev); 1011 cfg80211_unlock_rdev(rdev);
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 693275a16a26..3fc2df86278f 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -574,3 +574,111 @@ void cfg80211_upload_connect_keys(struct wireless_dev *wdev)
574 kfree(wdev->connect_keys); 574 kfree(wdev->connect_keys);
575 wdev->connect_keys = NULL; 575 wdev->connect_keys = NULL;
576} 576}
577
578static void cfg80211_process_wdev_events(struct wireless_dev *wdev)
579{
580 struct cfg80211_event *ev;
581 unsigned long flags;
582 const u8 *bssid = NULL;
583
584 spin_lock_irqsave(&wdev->event_lock, flags);
585 while (!list_empty(&wdev->event_list)) {
586 ev = list_first_entry(&wdev->event_list,
587 struct cfg80211_event, list);
588 list_del(&ev->list);
589 spin_unlock_irqrestore(&wdev->event_lock, flags);
590
591 wdev_lock(wdev);
592 switch (ev->type) {
593 case EVENT_CONNECT_RESULT:
594 if (!is_zero_ether_addr(ev->cr.bssid))
595 bssid = ev->cr.bssid;
596 __cfg80211_connect_result(
597 wdev->netdev, bssid,
598 ev->cr.req_ie, ev->cr.req_ie_len,
599 ev->cr.resp_ie, ev->cr.resp_ie_len,
600 ev->cr.status,
601 ev->cr.status == WLAN_STATUS_SUCCESS,
602 NULL);
603 break;
604 case EVENT_ROAMED:
605 __cfg80211_roamed(wdev, ev->rm.bssid,
606 ev->rm.req_ie, ev->rm.req_ie_len,
607 ev->rm.resp_ie, ev->rm.resp_ie_len);
608 break;
609 case EVENT_DISCONNECTED:
610 __cfg80211_disconnected(wdev->netdev,
611 ev->dc.ie, ev->dc.ie_len,
612 ev->dc.reason, true);
613 break;
614 case EVENT_IBSS_JOINED:
615 __cfg80211_ibss_joined(wdev->netdev, ev->ij.bssid);
616 break;
617 }
618 wdev_unlock(wdev);
619
620 kfree(ev);
621
622 spin_lock_irqsave(&wdev->event_lock, flags);
623 }
624 spin_unlock_irqrestore(&wdev->event_lock, flags);
625}
626
627void cfg80211_process_rdev_events(struct cfg80211_registered_device *rdev)
628{
629 struct wireless_dev *wdev;
630
631 ASSERT_RTNL();
632 ASSERT_RDEV_LOCK(rdev);
633
634 mutex_lock(&rdev->devlist_mtx);
635
636 list_for_each_entry(wdev, &rdev->netdev_list, list)
637 cfg80211_process_wdev_events(wdev);
638
639 mutex_unlock(&rdev->devlist_mtx);
640}
641
642int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
643 struct net_device *dev, enum nl80211_iftype ntype,
644 u32 *flags, struct vif_params *params)
645{
646 int err;
647 enum nl80211_iftype otype = dev->ieee80211_ptr->iftype;
648
649 ASSERT_RDEV_LOCK(rdev);
650
651 /* don't support changing VLANs, you just re-create them */
652 if (otype == NL80211_IFTYPE_AP_VLAN)
653 return -EOPNOTSUPP;
654
655 if (!rdev->ops->change_virtual_intf ||
656 !(rdev->wiphy.interface_modes & (1 << ntype)))
657 return -EOPNOTSUPP;
658
659 if (ntype != otype) {
660 switch (otype) {
661 case NL80211_IFTYPE_ADHOC:
662 cfg80211_leave_ibss(rdev, dev, false);
663 break;
664 case NL80211_IFTYPE_STATION:
665 cfg80211_disconnect(rdev, dev,
666 WLAN_REASON_DEAUTH_LEAVING, true);
667 break;
668 case NL80211_IFTYPE_MESH_POINT:
669 /* mesh should be handled? */
670 break;
671 default:
672 break;
673 }
674
675 cfg80211_process_rdev_events(rdev);
676 }
677
678 err = rdev->ops->change_virtual_intf(&rdev->wiphy, dev,
679 ntype, flags, params);
680
681 WARN_ON(!err && dev->ieee80211_ptr->iftype != ntype);
682
683 return err;
684}
diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c
index c12029b1def0..429dd06a4ecc 100644
--- a/net/wireless/wext-compat.c
+++ b/net/wireless/wext-compat.c
@@ -70,18 +70,8 @@ int cfg80211_wext_siwmode(struct net_device *dev, struct iw_request_info *info,
70 enum nl80211_iftype type; 70 enum nl80211_iftype type;
71 int ret; 71 int ret;
72 72
73 if (!wdev)
74 return -EOPNOTSUPP;
75
76 rdev = wiphy_to_dev(wdev->wiphy); 73 rdev = wiphy_to_dev(wdev->wiphy);
77 74
78 if (!rdev->ops->change_virtual_intf)
79 return -EOPNOTSUPP;
80
81 /* don't support changing VLANs, you just re-create them */
82 if (wdev->iftype == NL80211_IFTYPE_AP_VLAN)
83 return -EOPNOTSUPP;
84
85 switch (*mode) { 75 switch (*mode) {
86 case IW_MODE_INFRA: 76 case IW_MODE_INFRA:
87 type = NL80211_IFTYPE_STATION; 77 type = NL80211_IFTYPE_STATION;
@@ -104,9 +94,9 @@ int cfg80211_wext_siwmode(struct net_device *dev, struct iw_request_info *info,
104 94
105 memset(&vifparams, 0, sizeof(vifparams)); 95 memset(&vifparams, 0, sizeof(vifparams));
106 96
107 ret = rdev->ops->change_virtual_intf(wdev->wiphy, dev, type, 97 cfg80211_lock_rdev(rdev);
108 NULL, &vifparams); 98 ret = cfg80211_change_iface(rdev, dev, type, NULL, &vifparams);
109 WARN_ON(!ret && wdev->iftype != type); 99 cfg80211_unlock_rdev(rdev);
110 100
111 return ret; 101 return ret;
112} 102}