blob: 1544d661bbc6f29352c930e51ed1ddacfb850fad [file] [log] [blame]
/*SH0
*******************************************************************************
** **
** Copyright (c) 2010-2013 Quantenna Communications, Inc. **
** All Rights Reserved **
** **
** File : ieee80211_tdls.c **
** Description : Tunnelled Direct-Link Setup **
** **
** This module implements the IEEE Std 802.11z specification as well as a **
** proprietary discovery mechanism. **
** **
*******************************************************************************
** **
** Redistribution and use in source and binary forms, with or without **
** modification, are permitted provided that the following conditions **
** are met: **
** 1. Redistributions of source code must retain the above copyright **
** notice, this list of conditions and the following disclaimer. **
** 2. Redistributions in binary form must reproduce the above copyright **
** notice, this list of conditions and the following disclaimer in the **
** documentation and/or other materials provided with the distribution. **
** 3. The name of the author may not be used to endorse or promote products **
** derived from this software without specific prior written permission. **
** **
** Alternatively, this software may be distributed under the terms of the **
** GNU General Public License ("GPL") version 2, or (at your option) any **
** later version as published by the Free Software Foundation. **
** **
** In the case this software is distributed under the GPL license, **
** you should have received a copy of the GNU General Public License **
** along with this software; if not, write to the Free Software **
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA **
** **
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR **
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES**
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. **
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, **
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT **
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,**
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY **
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT **
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF **
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **
** **
*******************************************************************************
EH0*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/sort.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/if_vlan.h>
#include <net/iw_handler.h>
#include "net80211/if_llc.h"
#include "net80211/if_ethersubr.h"
#include "net80211/if_media.h"
#include "net80211/ieee80211.h"
#include "net80211/ieee80211_tdls.h"
#include "net80211/ieee80211_var.h"
#include "net80211/ieee80211_dot11_msg.h"
#include "net80211/ieee80211_linux.h"
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
#include <linux/if_bridge.h>
#include "linux/net/bridge/br_private.h"
#endif
#define IEEE80211_TDLS_FRAME_MAX 512
#define IEEE80211_TDLS_MAX_BRIDGE_CLIENTS 256
#define IEEE80211_TDLS_BR_SUB_PORT_SHIFT 4
/*
* FIXME: Make bridge table extraction more efficient. Possible improvements:
* - Support a filter on the fillbuf call so that only the entries we want are
* returned. Or perhaps a new, specialised bridge function.
* - Build an ordered linked list of pointers to bridge entries instead of
* sorting after the fact. This would be faster and save stack space (only 4
* bytes per bridge entry), but we'd have to lock out bridge updates while
* building the IE from the pointers.
* - Maintain the linked list in the bridge instead of creating it during each
* call.
*
* FIXME: This bridge_entries pointer doesn't belong here, fix with above.
*/
static struct __fdb_entry *bridge_entries;
static const char *ieee80211_tdls_action_name[] = {
"setup request",
"setup response",
"setup confirm",
"teardown",
"peer traffic indication",
"channel switch request",
"channel switch response",
"peer PSM request",
"peer PSM response",
"peer traffic response",
"discovery request"
};
static const char *ieee80211_tdls_stats_string[] = {
"none",
"inactive",
"starting",
"active",
"idle"
};
static __inline int
bit_num(int32_t val)
{
int i;
for (i = 0; i < 32; i++) {
if (val == 0)
return i;
val = val >> 1;
}
return i;
}
const char *
ieee80211_tdls_action_name_get(uint8_t action)
{
if (action >= ARRAY_SIZE(ieee80211_tdls_action_name))
return "unknown";
return ieee80211_tdls_action_name[action];
}
const char *
ieee80211_tdls_status_string_get(uint8_t stats)
{
if (stats >= ARRAY_SIZE(ieee80211_tdls_stats_string))
return "unknown";
return ieee80211_tdls_stats_string[stats];
}
/*
* TDLS is not allowed when using TKIP
* Returns 0 if the security config is valid, else 1.
*/
static int
ieee80211_tdls_sec_mode_valid(struct ieee80211vap *vap)
{
if (vap->iv_flags & IEEE80211_F_PRIVACY) {
if (vap->iv_bss->ni_ucastkey.wk_ciphertype == IEEE80211_CIPHER_TKIP)
return 0;
}
return 1;
}
static int
ieee80211_tdls_get_privacy(struct ieee80211vap *vap)
{
return vap->iv_bss->ni_ucastkey.wk_ciphertype != IEEE80211_CIPHER_NONE;
}
int
ieee80211_tdls_get_smoothed_rssi(struct ieee80211vap *vap, struct ieee80211_node *ni)
{
struct ieee80211com *ic = vap->iv_ic;
int32_t rssi = ic->ic_rssi(ni);
int32_t cur_rssi = 0;
int32_t cur_smthd_rssi = 0;
if (rssi < -1 && rssi > -1200)
cur_rssi = rssi;
else if (ni->ni_rssi > 0)
/* Correct pseudo RSSIs that apparently still get into the node table */
cur_rssi = (ni->ni_rssi * 10) - 900;
cur_smthd_rssi = ic->ic_smoothed_rssi(ni);
if ((cur_smthd_rssi > -1) || (cur_smthd_rssi < -1200))
cur_smthd_rssi = cur_rssi;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Peer: %pM, rssi=%d\n", __func__,
ni->ni_macaddr, cur_smthd_rssi);
return cur_smthd_rssi;
}
static void
ieee80211_tdls_update_peer_assoc_bw(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211com *ic = peer_ni->ni_vap->iv_ic;
struct ieee80211_ie_htcap *htcap = (struct ieee80211_ie_htcap *)tdls->htcap;
struct ieee80211_ie_vhtcap *vhtcap = (struct ieee80211_ie_vhtcap *)tdls->vhtcap;
enum ieee80211_vhtop_chanwidth assoc_vhtop_bw;
enum ieee80211_vhtop_chanwidth bss_bw;
if (htcap && (ic->ic_bss_bw < BW_HT40))
peer_ni->ni_htcap.cap &= ~(IEEE80211_HTCAP_C_CHWIDTH40 |
IEEE80211_HTCAP_C_SHORTGI40);
if (vhtcap) {
switch (IEEE80211_VHTCAP_GET_CHANWIDTH(vhtcap)) {
case IEEE80211_VHTCAP_CW_160M:
assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_160MHZ;
break;
case IEEE80211_VHTCAP_CW_160_AND_80P80M:
assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_80PLUS80MHZ;
break;
case IEEE80211_VHTCAP_CW_80M_ONLY:
default:
assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_80MHZ;
break;
}
switch (ic->ic_bss_bw) {
case BW_HT160:
bss_bw = IEEE80211_VHTOP_CHAN_WIDTH_160MHZ;
break;
case BW_HT80:
bss_bw = IEEE80211_VHTOP_CHAN_WIDTH_80MHZ;
break;
case BW_HT40:
case BW_HT20:
default:
bss_bw = IEEE80211_VHTOP_CHAN_WIDTH_20_40MHZ;
break;
}
assoc_vhtop_bw = min(bss_bw, assoc_vhtop_bw);
peer_ni->ni_vhtop.chanwidth = min(ic->ic_vhtop.chanwidth, assoc_vhtop_bw);
if (IS_IEEE80211_11NG_VHT_ENABLED(ic)) {
assoc_vhtop_bw = IEEE80211_VHTOP_CHAN_WIDTH_20_40MHZ;
assoc_vhtop_bw = min(bss_bw, assoc_vhtop_bw);
peer_ni->ni_vhtop.chanwidth =
min(ic->ic_vhtop_24g.chanwidth, assoc_vhtop_bw);
}
}
}
static void
ieee80211_tdls_update_rates(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211com *ic;
if (!peer_ni || !tdls)
return;
ic = peer_ni->ni_vap->iv_ic;
if (tdls->rates)
ieee80211_parse_rates(peer_ni, tdls->rates, tdls->xrates);
if (tdls->htcap) {
peer_ni->ni_flags |= IEEE80211_NODE_HT;
ieee80211_parse_htcap(peer_ni, tdls->htcap);
if (tdls->htinfo)
ieee80211_parse_htinfo(peer_ni, tdls->htinfo);
if (IS_IEEE80211_VHT_ENABLED(ic) && tdls->vhtcap) {
peer_ni->ni_flags |= IEEE80211_NODE_VHT;
ieee80211_parse_vhtcap(peer_ni, tdls->vhtcap);
if (tdls->vhtop)
ieee80211_parse_vhtop(peer_ni, tdls->vhtop);
else
ieee80211_tdls_update_peer_assoc_bw(peer_ni, tdls);
} else {
peer_ni->ni_flags &= ~IEEE80211_NODE_VHT;
memset(&peer_ni->ni_vhtcap, 0, sizeof(peer_ni->ni_vhtcap));
memset(&peer_ni->ni_vhtop, 0, sizeof(peer_ni->ni_vhtop));
}
} else {
memset(&peer_ni->ni_htcap, 0, sizeof(peer_ni->ni_htcap));
memset(&peer_ni->ni_htinfo, 0, sizeof(peer_ni->ni_htinfo));
memset(&peer_ni->ni_vhtcap, 0, sizeof(peer_ni->ni_vhtcap));
memset(&peer_ni->ni_vhtop, 0, sizeof(peer_ni->ni_vhtop));
peer_ni->ni_flags &= ~IEEE80211_NODE_HT;
peer_ni->ni_flags &= ~IEEE80211_NODE_VHT;
}
ieee80211_fix_rate(peer_ni, IEEE80211_F_DONEGO |
IEEE80211_F_DOXSECT | IEEE80211_F_DODEL);
ieee80211_fix_ht_rate(peer_ni, IEEE80211_F_DONEGO |
IEEE80211_F_DOXSECT | IEEE80211_F_DODEL);
}
int
ieee80211_tdls_set_key(struct ieee80211vap *vap, struct ieee80211_node *ni)
{
struct ieee80211_key *wk;
const uint8_t *mac;
int error = 0;
if (ni == NULL)
return -1;
mac = ni->ni_macaddr;
wk = &ni->ni_ucastkey;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"[%s] Setting key bcast=%u\n", ether_sprintf(mac),
IEEE80211_IS_MULTICAST(mac));
if (wk->wk_keylen != 0) {
ieee80211_key_update_begin(vap);
error = vap->iv_key_set(vap, wk, mac);
ieee80211_key_update_end(vap);
}
return error;
}
int
ieee80211_tdls_del_key(struct ieee80211vap *vap, struct ieee80211_node *ni)
{
struct ieee80211_key *wk;
const uint8_t *mac;
int error = 0;
if (ni == NULL)
return -1;
mac = ni->ni_macaddr;
wk = &ni->ni_ucastkey;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"[%s] deleting key bcast=%u\n", ether_sprintf(mac),
IEEE80211_IS_MULTICAST(mac));
/* wk must be set to ni->ni_ucastkey for sw crypto */
wk->wk_ciphertype = 0;
wk->wk_keytsc = 0;
wk->wk_keylen = sizeof(wk->wk_key);
memset(wk->wk_key, 0, sizeof(wk->wk_key));
ieee80211_key_update_begin(vap);
error = vap->iv_key_delete(vap, wk, mac);
ieee80211_key_update_end(vap);
return error;
}
static void
ieee80211_create_tdls_peer(struct ieee80211vap *vap, struct ieee80211_node *peer_ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *ni = TAILQ_FIRST(&ic->ic_vaps)->iv_bss;
ieee80211_node_set_chan(ic, peer_ni);
peer_ni->ni_capinfo = ni->ni_capinfo;
peer_ni->ni_txpower = ni->ni_txpower;
peer_ni->ni_ath_flags = vap->iv_ath_cap;
peer_ni->ni_flags |= (IEEE80211_NODE_AUTH | IEEE80211_NODE_QOS);
peer_ni->ni_node_type = IEEE80211_NODE_TYPE_TDLS;
if (tdls && tdls->supp_chan)
ieee80211_parse_supp_chan(peer_ni, tdls->supp_chan);
ieee80211_tdls_update_rates(peer_ni, tdls);
ieee80211_update_current_mode(peer_ni);
peer_ni->ni_start_time_assoc = get_jiffies_64();
if (ic->ic_newassoc != NULL)
ic->ic_newassoc(peer_ni, 1);
}
void
ieee80211_tdls_update_uapsd_indicication_windows(struct ieee80211vap *vap)
{
if (vap->iv_opmode == IEEE80211_M_STA && vap->iv_ic->ic_set_tdls_param)
vap->iv_ic->ic_set_tdls_param(vap->iv_bss, IOCTL_TDLS_UAPSD_IND_WND,
(int)vap->tdls_uapsd_indicat_wnd);
}
static void
ieee80211_tdls_update_peer(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = peer_ni->ni_vap;
int ni_update_required = 0;
if (tdls == NULL)
return;
if ((tdls->act != IEEE80211_ACTION_TDLS_SETUP_REQ) &&
(tdls->act != IEEE80211_ACTION_TDLS_SETUP_RESP))
return;
if (tdls->supp_chan)
ieee80211_parse_supp_chan(peer_ni, tdls->supp_chan);
if (tdls->qtn_info) {
if (peer_ni->ni_qtn_assoc_ie == NULL)
ni_update_required = 1;
else if (memcmp(tdls->qtn_info, peer_ni->ni_qtn_assoc_ie,
sizeof(struct ieee80211_ie_qtn)))
ni_update_required = 1;
}
if (tdls->htcap && memcmp(tdls->htcap, &peer_ni->ni_ie_htcap,
sizeof(peer_ni->ni_ie_htcap)))
ni_update_required = 1;
if (tdls->vhtcap && memcmp(tdls->vhtcap, &peer_ni->ni_ie_vhtcap,
sizeof(peer_ni->ni_ie_vhtcap)))
ni_update_required = 1;
if (ni_update_required) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: update peer %pM, tdls_status: %d, ref_cnt = %d\n", __func__,
peer_ni->ni_macaddr, peer_ni->tdls_status, ieee80211_node_refcnt(peer_ni));
ieee80211_tdls_update_rates(peer_ni, tdls);
ieee80211_input_tdls_qtnie(peer_ni, vap, (struct ieee80211_ie_qtn *)tdls->qtn_info);
ieee80211_update_current_mode(peer_ni);
}
}
/*
* Find or create a peer node
* The returned node structure must be freed after use
* Returns a pointer to a node structure if successful, else NULL
*/
static struct ieee80211_node *
ieee80211_tdls_find_or_create_peer(struct ieee80211_node *ni, uint8_t *peer_mac,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *peer_ni = NULL;
peer_ni = ieee80211_find_node(&ic->ic_sta, peer_mac);
if (peer_ni == NULL) {
peer_ni = ieee80211_alloc_node(&ic->ic_sta, vap, peer_mac, "TDLS peer");
if (peer_ni == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: could not create peer node %pM\n",
__func__, peer_ni->ni_macaddr);
return NULL;
}
peer_ni->tdls_status = IEEE80211_TDLS_NODE_STATUS_INACTIVE;
if (ieee80211_aid_acquire(ic, peer_ni)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: could not create peer node %pM"
" - too many nodes\n",
__func__, peer_ni->ni_macaddr);
ieee80211_node_decref(peer_ni);
ieee80211_free_node(peer_ni);
return NULL;
}
IEEE80211_ADDR_COPY(peer_ni->ni_bssid, ni->ni_bssid);
if (tdls && tdls->qtn_info != NULL)
ieee80211_input_tdls_qtnie(peer_ni, vap,
(struct ieee80211_ie_qtn *)tdls->qtn_info);
/* Add node to macfw iv_aid_ni table */
ieee80211_create_tdls_peer(vap, peer_ni, tdls);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: created peer node %pM aid = %d, ref_cnt = %d\n", __func__,
peer_ni->ni_macaddr, IEEE80211_NODE_AID(peer_ni), ieee80211_node_refcnt(peer_ni));
} else {
ieee80211_tdls_update_peer(peer_ni, tdls);
}
return peer_ni;
}
int
ieee80211_tdls_send_event(struct ieee80211_node *peer_ni,
enum ieee80211_tdls_event event, void *data)
{
struct ieee80211vap *vap = peer_ni->ni_vap;
struct ieee80211_tdls_event_data event_data;
union iwreq_data wreq;
char *event_name = NULL;
switch (event) {
case IEEE80211_EVENT_TDLS:
event_name = "EVENT_TDLS";
break;
case IEEE80211_EVENT_STATION_LOW_ACK:
event_name = "EVENT_STA_LOW_ACK";
break;
default:
break;
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS:%s Send event %s, run operation %d\n", __func__,
event_name, *((enum ieee80211_tdls_operation *)data));
memset(&event_data, 0, sizeof(event_data));
strncpy(event_data.name, event_name, sizeof(event_data.name));
event_data.index = event;
event_data.sub_index = *((enum ieee80211_tdls_operation *)data);
memcpy(event_data.peer_mac, peer_ni->ni_macaddr, IEEE80211_ADDR_LEN);
memset(&wreq, 0, sizeof(wreq));
wreq.data.length = sizeof(event_data);
wireless_send_event(vap->iv_dev, IWEVCUSTOM, &wreq, (char *)&event_data);
return 0;
}
EXPORT_SYMBOL(ieee80211_tdls_send_event);
static struct tdls_peer_ps_info *
ieee80211_tdls_find_or_create_peer_ps_info(struct ieee80211vap *vap,
struct ieee80211_node *peer_ni)
{
int found = 0;
unsigned long flags;
struct tdls_peer_ps_info *peer_ps_info = NULL;
int hash = IEEE80211_NODE_HASH(peer_ni->ni_macaddr);
spin_lock_irqsave(&vap->tdls_ps_lock, flags);
LIST_FOREACH(peer_ps_info, &vap->tdls_ps_hash[hash], peer_hash) {
if (IEEE80211_ADDR_EQ(peer_ps_info->peer_addr, peer_ni->ni_macaddr)) {
found = 1;
break;
}
}
spin_unlock_irqrestore(&vap->tdls_ps_lock, flags);
if (found == 1)
return peer_ps_info;
MALLOC(peer_ps_info, struct tdls_peer_ps_info *,
sizeof(*peer_ps_info), M_DEVBUF, M_WAITOK);
if (peer_ps_info != NULL) {
memcpy(peer_ps_info->peer_addr, peer_ni->ni_macaddr, IEEE80211_ADDR_LEN);
peer_ps_info->tdls_path_down_cnt = 0;
peer_ps_info->tdls_link_disabled_ints = 0;
spin_lock_irqsave(&vap->tdls_ps_lock, flags);
LIST_INSERT_HEAD(&vap->tdls_ps_hash[hash], peer_ps_info, peer_hash);
spin_unlock_irqrestore(&vap->tdls_ps_lock, flags);
}
return peer_ps_info;
}
static void
ieee80211_tdls_peer_ps_info_decre(struct ieee80211vap *vap)
{
int i;
unsigned long flags;
struct tdls_peer_ps_info *peer_ps_info = NULL;
spin_lock_irqsave(&vap->tdls_ps_lock, flags);
for (i = 0; i < IEEE80211_NODE_HASHSIZE; i++) {
LIST_FOREACH(peer_ps_info, &vap->tdls_ps_hash[i], peer_hash) {
if ((peer_ps_info != NULL) &&
(peer_ps_info->tdls_link_disabled_ints > 0))
peer_ps_info->tdls_link_disabled_ints--;
}
}
spin_unlock_irqrestore(&vap->tdls_ps_lock, flags);
}
void
ieee80211_tdls_free_peer_ps_info(struct ieee80211vap *vap)
{
int i;
unsigned long flags;
struct tdls_peer_ps_info *peer_ps_info = NULL;
spin_lock_irqsave(&vap->tdls_ps_lock, flags);
for (i = 0; i < IEEE80211_NODE_HASHSIZE; i++) {
LIST_FOREACH(peer_ps_info, &vap->tdls_ps_hash[i], peer_hash) {
if (peer_ps_info != NULL) {
LIST_REMOVE(peer_ps_info, peer_hash);
FREE(peer_ps_info, M_DEVBUF);
}
}
}
spin_unlock_irqrestore(&vap->tdls_ps_lock, flags);
}
static int
ieee80211_tdls_state_should_move(struct ieee80211vap *vap,
struct ieee80211_node *peer_ni)
{
int mu = STATS_SU;
#define QTN_TDLS_RATE_CHANGE_THRSH 100
struct tdls_peer_ps_info *peer_ps_info = NULL;
int32_t cur_ap_tx_rate = vap->iv_bss->ni_shared_stats->tx[mu].avg_tx_phy_rate;
int32_t last_ap_tx_rate = vap->iv_bss->last_tx_phy_rate;
int32_t ap_rate_diff;
int should_move = 1;
peer_ps_info = ieee80211_tdls_find_or_create_peer_ps_info(vap, peer_ni);
if ((peer_ps_info) && (peer_ps_info->tdls_link_disabled_ints > 0)) {
if (cur_ap_tx_rate > last_ap_tx_rate)
ap_rate_diff = cur_ap_tx_rate - last_ap_tx_rate;
else
ap_rate_diff = last_ap_tx_rate - cur_ap_tx_rate;
if (ap_rate_diff > QTN_TDLS_RATE_CHANGE_THRSH) {
peer_ps_info->tdls_path_down_cnt = 0;
peer_ps_info->tdls_link_disabled_ints = 0;
} else {
should_move = 0;
}
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Peer: %pM link_disabled_ints = %d, "
"should %s move to next state\n", __func__, peer_ni->ni_macaddr,
(peer_ps_info == NULL) ? 0 : peer_ps_info->tdls_link_disabled_ints,
should_move ? "" : "not");
return should_move;
}
/*
* Decide if should try to setup TDLS link
* Return 0: shouldn't setup
* Return 1: setup TDLS link
*/
int
ieee80211_tdls_link_should_setup(struct ieee80211vap *vap,
struct ieee80211_node *peer_ni)
{
int should_setup = 1;
int32_t smthd_rssi = ieee80211_tdls_get_smoothed_rssi(vap, peer_ni);
if (IEEE80211_NODE_IS_TDLS_ACTIVE(peer_ni)) {
should_setup = 0;
goto OUT;
}
if (vap->tdls_path_sel_prohibited == 1)
goto OUT;
/*
* if peer is 3rd-party STA, we will establish TDLS Link first, then send trainning packet,
* otherwise, training packets will be treated as attacking packets by 3rd-party STA,
* and it send deauth frame to QTN-STA, the result is not what we exspect.
*/
if ((vap->tdls_discovery_interval > 0) && peer_ni->ni_qtn_assoc_ie) {
should_setup = 0;
goto OUT;
}
if (smthd_rssi < vap->tdls_min_valid_rssi) {
should_setup = 0;
goto OUT;
}
if (timer_pending(&vap->tdls_rate_detect_timer))
should_setup = ieee80211_tdls_state_should_move(vap, peer_ni);
OUT:
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: %s peer: %pM RSSI = %d, should %s setup TDLS link\n",
__func__, peer_ni->ni_qtn_assoc_ie ? "qtn" : "non-qtn",
peer_ni->ni_macaddr, smthd_rssi, should_setup ? "" : "not");
return should_setup;
}
int
ieee80211_tdls_link_should_response(struct ieee80211vap *vap,
struct ieee80211_node *peer_ni)
{
int should_resp = 1;
int32_t smthd_rssi = ieee80211_tdls_get_smoothed_rssi(vap, peer_ni);
if (vap->tdls_path_sel_prohibited == 1)
goto OUT;
if (smthd_rssi < vap->tdls_min_valid_rssi) {
should_resp = 0;
goto OUT;
}
if (timer_pending(&vap->tdls_rate_detect_timer))
should_resp = ieee80211_tdls_state_should_move(vap, peer_ni);
OUT:
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Peer: %pM RSSI = %d, should %s send response frame\n",
__func__, peer_ni->ni_macaddr, smthd_rssi, should_resp ? "" : "not");
return should_resp;
}
/*
* Decide if TDLS link should be torn down or established
* Return 0: teardown TDLS link
* Return 1: establish TDLS link
*/
static int
ieee80211_tdls_data_path_selection(struct ieee80211vap *vap,
struct ieee80211_node *peer_ni)
{
int mu = STATS_SU;
int32_t last_ap_tx_rate = vap->iv_bss->last_tx_phy_rate;
int32_t last_peer_tx_rate = peer_ni->last_tx_phy_rate;
int32_t avg_ap_tx_rate = 0;
int32_t avg_peer_tx_rate = 0;
int32_t smthd_rssi = 0;
int is_tdls_path = -1;
int use_tdls_path = 0;
int32_t cur_ap_tx_rate = vap->iv_bss->ni_shared_stats->tx[mu].avg_tx_phy_rate;
int32_t cur_peer_tx_rate = peer_ni->ni_shared_stats->tx[mu].avg_tx_phy_rate;
if (vap->tdls_path_sel_prohibited == 1) {
is_tdls_path = 1;
goto out;
}
if (last_ap_tx_rate <= 0)
last_ap_tx_rate = cur_ap_tx_rate;
avg_ap_tx_rate = last_ap_tx_rate * vap->tdls_phy_rate_wgt / 10 +
cur_ap_tx_rate * (10 - vap->tdls_phy_rate_wgt) / 10;
if (last_peer_tx_rate <= 0)
last_peer_tx_rate = cur_peer_tx_rate;
avg_peer_tx_rate = last_peer_tx_rate * vap->tdls_phy_rate_wgt / 10 +
cur_peer_tx_rate * (10 - vap->tdls_phy_rate_wgt) / 10;
smthd_rssi = ieee80211_tdls_get_smoothed_rssi(vap, peer_ni);
if (smthd_rssi >= vap->tdls_min_valid_rssi) {
if ((vap->iv_bss->ni_training_flag == NI_TRAINING_END) &&
(peer_ni->ni_training_flag == NI_TRAINING_END)) {
/*
* Use the TDLS path if the Tx rate is better than a predefined proportion
* of the Tx rate via the AP. E.g. if the weighting is 8, then the direct
* rate must be at least 80% of the rate via the AP.
*/
if ((avg_peer_tx_rate > vap->tdls_path_sel_rate_thrshld) &&
(avg_peer_tx_rate >= avg_ap_tx_rate * vap->tdls_path_sel_weight / 10))
use_tdls_path = 1;
else
use_tdls_path = 0;
} else {
use_tdls_path = 1;
}
} else {
use_tdls_path = 0;
}
if (use_tdls_path != peer_ni->tdls_last_path_sel)
peer_ni->tdls_path_sel_num = 0;
if (use_tdls_path == 0)
peer_ni->tdls_path_sel_num--;
else
peer_ni->tdls_path_sel_num++;
if (peer_ni->tdls_path_sel_num >= vap->tdls_switch_ints)
is_tdls_path = 1;
else if (peer_ni->tdls_path_sel_num <= (0 - vap->tdls_switch_ints))
is_tdls_path = 0;
vap->iv_bss->last_tx_phy_rate = avg_ap_tx_rate;
peer_ni->last_tx_phy_rate = avg_peer_tx_rate;
peer_ni->tdls_last_path_sel = use_tdls_path;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: peer %pM rssi=%d, path_sel_num=%d, avg_ap_rate=%d, avg_peer_rate=%d\n",
__func__, peer_ni->ni_macaddr, smthd_rssi, peer_ni->tdls_path_sel_num,
avg_ap_tx_rate, avg_peer_tx_rate);
out:
if (is_tdls_path == 1)
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Creating TDLS data link with %pM\n",
__func__, peer_ni->ni_macaddr);
else if (is_tdls_path == 0)
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Tearing down TDLS data link with %pM\n",
__func__, peer_ni->ni_macaddr);
return is_tdls_path;
}
int ieee80211_tdls_remain_on_channel(struct ieee80211vap *vap,
struct ieee80211_node *ni, uint8_t chan, uint8_t bandwidth,
uint64_t start_tsf, uint32_t timeout, uint32_t duration)
{
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *newchan = NULL;
int ret = 0;
if (ic->ic_flags & IEEE80211_F_CHANSWITCH) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Channel switch already in progress, owner=%d\n",
__func__, ic->ic_csa_reason);
return -1;
}
newchan = ic->ic_findchannel(ic, chan, ic->ic_des_mode);
if (newchan == NULL) {
newchan = ic->ic_findchannel(ic, chan, IEEE80211_MODE_AUTO);
if (newchan == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS,
IEEE80211_TDLS_MSG_DBG, "TDLS %s: Fail to find target channel\n", __func__);
return -1;
}
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Start to switch channel %d (bw:%d), start_tsf: %llu, duration: %u\n",
__func__, chan, bandwidth, start_tsf, duration);
ret = ic->ic_remain_on_channel(ic, ni, newchan, bandwidth, start_tsf, timeout, duration, 0);
if (!ret)
vap->tdls_cs_node = ni;
return ret;
}
/*
* Initialise an action frame
*/
static struct sk_buff *
ieee80211_tdls_init_frame(struct ieee80211_node *ni, uint8_t **frm_p,
uint8_t action, uint8_t direct)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ether_header *eh;
struct sk_buff *skb;
uint8_t *frm = *frm_p;
uint8_t payload_type = IEEE80211_SNAP_TYPE_TDLS;
uint16_t frm_len = IEEE80211_TDLS_FRAME_MAX;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_NODE, IEEE80211_TDLS_MSG_DBG,
"%s: Sending %s\n", __func__, ieee80211_tdls_action_name_get(action));
skb = dev_alloc_skb(frm_len);
if (skb == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_NODE, IEEE80211_TDLS_MSG_WARN,
"%s: cannot get buf; size %u", __func__, frm_len);
vap->iv_stats.is_tx_nobuf++;
return NULL;
}
frm = skb_put(skb, frm_len);
skb->priority = WME_AC_VI; /* "unless specified otherwise" 11.21.2 */
M_FLAG_SET(skb, M_CLASSIFY);
eh = (struct ether_header *)frm;
if (direct) {
IEEE80211_ADDR_COPY(eh->ether_dhost, ni->ni_macaddr);
IEEE80211_ADDR_COPY(eh->ether_shost, vap->iv_myaddr);
} else {
IEEE80211_ADDR_COPY(eh->ether_dhost, vap->iv_dev->broadcast);
IEEE80211_ADDR_COPY(eh->ether_shost, vap->iv_myaddr);
}
eh->ether_type = htons(ETHERTYPE_80211MGT);
frm += ETHER_HDR_LEN;
*frm++ = payload_type;
*frm++ = IEEE80211_ACTION_CAT_TDLS;
*frm++ = action;
*frm_p = frm;
return skb;
}
static int
ieee80211_tdls_over_qhop_enabled(struct ieee80211vap *vap)
{
uint8_t ext_role = vap->iv_bss->ni_ext_role;
return (vap->tdls_over_qhop_en &&
(ext_role != IEEE80211_EXTENDER_ROLE_NONE));
}
static int
ieee80211_tdls_ext_bssid_allowed(struct ieee80211vap *vap, u8 *bssid)
{
struct ieee80211_node *ni = vap->iv_bss;
struct ieee80211_qtn_ext_bssid *ext_bssid;
int i;
if (!ni || !ni->ni_ext_bssid_ie)
return 0;
ext_bssid = (struct ieee80211_qtn_ext_bssid *)ni->ni_ext_bssid_ie;
if (!is_zero_ether_addr(ext_bssid->mbs_bssid) &&
IEEE80211_ADDR_EQ(ext_bssid->mbs_bssid, bssid)) {
return 1;
} else {
for (i = 0; i < QTN_MAX_RBS_NUM; i++) {
if (!is_zero_ether_addr(ext_bssid->rbs_bssid[i]) &&
IEEE80211_ADDR_EQ(ext_bssid->rbs_bssid[i], bssid))
return 1;
}
}
return 0;
}
static int
ieee80211_tdls_copy_link_id(struct ieee80211_node *ni, uint8_t **frm,
struct ieee80211_tdls_action_data *data)
{
uint8_t *ie;
uint8_t *ie_end;
if (!ni || !frm || !data)
return 0;
ie = data->ie_buf;
ie_end = ie + data->ie_buflen;
while (ie < ie_end) {
switch (*ie) {
case IEEE80211_ELEMID_TDLS_LINK_ID:
memcpy(*frm, ie, ie[1] + 2);
*frm += ie[1] + 2;
return 1;
default:
break;
}
ie += ie[1] + 2;
}
return 0;
}
static int
ieee80211_tdls_add_tlv_link_id(struct ieee80211_node *ni,
struct sk_buff *skb, uint8_t action, uint8_t **frm, uint8_t *da,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211_tdls_link_id *link_id;
if ((*frm + sizeof(*link_id)) > (skb->data + skb->len)) {
IEEE80211_DPRINTF(vap, IEEE80211_MSG_ELEMID,
"[%s] TDLS %s frame is too big\n", vap->iv_dev->name,
ieee80211_tdls_action_name_get(action));
vap->iv_stats.is_rx_elem_toosmall++;
return 1;
}
if (!ieee80211_tdls_copy_link_id(ni, frm, data)) {
link_id = (struct ieee80211_tdls_link_id *)*frm;
link_id->id = IEEE80211_ELEMID_TDLS_LINK_ID;
link_id->len = sizeof(*link_id) - 2;
IEEE80211_ADDR_COPY(link_id->bssid, ni->ni_bssid);
switch (action) {
case IEEE80211_ACTION_TDLS_DISC_REQ:
IEEE80211_ADDR_COPY(link_id->init_sa, vap->iv_myaddr);
IEEE80211_ADDR_COPY(link_id->resp_sa, da);
break;
case IEEE80211_ACTION_PUB_TDLS_DISC_RESP:
IEEE80211_ADDR_COPY(link_id->init_sa, da);
IEEE80211_ADDR_COPY(link_id->resp_sa, vap->iv_myaddr);
break;
case IEEE80211_ACTION_TDLS_SETUP_REQ:
case IEEE80211_ACTION_TDLS_SETUP_RESP:
case IEEE80211_ACTION_TDLS_SETUP_CONFIRM:
case IEEE80211_ACTION_TDLS_TEARDOWN:
case IEEE80211_ACTION_TDLS_PTI:
case IEEE80211_ACTION_TDLS_PEER_TRAF_RESP:
case IEEE80211_ACTION_TDLS_CS_REQ:
case IEEE80211_ACTION_TDLS_CS_RESP:
/*
* tdls_initiator means who starts to setup TDLS link firstly.
* 1 indicates our own peer setups TDLS link.
* 0 indicates the other peer setups TDLS link.
*/
if (ni->tdls_initiator) {
IEEE80211_ADDR_COPY(link_id->init_sa, vap->iv_myaddr);
IEEE80211_ADDR_COPY(link_id->resp_sa, da);
} else {
IEEE80211_ADDR_COPY(link_id->init_sa, da);
IEEE80211_ADDR_COPY(link_id->resp_sa, vap->iv_myaddr);
}
break;
}
*frm += sizeof(*link_id);
}
return 0;
}
static int
ieee80211_tdls_add_cap(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
uint8_t *cap_p = *frm;
uint16_t capinfo = 0;
if (vap->iv_opmode == IEEE80211_M_IBSS)
capinfo = IEEE80211_CAPINFO_IBSS;
else
capinfo = IEEE80211_CAPINFO_ESS;
if (vap->iv_flags & IEEE80211_F_PRIVACY)
capinfo |= IEEE80211_CAPINFO_PRIVACY;
if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) &&
IEEE80211_IS_CHAN_2GHZ(ic->ic_bsschan))
capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE;
if (ic->ic_flags & IEEE80211_F_SHSLOT)
capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME;
if (ic->ic_flags & IEEE80211_F_DOTH)
capinfo |= IEEE80211_CAPINFO_SPECTRUM_MGMT;
*(__le16 *)cap_p = htole16(capinfo);
*frm += 2;
return 0;
}
static int
ieee80211_tdls_add_tlv_rates(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
uint32_t mode = ic->ic_curmode;
struct ieee80211_rateset *rs = &ic->ic_sup_rates[mode];
uint8_t *ie = *frm;
int nrates;
*ie++ = IEEE80211_ELEMID_RATES;
nrates = rs->rs_legacy_nrates;
if (nrates > IEEE80211_RATE_SIZE)
nrates = IEEE80211_RATE_SIZE;
*ie++ = nrates;
memcpy(ie, rs->rs_rates, nrates);
*frm += nrates + 2;
return 0;
}
static int
ieee80211_tdls_add_tlv_country(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
uint8_t *ie = *frm;
if (ic->ic_country_ie.country_len > 0) {
memcpy(ie, (uint8_t *)&ic->ic_country_ie,
ic->ic_country_ie.country_len + 2);
*frm += ic->ic_country_ie.country_len + 2;
}
return 0;
}
static int
ieee80211_tdls_add_tlv_xrates(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
uint32_t mode = ic->ic_curmode;
struct ieee80211_rateset *rs = &ic->ic_sup_rates[mode];
uint8_t *ie = *frm;
int nrates = 0;
if (rs->rs_nrates > IEEE80211_RATE_SIZE) {
nrates = rs->rs_legacy_nrates - IEEE80211_RATE_SIZE;
if (nrates) {
*ie++ = IEEE80211_ELEMID_XRATES;
*ie++ = nrates;
memcpy(ie, rs->rs_rates + IEEE80211_RATE_SIZE, nrates);
*frm += nrates + 2;
}
}
return 0;
}
static int
ieee80211_tdls_add_tlv_rsn(struct ieee80211_node *ni, uint8_t **frm,
struct ieee80211_tdls_action_data *data)
{
uint8_t *ie;
uint8_t *ie_end;
if ((!ni) || (!frm) || (!data))
return 1;
ie = data->ie_buf;
ie_end = ie + data->ie_buflen;
while (ie < ie_end) {
switch (*ie) {
case IEEE80211_ELEMID_RSN:
memcpy(*frm, ie, ie[1] + 2);
*frm += ie[1] + 2;
break;
default:
break;
}
ie += ie[1] + 2;
}
return 0;
}
static int
ieee80211_tdls_add_tlv_ext_cap(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
uint8_t *excap_p = *frm;
uint32_t excapinfo[2] = {0, 0};
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_PROHIB)
excapinfo[1] |= IEEE80211_EXTCAP2_TDLS_PROHIB;
else
excapinfo[1] |= IEEE80211_EXTCAP2_TDLS;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB)
excapinfo[1] |= IEEE80211_EXTCAP2_TDLS_CS_PROHIB;
else
excapinfo[0] |= IEEE80211_EXTCAP1_TDLS_CS;
excapinfo[0] |= IEEE80211_EXTCAP1_TDLS_UAPSD;
*excap_p++ = IEEE80211_ELEMID_EXTCAP;
*excap_p++ = IEEE8211_EXTCAP_LENGTH;
*(__le32 *)excap_p = htole32(excapinfo[0]);
excap_p += 4;
*(__le32 *)excap_p = htole32(excapinfo[1]);
*frm += IEEE8211_EXTCAP_LENGTH + 2;
return 0;
}
static int
ieee80211_tdls_add_tlv_qos_cap(struct ieee80211_node *ni, uint8_t **frm)
{
static const u_int8_t oui[3] = {0x00, 0x50, 0xf2};
struct ieee80211_ie_wme *ie = (struct ieee80211_ie_wme *) *frm;
ie->wme_id = IEEE80211_ELEMID_VENDOR;
ie->wme_len = sizeof(*ie) - 2;
memcpy(ie->wme_oui,oui,sizeof(oui));
ie->wme_type = WME_OUI_TYPE;
ie->wme_subtype = WME_INFO_OUI_SUBTYPE;
ie->wme_version = WME_VERSION;
ie->wme_info = 0;
ie->wme_info |= WME_UAPSD_MASK;
*frm += sizeof(*ie);
return 0;
}
static int
ieee80211_tdls_add_tlv_edca_param(struct ieee80211_node *ni, uint8_t **frm)
{
#define ADDSHORT(frm, v) do { \
frm[0] = (v) & 0xff; \
frm[1] = (v) >> 8; \
frm += 2; \
} while (0)
static const u_int8_t oui[3] = {0x00, 0x50, 0xf2};
struct ieee80211_wme_param *ie = (struct ieee80211_wme_param *) *frm;
struct ieee80211_wme_state *wme = &ni->ni_ic->ic_wme;
struct ieee80211vap *vap = ni->ni_vap;
u_int8_t *frame_p = NULL;
int i;
ie->param_id = IEEE80211_ELEMID_VENDOR;
ie->param_len = sizeof(*ie) - 2;
memcpy(ie->param_oui,oui,sizeof(oui));
ie->param_oui_type = WME_OUI_TYPE;
ie->param_oui_sybtype = WME_PARAM_OUI_SUBTYPE;
ie->param_version = WME_VERSION;
ie->param_qosInfo = 0;
ie->param_qosInfo |= WME_UAPSD_MASK;
ie->param_reserved = 0;
frame_p = *frm + 10;
for(i = 0; i < WME_NUM_AC; i++) {
const struct wmm_params *ac;
ac = &wme->wme_bssChanParams.cap_wmeParams[i];
*frame_p++ = SM(i, WME_PARAM_ACI) |
SM(ac->wmm_acm, WME_PARAM_ACM) |
SM(ac->wmm_aifsn, WME_PARAM_AIFSN);
*frame_p++ = SM(ac->wmm_logcwmax, WME_PARAM_LOGCWMAX) |
SM(ac->wmm_logcwmin, WME_PARAM_LOGCWMIN);
ADDSHORT(frame_p, ac->wmm_txopLimit);
}
*frm += sizeof(*ie);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"ieee80211_tdls_add_tlv_edca_param: add edca parameter qos info"
" is [%02x] reserve is [%02x]\n", ie->param_qosInfo, ie->param_reserved);
return 0;
#undef ADDSHORT
}
static int
ieee80211_tdls_add_tlv_ftie(struct ieee80211_node *ni, uint8_t **frm,
struct ieee80211_tdls_action_data *data)
{
uint8_t *ie;
uint8_t *ie_end;
if ((!ni) || (!frm) || (!data))
return 1;
ie = data->ie_buf;
ie_end = ie + data->ie_buflen;
while (ie < ie_end) {
switch (*ie) {
case IEEE80211_ELEMID_FTIE:
memcpy(*frm, ie, ie[1] + 2);
*frm += ie[1] + 2;
break;
default:
break;
}
ie += ie[1] + 2;
}
return 0;
}
static int
ieee80211_tdls_add_tlv_tpk_timeout(struct ieee80211_node *ni, uint8_t **frm,
struct ieee80211_tdls_action_data *data)
{
uint8_t *ie;
uint8_t *ie_end;
if ((!ni) || (!frm) || (!data))
return 1;
ie = data->ie_buf;
ie_end = ie + data->ie_buflen;
while (ie < ie_end) {
switch (*ie) {
case IEEE80211_ELEMID_TIMEOUT_INT:
memcpy(*frm, ie, ie[1] + 2);
*frm += ie[1] + 2;
break;
default:
break;
}
ie += ie[1] + 2;
}
return 0;
}
static int
ieee80211_tdls_add_tlv_sup_reg_class(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
uint8_t *ie = *frm;
uint8_t *ie_len = *frm + 1;
uint8_t cur_reg_class;
int bandwidth;
int i;
bandwidth = ieee80211_get_bw(ic);
cur_reg_class = ieee80211_get_current_operating_class(ic->ic_country_code,
ic->ic_bsschan->ic_ieee, bandwidth);
*ie++ = IEEE80211_ELEMID_REG_CLASSES;
*ie++ = 1;
*ie++ = cur_reg_class;
for (i = 0; i < IEEE80211_OPER_CLASS_MAX; i++) {
if (isset(ic->ic_oper_class, i)) {
*ie++ = i;
(*ie_len)++;
}
}
*frm += *ie_len + 2;
return 0;
}
static int
ieee80211_tdls_add_tlv_ht_cap(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_htcap htcap;
if (!IS_IEEE80211_11NA(ic) && !IS_IEEE80211_11NG(ic) &&
!IS_IEEE80211_VHT_ENABLED(ic))
return 0;
memcpy(&htcap, &ic->ic_htcap, sizeof(htcap));
if (ic->ic_bss_bw < BW_HT40)
htcap.cap &= ~(IEEE80211_HTCAP_C_CHWIDTH40 |
IEEE80211_HTCAP_C_SHORTGI40);
*frm = ieee80211_add_htcap(ni, *frm, &htcap,
IEEE80211_FC0_SUBTYPE_ACTION);
return 0;
}
static int
ieee80211_tdls_add_tlv_ht_oper(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211com *ic = ni->ni_vap->iv_ic;
struct ieee80211_htinfo htinfo;
int16_t htinfo_channel_width = 0;
int16_t htinfo_2nd_channel_offset = 0;
if (!IS_IEEE80211_11NA(ic) && !IS_IEEE80211_11NG(ic) &&
!IS_IEEE80211_VHT_ENABLED(ic))
return 0;
memcpy(&htinfo, &ic->ic_htinfo, sizeof(htinfo));
ieee80211_get_channel_bw_offset(ic, &htinfo_channel_width,
&htinfo_2nd_channel_offset);
if (IEEE80211_IS_CHAN_ANYN(ic->ic_bsschan) &&
(ic->ic_curmode >= IEEE80211_MODE_11NA)) {
htinfo.ctrlchannel = ieee80211_chan2ieee(ic, ic->ic_bsschan);
if (ic->ic_bss_bw >= BW_HT40) {
htinfo.byte1 |= (htinfo_channel_width ?
IEEE80211_HTINFO_B1_REC_TXCHWIDTH_40 : 0x0);
htinfo.choffset = htinfo_2nd_channel_offset;
} else {
htinfo.byte1 &= ~IEEE80211_HTINFO_B1_REC_TXCHWIDTH_40;
htinfo.choffset = IEEE80211_HTINFO_CHOFF_SCN;
}
}
*frm = ieee80211_add_htinfo(ni, *frm, &htinfo);
return 0;
}
static int
ieee80211_tdls_add_tlv_bss_2040_coex(struct ieee80211_node *ni, u_int8_t **frm)
{
uint8_t coex = 0;
/* Test case 5.9 requires information request bit must be set */
coex |= WLAN_20_40_BSS_COEX_INFO_REQ;
*frm = ieee80211_add_20_40_bss_coex_ie(*frm, coex);
return 0;
}
static void
ieee80211_tdls_add_sec_chan_off(uint8_t **frm,
struct ieee80211vap *vap, uint8_t csa_chan)
{
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *chan = NULL;
uint8_t sec_position = IEEE80211_HTINFO_EXTOFFSET_NA;
struct ieee80211_ie_sec_chan_off *sco = (struct ieee80211_ie_sec_chan_off *)(*frm);
chan = ieee80211_find_channel_by_ieee(ic, csa_chan);
if (!chan) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: failed to find the target channel %u\n", __func__, csa_chan);
return;
}
if (chan->ic_flags & IEEE80211_CHAN_HT40D)
sec_position = IEEE80211_HTINFO_EXTOFFSET_BELOW;
else if (chan->ic_flags & IEEE80211_CHAN_HT40U)
sec_position = IEEE80211_HTINFO_EXTOFFSET_ABOVE;
sco->sco_id = IEEE80211_ELEMID_SEC_CHAN_OFF;
sco->sco_len = 1;
sco->sco_off = sec_position;
*frm += sizeof(struct ieee80211_ie_sec_chan_off);
return;
}
static int
ieee80211_tdls_add_tlv_2nd_chan_off(struct ieee80211_node *ni,
u_int8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *newchan = NULL;
if (vap->tdls_off_chan_bw < BW_HT40)
return 0;
newchan = ic->ic_findchannel(ic, vap->tdls_target_chan, IEEE80211_MODE_AUTO);
if (newchan == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: failed to find the target channel %u\n",
__func__, vap->tdls_target_chan);
return -1;
}
ieee80211_tdls_add_sec_chan_off(frm, vap, newchan->ic_ieee);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"%s: Add second channel offset IE\n", __func__);
return 0;
}
static int
ieee80211_tdls_add_tlv_vhtcap(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211com *ic = ni->ni_vap->iv_ic;
if (!IS_IEEE80211_VHT_ENABLED(ic))
return 0;
*frm = ieee80211_add_vhtcap(ni, *frm, &ic->ic_vhtcap,
IEEE80211_FC0_SUBTYPE_ACTION);
return 0;
}
static int
ieee80211_tdls_add_tlv_vhtop(struct ieee80211_node *ni, uint8_t **frm)
{
struct ieee80211com *ic = ni->ni_vap->iv_ic;
struct ieee80211_vhtop vhtop;
uint8_t bw = BW_HT20;
if (!IS_IEEE80211_VHT_ENABLED(ic))
return 0;
memcpy(&vhtop, &ic->ic_vhtop, sizeof(vhtop));
if (IEEE80211_IS_VHT_20(ic))
bw = MIN(ic->ic_bss_bw, BW_HT20);
else if (IEEE80211_IS_VHT_40(ic))
bw = MIN(ic->ic_bss_bw, BW_HT40);
else if (IEEE80211_IS_VHT_80(ic))
bw = MIN(ic->ic_bss_bw, BW_HT80);
else if (IEEE80211_IS_VHT_160(ic))
bw = MIN(ic->ic_bss_bw, BW_HT160);
if ((bw == BW_HT20) || (bw == BW_HT40)) {
vhtop.chanwidth = IEEE80211_VHTOP_CHAN_WIDTH_20_40MHZ;
} else if (bw == BW_HT80) {
vhtop.chanwidth = IEEE80211_VHTOP_CHAN_WIDTH_80MHZ;
vhtop.centerfreq0 = ic->ic_bsschan->ic_center_f_80MHz;
} else if (bw == BW_HT160) {
vhtop.chanwidth = IEEE80211_VHTOP_CHAN_WIDTH_160MHZ;
vhtop.centerfreq0 = ic->ic_bsschan->ic_center_f_160MHz;
}
memcpy(&ni->ni_vhtop, &vhtop, sizeof(ni->ni_vhtop));
*frm = ieee80211_add_vhtop(ni, *frm, &vhtop);
return 0;
}
static int
ieee80211_tdls_add_tlv_aid(struct ieee80211_node *ni, u_int8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_ie_aid *aid = (struct ieee80211_ie_aid *)(*frm);
if (!IS_IEEE80211_VHT_ENABLED(ic))
return 0;
aid->aid_id= IEEE80211_ELEMID_AID;
aid->aid_len= 2;
aid->aid = htole16(vap->iv_bss->ni_associd);
*frm += sizeof(struct ieee80211_ie_aid);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"%s: Add AID IE, AID = %d\n", __func__, aid->aid);
return 0;
}
static int
ieee80211_tdls_add_tlv_wide_bw_cs(struct ieee80211_node *ni,
u_int8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_ie_wbchansw *ie = (struct ieee80211_ie_wbchansw *)(*frm);
struct ieee80211_channel *des_chan = NULL;
u_int32_t chwidth = 0;
if (vap->tdls_off_chan_bw <= BW_HT40)
return 0;
des_chan = ic->ic_findchannel(ic, vap->tdls_target_chan, IEEE80211_MODE_AUTO);
if (des_chan == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Fail to find the target channel\n", __func__);
return -1;
}
ie->wbcs_id = IEEE80211_ELEMID_WBWCHANSWITCH;
ie->wbcs_len = sizeof(struct ieee80211_ie_wbchansw) - 2;
switch (vap->tdls_off_chan_bw) {
case BW_HT20:
case BW_HT40:
chwidth = IEEE80211_VHTOP_CHAN_WIDTH_20_40MHZ;
break;
case BW_HT80:
chwidth = IEEE80211_VHTOP_CHAN_WIDTH_80MHZ;
break;
default:
chwidth = IEEE80211_VHTOP_CHAN_WIDTH_80MHZ;
}
ie->wbcs_newchanw = chwidth;
if (vap->tdls_off_chan_bw == BW_HT40) {
ie->wbcs_newchancf0 = des_chan->ic_center_f_40MHz;
ie->wbcs_newchancf1 = 0;
} else if (vap->tdls_off_chan_bw == BW_HT80) {
ie->wbcs_newchancf0 = des_chan->ic_center_f_80MHz;
ie->wbcs_newchancf1 = 0;
} else {
ie->wbcs_newchancf0 = 0;
ie->wbcs_newchancf1 = 0;
}
*frm += sizeof(struct ieee80211_ie_wbchansw);
return 0;
}
static int
ieee80211_tdls_add_tlv_vht_tx_power_evlope(struct ieee80211_node *ni,
u_int8_t **frm)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_ie_vtxpwren *ie = (struct ieee80211_ie_vtxpwren *)(*frm);
u_int8_t local_max_tx_pwrcnt = 0;
struct ieee80211_channel *des_chan = NULL;
if (!IS_IEEE80211_VHT_ENABLED(ic) || !(ic->ic_flags & IEEE80211_F_DOTH))
return 0;
des_chan = ic->ic_findchannel(ic, vap->tdls_target_chan, IEEE80211_MODE_AUTO);
if (des_chan == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Fail to find the target channel\n", __func__);
return -1;
}
switch (vap->tdls_off_chan_bw) {
case BW_HT20:
local_max_tx_pwrcnt = IEEE80211_TX_POW_FOR_20MHZ;
break;
case BW_HT40:
local_max_tx_pwrcnt = IEEE80211_TX_POW_FOR_40MHZ;
break;
case BW_HT80:
local_max_tx_pwrcnt = IEEE80211_TX_POW_FOR_80MHZ;
break;
default:
local_max_tx_pwrcnt = IEEE80211_TX_POW_FOR_80MHZ;
}
ie->vtxpwren_id = IEEE80211_ELEMID_VHTXMTPWRENVLP;
ie->vtxpwren_len = sizeof(struct ieee80211_ie_vtxpwren) - 2;
ie->vtxpwren_txpwr_info = local_max_tx_pwrcnt;
ie->vtxpwren_tp20 = des_chan->ic_maxregpower - ic->ic_pwr_constraint;
ie->vtxpwren_tp40 = des_chan->ic_maxregpower - ic->ic_pwr_constraint;
ie->vtxpwren_tp80 = des_chan->ic_maxregpower - ic->ic_pwr_constraint;
ie->vtxpwren_tp160 = 0;
*frm += sizeof(struct ieee80211_ie_vtxpwren);
return 0;
}
static uint8_t *
ieee80211_tdls_find_cs_timing(uint8_t *buf, uint32_t buf_len)
{
uint8_t *ie = buf;
uint8_t *ie_end = ie + buf_len;
while (ie < ie_end) {
switch (*ie) {
case IEEE80211_ELEMID_TDLS_CS_TIMING:
return ie;
default:
break;
}
ie += ie[1] + 2;
}
return NULL;
}
static int
ieee80211_tdls_copy_cs_timing(struct ieee80211_node *ni, uint8_t **frm,
struct ieee80211_tdls_action_data *data)
{
uint8_t *ie;
uint8_t *ie_end;
if (!ni || !frm || !data)
return 1;
ie = data->ie_buf;
ie_end = ie + data->ie_buflen;
while (ie < ie_end) {
switch (*ie) {
case IEEE80211_ELEMID_TDLS_CS_TIMING:
memcpy(*frm, ie, ie[1] + 2);
*frm += ie[1] + 2;
return 0;
default:
break;
}
ie += ie[1] + 2;
}
return 1;
}
static int
ieee80211_tdls_add_tlv_cs_timimg(struct ieee80211_node *ni, u_int8_t **frm,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211_tdls_cs_timing *cs_timing = (struct ieee80211_tdls_cs_timing *)(*frm);
uint16_t sw_time;
uint16_t sw_timeout;
sw_time = DEFAULT_TDLS_CH_SW_NEGO_TIME;
sw_timeout = DEFAULT_TDLS_CH_SW_NEGO_TIME + DEFAULT_TDLS_CH_SW_TIMEOUT;
cs_timing->id = IEEE80211_ELEMID_TDLS_CS_TIMING;
cs_timing->len = 4;
cs_timing->switch_time = cpu_to_le16(sw_time);
cs_timing->switch_timeout = cpu_to_le16(sw_timeout);
*frm += sizeof(struct ieee80211_tdls_cs_timing);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"%s: Add cs timing IE, cs_time = %d us, cs_timeout = %d us \n",
__func__, sw_time, sw_timeout);
return 0;
}
static int
ieee80211_tdls_compare_fdb_entry(const void *a, const void *b)
{
struct __fdb_entry * fdb_a = (struct __fdb_entry *) a;
struct __fdb_entry * fdb_b = (struct __fdb_entry *) b;
if (fdb_a->ageing_timer_value > fdb_b->ageing_timer_value)
return 1;
if (fdb_a->ageing_timer_value < fdb_b->ageing_timer_value)
return -1;
return 0;
}
static int
ieee80211_tdls_add_tlv_downstream_clients(struct ieee80211_node *ni, uint8_t **frm, size_t buf_size)
{
struct ieee80211vap *vap = ni->ni_vap;
int bridge_entry_cnt = 0;
int max_clients = 0;
int client_list_cnt = 0;
struct ieee80211_ie_qtn_tdls_clients *clients;
int i;
struct net_bridge_port *br_port = get_br_port(vap->iv_dev);
if (buf_size < sizeof(*clients)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: Not enough space for TDLS downstream "
"client TLV, increase TDLS frame size\n", __func__);
return 1;
}
/* First, extract all bridge entries */
if (!br_fdb_fillbuf_hook || !br_port) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: Not a bridge port or bridge (%p) or "
"callback func was not initialized (%p)",
__func__, br_port, br_fdb_fillbuf_hook);
return 1;
}
if (!bridge_entries) {
bridge_entries = (struct __fdb_entry *) kmalloc(
sizeof(struct __fdb_entry) *
IEEE80211_TDLS_MAX_BRIDGE_CLIENTS, GFP_KERNEL);
if (bridge_entries == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: can't alloc space for "
"downstream bridge entries", __func__);
return 1;
}
}
bridge_entry_cnt = br_fdb_fillbuf_hook(br_port->br,
bridge_entries, IEEE80211_TDLS_MAX_BRIDGE_CLIENTS, 0);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: bridge_entry_cnt=%d\n", bridge_entry_cnt);
if (bridge_entry_cnt >= IEEE80211_TDLS_MAX_BRIDGE_CLIENTS)
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: at maximum # of TDLS bridge entries "
"(%d)\n", __func__, bridge_entry_cnt);
/* Sort the bridge entries by age */
sort(bridge_entries, bridge_entry_cnt, sizeof(struct __fdb_entry),
ieee80211_tdls_compare_fdb_entry, NULL);
/* Calculate the space we have for downstream entries in the frame */
max_clients = (buf_size - sizeof(*clients)) / IEEE80211_ADDR_LEN;
if (max_clients > IEEE80211_QTN_IE_DOWNSTREAM_MAC_MAX)
max_clients = IEEE80211_QTN_IE_DOWNSTREAM_MAC_MAX;
/* Fill out the frame */
clients = (struct ieee80211_ie_qtn_tdls_clients *)*frm;
clients->qtn_ie_id = IEEE80211_ELEMID_VENDOR;
clients->qtn_ie_oui[0] = QTN_OUI & 0xff;
clients->qtn_ie_oui[1] = (QTN_OUI >> 8) & 0xff;
clients->qtn_ie_oui[2] = (QTN_OUI >> 16) & 0xff;
clients->qtn_ie_type = QTN_OUI_TDLS_BRMACS;
for (i = 0; i < bridge_entry_cnt; i++) {
if (bridge_entries[i].is_local || !bridge_entries[i].is_wlan) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: Bridge table macs %pM"
" %s a local address and %s a wlan device\n",
bridge_entries[i].mac_addr,
bridge_entries[i].is_local ? "is" : "is not",
bridge_entries[i].is_wlan ? "is" : "is not");
IEEE80211_ADDR_COPY(clients->qtn_ie_mac +
client_list_cnt * IEEE80211_ADDR_LEN,
bridge_entries[i].mac_addr);
client_list_cnt++;
if (client_list_cnt >= max_clients)
break;
}
}
clients->qtn_ie_mac_cnt = client_list_cnt;
clients->qtn_ie_len = sizeof(*clients) - 2 +
(client_list_cnt * IEEE80211_ADDR_LEN);
*frm += sizeof(*clients) + (client_list_cnt * IEEE80211_ADDR_LEN);
return 0;
}
static void
ieee80211_tdls_add_bridge_entry(struct ieee80211_node *ni, uint8_t *addr,
__u16 sub_port)
{
struct ieee80211vap *vap = ni->ni_vap;
struct net_bridge_port *br_port = get_br_port(vap->iv_dev);
rcu_read_lock();
if (br_fdb_update_const_hook && br_port) {
br_fdb_update_const_hook(br_port->br, br_port, addr, 0, 0,
IEEE80211_NODE_IDX_MAP(sub_port));
}
rcu_read_unlock();
}
int
ieee80211_tdls_add_bridge_entry_for_peer(struct ieee80211_node *peer_ni)
{
IEEE80211_TDLS_DPRINTF(peer_ni->ni_vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: for mac %pM ncidx = 0x%x\n", __func__,
peer_ni->ni_macaddr, peer_ni->ni_node_idx);
ieee80211_tdls_add_bridge_entry(peer_ni, peer_ni->ni_macaddr,
peer_ni->ni_node_idx);
return 0;
}
int
ieee80211_tdls_disable_peer_link(struct ieee80211_node *ni)
{
struct ieee80211vap *vap = ni->ni_vap;
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
struct net_bridge_port *br_port = get_br_port(vap->iv_dev);
#endif
if (!IEEE80211_NODE_IS_TDLS_ACTIVE(ni) &&
!IEEE80211_NODE_IS_TDLS_STARTING(ni))
return 0;
if (ni->ni_node_idx) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"Purge subport [0x%x] since link disabled\n", ni->ni_node_idx);
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
/*
* Delete all bridge table entries for the AID. They would eventually
* age out, but in the mean time data will be directed to the wrong
* sub_port (AID) until the bridge entries get updated by upstream
* traffic from the endpoint.
* Multicast port entries for the AID (sub_port) are not aged and would
* hang around for ever, so they are also deleted
*/
if (br_fdb_delete_by_sub_port_hook &&
br_port) {
br_fdb_delete_by_sub_port_hook(br_port->br, br_port,
ni->ni_node_idx);
}
#endif
}
ni->tdls_initiator = 0;
if (ni->ni_ext_flags & IEEE80211_NODE_TDLS_AUTH) {
ieee80211_sta_assocs_dec(vap, __func__);
ieee80211_nonqtn_sta_leave(vap, ni, __func__);
}
/* Restore ni_bssid to the local AP BSSID */
if (!IEEE80211_ADDR_EQ(ni->ni_bssid, vap->iv_bss->ni_bssid))
IEEE80211_ADDR_COPY(ni->ni_bssid, vap->iv_bss->ni_bssid);
if (vap->tdls_cs_node && (vap->tdls_cs_node == ni))
ieee80211_tdls_return_to_base_channel(vap, 0);
ieee80211_tdls_update_node_status(ni, IEEE80211_TDLS_NODE_STATUS_IDLE);
ni->ni_ext_flags &= ~IEEE80211_NODE_TDLS_AUTH;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS peer %pM teared down\n", ni->ni_macaddr);
return 0;
}
static int
ieee80211_tdls_check_target_chan(struct ieee80211_node *ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
int tar_chan = tdls->target_chan;
struct ieee80211_channel *chan = NULL;
chan = ic->ic_findchannel(ic, tar_chan, IEEE80211_MODE_AUTO);
if (isclr(ic->ic_chan_active, tar_chan) || !chan ||
(chan->ic_flags & IEEE80211_CHAN_DFS))
return 1;
else
return 0;
}
static int
ieee80211_tdls_check_2nd_chan_off(struct ieee80211_node *ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *chan = NULL;
struct ieee80211_ie_sec_chan_off *sco =
(struct ieee80211_ie_sec_chan_off *)tdls->sec_chan_off;
int sec_chan;
int tar_chan;
if (!sco) {
vap->tdls_off_chan_bw = BW_HT20;
return 0;
}
sec_chan = sco->sco_off;
tar_chan = tdls->target_chan;
chan = ic->ic_findchannel(ic, tar_chan, IEEE80211_MODE_AUTO);
if (!chan)
return 1;
if ((sec_chan == IEEE80211_HTINFO_EXTOFFSET_BELOW) &&
(chan->ic_flags & IEEE80211_CHAN_HT40D)) {
vap->tdls_off_chan_bw = BW_HT40;
return 0;
}
if ((sec_chan == IEEE80211_HTINFO_EXTOFFSET_ABOVE) &&
(chan->ic_flags & IEEE80211_CHAN_HT40U)) {
vap->tdls_off_chan_bw = BW_HT40;
return 0;
}
if ((sec_chan == IEEE80211_HTINFO_EXTOFFSET_NA) &&
(chan->ic_flags & IEEE80211_CHAN_HT20)) {
vap->tdls_off_chan_bw = BW_HT20;
return 0;
}
return 0;
}
static int
ieee80211_tdls_check_wide_bw_cs(struct ieee80211_node *ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *chan = NULL;
struct ieee80211_ie_wbchansw *bw_cs =
(struct ieee80211_ie_wbchansw *)tdls->wide_bw_cs;
if (!bw_cs)
return 1;
if (bw_cs->wbcs_newchanw == 0)
return 1;
chan = ic->ic_findchannel(ic, tdls->target_chan, IEEE80211_MODE_AUTO);
if (!chan)
return 1;
if ((bw_cs->wbcs_newchanw == 1) &&
(chan->ic_center_f_80MHz == bw_cs->wbcs_newchancf0)) {
vap->tdls_off_chan_bw = BW_HT80;
return 0;
}
if ((bw_cs->wbcs_newchanw == 2) &&
(chan->ic_center_f_160MHz== bw_cs->wbcs_newchancf0)) {
vap->tdls_off_chan_bw = BW_HT160;
return 0;
}
return 1;
}
static int
ieee80211_tdls_check_reg_class(struct ieee80211_node *ni,
struct ieee80211_tdls_params *tdls)
{
return 0;
}
static int
ieee80211_tdls_check_link_id(struct ieee80211_node *ni,
struct ieee80211_tdls_params *tdls, uint8_t action)
{
struct ieee80211vap *vap = ni->ni_vap;
int ret = 1;
if (!tdls->link_id)
return ret;
if (ieee80211_tdls_over_qhop_enabled(vap))
ret = !ieee80211_tdls_ext_bssid_allowed(vap, tdls->link_id->bssid);
else
ret = !IEEE80211_ADDR_EQ(ni->ni_bssid, tdls->link_id->bssid);
return ret;
}
static int
ieee80211_tdls_check_chan_switch_timing(struct ieee80211_node *ni,
struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_tdls_cs_timing *cs_timing =
(struct ieee80211_tdls_cs_timing *)(tdls->cs_timing);
uint32_t sw_time;
uint32_t sw_timeout;
uint64_t cur_tsf;
uint64_t tbtt;
uint32_t duration;
if (!cs_timing)
return 1;
sw_time = cs_timing->switch_time;
sw_timeout = cs_timing->switch_timeout;
ic->ic_get_tsf(&cur_tsf);
tbtt = vap->iv_bss->ni_shared_stats->dtim_tbtt;
duration = (uint32_t)(tbtt - cur_tsf);
if ((sw_timeout < DEFAULT_TDLS_CH_SW_TIMEOUT) ||
(sw_timeout <= sw_time))
return 1;
return 0;
}
static uint8_t
ieee80211_tdls_select_target_channel(struct ieee80211vap *vap)
{
uint8_t tar_chan = 0;
tar_chan = IEEE80211_DEFAULT_5_GHZ_CHANNEL;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Target channel %d\n", __func__, tar_chan);
return tar_chan;
}
int
ieee80211_tdls_channel_switch_allowed(struct ieee80211vap *vap)
{
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
struct ieee80211_node *tmp;
TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, tmp) {
if (ni->ni_flags & IEEE80211_NODE_PS_DELIVERING)
return 0;
}
return 1;
}
extern int dev_queue_xmit(struct sk_buff *skb);
static void
ieee80211_tdls_send_frame(struct ieee80211_node *ni, struct sk_buff *skb)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211_node *bss;
int err;
if ((ni->ni_vap)->iv_debug & IEEE80211_MSG_OUTPUT) {
int i;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"Xmit:dev[%s]len[%d]bcast[%d]pri[%d]\n",
ni->ni_vap->iv_dev->name, skb->len,
ni->ni_stats.ns_tx_bcast, skb->priority);
for(i = 0; i < 64; i += 16) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"Data[%p]: %02x %02x %02x %02x", &skb->data[i<<4],
skb->data[i << 4], skb->data[(i << 4) + 1],
skb->data[(i << 4) + 2], skb->data[(i << 4) + 3]);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
" %02x %02x %02x %02x\t",
skb->data[(i << 4) + 4], skb->data[(i << 4) + 5],
skb->data[(i << 4) + 6], skb->data[(i << 4) + 7]);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"%02x %02x %02x %02x",
skb->data[(i << 4) + 8], skb->data[(i << 4) + 9],
skb->data[(i << 4) + 10], skb->data[(i << 4) + 11]);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
" %02x %02x %02x %02x\n",
skb->data[(i << 4) + 12], skb->data[(i << 4) + 13],
skb->data[(i << 4) + 14], skb->data[(i << 4) + 15]);
}
}
bss = vap->iv_bss;
ieee80211_ref_node(bss);
skb->dev = bss->ni_vap->iv_dev;
QTN_SKB_CB_NI(skb) = bss;
M_FLAG_SET(skb, M_NO_AMSDU);
vap->iv_stats.is_tx_tdls++;
IEEE80211_NODE_STAT(ni, tx_tdls_action);
err = dev_queue_xmit(skb);
if (err < 0) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Sending failed\n", __func__);
ieee80211_free_node(bss);
}
}
static void
ieee80211_tdls_send_frame_over_tdls(struct ieee80211_node *peer_ni, struct sk_buff *skb)
{
struct ieee80211vap *vap = peer_ni->ni_vap;
int err;
if (!IEEE80211_NODE_IS_TDLS_ACTIVE(peer_ni))
return;
if (vap->iv_debug & IEEE80211_MSG_OUTPUT) {
int i;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"Xmit:dev[%s]len[%d]bcast[%d]pri[%d]\n",
peer_ni->ni_vap->iv_dev->name, skb->len,
peer_ni->ni_stats.ns_tx_bcast, skb->priority);
for(i = 0; i < 64; i += 16) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"Data[%p]: %02x %02x %02x %02x", &skb->data[i<<4],
skb->data[i << 4], skb->data[(i << 4) + 1],
skb->data[(i << 4) + 2], skb->data[(i << 4) + 3]);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
" %02x %02x %02x %02x\t",
skb->data[(i << 4) + 4], skb->data[(i << 4) + 5],
skb->data[(i << 4) + 6], skb->data[(i << 4) + 7]);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"%02x %02x %02x %02x",
skb->data[(i << 4) + 8], skb->data[(i << 4) + 9],
skb->data[(i << 4) + 10], skb->data[(i << 4) + 11]);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
" %02x %02x %02x %02x\n",
skb->data[(i << 4) + 12], skb->data[(i << 4) + 13],
skb->data[(i << 4) + 14], skb->data[(i << 4) + 15]);
}
}
ieee80211_ref_node(peer_ni);
skb->dev = peer_ni->ni_vap->iv_dev;
QTN_SKB_CB_NI(skb) = peer_ni;
M_FLAG_SET(skb, M_NO_AMSDU);
vap->iv_stats.is_tx_tdls++;
IEEE80211_NODE_STAT(peer_ni, tx_tdls_action);
err = dev_queue_xmit(skb);
if (err < 0) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Sending failed\n", __func__);
ieee80211_free_node(peer_ni);
}
}
/*
* Send a Setup Confirm
*/
static int
ieee80211_tdls_send_setup_confirm(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
uint8_t action = IEEE80211_ACTION_TDLS_SETUP_CONFIRM;
struct sk_buff *skb;
uint8_t *frm = NULL;
uint16_t frm_len = IEEE80211_TDLS_FRAME_MAX;
if (!peer_ni || !data)
return 1;
vap = peer_ni->ni_vap;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: tdls function prohibited, don't send setup confirm\n", __func__);
return 1;
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: send setup confirm frame to %pM\n", __func__, peer_ni->ni_macaddr);
peer_ni->tdls_initiator = 1;
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb)
goto error;
memcpy(frm, &data->status, sizeof(data->status));
frm += sizeof(data->status);
*frm = data->dtoken;
frm += sizeof(data->dtoken);
if (le16toh(data->status) == IEEE80211_STATUS_SUCCESS) {
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_rsn(peer_ni, &frm, data))
goto error;
}
if (ieee80211_tdls_add_tlv_edca_param(peer_ni, &frm))
goto error;
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_ftie(peer_ni, &frm, data))
goto error;
if (ieee80211_tdls_add_tlv_tpk_timeout(peer_ni, &frm, data))
goto error;
}
if (!IEEE80211_NODE_IS_HT(vap->iv_bss) &&
IEEE80211_NODE_IS_HT(peer_ni))
ieee80211_tdls_add_tlv_ht_oper(peer_ni, &frm);
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action, &frm,
peer_ni->ni_macaddr, data))
goto error;
if (!IEEE80211_NODE_IS_VHT(vap->iv_bss) &&
IEEE80211_NODE_IS_VHT(peer_ni))
ieee80211_tdls_add_tlv_vhtop(peer_ni, &frm);
}
frm = ieee80211_add_qtn_ie(frm, peer_ni->ni_ic,
IEEE80211_QTN_BRIDGEMODE, IEEE80211_QTN_BRIDGEMODE,
vap->iv_implicit_ba, IEEE80211_DEFAULT_BA_WINSIZE_H, 0);
if (ieee80211_tdls_add_tlv_downstream_clients(peer_ni, &frm,
(frm_len - (frm - skb->data))))
goto error;
skb_trim(skb, frm - skb->data);
ieee80211_tdls_send_frame(peer_ni, skb);
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
/*
* Send a Setup Response
* A reference to the peer_ni structure must be held before calling this function, and
* must be freed on return if not sent.
* Returns 0 if successful, else 1.
* Note: Link ID is always present, not just if status code is 0 (correction to 7.4.11.2).
*/
static int
ieee80211_tdls_send_setup_resp(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
uint8_t action = IEEE80211_ACTION_TDLS_SETUP_RESP;
struct sk_buff *skb;
uint8_t *frm = NULL;
uint16_t frm_len = IEEE80211_TDLS_FRAME_MAX;
struct ieee80211com *ic;
if (!peer_ni || !data)
return 1;
vap = peer_ni->ni_vap;
ic = vap->iv_ic;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: tdls function prohibited, don't send setup response\n", __func__);
return 1;
}
if (!ieee80211_tdls_link_should_response(vap, peer_ni)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Don't send setup response to Peer %pM due to bad link\n",
__func__, peer_ni->ni_macaddr);
return 1;
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: send setup response frame to %pM\n", __func__, peer_ni->ni_macaddr);
peer_ni->tdls_initiator = 0;
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb)
goto error;
memcpy(frm, &data->status, sizeof(data->status));
frm += sizeof(data->status);
*frm = data->dtoken;
frm += sizeof(data->dtoken);
if (le16toh(data->status) == IEEE80211_STATUS_SUCCESS) {
if (ieee80211_tdls_add_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_rates(peer_ni, &frm))
goto error;
if ((ic->ic_flags_ext & IEEE80211_FEXT_COUNTRYIE)
|| ((ic->ic_flags & IEEE80211_F_DOTH) &&
(ic->ic_flags_ext & IEEE80211_FEXT_TPC))) {
if (ieee80211_tdls_add_tlv_country(peer_ni, &frm))
goto error;
}
if (ieee80211_tdls_add_tlv_xrates(peer_ni, &frm))
goto error;
if (!(vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB))
frm = ieee80211_add_supported_chans(frm, ic);
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_rsn(peer_ni, &frm, data))
goto error;
}
if (ieee80211_tdls_add_tlv_ext_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_qos_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_ftie(peer_ni, &frm, data))
goto error;
if (ieee80211_tdls_add_tlv_tpk_timeout(peer_ni, &frm, data))
goto error;
}
if (!(vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB)) {
if (ieee80211_tdls_add_tlv_sup_reg_class(peer_ni, &frm))
goto error;
}
if (ieee80211_tdls_add_tlv_ht_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_bss_2040_coex(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action, &frm,
peer_ni->ni_macaddr, data))
goto error;
if (ieee80211_tdls_add_tlv_aid(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_vhtcap(peer_ni, &frm))
goto error;
}
frm = ieee80211_add_qtn_ie(frm, peer_ni->ni_ic,
IEEE80211_QTN_BRIDGEMODE, IEEE80211_QTN_BRIDGEMODE,
vap->iv_implicit_ba, IEEE80211_DEFAULT_BA_WINSIZE_H, 0);
if (ieee80211_tdls_add_tlv_downstream_clients(peer_ni, &frm,
(frm_len - (frm - skb->data))))
goto error;
skb_trim(skb, frm - skb->data);
ieee80211_tdls_send_frame(peer_ni, skb);
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
/*
* Send a Setup Request
* A reference to the peer_ni structure must be held before calling this function, and
* must be freed on return if not sent.
* Returns 0 if sent successfully, else 1.
*/
static int
ieee80211_tdls_send_setup_req(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
uint8_t action = IEEE80211_ACTION_TDLS_SETUP_REQ;
struct sk_buff *skb;
uint8_t *frm = NULL;
uint16_t frm_len = IEEE80211_TDLS_FRAME_MAX;
if (!peer_ni || !data)
return 1;
vap = peer_ni->ni_vap;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: tdls function prohibited, don't send setup request\n", __func__);
return 1;
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: send SETUP REQUEST frame to %pM\n", __func__, peer_ni->ni_macaddr);
peer_ni->tdls_initiator = 1;
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb)
goto error;
*frm = data->dtoken;
frm += sizeof(data->dtoken);
if (ieee80211_tdls_add_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_rates(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_country(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_xrates(peer_ni, &frm))
goto error;
if (!(vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB))
frm = ieee80211_add_supported_chans(frm, vap->iv_ic);
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_rsn(peer_ni, &frm, data))
goto error;
}
if (ieee80211_tdls_add_tlv_ext_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_qos_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_ftie(peer_ni, &frm, data))
goto error;
if (ieee80211_tdls_add_tlv_tpk_timeout(peer_ni, &frm, data))
goto error;
}
if (!(vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB)) {
if (ieee80211_tdls_add_tlv_sup_reg_class(peer_ni, &frm))
goto error;
}
if (ieee80211_tdls_add_tlv_ht_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_bss_2040_coex(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action, &frm,
peer_ni->ni_macaddr, data))
goto error;
if (ieee80211_tdls_add_tlv_aid(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_vhtcap(peer_ni, &frm))
goto error;
frm = ieee80211_add_qtn_ie(frm, peer_ni->ni_ic,
IEEE80211_QTN_BRIDGEMODE, IEEE80211_QTN_BRIDGEMODE,
vap->iv_implicit_ba, IEEE80211_DEFAULT_BA_WINSIZE_H, 0);
if (ieee80211_tdls_add_tlv_downstream_clients(peer_ni, &frm,
(frm_len - (frm - skb->data))))
goto error;
skb_trim(skb, frm - skb->data);
ieee80211_tdls_send_frame(peer_ni, skb);
peer_ni->tdls_setup_start = jiffies;
ieee80211_tdls_update_node_status(peer_ni,
IEEE80211_TDLS_NODE_STATUS_STARTING);
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
/*
* Send a Discovery Response
* Returns 0 if sent successfully, else 1.
* Note: Discovery response is a public action frame. All other TDLS frames are
* management over data.
*/
static int
ieee80211_tdls_send_disc_resp(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
struct sk_buff *skb = NULL;
uint16_t frm_len = IEEE80211_TDLS_FRAME_MAX;
uint8_t *frm;
uint8_t action = IEEE80211_ACTION_PUB_TDLS_DISC_RESP;
struct ieee80211_action *ia;
struct ieee80211_tdls_link_id *link_id = NULL;
uint8_t *bssid;
if (!peer_ni) {
printk(KERN_WARNING "%s: Invalid peer node\n", __func__);
return 1;
}
vap = peer_ni->ni_vap;
if (!data) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Invalid argument data\n", __func__);
goto error;
}
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: tdls function prohibited, don't send discover response\n", __func__);
goto error;
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: send DISC RESPONSE frame to %pM\n", __func__, peer_ni->ni_macaddr);
skb = ieee80211_getmgtframe(&frm, frm_len);
if (skb == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_NODE, IEEE80211_TDLS_MSG_WARN,
"%s: cannot get buf; size %u", __func__, frm_len);
vap->iv_stats.is_tx_nobuf++;
goto error;
}
ia = (struct ieee80211_action *)frm;
ia->ia_category = IEEE80211_ACTION_CAT_PUBLIC;
ia->ia_action = IEEE80211_ACTION_PUB_TDLS_DISC_RESP;
frm += sizeof(*ia);
/* Fixed Length Fields */
*frm = data->dtoken;
frm += sizeof(data->dtoken);
if (ieee80211_tdls_add_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_rates(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_xrates(peer_ni, &frm))
goto error;
frm = ieee80211_add_supported_chans(frm, vap->iv_ic);
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_rsn(peer_ni, &frm, data))
goto error;
}
if (ieee80211_tdls_add_tlv_ext_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_ftie(peer_ni, &frm, data))
goto error;
if (ieee80211_tdls_add_tlv_tpk_timeout(peer_ni, &frm, data))
goto error;
}
if (!(vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB)) {
if (ieee80211_tdls_add_tlv_sup_reg_class(peer_ni, &frm))
goto error;
}
if (ieee80211_tdls_add_tlv_ht_cap(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_bss_2040_coex(peer_ni, &frm))
goto error;
link_id = (struct ieee80211_tdls_link_id *)frm;
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action, &frm,
peer_ni->ni_macaddr, data))
goto error;
if (ieee80211_tdls_add_tlv_vhtcap(peer_ni, &frm))
goto error;
frm = ieee80211_add_qtn_ie(frm, peer_ni->ni_ic,
IEEE80211_QTN_BRIDGEMODE, IEEE80211_QTN_BRIDGEMODE,
vap->iv_implicit_ba, IEEE80211_DEFAULT_BA_WINSIZE_H, 0);
if (ieee80211_tdls_add_tlv_downstream_clients(peer_ni, &frm, frm_len -
(frm - skb->data))) {
goto error;
}
skb_trim(skb, frm - skb->data);
vap->iv_stats.is_tx_tdls++;
IEEE80211_NODE_STAT(peer_ni, tx_tdls_action);
bssid = peer_ni->ni_bssid;
if (ieee80211_tdls_over_qhop_enabled(vap) && link_id)
bssid = link_id->bssid;
ieee80211_tdls_mgmt_output(peer_ni, skb,
IEEE80211_FC0_TYPE_MGT, IEEE80211_FC0_SUBTYPE_ACTION, peer_ni->ni_macaddr, bssid);
return 0;
error:
if (skb)
dev_kfree_skb(skb);
ieee80211_free_node(peer_ni);
return 1;
}
/*
* Send a Discovery Request
* Returns 0 if sent successfully, else 1.
*/
static int
ieee80211_tdls_send_disc_req(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
static uint8_t dtoken = 0;
uint8_t action = IEEE80211_ACTION_TDLS_DISC_REQ;
struct sk_buff *skb;
uint8_t *frm = NULL;
uint16_t frm_len = IEEE80211_TDLS_FRAME_MAX;
if (!peer_ni) {
printk(KERN_WARNING "%s: Invalid peer node\n", __func__);
return 1;
}
vap = peer_ni->ni_vap;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: tdls function prohibited, don't send discover request\n", __func__);
return 1;
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: send DISC RESQUEST frame to %pM\n", __func__, peer_ni->ni_macaddr);
if (peer_ni == vap->iv_bss)
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 0);
else
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: can't alloc space for disc request frame", __func__);
goto error;
}
if (!data) {
*frm = dtoken++;
frm += sizeof(dtoken);
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action,
&frm, vap->iv_dev->broadcast, data)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: Failed to add link id for bcast", __func__);
goto error;
}
} else {
*frm = data->dtoken;
frm += sizeof(data->dtoken);
memcpy(frm, data->ie_buf, le32toh(data->ie_buflen));
frm += le32toh(data->ie_buflen);
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action,
&frm, peer_ni->ni_macaddr, data)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: Failed to add link id", __func__);
goto error;
}
}
if (ieee80211_tdls_add_tlv_rates(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_xrates(peer_ni, &frm))
goto error;
if (ieee80211_tdls_add_tlv_ht_cap(peer_ni, &frm))
goto error;
frm = ieee80211_add_qtn_ie(frm, peer_ni->ni_ic,
IEEE80211_QTN_BRIDGEMODE, IEEE80211_QTN_BRIDGEMODE,
vap->iv_implicit_ba, IEEE80211_DEFAULT_BA_WINSIZE_H, 0);
if (ieee80211_tdls_add_tlv_downstream_clients(peer_ni, &frm, frm_len -
(frm - skb->data))) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: Failed to add DS clients", __func__);
goto error;
}
skb_trim(skb, frm - skb->data);
ieee80211_tdls_send_frame(peer_ni, skb);
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
static int
ieee80211_tdls_send_teardown(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
uint8_t action = IEEE80211_ACTION_TDLS_TEARDOWN;
struct sk_buff *skb = NULL;
struct sk_buff *skb2 = NULL;
uint8_t *frm = NULL;
uint16_t frm_len = IEEE80211_TDLS_FRAME_MAX;
if (!peer_ni || !data)
return 1;
vap = peer_ni->ni_vap;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: send teardown frame to %pM\n", __func__, peer_ni->ni_macaddr);
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb)
goto error;
memcpy(frm, &data->status, sizeof(data->status));
frm += sizeof(data->status);
if (ieee80211_tdls_get_privacy(vap)) {
if (ieee80211_tdls_add_tlv_ftie(peer_ni, &frm, data))
goto error;
}
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action, &frm,
peer_ni->ni_macaddr, data))
goto error;
frm = ieee80211_add_qtn_ie(frm, peer_ni->ni_ic,
IEEE80211_QTN_BRIDGEMODE, IEEE80211_QTN_BRIDGEMODE,
vap->iv_implicit_ba, IEEE80211_DEFAULT_BA_WINSIZE_H, 0);
if (ieee80211_tdls_add_tlv_downstream_clients(peer_ni, &frm, frm_len -
(frm - skb->data))) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: Failed to add DS clients", __func__);
goto error;
}
/*
* Send teardown frame via AP and TDLS link simultaneously to
* avoid peer fails to receive it.
*/
skb_trim(skb, frm - skb->data);
skb2 = skb_copy(skb, GFP_ATOMIC);
ieee80211_tdls_send_frame_over_tdls(peer_ni, skb);
if (skb2)
ieee80211_tdls_send_frame(peer_ni, skb2);
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
/*
* Send a Peer Traffic Indication Request
* Returns 0 if sent successfully, else 1.
*/
static int
ieee80211_tdls_send_pti_req(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap= peer_ni->ni_vap;
static uint8_t dtoken = 0;
uint8_t action = IEEE80211_ACTION_TDLS_PTI;
struct sk_buff *skb = NULL;
uint8_t *frm = NULL;
uint32_t pti = 0;
uint32_t pti_ctrl = 0;
/* Although it's unlikely, pti maybe 0 */
pti = vap->iv_ic->ic_get_tdls_param(peer_ni, IOCTL_TDLS_PTI);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: ni=%p ni_ref=%d vap=%p iv_myaddr=%pM\n ni_bssid=%pM"
" traffic indicator 0x%x\n", __func__, peer_ni,
ieee80211_node_refcnt(peer_ni), vap,
vap->iv_myaddr, peer_ni->ni_bssid, pti);
if (pti == 0)
goto error;
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: can't alloc space for pti request frame", __func__);
goto error;
}
if (!data) {
*frm = dtoken++;
frm += sizeof(dtoken);
} else {
*frm = data->dtoken;
frm += sizeof(data->dtoken);
}
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action,
&frm, peer_ni->ni_macaddr, data)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: Failed to add link id", __func__);
goto error;
}
pti_ctrl = vap->iv_ic->ic_get_tdls_param(peer_ni, IOCTL_TDLS_PTI_CTRL);
*frm++ = IEEE80211_ELEMID_TDLS_PTI_CTRL;
*frm++ = 3;
*frm++ = (pti_ctrl >> 16) & 0xFF;
*((uint16_t*)frm) = htole16(pti_ctrl) & 0xFFFF;
frm += 2;
*frm++ = IEEE80211_ELEMID_TDLS_PU_BUF_STAT;
*frm++ = 1;
*frm++ = (pti & 0xF);
skb_trim(skb, frm - skb->data);
ieee80211_tdls_send_frame(peer_ni, skb);
vap->iv_ic->ic_set_tdls_param(peer_ni, IOCTL_TDLS_PTI_PENDING, 1);
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
static int
ieee80211_tdls_get_off_chan_and_bw(struct ieee80211vap *vap,
struct ieee80211_node *ni, uint8_t *tar_chan, uint8_t *tar_bw)
{
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *chan = NULL;
int bw_tmp = BW_INVALID;
int chan_tmp = 0;
if (vap->tdls_fixed_off_chan == TDLS_INVALID_CHANNEL_NUM)
chan_tmp = ieee80211_tdls_select_target_channel(vap);
else
chan_tmp = vap->tdls_fixed_off_chan;
chan = ic->ic_findchannel(ic, chan_tmp, IEEE80211_MODE_AUTO);
if (!chan)
return 1;
bw_tmp = ieee80211_get_max_bw(vap, ni, chan_tmp);
if (vap->tdls_fixed_off_chan_bw != BW_INVALID)
bw_tmp = MIN(vap->tdls_fixed_off_chan_bw, bw_tmp);
*tar_chan = chan_tmp;
*tar_bw = bw_tmp;
return 0;
}
int
ieee80211_tdls_send_chan_switch_req(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
struct ieee80211com *ic;
uint8_t action = IEEE80211_ACTION_TDLS_CS_REQ;
struct sk_buff *skb;
uint8_t *frm = NULL;
uint8_t reg_class = 0;
if (!peer_ni)
return 1;
vap = peer_ni->ni_vap;
ic = vap->iv_ic;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Don't send channel switch req to peer %pM since tdls prohibited\n",
peer_ni->ni_macaddr);
return 1;
}
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Don't send channel switch req to peer %pM since channel switch"
" prohibited\n", peer_ni->ni_macaddr);
return 1;
}
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PASSIVE) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Don't send channel switch req to peer %pM since passive mode\n",
peer_ni->ni_macaddr);
return 1;
}
if (vap->tdls_chan_switching == 1) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Don't send channel switch req to peer %pM since channel switch"
" in progress\n", peer_ni->ni_macaddr);
return 1;
}
if (peer_ni->tdls_no_send_cs_resp == 1) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Don't send channel switch req to peer %pM since channel switch"
" request processing\n", peer_ni->ni_macaddr);
return 1;
}
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to alloc skb\n", __func__);
goto error;
}
if (ieee80211_tdls_get_off_chan_and_bw(vap, peer_ni,
&vap->tdls_target_chan, &vap->tdls_off_chan_bw)) {
if (vap->tdls_target_chan == 0)
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Skip channel switch since current channel is best\n", __func__);
else
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to get target channel and bandwidth\n", __func__);
goto error;
}
if (vap->tdls_target_chan == ic->ic_curchan->ic_ieee) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Skip channel switch since off channel is equal with"
" current channel\n", __func__);
goto error;
}
reg_class = ieee80211_get_current_operating_class(ic->ic_country_code,
vap->tdls_target_chan, vap->tdls_off_chan_bw);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: send channel switch frame to %pM, tar_chan: %d, BW: %d, reg_class:%d\n",
peer_ni->ni_macaddr, vap->tdls_target_chan, vap->tdls_off_chan_bw, reg_class);
*frm = vap->tdls_target_chan;
frm += sizeof(vap->tdls_target_chan);
*frm = reg_class;
frm += sizeof(reg_class);
if (ieee80211_tdls_add_tlv_2nd_chan_off(peer_ni, &frm)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to add second channel offset IE for CS request\n", __func__);
goto error;
}
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action, &frm,
peer_ni->ni_macaddr, data)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to add link ID IE for CS request\n", __func__);
goto error;
}
if (ieee80211_tdls_add_tlv_cs_timimg(peer_ni, &frm, data)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to add cs_timing IE for CS request\n", __func__);
goto error;
}
if (ieee80211_tdls_add_tlv_wide_bw_cs(peer_ni, &frm)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to add wide_bw_cs IE for CS request\n", __func__);
goto error;
}
if (ieee80211_tdls_add_tlv_vht_tx_power_evlope(peer_ni, &frm)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to add vht_tx_power_envlope IE for CS request\n", __func__);
goto error;
}
skb_trim(skb, frm - skb->data);
ieee80211_tdls_send_frame_over_tdls(peer_ni, skb);
peer_ni->tdls_send_cs_req = 1;
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
int
ieee80211_tdls_send_chan_switch_resp(struct ieee80211_node *peer_ni,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap;
struct ieee80211com *ic;
uint8_t action = IEEE80211_ACTION_TDLS_CS_RESP;
struct ieee80211_tdls_cs_timing *cs_timing = NULL;
struct sk_buff *skb;
uint8_t *frm = NULL;
uint16_t status;
uint8_t tar_chan;
uint64_t cur_tsf;
uint64_t start_tsf;
uint64_t tbtt;
uint32_t duration = 0;
uint32_t timeout = 0;
int chan_switch;
if ((!peer_ni) || (!data))
return 1;
vap = peer_ni->ni_vap;
ic = vap->iv_ic;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Don't send channel switch resp to peer %pM since tdls prohibited\n",
peer_ni->ni_macaddr);
return 1;
}
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Don't send channel switch resp to peer %pM since channel switch"
" prohibited\n", peer_ni->ni_macaddr);
return 1;
}
tar_chan = data->dtoken;
status = data->status;
cs_timing = (struct ieee80211_tdls_cs_timing *)
ieee80211_tdls_find_cs_timing(data->ie_buf, data->ie_buflen);
if (!cs_timing)
return 1;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: send channel switch response frame to %pM\n", peer_ni->ni_macaddr);
skb = ieee80211_tdls_init_frame(peer_ni, &frm, action, 1);
if (!skb) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to alloc skb\n", __func__);
goto error;
}
*((uint16_t *)frm) = cpu_to_le16(status);
frm += sizeof(status);
if (ieee80211_tdls_add_tlv_link_id(peer_ni, skb, action, &frm,
peer_ni->ni_macaddr, data)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to add link ID IE for channel switch resp\n", __func__);
goto error;
}
if (ieee80211_tdls_copy_cs_timing(peer_ni, &frm, data)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Fail to add cs timing IE for channel switch resp\n", __func__);
goto error;
}
chan_switch = (!status && !vap->tdls_chan_switching);
if (chan_switch)
ieee80211_sta_pwrsave(vap, 1);
skb_trim(skb, frm - skb->data);
ieee80211_tdls_send_frame_over_tdls(peer_ni, skb);
peer_ni->tdls_no_send_cs_resp = 0;
if (chan_switch) {
ic->ic_get_tsf(&cur_tsf);
vap->tdls_target_chan = tar_chan;
vap->tdls_cs_time = cs_timing->switch_time;
vap->tdls_cs_timeout = cs_timing->switch_timeout;
tbtt = vap->iv_bss->ni_shared_stats->dtim_tbtt;
start_tsf = cur_tsf + cs_timing->switch_time
- DEFAULT_TDLS_CH_SW_PROC_TIME;
timeout = cs_timing->switch_timeout - cs_timing->switch_time
+ DEFAULT_TDLS_CH_SW_PROC_TIME;
if (tbtt > (start_tsf + DEFAULT_TDLS_CH_SW_OC_MARGIN))
duration = (uint32_t)(tbtt - start_tsf - DEFAULT_TDLS_CH_SW_OC_MARGIN);
duration = MAX(duration, cs_timing->switch_timeout - cs_timing->switch_time);
vap->tdls_cs_duration = duration;
if (ieee80211_tdls_remain_on_channel(vap, peer_ni, tar_chan, vap->tdls_off_chan_bw,
start_tsf, timeout, vap->tdls_cs_duration) != 0) {
ieee80211_sta_pwrsave(vap, 0);
}
}
return 0;
error:
if (skb)
dev_kfree_skb(skb);
return 1;
}
int
ieee80211_tdls_validate_vap_state(struct ieee80211vap *vap)
{
if (vap->iv_state != IEEE80211_S_RUN) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: not in run state\n", __func__);
return 1;
}
/* TDLS is not allowed when disabled by the AP */
if (!ieee80211_tdls_sec_mode_valid(vap)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: ignore TDLS because of TKIP\n", __func__);
return 1;
}
return 0;
}
int
ieee80211_tdls_validate_params(struct ieee80211_node *ni, struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
if (ieee80211_tdls_validate_vap_state(vap) != 0)
return 1;
return 0;
}
static void
ieee80211_tdls_add_clients_to_bridge(struct ieee80211_node *ni,
struct ieee80211_node *peer_ni, uint8_t *ie_buf)
{
struct ieee80211_ie_qtn_tdls_clients *clients =
(struct ieee80211_ie_qtn_tdls_clients *)ie_buf;
struct ieee80211vap *vap = ni->ni_vap;
if (clients != NULL) {
uint8_t i;
/* Validate qtn_ie_mac_cnt */
if ((clients->qtn_ie_mac_cnt * IEEE80211_ADDR_LEN) !=
(clients->qtn_ie_len - 5)) {
IEEE80211_DPRINTF(vap, IEEE80211_MSG_ELEMID,
"[%s] Bad qtn_ie_mac_cnt in TDLS client list\n",
vap->iv_dev->name);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS error %s: Received TDLS clients "
"with bad qtn_ie_mac_cnt\n", __func__);
return;
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS number of clients = %d\n", clients->qtn_ie_mac_cnt);
for (i = 0; i < clients->qtn_ie_mac_cnt; i++) {
uint8_t *m = &clients->qtn_ie_mac[i * IEEE80211_ADDR_LEN];
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS client: %pM ncidx 0x%x\n", m, peer_ni->ni_node_idx);
if (!is_multicast_ether_addr(m)) {
ieee80211_tdls_add_bridge_entry(peer_ni, m,
peer_ni->ni_node_idx);
}
}
}
}
/*
* Process a Discovery Response (Public Action frame)
*/
void
ieee80211_tdls_recv_disc_resp(struct ieee80211_node *ni, struct sk_buff *skb,
int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211_node *peer_ni;
enum ieee80211_tdls_operation operation;
if (!tdls || (ieee80211_tdls_validate_params(ni, tdls) != 0))
return;
tdls->act = IEEE80211_ACTION_PUB_TDLS_DISC_RESP;
/*
* A discovery response may be unsolicited. Find or create the peer node.
*/
peer_ni = ieee80211_tdls_find_or_create_peer(ni, tdls->sa, tdls);
if (peer_ni == NULL)
return;
peer_ni->ni_rssi = rssi;
peer_ni->tdls_last_seen = jiffies;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received DISC RESP from peer %pM at %u, rssi=%d\n",
peer_ni->ni_macaddr, peer_ni->tdls_last_seen, rssi);
if (tdls->qtn_brmacs != NULL) {
if (peer_ni->ni_qtn_brmacs == NULL)
MALLOC(peer_ni->ni_qtn_brmacs, uint8_t *,
IEEE80211_MAX_IE_LEN, M_DEVBUF, M_WAITOK);
if (peer_ni->ni_qtn_brmacs != NULL)
memcpy(peer_ni->ni_qtn_brmacs, tdls->qtn_brmacs,
tdls->qtn_brmacs[1] + 2);
}
/* FIXME What if this node thinks we are active but the other node doesn't think so? */
if (IEEE80211_NODE_IS_TDLS_ACTIVE(peer_ni)) {
/* Extract downstream mac addresses */
ieee80211_tdls_add_clients_to_bridge(ni, peer_ni, tdls->qtn_brmacs);
} else if (ieee80211_tdls_link_should_setup(vap, peer_ni)) {
operation = IEEE80211_TDLS_SETUP;
if (ieee80211_tdls_send_event(peer_ni, IEEE80211_EVENT_TDLS, &operation))
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Send event %d failed\n", __func__, operation);
}
ieee80211_free_node(peer_ni);
}
/*
* Process a Setup Request
*/
void
ieee80211_tdls_recv_setup_req(struct ieee80211_node *ni, struct sk_buff *skb,
int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211_node *peer_ni;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,"%s",
"TDLS: received SETUP REQUEST, but tdls function prohibited, drop it\n");
return;
}
if (!tdls || (ieee80211_tdls_validate_params(ni, tdls) != 0))
return;
/* We need to do this in the absence of explicit discovery message. */
peer_ni = ieee80211_tdls_find_or_create_peer(ni, tdls->sa, tdls);
if (peer_ni) {
peer_ni->tdls_initiator = 0;
peer_ni->tdls_last_seen = jiffies;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received SETUP REQUEST from peer %pM at %u, rssi=%d\n",
peer_ni->ni_macaddr, peer_ni->tdls_last_seen, rssi);
peer_ni->tdls_setup_start = jiffies;
ieee80211_tdls_update_node_status(peer_ni,
IEEE80211_TDLS_NODE_STATUS_STARTING);
peer_ni->ni_vendor = PEER_VENDOR_NONE;
if (tdls->qtn_info != NULL)
peer_ni->ni_vendor = PEER_VENDOR_QTN;
if (tdls->aid != NULL)
peer_ni->tdls_peer_associd = le16toh(tdls->aid->aid);
if (tdls->qtn_brmacs != NULL) {
if (peer_ni->ni_qtn_brmacs == NULL)
MALLOC(peer_ni->ni_qtn_brmacs, uint8_t *,
IEEE80211_MAX_IE_LEN, M_DEVBUF, M_WAITOK);
if (peer_ni->ni_qtn_brmacs != NULL)
memcpy(peer_ni->ni_qtn_brmacs, tdls->qtn_brmacs,
tdls->qtn_brmacs[1] + 2);
}
if (ieee80211_tdls_over_qhop_enabled(vap)) {
if (ieee80211_tdls_ext_bssid_allowed(vap, tdls->link_id->bssid) &&
!IEEE80211_ADDR_EQ(peer_ni->ni_bssid, tdls->link_id->bssid))
IEEE80211_ADDR_COPY(peer_ni->ni_bssid, tdls->link_id->bssid);
} else {
if (!IEEE80211_ADDR_EQ(peer_ni->ni_bssid, vap->iv_bss->ni_bssid))
IEEE80211_ADDR_COPY(peer_ni->ni_bssid, vap->iv_bss->ni_bssid);
}
ieee80211_free_node(peer_ni);
}
}
/*
* Process a Setup Response
*/
void
ieee80211_tdls_recv_setup_resp(struct ieee80211_node *ni, struct sk_buff *skb,
int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211_node *peer_ni;
struct ieee80211vap *vap = ni->ni_vap;
if (!tdls || (ieee80211_tdls_validate_params(ni, tdls) != 0))
return;
/* A setup response may be unsolicited. Find or create the peer node. */
peer_ni = ieee80211_tdls_find_or_create_peer(ni, tdls->sa, tdls);
if (peer_ni != NULL) {
peer_ni->ni_rssi = rssi;
peer_ni->tdls_last_seen = jiffies;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received SETUP RESP from peer %pM at %u, rssi=%d\n",
peer_ni->ni_macaddr, peer_ni->tdls_last_seen, rssi);
peer_ni->ni_vendor = PEER_VENDOR_NONE;
if (tdls->qtn_info != NULL)
peer_ni->ni_vendor = PEER_VENDOR_QTN;
peer_ni->tdls_initiator = 1;
if (tdls->aid != NULL)
peer_ni->tdls_peer_associd = le16toh(tdls->aid->aid);
if (tdls->qtn_brmacs != NULL) {
if (peer_ni->ni_qtn_brmacs == NULL)
MALLOC(peer_ni->ni_qtn_brmacs, uint8_t *,
IEEE80211_MAX_IE_LEN, M_DEVBUF, M_WAITOK);
if (peer_ni->ni_qtn_brmacs != NULL)
memcpy(peer_ni->ni_qtn_brmacs, tdls->qtn_brmacs,
tdls->qtn_brmacs[1] + 2);
}
ieee80211_free_node(peer_ni);
}
}
/*
* Process a Discovery Request
*/
void
ieee80211_tdls_recv_disc_req(struct ieee80211_node *ni, struct sk_buff *skb,
int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211_node *peer_ni;
if (tdls == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Required TLV is missing\n", __func__);
return;
}
if (ieee80211_tdls_validate_params(ni, tdls) != 0)
return;
if (memcmp(ni->ni_vap->iv_myaddr, tdls->sa, IEEE80211_ADDR_LEN) == 0)
return;
/* when tdls function is prohibited, ignore discovery request */
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: receive discover request, but tdls function prohibited, drop it\n", __func__);
return;
}
/* Find or create the peer node */
peer_ni = ieee80211_tdls_find_or_create_peer(ni, tdls->sa, tdls);
if (peer_ni) {
peer_ni->tdls_last_seen = jiffies;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received DISC REQUEST from peer %pM at %u, rssi=%d\n",
peer_ni->ni_macaddr, peer_ni->tdls_last_seen, rssi);
if (IEEE80211_NODE_IS_TDLS_ACTIVE(peer_ni)) {
ieee80211_tdls_add_clients_to_bridge(ni, peer_ni, tdls->qtn_brmacs);
} else if (tdls->qtn_brmacs != NULL) {
if (peer_ni->ni_qtn_brmacs == NULL)
MALLOC(peer_ni->ni_qtn_brmacs, uint8_t *,
IEEE80211_MAX_IE_LEN, M_DEVBUF, M_WAITOK);
if (peer_ni->ni_qtn_brmacs != NULL)
memcpy(peer_ni->ni_qtn_brmacs, tdls->qtn_brmacs,
tdls->qtn_brmacs[1] + 2);
}
ieee80211_free_node(peer_ni);
}
}
/*
* Process a channel switch Request
*/
void
ieee80211_tdls_recv_chan_switch_req(struct ieee80211_node *ni, struct sk_buff *skb,
int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211_node *peer_ni;
struct ieee80211_tdls_action_data *data;
uint16_t status = 0;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: drop channel switch req since tdls prohibited\n", __func__);
return;
}
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: drop channel switch req since tdls chan switch prohibited\n", __func__);
return;
}
if (ieee80211_tdls_validate_params(ni, tdls) != 0)
return;
peer_ni = ieee80211_tdls_find_or_create_peer(ni, tdls->sa, tdls);
if (peer_ni) {
peer_ni->ni_rssi = rssi;
peer_ni->tdls_last_seen = jiffies;
peer_ni->tdls_no_send_cs_resp = 1;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received CHAN SWITCH REQUEST from peer %pM at %u, rssi=%d\n",
peer_ni->ni_macaddr, peer_ni->tdls_last_seen, rssi);
MALLOC(data, struct ieee80211_tdls_action_data *,
sizeof(struct ieee80211_tdls_action_data) +
sizeof(struct ieee80211_tdls_cs_timing),
M_DEVBUF, M_WAITOK);
if (ieee80211_tdls_check_target_chan(peer_ni, tdls)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Reject channel switch req from peer: %pM due to invalid"
" target channel: %u\n", peer_ni->ni_macaddr, tdls->target_chan);
status = IEEE80211_STATUS_PEER_MECHANISM_REJECT;
}
if (ieee80211_tdls_check_reg_class(peer_ni, tdls)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Reject channel switch reqt from peer: %pM due to invalid"
" reglatory class: %d \n", peer_ni->ni_macaddr, tdls->reg_class);
status = IEEE80211_STATUS_PEER_MECHANISM_REJECT;
}
if (ieee80211_tdls_check_wide_bw_cs(peer_ni, tdls)) {
if (ieee80211_tdls_check_2nd_chan_off(peer_ni, tdls)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Reject channel switch requset from peer: %pM due to invalid"
" second channel offset: %d \n", peer_ni->ni_macaddr, tdls->sec_chan_off[2]);
status = IEEE80211_STATUS_PEER_MECHANISM_REJECT;
}
}
if (ieee80211_tdls_check_link_id(peer_ni, tdls, IEEE80211_ACTION_TDLS_CS_REQ)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Reject channel switch requset from peer: %pM due to invalid"
" link id\n", peer_ni->ni_macaddr);
status = IEEE80211_STATUS_PEER_MECHANISM_REJECT;
}
if (ieee80211_tdls_check_chan_switch_timing(peer_ni, tdls)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS: Reject channel switch requset from peer: %pM due to invalid"
" switch timing parameter\n", peer_ni->ni_macaddr);
status = IEEE80211_STATUS_PEER_MECHANISM_REJECT;
}
memcpy(data->dest_mac, peer_ni->ni_macaddr, sizeof(data->dest_mac));
data->status = cpu_to_le16(status);
data->dtoken = tdls->target_chan;
data->ie_buflen = sizeof(struct ieee80211_tdls_cs_timing);
memcpy(data->ie_buf, tdls->cs_timing, data->ie_buflen);
ieee80211_tdls_send_chan_switch_resp(peer_ni, data);
FREE(data, M_DEVBUF);
ieee80211_free_node(peer_ni);
}
}
/*
* Process a channel switch Response
*/
void
ieee80211_tdls_recv_chan_switch_resp(struct ieee80211_node *ni,
struct sk_buff *skb, int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_tdls_cs_timing *cs_timing =
(struct ieee80211_tdls_cs_timing *)tdls->cs_timing;
struct ieee80211_node *peer_ni;
int tar_chan;
int chan_bw;
uint64_t cur_tsf;
uint64_t start_tsf;
uint64_t tbtt;
uint32_t duration = 0;
uint32_t timeout = 0;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: drop channel switch resp since tdls prohibited\n", __func__);
return;
}
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: drop channel switch resp since tdls chan switch"
" prohibited\n", __func__);
return;
}
if (ieee80211_tdls_validate_params(ni, tdls) != 0) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: TDLS parameters verification fails\n", __func__);
return;
}
peer_ni = ieee80211_tdls_find_or_create_peer(ni, tdls->sa, tdls);
if (peer_ni != NULL) {
peer_ni->ni_rssi = rssi;
peer_ni->tdls_last_seen = jiffies;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received CHAN SWITCH RESP from peer %pM at %u, rssi=%d\n",
peer_ni->ni_macaddr, peer_ni->tdls_last_seen, rssi);
if (tdls->status == 0) {
ic->ic_get_tsf(&cur_tsf);
/*
* TDLS link should be returned back base channel
* on reception unsolicited TDLS channel switch response
*/
if ((ni->tdls_send_cs_req == 0) &&
(vap->tdls_chan_switching == 1) &&
(vap->tdls_cs_time == cs_timing->switch_time) &&
(vap->tdls_cs_timeout == cs_timing->switch_timeout)) {
tar_chan = ic->ic_bsschan->ic_ieee;
chan_bw = ieee80211_get_bw(ic);
} else {
tar_chan = vap->tdls_target_chan;
chan_bw = vap->tdls_off_chan_bw;
}
vap->tdls_cs_time = cs_timing->switch_time;
vap->tdls_cs_timeout = cs_timing->switch_timeout;
tbtt = vap->iv_bss->ni_shared_stats->dtim_tbtt;
start_tsf = cur_tsf + cs_timing->switch_time
- DEFAULT_TDLS_CH_SW_PROC_TIME;
timeout = cs_timing->switch_timeout - cs_timing->switch_time
+ DEFAULT_TDLS_CH_SW_PROC_TIME;
if (tbtt > (start_tsf + DEFAULT_TDLS_CH_SW_OC_MARGIN))
duration = (uint32_t)(tbtt - start_tsf - DEFAULT_TDLS_CH_SW_OC_MARGIN);
duration = MAX(duration, cs_timing->switch_timeout - cs_timing->switch_time);
vap->tdls_cs_duration = duration;
ieee80211_sta_pwrsave(vap, 1);
if (ieee80211_tdls_remain_on_channel(vap, peer_ni, tar_chan,
chan_bw, start_tsf, timeout, vap->tdls_cs_duration) != 0) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Peer %pM channel switch fails\n", __func__, peer_ni->ni_macaddr);
ieee80211_sta_pwrsave(vap, 0);
}
} else {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Peer %pM rejects channel switch requset\n",
__func__, peer_ni->ni_macaddr);
}
peer_ni->tdls_send_cs_req = 0;
ieee80211_free_node(peer_ni);
}
}
/*
* Make TDLS link switch to base channel
* Returns:
* 1 - returned to base channel
* 0 - failed to return to base channel
*/
int
ieee80211_tdls_return_to_base_channel(struct ieee80211vap *vap, int ap_disassoc)
{
#define IEEE80211_TDLS_RET_BASE_CHAN_WAIT_TIME 5
#define IEEE80211_TDLS_RET_BASE_CHAN_WAIT_CYCL 10
struct ieee80211com *ic = vap->iv_ic;
int tar_chan = ic->ic_bsschan->ic_ieee;
int chan_bw = 0;
uint8_t count = 0;
uint64_t cur_tsf;
uint64_t start_tsf;
if ((vap->tdls_chan_switching == 0) ||
(vap->tdls_cs_node == NULL))
return 1;
if (ap_disassoc)
vap->tdls_cs_disassoc_pending = 1;
IEEE80211_TDLS_DPRINTF(vap,
IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: TDLS link with peer %pM needs to return base channel,"
" disassoc_peeding = %d\n", __func__,
vap->tdls_cs_node->ni_macaddr, vap->tdls_cs_disassoc_pending);
ic->ic_get_tsf(&cur_tsf);
start_tsf = cur_tsf + DEFAULT_TDLS_CH_SW_PROC_TIME;
ieee80211_tdls_remain_on_channel(vap, vap->tdls_cs_node, tar_chan, chan_bw,
start_tsf, DEFAULT_TDLS_CH_SW_TIMEOUT, vap->tdls_cs_duration);
if (!in_interrupt()) {
while (vap->tdls_chan_switching == 1) {
msleep(IEEE80211_TDLS_RET_BASE_CHAN_WAIT_TIME);
if (count++ > IEEE80211_TDLS_RET_BASE_CHAN_WAIT_CYCL) {
IEEE80211_TDLS_DPRINTF(vap,
IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: TDLS link with peer %pM failed to return"
" to base channel\n", __func__,
vap->tdls_cs_node->ni_macaddr);
break;
}
}
}
if (vap->tdls_chan_switching == 0) {
vap->tdls_cs_disassoc_pending = 0;
return 1;
}
return 0;
}
EXPORT_SYMBOL(ieee80211_tdls_return_to_base_channel);
/*
* Process a Setup Confirm
*/
void
ieee80211_tdls_recv_setup_confirm(struct ieee80211_node *ni, struct sk_buff *skb,
int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211_node *peer_ni;
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
if (!tdls || (ieee80211_tdls_validate_params(ni, tdls) != 0)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: TDLS parameters verification fails\n", __func__);
return;
}
peer_ni = ieee80211_tdls_find_or_create_peer(ni, tdls->sa, tdls);
if (peer_ni == NULL)
return;
peer_ni->tdls_initiator = 0;
peer_ni->tdls_last_seen = jiffies;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received SETUP CONFIRM from peer %pM at %u, rssi=%d\n",
peer_ni->ni_macaddr, peer_ni->tdls_last_seen, rssi);
if (tdls->htinfo)
ieee80211_parse_htinfo(peer_ni, tdls->htinfo);
if (IS_IEEE80211_VHT_ENABLED(ic) && tdls->vhtop)
ieee80211_parse_vhtop(peer_ni, tdls->vhtop);
if (tdls->qtn_brmacs != NULL) {
if (peer_ni->ni_qtn_brmacs == NULL)
MALLOC(peer_ni->ni_qtn_brmacs, uint8_t *,
IEEE80211_MAX_IE_LEN, M_DEVBUF, M_WAITOK);
if (peer_ni->ni_qtn_brmacs != NULL)
memcpy(peer_ni->ni_qtn_brmacs, tdls->qtn_brmacs,
tdls->qtn_brmacs[1] + 2);
}
ieee80211_free_node(peer_ni);
}
void
ieee80211_tdls_recv_teardown(struct ieee80211_node *ni, struct sk_buff *skb,
int rssi, struct ieee80211_tdls_params *tdls)
{
struct ieee80211vap *vap = ni->ni_vap;
if (ieee80211_tdls_validate_params(ni, tdls) != 0) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: TDLS parameters verification fails\n", __func__);
return;
}
ni->tdls_last_seen = jiffies;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: received TEARDOWN from peer %pM at %u, rssi=%d\n", tdls->sa,
ni->tdls_last_seen, rssi);
}
int
ieee80211_tdls_send_action_frame(struct net_device *ndev,
struct ieee80211_tdls_action_data *data)
{
struct ieee80211vap *vap = netdev_priv(ndev);
struct ieee80211_node *ni = vap->iv_bss;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *peer_ni;
int ret = 0;
if (data == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Action data is NULL\n", __func__);
return -1;
}
if (vap->iv_opmode != IEEE80211_M_STA) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: vap is not in STA mode\n", __func__);
return -1;
}
if (ieee80211_tdls_validate_vap_state(vap) != 0) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: vap is not in correct state\n", __func__);
return -1;
}
if (IEEE80211_ADDR_EQ(vap->iv_bss->ni_macaddr, data->dest_mac)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Should not send to BSS\n", __func__);
return -1;
}
if (is_multicast_ether_addr(data->dest_mac)) {
if (data->action == IEEE80211_ACTION_TDLS_DISC_REQ) {
peer_ni = vap->iv_bss;
ieee80211_ref_node(peer_ni);
} else {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Dest address of %s action must be unicast\n",
__func__, ieee80211_tdls_action_name_get(data->action));
return -1;
}
} else {
if ((data->action == IEEE80211_ACTION_TDLS_TEARDOWN) ||
(data->action == IEEE80211_ACTION_TDLS_PTI))
peer_ni = ieee80211_find_node(&ic->ic_sta, data->dest_mac);
else
peer_ni = ieee80211_tdls_find_or_create_peer(ni, data->dest_mac, NULL);
if (peer_ni == NULL) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Peer node is not found\n", __func__);
return -1;
}
}
switch (data->action) {
case IEEE80211_ACTION_TDLS_SETUP_REQ:
ret = ieee80211_tdls_send_setup_req(peer_ni, data);
ieee80211_free_node(peer_ni);
break;
case IEEE80211_ACTION_TDLS_SETUP_RESP:
ret = ieee80211_tdls_send_setup_resp(peer_ni, data);
ieee80211_free_node(peer_ni);
break;
case IEEE80211_ACTION_TDLS_SETUP_CONFIRM:
ret = ieee80211_tdls_send_setup_confirm(peer_ni, data);
ieee80211_free_node(peer_ni);
break;
case IEEE80211_ACTION_TDLS_TEARDOWN:
ret = ieee80211_tdls_send_teardown(peer_ni, data);
ieee80211_free_node(peer_ni);
break;
case IEEE80211_ACTION_TDLS_DISC_REQ:
ret = ieee80211_tdls_send_disc_req(peer_ni, data);
ieee80211_free_node(peer_ni);
break;
case IEEE80211_ACTION_PUB_TDLS_DISC_RESP:
ret = ieee80211_tdls_send_disc_resp(peer_ni, data);
break;
case IEEE80211_ACTION_TDLS_PTI:
ret = ieee80211_tdls_send_pti_req(peer_ni, data);
ieee80211_free_node(peer_ni);
break;
default:
ieee80211_free_node(peer_ni);
break;
}
return ret;
}
/*
* Periodically send TDLS discovery requests
*/
void
ieee80211_tdls_trigger_rate_detection(unsigned long arg)
{
struct ieee80211vap *vap = (struct ieee80211vap *) arg;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: discovery timeout\n", __func__);
if (ieee80211_tdls_validate_vap_state(vap) != 0) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: not sending disc req while not associated\n", __func__);
} else {
ieee80211_tdls_send_disc_req(vap->iv_bss, NULL);
schedule_delayed_work(&vap->tdls_rate_detect_work,
DEFAULT_TDLS_RATE_DETECTION_WAITING_T * HZ);
mod_timer(&vap->tdls_rate_detect_timer,
jiffies + vap->tdls_discovery_interval * HZ);
}
}
static void
ieee80211_tdls_bottom_half_rate_detetion(struct work_struct *work)
{
struct delayed_work *dwork = (struct delayed_work *)work;
struct ieee80211vap *vap =
container_of(dwork, struct ieee80211vap, tdls_rate_detect_work);
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
struct ieee80211_node *next;
uint8_t random;
int mu = STATS_SU;
get_random_bytes(&random, sizeof(random));
if (vap->tdls_path_sel_prohibited == 0) {
if (vap->iv_bss->ni_shared_stats->tx[mu].pkts_per_sec <
vap->tdls_path_sel_pps_thrshld)
ieee80211_tdls_add_rate_detection(vap->iv_bss);
TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) {
/*
* Don't send training packets to 3rd part TDLS peer before
* TDLS peer is established since it could cause 3rd part TDLS
* peer send deauth frame.
*/
if (!ni->ni_qtn_assoc_ie &&
!IEEE80211_NODE_IS_TDLS_ACTIVE(ni))
continue;
if (IEEE80211_NODE_IS_TDLS_ACTIVE(ni)) {
if ((ni->tdls_initiator == 1) &&
(ni->ni_shared_stats->tx[mu].pkts_per_sec <
vap->tdls_path_sel_pps_thrshld))
ieee80211_tdls_add_rate_detection(ni);
} else if (!IEEE80211_NODE_IS_NONE_TDLS(ni)) {
ieee80211_tdls_add_rate_detection(ni);
}
}
ieee80211_tdls_peer_ps_info_decre(vap);
}
schedule_delayed_work(&vap->tdls_link_switch_work,
(DEFAULT_TDLS_RATE_DETECTION_WAITING_T + (random % 10)) * HZ);
}
static void
ieee80211_tdls_data_link_switch(struct work_struct *work)
{
struct delayed_work *dwork = (struct delayed_work *)work;
struct ieee80211vap *vap =
container_of(dwork, struct ieee80211vap, tdls_link_switch_work);
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
enum ieee80211_tdls_operation operation;
struct tdls_peer_ps_info *peer_ps_info = NULL;
int link_switch = -1;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Check if need to switch data link\n", __func__);
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
if (IEEE80211_NODE_IS_TDLS_STARTING(ni) &&
(time_after(jiffies, ni->tdls_setup_start +
DEFAULT_TDLS_SETUP_EXPIRE_DURATION * HZ)))
ieee80211_tdls_disable_peer_link(ni);
if (IEEE80211_NODE_IS_NONE_TDLS(ni) ||
IEEE80211_NODE_IS_TDLS_STARTING(ni))
continue;
if (vap->tdls_path_sel_prohibited == 0) {
peer_ps_info = ieee80211_tdls_find_or_create_peer_ps_info(vap, ni);
if ((peer_ps_info) && (peer_ps_info->tdls_link_disabled_ints > 0)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Peer %pM status: %d, disabled_ints: %d\n",
__func__, ni->ni_macaddr, ni->tdls_status,
peer_ps_info->tdls_link_disabled_ints);
continue;
}
if (IEEE80211_NODE_IS_TDLS_ACTIVE(ni) &&
(ni->tdls_initiator == 0)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Peer %pM status: %d, initiator: %d\n", __func__,
ni->ni_macaddr, ni->tdls_status, ni->tdls_initiator);
continue;
}
}
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Peer %pM status: %d, rssi: %d, disabled_ints: %d\n", __func__,
ni->ni_macaddr, ni->tdls_status, ieee80211_tdls_get_smoothed_rssi(vap, ni),
(peer_ps_info == NULL) ? 0 : peer_ps_info->tdls_link_disabled_ints);
link_switch = ieee80211_tdls_data_path_selection(vap, ni);
if (link_switch == 1) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Setting up TDLS link with peer %pM\n",
__func__, ni->ni_macaddr);
if (peer_ps_info != NULL) {
peer_ps_info->tdls_path_down_cnt = 0;
peer_ps_info->tdls_link_disabled_ints = 0;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Clear path selection info, path_down_cnt = %d,"
" link_disabled_ints = %d\n", __func__,
peer_ps_info->tdls_path_down_cnt,
peer_ps_info->tdls_link_disabled_ints);
}
if (!IEEE80211_NODE_IS_TDLS_ACTIVE(ni)) {
operation = IEEE80211_TDLS_SETUP;
ieee80211_tdls_send_event(ni, IEEE80211_EVENT_TDLS, &operation);
}
} else if (link_switch == 0) {
if (IEEE80211_NODE_IS_TDLS_ACTIVE(ni)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: tearing down TDLS link with peer %pM\n",
__func__, ni->ni_macaddr);
if (peer_ps_info != NULL) {
peer_ps_info->tdls_path_down_cnt++;
peer_ps_info->tdls_link_disabled_ints = DEFAULT_TDLS_LINK_DISABLE_SCALE *
peer_ps_info->tdls_path_down_cnt;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Set path selection info, path_down_cnt = %d,"
" link_disabled_ints = %d\n", __func__,
peer_ps_info->tdls_path_down_cnt,
peer_ps_info->tdls_link_disabled_ints);
}
operation = IEEE80211_TDLS_TEARDOWN;
ieee80211_tdls_send_event(ni, IEEE80211_EVENT_TDLS, &operation);
}
}
}
IEEE80211_NODE_UNLOCK(nt);
}
/*
* Start or stop periodic TDLS discovery
* Start broadcasting TDLS discovery frames every <value> seconds.
* A value of 0 stops TDLS discovery.
* Returns 0 if config applied, else 1.
*/
int
ieee80211_tdls_cfg_disc_int(struct ieee80211vap *vap, int value)
{
struct net_device *dev = vap->iv_dev;
unsigned int pre_disc_interval = 0;
if (vap->iv_opmode != IEEE80211_M_STA) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: TDLS: discovery is only supported on STA nodes\n",
dev->name);
return -1;
}
/* TDLS recheck after assoc in case security mode changes */
if (!ieee80211_tdls_sec_mode_valid(vap)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"%s: TDLS: not allowed when using TKIP\n",
dev->name);
return -1;
}
if (value <= 0)
value = 0;
pre_disc_interval = vap->tdls_discovery_interval;
vap->tdls_discovery_interval = value;
if ((vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) == 0) {
if ((pre_disc_interval == 0) && (vap->tdls_discovery_interval > 0))
mod_timer(&vap->tdls_rate_detect_timer, jiffies + HZ);
if ((vap->tdls_discovery_interval == 0) && timer_pending(&vap->tdls_rate_detect_timer)) {
del_timer(&vap->tdls_rate_detect_timer);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
cancel_delayed_work_sync(&vap->tdls_rate_detect_work);
cancel_delayed_work_sync(&vap->tdls_link_switch_work);
#else
cancel_rearming_delayed_work(&vap->tdls_rate_detect_work);
cancel_rearming_delayed_work(&vap->tdls_link_switch_work);
#endif
ieee80211_tdls_free_peer_ps_info(vap);
}
}
return 0;
}
int
ieee80211_tdls_enable_peer_link(struct ieee80211vap *vap, struct ieee80211_node *ni)
{
struct ieee80211com *ic = vap->iv_ic;
if (ic->ic_newassoc != NULL)
ic->ic_newassoc(ni, 0);
if (!(ni->ni_ext_flags & IEEE80211_NODE_TDLS_AUTH)) {
ieee80211_sta_assocs_inc(vap, __func__);
ieee80211_nonqtn_sta_join(vap, ni, __func__);
}
if (ni->ni_qtn_assoc_ie && ni->ni_implicit_ba_valid)
ieee80211_node_implicit_ba_setup(ni);
ieee80211_tdls_set_key(vap, ni);
ieee80211_tdls_update_node_status(ni, IEEE80211_TDLS_NODE_STATUS_ACTIVE);
_ieee80211_node_authorize(ni);
ni->ni_ext_flags |= IEEE80211_NODE_TDLS_AUTH;
ieee80211_tdls_set_link_timeout(vap, ni);
ieee80211_tdls_add_bridge_entry_for_peer(ni);
ieee80211_tdls_add_clients_to_bridge(vap->iv_bss, ni, ni->ni_qtn_brmacs);
printk(KERN_INFO "%s: TDLS peer %s associated, tot=%u/%u\n",
vap->iv_dev->name, ether_sprintf(ni->ni_macaddr),
ic->ic_sta_assoc, ic->ic_nonqtn_sta);
return 0;
}
int
ieee80211_tdls_node_leave(struct ieee80211vap *vap, struct ieee80211_node *ni)
{
ni->ni_chan_num = 0;
memset(ni->ni_supp_chans, 0, sizeof(ni->ni_supp_chans));
ieee80211_tdls_del_key(vap, ni);
ieee80211_tdls_update_node_status(ni, IEEE80211_TDLS_NODE_STATUS_INACTIVE);
ieee80211_node_leave(ni);
return 0;
}
int
ieee80211_tdls_teardown_all_link(struct ieee80211vap *vap)
{
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
enum ieee80211_tdls_operation operation;
if (vap->iv_opmode != IEEE80211_M_STA)
return -1;
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
if (!IEEE80211_NODE_IS_NONE_TDLS(ni)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: tearing down TDLS link with peer %pM\n",__func__, ni->ni_macaddr);
operation = IEEE80211_TDLS_TEARDOWN;
ieee80211_tdls_send_event(ni, IEEE80211_EVENT_TDLS, &operation);
}
}
IEEE80211_NODE_UNLOCK(nt);
return 0;
}
int
ieee80211_tdls_free_all_inactive_peers(struct ieee80211vap *vap)
{
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni, *next;
if (vap->iv_opmode != IEEE80211_M_STA)
return -1;
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) {
if (!ieee80211_node_is_running(ni)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: free TDLS peer %pM\n", __func__, ni->ni_macaddr);
ieee80211_tdls_node_leave(vap, ni);
}
}
IEEE80211_NODE_UNLOCK(nt);
return 0;
}
int
ieee80211_tdls_free_all_peers(struct ieee80211vap *vap)
{
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni, *next;
if (vap->iv_opmode != IEEE80211_M_STA)
return -1;
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, next) {
if (!IEEE80211_NODE_IS_NONE_TDLS(ni)) {
ieee80211_tdls_disable_peer_link(ni);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: free TDLS peer %pM\n", __func__, ni->ni_macaddr);
ieee80211_tdls_node_leave(vap, ni);
}
}
IEEE80211_NODE_UNLOCK(nt);
return 0;
}
int
ieee80211_tdls_init_disc_timer(struct ieee80211vap *vap)
{
init_timer(&vap->tdls_rate_detect_timer);
vap->tdls_rate_detect_timer.function = ieee80211_tdls_trigger_rate_detection;
vap->tdls_rate_detect_timer.data = (unsigned long) vap;
INIT_DELAYED_WORK(&vap->tdls_rate_detect_work, ieee80211_tdls_bottom_half_rate_detetion);
INIT_DELAYED_WORK(&vap->tdls_link_switch_work, ieee80211_tdls_data_link_switch);
return 0;
}
int
ieee80211_tdls_clear_disc_timer(struct ieee80211vap *vap)
{
if (vap == NULL)
return -1;
if (vap->tdls_discovery_interval > 0) {
del_timer(&vap->tdls_rate_detect_timer);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
cancel_delayed_work_sync(&vap->tdls_rate_detect_work);
cancel_delayed_work_sync(&vap->tdls_link_switch_work);
#else
cancel_rearming_delayed_work(&vap->tdls_rate_detect_work);
cancel_rearming_delayed_work(&vap->tdls_link_switch_work);
#endif
ieee80211_tdls_free_peer_ps_info(vap);
}
return 0;
}
int
ieee80211_tdls_start_disc_timer(struct ieee80211vap *vap)
{
if (vap == NULL)
return -1;
if (vap->iv_opmode != IEEE80211_M_STA)
return -1;
/* TDLS recheck after assoc in case security mode changes */
if (!ieee80211_tdls_sec_mode_valid(vap))
return -1;
if (vap->tdls_discovery_interval > 0)
mod_timer(&vap->tdls_rate_detect_timer, jiffies + HZ);
return 0;
}
void
ieee80211_tdls_node_expire(unsigned long arg)
{
struct ieee80211vap *vap = (struct ieee80211vap *) arg;
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
int ni_expired;
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
if (IEEE80211_NODE_IS_NONE_TDLS(ni))
continue;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS: peer %pM, last_seen: %u, Now: %u\n",
ni->ni_macaddr, ni->tdls_last_seen, jiffies);
if (IEEE80211_NODE_IS_TDLS_STARTING(ni) &&
(time_after(jiffies, ni->tdls_setup_start +
DEFAULT_TDLS_SETUP_EXPIRE_DURATION * HZ)))
ieee80211_tdls_disable_peer_link(ni);
if (IEEE80211_NODE_IS_TDLS_INACTIVE(ni) ||
IEEE80211_NODE_IS_TDLS_IDLE(ni)) {
ni_expired = time_after(jiffies,
ni->tdls_last_seen + vap->tdls_node_life_cycle * HZ);
if (ni_expired)
ieee80211_tdls_node_leave(vap, ni);
}
}
IEEE80211_NODE_UNLOCK(nt);
mod_timer(&vap->tdls_node_expire_timer,
jiffies + vap->tdls_node_life_cycle * HZ);
}
int
ieee80211_tdls_start_node_expire_timer(struct ieee80211vap *vap)
{
if (vap->iv_opmode != IEEE80211_M_STA)
return -1;
if (vap->tdls_node_life_cycle > 0) {
if (timer_pending(&vap->tdls_node_expire_timer))
del_timer(&vap->tdls_node_expire_timer);
mod_timer(&vap->tdls_node_expire_timer,
jiffies + vap->tdls_node_life_cycle * HZ);
}
return 0;
}
int
ieee80211_tdls_init_node_expire_timer(struct ieee80211vap *vap)
{
if (vap == NULL)
return -1;
init_timer(&vap->tdls_node_expire_timer);
vap->tdls_node_expire_timer.function = ieee80211_tdls_node_expire;
vap->tdls_node_expire_timer.data = (unsigned long) vap;
if ((vap->iv_flags_ext & IEEE80211_FEXT_TDLS_DISABLED) == 0)
ieee80211_tdls_start_node_expire_timer(vap);
return 0;
}
int
ieee80211_tdls_clear_node_expire_timer(struct ieee80211vap *vap)
{
if (vap)
del_timer(&vap->tdls_node_expire_timer);
return 0;
}
void
ieee80211_tdls_all_peer_disabled(unsigned long arg)
{
struct ieee80211vap *vap = (struct ieee80211vap *) arg;
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
int all_disabled = 1;
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
if (IEEE80211_NODE_IS_TDLS_ACTIVE(ni)) {
all_disabled = 0;
break;
}
}
IEEE80211_NODE_UNLOCK(nt);
if (all_disabled) {
del_timer(&vap->tdls_disassoc_timer);
ieee80211_new_state(vap, vap->tdls_pending_state,
vap->tdls_pending_arg);
} else {
mod_timer(&vap->tdls_disassoc_timer, jiffies + HZ / 2);
}
}
int
ieee80211_tdls_init_disassoc_pending_timer(struct ieee80211vap *vap)
{
if (vap == NULL)
return -1;
init_timer(&vap->tdls_disassoc_timer);
vap->tdls_disassoc_timer.function = ieee80211_tdls_all_peer_disabled;
vap->tdls_disassoc_timer.data = (unsigned long)vap;
return 0;
}
int
ieee80211_tdls_pend_disassociation(struct ieee80211vap *vap,
enum ieee80211_state nstate, int arg)
{
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
int need_pending = 0;
if ((vap->iv_opmode == IEEE80211_M_STA) && (vap->iv_state == IEEE80211_S_RUN) &&
((nstate == IEEE80211_S_INIT) || (nstate == IEEE80211_S_AUTH) ||
(nstate == IEEE80211_S_ASSOC))) {
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
if (IEEE80211_NODE_IS_TDLS_ACTIVE(ni)) {
need_pending = 1;
break;
}
}
IEEE80211_NODE_UNLOCK(nt);
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: pend disassoication with AP, nstate: %d\n", __func__, nstate);
if (need_pending) {
ieee80211_tdls_teardown_all_link(vap);
vap->tdls_pending_state = nstate;
vap->tdls_pending_arg = arg;
mod_timer(&vap->tdls_disassoc_timer, jiffies + HZ / 2);
}
}
return need_pending;
}
EXPORT_SYMBOL(ieee80211_tdls_pend_disassociation);
int
ieee80211_tdls_set_link_timeout(struct ieee80211vap *vap, struct ieee80211_node *ni)
{
uint16_t elapsed_count = 0;
if ((vap == NULL) || (ni == NULL))
return -1;
if (vap->iv_opmode != IEEE80211_M_STA)
return -1;
elapsed_count = ni->ni_inact_reload - ni->ni_inact;
if ((vap->tdls_timeout_time % IEEE80211_INACT_WAIT) != 0)
ni->ni_inact_reload = vap->tdls_timeout_time / IEEE80211_INACT_WAIT + 1;
else
ni->ni_inact_reload = vap->tdls_timeout_time / IEEE80211_INACT_WAIT;
if (ni->ni_inact_reload > elapsed_count)
ni->ni_inact = ni->ni_inact_reload - elapsed_count;
else
ni->ni_inact = IEEE80211_INACT_SEND_PKT_THRSH;
return 0;
}
void
ieee80211_tdls_update_node_status(struct ieee80211_node *ni, enum ni_tdls_status stats)
{
struct ieee80211vap *vap = ni->ni_vap;
if (ni->tdls_status != stats) {
ni->tdls_status = stats;
vap->iv_ic->ic_set_tdls_param(ni, IOCTL_TDLS_STATUS, (int)ni->tdls_status);
}
}
int
ieee80211_tdls_start_channel_switch(struct ieee80211vap *vap,
struct ieee80211_node *peer_ni)
{
int ret = 0;
if ((!vap) || (vap->iv_opmode != IEEE80211_M_STA))
return 1;
if (vap->iv_flags_ext & IEEE80211_FEXT_TDLS_CS_PROHIB) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: Channel switch function has been prohibited\n", __func__);
return 1;
}
if (!ieee80211_tdls_channel_switch_allowed(vap)) {
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_WARN,
"TDLS %s: Don't start channel switch due to "
"fail to enter power save state\n",__func__);
return 1;
}
ret = ieee80211_tdls_send_chan_switch_req(peer_ni, NULL);
return ret;
}
void
ieee80211_tdls_vattach(struct ieee80211vap *vap)
{
ieee80211_tdls_init_disc_timer(vap);
ieee80211_tdls_init_node_expire_timer(vap);
ieee80211_tdls_init_disassoc_pending_timer(vap);
ieee80211_tdls_update_uapsd_indicication_windows(vap);
}
void
ieee80211_tdls_vdetach(struct ieee80211vap *vap)
{
del_timer(&vap->tdls_rate_detect_timer);
del_timer(&vap->tdls_node_expire_timer);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
cancel_delayed_work_sync(&vap->tdls_rate_detect_work);
cancel_delayed_work_sync(&vap->tdls_link_switch_work);
#else
cancel_rearming_delayed_work(&vap->tdls_rate_detect_work);
cancel_rearming_delayed_work(&vap->tdls_link_switch_work);
#endif
del_timer(&vap->tdls_disassoc_timer);
ieee80211_tdls_free_peer_ps_info(vap);
ieee80211_tdls_free_all_peers(vap);
}
/* update tdls link timeout time for the peers who has established tdls link with station */
int ieee80211_tdls_update_link_timeout(struct ieee80211vap *vap)
{
struct ieee80211_node_table *nt = NULL;
struct ieee80211_node *ni = NULL;
uint16_t elapsed_count = 0;
if ((NULL == vap) || (vap->iv_opmode != IEEE80211_M_STA))
return -1;
nt = &vap->iv_ic->ic_sta;
IEEE80211_TDLS_DPRINTF(vap, IEEE80211_MSG_TDLS, IEEE80211_TDLS_MSG_DBG,
"TDLS %s: update link timeout time [%u]\n", __func__, vap->tdls_timeout_time);
IEEE80211_NODE_LOCK(nt);
TAILQ_FOREACH(ni, &nt->nt_node, ni_list) {
if (IEEE80211_NODE_IS_TDLS_ACTIVE(ni) ||
IEEE80211_NODE_IS_TDLS_IDLE(ni)) {
elapsed_count = ni->ni_inact_reload - ni->ni_inact;
if ((vap->tdls_timeout_time % IEEE80211_INACT_WAIT) != 0)
ni->ni_inact_reload = vap->tdls_timeout_time / IEEE80211_INACT_WAIT + 1;
else
ni->ni_inact_reload = vap->tdls_timeout_time / IEEE80211_INACT_WAIT;
if (ni->ni_inact_reload > elapsed_count)
ni->ni_inact = ni->ni_inact_reload - elapsed_count;
else
ni->ni_inact = IEEE80211_INACT_SEND_PKT_THRSH;
}
}
IEEE80211_NODE_UNLOCK(nt);
return 0;
}