hostapd/wpa_supplicant/rrm.c
Avraham Stern e1b96e1126 wpa_supplicant: Send rejection for unsupported radio measurements
Send measurement report with the mode field set to incapable in response
to measurement requests with unsupported measurement types.

In addition, measurements requests that request parallel measurements
are rejected since these features are not supported.

Measurement request frames with the enable bit set are ignored since
these are not really requesting measurements and are not supported for
now.

Signed-off-by: Avraham Stern <avraham.stern@intel.com>
2017-01-03 15:18:29 +02:00

624 lines
17 KiB
C

/*
* wpa_supplicant - Radio Measurements
* Copyright (c) 2003-2016, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "utils/common.h"
#include "utils/eloop.h"
#include "common/ieee802_11_common.h"
#include "wpa_supplicant_i.h"
#include "driver_i.h"
#include "bss.h"
static void wpas_rrm_neighbor_rep_timeout_handler(void *data, void *user_ctx)
{
struct rrm_data *rrm = data;
if (!rrm->notify_neighbor_rep) {
wpa_printf(MSG_ERROR,
"RRM: Unexpected neighbor report timeout");
return;
}
wpa_printf(MSG_DEBUG, "RRM: Notifying neighbor report - NONE");
rrm->notify_neighbor_rep(rrm->neighbor_rep_cb_ctx, NULL);
rrm->notify_neighbor_rep = NULL;
rrm->neighbor_rep_cb_ctx = NULL;
}
/*
* wpas_rrm_reset - Clear and reset all RRM data in wpa_supplicant
* @wpa_s: Pointer to wpa_supplicant
*/
void wpas_rrm_reset(struct wpa_supplicant *wpa_s)
{
wpa_s->rrm.rrm_used = 0;
eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, &wpa_s->rrm,
NULL);
if (wpa_s->rrm.notify_neighbor_rep)
wpas_rrm_neighbor_rep_timeout_handler(&wpa_s->rrm, NULL);
wpa_s->rrm.next_neighbor_rep_token = 1;
}
/*
* wpas_rrm_process_neighbor_rep - Handle incoming neighbor report
* @wpa_s: Pointer to wpa_supplicant
* @report: Neighbor report buffer, prefixed by a 1-byte dialog token
* @report_len: Length of neighbor report buffer
*/
void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s,
const u8 *report, size_t report_len)
{
struct wpabuf *neighbor_rep;
wpa_hexdump(MSG_DEBUG, "RRM: New Neighbor Report", report, report_len);
if (report_len < 1)
return;
if (report[0] != wpa_s->rrm.next_neighbor_rep_token - 1) {
wpa_printf(MSG_DEBUG,
"RRM: Discarding neighbor report with token %d (expected %d)",
report[0], wpa_s->rrm.next_neighbor_rep_token - 1);
return;
}
eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, &wpa_s->rrm,
NULL);
if (!wpa_s->rrm.notify_neighbor_rep) {
wpa_printf(MSG_ERROR, "RRM: Unexpected neighbor report");
return;
}
/* skipping the first byte, which is only an id (dialog token) */
neighbor_rep = wpabuf_alloc(report_len - 1);
if (neighbor_rep == NULL)
return;
wpabuf_put_data(neighbor_rep, report + 1, report_len - 1);
wpa_printf(MSG_DEBUG, "RRM: Notifying neighbor report (token = %d)",
report[0]);
wpa_s->rrm.notify_neighbor_rep(wpa_s->rrm.neighbor_rep_cb_ctx,
neighbor_rep);
wpa_s->rrm.notify_neighbor_rep = NULL;
wpa_s->rrm.neighbor_rep_cb_ctx = NULL;
}
#if defined(__CYGWIN__) || defined(CONFIG_NATIVE_WINDOWS)
/* Workaround different, undefined for Windows, error codes used here */
#define ENOTCONN -1
#define EOPNOTSUPP -1
#define ECANCELED -1
#endif
/* Measurement Request element + Location Subject + Maximum Age subelement */
#define MEASURE_REQUEST_LCI_LEN (3 + 1 + 4)
/* Measurement Request element + Location Civic Request */
#define MEASURE_REQUEST_CIVIC_LEN (3 + 5)
/**
* wpas_rrm_send_neighbor_rep_request - Request a neighbor report from our AP
* @wpa_s: Pointer to wpa_supplicant
* @ssid: if not null, this is sent in the request. Otherwise, no SSID IE
* is sent in the request.
* @lci: if set, neighbor request will include LCI request
* @civic: if set, neighbor request will include civic location request
* @cb: Callback function to be called once the requested report arrives, or
* timed out after RRM_NEIGHBOR_REPORT_TIMEOUT seconds.
* In the former case, 'neighbor_rep' is a newly allocated wpabuf, and it's
* the requester's responsibility to free it.
* In the latter case NULL will be sent in 'neighbor_rep'.
* @cb_ctx: Context value to send the callback function
* Returns: 0 in case of success, negative error code otherwise
*
* In case there is a previous request which has not been answered yet, the
* new request fails. The caller may retry after RRM_NEIGHBOR_REPORT_TIMEOUT.
* Request must contain a callback function.
*/
int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
const struct wpa_ssid_value *ssid,
int lci, int civic,
void (*cb)(void *ctx,
struct wpabuf *neighbor_rep),
void *cb_ctx)
{
struct wpabuf *buf;
const u8 *rrm_ie;
if (wpa_s->wpa_state != WPA_COMPLETED || wpa_s->current_ssid == NULL) {
wpa_printf(MSG_DEBUG, "RRM: No connection, no RRM.");
return -ENOTCONN;
}
if (!wpa_s->rrm.rrm_used) {
wpa_printf(MSG_DEBUG, "RRM: No RRM in current connection.");
return -EOPNOTSUPP;
}
rrm_ie = wpa_bss_get_ie(wpa_s->current_bss,
WLAN_EID_RRM_ENABLED_CAPABILITIES);
if (!rrm_ie || !(wpa_s->current_bss->caps & IEEE80211_CAP_RRM) ||
!(rrm_ie[2] & WLAN_RRM_CAPS_NEIGHBOR_REPORT)) {
wpa_printf(MSG_DEBUG,
"RRM: No network support for Neighbor Report.");
return -EOPNOTSUPP;
}
if (!cb) {
wpa_printf(MSG_DEBUG,
"RRM: Neighbor Report request must provide a callback.");
return -EINVAL;
}
/* Refuse if there's a live request */
if (wpa_s->rrm.notify_neighbor_rep) {
wpa_printf(MSG_DEBUG,
"RRM: Currently handling previous Neighbor Report.");
return -EBUSY;
}
/* 3 = action category + action code + dialog token */
buf = wpabuf_alloc(3 + (ssid ? 2 + ssid->ssid_len : 0) +
(lci ? 2 + MEASURE_REQUEST_LCI_LEN : 0) +
(civic ? 2 + MEASURE_REQUEST_CIVIC_LEN : 0));
if (buf == NULL) {
wpa_printf(MSG_DEBUG,
"RRM: Failed to allocate Neighbor Report Request");
return -ENOMEM;
}
wpa_printf(MSG_DEBUG, "RRM: Neighbor report request (for %s), token=%d",
(ssid ? wpa_ssid_txt(ssid->ssid, ssid->ssid_len) : ""),
wpa_s->rrm.next_neighbor_rep_token);
wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
wpabuf_put_u8(buf, WLAN_RRM_NEIGHBOR_REPORT_REQUEST);
wpabuf_put_u8(buf, wpa_s->rrm.next_neighbor_rep_token);
if (ssid) {
wpabuf_put_u8(buf, WLAN_EID_SSID);
wpabuf_put_u8(buf, ssid->ssid_len);
wpabuf_put_data(buf, ssid->ssid, ssid->ssid_len);
}
if (lci) {
/* IEEE P802.11-REVmc/D5.0 9.4.2.21 */
wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
wpabuf_put_u8(buf, MEASURE_REQUEST_LCI_LEN);
/*
* Measurement token; nonzero number that is unique among the
* Measurement Request elements in a particular frame.
*/
wpabuf_put_u8(buf, 1); /* Measurement Token */
/*
* Parallel, Enable, Request, and Report bits are 0, Duration is
* reserved.
*/
wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
wpabuf_put_u8(buf, MEASURE_TYPE_LCI); /* Measurement Type */
/* IEEE P802.11-REVmc/D5.0 9.4.2.21.10 - LCI request */
/* Location Subject */
wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
/* Optional Subelements */
/*
* IEEE P802.11-REVmc/D5.0 Figure 9-170
* The Maximum Age subelement is required, otherwise the AP can
* send only data that was determined after receiving the
* request. Setting it here to unlimited age.
*/
wpabuf_put_u8(buf, LCI_REQ_SUBELEM_MAX_AGE);
wpabuf_put_u8(buf, 2);
wpabuf_put_le16(buf, 0xffff);
}
if (civic) {
/* IEEE P802.11-REVmc/D5.0 9.4.2.21 */
wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
wpabuf_put_u8(buf, MEASURE_REQUEST_CIVIC_LEN);
/*
* Measurement token; nonzero number that is unique among the
* Measurement Request elements in a particular frame.
*/
wpabuf_put_u8(buf, 2); /* Measurement Token */
/*
* Parallel, Enable, Request, and Report bits are 0, Duration is
* reserved.
*/
wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
/* Measurement Type */
wpabuf_put_u8(buf, MEASURE_TYPE_LOCATION_CIVIC);
/* IEEE P802.11-REVmc/D5.0 9.4.2.21.14:
* Location Civic request */
/* Location Subject */
wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
wpabuf_put_u8(buf, 0); /* Civic Location Type: IETF RFC 4776 */
/* Location Service Interval Units: Seconds */
wpabuf_put_u8(buf, 0);
/* Location Service Interval: 0 - Only one report is requested
*/
wpabuf_put_le16(buf, 0);
/* No optional subelements */
}
wpa_s->rrm.next_neighbor_rep_token++;
if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
wpa_s->own_addr, wpa_s->bssid,
wpabuf_head(buf), wpabuf_len(buf), 0) < 0) {
wpa_printf(MSG_DEBUG,
"RRM: Failed to send Neighbor Report Request");
wpabuf_free(buf);
return -ECANCELED;
}
wpa_s->rrm.neighbor_rep_cb_ctx = cb_ctx;
wpa_s->rrm.notify_neighbor_rep = cb;
eloop_register_timeout(RRM_NEIGHBOR_REPORT_TIMEOUT, 0,
wpas_rrm_neighbor_rep_timeout_handler,
&wpa_s->rrm, NULL);
wpabuf_free(buf);
return 0;
}
static int wpas_rrm_report_elem(struct wpabuf *buf, u8 token, u8 mode, u8 type,
const u8 *data, size_t data_len)
{
if (wpabuf_tailroom(buf) < 5 + data_len)
return -1;
wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT);
wpabuf_put_u8(buf, 3 + data_len);
wpabuf_put_u8(buf, token);
wpabuf_put_u8(buf, mode);
wpabuf_put_u8(buf, type);
if (data_len)
wpabuf_put_data(buf, data, data_len);
return 0;
}
static struct wpabuf * wpas_rrm_build_lci_report(struct wpa_supplicant *wpa_s,
const u8 *request, size_t len,
struct wpabuf *report)
{
u8 token, type, subject;
u16 max_age = 0;
struct os_reltime t, diff;
unsigned long diff_l;
u8 *ptoken;
const u8 *subelem;
if (!wpa_s->lci || len < 3 + 4)
return report;
token = *request++;
/* Measurement request mode isn't used */
request++;
type = *request++;
subject = *request++;
len -= 4;
wpa_printf(MSG_DEBUG,
"Measurement request token %u type %u location subject %u",
token, type, subject);
if (type != MEASURE_TYPE_LCI || subject != LOCATION_SUBJECT_REMOTE) {
wpa_printf(MSG_INFO,
"Not building LCI report - bad type or location subject");
return report;
}
/* Subelements are formatted exactly like elements */
subelem = get_ie(request, len, LCI_REQ_SUBELEM_MAX_AGE);
if (subelem && subelem[1] == 2)
max_age = WPA_GET_LE16(subelem + 2);
if (os_get_reltime(&t))
return report;
os_reltime_sub(&t, &wpa_s->lci_time, &diff);
/* LCI age is calculated in 10th of a second units. */
diff_l = diff.sec * 10 + diff.usec / 100000;
if (max_age != 0xffff && max_age < diff_l)
return report;
if (wpabuf_resize(&report, 2 + wpabuf_len(wpa_s->lci)))
return report;
wpabuf_put_u8(report, WLAN_EID_MEASURE_REPORT);
wpabuf_put_u8(report, wpabuf_len(wpa_s->lci));
/* We'll override user's measurement token */
ptoken = wpabuf_put(report, 0);
wpabuf_put_buf(report, wpa_s->lci);
*ptoken = token;
return report;
}
static void wpas_rrm_send_msr_report_mpdu(struct wpa_supplicant *wpa_s,
const u8 *data, size_t len)
{
struct wpabuf *report = wpabuf_alloc(len + 3);
if (!report)
return;
wpabuf_put_u8(report, WLAN_ACTION_RADIO_MEASUREMENT);
wpabuf_put_u8(report, WLAN_RRM_RADIO_MEASUREMENT_REPORT);
wpabuf_put_u8(report, wpa_s->rrm.token);
wpabuf_put_data(report, data, len);
if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
wpa_s->own_addr, wpa_s->bssid,
wpabuf_head(report), wpabuf_len(report), 0)) {
wpa_printf(MSG_ERROR,
"RRM: Radio measurement report failed: Sending Action frame failed");
}
wpabuf_free(report);
}
static void wpas_rrm_send_msr_report(struct wpa_supplicant *wpa_s,
struct wpabuf *buf)
{
int len = wpabuf_len(buf);
const u8 *pos = wpabuf_head_u8(buf), *next = pos;
#define MPDU_REPORT_LEN (int) (IEEE80211_MAX_MMPDU_SIZE - IEEE80211_HDRLEN - 3)
while (len) {
int send_len = (len > MPDU_REPORT_LEN) ? next - pos : len;
if (send_len == len ||
(send_len + next[1] + 2) > MPDU_REPORT_LEN) {
wpas_rrm_send_msr_report_mpdu(wpa_s, pos, send_len);
len -= send_len;
pos = next;
}
next += next[1] + 2;
}
#undef MPDU_REPORT_LEN
}
static int
wpas_rrm_handle_msr_req_element(
struct wpa_supplicant *wpa_s,
const struct rrm_measurement_request_element *req,
struct wpabuf **buf)
{
wpa_printf(MSG_DEBUG, "Measurement request type %d token %d",
req->type, req->token);
if (req->mode & MEASUREMENT_REQUEST_MODE_ENABLE) {
/* Enable bit is not supported for now */
wpa_printf(MSG_DEBUG, "RRM: Enable bit not supported, ignore");
return 0;
}
if ((req->mode & MEASUREMENT_REQUEST_MODE_PARALLEL) &&
req->type > MEASURE_TYPE_RPI_HIST) {
/* Parallel measurements are not supported for now */
wpa_printf(MSG_DEBUG,
"RRM: Parallel measurements are not supported, reject");
goto reject;
}
switch (req->type) {
case MEASURE_TYPE_LCI:
*buf = wpas_rrm_build_lci_report(wpa_s, &req->token, req->len,
*buf);
return 0;
default:
wpa_printf(MSG_INFO,
"RRM: Unsupported radio measurement type %u",
req->type);
break;
}
reject:
if (wpabuf_resize(buf, sizeof(struct rrm_measurement_report_element))) {
wpa_printf(MSG_DEBUG, "RRM: Memory allocation failed");
return -1;
}
if (wpas_rrm_report_elem(*buf, req->token,
MEASUREMENT_REPORT_MODE_REJECT_INCAPABLE,
req->type, NULL, 0) < 0) {
wpa_printf(MSG_DEBUG, "RRM: Failed to add report element");
return -1;
}
return 0;
}
static struct wpabuf *
wpas_rrm_process_msr_req_elems(struct wpa_supplicant *wpa_s, const u8 *pos,
size_t len)
{
struct wpabuf *buf = NULL;
while (len) {
const struct rrm_measurement_request_element *req;
int res;
if (len < 2) {
wpa_printf(MSG_DEBUG, "RRM: Truncated element");
goto out;
}
req = (const struct rrm_measurement_request_element *) pos;
if (req->eid != WLAN_EID_MEASURE_REQUEST) {
wpa_printf(MSG_DEBUG,
"RRM: Expected Measurement Request element, but EID is %u",
req->eid);
goto out;
}
if (req->len < 3) {
wpa_printf(MSG_DEBUG, "RRM: Element length too short");
goto out;
}
if (req->len > len - 2) {
wpa_printf(MSG_DEBUG, "RRM: Element length too long");
goto out;
}
res = wpas_rrm_handle_msr_req_element(wpa_s, req, &buf);
if (res < 0)
goto out;
pos += req->len + 2;
len -= req->len + 2;
}
return buf;
out:
wpabuf_free(buf);
return NULL;
}
void wpas_rrm_handle_radio_measurement_request(struct wpa_supplicant *wpa_s,
const u8 *src,
const u8 *frame, size_t len)
{
struct wpabuf *report;
if (wpa_s->wpa_state != WPA_COMPLETED) {
wpa_printf(MSG_INFO,
"RRM: Ignoring radio measurement request: Not associated");
return;
}
if (!wpa_s->rrm.rrm_used) {
wpa_printf(MSG_INFO,
"RRM: Ignoring radio measurement request: Not RRM network");
return;
}
if (len < 3) {
wpa_printf(MSG_INFO,
"RRM: Ignoring too short radio measurement request");
return;
}
wpa_s->rrm.token = *frame;
/* Number of repetitions is not supported */
report = wpas_rrm_process_msr_req_elems(wpa_s, frame + 3, len - 3);
if (!report)
return;
wpas_rrm_send_msr_report(wpa_s, report);
wpabuf_free(report);
}
void wpas_rrm_handle_link_measurement_request(struct wpa_supplicant *wpa_s,
const u8 *src,
const u8 *frame, size_t len,
int rssi)
{
struct wpabuf *buf;
const struct rrm_link_measurement_request *req;
struct rrm_link_measurement_report report;
if (wpa_s->wpa_state != WPA_COMPLETED) {
wpa_printf(MSG_INFO,
"RRM: Ignoring link measurement request. Not associated");
return;
}
if (!wpa_s->rrm.rrm_used) {
wpa_printf(MSG_INFO,
"RRM: Ignoring link measurement request. Not RRM network");
return;
}
if (!(wpa_s->drv_rrm_flags & WPA_DRIVER_FLAGS_TX_POWER_INSERTION)) {
wpa_printf(MSG_INFO,
"RRM: Measurement report failed. TX power insertion not supported");
return;
}
req = (const struct rrm_link_measurement_request *) frame;
if (len < sizeof(*req)) {
wpa_printf(MSG_INFO,
"RRM: Link measurement report failed. Request too short");
return;
}
os_memset(&report, 0, sizeof(report));
report.tpc.eid = WLAN_EID_TPC_REPORT;
report.tpc.len = 2;
report.rsni = 255; /* 255 indicates that RSNI is not available */
report.dialog_token = req->dialog_token;
/*
* It's possible to estimate RCPI based on RSSI in dBm. This
* calculation will not reflect the correct value for high rates,
* but it's good enough for Action frames which are transmitted
* with up to 24 Mbps rates.
*/
if (!rssi)
report.rcpi = 255; /* not available */
else if (rssi < -110)
report.rcpi = 0;
else if (rssi > 0)
report.rcpi = 220;
else
report.rcpi = (rssi + 110) * 2;
/* action_category + action_code */
buf = wpabuf_alloc(2 + sizeof(report));
if (buf == NULL) {
wpa_printf(MSG_ERROR,
"RRM: Link measurement report failed. Buffer allocation failed");
return;
}
wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
wpabuf_put_u8(buf, WLAN_RRM_LINK_MEASUREMENT_REPORT);
wpabuf_put_data(buf, &report, sizeof(report));
wpa_hexdump(MSG_DEBUG, "RRM: Link measurement report:",
wpabuf_head(buf), wpabuf_len(buf));
if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
wpa_s->own_addr, wpa_s->bssid,
wpabuf_head(buf), wpabuf_len(buf), 0)) {
wpa_printf(MSG_ERROR,
"RRM: Link measurement report failed. Send action failed");
}
wpabuf_free(buf);
}