Arik Nemtsov | 95224fe | 2014-05-01 10:17:28 +0300 | [diff] [blame] | 1 | /* |
| 2 | * mac80211 TDLS handling code |
| 3 | * |
| 4 | * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> |
| 5 | * Copyright 2014, Intel Corporation |
| 6 | * |
| 7 | * This file is GPLv2 as found in COPYING. |
| 8 | */ |
| 9 | |
| 10 | #include <linux/ieee80211.h> |
| 11 | #include "ieee80211_i.h" |
| 12 | |
| 13 | static void ieee80211_tdls_add_ext_capab(struct sk_buff *skb) |
| 14 | { |
| 15 | u8 *pos = (void *)skb_put(skb, 7); |
| 16 | |
| 17 | *pos++ = WLAN_EID_EXT_CAPABILITY; |
| 18 | *pos++ = 5; /* len */ |
| 19 | *pos++ = 0x0; |
| 20 | *pos++ = 0x0; |
| 21 | *pos++ = 0x0; |
| 22 | *pos++ = 0x0; |
| 23 | *pos++ = WLAN_EXT_CAPA5_TDLS_ENABLED; |
| 24 | } |
| 25 | |
| 26 | static u16 ieee80211_get_tdls_sta_capab(struct ieee80211_sub_if_data *sdata) |
| 27 | { |
| 28 | struct ieee80211_local *local = sdata->local; |
| 29 | u16 capab; |
| 30 | |
| 31 | capab = 0; |
| 32 | if (ieee80211_get_sdata_band(sdata) != IEEE80211_BAND_2GHZ) |
| 33 | return capab; |
| 34 | |
| 35 | if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_SLOT_INCAPABLE)) |
| 36 | capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; |
| 37 | if (!(local->hw.flags & IEEE80211_HW_2GHZ_SHORT_PREAMBLE_INCAPABLE)) |
| 38 | capab |= WLAN_CAPABILITY_SHORT_PREAMBLE; |
| 39 | |
| 40 | return capab; |
| 41 | } |
| 42 | |
Johannes Berg | 3b3a016 | 2014-05-19 17:19:31 +0200 | [diff] [blame] | 43 | static void ieee80211_tdls_add_link_ie(struct sk_buff *skb, const u8 *src_addr, |
| 44 | const u8 *peer, const u8 *bssid) |
Arik Nemtsov | 95224fe | 2014-05-01 10:17:28 +0300 | [diff] [blame] | 45 | { |
| 46 | struct ieee80211_tdls_lnkie *lnkid; |
| 47 | |
| 48 | lnkid = (void *)skb_put(skb, sizeof(struct ieee80211_tdls_lnkie)); |
| 49 | |
| 50 | lnkid->ie_type = WLAN_EID_LINK_ID; |
| 51 | lnkid->ie_len = sizeof(struct ieee80211_tdls_lnkie) - 2; |
| 52 | |
| 53 | memcpy(lnkid->bssid, bssid, ETH_ALEN); |
| 54 | memcpy(lnkid->init_sta, src_addr, ETH_ALEN); |
| 55 | memcpy(lnkid->resp_sta, peer, ETH_ALEN); |
| 56 | } |
| 57 | |
| 58 | static int |
| 59 | ieee80211_prep_tdls_encap_data(struct wiphy *wiphy, struct net_device *dev, |
Johannes Berg | 3b3a016 | 2014-05-19 17:19:31 +0200 | [diff] [blame] | 60 | const u8 *peer, u8 action_code, u8 dialog_token, |
Arik Nemtsov | 95224fe | 2014-05-01 10:17:28 +0300 | [diff] [blame] | 61 | u16 status_code, struct sk_buff *skb) |
| 62 | { |
| 63 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); |
| 64 | enum ieee80211_band band = ieee80211_get_sdata_band(sdata); |
| 65 | struct ieee80211_tdls_data *tf; |
| 66 | |
| 67 | tf = (void *)skb_put(skb, offsetof(struct ieee80211_tdls_data, u)); |
| 68 | |
| 69 | memcpy(tf->da, peer, ETH_ALEN); |
| 70 | memcpy(tf->sa, sdata->vif.addr, ETH_ALEN); |
| 71 | tf->ether_type = cpu_to_be16(ETH_P_TDLS); |
| 72 | tf->payload_type = WLAN_TDLS_SNAP_RFTYPE; |
| 73 | |
| 74 | switch (action_code) { |
| 75 | case WLAN_TDLS_SETUP_REQUEST: |
| 76 | tf->category = WLAN_CATEGORY_TDLS; |
| 77 | tf->action_code = WLAN_TDLS_SETUP_REQUEST; |
| 78 | |
| 79 | skb_put(skb, sizeof(tf->u.setup_req)); |
| 80 | tf->u.setup_req.dialog_token = dialog_token; |
| 81 | tf->u.setup_req.capability = |
| 82 | cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); |
| 83 | |
| 84 | ieee80211_add_srates_ie(sdata, skb, false, band); |
| 85 | ieee80211_add_ext_srates_ie(sdata, skb, false, band); |
| 86 | ieee80211_tdls_add_ext_capab(skb); |
| 87 | break; |
| 88 | case WLAN_TDLS_SETUP_RESPONSE: |
| 89 | tf->category = WLAN_CATEGORY_TDLS; |
| 90 | tf->action_code = WLAN_TDLS_SETUP_RESPONSE; |
| 91 | |
| 92 | skb_put(skb, sizeof(tf->u.setup_resp)); |
| 93 | tf->u.setup_resp.status_code = cpu_to_le16(status_code); |
| 94 | tf->u.setup_resp.dialog_token = dialog_token; |
| 95 | tf->u.setup_resp.capability = |
| 96 | cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); |
| 97 | |
| 98 | ieee80211_add_srates_ie(sdata, skb, false, band); |
| 99 | ieee80211_add_ext_srates_ie(sdata, skb, false, band); |
| 100 | ieee80211_tdls_add_ext_capab(skb); |
| 101 | break; |
| 102 | case WLAN_TDLS_SETUP_CONFIRM: |
| 103 | tf->category = WLAN_CATEGORY_TDLS; |
| 104 | tf->action_code = WLAN_TDLS_SETUP_CONFIRM; |
| 105 | |
| 106 | skb_put(skb, sizeof(tf->u.setup_cfm)); |
| 107 | tf->u.setup_cfm.status_code = cpu_to_le16(status_code); |
| 108 | tf->u.setup_cfm.dialog_token = dialog_token; |
| 109 | break; |
| 110 | case WLAN_TDLS_TEARDOWN: |
| 111 | tf->category = WLAN_CATEGORY_TDLS; |
| 112 | tf->action_code = WLAN_TDLS_TEARDOWN; |
| 113 | |
| 114 | skb_put(skb, sizeof(tf->u.teardown)); |
| 115 | tf->u.teardown.reason_code = cpu_to_le16(status_code); |
| 116 | break; |
| 117 | case WLAN_TDLS_DISCOVERY_REQUEST: |
| 118 | tf->category = WLAN_CATEGORY_TDLS; |
| 119 | tf->action_code = WLAN_TDLS_DISCOVERY_REQUEST; |
| 120 | |
| 121 | skb_put(skb, sizeof(tf->u.discover_req)); |
| 122 | tf->u.discover_req.dialog_token = dialog_token; |
| 123 | break; |
| 124 | default: |
| 125 | return -EINVAL; |
| 126 | } |
| 127 | |
| 128 | return 0; |
| 129 | } |
| 130 | |
| 131 | static int |
| 132 | ieee80211_prep_tdls_direct(struct wiphy *wiphy, struct net_device *dev, |
Johannes Berg | 3b3a016 | 2014-05-19 17:19:31 +0200 | [diff] [blame] | 133 | const u8 *peer, u8 action_code, u8 dialog_token, |
Arik Nemtsov | 95224fe | 2014-05-01 10:17:28 +0300 | [diff] [blame] | 134 | u16 status_code, struct sk_buff *skb) |
| 135 | { |
| 136 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); |
| 137 | enum ieee80211_band band = ieee80211_get_sdata_band(sdata); |
| 138 | struct ieee80211_mgmt *mgmt; |
| 139 | |
| 140 | mgmt = (void *)skb_put(skb, 24); |
| 141 | memset(mgmt, 0, 24); |
| 142 | memcpy(mgmt->da, peer, ETH_ALEN); |
| 143 | memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); |
| 144 | memcpy(mgmt->bssid, sdata->u.mgd.bssid, ETH_ALEN); |
| 145 | |
| 146 | mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | |
| 147 | IEEE80211_STYPE_ACTION); |
| 148 | |
| 149 | switch (action_code) { |
| 150 | case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: |
| 151 | skb_put(skb, 1 + sizeof(mgmt->u.action.u.tdls_discover_resp)); |
| 152 | mgmt->u.action.category = WLAN_CATEGORY_PUBLIC; |
| 153 | mgmt->u.action.u.tdls_discover_resp.action_code = |
| 154 | WLAN_PUB_ACTION_TDLS_DISCOVER_RES; |
| 155 | mgmt->u.action.u.tdls_discover_resp.dialog_token = |
| 156 | dialog_token; |
| 157 | mgmt->u.action.u.tdls_discover_resp.capability = |
| 158 | cpu_to_le16(ieee80211_get_tdls_sta_capab(sdata)); |
| 159 | |
| 160 | ieee80211_add_srates_ie(sdata, skb, false, band); |
| 161 | ieee80211_add_ext_srates_ie(sdata, skb, false, band); |
| 162 | ieee80211_tdls_add_ext_capab(skb); |
| 163 | break; |
| 164 | default: |
| 165 | return -EINVAL; |
| 166 | } |
| 167 | |
| 168 | return 0; |
| 169 | } |
| 170 | |
| 171 | int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, |
Johannes Berg | 3b3a016 | 2014-05-19 17:19:31 +0200 | [diff] [blame] | 172 | const u8 *peer, u8 action_code, u8 dialog_token, |
Arik Nemtsov | 95224fe | 2014-05-01 10:17:28 +0300 | [diff] [blame] | 173 | u16 status_code, u32 peer_capability, |
| 174 | const u8 *extra_ies, size_t extra_ies_len) |
| 175 | { |
| 176 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); |
| 177 | struct ieee80211_local *local = sdata->local; |
| 178 | struct sk_buff *skb = NULL; |
| 179 | bool send_direct; |
| 180 | int ret; |
| 181 | |
| 182 | if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) |
| 183 | return -ENOTSUPP; |
| 184 | |
| 185 | /* make sure we are in managed mode, and associated */ |
| 186 | if (sdata->vif.type != NL80211_IFTYPE_STATION || |
| 187 | !sdata->u.mgd.associated) |
| 188 | return -EINVAL; |
| 189 | |
| 190 | tdls_dbg(sdata, "TDLS mgmt action %d peer %pM\n", |
| 191 | action_code, peer); |
| 192 | |
| 193 | skb = dev_alloc_skb(local->hw.extra_tx_headroom + |
| 194 | max(sizeof(struct ieee80211_mgmt), |
| 195 | sizeof(struct ieee80211_tdls_data)) + |
| 196 | 50 + /* supported rates */ |
| 197 | 7 + /* ext capab */ |
| 198 | extra_ies_len + |
| 199 | sizeof(struct ieee80211_tdls_lnkie)); |
| 200 | if (!skb) |
| 201 | return -ENOMEM; |
| 202 | |
| 203 | skb_reserve(skb, local->hw.extra_tx_headroom); |
| 204 | |
| 205 | switch (action_code) { |
| 206 | case WLAN_TDLS_SETUP_REQUEST: |
| 207 | case WLAN_TDLS_SETUP_RESPONSE: |
| 208 | case WLAN_TDLS_SETUP_CONFIRM: |
| 209 | case WLAN_TDLS_TEARDOWN: |
| 210 | case WLAN_TDLS_DISCOVERY_REQUEST: |
| 211 | ret = ieee80211_prep_tdls_encap_data(wiphy, dev, peer, |
| 212 | action_code, dialog_token, |
| 213 | status_code, skb); |
| 214 | send_direct = false; |
| 215 | break; |
| 216 | case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: |
| 217 | ret = ieee80211_prep_tdls_direct(wiphy, dev, peer, action_code, |
| 218 | dialog_token, status_code, |
| 219 | skb); |
| 220 | send_direct = true; |
| 221 | break; |
| 222 | default: |
| 223 | ret = -ENOTSUPP; |
| 224 | break; |
| 225 | } |
| 226 | |
| 227 | if (ret < 0) |
| 228 | goto fail; |
| 229 | |
| 230 | if (extra_ies_len) |
| 231 | memcpy(skb_put(skb, extra_ies_len), extra_ies, extra_ies_len); |
| 232 | |
| 233 | /* the TDLS link IE is always added last */ |
| 234 | switch (action_code) { |
| 235 | case WLAN_TDLS_SETUP_REQUEST: |
| 236 | case WLAN_TDLS_SETUP_CONFIRM: |
| 237 | case WLAN_TDLS_TEARDOWN: |
| 238 | case WLAN_TDLS_DISCOVERY_REQUEST: |
| 239 | /* we are the initiator */ |
| 240 | ieee80211_tdls_add_link_ie(skb, sdata->vif.addr, peer, |
| 241 | sdata->u.mgd.bssid); |
| 242 | break; |
| 243 | case WLAN_TDLS_SETUP_RESPONSE: |
| 244 | case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: |
| 245 | /* we are the responder */ |
| 246 | ieee80211_tdls_add_link_ie(skb, peer, sdata->vif.addr, |
| 247 | sdata->u.mgd.bssid); |
| 248 | break; |
| 249 | default: |
| 250 | ret = -ENOTSUPP; |
| 251 | goto fail; |
| 252 | } |
| 253 | |
| 254 | if (send_direct) { |
| 255 | ieee80211_tx_skb(sdata, skb); |
| 256 | return 0; |
| 257 | } |
| 258 | |
| 259 | /* |
| 260 | * According to 802.11z: Setup req/resp are sent in AC_BK, otherwise |
| 261 | * we should default to AC_VI. |
| 262 | */ |
| 263 | switch (action_code) { |
| 264 | case WLAN_TDLS_SETUP_REQUEST: |
| 265 | case WLAN_TDLS_SETUP_RESPONSE: |
| 266 | skb_set_queue_mapping(skb, IEEE80211_AC_BK); |
| 267 | skb->priority = 2; |
| 268 | break; |
| 269 | default: |
| 270 | skb_set_queue_mapping(skb, IEEE80211_AC_VI); |
| 271 | skb->priority = 5; |
| 272 | break; |
| 273 | } |
| 274 | |
| 275 | /* disable bottom halves when entering the Tx path */ |
| 276 | local_bh_disable(); |
| 277 | ret = ieee80211_subif_start_xmit(skb, dev); |
| 278 | local_bh_enable(); |
| 279 | |
| 280 | return ret; |
| 281 | |
| 282 | fail: |
| 283 | dev_kfree_skb(skb); |
| 284 | return ret; |
| 285 | } |
| 286 | |
| 287 | int ieee80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, |
Johannes Berg | 3b3a016 | 2014-05-19 17:19:31 +0200 | [diff] [blame] | 288 | const u8 *peer, enum nl80211_tdls_operation oper) |
Arik Nemtsov | 95224fe | 2014-05-01 10:17:28 +0300 | [diff] [blame] | 289 | { |
| 290 | struct sta_info *sta; |
| 291 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); |
| 292 | |
| 293 | if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) |
| 294 | return -ENOTSUPP; |
| 295 | |
| 296 | if (sdata->vif.type != NL80211_IFTYPE_STATION) |
| 297 | return -EINVAL; |
| 298 | |
| 299 | tdls_dbg(sdata, "TDLS oper %d peer %pM\n", oper, peer); |
| 300 | |
| 301 | switch (oper) { |
| 302 | case NL80211_TDLS_ENABLE_LINK: |
| 303 | rcu_read_lock(); |
| 304 | sta = sta_info_get(sdata, peer); |
| 305 | if (!sta) { |
| 306 | rcu_read_unlock(); |
| 307 | return -ENOLINK; |
| 308 | } |
| 309 | |
| 310 | set_sta_flag(sta, WLAN_STA_TDLS_PEER_AUTH); |
| 311 | rcu_read_unlock(); |
| 312 | break; |
| 313 | case NL80211_TDLS_DISABLE_LINK: |
| 314 | return sta_info_destroy_addr(sdata, peer); |
| 315 | case NL80211_TDLS_TEARDOWN: |
| 316 | case NL80211_TDLS_SETUP: |
| 317 | case NL80211_TDLS_DISCOVERY_REQ: |
| 318 | /* We don't support in-driver setup/teardown/discovery */ |
| 319 | return -ENOTSUPP; |
| 320 | default: |
| 321 | return -ENOTSUPP; |
| 322 | } |
| 323 | |
| 324 | return 0; |
| 325 | } |