From 5dfdaf58d61f06a458529430c24b1191ea4d1a27 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 19 Dec 2007 02:03:33 +0100 Subject: [PATCH] mac80211: add beacon configuration via cfg80211 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 Signed-off-by: John W. Linville --- net/mac80211/cfg.c | 156 +++++++++++++++++++++++++++++++++ net/mac80211/debugfs_netdev.c | 27 ------ net/mac80211/ieee80211.c | 9 +- net/mac80211/ieee80211_i.h | 14 +-- net/mac80211/ieee80211_iface.c | 4 +- net/mac80211/tx.c | 65 ++++++++------ 6 files changed, 213 insertions(+), 62 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index d02d9ef6b1ef..5a4c6edd9348 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #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, }; diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index d2d3c076c853..3500fe0d380b 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -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) diff --git a/net/mac80211/ieee80211.c b/net/mac80211/ieee80211.c index c9981701ef68..859682eee547 100644 --- a/net/mac80211/ieee80211.c +++ b/net/mac80211/ieee80211.c @@ -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); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 91edaad4c620..08a6c0cff690 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -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; diff --git a/net/mac80211/ieee80211_iface.c b/net/mac80211/ieee80211_iface.c index be96aa84c79a..92f1eb2da311 100644 --- a/net/mac80211/ieee80211_iface.c +++ b/net/mac80211/ieee80211_iface.c @@ -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--; diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 99590e4ce30b..51c0f00d02d1 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -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 */ -- 2.20.1