diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 4c2b55940..7040069ad 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -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) { 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) { if (hostapd_ctrl_iface_req_lci(hapd, buf + 8)) 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 { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index 563d8d59e..ff133f6ae 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -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 { const char *cmd; 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 }, { "remove_neighbor", hostapd_cli_cmd_remove_neighbor }, { "req_lci", hostapd_cli_cmd_req_lci }, + { "req_range", hostapd_cli_cmd_req_range }, { NULL, NULL } }; diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 76b0ca6d0..4dba8cb36 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -300,7 +300,9 @@ struct hostapd_data { struct dl_list nr_db; u8 lci_req_token; + u8 range_req_token; unsigned int lci_req_active:1; + unsigned int range_req_active:1; }; diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c index 1156aa2ad..a2efff618 100644 --- a/src/ap/neighbor_db.c +++ b/src/ap/neighbor_db.c @@ -14,7 +14,7 @@ #include "neighbor_db.h" -static struct hostapd_neighbor_entry * +struct hostapd_neighbor_entry * hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid, 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, list) { if (os_memcmp(bssid, nr->bssid, ETH_ALEN) == 0 && - ssid->ssid_len == nr->ssid.ssid_len && - os_memcmp(ssid->ssid, nr->ssid.ssid, ssid->ssid_len) == 0) + (!ssid || + (ssid->ssid_len == nr->ssid.ssid_len && + os_memcmp(ssid->ssid, nr->ssid.ssid, + ssid->ssid_len) == 0))) return nr; } return NULL; diff --git a/src/ap/neighbor_db.h b/src/ap/neighbor_db.h index 40c42e725..c22e043c1 100644 --- a/src/ap/neighbor_db.h +++ b/src/ap/neighbor_db.h @@ -10,6 +10,9 @@ #ifndef 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, const struct wpa_ssid_value *ssid, const struct wpabuf *nr, const struct wpabuf *lci, diff --git a/src/ap/rrm.c b/src/ap/rrm.c index 8548ea406..3569f955b 100644 --- a/src/ap/rrm.c +++ b/src/ap/rrm.c @@ -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, 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: hostapd_handle_lci_report(hapd, token, ie + 2, ie[1]); break; + case MEASURE_TYPE_FTM_RANGE: + hostapd_handle_range_report(hapd, token, ie + 2, ie[1]); + break; default: wpa_printf(MSG_DEBUG, "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) { hostpad_free_neighbor_db(hapd); eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL); hapd->lci_req_active = 0; + eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL); + hapd->range_req_active = 0; } diff --git a/src/ap/rrm.h b/src/ap/rrm.h index f3e15bd73..f07fd41ac 100644 --- a/src/ap/rrm.h +++ b/src/ap/rrm.h @@ -10,9 +10,19 @@ #ifndef 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, const u8 *buf, size_t len); 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); #endif /* RRM_H */ diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index cbf8336fd..5be747b46 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -368,6 +368,14 @@ #define WLAN_RRM_CAPS_NEIGHBOR_REPORT BIT(1) /* byte 2 (out of 5) */ #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 */ #define WLAN_TIMEOUT_REASSOC_DEADLINE 1