mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-20 12:39:25 +01:00
0611632d7b
In the case that a netdev is powered down, or an interface type change occurs, the station object will be removed and any watches will be freed. Since rrm is created when the netdev is created and persists across iftype and power up/down changes, it should provide a destroy callback to station_add_state_watch so that it can be notified when the watch is removed.
860 lines
22 KiB
C
860 lines
22 KiB
C
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2019 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
#include <linux/if_ether.h>
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#include "ell/useful.h"
|
|
#include "src/module.h"
|
|
#include "src/mpdu.h"
|
|
#include "src/netdev.h"
|
|
#include "src/iwd.h"
|
|
#include "src/ie.h"
|
|
#include "src/util.h"
|
|
#include "src/station.h"
|
|
#include "src/scan.h"
|
|
#include "src/nl80211util.h"
|
|
#include "src/wiphy.h"
|
|
#include "src/frame-xchg.h"
|
|
|
|
#include "linux/nl80211.h"
|
|
|
|
/* Limit requests per second */
|
|
#define MAX_REQUESTS_PER_SEC 2
|
|
/* Microseconds between requests */
|
|
#define MIN_MICROS_BETWEEN_REQUESTS (1000000 / MAX_REQUESTS_PER_SEC)
|
|
|
|
/* 802.11-2016 Table 9-90 */
|
|
#define REPORT_DETAIL_NO_FIELDS_OR_ELEMS 0
|
|
#define REPORT_DETAIL_ALL_FIELDS_AND_ANY_REQUEST_ELEMS 1
|
|
#define REPORT_DETAIL_ALL_FIELDS_AND_ELEMS 2
|
|
|
|
/* 802.11-2016 Table 9-192 */
|
|
#define REPORT_REJECT_LATE (1 << 0)
|
|
#define REPORT_REJECT_INCAPABLE (1 << 1)
|
|
#define REPORT_REJECT_REFUSED (1 << 2)
|
|
|
|
/* 802.11-2016 Table 9-87 */
|
|
enum rrm_beacon_req_mode {
|
|
RRM_BEACON_REQ_MODE_PASSIVE = 0,
|
|
RRM_BEACON_REQ_MODE_ACTIVE = 1,
|
|
RRM_BEACON_REQ_MODE_TABLE = 2,
|
|
};
|
|
|
|
/* 802.11-2016 Table 9-88 */
|
|
enum rrm_beacon_req_subelem_id {
|
|
RRM_BEACON_REQ_SUBELEM_ID_SSID = 0,
|
|
RRM_BEACON_REQ_SUBELEM_ID_BEACON_REPORTING = 1,
|
|
RRM_BEACON_REQ_SUBELEM_ID_REPORTING_DETAIL = 2,
|
|
/* 3 - 9 reserved */
|
|
RRM_BEACON_REQ_SUBELEM_ID_REQUEST = 10,
|
|
RRM_BEACON_REQ_SUBELEM_ID_EXT_REQUEST = 11,
|
|
/* 12 - 50 reserved */
|
|
RRM_BEACON_REQ_SUBELEM_ID_AP_CHAN_REPORT = 51,
|
|
/* 52 - 162 reserved */
|
|
RRM_BEACON_REQ_SUBELEM_ID_WIDE_BAND_SWITCH = 163,
|
|
/* 164 - 220 reserved */
|
|
RRM_BEACON_REQ_SUBELEM_ID_VENDOR = 221,
|
|
/* 222 - 255 reserved */
|
|
};
|
|
|
|
/* 802.11-2016 Annex C - dot11PHYType */
|
|
enum rrm_phy_type {
|
|
RRM_PHY_TYPE_DSSS = 2,
|
|
RRM_PHY_TYPE_OFDM = 4,
|
|
RRM_PHY_TYPE_HRDSSS = 5,
|
|
RRM_PHY_TYPE_ERP = 6,
|
|
RRM_PHY_TYPE_HT = 7,
|
|
RRM_PHY_TYPE_DMG = 8,
|
|
RRM_PHY_TYPE_VHT = 9,
|
|
RRM_PHY_TYPE_TVHT = 10,
|
|
};
|
|
|
|
struct rrm_request_info {
|
|
uint8_t dialog_token; /* dialog token in Radio Measurement Request */
|
|
uint8_t mtoken; /* token in measurement request element */
|
|
uint8_t mode;
|
|
uint8_t type; /* request type (only beacon supported) */
|
|
};
|
|
|
|
struct rrm_beacon_req_info {
|
|
struct rrm_request_info info;
|
|
uint8_t oper_class;
|
|
uint8_t channel; /* The single channel provided in request */
|
|
uint16_t duration;
|
|
uint8_t bssid[6]; /* Request filtered by BSSID */
|
|
char ssid[33]; /* Request filtered by SSID */
|
|
bool has_ssid;
|
|
uint32_t scan_id;
|
|
uint64_t scan_start_time;
|
|
};
|
|
|
|
/* Per-netdev state */
|
|
struct rrm_state {
|
|
struct station *station;
|
|
uint32_t watch_id;
|
|
uint32_t ifindex;
|
|
uint64_t wdev_id;
|
|
struct rrm_request_info *pending;
|
|
|
|
uint64_t last_request;
|
|
};
|
|
|
|
/* 802.11, Section 9.4.2.22.7 */
|
|
struct rrm_beacon_report {
|
|
uint8_t oper_class;
|
|
uint8_t channel;
|
|
__le64 scan_start_time;
|
|
__le16 duration;
|
|
uint8_t frame_info;
|
|
uint8_t rcpi;
|
|
uint8_t rsni;
|
|
uint8_t bssid[6];
|
|
uint8_t antenna_id;
|
|
__le32 parent_tsf;
|
|
uint8_t subelements[0];
|
|
} __attribute__ ((packed));
|
|
|
|
static struct l_queue *states;
|
|
static struct l_genl_family *nl80211;
|
|
static uint32_t netdev_watch;
|
|
|
|
static void rrm_info_destroy(void *data)
|
|
{
|
|
struct rrm_request_info *info = data;
|
|
/* TODO: once more request types are added, check type */
|
|
struct rrm_beacon_req_info *beacon = l_container_of(info,
|
|
struct rrm_beacon_req_info,
|
|
info);
|
|
|
|
l_free(beacon);
|
|
}
|
|
|
|
static uint8_t rrm_phy_type(struct scan_bss *bss)
|
|
{
|
|
if (bss->vht_capable)
|
|
return RRM_PHY_TYPE_VHT;
|
|
|
|
if (bss->ht_capable)
|
|
return RRM_PHY_TYPE_HT;
|
|
|
|
/*
|
|
* Default to 802.11g phy type. You can get quite fancy here determining
|
|
* the phy type by looking at the frequency and operator class among
|
|
* other things. Since 802.11a/b are so old, defaulting to 802.11g just
|
|
* removes a lot of complexity. Above, HT/VHT are easy as all you need
|
|
* to look for is the presence of the IE.
|
|
*/
|
|
return RRM_PHY_TYPE_ERP;
|
|
}
|
|
|
|
static void rrm_send_response_cb(struct l_genl_msg *msg, void *user_data)
|
|
{
|
|
int err = l_genl_msg_get_error(msg);
|
|
|
|
if (err < 0)
|
|
l_error("Error sending response: %d", err);
|
|
}
|
|
|
|
static bool rrm_send_response(struct rrm_state *rrm,
|
|
const uint8_t *frame, size_t len)
|
|
{
|
|
struct netdev *netdev = netdev_find(rrm->ifindex);
|
|
const uint8_t *own_addr = netdev_get_address(netdev);
|
|
struct scan_bss *bss = station_get_connected_bss(rrm->station);
|
|
struct l_genl_msg *msg;
|
|
struct iovec iov;
|
|
|
|
iov.iov_base = (void *)frame;
|
|
iov.iov_len = len;
|
|
|
|
msg = nl80211_build_cmd_frame(rrm->ifindex, own_addr, bss->addr,
|
|
bss->frequency, &iov, 1);
|
|
|
|
if (!l_genl_family_send(nl80211, msg, rrm_send_response_cb,
|
|
NULL, NULL)) {
|
|
l_genl_msg_unref(msg);
|
|
l_error("Failed to send report for "MAC,
|
|
MAC_STR(bss->addr));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void rrm_reject_measurement_request(struct rrm_state *rrm,
|
|
uint8_t mode)
|
|
{
|
|
struct rrm_request_info *info = rrm->pending;
|
|
uint8_t frame[8];
|
|
|
|
frame[0] = 0x05; /* Category: Radio Measurement */
|
|
frame[1] = 0x01; /* Action: Radio Measurement Report */
|
|
frame[2] = info->dialog_token;
|
|
frame[3] = IE_TYPE_MEASUREMENT_REPORT;
|
|
frame[4] = 3;
|
|
frame[5] = info->mtoken;
|
|
frame[6] = mode;
|
|
frame[7] = info->type;
|
|
|
|
if (!rrm_send_response(rrm, frame, sizeof(frame)))
|
|
l_error("failed to send rejection");
|
|
|
|
rrm_info_destroy(info);
|
|
rrm->pending = NULL;
|
|
}
|
|
|
|
static void rrm_build_measurement_report(struct rrm_request_info *info,
|
|
const void *report, size_t report_len,
|
|
uint8_t *to)
|
|
{
|
|
*to++ = IE_TYPE_MEASUREMENT_REPORT;
|
|
*to++ = 3 + report_len;
|
|
*to++ = info->mtoken;
|
|
*to++ = 0;
|
|
*to++ = info->type;
|
|
|
|
if (report)
|
|
memcpy(to, report, report_len);
|
|
}
|
|
|
|
/* 802.11 Table 9-154 */
|
|
static uint8_t mdb_to_rcpi(int32_t mdb)
|
|
{
|
|
if (mdb <= -10950)
|
|
return 0;
|
|
else if (mdb >= -10950 && mdb < 0)
|
|
return (2 * (mdb + 11000)) / 100;
|
|
else
|
|
return 220;
|
|
}
|
|
|
|
/*
|
|
* 802.11-2016 11.11.9.1 Beacon report
|
|
*
|
|
* "If the stored beacon information is based on a measurement made by
|
|
* the reporting STA, and if the actual measurement start time,
|
|
* measurement duration, and Parent TSF are available for this
|
|
* measurement, then the beacon report shall include the actual
|
|
* measurement start time, measurement duration, and Parent TSF;
|
|
* otherwise the actual measurement start time, measurement duration,
|
|
* and Parent TSF shall be set to 0. The RCPI and RSNI for that stored
|
|
* beacon measurement may be included in the beacon report; otherwise
|
|
* the beacon report shall indicate that RCPI and RSNI measurements
|
|
* are not available"
|
|
*
|
|
* Since accurate timing is unreliable we are setting start/duration/TSF time to
|
|
* zero for all cases (table, passive, active).
|
|
*/
|
|
static size_t build_report_for_bss(struct rrm_beacon_req_info *beacon,
|
|
struct scan_bss *bss,
|
|
uint8_t *to)
|
|
{
|
|
struct rrm_beacon_report *report = (struct rrm_beacon_report *) to;
|
|
|
|
report->oper_class = beacon->oper_class;
|
|
report->channel = scan_freq_to_channel(bss->frequency, NULL);
|
|
report->scan_start_time = L_CPU_TO_LE64(beacon->scan_start_time);
|
|
report->duration = L_CPU_TO_LE16(beacon->duration);
|
|
report->frame_info = rrm_phy_type(bss);
|
|
report->rcpi = mdb_to_rcpi(bss->signal_strength);
|
|
|
|
/* RSNI not available (could get this from GET_SURVEY) */
|
|
report->rsni = 255;
|
|
memcpy(report->bssid, bss->addr, 6);
|
|
/* Antenna identifier unknown */
|
|
report->antenna_id = 0;
|
|
|
|
/*
|
|
* 802.11 9.4.2.22.7 Beacon report
|
|
*
|
|
* "The Parent TSF field contains the lower 4 octets of the measuring
|
|
* STA's TSF timer value"
|
|
*/
|
|
report->parent_tsf = L_CPU_TO_LE32(bss->parent_tsf);
|
|
|
|
/*
|
|
* TODO: Support optional subelements
|
|
*
|
|
* (see "TODO: Support Reported Frame Body..." below)
|
|
*/
|
|
|
|
return sizeof(struct rrm_beacon_report);
|
|
}
|
|
|
|
static bool bss_in_request_range(struct rrm_beacon_req_info *beacon,
|
|
struct scan_bss *bss)
|
|
{
|
|
uint8_t channel = scan_freq_to_channel(bss->frequency, NULL);
|
|
|
|
/* Must be a table measurement */
|
|
if (beacon->channel == 0 || beacon->channel == 255)
|
|
return true;
|
|
|
|
if (beacon->channel == channel)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool rrm_report_beacon_results(struct rrm_state *rrm,
|
|
struct l_queue *bss_list)
|
|
{
|
|
struct rrm_beacon_req_info *beacon = l_container_of(rrm->pending,
|
|
struct rrm_beacon_req_info,
|
|
info);
|
|
bool wildcard = util_is_broadcast_address(beacon->bssid);
|
|
const struct l_queue_entry *entry;
|
|
uint8_t frame[512];
|
|
uint8_t *ptr = frame;
|
|
|
|
*ptr++ = 0x05; /* Category: Radio Measurement */
|
|
*ptr++ = 0x01; /* Action: Radio Measurement Report */
|
|
*ptr++ = beacon->info.dialog_token;
|
|
|
|
for (entry = l_queue_get_entries(bss_list); entry;
|
|
entry = entry->next) {
|
|
struct scan_bss *bss = entry->data;
|
|
uint8_t report[257];
|
|
size_t report_len;
|
|
|
|
/* If request included a specific BSSID match only this BSS */
|
|
if (!wildcard && memcmp(bss->addr, beacon->bssid, 6) != 0)
|
|
continue;
|
|
|
|
/* If request was for a certain SSID, match only this SSID */
|
|
if (beacon->has_ssid && strncmp(beacon->ssid,
|
|
(const char *)bss->ssid,
|
|
sizeof(bss->ssid)) != 0)
|
|
continue;
|
|
|
|
/*
|
|
* The kernel may have returned a cached scan, so we have to
|
|
* sort out any non-matching frequencies before building the
|
|
* report
|
|
*/
|
|
if (!bss_in_request_range(beacon, bss))
|
|
continue;
|
|
|
|
report_len = build_report_for_bss(beacon, bss, report);
|
|
|
|
rrm_build_measurement_report(&beacon->info, report,
|
|
report_len, ptr);
|
|
|
|
ptr += report_len + 5;
|
|
}
|
|
|
|
rrm_info_destroy(&beacon->info);
|
|
rrm->pending = NULL;
|
|
|
|
return rrm_send_response(rrm, frame, ptr - frame);
|
|
}
|
|
|
|
static void rrm_handle_beacon_table(struct rrm_state *rrm,
|
|
struct rrm_beacon_req_info *beacon)
|
|
{
|
|
struct l_queue *bss_list;
|
|
|
|
bss_list = station_get_bss_list(rrm->station);
|
|
if (!bss_list) {
|
|
rrm_reject_measurement_request(rrm, REPORT_REJECT_INCAPABLE);
|
|
return;
|
|
}
|
|
|
|
if (!rrm_report_beacon_results(rrm, bss_list))
|
|
l_error("Error reporting beacon table results");
|
|
}
|
|
|
|
static bool rrm_scan_results(int err, struct l_queue *bss_list,
|
|
const struct scan_freq_set *freqs,
|
|
void *userdata)
|
|
{
|
|
struct rrm_state *rrm = userdata;
|
|
struct rrm_beacon_req_info *beacon = l_container_of(rrm->pending,
|
|
struct rrm_beacon_req_info,
|
|
info);
|
|
|
|
beacon->scan_id = 0;
|
|
|
|
l_debug("RRM scan results for %u APs", l_queue_length(bss_list));
|
|
|
|
rrm_report_beacon_results(rrm, bss_list);
|
|
/* We aren't saving this BSS list */
|
|
return false;
|
|
}
|
|
|
|
static void rrm_scan_triggered(int err, void *userdata)
|
|
{
|
|
struct rrm_state *rrm = userdata;
|
|
struct rrm_beacon_req_info *beacon = l_container_of(rrm->pending,
|
|
struct rrm_beacon_req_info,
|
|
info);
|
|
|
|
if (err < 0) {
|
|
l_error("Could not start RRM scan");
|
|
rrm_reject_measurement_request(rrm, REPORT_REJECT_INCAPABLE);
|
|
return;
|
|
}
|
|
|
|
beacon->scan_start_time = scan_get_triggered_time(rrm->wdev_id,
|
|
beacon->scan_id);
|
|
}
|
|
|
|
static void rrm_handle_beacon_scan(struct rrm_state *rrm,
|
|
struct rrm_beacon_req_info *beacon,
|
|
bool passive)
|
|
{
|
|
struct scan_freq_set *freqs = scan_freq_set_new();
|
|
struct scan_parameters params = {
|
|
.freqs = freqs,
|
|
.flush = true,
|
|
.duration = beacon->duration,
|
|
.duration_mandatory = test_bit(&beacon->info.mode, 4),
|
|
};
|
|
enum scan_band band = scan_oper_class_to_band(NULL, beacon->oper_class);
|
|
uint32_t freq;
|
|
|
|
freq = scan_channel_to_freq(beacon->channel, band);
|
|
scan_freq_set_add(freqs, freq);
|
|
|
|
if (passive)
|
|
beacon->scan_id = scan_passive_full(rrm->wdev_id, ¶ms,
|
|
rrm_scan_triggered,
|
|
rrm_scan_results, rrm,
|
|
NULL);
|
|
else
|
|
beacon->scan_id = scan_active_full(rrm->wdev_id, ¶ms,
|
|
rrm_scan_triggered,
|
|
rrm_scan_results, rrm,
|
|
NULL);
|
|
|
|
scan_freq_set_free(freqs);
|
|
|
|
if (beacon->scan_id == 0) {
|
|
rrm_info_destroy(&beacon->info);
|
|
rrm->pending = NULL;
|
|
}
|
|
}
|
|
|
|
static bool rrm_verify_beacon_request(const uint8_t *request, size_t len)
|
|
{
|
|
if (len < 13)
|
|
return false;
|
|
|
|
if (request[6] != RRM_BEACON_REQ_MODE_TABLE) {
|
|
/*
|
|
* Rejecting any iterative measurements, only accepting explicit
|
|
* channels and operating classes except for table measurements.
|
|
*/
|
|
if (request[0] == 0 || request[0] == 255 ||
|
|
request[1] == 0 || request[1] == 255)
|
|
return false;
|
|
|
|
/*
|
|
* Not handling random interval requests. We can omit this
|
|
* check for table requests since we just return whatever we
|
|
* have cached.
|
|
*/
|
|
if (!l_memeqzero(request + 2, 2))
|
|
return false;
|
|
}
|
|
|
|
/* Check this is a valid operating class */
|
|
if (!scan_oper_class_to_band(NULL, request[0]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void rrm_handle_beacon_request(struct rrm_state *rrm,
|
|
uint8_t dialog_token,
|
|
const uint8_t *request, size_t len)
|
|
{
|
|
struct wiphy *wiphy = station_get_wiphy(rrm->station);
|
|
struct rrm_beacon_req_info *beacon;
|
|
struct ie_tlv_iter iter;
|
|
/*
|
|
* 802.11-2016 - Table 9-90
|
|
*
|
|
* "All fixed-length fields and elements (default, used when Reporting
|
|
* Detail subelement is not included in a Beacon request)"
|
|
*/
|
|
uint8_t detail = REPORT_DETAIL_NO_FIELDS_OR_ELEMS;
|
|
|
|
beacon = l_new(struct rrm_beacon_req_info, 1);
|
|
|
|
beacon->info.dialog_token = dialog_token;
|
|
beacon->info.mtoken = request[0];
|
|
beacon->info.mode = request[1];
|
|
beacon->info.type = request[2];
|
|
|
|
rrm->pending = &beacon->info;
|
|
/*
|
|
* 802.11-2016 11.11.8
|
|
*
|
|
* "A STA may also refuse to enable triggered autonomous
|
|
* reporting. In this case a Measurement Report element shall be
|
|
* returned to the requesting STA with the refused bit set to 1"
|
|
*
|
|
* At least for the time being, we will not support autonomous
|
|
* reporting, so decline any request to do so.
|
|
*/
|
|
if (test_bit(&beacon->info.mode, 1))
|
|
goto reject_refused;
|
|
|
|
/*
|
|
* Some drivers (non mac80211) do not allow setting a duration/mandatory
|
|
* bit in scan requests. The actual duration value can be ignored in
|
|
* this case but if the requests includes the duration mandatory bit we
|
|
* must reject this request.
|
|
*/
|
|
if (!wiphy_has_ext_feature(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL)
|
|
&& test_bit(&beacon->info.mode, 4))
|
|
goto reject_incapable;
|
|
|
|
/* advance to beacon request */
|
|
request += 3;
|
|
len -= 3;
|
|
|
|
if (!rrm_verify_beacon_request(request, len))
|
|
goto reject_refused;
|
|
|
|
beacon->oper_class = request[0];
|
|
beacon->channel = request[1];
|
|
beacon->duration = l_get_le16(request + 4);
|
|
memcpy(beacon->bssid, request + 7, 6);
|
|
|
|
ie_tlv_iter_init(&iter, request + 13, len - 13);
|
|
|
|
while (ie_tlv_iter_next(&iter)) {
|
|
uint8_t length = ie_tlv_iter_get_length(&iter);
|
|
const unsigned char *data = ie_tlv_iter_get_data(&iter);
|
|
|
|
switch (ie_tlv_iter_get_tag(&iter)) {
|
|
case RRM_BEACON_REQ_SUBELEM_ID_SSID:
|
|
if (beacon->has_ssid)
|
|
continue;
|
|
/*
|
|
* Zero length is wildcard SSID, which has the same
|
|
* effect as no SSID.
|
|
*/
|
|
if (length > 0 && length <= 32) {
|
|
memcpy(beacon->ssid, data, length);
|
|
beacon->has_ssid = true;
|
|
}
|
|
|
|
break;
|
|
case RRM_BEACON_REQ_SUBELEM_ID_REPORTING_DETAIL:
|
|
if (length != 1) {
|
|
l_error("Invalid length in reporting detail");
|
|
goto reject_refused;
|
|
}
|
|
|
|
detail = l_get_u8(data);
|
|
break;
|
|
case RRM_BEACON_REQ_SUBELEM_ID_BEACON_REPORTING:
|
|
/*
|
|
* 802.11-2016 9.4.2.21.7
|
|
*
|
|
* "The Beacon reporting subelement is optionally
|
|
* present in a Beacon request for repeated
|
|
* measurements; otherwise it is not present"
|
|
*
|
|
* Since repeated measurements are not supported we can
|
|
* reject this request now.
|
|
*/
|
|
case RRM_BEACON_REQ_SUBELEM_ID_AP_CHAN_REPORT:
|
|
/*
|
|
* Only supporting single channel requests
|
|
*/
|
|
goto reject_incapable;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TODO: Support Reported Frame Body of 1 and 2. This requires that all
|
|
* fixed length fields are available from the scan request. Currently
|
|
* scan.c parses out only the details we care about. There is also
|
|
* limitations on length, and some IEs are treated specially and
|
|
* truncated. This adds quite a bit of complexity. For now skip these
|
|
* types of frame body reports.
|
|
*/
|
|
if (detail != REPORT_DETAIL_NO_FIELDS_OR_ELEMS) {
|
|
l_debug("Unsupported report detail");
|
|
goto reject_incapable;
|
|
}
|
|
|
|
/* Mode */
|
|
switch (request[6]) {
|
|
case RRM_BEACON_REQ_MODE_PASSIVE:
|
|
rrm_handle_beacon_scan(rrm, beacon, true);
|
|
return;
|
|
case RRM_BEACON_REQ_MODE_ACTIVE:
|
|
rrm_handle_beacon_scan(rrm, beacon, false);
|
|
return;
|
|
case RRM_BEACON_REQ_MODE_TABLE:
|
|
rrm_handle_beacon_table(rrm, beacon);
|
|
return;
|
|
default:
|
|
l_error("Unknown beacon mode %u", request[6]);
|
|
/* fall through to refused */
|
|
}
|
|
|
|
reject_refused:
|
|
rrm_reject_measurement_request(rrm, REPORT_REJECT_REFUSED);
|
|
return;
|
|
|
|
reject_incapable:
|
|
rrm_reject_measurement_request(rrm, REPORT_REJECT_INCAPABLE);
|
|
}
|
|
|
|
static void rrm_cancel_pending(struct rrm_state *rrm)
|
|
{
|
|
if (rrm->pending) {
|
|
struct rrm_beacon_req_info *beacon;
|
|
|
|
beacon = l_container_of(rrm->pending,
|
|
struct rrm_beacon_req_info,
|
|
info);
|
|
if (beacon->scan_id)
|
|
scan_cancel(rrm->wdev_id, beacon->scan_id);
|
|
|
|
rrm_info_destroy(rrm->pending);
|
|
rrm->pending = NULL;
|
|
}
|
|
}
|
|
|
|
static void rrm_station_watch_cb(enum station_state state, void *userdata)
|
|
{
|
|
struct rrm_state *rrm = userdata;
|
|
|
|
switch (state) {
|
|
case STATION_STATE_DISCONNECTING:
|
|
case STATION_STATE_DISCONNECTED:
|
|
rrm_cancel_pending(rrm);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void rrm_station_watch_destroy(void *user_data)
|
|
{
|
|
struct rrm_state *rrm = user_data;
|
|
|
|
l_debug("");
|
|
|
|
rrm_cancel_pending(rrm);
|
|
rrm->watch_id = 0;
|
|
rrm->station = NULL;
|
|
}
|
|
|
|
static void rrm_frame_watch_cb(const struct mmpdu_header *mpdu,
|
|
const void *body, size_t body_len,
|
|
int rssi, void *user_data)
|
|
{
|
|
struct rrm_state *rrm = user_data;
|
|
const uint8_t *request = body;
|
|
uint8_t dialog_token;
|
|
struct ie_tlv_iter iter;
|
|
struct scan_bss *bss;
|
|
|
|
if (!rrm->station) {
|
|
/*
|
|
* Most likely this is the first RRM request, find the station
|
|
* interface and save it off for future use
|
|
*/
|
|
rrm->station = station_find(rrm->ifindex);
|
|
|
|
if (!rrm->station) {
|
|
l_error("station interface could not be found");
|
|
return;
|
|
}
|
|
|
|
rrm->watch_id = station_add_state_watch(rrm->station,
|
|
rrm_station_watch_cb, rrm,
|
|
rrm_station_watch_destroy);
|
|
}
|
|
|
|
/*
|
|
* Ignore if not connected or already have an outstanding request
|
|
*/
|
|
if (station_get_state(rrm->station) != STATION_STATE_CONNECTED ||
|
|
rrm->pending)
|
|
return;
|
|
|
|
bss = station_get_connected_bss(rrm->station);
|
|
|
|
if (memcmp(bss->addr, mpdu->address_2, 6))
|
|
return;
|
|
|
|
if (body_len < 5)
|
|
return;
|
|
|
|
if (request[0] != 0x05)
|
|
return;
|
|
|
|
if (request[1] != 0x00)
|
|
return;
|
|
|
|
/*
|
|
* We have reached our max requests per second, no point in continuing
|
|
*/
|
|
if (l_time_now() - rrm->last_request < MIN_MICROS_BETWEEN_REQUESTS) {
|
|
l_debug("Max requests per second reached, ignoring request");
|
|
return;
|
|
}
|
|
|
|
dialog_token = request[2];
|
|
|
|
/* Update time regardless of success */
|
|
rrm->last_request = l_time_now();
|
|
|
|
ie_tlv_iter_init(&iter, request + 5, body_len - 5);
|
|
|
|
while (ie_tlv_iter_next(&iter)) {
|
|
const uint8_t *req;
|
|
size_t req_len;
|
|
|
|
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_MEASUREMENT_REQUEST)
|
|
continue;
|
|
|
|
req = ie_tlv_iter_get_data(&iter);
|
|
req_len = ie_tlv_iter_get_length(&iter);
|
|
|
|
if (req_len < 3)
|
|
return;
|
|
|
|
switch (req[2]) {
|
|
case 5: /* beacon */
|
|
rrm_handle_beacon_request(rrm, dialog_token, req,
|
|
req_len);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void rrm_state_destroy(void *data)
|
|
{
|
|
struct rrm_state *rrm = data;
|
|
|
|
l_warn("RRM states still exist on exit!");
|
|
|
|
l_free(rrm);
|
|
}
|
|
|
|
static void rrm_new_state(struct netdev *netdev)
|
|
{
|
|
struct rrm_state *rrm;
|
|
uint16_t frame_type = 0x00d0;
|
|
uint8_t prefix[] = { 0x05, 0x00 };
|
|
|
|
if (netdev_get_iftype(netdev) != NETDEV_IFTYPE_STATION)
|
|
return;
|
|
|
|
rrm = l_new(struct rrm_state, 1);
|
|
|
|
rrm->last_request = l_time_now();
|
|
rrm->ifindex = netdev_get_ifindex(netdev);
|
|
rrm->wdev_id = netdev_get_wdev_id(netdev);
|
|
|
|
frame_watch_add(rrm->wdev_id, 0, frame_type, prefix, sizeof(prefix),
|
|
rrm_frame_watch_cb, rrm, NULL);
|
|
|
|
l_queue_push_head(states, rrm);
|
|
}
|
|
|
|
static bool match_ifindex(const void *a, const void *b)
|
|
{
|
|
const struct rrm_state *rrm = a;
|
|
uint32_t ifindex = L_PTR_TO_UINT(b);
|
|
|
|
return rrm->ifindex == ifindex;
|
|
}
|
|
|
|
static void rrm_netdev_watch(struct netdev *netdev,
|
|
enum netdev_watch_event event, void *user_data)
|
|
{
|
|
struct rrm_state *rrm;
|
|
uint32_t ifindex = netdev_get_ifindex(netdev);
|
|
|
|
switch (event) {
|
|
case NETDEV_WATCH_EVENT_NEW:
|
|
rrm_new_state(netdev);
|
|
return;
|
|
case NETDEV_WATCH_EVENT_DEL:
|
|
/*
|
|
* This event is triggered by the netdev being removed, which
|
|
* causes all frame watches to be unregistered by the kernel.
|
|
* Given the above, there's no need to unregister anything
|
|
* manually.
|
|
*/
|
|
rrm = l_queue_remove_if(states, match_ifindex,
|
|
L_UINT_TO_PTR(ifindex));
|
|
if (rrm) {
|
|
if (rrm->station && rrm->watch_id)
|
|
station_remove_state_watch(rrm->station,
|
|
rrm->watch_id);
|
|
|
|
l_free(rrm);
|
|
}
|
|
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int rrm_init(void)
|
|
{
|
|
struct l_genl *genl = iwd_get_genl();
|
|
|
|
states = l_queue_new();
|
|
|
|
nl80211 = l_genl_family_new(genl, NL80211_GENL_NAME);
|
|
|
|
netdev_watch = netdev_watch_add(rrm_netdev_watch, NULL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rrm_exit(void)
|
|
{
|
|
l_genl_family_free(nl80211);
|
|
nl80211 = NULL;
|
|
|
|
netdev_watch_remove(netdev_watch);
|
|
|
|
l_queue_destroy(states, rrm_state_destroy);
|
|
}
|
|
|
|
IWD_MODULE(rrm, rrm_init, rrm_exit);
|
|
IWD_MODULE_DEPENDS(rrm, netdev);
|