065563b20a
STA MLD setup links may get removed if AP MLD remove the corresponding affiliated APs with Multi-Link reconfiguration as described in P802.11be_D3.0, section 35.3.6.2.2 Removing affiliated APs. Currently, there is no support to notify such operation to cfg80211 and userspace. Add support for the drivers to indicate STA MLD setup links removal to cfg80211 and notify the same to userspace. Upon receiving such indication from the driver, clear the MLO links information of the removed links in the WDEV. Signed-off-by: Veerendranath Jakkam <quic_vjakkam@quicinc.com> Link: https://lore.kernel.org/r/20230317142153.237900-1-quic_vjakkam@quicinc.com [rename function and attribute, fix kernel-doc] Signed-off-by: Johannes Berg <johannes.berg@intel.com>
1628 lines
43 KiB
C
1628 lines
43 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* SME code for cfg80211
|
|
* both driver SME event handling and the SME implementation
|
|
* (for nl80211's connect() and wext)
|
|
*
|
|
* Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
|
|
* Copyright (C) 2009, 2020, 2022-2023 Intel Corporation. All rights reserved.
|
|
* Copyright 2017 Intel Deutschland GmbH
|
|
*/
|
|
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/wireless.h>
|
|
#include <linux/export.h>
|
|
#include <net/iw_handler.h>
|
|
#include <net/cfg80211.h>
|
|
#include <net/rtnetlink.h>
|
|
#include "nl80211.h"
|
|
#include "reg.h"
|
|
#include "rdev-ops.h"
|
|
|
|
/*
|
|
* Software SME in cfg80211, using auth/assoc/deauth calls to the
|
|
* driver. This is for implementing nl80211's connect/disconnect
|
|
* and wireless extensions (if configured.)
|
|
*/
|
|
|
|
struct cfg80211_conn {
|
|
struct cfg80211_connect_params params;
|
|
/* these are sub-states of the _CONNECTING sme_state */
|
|
enum {
|
|
CFG80211_CONN_SCANNING,
|
|
CFG80211_CONN_SCAN_AGAIN,
|
|
CFG80211_CONN_AUTHENTICATE_NEXT,
|
|
CFG80211_CONN_AUTHENTICATING,
|
|
CFG80211_CONN_AUTH_FAILED_TIMEOUT,
|
|
CFG80211_CONN_ASSOCIATE_NEXT,
|
|
CFG80211_CONN_ASSOCIATING,
|
|
CFG80211_CONN_ASSOC_FAILED,
|
|
CFG80211_CONN_ASSOC_FAILED_TIMEOUT,
|
|
CFG80211_CONN_DEAUTH,
|
|
CFG80211_CONN_ABANDON,
|
|
CFG80211_CONN_CONNECTED,
|
|
} state;
|
|
u8 bssid[ETH_ALEN], prev_bssid[ETH_ALEN];
|
|
const u8 *ie;
|
|
size_t ie_len;
|
|
bool auto_auth, prev_bssid_valid;
|
|
};
|
|
|
|
static void cfg80211_sme_free(struct wireless_dev *wdev)
|
|
{
|
|
if (!wdev->conn)
|
|
return;
|
|
|
|
kfree(wdev->conn->ie);
|
|
kfree(wdev->conn);
|
|
wdev->conn = NULL;
|
|
}
|
|
|
|
static int cfg80211_conn_scan(struct wireless_dev *wdev)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_scan_request *request;
|
|
int n_channels, err;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (rdev->scan_req || rdev->scan_msg)
|
|
return -EBUSY;
|
|
|
|
if (wdev->conn->params.channel)
|
|
n_channels = 1;
|
|
else
|
|
n_channels = ieee80211_get_num_supported_channels(wdev->wiphy);
|
|
|
|
request = kzalloc(sizeof(*request) + sizeof(request->ssids[0]) +
|
|
sizeof(request->channels[0]) * n_channels,
|
|
GFP_KERNEL);
|
|
if (!request)
|
|
return -ENOMEM;
|
|
|
|
if (wdev->conn->params.channel) {
|
|
enum nl80211_band band = wdev->conn->params.channel->band;
|
|
struct ieee80211_supported_band *sband =
|
|
wdev->wiphy->bands[band];
|
|
|
|
if (!sband) {
|
|
kfree(request);
|
|
return -EINVAL;
|
|
}
|
|
request->channels[0] = wdev->conn->params.channel;
|
|
request->rates[band] = (1 << sband->n_bitrates) - 1;
|
|
} else {
|
|
int i = 0, j;
|
|
enum nl80211_band band;
|
|
struct ieee80211_supported_band *bands;
|
|
struct ieee80211_channel *channel;
|
|
|
|
for (band = 0; band < NUM_NL80211_BANDS; band++) {
|
|
bands = wdev->wiphy->bands[band];
|
|
if (!bands)
|
|
continue;
|
|
for (j = 0; j < bands->n_channels; j++) {
|
|
channel = &bands->channels[j];
|
|
if (channel->flags & IEEE80211_CHAN_DISABLED)
|
|
continue;
|
|
request->channels[i++] = channel;
|
|
}
|
|
request->rates[band] = (1 << bands->n_bitrates) - 1;
|
|
}
|
|
n_channels = i;
|
|
}
|
|
request->n_channels = n_channels;
|
|
request->ssids = (void *)&request->channels[n_channels];
|
|
request->n_ssids = 1;
|
|
|
|
memcpy(request->ssids[0].ssid, wdev->conn->params.ssid,
|
|
wdev->conn->params.ssid_len);
|
|
request->ssids[0].ssid_len = wdev->conn->params.ssid_len;
|
|
|
|
eth_broadcast_addr(request->bssid);
|
|
|
|
request->wdev = wdev;
|
|
request->wiphy = &rdev->wiphy;
|
|
request->scan_start = jiffies;
|
|
|
|
rdev->scan_req = request;
|
|
|
|
err = rdev_scan(rdev, request);
|
|
if (!err) {
|
|
wdev->conn->state = CFG80211_CONN_SCANNING;
|
|
nl80211_send_scan_start(rdev, wdev);
|
|
dev_hold(wdev->netdev);
|
|
} else {
|
|
rdev->scan_req = NULL;
|
|
kfree(request);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int cfg80211_conn_do_work(struct wireless_dev *wdev,
|
|
enum nl80211_timeout_reason *treason)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_connect_params *params;
|
|
struct cfg80211_auth_request auth_req = {};
|
|
struct cfg80211_assoc_request req = {};
|
|
int err;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (!wdev->conn)
|
|
return 0;
|
|
|
|
params = &wdev->conn->params;
|
|
|
|
switch (wdev->conn->state) {
|
|
case CFG80211_CONN_SCANNING:
|
|
/* didn't find it during scan ... */
|
|
return -ENOENT;
|
|
case CFG80211_CONN_SCAN_AGAIN:
|
|
return cfg80211_conn_scan(wdev);
|
|
case CFG80211_CONN_AUTHENTICATE_NEXT:
|
|
if (WARN_ON(!rdev->ops->auth))
|
|
return -EOPNOTSUPP;
|
|
wdev->conn->state = CFG80211_CONN_AUTHENTICATING;
|
|
auth_req.key = params->key;
|
|
auth_req.key_len = params->key_len;
|
|
auth_req.key_idx = params->key_idx;
|
|
auth_req.auth_type = params->auth_type;
|
|
auth_req.bss = cfg80211_get_bss(&rdev->wiphy, params->channel,
|
|
params->bssid,
|
|
params->ssid, params->ssid_len,
|
|
IEEE80211_BSS_TYPE_ESS,
|
|
IEEE80211_PRIVACY_ANY);
|
|
auth_req.link_id = -1;
|
|
err = cfg80211_mlme_auth(rdev, wdev->netdev, &auth_req);
|
|
cfg80211_put_bss(&rdev->wiphy, auth_req.bss);
|
|
return err;
|
|
case CFG80211_CONN_AUTH_FAILED_TIMEOUT:
|
|
*treason = NL80211_TIMEOUT_AUTH;
|
|
return -ENOTCONN;
|
|
case CFG80211_CONN_ASSOCIATE_NEXT:
|
|
if (WARN_ON(!rdev->ops->assoc))
|
|
return -EOPNOTSUPP;
|
|
wdev->conn->state = CFG80211_CONN_ASSOCIATING;
|
|
if (wdev->conn->prev_bssid_valid)
|
|
req.prev_bssid = wdev->conn->prev_bssid;
|
|
req.ie = params->ie;
|
|
req.ie_len = params->ie_len;
|
|
req.use_mfp = params->mfp != NL80211_MFP_NO;
|
|
req.crypto = params->crypto;
|
|
req.flags = params->flags;
|
|
req.ht_capa = params->ht_capa;
|
|
req.ht_capa_mask = params->ht_capa_mask;
|
|
req.vht_capa = params->vht_capa;
|
|
req.vht_capa_mask = params->vht_capa_mask;
|
|
req.link_id = -1;
|
|
|
|
req.bss = cfg80211_get_bss(&rdev->wiphy, params->channel,
|
|
params->bssid,
|
|
params->ssid, params->ssid_len,
|
|
IEEE80211_BSS_TYPE_ESS,
|
|
IEEE80211_PRIVACY_ANY);
|
|
if (!req.bss) {
|
|
err = -ENOENT;
|
|
} else {
|
|
err = cfg80211_mlme_assoc(rdev, wdev->netdev, &req);
|
|
cfg80211_put_bss(&rdev->wiphy, req.bss);
|
|
}
|
|
|
|
if (err)
|
|
cfg80211_mlme_deauth(rdev, wdev->netdev, params->bssid,
|
|
NULL, 0,
|
|
WLAN_REASON_DEAUTH_LEAVING,
|
|
false);
|
|
return err;
|
|
case CFG80211_CONN_ASSOC_FAILED_TIMEOUT:
|
|
*treason = NL80211_TIMEOUT_ASSOC;
|
|
fallthrough;
|
|
case CFG80211_CONN_ASSOC_FAILED:
|
|
cfg80211_mlme_deauth(rdev, wdev->netdev, params->bssid,
|
|
NULL, 0,
|
|
WLAN_REASON_DEAUTH_LEAVING, false);
|
|
return -ENOTCONN;
|
|
case CFG80211_CONN_DEAUTH:
|
|
cfg80211_mlme_deauth(rdev, wdev->netdev, params->bssid,
|
|
NULL, 0,
|
|
WLAN_REASON_DEAUTH_LEAVING, false);
|
|
fallthrough;
|
|
case CFG80211_CONN_ABANDON:
|
|
/* free directly, disconnected event already sent */
|
|
cfg80211_sme_free(wdev);
|
|
return 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void cfg80211_conn_work(struct work_struct *work)
|
|
{
|
|
struct cfg80211_registered_device *rdev =
|
|
container_of(work, struct cfg80211_registered_device, conn_work);
|
|
struct wireless_dev *wdev;
|
|
u8 bssid_buf[ETH_ALEN], *bssid = NULL;
|
|
enum nl80211_timeout_reason treason;
|
|
|
|
wiphy_lock(&rdev->wiphy);
|
|
|
|
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
|
|
if (!wdev->netdev)
|
|
continue;
|
|
|
|
wdev_lock(wdev);
|
|
if (!netif_running(wdev->netdev)) {
|
|
wdev_unlock(wdev);
|
|
continue;
|
|
}
|
|
if (!wdev->conn ||
|
|
wdev->conn->state == CFG80211_CONN_CONNECTED) {
|
|
wdev_unlock(wdev);
|
|
continue;
|
|
}
|
|
if (wdev->conn->params.bssid) {
|
|
memcpy(bssid_buf, wdev->conn->params.bssid, ETH_ALEN);
|
|
bssid = bssid_buf;
|
|
}
|
|
treason = NL80211_TIMEOUT_UNSPECIFIED;
|
|
if (cfg80211_conn_do_work(wdev, &treason)) {
|
|
struct cfg80211_connect_resp_params cr;
|
|
|
|
memset(&cr, 0, sizeof(cr));
|
|
cr.status = -1;
|
|
cr.links[0].bssid = bssid;
|
|
cr.timeout_reason = treason;
|
|
__cfg80211_connect_result(wdev->netdev, &cr, false);
|
|
}
|
|
wdev_unlock(wdev);
|
|
}
|
|
|
|
wiphy_unlock(&rdev->wiphy);
|
|
}
|
|
|
|
static void cfg80211_step_auth_next(struct cfg80211_conn *conn,
|
|
struct cfg80211_bss *bss)
|
|
{
|
|
memcpy(conn->bssid, bss->bssid, ETH_ALEN);
|
|
conn->params.bssid = conn->bssid;
|
|
conn->params.channel = bss->channel;
|
|
conn->state = CFG80211_CONN_AUTHENTICATE_NEXT;
|
|
}
|
|
|
|
/* Returned bss is reference counted and must be cleaned up appropriately. */
|
|
static struct cfg80211_bss *cfg80211_get_conn_bss(struct wireless_dev *wdev)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_bss *bss;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
bss = cfg80211_get_bss(wdev->wiphy, wdev->conn->params.channel,
|
|
wdev->conn->params.bssid,
|
|
wdev->conn->params.ssid,
|
|
wdev->conn->params.ssid_len,
|
|
wdev->conn_bss_type,
|
|
IEEE80211_PRIVACY(wdev->conn->params.privacy));
|
|
if (!bss)
|
|
return NULL;
|
|
|
|
cfg80211_step_auth_next(wdev->conn, bss);
|
|
schedule_work(&rdev->conn_work);
|
|
|
|
return bss;
|
|
}
|
|
|
|
static void __cfg80211_sme_scan_done(struct net_device *dev)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_bss *bss;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (!wdev->conn)
|
|
return;
|
|
|
|
if (wdev->conn->state != CFG80211_CONN_SCANNING &&
|
|
wdev->conn->state != CFG80211_CONN_SCAN_AGAIN)
|
|
return;
|
|
|
|
bss = cfg80211_get_conn_bss(wdev);
|
|
if (bss)
|
|
cfg80211_put_bss(&rdev->wiphy, bss);
|
|
else
|
|
schedule_work(&rdev->conn_work);
|
|
}
|
|
|
|
void cfg80211_sme_scan_done(struct net_device *dev)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
|
|
wdev_lock(wdev);
|
|
__cfg80211_sme_scan_done(dev);
|
|
wdev_unlock(wdev);
|
|
}
|
|
|
|
void cfg80211_sme_rx_auth(struct wireless_dev *wdev, const u8 *buf, size_t len)
|
|
{
|
|
struct wiphy *wiphy = wdev->wiphy;
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
|
|
struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)buf;
|
|
u16 status_code = le16_to_cpu(mgmt->u.auth.status_code);
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (!wdev->conn || wdev->conn->state == CFG80211_CONN_CONNECTED)
|
|
return;
|
|
|
|
if (status_code == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG &&
|
|
wdev->conn->auto_auth &&
|
|
wdev->conn->params.auth_type != NL80211_AUTHTYPE_NETWORK_EAP) {
|
|
/* select automatically between only open, shared, leap */
|
|
switch (wdev->conn->params.auth_type) {
|
|
case NL80211_AUTHTYPE_OPEN_SYSTEM:
|
|
if (wdev->connect_keys)
|
|
wdev->conn->params.auth_type =
|
|
NL80211_AUTHTYPE_SHARED_KEY;
|
|
else
|
|
wdev->conn->params.auth_type =
|
|
NL80211_AUTHTYPE_NETWORK_EAP;
|
|
break;
|
|
case NL80211_AUTHTYPE_SHARED_KEY:
|
|
wdev->conn->params.auth_type =
|
|
NL80211_AUTHTYPE_NETWORK_EAP;
|
|
break;
|
|
default:
|
|
/* huh? */
|
|
wdev->conn->params.auth_type =
|
|
NL80211_AUTHTYPE_OPEN_SYSTEM;
|
|
break;
|
|
}
|
|
wdev->conn->state = CFG80211_CONN_AUTHENTICATE_NEXT;
|
|
schedule_work(&rdev->conn_work);
|
|
} else if (status_code != WLAN_STATUS_SUCCESS) {
|
|
struct cfg80211_connect_resp_params cr;
|
|
|
|
memset(&cr, 0, sizeof(cr));
|
|
cr.status = status_code;
|
|
cr.links[0].bssid = mgmt->bssid;
|
|
cr.timeout_reason = NL80211_TIMEOUT_UNSPECIFIED;
|
|
__cfg80211_connect_result(wdev->netdev, &cr, false);
|
|
} else if (wdev->conn->state == CFG80211_CONN_AUTHENTICATING) {
|
|
wdev->conn->state = CFG80211_CONN_ASSOCIATE_NEXT;
|
|
schedule_work(&rdev->conn_work);
|
|
}
|
|
}
|
|
|
|
bool cfg80211_sme_rx_assoc_resp(struct wireless_dev *wdev, u16 status)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
|
|
if (!wdev->conn)
|
|
return false;
|
|
|
|
if (status == WLAN_STATUS_SUCCESS) {
|
|
wdev->conn->state = CFG80211_CONN_CONNECTED;
|
|
return false;
|
|
}
|
|
|
|
if (wdev->conn->prev_bssid_valid) {
|
|
/*
|
|
* Some stupid APs don't accept reassoc, so we
|
|
* need to fall back to trying regular assoc;
|
|
* return true so no event is sent to userspace.
|
|
*/
|
|
wdev->conn->prev_bssid_valid = false;
|
|
wdev->conn->state = CFG80211_CONN_ASSOCIATE_NEXT;
|
|
schedule_work(&rdev->conn_work);
|
|
return true;
|
|
}
|
|
|
|
wdev->conn->state = CFG80211_CONN_ASSOC_FAILED;
|
|
schedule_work(&rdev->conn_work);
|
|
return false;
|
|
}
|
|
|
|
void cfg80211_sme_deauth(struct wireless_dev *wdev)
|
|
{
|
|
cfg80211_sme_free(wdev);
|
|
}
|
|
|
|
void cfg80211_sme_auth_timeout(struct wireless_dev *wdev)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
|
|
if (!wdev->conn)
|
|
return;
|
|
|
|
wdev->conn->state = CFG80211_CONN_AUTH_FAILED_TIMEOUT;
|
|
schedule_work(&rdev->conn_work);
|
|
}
|
|
|
|
void cfg80211_sme_disassoc(struct wireless_dev *wdev)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
|
|
if (!wdev->conn)
|
|
return;
|
|
|
|
wdev->conn->state = CFG80211_CONN_DEAUTH;
|
|
schedule_work(&rdev->conn_work);
|
|
}
|
|
|
|
void cfg80211_sme_assoc_timeout(struct wireless_dev *wdev)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
|
|
if (!wdev->conn)
|
|
return;
|
|
|
|
wdev->conn->state = CFG80211_CONN_ASSOC_FAILED_TIMEOUT;
|
|
schedule_work(&rdev->conn_work);
|
|
}
|
|
|
|
void cfg80211_sme_abandon_assoc(struct wireless_dev *wdev)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
|
|
if (!wdev->conn)
|
|
return;
|
|
|
|
wdev->conn->state = CFG80211_CONN_ABANDON;
|
|
schedule_work(&rdev->conn_work);
|
|
}
|
|
|
|
static void cfg80211_wdev_release_bsses(struct wireless_dev *wdev)
|
|
{
|
|
unsigned int link;
|
|
|
|
for_each_valid_link(wdev, link) {
|
|
if (!wdev->links[link].client.current_bss)
|
|
continue;
|
|
cfg80211_unhold_bss(wdev->links[link].client.current_bss);
|
|
cfg80211_put_bss(wdev->wiphy,
|
|
&wdev->links[link].client.current_bss->pub);
|
|
wdev->links[link].client.current_bss = NULL;
|
|
}
|
|
}
|
|
|
|
void cfg80211_wdev_release_link_bsses(struct wireless_dev *wdev, u16 link_mask)
|
|
{
|
|
unsigned int link;
|
|
|
|
for_each_valid_link(wdev, link) {
|
|
if (!wdev->links[link].client.current_bss ||
|
|
!(link_mask & BIT(link)))
|
|
continue;
|
|
cfg80211_unhold_bss(wdev->links[link].client.current_bss);
|
|
cfg80211_put_bss(wdev->wiphy,
|
|
&wdev->links[link].client.current_bss->pub);
|
|
wdev->links[link].client.current_bss = NULL;
|
|
}
|
|
}
|
|
|
|
static int cfg80211_sme_get_conn_ies(struct wireless_dev *wdev,
|
|
const u8 *ies, size_t ies_len,
|
|
const u8 **out_ies, size_t *out_ies_len)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
u8 *buf;
|
|
size_t offs;
|
|
|
|
if (!rdev->wiphy.extended_capabilities_len ||
|
|
(ies && cfg80211_find_ie(WLAN_EID_EXT_CAPABILITY, ies, ies_len))) {
|
|
*out_ies = kmemdup(ies, ies_len, GFP_KERNEL);
|
|
if (!*out_ies)
|
|
return -ENOMEM;
|
|
*out_ies_len = ies_len;
|
|
return 0;
|
|
}
|
|
|
|
buf = kmalloc(ies_len + rdev->wiphy.extended_capabilities_len + 2,
|
|
GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
if (ies_len) {
|
|
static const u8 before_extcapa[] = {
|
|
/* not listing IEs expected to be created by driver */
|
|
WLAN_EID_RSN,
|
|
WLAN_EID_QOS_CAPA,
|
|
WLAN_EID_RRM_ENABLED_CAPABILITIES,
|
|
WLAN_EID_MOBILITY_DOMAIN,
|
|
WLAN_EID_SUPPORTED_REGULATORY_CLASSES,
|
|
WLAN_EID_BSS_COEX_2040,
|
|
};
|
|
|
|
offs = ieee80211_ie_split(ies, ies_len, before_extcapa,
|
|
ARRAY_SIZE(before_extcapa), 0);
|
|
memcpy(buf, ies, offs);
|
|
/* leave a whole for extended capabilities IE */
|
|
memcpy(buf + offs + rdev->wiphy.extended_capabilities_len + 2,
|
|
ies + offs, ies_len - offs);
|
|
} else {
|
|
offs = 0;
|
|
}
|
|
|
|
/* place extended capabilities IE (with only driver capabilities) */
|
|
buf[offs] = WLAN_EID_EXT_CAPABILITY;
|
|
buf[offs + 1] = rdev->wiphy.extended_capabilities_len;
|
|
memcpy(buf + offs + 2,
|
|
rdev->wiphy.extended_capabilities,
|
|
rdev->wiphy.extended_capabilities_len);
|
|
|
|
*out_ies = buf;
|
|
*out_ies_len = ies_len + rdev->wiphy.extended_capabilities_len + 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cfg80211_sme_connect(struct wireless_dev *wdev,
|
|
struct cfg80211_connect_params *connect,
|
|
const u8 *prev_bssid)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_bss *bss;
|
|
int err;
|
|
|
|
if (!rdev->ops->auth || !rdev->ops->assoc)
|
|
return -EOPNOTSUPP;
|
|
|
|
cfg80211_wdev_release_bsses(wdev);
|
|
|
|
if (wdev->connected) {
|
|
cfg80211_sme_free(wdev);
|
|
wdev->connected = false;
|
|
}
|
|
|
|
if (wdev->conn)
|
|
return -EINPROGRESS;
|
|
|
|
wdev->conn = kzalloc(sizeof(*wdev->conn), GFP_KERNEL);
|
|
if (!wdev->conn)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Copy all parameters, and treat explicitly IEs, BSSID, SSID.
|
|
*/
|
|
memcpy(&wdev->conn->params, connect, sizeof(*connect));
|
|
if (connect->bssid) {
|
|
wdev->conn->params.bssid = wdev->conn->bssid;
|
|
memcpy(wdev->conn->bssid, connect->bssid, ETH_ALEN);
|
|
}
|
|
|
|
if (cfg80211_sme_get_conn_ies(wdev, connect->ie, connect->ie_len,
|
|
&wdev->conn->ie,
|
|
&wdev->conn->params.ie_len)) {
|
|
kfree(wdev->conn);
|
|
wdev->conn = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
wdev->conn->params.ie = wdev->conn->ie;
|
|
|
|
if (connect->auth_type == NL80211_AUTHTYPE_AUTOMATIC) {
|
|
wdev->conn->auto_auth = true;
|
|
/* start with open system ... should mostly work */
|
|
wdev->conn->params.auth_type =
|
|
NL80211_AUTHTYPE_OPEN_SYSTEM;
|
|
} else {
|
|
wdev->conn->auto_auth = false;
|
|
}
|
|
|
|
wdev->conn->params.ssid = wdev->u.client.ssid;
|
|
wdev->conn->params.ssid_len = wdev->u.client.ssid_len;
|
|
|
|
/* see if we have the bss already */
|
|
bss = cfg80211_get_bss(wdev->wiphy, wdev->conn->params.channel,
|
|
wdev->conn->params.bssid,
|
|
wdev->conn->params.ssid,
|
|
wdev->conn->params.ssid_len,
|
|
wdev->conn_bss_type,
|
|
IEEE80211_PRIVACY(wdev->conn->params.privacy));
|
|
|
|
if (prev_bssid) {
|
|
memcpy(wdev->conn->prev_bssid, prev_bssid, ETH_ALEN);
|
|
wdev->conn->prev_bssid_valid = true;
|
|
}
|
|
|
|
/* we're good if we have a matching bss struct */
|
|
if (bss) {
|
|
enum nl80211_timeout_reason treason;
|
|
|
|
cfg80211_step_auth_next(wdev->conn, bss);
|
|
err = cfg80211_conn_do_work(wdev, &treason);
|
|
cfg80211_put_bss(wdev->wiphy, bss);
|
|
} else {
|
|
/* otherwise we'll need to scan for the AP first */
|
|
err = cfg80211_conn_scan(wdev);
|
|
|
|
/*
|
|
* If we can't scan right now, then we need to scan again
|
|
* after the current scan finished, since the parameters
|
|
* changed (unless we find a good AP anyway).
|
|
*/
|
|
if (err == -EBUSY) {
|
|
err = 0;
|
|
wdev->conn->state = CFG80211_CONN_SCAN_AGAIN;
|
|
}
|
|
}
|
|
|
|
if (err)
|
|
cfg80211_sme_free(wdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cfg80211_sme_disconnect(struct wireless_dev *wdev, u16 reason)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
int err;
|
|
|
|
if (!wdev->conn)
|
|
return 0;
|
|
|
|
if (!rdev->ops->deauth)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (wdev->conn->state == CFG80211_CONN_SCANNING ||
|
|
wdev->conn->state == CFG80211_CONN_SCAN_AGAIN) {
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
|
|
/* wdev->conn->params.bssid must be set if > SCANNING */
|
|
err = cfg80211_mlme_deauth(rdev, wdev->netdev,
|
|
wdev->conn->params.bssid,
|
|
NULL, 0, reason, false);
|
|
out:
|
|
cfg80211_sme_free(wdev);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* code shared for in-device and software SME
|
|
*/
|
|
|
|
static bool cfg80211_is_all_idle(void)
|
|
{
|
|
struct cfg80211_registered_device *rdev;
|
|
struct wireless_dev *wdev;
|
|
bool is_all_idle = true;
|
|
|
|
/*
|
|
* All devices must be idle as otherwise if you are actively
|
|
* scanning some new beacon hints could be learned and would
|
|
* count as new regulatory hints.
|
|
* Also if there is any other active beaconing interface we
|
|
* need not issue a disconnect hint and reset any info such
|
|
* as chan dfs state, etc.
|
|
*/
|
|
list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
|
|
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
|
|
wdev_lock(wdev);
|
|
if (wdev->conn || wdev->connected ||
|
|
cfg80211_beaconing_iface_active(wdev))
|
|
is_all_idle = false;
|
|
wdev_unlock(wdev);
|
|
}
|
|
}
|
|
|
|
return is_all_idle;
|
|
}
|
|
|
|
static void disconnect_work(struct work_struct *work)
|
|
{
|
|
rtnl_lock();
|
|
if (cfg80211_is_all_idle())
|
|
regulatory_hint_disconnect();
|
|
rtnl_unlock();
|
|
}
|
|
|
|
DECLARE_WORK(cfg80211_disconnect_work, disconnect_work);
|
|
|
|
static void
|
|
cfg80211_connect_result_release_bsses(struct wireless_dev *wdev,
|
|
struct cfg80211_connect_resp_params *cr)
|
|
{
|
|
unsigned int link;
|
|
|
|
for_each_valid_link(cr, link) {
|
|
if (!cr->links[link].bss)
|
|
continue;
|
|
cfg80211_unhold_bss(bss_from_pub(cr->links[link].bss));
|
|
cfg80211_put_bss(wdev->wiphy, cr->links[link].bss);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* API calls for drivers implementing connect/disconnect and
|
|
* SME event handling
|
|
*/
|
|
|
|
/* This method must consume bss one way or another */
|
|
void __cfg80211_connect_result(struct net_device *dev,
|
|
struct cfg80211_connect_resp_params *cr,
|
|
bool wextev)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
const struct element *country_elem = NULL;
|
|
const struct element *ssid;
|
|
const u8 *country_data;
|
|
u8 country_datalen;
|
|
#ifdef CONFIG_CFG80211_WEXT
|
|
union iwreq_data wrqu;
|
|
#endif
|
|
unsigned int link;
|
|
const u8 *connected_addr;
|
|
bool bss_not_found = false;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
|
|
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
|
|
goto out;
|
|
|
|
if (cr->valid_links) {
|
|
if (WARN_ON(!cr->ap_mld_addr))
|
|
goto out;
|
|
|
|
for_each_valid_link(cr, link) {
|
|
if (WARN_ON(!cr->links[link].addr))
|
|
goto out;
|
|
}
|
|
|
|
if (WARN_ON(wdev->connect_keys))
|
|
goto out;
|
|
}
|
|
|
|
wdev->unprot_beacon_reported = 0;
|
|
nl80211_send_connect_result(wiphy_to_rdev(wdev->wiphy), dev, cr,
|
|
GFP_KERNEL);
|
|
connected_addr = cr->valid_links ? cr->ap_mld_addr : cr->links[0].bssid;
|
|
|
|
#ifdef CONFIG_CFG80211_WEXT
|
|
if (wextev && !cr->valid_links) {
|
|
if (cr->req_ie && cr->status == WLAN_STATUS_SUCCESS) {
|
|
memset(&wrqu, 0, sizeof(wrqu));
|
|
wrqu.data.length = cr->req_ie_len;
|
|
wireless_send_event(dev, IWEVASSOCREQIE, &wrqu,
|
|
cr->req_ie);
|
|
}
|
|
|
|
if (cr->resp_ie && cr->status == WLAN_STATUS_SUCCESS) {
|
|
memset(&wrqu, 0, sizeof(wrqu));
|
|
wrqu.data.length = cr->resp_ie_len;
|
|
wireless_send_event(dev, IWEVASSOCRESPIE, &wrqu,
|
|
cr->resp_ie);
|
|
}
|
|
|
|
memset(&wrqu, 0, sizeof(wrqu));
|
|
wrqu.ap_addr.sa_family = ARPHRD_ETHER;
|
|
if (connected_addr && cr->status == WLAN_STATUS_SUCCESS) {
|
|
memcpy(wrqu.ap_addr.sa_data, connected_addr, ETH_ALEN);
|
|
memcpy(wdev->wext.prev_bssid, connected_addr, ETH_ALEN);
|
|
wdev->wext.prev_bssid_valid = true;
|
|
}
|
|
wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
|
|
}
|
|
#endif
|
|
|
|
if (cr->status == WLAN_STATUS_SUCCESS) {
|
|
if (!wiphy_to_rdev(wdev->wiphy)->ops->connect) {
|
|
for_each_valid_link(cr, link) {
|
|
if (WARN_ON_ONCE(!cr->links[link].bss))
|
|
break;
|
|
}
|
|
}
|
|
|
|
for_each_valid_link(cr, link) {
|
|
/* don't do extra lookups for failures */
|
|
if (cr->links[link].status != WLAN_STATUS_SUCCESS)
|
|
continue;
|
|
|
|
if (cr->links[link].bss)
|
|
continue;
|
|
|
|
cr->links[link].bss =
|
|
cfg80211_get_bss(wdev->wiphy, NULL,
|
|
cr->links[link].bssid,
|
|
wdev->u.client.ssid,
|
|
wdev->u.client.ssid_len,
|
|
wdev->conn_bss_type,
|
|
IEEE80211_PRIVACY_ANY);
|
|
if (!cr->links[link].bss) {
|
|
bss_not_found = true;
|
|
break;
|
|
}
|
|
cfg80211_hold_bss(bss_from_pub(cr->links[link].bss));
|
|
}
|
|
}
|
|
|
|
cfg80211_wdev_release_bsses(wdev);
|
|
|
|
if (cr->status != WLAN_STATUS_SUCCESS) {
|
|
kfree_sensitive(wdev->connect_keys);
|
|
wdev->connect_keys = NULL;
|
|
wdev->u.client.ssid_len = 0;
|
|
wdev->conn_owner_nlportid = 0;
|
|
cfg80211_connect_result_release_bsses(wdev, cr);
|
|
cfg80211_sme_free(wdev);
|
|
return;
|
|
}
|
|
|
|
if (WARN_ON(bss_not_found)) {
|
|
cfg80211_connect_result_release_bsses(wdev, cr);
|
|
return;
|
|
}
|
|
|
|
memset(wdev->links, 0, sizeof(wdev->links));
|
|
for_each_valid_link(cr, link) {
|
|
if (cr->links[link].status == WLAN_STATUS_SUCCESS)
|
|
continue;
|
|
cr->valid_links &= ~BIT(link);
|
|
/* don't require bss pointer for failed links */
|
|
if (!cr->links[link].bss)
|
|
continue;
|
|
cfg80211_unhold_bss(bss_from_pub(cr->links[link].bss));
|
|
cfg80211_put_bss(wdev->wiphy, cr->links[link].bss);
|
|
}
|
|
wdev->valid_links = cr->valid_links;
|
|
for_each_valid_link(cr, link)
|
|
wdev->links[link].client.current_bss =
|
|
bss_from_pub(cr->links[link].bss);
|
|
wdev->connected = true;
|
|
ether_addr_copy(wdev->u.client.connected_addr, connected_addr);
|
|
if (cr->valid_links) {
|
|
for_each_valid_link(cr, link)
|
|
memcpy(wdev->links[link].addr, cr->links[link].addr,
|
|
ETH_ALEN);
|
|
}
|
|
|
|
cfg80211_upload_connect_keys(wdev);
|
|
|
|
rcu_read_lock();
|
|
for_each_valid_link(cr, link) {
|
|
country_elem =
|
|
ieee80211_bss_get_elem(cr->links[link].bss,
|
|
WLAN_EID_COUNTRY);
|
|
if (country_elem)
|
|
break;
|
|
}
|
|
if (!country_elem) {
|
|
rcu_read_unlock();
|
|
return;
|
|
}
|
|
|
|
country_datalen = country_elem->datalen;
|
|
country_data = kmemdup(country_elem->data, country_datalen, GFP_ATOMIC);
|
|
rcu_read_unlock();
|
|
|
|
if (!country_data)
|
|
return;
|
|
|
|
regulatory_hint_country_ie(wdev->wiphy,
|
|
cr->links[link].bss->channel->band,
|
|
country_data, country_datalen);
|
|
kfree(country_data);
|
|
|
|
if (!wdev->u.client.ssid_len) {
|
|
rcu_read_lock();
|
|
for_each_valid_link(cr, link) {
|
|
ssid = ieee80211_bss_get_elem(cr->links[link].bss,
|
|
WLAN_EID_SSID);
|
|
|
|
if (!ssid || !ssid->datalen)
|
|
continue;
|
|
|
|
memcpy(wdev->u.client.ssid, ssid->data, ssid->datalen);
|
|
wdev->u.client.ssid_len = ssid->datalen;
|
|
break;
|
|
}
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
return;
|
|
out:
|
|
for_each_valid_link(cr, link)
|
|
cfg80211_put_bss(wdev->wiphy, cr->links[link].bss);
|
|
}
|
|
|
|
static void cfg80211_update_link_bss(struct wireless_dev *wdev,
|
|
struct cfg80211_bss **bss)
|
|
{
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_internal_bss *ibss;
|
|
|
|
if (!*bss)
|
|
return;
|
|
|
|
ibss = bss_from_pub(*bss);
|
|
if (list_empty(&ibss->list)) {
|
|
struct cfg80211_bss *found = NULL, *tmp = *bss;
|
|
|
|
found = cfg80211_get_bss(wdev->wiphy, NULL,
|
|
(*bss)->bssid,
|
|
wdev->u.client.ssid,
|
|
wdev->u.client.ssid_len,
|
|
wdev->conn_bss_type,
|
|
IEEE80211_PRIVACY_ANY);
|
|
if (found) {
|
|
/* The same BSS is already updated so use it
|
|
* instead, as it has latest info.
|
|
*/
|
|
*bss = found;
|
|
} else {
|
|
/* Update with BSS provided by driver, it will
|
|
* be freshly added and ref cnted, we can free
|
|
* the old one.
|
|
*
|
|
* signal_valid can be false, as we are not
|
|
* expecting the BSS to be found.
|
|
*
|
|
* keep the old timestamp to avoid confusion
|
|
*/
|
|
cfg80211_bss_update(rdev, ibss, false,
|
|
ibss->ts);
|
|
}
|
|
|
|
cfg80211_put_bss(wdev->wiphy, tmp);
|
|
}
|
|
}
|
|
|
|
/* Consumes bss object(s) one way or another */
|
|
void cfg80211_connect_done(struct net_device *dev,
|
|
struct cfg80211_connect_resp_params *params,
|
|
gfp_t gfp)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_event *ev;
|
|
unsigned long flags;
|
|
u8 *next;
|
|
size_t link_info_size = 0;
|
|
unsigned int link;
|
|
|
|
for_each_valid_link(params, link) {
|
|
cfg80211_update_link_bss(wdev, ¶ms->links[link].bss);
|
|
link_info_size += params->links[link].bssid ? ETH_ALEN : 0;
|
|
link_info_size += params->links[link].addr ? ETH_ALEN : 0;
|
|
}
|
|
|
|
ev = kzalloc(sizeof(*ev) + (params->ap_mld_addr ? ETH_ALEN : 0) +
|
|
params->req_ie_len + params->resp_ie_len +
|
|
params->fils.kek_len + params->fils.pmk_len +
|
|
(params->fils.pmkid ? WLAN_PMKID_LEN : 0) + link_info_size,
|
|
gfp);
|
|
|
|
if (!ev) {
|
|
for_each_valid_link(params, link)
|
|
cfg80211_put_bss(wdev->wiphy,
|
|
params->links[link].bss);
|
|
return;
|
|
}
|
|
|
|
ev->type = EVENT_CONNECT_RESULT;
|
|
next = ((u8 *)ev) + sizeof(*ev);
|
|
if (params->ap_mld_addr) {
|
|
ev->cr.ap_mld_addr = next;
|
|
memcpy((void *)ev->cr.ap_mld_addr, params->ap_mld_addr,
|
|
ETH_ALEN);
|
|
next += ETH_ALEN;
|
|
}
|
|
if (params->req_ie_len) {
|
|
ev->cr.req_ie = next;
|
|
ev->cr.req_ie_len = params->req_ie_len;
|
|
memcpy((void *)ev->cr.req_ie, params->req_ie,
|
|
params->req_ie_len);
|
|
next += params->req_ie_len;
|
|
}
|
|
if (params->resp_ie_len) {
|
|
ev->cr.resp_ie = next;
|
|
ev->cr.resp_ie_len = params->resp_ie_len;
|
|
memcpy((void *)ev->cr.resp_ie, params->resp_ie,
|
|
params->resp_ie_len);
|
|
next += params->resp_ie_len;
|
|
}
|
|
if (params->fils.kek_len) {
|
|
ev->cr.fils.kek = next;
|
|
ev->cr.fils.kek_len = params->fils.kek_len;
|
|
memcpy((void *)ev->cr.fils.kek, params->fils.kek,
|
|
params->fils.kek_len);
|
|
next += params->fils.kek_len;
|
|
}
|
|
if (params->fils.pmk_len) {
|
|
ev->cr.fils.pmk = next;
|
|
ev->cr.fils.pmk_len = params->fils.pmk_len;
|
|
memcpy((void *)ev->cr.fils.pmk, params->fils.pmk,
|
|
params->fils.pmk_len);
|
|
next += params->fils.pmk_len;
|
|
}
|
|
if (params->fils.pmkid) {
|
|
ev->cr.fils.pmkid = next;
|
|
memcpy((void *)ev->cr.fils.pmkid, params->fils.pmkid,
|
|
WLAN_PMKID_LEN);
|
|
next += WLAN_PMKID_LEN;
|
|
}
|
|
ev->cr.fils.update_erp_next_seq_num = params->fils.update_erp_next_seq_num;
|
|
if (params->fils.update_erp_next_seq_num)
|
|
ev->cr.fils.erp_next_seq_num = params->fils.erp_next_seq_num;
|
|
ev->cr.valid_links = params->valid_links;
|
|
for_each_valid_link(params, link) {
|
|
if (params->links[link].bss)
|
|
cfg80211_hold_bss(
|
|
bss_from_pub(params->links[link].bss));
|
|
ev->cr.links[link].bss = params->links[link].bss;
|
|
|
|
if (params->links[link].addr) {
|
|
ev->cr.links[link].addr = next;
|
|
memcpy((void *)ev->cr.links[link].addr,
|
|
params->links[link].addr,
|
|
ETH_ALEN);
|
|
next += ETH_ALEN;
|
|
}
|
|
if (params->links[link].bssid) {
|
|
ev->cr.links[link].bssid = next;
|
|
memcpy((void *)ev->cr.links[link].bssid,
|
|
params->links[link].bssid,
|
|
ETH_ALEN);
|
|
next += ETH_ALEN;
|
|
}
|
|
}
|
|
ev->cr.status = params->status;
|
|
ev->cr.timeout_reason = params->timeout_reason;
|
|
|
|
spin_lock_irqsave(&wdev->event_lock, flags);
|
|
list_add_tail(&ev->list, &wdev->event_list);
|
|
spin_unlock_irqrestore(&wdev->event_lock, flags);
|
|
queue_work(cfg80211_wq, &rdev->event_work);
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_connect_done);
|
|
|
|
/* Consumes bss object one way or another */
|
|
void __cfg80211_roamed(struct wireless_dev *wdev,
|
|
struct cfg80211_roam_info *info)
|
|
{
|
|
#ifdef CONFIG_CFG80211_WEXT
|
|
union iwreq_data wrqu;
|
|
#endif
|
|
unsigned int link;
|
|
const u8 *connected_addr;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
|
|
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
|
|
goto out;
|
|
|
|
if (WARN_ON(!wdev->connected))
|
|
goto out;
|
|
|
|
if (info->valid_links) {
|
|
if (WARN_ON(!info->ap_mld_addr))
|
|
goto out;
|
|
|
|
for_each_valid_link(info, link) {
|
|
if (WARN_ON(!info->links[link].addr))
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
cfg80211_wdev_release_bsses(wdev);
|
|
|
|
for_each_valid_link(info, link) {
|
|
if (WARN_ON(!info->links[link].bss))
|
|
goto out;
|
|
}
|
|
|
|
memset(wdev->links, 0, sizeof(wdev->links));
|
|
wdev->valid_links = info->valid_links;
|
|
for_each_valid_link(info, link) {
|
|
cfg80211_hold_bss(bss_from_pub(info->links[link].bss));
|
|
wdev->links[link].client.current_bss =
|
|
bss_from_pub(info->links[link].bss);
|
|
}
|
|
|
|
connected_addr = info->valid_links ?
|
|
info->ap_mld_addr :
|
|
info->links[0].bss->bssid;
|
|
ether_addr_copy(wdev->u.client.connected_addr, connected_addr);
|
|
if (info->valid_links) {
|
|
for_each_valid_link(info, link)
|
|
memcpy(wdev->links[link].addr, info->links[link].addr,
|
|
ETH_ALEN);
|
|
}
|
|
wdev->unprot_beacon_reported = 0;
|
|
nl80211_send_roamed(wiphy_to_rdev(wdev->wiphy),
|
|
wdev->netdev, info, GFP_KERNEL);
|
|
|
|
#ifdef CONFIG_CFG80211_WEXT
|
|
if (!info->valid_links) {
|
|
if (info->req_ie) {
|
|
memset(&wrqu, 0, sizeof(wrqu));
|
|
wrqu.data.length = info->req_ie_len;
|
|
wireless_send_event(wdev->netdev, IWEVASSOCREQIE,
|
|
&wrqu, info->req_ie);
|
|
}
|
|
|
|
if (info->resp_ie) {
|
|
memset(&wrqu, 0, sizeof(wrqu));
|
|
wrqu.data.length = info->resp_ie_len;
|
|
wireless_send_event(wdev->netdev, IWEVASSOCRESPIE,
|
|
&wrqu, info->resp_ie);
|
|
}
|
|
|
|
memset(&wrqu, 0, sizeof(wrqu));
|
|
wrqu.ap_addr.sa_family = ARPHRD_ETHER;
|
|
memcpy(wrqu.ap_addr.sa_data, connected_addr, ETH_ALEN);
|
|
memcpy(wdev->wext.prev_bssid, connected_addr, ETH_ALEN);
|
|
wdev->wext.prev_bssid_valid = true;
|
|
wireless_send_event(wdev->netdev, SIOCGIWAP, &wrqu, NULL);
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
out:
|
|
for_each_valid_link(info, link)
|
|
cfg80211_put_bss(wdev->wiphy, info->links[link].bss);
|
|
}
|
|
|
|
/* Consumes info->links.bss object(s) one way or another */
|
|
void cfg80211_roamed(struct net_device *dev, struct cfg80211_roam_info *info,
|
|
gfp_t gfp)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_event *ev;
|
|
unsigned long flags;
|
|
u8 *next;
|
|
unsigned int link;
|
|
size_t link_info_size = 0;
|
|
bool bss_not_found = false;
|
|
|
|
for_each_valid_link(info, link) {
|
|
link_info_size += info->links[link].addr ? ETH_ALEN : 0;
|
|
link_info_size += info->links[link].bssid ? ETH_ALEN : 0;
|
|
|
|
if (info->links[link].bss)
|
|
continue;
|
|
|
|
info->links[link].bss =
|
|
cfg80211_get_bss(wdev->wiphy,
|
|
info->links[link].channel,
|
|
info->links[link].bssid,
|
|
wdev->u.client.ssid,
|
|
wdev->u.client.ssid_len,
|
|
wdev->conn_bss_type,
|
|
IEEE80211_PRIVACY_ANY);
|
|
|
|
if (!info->links[link].bss) {
|
|
bss_not_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (WARN_ON(bss_not_found))
|
|
goto out;
|
|
|
|
ev = kzalloc(sizeof(*ev) + info->req_ie_len + info->resp_ie_len +
|
|
info->fils.kek_len + info->fils.pmk_len +
|
|
(info->fils.pmkid ? WLAN_PMKID_LEN : 0) +
|
|
(info->ap_mld_addr ? ETH_ALEN : 0) + link_info_size, gfp);
|
|
if (!ev)
|
|
goto out;
|
|
|
|
ev->type = EVENT_ROAMED;
|
|
next = ((u8 *)ev) + sizeof(*ev);
|
|
if (info->req_ie_len) {
|
|
ev->rm.req_ie = next;
|
|
ev->rm.req_ie_len = info->req_ie_len;
|
|
memcpy((void *)ev->rm.req_ie, info->req_ie, info->req_ie_len);
|
|
next += info->req_ie_len;
|
|
}
|
|
if (info->resp_ie_len) {
|
|
ev->rm.resp_ie = next;
|
|
ev->rm.resp_ie_len = info->resp_ie_len;
|
|
memcpy((void *)ev->rm.resp_ie, info->resp_ie,
|
|
info->resp_ie_len);
|
|
next += info->resp_ie_len;
|
|
}
|
|
if (info->fils.kek_len) {
|
|
ev->rm.fils.kek = next;
|
|
ev->rm.fils.kek_len = info->fils.kek_len;
|
|
memcpy((void *)ev->rm.fils.kek, info->fils.kek,
|
|
info->fils.kek_len);
|
|
next += info->fils.kek_len;
|
|
}
|
|
if (info->fils.pmk_len) {
|
|
ev->rm.fils.pmk = next;
|
|
ev->rm.fils.pmk_len = info->fils.pmk_len;
|
|
memcpy((void *)ev->rm.fils.pmk, info->fils.pmk,
|
|
info->fils.pmk_len);
|
|
next += info->fils.pmk_len;
|
|
}
|
|
if (info->fils.pmkid) {
|
|
ev->rm.fils.pmkid = next;
|
|
memcpy((void *)ev->rm.fils.pmkid, info->fils.pmkid,
|
|
WLAN_PMKID_LEN);
|
|
next += WLAN_PMKID_LEN;
|
|
}
|
|
ev->rm.fils.update_erp_next_seq_num = info->fils.update_erp_next_seq_num;
|
|
if (info->fils.update_erp_next_seq_num)
|
|
ev->rm.fils.erp_next_seq_num = info->fils.erp_next_seq_num;
|
|
if (info->ap_mld_addr) {
|
|
ev->rm.ap_mld_addr = next;
|
|
memcpy((void *)ev->rm.ap_mld_addr, info->ap_mld_addr,
|
|
ETH_ALEN);
|
|
next += ETH_ALEN;
|
|
}
|
|
ev->rm.valid_links = info->valid_links;
|
|
for_each_valid_link(info, link) {
|
|
ev->rm.links[link].bss = info->links[link].bss;
|
|
|
|
if (info->links[link].addr) {
|
|
ev->rm.links[link].addr = next;
|
|
memcpy((void *)ev->rm.links[link].addr,
|
|
info->links[link].addr,
|
|
ETH_ALEN);
|
|
next += ETH_ALEN;
|
|
}
|
|
|
|
if (info->links[link].bssid) {
|
|
ev->rm.links[link].bssid = next;
|
|
memcpy((void *)ev->rm.links[link].bssid,
|
|
info->links[link].bssid,
|
|
ETH_ALEN);
|
|
next += ETH_ALEN;
|
|
}
|
|
}
|
|
|
|
spin_lock_irqsave(&wdev->event_lock, flags);
|
|
list_add_tail(&ev->list, &wdev->event_list);
|
|
spin_unlock_irqrestore(&wdev->event_lock, flags);
|
|
queue_work(cfg80211_wq, &rdev->event_work);
|
|
|
|
return;
|
|
out:
|
|
for_each_valid_link(info, link)
|
|
cfg80211_put_bss(wdev->wiphy, info->links[link].bss);
|
|
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_roamed);
|
|
|
|
void __cfg80211_port_authorized(struct wireless_dev *wdev, const u8 *bssid,
|
|
const u8 *td_bitmap, u8 td_bitmap_len)
|
|
{
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
|
|
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
|
|
return;
|
|
|
|
if (WARN_ON(!wdev->connected) ||
|
|
WARN_ON(!ether_addr_equal(wdev->u.client.connected_addr, bssid)))
|
|
return;
|
|
|
|
nl80211_send_port_authorized(wiphy_to_rdev(wdev->wiphy), wdev->netdev,
|
|
bssid, td_bitmap, td_bitmap_len);
|
|
}
|
|
|
|
void cfg80211_port_authorized(struct net_device *dev, const u8 *bssid,
|
|
const u8 *td_bitmap, u8 td_bitmap_len, gfp_t gfp)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_event *ev;
|
|
unsigned long flags;
|
|
|
|
if (WARN_ON(!bssid))
|
|
return;
|
|
|
|
ev = kzalloc(sizeof(*ev) + td_bitmap_len, gfp);
|
|
if (!ev)
|
|
return;
|
|
|
|
ev->type = EVENT_PORT_AUTHORIZED;
|
|
memcpy(ev->pa.bssid, bssid, ETH_ALEN);
|
|
ev->pa.td_bitmap = ((u8 *)ev) + sizeof(*ev);
|
|
ev->pa.td_bitmap_len = td_bitmap_len;
|
|
memcpy((void *)ev->pa.td_bitmap, td_bitmap, td_bitmap_len);
|
|
|
|
/*
|
|
* Use the wdev event list so that if there are pending
|
|
* connected/roamed events, they will be reported first.
|
|
*/
|
|
spin_lock_irqsave(&wdev->event_lock, flags);
|
|
list_add_tail(&ev->list, &wdev->event_list);
|
|
spin_unlock_irqrestore(&wdev->event_lock, flags);
|
|
queue_work(cfg80211_wq, &rdev->event_work);
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_port_authorized);
|
|
|
|
void __cfg80211_disconnected(struct net_device *dev, const u8 *ie,
|
|
size_t ie_len, u16 reason, bool from_ap)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
int i;
|
|
#ifdef CONFIG_CFG80211_WEXT
|
|
union iwreq_data wrqu;
|
|
#endif
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
|
|
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
|
|
return;
|
|
|
|
cfg80211_wdev_release_bsses(wdev);
|
|
wdev->connected = false;
|
|
wdev->u.client.ssid_len = 0;
|
|
wdev->conn_owner_nlportid = 0;
|
|
kfree_sensitive(wdev->connect_keys);
|
|
wdev->connect_keys = NULL;
|
|
|
|
nl80211_send_disconnected(rdev, dev, reason, ie, ie_len, from_ap);
|
|
|
|
/* stop critical protocol if supported */
|
|
if (rdev->ops->crit_proto_stop && rdev->crit_proto_nlportid) {
|
|
rdev->crit_proto_nlportid = 0;
|
|
rdev_crit_proto_stop(rdev, wdev);
|
|
}
|
|
|
|
/*
|
|
* Delete all the keys ... pairwise keys can't really
|
|
* exist any more anyway, but default keys might.
|
|
*/
|
|
if (rdev->ops->del_key) {
|
|
int max_key_idx = 5;
|
|
|
|
if (wiphy_ext_feature_isset(
|
|
wdev->wiphy,
|
|
NL80211_EXT_FEATURE_BEACON_PROTECTION) ||
|
|
wiphy_ext_feature_isset(
|
|
wdev->wiphy,
|
|
NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT))
|
|
max_key_idx = 7;
|
|
for (i = 0; i <= max_key_idx; i++)
|
|
rdev_del_key(rdev, dev, -1, i, false, NULL);
|
|
}
|
|
|
|
rdev_set_qos_map(rdev, dev, NULL);
|
|
|
|
#ifdef CONFIG_CFG80211_WEXT
|
|
memset(&wrqu, 0, sizeof(wrqu));
|
|
wrqu.ap_addr.sa_family = ARPHRD_ETHER;
|
|
wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL);
|
|
wdev->wext.connect.ssid_len = 0;
|
|
#endif
|
|
|
|
schedule_work(&cfg80211_disconnect_work);
|
|
}
|
|
|
|
void cfg80211_disconnected(struct net_device *dev, u16 reason,
|
|
const u8 *ie, size_t ie_len,
|
|
bool locally_generated, gfp_t gfp)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
struct cfg80211_event *ev;
|
|
unsigned long flags;
|
|
|
|
ev = kzalloc(sizeof(*ev) + ie_len, gfp);
|
|
if (!ev)
|
|
return;
|
|
|
|
ev->type = EVENT_DISCONNECTED;
|
|
ev->dc.ie = ((u8 *)ev) + sizeof(*ev);
|
|
ev->dc.ie_len = ie_len;
|
|
memcpy((void *)ev->dc.ie, ie, ie_len);
|
|
ev->dc.reason = reason;
|
|
ev->dc.locally_generated = locally_generated;
|
|
|
|
spin_lock_irqsave(&wdev->event_lock, flags);
|
|
list_add_tail(&ev->list, &wdev->event_list);
|
|
spin_unlock_irqrestore(&wdev->event_lock, flags);
|
|
queue_work(cfg80211_wq, &rdev->event_work);
|
|
}
|
|
EXPORT_SYMBOL(cfg80211_disconnected);
|
|
|
|
/*
|
|
* API calls for nl80211/wext compatibility code
|
|
*/
|
|
int cfg80211_connect(struct cfg80211_registered_device *rdev,
|
|
struct net_device *dev,
|
|
struct cfg80211_connect_params *connect,
|
|
struct cfg80211_cached_keys *connkeys,
|
|
const u8 *prev_bssid)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
int err;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
/*
|
|
* If we have an ssid_len, we're trying to connect or are
|
|
* already connected, so reject a new SSID unless it's the
|
|
* same (which is the case for re-association.)
|
|
*/
|
|
if (wdev->u.client.ssid_len &&
|
|
(wdev->u.client.ssid_len != connect->ssid_len ||
|
|
memcmp(wdev->u.client.ssid, connect->ssid, wdev->u.client.ssid_len)))
|
|
return -EALREADY;
|
|
|
|
/*
|
|
* If connected, reject (re-)association unless prev_bssid
|
|
* matches the current BSSID.
|
|
*/
|
|
if (wdev->connected) {
|
|
if (!prev_bssid)
|
|
return -EALREADY;
|
|
if (!ether_addr_equal(prev_bssid,
|
|
wdev->u.client.connected_addr))
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
/*
|
|
* Reject if we're in the process of connecting with WEP,
|
|
* this case isn't very interesting and trying to handle
|
|
* it would make the code much more complex.
|
|
*/
|
|
if (wdev->connect_keys)
|
|
return -EINPROGRESS;
|
|
|
|
cfg80211_oper_and_ht_capa(&connect->ht_capa_mask,
|
|
rdev->wiphy.ht_capa_mod_mask);
|
|
cfg80211_oper_and_vht_capa(&connect->vht_capa_mask,
|
|
rdev->wiphy.vht_capa_mod_mask);
|
|
|
|
if (connkeys && connkeys->def >= 0) {
|
|
int idx;
|
|
u32 cipher;
|
|
|
|
idx = connkeys->def;
|
|
cipher = connkeys->params[idx].cipher;
|
|
/* If given a WEP key we may need it for shared key auth */
|
|
if (cipher == WLAN_CIPHER_SUITE_WEP40 ||
|
|
cipher == WLAN_CIPHER_SUITE_WEP104) {
|
|
connect->key_idx = idx;
|
|
connect->key = connkeys->params[idx].key;
|
|
connect->key_len = connkeys->params[idx].key_len;
|
|
|
|
/*
|
|
* If ciphers are not set (e.g. when going through
|
|
* iwconfig), we have to set them appropriately here.
|
|
*/
|
|
if (connect->crypto.cipher_group == 0)
|
|
connect->crypto.cipher_group = cipher;
|
|
|
|
if (connect->crypto.n_ciphers_pairwise == 0) {
|
|
connect->crypto.n_ciphers_pairwise = 1;
|
|
connect->crypto.ciphers_pairwise[0] = cipher;
|
|
}
|
|
}
|
|
} else {
|
|
if (WARN_ON(connkeys))
|
|
return -EINVAL;
|
|
|
|
/* connect can point to wdev->wext.connect which
|
|
* can hold key data from a previous connection
|
|
*/
|
|
connect->key = NULL;
|
|
connect->key_len = 0;
|
|
connect->key_idx = 0;
|
|
}
|
|
|
|
wdev->connect_keys = connkeys;
|
|
memcpy(wdev->u.client.ssid, connect->ssid, connect->ssid_len);
|
|
wdev->u.client.ssid_len = connect->ssid_len;
|
|
|
|
wdev->conn_bss_type = connect->pbss ? IEEE80211_BSS_TYPE_PBSS :
|
|
IEEE80211_BSS_TYPE_ESS;
|
|
|
|
if (!rdev->ops->connect)
|
|
err = cfg80211_sme_connect(wdev, connect, prev_bssid);
|
|
else
|
|
err = rdev_connect(rdev, dev, connect);
|
|
|
|
if (err) {
|
|
wdev->connect_keys = NULL;
|
|
/*
|
|
* This could be reassoc getting refused, don't clear
|
|
* ssid_len in that case.
|
|
*/
|
|
if (!wdev->connected)
|
|
wdev->u.client.ssid_len = 0;
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cfg80211_disconnect(struct cfg80211_registered_device *rdev,
|
|
struct net_device *dev, u16 reason, bool wextev)
|
|
{
|
|
struct wireless_dev *wdev = dev->ieee80211_ptr;
|
|
int err = 0;
|
|
|
|
ASSERT_WDEV_LOCK(wdev);
|
|
|
|
kfree_sensitive(wdev->connect_keys);
|
|
wdev->connect_keys = NULL;
|
|
|
|
wdev->conn_owner_nlportid = 0;
|
|
|
|
if (wdev->conn)
|
|
err = cfg80211_sme_disconnect(wdev, reason);
|
|
else if (!rdev->ops->disconnect)
|
|
cfg80211_mlme_down(rdev, dev);
|
|
else if (wdev->u.client.ssid_len)
|
|
err = rdev_disconnect(rdev, dev, reason);
|
|
|
|
/*
|
|
* Clear ssid_len unless we actually were fully connected,
|
|
* in which case cfg80211_disconnected() will take care of
|
|
* this later.
|
|
*/
|
|
if (!wdev->connected)
|
|
wdev->u.client.ssid_len = 0;
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Used to clean up after the connection / connection attempt owner socket
|
|
* disconnects
|
|
*/
|
|
void cfg80211_autodisconnect_wk(struct work_struct *work)
|
|
{
|
|
struct wireless_dev *wdev =
|
|
container_of(work, struct wireless_dev, disconnect_wk);
|
|
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
|
|
|
|
wiphy_lock(wdev->wiphy);
|
|
wdev_lock(wdev);
|
|
|
|
if (wdev->conn_owner_nlportid) {
|
|
switch (wdev->iftype) {
|
|
case NL80211_IFTYPE_ADHOC:
|
|
__cfg80211_leave_ibss(rdev, wdev->netdev, false);
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_P2P_GO:
|
|
__cfg80211_stop_ap(rdev, wdev->netdev, -1, false);
|
|
break;
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
__cfg80211_leave_mesh(rdev, wdev->netdev);
|
|
break;
|
|
case NL80211_IFTYPE_STATION:
|
|
case NL80211_IFTYPE_P2P_CLIENT:
|
|
/*
|
|
* Use disconnect_bssid if still connecting and
|
|
* ops->disconnect not implemented. Otherwise we can
|
|
* use cfg80211_disconnect.
|
|
*/
|
|
if (rdev->ops->disconnect || wdev->connected)
|
|
cfg80211_disconnect(rdev, wdev->netdev,
|
|
WLAN_REASON_DEAUTH_LEAVING,
|
|
true);
|
|
else
|
|
cfg80211_mlme_deauth(rdev, wdev->netdev,
|
|
wdev->disconnect_bssid,
|
|
NULL, 0,
|
|
WLAN_REASON_DEAUTH_LEAVING,
|
|
false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
wdev_unlock(wdev);
|
|
wiphy_unlock(wdev->wiphy);
|
|
}
|