Search Linux Wireless

[PATCH] d80211: Fix concurrency issues in ieee80211_sta.c

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



d80211: Fix concurrency issues in ieee80211_sta.c

This fixes most concurrency issues in ieee80211_sta.c and partially
prevents scans from running over an association in progress and vice versa.
This is achieved by forcing all potentially racy code to run from a
workqueue.

Signed-off-by: Michael Wu <flamingice@xxxxxxxxxxxx>
---

 net/d80211/ieee80211.c       |    6 +
 net/d80211/ieee80211_i.h     |    9 ++
 net/d80211/ieee80211_iface.c |    5 +
 net/d80211/ieee80211_sta.c   |  202 ++++++++++++++++++++++++++++++------------
 4 files changed, 162 insertions(+), 60 deletions(-)

diff --git a/net/d80211/ieee80211.c b/net/d80211/ieee80211.c
index 7a92bfe..055e8a2 100644
--- a/net/d80211/ieee80211.c
+++ b/net/d80211/ieee80211.c
@@ -2179,7 +2179,9 @@ void ieee80211_if_shutdown(struct net_de
 	case IEEE80211_IF_TYPE_STA:
 	case IEEE80211_IF_TYPE_IBSS:
 		sdata->u.sta.state = IEEE80211_DISABLED;
-		cancel_delayed_work(&sdata->u.sta.work);
+		del_timer_sync(&sdata->u.sta.timer);
+		flush_scheduled_work();
+		skb_queue_purge(&sdata->u.sta.skb_queue);
 		if (!local->ops->hw_scan &&
 		    local->scan_dev == sdata->dev) {
 			local->sta_scanning = 0;
@@ -4023,7 +4025,7 @@ void ieee80211_rx_irqsafe(struct ieee802
 
 	skb->dev = local->mdev;
 	/* copy status into skb->cb for use by tasklet */
-	memcpy(skb->cb, status, sizeof(struct ieee80211_rx_status));
+	memcpy(skb->cb, status, sizeof(*status));
 	skb->pkt_type = ieee80211_rx_msg;
 	skb_queue_tail(&local->skb_queue, skb);
 	tasklet_schedule(&local->tasklet);
diff --git a/net/d80211/ieee80211_i.h b/net/d80211/ieee80211_i.h
index bb8fc83..9307882 100644
--- a/net/d80211/ieee80211_i.h
+++ b/net/d80211/ieee80211_i.h
@@ -242,7 +242,8 @@ struct ieee80211_if_sta {
 		IEEE80211_ASSOCIATE, IEEE80211_ASSOCIATED,
 		IEEE80211_IBSS_SEARCH, IEEE80211_IBSS_JOINED
 	} state;
-	struct delayed_work work;
+	struct timer_list timer;
+	struct work_struct work;
 	u8 bssid[ETH_ALEN], prev_bssid[ETH_ALEN];
 	u8 ssid[IEEE80211_MAX_SSID_LEN];
 	size_t ssid_len;
@@ -267,6 +268,11 @@ struct ieee80211_if_sta {
 	unsigned int create_ibss:1;
 	unsigned int mixed_cell:1;
 	unsigned int wmm_enabled:1;
+#define IEEE80211_STA_REQ_SCAN 0
+#define IEEE80211_STA_REQ_AUTH 1
+#define IEEE80211_STA_REQ_RUN  2
+	unsigned long request;
+	struct sk_buff_head skb_queue;	
 
 	int key_mgmt;
 	unsigned long last_probe;
@@ -660,6 +666,7 @@ int ieee80211_set_compression(struct iee
 			      struct net_device *dev, struct sta_info *sta);
 int ieee80211_init_client(struct net_device *dev);
 /* ieee80211_sta.c */
+void ieee80211_sta_timer(unsigned long data);
 void ieee80211_sta_work(struct work_struct *work);
 void ieee80211_sta_scan_work(struct work_struct *work);
 void ieee80211_sta_rx_mgmt(struct net_device *dev, struct sk_buff *skb,
diff --git a/net/d80211/ieee80211_iface.c b/net/d80211/ieee80211_iface.c
index 342ad2a..939e289 100644
--- a/net/d80211/ieee80211_iface.c
+++ b/net/d80211/ieee80211_iface.c
@@ -185,7 +185,10 @@ void ieee80211_if_set_type(struct net_de
 		struct ieee80211_if_sta *ifsta;
 
 		ifsta = &sdata->u.sta;
-		INIT_DELAYED_WORK(&ifsta->work, ieee80211_sta_work);
+		INIT_WORK(&ifsta->work, ieee80211_sta_work);
+		setup_timer(&ifsta->timer, ieee80211_sta_timer,
+			    (unsigned long) ifsta);
+		skb_queue_head_init(&ifsta->skb_queue);
 
 		ifsta->capab = WLAN_CAPABILITY_ESS;
 		ifsta->auth_algs = IEEE80211_AUTH_ALG_OPEN |
diff --git a/net/d80211/ieee80211_sta.c b/net/d80211/ieee80211_sta.c
index 0e45a65..7b6a76b 100644
--- a/net/d80211/ieee80211_sta.c
+++ b/net/d80211/ieee80211_sta.c
@@ -64,6 +64,10 @@ static void ieee80211_rx_bss_put(struct
 static int ieee80211_sta_find_ibss(struct net_device *dev,
 				   struct ieee80211_if_sta *ifsta);
 static int ieee80211_sta_wep_configured(struct net_device *dev);
+static int ieee80211_sta_start_scan(struct net_device *dev,
+				    u8 *ssid, size_t ssid_len);
+static void ieee80211_sta_reset_auth(struct net_device *dev,
+				     struct ieee80211_if_sta *ifsta);
 
 
 /* Parsed Information Elements */
@@ -466,7 +470,7 @@ static void ieee80211_authenticate(struc
 
 	ieee80211_send_auth(dev, ifsta, 1, NULL, 0, 0);
 
-	schedule_delayed_work(&ifsta->work, IEEE80211_AUTH_TIMEOUT);
+	mod_timer(&ifsta->timer, jiffies + IEEE80211_AUTH_TIMEOUT);
 }
 
 
@@ -694,7 +698,7 @@ static void ieee80211_associate(struct n
 
 	ieee80211_send_assoc(dev, ifsta);
 
-	schedule_delayed_work(&ifsta->work, IEEE80211_ASSOC_TIMEOUT);
+	mod_timer(&ifsta->timer, jiffies + IEEE80211_ASSOC_TIMEOUT);
 }
 
 
@@ -752,10 +756,10 @@ static void ieee80211_associated(struct
 		memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN);
 		wrqu.ap_addr.sa_family = ARPHRD_ETHER;
 		wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
-		schedule_delayed_work(&ifsta->work,
+		mod_timer(&ifsta->timer, jiffies + 
 				      IEEE80211_MONITORING_INTERVAL + 30 * HZ);
 	} else {
-		schedule_delayed_work(&ifsta->work,
+		mod_timer(&ifsta->timer, jiffies + 
 				      IEEE80211_MONITORING_INTERVAL);
 	}
 }
@@ -846,8 +850,7 @@ static void ieee80211_auth_completed(str
 static void ieee80211_auth_challenge(struct net_device *dev,
 				     struct ieee80211_if_sta *ifsta,
 				     struct ieee80211_mgmt *mgmt,
-				     size_t len,
-				     struct ieee80211_rx_status *rx_status)
+				     size_t len)
 {
 	u8 *pos;
 	struct ieee802_11_elems elems;
@@ -873,8 +876,7 @@ static void ieee80211_auth_challenge(str
 static void ieee80211_rx_mgmt_auth(struct net_device *dev,
 				   struct ieee80211_if_sta *ifsta,
 				   struct ieee80211_mgmt *mgmt,
-				   size_t len,
-				   struct ieee80211_rx_status *rx_status)
+				   size_t len)
 {
 	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 	u16 auth_alg, auth_transaction, status_code;
@@ -993,8 +995,7 @@ static void ieee80211_rx_mgmt_auth(struc
 		if (ifsta->auth_transaction == 4)
 			ieee80211_auth_completed(dev, ifsta);
 		else
-			ieee80211_auth_challenge(dev, ifsta, mgmt, len,
-						 rx_status);
+			ieee80211_auth_challenge(dev, ifsta, mgmt, len);
 		break;
 	}
 }
@@ -1003,8 +1004,7 @@ static void ieee80211_rx_mgmt_auth(struc
 static void ieee80211_rx_mgmt_deauth(struct net_device *dev,
 				     struct ieee80211_if_sta *ifsta,
 				     struct ieee80211_mgmt *mgmt,
-				     size_t len,
-				     struct ieee80211_rx_status *rx_status)
+				     size_t len)
 {
 	u16 reason_code;
 
@@ -1037,7 +1037,7 @@ static void ieee80211_rx_mgmt_deauth(str
 	    ifsta->state == IEEE80211_ASSOCIATE ||
 	    ifsta->state == IEEE80211_ASSOCIATED) {
 		ifsta->state = IEEE80211_AUTHENTICATE;
-		schedule_delayed_work(&ifsta->work,
+		mod_timer(&ifsta->timer, jiffies +
 				      IEEE80211_RETRY_AUTH_INTERVAL);
 	}
 
@@ -1049,8 +1049,7 @@ static void ieee80211_rx_mgmt_deauth(str
 static void ieee80211_rx_mgmt_disassoc(struct net_device *dev,
 				       struct ieee80211_if_sta *ifsta,
 				       struct ieee80211_mgmt *mgmt,
-				       size_t len,
-				       struct ieee80211_rx_status *rx_status)
+				       size_t len)
 {
 	u16 reason_code;
 
@@ -1080,7 +1079,7 @@ static void ieee80211_rx_mgmt_disassoc(s
 
 	if (ifsta->state == IEEE80211_ASSOCIATED) {
 		ifsta->state = IEEE80211_ASSOCIATE;
-		schedule_delayed_work(&ifsta->work,
+		mod_timer(&ifsta->timer, jiffies +
 				      IEEE80211_RETRY_AUTH_INTERVAL);
 	}
 
@@ -1092,7 +1091,6 @@ static void ieee80211_rx_mgmt_assoc_resp
 					 struct ieee80211_if_sta *ifsta,
 					 struct ieee80211_mgmt *mgmt,
 					 size_t len,
-					 struct ieee80211_rx_status *rx_status,
 					 int reassoc)
 {
 	struct ieee80211_local *local = dev->ieee80211_ptr;
@@ -1735,6 +1733,47 @@ void ieee80211_sta_rx_mgmt(struct net_de
 
 	switch (fc & IEEE80211_FCTL_STYPE) {
 	case IEEE80211_STYPE_PROBE_REQ:
+	case IEEE80211_STYPE_PROBE_RESP:
+	case IEEE80211_STYPE_BEACON:
+	case IEEE80211_STYPE_AUTH:
+	case IEEE80211_STYPE_ASSOC_RESP:
+	case IEEE80211_STYPE_REASSOC_RESP:
+	case IEEE80211_STYPE_DEAUTH:
+	case IEEE80211_STYPE_DISASSOC:
+		memcpy(skb->cb, rx_status, sizeof(*rx_status));
+		skb_queue_tail(&ifsta->skb_queue, skb);
+		schedule_work(&ifsta->work);
+		return;
+	default:
+		printk(KERN_DEBUG "%s: received unknown management frame - "
+		       "stype=%d\n", dev->name,
+		       (fc & IEEE80211_FCTL_STYPE) >> 4);
+		break;
+	}
+
+ fail:
+	dev_kfree_skb(skb);
+}
+
+
+static void ieee80211_sta_rx_queued_mgmt(struct net_device *dev,
+					 struct sk_buff *skb)
+{
+	struct ieee80211_rx_status *rx_status;
+	struct ieee80211_sub_if_data *sdata;
+	struct ieee80211_if_sta *ifsta;
+	struct ieee80211_mgmt *mgmt;
+	u16 fc;
+
+	sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+	ifsta = &sdata->u.sta;
+
+	rx_status = (struct ieee80211_rx_status *) skb->cb;
+	mgmt = (struct ieee80211_mgmt *) skb->data;
+	fc = le16_to_cpu(mgmt->frame_control);
+
+	switch (fc & IEEE80211_FCTL_STYPE) {
+	case IEEE80211_STYPE_PROBE_REQ:
 		ieee80211_rx_mgmt_probe_req(dev, ifsta, mgmt, skb->len,
 					    rx_status);
 		break;
@@ -1745,33 +1784,21 @@ void ieee80211_sta_rx_mgmt(struct net_de
 		ieee80211_rx_mgmt_beacon(dev, mgmt, skb->len, rx_status);
 		break;
 	case IEEE80211_STYPE_AUTH:
-		ieee80211_rx_mgmt_auth(dev, ifsta, mgmt, skb->len, rx_status);
+		ieee80211_rx_mgmt_auth(dev, ifsta, mgmt, skb->len);
 		break;
 	case IEEE80211_STYPE_ASSOC_RESP:
-		ieee80211_rx_mgmt_assoc_resp(dev, ifsta, mgmt, skb->len,
-					     rx_status, 0);
+		ieee80211_rx_mgmt_assoc_resp(dev, ifsta, mgmt, skb->len, 0);
 		break;
 	case IEEE80211_STYPE_REASSOC_RESP:
-		ieee80211_rx_mgmt_assoc_resp(dev, ifsta, mgmt, skb->len,
-					     rx_status, 1);
+		ieee80211_rx_mgmt_assoc_resp(dev, ifsta, mgmt, skb->len, 1);
 		break;
 	case IEEE80211_STYPE_DEAUTH:
-		ieee80211_rx_mgmt_deauth(dev, ifsta, mgmt, skb->len,
-					 rx_status);
+		ieee80211_rx_mgmt_deauth(dev, ifsta, mgmt, skb->len);
 		break;
 	case IEEE80211_STYPE_DISASSOC:
-		ieee80211_rx_mgmt_disassoc(dev, ifsta, mgmt, skb->len,
-					   rx_status);
-		break;
-	default:
-		printk(KERN_DEBUG "%s: received unknown management frame - "
-		       "stype=%d\n", dev->name,
-		       (fc & IEEE80211_FCTL_STYPE) >> 4);
+		ieee80211_rx_mgmt_disassoc(dev, ifsta, mgmt, skb->len);
 		break;
 	}
-
- fail:
-	dev_kfree_skb(skb);
 }
 
 
@@ -1844,7 +1871,7 @@ static void ieee80211_sta_expire(struct
 static void ieee80211_sta_merge_ibss(struct net_device *dev,
 				     struct ieee80211_if_sta *ifsta)
 {
-	schedule_delayed_work(&ifsta->work, IEEE80211_IBSS_MERGE_INTERVAL);
+	mod_timer(&ifsta->timer, jiffies + IEEE80211_IBSS_MERGE_INTERVAL);
 
 	ieee80211_sta_expire(dev);
 	if (ieee80211_sta_active_ibss(dev))
@@ -1856,16 +1883,32 @@ static void ieee80211_sta_merge_ibss(str
 }
 
 
+void ieee80211_sta_timer(unsigned long data)
+{
+	struct ieee80211_if_sta *ifsta = (struct ieee80211_if_sta *) data;
+	set_bit(IEEE80211_STA_REQ_RUN, &ifsta->request);
+	schedule_work(&ifsta->work);
+}
+
+
 void ieee80211_sta_work(struct work_struct *work)
 {
 	struct ieee80211_sub_if_data *sdata =
-		container_of(work, struct ieee80211_sub_if_data, u.sta.work.work);
+		container_of(work, struct ieee80211_sub_if_data, u.sta.work);
 	struct net_device *dev = sdata->dev;
+	struct ieee80211_local *local = dev->ieee80211_ptr;
 	struct ieee80211_if_sta *ifsta;
+	struct sk_buff *skb;
 
 	if (!netif_running(dev))
 		return;
 
+	/* TODO: scan_dev check should be removed once scan_completed wakes
+	 * every STA interface */
+	if (local->sta_scanning &&
+	    local->scan_dev == dev)
+		return;
+
 	sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 	if (sdata->type != IEEE80211_IF_TYPE_STA &&
 	    sdata->type != IEEE80211_IF_TYPE_IBSS) {
@@ -1875,6 +1918,22 @@ void ieee80211_sta_work(struct work_stru
 	}
 	ifsta = &sdata->u.sta;
 
+	while ((skb = skb_dequeue(&ifsta->skb_queue)))
+		ieee80211_sta_rx_queued_mgmt(dev, skb);
+
+	if (ifsta->state != IEEE80211_AUTHENTICATE &&
+	    ifsta->state != IEEE80211_ASSOCIATE &&
+	    test_and_clear_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request)) {
+		ieee80211_sta_start_scan(dev, NULL, 0);
+		return;
+	}
+
+	if (test_and_clear_bit(IEEE80211_STA_REQ_AUTH, &ifsta->request)) {
+		ifsta->state = IEEE80211_AUTHENTICATE;
+		ieee80211_sta_reset_auth(dev, ifsta);
+	} else if (!test_and_clear_bit(IEEE80211_STA_REQ_RUN, &ifsta->request))
+		return;
+
 	switch (ifsta->state) {
 	case IEEE80211_DISABLED:
 		break;
@@ -1909,14 +1968,10 @@ void ieee80211_sta_work(struct work_stru
 }
 
 
-static void ieee80211_sta_new_auth(struct net_device *dev,
-				   struct ieee80211_if_sta *ifsta)
+static void ieee80211_sta_reset_auth(struct net_device *dev,
+				     struct ieee80211_if_sta *ifsta)
 {
 	struct ieee80211_local *local = dev->ieee80211_ptr;
-	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
-
-	if (sdata->type != IEEE80211_IF_TYPE_STA)
-		return;
 
 	if (local->ops->reset_tsf) {
 		/* Reset own TSF to allow time synchronization work. */
@@ -1938,7 +1993,19 @@ static void ieee80211_sta_new_auth(struc
 	       ifsta->auth_alg);
 	ifsta->auth_transaction = -1;
 	ifsta->associated = ifsta->auth_tries = ifsta->assoc_tries = 0;
-	ieee80211_authenticate(dev, ifsta);
+}
+
+
+static void ieee80211_sta_new_auth(struct net_device *dev,
+				   struct ieee80211_if_sta *ifsta)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+	if (sdata->type != IEEE80211_IF_TYPE_STA)
+		return;
+
+	set_bit(IEEE80211_STA_REQ_AUTH, &ifsta->request);
+	schedule_work(&ifsta->work);
 }
 
 
@@ -2123,7 +2190,7 @@ static int ieee80211_sta_join_ibss(struc
 	}
 
 	ifsta->state = IEEE80211_IBSS_JOINED;
-	schedule_delayed_work(&ifsta->work, IEEE80211_IBSS_MERGE_INTERVAL);
+	mod_timer(&ifsta->timer, jiffies + IEEE80211_IBSS_MERGE_INTERVAL);
 
 	ieee80211_rx_bss_put(dev, bss);
 
@@ -2240,7 +2307,7 @@ static int ieee80211_sta_find_ibss(struc
 	/* Selected IBSS not found in current scan results - try to scan */
 	if (ifsta->state == IEEE80211_IBSS_JOINED &&
 	    !ieee80211_sta_active_ibss(dev)) {
-		schedule_delayed_work(&ifsta->work,
+		mod_timer(&ifsta->timer, jiffies + 
 				      IEEE80211_IBSS_MERGE_INTERVAL);
 	} else if (time_after(jiffies, local->last_scan_completed +
 			      IEEE80211_SCAN_INTERVAL)) {
@@ -2269,7 +2336,7 @@ static int ieee80211_sta_find_ibss(struc
 		}
 
 		ifsta->state = IEEE80211_IBSS_SEARCH;
-		schedule_delayed_work(&ifsta->work, interval);
+		mod_timer(&ifsta->timer, jiffies + interval);
 		return 0;
 	}
 
@@ -2432,8 +2499,9 @@ void ieee80211_scan_completed(struct iee
 	union iwreq_data wrqu;
 
 	printk(KERN_DEBUG "%s: scan completed\n", dev->name);
-	local->sta_scanning = 0;
 	local->last_scan_completed = jiffies;
+	wmb();
+	local->sta_scanning = 0;
 
 	memset(&wrqu, 0, sizeof(wrqu));
 	wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL);
@@ -2444,7 +2512,9 @@ void ieee80211_scan_completed(struct iee
 		    (!ifsta->state == IEEE80211_IBSS_JOINED &&
 		    !ieee80211_sta_active_ibss(dev)))
 			ieee80211_sta_find_ibss(dev, ifsta);
-	}
+	/* TODO: need to wake every sta interface */
+	} else if (sdata->type == IEEE80211_IF_TYPE_STA)
+		ieee80211_sta_timer((unsigned long)&sdata->u.sta);
 }
 EXPORT_SYMBOL(ieee80211_scan_completed);
 
@@ -2533,16 +2603,13 @@ void ieee80211_sta_scan_work(struct work
 		break;
 	}
 
-	if (local->sta_scanning) {
-		if (next_delay)
-			schedule_delayed_work(&local->scan_work, next_delay);
-		else
-			schedule_work(&local->scan_work.work);
-	}
+	if (local->sta_scanning)
+		schedule_delayed_work(&local->scan_work, next_delay);
 }
 
 
-int ieee80211_sta_req_scan(struct net_device *dev, u8 *ssid, size_t ssid_len)
+static int ieee80211_sta_start_scan(struct net_device *dev,
+				    u8 *ssid, size_t ssid_len)
 {
 	struct ieee80211_local *local = dev->ieee80211_ptr;
 
@@ -2603,12 +2670,35 @@ int ieee80211_sta_req_scan(struct net_de
 					 list);
 	local->scan_channel_idx = 0;
 	local->scan_dev = dev;
-	schedule_work(&local->scan_work.work);
+	schedule_delayed_work(&local->scan_work, 0);
 
 	return 0;
 }
 
 
+int ieee80211_sta_req_scan(struct net_device *dev, u8 *ssid, size_t ssid_len)
+{
+	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+	struct ieee80211_if_sta *ifsta = &sdata->u.sta;
+	struct ieee80211_local *local = dev->ieee80211_ptr;
+
+	if (sdata->type != IEEE80211_IF_TYPE_STA)
+		return ieee80211_sta_start_scan(dev, ssid, ssid_len);
+
+	if (local->sta_scanning) {
+		if (local->scan_dev == dev)
+			return 0;
+		return -EBUSY;
+	}
+
+	if (time_after(local->last_scan_completed + HZ, jiffies))
+		return -EAGAIN;
+
+	set_bit(IEEE80211_STA_REQ_SCAN, &ifsta->request);
+	schedule_work(&ifsta->work);
+	return 0;
+}
+
 static char *
 ieee80211_sta_scan_result(struct net_device *dev,
 			  struct ieee80211_sta_bss *bss,

Attachment: pgpVqQP3sFFr4.pgp
Description: PGP signature


[Index of Archives]     [Linux Host AP]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]
  Powered by Linux