hostapd: Add FTM range request

Add FTM range request via RRM. The AP sends Radio measurement request
with FTM range request as a request for the receiving STA to send FTM
requests to the given list of APs. The neighbor report part of the
request is taken from the neighbor database.

The control interface command is:

REQ_RANGE <dst addr> <rand_int> <min_ap> <responder> [<responder>..]

dst addr: MAC address of an associated STA
rand_int: Randomization Interval (0..65535) in TUs
min_ap: Minimum AP Count (1..15); minimum number of requested FTM ranges
	between the associated STA and the listed APs
responder: List of BSSIDs for neighboring APs for which a measurement
	is requested

Signed-off-by: David Spinadel <david.spinadel@intel.com>
This commit is contained in:
David Spinadel 2016-04-06 19:42:12 +03:00 committed by Jouni Malinen
parent f4f185a224
commit 220754c553
8 changed files with 253 additions and 3 deletions

View file

@ -2087,6 +2087,65 @@ static int hostapd_ctrl_iface_req_lci(struct hostapd_data *hapd,
} }
int hostapd_ctrl_iface_req_range(struct hostapd_data *hapd, char *cmd)
{
u8 addr[ETH_ALEN];
char *token, *context = NULL;
int random_interval, min_ap;
u8 responders[ETH_ALEN * RRM_RANGE_REQ_MAX_RESPONDERS];
unsigned int n_responders;
token = str_token(cmd, " ", &context);
if (!token || hwaddr_aton(token, addr)) {
wpa_printf(MSG_INFO,
"CTRL: REQ_RANGE - Bad destination address");
return -1;
}
token = str_token(cmd, " ", &context);
if (!token)
return -1;
random_interval = atoi(token);
if (random_interval < 0 || random_interval > 0xffff)
return -1;
token = str_token(cmd, " ", &context);
if (!token)
return -1;
min_ap = atoi(token);
if (min_ap <= 0 || min_ap > WLAN_RRM_RANGE_REQ_MAX_MIN_AP)
return -1;
n_responders = 0;
while ((token = str_token(cmd, " ", &context))) {
if (n_responders == RRM_RANGE_REQ_MAX_RESPONDERS) {
wpa_printf(MSG_INFO,
"CTRL: REQ_RANGE: Too many responders");
return -1;
}
if (hwaddr_aton(token, responders + n_responders * ETH_ALEN)) {
wpa_printf(MSG_INFO,
"CTRL: REQ_RANGE: Bad responder address");
return -1;
}
n_responders++;
}
if (!n_responders) {
wpa_printf(MSG_INFO,
"CTRL: REQ_RANGE - No FTM responder address");
return -1;
}
return hostapd_send_range_req(hapd, addr, random_interval, min_ap,
responders, n_responders);
}
static int hostapd_ctrl_iface_set_neighbor(struct hostapd_data *hapd, char *buf) static int hostapd_ctrl_iface_set_neighbor(struct hostapd_data *hapd, char *buf)
{ {
struct wpa_ssid_value ssid; struct wpa_ssid_value ssid;
@ -2457,6 +2516,9 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
} else if (os_strncmp(buf, "REQ_LCI ", 8) == 0) { } else if (os_strncmp(buf, "REQ_LCI ", 8) == 0) {
if (hostapd_ctrl_iface_req_lci(hapd, buf + 8)) if (hostapd_ctrl_iface_req_lci(hapd, buf + 8))
reply_len = -1; reply_len = -1;
} else if (os_strncmp(buf, "REQ_RANGE ", 10) == 0) {
if (hostapd_ctrl_iface_req_range(hapd, buf + 10))
reply_len = -1;
} else { } else {
os_memcpy(reply, "UNKNOWN COMMAND\n", 16); os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
reply_len = 16; reply_len = 16;

View file

@ -1206,6 +1206,18 @@ static int hostapd_cli_cmd_req_lci(struct wpa_ctrl *ctrl, int argc,
} }
static int hostapd_cli_cmd_req_range(struct wpa_ctrl *ctrl, int argc,
char *argv[])
{
if (argc < 4) {
printf("Invalid req_range command: needs at least 4 arguments - dest address, randomization interval, min AP count, and 1 to 16 AP addresses\n");
return -1;
}
return hostapd_cli_cmd(ctrl, "REQ_RANGE", 4, argc, argv);
}
struct hostapd_cli_cmd { struct hostapd_cli_cmd {
const char *cmd; const char *cmd;
int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]); int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]);
@ -1270,6 +1282,7 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
{ "set_neighbor", hostapd_cli_cmd_set_neighbor }, { "set_neighbor", hostapd_cli_cmd_set_neighbor },
{ "remove_neighbor", hostapd_cli_cmd_remove_neighbor }, { "remove_neighbor", hostapd_cli_cmd_remove_neighbor },
{ "req_lci", hostapd_cli_cmd_req_lci }, { "req_lci", hostapd_cli_cmd_req_lci },
{ "req_range", hostapd_cli_cmd_req_range },
{ NULL, NULL } { NULL, NULL }
}; };

View file

@ -300,7 +300,9 @@ struct hostapd_data {
struct dl_list nr_db; struct dl_list nr_db;
u8 lci_req_token; u8 lci_req_token;
u8 range_req_token;
unsigned int lci_req_active:1; unsigned int lci_req_active:1;
unsigned int range_req_active:1;
}; };

View file

@ -14,7 +14,7 @@
#include "neighbor_db.h" #include "neighbor_db.h"
static struct hostapd_neighbor_entry * struct hostapd_neighbor_entry *
hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid, hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid,
const struct wpa_ssid_value *ssid) const struct wpa_ssid_value *ssid)
{ {
@ -23,8 +23,10 @@ hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid,
dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry,
list) { list) {
if (os_memcmp(bssid, nr->bssid, ETH_ALEN) == 0 && if (os_memcmp(bssid, nr->bssid, ETH_ALEN) == 0 &&
ssid->ssid_len == nr->ssid.ssid_len && (!ssid ||
os_memcmp(ssid->ssid, nr->ssid.ssid, ssid->ssid_len) == 0) (ssid->ssid_len == nr->ssid.ssid_len &&
os_memcmp(ssid->ssid, nr->ssid.ssid,
ssid->ssid_len) == 0)))
return nr; return nr;
} }
return NULL; return NULL;

View file

@ -10,6 +10,9 @@
#ifndef NEIGHBOR_DB_H #ifndef NEIGHBOR_DB_H
#define NEIGHBOR_DB_H #define NEIGHBOR_DB_H
struct hostapd_neighbor_entry *
hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid,
const struct wpa_ssid_value *ssid);
int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid, int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid,
const struct wpa_ssid_value *ssid, const struct wpa_ssid_value *ssid,
const struct wpabuf *nr, const struct wpabuf *lci, const struct wpabuf *nr, const struct wpabuf *lci,

View file

@ -44,6 +44,31 @@ static void hostapd_handle_lci_report(struct hostapd_data *hapd, u8 token,
} }
static void hostapd_range_rep_timeout_handler(void *eloop_data, void *user_ctx)
{
struct hostapd_data *hapd = eloop_data;
wpa_printf(MSG_DEBUG, "RRM: Range request (token %u) timed out",
hapd->range_req_token);
hapd->range_req_active = 0;
}
static void hostapd_handle_range_report(struct hostapd_data *hapd, u8 token,
const u8 *pos, size_t len)
{
if (!hapd->range_req_active || hapd->range_req_token != token) {
wpa_printf(MSG_DEBUG, "Unexpected range report, token %u",
token);
return;
}
hapd->range_req_active = 0;
eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL);
wpa_printf(MSG_DEBUG, "Range report token %u len %zu", token, len);
}
static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd, static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd,
const u8 *buf, size_t len) const u8 *buf, size_t len)
{ {
@ -67,6 +92,9 @@ static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd,
case MEASURE_TYPE_LCI: case MEASURE_TYPE_LCI:
hostapd_handle_lci_report(hapd, token, ie + 2, ie[1]); hostapd_handle_lci_report(hapd, token, ie + 2, ie[1]);
break; break;
case MEASURE_TYPE_FTM_RANGE:
hostapd_handle_range_report(hapd, token, ie + 2, ie[1]);
break;
default: default:
wpa_printf(MSG_DEBUG, wpa_printf(MSG_DEBUG,
"Measurement report type %u is not supported", "Measurement report type %u is not supported",
@ -386,9 +414,131 @@ int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr)
} }
int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr,
u16 random_interval, u8 min_ap,
const u8 *responders, unsigned int n_responders)
{
struct wpabuf *buf;
struct sta_info *sta;
u8 *len;
unsigned int i;
int ret;
wpa_printf(MSG_DEBUG, "Request range: dest addr " MACSTR
" rand interval %u min AP %u n_responders %u", MAC2STR(addr),
random_interval, min_ap, n_responders);
if (min_ap == 0 || min_ap > n_responders) {
wpa_printf(MSG_INFO, "Request range: Wrong min AP count");
return -1;
}
sta = ap_get_sta(hapd, addr);
if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) {
wpa_printf(MSG_INFO,
"Request range: Destination address is not connected");
return -1;
}
if (!(sta->rrm_enabled_capa[4] & WLAN_RRM_CAPS_FTM_RANGE_REPORT)) {
wpa_printf(MSG_ERROR,
"Request range: Destination station does not support FTM range report in RRM");
return -1;
}
if (hapd->range_req_active) {
wpa_printf(MSG_DEBUG,
"Request range: Range request is already in process; overriding");
hapd->range_req_active = 0;
eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
hostapd_range_rep_timeout_handler, hapd,
NULL);
}
/* Action + measurement type + token + reps + EID + len = 7 */
buf = wpabuf_alloc(7 + 255);
if (!buf)
return -1;
hapd->range_req_token++;
if (!hapd->range_req_token) /* For wraparounds */
hapd->range_req_token++;
/* IEEE P802.11-REVmc/D5.0, 9.6.7.2 */
wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST);
wpabuf_put_u8(buf, hapd->range_req_token); /* Dialog Token */
wpabuf_put_le16(buf, 0); /* Number of Repetitions */
/* IEEE P802.11-REVmc/D5.0, 9.4.2.21 */
wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
len = wpabuf_put(buf, 1); /* Length will be set later */
wpabuf_put_u8(buf, 1); /* Measurement Token */
/*
* Parallel and Enable bits are 0; Duration, Request, and Report are
* reserved.
*/
wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
wpabuf_put_u8(buf, MEASURE_TYPE_FTM_RANGE); /* Measurement Type */
/* IEEE P802.11-REVmc/D5.0, 9.4.2.21.19 */
wpabuf_put_le16(buf, random_interval); /* Randomization Interval */
wpabuf_put_u8(buf, min_ap); /* Minimum AP Count */
/* FTM Range Subelements */
/*
* Taking the neighbor report part of the range request from neighbor
* database instead of requesting the separate bits of data from the
* user.
*/
for (i = 0; i < n_responders; i++) {
struct hostapd_neighbor_entry *nr;
nr = hostapd_neighbor_get(hapd, responders + ETH_ALEN * i,
NULL);
if (!nr) {
wpa_printf(MSG_INFO, "Missing neighbor report for "
MACSTR, MAC2STR(responders + ETH_ALEN * i));
wpabuf_free(buf);
return -1;
}
if (wpabuf_tailroom(buf) < 2 + wpabuf_len(nr->nr)) {
wpa_printf(MSG_ERROR, "Too long range request");
wpabuf_free(buf);
return -1;
}
wpabuf_put_u8(buf, WLAN_EID_NEIGHBOR_REPORT);
wpabuf_put_u8(buf, wpabuf_len(nr->nr));
wpabuf_put_buf(buf, nr->nr);
}
/* Action + measurement type + token + reps + EID + len = 7 */
*len = wpabuf_len(buf) - 7;
ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
wpabuf_head(buf), wpabuf_len(buf));
wpabuf_free(buf);
if (ret)
return ret;
hapd->range_req_active = 1;
eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
hostapd_range_rep_timeout_handler, hapd, NULL);
return 0;
}
void hostapd_clean_rrm(struct hostapd_data *hapd) void hostapd_clean_rrm(struct hostapd_data *hapd)
{ {
hostpad_free_neighbor_db(hapd); hostpad_free_neighbor_db(hapd);
eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL); eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL);
hapd->lci_req_active = 0; hapd->lci_req_active = 0;
eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL);
hapd->range_req_active = 0;
} }

View file

@ -10,9 +10,19 @@
#ifndef RRM_H #ifndef RRM_H
#define RRM_H #define RRM_H
/*
* Max measure request length is 255, -6 of the body we have 249 for the
* neighbor report elements. Each neighbor report element is at least 2 + 13
* bytes, so we can't have more than 16 responders in the request.
*/
#define RRM_RANGE_REQ_MAX_RESPONDERS 16
void hostapd_handle_radio_measurement(struct hostapd_data *hapd, void hostapd_handle_radio_measurement(struct hostapd_data *hapd,
const u8 *buf, size_t len); const u8 *buf, size_t len);
int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr); int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr);
int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr,
u16 random_interval, u8 min_ap,
const u8 *responders, unsigned int n_responders);
void hostapd_clean_rrm(struct hostapd_data *hapd); void hostapd_clean_rrm(struct hostapd_data *hapd);
#endif /* RRM_H */ #endif /* RRM_H */

View file

@ -368,6 +368,14 @@
#define WLAN_RRM_CAPS_NEIGHBOR_REPORT BIT(1) #define WLAN_RRM_CAPS_NEIGHBOR_REPORT BIT(1)
/* byte 2 (out of 5) */ /* byte 2 (out of 5) */
#define WLAN_RRM_CAPS_LCI_MEASUREMENT BIT(4) #define WLAN_RRM_CAPS_LCI_MEASUREMENT BIT(4)
/* byte 5 (out of 5) */
#define WLAN_RRM_CAPS_FTM_RANGE_REPORT BIT(2)
/*
* IEEE P802.11-REVmc/D5.0, 9.4.2.21.19 (Fine Timing Measurement Range
* request) - Minimum AP count
*/
#define WLAN_RRM_RANGE_REQ_MAX_MIN_AP 15
/* Timeout Interval Type */ /* Timeout Interval Type */
#define WLAN_TIMEOUT_REASSOC_DEADLINE 1 #define WLAN_TIMEOUT_REASSOC_DEADLINE 1