diff options
Diffstat (limited to 'net/mac80211/mesh.c')
| -rw-r--r-- | net/mac80211/mesh.c | 299 | 
1 files changed, 280 insertions, 19 deletions
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c index 707ac61d63e..6495a3f0428 100644 --- a/net/mac80211/mesh.c +++ b/net/mac80211/mesh.c @@ -12,6 +12,7 @@  #include <asm/unaligned.h>  #include "ieee80211_i.h"  #include "mesh.h" +#include "driver-ops.h"  static int mesh_allocated;  static struct kmem_cache *rm_cache; @@ -258,6 +259,9 @@ int mesh_add_meshconf_ie(struct ieee80211_sub_if_data *sdata,  	*pos++ = WLAN_EID_MESH_CONFIG;  	*pos++ = meshconf_len; +	/* save a pointer for quick updates in pre-tbtt */ +	ifmsh->meshconf_offset = pos - skb->data; +  	/* Active path selection protocol ID */  	*pos++ = ifmsh->mesh_pp_id;  	/* Active path selection metric ID   */ @@ -362,20 +366,15 @@ int mesh_add_rsn_ie(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb)  		return 0;  	/* find RSN IE */ -	data = ifmsh->ie; -	while (data < ifmsh->ie + ifmsh->ie_len) { -		if (*data == WLAN_EID_RSN) { -			len = data[1] + 2; -			break; -		} -		data++; -	} +	data = cfg80211_find_ie(WLAN_EID_RSN, ifmsh->ie, ifmsh->ie_len); +	if (!data) +		return 0; -	if (len) { -		if (skb_tailroom(skb) < len) -			return -ENOMEM; -		memcpy(skb_put(skb, len), data, len); -	} +	len = data[1] + 2; + +	if (skb_tailroom(skb) < len) +		return -ENOMEM; +	memcpy(skb_put(skb, len), data, len);  	return 0;  } @@ -610,6 +609,7 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh)  	struct sk_buff *skb;  	struct ieee80211_mgmt *mgmt;  	struct ieee80211_chanctx_conf *chanctx_conf; +	struct mesh_csa_settings *csa;  	enum ieee80211_band band;  	u8 *pos;  	struct ieee80211_sub_if_data *sdata; @@ -624,6 +624,10 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh)  	head_len = hdr_len +  		   2 + /* NULL SSID */ +		   /* Channel Switch Announcement */ +		   2 + sizeof(struct ieee80211_channel_sw_ie) + +		   /* Mesh Channel Swith Parameters */ +		   2 + sizeof(struct ieee80211_mesh_chansw_params_ie) +  		   2 + 8 + /* supported rates */  		   2 + 3; /* DS params */  	tail_len = 2 + (IEEE80211_MAX_SUPP_RATES - 8) + @@ -665,6 +669,35 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh)  	*pos++ = WLAN_EID_SSID;  	*pos++ = 0x0; +	rcu_read_lock(); +	csa = rcu_dereference(ifmsh->csa); +	if (csa) { +		pos = skb_put(skb, 13); +		memset(pos, 0, 13); +		*pos++ = WLAN_EID_CHANNEL_SWITCH; +		*pos++ = 3; +		*pos++ = 0x0; +		*pos++ = ieee80211_frequency_to_channel( +				csa->settings.chandef.chan->center_freq); +		sdata->csa_counter_offset_beacon[0] = hdr_len + 6; +		*pos++ = csa->settings.count; +		*pos++ = WLAN_EID_CHAN_SWITCH_PARAM; +		*pos++ = 6; +		if (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_INIT) { +			*pos++ = ifmsh->mshcfg.dot11MeshTTL; +			*pos |= WLAN_EID_CHAN_SWITCH_PARAM_INITIATOR; +		} else { +			*pos++ = ifmsh->chsw_ttl; +		} +		*pos++ |= csa->settings.block_tx ? +			  WLAN_EID_CHAN_SWITCH_PARAM_TX_RESTRICT : 0x00; +		put_unaligned_le16(WLAN_REASON_MESH_CHAN, pos); +		pos += 2; +		put_unaligned_le16(ifmsh->pre_value, pos); +		pos += 2; +	} +	rcu_read_unlock(); +  	if (ieee80211_add_srates_ie(sdata, skb, true, band) ||  	    mesh_add_ds_params_ie(sdata, skb))  		goto out_free; @@ -688,6 +721,8 @@ ieee80211_mesh_build_beacon(struct ieee80211_if_mesh *ifmsh)  	bcn->tail_len = skb->len;  	memcpy(bcn->tail, skb->data, bcn->tail_len); +	bcn->meshconf = (struct ieee80211_meshconf_ie *) +					(bcn->tail + ifmsh->meshconf_offset);  	dev_kfree_skb(skb);  	rcu_assign_pointer(ifmsh->beacon, bcn); @@ -767,6 +802,7 @@ int ieee80211_start_mesh(struct ieee80211_sub_if_data *sdata)  		return -ENOMEM;  	} +	ieee80211_recalc_dtim(local, sdata);  	ieee80211_bss_info_change_notify(sdata, changed);  	netif_carrier_on(sdata->dev); @@ -788,7 +824,7 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata)  	ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_BEACON_ENABLED);  	bcn = rcu_dereference_protected(ifmsh->beacon,  					lockdep_is_held(&sdata->wdev.mtx)); -	rcu_assign_pointer(ifmsh->beacon, NULL); +	RCU_INIT_POINTER(ifmsh->beacon, NULL);  	kfree_rcu(bcn, rcu_head);  	/* flush STAs and mpaths on this iface */ @@ -812,6 +848,97 @@ void ieee80211_stop_mesh(struct ieee80211_sub_if_data *sdata)  	ieee80211_configure_filter(local);  } +static bool +ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata, +				 struct ieee802_11_elems *elems, bool beacon) +{ +	struct cfg80211_csa_settings params; +	struct ieee80211_csa_ie csa_ie; +	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; +	enum ieee80211_band band = ieee80211_get_sdata_band(sdata); +	int err; +	u32 sta_flags; + +	sdata_assert_lock(sdata); + +	sta_flags = IEEE80211_STA_DISABLE_VHT; +	switch (sdata->vif.bss_conf.chandef.width) { +	case NL80211_CHAN_WIDTH_20_NOHT: +		sta_flags |= IEEE80211_STA_DISABLE_HT; +	case NL80211_CHAN_WIDTH_20: +		sta_flags |= IEEE80211_STA_DISABLE_40MHZ; +		break; +	default: +		break; +	} + +	memset(¶ms, 0, sizeof(params)); +	memset(&csa_ie, 0, sizeof(csa_ie)); +	err = ieee80211_parse_ch_switch_ie(sdata, elems, beacon, band, +					   sta_flags, sdata->vif.addr, +					   &csa_ie); +	if (err < 0) +		return false; +	if (err) +		return false; + +	params.chandef = csa_ie.chandef; +	params.count = csa_ie.count; + +	if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, ¶ms.chandef, +				     IEEE80211_CHAN_DISABLED)) { +		sdata_info(sdata, +			   "mesh STA %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), aborting\n", +			   sdata->vif.addr, +			   params.chandef.chan->center_freq, +			   params.chandef.width, +			   params.chandef.center_freq1, +			   params.chandef.center_freq2); +		return false; +	} + +	err = cfg80211_chandef_dfs_required(sdata->local->hw.wiphy, +					    ¶ms.chandef, +					    NL80211_IFTYPE_MESH_POINT); +	if (err < 0) +		return false; +	if (err > 0) +		/* TODO: DFS not (yet) supported */ +		return false; + +	params.radar_required = err; + +	if (cfg80211_chandef_identical(¶ms.chandef, +				       &sdata->vif.bss_conf.chandef)) { +		mcsa_dbg(sdata, +			 "received csa with an identical chandef, ignoring\n"); +		return true; +	} + +	mcsa_dbg(sdata, +		 "received channel switch announcement to go to channel %d MHz\n", +		 params.chandef.chan->center_freq); + +	params.block_tx = csa_ie.mode & WLAN_EID_CHAN_SWITCH_PARAM_TX_RESTRICT; +	if (beacon) { +		ifmsh->chsw_ttl = csa_ie.ttl - 1; +		if (ifmsh->pre_value >= csa_ie.pre_value) +			return false; +		ifmsh->pre_value = csa_ie.pre_value; +	} + +	if (ifmsh->chsw_ttl >= ifmsh->mshcfg.dot11MeshTTL) +		return false; + +	ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_REPEATER; + +	if (ieee80211_channel_switch(sdata->local->hw.wiphy, sdata->dev, +				     ¶ms) < 0) +		return false; + +	return true; +} +  static void  ieee80211_mesh_rx_probe_req(struct ieee80211_sub_if_data *sdata,  			    struct ieee80211_mgmt *mgmt, size_t len) @@ -918,6 +1045,139 @@ static void ieee80211_mesh_rx_bcn_presp(struct ieee80211_sub_if_data *sdata,  	if (ifmsh->sync_ops)  		ifmsh->sync_ops->rx_bcn_presp(sdata,  			stype, mgmt, &elems, rx_status); + +	if (ifmsh->csa_role != IEEE80211_MESH_CSA_ROLE_INIT && +	    !sdata->vif.csa_active) +		ieee80211_mesh_process_chnswitch(sdata, &elems, true); +} + +int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata) +{ +	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; +	struct mesh_csa_settings *tmp_csa_settings; +	int ret = 0; +	int changed = 0; + +	/* Reset the TTL value and Initiator flag */ +	ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE; +	ifmsh->chsw_ttl = 0; + +	/* Remove the CSA and MCSP elements from the beacon */ +	tmp_csa_settings = rcu_dereference(ifmsh->csa); +	RCU_INIT_POINTER(ifmsh->csa, NULL); +	if (tmp_csa_settings) +		kfree_rcu(tmp_csa_settings, rcu_head); +	ret = ieee80211_mesh_rebuild_beacon(sdata); +	if (ret) +		return -EINVAL; + +	changed |= BSS_CHANGED_BEACON; + +	mcsa_dbg(sdata, "complete switching to center freq %d MHz", +		 sdata->vif.bss_conf.chandef.chan->center_freq); +	return changed; +} + +int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata, +			      struct cfg80211_csa_settings *csa_settings) +{ +	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; +	struct mesh_csa_settings *tmp_csa_settings; +	int ret = 0; + +	tmp_csa_settings = kmalloc(sizeof(*tmp_csa_settings), +				   GFP_ATOMIC); +	if (!tmp_csa_settings) +		return -ENOMEM; + +	memcpy(&tmp_csa_settings->settings, csa_settings, +	       sizeof(struct cfg80211_csa_settings)); + +	rcu_assign_pointer(ifmsh->csa, tmp_csa_settings); + +	ret = ieee80211_mesh_rebuild_beacon(sdata); +	if (ret) { +		tmp_csa_settings = rcu_dereference(ifmsh->csa); +		RCU_INIT_POINTER(ifmsh->csa, NULL); +		kfree_rcu(tmp_csa_settings, rcu_head); +		return ret; +	} + +	return BSS_CHANGED_BEACON; +} + +static int mesh_fwd_csa_frame(struct ieee80211_sub_if_data *sdata, +			       struct ieee80211_mgmt *mgmt, size_t len) +{ +	struct ieee80211_mgmt *mgmt_fwd; +	struct sk_buff *skb; +	struct ieee80211_local *local = sdata->local; +	u8 *pos = mgmt->u.action.u.chan_switch.variable; +	size_t offset_ttl; + +	skb = dev_alloc_skb(local->tx_headroom + len); +	if (!skb) +		return -ENOMEM; +	skb_reserve(skb, local->tx_headroom); +	mgmt_fwd = (struct ieee80211_mgmt *) skb_put(skb, len); + +	/* offset_ttl is based on whether the secondary channel +	 * offset is available or not. Substract 1 from the mesh TTL +	 * and disable the initiator flag before forwarding. +	 */ +	offset_ttl = (len < 42) ? 7 : 10; +	*(pos + offset_ttl) -= 1; +	*(pos + offset_ttl + 1) &= ~WLAN_EID_CHAN_SWITCH_PARAM_INITIATOR; + +	memcpy(mgmt_fwd, mgmt, len); +	eth_broadcast_addr(mgmt_fwd->da); +	memcpy(mgmt_fwd->sa, sdata->vif.addr, ETH_ALEN); +	memcpy(mgmt_fwd->bssid, sdata->vif.addr, ETH_ALEN); + +	ieee80211_tx_skb(sdata, skb); +	return 0; +} + +static void mesh_rx_csa_frame(struct ieee80211_sub_if_data *sdata, +			      struct ieee80211_mgmt *mgmt, size_t len) +{ +	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; +	struct ieee802_11_elems elems; +	u16 pre_value; +	bool fwd_csa = true; +	size_t baselen; +	u8 *pos; + +	if (mgmt->u.action.u.measurement.action_code != +	    WLAN_ACTION_SPCT_CHL_SWITCH) +		return; + +	pos = mgmt->u.action.u.chan_switch.variable; +	baselen = offsetof(struct ieee80211_mgmt, +			   u.action.u.chan_switch.variable); +	ieee802_11_parse_elems(pos, len - baselen, false, &elems); + +	ifmsh->chsw_ttl = elems.mesh_chansw_params_ie->mesh_ttl; +	if (!--ifmsh->chsw_ttl) +		fwd_csa = false; + +	pre_value = le16_to_cpu(elems.mesh_chansw_params_ie->mesh_pre_value); +	if (ifmsh->pre_value >= pre_value) +		return; + +	ifmsh->pre_value = pre_value; + +	if (!sdata->vif.csa_active && +	    !ieee80211_mesh_process_chnswitch(sdata, &elems, false)) { +		mcsa_dbg(sdata, "Failed to process CSA action frame"); +		return; +	} + +	/* forward or re-broadcast the CSA frame */ +	if (fwd_csa) { +		if (mesh_fwd_csa_frame(sdata, mgmt, len) < 0) +			mcsa_dbg(sdata, "Failed to forward the CSA frame"); +	}  }  static void ieee80211_mesh_rx_mgmt_action(struct ieee80211_sub_if_data *sdata, @@ -939,6 +1199,9 @@ static void ieee80211_mesh_rx_mgmt_action(struct ieee80211_sub_if_data *sdata,  		if (mesh_action_is_path_sel(mgmt))  			mesh_rx_path_sel_frame(sdata, mgmt, len);  		break; +	case WLAN_CATEGORY_SPECTRUM_MGMT: +		mesh_rx_csa_frame(sdata, mgmt, len); +		break;  	}  } @@ -952,7 +1215,7 @@ void ieee80211_mesh_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,  	sdata_lock(sdata);  	/* mesh already went down */ -	if (!sdata->wdev.mesh_id_len) +	if (!sdata->u.mesh.mesh_id_len)  		goto out;  	rx_status = IEEE80211_SKB_RXCB(skb); @@ -1005,7 +1268,7 @@ void ieee80211_mesh_work(struct ieee80211_sub_if_data *sdata)  	sdata_lock(sdata);  	/* mesh already went down */ -	if (!sdata->wdev.mesh_id_len) +	if (!sdata->u.mesh.mesh_id_len)  		goto out;  	if (ifmsh->preq_queue_len && @@ -1056,13 +1319,11 @@ void ieee80211_mesh_init_sdata(struct ieee80211_sub_if_data *sdata)  		    (unsigned long) sdata);  	ifmsh->accepting_plinks = true; -	ifmsh->preq_id = 0; -	ifmsh->sn = 0; -	ifmsh->num_gates = 0;  	atomic_set(&ifmsh->mpaths, 0);  	mesh_rmc_init(sdata);  	ifmsh->last_preq = jiffies;  	ifmsh->next_perr = jiffies; +	ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE;  	/* Allocate all mesh structures when creating the first mesh interface. */  	if (!mesh_allocated)  		ieee80211s_init();  | 
