diff options
Diffstat (limited to 'net/mac80211/tdls.c')
| -rw-r--r-- | net/mac80211/tdls.c | 325 | 
1 files changed, 325 insertions, 0 deletions
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c new file mode 100644 index 00000000000..652813b2d3d --- /dev/null +++ b/net/mac80211/tdls.c @@ -0,0 +1,325 @@ +/* + * mac80211 TDLS handling code + * + * Copyright 2006-2010	Johannes Berg <johannes@sipsolutions.net> + * Copyright 2014, Intel Corporation + * + * This file is GPLv2 as found in COPYING. + */ + +#include <linux/ieee80211.h> +#include "ieee80211_i.h" + +static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb) +{ +	u8 *pos = (void *)skb_put(skb, 7); + +	*pos++ = WLAN_EID_EXT_CAPABILITY; +	*pos++ = 5; /* len */ +	*pos++ = 0x0; +	*pos++ = 0x0; +	*pos++ = 0x0; +	*pos++ = 0x0; +	*pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED; +} + +static u16 ieee80211_get_tdls_sta_capab(struct ieee80211_sub_if_data *sdata) +{ +	struct ieee80211_local *local = sdata->local; +	u16 capab; + +	capab = 0; +	if (ieee80211_get_sdata_band(sdata) != IEEE80211_BAND_2GHZ) +		return capab; + +	if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE)) +		capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; +	if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_PREAMBLE_INCAPABLE)) +		capab |= WLAN_CAPABILITY_SHORT_PREAMBLE; + +	return capab; +} + +static void ieee80211_tdls_add_link_ie(struct sk_buff *skb, const u8 *src_addr, +				       const u8 *peer, const u8 *bssid) +{ +	struct ieee80211_tdls_lnkie *lnkid; + +	lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie)); + +	lnkid->ie_type = WLAN_EID_LINK_ID; +	lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2; + +	memcpy(lnkid->bssid, bssid, ETH_ALEN); +	memcpy(lnkid->init_sta, src_addr, ETH_ALEN); +	memcpy(lnkid->resp_sta, peer, ETH_ALEN); +} + +static int +ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev, +			       const u8 *peer, u8 action_code, u8 dialog_token, +			       u16 status_code, struct sk_buff *skb) +{ +	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); +	enum ieee80211_band band = ieee80211_get_sdata_band(sdata); +	struct ieee80211_tdls_data *tf; + +	tf = (void *)skb_put(skb, offsetof(struct ieee80211_tdls_data, u)); + +	memcpy(tf->da, peer, ETH_ALEN); +	memcpy(tf->sa, sdata->vif.addr, ETH_ALEN); +	tf->ether_type = cpu_to_be16(ETH_P_TDLS); +	tf->payload_type = WLAN_TDLS_SNAP_RFTYPE; + +	switch (action_code) { +	case WLAN_TDLS_SETUP_REQUEST: +		tf->category = WLAN_CATEGORY_TDLS; +		tf->action_code = WLAN_TDLS_SETUP_REQUEST; + +		skb_put(skb, sizeof(tf->u.setup_req)); +		tf->u.setup_req.dialog_token = dialog_token; +		tf->u.setup_req.capability = +			cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); + +		ieee80211_add_srates_ie(sdata, skb, false, band); +		ieee80211_add_ext_srates_ie(sdata, skb, false, band); +		ieee80211_tdls_add_ext_capab(skb); +		break; +	case WLAN_TDLS_SETUP_RESPONSE: +		tf->category = WLAN_CATEGORY_TDLS; +		tf->action_code = WLAN_TDLS_SETUP_RESPONSE; + +		skb_put(skb, sizeof(tf->u.setup_resp)); +		tf->u.setup_resp.status_code = cpu_to_le16(status_code); +		tf->u.setup_resp.dialog_token = dialog_token; +		tf->u.setup_resp.capability = +			cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); + +		ieee80211_add_srates_ie(sdata, skb, false, band); +		ieee80211_add_ext_srates_ie(sdata, skb, false, band); +		ieee80211_tdls_add_ext_capab(skb); +		break; +	case WLAN_TDLS_SETUP_CONFIRM: +		tf->category = WLAN_CATEGORY_TDLS; +		tf->action_code = WLAN_TDLS_SETUP_CONFIRM; + +		skb_put(skb, sizeof(tf->u.setup_cfm)); +		tf->u.setup_cfm.status_code = cpu_to_le16(status_code); +		tf->u.setup_cfm.dialog_token = dialog_token; +		break; +	case WLAN_TDLS_TEARDOWN: +		tf->category = WLAN_CATEGORY_TDLS; +		tf->action_code = WLAN_TDLS_TEARDOWN; + +		skb_put(skb, sizeof(tf->u.teardown)); +		tf->u.teardown.reason_code = cpu_to_le16(status_code); +		break; +	case WLAN_TDLS_DISCOVERY_REQUEST: +		tf->category = WLAN_CATEGORY_TDLS; +		tf->action_code = WLAN_TDLS_DISCOVERY_REQUEST; + +		skb_put(skb, sizeof(tf->u.discover_req)); +		tf->u.discover_req.dialog_token = dialog_token; +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int +ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev, +			   const u8 *peer, u8 action_code, u8 dialog_token, +			   u16 status_code, struct sk_buff *skb) +{ +	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); +	enum ieee80211_band band = ieee80211_get_sdata_band(sdata); +	struct ieee80211_mgmt *mgmt; + +	mgmt = (void *)skb_put(skb, 24); +	memset(mgmt, 0, 24); +	memcpy(mgmt->da, peer, ETH_ALEN); +	memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); +	memcpy(mgmt->bssid, sdata->u.mgd.bssid, ETH_ALEN); + +	mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | +					  IEEE80211_STYPE_ACTION); + +	switch (action_code) { +	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: +		skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp)); +		mgmt->u.action.category = WLAN_CATEGORY_PUBLIC; +		mgmt->u.action.u.tdls_discover_resp.action_code = +			WLAN_PUB_ACTION_TDLS_DISCOVER_RES; +		mgmt->u.action.u.tdls_discover_resp.dialog_token = +			dialog_token; +		mgmt->u.action.u.tdls_discover_resp.capability = +			cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); + +		ieee80211_add_srates_ie(sdata, skb, false, band); +		ieee80211_add_ext_srates_ie(sdata, skb, false, band); +		ieee80211_tdls_add_ext_capab(skb); +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, +			const u8 *peer, u8 action_code, u8 dialog_token, +			u16 status_code, u32 peer_capability, +			const u8 *extra_ies, size_t extra_ies_len) +{ +	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); +	struct ieee80211_local *local = sdata->local; +	struct sk_buff *skb = NULL; +	bool send_direct; +	int ret; + +	if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) +		return -ENOTSUPP; + +	/* make sure we are in managed mode, and associated */ +	if (sdata->vif.type != NL80211_IFTYPE_STATION || +	    !sdata->u.mgd.associated) +		return -EINVAL; + +	tdls_dbg(sdata, "TDLS mgmt action %d peer %pM\n", +		 action_code, peer); + +	skb = dev_alloc_skb(local->hw.extra_tx_headroom + +			    max(sizeof(struct ieee80211_mgmt), +				sizeof(struct ieee80211_tdls_data)) + +			    50 + /* supported rates */ +			    7 + /* ext capab */ +			    extra_ies_len + +			    sizeof(struct ieee80211_tdls_lnkie)); +	if (!skb) +		return -ENOMEM; + +	skb_reserve(skb, local->hw.extra_tx_headroom); + +	switch (action_code) { +	case WLAN_TDLS_SETUP_REQUEST: +	case WLAN_TDLS_SETUP_RESPONSE: +	case WLAN_TDLS_SETUP_CONFIRM: +	case WLAN_TDLS_TEARDOWN: +	case WLAN_TDLS_DISCOVERY_REQUEST: +		ret = ieee80211_prep_tdls_encap_data(wiphy, dev, peer, +						     action_code, dialog_token, +						     status_code, skb); +		send_direct = false; +		break; +	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: +		ret = ieee80211_prep_tdls_direct(wiphy, dev, peer, action_code, +						 dialog_token, status_code, +						 skb); +		send_direct = true; +		break; +	default: +		ret = -ENOTSUPP; +		break; +	} + +	if (ret < 0) +		goto fail; + +	if (extra_ies_len) +		memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len); + +	/* the TDLS link IE is always added last */ +	switch (action_code) { +	case WLAN_TDLS_SETUP_REQUEST: +	case WLAN_TDLS_SETUP_CONFIRM: +	case WLAN_TDLS_TEARDOWN: +	case WLAN_TDLS_DISCOVERY_REQUEST: +		/* we are the initiator */ +		ieee80211_tdls_add_link_ie(skb, sdata->vif.addr, peer, +					   sdata->u.mgd.bssid); +		break; +	case WLAN_TDLS_SETUP_RESPONSE: +	case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: +		/* we are the responder */ +		ieee80211_tdls_add_link_ie(skb, peer, sdata->vif.addr, +					   sdata->u.mgd.bssid); +		break; +	default: +		ret = -ENOTSUPP; +		goto fail; +	} + +	if (send_direct) { +		ieee80211_tx_skb(sdata, skb); +		return 0; +	} + +	/* +	 * According to 802.11z: Setup req/resp are sent in AC_BK, otherwise +	 * we should default to AC_VI. +	 */ +	switch (action_code) { +	case WLAN_TDLS_SETUP_REQUEST: +	case WLAN_TDLS_SETUP_RESPONSE: +		skb_set_queue_mapping(skb, IEEE80211_AC_BK); +		skb->priority = 2; +		break; +	default: +		skb_set_queue_mapping(skb, IEEE80211_AC_VI); +		skb->priority = 5; +		break; +	} + +	/* disable bottom halves when entering the Tx path */ +	local_bh_disable(); +	ret = ieee80211_subif_start_xmit(skb, dev); +	local_bh_enable(); + +	return ret; + +fail: +	dev_kfree_skb(skb); +	return ret; +} + +int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, +			const u8 *peer, enum nl80211_tdls_operation oper) +{ +	struct sta_info *sta; +	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + +	if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) +		return -ENOTSUPP; + +	if (sdata->vif.type != NL80211_IFTYPE_STATION) +		return -EINVAL; + +	tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer); + +	switch (oper) { +	case NL80211_TDLS_ENABLE_LINK: +		rcu_read_lock(); +		sta = sta_info_get(sdata, peer); +		if (!sta) { +			rcu_read_unlock(); +			return -ENOLINK; +		} + +		set_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH); +		rcu_read_unlock(); +		break; +	case NL80211_TDLS_DISABLE_LINK: +		return sta_info_destroy_addr(sdata, peer); +	case NL80211_TDLS_TEARDOWN: +	case NL80211_TDLS_SETUP: +	case NL80211_TDLS_DISCOVERY_REQ: +		/* We don't support in-driver setup/teardown/discovery */ +		return -ENOTSUPP; +	default: +		return -ENOTSUPP; +	} + +	return 0; +}  | 
