mac80211: add beacon configuration via cfg80211
Johannes Berg [Wed, 19 Dec 2007 01:03:33 +0000 (02:03 +0100)]
This patch implements the cfg80211 hooks for configuring beaconing
on an access point interface in mac80211. While doing so, it fixes
a number of races that could badly crash the machine when the
beacon is changed while being requested by the driver.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

net/mac80211/cfg.c
net/mac80211/debugfs_netdev.c
net/mac80211/ieee80211.c
net/mac80211/ieee80211_i.h
net/mac80211/ieee80211_iface.c
net/mac80211/tx.c

index d02d9ef..5a4c6ed 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/nl80211.h>
 #include <linux/rtnetlink.h>
 #include <net/net_namespace.h>
+#include <linux/rcupdate.h>
 #include <net/cfg80211.h>
 #include "ieee80211_i.h"
 #include "cfg.h"
@@ -294,6 +295,158 @@ static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev,
        return 0;
 }
 
+/*
+ * This handles both adding a beacon and setting new beacon info
+ */
+static int ieee80211_config_beacon(struct ieee80211_sub_if_data *sdata,
+                                  struct beacon_parameters *params)
+{
+       struct beacon_data *new, *old;
+       int new_head_len, new_tail_len;
+       int size;
+       int err = -EINVAL;
+
+       old = sdata->u.ap.beacon;
+
+       /* head must not be zero-length */
+       if (params->head && !params->head_len)
+               return -EINVAL;
+
+       /*
+        * This is a kludge. beacon interval should really be part
+        * of the beacon information.
+        */
+       if (params->interval) {
+               sdata->local->hw.conf.beacon_int = params->interval;
+               if (ieee80211_hw_config(sdata->local))
+                       return -EINVAL;
+               /*
+                * We updated some parameter so if below bails out
+                * it's not an error.
+                */
+               err = 0;
+       }
+
+       /* Need to have a beacon head if we don't have one yet */
+       if (!params->head && !old)
+               return err;
+
+       /* sorry, no way to start beaconing without dtim period */
+       if (!params->dtim_period && !old)
+               return err;
+
+       /* new or old head? */
+       if (params->head)
+               new_head_len = params->head_len;
+       else
+               new_head_len = old->head_len;
+
+       /* new or old tail? */
+       if (params->tail || !old)
+               /* params->tail_len will be zero for !params->tail */
+               new_tail_len = params->tail_len;
+       else
+               new_tail_len = old->tail_len;
+
+       size = sizeof(*new) + new_head_len + new_tail_len;
+
+       new = kzalloc(size, GFP_KERNEL);
+       if (!new)
+               return -ENOMEM;
+
+       /* start filling the new info now */
+
+       /* new or old dtim period? */
+       if (params->dtim_period)
+               new->dtim_period = params->dtim_period;
+       else
+               new->dtim_period = old->dtim_period;
+
+       /*
+        * pointers go into the block we allocated,
+        * memory is | beacon_data | head | tail |
+        */
+       new->head = ((u8 *) new) + sizeof(*new);
+       new->tail = new->head + new_head_len;
+       new->head_len = new_head_len;
+       new->tail_len = new_tail_len;
+
+       /* copy in head */
+       if (params->head)
+               memcpy(new->head, params->head, new_head_len);
+       else
+               memcpy(new->head, old->head, new_head_len);
+
+       /* copy in optional tail */
+       if (params->tail)
+               memcpy(new->tail, params->tail, new_tail_len);
+       else
+               if (old)
+                       memcpy(new->tail, old->tail, new_tail_len);
+
+       rcu_assign_pointer(sdata->u.ap.beacon, new);
+
+       synchronize_rcu();
+
+       kfree(old);
+
+       return ieee80211_if_config_beacon(sdata->dev);
+}
+
+static int ieee80211_add_beacon(struct wiphy *wiphy, struct net_device *dev,
+                               struct beacon_parameters *params)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct beacon_data *old;
+
+       if (sdata->vif.type != IEEE80211_IF_TYPE_AP)
+               return -EINVAL;
+
+       old = sdata->u.ap.beacon;
+
+       if (old)
+               return -EALREADY;
+
+       return ieee80211_config_beacon(sdata, params);
+}
+
+static int ieee80211_set_beacon(struct wiphy *wiphy, struct net_device *dev,
+                               struct beacon_parameters *params)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct beacon_data *old;
+
+       if (sdata->vif.type != IEEE80211_IF_TYPE_AP)
+               return -EINVAL;
+
+       old = sdata->u.ap.beacon;
+
+       if (!old)
+               return -ENOENT;
+
+       return ieee80211_config_beacon(sdata, params);
+}
+
+static int ieee80211_del_beacon(struct wiphy *wiphy, struct net_device *dev)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct beacon_data *old;
+
+       if (sdata->vif.type != IEEE80211_IF_TYPE_AP)
+               return -EINVAL;
+
+       old = sdata->u.ap.beacon;
+
+       if (!old)
+               return -ENOENT;
+
+       rcu_assign_pointer(sdata->u.ap.beacon, NULL);
+       synchronize_rcu();
+       kfree(old);
+
+       return ieee80211_if_config_beacon(dev);
+}
+
 struct cfg80211_ops mac80211_config_ops = {
        .add_virtual_intf = ieee80211_add_iface,
        .del_virtual_intf = ieee80211_del_iface,
@@ -302,5 +455,8 @@ struct cfg80211_ops mac80211_config_ops = {
        .del_key = ieee80211_del_key,
        .get_key = ieee80211_get_key,
        .set_default_key = ieee80211_config_default_key,
+       .add_beacon = ieee80211_add_beacon,
+       .set_beacon = ieee80211_set_beacon,
+       .del_beacon = ieee80211_del_beacon,
        .get_station = ieee80211_get_station,
 };
index d2d3c07..3500fe0 100644 (file)
@@ -124,7 +124,6 @@ __IEEE80211_IF_FILE(flags);
 
 /* AP attributes */
 IEEE80211_IF_FILE(num_sta_ps, u.ap.num_sta_ps, ATOMIC);
-IEEE80211_IF_FILE(dtim_period, u.ap.dtim_period, DEC);
 IEEE80211_IF_FILE(dtim_count, u.ap.dtim_count, DEC);
 IEEE80211_IF_FILE(num_beacons, u.ap.num_beacons, DEC);
 IEEE80211_IF_FILE(force_unicast_rateidx, u.ap.force_unicast_rateidx, DEC);
@@ -138,26 +137,6 @@ static ssize_t ieee80211_if_fmt_num_buffered_multicast(
 }
 __IEEE80211_IF_FILE(num_buffered_multicast);
 
-static ssize_t ieee80211_if_fmt_beacon_head_len(
-       const struct ieee80211_sub_if_data *sdata, char *buf, int buflen)
-{
-       if (sdata->u.ap.beacon_head)
-               return scnprintf(buf, buflen, "%d\n",
-                                sdata->u.ap.beacon_head_len);
-       return scnprintf(buf, buflen, "\n");
-}
-__IEEE80211_IF_FILE(beacon_head_len);
-
-static ssize_t ieee80211_if_fmt_beacon_tail_len(
-       const struct ieee80211_sub_if_data *sdata, char *buf, int buflen)
-{
-       if (sdata->u.ap.beacon_tail)
-               return scnprintf(buf, buflen, "%d\n",
-                                sdata->u.ap.beacon_tail_len);
-       return scnprintf(buf, buflen, "\n");
-}
-__IEEE80211_IF_FILE(beacon_tail_len);
-
 /* WDS attributes */
 IEEE80211_IF_FILE(peer, u.wds.remote_addr, MAC);
 
@@ -192,14 +171,11 @@ static void add_ap_files(struct ieee80211_sub_if_data *sdata)
        DEBUGFS_ADD(drop_unencrypted, ap);
        DEBUGFS_ADD(ieee802_1x_pac, ap);
        DEBUGFS_ADD(num_sta_ps, ap);
-       DEBUGFS_ADD(dtim_period, ap);
        DEBUGFS_ADD(dtim_count, ap);
        DEBUGFS_ADD(num_beacons, ap);
        DEBUGFS_ADD(force_unicast_rateidx, ap);
        DEBUGFS_ADD(max_ratectrl_rateidx, ap);
        DEBUGFS_ADD(num_buffered_multicast, ap);
-       DEBUGFS_ADD(beacon_head_len, ap);
-       DEBUGFS_ADD(beacon_tail_len, ap);
 }
 
 static void add_wds_files(struct ieee80211_sub_if_data *sdata)
@@ -281,14 +257,11 @@ static void del_ap_files(struct ieee80211_sub_if_data *sdata)
        DEBUGFS_DEL(drop_unencrypted, ap);
        DEBUGFS_DEL(ieee802_1x_pac, ap);
        DEBUGFS_DEL(num_sta_ps, ap);
-       DEBUGFS_DEL(dtim_period, ap);
        DEBUGFS_DEL(dtim_count, ap);
        DEBUGFS_DEL(num_beacons, ap);
        DEBUGFS_DEL(force_unicast_rateidx, ap);
        DEBUGFS_DEL(max_ratectrl_rateidx, ap);
        DEBUGFS_DEL(num_buffered_multicast, ap);
-       DEBUGFS_DEL(beacon_head_len, ap);
-       DEBUGFS_DEL(beacon_tail_len, ap);
 }
 
 static void del_wds_files(struct ieee80211_sub_if_data *sdata)
index c998170..859682e 100644 (file)
@@ -321,10 +321,17 @@ static int ieee80211_stop(struct net_device *dev)
 
        dev_mc_unsync(local->mdev, dev);
 
-       /* down all dependent devices, that is VLANs */
+       /* APs need special treatment */
        if (sdata->vif.type == IEEE80211_IF_TYPE_AP) {
                struct ieee80211_sub_if_data *vlan, *tmp;
+               struct beacon_data *old_beacon = sdata->u.ap.beacon;
 
+               /* remove beacon */
+               rcu_assign_pointer(sdata->u.ap.beacon, NULL);
+               synchronize_rcu();
+               kfree(old_beacon);
+
+               /* down all dependent devices, that is VLANs */
                list_for_each_entry_safe(vlan, tmp, &sdata->u.ap.vlans,
                                         u.vlan.list)
                        dev_close(vlan->dev);
index 91edaad..08a6c0c 100644 (file)
@@ -190,9 +190,14 @@ typedef ieee80211_txrx_result (*ieee80211_tx_handler)
 typedef ieee80211_txrx_result (*ieee80211_rx_handler)
 (struct ieee80211_txrx_data *rx);
 
+struct beacon_data {
+       u8 *head, *tail;
+       int head_len, tail_len;
+       int dtim_period;
+};
+
 struct ieee80211_if_ap {
-       u8 *beacon_head, *beacon_tail;
-       int beacon_head_len, beacon_tail_len;
+       struct beacon_data *beacon;
 
        struct list_head vlans;
 
@@ -205,7 +210,7 @@ struct ieee80211_if_ap {
        u8 tim[sizeof(unsigned long) * BITS_TO_LONGS(IEEE80211_MAX_AID + 1)];
        atomic_t num_sta_ps; /* number of stations in PS mode */
        struct sk_buff_head ps_bc_buf;
-       int dtim_period, dtim_count;
+       int dtim_count;
        int force_unicast_rateidx; /* forced TX rateidx for unicast frames */
        int max_ratectrl_rateidx; /* max TX rateidx for rate control */
        int num_beacons; /* number of TXed beacon frames for this BSS */
@@ -360,14 +365,11 @@ struct ieee80211_sub_if_data {
                        struct dentry *drop_unencrypted;
                        struct dentry *ieee802_1x_pac;
                        struct dentry *num_sta_ps;
-                       struct dentry *dtim_period;
                        struct dentry *dtim_count;
                        struct dentry *num_beacons;
                        struct dentry *force_unicast_rateidx;
                        struct dentry *max_ratectrl_rateidx;
                        struct dentry *num_buffered_multicast;
-                       struct dentry *beacon_head_len;
-                       struct dentry *beacon_tail_len;
                } ap;
                struct {
                        struct dentry *channel_use;
index be96aa8..92f1eb2 100644 (file)
@@ -126,7 +126,6 @@ void ieee80211_if_set_type(struct net_device *dev, int type)
                sdata->u.vlan.ap = NULL;
                break;
        case IEEE80211_IF_TYPE_AP:
-               sdata->u.ap.dtim_period = 2;
                sdata->u.ap.force_unicast_rateidx = -1;
                sdata->u.ap.max_ratectrl_rateidx = -1;
                skb_queue_head_init(&sdata->u.ap.ps_bc_buf);
@@ -207,8 +206,7 @@ void ieee80211_if_reinit(struct net_device *dev)
                        }
                }
 
-               kfree(sdata->u.ap.beacon_head);
-               kfree(sdata->u.ap.beacon_tail);
+               kfree(sdata->u.ap.beacon);
 
                while ((skb = skb_dequeue(&sdata->u.ap.ps_bc_buf))) {
                        local->total_ps_buffered--;
index 99590e4..51c0f00 100644 (file)
@@ -1628,7 +1628,8 @@ void ieee80211_tx_pending(unsigned long data)
 
 static void ieee80211_beacon_add_tim(struct ieee80211_local *local,
                                     struct ieee80211_if_ap *bss,
-                                    struct sk_buff *skb)
+                                    struct sk_buff *skb,
+                                    struct beacon_data *beacon)
 {
        u8 *pos, *tim;
        int aid0 = 0;
@@ -1644,7 +1645,7 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local,
                                          IEEE80211_MAX_AID+1);
 
        if (bss->dtim_count == 0)
-               bss->dtim_count = bss->dtim_period - 1;
+               bss->dtim_count = beacon->dtim_period - 1;
        else
                bss->dtim_count--;
 
@@ -1652,7 +1653,7 @@ static void ieee80211_beacon_add_tim(struct ieee80211_local *local,
        *pos++ = WLAN_EID_TIM;
        *pos++ = 4;
        *pos++ = bss->dtim_count;
-       *pos++ = bss->dtim_period;
+       *pos++ = beacon->dtim_period;
 
        if (bss->dtim_count == 0 && !skb_queue_empty(&bss->ps_bc_buf))
                aid0 = 1;
@@ -1700,44 +1701,43 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
        struct ieee80211_sub_if_data *sdata = NULL;
        struct ieee80211_if_ap *ap = NULL;
        struct rate_selection rsel;
-       u8 *b_head, *b_tail;
-       int bh_len, bt_len;
+       struct beacon_data *beacon;
+
+       rcu_read_lock();
 
        sdata = vif_to_sdata(vif);
        bdev = sdata->dev;
        ap = &sdata->u.ap;
 
-       if (!ap || sdata->vif.type != IEEE80211_IF_TYPE_AP ||
-           !ap->beacon_head) {
+       beacon = rcu_dereference(ap->beacon);
+
+       if (!ap || sdata->vif.type != IEEE80211_IF_TYPE_AP || !beacon) {
 #ifdef CONFIG_MAC80211_VERBOSE_DEBUG
                if (net_ratelimit())
                        printk(KERN_DEBUG "no beacon data avail for %s\n",
                               bdev->name);
 #endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
-               return NULL;
+               skb = NULL;
+               goto out;
        }
 
-       /* Assume we are generating the normal beacon locally */
-       b_head = ap->beacon_head;
-       b_tail = ap->beacon_tail;
-       bh_len = ap->beacon_head_len;
-       bt_len = ap->beacon_tail_len;
-
-       skb = dev_alloc_skb(local->tx_headroom +
-               bh_len + bt_len + 256 /* maximum TIM len */);
+       /* headroom, head length, tail length and maximum TIM length */
+       skb = dev_alloc_skb(local->tx_headroom + beacon->head_len +
+                           beacon->tail_len + 256);
        if (!skb)
-               return NULL;
+               goto out;
 
        skb_reserve(skb, local->tx_headroom);
-       memcpy(skb_put(skb, bh_len), b_head, bh_len);
+       memcpy(skb_put(skb, beacon->head_len), beacon->head,
+              beacon->head_len);
 
        ieee80211_include_sequence(sdata, (struct ieee80211_hdr *)skb->data);
 
-       ieee80211_beacon_add_tim(local, ap, skb);
+       ieee80211_beacon_add_tim(local, ap, skb, beacon);
 
-       if (b_tail) {
-               memcpy(skb_put(skb, bt_len), b_tail, bt_len);
-       }
+       if (beacon->tail)
+               memcpy(skb_put(skb, beacon->tail_len), beacon->tail,
+                      beacon->tail_len);
 
        if (control) {
                rate_control_get_rate(local->mdev, local->oper_hw_mode, skb,
@@ -1749,7 +1749,8 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
                                       wiphy_name(local->hw.wiphy));
                        }
                        dev_kfree_skb(skb);
-                       return NULL;
+                       skb = NULL;
+                       goto out;
                }
 
                control->tx_rate =
@@ -1764,6 +1765,9 @@ struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
        }
 
        ap->num_beacons++;
+
+ out:
+       rcu_read_unlock();
        return skb;
 }
 EXPORT_SYMBOL(ieee80211_beacon_get);
@@ -1815,13 +1819,24 @@ ieee80211_get_buffered_bc(struct ieee80211_hw *hw,
        struct net_device *bdev;
        struct ieee80211_sub_if_data *sdata;
        struct ieee80211_if_ap *bss = NULL;
+       struct beacon_data *beacon;
 
        sdata = vif_to_sdata(vif);
        bdev = sdata->dev;
 
-       if (!bss || sdata->vif.type != IEEE80211_IF_TYPE_AP ||
-           !bss->beacon_head)
+
+       if (!bss)
+               return NULL;
+
+       rcu_read_lock();
+       beacon = rcu_dereference(bss->beacon);
+
+       if (sdata->vif.type != IEEE80211_IF_TYPE_AP || !beacon ||
+           !beacon->head) {
+               rcu_read_unlock();
                return NULL;
+       }
+       rcu_read_unlock();
 
        if (bss->dtim_count != 0)
                return NULL; /* send buffered bc/mc only after DTIM beacon */