diff options
Diffstat (limited to 'net/mac80211/mesh_plink.c')
| -rw-r--r-- | net/mac80211/mesh_plink.c | 237 | 
1 files changed, 166 insertions, 71 deletions
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c index 4e53c4cbca9..8cc8461b48a 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);  }  |