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