aboutsummaryrefslogtreecommitdiff
path: root/net/mac80211/mesh_plink.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac80211/mesh_plink.c')
-rw-r--r--net/mac80211/mesh_plink.c237
1 files changed, 166 insertions, 71 deletions
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 4e53c4cbca9e..8cc8461b48a0 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -82,20 +82,14 @@ static inline void mesh_plink_fsm_restart(struct sta_info *sta)
}
/*
- * NOTE: This is just an alias for sta_info_alloc(), see notes
- * on it in the lifecycle management section!
+ * Allocate mesh sta entry and insert into station table
*/
static struct sta_info *mesh_plink_alloc(struct ieee80211_sub_if_data *sdata,
- u8 *hw_addr, u32 rates,
- struct ieee802_11_elems *elems)
+ u8 *hw_addr)
{
- struct ieee80211_local *local = sdata->local;
- struct ieee80211_supported_band *sband;
struct sta_info *sta;
- sband = local->hw.wiphy->bands[local->oper_channel->band];
-
- if (local->num_sta >= MESH_MAX_PLINKS)
+ if (sdata->local->num_sta >= MESH_MAX_PLINKS)
return NULL;
sta = sta_info_alloc(sdata, hw_addr, GFP_KERNEL);
@@ -108,16 +102,70 @@ static struct sta_info *mesh_plink_alloc(struct ieee80211_sub_if_data *sdata,
set_sta_flag(sta, WLAN_STA_WME);
- sta->sta.supp_rates[local->hw.conf.channel->band] = rates;
- if (elems->ht_cap_elem)
- ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
- elems->ht_cap_elem,
- &sta->sta.ht_cap);
- rate_control_rate_init(sta);
-
return sta;
}
+/** mesh_set_ht_prot_mode - set correct HT protection mode
+ *
+ * Section 9.23.3.5 of IEEE 80211s standard describes the protection rules for
+ * HT mesh STA in a MBSS. Three HT protection modes are supported for now,
+ * non-HT mixed mode, 20MHz-protection and no-protection mode. non-HT mixed
+ * mode is selected if any non-HT peers are present in our MBSS.
+ * 20MHz-protection mode is selected if all peers in our 20/40MHz MBSS support
+ * HT and atleast one HT20 peer is present. Otherwise no-protection mode is
+ * selected.
+ */
+static u32 mesh_set_ht_prot_mode(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta;
+ u32 changed = 0;
+ u16 ht_opmode;
+ bool non_ht_sta = false, ht20_sta = false;
+
+ if (local->_oper_channel_type == NL80211_CHAN_NO_HT)
+ return 0;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(sta, &local->sta_list, list) {
+ if (sdata == sta->sdata &&
+ sta->plink_state == NL80211_PLINK_ESTAB) {
+ switch (sta->ch_type) {
+ case NL80211_CHAN_NO_HT:
+ mpl_dbg("mesh_plink %pM: nonHT sta (%pM) is present",
+ sdata->vif.addr, sta->sta.addr);
+ non_ht_sta = true;
+ goto out;
+ case NL80211_CHAN_HT20:
+ mpl_dbg("mesh_plink %pM: HT20 sta (%pM) is present",
+ sdata->vif.addr, sta->sta.addr);
+ ht20_sta = true;
+ default:
+ break;
+ }
+ }
+ }
+out:
+ rcu_read_unlock();
+
+ if (non_ht_sta)
+ ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED;
+ else if (ht20_sta && local->_oper_channel_type > NL80211_CHAN_HT20)
+ ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_20MHZ;
+ else
+ ht_opmode = IEEE80211_HT_OP_MODE_PROTECTION_NONE;
+
+ if (sdata->vif.bss_conf.ht_operation_mode != ht_opmode) {
+ sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
+ sdata->u.mesh.mshcfg.ht_opmode = ht_opmode;
+ changed = BSS_CHANGED_HT;
+ mpl_dbg("mesh_plink %pM: protection mode changed to %d",
+ sdata->vif.addr, ht_opmode);
+ }
+
+ return changed;
+}
+
/**
* __mesh_plink_deactivate - deactivate mesh peer link
*
@@ -187,7 +235,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
2 + sdata->u.mesh.mesh_id_len +
2 + sizeof(struct ieee80211_meshconf_ie) +
2 + sizeof(struct ieee80211_ht_cap) +
- 2 + sizeof(struct ieee80211_ht_info) +
+ 2 + sizeof(struct ieee80211_ht_operation) +
2 + 8 + /* peering IE */
sdata->u.mesh.ie_len);
if (!skb)
@@ -212,8 +260,8 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
pos = skb_put(skb, 2);
memcpy(pos + 2, &plid, 2);
}
- if (ieee80211_add_srates_ie(&sdata->vif, skb) ||
- ieee80211_add_ext_srates_ie(&sdata->vif, skb) ||
+ if (ieee80211_add_srates_ie(&sdata->vif, skb, true) ||
+ ieee80211_add_ext_srates_ie(&sdata->vif, skb, true) ||
mesh_add_rsn_ie(skb, sdata) ||
mesh_add_meshid_ie(skb, sdata) ||
mesh_add_meshconf_ie(skb, sdata))
@@ -263,7 +311,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
if (action != WLAN_SP_MESH_PEERING_CLOSE) {
if (mesh_add_ht_cap_ie(skb, sdata) ||
- mesh_add_ht_info_ie(skb, sdata))
+ mesh_add_ht_oper_ie(skb, sdata))
return -1;
}
@@ -274,43 +322,93 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
return 0;
}
-void mesh_neighbour_update(u8 *hw_addr, u32 rates,
- struct ieee80211_sub_if_data *sdata,
- struct ieee802_11_elems *elems)
+/* mesh_peer_init - initialize new mesh peer and return resulting sta_info
+ *
+ * @sdata: local meshif
+ * @addr: peer's address
+ * @elems: IEs from beacon or mesh peering frame
+ *
+ * call under RCU
+ */
+static struct sta_info *mesh_peer_init(struct ieee80211_sub_if_data *sdata,
+ u8 *addr,
+ struct ieee802_11_elems *elems)
{
struct ieee80211_local *local = sdata->local;
+ enum ieee80211_band band = local->oper_channel->band;
+ struct ieee80211_supported_band *sband;
+ u32 rates, basic_rates = 0;
struct sta_info *sta;
+ bool insert = false;
- rcu_read_lock();
+ sband = local->hw.wiphy->bands[band];
+ rates = ieee80211_sta_get_rates(local, elems, band, &basic_rates);
- sta = sta_info_get(sdata, hw_addr);
+ sta = sta_info_get(sdata, addr);
if (!sta) {
- rcu_read_unlock();
- /* Userspace handles peer allocation when security is enabled
- * */
- if (sdata->u.mesh.security & IEEE80211_MESH_SEC_AUTHED)
- cfg80211_notify_new_peer_candidate(sdata->dev, hw_addr,
- elems->ie_start, elems->total_len,
- GFP_KERNEL);
- else
- sta = mesh_plink_alloc(sdata, hw_addr, rates, elems);
+ sta = mesh_plink_alloc(sdata, addr);
if (!sta)
- return;
- if (sta_info_insert_rcu(sta)) {
- rcu_read_unlock();
- return;
- }
+ return NULL;
+ insert = true;
}
+ spin_lock_bh(&sta->lock);
sta->last_rx = jiffies;
- sta->sta.supp_rates[local->hw.conf.channel->band] = rates;
+ sta->sta.supp_rates[band] = rates;
+ if (elems->ht_cap_elem &&
+ sdata->local->_oper_channel_type != NL80211_CHAN_NO_HT)
+ ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
+ elems->ht_cap_elem,
+ &sta->sta.ht_cap);
+ else
+ memset(&sta->sta.ht_cap, 0, sizeof(sta->sta.ht_cap));
+
+ if (elems->ht_operation) {
+ if (!(elems->ht_operation->ht_param &
+ IEEE80211_HT_PARAM_CHAN_WIDTH_ANY))
+ sta->sta.ht_cap.cap &=
+ ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ sta->ch_type =
+ ieee80211_ht_oper_to_channel_type(elems->ht_operation);
+ }
+
+ rate_control_rate_init(sta);
+ spin_unlock_bh(&sta->lock);
+
+ if (insert && sta_info_insert(sta))
+ return NULL;
+
+ return sta;
+}
+
+void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
+ u8 *hw_addr,
+ struct ieee802_11_elems *elems)
+{
+ struct sta_info *sta;
+
+ /* Userspace handles peer allocation when security is enabled */
+ if (sdata->u.mesh.security & IEEE80211_MESH_SEC_AUTHED) {
+ cfg80211_notify_new_peer_candidate(sdata->dev, hw_addr,
+ elems->ie_start,
+ elems->total_len,
+ GFP_KERNEL);
+ return;
+ }
+
+ rcu_read_lock();
+ sta = mesh_peer_init(sdata, hw_addr, elems);
+ if (!sta)
+ goto out;
+
if (mesh_peer_accepts_plinks(elems) &&
- sta->plink_state == NL80211_PLINK_LISTEN &&
- sdata->u.mesh.accepting_plinks &&
- sdata->u.mesh.mshcfg.auto_open_plinks &&
- rssi_threshold_check(sta, sdata))
+ sta->plink_state == NL80211_PLINK_LISTEN &&
+ sdata->u.mesh.accepting_plinks &&
+ sdata->u.mesh.mshcfg.auto_open_plinks &&
+ rssi_threshold_check(sta, sdata))
mesh_plink_open(sta);
+out:
rcu_read_unlock();
}
@@ -456,15 +554,15 @@ void mesh_plink_block(struct sta_info *sta)
void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_mgmt *mgmt,
size_t len, struct ieee80211_rx_status *rx_status)
{
- struct ieee80211_local *local = sdata->local;
struct ieee802_11_elems elems;
struct sta_info *sta;
enum plink_event event;
enum ieee80211_self_protected_actioncode ftype;
size_t baselen;
- bool deactivated, matches_local = true;
+ bool matches_local = true;
u8 ie_len;
u8 *baseaddr;
+ u32 changed = 0;
__le16 plid, llid, reason;
#ifdef CONFIG_MAC80211_VERBOSE_MPL_DEBUG
static const char *mplstates[] = {
@@ -560,7 +658,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
/* Now we will figure out the appropriate event... */
event = PLINK_UNDEFINED;
if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
- (!mesh_matches_local(&elems, sdata))) {
+ !mesh_matches_local(sdata, &elems)) {
matches_local = false;
switch (ftype) {
case WLAN_SP_MESH_PEERING_OPEN:
@@ -583,29 +681,13 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
return;
} else if (!sta) {
/* ftype == WLAN_SP_MESH_PEERING_OPEN */
- u32 rates;
-
- rcu_read_unlock();
-
if (!mesh_plink_free_count(sdata)) {
mpl_dbg("Mesh plink error: no more free plinks\n");
- return;
- }
-
- rates = ieee80211_sta_get_rates(local, &elems, rx_status->band);
- sta = mesh_plink_alloc(sdata, mgmt->sa, rates, &elems);
- if (!sta) {
- mpl_dbg("Mesh plink error: plink table full\n");
- return;
- }
- if (sta_info_insert_rcu(sta)) {
rcu_read_unlock();
return;
}
event = OPN_ACPT;
- spin_lock_bh(&sta->lock);
} else if (matches_local) {
- spin_lock_bh(&sta->lock);
switch (ftype) {
case WLAN_SP_MESH_PEERING_OPEN:
if (!mesh_plink_free_count(sdata) ||
@@ -642,12 +724,19 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
break;
default:
mpl_dbg("Mesh plink: unknown frame subtype\n");
- spin_unlock_bh(&sta->lock);
rcu_read_unlock();
return;
}
- } else {
- spin_lock_bh(&sta->lock);
+ }
+
+ if (event == OPN_ACPT) {
+ /* allocate sta entry if necessary and update info */
+ sta = mesh_peer_init(sdata, mgmt->sa, &elems);
+ if (!sta) {
+ mpl_dbg("Mesh plink: failed to init peer!\n");
+ rcu_read_unlock();
+ return;
+ }
}
mpl_dbg("Mesh plink (peer, state, llid, plid, event): %pM %s %d %d %d\n",
@@ -655,6 +744,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
le16_to_cpu(sta->llid), le16_to_cpu(sta->plid),
event);
reason = 0;
+ spin_lock_bh(&sta->lock);
switch (sta->plink_state) {
/* spin_unlock as soon as state is updated at each case */
case NL80211_PLINK_LISTEN:
@@ -758,7 +848,8 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
sta->plink_state = NL80211_PLINK_ESTAB;
spin_unlock_bh(&sta->lock);
mesh_plink_inc_estab_count(sdata);
- ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON);
+ changed |= mesh_set_ht_prot_mode(sdata);
+ changed |= BSS_CHANGED_BEACON;
mpl_dbg("Mesh plink with %pM ESTABLISHED\n",
sta->sta.addr);
break;
@@ -793,7 +884,8 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
sta->plink_state = NL80211_PLINK_ESTAB;
spin_unlock_bh(&sta->lock);
mesh_plink_inc_estab_count(sdata);
- ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON);
+ changed |= mesh_set_ht_prot_mode(sdata);
+ changed |= BSS_CHANGED_BEACON;
mpl_dbg("Mesh plink with %pM ESTABLISHED\n",
sta->sta.addr);
mesh_plink_frame_tx(sdata,
@@ -811,13 +903,13 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
case CLS_ACPT:
reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
sta->reason = reason;
- deactivated = __mesh_plink_deactivate(sta);
+ __mesh_plink_deactivate(sta);
sta->plink_state = NL80211_PLINK_HOLDING;
llid = sta->llid;
mod_plink_timer(sta, dot11MeshHoldingTimeout(sdata));
spin_unlock_bh(&sta->lock);
- if (deactivated)
- ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON);
+ changed |= mesh_set_ht_prot_mode(sdata);
+ changed |= BSS_CHANGED_BEACON;
mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
sta->sta.addr, llid, plid, reason);
break;
@@ -864,4 +956,7 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata, struct ieee80211_m
}
rcu_read_unlock();
+
+ if (changed)
+ ieee80211_bss_info_change_notify(sdata, changed);
}