hostapd: Add FTM range request
authorDavid Spinadel <david.spinadel@intel.com>
Wed, 6 Apr 2016 16:42:12 +0000 (19:42 +0300)
committerJouni Malinen <j@w1.fi>
Sun, 17 Apr 2016 09:29:12 +0000 (12:29 +0300)
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>
hostapd/ctrl_iface.c
hostapd/hostapd_cli.c
src/ap/hostapd.h
src/ap/neighbor_db.c
src/ap/neighbor_db.h
src/ap/rrm.c
src/ap/rrm.h
src/common/ieee802_11_defs.h

index 4c2b559..7040069 100644 (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)
 {
        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;
index 563d8d5..ff133f6 100644 (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 {
        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 }
 };
 
index 76b0ca6..4dba8cb 100644 (file)
@@ -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;
 };
 
 
index 1156aa2..a2efff6 100644 (file)
@@ -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;
index 40c42e7..c22e043 100644 (file)
@@ -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,
index 8548ea4..3569f95 100644 (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,
                                             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;
 }
index f3e15bd..f07fd41 100644 (file)
 #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 */
index cbf8336..5be747b 100644 (file)
 #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